From 55e5f115523fbff8b3dcfbae80c40761a26b3296 Mon Sep 17 00:00:00 2001 From: Igor20264 Date: Fri, 4 Sep 2026 22:28:39 +0300 Subject: [PATCH] Update 0.4.0 - Add\Rework UI - Add Split Table and Listing - Add Support Customazeble schems --- .gitignore | 9 + ReadMe.docx | Bin 0 -> 33029 bytes _bpmn_preview.png | Bin 0 -> 17970 bytes _bpmn_sprites.puml | 3468 +++++++++++++++++++++++++ build-exe-fast.bat | 85 + build-exe.bat | 68 + md2gost.fast.spec | 162 ++ md2gost.spec | 123 + md2gost/README.docx | Bin 0 -> 92382 bytes md2gost/README.md | 148 +- md2gost/__init__.py | 9 + md2gost/__main__.py | 252 +- md2gost/checker/__init__.py | 119 +- md2gost/converter.py | 23 +- md2gost/diagram_includes.py | 261 ++ md2gost/diagram_renderer.py | 429 ++- md2gost/diagram_schemes.py | 335 +++ md2gost/diagrams/BPMN.puml | 1126 ++++++++ md2gost/diagrams/schemes.json | 60 + md2gost/dnd.py | 326 +++ md2gost/docx_elements.py | 25 + md2gost/docx_svg.py | 55 + md2gost/extended_markdown/caption.py | 25 +- md2gost/extended_markdown/image.py | 8 +- md2gost/gui.py | 1260 +++++++++ md2gost/help_content.py | 258 ++ md2gost/layout_tracker.py | 10 + md2gost/page_fill_check.py | 226 ++ md2gost/page_geometry.py | 142 + md2gost/parser_.py | 11 +- md2gost/pipeline.py | 303 +++ md2gost/profiles.py | 11 +- md2gost/renderable/caption.py | 8 +- md2gost/renderable/diagram.py | 13 +- md2gost/renderable/heading.py | 4 +- md2gost/renderable/image.py | 80 +- md2gost/renderable/list.py | 4 + md2gost/renderable/listing.py | 123 +- md2gost/renderable/paragraph.py | 24 +- md2gost/renderable/table.py | 8 +- md2gost/renderable/toc.py | 7 +- md2gost/renderable_factory.py | 29 +- md2gost/renderer.py | 303 ++- md2gost/styles.py | 48 +- md2gost/vendor/.gitkeep | 0 md2gost/word_fix.py | 525 ++++ prompts/generate-mirea-report.md | 45 +- prompts/generate-pis-custom-report.md | 4 +- pyproject.toml | 9 + scripts/_gen_bpmn_sprites.py | 461 ++++ scripts/check_page_fill.bas | 85 + scripts/fetch_plantuml.py | 30 + scripts/md2gost_exe.py | 6 + scripts/pyi_rth_mplbackend.py | 4 + tests/test_diagram_renderer.py | 411 ++- tests/test_landscape.py | 200 ++ tests/test_mirea_tz.py | 161 ++ tests/test_pipeline.py | 186 ++ tests/test_table_merge.py | 6 +- tests/test_word_fix.py | 110 + tmp_listing_word.docx | Bin 0 -> 28898 bytes tmp_listing_word.md | 13 + tmp_readme_word.docx | Bin 0 -> 92372 bytes tmp_readme_word2.docx | Bin 0 -> 92257 bytes todo.txt | 2 + 65 files changed, 11833 insertions(+), 413 deletions(-) create mode 100644 ReadMe.docx create mode 100644 _bpmn_preview.png create mode 100644 _bpmn_sprites.puml create mode 100644 build-exe-fast.bat create mode 100644 build-exe.bat create mode 100644 md2gost.fast.spec create mode 100644 md2gost.spec create mode 100644 md2gost/README.docx create mode 100644 md2gost/diagram_includes.py create mode 100644 md2gost/diagram_schemes.py create mode 100644 md2gost/diagrams/BPMN.puml create mode 100644 md2gost/diagrams/schemes.json create mode 100644 md2gost/dnd.py create mode 100644 md2gost/docx_svg.py create mode 100644 md2gost/gui.py create mode 100644 md2gost/help_content.py create mode 100644 md2gost/page_fill_check.py create mode 100644 md2gost/page_geometry.py create mode 100644 md2gost/pipeline.py create mode 100644 md2gost/vendor/.gitkeep create mode 100644 md2gost/word_fix.py create mode 100644 scripts/_gen_bpmn_sprites.py create mode 100644 scripts/check_page_fill.bas create mode 100644 scripts/fetch_plantuml.py create mode 100644 scripts/md2gost_exe.py create mode 100644 scripts/pyi_rth_mplbackend.py create mode 100644 tests/test_landscape.py create mode 100644 tests/test_pipeline.py create mode 100644 tests/test_word_fix.py create mode 100644 tmp_listing_word.docx create mode 100644 tmp_listing_word.md create mode 100644 tmp_readme_word.docx create mode 100644 tmp_readme_word2.docx create mode 100644 todo.txt diff --git a/.gitignore b/.gitignore index d40ae39..16474dd 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,8 @@ MANIFEST # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec +!md2gost.spec +!md2gost.fast.spec # Installer logs pip-log.txt @@ -130,6 +132,7 @@ venv.bak/ # md2gost diagram PNG cache .md2gost-cache/ +md2gost/vendor/*.jar # LaTeX build artefacts *_latex/ @@ -172,3 +175,9 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ + +# md2gost runtime (created next to app / cwd on first run) +md2gost.schemes.json +md2gost.include-cache.json +include-cache/ +.md2gost-cache/ diff --git a/ReadMe.docx b/ReadMe.docx new file mode 100644 index 0000000000000000000000000000000000000000..0bc0b6ed948bc629cf0402e1028c73163650f3b4 GIT binary patch literal 33029 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%0AMJAO6n?zpDDFoW9~w}59Z$9Zy2unSmE z4=-1kNQ~yA97-OA<6@6nD#}N0)L=>WU#j1eR!9oVVI{TS7t>aBGo4=w7q4h`#G|Uv zJ@u>S)#1H57&6CvD!T=}r%^=QX4ux#CUobW7h;3UZ}SE z^?QAPG4|>vJQkJ^d+h<>&6>zuiPQ_krVBak`xVZWB?v3TRzmxZD-vM;44=*vFx!Wk zl>@F@B732oU>Ba=>QaaeWk`H~tt;W;4PC_6AN`sjwh$YaT%-Iu#rRi6{#rI)#zNh` zOh9ZPw&u&g=LQkIGD+8ntq-!sV;89YTEvwiJHfpd-3c%twy!t3SgngL9jpf(I>dhi z&|>F#t~g-$XHe}D+n;5PB(75fwTP-m^uqeDdy`{AY~1g6vDjB!$k>iNk%@f^g1yl6 zFcSNE6(Y9(%M9Cpn6dN!n$eVX0Q(|)3$cOFnx`XmeED&96xAz}_7Ba<{};`0T0c&5 zR;x$~5|A#4OD+gl(gl-VflHv0><}{5ijsG6rHm|Xu~>(TvXSK}QXQ`kK!(RTkha{9 z-|!1Wwz}P2eBouUlJls$u-+KMz=W(6W5PeZ;#QUAqt9YTmo(vyfN^g>#K2 zgo?5B6!tF5VKJ#aoK4;@Ewk79nXCX_~CgMwYQ>&{33 zq}YK(#c5BBNqW=GXH5NQ|AlEss}XrLw&J#Z(nMFixV;FA$M*ejEmc)>inf}x zRjzGq)(lPi+r_O$KJvn_S=BN~we|7kUzW4AW9jfz4T~~;g8rph`EC$nQ z$6~lxN6dw!oeEtuGuIfZ5m#px&Tkh+E$W}+lq>0wY`dEE80$%OH{C{_DNvjTD_cU{ z6%iMYE*)u#z{O=+)@tq*j&)h9&ZQXZySO0?%@M1bWqE>qOEj&J%ke`i)saz8xa%Ft zGlavBc(*z_GF#uz%!gNG4W0|s&D32gZ7WmfQVNk5*TpCp>qFL6jmORUR!aXnuQgk< zRLOJut4mD!kV{ev75aLbR!T%2Znqt#QAcW3QnLxF^<;2{{r5aw{54N?U4PA!_4hm_ z4Y`;#G_fAaP5%p5-MZ#881C~lbJLRIiXd}C=u^HUimc~QX*-Lf$fhve`jhJ`X)`vi z{Qafoq0LR2iX3q(#!6lCdHqcl=jA}+=K(4cW$?oNiPMZVc%tcj))a?G{Mr9$>@A?; zTDGlW9D=($1POtljT79R;I6?MClK5{xVyW%1PBlyxVyW%``^hu_nmY2-#5l%0D~U% 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
H) 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 I{eJcT0iwP-g8%>k literal 0 HcmV?d00001 diff --git a/_bpmn_preview.png b/_bpmn_preview.png new file mode 100644 index 0000000000000000000000000000000000000000..05cf39e08f722e0ffae8961963e2bcd2a42901f7 GIT binary patch literal 17970 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 BB3J+b literal 0 HcmV?d00001 diff --git a/_bpmn_sprites.puml b/_bpmn_sprites.puml new file mode 100644 index 0000000..541241d --- /dev/null +++ b/_bpmn_sprites.puml @@ -0,0 +1,3468 @@ +' auto-generated geometric sprites (BPMN 2.0 notation) +sprite $evStart [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000012345432100000000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +0000000000000027CC95322122359CC72000000000000000 +00000000000016CB520000000000025BC610000000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000004BA300000000000000000003AB400000000000 +0000000004B8100000000000000000000018B40000000000 +000000003B810000000000000000000000018B3000000000 +00000001AA100000000000000000000000001AA100000000 +00000006C30000000000000000000000000003C600000000 +0000002C6000000000000000000000000000006C20000000 +0000007B1000000000000000000000000000001B70000000 +000002C500000000000000000000000000000005C2000000 +000005C200000000000000000000000000000002C5000000 +000009900000000000000000000000000000000099000000 +00001B50000000000000000000000000000000005B100000 +00002C30000000000000000000000000000000003C200000 +00003C20000000000000000000000000000000002C300000 +00004C20000000000000000000000000000000002C400000 +00005C10000000000000000000000000000000001C500000 +00004C20000000000000000000000000000000002C400000 +00003C20000000000000000000000000000000002C300000 +00002C30000000000000000000000000000000003C200000 +00001B50000000000000000000000000000000005B100000 +000009900000000000000000000000000000000099000000 +000005C200000000000000000000000000000002C5000000 +000002C500000000000000000000000000000005C2000000 +0000007B1000000000000000000000000000001B70000000 +0000002C6000000000000000000000000000006C20000000 +00000006C30000000000000000000000000003C600000000 +00000001AA100000000000000000000000001AA100000000 +000000003B810000000000000000000000018B3000000000 +0000000004B8100000000000000000000018B40000000000 +00000000004BA300000000000000000003AB400000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000000016CB520000000000025BC610000000000000 +0000000000000027CC95322122359CC72000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +000000000000000000012345432100000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evStartMsg [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000012345432100000000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +0000000000000027CC95322122359CC72000000000000000 +00000000000016CB520000000000025BC610000000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000004BA300000000000000000003AB400000000000 +0000000004B8100000000000000000000018B40000000000 +000000003B810000000000000000000000018B3000000000 +00000001AA100000000000000000000000001AA100000000 +00000006C30000000000000000000000000003C600000000 +0000002C6000000000000000000000000000006C20000000 +0000007B1000000000000000000000000000001B70000000 +000002C500000000000000000000000000000005C2000000 +000005C200000008888888888888888800000002C5000000 +000009900000008FFFFFFFFFFFFFFFFF8000000099000000 +00001B500000008FDA88888888888ADF800000005B100000 +00002C300000008F854000000000458F800000003C200000 +00003C200000008F815510000015518F800000002C300000 +00004C200000008F800451000154008F800000002C400000 +00005C100000008F800036202630008F800000001C500000 +00004C200000008F800002646200008F800000002C400000 +00003C200000008F800000252000008F800000002C300000 +00002C300000008F800000000000008F800000003C200000 +00001B500000008FB8888888888888BF800000005B100000 +000009900000008FFFFFFFFFFFFFFFFF8000000099000000 +000005C200000008888888888888888800000002C5000000 +000002C500000000000000000000000000000005C2000000 +0000007B1000000000000000000000000000001B70000000 +0000002C6000000000000000000000000000006C20000000 +00000006C30000000000000000000000000003C600000000 +00000001AA100000000000000000000000001AA100000000 +000000003B810000000000000000000000018B3000000000 +0000000004B8100000000000000000000018B40000000000 +00000000004BA300000000000000000003AB400000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000000016CB520000000000025BC610000000000000 +0000000000000027CC95322122359CC72000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +000000000000000000012345432100000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evStartTimer [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000012345432100000000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +0000000000000027CC95322122359CC72000000000000000 +00000000000016CB520000000000025BC610000000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000004BA300000000000000000003AB400000000000 +0000000004B8100000000000000000000018B40000000000 +000000003B810000000000000000000000018B3000000000 +00000001AA100000000000000000000000001AA100000000 +00000006C30000000000000000000000000003C600000000 +0000002C6000000000000000000000000000006C20000000 +0000007B1000000000000133310000000000001B70000000 +000002C50000000000039BBBBB93000000000005C2000000 +000005C200000000006C8312138C600000000002C5000000 +000009900000000006B400151004B6000000000099000000 +00001B50000000003C40001510004C30000000005B100000 +00002C30000000009800001510000890000000003C200000 +00003C2000000001B3000015100003B1000000002C300000 +00004C2000000003B1000015111001B3000000002C400000 +00005C1000000003B0000015555300B3000000001C500000 +00004C2000000003B1000001111001B3000000002C400000 +00003C2000000001B3000000000003B1000000002C300000 +00002C30000000009800000000000890000000003C200000 +00001B50000000003C40000000004C30000000005B100000 +000009900000000006B400000004B6000000000099000000 +000005C200000000006C8310138C600000000002C5000000 +000002C50000000000039BBBBB93000000000005C2000000 +0000007B1000000000000133310000000000001B70000000 +0000002C6000000000000000000000000000006C20000000 +00000006C30000000000000000000000000003C600000000 +00000001AA100000000000000000000000001AA100000000 +000000003B810000000000000000000000018B3000000000 +0000000004B8100000000000000000000018B40000000000 +00000000004BA300000000000000000003AB400000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000000016CB520000000000025BC610000000000000 +0000000000000027CC95322122359CC72000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +000000000000000000012345432100000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evStartSignal [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000012345432100000000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +0000000000000027CC95322122359CC72000000000000000 +00000000000016CB520000000000025BC610000000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000004BA300000000000000000003AB400000000000 +0000000004B8100000000000000000000018B40000000000 +000000003B810000000000000000000000018B3000000000 +00000001AA100000000000000000000000001AA100000000 +00000006C30000000000000000000000000003C600000000 +0000002C6000000000000000000000000000006C20000000 +0000007B1000000000000003000000000000001B70000000 +000002C500000000000000373000000000000005C2000000 +000005C200000000000001646100000000000002C5000000 +000009900000000000000351530000000000000099000000 +00001B50000000000000162026100000000000005B100000 +00002C30000000000000450005400000000000003C200000 +00003C20000000000001620002610000000000002C300000 +00004C20000000000004500000540000000000002C400000 +00005C10000000000026200000262000000000001C500000 +00004C20000000000054000000045000000000002C400000 +00003C20000000000261000000016200000000002C300000 +00002C30000000000540000000004500000000003C200000 +00001B50000000002610000000001620000000005B100000 +000009900000000054111111111114500000000099000000 +000005C200000002666666666666666200000002C5000000 +000002C500000000111111111111111000000005C2000000 +0000007B1000000000000000000000000000001B70000000 +0000002C6000000000000000000000000000006C20000000 +00000006C30000000000000000000000000003C600000000 +00000001AA100000000000000000000000001AA100000000 +000000003B810000000000000000000000018B3000000000 +0000000004B8100000000000000000000018B40000000000 +00000000004BA300000000000000000003AB400000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000000016CB520000000000025BC610000000000000 +0000000000000027CC95322122359CC72000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +000000000000000000012345432100000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evStartCond [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000012345432100000000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +0000000000000027CC95322122359CC72000000000000000 +00000000000016CB520000000000025BC610000000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000004BA300000000000000000003AB400000000000 +0000000004B8100000000000000000000018B40000000000 +000000003B810000000000000000000000018B3000000000 +00000001AA100000000000000000000000001AA100000000 +00000006C30000000000000000000000000003C600000000 +0000002C6000000004888888888884000000006C20000000 +0000007B100000004BFFFFFFFFFFFB400000001B70000000 +000002C5000000008FB888888888BF8000000005C2000000 +000005C2000000008F80000000008F8000000002C5000000 +00000990000000008F80000000008F800000000099000000 +00001B50000000008F80000000008F80000000005B100000 +00002C30000000008F82555555528F80000000003C200000 +00003C20000000008F80000000008F80000000002C300000 +00004C20000000008F80000000008F80000000002C400000 +00005C10000000008F82555555528F80000000001C500000 +00004C20000000008F80000000008F80000000002C400000 +00003C20000000008F80000000008F80000000002C300000 +00002C30000000008F82555555528F80000000003C200000 +00001B50000000008F80000000008F80000000005B100000 +00000990000000008F80000000008F800000000099000000 +000005C2000000008F80000000008F8000000002C5000000 +000002C5000000008FB888888888BF8000000005C2000000 +0000007B100000004BFFFFFFFFFFFB400000001B70000000 +0000002C6000000004888888888884000000006C20000000 +00000006C30000000000000000000000000003C600000000 +00000001AA100000000000000000000000001AA100000000 +000000003B810000000000000000000000018B3000000000 +0000000004B8100000000000000000000018B40000000000 +00000000004BA300000000000000000003AB400000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000000016CB520000000000025BC610000000000000 +0000000000000027CC95322122359CC72000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +000000000000000000012345432100000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evStartError [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000012345432100000000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +0000000000000027CC95322122359CC72000000000000000 +00000000000016CB520000000000025BC610000000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000004BA300000000000000000003AB400000000000 +0000000004B8100000000000000000000018B40000000000 +000000003B810000000000000000000000018B3000000000 +00000001AA100000000000000000000000001AA100000000 +00000006C30000000000000000000000000003C600000000 +0000002C6000000000000000100000000000006C20000000 +0000007B1000000000000001510000000000001B70000000 +000002C500000000000000045000000000000005C2000000 +000005C200000000000000162000000000000002C5000000 +000009900000000000000045000000000000000099000000 +00001B50000000000000016300000000000000005B100000 +00002C30000000000000036100000000000000003C200000 +00003C20000000000000164111100000000000002C300000 +00004C20000000000000366666610000000000002C400000 +00005C10000000000000621114400000000000001C500000 +00004C20000000000002644426200000000000002C400000 +00003C20000000000002444555000000000000002C300000 +00002C30000000000000000662000000000000003C200000 +00001B50000000000000001660000000000000005B100000 +000009900000000000000027300000000000000099000000 +000005C200000000000000461000000000000002C5000000 +000002C500000000000000640000000000000005C2000000 +0000007B1000000000000151000000000000001B70000000 +0000002C6000000000000010000000000000006C20000000 +00000006C30000000000000000000000000003C600000000 +00000001AA100000000000000000000000001AA100000000 +000000003B810000000000000000000000018B3000000000 +0000000004B8100000000000000000000018B40000000000 +00000000004BA300000000000000000003AB400000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000000016CB520000000000025BC610000000000000 +0000000000000027CC95322122359CC72000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +000000000000000000012345432100000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evStartEsc [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000012345432100000000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +0000000000000027CC95322122359CC72000000000000000 +00000000000016CB520000000000025BC610000000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000004BA300000000000000000003AB400000000000 +0000000004B8100000000000000000000018B40000000000 +000000003B810000000000000000000000018B3000000000 +00000001AA100000000000000000000000001AA100000000 +00000006C30000000000000000000000000003C600000000 +0000002C6000000000000000000000000000006C20000000 +0000007B1000000000000000000000000000001B70000000 +000002C500000000000000000000000000000005C2000000 +000005C200000000000000000000000000000002C5000000 +000009900000000000000000000000000000000099000000 +00001B50000000000000000000000000000000005B100000 +00002C30000000000000000100000000000000003C200000 +00003C20000000000000002620000000000000002C300000 +00004C20000000000000026662000000000000002C400000 +00005C10000000000000263636200000000000001C500000 +00004C20000000000002621612620000000000002C400000 +00003C20000000000026201610262000000000002C300000 +00002C30000000000263112621136200000000003C200000 +00001B50000000001666666666666610000000005B100000 +000009900000000001111126211111000000000099000000 +000005C200000000000000161000000000000002C5000000 +000002C500000000000000151000000000000005C2000000 +0000007B1000000000000001000000000000001B70000000 +0000002C6000000000000000000000000000006C20000000 +00000006C30000000000000000000000000003C600000000 +00000001AA100000000000000000000000001AA100000000 +000000003B810000000000000000000000018B3000000000 +0000000004B8100000000000000000000018B40000000000 +00000000004BA300000000000000000003AB400000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000000016CB520000000000025BC610000000000000 +0000000000000027CC95322122359CC72000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +000000000000000000012345432100000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evStartMulti [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000012345432100000000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +0000000000000027CC95322122359CC72000000000000000 +00000000000016CB520000000000025BC610000000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000004BA300000000000000000003AB400000000000 +0000000004B8100000000000000000000018B40000000000 +000000003B810000000000000000000000018B3000000000 +00000001AA100000000000000000000000001AA100000000 +00000006C30000000000000000000000000003C600000000 +0000002C6000000000000000000000000000006C20000000 +0000007B1000000000000013100000000000001B70000000 +000002C500000000000001565100000000000005C2000000 +000005C200000000000036313630000000000002C5000000 +000009900000000000146200026410000000000099000000 +00001B50000000000255100000155200000000005B100000 +00002C30000000003630000000003630000000003C200000 +00003C20000000016200000000000261000000002C300000 +00004C20000000005300000000000350000000002C400000 +00005C10000000002500000000000520000000001C500000 +00004C20000000001610000000001610000000002C400000 +00003C20000000000530000000003500000000002C300000 +00002C30000000000350000000005300000000003C200000 +00001B50000000000161000000016100000000005B100000 +000009900000000000531111111350000000000099000000 +000005C200000000003666666666300000000002C5000000 +000002C500000000000111111111000000000005C2000000 +0000007B1000000000000000000000000000001B70000000 +0000002C6000000000000000000000000000006C20000000 +00000006C30000000000000000000000000003C600000000 +00000001AA100000000000000000000000001AA100000000 +000000003B810000000000000000000000018B3000000000 +0000000004B8100000000000000000000018B40000000000 +00000000004BA300000000000000000003AB400000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000000016CB520000000000025BC610000000000000 +0000000000000027CC95322122359CC72000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +000000000000000000012345432100000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evStartPar [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000012345432100000000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +0000000000000027CC95322122359CC72000000000000000 +00000000000016CB520000000000025BC610000000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000004BA300000000000000000003AB400000000000 +0000000004B8100000000000000000000018B40000000000 +000000003B810000000000000000000000018B3000000000 +00000001AA100000000000000000000000001AA100000000 +00000006C30000000000000000000000000003C600000000 +0000002C6000000000000000000000000000006C20000000 +0000007B1000000000001356531000000000001B70000000 +000002C5000000000015BBA9ABB5100000000005C2000000 +000005C200000000019B5101015B910000000002C5000000 +000009900000000019A200151002A9100000000099000000 +00001B50000000005B20001610002B50000000005B100000 +00002C3000000001B5000016100005B1000000003C200000 +00003C2000000003B1000016100001B3000000002C300000 +00004C2000000005A0111126211110A5000000002C400000 +00005C10000000069156666666665196000000001C500000 +00004C2000000005A0111126211110A5000000002C400000 +00003C2000000003B1000016100001B3000000002C300000 +00002C3000000001B5000016100005B1000000003C200000 +00001B50000000005B20001610002B50000000005B100000 +000009900000000019A200151002A9100000000099000000 +000005C200000000019B5101015B910000000002C5000000 +000002C5000000000015BBA9ABB5100000000005C2000000 +0000007B1000000000001356531000000000001B70000000 +0000002C6000000000000000000000000000006C20000000 +00000006C30000000000000000000000000003C600000000 +00000001AA100000000000000000000000001AA100000000 +000000003B810000000000000000000000018B3000000000 +0000000004B8100000000000000000000018B40000000000 +00000000004BA300000000000000000003AB400000000000 +000000000003AC6100000000000000016CA3000000000000 +00000000000016CB520000000000025BC610000000000000 +0000000000000027CC95322122359CC72000000000000000 +0000000000000000259BCCCCCCCB95200000000000000000 +000000000000000000012345432100000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evStartNI [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000002345432000000000000000000000 +000000000000000000116CCCCC6110000000000000000000 +000000000000000111101221221011110000000000000000 +000000000000164100000000000000014610000000000000 +000000000003AC4000000000000000004CA3000000000000 +00000000004BA300000000000000000003AB400000000000 +0000000004B8100000000000000000000018B40000000000 +000000000881000000000000000000000001880000000000 +000000001110000000000000000000000000111000000000 +000000011000000000000000000000000000001100000000 +000000010000000000000000000000000000000100000000 +000000110000000000000000000000000000000110000000 +000000100000000000000000000000000000000010000000 +000005910000000000000000000000000000000195000000 +000009900000000000000000000000000000000099000000 +00001B50000000000000000000000000000000005B100000 +00002C30000000000000000000000000000000003C200000 +00003C20000000000000000000000000000000002C300000 +00004C20000000000000000000000000000000002C400000 +000026100000000000000000000000000000000016200000 +000001000000000000000000000000000000000001000000 +000001000000000000000000000000000000000001000000 +000001000000000000000000000000000000000001000000 +000001000000000000000000000000000000000001000000 +000001100000000000000000000000000000000011000000 +000000310000000000000000000000000000000130000000 +000002C500000000000000000000000000000005C2000000 +0000007B1000000000000000000000000000001B70000000 +0000002C6000000000000000000000000000006C20000000 +00000006C30000000000000000000000000003C600000000 +00000001AA000000000000000000000000000AA100000000 +000000003410000000000000000000000000143000000000 +000000000011000000000000000000000001100000000000 +000000000001100000000000000000000011000000000000 +000000000000112100000000000000012110000000000000 +000000000000018B520000000000025B8100000000000000 +0000000000000027CC95200000259CC72000000000000000 +0000000000000000259B6111116B95200000000000000000 +000000000000000000010000000100000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evStartMsgNI [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000002345432000000000000000000000 +000000000000000000116CCCCC6110000000000000000000 +000000000000000111101221221011110000000000000000 +000000000000164100000000000000014610000000000000 +000000000003AC4000000000000000004CA3000000000000 +00000000004BA300000000000000000003AB400000000000 +0000000004B8100000000000000000000018B40000000000 +000000000881000000000000000000000001880000000000 +000000001110000000000000000000000000111000000000 +000000011000000000000000000000000000001100000000 +000000010000000000000000000000000000000100000000 +000000110000000000000000000000000000000110000000 +000000100000000000000000000000000000000010000000 +000005910000000888888888888888880000000195000000 +000009900000008FFFFFFFFFFFFFFFFF8000000099000000 +00001B500000008FDA88888888888ADF800000005B100000 +00002C300000008F854000000000458F800000003C200000 +00003C200000008F815510000015518F800000002C300000 +00004C200000008F800451000154008F800000002C400000 +000026100000008F800036202630008F8000000016200000 +000001000000008F800002646200008F8000000001000000 +000001000000008F800000252000008F8000000001000000 +000001000000008F800000000000008F8000000001000000 +000001000000008FB8888888888888BF8000000001000000 +000001100000008FFFFFFFFFFFFFFFFF8000000011000000 +000000310000000888888888888888880000000130000000 +000002C500000000000000000000000000000005C2000000 +0000007B1000000000000000000000000000001B70000000 +0000002C6000000000000000000000000000006C20000000 +00000006C30000000000000000000000000003C600000000 +00000001AA000000000000000000000000000AA100000000 +000000003410000000000000000000000000143000000000 +000000000011000000000000000000000001100000000000 +000000000001100000000000000000000011000000000000 +000000000000112100000000000000012110000000000000 +000000000000018B520000000000025B8100000000000000 +0000000000000027CC95200000259CC72000000000000000 +0000000000000000259B6111116B95200000000000000000 +000000000000000000010000000100000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evStartTimerNI [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000002345432000000000000000000000 +000000000000000000116CCCCC6110000000000000000000 +000000000000000111101221221011110000000000000000 +000000000000164100000000000000014610000000000000 +000000000003AC4000000000000000004CA3000000000000 +00000000004BA300000000000000000003AB400000000000 +0000000004B8100000000000000000000018B40000000000 +000000000881000000000000000000000001880000000000 +000000001110000000000000000000000000111000000000 +000000011000000000000000000000000000001100000000 +000000010000000000000000000000000000000100000000 +000000110000000000000133310000000000000110000000 +000000100000000000039BBBBB9300000000000010000000 +0000059100000000006C8312138C60000000000195000000 +000009900000000006B400151004B6000000000099000000 +00001B50000000003C40001510004C30000000005B100000 +00002C30000000009800001510000890000000003C200000 +00003C2000000001B3000015100003B1000000002C300000 +00004C2000000003B1000015111001B3000000002C400000 +0000261000000003B0000015555300B30000000016200000 +0000010000000003B1000001111001B30000000001000000 +0000010000000001B3000000000003B10000000001000000 +000001000000000098000000000008900000000001000000 +00000100000000003C40000000004C300000000001000000 +000001100000000006B400000004B6000000000011000000 +0000003100000000006C8310138C60000000000130000000 +000002C50000000000039BBBBB93000000000005C2000000 +0000007B1000000000000133310000000000001B70000000 +0000002C6000000000000000000000000000006C20000000 +00000006C30000000000000000000000000003C600000000 +00000001AA000000000000000000000000000AA100000000 +000000003410000000000000000000000000143000000000 +000000000011000000000000000000000001100000000000 +000000000001100000000000000000000011000000000000 +000000000000112100000000000000012110000000000000 +000000000000018B520000000000025B8100000000000000 +0000000000000027CC95200000259CC72000000000000000 +0000000000000000259B6111116B95200000000000000000 +000000000000000000010000000100000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evStartSignalNI [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000002345432000000000000000000000 +000000000000000000116CCCCC6110000000000000000000 +000000000000000111101221221011110000000000000000 +000000000000164100000000000000014610000000000000 +000000000003AC4000000000000000004CA3000000000000 +00000000004BA300000000000000000003AB400000000000 +0000000004B8100000000000000000000018B40000000000 +000000000881000000000000000000000001880000000000 +000000001110000000000000000000000000111000000000 +000000011000000000000000000000000000001100000000 +000000010000000000000000000000000000000100000000 +000000110000000000000003000000000000000110000000 +000000100000000000000037300000000000000010000000 +000005910000000000000164610000000000000195000000 +000009900000000000000351530000000000000099000000 +00001B50000000000000162026100000000000005B100000 +00002C30000000000000450005400000000000003C200000 +00003C20000000000001620002610000000000002C300000 +00004C20000000000004500000540000000000002C400000 +000026100000000000262000002620000000000016200000 +000001000000000000540000000450000000000001000000 +000001000000000002610000000162000000000001000000 +000001000000000005400000000045000000000001000000 +000001000000000026100000000016200000000001000000 +000001100000000054111111111114500000000011000000 +000000310000000266666666666666620000000130000000 +000002C500000000111111111111111000000005C2000000 +0000007B1000000000000000000000000000001B70000000 +0000002C6000000000000000000000000000006C20000000 +00000006C30000000000000000000000000003C600000000 +00000001AA000000000000000000000000000AA100000000 +000000003410000000000000000000000000143000000000 +000000000011000000000000000000000001100000000000 +000000000001100000000000000000000011000000000000 +000000000000112100000000000000012110000000000000 +000000000000018B520000000000025B8100000000000000 +0000000000000027CC95200000259CC72000000000000000 +0000000000000000259B6111116B95200000000000000000 +000000000000000000010000000100000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evInter [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000000000000002B500890000000 +000003C202B5000000000000000000000005B202C3000000 +000007A006B1000000000000000000000001B600A7000000 +00000B501B500000000000000000000000005B105B000000 +00002C303C200000000000000000000000002C303C200000 +00003B105B000000000000000000000000000B501B300000 +00005B00780000000000000000000000000008700B500000 +00006A00970000000000000000000000000007900A600000 +000069009600000000000000000000000000069009600000 +00006A00970000000000000000000000000007900A600000 +00005B00780000000000000000000000000008700B500000 +00003B105B000000000000000000000000000B501B300000 +00002C303C200000000000000000000000002C303C200000 +00000B501B500000000000000000000000005B105B000000 +000007A006B1000000000000000000000001B600A7000000 +000003C202B5000000000000000000000005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evCatchMsg [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000000000000002B500890000000 +000003C202B5000000000000000000000005B202C3000000 +000007A006B1000888888888888888880001B600A7000000 +00000B501B50008FFFFFFFFFFFFFFFFF80005B105B000000 +00002C303C20008FDA88888888888ADF80002C303C200000 +00003B105B00008F854000000000458F80000B501B300000 +00005B007800008F815510000015518F800008700B500000 +00006A009700008F800451000154008F800007900A600000 +000069009600008F800036202630008F8000069009600000 +00006A009700008F800002646200008F800007900A600000 +00005B007800008F800000252000008F800008700B500000 +00003B105B00008F800000000000008F80000B501B300000 +00002C303C20008FB8888888888888BF80002C303C200000 +00000B501B50008FFFFFFFFFFFFFFFFF80005B105B000000 +000007A006B1000888888888888888880001B600A7000000 +000003C202B5000000000000000000000005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evCatchTimer [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000013331000000002B500890000000 +000003C202B5000000039BBBBB9300000005B202C3000000 +000007A006B10000006C8312138C60000001B600A7000000 +00000B501B50000006B400151004B60000005B105B000000 +00002C303C2000003C40001510004C3000002C303C200000 +00003B105B000000980000151000089000000B501B300000 +00005B0078000001B3000015100003B1000008700B500000 +00006A0097000003B1000015111001B3000007900A600000 +0000690096000003B0000015555300B30000069009600000 +00006A0097000003B1000001111001B3000007900A600000 +00005B0078000001B3000000000003B1000008700B500000 +00003B105B000000980000000000089000000B501B300000 +00002C303C2000003C40000000004C3000002C303C200000 +00000B501B50000006B400000004B60000005B105B000000 +000007A006B10000006C8310138C60000001B600A7000000 +000003C202B5000000039BBBBB9300000005B202C3000000 +00000098005B20000000013331000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evCatchSignal [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000300000000002B500890000000 +000003C202B5000000000037300000000005B202C3000000 +000007A006B1000000000164610000000001B600A7000000 +00000B501B500000000003515300000000005B105B000000 +00002C303C200000000016202610000000002C303C200000 +00003B105B000000000045000540000000000B501B300000 +00005B00780000000001620002610000000008700B500000 +00006A00970000000004500000540000000007900A600000 +000069009600000000262000002620000000069009600000 +00006A00970000000054000000045000000007900A600000 +00005B00780000000261000000016200000008700B500000 +00003B105B000000054000000000450000000B501B300000 +00002C303C200000261000000000162000002C303C200000 +00000B501B500000541111111111145000005B105B000000 +000007A006B1000266666666666666620001B600A7000000 +000003C202B5000011111111111111100005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evCatchError [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000100000000199103C30000000 +00000098005B20000000000151000000002B500890000000 +000003C202B5000000000004500000000005B202C3000000 +000007A006B1000000000016200000000001B600A7000000 +00000B501B500000000000450000000000005B105B000000 +00002C303C200000000001630000000000002C303C200000 +00003B105B000000000003610000000000000B501B300000 +00005B00780000000000164111100000000008700B500000 +00006A00970000000000366666610000000007900A600000 +000069009600000000006211144000000000069009600000 +00006A00970000000002644426200000000007900A600000 +00005B00780000000002444555000000000008700B500000 +00003B105B000000000000066200000000000B501B300000 +00002C303C200000000000166000000000002C303C200000 +00000B501B500000000000273000000000005B105B000000 +000007A006B1000000000046100000000001B600A7000000 +000003C202B5000000000064000000000005B202C3000000 +00000098005B20000000015100000000002B500890000000 +0000003C3019910000000010000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evCatchEsc [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000000000000002B500890000000 +000003C202B5000000000000000000000005B202C3000000 +000007A006B1000000000000000000000001B600A7000000 +00000B501B500000000000000000000000005B105B000000 +00002C303C200000000000000000000000002C303C200000 +00003B105B000000000000010000000000000B501B300000 +00005B00780000000000002620000000000008700B500000 +00006A00970000000000026662000000000007900A600000 +000069009600000000002636362000000000069009600000 +00006A00970000000002621612620000000007900A600000 +00005B00780000000026201610262000000008700B500000 +00003B105B000000026311262113620000000B501B300000 +00002C303C200000166666666666661000002C303C200000 +00000B501B500000011111262111110000005B105B000000 +000007A006B1000000000016100000000001B600A7000000 +000003C202B5000000000015100000000005B202C3000000 +00000098005B20000000000100000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evCatchCond [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910004888888888884000199103C30000000 +00000098005B20004BFFFFFFFFFFFB40002B500890000000 +000003C202B500008FB888888888BF800005B202C3000000 +000007A006B100008F80000000008F800001B600A7000000 +00000B501B5000008F80000000008F8000005B105B000000 +00002C303C2000008F80000000008F8000002C303C200000 +00003B105B0000008F82555555528F8000000B501B300000 +00005B00780000008F80000000008F80000008700B500000 +00006A00970000008F80000000008F80000007900A600000 +00006900960000008F82555555528F800000069009600000 +00006A00970000008F80000000008F80000007900A600000 +00005B00780000008F80000000008F80000008700B500000 +00003B105B0000008F82555555528F8000000B501B300000 +00002C303C2000008F80000000008F8000002C303C200000 +00000B501B5000008F80000000008F8000005B105B000000 +000007A006B100008F80000000008F800001B600A7000000 +000003C202B500008FB888888888BF800005B202C3000000 +00000098005B20004BFFFFFFFFFFFB40002B500890000000 +0000003C3019910004888888888884000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evCatchLink [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000000000000002B500890000000 +000003C202B5000000000000000000000005B202C3000000 +000007A006B1000000000000000000000001B600A7000000 +00000B501B500000000000001000000000005B105B000000 +00002C303C200000000000016200000000002C303C200000 +00003B105B000000000000002620000000000B501B300000 +00005B00780000000000000002620000000008700B500000 +00006A00970000011111111111362000000007900A600000 +000069009600001566666666666661000000069009600000 +00006A00970000011111111111362000000007900A600000 +00005B00780000000000000002620000000008700B500000 +00003B105B000000000000002620000000000B501B300000 +00002C303C200000000000016200000000002C303C200000 +00000B501B500000000000001000000000005B105B000000 +000007A006B1000000000000000000000001B600A7000000 +000003C202B5000000000000000000000005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evCatchComp [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000000000000002B500890000000 +000003C202B5000100000001000000000005B202C3000000 +000007A006B1000541000026200000000001B600A7000000 +00000B501B500005551000266300000000005B105B000000 +00002C303C200005346200252640000000002C303C200000 +00003B105B000005203630250155100000000B501B300000 +00005B00780000052002543500145200000008700B500000 +00006A00970000052000155500003630000007900A600000 +000069009600000520000155000003610000069009600000 +00006A00970000052000155500003630000007900A600000 +00005B00780000052002543500145200000008700B500000 +00003B105B000005203630250155100000000B501B300000 +00002C303C200005346200252640000000002C303C200000 +00000B501B500005551000266300000000005B105B000000 +000007A006B1000541000026200000000001B600A7000000 +000003C202B5000100000001000000000005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evCatchCancel [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000000000000002B500890000000 +000003C202B5000001000000000001000005B202C3000000 +000007A006B1000016300000000036100001B600A7000000 +00000B501B500000036300000003630000005B105B000000 +00002C303C200000003630000036300000002C303C200000 +00003B105B000000000363000363000000000B501B300000 +00005B00780000000000363036300000000008700B500000 +00006A00970000000000036463000000000007900A600000 +000069009600000000000048400000000000069009600000 +00006A00970000000000036463000000000007900A600000 +00005B00780000000000363036300000000008700B500000 +00003B105B000000000363000363000000000B501B300000 +00002C303C200000003630000036300000002C303C200000 +00000B501B500000036300000003630000005B105B000000 +000007A006B1000016300000000036100001B600A7000000 +000003C202B5000001000000000001000005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evCatchMulti [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000001310000000002B500890000000 +000003C202B5000000000156510000000005B202C3000000 +000007A006B1000000003631363000000001B600A7000000 +00000B501B500000001462000264100000005B105B000000 +00002C303C200000025510000015520000002C303C200000 +00003B105B000000363000000000363000000B501B300000 +00005B00780000016200000000000261000008700B500000 +00006A00970000005300000000000350000007900A600000 +000069009600000025000000000005200000069009600000 +00006A00970000001610000000001610000007900A600000 +00005B00780000000530000000003500000008700B500000 +00003B105B000000035000000000530000000B501B300000 +00002C303C200000016100000001610000002C303C200000 +00000B501B500000005311111113500000005B105B000000 +000007A006B1000000366666666630000001B600A7000000 +000003C202B5000000011111111100000005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evCatchPar [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000135653100000002B500890000000 +000003C202B500000015BBA9ABB510000005B202C3000000 +000007A006B10000019B5101015B91000001B600A7000000 +00000B501B50000019A200151002A91000005B105B000000 +00002C303C2000005B20001610002B5000002C303C200000 +00003B105B000001B5000016100005B100000B501B300000 +00005B0078000003B1000016100001B3000008700B500000 +00006A0097000005A0111126211110A5000007900A600000 +000069009600000691566666666651960000069009600000 +00006A0097000005A0111126211110A5000007900A600000 +00005B0078000003B1000016100001B3000008700B500000 +00003B105B000001B5000016100005B100000B501B300000 +00002C303C2000005B20001610002B5000002C303C200000 +00000B501B50000019A200151002A91000005B105B000000 +000007A006B10000019B5101015B91000001B600A7000000 +000003C202B500000015BBA9ABB510000005B202C3000000 +00000098005B20000000135653100000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evThrowMsg [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000000000000002B500890000000 +000003C202B5000000000000000000000005B202C3000000 +000007A006B1000888888888888888880001B600A7000000 +00000B501B50008FFFFFFFFFFFFFFFFF80005B105B000000 +00002C303C20008FFFFFFFFFFFFFFFFF80002C303C200000 +00003B105B00008FFFFFFFFFFFFFFFFF80000B501B300000 +00005B007800008FFFFFFFFFFFFFFFFF800008700B500000 +00006A009700008FFFFFFFFFFFFFFFFF800007900A600000 +000069009600008FFFFFFFFFFFFFFFFF8000069009600000 +00006A009700008FFFFFFFFFFFFFFFFF800007900A600000 +00005B007800008FFFFFFFFFFFFFFFFF800008700B500000 +00003B105B00008FFFFFFFFFFFFFFFFF80000B501B300000 +00002C303C20008FFFFFFFFFFFFFFFFF80002C303C200000 +00000B501B50008FFFFFFFFFFFFFFFFF80005B105B000000 +000007A006B1000888888888888888880001B600A7000000 +000003C202B5000000000000000000000005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evThrowSignal [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000300000000002B500890000000 +000003C202B500000000003F300000000005B202C3000000 +000007A006B10000000001AFA10000000001B600A7000000 +00000B501B500000000006FFF600000000005B105B000000 +00002C303C20000000001CFFFC10000000002C303C200000 +00003B105B00000000006FFFFF60000000000B501B300000 +00005B00780000000001CFFFFFC10000000008700B500000 +00006A00970000000006FFFFFFF60000000007900A600000 +0000690096000000002DFFFFFFFD20000000069009600000 +00006A0097000000007FFFFFFFFF7000000007900A600000 +00005B007800000002DFFFFFFFFFD200000008700B500000 +00003B105B00000009FFFFFFFFFFF90000000B501B300000 +00002C303C2000002FFFFFFFFFFFFF2000002C303C200000 +00000B501B5000009FFFFFFFFFFFFF9000005B105B000000 +000007A006B10002FFFFFFFFFFFFFFF20001B600A7000000 +000003C202B5000011111111111111100005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evThrowEsc [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000000000000002B500890000000 +000003C202B5000000000000000000000005B202C3000000 +000007A006B1000000000000000000000001B600A7000000 +00000B501B500000000000000000000000005B105B000000 +00002C303C200000000000000000000000002C303C200000 +00003B105B000000000000010000000000000B501B300000 +00005B00780000000000002620000000000008700B500000 +00006A00970000000000026662000000000007900A600000 +000069009600000000002636362000000000069009600000 +00006A00970000000002621612620000000007900A600000 +00005B00780000000026201610262000000008700B500000 +00003B105B000000026311262113620000000B501B300000 +00002C303C200000166666666666661000002C303C200000 +00000B501B500000011111262111110000005B105B000000 +000007A006B1000000000016100000000001B600A7000000 +000003C202B5000000000015100000000005B202C3000000 +00000098005B20000000000100000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evThrowLink [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000000000000002B500890000000 +000003C202B5000000000000000000000005B202C3000000 +000007A006B1000000000000000000000001B600A7000000 +00000B501B500000000000001000000000005B105B000000 +00002C303C200000000000016200000000002C303C200000 +00003B105B000000000000002620000000000B501B300000 +00005B00780000000000000002620000000008700B500000 +00006A00970000011111111111362000000007900A600000 +000069009600001566666666666661000000069009600000 +00006A00970000011111111111362000000007900A600000 +00005B00780000000000000002620000000008700B500000 +00003B105B000000000000002620000000000B501B300000 +00002C303C200000000000016200000000002C303C200000 +00000B501B500000000000001000000000005B105B000000 +000007A006B1000000000000000000000001B600A7000000 +000003C202B5000000000000000000000005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evThrowComp [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000000000000002B500890000000 +000003C202B5000100000001000000000005B202C3000000 +000007A006B1000541000026200000000001B600A7000000 +00000B501B500005551000266300000000005B105B000000 +00002C303C200005346200252640000000002C303C200000 +00003B105B000005203630250155100000000B501B300000 +00005B00780000052002543500145200000008700B500000 +00006A00970000052000155500003630000007900A600000 +000069009600000520000155000003610000069009600000 +00006A00970000052000155500003630000007900A600000 +00005B00780000052002543500145200000008700B500000 +00003B105B000005203630250155100000000B501B300000 +00002C303C200005346200252640000000002C303C200000 +00000B501B500005551000266300000000005B105B000000 +000007A006B1000541000026200000000001B600A7000000 +000003C202B5000100000001000000000005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evThrowMulti [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000001310000000002B500890000000 +000003C202B5000000000156510000000005B202C3000000 +000007A006B1000000003631363000000001B600A7000000 +00000B501B500000001462000264100000005B105B000000 +00002C303C200000025510000015520000002C303C200000 +00003B105B000000363000000000363000000B501B300000 +00005B00780000016200000000000261000008700B500000 +00006A00970000005300000000000350000007900A600000 +000069009600000025000000000005200000069009600000 +00006A00970000001610000000001610000007900A600000 +00005B00780000000530000000003500000008700B500000 +00003B105B000000035000000000530000000B501B300000 +00002C303C200000016100000001610000002C303C200000 +00000B501B500000005311111113500000005B105B000000 +000007A006B1000000366666666630000001B600A7000000 +000003C202B5000000011111111100000005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evEnd [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000001345677765431000000000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000026AEC841000000000000000148CEA62000000000 +000000016AEB73000000000000000000037BEA6100000000 +000000049DC7300000000000000000000037CD9400000000 +00000038CD830000000000000000000000038DC830000000 +0000015BE94000000000000000000000000049EB51000000 +0000038DB61000000000000000000000000016BD83000000 +000005AE9400000000000000000000000000049EA5000000 +000017CC7100000000000000000000000000017CC7100000 +000039EA4000000000000000000000000000004AE9300000 +00004AE830000000000000000000000000000038EA400000 +00015BD720000000000000000000000000000027DB510000 +00016CC610000000000000000000000000000016CC610000 +00017CC610000000000000000000000000000016CC710000 +00017DB610000000000000000000000000000016BD710000 +00017CC610000000000000000000000000000016CC710000 +00016CC610000000000000000000000000000016CC610000 +00015BD720000000000000000000000000000027DB510000 +00004AE830000000000000000000000000000038EA400000 +000039EA4000000000000000000000000000004AE9300000 +000017CC7100000000000000000000000000017CC7100000 +000005AE9400000000000000000000000000049EA5000000 +0000038DB61000000000000000000000000016BD83000000 +0000015BE94000000000000000000000000049EB51000000 +00000038CD830000000000000000000000038DC830000000 +000000049DC7300000000000000000000037CD9400000000 +000000016AEB73000000000000000000037BEA6100000000 +0000000026AEC841000000000000000148CEA62000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +000000000000000001345677765431000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evEndMsg [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000001345677765431000000000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000026AEC841000000000000000148CEA62000000000 +000000016AEB73000000000000000000037BEA6100000000 +000000049DC7300000000000000000000037CD9400000000 +00000038CD830000000000000000000000038DC830000000 +0000015BE94000000000000000000000000049EB51000000 +0000038DB61000000000000000000000000016BD83000000 +000005AE9400000000000000000000000000049EA5000000 +000017CC7100000888888888888888880000017CC7100000 +000039EA4000008FFFFFFFFFFFFFFFFF8000004AE9300000 +00004AE83000008FFFFFFFFFFFFFFFFF80000038EA400000 +00015BD72000008FFFFFFFFFFFFFFFFF80000027DB510000 +00016CC61000008FFFFFFFFFFFFFFFFF80000016CC610000 +00017CC61000008FFFFFFFFFFFFFFFFF80000016CC710000 +00017DB61000008FFFFFFFFFFFFFFFFF80000016BD710000 +00017CC61000008FFFFFFFFFFFFFFFFF80000016CC710000 +00016CC61000008FFFFFFFFFFFFFFFFF80000016CC610000 +00015BD72000008FFFFFFFFFFFFFFFFF80000027DB510000 +00004AE83000008FFFFFFFFFFFFFFFFF80000038EA400000 +000039EA4000008FFFFFFFFFFFFFFFFF8000004AE9300000 +000017CC7100000888888888888888880000017CC7100000 +000005AE9400000000000000000000000000049EA5000000 +0000038DB61000000000000000000000000016BD83000000 +0000015BE94000000000000000000000000049EB51000000 +00000038CD830000000000000000000000038DC830000000 +000000049DC7300000000000000000000037CD9400000000 +000000016AEB73000000000000000000037BEA6100000000 +0000000026AEC841000000000000000148CEA62000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +000000000000000001345677765431000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evEndError [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000001345677765431000000000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000026AEC841000000000000000148CEA62000000000 +000000016AEB73000000000000000000037BEA6100000000 +000000049DC7300000000000000000000037CD9400000000 +00000038CD830000000000000000000000038DC830000000 +0000015BE94000000000000010000000000049EB51000000 +0000038DB61000000000000151000000000016BD83000000 +000005AE9400000000000004500000000000049EA5000000 +000017CC7100000000000016200000000000017CC7100000 +000039EA4000000000000045000000000000004AE9300000 +00004AE830000000000001630000000000000038EA400000 +00015BD720000000000003610000000000000027DB510000 +00016CC610000000000016411110000000000016CC610000 +00017CC610000000000036666661000000000016CC710000 +00017DB610000000000062111440000000000016BD710000 +00017CC610000000000264442620000000000016CC710000 +00016CC610000000000244455500000000000016CC610000 +00015BD720000000000000066200000000000027DB510000 +00004AE830000000000000166000000000000038EA400000 +000039EA4000000000000027300000000000004AE9300000 +000017CC7100000000000046100000000000017CC7100000 +000005AE9400000000000064000000000000049EA5000000 +0000038DB61000000000015100000000000016BD83000000 +0000015BE94000000000001000000000000049EB51000000 +00000038CD830000000000000000000000038DC830000000 +000000049DC7300000000000000000000037CD9400000000 +000000016AEB73000000000000000000037BEA6100000000 +0000000026AEC841000000000000000148CEA62000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +000000000000000001345677765431000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evEndEsc [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000001345677765431000000000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000026AEC841000000000000000148CEA62000000000 +000000016AEB73000000000000000000037BEA6100000000 +000000049DC7300000000000000000000037CD9400000000 +00000038CD830000000000000000000000038DC830000000 +0000015BE94000000000000000000000000049EB51000000 +0000038DB61000000000000000000000000016BD83000000 +000005AE9400000000000000000000000000049EA5000000 +000017CC7100000000000000000000000000017CC7100000 +000039EA4000000000000000000000000000004AE9300000 +00004AE830000000000000000000000000000038EA400000 +00015BD720000000000000010000000000000027DB510000 +00016CC610000000000000262000000000000016CC610000 +00017CC610000000000002666200000000000016CC710000 +00017DB610000000000026363620000000000016BD710000 +00017CC610000000000262161262000000000016CC710000 +00016CC610000000002620161026200000000016CC610000 +00015BD720000000026311262113620000000027DB510000 +00004AE830000000166666666666661000000038EA400000 +000039EA4000000001111126211111000000004AE9300000 +000017CC7100000000000016100000000000017CC7100000 +000005AE9400000000000015100000000000049EA5000000 +0000038DB61000000000000100000000000016BD83000000 +0000015BE94000000000000000000000000049EB51000000 +00000038CD830000000000000000000000038DC830000000 +000000049DC7300000000000000000000037CD9400000000 +000000016AEB73000000000000000000037BEA6100000000 +0000000026AEC841000000000000000148CEA62000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +000000000000000001345677765431000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evEndCancel [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000001345677765431000000000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000026AEC841000000000000000148CEA62000000000 +000000016AEB73000000000000000000037BEA6100000000 +000000049DC7300000000000000000000037CD9400000000 +00000038CD830000000000000000000000038DC830000000 +0000015BE94000000000000000000000000049EB51000000 +0000038DB61000000000000000000000000016BD83000000 +000005AE9400000001000000000001000000049EA5000000 +000017CC7100000016300000000036100000017CC7100000 +000039EA4000000003630000000363000000004AE9300000 +00004AE830000000003630000036300000000038EA400000 +00015BD720000000000363000363000000000027DB510000 +00016CC610000000000036303630000000000016CC610000 +00017CC610000000000003646300000000000016CC710000 +00017DB610000000000000484000000000000016BD710000 +00017CC610000000000003646300000000000016CC710000 +00016CC610000000000036303630000000000016CC610000 +00015BD720000000000363000363000000000027DB510000 +00004AE830000000003630000036300000000038EA400000 +000039EA4000000003630000000363000000004AE9300000 +000017CC7100000016300000000036100000017CC7100000 +000005AE9400000001000000000001000000049EA5000000 +0000038DB61000000000000000000000000016BD83000000 +0000015BE94000000000000000000000000049EB51000000 +00000038CD830000000000000000000000038DC830000000 +000000049DC7300000000000000000000037CD9400000000 +000000016AEB73000000000000000000037BEA6100000000 +0000000026AEC841000000000000000148CEA62000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +000000000000000001345677765431000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evEndComp [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000001345677765431000000000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000026AEC841000000000000000148CEA62000000000 +000000016AEB73000000000000000000037BEA6100000000 +000000049DC7300000000000000000000037CD9400000000 +00000038CD830000000000000000000000038DC830000000 +0000015BE94000000000000000000000000049EB51000000 +0000038DB61000000000000000000000000016BD83000000 +000005AE9400000100000001000000000000049EA5000000 +000017CC7100000541000026200000000000017CC7100000 +000039EA4000000555100026630000000000004AE9300000 +00004AE830000005346200252640000000000038EA400000 +00015BD720000005203630250155100000000027DB510000 +00016CC610000005200254350014520000000016CC610000 +00017CC610000005200015550000363000000016CC710000 +00017DB610000005200001550000036100000016BD710000 +00017CC610000005200015550000363000000016CC710000 +00016CC610000005200254350014520000000016CC610000 +00015BD720000005203630250155100000000027DB510000 +00004AE830000005346200252640000000000038EA400000 +000039EA4000000555100026630000000000004AE9300000 +000017CC7100000541000026200000000000017CC7100000 +000005AE9400000100000001000000000000049EA5000000 +0000038DB61000000000000000000000000016BD83000000 +0000015BE94000000000000000000000000049EB51000000 +00000038CD830000000000000000000000038DC830000000 +000000049DC7300000000000000000000037CD9400000000 +000000016AEB73000000000000000000037BEA6100000000 +0000000026AEC841000000000000000148CEA62000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +000000000000000001345677765431000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evEndSignal [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000001345677765431000000000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000026AEC841000000000000000148CEA62000000000 +000000016AEB73000000000000000000037BEA6100000000 +000000049DC7300000000000000000000037CD9400000000 +00000038CD830000000000000000000000038DC830000000 +0000015BE94000000000000000000000000049EB51000000 +0000038DB61000000000000300000000000016BD83000000 +000005AE940000000000003F300000000000049EA5000000 +000017CC71000000000001AFA10000000000017CC7100000 +000039EA40000000000006FFF60000000000004AE9300000 +00004AE83000000000001CFFFC10000000000038EA400000 +00015BD72000000000006FFFFF60000000000027DB510000 +00016CC6100000000001CFFFFFC1000000000016CC610000 +00017CC6100000000006FFFFFFF6000000000016CC710000 +00017DB610000000002DFFFFFFFD200000000016BD710000 +00017CC610000000007FFFFFFFFF700000000016CC710000 +00016CC61000000002DFFFFFFFFFD20000000016CC610000 +00015BD72000000009FFFFFFFFFFF90000000027DB510000 +00004AE8300000002FFFFFFFFFFFFF2000000038EA400000 +000039EA400000009FFFFFFFFFFFFF900000004AE9300000 +000017CC71000002FFFFFFFFFFFFFFF20000017CC7100000 +000005AE9400000011111111111111100000049EA5000000 +0000038DB61000000000000000000000000016BD83000000 +0000015BE94000000000000000000000000049EB51000000 +00000038CD830000000000000000000000038DC830000000 +000000049DC7300000000000000000000037CD9400000000 +000000016AEB73000000000000000000037BEA6100000000 +0000000026AEC841000000000000000148CEA62000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +000000000000000001345677765431000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evEndMulti [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000001345677765431000000000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000026AEC841000000000000000148CEA62000000000 +000000016AEB73000000000000000000037BEA6100000000 +000000049DC7300000000000000000000037CD9400000000 +00000038CD830000000000000000000000038DC830000000 +0000015BE94000000000000000000000000049EB51000000 +0000038DB61000000000001310000000000016BD83000000 +000005AE9400000000000156510000000000049EA5000000 +000017CC7100000000003631363000000000017CC7100000 +000039EA4000000000146200026410000000004AE9300000 +00004AE830000000025510000015520000000038EA400000 +00015BD720000000363000000000363000000027DB510000 +00016CC610000001620000000000026100000016CC610000 +00017CC610000000530000000000035000000016CC710000 +00017DB610000000250000000000052000000016BD710000 +00017CC610000000161000000000161000000016CC710000 +00016CC610000000053000000000350000000016CC610000 +00015BD720000000035000000000530000000027DB510000 +00004AE830000000016100000001610000000038EA400000 +000039EA4000000000531111111350000000004AE9300000 +000017CC7100000000366666666630000000017CC7100000 +000005AE9400000000011111111100000000049EA5000000 +0000038DB61000000000000000000000000016BD83000000 +0000015BE94000000000000000000000000049EB51000000 +00000038CD830000000000000000000000038DC830000000 +000000049DC7300000000000000000000037CD9400000000 +000000016AEB73000000000000000000037BEA6100000000 +0000000026AEC841000000000000000148CEA62000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +000000000000000001345677765431000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evTerminate [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000001345677765431000000000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000026AEC841000000000000000148CEA62000000000 +000000016AEB73000000000000000000037BEA6100000000 +000000049DC7300000000000000000000037CD9400000000 +00000038CD830000000000000000000000038DC830000000 +0000015BE94000000000000000000000000049EB51000000 +0000038DB61000000000000100000000000016BD83000000 +000005AE940000000000379A973000000000049EA5000000 +000017CC710000000027CFFFFFC720000000017CC7100000 +000039EA40000000029EFFFFFFFE92000000004AE9300000 +00004AE83000000007EFFFFFFFFFE70000000038EA400000 +00015BD7200000003CFFFFFFFFFFFC3000000027DB510000 +00016CC6100000007FFFFFFFFFFFFF7000000016CC610000 +00017CC6100000009FFFFFFFFFFFFF9000000016CC710000 +00017DB610000001AFFFFFFFFFFFFFA100000016BD710000 +00017CC6100000009FFFFFFFFFFFFF9000000016CC710000 +00016CC6100000007FFFFFFFFFFFFF7000000016CC610000 +00015BD7200000003CFFFFFFFFFFFC3000000027DB510000 +00004AE83000000007EFFFFFFFFFE70000000038EA400000 +000039EA40000000029EFFFFFFFE92000000004AE9300000 +000017CC710000000027CFFFFFC720000000017CC7100000 +000005AE940000000000379A973000000000049EA5000000 +0000038DB61000000000000100000000000016BD83000000 +0000015BE94000000000000000000000000049EB51000000 +00000038CD830000000000000000000000038DC830000000 +000000049DC7300000000000000000000037CD9400000000 +000000016AEB73000000000000000000037BEA6100000000 +0000000026AEC841000000000000000148CEA62000000000 +00000000026ADD9641000000000001469DDA620000000000 +0000000000269CEB974321111123479BEC96200000000000 +00000000000148BDECA876666678ACEDB841000000000000 +0000000000000358ACEEDCCBCCDEECA85300000000000000 +0000000000000013579ABCCDCCBA97531000000000000000 +000000000000000001345677765431000000000000000000 +000000000000000000001111111000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evBoundMsg [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000000000000002B500890000000 +000003C202B5000000000000000000000005B202C3000000 +000007A006B1000888888888888888880001B600A7000000 +00000B501B50008FFFFFFFFFFFFFFFFF80005B105B000000 +00002C303C20008FDA88888888888ADF80002C303C200000 +00003B105B00008F854000000000458F80000B501B300000 +00005B007800008F815510000015518F800008700B500000 +00006A009700008F800451000154008F800007900A600000 +000069009600008F800036202630008F8000069009600000 +00006A009700008F800002646200008F800007900A600000 +00005B007800008F800000252000008F800008700B500000 +00003B105B00008F800000000000008F80000B501B300000 +00002C303C20008FB8888888888888BF80002C303C200000 +00000B501B50008FFFFFFFFFFFFFFFFF80005B105B000000 +000007A006B1000888888888888888880001B600A7000000 +000003C202B5000000000000000000000005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evBoundTimer [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000013331000000002B500890000000 +000003C202B5000000039BBBBB9300000005B202C3000000 +000007A006B10000006C8312138C60000001B600A7000000 +00000B501B50000006B400151004B60000005B105B000000 +00002C303C2000003C40001510004C3000002C303C200000 +00003B105B000000980000151000089000000B501B300000 +00005B0078000001B3000015100003B1000008700B500000 +00006A0097000003B1000015111001B3000007900A600000 +0000690096000003B0000015555300B30000069009600000 +00006A0097000003B1000001111001B3000007900A600000 +00005B0078000001B3000000000003B1000008700B500000 +00003B105B000000980000000000089000000B501B300000 +00002C303C2000003C40000000004C3000002C303C200000 +00000B501B50000006B400000004B60000005B105B000000 +000007A006B10000006C8310138C60000001B600A7000000 +000003C202B5000000039BBBBB9300000005B202C3000000 +00000098005B20000000013331000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evBoundError [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000100000000199103C30000000 +00000098005B20000000000151000000002B500890000000 +000003C202B5000000000004500000000005B202C3000000 +000007A006B1000000000016200000000001B600A7000000 +00000B501B500000000000450000000000005B105B000000 +00002C303C200000000001630000000000002C303C200000 +00003B105B000000000003610000000000000B501B300000 +00005B00780000000000164111100000000008700B500000 +00006A00970000000000366666610000000007900A600000 +000069009600000000006211144000000000069009600000 +00006A00970000000002644426200000000007900A600000 +00005B00780000000002444555000000000008700B500000 +00003B105B000000000000066200000000000B501B300000 +00002C303C200000000000166000000000002C303C200000 +00000B501B500000000000273000000000005B105B000000 +000007A006B1000000000046100000000001B600A7000000 +000003C202B5000000000064000000000005B202C3000000 +00000098005B20000000015100000000002B500890000000 +0000003C3019910000000010000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evBoundEsc [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000000000000002B500890000000 +000003C202B5000000000000000000000005B202C3000000 +000007A006B1000000000000000000000001B600A7000000 +00000B501B500000000000000000000000005B105B000000 +00002C303C200000000000000000000000002C303C200000 +00003B105B000000000000010000000000000B501B300000 +00005B00780000000000002620000000000008700B500000 +00006A00970000000000026662000000000007900A600000 +000069009600000000002636362000000000069009600000 +00006A00970000000002621612620000000007900A600000 +00005B00780000000026201610262000000008700B500000 +00003B105B000000026311262113620000000B501B300000 +00002C303C200000166666666666661000002C303C200000 +00000B501B500000011111262111110000005B105B000000 +000007A006B1000000000016100000000001B600A7000000 +000003C202B5000000000015100000000005B202C3000000 +00000098005B20000000000100000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evBoundSignal [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000300000000002B500890000000 +000003C202B5000000000037300000000005B202C3000000 +000007A006B1000000000164610000000001B600A7000000 +00000B501B500000000003515300000000005B105B000000 +00002C303C200000000016202610000000002C303C200000 +00003B105B000000000045000540000000000B501B300000 +00005B00780000000001620002610000000008700B500000 +00006A00970000000004500000540000000007900A600000 +000069009600000000262000002620000000069009600000 +00006A00970000000054000000045000000007900A600000 +00005B00780000000261000000016200000008700B500000 +00003B105B000000054000000000450000000B501B300000 +00002C303C200000261000000000162000002C303C200000 +00000B501B500000541111111111145000005B105B000000 +000007A006B1000266666666666666620001B600A7000000 +000003C202B5000011111111111111100005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evBoundCancel [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000000000000002B500890000000 +000003C202B5000001000000000001000005B202C3000000 +000007A006B1000016300000000036100001B600A7000000 +00000B501B500000036300000003630000005B105B000000 +00002C303C200000003630000036300000002C303C200000 +00003B105B000000000363000363000000000B501B300000 +00005B00780000000000363036300000000008700B500000 +00006A00970000000000036463000000000007900A600000 +000069009600000000000048400000000000069009600000 +00006A00970000000000036463000000000007900A600000 +00005B00780000000000363036300000000008700B500000 +00003B105B000000000363000363000000000B501B300000 +00002C303C200000003630000036300000002C303C200000 +00000B501B500000036300000003630000005B105B000000 +000007A006B1000016300000000036100001B600A7000000 +000003C202B5000001000000000001000005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evBoundComp [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910000000000000000000199103C30000000 +00000098005B20000000000000000000002B500890000000 +000003C202B5000100000001000000000005B202C3000000 +000007A006B1000541000026200000000001B600A7000000 +00000B501B500005551000266300000000005B105B000000 +00002C303C200005346200252640000000002C303C200000 +00003B105B000005203630250155100000000B501B300000 +00005B00780000052002543500145200000008700B500000 +00006A00970000052000155500003630000007900A600000 +000069009600000520000155000003610000069009600000 +00006A00970000052000155500003630000007900A600000 +00005B00780000052002543500145200000008700B500000 +00003B105B000005203630250155100000000B501B300000 +00002C303C200005346200252640000000002C303C200000 +00000B501B500005551000266300000000005B105B000000 +000007A006B1000541000026200000000001B600A7000000 +000003C202B5000100000001000000000005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019910000000000000000000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evBoundCond [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +0000000000000039CA53100000135AC93000000000000000 +00000000000028C82000000000000028C820000000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000005C710026BCB87678BCB620017C500000000000 +0000000005C50015BB52000000025BB51005C50000000000 +000000004C50029B510000000000015B92005C4000000000 +00000002B7002B9200000000000000029B2007B200000000 +00000008A102B810000000000000000018B201A800000000 +0000003C3019910004888888888884000199103C30000000 +00000098005B20004BFFFFFFFFFFFB40002B500890000000 +000003C202B500008FB888888888BF800005B202C3000000 +000007A006B100008F80000000008F800001B600A7000000 +00000B501B5000008F80000000008F8000005B105B000000 +00002C303C2000008F80000000008F8000002C303C200000 +00003B105B0000008F82555555528F8000000B501B300000 +00005B00780000008F80000000008F80000008700B500000 +00006A00970000008F80000000008F80000007900A600000 +00006900960000008F82555555528F800000069009600000 +00006A00970000008F80000000008F80000007900A600000 +00005B00780000008F80000000008F80000008700B500000 +00003B105B0000008F82555555528F8000000B501B300000 +00002C303C2000008F80000000008F8000002C303C200000 +00000B501B5000008F80000000008F8000005B105B000000 +000007A006B100008F80000000008F800001B600A7000000 +000003C202B500008FB888888888BF800005B202C3000000 +00000098005B20004BFFFFFFFFFFFB40002B500890000000 +0000003C3019910004888888888884000199103C30000000 +00000008A102B810000000000000000018B201A800000000 +00000002B7002B9200000000000000029B2007B200000000 +000000004C50029B510000000000015B92005C4000000000 +0000000005C50015BB52000000025BB51005C50000000000 +00000000005C710026BCB87678BCB620017C500000000000 +000000000004BA3000135799975310003AB4000000000000 +00000000000028C82000000000000028C820000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BCBBA9ABBCB7300000000000000000 +000000000000000000023566653200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evBoundMsgNI [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000003566653000000000000000000000 +000000000000000001116BA9AB6111000000000000000000 +000000000000000111000000000001110000000000000000 +000000000000284100000000000000014820000000000000 +000000000004BA2000000799970000002AB4000000000000 +00000000005C71000011187678111000017C500000000000 +0000000005C5001511000000000001151005C50000000000 +000000000950029B300000000000003B9200590000000000 +0000000011002B9200000000000000029B20011000000000 +000000011000B810000000000000000018B0001100000000 +000000010001310000000000000000000131000100000000 +000000110001000000000000000000000001000110000000 +000000100010000000000000000000000000100010000000 +000007800010000888888888888888880000100087000000 +00000B501620008FFFFFFFFFFFFFFFFF800026105B000000 +00002C303C20008FDA88888888888ADF80002C303C200000 +00003B105B00008F854000000000458F80000B501B300000 +00005B007800008F815510000015518F800008700B500000 +00006A009700008F800451000154008F800007900A600000 +000035005300008F800036202630008F8000035005300000 +000001001100008F800002646200008F8000011001000000 +000001001100008F800000252000008F8000011001000000 +000001000100008F800000000000008F8000010001000000 +000001000100008FB8888888888888BF8000010001000000 +000001000540008FFFFFFFFFFFFFFFFF8000450001000000 +0000013006B1000888888888888888880001B60031000000 +000003C202B5000000000000000000000005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019700000000000000000000079103C30000000 +00000008A10111000000000000000000011101A800000000 +00000002B70001100000000000000000110007B200000000 +000000004400001121000000000001211000044000000000 +0000000000100000BB52000000025BB00000100000000000 +000000000001100026BCB11111BCB6200011000000000000 +000000000000111000135111115310001110000000000000 +000000000000019820000000000000289100000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BC6111116CB7300000000000000000 +000000000000000000020001000200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evBoundTimerNI [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000003566653000000000000000000000 +000000000000000001116BA9AB6111000000000000000000 +000000000000000111000000000001110000000000000000 +000000000000284100000000000000014820000000000000 +000000000004BA2000000799970000002AB4000000000000 +00000000005C71000011187678111000017C500000000000 +0000000005C5001511000000000001151005C50000000000 +000000000950029B300000000000003B9200590000000000 +0000000011002B9200000000000000029B20011000000000 +000000011000B810000000000000000018B0001100000000 +000000010001310000000000000000000131000100000000 +000000110001000000000133310000000001000110000000 +000000100010000000039BBBBB9300000000100010000000 +0000078000100000006C8312138C60000000100087000000 +00000B501620000006B400151004B600000026105B000000 +00002C303C2000003C40001510004C3000002C303C200000 +00003B105B000000980000151000089000000B501B300000 +00005B0078000001B3000015100003B1000008700B500000 +00006A0097000003B1000015111001B3000007900A600000 +0000350053000003B0000015555300B30000035005300000 +0000010011000003B1000001111001B30000011001000000 +0000010011000001B3000000000003B10000011001000000 +000001000100000098000000000008900000010001000000 +00000100010000003C40000000004C300000010001000000 +000001000540000006B400000004B6000000450001000000 +0000013006B10000006C8310138C60000001B60031000000 +000003C202B5000000039BBBBB9300000005B202C3000000 +00000098005B20000000013331000000002B500890000000 +0000003C3019700000000000000000000079103C30000000 +00000008A10111000000000000000000011101A800000000 +00000002B70001100000000000000000110007B200000000 +000000004400001121000000000001211000044000000000 +0000000000100000BB52000000025BB00000100000000000 +000000000001100026BCB11111BCB6200011000000000000 +000000000000111000135111115310001110000000000000 +000000000000019820000000000000289100000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BC6111116CB7300000000000000000 +000000000000000000020001000200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evBoundEscNI [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000003566653000000000000000000000 +000000000000000001116BA9AB6111000000000000000000 +000000000000000111000000000001110000000000000000 +000000000000284100000000000000014820000000000000 +000000000004BA2000000799970000002AB4000000000000 +00000000005C71000011187678111000017C500000000000 +0000000005C5001511000000000001151005C50000000000 +000000000950029B300000000000003B9200590000000000 +0000000011002B9200000000000000029B20011000000000 +000000011000B810000000000000000018B0001100000000 +000000010001310000000000000000000131000100000000 +000000110001000000000000000000000001000110000000 +000000100010000000000000000000000000100010000000 +000007800010000000000000000000000000100087000000 +00000B50162000000000000000000000000026105B000000 +00002C303C200000000000000000000000002C303C200000 +00003B105B000000000000010000000000000B501B300000 +00005B00780000000000002620000000000008700B500000 +00006A00970000000000026662000000000007900A600000 +000035005300000000002636362000000000035005300000 +000001001100000000026216126200000000011001000000 +000001001100000000262016102620000000011001000000 +000001000100000002631126211362000000010001000000 +000001000100000016666666666666100000010001000000 +000001000540000001111126211111000000450001000000 +0000013006B1000000000016100000000001B60031000000 +000003C202B5000000000015100000000005B202C3000000 +00000098005B20000000000100000000002B500890000000 +0000003C3019700000000000000000000079103C30000000 +00000008A10111000000000000000000011101A800000000 +00000002B70001100000000000000000110007B200000000 +000000004400001121000000000001211000044000000000 +0000000000100000BB52000000025BB00000100000000000 +000000000001100026BCB11111BCB6200011000000000000 +000000000000111000135111115310001110000000000000 +000000000000019820000000000000289100000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BC6111116CB7300000000000000000 +000000000000000000020001000200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evBoundSignalNI [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000003566653000000000000000000000 +000000000000000001116BA9AB6111000000000000000000 +000000000000000111000000000001110000000000000000 +000000000000284100000000000000014820000000000000 +000000000004BA2000000799970000002AB4000000000000 +00000000005C71000011187678111000017C500000000000 +0000000005C5001511000000000001151005C50000000000 +000000000950029B300000000000003B9200590000000000 +0000000011002B9200000000000000029B20011000000000 +000000011000B810000000000000000018B0001100000000 +000000010001310000000000000000000131000100000000 +000000110001000000000003000000000001000110000000 +000000100010000000000037300000000000100010000000 +000007800010000000000164610000000000100087000000 +00000B50162000000000035153000000000026105B000000 +00002C303C200000000016202610000000002C303C200000 +00003B105B000000000045000540000000000B501B300000 +00005B00780000000001620002610000000008700B500000 +00006A00970000000004500000540000000007900A600000 +000035005300000000262000002620000000035005300000 +000001001100000000540000000450000000011001000000 +000001001100000002610000000162000000011001000000 +000001000100000005400000000045000000010001000000 +000001000100000026100000000016200000010001000000 +000001000540000054111111111114500000450001000000 +0000013006B1000266666666666666620001B60031000000 +000003C202B5000011111111111111100005B202C3000000 +00000098005B20000000000000000000002B500890000000 +0000003C3019700000000000000000000079103C30000000 +00000008A10111000000000000000000011101A800000000 +00000002B70001100000000000000000110007B200000000 +000000004400001121000000000001211000044000000000 +0000000000100000BB52000000025BB00000100000000000 +000000000001100026BCB11111BCB6200011000000000000 +000000000000111000135111115310001110000000000000 +000000000000019820000000000000289100000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BC6111116CB7300000000000000000 +000000000000000000020001000200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $evBoundCondNI [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000003566653000000000000000000000 +000000000000000001116BA9AB6111000000000000000000 +000000000000000111000000000001110000000000000000 +000000000000284100000000000000014820000000000000 +000000000004BA2000000799970000002AB4000000000000 +00000000005C71000011187678111000017C500000000000 +0000000005C5001511000000000001151005C50000000000 +000000000950029B300000000000003B9200590000000000 +0000000011002B9200000000000000029B20011000000000 +000000011000B810000000000000000018B0001100000000 +000000010001310004888888888884000131000100000000 +00000011000100004BFFFFFFFFFFFB400001000110000000 +00000010001000008FB888888888BF800000100010000000 +00000780001000008F80000000008F800000100087000000 +00000B50162000008F80000000008F80000026105B000000 +00002C303C2000008F80000000008F8000002C303C200000 +00003B105B0000008F82555555528F8000000B501B300000 +00005B00780000008F80000000008F80000008700B500000 +00006A00970000008F80000000008F80000007900A600000 +00003500530000008F82555555528F800000035005300000 +00000100110000008F80000000008F800000011001000000 +00000100110000008F80000000008F800000011001000000 +00000100010000008F82555555528F800000010001000000 +00000100010000008F80000000008F800000010001000000 +00000100054000008F80000000008F800000450001000000 +0000013006B100008F80000000008F800001B60031000000 +000003C202B500008FB888888888BF800005B202C3000000 +00000098005B20004BFFFFFFFFFFFB40002B500890000000 +0000003C3019700004888888888884000079103C30000000 +00000008A10111000000000000000000011101A800000000 +00000002B70001100000000000000000110007B200000000 +000000004400001121000000000001211000044000000000 +0000000000100000BB52000000025BB00000100000000000 +000000000001100026BCB11111BCB6200011000000000000 +000000000000111000135111115310001110000000000000 +000000000000019820000000000000289100000000000000 +0000000000000039CA53100000135AC93000000000000000 +000000000000000037BC6111116CB7300000000000000000 +000000000000000000020001000200000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $gwXor [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000001000000000000000000000000 +00000000000000000000004C400000000000000000000000 +0000000000000000000004C6C40000000000000000000000 +000000000000000000004C404C4000000000000000000000 +00000000000000000004C40004C400000000000000000000 +0000000000000000004C4000004C40000000000000000000 +000000000000000004C400000004C4000000000000000000 +00000000000000004C40000000004C400000000000000000 +0000000000000004C4000000000004C40000000000000000 +000000000000004C400000000000004C4000000000000000 +00000000000004C40000000000000004C400000000000000 +0000000000004C4000000000000000004C40000000000000 +000000000004C400000000000000000004C4000000000000 +00000000004C40010000000000000001004C400000000000 +0000000004C4001630000000000000361004C40000000000 +000000004C400003630000000000036300004C4000000000 +00000004C40000003630000000003630000004C400000000 +0000004C4000000003630000000363000000004C40000000 +000004C400000000003630000036300000000004C4000000 +00004C40000000000003630003630000000000004C400000 +0004C4000000000000003631363000000000000004C40000 +004C400000000000000003646300000000000000004C4000 +01C60000000000000000014841000000000000000006C100 +004C400000000000000003646300000000000000004C4000 +0004C4000000000000003631363000000000000004C40000 +00004C40000000000003630003630000000000004C400000 +000004C400000000003630000036300000000004C4000000 +0000004C4000000003630000000363000000004C40000000 +00000004C40000003630000000003630000004C400000000 +000000004C400003630000000000036300004C4000000000 +0000000004C4001630000000000000361004C40000000000 +00000000004C40010000000000000001004C400000000000 +000000000004C400000000000000000004C4000000000000 +0000000000004C4000000000000000004C40000000000000 +00000000000004C40000000000000004C400000000000000 +000000000000004C400000000000004C4000000000000000 +0000000000000004C4000000000004C40000000000000000 +00000000000000004C40000000004C400000000000000000 +000000000000000004C400000004C4000000000000000000 +0000000000000000004C4000004C40000000000000000000 +00000000000000000004C40004C400000000000000000000 +000000000000000000004C404C4000000000000000000000 +0000000000000000000004C6C40000000000000000000000 +00000000000000000000004C400000000000000000000000 +000000000000000000000001000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $gwAnd [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000001000000000000000000000000 +00000000000000000000004C400000000000000000000000 +0000000000000000000004C6C40000000000000000000000 +000000000000000000004C404C4000000000000000000000 +00000000000000000004C40004C400000000000000000000 +0000000000000000004C4000004C40000000000000000000 +000000000000000004C400000004C4000000000000000000 +00000000000000004C40000000004C400000000000000000 +0000000000000004C4000000000004C40000000000000000 +000000000000004C400000000000004C4000000000000000 +00000000000004C40000000000000004C400000000000000 +0000000000004C4000000000000000004C40000000000000 +000000000004C400000000020000000004C4000000000000 +00000000004C40000000002620000000004C400000000000 +0000000004C4000000000026200000000004C40000000000 +000000004C400000000000262000000000004C4000000000 +00000004C40000000000002620000000000004C400000000 +0000004C4000000000000026200000000000004C40000000 +000004C400000000000000262000000000000004C4000000 +00004C40000000000000002620000000000000004C400000 +0004C4000000000000000026200000000000000004C40000 +004C400000000022222222262222222220000000004C4000 +01C60000000002666666666666666666620000000006C100 +004C400000000022222222262222222220000000004C4000 +0004C4000000000000000026200000000000000004C40000 +00004C40000000000000002620000000000000004C400000 +000004C400000000000000262000000000000004C4000000 +0000004C4000000000000026200000000000004C40000000 +00000004C40000000000002620000000000004C400000000 +000000004C400000000000262000000000004C4000000000 +0000000004C4000000000026200000000004C40000000000 +00000000004C40000000002620000000004C400000000000 +000000000004C400000000020000000004C4000000000000 +0000000000004C4000000000000000004C40000000000000 +00000000000004C40000000000000004C400000000000000 +000000000000004C400000000000004C4000000000000000 +0000000000000004C4000000000004C40000000000000000 +00000000000000004C40000000004C400000000000000000 +000000000000000004C400000004C4000000000000000000 +0000000000000000004C4000004C40000000000000000000 +00000000000000000004C40004C400000000000000000000 +000000000000000000004C404C4000000000000000000000 +0000000000000000000004C6C40000000000000000000000 +00000000000000000000004C400000000000000000000000 +000000000000000000000001000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $gwOr [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000001000000000000000000000000 +00000000000000000000004C400000000000000000000000 +0000000000000000000004C6C40000000000000000000000 +000000000000000000004C404C4000000000000000000000 +00000000000000000004C40004C400000000000000000000 +0000000000000000004C4000004C40000000000000000000 +000000000000000004C400000004C4000000000000000000 +00000000000000004C40000000004C400000000000000000 +0000000000000004C4000000000004C40000000000000000 +000000000000004C400000000000004C4000000000000000 +00000000000004C40000000000000004C400000000000000 +0000000000004C4000000000000000004C40000000000000 +000000000004C400000000000000000004C4000000000000 +00000000004C40000000000000000000004C400000000000 +0000000004C4000000000123210000000004C40000000000 +000000004C40000000027BCCCB72000000004C4000000000 +00000004C4000000004BB63336BB4000000004C400000000 +0000004C4000000004C710000017C4000000004C40000000 +000004C4000000002B71000000017B2000000004C4000000 +00004C40000000007B10000000001B70000000004C400000 +0004C40000000001B6000000000006B10000000004C40000 +004C400000000002C3000000000003C200000000004C4000 +01C6000000000003C3000000000003C3000000000006C100 +004C400000000002C3000000000003C200000000004C4000 +0004C40000000001B6000000000006B10000000004C40000 +00004C40000000007B10000000001B70000000004C400000 +000004C4000000002B71000000017B2000000004C4000000 +0000004C4000000004C710000017C4000000004C40000000 +00000004C4000000004BB63336BB4000000004C400000000 +000000004C40000000027BCCCB72000000004C4000000000 +0000000004C4000000000123210000000004C40000000000 +00000000004C40000000000000000000004C400000000000 +000000000004C400000000000000000004C4000000000000 +0000000000004C4000000000000000004C40000000000000 +00000000000004C40000000000000004C400000000000000 +000000000000004C400000000000004C4000000000000000 +0000000000000004C4000000000004C40000000000000000 +00000000000000004C40000000004C400000000000000000 +000000000000000004C400000004C4000000000000000000 +0000000000000000004C4000004C40000000000000000000 +00000000000000000004C40004C400000000000000000000 +000000000000000000004C404C4000000000000000000000 +0000000000000000000004C6C40000000000000000000000 +00000000000000000000004C400000000000000000000000 +000000000000000000000001000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $gwComplex [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000001000000000000000000000000 +00000000000000000000004C400000000000000000000000 +0000000000000000000004C6C40000000000000000000000 +000000000000000000004C404C4000000000000000000000 +00000000000000000004C40004C400000000000000000000 +0000000000000000004C4000004C40000000000000000000 +000000000000000004C400000004C4000000000000000000 +00000000000000004C40000000004C400000000000000000 +0000000000000004C4000000000004C40000000000000000 +000000000000004C400000000000004C4000000000000000 +00000000000004C40000000000000004C400000000000000 +0000000000004C4000000000000000004C40000000000000 +000000000004C400000000000000000004C4000000000000 +00000000004C40000000000000000000004C400000000000 +0000000004C4000000000001000000000004C40000000000 +000000004C400000000000151000000000004C4000000000 +00000004C40000000010001610001000000004C400000000 +0000004C4000000001520016100251000000004C40000000 +000004C400000000002620161026200000000004C4000000 +00004C40000000000002621612620000000000004C400000 +0004C4000000000000002636362000000000000004C40000 +004C400000000000111113666311111000000000004C4000 +01C60000000000015666666866666651000000000006C100 +004C400000000000111113666311111000000000004C4000 +0004C4000000000000002636362000000000000004C40000 +00004C40000000000002621612620000000000004C400000 +000004C400000000002620161026200000000004C4000000 +0000004C4000000001520016100251000000004C40000000 +00000004C40000000010001610001000000004C400000000 +000000004C400000000000151000000000004C4000000000 +0000000004C4000000000001000000000004C40000000000 +00000000004C40000000000000000000004C400000000000 +000000000004C400000000000000000004C4000000000000 +0000000000004C4000000000000000004C40000000000000 +00000000000004C40000000000000004C400000000000000 +000000000000004C400000000000004C4000000000000000 +0000000000000004C4000000000004C40000000000000000 +00000000000000004C40000000004C400000000000000000 +000000000000000004C400000004C4000000000000000000 +0000000000000000004C4000004C40000000000000000000 +00000000000000000004C40004C400000000000000000000 +000000000000000000004C404C4000000000000000000000 +0000000000000000000004C6C40000000000000000000000 +00000000000000000000004C400000000000000000000000 +000000000000000000000001000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $gwEvent [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000001000000000000000000000000 +00000000000000000000004C400000000000000000000000 +0000000000000000000004C6C40000000000000000000000 +000000000000000000004C404C4000000000000000000000 +00000000000000000004C40004C400000000000000000000 +0000000000000000004C4000004C40000000000000000000 +000000000000000004C400000004C4000000000000000000 +00000000000000004C40000000004C400000000000000000 +0000000000000004C4000000000004C40000000000000000 +000000000000004C400000000000004C4000000000000000 +00000000000004C40000000000000004C400000000000000 +0000000000004C4000002467642000004C40000000000000 +000000000004C400014ABA878ABA410004C4000000000000 +00000000004C40002AB510000015BA20004C400000000000 +0000000004C40003B7100013100017B30004C40000000000 +000000004C40002B600001565100006B20004C4000000000 +00000004C40001A70000363136300007A10004C400000000 +0000004C400004B10014620002641001B400004C40000000 +000004C400000A5002551000001552005A000004C4000000 +00004C4000002B1036300000000036301B2000004C400000 +0004C40000004A0162000000000002610A40000004C40000 +004C400000006800530000000000035008600000004C4000 +01C60000000077002500000000000520077000000006C100 +004C400000006800161000000000161008600000004C4000 +0004C40000004A0005300000000035000A40000004C40000 +00004C4000002B1003500000000053001B2000004C400000 +000004C400000A5001610000000161005A000004C4000000 +0000004C400004B10053111111135001B400004C40000000 +00000004C40001A70036666666663007A10004C400000000 +000000004C40002B600111111111006B20004C4000000000 +0000000004C40003B7100000000017B30004C40000000000 +00000000004C40002AB510000015BA20004C400000000000 +000000000004C400014ABA878ABA410004C4000000000000 +0000000000004C4000002467642000004C40000000000000 +00000000000004C40000000000000004C400000000000000 +000000000000004C400000000000004C4000000000000000 +0000000000000004C4000000000004C40000000000000000 +00000000000000004C40000000004C400000000000000000 +000000000000000004C400000004C4000000000000000000 +0000000000000000004C4000004C40000000000000000000 +00000000000000000004C40004C400000000000000000000 +000000000000000000004C404C4000000000000000000000 +0000000000000000000004C6C40000000000000000000000 +00000000000000000000004C400000000000000000000000 +000000000000000000000001000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $gwParEvent [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000001000000000000000000000000 +00000000000000000000004C400000000000000000000000 +0000000000000000000004C6C40000000000000000000000 +000000000000000000004C404C4000000000000000000000 +00000000000000000004C40004C400000000000000000000 +0000000000000000004C4000004C40000000000000000000 +000000000000000004C400000004C4000000000000000000 +00000000000000004C40000000004C400000000000000000 +0000000000000004C4000000000004C40000000000000000 +000000000000004C400000000000004C4000000000000000 +00000000000004C40000000000000004C400000000000000 +0000000000004C4000000000000000004C40000000000000 +000000000004C400000000000000000004C4000000000000 +00000000004C40000000000000000000004C400000000000 +0000000004C4000000001356531000000004C40000000000 +000000004C4000000015BBA9ABB5100000004C4000000000 +00000004C4000000019B5101015B9100000004C400000000 +0000004C4000000019A200151002A9100000004C40000000 +000004C4000000005B20001610002B5000000004C4000000 +00004C4000000001B5000016100005B1000000004C400000 +0004C40000000003B1000016100001B30000000004C40000 +004C400000000005A0111126211110A500000000004C4000 +01C60000000000069156666666665196000000000006C100 +004C400000000005A0111126211110A500000000004C4000 +0004C40000000003B1000016100001B30000000004C40000 +00004C4000000001B5000016100005B1000000004C400000 +000004C4000000005B20001610002B5000000004C4000000 +0000004C4000000019A200151002A9100000004C40000000 +00000004C4000000019B5101015B9100000004C400000000 +000000004C4000000015BBA9ABB5100000004C4000000000 +0000000004C4000000001356531000000004C40000000000 +00000000004C40000000000000000000004C400000000000 +000000000004C400000000000000000004C4000000000000 +0000000000004C4000000000000000004C40000000000000 +00000000000004C40000000000000004C400000000000000 +000000000000004C400000000000004C4000000000000000 +0000000000000004C4000000000004C40000000000000000 +00000000000000004C40000000004C400000000000000000 +000000000000000004C400000004C4000000000000000000 +0000000000000000004C4000004C40000000000000000000 +00000000000000000004C40004C400000000000000000000 +000000000000000000004C404C4000000000000000000000 +0000000000000000000004C6C40000000000000000000000 +00000000000000000000004C400000000000000000000000 +000000000000000000000001000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $icUser [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000034300000000000000000000000 +0000000000000000000019CEC91000000000000000000000 +000000000000000000008DFFFD8000000000000000000000 +00000000000000000001BFFFFFB100000000000000000000 +00000000000000000002CFFFFFC200000000000000000000 +00000000000000000001BEFFFEB100000000000000000000 +000000000000000000005DEFED5000000000000000000000 +0000000000000000000004ABA40000000000000000000000 +000000000000000000030000000300000000000000000000 +000000000000000000260000000620000000000000000000 +000000000000000000440000000440000000000000000000 +000000000000000001610000000161000000000000000000 +000000000000000002500000000052000000000000000000 +000000000000000005311111111135000000000000000000 +000000000000000016666666666666100000000000000000 +000000000000000001111111111111000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $icService [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000003000000000000000000000000 +000000000000000000000016100000000000000000000000 +000000000000000001200016100021000000000000000000 +000000000000000002620147410262000000000000000000 +000000000000000000265BCCCB5620000000000000000000 +00000000000000000005C73537C500000000000000000000 +0000000000000000001B7400047B10000000000000000000 +0000000000000000114C3000003C41100000000000000000 +0000000000000003667C5000005C76630000000000000000 +0000000000000000114C3000003C41100000000000000000 +0000000000000000001B7400047B10000000000000000000 +00000000000000000005C73537C500000000000000000000 +000000000000000000265BCCCB5620000000000000000000 +000000000000000002620147410262000000000000000000 +000000000000000001200016100021000000000000000000 +000000000000000000000016100000000000000000000000 +000000000000000000000003000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $icScript [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000004888888888884000000000000000000 +00000000000000004BFFFFFFFFFFFB400000000000000000 +00000000000000008FB888888888BF800000000000000000 +00000000000000008F80000000008F800000000000000000 +00000000000000008F80000000008F800000000000000000 +00000000000000008F80000000008F800000000000000000 +00000000000000008F82555555528F800000000000000000 +00000000000000008F80000000008F800000000000000000 +00000000000000008F80000000008F800000000000000000 +00000000000000008F82555555528F800000000000000000 +00000000000000008F80000000008F800000000000000000 +00000000000000008F80000000008F800000000000000000 +00000000000000008F82555555528F800000000000000000 +00000000000000008F80000000008F800000000000000000 +00000000000000008F80000000008F800000000000000000 +00000000000000008F80000000008F800000000000000000 +00000000000000008FB888888888BF800000000000000000 +00000000000000004BFFFFFFFFFFFB400000000000000000 +000000000000000004888888888884000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $icManual [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000010000000000000000000000000 +000000000000000000000151003000000000000000000000 +000000000000000000100160016100000000000000000000 +000000000000000001510260026000100000000000000000 +000000000000000003500350035001510000000000000000 +000000000000000005300440044002600000000000000000 +000000000000000016100530053003500000000000000000 +000000000000000035000620052005300000000000000000 +000000000000000053112621262116200000000000000000 +000000000000000166666666666666610000000000000000 +000000000000000011111111111111100000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $icRule [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000011111111111111100000000000000000 +000000000000000156666666666666510000000000000000 +000000000000000161111116111111610000000000000000 +000000000000000161000016100001610000000000000000 +000000000000000161000016100001610000000000000000 +000000000000000161000016100001610000000000000000 +000000000000000161111116111111610000000000000000 +000000000000000166666666666666610000000000000000 +000000000000000161111116111111610000000000000000 +000000000000000161000016100001610000000000000000 +000000000000000161000016100001610000000000000000 +000000000000000161000016100001610000000000000000 +000000000000000161111116111111610000000000000000 +000000000000000156666666666666510000000000000000 +000000000000000011111111111111100000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $icPlusbox [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000048888888888888400000000000000000 +0000000000000004BFFFFFFFFFFFFFB40000000000000000 +0000000000000008FB88888888888BF80000000000000000 +0000000000000008F8000003000008F80000000000000000 +0000000000000008F8000016100008F80000000000000000 +0000000000000008F8000016100008F80000000000000000 +0000000000000008F8000016100008F80000000000000000 +0000000000000008F8011126211108F80000000000000000 +0000000000000008F8366666666638F80000000000000000 +0000000000000008F8011126211108F80000000000000000 +0000000000000008F8000016100008F80000000000000000 +0000000000000008F8000016100008F80000000000000000 +0000000000000008F8000016100008F80000000000000000 +0000000000000008F8000003000008F80000000000000000 +0000000000000008FB88888888888BF80000000000000000 +0000000000000004BFFFFFFFFFFFFFB40000000000000000 +000000000000000048888888888888400000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $icMsg [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000888888888888888880000000000000000 +000000000000008FFFFFFFFFFFFFFFFF8000000000000000 +000000000000008FDA88888888888ADF8000000000000000 +000000000000008F854000000000458F8000000000000000 +000000000000008F815510000015518F8000000000000000 +000000000000008F800451000154008F8000000000000000 +000000000000008F800036202630008F8000000000000000 +000000000000008F800002646200008F8000000000000000 +000000000000008F800000252000008F8000000000000000 +000000000000008F800000000000008F8000000000000000 +000000000000008FB8888888888888BF8000000000000000 +000000000000008FFFFFFFFFFFFFFFFF8000000000000000 +000000000000000888888888888888880000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $icMsgf [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000888888888888888880000000000000000 +000000000000008FFFFFFFFFFFFFFFFF8000000000000000 +000000000000008FFFFFFFFFFFFFFFFF8000000000000000 +000000000000008FFFFFFFFFFFFFFFFF8000000000000000 +000000000000008FFFFFFFFFFFFFFFFF8000000000000000 +000000000000008FFFFFFFFFFFFFFFFF8000000000000000 +000000000000008FFFFFFFFFFFFFFFFF8000000000000000 +000000000000008FFFFFFFFFFFFFFFFF8000000000000000 +000000000000008FFFFFFFFFFFFFFFFF8000000000000000 +000000000000008FFFFFFFFFFFFFFFFF8000000000000000 +000000000000008FFFFFFFFFFFFFFFFF8000000000000000 +000000000000008FFFFFFFFFFFFFFFFF8000000000000000 +000000000000000888888888888888880000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} + +sprite $icLoop [48x48/16] { +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000002578752000000000000000000000 +00000000000000000039CB989BC930000000000000000000 +000000000000000005C931000139C5000000000000000000 +00000000000000005D61000000016D500000000000000000 +0000000000000003C6000000000006C30000000000000000 +000000000000000991000000000001990000000000000000 +000000000000002C300000000000003C2000000000000000 +000000000000005B100000000000001B5000000000000000 +000000000000007900000000000000097000000000000000 +000000000000008800000000000000088000000000000000 +000000000000007900000000000000097000000000000000 +000000000000005B100000000000011B5000000000000000 +000000000000002C300000000000265C2000000000000000 +000000000000000991000000000264A90000000000000000 +0000000000000003C6000000002636C62000000000000000 +00000000000000005D61000002636D526200000000000000 +000000000000000005C931001659C5002610000000000000 +00000000000000000039CB989BC930000100000000000000 +000000000000000000002578752000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000 +} diff --git a/build-exe-fast.bat b/build-exe-fast.bat new file mode 100644 index 0000000..0b26dd0 --- /dev/null +++ b/build-exe-fast.bat @@ -0,0 +1,85 @@ +@echo off +setlocal EnableExtensions +chcp 65001 >nul +cd /d "%~dp0" + +set "DO_PAUSE=1" +set "PYI_CLEAN=" +for %%A in (%*) do ( + if /i "%%~A"=="nopause" set "DO_PAUSE=0" + if /i "%%~A"=="clean" set "PYI_CLEAN=--clean" +) + +echo === md2gost: быстрая сборка exe (onefile, шрифты + plantuml.jar) === +echo Каталог: %CD% +echo Spec: md2gost.fast.spec +echo Кэш: build-fast\ (без --clean, пока не передадите clean) +echo. + +where python >nul 2>&1 +if errorlevel 1 ( + echo Python не найден в PATH. Установите Python 3.10+ и отметьте "Add python.exe to PATH". + goto :fail +) + +python -c "import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)" +if errorlevel 1 ( + echo Нужен Python 3.10 или новее. + python --version + goto :fail +) + +echo [1/4] Зависимости ... +python -c "import PyInstaller, matplotlib, docx, marko, lxml, pygments, PIL, freetype, latex2mathml, requests, docxcompose" 1>nul 2>nul +if errorlevel 1 ( + echo Ставлю проект + PyInstaller + matplotlib ... + python -m pip install -q -e . "pyinstaller>=6.0" "matplotlib>=3.7" + if errorlevel 1 ( + echo Не удалось поставить зависимости. + goto :fail + ) +) else ( + echo Уже стоят, pip пропускаю. +) + +if not exist "md2gost\Template.docx" ( + echo Нет md2gost\Template.docx — сборка бессмысленна. + goto :fail +) + +echo [2/4] PlantUML jar (обязателен, вшивается в exe) ... +python scripts\fetch_plantuml.py +if not exist "md2gost\vendor\plantuml.jar" ( + echo Нет md2gost\vendor\plantuml.jar — portable exe без jar не собираем. + goto :fail +) + +echo [3/4] PyInstaller onefile, кэш build-fast ... +python -m PyInstaller --noconfirm %PYI_CLEAN% --workpath build-fast --distpath dist md2gost.fast.spec +if errorlevel 1 ( + echo PyInstaller завершился с ошибкой. + goto :fail +) + +if not exist "dist\md2gost.exe" ( + echo dist\md2gost.exe не появился. + goto :fail +) + +echo. +echo [4/4] Готово: +echo %CD%\dist\md2gost.exe +echo. +echo Двойной клик — GUI. Из консоли: md2gost.exe report.md -o report.docx +echo Повторно без clean — быстрее за счёт кэша Analysis. +echo Полный пересбор: build-exe-fast.bat clean +echo. + +if "%DO_PAUSE%"=="1" pause +exit /b 0 + +:fail +echo. +echo Сборка не удалась. +if "%DO_PAUSE%"=="1" pause +exit /b 1 diff --git a/build-exe.bat b/build-exe.bat new file mode 100644 index 0000000..8fc11cf --- /dev/null +++ b/build-exe.bat @@ -0,0 +1,68 @@ +@echo off +setlocal EnableExtensions +chcp 65001 >nul +cd /d "%~dp0" + +echo === md2gost: сборка exe === +echo Каталог: %CD% +echo. + +where python >nul 2>&1 +if errorlevel 1 ( + echo Python не найден в PATH. Установите Python 3.10+ и отметьте "Add python.exe to PATH". + goto :fail +) + +python -c "import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)" +if errorlevel 1 ( + echo Нужен Python 3.10 или новее. + python --version + goto :fail +) + +echo [1/4] Зависимости проекта + PyInstaller + matplotlib ... +python -m pip install -q -e . "pyinstaller>=6.0" "matplotlib>=3.7" +if errorlevel 1 ( + echo Не удалось поставить зависимости. + goto :fail +) + +if not exist "md2gost\Template.docx" ( + echo Нет md2gost\Template.docx — сборка бессмысленна. + goto :fail +) + +echo [2/4] PlantUML jar (вшивается в exe, если скачается^) ... +python scripts\fetch_plantuml.py +if not exist "md2gost\vendor\plantuml.jar" ( + echo Предупреждение: plantuml.jar нет — в exe диаграммы пойдут через kroki.io. +) + +echo [3/4] PyInstaller (один файл, без консоли; CLI подцепит консоль сам^) ... +python -m PyInstaller --noconfirm --clean md2gost.spec +if errorlevel 1 ( + echo PyInstaller завершился с ошибкой. + goto :fail +) + +if not exist "dist\md2gost.exe" ( + echo dist\md2gost.exe не появился. + goto :fail +) + +echo. +echo [4/4] Готово: +echo %CD%\dist\md2gost.exe +echo. +echo Двойной клик — GUI. Из консоли: md2gost.exe report.md -o report.docx +echo. + +if /i "%~1"=="nopause" goto :eof +pause +exit /b 0 + +:fail +echo. +echo Сборка не удалась. +if /i not "%~1"=="nopause" pause +exit /b 1 diff --git a/md2gost.fast.spec b/md2gost.fast.spec new file mode 100644 index 0000000..bb63df0 --- /dev/null +++ b/md2gost.fast.spec @@ -0,0 +1,162 @@ +# -*- mode: python ; coding: utf-8 -*- +"""Faster onefile freeze: same portable exe (fonts + plantuml.jar + onefile). + +Keeps matplotlib (system font lookup) and bundles plantuml.jar. +Speeds Analysis by pinning the Agg backend, skipping duplicate pygments +collection (the stock hook already pulls all lexers), and not probing Qt/Gtk. +""" +import os +from pathlib import Path + +from PyInstaller.utils.hooks import ( + collect_data_files, + collect_dynamic_libs, + collect_submodules, +) + +# Isolated matplotlib hook subprocesses inherit this and skip GUI backend probes. +os.environ["MPLBACKEND"] = "Agg" + +ROOT = Path(SPECPATH).resolve() + +datas = [ + (str(ROOT / "md2gost" / "Template.docx"), "md2gost"), + (str(ROOT / "md2gost" / "mml2omml"), "md2gost"), + (str(ROOT / "md2gost" / "diagrams"), "md2gost/diagrams"), + (str(ROOT / "prompts"), "prompts"), +] +_plantuml = ROOT / "md2gost" / "vendor" / "plantuml.jar" +if not _plantuml.is_file(): + raise SystemExit( + "md2gost.fast.spec: нет md2gost/vendor/plantuml.jar — " + "сначала scripts/fetch_plantuml.py (jar обязателен в portable exe)" + ) +datas.append((str(_plantuml), "md2gost/vendor")) + +binaries = [] +hiddenimports = collect_submodules("md2gost") + +datas += collect_data_files("certifi") +datas += collect_data_files("lxml") +datas += collect_data_files("latex2mathml") +datas += collect_data_files("docx") +# Explicit mpl-data so DejaVu + matplotlib font cache files are always in the exe. +# hook-matplotlib.py also collects this; PyInstaller dedupes by dest path. +datas += collect_data_files("matplotlib", includes=["mpl-data/**"]) + +for pkg in ("lxml", "freetype"): + try: + binaries += collect_dynamic_libs(pkg) + except Exception: + pass + +hiddenimports += [ + "lxml", + "lxml.etree", + "lxml._elementpath", + "PIL", + "PIL.Image", + "PIL.PngImagePlugin", + "pygments", + "pygments.lexers", + "pygments.formatters", + "freetype", + "docx", + "docxcompose", + "docxcompose.composer", + "marko", + "marko.ext.gfm", + "latex2mathml", + "latex2mathml.converter", + "requests", + "certifi", + "matplotlib", + "matplotlib.font_manager", + "matplotlib.ft2font", + "tkinter", + "tkinter.filedialog", + "tkinter.messagebox", + "tkinter.ttk", +] +# Do NOT collect_submodules("pygments.lexers") here — hook-pygments.py already +# collects all lexers/formatters/styles once during Analysis. +try: + hiddenimports += collect_submodules("tkinterdnd2") +except Exception: + pass + +excludes = [ + "pytest", + "PyQt5", + "PyQt6", + "PySide2", + "PySide6", + "IPython", + "jupyter", + "notebook", + "zmq", + "scipy", + "pandas", + "tornado", + "numpy.tests", + "matplotlib.tests", + "matplotlib.testing", + "matplotlib.backends.qt_compat", + "matplotlib.backends.backend_qtagg", + "matplotlib.backends.backend_qtcairo", + "matplotlib.backends.backend_qt5agg", + "matplotlib.backends.backend_qt5cairo", + "matplotlib.backends.backend_qt5", + "matplotlib.backends.backend_qt", + "matplotlib.backends.backend_gtk3", + "matplotlib.backends.backend_gtk3agg", + "matplotlib.backends.backend_gtk3cairo", + "matplotlib.backends.backend_gtk4", + "matplotlib.backends.backend_gtk4agg", + "matplotlib.backends.backend_gtk4cairo", + "matplotlib.backends.backend_macosx", + "matplotlib.backends.backend_tkagg", + "matplotlib.backends.backend_tkcairo", + "matplotlib.backends.backend_wx", + "matplotlib.backends.backend_wxagg", + "matplotlib.backends.backend_wxcairo", + "matplotlib.backends.backend_nbagg", + "matplotlib.backends.backend_webagg", + "PIL.ImageQt", +] + +a = Analysis( + [str(ROOT / "scripts" / "md2gost_exe.py")], + pathex=[str(ROOT)], + binaries=binaries, + datas=datas, + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={ + "matplotlib": {"backends": "Agg"}, + }, + runtime_hooks=[str(ROOT / "scripts" / "pyi_rth_mplbackend.py")], + excludes=excludes, + noarchive=False, +) + +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name="md2gost", + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=False, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/md2gost.spec b/md2gost.spec new file mode 100644 index 0000000..a0f0d27 --- /dev/null +++ b/md2gost.spec @@ -0,0 +1,123 @@ +# -*- mode: python ; coding: utf-8 -*- +from pathlib import Path + +from PyInstaller.utils.hooks import ( + collect_data_files, + collect_dynamic_libs, + collect_submodules, +) + +ROOT = Path(SPECPATH).resolve() + +datas = [ + (str(ROOT / "md2gost" / "Template.docx"), "md2gost"), + (str(ROOT / "md2gost" / "mml2omml"), "md2gost"), + (str(ROOT / "md2gost" / "diagrams"), "md2gost/diagrams"), + (str(ROOT / "prompts"), "prompts"), +] +_plantuml = ROOT / "md2gost" / "vendor" / "plantuml.jar" +if _plantuml.is_file(): + datas.append((str(_plantuml), "md2gost/vendor")) +binaries = [] +hiddenimports = collect_submodules("md2gost") + +datas += collect_data_files("certifi") +datas += collect_data_files("lxml") +datas += collect_data_files("latex2mathml") +datas += collect_data_files("docx") +datas += collect_data_files("matplotlib", includes=["mpl-data/**"]) + +for pkg in ("lxml", "freetype"): + try: + binaries += collect_dynamic_libs(pkg) + except Exception: + pass + +hiddenimports += [ + "lxml", + "lxml.etree", + "lxml._elementpath", + "PIL", + "PIL.Image", + "PIL.PngImagePlugin", + "pygments", + "pygments.lexers", + "pygments.formatters", + "freetype", + "docx", + "docxcompose", + "docxcompose.composer", + "marko", + "marko.ext.gfm", + "latex2mathml", + "latex2mathml.converter", + "requests", + "certifi", + "matplotlib", + "matplotlib.font_manager", + "matplotlib.ft2font", + "tkinter", + "tkinter.filedialog", + "tkinter.messagebox", + "tkinter.ttk", +] +hiddenimports += collect_submodules("pygments.lexers") +hiddenimports += collect_submodules("marko") +try: + hiddenimports += collect_submodules("tkinterdnd2") +except Exception: + pass + +excludes = [ + "pytest", + "PyQt5", + "PyQt6", + "PySide2", + "PySide6", + "IPython", + "jupyter", + "notebook", + "zmq", + "matplotlib.backends.qt_compat", + "matplotlib.backends.backend_qtagg", + "matplotlib.backends.backend_qt5agg", + "matplotlib.backends.backend_qt5", + "matplotlib.backends.backend_qt", + "matplotlib.backends.backend_gtk3", + "matplotlib.backends.backend_gtk4", + "matplotlib.backends.backend_macosx", +] + +a = Analysis( + [str(ROOT / "scripts" / "md2gost_exe.py")], + pathex=[str(ROOT)], + binaries=binaries, + datas=datas, + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=excludes, + noarchive=False, +) + +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name="md2gost", + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=False, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/md2gost/README.docx b/md2gost/README.docx new file mode 100644 index 0000000000000000000000000000000000000000..31b298a8ee80033a4fee8c03b3e4360b1df6cbfe GIT binary patch literal 92382 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 H3HE;gb%` — colspan (ячейка слева) | | Листинг | `%id Подпись` перед code fence | -| Диаграмма UML/BPMN/C4 | `%id Подпись` + ````uml` / ````bpmn` / ````c4` → PNG (Рисунок); `+listing` — ещё и Листинг | +| Диаграмма UML / Mermaid / схемы | `%id Подпись` + ````uml` / ````uml-c4` / ````bpmn` / ````mermaid` → Рисунок; `+listing` — ещё и Листинг. Схемы в `md2gost.schemes.json`. IDEF0 нет | | Формула | `%eq1` + `$$…$$` + `@Формула:eq1` (номер только при ссылке) | | Источник | `[1]` в тексте; `[1]: …` в списке | +| Разрыв страницы | `---` на отдельной строке + `--hr-pagebreak` (по умолчанию `---` игнорируется) | ### Таблицы со склеиванием @@ -98,22 +129,44 @@ python -m md2gost report.md -o report.docx --toc manual Разные шапки «первый раз / продолжение» есть в LaTeX (`longtable`), не в Office. Наша оценка высоты строк ≠ вёрстка Word → если резать таблицу в скрипте, получается -mid-page «Продолжение…» (как было на 2.4). Поэтому по умолчанию таблицу **не режем**. +mid-page «Продолжение…» (как было на 2.4). По умолчанию режим **`word`**: после save +Word COM режет по реальной пагинации. Без Word — укажите `off` или поставьте Word + pywin32. | Режим | Поведение | |--------|-----------| -| **`off`** / **`soft`** (по умолчанию) | Одна таблица Word; перенос строк делает Word. Без автоподписи. Первая строка — повторяющаяся шапка (`tblHeader`). «Продолжение…» — вручную в markdown, если нужно | +| **`word`** (по умолчанию) | Как `off` при рендере; после save Word COM: `Split` + «Продолжение Таблицы N». Нужны Windows, Word, pywin32. Шапка на продолжении не повторяется (вкл: `--table-repeat-header`) | +| **`off`** / **`soft`** | Одна таблица Word; перенос строк делает Word. Без автоподписи. Первая строка — повторяющаяся шапка (`tblHeader`). «Продолжение…» — вручную в markdown, если нужно | | **`legacy`** | Режем по нашей оценке высоты + «Продолжение…» с `page_break_before` (могут быть дыры) | | **`caption`** | Режем по оценке + явный PageBreak + «Продолжение…» (то же ограничение точности) | ```bash +python -m md2gost report.md -o report.docx --table-continuation word python -m md2gost report.md -o report.docx --table-continuation off python -m md2gost report.md -o report.docx --table-continuation caption ``` +### Продолжение листинга (`--listing-continuation`) + +Те же режимы, что у таблиц. По умолчанию **`word`**. + +| Режим | Поведение | +|--------|-----------| +| **`word`** (по умолчанию) | После save Word COM + «Продолжение Листинга N» (Windows + Word + pywin32) | +| **`off`** / **`soft`** | Один блок кода; пагинацию делает Word. «Продолжение…» — вручную в markdown, если нужно | +| **`legacy`** | Режем по оценке высоты + «Продолжение Листинга N» с `page_break_before` | +| **`caption`** | Режем по оценке + явный PageBreak + «Продолжение Листинга N» | + +```bash +python -m md2gost report.md -o report.docx --listing-continuation word +python -m md2gost report.md -o report.docx --listing-continuation off +python -m md2gost report.md -o report.docx --listing-continuation caption +``` + ### Диаграммы -```markdown +В отчёте пишите так (пример в 4 обратных кавычках, чтобы вложенный ` ```uml ` не ломал разметку): + +````markdown %usecase1 Диаграмма прецедентов +listing ```uml @@ -122,19 +175,86 @@ actor Student Student --> (Login) @enduml ``` +```` + +Широкая схема на альбомной странице — флаг `+landscape` в той же строке `%`: + +%arch1 Архитектура +landscape + +```uml-c4 +Person(user, "Студент") +System(app, "Портал") ``` -Рендер (по приоритету): +````markdown +%arch1 Архитектура +landscape -1. `PLANTUML_JAR` / `--plantuml-jar` + Java → `plantuml.jar` -2. `KROKI_URL` / `--kroki-url` (по умолчанию `http://localhost:8000`) -3. remote `https://kroki.io` при `--diagram-fallback remote` (по умолчанию; предупреждение в лог) +```uml-c4 +Person(user, "Студент") +System(app, "Портал") +``` +```` -Кэш PNG: `{каталог_md}/.md2gost-cache/`. +Схемы (`c4`, `usecase`, свои): при первом запуске рядом с приложением создаётся `md2gost.schemes.json`. В markdown — оградка `uml-` или короткое ``: +````markdown +%arch C4 + +```uml-c4 +Person(user, "Студент") +System(app, "Портал") +Rel(user, app, "логин") +``` +```` + +URL в `includes` схемы скачиваются в кэш (`md2gost.include-cache.json` + папка `include-cache/`). CLI: `--schemes path.json`. **BPMN 2.0** — оградка ````bpmn` / ````uml-bpmn` (макросы `Start`, `UserTask`, `XOR`, `Flow`, `Pool`…; библиотека `diagrams/BPMN.puml`). **Mermaid** — ````mermaid` / ````mmd` через тот же Kroki (свой `--kroki-url` или kroki.io); jar не используется. **IDEF0** конвертер не рисует — вставляйте готовый PNG. + +```bpmn +StartMessage(s, "заявка") +UserTask(t, "Проверить") +XOR(gw, "ок?") +End(e_ok) +End(e_no) +Flow(s, t) +Flow(t, gw) +CondFlow(gw, e_ok, "да") +DefaultFlow(gw, e_no) +``` +````markdown +%bpmn1 Процесс заявки + +```bpmn +StartMessage(s, "заявка") +UserTask(t, "Проверить") +XOR(gw, "ок?") +End(e_ok) +End(e_no) +Flow(s, t) +Flow(t, gw) +CondFlow(gw, e_ok, "да") +DefaultFlow(gw, e_no) +``` +```` + +Обычному пользователю jar/Kroki указывать не нужно. Порядок для UML: + +1. Вшитый / скачанный `plantuml.jar` + Java (exe кладёт jar внутрь; иначе качаем в `%LOCALAPPDATA%\md2gost\`) +2. Локальный Kroki (`KROKI_URL` / `--kroki-url`, по умолчанию `http://localhost:8000`) +3. Интернет `https://kroki.io` при `--diagram-fallback remote` (по умолчанию) + +Mermaid всегда идёт через Kroki (шаги 2–3). + +Формат в Word: `--diagram-format png` (по умолчанию; PlantUML рендерится с `--diagram-scale`, по умолчанию 2 — только качество, размер на странице как при 1) или `svg` — вектор через `svgBlip` + PNG-запасной (Word 2016+; LibreOffice покажет растр). + +Широкие схемы/таблицы: в подписи флаг `+landscape` — отдельная альбомная A4-страница, затем снова книжная. + +Свой jar — только если нужен другой файл: `--plantuml-jar` или поле на вкладке «Диаграммы». + +Кэш: `{каталог_md}/.md2gost-cache/` (`*.png`, при svg ещё `*.svg`). ```bash -python -m md2gost report.md -o report.docx --plantuml-jar C:\tools\plantuml.jar +python scripts/fetch_plantuml.py python -m md2gost report.md -o report.docx --diagram-fallback local +python -m md2gost report.md -o report.docx --diagram-format svg ``` Подробности и ИИ-промпт: [`prompts/`](../prompts/). @@ -144,3 +264,5 @@ PDF через LaTeX (XeLaTeX, шаблон МИРЭА): [`md2latex/README.md`]( ## Проверки `--check` печатает замечания по структуре, «рис.», ссылкам во введении, числу/возрасту источников, приложениям и т.д. `--strict` завершает процесс с кодом 1 при ошибках. + +`--check-pages` — пост-проверка полупустых страниц **через Microsoft Word** (Windows + Word + `pip install pywin32`). Все находки помечены как эвристика и **могут быть ложными**; не влияют на `--strict`. Можно вызвать для готового файла: `python -m md2gost report.docx --check-pages`. Макрос Word: [`scripts/check_page_fill.bas`](../scripts/check_page_fill.bas). diff --git a/md2gost/__init__.py b/md2gost/__init__.py index a7f8ba6..527c287 100644 --- a/md2gost/__init__.py +++ b/md2gost/__init__.py @@ -1,8 +1,17 @@ """md2gost — Markdown → DOCX (MIREA TZ / GOST).""" import os +import sys def package_dir() -> str: """Directory of the md2gost package (Template.docx, mml2omml, etc.).""" + if getattr(sys, "frozen", False): + meipass = getattr(sys, "_MEIPASS", None) + if meipass: + bundled = os.path.join(meipass, "md2gost") + if os.path.isfile(os.path.join(bundled, "Template.docx")): + return bundled + if os.path.isfile(os.path.join(meipass, "Template.docx")): + return meipass return os.path.dirname(os.path.abspath(__file__)) diff --git a/md2gost/__main__.py b/md2gost/__main__.py index 7db591b..bb1d358 100644 --- a/md2gost/__main__.py +++ b/md2gost/__main__.py @@ -1,41 +1,44 @@ #!/usr/bin/env python from argparse import ArgumentParser, BooleanOptionalAction -import os -import os.path import sys -from getpass import getuser -from docx import Document - -from .converter import Converter +from .pipeline import ConvertRequest, convert, should_launch_gui from .profiles import ( + DEFAULT_HEADING_NUMBERING, + DEFAULT_TABLE_CONTINUATION, + DEFAULT_LISTING_CONTINUATION, + DEFAULT_TOC_MODE, DOC_TYPES, HEADING_NUMBERING_MODES, - DEFAULT_HEADING_NUMBERING, - TOC_MODES, - DEFAULT_TOC_MODE, TABLE_CONTINUATION_MODES, - DEFAULT_TABLE_CONTINUATION, - get_profile, + LISTING_CONTINUATION_MODES, + TOC_MODES, ) -from .checker import check_markdown, format_report -def main(): +def build_parser() -> ArgumentParser: parser = ArgumentParser( prog="md2gost", description=( "Генерация DOCX-отчётов из Markdown по ТЗ МИРЭА / ГОСТ. " "Типы: coursework/practice/vkr, APID_coursework, PIS_custom. " + "Без файла или с --gui открывается окно. " "FODT: python -m md2fodt …" ), ) - parser.add_argument("filename", help="Путь до исходного markdown файла") + parser.add_argument( + "filename", nargs="?", + help="Путь до .md (или .docx с --check-pages). Без файла открывается GUI", + ) + parser.add_argument( + "--gui", action="store_true", + help="Открыть графический интерфейс (можно сразу передать .md)", + ) parser.add_argument("-o", "--output", help="Путь до сгенерированного .docx") parser.add_argument("-t", "--template", help="Путь до шаблона .docx") parser.add_argument( - "--type", dest="doc_type", choices=DOC_TYPES, default="coursework", - help="Тип: coursework | practice | vkr | PIS_custom | APID_coursework", + "--type", dest="doc_type", choices=DOC_TYPES, default="practice", + help="Тип: practice | coursework | vkr | PIS_custom | APID_coursework (по умолчанию practice)", ) parser.add_argument( "--heading-numbering", @@ -64,19 +67,53 @@ def main(): default=DEFAULT_TABLE_CONTINUATION, help=( "Таблицы длиннее страницы: " - "off/soft — одна таблица, пагинация Word, без авто«Продолжение» (по умолчанию); " + "word — после сохранения Word COM режет по реальной пагинации + «Продолжение…» " + "(по умолчанию; нужны Windows, Word, pywin32); " + "off/soft — одна таблица, пагинация Word, без авто«Продолжение»; " "legacy/caption — режем по оценке высоты и вставляем «Продолжение…» " "(оценка ≠ Word, возможны артефакты). " f"По умолчанию: {DEFAULT_TABLE_CONTINUATION}." ), ) + parser.add_argument( + "--listing-continuation", + choices=LISTING_CONTINUATION_MODES, + default=DEFAULT_LISTING_CONTINUATION, + help=( + "Листинги длиннее страницы: " + "word — после сохранения Word COM + «Продолжение Листинга…» " + "(по умолчанию; нужны Windows, Word, pywin32); " + "off/soft — один блок, пагинация Word, без авто«Продолжение»; " + "legacy/caption — режем по оценке высоты и вставляем «Продолжение Листинга…» " + "(оценка ≠ Word, возможны артефакты). " + f"По умолчанию: {DEFAULT_LISTING_CONTINUATION}." + ), + ) + parser.add_argument( + "--table-repeat-header", + action=BooleanOptionalAction, + default=False, + help=( + "При --table-continuation word: повторять шапку таблицы на каждом фрагменте " + "после разрыва. По умолчанию выкл." + ), + ) parser.add_argument( "--emdash-to-hyphen", action=BooleanOptionalAction, - default=True, + default=False, help=( "Автозамена типографского тире «—» на дефис «-» в тексте и подписях. " - "Включено по умолчанию; отключить: --no-emdash-to-hyphen." + "По умолчанию выключено (тире «—» по методичке); включить: --emdash-to-hyphen." + ), + ) + parser.add_argument( + "--hr-pagebreak", + action=BooleanOptionalAction, + default=False, + help=( + "Строка «---» / «***» / «___» — разрыв страницы Word. " + "По умолчанию такие строки игнорируются; включить: --hr-pagebreak." ), ) parser.add_argument("--title", help="DOCX титульного листа (вставляется перед телом)") @@ -85,6 +122,15 @@ def main(): action="store_true") parser.add_argument("--check-only", help="Только проверка, без генерации документа", action="store_true") + parser.add_argument( + "--check-pages", + help=( + "После конвертации (или для готового .docx) проверить полупустые страницы " + "через Microsoft Word. Эвристика — возможны ложные срабатывания. " + "Нужны Windows, Word и pywin32." + ), + action="store_true", + ) parser.add_argument("--strict", help="Код выхода 1 при ошибках проверки", action="store_true") parser.add_argument("--syntax-highlighting", help="Подсветка синтаксиса в листингах", @@ -103,108 +149,104 @@ def main(): default="remote", help="Если локальный рендер UML недоступен: remote (kroki.io), local (ошибка), off", ) + parser.add_argument( + "--diagram-format", + choices=["png", "svg"], + default="png", + help="Формат схем в Word: png (по умолчанию) или svg (вектор + PNG-запасной, Word 2016+)", + ) + parser.add_argument( + "--diagram-scale", + type=float, + default=2.0, + help="Масштаб рендера PlantUML PNG (качество); размер на странице как при 1. По умолчанию 2", + ) + parser.add_argument( + "--schemes", + dest="schemes_path", + help="Путь к md2gost.schemes.json (иначе рядом с приложением / с .md)", + ) parser.add_argument("--debug", help="Добавляет отладочные данные в документ", action="store_true") + return parser - args = parser.parse_args() - filename, output, template, debug = \ - args.filename, args.output, args.template, args.debug - if args.syntax_highlighting: - os.environ["SYNTAX_HIGHLIGHTING"] = "1" - from .diagram_renderer import configure_diagrams - configure_diagrams( - plantuml_jar=args.plantuml_jar, - kroki_url=args.kroki_url, - fallback=args.diagram_fallback, - ) - - if not filename.endswith(".md"): - print("Error: filename must have md format") - exit(1) - - os.environ["WORKING_DIR"] = os.path.dirname(os.path.abspath(filename)) or "." - - with open(filename, encoding="utf-8") as f: - md_text = f.read() - - if args.check or args.check_only: - issues = check_markdown(md_text, args.doc_type) - print(format_report(issues)) - errors = [i for i in issues if i.severity == "error"] - if args.strict and errors: - sys.exit(1) - if args.check_only: - sys.exit(0 if not errors else (1 if args.strict else 0)) - - if not output: - output = os.path.basename(filename).replace(".md", ".docx") - elif not output.endswith(".docx"): - print("Error: output file must have docx format") - exit(1) - - if not template: - from . import package_dir - template = os.path.join(package_dir(), "Template.docx") - - converter = Converter( - filename, output, template, debug, +def request_from_args(args) -> ConvertRequest: + return ConvertRequest( + filename=args.filename or "", + output=args.output, + template=args.template, doc_type=args.doc_type, heading_numbering=args.heading_numbering, - emdash_to_hyphen=args.emdash_to_hyphen, toc_mode=args.toc, table_continuation=args.table_continuation, + listing_continuation=args.listing_continuation, + table_repeat_header=bool(getattr(args, "table_repeat_header", False)), + emdash_to_hyphen=args.emdash_to_hyphen, + hr_pagebreak=args.hr_pagebreak, + title=args.title, + assignment=args.assignment, + check=args.check, + check_only=args.check_only, + check_pages=bool(getattr(args, "check_pages", False)), + strict=args.strict, + syntax_highlighting=bool(args.syntax_highlighting), + plantuml_jar=args.plantuml_jar, + kroki_url=args.kroki_url, + diagram_fallback=args.diagram_fallback, + diagram_format=args.diagram_format, + diagram_scale=float(args.diagram_scale), + schemes_path=args.schemes_path, + debug=args.debug, + open_when_done=bool(args.debug), ) - converter.convert() - document = converter.document - # Front matter is appended *into* a shell that already has coursework styles. - # Never use title.docx as compose base: python-docx default template has - # Calibri + accent-blue headings and would override ГОСТ стили. - if args.title or args.assignment: +def _argv_needs_console(argv: list[str]) -> bool: + """Frozen windowed exe: attach a console for CLI / --help, not for GUI.""" + args = argv[1:] + if not args: + return False + if "-h" in args or "--help" in args: + return True + if "--gui" in args: + return False + return any(not a.startswith("-") for a in args) + + +def _enable_windows_console() -> None: + if sys.platform != "win32" or not getattr(sys, "frozen", False): + return + try: + import ctypes + kernel32 = ctypes.windll.kernel32 + if not kernel32.AttachConsole(0xFFFFFFFF): + if not kernel32.GetConsoleWindow(): + kernel32.AllocConsole() + sys.stdout = open("CONOUT$", "w", encoding="utf-8", errors="replace") + sys.stderr = open("CONOUT$", "w", encoding="utf-8", errors="replace") try: - from docxcompose.composer import Composer - except ImportError: - print("Error: docxcompose required for --title/--assignment") - sys.exit(3) - from .styles import apply_document_styles + sys.stdin = open("CONIN$", "r", encoding="utf-8", errors="replace") + except OSError: + pass + except Exception: + pass - shell = Document(template) - apply_document_styles(shell, get_profile(args.doc_type).style_preset) - body = shell.element.body - for child in list(body): - if not child.tag.endswith("}sectPr"): - body.remove(child) - composer = Composer(shell) - if args.title: - composer.append(Document(args.title)) - shell.add_page_break() - if args.assignment: - composer.append(Document(args.assignment)) - shell.add_page_break() - composer.append(document) - document = composer.doc - apply_document_styles(document, get_profile(args.doc_type).style_preset) +def main(): + if _argv_needs_console(sys.argv): + _enable_windows_console() + parser = build_parser() + args = parser.parse_args() + req = request_from_args(args) - document.core_properties.author = getuser() - document.core_properties.comments = \ - "Создано при помощи md2gost (ТЗ МИРЭА)" + if should_launch_gui(args.filename, args.gui): + from .gui import run_gui + run_gui(req) + return - document.save(output) - print(f"Generated document: {os.path.abspath(output)}") - - if debug: - import platform - if platform.system() == 'Darwin': - import subprocess - subprocess.call(('open', output)) - elif platform.system() == 'Windows': - os.startfile(output) - else: - import subprocess - subprocess.call(('xdg-open', output)) + result = convert(req) + sys.exit(result.exit_code) if __name__ == "__main__": diff --git a/md2gost/checker/__init__.py b/md2gost/checker/__init__.py index e1b80b3..ce2ea11 100644 --- a/md2gost/checker/__init__.py +++ b/md2gost/checker/__init__.py @@ -258,12 +258,36 @@ def check_bibliography(text: str, profile: DocProfile) -> list[Issue]: n = len(entries) if profile.sectional_biblio: - # Count per ## section roughly - if n < profile.min_sources: + # Count per ## section inside bibliography + sections: list[tuple[str, int]] = [] + current_title: str | None = None + current_count = 0 + for line in block.splitlines(): + hm = re.match(r"^#{2,6}\s+(\*?)(.+)$", line) + if hm: + if current_title is not None: + sections.append((current_title, current_count)) + current_title = hm.group(2).strip() + current_count = 0 + continue + if BIBLIO_LINE_RE.match(line.strip()): + current_count += 1 + if current_title is not None: + sections.append((current_title, current_count)) + + if sections: + for title, cnt in sections: + if cnt < profile.min_sources: + issues.append(Issue( + "biblio.count", "warning", + f"ВКР: в разделе списка «{title}» желательно ≥{profile.min_sources} " + f"источников (сейчас {cnt})", + )) + elif n < profile.min_sources: issues.append(Issue( "biblio.count", "warning", - f"ВКР: в каждом разделе списка желательно ≥{profile.min_sources} источников " - f"(сейчас всего {n})", + f"ВКР: список должен делиться на разделы; в каждом ≥{profile.min_sources} " + f"источников (сейчас всего {n}, разделов нет)", )) else: if n < profile.min_sources: @@ -288,13 +312,10 @@ def check_bibliography(text: str, profile: DocProfile) -> list[Issue]: )) # Citation order vs first appearance - # Exclude intro/conclusion from citation scan for order body_for_cites = text cites = find_citations(body_for_cites) - # Filter cites that appear only in intro/conclusion — still listed keys = [e[0] for e in entries] if cites and keys: - # First N unique cites should match order of keys for simple lists if not profile.sectional_biblio: expected = cites[:len(keys)] if keys != expected and set(keys) == set(expected): @@ -313,16 +334,15 @@ def check_bibliography(text: str, profile: DocProfile) -> list[Issue]: def check_object_refs(text: str) -> list[Issue]: issues = [] - # Captions / images labels = set(re.findall(r"^%(\w+)", text, re.M)) - # image titles with %id labels.update(re.findall(r'!\[[^\]]*\]\([^)]*%(\w+)', text)) refs = set(re.findall(r"@[\wА-Яа-я]+:(\w+)", text)) for lab in labels: - # At least one @?:lab or word reference — soft check - if lab not in refs and f"@{lab}" not in text: - # only warn if label looks intentional - pass + if lab not in refs: + issues.append(Issue( + "ref.unused", "warning", + f"Метка «{lab}» объявлена, но в тексте нет ссылки @…:{lab}", + )) for ref in refs: if ref not in labels: issues.append(Issue( @@ -342,6 +362,66 @@ def check_appendices(text: str) -> list[Issue]: f"Буква «{letter}» не используется для обозначения приложений", _line_of(text, m.start()), )) + + # Multiple lettered appendices should have a list after # *ПРИЛОЖЕНИЯ + apps = list(re.finditer( + r"^#{1,6}\s+\*?Приложение\s+[А-ЯA-ZЁ]\b", + text, re.M | re.I, + )) + # Filter out the section header «ПРИЛОЖЕНИЯ» / bare «ПРИЛОЖЕНИЕ» + lettered = [] + for m in apps: + line = m.group(0) + if re.search(r"(?i)приложение\s+[А-ЯA-ZЁ]", line): + lettered.append(m) + if len(lettered) >= 2: + sec = re.search( + r"^#\s*\*?\s*ПРИЛОЖЕНИЯ?\s*$", + text, re.M | re.I, + ) + if sec: + between = text[sec.end(): lettered[0].start()] + # Expect a plain-text list (not only blank / headings) + has_list = bool(re.search(r"(?im)^\s*[-–—*]|\bприложение\s+[А-ЯA-ZЁ]\b", between)) + has_prose = bool(re.search(r"[А-Яа-яA-Za-z]{3,}", between)) + if not (has_list or has_prose): + issues.append(Issue( + "appendix.toc", "warning", + "При нескольких приложениях после «ПРИЛОЖЕНИЯ» нужен перечень " + "(основной текст) с номерами и названиями", + _line_of(text, sec.start()), + )) + return issues + + +def check_continuation_hints( + text: str, + *, + table_continuation: str = "off", + listing_continuation: str = "off", +) -> list[Issue]: + """Warn that Word cannot auto-insert «Продолжение…» when mode is off/soft.""" + issues: list[Issue] = [] + soft = {"off", "soft"} + if table_continuation in soft and _TABLE_BLOCK_RE.search(text): + issues.append(Issue( + "table.continuation", "warning", + "Word сам не вставит «Продолжение Таблицы N» при переносе. " + "Варианты: --table-continuation word (точный разрыв через Word COM) " + "или caption (оценка высоты ≠ вёрстка Word, возможны артефакты).", + )) + # Long fenced code blocks (likely listings) + long_listing = False + for m in re.finditer(r"^```[^\n]*\n([\s\S]*?)^```", text, re.M): + if m.group(1).count("\n") >= 40: + long_listing = True + break + if listing_continuation in soft and long_listing: + issues.append(Issue( + "listing.continuation", "warning", + "Длинный листинг: Word сам не вставит «Продолжение Листинга N». " + "Варианты: --listing-continuation word или caption.", + )) return issues @@ -404,7 +484,13 @@ def check_table_merge(text: str) -> list[Issue]: return issues -def check_markdown(text: str, doc_type: str = "coursework") -> list[Issue]: +def check_markdown( + text: str, + doc_type: str = "coursework", + *, + table_continuation: str = "off", + listing_continuation: str = "off", +) -> list[Issue]: profile = get_profile(doc_type) issues: list[Issue] = [] issues.extend(check_structure(text, profile)) @@ -417,6 +503,11 @@ def check_markdown(text: str, doc_type: str = "coursework") -> list[Issue]: issues.extend(check_object_refs(text)) issues.extend(check_appendices(text)) issues.extend(check_table_merge(text)) + issues.extend(check_continuation_hints( + text, + table_continuation=table_continuation, + listing_continuation=listing_continuation, + )) return issues diff --git a/md2gost/converter.py b/md2gost/converter.py index 17053ba..548eec1 100644 --- a/md2gost/converter.py +++ b/md2gost/converter.py @@ -15,7 +15,9 @@ from .profiles import ( DEFAULT_TOC_MODE, TOC_MODES, DEFAULT_TABLE_CONTINUATION, + DEFAULT_LISTING_CONTINUATION, TABLE_CONTINUATION_MODES, + LISTING_CONTINUATION_MODES, ) from .label_pass import ( assign_numbers, @@ -26,6 +28,8 @@ from .label_pass import ( from .renderable.heading import Heading from .renderable.toc import ToC from .renderable.table import Table +from .renderable.listing import Listing +from .renderable.diagram import DiagramFigure class Converter: @@ -33,11 +37,13 @@ class Converter: def __init__(self, input_path: str, output_path: str, template_path: str = None, debug: bool = False, - doc_type: str = "coursework", + doc_type: str = "practice", heading_numbering: str = DEFAULT_HEADING_NUMBERING, emdash_to_hyphen: bool = False, toc_mode: str = DEFAULT_TOC_MODE, - table_continuation: str = DEFAULT_TABLE_CONTINUATION): + table_continuation: str = DEFAULT_TABLE_CONTINUATION, + listing_continuation: str = DEFAULT_LISTING_CONTINUATION, + hr_pagebreak: bool = False): if heading_numbering not in HEADING_NUMBERING_MODES: raise ValueError( f"heading_numbering must be one of {HEADING_NUMBERING_MODES}, " @@ -52,12 +58,19 @@ class Converter: f"table_continuation must be one of {TABLE_CONTINUATION_MODES}, " f"got {table_continuation!r}" ) + if listing_continuation not in LISTING_CONTINUATION_MODES: + raise ValueError( + f"listing_continuation must be one of {LISTING_CONTINUATION_MODES}, " + f"got {listing_continuation!r}" + ) self._output_path = output_path self._doc_type = doc_type self._heading_numbering = heading_numbering self._emdash_to_hyphen = emdash_to_hyphen self._toc_mode = toc_mode self._table_continuation = table_continuation + self._listing_continuation = listing_continuation + self._hr_pagebreak = hr_pagebreak self._profile = get_profile(doc_type) self._document: Document = docx.Document(template_path) self._document._body.clear_content() @@ -67,7 +80,7 @@ class Converter: raw = f.read() self._raw_markdown = raw self._preprocessed = preprocess_markdown(raw, emdash_to_hyphen=emdash_to_hyphen) - self.parser = Parser(self._document, self._preprocessed) + self.parser = Parser(self._document, self._preprocessed, hr_pagebreak=hr_pagebreak) def convert(self): renderables = list(self.parser.parse()) @@ -85,6 +98,10 @@ class Converter: r.set_toc_mode(self._toc_mode) elif isinstance(r, Table): r.set_continuation_mode(self._table_continuation) + elif isinstance(r, Listing): + r.set_continuation_mode(self._listing_continuation) + elif isinstance(r, DiagramFigure) and r.listing: + r.listing.set_continuation_mode(self._listing_continuation) formula_refs = find_formula_refs(self._raw_markdown) registry = assign_numbers( diff --git a/md2gost/diagram_includes.py b/md2gost/diagram_includes.py new file mode 100644 index 0000000..7f44609 --- /dev/null +++ b/md2gost/diagram_includes.py @@ -0,0 +1,261 @@ +"""HTTP(S) include cache: index URL → local file, never overwrite existing cache files.""" + +from __future__ import annotations + +import hashlib +import json +import logging +import re +import sys +from pathlib import Path +from urllib.parse import urlparse, urljoin + +import requests + +_log = logging.getLogger(__name__) + +INCLUDE_CACHE_INDEX = "md2gost.include-cache.json" +INCLUDE_CACHE_DIR = "include-cache" +MAX_INCLUDE_DEPTH = 8 + +_HTTP_RE = re.compile(r"^https?://", re.I) +_INCLUDE_LINE_RE = re.compile( + r"^[ \t]*!(?:includeurl|include)[ \t]+(?P[\"']?)(?P[^\s\"']+)(?P=q)", + re.I | re.M, +) + + +def app_dir() -> Path: + """Directory next to the running application (exe or cwd for python -m).""" + if getattr(sys, "frozen", False): + return Path(sys.executable).resolve().parent + return Path.cwd() + + +def include_cache_index_path(base: Path | None = None) -> Path: + return (base or app_dir()) / INCLUDE_CACHE_INDEX + + +def include_cache_files_dir(base: Path | None = None) -> Path: + return (base or app_dir()) / INCLUDE_CACHE_DIR + + +def is_http_url(value: str) -> bool: + return bool(_HTTP_RE.match((value or "").strip())) + + +def _load_index(path: Path) -> dict[str, str]: + if not path.is_file(): + return {} + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + _log.warning("Не удалось прочитать индекс кэша includes: %s", exc) + return {} + if not isinstance(data, dict): + return {} + out: dict[str, str] = {} + for key, val in data.items(): + if isinstance(key, str) and isinstance(val, str) and is_http_url(key): + out[key] = val + return out + + +def _save_index(path: Path, index: dict[str, str]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + json.dumps(index, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8", + ) + + +def _unique_cache_path(url: str, files_dir: Path) -> Path: + """New file path for URL; never reuse/overwrite an existing cache file.""" + files_dir.mkdir(parents=True, exist_ok=True) + parsed = urlparse(url) + base = Path(parsed.path).name or "include.puml" + if not base.lower().endswith((".puml", ".iuml", ".pu", ".txt")): + base = base + ".puml" if "." not in base else base + digest = hashlib.sha256(url.encode("utf-8")).hexdigest()[:10] + stem = Path(base).stem + suffix = Path(base).suffix or ".puml" + # sanitize stem + safe = re.sub(r"[^a-zA-Z0-9._-]+", "_", stem)[:40] or "include" + candidate = files_dir / f"{safe}_{digest}{suffix}" + if not candidate.exists(): + return candidate + n = 1 + while True: + alt = files_dir / f"{safe}_{digest}_{n}{suffix}" + if not alt.exists(): + return alt + n += 1 + + +def _download(url: str, dest: Path) -> None: + dest.parent.mkdir(parents=True, exist_ok=True) + tmp = dest.with_suffix(dest.suffix + ".part") + try: + resp = requests.get(url, timeout=60) + resp.raise_for_status() + tmp.write_bytes(resp.content) + tmp.replace(dest) + except Exception: + tmp.unlink(missing_ok=True) + raise + + +def resolve_url( + url: str, + *, + base: Path | None = None, + depth: int = 0, +) -> Path: + """ + Return local path for URL. Uses index if present and file exists; + otherwise downloads to a *new* file and records the mapping. + Never overwrites an existing cache file for a different URL. + """ + url = url.strip() + if not is_http_url(url): + raise ValueError(f"Не URL: {url}") + if depth > MAX_INCLUDE_DEPTH: + raise RuntimeError(f"Слишком глубокая цепочка includes ({MAX_INCLUDE_DEPTH}): {url}") + + root = base or app_dir() + index_path = include_cache_index_path(root) + files_dir = include_cache_files_dir(root) + index = _load_index(index_path) + + cached = index.get(url) + if cached: + path = Path(cached) + if path.is_file(): + _rewrite_nested_includes(path, parent_url=url, base=root, depth=depth) + return path + _log.warning("Файл кэша пропал (%s), скачаю заново: %s", path, url) + + dest = _unique_cache_path(url, files_dir) + try: + _download(url, dest) + except Exception as exc: + raise RuntimeError( + f"Не удалось скачать include {url}: {exc}. " + "Нет сети и нет кэша — положите файл вручную или проверьте URL." + ) from exc + + index[url] = str(dest.resolve()) + _save_index(index_path, index) + _log.info("Include cached: %s → %s", url, dest) + _rewrite_nested_includes(dest, parent_url=url, base=root, depth=depth) + return dest + + +def _absolute_include_url(ref: str, parent_url: str | None) -> str | None: + """Turn include ref into absolute http(s) URL, or None if local/stdlib.""" + ref = ref.strip() + if is_http_url(ref): + return ref + if ref.startswith("<") and ref.endswith(">"): + return None # PlantUML stdlib + if parent_url and not Path(ref).is_absolute(): + # relative to parent URL directory + return urljoin(parent_url, ref) + return None + + +def _rewrite_nested_includes( + path: Path, *, parent_url: str, base: Path, depth: int, +) -> None: + """Rewrite http(s) / relative !include inside a cached .puml to local paths.""" + try: + text = path.read_text(encoding="utf-8") + except OSError: + return + + changed = False + + def repl(match: re.Match[str]) -> str: + nonlocal changed + ref = match.group("ref") + abs_url = _absolute_include_url(ref, parent_url) + if not abs_url: + return match.group(0) + local = resolve_url(abs_url, base=base, depth=depth + 1) + changed = True + local_s = str(local.resolve()).replace("\\", "/") + return f"!include {local_s}" + + new_text = _INCLUDE_LINE_RE.sub(repl, text) + if changed and new_text != text: + path.write_text(new_text, encoding="utf-8") + + +def resolve_include_ref( + ref: str, + *, + base: Path | None = None, + schemes_dir: Path | None = None, +) -> str: + """ + Resolve include entry to a local path string suitable for !include. + Local paths are resolved relative to schemes_dir / diagrams / cwd. + """ + ref = (ref or "").strip() + if not ref: + raise ValueError("Пустой include") + if is_http_url(ref): + return str(resolve_url(ref, base=base).resolve()).replace("\\", "/") + + path = Path(ref) + if path.is_file(): + return str(path.resolve()).replace("\\", "/") + candidates: list[Path] = [] + if schemes_dir: + candidates.append(schemes_dir / ref) + from . import package_dir + candidates.append(Path(package_dir()) / "diagrams" / ref) + candidates.append(Path.cwd() / ref) + for cand in candidates: + if cand.is_file(): + return str(cand.resolve()).replace("\\", "/") + raise FileNotFoundError(f"Include не найден: {ref}") + + +def rewrite_http_includes_in_source(source: str, *, base: Path | None = None) -> str: + """Replace http(s) !include / !includeurl in prepared source with cached local paths.""" + + def repl(match: re.Match[str]) -> str: + ref = match.group("ref") + if not is_http_url(ref): + return match.group(0) + local = str(resolve_url(ref, base=base).resolve()).replace("\\", "/") + return f"!include {local}" + + return _INCLUDE_LINE_RE.sub(repl, source) + + +def reset_include_cache(base: Path | None = None) -> int: + """ + Delete index and only files listed in it. Returns number of files removed. + Other files in include-cache/ are left untouched. + """ + root = base or app_dir() + index_path = include_cache_index_path(root) + index = _load_index(index_path) + removed = 0 + for _url, path_s in list(index.items()): + path = Path(path_s) + try: + if path.is_file(): + path.unlink() + removed += 1 + except OSError as exc: + _log.warning("Не удалось удалить %s: %s", path, exc) + if index_path.is_file(): + index_path.unlink(missing_ok=True) + return removed + + +def cache_entry_count(base: Path | None = None) -> int: + return len(_load_index(include_cache_index_path(base or app_dir()))) diff --git a/md2gost/diagram_renderer.py b/md2gost/diagram_renderer.py index 6c82140..1d9f811 100644 --- a/md2gost/diagram_renderer.py +++ b/md2gost/diagram_renderer.py @@ -1,12 +1,14 @@ -"""Render UML/BPMN/C4 fenced blocks to PNG (PlantUML jar / Kroki local / remote).""" +"""Render UML / Mermaid / scheme fenced blocks to PNG (+ optional SVG).""" from __future__ import annotations import hashlib import logging import os +import re import shutil import subprocess +import sys import tempfile from dataclasses import dataclass from pathlib import Path @@ -15,13 +17,45 @@ from typing import Literal import requests from . import package_dir +from .diagram_schemes import ( + configure_schemes, + ensure_user_schemes, + is_diagram_lang, + prepare_with_schemes, +) -DIAGRAM_LANGS = frozenset({"uml", "plantuml", "bpmn", "c4"}) +# Back-compat: static set used by older tests; runtime uses is_diagram_lang(). +DIAGRAM_LANGS = frozenset( + { + "uml", + "plantuml", + "c4", + "c4context", + "c4component", + "usecase", + "bpmn", + "mermaid", + "mmd", + } +) FallbackMode = Literal["local", "remote", "off"] +DiagramFormat = Literal["png", "svg"] DEFAULT_KROKI_URL = "http://localhost:8000" REMOTE_KROKI_URL = "https://kroki.io" +# Sharper raster when Word shrinks the figure to page width (display size stays ×1). +DEFAULT_DIAGRAM_SCALE = 2.0 +# Avoid PlantUML clipping large scaled diagrams (default limit is 4096). +PLANTUML_LIMIT_SIZE = "8192" + +# Pinned official jar (GPL). Bundled at exe build; otherwise downloaded once to user cache. +PLANTUML_VERSION = "1.2025.4" +PLANTUML_JAR_URL = ( + f"https://github.com/plantuml/plantuml/releases/download/" + f"v{PLANTUML_VERSION}/plantuml-{PLANTUML_VERSION}.jar" +) + _log = logging.getLogger(__name__) @@ -31,6 +65,17 @@ class DiagramConfig: kroki_url: str | None = None fallback: FallbackMode = "remote" cache_dir: str | None = None + schemes_path: str | None = None + diagram_format: DiagramFormat = "png" + diagram_scale: float = DEFAULT_DIAGRAM_SCALE + + +@dataclass +class DiagramRenderResult: + png_path: str + svg_path: str | None = None + # PlantUML render scale; Image divides display size by this to keep ×1 layout. + pixel_scale: float = 1.0 _CONFIG = DiagramConfig() @@ -41,13 +86,26 @@ def configure_diagrams( kroki_url: str | None = None, fallback: FallbackMode = "remote", cache_dir: str | None = None, + schemes_path: str | None = None, + md_dir: str | None = None, + diagram_format: DiagramFormat = "png", + diagram_scale: float | None = None, ) -> None: global _CONFIG + ensure_user_schemes() + configure_schemes(schemes_path=schemes_path, md_dir=md_dir) + fmt: DiagramFormat = "svg" if diagram_format == "svg" else "png" + scale = DEFAULT_DIAGRAM_SCALE if diagram_scale is None else float(diagram_scale) + if scale < 1: + scale = 1.0 _CONFIG = DiagramConfig( - plantuml_jar=plantuml_jar or os.environ.get("PLANTUML_JAR"), + plantuml_jar=resolve_plantuml_jar(plantuml_jar or None, download=False), kroki_url=kroki_url or os.environ.get("KROKI_URL"), fallback=fallback, cache_dir=cache_dir, + schemes_path=schemes_path, + diagram_format=fmt, + diagram_scale=scale, ) @@ -57,65 +115,175 @@ def diagrams_dir() -> Path: def prepare_source(lang: str, source: str) -> tuple[str, str]: """Return (prepared_source, kroki_diagram_type).""" - lang = (lang or "uml").lower().strip() - text = source.strip() - - if lang == "bpmn": - if "@startbpmn" not in text.lower() and "\n" - ) - body = text - if body.lower().startswith("@startuml"): - lines = body.splitlines() - text = lines[0] + "\n" + includes + "\n".join(lines[1:]) - else: - text = f"@startuml\n{includes}{body}\n@enduml" - elif not text.lower().startswith("@start"): - text = f"@startuml\n{text}\n@enduml" - return text, "plantuml" - - # uml / plantuml - if not text.lower().startswith("@start"): - text = f"@startuml\n{text}\n@enduml" - return text, "plantuml" + return prepare_with_schemes(lang, source) -def _cache_path(source: str, cache_dir: str | None = None) -> Path: +def _cache_root(cache_dir: str | None = None) -> Path: root = cache_dir or _CONFIG.cache_dir if not root: wd = os.environ.get("WORKING_DIR", ".") root = os.path.join(wd, ".md2gost-cache") - Path(root).mkdir(parents=True, exist_ok=True) - digest = hashlib.sha256(source.encode("utf-8")).hexdigest()[:24] - return Path(root) / f"{digest}.png" + path = Path(root) + path.mkdir(parents=True, exist_ok=True) + return path + + +def _cache_digest(source: str) -> str: + return hashlib.sha256(source.encode("utf-8")).hexdigest()[:24] + + +def _cache_paths(source: str, cache_dir: str | None = None) -> tuple[Path, Path]: + root = _cache_root(cache_dir) + digest = _cache_digest(source) + return root / f"{digest}.png", root / f"{digest}.svg" def _java_available() -> bool: return shutil.which("java") is not None -def _render_plantuml_jar(source: str, out_png: Path, jar: str) -> bool: +def vendor_plantuml_path() -> Path: + return Path(package_dir()) / "vendor" / "plantuml.jar" + + +def cached_plantuml_path() -> Path: + if os.name == "nt": + root = Path(os.environ.get("LOCALAPPDATA") or Path.home()) / "md2gost" + else: + root = Path.home() / ".md2gost" + return root / "plantuml.jar" + + +def iter_plantuml_candidates(explicit: str | None = None) -> list[Path]: + paths: list[Path] = [] + if explicit: + paths.append(Path(explicit)) + env = os.environ.get("PLANTUML_JAR") + if env: + paths.append(Path(env)) + paths.append(vendor_plantuml_path()) + if getattr(sys, "frozen", False): + mei = getattr(sys, "_MEIPASS", None) + if mei: + paths.append(Path(mei) / "md2gost" / "vendor" / "plantuml.jar") + paths.append(Path(mei) / "vendor" / "plantuml.jar") + paths.append(Path(sys.executable).resolve().parent / "plantuml.jar") + paths.append(cached_plantuml_path()) + seen: set[str] = set() + out: list[Path] = [] + for path in paths: + key = str(path) + if key in seen: + continue + seen.add(key) + out.append(path) + return out + + +def resolve_plantuml_jar(explicit: str | None = None, *, download: bool = False) -> str | None: + """Find a plantuml.jar: explicit path, env, bundled vendor, user cache; optionally download.""" + for path in iter_plantuml_candidates(explicit): + if path.is_file() and path.stat().st_size > 1000: + return str(path) + if download: + dest = cached_plantuml_path() + if fetch_plantuml_jar(dest): + return str(dest) + return None + + +def fetch_plantuml_jar(dest: Path, url: str = PLANTUML_JAR_URL) -> bool: + """Download official plantuml.jar to dest. Returns True on success.""" + dest.parent.mkdir(parents=True, exist_ok=True) + tmp = dest.with_suffix(".jar.part") + try: + resp = requests.get(url, timeout=120, stream=True) + resp.raise_for_status() + size = 0 + with open(tmp, "wb") as fh: + for chunk in resp.iter_content(chunk_size=65536): + if chunk: + fh.write(chunk) + size += len(chunk) + if size < 1000: + tmp.unlink(missing_ok=True) + return False + tmp.replace(dest) + _log.info("PlantUML jar: %s", dest) + return True + except Exception as exc: + _log.warning("Не удалось скачать plantuml.jar: %s", exc) + tmp.unlink(missing_ok=True) + return False + + +def diagram_engine_status(explicit_jar: str | None = None) -> str: + """Human-readable status for the GUI.""" + java = shutil.which("java") + jar = resolve_plantuml_jar(explicit_jar, download=False) + if java: + java_line = f"Java: есть ({java})" + else: + java_line = ( + "Java: не найдена. Локальный PlantUML не запустится — " + "картинки пойдут через интернет (kroki.io)." + ) + if jar: + jar_line = f"PlantUML: {jar}" + else: + jar_line = ( + "PlantUML: файла нет. Нажмите «Скачать PlantUML» (нужна Java) " + "или оставьте как есть — картинки нарисует интернет (kroki.io)." + ) + return java_line + "\n" + jar_line + + +def _ensure_plantuml_png_scale(source: str, scale: float = DEFAULT_DIAGRAM_SCALE) -> str: + """Inject `scale N` into PlantUML source unless the author already set scale/dpi.""" + if scale <= 1: + return source + if re.search(r"(?im)^\s*scale\b", source): + return source + if re.search(r"(?im)^\s*skinparam\s+dpi\b", source): + return source + lines = source.splitlines() + if not lines: + return source + insert_at = 0 + if lines[0].strip().lower().startswith("@start"): + insert_at = 1 + scale_line = f"scale {scale:g}" + lines.insert(insert_at, scale_line) + return "\n".join(lines) + ("\n" if source.endswith("\n") else "") + + +def _is_png(data: bytes) -> bool: + return data.startswith(b"\x89PNG") + + +def _is_svg(data: bytes) -> bool: + head = data.lstrip()[:200].lower() + return head.startswith(b" bool: if not jar or not os.path.isfile(jar) or not _java_available(): return False with tempfile.TemporaryDirectory() as tmp: src = Path(tmp) / "diagram.puml" src.write_text(source, encoding="utf-8") cmd = [ - "java", "-jar", jar, - "-tpng", + "java", + f"-Dplantuml.include.path={diagrams_dir().resolve()}", + f"-DPLANTUML_LIMIT_SIZE={PLANTUML_LIMIT_SIZE}", + "-jar", jar, + f"-t{fmt}", "-charset", "UTF-8", - f"-I{diagrams_dir()}", "-o", tmp, str(src), ] @@ -126,16 +294,43 @@ def _render_plantuml_jar(source: str, out_png: Path, jar: str) -> bool: except (OSError, subprocess.TimeoutExpired) as e: _log.warning("PlantUML jar failed: %s", e) return False - produced = Path(tmp) / "diagram.png" + produced = Path(tmp) / f"diagram.{fmt}" if proc.returncode != 0 or not produced.is_file(): _log.warning("PlantUML jar error: %s", proc.stderr or proc.stdout) return False - shutil.copyfile(produced, out_png) + data = produced.read_bytes() + if fmt == "png" and not _is_png(data): + return False + if fmt == "svg" and not _is_svg(data): + return False + shutil.copyfile(produced, out_path) return True -def _render_kroki(source: str, diagram_type: str, base_url: str, out_png: Path) -> bool: - url = base_url.rstrip("/") + f"/{diagram_type}/png" +def _render_plantuml_jar( + source: str, + out_png: Path, + jar: str, + *, + out_svg: Path | None = None, +) -> bool: + """Render PNG; optionally also SVG from the same jar.""" + if not _render_plantuml_jar_one(source, out_png, jar, "png"): + return False + if out_svg is not None: + if not _render_plantuml_jar_one(source, out_svg, jar, "svg"): + _log.warning("PlantUML jar: PNG ok, SVG failed") + return True + + +def _render_kroki( + source: str, + diagram_type: str, + base_url: str, + out_path: Path, + fmt: Literal["png", "svg"] = "png", +) -> bool: + url = base_url.rstrip("/") + f"/{diagram_type}/{fmt}" try: resp = requests.post( url, @@ -143,16 +338,38 @@ def _render_kroki(source: str, diagram_type: str, base_url: str, out_png: Path) headers={"Content-Type": "text/plain"}, timeout=60, ) - if resp.status_code != 200 or not resp.content.startswith(b"\x89PNG"): + if resp.status_code != 200: _log.warning("Kroki %s → HTTP %s", url, resp.status_code) return False - out_png.write_bytes(resp.content) + data = resp.content + if fmt == "png" and not _is_png(data): + _log.warning("Kroki %s → not PNG", url) + return False + if fmt == "svg" and not _is_svg(data): + _log.warning("Kroki %s → not SVG", url) + return False + out_path.write_bytes(data) return True except requests.RequestException as e: _log.warning("Kroki request failed (%s): %s", url, e) return False +def _render_kroki_pair( + source: str, + diagram_type: str, + base_url: str, + out_png: Path, + out_svg: Path | None, +) -> bool: + if not _render_kroki(source, diagram_type, base_url, out_png, "png"): + return False + if out_svg is not None: + if not _render_kroki(source, diagram_type, base_url, out_svg, "svg"): + _log.warning("Kroki: PNG ok, SVG failed (%s)", base_url) + return True + + def render_diagram( lang: str, source: str, @@ -161,36 +378,91 @@ def render_diagram( kroki_url: str | None = None, fallback: FallbackMode | None = None, cache_dir: str | None = None, -) -> str: - """Render diagram to PNG path. Raises RuntimeError if all backends fail.""" + diagram_format: DiagramFormat | None = None, + diagram_scale: float | None = None, +) -> DiagramRenderResult: + """Render diagram to PNG (+ optional SVG). Raises RuntimeError if all backends fail.""" + ensure_user_schemes() prepared, kroki_type = prepare_source(lang, source) - out = _cache_path(prepared, cache_dir) - if out.is_file() and out.stat().st_size > 0: - return str(out) + scale = _CONFIG.diagram_scale if diagram_scale is None else float(diagram_scale) + if scale < 1: + scale = 1.0 + # pixel_scale: only when we injected scale (author override → treat as 1 for display). + pixel_scale = 1.0 + if kroki_type == "plantuml" and scale > 1: + before = prepared + prepared = _ensure_plantuml_png_scale(prepared, scale) + if prepared != before: + pixel_scale = scale + out_png, out_svg_path = _cache_paths(prepared, cache_dir) + fmt: DiagramFormat = ( + diagram_format + if diagram_format is not None + else _CONFIG.diagram_format + ) + want_svg = fmt == "svg" + svg_target = out_svg_path if want_svg else None + + if out_png.is_file() and out_png.stat().st_size > 0: + has_svg = out_svg_path.is_file() and out_svg_path.stat().st_size > 0 + if not want_svg or has_svg: + return DiagramRenderResult( + png_path=str(out_png), + svg_path=str(out_svg_path) if (want_svg and has_svg) else None, + pixel_scale=pixel_scale, + ) + # PNG cached but SVG missing in svg mode — fall through to fill SVG. jar = plantuml_jar if plantuml_jar is not None else _CONFIG.plantuml_jar - jar = jar or os.environ.get("PLANTUML_JAR") + jar = resolve_plantuml_jar(jar or None, download=False) local_kroki = kroki_url if kroki_url is not None else _CONFIG.kroki_url local_kroki = local_kroki or os.environ.get("KROKI_URL") or DEFAULT_KROKI_URL mode: FallbackMode = fallback if fallback is not None else _CONFIG.fallback - # 1) PlantUML jar - if _render_plantuml_jar(prepared, out, jar or ""): - return str(out) + def _result() -> DiagramRenderResult: + svg = None + if want_svg and out_svg_path.is_file() and out_svg_path.stat().st_size > 0: + svg = str(out_svg_path) + return DiagramRenderResult( + png_path=str(out_png), svg_path=svg, pixel_scale=pixel_scale, + ) + + # 1) PlantUML jar — only for plantuml type + if kroki_type == "plantuml": + if _render_plantuml_jar(prepared, out_png, jar or "", out_svg=svg_target): + return _result() # 2) Local Kroki - if _render_kroki(prepared, kroki_type, local_kroki, out): - return str(out) + if _render_kroki_pair(prepared, kroki_type, local_kroki, out_png, svg_target): + return _result() + + def _png_fallback_or_raise(message: str) -> DiagramRenderResult: + if out_png.is_file() and out_png.stat().st_size > 0: + _log.warning("Диаграмма: SVG недоступен, вставляю только PNG") + return DiagramRenderResult( + png_path=str(out_png), svg_path=None, pixel_scale=pixel_scale, + ) + raise RuntimeError(message) if mode == "off": - raise RuntimeError( - "Не удалось отрендерить диаграмму локально " - "(задайте --plantuml-jar или KROKI_URL; remote fallback отключён)" + if kroki_type == "plantuml": + return _png_fallback_or_raise( + "Не удалось отрендерить диаграмму локально " + "(задайте --plantuml-jar или KROKI_URL; remote fallback отключён)" + ) + return _png_fallback_or_raise( + "Не удалось отрендерить диаграмму через локальный Kroki " + "(задайте --kroki-url / KROKI_URL; remote fallback отключён)" ) if mode == "local": - raise RuntimeError( - "Локальный рендер диаграммы недоступен " - "(Java+plantuml.jar или локальный Kroki)" + if kroki_type == "plantuml": + return _png_fallback_or_raise( + "Локальный рендер диаграммы недоступен " + "(Java+plantuml.jar или локальный Kroki)" + ) + return _png_fallback_or_raise( + "Локальный Kroki недоступен для диаграммы " + f"(тип {kroki_type}; задайте работающий --kroki-url)" ) # 3) Remote fallback @@ -198,7 +470,26 @@ def render_diagram( "Диаграмма: локальный рендер недоступен, использую remote %s", REMOTE_KROKI_URL, ) - if _render_kroki(prepared, kroki_type, REMOTE_KROKI_URL, out): - return str(out) + if _render_kroki_pair(prepared, kroki_type, REMOTE_KROKI_URL, out_png, svg_target): + return _result() - raise RuntimeError("Не удалось отрендерить диаграмму (PlantUML/Kroki)") + return _png_fallback_or_raise( + "Не удалось отрендерить диаграмму (PlantUML/Kroki)" + ) + + +# Re-export for callers / tests +__all__ = [ + "DIAGRAM_LANGS", + "DiagramRenderResult", + "configure_diagrams", + "diagram_engine_status", + "diagrams_dir", + "fetch_plantuml_jar", + "is_diagram_lang", + "prepare_source", + "render_diagram", + "resolve_plantuml_jar", + "PLANTUML_JAR_URL", + "PLANTUML_VERSION", +] diff --git a/md2gost/diagram_schemes.py b/md2gost/diagram_schemes.py new file mode 100644 index 0000000..f1f2c78 --- /dev/null +++ b/md2gost/diagram_schemes.py @@ -0,0 +1,335 @@ +"""Configurable PlantUML diagram schemes (uml-c4, …) from md2gost.schemes.json.""" + +from __future__ import annotations + +import json +import logging +import re +import shutil +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +from . import package_dir +from .diagram_includes import ( + app_dir, + resolve_include_ref, + rewrite_http_includes_in_source, +) + +_log = logging.getLogger(__name__) + +SCHEMES_FILENAME = "md2gost.schemes.json" +SCHEME_ID_RE = re.compile(r"^[a-z][a-z0-9_]*$") +BUILTIN_DIAGRAM_LANGS = frozenset({"uml", "plantuml"}) +# Rendered via Kroki only (not PlantUML jar / schemes). +KROKI_DIAGRAM_LANGS = frozenset({"mermaid", "mmd"}) +KROKI_TYPE_BY_LANG = {"mermaid": "mermaid", "mmd": "mermaid"} + + +@dataclass +class DiagramScheme: + id: str + title: str = "" + version: str = "" + author: str = "" + docs: str = "" + ai_prompt: str = "" + includes: list[str] = field(default_factory=list) + prefix: str = "" + postfix: str = "" + theme: str = "" + + def to_dict(self) -> dict[str, Any]: + data: dict[str, Any] = { + "title": self.title, + "version": self.version, + "author": self.author, + "docs": self.docs, + "ai-prompt": self.ai_prompt, + "includes": list(self.includes), + "prefix": self.prefix, + "postfix": self.postfix, + } + if self.theme: + data["theme"] = self.theme + return data + + @classmethod + def from_dict(cls, scheme_id: str, data: dict[str, Any]) -> DiagramScheme: + includes = data.get("includes") or [] + if isinstance(includes, str): + includes = [includes] + return cls( + id=scheme_id, + title=str(data.get("title") or scheme_id), + version=str(data.get("version") or ""), + author=str(data.get("author") or ""), + docs=str(data.get("docs") or ""), + ai_prompt=str(data.get("ai-prompt") or data.get("ai_prompt") or ""), + includes=[str(x) for x in includes], + prefix=str(data.get("prefix") or ""), + postfix=str(data.get("postfix") or ""), + theme=str(data.get("theme") or ""), + ) + + +def bundled_schemes_path() -> Path: + return Path(package_dir()) / "diagrams" / "schemes.json" + + +def user_schemes_path(base: Path | None = None) -> Path: + return (base or app_dir()) / SCHEMES_FILENAME + + +def ensure_user_schemes(base: Path | None = None) -> Path: + """ + On first run copy bundled template next to the app. + Never overwrite an existing user file. + """ + dest = user_schemes_path(base) + if dest.is_file(): + return dest + src = bundled_schemes_path() + dest.parent.mkdir(parents=True, exist_ok=True) + if src.is_file(): + shutil.copyfile(src, dest) + _log.info("Создан файл схем: %s", dest) + else: + dest.write_text("{}\n", encoding="utf-8") + _log.warning("Шаблон схем не найден (%s), создан пустой %s", src, dest) + return dest + + +def _load_schemes_file(path: Path) -> dict[str, DiagramScheme]: + if not path.is_file(): + return {} + try: + raw = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + _log.warning("Не удалось прочитать схемы %s: %s", path, exc) + return {} + if not isinstance(raw, dict): + return {} + out: dict[str, DiagramScheme] = {} + for key, val in raw.items(): + sid = str(key).lower().strip() + if not SCHEME_ID_RE.match(sid): + _log.warning("Пропуск схемы с недопустимым id: %s", key) + continue + if not isinstance(val, dict): + continue + out[sid] = DiagramScheme.from_dict(sid, val) + return out + + +def load_schemes_from_path(path: Path) -> dict[str, DiagramScheme]: + return _load_schemes_file(path) + + +def save_schemes(schemes: dict[str, DiagramScheme], path: Path | None = None) -> Path: + dest = path or user_schemes_path() + payload = {sid: scheme.to_dict() for sid, scheme in sorted(schemes.items())} + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_text( + json.dumps(payload, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8", + ) + return dest + + +def scheme_id_from_lang(lang: str) -> str | None: + """Normalize fence lang to scheme id, or None for bare uml/plantuml.""" + lang = (lang or "").lower().strip() + if not lang: + return None + if lang in BUILTIN_DIAGRAM_LANGS: + return None + if lang.startswith("uml-"): + sid = lang[4:] + return sid if SCHEME_ID_RE.match(sid) else None + if SCHEME_ID_RE.match(lang): + return lang + return None + + +def load_merged_schemes( + *, + base: Path | None = None, + extra_path: Path | None = None, + md_dir: Path | None = None, +) -> dict[str, DiagramScheme]: + """ + Merge layers (later wins entirely per id): + 1) bundled template + 2) user file next to app (created on first run) + 3) md2gost.schemes.json next to .md + 4) explicit --schemes path + """ + ensure_user_schemes(base) + merged: dict[str, DiagramScheme] = {} + merged.update(_load_schemes_file(bundled_schemes_path())) + merged.update(_load_schemes_file(user_schemes_path(base))) + if md_dir: + near_md = Path(md_dir) / SCHEMES_FILENAME + if near_md.is_file(): + user = user_schemes_path(base).resolve() + try: + if near_md.resolve() != user: + merged.update(_load_schemes_file(near_md)) + except OSError: + merged.update(_load_schemes_file(near_md)) + if extra_path: + merged.update(_load_schemes_file(Path(extra_path))) + return merged + + +_SCHEMES_CACHE: dict[str, DiagramScheme] | None = None +_SCHEMES_EXTRA: Path | None = None +_SCHEMES_MD_DIR: Path | None = None + + +def configure_schemes( + *, + schemes_path: str | None = None, + md_dir: str | None = None, + base: Path | None = None, +) -> dict[str, DiagramScheme]: + global _SCHEMES_CACHE, _SCHEMES_EXTRA, _SCHEMES_MD_DIR + _SCHEMES_EXTRA = Path(schemes_path) if schemes_path else None + _SCHEMES_MD_DIR = Path(md_dir) if md_dir else None + _SCHEMES_CACHE = load_merged_schemes( + base=base, + extra_path=_SCHEMES_EXTRA, + md_dir=_SCHEMES_MD_DIR, + ) + return _SCHEMES_CACHE + + +def get_schemes() -> dict[str, DiagramScheme]: + global _SCHEMES_CACHE + if _SCHEMES_CACHE is None: + _SCHEMES_CACHE = load_merged_schemes() + return _SCHEMES_CACHE + + +def reload_schemes(**kwargs) -> dict[str, DiagramScheme]: + return configure_schemes(**kwargs) + + +def get_scheme(scheme_id: str) -> DiagramScheme | None: + return get_schemes().get(scheme_id) + + +def is_diagram_lang(lang: str) -> bool: + """True if fence should render as a diagram (not a plain listing).""" + lang = (lang or "").lower().strip() + if lang in BUILTIN_DIAGRAM_LANGS or lang in KROKI_DIAGRAM_LANGS: + return True + if lang.startswith("uml-"): + # Force diagram path so missing scheme becomes an error, not a listing. + return bool(SCHEME_ID_RE.match(lang[4:])) + return lang in get_schemes() + + +def apply_scheme( + scheme: DiagramScheme, + body: str, + *, + base: Path | None = None, +) -> str: + """Wrap body with includes / prefix / postfix / theme; resolve URL includes.""" + text = body.strip() + has_start = text.lower().startswith("@start") + + include_lines: list[str] = [] + schemes_dir = user_schemes_path(base).parent + for ref in scheme.includes: + local = resolve_include_ref(ref, base=base, schemes_dir=schemes_dir) + include_lines.append(f"!include {local}") + + theme_line = "" + if scheme.theme and "!theme" not in text.lower(): + t = scheme.theme.strip() + if not t.lower().startswith("!theme"): + t = f"!theme {t}" + theme_line = t + + prefix = scheme.prefix or "" + postfix = scheme.postfix or "" + + # Avoid double @startuml / @enduml when body already has @start… + if has_start: + prefix_use = _strip_start_end_wrappers(prefix) + postfix_use = _strip_start_end_wrappers(postfix) + head_bits = [ln for ln in include_lines if ln] + if theme_line: + head_bits.append(theme_line) + if prefix_use.strip(): + head_bits.append(prefix_use.strip()) + if head_bits: + lines = text.splitlines() + text = lines[0] + "\n" + "\n".join(head_bits) + "\n" + "\n".join(lines[1:]) + if postfix_use.strip(): + text = text.rstrip() + "\n" + postfix_use.strip() + else: + parts: list[str] = [] + p = prefix.rstrip("\n") if prefix else "" + pf = postfix.lstrip("\n") if postfix else "" + if not p.lstrip().lower().startswith("@start"): + parts.append("@startuml") + if p: + parts.append(p) + parts.extend(include_lines) + if theme_line: + parts.append(theme_line) + parts.append(text) + if pf: + parts.append(pf) + elif "@enduml" not in text.lower(): + parts.append("@enduml") + text = "\n".join(parts) + + text = rewrite_http_includes_in_source(text, base=base) + return text + + +def _strip_start_end_wrappers(chunk: str) -> str: + """Remove leading @start… and trailing @end… lines from prefix/postfix.""" + lines = chunk.splitlines() + while lines and lines[0].strip().lower().startswith("@start"): + lines = lines[1:] + while lines and lines[-1].strip().lower().startswith("@end"): + lines = lines[:-1] + return "\n".join(lines) + + +def prepare_with_schemes(lang: str, source: str, *, base: Path | None = None) -> tuple[str, str]: + """ + Prepare diagram source using schemes. + Returns (prepared_source, kroki_diagram_type). + Raises ValueError if uml- / named scheme is missing. + """ + lang = (lang or "uml").lower().strip() + text = source.strip() + + if lang in KROKI_DIAGRAM_LANGS: + return text, KROKI_TYPE_BY_LANG[lang] + + sid = scheme_id_from_lang(lang) + + if sid is None: + # bare uml / plantuml + if not text.lower().startswith("@start"): + text = f"@startuml\n{text}\n@enduml" + text = rewrite_http_includes_in_source(text, base=base) + return text, "plantuml" + + scheme = get_scheme(sid) + if scheme is None: + raise ValueError( + f"Схема диаграммы «{sid}» не найдена " + f"(оградка ```{lang}). Добавьте её в {SCHEMES_FILENAME}." + ) + prepared = apply_scheme(scheme, text, base=base) + return prepared, "plantuml" diff --git a/md2gost/diagrams/BPMN.puml b/md2gost/diagrams/BPMN.puml new file mode 100644 index 0000000..02b8c5b --- /dev/null +++ b/md2gost/diagrams/BPMN.puml @@ -0,0 +1,1126 @@ +' md2gost BPMN 2.0 macros for PlantUML +' Notation: OMG BPMN 2.0.2 (formal/2013-12-09), Ch. 7 graphical elements, +' Ch. 10 Process (events, activities, gateways), Ch. 9 Collaboration (pools, message flow). +' PlantUML cannot attach boundary events to the activity border or draw a true +' double-circle; thickness + stereotypes approximate start / intermediate / end. +' Gateways use hexagon — in this renderer it reads as a BPMN diamond. +' +' Usage (scheme ```bpmn / ```uml-bpmn wraps @startuml + this include): +' Pool(company, "Компания") { +' Lane(op, "Оператор") { +' Start(s) +' UserTask(t, "Проверить") +' XOR(gw) +' End(e) +' Flow(s, t) +' Flow(t, gw) +' CondFlow(gw, e, "нет") +' } +' } + +!ifndef BPMN_PUML +!define BPMN_PUML + +hide stereotype +skinparam shadowing false +skinparam linetype polyline +skinparam nodesep 28 +skinparam ranksep 42 +skinparam defaultTextAlignment center +skinparam wrapWidth 140 +skinparam ArrowColor #212121 +skinparam Padding 2 + +' --------------------------------------------------------------------------- +' Sprites (event / task markers, BPMN 2.0 Table 7.2–7.4) +' --------------------------------------------------------------------------- + +sprite $bpmnMsg [16x11/16] { +0000000000000000 +0FFFFFFFFFFFFFF0 +0F000000000000F0 +0FF0000000000FF0 +0F0FF000000FF0F0 +0F000FFFFFF000F0 +0F0000FFFF0000F0 +0F000000000000F0 +0F000000000000F0 +0FFFFFFFFFFFFFF0 +0000000000000000 +} + +sprite $bpmnMsgFill [16x11/16] { +0000000000000000 +0FFFFFFFFFFFFFF0 +0FFFFFFFFFFFFFF0 +0FFFFFFFFFFFFFF0 +0FFFFFFFFFFFFFF0 +0FFF0FFFFFF0FFF0 +0FF000FFFF000FF0 +0F00000FF00000F0 +0F000000000000F0 +0FFFFFFFFFFFFFF0 +0000000000000000 +} + +sprite $bpmnTimer [16x10/16] { +000000FFFF000000 +0000FF0000FF0000 +000F000FF000F000 +00F0000FF0000F00 +00F0000FF0000F00 +00F0000FFFF00F00 +00F0000000000F00 +000F00000000F000 +0000FF0000FF0000 +000000FFFF000000 +} + +sprite $bpmnError [16x9/16] { +0000000FF0000000 +000000FF00000000 +00000FFFFF000000 +0000FF0000000000 +000FFFFF00000000 +000000FF00000000 +00000FF000000000 +0000FF0000000000 +000FF00000000000 +} + +sprite $bpmnSignal [16x8/16] { +00000000F0000000 +0000000FFF000000 +000000FF0FF00000 +00000FF000FF0000 +0000FF00000FF000 +000FF0000000FF00 +00FF000000000FF0 +0FFFFFFFFFFFFFFF +} + +sprite $bpmnSignalFill [16x8/16] { +00000000F0000000 +0000000FFF000000 +000000FFFFF00000 +00000FFFFFFF0000 +0000FFFFFFFFF000 +000FFFFFFFFFFF00 +00FFFFFFFFFFFFF0 +0FFFFFFFFFFFFFFF +} + +sprite $bpmnTerminate [16x9/16] { +00000FFFFFF00000 +000FFFFFFFFFF000 +00FFFFFFFFFFFF00 +0FFFFFFFFFFFFFF0 +0FFFFFFFFFFFFFF0 +0FFFFFFFFFFFFFF0 +00FFFFFFFFFFFF00 +000FFFFFFFFFF000 +00000FFFFFF00000 +} + +sprite $bpmnEscalation [16x9/16] { +00000000F0000000 +0000000FFF000000 +000000FF0FF00000 +00000FF000FF0000 +0000FFFFFFFFF000 +000000F000F00000 +000000F000F00000 +000000F000F00000 +000000FFFFF00000 +} + +sprite $bpmnComp [16x7/16] { +00F0000F00000000 +0FF000FF00000000 +FFF00FFF00000000 +FFFF0FFFF0000000 +FFF00FFF00000000 +0FF000FF00000000 +00F0000F00000000 +} + +sprite $bpmnLink [16x7/16] { +0000000000000000 +00000000000F0000 +00000000000FF000 +0FFFFFFFFFFFFF00 +00000000000FF000 +00000000000F0000 +0000000000000000 +} + +sprite $bpmnCond [16x10/16] { +0FFFFFFFFFF00000 +0F00000000FF0000 +0F00000000F0F000 +0F00000000FFF000 +0F00FFFF0000F000 +0F0000000000F000 +0F00FFFF0000F000 +0F0000000000F000 +0F00FFFF0000F000 +0FFFFFFFFFFFF000 +} + +sprite $bpmnMulti [16x8/16] { +00000000F0000000 +000000FF0FF00000 +00000F00000F0000 +0000F0000000F000 +000F000000000F00 +000F000000000F00 +0000F0000000F000 +00000FFFFFFF0000 +} + +sprite $bpmnParMulti [16x9/16] { +00000FFFFFF00000 +000FF000000FF000 +00F0000FF0000F00 +0F00000FF00000F0 +0F00FFFFFFFF00F0 +0F00000FF00000F0 +00F0000FF0000F00 +000FF000000FF000 +00000FFFFFF00000 +} + +sprite $bpmnCancel [16x11/16] { +0FF0000000000FF0 +00FF00000000FF00 +000FF000000FF000 +0000FF0000FF0000 +00000FF00FF00000 +000000FFFF000000 +00000FF00FF00000 +0000FF0000FF0000 +000FF000000FF000 +00FF00000000FF00 +0FF0000000000FF0 +} + +sprite $bpmnUser [16x10/16] { +000000FFFF000000 +00000FFFFFF00000 +00000FFFFFF00000 +000000FFFF000000 +0000000000000000 +0000FFFFFFFF0000 +000FF000000FF000 +000FF000000FF000 +000FF000000FF000 +000FF000000FF000 +} + +sprite $bpmnService [16x9/16] { +000000FFFF000000 +0000FFF00FFF0000 +000FF0FFFF0FF000 +00FF00FFFF00FF00 +00F0000000000F00 +00FF00FFFF00FF00 +000FF0FFFF0FF000 +0000FFF00FFF0000 +000000FFFF000000 +} + +sprite $bpmnScript [16x9/16] { +0FFFFFFFFFF00000 +0F00000000FF0000 +0F00000000F0F000 +0F00FFFF00FFF000 +0F0000000000F000 +0F00FFFF0000F000 +0F0000000000F000 +0F00FFFF0000F000 +0FFFFFFFFFFFF000 +} + +sprite $bpmnManual [16x7/16] { +000000000FF00000 +00000000FFFF0000 +0000FF0FF0FF0000 +000FFFFFFFFFF000 +000FFFFFFFFFF000 +0000FFFFFFFF0000 +00000FFFFFF00000 +} + +sprite $bpmnRule [16x7/16] { +0FFFFFFFFFFFFFF0 +0F000F000F0000F0 +0FFFFFFFFFFFFFF0 +0F000F000F0000F0 +0FFFFFFFFFFFFFF0 +0F000F000F0000F0 +0FFFFFFFFFFFFFF0 +} + +sprite $bpmnPlusBox [12x9/16] { +FFFFFFFFFFFF +F0000000000F +F0000FF0000F +F0000FF0000F +F0FFFFFFFF0F +F0000FF0000F +F0000FF0000F +F0000000000F +FFFFFFFFFFFF +} + +sprite $bpmnLoop [16x7/16] { +0000FFFFFF000000 +000F000000F00000 +00F000000000000F +0F00000000000FF0 +0F000000000F0000 +00F0000000F00000 +000FFFFFF0000000 +} + +sprite $bpmnAdhoc [16x5/16] { +0000000000000000 +0FF0000FFF000FF0 +000FF0F000FF0000 +00000F00000F0000 +0000000000000000 +} + +sprite $bpmnMIPar [13x5/16] { +00F000F000F00 +00F000F000F00 +00F000F000F00 +00F000F000F00 +00F000F000F00 +} + +sprite $bpmnMISeq [13x7/16] { +0000000000000 +0FFFFFFFFFFF0 +0000000000000 +0FFFFFFFFFFF0 +0000000000000 +0FFFFFFFFFFF0 +0000000000000 +} + +' --------------------------------------------------------------------------- +' Skin (colours are conventional, not mandated by the spec) +' --------------------------------------------------------------------------- + +skinparam circle<> { + BackgroundColor #C8E6C9 + BorderColor #2E7D32 + BorderThickness 1.5 + FontSize 10 +} + +skinparam circle<> { + BackgroundColor #C8E6C9 + BorderColor #2E7D32 + BorderThickness 1.5 + BorderStyle dashed + FontSize 10 +} + +skinparam circle<> { + BackgroundColor #FFF3E0 + BorderColor #EF6C00 + BorderThickness 2 + FontSize 10 +} + +skinparam circle<> { + BackgroundColor #FFE0B2 + BorderColor #E65100 + BorderThickness 2 + FontSize 10 +} + +skinparam circle<> { + BackgroundColor #FFCDD2 + BorderColor #B71C1C + BorderThickness 3.5 + FontSize 10 +} + +skinparam circle<> { + BackgroundColor #FFF3E0 + BorderColor #EF6C00 + BorderThickness 2 + FontSize 9 +} + +skinparam circle<> { + BackgroundColor #FFF3E0 + BorderColor #EF6C00 + BorderThickness 2 + BorderStyle dashed + FontSize 9 +} + +skinparam hexagon<> { + BackgroundColor #FFF8E1 + BorderColor #F9A825 + BorderThickness 1.5 + FontSize 12 +} + +skinparam rectangle<> { + RoundCorner 22 + BackgroundColor #FFFFFF + BorderColor #1565C0 + BorderThickness 1.4 + FontSize 11 +} + +skinparam rectangle<> { + RoundCorner 22 + BackgroundColor #FAFAFA + BorderColor #1565C0 + BorderThickness 1.4 + FontSize 11 +} + +skinparam rectangle<> { + RoundCorner 22 + BackgroundColor #FFFFFF + BorderColor #0D47A1 + BorderThickness 3 + FontSize 11 +} + +skinparam rectangle<> { + RoundCorner 22 + BackgroundColor #FAFAFA + BorderColor #0D47A1 + BorderThickness 2.5 + FontSize 11 +} + +skinparam rectangle<> { + RoundCorner 22 + BackgroundColor #FAFAFA + BorderColor #6A1B9A + BorderThickness 1.4 + BorderStyle dashed + FontSize 11 +} + +skinparam rectangle<> { + RoundCorner 0 + BackgroundColor #F5F5F5 + BorderColor #212121 + BorderThickness 1.6 + FontSize 12 + FontStyle bold +} + +skinparam rectangle<> { + RoundCorner 0 + BackgroundColor #FFFFFF + BorderColor #757575 + BorderThickness 1 + FontSize 11 +} + +skinparam rectangle<> { + RoundCorner 0 + BackgroundColor #EEEEEE + BorderColor #212121 + BorderThickness 1.6 + FontSize 12 + FontStyle bold +} + +skinparam rectangle<> { + RoundCorner 8 + BackgroundColor transparent + BorderColor #9E9E9E + BorderThickness 1.2 + BorderStyle dashed + FontSize 10 +} + +skinparam file<> { + BackgroundColor #E3F2FD + BorderColor #1565C0 + FontSize 10 +} + +skinparam database<> { + BackgroundColor #E8F5E9 + BorderColor #2E7D32 + FontSize 10 +} + +skinparam note { + BackgroundColor #FFFDE7 + BorderColor #F9A825 + FontSize 10 +} + +' --------------------------------------------------------------------------- +' Internals +' --------------------------------------------------------------------------- + +!unquoted procedure $bpmnCircle($alias, $stereo, $icon, $label) + !if ($icon == "") + !if ($label == "") + circle " " as $alias <<$stereo>> + !else + circle "$label" as $alias <<$stereo>> + !endif + !else + !if ($label == "") + circle "<$$icon>" as $alias <<$stereo>> + !else + circle "<$$icon>\n$label" as $alias <<$stereo>> + !endif + !endif +!endprocedure + +!unquoted procedure $bpmnGW($alias, $mark, $label) + !if ($label == "") + hexagon "$mark" as $alias <> + !else + hexagon "$mark\n$label" as $alias <> + !endif +!endprocedure + +!unquoted procedure $bpmnRect($alias, $stereo, $icon, $label, $marker="") + !if ($icon == "") + !if ($marker == "") + rectangle "$label" as $alias <<$stereo>> + !else + rectangle "$label\n\n<$$marker>" as $alias <<$stereo>> + !endif + !else + !if ($marker == "") + rectangle "<$$icon> $label" as $alias <<$stereo>> + !else + rectangle "<$$icon> $label\n\n<$$marker>" as $alias <<$stereo>> + !endif + !endif +!endprocedure + +' --------------------------------------------------------------------------- +' Swimlanes — BPMN 2.0 §9.2 Pool / §9.3 Lane +' Pool(...) { ... } Lane(...) { ... } +' --------------------------------------------------------------------------- + +!unquoted procedure Pool($alias, $label) + rectangle "$label" as $alias <> +!endprocedure + +!unquoted procedure Lane($alias, $label) + rectangle "$label" as $alias <> +!endprocedure + +!unquoted procedure BlackBoxPool($alias, $label) + rectangle "$label" as $alias <> +!endprocedure + +!unquoted procedure Participant($alias, $label) + BlackBoxPool($alias, $label) +!endprocedure + +!unquoted procedure Group($alias, $label) + rectangle "$label" as $alias <> +!endprocedure + +' --------------------------------------------------------------------------- +' Start events — §10.5.2 (None, Message, Timer, Conditional, Signal, Error, +' Escalation, Multiple, Parallel Multiple). Error/Escalation only in event sub-process. +' --------------------------------------------------------------------------- + +!unquoted procedure Start($alias, $label="") + $bpmnCircle($alias, "bpmnStart", "", $label) +!endprocedure + +!unquoted procedure StartEvent($alias, $label="") + Start($alias, $label) +!endprocedure + +!unquoted procedure StartMessage($alias, $label="") + $bpmnCircle($alias, "bpmnStart", "bpmnMsg", $label) +!endprocedure + +!unquoted procedure StartTimer($alias, $label="") + $bpmnCircle($alias, "bpmnStart", "bpmnTimer", $label) +!endprocedure + +!unquoted procedure StartCondition($alias, $label="") + $bpmnCircle($alias, "bpmnStart", "bpmnCond", $label) +!endprocedure + +!unquoted procedure StartConditional($alias, $label="") + StartCondition($alias, $label) +!endprocedure + +!unquoted procedure StartSignal($alias, $label="") + $bpmnCircle($alias, "bpmnStart", "bpmnSignal", $label) +!endprocedure + +!unquoted procedure StartError($alias, $label="") + $bpmnCircle($alias, "bpmnStart", "bpmnError", $label) +!endprocedure + +!unquoted procedure StartEscalation($alias, $label="") + $bpmnCircle($alias, "bpmnStart", "bpmnEscalation", $label) +!endprocedure + +!unquoted procedure StartMultiple($alias, $label="") + $bpmnCircle($alias, "bpmnStart", "bpmnMulti", $label) +!endprocedure + +!unquoted procedure StartParallelMultiple($alias, $label="") + $bpmnCircle($alias, "bpmnStart", "bpmnParMulti", $label) +!endprocedure + +!unquoted procedure StartNI($alias, $label="") + $bpmnCircle($alias, "bpmnStartNI", "", $label) +!endprocedure + +!unquoted procedure StartMessageNI($alias, $label="") + $bpmnCircle($alias, "bpmnStartNI", "bpmnMsg", $label) +!endprocedure + +!unquoted procedure StartTimerNI($alias, $label="") + $bpmnCircle($alias, "bpmnStartNI", "bpmnTimer", $label) +!endprocedure + +!unquoted procedure StartSignalNI($alias, $label="") + $bpmnCircle($alias, "bpmnStartNI", "bpmnSignal", $label) +!endprocedure + +' --------------------------------------------------------------------------- +' Intermediate catching / throwing — §10.5.4 +' --------------------------------------------------------------------------- + +!unquoted procedure Intermediate($alias, $label="") + $bpmnCircle($alias, "bpmnInter", "", $label) +!endprocedure + +!unquoted procedure CatchMessage($alias, $label="") + $bpmnCircle($alias, "bpmnInter", "bpmnMsg", $label) +!endprocedure + +!unquoted procedure ThrowMessage($alias, $label="") + $bpmnCircle($alias, "bpmnThrow", "bpmnMsgFill", $label) +!endprocedure + +!unquoted procedure CatchTimer($alias, $label="") + $bpmnCircle($alias, "bpmnInter", "bpmnTimer", $label) +!endprocedure + +!unquoted procedure CatchSignal($alias, $label="") + $bpmnCircle($alias, "bpmnInter", "bpmnSignal", $label) +!endprocedure + +!unquoted procedure ThrowSignal($alias, $label="") + $bpmnCircle($alias, "bpmnThrow", "bpmnSignalFill", $label) +!endprocedure + +!unquoted procedure CatchError($alias, $label="") + $bpmnCircle($alias, "bpmnInter", "bpmnError", $label) +!endprocedure + +!unquoted procedure CatchEscalation($alias, $label="") + $bpmnCircle($alias, "bpmnInter", "bpmnEscalation", $label) +!endprocedure + +!unquoted procedure ThrowEscalation($alias, $label="") + $bpmnCircle($alias, "bpmnThrow", "bpmnEscalation", $label) +!endprocedure + +!unquoted procedure CatchCondition($alias, $label="") + $bpmnCircle($alias, "bpmnInter", "bpmnCond", $label) +!endprocedure + +!unquoted procedure CatchLink($alias, $label="") + $bpmnCircle($alias, "bpmnInter", "bpmnLink", $label) +!endprocedure + +!unquoted procedure ThrowLink($alias, $label="") + $bpmnCircle($alias, "bpmnThrow", "bpmnLink", $label) +!endprocedure + +!unquoted procedure CatchCompensation($alias, $label="") + $bpmnCircle($alias, "bpmnInter", "bpmnComp", $label) +!endprocedure + +!unquoted procedure ThrowCompensation($alias, $label="") + $bpmnCircle($alias, "bpmnThrow", "bpmnComp", $label) +!endprocedure + +!unquoted procedure CatchCancel($alias, $label="") + $bpmnCircle($alias, "bpmnInter", "bpmnCancel", $label) +!endprocedure + +!unquoted procedure CatchMultiple($alias, $label="") + $bpmnCircle($alias, "bpmnInter", "bpmnMulti", $label) +!endprocedure + +!unquoted procedure ThrowMultiple($alias, $label="") + $bpmnCircle($alias, "bpmnThrow", "bpmnMulti", $label) +!endprocedure + +!unquoted procedure CatchParallelMultiple($alias, $label="") + $bpmnCircle($alias, "bpmnInter", "bpmnParMulti", $label) +!endprocedure + +' --------------------------------------------------------------------------- +' Boundary events — interrupting (solid) / non-interrupting (dashed). Attach() +' --------------------------------------------------------------------------- + +!unquoted procedure BoundaryMessage($alias, $label="") + $bpmnCircle($alias, "bpmnBoundary", "bpmnMsg", $label) +!endprocedure + +!unquoted procedure BoundaryTimer($alias, $label="") + $bpmnCircle($alias, "bpmnBoundary", "bpmnTimer", $label) +!endprocedure + +!unquoted procedure BoundaryError($alias, $label="") + $bpmnCircle($alias, "bpmnBoundary", "bpmnError", $label) +!endprocedure + +!unquoted procedure BoundaryEscalation($alias, $label="") + $bpmnCircle($alias, "bpmnBoundary", "bpmnEscalation", $label) +!endprocedure + +!unquoted procedure BoundarySignal($alias, $label="") + $bpmnCircle($alias, "bpmnBoundary", "bpmnSignal", $label) +!endprocedure + +!unquoted procedure BoundaryCancel($alias, $label="") + $bpmnCircle($alias, "bpmnBoundary", "bpmnCancel", $label) +!endprocedure + +!unquoted procedure BoundaryCompensation($alias, $label="") + $bpmnCircle($alias, "bpmnBoundary", "bpmnComp", $label) +!endprocedure + +!unquoted procedure BoundaryCondition($alias, $label="") + $bpmnCircle($alias, "bpmnBoundary", "bpmnCond", $label) +!endprocedure + +!unquoted procedure BoundaryMessageNI($alias, $label="") + $bpmnCircle($alias, "bpmnBoundaryNI", "bpmnMsg", $label) +!endprocedure + +!unquoted procedure BoundaryTimerNI($alias, $label="") + $bpmnCircle($alias, "bpmnBoundaryNI", "bpmnTimer", $label) +!endprocedure + +!unquoted procedure BoundaryEscalationNI($alias, $label="") + $bpmnCircle($alias, "bpmnBoundaryNI", "bpmnEscalation", $label) +!endprocedure + +!unquoted procedure BoundarySignalNI($alias, $label="") + $bpmnCircle($alias, "bpmnBoundaryNI", "bpmnSignal", $label) +!endprocedure + +!unquoted procedure BoundaryConditionNI($alias, $label="") + $bpmnCircle($alias, "bpmnBoundaryNI", "bpmnCond", $label) +!endprocedure + +!unquoted procedure Attach($activity, $event) + $activity -[hidden]down- $event +!endprocedure + +!unquoted procedure Attach_R($activity, $event) + $activity -[hidden]right- $event +!endprocedure + +' --------------------------------------------------------------------------- +' End events — §10.5.3 (None, Message, Error, Escalation, Cancel, Compensation, +' Signal, Multiple, Terminate) +' --------------------------------------------------------------------------- + +!unquoted procedure End($alias, $label="") + $bpmnCircle($alias, "bpmnEnd", "", $label) +!endprocedure + +!unquoted procedure EndEvent($alias, $label="") + End($alias, $label) +!endprocedure + +!unquoted procedure EndMessage($alias, $label="") + $bpmnCircle($alias, "bpmnEnd", "bpmnMsgFill", $label) +!endprocedure + +!unquoted procedure EndError($alias, $label="") + $bpmnCircle($alias, "bpmnEnd", "bpmnError", $label) +!endprocedure + +!unquoted procedure EndEscalation($alias, $label="") + $bpmnCircle($alias, "bpmnEnd", "bpmnEscalation", $label) +!endprocedure + +!unquoted procedure EndCancel($alias, $label="") + $bpmnCircle($alias, "bpmnEnd", "bpmnCancel", $label) +!endprocedure + +!unquoted procedure EndCompensation($alias, $label="") + $bpmnCircle($alias, "bpmnEnd", "bpmnComp", $label) +!endprocedure + +!unquoted procedure EndSignal($alias, $label="") + $bpmnCircle($alias, "bpmnEnd", "bpmnSignalFill", $label) +!endprocedure + +!unquoted procedure EndMultiple($alias, $label="") + $bpmnCircle($alias, "bpmnEnd", "bpmnMulti", $label) +!endprocedure + +!unquoted procedure EndTerminate($alias, $label="") + $bpmnCircle($alias, "bpmnEnd", "bpmnTerminate", $label) +!endprocedure + +!unquoted procedure Terminate($alias, $label="") + EndTerminate($alias, $label) +!endprocedure + +' --------------------------------------------------------------------------- +' Activities — §10.3 Task types + markers (loop, MI, compensation, ad-hoc) +' --------------------------------------------------------------------------- + +!unquoted procedure Task($alias, $label, $type="none", $marker="") + !if ($type == "user") + $bpmnRect($alias, "bpmnTask", "bpmnUser", $label, $marker) + !elseif ($type == "service") + $bpmnRect($alias, "bpmnTask", "bpmnService", $label, $marker) + !elseif ($type == "script") + $bpmnRect($alias, "bpmnTask", "bpmnScript", $label, $marker) + !elseif ($type == "manual") + $bpmnRect($alias, "bpmnTask", "bpmnManual", $label, $marker) + !elseif ($type == "send") + $bpmnRect($alias, "bpmnTask", "bpmnMsgFill", $label, $marker) + !elseif ($type == "receive") + $bpmnRect($alias, "bpmnTask", "bpmnMsg", $label, $marker) + !elseif ($type == "rule") + $bpmnRect($alias, "bpmnTask", "bpmnRule", $label, $marker) + !elseif ($type == "businessrule") + $bpmnRect($alias, "bpmnTask", "bpmnRule", $label, $marker) + !else + $bpmnRect($alias, "bpmnTask", "", $label, $marker) + !endif +!endprocedure + +!unquoted procedure UserTask($alias, $label, $marker="") + Task($alias, $label, "user", $marker) +!endprocedure + +!unquoted procedure ServiceTask($alias, $label, $marker="") + Task($alias, $label, "service", $marker) +!endprocedure + +!unquoted procedure ScriptTask($alias, $label, $marker="") + Task($alias, $label, "script", $marker) +!endprocedure + +!unquoted procedure ManualTask($alias, $label, $marker="") + Task($alias, $label, "manual", $marker) +!endprocedure + +!unquoted procedure SendTask($alias, $label, $marker="") + Task($alias, $label, "send", $marker) +!endprocedure + +!unquoted procedure ReceiveTask($alias, $label, $marker="") + Task($alias, $label, "receive", $marker) +!endprocedure + +!unquoted procedure BusinessRuleTask($alias, $label, $marker="") + Task($alias, $label, "rule", $marker) +!endprocedure + +!unquoted procedure LoopTask($alias, $label) + Task($alias, $label, "none", "bpmnLoop") +!endprocedure + +!unquoted procedure SubProcess($alias, $label) + $bpmnRect($alias, "bpmnSubProcess", "", $label, "bpmnPlusBox") +!endprocedure + +!unquoted procedure SubProcessExpanded($alias, $label) + rectangle "$label" as $alias <> +!endprocedure + +!unquoted procedure CallActivity($alias, $label) + $bpmnRect($alias, "bpmnCall", "", $label, "bpmnPlusBox") +!endprocedure + +!unquoted procedure Transaction($alias, $label) + $bpmnRect($alias, "bpmnTransaction", "", $label, "bpmnPlusBox") +!endprocedure + +!unquoted procedure EventSubProcess($alias, $label) + rectangle "$label" as $alias <> +!endprocedure + +!unquoted procedure AdHocSubProcess($alias, $label) + $bpmnRect($alias, "bpmnSubProcess", "", $label, "bpmnAdhoc") +!endprocedure + +' --------------------------------------------------------------------------- +' Gateways — §10.6 (Exclusive, Parallel, Inclusive, Complex, Event-Based) +' --------------------------------------------------------------------------- + +!unquoted procedure Exclusive($alias, $label="") + $bpmnGW($alias, "×", $label) +!endprocedure + +!unquoted procedure XOR($alias, $label="") + Exclusive($alias, $label) +!endprocedure + +!unquoted procedure Xor($alias, $label="") + Exclusive($alias, $label) +!endprocedure + +!unquoted procedure Gateway($alias, $label="") + Exclusive($alias, $label) +!endprocedure + +!unquoted procedure Parallel($alias, $label="") + $bpmnGW($alias, "+", $label) +!endprocedure + +!unquoted procedure AND($alias, $label="") + Parallel($alias, $label) +!endprocedure + +!unquoted procedure And($alias, $label="") + Parallel($alias, $label) +!endprocedure + +!unquoted procedure Inclusive($alias, $label="") + $bpmnGW($alias, "○", $label) +!endprocedure + +!unquoted procedure OR($alias, $label="") + Inclusive($alias, $label) +!endprocedure + +!unquoted procedure Or($alias, $label="") + Inclusive($alias, $label) +!endprocedure + +!unquoted procedure ComplexGW($alias, $label="") + $bpmnGW($alias, "*", $label) +!endprocedure + +!unquoted procedure Complex($alias, $label="") + ComplexGW($alias, $label) +!endprocedure + +!unquoted procedure EventBased($alias, $label="") + $bpmnGW($alias, "◇", $label) +!endprocedure + +!unquoted procedure EventBasedGateway($alias, $label="") + EventBased($alias, $label) +!endprocedure + +!unquoted procedure ExclusiveEventBased($alias, $label="") + $bpmnGW($alias, "◇", $label) +!endprocedure + +!unquoted procedure ParallelEventBased($alias, $label="") + $bpmnGW($alias, "⊕", $label) +!endprocedure + +' --------------------------------------------------------------------------- +' Connecting objects — §8.4.13 Sequence Flow, Message Flow, Association +' Default = slash; conditional = mini-diamond (label prefixes as stand-in) +' --------------------------------------------------------------------------- + +!unquoted procedure Flow($from, $to, $label="") + !if ($label == "") + $from --> $to + !else + $from --> $to : $label + !endif +!endprocedure + +!unquoted procedure Flow_R($from, $to, $label="") + !if ($label == "") + $from -right-> $to + !else + $from -right-> $to : $label + !endif +!endprocedure + +!unquoted procedure Flow_L($from, $to, $label="") + !if ($label == "") + $from -left-> $to + !else + $from -left-> $to : $label + !endif +!endprocedure + +!unquoted procedure Flow_D($from, $to, $label="") + !if ($label == "") + $from -down-> $to + !else + $from -down-> $to : $label + !endif +!endprocedure + +!unquoted procedure Flow_U($from, $to, $label="") + !if ($label == "") + $from -up-> $to + !else + $from -up-> $to : $label + !endif +!endprocedure + +!unquoted procedure SequenceFlow($from, $to, $label="") + Flow($from, $to, $label) +!endprocedure + +!unquoted procedure CondFlow($from, $to, $cond) + $from --> $to : ◇ $cond +!endprocedure + +!unquoted procedure DefaultFlow($from, $to, $label="") + !if ($label == "") + $from --> $to : ╱ + !else + $from --> $to : ╱ $label + !endif +!endprocedure + +!unquoted procedure MessageFlow($from, $to, $label="") + !if ($label == "") + $from ..> $to + !else + $from ..> $to : $label + !endif +!endprocedure + +!unquoted procedure MessageFlow_R($from, $to, $label="") + !if ($label == "") + $from -[dashed]right-> $to + !else + $from -[dashed]right-> $to : $label + !endif +!endprocedure + +!unquoted procedure Assoc($from, $to, $label="") + !if ($label == "") + $from .. $to + !else + $from .. $to : $label + !endif +!endprocedure + +!unquoted procedure Association($from, $to, $label="") + Assoc($from, $to, $label) +!endprocedure + +!unquoted procedure DataAssoc($from, $to, $label="") + !if ($label == "") + $from ..> $to + !else + $from ..> $to : $label + !endif +!endprocedure + +' --------------------------------------------------------------------------- +' Data / artifacts — §7.1.1.7 Data, Group, Text Annotation +' --------------------------------------------------------------------------- + +!unquoted procedure DataObject($alias, $label) + file "$label" as $alias <> +!endprocedure + +!unquoted procedure DataInput($alias, $label) + file "▷ $label" as $alias <> +!endprocedure + +!unquoted procedure DataOutput($alias, $label) + file "$label ▷" as $alias <> +!endprocedure + +!unquoted procedure Collection($alias, $label) + file "$label\n<$bpmnMIPar>" as $alias <> +!endprocedure + +!unquoted procedure DataStore($alias, $label) + database "$label" as $alias <> +!endprocedure + +!unquoted procedure Annotation($alias, $text) + note "$text" as $alias +!endprocedure + +!unquoted procedure TextAnnotation($alias, $text) + Annotation($alias, $text) +!endprocedure + +' --------------------------------------------------------------------------- +' Layout helpers +' --------------------------------------------------------------------------- + +!unquoted procedure Lay_R($from, $to) + $from -[hidden]right-> $to +!endprocedure + +!unquoted procedure Lay_L($from, $to) + $from -[hidden]left-> $to +!endprocedure + +!unquoted procedure Lay_D($from, $to) + $from -[hidden]down-> $to +!endprocedure + +!unquoted procedure Lay_U($from, $to) + $from -[hidden]up-> $to +!endprocedure + +!procedure BPMN_HORIZONTAL() + left to right direction +!endprocedure + +!procedure BPMN_VERTICAL() + top to bottom direction +!endprocedure + +!procedure BPMN_ORTHO() + skinparam linetype ortho +!endprocedure + +!procedure BPMN_POLYLINE() + skinparam linetype polyline +!endprocedure + +!procedure BPMN_LEGEND() +legend right + BPMN 2.0 + |= Элемент |= Смысл | + | тонкий круг | Start Event | + | двойная толщина | Intermediate | + | жирный круг | End Event | + | ● внутри конца | Terminate | + | скруглённый прямоугольник | Activity / Task | + | ромб × | Exclusive (XOR) | + | ромб + | Parallel (AND) | + | ромб ○ | Inclusive (OR) | + | ромб ◇ | Event-Based | + | сплошная стрелка | Sequence Flow | + | пунктир | Message Flow | + | ╱ | Default Flow | + | ◇ на стрелке | Conditional Flow | +endlegend +!endprocedure + +!endif diff --git a/md2gost/diagrams/schemes.json b/md2gost/diagrams/schemes.json new file mode 100644 index 0000000..e786982 --- /dev/null +++ b/md2gost/diagrams/schemes.json @@ -0,0 +1,60 @@ +{ + "c4": { + "title": "C4 Container", + "version": "1.0.0", + "author": "md2gost", + "docs": "Person(alias, \"Label\", \"descr?\")\nSystem(alias, \"Label\", \"descr?\")\nSystem_Ext(alias, \"Label\")\nContainer(alias, \"Label\", \"techn\", \"descr?\")\nContainerDb(alias, \"Label\", \"techn\")\nRel(from, to, \"label\", \"techn?\")\nRel_R / Rel_L / Rel_U / Rel_D — направление\nLAYOUT_WITH_LEGEND()", + "ai-prompt": "Рисуй C4 Container diagram. В блоке ```uml-c4 (или ```c4) пиши только макросы C4: Person, System, Container, Rel. Без @startuml и без !include — схема добавит сама.", + "includes": [ + "https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml" + ], + "prefix": "@startuml\n", + "postfix": "\nLAYOUT_WITH_LEGEND()\n@enduml" + }, + "c4context": { + "title": "C4 Context", + "version": "1.0.0", + "author": "md2gost", + "docs": "Person(alias, \"Label\")\nSystem(alias, \"Label\", \"descr?\")\nSystem_Ext(alias, \"Label\")\nSystem_Boundary(alias, \"Label\") { … }\nRel(from, to, \"label\")\nLAYOUT_WITH_LEGEND()", + "ai-prompt": "Рисуй C4 System Context. В блоке ```uml-c4context только макросы C4 Context. Без @startuml и !include.", + "includes": [ + "https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml" + ], + "prefix": "@startuml\n", + "postfix": "\nLAYOUT_WITH_LEGEND()\n@enduml" + }, + "c4component": { + "title": "C4 Component", + "version": "1.0.0", + "author": "md2gost", + "docs": "Container_Boundary(alias, \"Label\") { … }\nComponent(alias, \"Label\", \"techn\", \"descr?\")\nComponentDb(alias, \"Label\", \"techn\")\nRel(from, to, \"label\")\nLAYOUT_WITH_LEGEND()", + "ai-prompt": "Рисуй C4 Component diagram. В блоке ```uml-c4component только макросы C4 Component. Без @startuml и !include.", + "includes": [ + "https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml" + ], + "prefix": "@startuml\n", + "postfix": "\nLAYOUT_WITH_LEGEND()\n@enduml" + }, + "usecase": { + "title": "Прецеденты (Use Case)", + "version": "1.0.0", + "author": "md2gost", + "docs": "left to right direction\nactor \"Имя\" as A\nrectangle Система {\n usecase \"Сценарий\" as UC1\n}\nA --> UC1\nUC1 .> UC2 : <>\nUC3 .> UC1 : <>", + "ai-prompt": "Рисуй UML Use Case. В блоке ```uml-usecase (или ```usecase) — actor, usecase, связи. Без @startuml — схема обернёт сама.", + "includes": [], + "prefix": "@startuml\nleft to right direction\nskinparam actorStyle awesome\n", + "postfix": "\n@enduml" + }, + "bpmn": { + "title": "BPMN 2.0", + "version": "1.0.0", + "author": "md2gost", + "docs": "Pool(alias, \"Участник\") { Lane(alias, \"Роль\") { … } }\nStart / StartMessage / StartTimer / StartSignal\nCatchMessage / ThrowMessage / CatchTimer / CatchError\nEnd / EndMessage / EndError / EndTerminate\nTask(alias, \"Имя\") / UserTask / ServiceTask / ScriptTask / ManualTask / SendTask / ReceiveTask / BusinessRuleTask\nSubProcess / SubProcessExpanded { … } / CallActivity\nXOR / AND / OR / EventBased / ComplexGW\nFlow / Flow_R/L/U/D / CondFlow(from, to, \"условие\") / DefaultFlow / MessageFlow\nDataObject / DataStore / Annotation\nBoundaryError + Attach(task, event)\nBPMN_VERTICAL() / BPMN_LEGEND()", + "ai-prompt": "Рисуй BPMN 2.0 (OMG). В блоке ```uml-bpmn (или ```bpmn) только макросы библиотеки md2gost. Без @startuml и без !include.\n\nПравила: Sequence Flow (Flow) только внутри одного пула. Между пулами — MessageFlow. У процесса есть Start и хотя бы один End. Развилка и слияние — шлюзы (XOR/AND/OR), не рисуй ветвление со стрелок задачи без шлюза, если это решение. Подпись условия — на CondFlow/Flow от шлюза. DefaultFlow — ветка «иначе». Не используй activity-syntax (start/:task/;), mxgraph и чистый UML.\n\nМакросы:\nПул: Pool(alias, \"Имя\") { Lane(alias, \"Роль\") { ... } }\nСобытия: Start, StartMessage, StartTimer, StartSignal, StartCondition, Intermediate, CatchMessage, ThrowMessage, CatchTimer, CatchSignal, ThrowSignal, CatchError, End, EndMessage, EndError, EndTerminate, EndSignal\nЗадачи: Task, UserTask, ServiceTask, ScriptTask, ManualTask, SendTask, ReceiveTask, BusinessRuleTask, SubProcess, SubProcessExpanded { ... }, CallActivity\nШлюзы: Exclusive/XOR, Parallel/AND, Inclusive/OR, EventBased, ComplexGW\nПотоки: Flow, Flow_R, Flow_L, Flow_U, Flow_D, CondFlow(from,to,\"условие\"), DefaultFlow, MessageFlow, Assoc, DataAssoc\nДанные: DataObject, DataStore, Annotation\nГраница: BoundaryError/Timer/Message + Attach(task, event)\nМакет: Lay_R/L/U/D, BPMN_VERTICAL(), BPMN_LEGEND()\nПсевдонимы элементов — латиница (s, t1, gw, e_ok). Не называй alias зарезервированными словами end/start/group.", + "includes": [ + "BPMN.puml" + ], + "prefix": "@startuml\nleft to right direction\n", + "postfix": "\n@enduml" + } +} diff --git a/md2gost/dnd.py b/md2gost/dnd.py new file mode 100644 index 0000000..7ce61cb --- /dev/null +++ b/md2gost/dnd.py @@ -0,0 +1,326 @@ +"""File-drop helpers for the md2gost GUI (tkinterdnd2 or Windows WM_DROPFILES).""" + +from __future__ import annotations + +import os +import sys +from collections.abc import Callable +from urllib.parse import unquote, urlparse + +DropCallback = Callable[[list[str]], None] + + +def parse_tkdnd_files(data: str) -> list[str]: + """Parse a TkDND file list (`{C:\\a b.md} C:\\c.md`).""" + files: list[str] = [] + text = (data or "").strip() + i = 0 + n = len(text) + while i < n: + if text[i].isspace(): + i += 1 + continue + if text[i] == "{": + j = text.find("}", i + 1) + if j < 0: + files.append(text[i + 1 :]) + break + files.append(text[i + 1 : j]) + i = j + 1 + continue + j = i + while j < n and not text[j].isspace(): + j += 1 + files.append(text[i:j]) + i = j + return files + + +def normalize_drop_paths(items: list[str] | str) -> list[str]: + if isinstance(items, str): + items = parse_tkdnd_files(items) + out: list[str] = [] + for raw in items: + if isinstance(raw, bytes): + try: + raw = raw.decode("utf-8") + except UnicodeDecodeError: + raw = raw.decode(sys.getfilesystemencoding() or "utf-8", errors="replace") + path = raw.strip().strip('"') + if not path: + continue + if path.lower().startswith("file:"): + parsed = urlparse(path) + path = unquote(parsed.path) + if sys.platform == "win32" and path.startswith("/") and len(path) > 3 and path[2] == ":": + path = path[1:] + out.append(os.path.normpath(path)) + return out + + +def first_markdown(paths: list[str]) -> str | None: + for path in paths: + if path.lower().endswith(".md") and os.path.isfile(path): + return path + return None + + +def enable_file_drop(widget, callback: DropCallback) -> str: + """Enable dropping files onto widget. Returns backend name: tkdnd | win32 | none.""" + def _deliver(items: list[str] | str) -> None: + callback(normalize_drop_paths(items)) + + if _try_tkdnd(widget, _deliver): + return "tkdnd" + if sys.platform == "win32" and _WinDropHook.attach(widget, _deliver): + return "win32" + return "none" + + +def _try_tkdnd(widget, deliver: DropCallback) -> bool: + try: + from tkinterdnd2 import DND_FILES + except ImportError: + return False + register = getattr(widget, "drop_target_register", None) + bind = getattr(widget, "dnd_bind", None) + if register is None or bind is None: + root = widget.winfo_toplevel() + register = getattr(root, "drop_target_register", None) + bind = getattr(root, "dnd_bind", None) + target = root + else: + target = widget + if register is None or bind is None: + return False + try: + register(DND_FILES) + bind("<>", lambda event: deliver(getattr(event, "data", "") or "")) + bind("<>", lambda event: widget.event_generate("<>")) + bind("<>", lambda event: widget.event_generate("<>")) + except Exception: + return False + return True + + +class _WinDropHook: + """Subclass a Win32 HWND and accept WM_DROPFILES. Keep a strong ref on the widget.""" + + _hooks: list[_WinDropHook] = [] + + def __init__(self, widget, callback: DropCallback): + self.widget = widget + self.callback = callback + self._pending: list[str] = [] + self._old_proc = None + self._wndproc = None + self._hwnd = 0 + self._alive = True + + @classmethod + def attach(cls, widget, callback: DropCallback) -> bool: + try: + toplevel = widget.winfo_toplevel() + except Exception: + toplevel = widget + if getattr(toplevel, "_md2gost_dnd_hooked", False): + return True + + hook = cls(widget, callback) + + def start(_event=None): + if getattr(toplevel, "_md2gost_dnd_hooked", False): + return + if hook._install(): + toplevel._md2gost_dnd_hooked = True + toplevel._md2gost_dnd_hook = hook + cls._hooks.append(hook) + + widget.bind("", start, add="+") + widget.bind("", lambda e: hook._detach(), add="+") + try: + if widget.winfo_ismapped(): + start() + else: + widget.after_idle(start) + except Exception: + widget.after(200, start) + return True + + def _install(self) -> bool: + try: + api = _win32_drop_api() + except Exception: + return False + + hwnd = _toplevel_hwnd(self.widget) + if not hwnd: + return False + + WM_DROPFILES = 0x0233 + GWLP_WNDPROC = -4 + WS_EX_ACCEPTFILES = 0x00000010 + GWL_EXSTYLE = -20 + + def wndproc(hw, msg, wp, lp): + if msg == WM_DROPFILES: + try: + hdrop = int(wp) if wp is not None else 0 + if hdrop: + self._pending.extend(_query_drop_files(hdrop, api=api)) + api.DragFinish(hdrop) + except Exception: + pass + return 0 + if self._old_proc: + return api.CallWindowProc(self._old_proc, hw, msg, wp, lp) + return api.DefWindowProc(hw, msg, wp, lp) + + self._wndproc = api.WNDPROC(wndproc) + try: + ex = api.GetWindowLong(hwnd, GWL_EXSTYLE) or 0 + api.SetWindowLong(hwnd, GWL_EXSTYLE, int(ex) | WS_EX_ACCEPTFILES) + api.DragAcceptFiles(hwnd, True) + self._old_proc = api.GetWindowLongPtr(hwnd, GWLP_WNDPROC) + api.SetWindowLongPtr(hwnd, GWLP_WNDPROC, api.as_ptr(self._wndproc)) + except Exception: + return False + + self._hwnd = hwnd + self._poll() + return True + + def _poll(self) -> None: + if not self._alive: + return + if self._pending: + files = self._pending[:] + self._pending.clear() + try: + self.callback(files) + except Exception: + pass + try: + self.widget.after(120, self._poll) + except Exception: + self._alive = False + + def _detach(self) -> None: + self._alive = False + if not self._hwnd or self._old_proc is None: + return + try: + api = _win32_drop_api() + api.SetWindowLongPtr(self._hwnd, -4, self._old_proc) + except Exception: + pass + self._old_proc = None + + +class _Win32DropApi: + def __init__(self): + import ctypes + from ctypes import wintypes + + self._ctypes = ctypes + is64 = ctypes.sizeof(ctypes.c_void_p) == 8 + # wintypes.WPARAM/LPARAM historically were 32-bit; force pointer width. + WPARAM = ctypes.c_uint64 if is64 else ctypes.c_uint + LPARAM = ctypes.c_int64 if is64 else ctypes.c_long + HWND = ctypes.c_void_p + LRESULT = ctypes.c_int64 if is64 else ctypes.c_long + + user32 = ctypes.WinDLL("user32", use_last_error=True) + shell32 = ctypes.WinDLL("shell32", use_last_error=True) + + get_ptr = user32.GetWindowLongPtrW if is64 else user32.GetWindowLongW + set_ptr = user32.SetWindowLongPtrW if is64 else user32.SetWindowLongW + get_ptr.argtypes = [HWND, ctypes.c_int] + get_ptr.restype = ctypes.c_void_p + set_ptr.argtypes = [HWND, ctypes.c_int, ctypes.c_void_p] + set_ptr.restype = ctypes.c_void_p + + # GWL_EXSTYLE is a 32-bit style mask; Get/SetWindowLongW is enough. + get_long = user32.GetWindowLongW + set_long = user32.SetWindowLongW + get_long.argtypes = [HWND, ctypes.c_int] + get_long.restype = ctypes.c_long + set_long.argtypes = [HWND, ctypes.c_int, ctypes.c_long] + set_long.restype = ctypes.c_long + + call_proc = user32.CallWindowProcW + call_proc.argtypes = [ctypes.c_void_p, HWND, wintypes.UINT, WPARAM, LPARAM] + call_proc.restype = LRESULT + + def_proc = user32.DefWindowProcW + def_proc.argtypes = [HWND, wintypes.UINT, WPARAM, LPARAM] + def_proc.restype = LRESULT + + accept = shell32.DragAcceptFiles + accept.argtypes = [HWND, wintypes.BOOL] + accept.restype = None + + finish = shell32.DragFinish + finish.argtypes = [ctypes.c_void_p] + finish.restype = None + + query = shell32.DragQueryFileW + query.argtypes = [ctypes.c_void_p, wintypes.UINT, ctypes.c_wchar_p, wintypes.UINT] + query.restype = wintypes.UINT + + self.shell32 = shell32 + self.WNDPROC = ctypes.WINFUNCTYPE(LRESULT, HWND, wintypes.UINT, WPARAM, LPARAM) + self.GetWindowLongPtr = get_ptr + self.SetWindowLongPtr = set_ptr + self.GetWindowLong = get_long + self.SetWindowLong = set_long + self.CallWindowProc = call_proc + self.DefWindowProc = def_proc + self.DragAcceptFiles = accept + self.DragFinish = finish + self.DragQueryFileW = query + + def as_ptr(self, wndproc): + return self._ctypes.cast(wndproc, self._ctypes.c_void_p).value + + +_WIN32_DROP_API = None + + +def _win32_drop_api() -> _Win32DropApi: + global _WIN32_DROP_API + if _WIN32_DROP_API is None: + _WIN32_DROP_API = _Win32DropApi() + return _WIN32_DROP_API + + +def _toplevel_hwnd(widget) -> int: + try: + import ctypes + hwnd = int(widget.winfo_id()) + GA_ROOT = 2 + root = ctypes.windll.user32.GetAncestor(hwnd, GA_ROOT) + if root: + return int(root) + parent = ctypes.windll.user32.GetParent(hwnd) + return int(parent or hwnd) + except Exception: + try: + return int(widget.winfo_id()) + except Exception: + return 0 + + +def _query_drop_files(hdrop: int, api: _Win32DropApi | None = None) -> list[str]: + import ctypes + + query = (api or _win32_drop_api()).DragQueryFileW + count = query(hdrop, 0xFFFFFFFF, None, 0) + files: list[str] = [] + for i in range(count): + length = query(hdrop, i, None, 0) + buf = ctypes.create_unicode_buffer(length + 1) + query(hdrop, i, buf, length + 1) + if buf.value: + files.append(buf.value) + return files diff --git a/md2gost/docx_elements.py b/md2gost/docx_elements.py index f580403..4751610 100644 --- a/md2gost/docx_elements.py +++ b/md2gost/docx_elements.py @@ -24,6 +24,7 @@ __all__ = [ "create_table_row", "create_table_cell", "apply_cell_merge", + "set_table_box_borders", ] @@ -117,3 +118,27 @@ def apply_cell_merge(cell: _Cell, v_merge: str | None = None, grid_span: int | N tcPr.append(create_element("w:vMerge")) if grid_span and grid_span > 1: tcPr.append(create_element("w:gridSpan", {"w:val": str(grid_span)})) + + +def set_table_box_borders(table: Table, *, sz: str = "4", color: str = "000000") -> None: + """Outer frame only — no inside H/V lines (listings look like text in a box).""" + tbl = table._tbl + tblPr = tbl.tblPr + if tblPr is None: + tblPr = create_element("w:tblPr") + tbl.insert(0, tblPr) + for el in list(tblPr.findall(qn("w:tblBorders"))): + tblPr.remove(el) + solid = {"w:val": "single", "w:sz": sz, "w:space": "0", "w:color": color} + none = {"w:val": "nil"} + borders = create_element("w:tblBorders") + for edge, attrs in ( + ("w:top", solid), + ("w:left", solid), + ("w:bottom", solid), + ("w:right", solid), + ("w:insideH", none), + ("w:insideV", none), + ): + borders.append(create_element(edge, attrs)) + tblPr.append(borders) \ No newline at end of file diff --git a/md2gost/docx_svg.py b/md2gost/docx_svg.py new file mode 100644 index 0000000..864940e --- /dev/null +++ b/md2gost/docx_svg.py @@ -0,0 +1,55 @@ +"""Attach SVG as Word svgBlip alongside a PNG InlineShape (Office 2016+).""" + +from __future__ import annotations + +from pathlib import Path + +from docx.opc.constants import RELATIONSHIP_TYPE as RT +from docx.opc.part import Part +from docx.oxml import OxmlElement +from docx.oxml.ns import qn +from lxml import etree + +SVG_CONTENT_TYPE = "image/svg+xml" +SVG_BLIP_URI = "{96DAC541-7B7A-43C6-8E14-B03AE682B59D}" +ASVG_NS = "http://schemas.microsoft.com/office/drawing/2016/SVG/main" +ASVG_SVG_BLIP = f"{{{ASVG_NS}}}svgBlip" + + +def attach_svg_blip(run, inline_shape, svg_path: str | Path) -> bool: + """ + After run.add_picture(png), add an SVG part and asvg:svgBlip on the PNG blip. + Word 2016+ uses the vector; older apps keep the PNG. + Returns True if the SVG was attached. + """ + path = Path(svg_path) + if not path.is_file() or path.stat().st_size <= 0: + return False + data = path.read_bytes() + head = data.lstrip()[:200].lower() + if not (head.startswith(b" tuple[str | None, bool, bool]: + """Return (clean_text, with_listing, landscape) from caption / image-title tail.""" + text = (raw or "").strip() + with_listing = bool(_LISTING_FLAG_RE.search(text)) + landscape = bool(_LANDSCAPE_FLAG_RE.search(text)) + if with_listing: + text = _LISTING_FLAG_RE.sub(" ", text) + if landscape: + text = _LANDSCAPE_FLAG_RE.sub(" ", text) + text = " ".join(text.split()).strip() + return (text or None), with_listing, landscape class Caption(BlockElement): """Represents caption element - Syntax: %label Caption text [+listing] + Syntax: %label Caption text [+listing] [+landscape] """ + # Interrupt an open paragraph so «текст:\\n%id …» still becomes a Caption + # (otherwise marko treats the % line as paragraph continuation / plain text). + breaks_paragraph = True + priority = 6 pattern = r"\%(\w+)( (.+))?" def __init__(self, match: Match[str]): self.unique_name = match.group(1) raw = (match.group(3) or "").strip() - self.with_listing = bool(_LISTING_FLAG_RE.search(raw)) - if self.with_listing: - raw = _LISTING_FLAG_RE.sub(" ", raw).strip() - self.text = raw or None + self.text, self.with_listing, self.landscape = strip_caption_flags(raw) @classmethod def match(cls, source: Source) -> Match[str] | None: diff --git a/md2gost/extended_markdown/image.py b/md2gost/extended_markdown/image.py index fe0280e..2450318 100644 --- a/md2gost/extended_markdown/image.py +++ b/md2gost/extended_markdown/image.py @@ -2,6 +2,8 @@ import re from marko.inline import Image as Image_ +from .caption import strip_caption_flags + class Image(Image_): override = True @@ -9,7 +11,11 @@ class Image(Image_): super().__init__(match) self.unique_name = None + self.with_listing = False + self.landscape = False if self.title and (m := re.match(r"\%(\w+)( (.+))?", self.title)): self.unique_name = m.group(1) - self.title = (m.group(3) or "").strip() or None + self.title, self.with_listing, self.landscape = strip_caption_flags( + (m.group(3) or "").strip() + ) diff --git a/md2gost/gui.py b/md2gost/gui.py new file mode 100644 index 0000000..7504176 --- /dev/null +++ b/md2gost/gui.py @@ -0,0 +1,1260 @@ +"""Tkinter GUI for md2gost: drop a .md, pick options, convert.""" + +from __future__ import annotations + +import os +import sys +import threading +import tkinter as tk +from tkinter import filedialog, messagebox, ttk + +from .diagram_includes import cache_entry_count, reset_include_cache +from .diagram_renderer import diagram_engine_status +from .diagram_schemes import ( + DiagramScheme, + SCHEME_ID_RE, + ensure_user_schemes, + load_schemes_from_path, + reload_schemes, + save_schemes, + user_schemes_path, +) +from .dnd import enable_file_drop, first_markdown +from .help_content import SCHEMES_HELP, USAGE_HELP, load_prompt_catalog +from .pipeline import ConvertRequest, convert, default_output_path +from .profiles import ( + DEFAULT_HEADING_NUMBERING, + DEFAULT_TABLE_CONTINUATION, + DEFAULT_LISTING_CONTINUATION, + DEFAULT_TOC_MODE, + DOC_TYPES, + HEADING_NUMBERING_MODES, + TABLE_CONTINUATION_MODES, + LISTING_CONTINUATION_MODES, + TOC_MODES, +) + +DOC_TYPE_LABELS = { + "coursework": "Курсовая (ГОСТ / МИРЭА)", + "practice": "Практика", + "vkr": "ВКР", + "PIS_custom": "Отчёт по практическим (ПИС)", + "APID_coursework": "Курсовая АПИД", +} +HEADING_LABELS = { + "manual": "Из markdown (цифры уже в заголовках)", + "auto": "Автонумерация Word", +} +TOC_LABELS = { + "native": "Поле Word (обновить при открытии)", + "manual": "Собрать в md2gost", +} +TABLE_LABELS = { + "off": "Не резать", + "soft": "Как «не резать»", + "word": "Через Word после сборки (по умолчанию)", + "legacy": "Резать + «Продолжение»", + "caption": "Резать + разрыв страницы + «Продолжение»", +} +FALLBACK_LABELS = { + "remote": "Авто: локально, иначе интернет (kroki.io)", + "local": "Только этот компьютер (нужна Java)", + "off": "Не рисовать диаграммы", +} +FORMAT_LABELS = { + "png": "PNG (растр, совместимо везде)", + "svg": "SVG + PNG (вектор в Word 2016+)", +} + +NAVY = "#1e3a5f" +NAVY_DIM = "#4a6583" +PAPER = "#f6f3ec" +ZONE = "#e8eef5" +ZONE_OK = "#dceee3" +INK = "#243044" +MUTED = "#5c6b7a" +TIP_BG = "#ffffe1" + + +def _make_root() -> tuple[tk.Tk, str]: + try: + from tkinterdnd2 import TkinterDnD + return TkinterDnD.Tk(), "tkdnd" + except ImportError: + return tk.Tk(), "tk" + + +def _combo_values(mapping: dict[str, str], keys: tuple[str, ...]) -> list[str]: + return [f"{key} — {mapping.get(key, key)}" for key in keys] + + +def _combo_key(display: str, allowed: tuple[str, ...], default: str) -> str: + key = (display or "").split(" — ", 1)[0].strip() + return key if key in allowed else default + + +def _display(mapping: dict[str, str], key: str) -> str: + return f"{key} — {mapping.get(key, key)}" + + +class _Tooltip: + """Simple balloon tip on hover (Tk has no built-in tooltip).""" + + def __init__(self, widget, text: str, delay_ms: int = 400): + self.widget = widget + self.text = text + self.delay_ms = delay_ms + self._after_id: str | None = None + self._tip: tk.Toplevel | None = None + widget.bind("", self._on_enter, add="+") + widget.bind("", self._on_leave, add="+") + widget.bind("", self._on_leave, add="+") + + def _on_enter(self, _event=None) -> None: + self._cancel() + self._after_id = self.widget.after(self.delay_ms, self._show) + + def _on_leave(self, _event=None) -> None: + self._cancel() + self._hide() + + def _cancel(self) -> None: + if self._after_id is not None: + try: + self.widget.after_cancel(self._after_id) + except tk.TclError: + pass + self._after_id = None + + def _show(self) -> None: + self._after_id = None + if self._tip is not None or not self.text: + return + try: + x = self.widget.winfo_rootx() + 16 + y = self.widget.winfo_rooty() + self.widget.winfo_height() + 4 + except tk.TclError: + return + tip = tk.Toplevel(self.widget) + tip.wm_overrideredirect(True) + tip.wm_geometry(f"+{x}+{y}") + try: + tip.attributes("-topmost", True) + except tk.TclError: + pass + label = tk.Label( + tip, + text=self.text, + justify=tk.LEFT, + background=TIP_BG, + foreground=INK, + relief=tk.SOLID, + borderwidth=1, + padx=6, + pady=4, + font=("Segoe UI", 9) if os.name == "nt" else ("Helvetica", 9), + wraplength=320, + ) + label.pack() + self._tip = tip + + def _hide(self) -> None: + if self._tip is not None: + try: + self._tip.destroy() + except tk.TclError: + pass + self._tip = None + + +def _tip(widget, text: str) -> None: + if text: + _Tooltip(widget, text) + + +class Md2GostApp: + def __init__(self, initial: ConvertRequest | None = None): + self.req = initial or ConvertRequest() + self.root, _ = _make_root() + self.root.title("md2gost — Markdown → DOCX") + self.root.minsize(680, 560) + self.root.geometry("780x700") + self.root.configure(bg=PAPER) + + self._busy = False + self._auto_output = True + self._dnd_backend = "none" + self._last_md = "" + self._schemes: dict[str, DiagramScheme] = {} + self._scheme_ids: list[str] = [] + self._scheme_selecting = False + self._debug_next = False + + self._win_files: tk.Toplevel | None = None + self._win_diagrams: tk.Toplevel | None = None + self._win_schemes: tk.Toplevel | None = None + self._win_help: dict[str, tk.Toplevel] = {} + + ensure_user_schemes() + self._build_style() + self._init_vars() + self._build_menu() + self._build() + self._apply_request(self.req) + self.root.after(300, self._setup_dnd) + + if self.req.filename: + self._set_markdown(self.req.filename, from_user_output=bool(self.req.output)) + + def _build_style(self) -> None: + style = ttk.Style(self.root) + try: + style.theme_use("vista" if os.name == "nt" else "clam") + except tk.TclError: + pass + font = ("Segoe UI", 10) if os.name == "nt" else ("Helvetica", 10) + font_title = ("Segoe UI", 16, "bold") if os.name == "nt" else ("Helvetica", 16, "bold") + style.configure(".", background=PAPER, foreground=INK, font=font) + style.configure("TFrame", background=PAPER) + style.configure("TLabelframe", background=PAPER, foreground=NAVY) + style.configure("TLabelframe.Label", background=PAPER, foreground=NAVY, font=font) + style.configure("TLabel", background=PAPER, foreground=INK, font=font) + style.configure("Title.TLabel", font=font_title, foreground=NAVY, background=PAPER) + style.configure("Hint.TLabel", foreground=MUTED, background=PAPER) + style.configure("TCheckbutton", background=PAPER, font=font) + style.configure("TButton", font=font, padding=(10, 4)) + style.configure("TEntry", padding=3) + style.configure("TCombobox", padding=3) + + def _init_vars(self) -> None: + self.output_var = tk.StringVar() + self.type_var = tk.StringVar() + self.heading_var = tk.StringVar() + self.toc_var = tk.StringVar() + self.table_var = tk.StringVar() + self.listing_var = tk.StringVar() + self.emdash_var = tk.BooleanVar(value=False) + self.hr_pagebreak_var = tk.BooleanVar(value=False) + self.open_var = tk.BooleanVar(value=True) + self.check_var = tk.BooleanVar(value=False) + self.check_only_var = tk.BooleanVar(value=False) + self.strict_var = tk.BooleanVar(value=False) + self.check_pages_var = tk.BooleanVar(value=False) + self.table_repeat_header_var = tk.BooleanVar(value=False) + self.template_var = tk.StringVar() + self.title_var = tk.StringVar() + self.assignment_var = tk.StringVar() + self.fallback_var = tk.StringVar() + self.format_var = tk.StringVar() + self.plantuml_var = tk.StringVar() + self.kroki_var = tk.StringVar() + self.scale_var = tk.StringVar(value="2") + self.highlight_var = tk.BooleanVar(value=False) + self.debug_var = tk.BooleanVar(value=False) + self.output_var.trace_add("write", self._on_output_edited) + + def _build_menu(self) -> None: + # tearoff=0 обязателен: иначе index 0 — пунктир, и «Дебаг» перезаписывает «Справку» + menubar = tk.Menu(self.root, tearoff=0) + + settings = tk.Menu(menubar, tearoff=0) + settings.add_command(label="Файлы…", command=self._open_files_window) + settings.add_command(label="Диаграммы…", command=self._open_diagrams_window) + menubar.add_cascade(label="Настройки", menu=settings) + + menubar.add_command(label="Шаблоны UML", command=self._open_schemes_window) + + help_menu = tk.Menu(menubar, tearoff=0) + help_menu.add_command(label="Инструкция", command=lambda: self._open_help_window("usage")) + help_menu.add_command(label="Схемы", command=lambda: self._open_help_window("schemes")) + help_menu.add_command(label="Промпт для ИИ", command=lambda: self._open_help_window("prompts")) + menubar.add_cascade(label="Справка", menu=help_menu) + + menubar.add_command(label="Дебаг", command=self._toggle_debug) + self._debug_menu_index = int(menubar.index("end")) + self._menubar = menubar + self.root.config(menu=menubar) + + def _raise_or_create(self, existing: tk.Toplevel | None, factory) -> tk.Toplevel: + if existing is not None: + try: + if existing.winfo_exists(): + existing.deiconify() + existing.lift() + existing.focus_force() + return existing + except tk.TclError: + pass + return factory() + + def _open_files_window(self) -> None: + self._win_files = self._raise_or_create(self._win_files, self._create_files_window) + + def _open_diagrams_window(self) -> None: + self._win_diagrams = self._raise_or_create(self._win_diagrams, self._create_diagrams_window) + + def _open_schemes_window(self) -> None: + self._win_schemes = self._raise_or_create(self._win_schemes, self._create_schemes_window) + + def _open_help_window(self, kind: str) -> None: + win = self._win_help.get(kind) + if win is not None: + try: + if win.winfo_exists(): + win.deiconify() + win.lift() + win.focus_force() + return + except tk.TclError: + pass + self._win_help[kind] = self._create_help_window(kind) + + def _create_files_window(self) -> tk.Toplevel: + win = tk.Toplevel(self.root) + win.title("Настройки — Файлы") + win.configure(bg=PAPER) + win.minsize(520, 200) + win.geometry("620x240") + win.transient(self.root) + frame = ttk.Frame(win, padding=12) + frame.pack(fill=tk.BOTH, expand=True) + frame.columnconfigure(1, weight=1) + self._row_path(frame, 0, "Шаблон DOCX", self.template_var, self._browse_template, clearable=True, + tip="Пусто — встроенный Template.docx") + self._row_path(frame, 1, "Титульный лист", self.title_var, self._browse_title, clearable=True, + tip="DOCX титула, вставляется перед телом отчёта") + self._row_path(frame, 2, "Бланк задания", self.assignment_var, self._browse_assignment, clearable=True, + tip="DOCX бланка задания, вставляется перед телом отчёта") + hint = ttk.Label( + frame, + text="Пустой шаблон — встроенный Template.docx. Титул и задание вставляются перед телом отчёта.", + style="Hint.TLabel", + wraplength=560, + ) + hint.grid(row=3, column=0, columnspan=2, sticky="w", pady=(8, 0)) + win.protocol("WM_DELETE_WINDOW", lambda: self._close_toplevel(win, "_win_files")) + return win + + def _create_diagrams_window(self) -> tk.Toplevel: + win = tk.Toplevel(self.root) + win.title("Настройки — Диаграммы") + win.configure(bg=PAPER) + win.minsize(560, 420) + win.geometry("680x520") + win.transient(self.root) + extra = ttk.Frame(win, padding=12) + extra.pack(fill=tk.BOTH, expand=True) + extra.columnconfigure(1, weight=1) + + hint = ttk.Label( + extra, + text=( + "Блоки ```uml / ```uml-c4 / ```bpmn / ```mermaid сами станут рисунками.\n" + "Схемы редактируются в меню «Шаблоны UML» (файл md2gost.schemes.json).\n" + "UML: сначала PlantUML + Java, иначе интернет (kroki.io). Mermaid — только через Kroki." + ), + style="Hint.TLabel", + wraplength=640, + justify=tk.LEFT, + ) + hint.grid(row=0, column=0, columnspan=2, sticky="w", pady=(0, 8)) + + status_row = ttk.Frame(extra) + status_row.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(0, 8)) + status_row.columnconfigure(0, weight=1) + self.diag_status = ttk.Label(status_row, text="", style="Hint.TLabel", wraplength=480, justify=tk.LEFT) + self.diag_status.grid(row=0, column=0, sticky="w") + btn_dl = ttk.Button(status_row, text="Скачать PlantUML", command=self._download_plantuml) + btn_dl.grid(row=0, column=1, sticky="e", padx=(8, 0)) + _tip(btn_dl, "Скачать plantuml.jar для локальной отрисовки (нужна Java)") + + self._combo( + extra, 2, "Как рисовать", + self.fallback_var, + _combo_values(FALLBACK_LABELS, ("remote", "local", "off")), + tip="Локально (PlantUML+Java), через интернет или не рисовать", + ) + self._combo( + extra, 3, "Формат схем", + self.format_var, + _combo_values(FORMAT_LABELS, ("png", "svg")), + tip="PNG везде; SVG — вектор в Word 2016+ (PNG рядом как запасной)", + ) + + ttk.Label(extra, text="Scale PNG (PlantUML)").grid(row=4, column=0, sticky="w", padx=(0, 8), pady=3) + scale_row = ttk.Frame(extra) + scale_row.grid(row=4, column=1, sticky="w", pady=3) + scale_entry = ttk.Entry(scale_row, textvariable=self.scale_var, width=8) + scale_entry.pack(side=tk.LEFT) + _tip(scale_entry, "Качество растра PlantUML; на странице размер как при 1") + ttk.Label( + scale_row, + text=" качество; на странице размер как при 1", + style="Hint.TLabel", + ).pack(side=tk.LEFT) + + ttk.Label( + extra, + text="SVG — вектор в Word 2016+ (PNG всегда рядом как запасной). LibreOffice покажет растр.\n" + "+landscape у %подписи — альбомная A4 под широкий рисунок/таблицу.", + style="Hint.TLabel", + wraplength=640, + ).grid(row=5, column=0, columnspan=2, sticky="w", pady=(2, 0)) + ttk.Label( + extra, + text="Свой plantuml.jar — только если хотите другой файл. Пусто = вшитый или скачаем сами.", + style="Hint.TLabel", + wraplength=640, + ).grid(row=6, column=0, columnspan=2, sticky="w", pady=(6, 0)) + self._row_path(extra, 7, "Свой plantuml.jar", self.plantuml_var, self._browse_jar, clearable=True, + tip="Пусто — вшитый jar или скачанный автоматически") + ttk.Label( + extra, + text="Свой Kroki — только если у вас крутится свой сервер. Пусто = сначала localhost:8000, потом kroki.io.", + style="Hint.TLabel", + wraplength=640, + ).grid(row=8, column=0, columnspan=2, sticky="w", pady=(6, 0)) + ttk.Label(extra, text="Свой Kroki URL").grid(row=9, column=0, sticky="w", padx=(0, 8), pady=3) + kroki_entry = ttk.Entry(extra, textvariable=self.kroki_var) + kroki_entry.grid(row=9, column=1, sticky="ew", pady=3) + _tip(kroki_entry, "URL своего Kroki; пусто — localhost:8000, затем kroki.io") + + extra_flags = ttk.Frame(extra) + extra_flags.grid(row=10, column=0, columnspan=2, sticky="w", pady=(8, 0)) + cb_hl = ttk.Checkbutton(extra_flags, text="Подсветка синтаксиса в листингах", variable=self.highlight_var) + cb_hl.pack(side=tk.LEFT, padx=(0, 16)) + _tip(cb_hl, "Цветная подсветка кода в листингах DOCX") + + cache_row = ttk.Frame(extra) + cache_row.grid(row=11, column=0, columnspan=2, sticky="w", pady=(10, 0)) + btn_open = ttk.Button(cache_row, text="Открыть файл схем", command=self._open_schemes_file) + btn_open.pack(side=tk.LEFT, padx=(0, 8)) + _tip(btn_open, "Открыть md2gost.schemes.json во внешней программе") + btn_cache = ttk.Button(cache_row, text="Сбросить кэш includes", command=self._reset_include_cache) + btn_cache.pack(side=tk.LEFT) + _tip(btn_cache, "Удалить скачанные !include файлы; при следующем рендере скачаются снова") + + self._refresh_diagram_status() + win.protocol("WM_DELETE_WINDOW", lambda: self._close_toplevel(win, "_win_diagrams")) + return win + + def _create_schemes_window(self) -> tk.Toplevel: + win = tk.Toplevel(self.root) + win.title("Шаблоны UML") + win.configure(bg=PAPER) + win.minsize(700, 480) + win.geometry("820x560") + win.transient(self.root) + parent = ttk.Frame(win, padding=12) + parent.pack(fill=tk.BOTH, expand=True) + parent.columnconfigure(0, weight=1) + parent.rowconfigure(1, weight=1) + self._build_schemes_tab(parent) + win.protocol("WM_DELETE_WINDOW", lambda: self._close_toplevel(win, "_win_schemes")) + return win + + def _create_help_window(self, kind: str) -> tk.Toplevel: + titles = { + "usage": "Справка — Инструкция", + "schemes": "Справка — Схемы", + "prompts": "Справка — Промпт для ИИ", + } + win = tk.Toplevel(self.root) + win.title(titles.get(kind, "Справка")) + win.configure(bg=PAPER) + win.minsize(560, 400) + win.geometry("700x560") + win.transient(self.root) + frame = ttk.Frame(win, padding=12) + frame.pack(fill=tk.BOTH, expand=True) + if kind == "usage": + self._build_help(frame) + elif kind == "schemes": + self._build_schemes_help(frame) + else: + self._build_prompts(frame) + + def on_close(): + self._win_help.pop(kind, None) + try: + win.destroy() + except tk.TclError: + pass + + win.protocol("WM_DELETE_WINDOW", on_close) + return win + + def _close_toplevel(self, win: tk.Toplevel, attr: str) -> None: + setattr(self, attr, None) + try: + win.destroy() + except tk.TclError: + pass + + def _toggle_debug(self) -> None: + if self.debug_var.get(): + self.debug_var.set(False) + self._debug_next = False + self._sync_debug_menu() + self._append_log("Дебаг выключен.") + return + self.debug_var.set(True) + self._debug_next = True + self._sync_debug_menu() + messagebox.showinfo( + "md2gost", + "Следующая сборка будет с отладкой.\n" + "В документ добавятся отладочные данные.\n" + "После этой конвертации режим сбросится.", + ) + self._append_log("Дебаг: следующая конвертация с отладочными данными.") + + def _sync_debug_menu(self) -> None: + label = "✓ Дебаг" if self.debug_var.get() else "Дебаг" + try: + idx = self._debug_menu_index + if self._menubar.type(idx) != "command": + return + self._menubar.entryconfigure(idx, label=label) + except tk.TclError: + pass + + def _build(self) -> None: + outer = ttk.Frame(self.root, padding=12) + outer.pack(fill=tk.BOTH, expand=True) + self._build_convert(outer) + self.root.bind("", self._on_return) + + def _on_return(self, event) -> str | None: + if isinstance(event.widget, tk.Text): + return None + self._convert() + return "break" + + def _build_help(self, parent) -> None: + ttk.Label( + parent, + text="Как пользоваться и синтаксис markdown", + style="Hint.TLabel", + ).pack(anchor=tk.W, pady=(0, 6)) + box = self._readonly_text(parent, USAGE_HELP.strip() + "\n") + box.pack(fill=tk.BOTH, expand=True) + + def _build_schemes_help(self, parent) -> None: + ttk.Label( + parent, + text="Настраиваемые схемы диаграмм (uml-c4, bpmn, …)", + style="Hint.TLabel", + ).pack(anchor=tk.W, pady=(0, 6)) + box = self._readonly_text(parent, SCHEMES_HELP.strip() + "\n") + box.pack(fill=tk.BOTH, expand=True) + + def _build_prompts(self, parent) -> None: + catalog = load_prompt_catalog() + ttk.Label( + parent, + text="Скопируйте промпт целиком и вставьте в ChatGPT / Cursor / Copilot. Следующим сообщением — тип, тема, черновик.", + style="Hint.TLabel", + wraplength=640, + ).pack(anchor=tk.W, pady=(0, 8)) + + if not catalog: + ttk.Label( + parent, + text="Файлы промптов не найдены (папка prompts/ рядом с программой).", + style="Hint.TLabel", + ).pack(anchor=tk.W) + return + + self._prompt_texts = {title: text for _name, title, text in catalog} + titles = [title for _name, title, _text in catalog] + self._prompt_choice = tk.StringVar(value=titles[0]) + + row = ttk.Frame(parent) + row.pack(fill=tk.X, pady=(0, 6)) + ttk.Label(row, text="Промпт").pack(side=tk.LEFT, padx=(0, 8)) + box = ttk.Combobox(row, textvariable=self._prompt_choice, values=titles, state="readonly") + box.pack(side=tk.LEFT, fill=tk.X, expand=True) + box.bind("<>", lambda _e: self._show_selected_prompt()) + btn = ttk.Button(row, text="Скопировать", command=self._copy_selected_prompt) + btn.pack(side=tk.LEFT, padx=(8, 0)) + _tip(btn, "Скопировать выбранный промпт в буфер обмена") + + self._prompt_view = self._readonly_text(parent, self._prompt_texts[titles[0]]) + self._prompt_view.pack(fill=tk.BOTH, expand=True) + self._copy_status = ttk.Label(parent, text="", style="Hint.TLabel") + self._copy_status.pack(anchor=tk.W, pady=(6, 0)) + + def _show_selected_prompt(self) -> None: + text = self._prompt_texts.get(self._prompt_choice.get(), "") + self._prompt_view.configure(state=tk.NORMAL) + self._prompt_view.delete("1.0", tk.END) + self._prompt_view.insert("1.0", text) + self._prompt_view.configure(state=tk.DISABLED) + self._copy_status.configure(text="") + + def _copy_selected_prompt(self) -> None: + text = self._prompt_texts.get(self._prompt_choice.get(), "") + if not text: + return + self.root.clipboard_clear() + self.root.clipboard_append(text) + self.root.update_idletasks() + self._copy_status.configure(text="Скопировано в буфер обмена — можно вставлять в ИИ.") + + def _readonly_text(self, parent, content: str) -> tk.Text: + wrap = ttk.Frame(parent) + text = tk.Text( + wrap, wrap=tk.WORD, relief=tk.FLAT, + bg="#fffdf8", fg=INK, padx=10, pady=8, + font=("Consolas", 9) if os.name == "nt" else ("monospace", 9), + ) + scroll = ttk.Scrollbar(wrap, command=text.yview) + text.configure(yscrollcommand=scroll.set) + text.insert("1.0", content) + text.configure(state=tk.DISABLED) + text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + scroll.pack(side=tk.RIGHT, fill=tk.Y) + text._md2gost_wrap = wrap # type: ignore[attr-defined] + + def pack(**kwargs): + wrap.pack(**kwargs) + + text.pack = pack # type: ignore[method-assign] + return text + + def _build_convert(self, outer) -> None: + self.zone = tk.Frame( + outer, bg=ZONE, highlightthickness=2, + highlightbackground=NAVY, highlightcolor=NAVY, cursor="hand2", + ) + self.zone.pack(fill=tk.X, pady=(0, 8)) + self.zone_label = tk.Label( + self.zone, + text="Перетащите .md сюда · или нажмите, чтобы выбрать файл", + bg=ZONE, fg=NAVY, font=("Segoe UI", 11) if os.name == "nt" else ("Helvetica", 11), + justify=tk.CENTER, pady=14, + ) + self.zone_label.pack(fill=tk.X) + self.zone_path = tk.Label( + self.zone, text="", bg=ZONE, fg=NAVY_DIM, + font=("Segoe UI", 9) if os.name == "nt" else ("Helvetica", 9), + ) + self.zone_path.pack(fill=tk.X, pady=(0, 8)) + for w in (self.zone, self.zone_label, self.zone_path): + w.bind("", lambda _e: self._browse_md()) + _tip(self.zone, "Перетащите .md или кликните, чтобы выбрать файл") + _tip(self.zone_label, "Перетащите .md или кликните, чтобы выбрать файл") + + grid = ttk.Frame(outer) + grid.pack(fill=tk.X) + grid.columnconfigure(1, weight=1) + self._row_path(grid, 0, "Выходной DOCX", self.output_var, self._browse_output, + tip="Куда сохранить документ; пусто — рядом с .md") + + main = ttk.LabelFrame(outer, text="Основные", padding=8) + main.pack(fill=tk.X, pady=(10, 8)) + main.columnconfigure(1, weight=1) + + self._combo(main, 0, "Тип документа", self.type_var, _combo_values(DOC_TYPE_LABELS, DOC_TYPES), + tip="Профиль оформления: ГОСТ МИРЭА, ПИС, АПИД…") + self._combo(main, 1, "Нумерация заголовков", self.heading_var, _combo_values(HEADING_LABELS, HEADING_NUMBERING_MODES), + tip="Цифры уже в markdown или автонумерация Word") + self._combo(main, 2, "Содержание", self.toc_var, _combo_values(TOC_LABELS, TOC_MODES), + tip="Поле Word или собранное md2gost") + self._combo(main, 3, "Продолжение таблиц", self.table_var, _combo_values(TABLE_LABELS, TABLE_CONTINUATION_MODES), + tip="off — Word сам переносит; word — Split + «Продолжение» после сборки (нужен Word)") + self._combo(main, 4, "Продолжение листингов", self.listing_var, _combo_values(TABLE_LABELS, LISTING_CONTINUATION_MODES), + tip="То же для листингов; word требует Windows + Word + pywin32") + + flags = ttk.Frame(main) + flags.grid(row=5, column=0, columnspan=2, sticky="w", pady=(8, 0)) + checks = [ + ("Заменять «—» на «-»", self.emdash_var, "Типографское тире → дефис (методичка обычно требует «—»)"), + ("--- → разрыв страницы", self.hr_pagebreak_var, "Строка --- станет page break в Word"), + ("Открыть после конвертации", self.open_var, "Открыть DOCX после успешной сборки"), + ("Проверить по ТЗ", self.check_var, "Замечания по структуре, «рис.», источникам"), + ("Только проверка", self.check_only_var, "Проверить без записи DOCX"), + ("Строгий режим (код 1 при ошибках)", self.strict_var, "Ненулевой код выхода при ошибках проверки"), + ("Проверить вёрстку в Word (эвристика)", self.check_pages_var, "Полупустые страницы; нужны Word + pywin32"), + ( + "Повторять шапку таблицы при word-разрыве", + self.table_repeat_header_var, + "Только для «Продолжение таблиц = word»; по умолчанию выкл", + ), + ] + for i, (text, var, tip) in enumerate(checks): + r, c = divmod(i, 3) + cb = ttk.Checkbutton(flags, text=text, variable=var) + span = 2 if c == 0 and i >= 6 else 1 + cb.grid(row=r, column=c, columnspan=span, sticky="w", padx=(0, 16), pady=2) + _tip(cb, tip) + + actions = ttk.Frame(outer) + actions.pack(fill=tk.X, pady=(4, 8)) + self.convert_btn = tk.Button( + actions, text="Конвертировать", + command=self._convert, + bg=NAVY, fg="white", activebackground="#16304d", activeforeground="white", + disabledforeground="#d0d5dc", + font=("Segoe UI", 12, "bold") if os.name == "nt" else ("Helvetica", 12, "bold"), + relief=tk.FLAT, padx=22, pady=7, cursor="hand2", + ) + self.convert_btn.pack(side=tk.LEFT) + _tip(self.convert_btn, "Собрать DOCX из выбранного markdown") + self.progress = ttk.Progressbar(actions, mode="indeterminate", length=180) + self.progress.pack(side=tk.LEFT, padx=8, fill=tk.X, expand=True) + + log_frame = ttk.LabelFrame(outer, text="Журнал", padding=4) + log_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 0)) + self.log = tk.Text( + log_frame, height=7, wrap=tk.WORD, relief=tk.FLAT, + bg="#fffdf8", fg=INK, padx=8, pady=6, + font=("Consolas", 9) if os.name == "nt" else ("monospace", 9), + ) + scroll = ttk.Scrollbar(log_frame, command=self.log.yview) + self.log.configure(yscrollcommand=scroll.set) + self.log.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + scroll.pack(side=tk.RIGHT, fill=tk.Y) + self.log.insert("1.0", "Готово к работе. Перетащите markdown-файл в верхнюю область.\n") + self.log.configure(state=tk.DISABLED) + + def _build_schemes_tab(self, parent) -> None: + ttk.Label( + parent, + text="В markdown: ```uml- или ```. Сохраняется в md2gost.schemes.json рядом с программой.", + style="Hint.TLabel", + wraplength=780, + ).grid(row=0, column=0, sticky="w", pady=(0, 6)) + + body = ttk.Frame(parent) + body.grid(row=1, column=0, sticky="nsew") + body.columnconfigure(1, weight=1) + body.rowconfigure(0, weight=1) + + left = ttk.Frame(body) + left.grid(row=0, column=0, sticky="nsw", padx=(0, 10)) + self._scheme_list = tk.Listbox(left, height=14, width=22, exportselection=False) + self._scheme_list.pack(side=tk.TOP, fill=tk.Y) + self._scheme_list.bind("<>", self._on_scheme_select) + _tip(self._scheme_list, "Список схем из md2gost.schemes.json") + btns = ttk.Frame(left) + btns.pack(fill=tk.X, pady=(6, 0)) + for text, cmd, tip in ( + ("Добавить", self._scheme_add, "Новая схема с id customN"), + ("Дублировать", self._scheme_dup, "Копия выбранной схемы"), + ("Удалить", self._scheme_del, "Удалить схему из файла пользователя"), + ("Сохранить", self._scheme_save_all, "Записать все схемы в md2gost.schemes.json"), + ("Открыть JSON", self._open_schemes_file, "Открыть файл схем во внешней программе"), + ("Сброс кэша", self._reset_include_cache, "Сбросить кэш скачанных includes"), + ): + b = ttk.Button(btns, text=text, command=cmd) + b.pack(fill=tk.X, pady=1) + _tip(b, tip) + + form = ttk.Frame(body) + form.grid(row=0, column=1, sticky="nsew") + form.columnconfigure(1, weight=1) + + self._sch_id = tk.StringVar() + self._sch_title = tk.StringVar() + self._sch_version = tk.StringVar() + self._sch_author = tk.StringVar() + row = 0 + for label, var, tip in ( + ("id", self._sch_id, "Латиница, цифры, _; начинается с буквы"), + ("title", self._sch_title, "Подпись в списке схем"), + ("version", self._sch_version, "Версия схемы"), + ("author", self._sch_author, "Автор схемы"), + ): + ttk.Label(form, text=label).grid(row=row, column=0, sticky="w", padx=(0, 8), pady=2) + e = ttk.Entry(form, textvariable=var) + e.grid(row=row, column=1, sticky="ew", pady=2) + _tip(e, tip) + row += 1 + + for label, attr, height, tip in ( + ("docs", "_sch_docs", 4, "Шпаргалка синтаксиса / макросов"), + ("ai-prompt", "_sch_ai", 3, "Заготовка промпта для ИИ"), + ("includes", "_sch_includes", 3, "Файлы или URL .puml, по одному в строке"), + ("prefix", "_sch_prefix", 3, "Текст перед телом (часто @startuml)"), + ("postfix", "_sch_postfix", 2, "Текст после тела (часто @enduml)"), + ): + ttk.Label(form, text=label).grid(row=row, column=0, sticky="nw", padx=(0, 8), pady=2) + w = tk.Text(form, height=height, wrap=tk.WORD, font=("Consolas", 9) if os.name == "nt" else ("monospace", 9)) + w.grid(row=row, column=1, sticky="ew", pady=2) + setattr(self, attr, w) + _tip(w, tip) + row += 1 + + self._scheme_fence_hint = ttk.Label(parent, text="", style="Hint.TLabel") + self._scheme_fence_hint.grid(row=2, column=0, sticky="w", pady=(6, 0)) + self._reload_schemes_ui() + + def _text_set(self, widget: tk.Text, value: str) -> None: + widget.delete("1.0", tk.END) + if value: + widget.insert("1.0", value) + + def _text_get(self, widget: tk.Text) -> str: + return widget.get("1.0", tk.END).rstrip("\n") + + def _reload_schemes_ui(self, select_id: str | None = None) -> None: + if not hasattr(self, "_scheme_list"): + return + path = ensure_user_schemes() + self._schemes = load_schemes_from_path(path) + self._scheme_ids = sorted(self._schemes.keys()) + self._scheme_list.delete(0, tk.END) + for sid in self._scheme_ids: + title = self._schemes[sid].title or sid + self._scheme_list.insert(tk.END, title) + if not self._scheme_ids: + self._clear_scheme_form() + return + target = select_id if select_id in self._scheme_ids else self._scheme_ids[0] + idx = self._scheme_ids.index(target) + self._scheme_list.selection_clear(0, tk.END) + self._scheme_list.selection_set(idx) + self._scheme_list.see(idx) + self._fill_scheme_form(self._schemes[target]) + + def _clear_scheme_form(self) -> None: + self._scheme_selecting = True + self._sch_id.set("") + self._sch_title.set("") + self._sch_version.set("") + self._sch_author.set("") + self._text_set(self._sch_docs, "") + self._text_set(self._sch_ai, "") + self._text_set(self._sch_includes, "") + self._text_set(self._sch_prefix, "") + self._text_set(self._sch_postfix, "") + self._scheme_fence_hint.configure(text="") + self._scheme_selecting = False + + def _fill_scheme_form(self, scheme: DiagramScheme) -> None: + self._scheme_selecting = True + self._sch_id.set(scheme.id) + self._sch_title.set(scheme.title) + self._sch_version.set(scheme.version) + self._sch_author.set(scheme.author) + self._text_set(self._sch_docs, scheme.docs) + self._text_set(self._sch_ai, scheme.ai_prompt) + self._text_set(self._sch_includes, "\n".join(scheme.includes)) + self._text_set(self._sch_prefix, scheme.prefix) + self._text_set(self._sch_postfix, scheme.postfix) + self._scheme_fence_hint.configure(text=f"Оградка: ```uml-{scheme.id} или ```{scheme.id}") + self._scheme_selecting = False + + def _on_scheme_select(self, _event=None) -> None: + if self._scheme_selecting: + return + sel = self._scheme_list.curselection() + if not sel: + return + sid = self._scheme_ids[sel[0]] + self._fill_scheme_form(self._schemes[sid]) + + def _scheme_from_form(self, old_id: str | None = None) -> DiagramScheme | None: + sid = self._sch_id.get().strip().lower() + if not SCHEME_ID_RE.match(sid): + messagebox.showerror("md2gost", "id схемы: латиница, цифры, _; начинается с буквы.") + return None + includes_raw = self._text_get(self._sch_includes) + includes = [ln.strip() for ln in includes_raw.splitlines() if ln.strip()] + return DiagramScheme( + id=sid, + title=self._sch_title.get().strip() or sid, + version=self._sch_version.get().strip(), + author=self._sch_author.get().strip(), + docs=self._text_get(self._sch_docs), + ai_prompt=self._text_get(self._sch_ai), + includes=includes, + prefix=self._text_get(self._sch_prefix), + postfix=self._text_get(self._sch_postfix), + ) + + def _scheme_commit_current(self) -> str | None: + """Write form into self._schemes. Returns new id or None on error.""" + sel = self._scheme_list.curselection() + old_id = self._scheme_ids[sel[0]] if sel else None + scheme = self._scheme_from_form(old_id) + if scheme is None: + return None + if old_id and old_id != scheme.id and old_id in self._schemes: + del self._schemes[old_id] + self._schemes[scheme.id] = scheme + return scheme.id + + def _scheme_add(self) -> None: + n = 1 + while f"custom{n}" in self._schemes: + n += 1 + sid = f"custom{n}" + self._schemes[sid] = DiagramScheme( + id=sid, + title="Новая схема", + version="1.0.0", + author="", + docs="", + ai_prompt="", + includes=[], + prefix="@startuml\n", + postfix="\n@enduml", + ) + self._reload_schemes_ui(select_id=sid) + + def _scheme_dup(self) -> None: + sel = self._scheme_list.curselection() + if not sel: + return + src = self._schemes[self._scheme_ids[sel[0]]] + n = 1 + while f"{src.id}_copy{n}" in self._schemes: + n += 1 + sid = f"{src.id}_copy{n}" + self._schemes[sid] = DiagramScheme( + id=sid, + title=src.title, + version=src.version, + author=src.author, + docs=src.docs, + ai_prompt=src.ai_prompt, + includes=list(src.includes), + prefix=src.prefix, + postfix=src.postfix, + theme=src.theme, + ) + self._reload_schemes_ui(select_id=sid) + + def _scheme_del(self) -> None: + sel = self._scheme_list.curselection() + if not sel: + return + sid = self._scheme_ids[sel[0]] + if not messagebox.askyesno("md2gost", f"Удалить схему «{sid}» из файла пользователя?"): + return + del self._schemes[sid] + path = user_schemes_path() + save_schemes(self._schemes, path) + reload_schemes() + self._reload_schemes_ui() + self._append_log(f"Схема удалена: {sid}") + + def _scheme_save_all(self) -> None: + sid = self._scheme_commit_current() + if sid is None and self._scheme_ids: + return + path = user_schemes_path() + save_schemes(self._schemes, path) + reload_schemes() + self._reload_schemes_ui(select_id=sid) + self._append_log(f"Схемы сохранены: {path}") + messagebox.showinfo("md2gost", f"Сохранено:\n{path}") + + def _open_schemes_file(self) -> None: + path = ensure_user_schemes() + try: + if os.name == "nt": + os.startfile(str(path)) # type: ignore[attr-defined] + elif sys.platform == "darwin": + import subprocess + subprocess.call(("open", str(path))) + else: + import subprocess + subprocess.call(("xdg-open", str(path))) + except OSError as exc: + messagebox.showerror("md2gost", f"Не удалось открыть:\n{path}\n{exc}") + + def _reset_include_cache(self) -> None: + n = cache_entry_count() + if not messagebox.askyesno( + "md2gost", + f"Сбросить кэш includes ({n} записей)?\n" + "Удалятся индекс и только файлы из него.\n" + "При следующем рендере URL скачаются снова.", + ): + return + removed = reset_include_cache() + self._append_log(f"Кэш includes сброшен, удалено файлов: {removed}") + messagebox.showinfo("md2gost", f"Кэш сброшен. Удалено файлов: {removed}") + + def _combo(self, parent, row: int, label: str, var: tk.StringVar, values: list[str], tip: str = "") -> None: + ttk.Label(parent, text=label).grid(row=row, column=0, sticky="w", padx=(0, 8), pady=3) + box = ttk.Combobox(parent, textvariable=var, values=values, state="readonly") + box.grid(row=row, column=1, sticky="ew", pady=3) + if tip: + _tip(box, tip) + + def _row_path(self, parent, row, label, var, browse, clearable=False, tip: str = ""): + ttk.Label(parent, text=label).grid(row=row, column=0, sticky="w", padx=(0, 8), pady=3) + cell = ttk.Frame(parent) + cell.grid(row=row, column=1, sticky="ew", pady=3) + cell.columnconfigure(0, weight=1) + entry = ttk.Entry(cell, textvariable=var) + entry.grid(row=0, column=0, sticky="ew") + if tip: + _tip(entry, tip) + btn = ttk.Button(cell, text="Обзор…", command=browse) + btn.grid(row=0, column=1, padx=(6, 0)) + _tip(btn, "Выбрать файл") + if clearable: + clear_btn = ttk.Button(cell, text="Сброс", command=lambda: var.set("")) + clear_btn.grid(row=0, column=2, padx=(4, 0)) + _tip(clear_btn, "Очистить поле") + + def _setup_dnd(self) -> None: + backend = enable_file_drop(self.zone, self._on_drop) + if backend == "none": + backend = enable_file_drop(self.root, self._on_drop) + self._dnd_backend = backend + if backend == "none": + self._append_log("Перетаскивание недоступно — выберите файл кликом по области.") + else: + self._append_log("Можно перетаскивать .md из проводника в окно.") + + def _on_drop(self, paths: list[str]) -> None: + md = first_markdown(paths) + if md: + self._set_markdown(md) + self._flash_zone(ZONE_OK) + return + if paths: + self._append_log("Нужен файл .md, а не: " + ", ".join(os.path.basename(p) for p in paths)) + self._flash_zone("#f8d7da") + + def _flash_zone(self, color: str) -> None: + self.zone.configure(bg=color) + self.zone_label.configure(bg=color) + self.zone_path.configure(bg=color) + self.root.after(700, lambda: self._set_zone_colors(ZONE if self._last_md else ZONE)) + + def _set_zone_colors(self, color: str) -> None: + self.zone.configure(bg=color) + self.zone_label.configure(bg=color) + self.zone_path.configure(bg=color) + + def _browse_md(self) -> None: + path = filedialog.askopenfilename( + title="Исходный Markdown", + filetypes=[("Markdown", "*.md"), ("Все файлы", "*.*")], + ) + if path: + self._set_markdown(path) + + def _browse_output(self) -> None: + initial = self.output_var.get() or (default_output_path(self._last_md) if self._last_md else "report.docx") + path = filedialog.asksaveasfilename( + title="Сохранить DOCX", + defaultextension=".docx", + initialfile=os.path.basename(initial), + initialdir=os.path.dirname(os.path.abspath(initial)) if initial else "", + filetypes=[("Word", "*.docx")], + ) + if path: + self._auto_output = False + self.output_var.set(path) + + def _browse_template(self) -> None: + self._pick_into(self.template_var, "Шаблон DOCX", [("Word", "*.docx")]) + + def _browse_title(self) -> None: + self._pick_into(self.title_var, "Титульный лист", [("Word", "*.docx")]) + + def _browse_assignment(self) -> None: + self._pick_into(self.assignment_var, "Бланк задания", [("Word", "*.docx")]) + + def _browse_jar(self) -> None: + self._pick_into(self.plantuml_var, "Свой plantuml.jar", [("JAR", "*.jar"), ("Все файлы", "*.*")]) + self._refresh_diagram_status() + + def _refresh_diagram_status(self) -> None: + label = getattr(self, "diag_status", None) + if label is None: + return + try: + if not label.winfo_exists(): + return + except tk.TclError: + return + explicit = self.plantuml_var.get().strip() or None + label.configure(text=diagram_engine_status(explicit)) + + def _download_plantuml(self) -> None: + from .diagram_renderer import cached_plantuml_path, fetch_plantuml_jar, vendor_plantuml_path + dest = vendor_plantuml_path() + try: + dest.parent.mkdir(parents=True, exist_ok=True) + probe = dest.parent / ".write_test" + probe.write_text("ok", encoding="utf-8") + probe.unlink() + except OSError: + dest = cached_plantuml_path() + self._append_log(f"Скачиваю PlantUML → {dest}") + if fetch_plantuml_jar(dest): + self._refresh_diagram_status() + messagebox.showinfo("md2gost", f"PlantUML сохранён:\n{dest}\n\nДля локальной отрисовки нужна Java.") + else: + messagebox.showerror( + "md2gost", + "Не удалось скачать plantuml.jar.\n" + "Можно ничего не делать — диаграммы нарисует kroki.io.", + ) + + def _pick_into(self, var: tk.StringVar, title: str, types) -> None: + path = filedialog.askopenfilename(title=title, filetypes=types) + if path: + var.set(path) + + def _on_output_edited(self, *_args) -> None: + if not self._last_md: + return + current = self.output_var.get().strip() + auto = default_output_path(self._last_md) + self._auto_output = (not current) or os.path.normcase(os.path.abspath(current)) == os.path.normcase(auto) + + def _set_markdown(self, path: str, from_user_output: bool = False) -> None: + path = os.path.abspath(path) + self._last_md = path + self.zone_label.configure(text=os.path.basename(path)) + self.zone_path.configure(text=path) + if not from_user_output and self._auto_output: + self.output_var.set(default_output_path(path)) + self._auto_output = True + self._append_log(f"Файл: {path}") + + def _apply_request(self, req: ConvertRequest) -> None: + self.type_var.set(_display(DOC_TYPE_LABELS, req.doc_type if req.doc_type in DOC_TYPES else "practice")) + self.heading_var.set(_display(HEADING_LABELS, req.heading_numbering or DEFAULT_HEADING_NUMBERING)) + self.toc_var.set(_display(TOC_LABELS, req.toc_mode or DEFAULT_TOC_MODE)) + self.table_var.set(_display(TABLE_LABELS, req.table_continuation or DEFAULT_TABLE_CONTINUATION)) + self.listing_var.set(_display(TABLE_LABELS, req.listing_continuation or DEFAULT_LISTING_CONTINUATION)) + self.fallback_var.set(_display(FALLBACK_LABELS, req.diagram_fallback or "remote")) + self.format_var.set(_display(FORMAT_LABELS, req.diagram_format or "png")) + scale = getattr(req, "diagram_scale", 2.0) or 2.0 + self.scale_var.set(str(scale).rstrip("0").rstrip(".") if isinstance(scale, float) else str(scale)) + self.emdash_var.set(bool(req.emdash_to_hyphen)) + self.hr_pagebreak_var.set(bool(req.hr_pagebreak)) + self.open_var.set(True) + self.check_var.set(bool(req.check)) + self.check_only_var.set(bool(req.check_only)) + self.strict_var.set(bool(req.strict)) + self.check_pages_var.set(bool(getattr(req, "check_pages", False))) + self.table_repeat_header_var.set(bool(getattr(req, "table_repeat_header", False))) + self.highlight_var.set(bool(req.syntax_highlighting)) + self.debug_var.set(bool(req.debug)) + self._debug_next = bool(req.debug) + self._sync_debug_menu() + self.template_var.set(req.template or "") + self.title_var.set(req.title or "") + self.assignment_var.set(req.assignment or "") + self.plantuml_var.set(req.plantuml_jar or "") + self.kroki_var.set(req.kroki_url or "") + self._refresh_diagram_status() + if req.output: + self.output_var.set(req.output) + self._auto_output = False + + def _collect(self) -> ConvertRequest: + return ConvertRequest( + filename=self._last_md, + output=self.output_var.get().strip() or None, + template=self.template_var.get().strip() or None, + doc_type=_combo_key(self.type_var.get(), DOC_TYPES, "coursework"), + heading_numbering=_combo_key(self.heading_var.get(), HEADING_NUMBERING_MODES, DEFAULT_HEADING_NUMBERING), + toc_mode=_combo_key(self.toc_var.get(), TOC_MODES, DEFAULT_TOC_MODE), + table_continuation=_combo_key(self.table_var.get(), TABLE_CONTINUATION_MODES, DEFAULT_TABLE_CONTINUATION), + listing_continuation=_combo_key(self.listing_var.get(), LISTING_CONTINUATION_MODES, DEFAULT_LISTING_CONTINUATION), + emdash_to_hyphen=self.emdash_var.get(), + hr_pagebreak=self.hr_pagebreak_var.get(), + title=self.title_var.get().strip() or None, + assignment=self.assignment_var.get().strip() or None, + check=self.check_var.get() or self.check_only_var.get(), + check_only=self.check_only_var.get(), + check_pages=self.check_pages_var.get(), + table_repeat_header=self.table_repeat_header_var.get(), + strict=self.strict_var.get(), + syntax_highlighting=self.highlight_var.get(), + plantuml_jar=self.plantuml_var.get().strip() or None, + kroki_url=self.kroki_var.get().strip() or None, + diagram_fallback=_combo_key(self.fallback_var.get(), ("remote", "local", "off"), "remote"), + diagram_format=_combo_key(self.format_var.get(), ("png", "svg"), "png"), + diagram_scale=self._parse_diagram_scale(), + debug=self.debug_var.get(), + open_when_done=self.open_var.get(), + ) + + def _parse_diagram_scale(self) -> float: + raw = (self.scale_var.get() or "2").strip().replace(",", ".") + try: + value = float(raw) + except ValueError: + return 2.0 + return value if value >= 1 else 1.0 + + def _convert(self) -> None: + if self._busy: + return + req = self._collect() + if not req.filename: + messagebox.showinfo("md2gost", "Сначала перетащите или выберите markdown-файл.") + return + if not req.check_only and req.output and os.path.isfile(req.output): + if not messagebox.askyesno("md2gost", f"Файл уже есть:\n{req.output}\n\nПерезаписать?"): + return + self._busy = True + self.convert_btn.configure(state=tk.DISABLED, bg=NAVY_DIM) + self.progress.start(12) + self._append_log("Конвертация…") + + def work(): + result = convert(req, log=lambda m: self.root.after(0, self._append_log, m)) + self.root.after(0, self._on_done, result) + + threading.Thread(target=work, daemon=True).start() + + def _on_done(self, result) -> None: + self._busy = False + self.convert_btn.configure(state=tk.NORMAL, bg=NAVY) + self.progress.stop() + if self._debug_next or self.debug_var.get(): + self.debug_var.set(False) + self._debug_next = False + self._sync_debug_menu() + self._append_log("Дебаг сброшен после этой сборки.") + if result.ok: + if result.output_path: + self._append_log("Готово.") + elif result.check_report: + self._append_log("Проверка завершена.") + else: + self._append_log(result.message or "Ошибка") + messagebox.showerror("md2gost", result.message or "Ошибка конвертации") + + def _append_log(self, text: str) -> None: + if not text: + return + if not hasattr(self, "log"): + return + self.log.configure(state=tk.NORMAL) + if not text.endswith("\n"): + text += "\n" + self.log.insert(tk.END, text) + self.log.see(tk.END) + self.log.configure(state=tk.DISABLED) + + def run(self) -> None: + self.root.mainloop() + + +def run_gui(initial: ConvertRequest | None = None) -> None: + Md2GostApp(initial).run() + + +def main() -> None: + run_gui() + + +if __name__ == "__main__": + main() diff --git a/md2gost/help_content.py b/md2gost/help_content.py new file mode 100644 index 0000000..8cfc18d --- /dev/null +++ b/md2gost/help_content.py @@ -0,0 +1,258 @@ +"""Usage text and AI prompt files for the GUI pages.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +from . import package_dir + +USAGE_HELP = """md2gost — Markdown → DOCX (ТЗ МИРЭА / ГОСТ 7.32) + +КАК ПОЛЬЗОВАТЬСЯ ОКНОМ +1. Перетащите .md в верхнюю область (или кликните по ней). +2. Выберите тип документа и параметры в блоке «Основные». + Шаблон / титул / задание — Настройки → Файлы. + PlantUML / Kroki — Настройки → Диаграммы. + Свои UML-схемы — меню «Шаблоны UML». +3. Нажмите «Конвертировать». Документ сохранится рядом с исходником (или по пути «Выходной DOCX»). + Дебаг (меню сверху) — следующая сборка с отладочными данными в документе. + +Типы: practice (по умолчанию) / coursework / vkr — ГОСТ МИРЭА; PIS_custom — отчёт по практикам; APID_coursework — курсовая АПИД. + +Полезные галочки +• «—» → «-» — заменить типографское тире на дефис (по умолчанию **выкл.**; методичка требует «—»). +• --- → разрыв страницы — по умолчанию выкл. (строка --- игнорируется). Вкл. — page break в Word. +• Проверить по ТЗ — замечания по структуре, «рис.», источникам. +• Проверить вёрстку в Word — полупустые страницы (эвристика, возможны ложные срабатывания; нужны Word + pywin32). +• Титул / задание — отдельные DOCX, вставляются перед телом отчёта. + +CLI (тот же движок) + python -m md2gost report.md -o report.docx --type coursework --check + python -m md2gost --gui + md2gost.exe report.md --type PIS_custom --title title.docx + md2gost.exe report.md --schemes path/to/md2gost.schemes.json + +СИНТАКСИС MARKDOWN + +Спецразделы (без номера, ПРОПИСНЫЕ, звёздочка): + # *СОДЕРЖАНИЕ + [TOC] + # *ВВЕДЕНИЕ + # *ЗАКЛЮЧЕНИЕ + # *СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ + # *ПРИЛОЖЕНИЯ + (после ПРИЛОЖЕНИЯ — перечень основным текстом, затем ## Приложение А Название) + +Нумерованные разделы: # 1 Название ## 1.1 Подраздел +Точку в конце заголовка не ставить. +СОДЕРЖАНИЕ и СПИСОК — по центру; Введение / Заключение / ПРИЛОЖЕНИЯ — слева. + +Рисунок + Текст со ссылкой на @Рисунок:arch. + ![описание](images/arch.png "%arch Название рисунка") +В тексте пишите «Рисунок», не «рис.» + +Таблица + %tbl1 Название + | A | B | + |---|---| + | 1 | 2 | +Ссылка: @Таблица:tbl1 +Склеивание: ^ — ячейка сверху (rowspan), > — ячейка слева (colspan). +Не ставить ^/> в шапке; > — не в первом столбце. + +Листинг + %code1 Название + ```python + print("ok") + ``` + +Диаграмма (PlantUML / Mermaid / схемы) → рисунок + %usecase1 Диаграмма прецедентов +listing + ```uml + @startuml + actor User + User --> (Login) + @enduml + ``` + + Широкая схема/таблица на альбомной странице: + %arch1 Архитектура +landscape + ```uml-c4 + … + ``` + + Mermaid (через Kroki, свой URL или kroki.io): + %seq Последовательность +listing + ```mermaid + sequenceDiagram + Alice->>Bob: hello + ``` + ++listing — ещё и листинг с исходником. ++landscape — отдельная альбомная страница (A4, повёрт на 90°) вокруг рисунка/таблицы, затем снова книжная. +Языки: uml, plantuml, mermaid / mmd, или uml- / из файла схем (c4, usecase, bpmn, …). +BPMN 2.0: оградка ```bpmn / ```uml-bpmn, макросы Start, UserTask, XOR, Flow, Pool. +Формат в Word: PNG по умолчанию (PlantUML рисуется ~2× для чёткости); --diagram-format svg — вектор + PNG-запасной (Word 2016+). +IDEF0 конвертер не рисует — вставляйте готовый PNG как обычный Рисунок. +Подробнее — Справка → Схемы и раздел ниже в инструкции. + +Формула (номер только если есть ссылка) + %eq1 + $$ E = mc^2 $$ + См. @Формула:eq1 + +Источники + В тексте: [1] + В списке: [1]: Иванов И. И. Название. — М.: Наука, 2023. — 120 с. + +Разрыв страницы + По умолчанию --- игнорируется. + Галочка «--- → разрыв страницы» или --hr-pagebreak: пустая строка, ---, пустая строка. + +Нумерация заголовков + manual — цифры уже в md (# 1 …); auto — нумерует Word. + +Содержание + native — поле Word (обновить при открытии); manual — собирает md2gost. + +Продолжение таблиц / листингов + word — после сборки Word COM режет по реальной пагинации и вставляет «Продолжение…» + (по умолчанию; нужны Windows + Word + pywin32). + off — не резать, Word сам переносит. + legacy / caption — режем по оценке высоты в md2gost (может не совпасть с Word). + +Промпт для ИИ — Справка → Промпт для ИИ: скопируйте и вставьте в ChatGPT / Cursor / Copilot, затем дайте тему и черновик. +""" + +SCHEMES_HELP = """СХЕМЫ ДИАГРАММ (PlantUML) + +Зачем +В markdown пишете только «тело» диаграммы. Обёртка (@startuml, !include, тема) +берётся из схемы в файле md2gost.schemes.json. + +Первый запуск +Рядом с программой (рядом с md2gost.exe или в текущей папке при python -m) +создаётся md2gost.schemes.json из встроенного шаблона. +Если файл уже есть — программа его не перезаписывает (ваши схемы сохраняются). + +Оградка в markdown + ```uml — обычный PlantUML (или ```plantuml) + ```uml-c4 — схема с id «c4» (то же, что ```c4) + ```uml-usecase — схема «usecase» + ```bpmn / ```uml-bpmn — BPMN 2.0 (макросы в diagrams/BPMN.puml) + ```mermaid / ```mmd — Mermaid через Kroki (не PlantUML) +Встроенные пресеты PlantUML: c4, c4context, c4component, usecase, bpmn. + +Пример + +%arch Архитектура +listing + +```uml-c4 +Person(user, "Студент") +System(app, "Портал") +Rel(user, app, "логин") +``` + +```bpmn +StartMessage(s, "заявка") +UserTask(t, "Проверить") +XOR(gw) +End(e_ok) +End(e_no) +Flow(s, t) +Flow(t, gw) +CondFlow(gw, e_ok, "да") +DefaultFlow(gw, e_no) +``` + +Поля схемы в JSON + title — подпись в GUI + version — версия схемы + author — автор + docs — шпаргалка синтаксиса (чтобы вспомнить макросы) + ai-prompt — заготовка промпта для ИИ / будущего MCP + includes — список файлов или http(s):// URL на .puml + prefix — текст перед телом (часто @startuml) + postfix — текст после тела (часто @enduml) + theme — опционально !theme … + +Свои схемы +1. Меню «Шаблоны UML» в GUI — добавьте / отредактируйте и сохраните. +2. Или откройте md2gost.schemes.json в редакторе («Открыть JSON» / «Открыть файл схем»). +3. CLI: --schemes путь.json; также подхватывается md2gost.schemes.json рядом с .md. + +Кэш includes из интернета +URL из includes (и !include https://… внутри .puml) при первом рендере +скачиваются. Индекс — md2gost.include-cache.json (только пары URL → файл). +Файлы лежат в папке include-cache/. Уже скачанные файлы не перезаписываются. +Повторный рендер без сети берёт путь из индекса. +«Сбросить кэш includes» в Настройки → Диаграммы или в «Шаблоны UML» удаляет индекс +и только файлы, перечисленные в нём. + +BPMN 2.0 (```bpmn) + Пул: Pool(alias, "Участник") { Lane(alias, "Роль") { … } } + События: Start, StartMessage, StartTimer, CatchMessage, ThrowMessage, + CatchTimer, CatchError, End, EndMessage, EndError, EndTerminate + Задачи: Task / UserTask / ServiceTask / ScriptTask / ManualTask / + SendTask / ReceiveTask / BusinessRuleTask / SubProcess + Шлюзы: XOR (Exclusive), AND (Parallel), OR (Inclusive), EventBased + Потоки: Flow, CondFlow(from, to, "условие"), DefaultFlow, MessageFlow + Данные: DataObject, DataStore, Annotation + Граница: BoundaryError/Timer/Message + Attach(task, event) + Sequence Flow только внутри пула; между пулами — MessageFlow. + +Чего нет + IDEF0 / DFD — PlantUML не умеет; вставляйте готовый PNG как Рисунок. + У BPMN нет «прилипания» boundary-события к кромке задачи (ставьте Attach) + и нет настоящей двойной окружности у intermediate (толщина линии). + +Рендер +UML: Java + plantuml.jar (локально) → иначе локальный Kroki → иначе kroki.io. +Mermaid: только Kroki (свой --kroki-url / localhost / kroki.io). +Формат: PNG по умолчанию (PlantUML ~scale 2 для чёткости, размер на странице как при 1; --diagram-scale); --diagram-format svg — PNG + SVG (svgBlip в Word 2016+). ++landscape у %подписи — альбомная страница под широкий рисунок/таблицу. +""" + +PROMPT_FILES = ( + ("generate-mirea-report.md", "МИРЭА / ГОСТ (курсовая, практика, ВКР)"), + ("generate-pis-custom-report.md", "ПИС — отчёт по практическим работам"), +) + + +def prompt_search_dirs() -> list[Path]: + dirs: list[Path] = [] + here = Path(package_dir()) + dirs.append(here / "prompts") + dirs.append(here.parent / "prompts") + if getattr(sys, "frozen", False): + mei = getattr(sys, "_MEIPASS", None) + if mei: + dirs.append(Path(mei) / "prompts") + dirs.append(Path(sys.executable).resolve().parent / "prompts") + seen: set[str] = set() + out: list[Path] = [] + for path in dirs: + key = str(path.resolve()) if path.exists() else str(path) + if key in seen: + continue + seen.add(key) + out.append(path) + return out + + +def load_prompt_catalog() -> list[tuple[str, str, str]]: + """Return list of (filename, title, text). Missing files are skipped.""" + catalog: list[tuple[str, str, str]] = [] + dirs = prompt_search_dirs() + for name, title in PROMPT_FILES: + text = None + for folder in dirs: + candidate = folder / name + if candidate.is_file(): + text = candidate.read_text(encoding="utf-8") + break + if text: + catalog.append((name, title, text)) + return catalog diff --git a/md2gost/layout_tracker.py b/md2gost/layout_tracker.py index 57aec08..4290516 100644 --- a/md2gost/layout_tracker.py +++ b/md2gost/layout_tracker.py @@ -51,3 +51,13 @@ class LayoutTracker: def new_page(self): self._state.new_page() + + def set_page_size(self, max_height: Length, max_width: Length) -> None: + """Switch usable area (e.g. portrait ↔ landscape) and start a fresh page.""" + self._state.max_height = max_height + self._state.max_width = max_width + # Align to a page boundary under the *new* geometry. + rem = self._state.remaining_page_height + if rem != max_height: + self._state.add_height(rem) + self._is_new_page = True diff --git a/md2gost/page_fill_check.py b/md2gost/page_fill_check.py new file mode 100644 index 0000000..0708eab --- /dev/null +++ b/md2gost/page_fill_check.py @@ -0,0 +1,226 @@ +"""Post-conversion heuristic: half-empty pages inside an unfinished section. + +Requires Windows + Microsoft Word + pywin32. Findings are ALWAYS heuristic — +false positives are expected (large figures, end of subsection, odd breaks). +Never treat as --strict errors. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +# Unused fraction of the text area below the last content on the page. +EMPTY_FRACTION_THRESHOLD = 0.40 + +FRONT_MATTER_HINTS = ( + "СОДЕРЖАНИЕ", + "ТИТУЛ", + "ЗАДАНИЕ", +) + + +@dataclass +class PageMetric: + """Synthetic / measured metrics for one page (CI-friendly).""" + + page_index: int # 1-based + content_bottom_frac: float # 0..1, how far down the text area content reaches + section_title: str + next_section_title: str | None + is_landscape: bool = False + is_last_doc_page: bool = False + mostly_figure_or_table: bool = False + + +@dataclass +class PageFillIssue: + page_index: int + message: str + severity: str = "heuristic" + + +def evaluate_page_fill( + pages: list[PageMetric], + *, + empty_threshold: float = EMPTY_FRACTION_THRESHOLD, +) -> list[PageFillIssue]: + """Pure heuristic over page metrics (no Word). Safe for unit tests.""" + issues: list[PageFillIssue] = [] + for i, page in enumerate(pages): + if page.is_landscape: + continue + if page.mostly_figure_or_table: + continue + if page.is_last_doc_page: + continue + title_u = (page.section_title or "").upper() + if any(h in title_u for h in FRONT_MATTER_HINTS): + continue + # Last page of this section (next page starts a different H1) + if page.next_section_title and page.next_section_title != page.section_title: + continue + empty_frac = 1.0 - page.content_bottom_frac + if empty_frac <= empty_threshold: + continue + # Same section continues on the next page + if i + 1 >= len(pages): + continue + nxt = pages[i + 1] + if nxt.section_title != page.section_title: + continue + issues.append(PageFillIssue( + page_index=page.page_index, + message=( + f"[heuristic] page.fill: на стр. {page.page_index} пустой низ " + f"~{empty_frac:.0%} полосы, а раздел «{page.section_title}» " + f"продолжается на следующей странице. " + f"Проверка эвристическая — возможны ложные срабатывания " + f"(крупный рисунок, конец пункта, нестандартный разрыв)." + ), + )) + return issues + + +def check_docx_page_fill(path: str) -> tuple[list[PageFillIssue], str]: + """ + Open DOCX in Word, repaginate, collect metrics, evaluate. + + Returns (issues, status_message). On missing Word/pywin32 returns ([], reason). + """ + try: + import win32com.client # type: ignore + except ImportError: + return [], ( + "Проверка вёрстки пропущена: нужен пакет pywin32 " + "(pip install pywin32) и Microsoft Word." + ) + + word = None + doc = None + try: + word = win32com.client.DispatchEx("Word.Application") + word.Visible = False + word.DisplayAlerts = 0 + abs_path = str(path) + doc = word.Documents.Open(abs_path, ReadOnly=True) + doc.Repaginate() + + pages_metrics: list[PageMetric] = [] + page_count = int(doc.ComputeStatistics(2)) # wdStatisticPages + for page_no in range(1, page_count + 1): + try: + metric = _measure_page(doc, page_no, page_count) + except Exception: + continue + if metric is not None: + pages_metrics.append(metric) + + issues = evaluate_page_fill(pages_metrics) + if not issues: + return [], ( + "Проверка вёрстки (Word): замечаний по полупустым страницам нет " + "(эвристика; возможны пропуски)." + ) + return issues, ( + f"Проверка вёрстки (Word): найдено замечаний — {len(issues)} " + f"(все эвристические, могут быть ложными)." + ) + except Exception as exc: + return [], f"Проверка вёрстки пропущена: не удалось открыть Word ({exc})." + finally: + try: + if doc is not None: + doc.Close(False) + except Exception: + pass + try: + if word is not None: + word.Quit() + except Exception: + pass + + +def _measure_page(doc, page_no: int, page_count: int) -> PageMetric | None: + """Best-effort measurement via Word COM selection / page setup.""" + selection = doc.Application.Selection + selection.GoTo(What=1, Which=1, Count=page_no) # wdGoToPage, wdGoToAbsolute + + section = selection.Sections(1) + ps = section.PageSetup + is_landscape = bool(int(ps.Orientation) == 1) # wdOrientLandscape + + page_h = float(ps.PageHeight) + top = float(ps.TopMargin) + bottom = float(ps.BottomMargin) + text_h = max(page_h - top - bottom, 1.0) + + start = int(selection.Start) + if page_no < page_count: + selection.GoTo(What=1, Which=1, Count=page_no + 1) + end = int(selection.Start) - 1 + else: + end = int(doc.Content.End) + if end < start: + end = start + rng = doc.Range(start, end) + + try: + # wdVerticalPositionRelativeToPage = 6 + vpos = float(rng.Information(6)) + content_bottom = max(0.0, min(1.0, (vpos - top) / text_h)) + except Exception: + content_bottom = 1.0 + + section_title = _heading_near(doc, start) + next_title = None + if page_no < page_count: + try: + selection.GoTo(What=1, Which=1, Count=page_no + 1) + next_title = _heading_near(doc, int(selection.Start)) + except Exception: + next_title = None + + mostly_object = False + try: + text_len = len((rng.Text or "").strip()) + if rng.Tables.Count >= 1 and text_len < 80: + mostly_object = True + if rng.InlineShapes.Count >= 1 and text_len < 80: + mostly_object = True + except Exception: + pass + + return PageMetric( + page_index=page_no, + content_bottom_frac=content_bottom, + section_title=section_title or "", + next_section_title=next_title, + is_landscape=is_landscape, + is_last_doc_page=(page_no == page_count), + mostly_figure_or_table=mostly_object, + ) + + +def _heading_near(doc, pos: int) -> str: + """Walk backwards for nearest Heading 1 style paragraph.""" + try: + p = doc.Range(pos, pos).Paragraphs(1) + for _ in range(80): + style = str(p.Style) + if "Heading 1" in style or "Заголовок 1" in style: + return (p.Range.Text or "").strip().replace("\r", "") + if p.Range.Start <= 1: + break + p = p.Previous() + if p is None: + break + except Exception: + pass + return "" + + +def format_page_fill_report(issues: list[PageFillIssue], status: str) -> str: + lines = [status] + for i in issues: + lines.append(f" [{i.severity}] page:{i.page_index}: {i.message}") + return "\n".join(lines) diff --git a/md2gost/page_geometry.py b/md2gost/page_geometry.py new file mode 100644 index 0000000..2e68ace --- /dev/null +++ b/md2gost/page_geometry.py @@ -0,0 +1,142 @@ +"""A4 page geometry helpers for portrait / landscape sections.""" + +from __future__ import annotations + +from docx.enum.section import WD_ORIENT +from docx.enum.text import WD_PARAGRAPH_ALIGNMENT +from docx.oxml.ns import qn +from docx.shared import Mm, Pt + +from .util import create_element + +# GOST-like A4 margins (same as styles._apply_common_page_and_body) +MARGIN_LEFT = Mm(30) +MARGIN_RIGHT = Mm(10) +MARGIN_TOP = Mm(20) +MARGIN_BOTTOM = Mm(20) + +A4_SHORT = Mm(210) +A4_LONG = Mm(297) + + +def is_landscape_section(section) -> bool: + """True if section is (or should be treated as) landscape A4.""" + try: + if section.orientation == WD_ORIENT.LANDSCAPE: + return True + except Exception: + pass + return int(section.page_width) > int(section.page_height) + + +def apply_section_geometry(section, *, landscape: bool) -> None: + """ + Set orientation and page size. + + Set orientation first (python-docx may swap w/h on change), then force A4 dims. + """ + target = WD_ORIENT.LANDSCAPE if landscape else WD_ORIENT.PORTRAIT + try: + section.orientation = target + except Exception: + pass + + if landscape: + section.page_width = A4_LONG + section.page_height = A4_SHORT + else: + section.page_width = A4_SHORT + section.page_height = A4_LONG + + section.left_margin = MARGIN_LEFT + section.right_margin = MARGIN_RIGHT + section.top_margin = MARGIN_TOP + section.bottom_margin = MARGIN_BOTTOM + + # Explicit orient on pgSz for Word. + pg_sz = section._sectPr.find(qn("w:pgSz")) + if pg_sz is None: + pg_sz = section._sectPr._add_pgSz() + if landscape: + pg_sz.set(qn("w:orient"), "landscape") + # Re-assert after XML tweak (some builds reshuffle). + section.page_width = A4_LONG + section.page_height = A4_SHORT + else: + if pg_sz.get(qn("w:orient")) is not None: + del pg_sz.attrib[qn("w:orient")] + section.page_width = A4_SHORT + section.page_height = A4_LONG + + # Vertical align: center on landscape (figures/tables only); top on portrait + sect_pr = section._sectPr + for el in list(sect_pr.findall(qn("w:vAlign"))): + sect_pr.remove(el) + if landscape: + sect_pr.append(create_element("w:vAlign", {"w:val": "center"})) + + +def content_size(*, landscape: bool) -> tuple: + """Return (max_height, max_width) usable content area for LayoutTracker.""" + if landscape: + page_w, page_h = A4_LONG, A4_SHORT + else: + page_w, page_h = A4_SHORT, A4_LONG + max_height = page_h - MARGIN_TOP - MARGIN_BOTTOM + max_width = page_w - MARGIN_LEFT - MARGIN_RIGHT + return max_height, max_width + + +def apply_centered_page_footer(section) -> None: + """Centered PAGE field, Times New Roman 12 (body section style).""" + footer = section.footer + footer.is_linked_to_previous = False + if not footer.paragraphs: + footer.add_paragraph() + paragraph = footer.paragraphs[0] + paragraph.clear() + paragraph.paragraph_format.first_line_indent = 0 + paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + run = paragraph.add_run() + run.font.name = "Times New Roman" + run.font.size = Pt(12) + paragraph._p.append(create_element("w:fldSimple", { + "w:instr": "PAGE \\* MERGEFORMAT", + })) + + +def clear_section_footer(section) -> None: + """Empty footer (no PAGE) — for title / assignment / TOC sections.""" + footer = section.footer + footer.is_linked_to_previous = False + for p in footer.paragraphs: + p.clear() + if not footer.paragraphs: + footer.add_paragraph() + + +def ensure_continuous_page_numbers(document) -> None: + """ + After docxcompose of title/assignment: no PAGE on early sections, + continuous numbering (do not restart at 1 on body section). + """ + from docx.oxml.ns import qn + + sections = list(document.sections) + if not sections: + return + + # Heuristic: sections before the first that already has a PAGE field / + # or first N-1 if title was prepended — clear footer on all but keep + # continuous pgNumType. Body renderer already puts PAGE on body section; + # after compose, early sections may inherit footers — clear empty ones + # that belong to front matter (no Heading-like body content is hard to + # detect), so: clear footer on every section that has no PAGE field, and + # strip w:pgNumType start=1 everywhere. + for section in sections: + sect_pr = section._sectPr + for child in list(sect_pr): + if child.tag == qn("w:pgNumType"): + # Keep continuous: remove start attribute if present + if child.get(qn("w:start")) is not None: + del child.attrib[qn("w:start")] diff --git a/md2gost/parser_.py b/md2gost/parser_.py index a4a9421..832c26a 100644 --- a/md2gost/parser_.py +++ b/md2gost/parser_.py @@ -1,7 +1,7 @@ from collections.abc import Generator from docx import Document -from marko.block import BlankLine +from marko.block import BlankLine, ThematicBreak from .extended_markdown import markdown, Caption from .renderable.caption import CaptionInfo @@ -12,13 +12,14 @@ from .renderable_factory import RenderableFactory class Parser: """Parses given markdown string and returns Renderable elements""" - def __init__(self, document: Document, text: str): + def __init__(self, document: Document, text: str, hr_pagebreak: bool = False): self._document = document self._parsed = markdown.parse(text) self._caption_info: CaptionInfo | None = None + self._hr_pagebreak = hr_pagebreak def parse(self) -> Generator[Renderable, None, None]: - factory = RenderableFactory(self._document._body) + factory = RenderableFactory(self._document._body, hr_pagebreak=self._hr_pagebreak) for marko_element in self._parsed.children: if isinstance(marko_element, BlankLine): @@ -29,8 +30,12 @@ class Parser: marko_element.unique_name, marko_element.text, getattr(marko_element, "with_listing", False), + getattr(marko_element, "landscape", False), ) continue + if isinstance(marko_element, ThematicBreak) and not self._hr_pagebreak: + continue + yield factory.create(marko_element, self._caption_info) self._caption_info = None diff --git a/md2gost/pipeline.py b/md2gost/pipeline.py new file mode 100644 index 0000000..d276609 --- /dev/null +++ b/md2gost/pipeline.py @@ -0,0 +1,303 @@ +"""Shared Markdown → DOCX pipeline for CLI and GUI.""" + +from __future__ import annotations + +import logging +import os +import platform +import subprocess +import traceback +from dataclasses import dataclass +from getpass import getuser +from typing import Callable + +from docx import Document + +from . import package_dir +from .checker import check_markdown, format_report +from .converter import Converter +from .profiles import ( + DEFAULT_HEADING_NUMBERING, + DEFAULT_TABLE_CONTINUATION, + DEFAULT_LISTING_CONTINUATION, + DEFAULT_TOC_MODE, + get_profile, +) + +LogFn = Callable[[str], None] + + +@dataclass +class ConvertRequest: + filename: str = "" + output: str | None = None + template: str | None = None + doc_type: str = "practice" + heading_numbering: str = DEFAULT_HEADING_NUMBERING + toc_mode: str = DEFAULT_TOC_MODE + table_continuation: str = DEFAULT_TABLE_CONTINUATION + listing_continuation: str = DEFAULT_LISTING_CONTINUATION + emdash_to_hyphen: bool = False + hr_pagebreak: bool = False + title: str | None = None + assignment: str | None = None + check: bool = False + check_only: bool = False + strict: bool = False + syntax_highlighting: bool = False + plantuml_jar: str | None = None + kroki_url: str | None = None + diagram_fallback: str = "remote" + diagram_format: str = "png" + diagram_scale: float = 2.0 + schemes_path: str | None = None + debug: bool = False + open_when_done: bool = False + check_pages: bool = False + table_repeat_header: bool = False + + +@dataclass +class ConvertResult: + ok: bool + exit_code: int = 0 + output_path: str | None = None + check_report: str = "" + message: str = "" + + +def default_output_path(filename: str) -> str: + base = os.path.basename(filename) + if base.lower().endswith(".md"): + base = base[:-3] + return os.path.join(os.path.dirname(os.path.abspath(filename)), base + ".docx") + + +def default_template_path() -> str: + return os.path.join(package_dir(), "Template.docx") + + +def open_document(path: str) -> None: + system = platform.system() + if system == "Darwin": + subprocess.call(("open", path)) + elif system == "Windows": + os.startfile(path) # type: ignore[attr-defined] + else: + subprocess.call(("xdg-open", path)) + + +def _fix_front_matter_after_compose(document, *, had_title: bool, had_assignment: bool) -> None: + """Clear PAGE on title/assignment sections; keep continuous page numbers.""" + from .page_geometry import clear_section_footer, ensure_continuous_page_numbers + + ensure_continuous_page_numbers(document) + n_front = int(bool(had_title)) + int(bool(had_assignment)) + for i, section in enumerate(document.sections): + if i < n_front: + clear_section_footer(section) + else: + break + ensure_continuous_page_numbers(document) + + +def convert(req: ConvertRequest, log: LogFn | None = None) -> ConvertResult: + """Run check and/or conversion. Does not re-raise; errors go into ConvertResult.""" + emit: LogFn = log if log is not None else print + + def fail(code: int, message: str, check_report: str = "") -> ConvertResult: + emit(message) + return ConvertResult(False, code, check_report=check_report, message=message) + + filename = (req.filename or "").strip() + # Standalone page-fill check on an existing DOCX + if req.check_pages and filename.lower().endswith(".docx") and not filename.lower().endswith(".md"): + from .page_fill_check import check_docx_page_fill, format_page_fill_report + issues, status = check_docx_page_fill(filename) + report = format_page_fill_report(issues, status) + emit(report) + return ConvertResult(True, 0, check_report=report, message=report) + + if not filename: + return fail(2, "Укажите исходный markdown-файл") + if not filename.lower().endswith(".md"): + return fail(1, "Исходный файл должен быть в формате .md") + if not os.path.isfile(filename): + return fail(2, f"Файл не найден: {filename}") + + output = (req.output or "").strip() or None + if output and not output.lower().endswith(".docx"): + return fail(1, "Выходной файл должен быть в формате .docx") + + for label, path in (("титул", req.title), ("задание", req.assignment), ("шаблон", req.template)): + if path and not os.path.isfile(path): + return fail(2, f"Файл ({label}) не найден: {path}") + + if req.syntax_highlighting: + os.environ["SYNTAX_HIGHLIGHTING"] = "1" + else: + os.environ.pop("SYNTAX_HIGHLIGHTING", None) + + from .diagram_renderer import configure_diagrams + + md_dir = os.path.dirname(os.path.abspath(filename)) or "." + configure_diagrams( + plantuml_jar=req.plantuml_jar or None, + kroki_url=req.kroki_url or None, + fallback=req.diagram_fallback, + schemes_path=req.schemes_path or None, + md_dir=md_dir, + diagram_format=req.diagram_format if req.diagram_format in ("png", "svg") else "png", + diagram_scale=req.diagram_scale, + ) + + os.environ["WORKING_DIR"] = md_dir + + with open(filename, encoding="utf-8") as f: + md_text = f.read() + + check_report = "" + if req.check or req.check_only: + issues = check_markdown( + md_text, + req.doc_type, + table_continuation=req.table_continuation, + listing_continuation=req.listing_continuation, + ) + check_report = format_report(issues) + emit(check_report) + errors = [i for i in issues if i.severity == "error"] + if req.strict and errors: + return fail(1, "Проверка ТЗ: есть ошибки (--strict)", check_report) + if req.check_only: + return ConvertResult( + True, 0, check_report=check_report, + message=check_report, + ) + + if not output: + output = default_output_path(filename) + + template = (req.template or "").strip() or default_template_path() + + handler = _CallbackLogHandler(emit) + handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) + root_log = logging.getLogger("md2gost") + root_log.addHandler(handler) + attached_diag = logging.getLogger("md2gost.diagram_renderer") + prev_diag_level = attached_diag.level + attached_diag.setLevel(logging.INFO) + attached_diag.addHandler(handler) + + try: + converter = Converter( + filename, output, template, req.debug, + doc_type=req.doc_type, + heading_numbering=req.heading_numbering, + emdash_to_hyphen=req.emdash_to_hyphen, + toc_mode=req.toc_mode, + table_continuation=req.table_continuation, + listing_continuation=req.listing_continuation, + hr_pagebreak=req.hr_pagebreak, + ) + converter.convert() + document = converter.document + + if req.title or req.assignment: + try: + from docxcompose.composer import Composer + except ImportError: + return fail(3, "Для титула/задания нужен пакет docxcompose", check_report) + from .styles import apply_document_styles + + shell = Document(template) + apply_document_styles(shell, get_profile(req.doc_type).style_preset) + body = shell.element.body + for child in list(body): + if not child.tag.endswith("}sectPr"): + body.remove(child) + + composer = Composer(shell) + if req.title: + composer.append(Document(req.title)) + shell.add_page_break() + if req.assignment: + composer.append(Document(req.assignment)) + shell.add_page_break() + composer.append(document) + document = composer.doc + apply_document_styles(document, get_profile(req.doc_type).style_preset) + _fix_front_matter_after_compose( + document, + had_title=bool(req.title), + had_assignment=bool(req.assignment), + ) + + document.core_properties.author = getuser() + document.core_properties.comments = "Создано при помощи md2gost (ТЗ МИРЭА)" + document.save(output) + except Exception as exc: + emit(traceback.format_exc()) + return ConvertResult( + False, 1, check_report=check_report, + message=f"Ошибка конвертации: {exc}", + ) + finally: + root_log.removeHandler(handler) + attached_diag.removeHandler(handler) + attached_diag.setLevel(prev_diag_level) + + abs_out = os.path.abspath(output) + emit(f"Generated document: {abs_out}") + + if req.table_continuation == "word" or req.listing_continuation == "word": + from .word_fix import fix_continuations + fix = fix_continuations( + abs_out, + tables=(req.table_continuation == "word"), + listings=(req.listing_continuation == "word"), + repeat_header=bool(req.table_repeat_header), + ) + emit(fix.message) + if fix.details: + for line in fix.details: + emit(line) + if not fix.ok: + check_report = ( + (check_report + "\n" + fix.message).strip() if check_report else fix.message + ) + + if req.check_pages: + from .page_fill_check import check_docx_page_fill, format_page_fill_report + issues, status = check_docx_page_fill(abs_out) + page_report = format_page_fill_report(issues, status) + emit(page_report) + check_report = (check_report + "\n" + page_report).strip() if check_report else page_report + + if req.debug or req.open_when_done: + try: + open_document(abs_out) + except Exception as exc: + emit(f"Не удалось открыть файл: {exc}") + + return ConvertResult( + True, 0, output_path=abs_out, check_report=check_report, + message=f"Generated document: {abs_out}", + ) + + +class _CallbackLogHandler(logging.Handler): + def __init__(self, emit: LogFn): + super().__init__() + self._emit = emit + + def emit(self, record: logging.LogRecord) -> None: + try: + self._emit(self.format(record)) + except Exception: + pass + + +def should_launch_gui(filename: str | None, gui_flag: bool) -> bool: + """GUI if --gui, or if no input file was given (interactive default).""" + return bool(gui_flag or not filename) diff --git a/md2gost/profiles.py b/md2gost/profiles.py index 3e78e7c..ebdb16e 100644 --- a/md2gost/profiles.py +++ b/md2gost/profiles.py @@ -19,12 +19,17 @@ DEFAULT_HEADING_NUMBERING = "manual" TOC_MODES = ("manual", "native") DEFAULT_TOC_MODE = "native" -# off — одна таблица Word, пагинацию делает Word (по умолчанию; без автоподписи) +# off — одна таблица Word, пагинацию делает Word (без автоподписи) # legacy — режем по оценке высоты + «Продолжение…» с page_break_before # caption — режем по оценке + явный PageBreak + «Продолжение…» # soft — синоним off (автоподпись mid-page без точной вёрстки Word невозможна в DOCX) -TABLE_CONTINUATION_MODES = ("off", "legacy", "caption", "soft") -DEFAULT_TABLE_CONTINUATION = "off" +# word — как off при рендере; после save Word COM режет по реальной пагинации + «Продолжение…» +TABLE_CONTINUATION_MODES = ("off", "legacy", "caption", "soft", "word") +DEFAULT_TABLE_CONTINUATION = "word" + +# Same modes as tables (word = post-process via Word COM after save). +LISTING_CONTINUATION_MODES = TABLE_CONTINUATION_MODES +DEFAULT_LISTING_CONTINUATION = DEFAULT_TABLE_CONTINUATION # section — Рисунок 1.1 / 2.1 (ТЗ МИРЭА); continuous — Рисунок 1, 2, 3 (ПИС) NUMBERING_SCOPES = ("section", "continuous") diff --git a/md2gost/renderable/caption.py b/md2gost/renderable/caption.py index 2116194..3825caf 100644 --- a/md2gost/renderable/caption.py +++ b/md2gost/renderable/caption.py @@ -16,7 +16,7 @@ from ..util import create_element # Map category → Word style name CAPTION_STYLES = { "Рисунок": "Caption Figure", - "Таблица": "Caption Table", + "Таблица": "Название таблицы", "Листинг": "Caption Listing", } @@ -26,6 +26,7 @@ class CaptionInfo: unique_name: str | None text: str | None with_listing: bool = False + landscape: bool = False class Caption(Renderable): @@ -40,7 +41,10 @@ class Caption(Renderable): try: self._docx_paragraph.style = style_name except KeyError: - self._docx_paragraph.style = "Caption" + try: + self._docx_paragraph.style = "Caption Table" if category == "Таблица" else "Caption" + except KeyError: + self._docx_paragraph.style = "Caption" # Format: «Рисунок 1.1 — Название» (or « - » if --emdash-to-hyphen) from ..profiles import dash_separator diff --git a/md2gost/renderable/diagram.py b/md2gost/renderable/diagram.py index db2a023..4cfb827 100644 --- a/md2gost/renderable/diagram.py +++ b/md2gost/renderable/diagram.py @@ -14,7 +14,7 @@ from ..rendered_info import RenderedInfo class DiagramFigure(Renderable, RequiresNumbering): - """UML/BPMN/C4 fence → PNG figure (+ optional source listing).""" + """UML / Mermaid / scheme fence → figure (+ optional source listing).""" def __init__( self, @@ -34,6 +34,7 @@ class DiagramFigure(Renderable, RequiresNumbering): ) self._number = None self._listing_number = None + self.landscape = bool(caption_info and caption_info.landscape) if caption_info and caption_info.unique_name: self.unique_name = caption_info.unique_name @@ -56,8 +57,14 @@ class DiagramFigure(Renderable, RequiresNumbering): def render(self, previous_rendered: RenderedInfo, layout_state: LayoutState) \ -> Generator[RenderedInfo, None, None]: - png_path = render_diagram(self._lang, self._source) - image = Image(self._parent, png_path, self._caption_info) + result = render_diagram(self._lang, self._source) + image = Image( + self._parent, + result.png_path, + self._caption_info, + svg_path=result.svg_path, + pixel_scale=result.pixel_scale, + ) if self._number is not None: image.set_number(self._number) yield from image.render(previous_rendered, layout_state) diff --git a/md2gost/renderable/heading.py b/md2gost/renderable/heading.py index 629c498..aa464f3 100644 --- a/md2gost/renderable/heading.py +++ b/md2gost/renderable/heading.py @@ -2,7 +2,6 @@ from copy import copy from typing import Generator import re -from docx.enum.text import WD_PARAGRAPH_ALIGNMENT from docx.text.paragraph import Paragraph as DocxParagraph from docx.shared import Parented, Length @@ -35,7 +34,8 @@ class Heading(Paragraph): if not numbered: self._remove_numbering() - self._docx_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + # Alignment decided later in Renderer: only СОДЕРЖАНИЕ / СПИСОК centered. + # ВВЕДЕНИЕ / ЗАКЛЮЧЕНИЕ / ПРИЛОЖЕНИЯ stay left like Heading 1 (1.25 cm indent). elif numbering_mode == "manual": # Digits already in markdown text — kill Word list numbering to avoid "1 1 …" self._remove_numbering() diff --git a/md2gost/renderable/image.py b/md2gost/renderable/image.py index 2c1db73..e1e0252 100644 --- a/md2gost/renderable/image.py +++ b/md2gost/renderable/image.py @@ -7,20 +7,32 @@ import os.path import requests from docx.enum.text import WD_PARAGRAPH_ALIGNMENT -from docx.shared import Parented, Length +from docx.shared import Parented, Length, Mm from docx.text.paragraph import Paragraph from .caption import Caption, CaptionInfo from .renderable import Renderable from .requires_numbering import RequiresNumbering +from ..docx_svg import attach_svg_blip from ..layout_tracker import LayoutState from ..rendered_info import RenderedInfo from ..sub_renderable import SubRenderable from ..util import create_element +# Leave room under the figure so «Рисунок N — …» stays on the same page. +_CAPTION_RESERVE = Mm(12) + class Image(Renderable, RequiresNumbering): - def __init__(self, parent: Parented, path: str, caption_info: CaptionInfo | None = None): + def __init__( + self, + parent: Parented, + path: str, + caption_info: CaptionInfo | None = None, + svg_path: str | None = None, + *, + pixel_scale: float = 1.0, + ): super().__init__("Рисунок") self._parent = parent self._caption_info = caption_info @@ -31,6 +43,8 @@ class Image(Renderable, RequiresNumbering): self._docx_paragraph.paragraph_format.line_spacing = 1 self._docx_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER self._invalid = False + self._native_width: Length | None = None + self._native_height: Length | None = None run = self._docx_paragraph.add_run() @@ -47,36 +61,82 @@ class Image(Renderable, RequiresNumbering): except FileNotFoundError: logging.warning(f"Invalid image path: {path}, skipping...") self._invalid = True + self._image = None + + # High-res PlantUML/Kroki PNG: keep on-page size as if scale were 1. + if not self._invalid and pixel_scale and pixel_scale > 1: + self._image.width = Length(int(self._image.width / pixel_scale)) + self._image.height = Length(int(self._image.height / pixel_scale)) + + if not self._invalid: + self._native_width = Length(int(self._image.width)) + self._native_height = Length(int(self._image.height)) + + if not self._invalid and svg_path: + try: + attach_svg_blip(run, self._image, svg_path) + except Exception as exc: + logging.warning("SVG blip attach failed (%s): %s", svg_path, exc) self._number = None + self.landscape = bool(caption_info and caption_info.landscape) if caption_info and caption_info.unique_name: self.unique_name = caption_info.unique_name + # Keep figure + caption together across Word pagination. + if not self._invalid and caption_info is not None: + self._docx_paragraph.paragraph_format.keep_with_next = True + def set_number(self, number: str): self._number = number + def _reset_native_size(self) -> None: + if self._image is None or self._native_width is None or self._native_height is None: + return + self._image.width = Length(int(self._native_width)) + self._image.height = Length(int(self._native_height)) + def render(self, previous_rendered: RenderedInfo, layout_state: LayoutState)\ -> Generator[RenderedInfo | SubRenderable, None, None]: if self._invalid: yield from [] return + # Re-fit from native size each time (Paragraph may have measured us earlier). + self._reset_native_size() + + has_caption = self._caption_info is not None + caption_reserve = _CAPTION_RESERVE if has_caption else Length(0) + max_w = layout_state.max_width + max_h = Length(max(0, int(layout_state.max_height) - int(caption_reserve))) + # limit width - if self._image.width > layout_state.max_width: + if self._image.width > max_w: height_by_width = self._image.height / self._image.width - self._image.width = layout_state.max_width + self._image.width = max_w self._image.height = Length(self._image.width * height_by_width) - # limit height - if self._image.height > layout_state.max_height: + # limit height (leave room for caption on the same page) + if self._image.height > max_h: width_by_height = self._image.width / self._image.height - self._image.height = layout_state.max_height + self._image.height = max_h self._image.width = Length(self._image.height * width_by_height) height = self._image.height - - if layout_state.remaining_page_height < height: - height += layout_state.remaining_page_height + need = Length(int(height) + int(caption_reserve)) + remaining = layout_state.remaining_page_height + if remaining < need: + if self.landscape: + # Already on a fresh landscape section — shrink instead of soft page-break + # (soft break + section break = empty landscape page before the figure). + avail = Length(max(0, int(remaining) - int(caption_reserve))) + if avail > 0 and self._image.height > avail: + ratio = int(avail) / int(self._image.height) + self._image.height = avail + self._image.width = Length(int(self._image.width * ratio)) + height = self._image.height + else: + height = Length(int(height) + int(remaining)) yield (rendered_image := RenderedInfo(self._docx_paragraph, Length(height))) diff --git a/md2gost/renderable/list.py b/md2gost/renderable/list.py index dd626ea..ad520d0 100644 --- a/md2gost/renderable/list.py +++ b/md2gost/renderable/list.py @@ -85,6 +85,10 @@ class List(Renderable): if self._paragraphs: self._paragraphs[-1]._docx_paragraph.paragraph_format.space_after = self._last_paragraph_space_after + if getattr(self, "_space_before_mm6", False) and self._paragraphs: + from docx.shared import Mm + self._paragraphs[0]._docx_paragraph.paragraph_format.space_before = Mm(6) + for paragraph in self._paragraphs: for x in paragraph.render(previous_rendered, copy(layout_state)): layout_state.add_height(x.height) diff --git a/md2gost/renderable/listing.py b/md2gost/renderable/listing.py index 11b3a27..8cc187c 100644 --- a/md2gost/renderable/listing.py +++ b/md2gost/renderable/listing.py @@ -2,23 +2,25 @@ from copy import copy import os from typing import Generator, Callable -from docx.oxml import CT_Tbl from docx.shared import Length, Pt, RGBColor, Twips -from docx.table import Table from pygments import highlight from pygments.formatter import Formatter from pygments.lexers import get_lexer_by_name from .caption import Caption, CaptionInfo +from .page_break import PageBreak from .paragraph import Paragraph from .renderable import Renderable from .requires_numbering import RequiresNumbering -from ..docx_elements import create_table, _twips +from ..docx_elements import create_table, create_table_row, create_table_cell, set_table_box_borders, _twips from ..layout_tracker import LayoutState +from ..profiles import DEFAULT_LISTING_CONTINUATION, LISTING_CONTINUATION_MODES from ..rendered_info import RenderedInfo from ..sub_renderable import SubRenderable +_WORD_PAGED_MODES = frozenset({"off", "soft", "word"}) + class DocxParagraphPygmentsFormatter(Formatter): def __init__(self, paragraphs: list[Paragraph], creator: Callable[[], Paragraph], **options): @@ -55,13 +57,14 @@ class Listing(Renderable, RequiresNumbering): self._caption_info = caption_info self._language = language self._parent = parent + self._continuation_mode = DEFAULT_LISTING_CONTINUATION self.paragraphs: list[Paragraph] = [] self._number = None if caption_info and caption_info.unique_name: self.unique_name = caption_info.unique_name def _create_table(self, parent, width: Length): - # todo: style inheritance + # Kept for tests / callers that still expect the helper; render uses multi-row. left_margin = Twips(int( parent.part.styles["Normal Table"]._element.xpath("w:tblPr/w:tblCellMar/w:left")[0].attrib[ "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}w"])) @@ -94,6 +97,51 @@ class Listing(Renderable, RequiresNumbering): def set_number(self, number: str): self._number = number + def set_continuation_mode(self, mode: str) -> None: + if mode not in LISTING_CONTINUATION_MODES: + raise ValueError( + f"listing continuation must be one of {LISTING_CONTINUATION_MODES}, got {mode!r}" + ) + self._continuation_mode = mode + + def _should_split_fragment( + self, line_height, layout_state: LayoutState, lines_in_fragment: int + ) -> bool: + if self._continuation_mode in _WORD_PAGED_MODES: + return False + if lines_in_fragment == 0: + return False + return line_height > layout_state.remaining_page_height + + def _make_continuation_paragraph(self) -> Paragraph: + continuation_paragraph = Paragraph(self._parent) + continuation_paragraph.add_run(f"Продолжение Листинга {self._number}") + continuation_paragraph.style = "Caption Listing" + continuation_paragraph.first_line_indent = 0 + return continuation_paragraph + + def _emit_page_break_and_optional_caption( + self, layout_state: LayoutState + ) -> Generator[RenderedInfo, None, None]: + mode = self._continuation_mode + if mode == "legacy": + continuation_paragraph = self._make_continuation_paragraph() + continuation_paragraph.page_break_before = True + info = next(continuation_paragraph.render(None, copy(layout_state))) + layout_state.add_height(info.height) + yield info + return + + page_break_info = next(PageBreak(self._parent).render(None, layout_state)) + layout_state.add_height(page_break_info.height) + yield page_break_info + + continuation_paragraph = self._make_continuation_paragraph() + continuation_paragraph._docx_paragraph.paragraph_format.keep_with_next = True + info = next(continuation_paragraph.render(None, copy(layout_state))) + layout_state.add_height(info.height) + yield info + def render(self, previous_rendered: RenderedInfo, layout_state: LayoutState)\ -> Generator[RenderedInfo | SubRenderable, None, None]: caption_rendered_infos = list( @@ -103,54 +151,65 @@ class Listing(Renderable, RequiresNumbering): layout_state.add_height(sum([info.height for info in caption_rendered_infos])) yield from caption_rendered_infos - table = self._create_table(self._parent, layout_state.max_width) + # One code line = one table row so Word can Split at real page breaks (mode word). + # Outer box only — no grid lines between lines (looks like text in a frame). + table = create_table(self._parent, 0, 1, self._listing_width(layout_state)) + set_table_box_borders(table) previous = None table_height = Pt(1) # table borders, 4 eights of point for each border + lines_in_fragment = 0 + col_w = self._listing_width(layout_state) - # if first line doesn't fit move listing to the next page - paragraph_layout_state = copy(layout_state) - paragraph_layout_state.max_width -= LISTING_OFFSET - paragraph_rendered_info = next(self.paragraphs[0].render(previous, paragraph_layout_state)) - if paragraph_rendered_info.height + table_height > layout_state.remaining_page_height: - table_height += layout_state.remaining_page_height - layout_state.add_height(layout_state.remaining_page_height) + # legacy/caption: if first line doesn't fit, burn the rest of the page + if self.paragraphs and self._continuation_mode not in _WORD_PAGED_MODES: + paragraph_layout_state = copy(layout_state) + paragraph_layout_state.max_width -= LISTING_OFFSET + first = next(self.paragraphs[0].render(previous, paragraph_layout_state)) + if first.height + table_height > layout_state.remaining_page_height: + table_height += layout_state.remaining_page_height + layout_state.add_height(layout_state.remaining_page_height) for paragraph in self.paragraphs: paragraph_layout_state = copy(layout_state) paragraph_layout_state.max_width -= LISTING_OFFSET paragraph_rendered_info = next(paragraph.render(previous, paragraph_layout_state)) - if paragraph_rendered_info.height > layout_state.remaining_page_height: # todo add before after - table_rendered_info = RenderedInfo(table, table_height) - yield table_rendered_info - - table_height = Pt(1) # table borders, 4 eights of point for each border - - continuation_paragraph = Paragraph(self._parent) - continuation_paragraph.add_run(f"Продолжение Листинга {self._number}") - continuation_paragraph.style = "Caption Listing" - continuation_paragraph.first_line_indent = 0 - continuation_paragraph.page_break_before = True - - continuation_rendered_info = next( - continuation_paragraph.render(None, copy(layout_state))) - - layout_state.add_height(continuation_rendered_info.height) - yield continuation_rendered_info - - table = self._create_table(self._parent, layout_state.max_width) + if self._should_split_fragment( + paragraph_rendered_info.height, layout_state, lines_in_fragment + ): + yield RenderedInfo(table, table_height) + yield from self._emit_page_break_and_optional_caption(layout_state) + table_height = Pt(1) + table = create_table(self._parent, 0, 1, col_w) + set_table_box_borders(table) previous = None + lines_in_fragment = 0 paragraph_layout_state = copy(layout_state) paragraph_layout_state.max_width -= LISTING_OFFSET paragraph_rendered_info = next(paragraph.render(previous, paragraph_layout_state)) - table._cells[0]._element.append(paragraph_rendered_info.docx_element._element) + row = create_table_row(table, header=False) + cell = create_table_cell(row, col_w) + cell._element.append(paragraph_rendered_info.docx_element._element) + row._element.append(cell._element) + table._element.append(row._element) + layout_state.add_height(paragraph_rendered_info.height) table_height += paragraph_rendered_info.height + lines_in_fragment += 1 previous = paragraph_rendered_info yield RenderedInfo(table, table_height) + + def _listing_width(self, layout_state: LayoutState) -> Length: + left_margin = Twips(int( + self._parent.part.styles["Normal Table"]._element.xpath("w:tblPr/w:tblCellMar/w:left")[0].attrib[ + "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}w"])) + right_margin = Twips(int( + self._parent.part.styles["Normal Table"]._element.xpath("w:tblPr/w:tblCellMar/w:right")[0].attrib[ + "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}w"])) + return Twips(_twips(layout_state.max_width) + _twips(left_margin) + _twips(right_margin)) diff --git a/md2gost/renderable/paragraph.py b/md2gost/renderable/paragraph.py index 7b61008..bdb5049 100644 --- a/md2gost/renderable/paragraph.py +++ b/md2gost/renderable/paragraph.py @@ -89,14 +89,16 @@ class Paragraph(Renderable): return link def add_inline_equation(self, formula: str): - # omml = inline_omml(latex_to_omml(formula)) - # for r in omml.xpath("//m:r", namespaces=omml.nsmap): - # r.append(create_element("w:rPr", [ - # create_element("w:sz", {"w:val": "24"}), - # create_element("w:szCs", {"w:val": "24"}), - # ])) - # self._docx_paragraph._element.append(omml) - self.add_run(formula, is_italic=True) + try: + omml = inline_omml(latex_to_omml(formula)) + for r in omml.xpath("//m:r", namespaces=omml.nsmap): + r.append(create_element("w:rPr", [ + create_element("w:sz", {"w:val": "28"}), + create_element("w:szCs", {"w:val": "28"}), + ])) + self._docx_paragraph._element.append(omml) + except Exception: + self.add_run(formula, is_italic=True) @property def page_break_before(self) -> bool: @@ -170,6 +172,12 @@ class Paragraph(Renderable): images = iter(self._images) for image in images: + # +landscape: section break handles the new page. Do NOT measure against + # portrait remaining (that queues add_to_new_page and leaves an empty page). + if getattr(image, "landscape", False): + yield SubRenderable(image, False) + continue + rendered_image = list(image.render(previous_rendered, copy(layout_state))) rendered_image_height = sum([x.height for x in rendered_image]) if rendered_image: diff --git a/md2gost/renderable/table.py b/md2gost/renderable/table.py index 074afb2..0b7d8ad 100644 --- a/md2gost/renderable/table.py +++ b/md2gost/renderable/table.py @@ -18,8 +18,9 @@ CELL_OFFSET = Pt(9) - Twips(108 * 2) # Slack only for modes that fragment by our height estimate (legacy/caption). ROW_HEIGHT_SLACK = Pt(4) -# Modes that do NOT cut the table into fragments — Word owns page breaks. -_WORD_PAGED_MODES = frozenset({"off", "soft"}) +# Modes that do NOT cut the table into fragments — Word owns page breaks +# (word: post-split via COM after save). +_WORD_PAGED_MODES = frozenset({"off", "soft", "word"}) class Table(Renderable, RequiresNumbering): @@ -35,6 +36,7 @@ class Table(Renderable, RequiresNumbering): self._cell_margin_lr = left_margin + right_margin self._number = "?" + self.landscape = bool(caption_info and caption_info.landscape) if caption_info and caption_info.unique_name: self.unique_name = caption_info.unique_name @@ -144,7 +146,7 @@ class Table(Renderable, RequiresNumbering): def _make_continuation_paragraph(self) -> Paragraph: continuation_paragraph = Paragraph(self._parent) continuation_paragraph.add_run(f"Продолжение Таблицы {self._number}") - continuation_paragraph.style = "Caption Table" + continuation_paragraph.style = "Название таблицы" continuation_paragraph.first_line_indent = 0 return continuation_paragraph diff --git a/md2gost/renderable/toc.py b/md2gost/renderable/toc.py index 8334767..e481804 100644 --- a/md2gost/renderable/toc.py +++ b/md2gost/renderable/toc.py @@ -116,7 +116,12 @@ class ToC(Renderable): continue display = title.strip() - if display.upper() in SPECIAL_TITLES or display.upper().startswith("ПРИЛОЖЕНИЕ"): + # Special sections and numbered H1 → uppercase in TOC (method guide) + if ( + display.upper() in SPECIAL_TITLES + or display.upper().startswith("ПРИЛОЖЕНИЕ") + or (level == 1 and numbered) + ): display = display.upper() p.add_run(" " * (level - 1)) diff --git a/md2gost/renderable_factory.py b/md2gost/renderable_factory.py index eae97f9..1ceeac9 100644 --- a/md2gost/renderable_factory.py +++ b/md2gost/renderable_factory.py @@ -15,12 +15,13 @@ from .renderable.list import List from .renderable.toc import ToC from .renderable.diagram import DiagramFigure from .label_pass import resolve_reference -from .diagram_renderer import DIAGRAM_LANGS +from .diagram_renderer import is_diagram_lang class RenderableFactory: - def __init__(self, parent: Parented): + def __init__(self, parent: Parented, hr_pagebreak: bool = False): self._parent = parent + self._hr_pagebreak = hr_pagebreak @singledispatchmethod def create(self, marko_element: extended_markdown.BlockElement, @@ -43,7 +44,12 @@ class RenderableFactory: elif isinstance(child, extended_markdown.CodeSpan): paragraph_or_link.add_run(child.children, is_italic=True) elif isinstance(child, extended_markdown.Image): - caption = CaptionInfo(child.unique_name, child.title) + caption = CaptionInfo( + child.unique_name, + child.title, + getattr(child, "with_listing", False), + getattr(child, "landscape", False), + ) paragraph_or_link.add_image(child.dest, caption) elif isinstance(child, extended_markdown.LineBreak): pass @@ -82,7 +88,7 @@ class RenderableFactory: def _(self, marko_code_block: extended_markdown.FencedCode, caption_info: CaptionInfo): lang = (marko_code_block.lang or "").strip().lower() source = marko_code_block.children[0].children - if lang in DIAGRAM_LANGS: + if is_diagram_lang(lang): return DiagramFigure( self._parent, lang, @@ -98,7 +104,7 @@ class RenderableFactory: def _(self, marko_code_block: extended_markdown.CodeBlock, caption_info: CaptionInfo): lang = (getattr(marko_code_block, "lang", "") or "").strip().lower() source = marko_code_block.children[0].children - if lang in DIAGRAM_LANGS: + if is_diagram_lang(lang): return DiagramFigure( self._parent, lang, @@ -161,3 +167,16 @@ class RenderableFactory: def _(self, marko_toc: extended_markdown.TOC, caption_info: CaptionInfo): toc = ToC(self._parent) return toc + + @create.register + def _(self, marko_hr: extended_markdown.ThematicBreak, caption_info: CaptionInfo): + if self._hr_pagebreak: + from .renderable.page_break import PageBreak + return PageBreak(self._parent) + paragraph = Paragraph(self._parent) + paragraph.add_run( + "ThematicBreak is not supported", + color=RGBColor.from_string("ff0000"), + ) + logging.warning("ThematicBreak is not supported") + return paragraph diff --git a/md2gost/renderer.py b/md2gost/renderer.py index cbea6c9..98ac382 100644 --- a/md2gost/renderer.py +++ b/md2gost/renderer.py @@ -3,18 +3,24 @@ from itertools import chain import re from docx.document import Document -from docx.shared import Length, Cm, Parented, Pt, Mm +from docx.shared import Length, Parented, Mm, Cm from docx.enum.text import WD_PARAGRAPH_ALIGNMENT +from docx.enum.section import WD_SECTION from .numberer import Numberer, APPENDIX_LETTERS from .renderable import Renderable from .renderable.requires_numbering import RequiresNumbering from .renderable.heading import Heading from .renderable.equation import Equation +from .renderable.paragraph import Paragraph from .rendered_info import RenderedInfo from .sub_renderable import SubRenderable -from .util import create_element from .layout_tracker import LayoutTracker +from .page_geometry import ( + apply_centered_page_footer, + apply_section_geometry, + content_size, +) if TYPE_CHECKING: from .debugger import Debugger @@ -22,7 +28,12 @@ if TYPE_CHECKING: BOTTOM_MARGIN = Mm(20) APPENDIX_RE = re.compile( - r"^ПРИЛОЖЕНИЕ\s+([А-ЯA-Z])\b", + r"^ПРИЛОЖЕНИЕ\s+([А-ЯA-ZЁ])\b", + re.IGNORECASE, +) +# Lettered appendix title: «Приложение А» or «Приложение А Название» +APPENDIX_ITEM_RE = re.compile( + r"^ПРИЛОЖЕНИЕ\s+([А-ЯA-ZЁ])(?:\s+(.+))?$", re.IGNORECASE, ) SPECIAL_CENTER = { @@ -48,13 +59,9 @@ class Renderer: self._appendix_index = 0 self._after_toc = False self._body_section_started = False + self._landscape_depth = 0 - max_height = (document.sections[0].page_height - - document.sections[0].top_margin - - BOTTOM_MARGIN) - max_width = (self._document.sections[0].page_width - - self._document.sections[0].left_margin - - self._document.sections[0].right_margin) + max_height, max_width = content_size(landscape=False) self._layout_tracker = LayoutTracker(max_height, max_width) # Front-matter section: no page numbers (титул / задание / содержание) @@ -62,6 +69,8 @@ class Renderer: self.previous_rendered = None self._to_new_page: list[Renderable] = [] + self._after_current: list[Renderable] = [] + self._need_space_after_object = False @staticmethod def _clear_footer(section): @@ -77,27 +86,39 @@ class Renderer: if self._body_section_started: return self._body_section_started = True - # Continuous/new page section break is inserted via last paragraph sectPr; - # python-docx: add_section creates new section. - from docx.enum.section import WD_ORIENT, WD_SECTION new_section = self._document.add_section(WD_SECTION.NEW_PAGE) - new_section.page_width = self._document.sections[0].page_width - new_section.page_height = self._document.sections[0].page_height - new_section.left_margin = Mm(30) - new_section.right_margin = Mm(10) - new_section.top_margin = Mm(20) - new_section.bottom_margin = Mm(20) + apply_section_geometry(new_section, landscape=False) + apply_centered_page_footer(new_section) - paragraph = new_section.footer.paragraphs[0] - paragraph.paragraph_format.first_line_indent = 0 - paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER - run = paragraph.add_run() - run.font.name = "Times New Roman" - run.font.size = Pt(12) - # PAGE field - paragraph._p.append(create_element("w:fldSimple", { - "w:instr": "PAGE \\* MERGEFORMAT" - })) + def _enter_landscape(self) -> None: + if self._landscape_depth > 0: + self._landscape_depth += 1 + return + self._flush_to_new_screen() + if self._after_toc: + self._ensure_body_section_with_page_numbers() + elif not self._body_section_started: + # No TOC — still need page numbers on subsequent sections. + self._body_section_started = True + apply_centered_page_footer(self._document.sections[0]) + section = self._document.add_section(WD_SECTION.NEW_PAGE) + apply_section_geometry(section, landscape=True) + apply_centered_page_footer(section) + max_height, max_width = content_size(landscape=True) + self._layout_tracker.set_page_size(max_height, max_width) + self._landscape_depth = 1 + + def _exit_landscape(self) -> None: + if self._landscape_depth <= 0: + return + self._landscape_depth -= 1 + if self._landscape_depth > 0: + return + section = self._document.add_section(WD_SECTION.NEW_PAGE) + apply_section_geometry(section, landscape=False) + apply_centered_page_footer(section) + max_height, max_width = content_size(landscape=False) + self._layout_tracker.set_page_size(max_height, max_width) def process(self, renderables: list[Renderable]): for i in range(len(renderables)): @@ -107,46 +128,84 @@ class Renderer: # If document had no TOC, still add page numbers to the only section if not self._body_section_started: section = self._document.sections[0] - paragraph = section.footer.paragraphs[0] - paragraph.paragraph_format.first_line_indent = 0 - paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER - paragraph._p.append(create_element("w:fldSimple", { - "w:instr": "PAGE \\* MERGEFORMAT" - })) + apply_centered_page_footer(section) if self._debugger: self._debugger.after_rendered() + @staticmethod + def _rewrite_heading_text(heading: Heading, text: str) -> None: + runs = heading._docx_paragraph.runs + if runs: + runs[0].text = text + for r in runs[1:]: + r.text = "" + else: + heading.add_run(text) + + def _make_appendix_title_paragraph(self, title: str) -> Paragraph: + """Title line under «Приложение А»: Normal, centered, no first-line indent.""" + p = Paragraph(self._document._body) + p.add_run(title) + p.style = "Normal" + p.first_line_indent = Cm(0) + pf = p._docx_paragraph.paragraph_format + pf.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + pf.left_indent = Cm(0) + pf.keep_with_next = True + return p + def _handle_heading(self, heading: Heading): text = (heading.text or "").strip() - upper = text.upper() + upper = text.upper().replace("Ё", "Е") if heading.level == 1 and heading.is_numbered: self._section_count += 1 self._numberer.enter_section(self._section_count) - # Appendix: «Приложение А» / «ПРИЛОЖЕНИЕ А» - m = APPENDIX_RE.match(upper.replace("Ё", "Е")) - if m or (heading.level <= 3 and upper.startswith("ПРИЛОЖЕНИЕ")): - letter = None - if m: - letter = m.group(1).upper() - else: - parts = upper.split() - if len(parts) >= 2 and parts[1] in APPENDIX_LETTERS: - letter = parts[1] - elif self._appendix_index < len(APPENDIX_LETTERS): - letter = APPENDIX_LETTERS[self._appendix_index] - self._appendix_index += 1 - if letter: + # Letter appendix item: «Приложение А» / «Приложение А Название» + # (not the section header «ПРИЛОЖЕНИЯ») + item = APPENDIX_ITEM_RE.match(upper) if upper.startswith("ПРИЛОЖЕНИЕ ") else None + if item: + letter = item.group(1).upper().replace("Ё", "Е") + title_tail = (item.group(2) or "").strip() + if letter in APPENDIX_LETTERS: + self._numberer.enter_appendix(letter) + elif self._appendix_index < len(APPENDIX_LETTERS): + letter = APPENDIX_LETTERS[self._appendix_index] + self._appendix_index += 1 self._numberer.enter_appendix(letter) - # Center special unnumbered headings that must be centered (already centered if unnumbered) - if upper in SPECIAL_CENTER or upper.startswith("ПРИЛОЖЕНИЕ"): + # Style as Heading 3, centered, new page (method guide §6) + heading.style = "Heading 3" + heading._level = 3 + heading._remove_numbering() heading._docx_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + heading._docx_paragraph.paragraph_format.left_indent = Cm(0) + heading._docx_paragraph.paragraph_format.first_line_indent = Cm(0) + heading.page_break_before = True + heading._docx_paragraph.paragraph_format.keep_with_next = True - # СОДЕРЖАНИЕ / СПИСОК — center even if somehow numbered - if upper in ("СОДЕРЖАНИЕ", "СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ"): + orig = (heading.text or "").strip() + m_orig = re.match(r"(?i)^Приложение\s+([А-ЯA-ZЁ])\b", orig) + letter_display = m_orig.group(1).upper().replace("Ё", "Е") if m_orig else letter + self._rewrite_heading_text(heading, f"Приложение {letter_display}") + + if title_tail: + m_tail = re.match(r"(?i)^Приложение\s+[А-ЯA-ZЁ]\s+(.+)$", orig) + title_text = m_tail.group(1).strip() if m_tail else title_tail + self._after_current.append(self._make_appendix_title_paragraph(title_text)) + return + + # Section «ПРИЛОЖЕНИЯ» / bare «ПРИЛОЖЕНИЕ» — left like H1, not centered + if upper in ("ПРИЛОЖЕНИЯ", "ПРИЛОЖЕНИЕ"): + heading._docx_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT + return + + # Only СОДЕРЖАНИЕ / СПИСОК … are centered (method guide p. 8) + if upper in SPECIAL_CENTER: heading._docx_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + heading._docx_paragraph.paragraph_format.left_indent = Cm(0) + heading._docx_paragraph.paragraph_format.first_line_indent = Cm(0) # Native Word TOC picks up Heading 1–3; «СОДЕРЖАНИЕ» must not list itself if upper == "СОДЕРЖАНИЕ": @@ -159,69 +218,113 @@ class Renderer: # After ToC page-break renderable we open body section — detected via ToC's PageBreak from .renderable.toc import ToC from .renderable.page_break import PageBreak + from .renderable.table import Table + from .renderable.listing import Listing + from .renderable.diagram import DiagramFigure + from .renderable.list import List as RList + if isinstance(renderable, PageBreak) and self._after_toc: self._ensure_body_section_with_page_numbers() if isinstance(renderable, ToC): self._after_toc = True - if not self._skip_numbering and isinstance(renderable, RequiresNumbering): - if isinstance(renderable, Equation): - label = renderable.unique_name - if renderable.needs_numbering or (label and label in self._numbered_equations): - renderable.enable_numbering() - number = self._numberer.next_number( - renderable.numbering_category, label) - renderable.set_number(number) - else: - number = self._numberer.next_number( - renderable.numbering_category, - getattr(renderable, "unique_name", None), - ) - renderable.set_number(number) + # Space 6 mm before next body paragraph after table/listing + if self._need_space_after_object: + self._need_space_after_object = False + if isinstance(renderable, (Paragraph, RList)) and not isinstance(renderable, Heading): + if isinstance(renderable, RList): + # Apply to first list item when rendered — mark on list + setattr(renderable, "_space_before_mm6", True) + else: + renderable._docx_paragraph.paragraph_format.space_before = Mm(6) - infos = renderable.render(self.previous_rendered, self._layout_tracker.current_state) + wants_landscape = bool(getattr(renderable, "landscape", False)) + deferred_listing = None + if wants_landscape and isinstance(renderable, DiagramFigure) and renderable.listing is not None: + # Method guide: landscape pages are for figures/tables only — listing after + deferred_listing = renderable.listing + renderable.listing = None + + if wants_landscape: + self._enter_landscape() try: - first = next(infos) - if isinstance(first, RenderedInfo) and first.height \ - >= self._layout_tracker.current_state.remaining_page_height: - self._flush_to_new_screen() - infos = renderable.render(self.previous_rendered, self._layout_tracker.current_state) - else: - infos = chain([first], infos) - except StopIteration: - pass - - for info in infos: - if isinstance(info, SubRenderable): - if info.add_to_new_page: - self._to_new_page.append(info.renderable) + if not self._skip_numbering and isinstance(renderable, RequiresNumbering): + if isinstance(renderable, Equation): + label = renderable.unique_name + if renderable.needs_numbering or (label and label in self._numbered_equations): + renderable.enable_numbering() + number = self._numberer.next_number( + renderable.numbering_category, label) + renderable.set_number(number) else: - self.render(info.renderable) - else: - self._add(info.docx_element, info.height) - self.previous_rendered = info + number = self._numberer.next_number( + renderable.numbering_category, + getattr(renderable, "unique_name", None), + ) + renderable.set_number(number) + + infos = renderable.render(self.previous_rendered, self._layout_tracker.current_state) + + try: + first = next(infos) + if isinstance(first, RenderedInfo) and first.height \ + >= self._layout_tracker.current_state.remaining_page_height: + self._flush_to_new_screen() + infos = renderable.render(self.previous_rendered, self._layout_tracker.current_state) + else: + infos = chain([first], infos) + except StopIteration: + pass + + for info in infos: + if isinstance(info, SubRenderable): + if info.add_to_new_page: + self._to_new_page.append(info.renderable) + else: + self.render(info.renderable) + else: + self._add(info.docx_element, info.height) + self.previous_rendered = info + finally: + if wants_landscape: + self._exit_landscape() + + # Appendix title line queued by _handle_heading + while self._after_current: + extra = self._after_current.pop(0) + self.render(extra) + + if deferred_listing is not None: + self.render(deferred_listing) + + if isinstance(renderable, (Table, Listing)): + self._need_space_after_object = True + elif isinstance(renderable, DiagramFigure) and deferred_listing is None and renderable.listing is None: + # Diagram figure alone — no table/listing spacing needed after image caption + pass def _flush_to_new_screen(self): while self._to_new_page: renderable = self._to_new_page.pop(0) - if not self._skip_numbering and isinstance(renderable, RequiresNumbering): - number = self._numberer.next_number( - renderable.numbering_category, - getattr(renderable, "unique_name", None), - ) - renderable.set_number(number) - for info_ in renderable.render(self.previous_rendered, self._layout_tracker.current_state): - if isinstance(info_, SubRenderable): - continue - self._add(info_.docx_element, info_.height) - self.previous_rendered = info_ + self.render(renderable) def _add(self, element: Parented, height: Length): - self._document._body._element.append( - element._element - ) + # MUST insert before the body-level w:sectPr. append() puts content after it, + # so section breaks from +landscape never wrap the figure (Word shows portrait). + body = self._document._body._element + el = element._element + from docx.oxml.ns import qn + sect_pr = None + for child in body: + if child.tag == qn("w:sectPr"): + sect_pr = child + break + if sect_pr is not None: + sect_pr.addprevious(el) + else: + body.append(el) self._layout_tracker.add_height(height) if self._debugger: diff --git a/md2gost/styles.py b/md2gost/styles.py index 910030e..8d42ef3 100644 --- a/md2gost/styles.py +++ b/md2gost/styles.py @@ -173,13 +173,11 @@ def apply_document_styles(document: Document, style_preset: str = "mirea") -> No def _apply_common_page_and_body(document: Document) -> None: + from .page_geometry import apply_section_geometry, is_landscape_section + for section in document.sections: - section.page_width = Mm(210) - section.page_height = Mm(297) - section.left_margin = Mm(30) - section.right_margin = Mm(10) - section.top_margin = Mm(20) - section.bottom_margin = Mm(20) + # Do not wipe landscape sections created for +landscape figures/tables. + apply_section_geometry(section, landscape=is_landscape_section(section)) _fix_toc_tab_stops(document) @@ -209,7 +207,7 @@ def _apply_common_captions_and_misc(document: Document) -> None: cpf.line_spacing_rule = WD_LINE_SPACING.SINGLE cpf.widow_control = True - # --- Caption Table: 12pt italic, left, above table --- + # --- Caption Table (legacy EN name) + «Название таблицы» (основной) --- caption_tbl = _ensure_style(document, "Caption Table", "Caption") _set_run_font(caption_tbl, "Times New Roman", 12, italic=True) tpf = caption_tbl.paragraph_format @@ -219,8 +217,22 @@ def _apply_common_captions_and_misc(document: Document) -> None: tpf.space_before = Mm(6) tpf.space_after = Mm(0) tpf.line_spacing_rule = WD_LINE_SPACING.SINGLE + tpf.keep_with_next = True tpf.widow_control = True + # ГОСТ-имя стиля подписи таблицы (те же параметры, что Caption Table) + caption_tbl_ru = _ensure_style(document, "Название таблицы", "Caption Table") + _set_run_font(caption_tbl_ru, "Times New Roman", 12, italic=True) + tpf_ru = caption_tbl_ru.paragraph_format + tpf_ru.alignment = WD_ALIGN_PARAGRAPH.LEFT + tpf_ru.first_line_indent = Cm(0) + tpf_ru.left_indent = Cm(0) + tpf_ru.space_before = Mm(6) + tpf_ru.space_after = Mm(0) + tpf_ru.line_spacing_rule = WD_LINE_SPACING.SINGLE + tpf_ru.keep_with_next = True + tpf_ru.widow_control = True + # --- Caption Listing (как таблицы) --- caption_lst = _ensure_style(document, "Caption Listing", "Caption") _set_run_font(caption_lst, "Times New Roman", 12, italic=True) @@ -256,7 +268,9 @@ def _apply_common_captions_and_misc(document: Document) -> None: table_text = _ensure_style(document, "Table Text", "Normal") _set_run_font(table_text, "Times New Roman", 12) ttf = table_text.paragraph_format + ttf.alignment = WD_ALIGN_PARAGRAPH.LEFT ttf.first_line_indent = Cm(0) + ttf.left_indent = Cm(0) ttf.space_before = Mm(0) ttf.space_after = Mm(0) ttf.line_spacing_rule = WD_LINE_SPACING.SINGLE @@ -276,12 +290,28 @@ def _apply_common_captions_and_misc(document: Document) -> None: bhpf = biblio_h.paragraph_format bhpf.alignment = WD_ALIGN_PARAGRAPH.CENTER bhpf.first_line_indent = Cm(0) - bhpf.left_indent = Cm(0) + bhpf.left_indent = Cm(1.25) # табл. 5.1 bhpf.space_before = Mm(6) bhpf.space_after = Mm(6) bhpf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE bhpf.keep_with_next = True + # TOC styles: TNR 14, 1.5, no bold, no first-line indent; toc 1 = ALL CAPS + for toc_name, all_caps in (("toc 1", True), ("toc 2", False), ("toc 3", False)): + try: + toc_style = document.styles[toc_name] + except KeyError: + toc_style = _ensure_style(document, toc_name, "Normal") + _set_run_font(toc_style, "Times New Roman", 14, bold=False) + toc_style.font.all_caps = all_caps + toc_style.font.bold = False + tpf_toc = toc_style.paragraph_format + tpf_toc.alignment = WD_ALIGN_PARAGRAPH.LEFT + tpf_toc.first_line_indent = Cm(0) + tpf_toc.space_before = Pt(0) + tpf_toc.space_after = Pt(0) + tpf_toc.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE + try: footer_style = document.styles["Footer"] _set_run_font(footer_style, "Times New Roman", 12) @@ -301,4 +331,6 @@ def _apply_common_captions_and_misc(document: Document) -> None: after = _ensure_style(document, "Space After Table", "Normal") apf = after.paragraph_format apf.space_before = Mm(6) + apf.space_after = Pt(0) apf.first_line_indent = Cm(1.25) + apf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE diff --git a/md2gost/vendor/.gitkeep b/md2gost/vendor/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/md2gost/word_fix.py b/md2gost/word_fix.py new file mode 100644 index 0000000..e52f669 --- /dev/null +++ b/md2gost/word_fix.py @@ -0,0 +1,525 @@ +"""Post-process DOCX in Word: split tables/listings at real page breaks + «Продолжение…». + +Requires Windows + Microsoft Word + pywin32. Pure helpers below are unit-testable +without Word. +""" + +from __future__ import annotations + +import os +import re +import sys +from dataclasses import dataclass, field + +# wdActiveEndPageNumber +_WD_ACTIVE_END_PAGE_NUMBER = 3 + +_CAPTION_RE = re.compile( + r"^(?:Продолжение\s+)?" + r"(?PТаблица|Таблицы|Листинг|Листинга)\s+" + r"(?P[\d.]+)", + re.IGNORECASE, +) +_CONTINUATION_RE = re.compile(r"^Продолжение\s+(?:Таблицы|Листинга)\b", re.IGNORECASE) + +MAX_PASSES = 5 + + +@dataclass +class CaptionInfo: + kind: str # "table" | "listing" + number: str + is_continuation: bool = False + + +@dataclass +class FixResult: + ok: bool + splits: int = 0 + skipped: int = 0 + message: str = "" + details: list[str] = field(default_factory=list) + + +def parse_caption_text(text: str) -> CaptionInfo | None: + """Parse «Таблица 2.1 — …» / «Продолжение Таблицы 2.1» / «Листинг 1 …».""" + raw = (text or "").replace("\r", "").replace("\x07", "").strip() + if not raw: + return None + first = raw.split("\n", 1)[0].strip() + m = _CAPTION_RE.match(first) + if not m: + return None + kind_raw = m.group("kind").lower() + if kind_raw.startswith("табл"): + kind = "table" + elif kind_raw.startswith("лист"): + kind = "listing" + else: + return None + return CaptionInfo( + kind=kind, + number=m.group("number"), + is_continuation=bool(_CONTINUATION_RE.match(first)), + ) + + +def find_page_break_row(page_numbers: list[int]) -> int | None: + """ + Return 1-based Word row index where a new page starts. + + page_numbers[i] is the page of row i+1. None if the table does not cross pages. + """ + if len(page_numbers) < 2: + return None + for i in range(1, len(page_numbers)): + if page_numbers[i] > page_numbers[i - 1]: + row_1based = i + 1 + if row_1based <= 1: + return None + return row_1based + return None + + +def continuation_label(kind: str, number: str) -> str: + if kind == "listing": + return f"Продолжение Листинга {number}" + return f"Продолжение Таблицы {number}" + + +def caption_style_name(kind: str) -> str: + return "Caption Listing" if kind == "listing" else "Название таблицы" + + +def fix_continuations( + path: str, + *, + tables: bool = True, + listings: bool = True, + repeat_header: bool = False, +) -> FixResult: + """ + Open DOCX in Word, split cross-page tables/listings, insert continuation captions. + + On missing Word/pywin32 returns ok=False with a reason (caller should not fail convert). + """ + if sys.platform != "win32": + return FixResult( + False, + message="Пост-разрыв таблиц (word): только Windows + Microsoft Word.", + ) + try: + import win32com.client # type: ignore + except ImportError: + return FixResult( + False, + message=( + "Пост-разрыв таблиц пропущен: нужен pywin32 " + "(pip install pywin32) и Microsoft Word." + ), + ) + + abs_path = os.path.abspath(path) + if not os.path.isfile(abs_path): + return FixResult(False, message=f"Файл не найден: {abs_path}") + + word = None + doc = None + splits = 0 + details: list[str] = [] + try: + word = win32com.client.DispatchEx("Word.Application") + word.Visible = False + word.DisplayAlerts = 0 + doc = word.Documents.Open(abs_path, ReadOnly=False) + for _pass in range(MAX_PASSES): + doc.Repaginate() + made = _fix_pass( + doc, + tables=tables, + listings=listings, + repeat_header=repeat_header, + details=details, + ) + if made == 0: + break + splits += made + + skipped = sum(1 for d in details if d.startswith("skip:")) + doc.Save() + msg = ( + f"Пост-разрыв Word: разрезов {splits}" + + (f", пропусков {skipped}" if skipped else "") + + "." + ) + return FixResult(True, splits=splits, skipped=skipped, message=msg, details=details) + except Exception as exc: + skipped = sum(1 for d in details if d.startswith("skip:")) + return FixResult( + False, + splits=splits, + skipped=skipped, + message=f"Пост-разрыв таблиц не удался: {exc}", + details=details, + ) + finally: + if doc is not None: + try: + doc.Close(False) + except Exception: + pass + if word is not None: + try: + word.Quit() + except Exception: + pass + + +def _row_page_numbers(table) -> list[int]: + """Page number at the start of each row (more reliable than end-of-range).""" + pages: list[int] = [] + n = int(table.Rows.Count) + for i in range(1, n + 1): + try: + rng = table.Rows(i).Range + # Collapse to start so a tall row reports where it begins + start = int(rng.Start) + page = int(rng.Document.Range(start, start).Information(_WD_ACTIVE_END_PAGE_NUMBER)) + except Exception: + try: + page = int(table.Rows(i).Range.Information(_WD_ACTIVE_END_PAGE_NUMBER)) + except Exception: + page = pages[-1] if pages else 1 + pages.append(page) + return pages + + +def _paragraph_page_numbers_in_cell(table) -> list[int]: + """Fallback: page of each paragraph in a single-cell table (legacy 1-row listings).""" + try: + if int(table.Rows.Count) != 1: + return [] + cell = table.Cell(1, 1) + paras = cell.Range.Paragraphs + pages: list[int] = [] + for i in range(1, int(paras.Count) + 1): + try: + start = int(paras(i).Range.Start) + page = int(paras(i).Range.Document.Range(start, start).Information( + _WD_ACTIVE_END_PAGE_NUMBER + )) + except Exception: + page = pages[-1] if pages else 1 + pages.append(page) + return pages + except Exception: + return [] + + +def _expand_single_cell_listing_to_rows(table) -> bool: + """ + Convert 1-row listing (many paragraphs in one cell) into one row per paragraph. + Returns True if the table was rewritten. + """ + try: + if int(table.Rows.Count) != 1: + return False + cell = table.Cell(1, 1) + paras = cell.Range.Paragraphs + count = int(paras.Count) + if count <= 1: + return False + # Collect plain texts first (mutating while iterating is unsafe) + texts: list[str] = [] + for i in range(1, count + 1): + t = (paras(i).Range.Text or "").replace("\r", "").replace("\x07", "") + texts.append(t) + # Clear cell, keep first paragraph as first row content + cell.Range.Text = texts[0] if texts else "" + # Add rows for remaining lines + for t in texts[1:]: + row = table.Rows.Add() + row.Cells(1).Range.Text = t + return True + except Exception: + return False + + +def _fix_pass( + doc, + *, + tables: bool, + listings: bool, + repeat_header: bool, + details: list[str], +) -> int: + """One pass over tables (bottom-up). Returns number of splits performed.""" + made = 0 + count = int(doc.Tables.Count) + for ti in range(count, 0, -1): + try: + table = doc.Tables(ti) + except Exception: + details.append(f"skip: table[{ti}] inaccessible") + continue + try: + if int(table.NestingLevel) > 1: + continue + except Exception: + pass + + caption = _caption_before_table(table) + if caption is None: + continue + if caption.kind == "table" and not tables: + continue + if caption.kind == "listing" and not listings: + continue + + # Legacy single-cell listings: expand to rows so Split works + if caption.kind == "listing" and int(table.Rows.Count) == 1: + para_pages = _paragraph_page_numbers_in_cell(table) + if find_page_break_row(para_pages) is not None: + if _expand_single_cell_listing_to_rows(table): + details.append(f"expand: listing {caption.number} → {table.Rows.Count} rows") + doc.Repaginate() + + pages = _row_page_numbers(table) + break_at = find_page_break_row(pages) + if break_at is None: + continue + if break_at > int(table.Rows.Count): + continue + + had_header = False + try: + had_header = bool(table.Rows(1).HeadingFormat) + except Exception: + had_header = False + + try: + table.Split(break_at) + except Exception as exc: + details.append(f"skip: split table[{ti}] row {break_at}: {exc}") + continue + + try: + cont = doc.Tables(ti + 1) + except Exception as exc: + details.append(f"skip: after split cannot get continuation table[{ti}+1]: {exc}") + made += 1 + continue + + before_cont = _paragraph_text_before_table(cont) + first_line = before_cont.strip().split("\n", 1)[0] if before_cont else "" + if not (first_line and _CONTINUATION_RE.match(first_line)): + try: + _insert_continuation_before(doc, cont, caption) + except Exception as exc: + details.append(f"skip: insert caption after split: {exc}") + + # Open bottom of first fragment (tables only — listings keep a full frame) + if caption.kind == "table": + try: + _clear_table_bottom_border(table) + except Exception as exc: + details.append(f"skip: clear bottom border: {exc}") + + if caption.kind == "listing": + try: + _apply_listing_box_borders(table) + _apply_listing_box_borders(cont) + except Exception as exc: + details.append(f"skip: listing borders: {exc}") + + # Header repeat is opt-in (default off) + if repeat_header and had_header and caption.kind == "table": + try: + _ensure_header_on_continuation(first_table=table, cont_table=cont) + except Exception as exc: + details.append(f"skip: header copy: {exc}") + + made += 1 + details.append(f"split: {caption.kind} {caption.number} @row {break_at}") + + return made + +def _caption_before_table(table) -> CaptionInfo | None: + text = _paragraph_text_before_table(table) + if not text: + return None + return parse_caption_text(text) + + +def _paragraph_text_before_table(table) -> str: + try: + rng = table.Range + if rng.Start <= 1: + return "" + doc = table.Range.Document + prev = doc.Range(rng.Start - 1, rng.Start) + p = prev.Paragraphs(1) + return (p.Range.Text or "").replace("\r", "").replace("\x07", "").strip() + except Exception: + return "" + + +def _insert_continuation_before(doc, table, caption: CaptionInfo) -> None: + """Insert caption paragraph *outside* the table (before its start). + + ``InsertBefore`` at ``table.Range.Start`` puts text into the first cell — + Word treats the table start as inside the table. Move one character before + the table, insert a paragraph break, then fill that new paragraph. + """ + label = continuation_label(caption.kind, caption.number) + style = caption_style_name(caption.kind) + + # wdCollapseStart=1, wdCharacter=1 + rng = table.Range.Duplicate + rng.Collapse(1) + start0 = int(table.Range.Start) + if start0 > 0: + rng.Move(1, -1) # land on the paragraph mark before the table + rng.InsertParagraphAfter() + # New empty paragraph sits between previous content and the table. + # Refresh table start — it moved forward by one paragraph mark. + table_start = int(table.Range.Start) + if table_start < 1: + raise RuntimeError("table at document start after insert") + # Paragraph immediately before the table + para = doc.Range(table_start - 1, table_start - 1).Paragraphs(1) + # Write into the paragraph without including the trailing \r that borders the table + text_rng = para.Range.Duplicate + # Exclude final paragraph mark so we don't merge into the table + if int(text_rng.End) > int(text_rng.Start): + text_rng.End = int(text_rng.End) - 1 + text_rng.Text = label + + try: + para.Style = style + except Exception: + # Fallback if RU style missing in older docs + try: + para.Style = "Caption Table" if caption.kind == "table" else "Caption" + except Exception: + pass + try: + para.Range.Font.Italic = True + para.Range.Font.Bold = False + para.Range.Font.Underline = 0 # wdUnderlineNone + para.Range.Font.Name = "Times New Roman" + para.Range.Font.Size = 12 + except Exception: + pass + try: + para.Format.FirstLineIndent = 0 + para.Format.SpaceAfter = 0 + para.Format.SpaceBefore = 6 # pt ≈ Mm(6) for first continuation look + para.Format.KeepWithNext = True + para.Format.Alignment = 0 # wdAlignParagraphLeft + except Exception: + pass + + # Sanity: caption must not live inside a table cell + try: + if int(para.Range.Tables.Count) > 0: + raise RuntimeError("continuation caption landed inside a table") + except AttributeError: + pass + + +# WdBorderType (Word): top=-1, left=-2, bottom=-3, right=-4, insideH=-5, insideV=-6 +_WD_BORDER_TOP = -1 +_WD_BORDER_LEFT = -2 +_WD_BORDER_BOTTOM = -3 +_WD_BORDER_RIGHT = -4 +_WD_BORDER_HORIZONTAL = -5 +_WD_BORDER_VERTICAL = -6 +_WD_LINE_STYLE_NONE = 0 +_WD_LINE_STYLE_SINGLE = 1 + + +def _set_border(table, border_id: int, *, line_style: int, line_width: float = 0.5) -> None: + b = table.Borders(border_id) + b.LineStyle = line_style + if line_style != _WD_LINE_STYLE_NONE: + try: + b.LineWidth = line_width + except Exception: + pass + + +def _clear_table_bottom_border(table) -> None: + _set_border(table, _WD_BORDER_BOTTOM, line_style=_WD_LINE_STYLE_NONE) + + +def _apply_listing_box_borders(table) -> None: + """Outer frame only — no inside H/V (code block look).""" + _set_border(table, _WD_BORDER_TOP, line_style=_WD_LINE_STYLE_SINGLE) + _set_border(table, _WD_BORDER_LEFT, line_style=_WD_LINE_STYLE_SINGLE) + _set_border(table, _WD_BORDER_BOTTOM, line_style=_WD_LINE_STYLE_SINGLE) + _set_border(table, _WD_BORDER_RIGHT, line_style=_WD_LINE_STYLE_SINGLE) + try: + _set_border(table, _WD_BORDER_HORIZONTAL, line_style=_WD_LINE_STYLE_NONE) + _set_border(table, _WD_BORDER_VERTICAL, line_style=_WD_LINE_STYLE_NONE) + except Exception: + pass + + +def _cell_plain_text(cell) -> str: + """Cell text without end-of-cell markers.""" + raw = cell.Range.Text or "" + return raw.replace("\r", "").replace("\x07", "").replace("\a", "").strip("\n") + + +def _ensure_header_on_continuation(*, first_table, cont_table) -> None: + """Prepend a copy of the first fragment's header row onto the continuation table.""" + try: + if int(cont_table.Rows.Count) < 1: + return + if bool(cont_table.Rows(1).HeadingFormat): + return + except Exception: + pass + + try: + hdr = first_table.Rows(1) + n_hdr = int(hdr.Cells.Count) + # Insert empty row above first data row + new_row = cont_table.Rows.Add(BeforeRow=cont_table.Rows(1)) + n_new = int(new_row.Cells.Count) + n = min(n_hdr, n_new) + for ci in range(1, n + 1): + try: + # Prefer FormattedText but strip the cell's terminal markers by + # assigning only the in-cell paragraph text. + src_cell = hdr.Cells(ci) + dst_cell = new_row.Cells(ci) + # Clear destination cell paragraphs then copy plain text + dst_cell.Range.Text = "" + plain = _cell_plain_text(src_cell) + # Setting Range.Text on a cell appends \r\a — pass plain only + if plain: + dst_cell.Range.Text = plain + # Best-effort: copy bold/italic from first paragraph of source + try: + src_font = src_cell.Range.Paragraphs(1).Range.Font + dst_p = dst_cell.Range.Paragraphs(1).Range + dst_p.Font.Bold = src_font.Bold + dst_p.Font.Italic = False # header data, not caption + dst_p.Font.Name = src_font.Name + dst_p.Font.Size = src_font.Size + except Exception: + pass + except Exception: + continue + new_row.HeadingFormat = True + # Ensure data rows are not marked as header + try: + for ri in range(2, int(cont_table.Rows.Count) + 1): + cont_table.Rows(ri).HeadingFormat = False + except Exception: + pass + except Exception: + # Don't leave a half-broken row — best effort only + pass diff --git a/prompts/generate-mirea-report.md b/prompts/generate-mirea-report.md index b0c0f9a..8218515 100644 --- a/prompts/generate-mirea-report.md +++ b/prompts/generate-mirea-report.md @@ -54,6 +54,7 @@ Правила заголовков: - Спецразделы **без номера**: `# *СОДЕРЖАНИЕ`, `# *ВВЕДЕНИЕ`, `# *ЗАКЛЮЧЕНИЕ`, `# *СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ`, `# *ПРИЛОЖЕНИЯ` — ПРОПИСНЫМИ. +- По центру в DOCX будут только «СОДЕРЖАНИЕ» и «СПИСОК…»; Введение / Заключение / ПРИЛОЖЕНИЯ — слева с отступом 1,25 см. - Нумерованные разделы основной части: `# 1 …`, `## 1.1 …` (или без цифр в тексте — нумерацию даст конвертер/Word; предпочтительно осмысленные названия без точки в конце). - В конце названия заголовка точка не ставится. @@ -99,9 +100,9 @@ | ^ | Требование 2 | ``` -### Диаграмма (UML / BPMN / C4) +### Диаграмма (PlantUML / Mermaid / схемы) -Перед fenced-блоком с языком `uml`, `plantuml`, `bpmn` или `c4` — `%id Подпись`. Конвертер рисует PNG как **Рисунок**. Опционально `+listing` — ещё и **Листинг** с исходником. +Перед fenced-блоком с языком `uml`, `plantuml`, `c4`, `uml-c4`, `usecase`, `bpmn` / `uml-bpmn`, `mermaid` / `mmd` и т.п. — `%id Подпись`. Конвертер рисует **Рисунок** (PNG; опционально SVG через `--diagram-format svg`). Опционально `+listing` — ещё и **Листинг** с исходником; `+landscape` — альбомная страница под широкий рисунок/таблицу. Схемы (`c4`, `bpmn`, …) берутся из `md2gost.schemes.json` (создаётся при первом запуске). Mermaid идёт через Kroki (не PlantUML). **IDEF0** не рисуется — вставляйте готовый PNG. ~~~markdown См. @Рисунок:usecase1 и @Листинг:usecase1. @@ -114,6 +115,28 @@ actor Student Student --> (Login) @enduml ``` + +%arch C4 контейнеры + +```uml-c4 +Person(user, "Студент") +System(app, "Портал") +Rel(user, app, "логин") +``` + +%bpmn1 Процесс заявки + +```bpmn +StartMessage(s, "заявка") +UserTask(t, "Проверить") +XOR(gw, "ок?") +End(e_ok) +End(e_no) +Flow(s, t) +Flow(t, gw) +CondFlow(gw, e_ok, "да") +DefaultFlow(gw, e_no) +``` ~~~ ### Листинг @@ -170,15 +193,27 @@ $$ ```markdown # *ПРИЛОЖЕНИЯ -## Приложение А Название +Приложение А — Листинг модуля +Приложение Б — Графический материал + +## Приложение А Листинг модуля + +… + +## Приложение Б Графический материал + +… ``` -Буквы: А, Б, В, Г, Д, Е, Ж, И, К… **Нельзя:** Ё, З, Й, О, Ч, Ь, Ы, Ъ. +- Раздел `# *ПРИЛОЖЕНИЯ` — ПРОПИСНЫМИ, **слева** (как H1), не по центру. +- При нескольких приложениях сразу после заголовка — **перечень** основным текстом. +- Каждое `## Приложение А Название` конвертер оформит как «Приложение А» (H3, по центру) + название следующей строкой. +- Буквы: А, Б, В, Г, Д, Е, Ж, И, К… **Нельзя:** Ё, З, Й, О, Ч, Ь, Ы, Ъ. ## Оформление текста (смысловые правила markdown) - Кавычки русские: «…». -- Тире в предложениях: «—» (с пробелами). Дефис в сложных словах и диапазонах: «-» без пробелов (100-150). +- Тире в предложениях: «—» (с пробелами). Дефис в сложных словах и диапазонах: «-» без пробелов (100-150). **Не заменяйте «—» на «-»** (в md2gost по умолчанию тире сохраняется). - Пробел после знака препинания, не перед ним; без пробела после «» и перед «». - Маркированный список: единообразный маркер; пункты со **строчной** буквы, в конце `;`, у последнего `.`. - Нумерованный список: с **прописной**, в конце `.`. diff --git a/prompts/generate-pis-custom-report.md b/prompts/generate-pis-custom-report.md index 728e137..f2c5db8 100644 --- a/prompts/generate-pis-custom-report.md +++ b/prompts/generate-pis-custom-report.md @@ -49,7 +49,7 @@ Листинг: `%id Название` + fenced code. -Диаграмма (Рисунок из PlantUML/BPMN/C4): +Диаграмма (Рисунок из PlantUML / Mermaid / схем): ``` %usecase1 Диаграмма прецедентов +listing @@ -62,7 +62,7 @@ User --> (Login) ``` ``` -Языки fence: `uml`, `plantuml`, `bpmn`, `c4`. Флаг `+listing` в строке `%` добавляет ещё Листинг с исходником. +Языки fence: `uml`, `plantuml`, `mermaid` / `mmd`, `c4` / `uml-c4`, `usecase`, `bpmn` / `uml-bpmn` и другие id из `md2gost.schemes.json`. Mermaid — через Kroki. IDEF0 не поддерживается. Флаг `+listing` в строке `%` добавляет ещё Листинг с исходником; `+landscape` — альбомная страница. Ссылки: `@Рисунок:usecase1`, `@Листинг:usecase1`. Формула (обычно работа №8): `$$ … $$` с `%eq_id` и ссылкой `@Формула:eq_id`. diff --git a/pyproject.toml b/pyproject.toml index 22fd3c7..aa674ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ [project.scripts] md2gost = "md2gost.__main__:main" +md2gost-gui = "md2gost.gui:main" md2latex = "md2latex.__main__:main" md2fodt = "md2fodt.__main__:main" @@ -26,6 +27,11 @@ dev = [ "pytest>=7.0", ] +[project.optional-dependencies] +word = [ + "pywin32>=306; sys_platform == 'win32'", +] + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" @@ -38,6 +44,8 @@ packages = ["md2gost", "md2latex", "md2fodt"] "md2gost/mml2omml" = "md2gost/mml2omml" "md2gost/diagrams" = "md2gost/diagrams" "latex/mirea" = "latex/mirea" +"prompts/generate-mirea-report.md" = "md2gost/prompts/generate-mirea-report.md" +"prompts/generate-pis-custom-report.md" = "md2gost/prompts/generate-pis-custom-report.md" # Legacy Poetry metadata (optional; prefer uv + [project] above) [tool.poetry] @@ -61,3 +69,4 @@ pygments = "^2.16.1" [tool.poetry.scripts] md2gost = "md2gost.__main__:main" +md2gost-gui = "md2gost.gui:main" diff --git a/scripts/_gen_bpmn_sprites.py b/scripts/_gen_bpmn_sprites.py new file mode 100644 index 0000000..b535935 --- /dev/null +++ b/scripts/_gen_bpmn_sprites.py @@ -0,0 +1,461 @@ +"""One-shot generator for BPMN.puml geometric sprites. Run from repo root.""" +from __future__ import annotations + +import math +from pathlib import Path + +N = 48 +HEX = "0123456789ABCDEF" +SS = 2 + + +def clamp01(v: float) -> float: + return 0.0 if v < 0 else 1.0 if v > 1 else v + + +def mix(grid: list[list[float]], x: int, y: int, v: float) -> None: + if 0 <= x < N and 0 <= y < N: + grid[y][x] = max(grid[y][x], clamp01(v)) + + +def ring(d: float, r: float, hw: float) -> float: + return clamp01(1.0 - abs(d - r) / hw) + + +def disk(d: float, r: float, hw: float = 0.65) -> float: + if d <= r - hw: + return 1.0 + if d >= r + hw: + return 0.0 + return clamp01((r + hw - d) / (2 * hw)) + + +def line_cov(px: float, py: float, x0: float, y0: float, x1: float, y1: float, hw: float) -> float: + dx, dy = x1 - x0, y1 - y0 + len2 = dx * dx + dy * dy + if len2 < 1e-6: + return disk(math.hypot(px - x0, py - y0), 0, hw) + t = clamp01(((px - x0) * dx + (py - y0) * dy) / len2) + qx, qy = x0 + t * dx, y0 + t * dy + return disk(math.hypot(px - qx, py - qy), 0, hw) + + +def diamond_m(px: float, py: float, cx: float, cy: float) -> float: + return abs(px - cx) + abs(py - cy) + + +def sample_event( + px: float, + py: float, + *, + kind: str, + dashed: bool, +) -> float: + cx = cy = (N - 1) / 2 + d = math.hypot(px - cx, py - cy) + v = 0.0 + if kind == "start": + v = max(v, ring(d, 18.2, 1.15)) + elif kind == "inter": + v = max(v, ring(d, 18.4, 1.05)) + v = max(v, ring(d, 14.6, 1.05)) + elif kind == "end": + v = max(v, ring(d, 17.6, 2.55)) + elif kind == "endfill": + v = max(v, ring(d, 17.6, 2.55)) + v = max(v, disk(d, 7.2, 0.7)) + if dashed and v > 0: + ang = (math.atan2(py - cy, px - cx) + math.pi) / (2 * math.pi) + if int(ang * 18) % 2 == 1: + v *= 0.08 + return v + + +def icon_msg(px: float, py: float, cx: float, cy: float, filled: bool) -> float: + # envelope 16x10 around center + x0, y0, x1, y1 = cx - 8, cy - 5, cx + 8, cy + 5 + v = 0.0 + # outer rect + inside = (x0 + 0.8 <= px <= x1 - 0.8) and (y0 + 0.8 <= py <= y1 - 0.8) + on_border = ( + (abs(px - x0) < 1.1 or abs(px - x1) < 1.1) and (y0 - 0.4 <= py <= y1 + 0.4) + ) or ( + (abs(py - y0) < 1.1 or abs(py - y1) < 1.1) and (x0 - 0.4 <= px <= x1 + 0.4) + ) + if filled: + if inside or on_border: + v = 1.0 + # inverted flap (white cut) approximated by not drawing center of flap + v = max(v, line_cov(px, py, x0, y0, cx, cy + 1.5, 0.9)) + v = max(v, line_cov(px, py, x1, y0, cx, cy + 1.5, 0.9)) + return v + v = max(v, 1.0 if on_border else 0.0) + v = max(v, line_cov(px, py, x0, y0, cx, cy + 1.8, 0.95)) + v = max(v, line_cov(px, py, x1, y0, cx, cy + 1.8, 0.95)) + return v + + +def icon_timer(px: float, py: float, cx: float, cy: float) -> float: + d = math.hypot(px - cx, py - cy) + v = ring(d, 7.2, 1.0) + v = max(v, line_cov(px, py, cx, cy, cx, cy - 5.2, 0.9)) + v = max(v, line_cov(px, py, cx, cy, cx + 3.6, cy, 0.9)) + return v + + +def icon_error(px: float, py: float, cx: float, cy: float) -> float: + pts = [ + (cx + 1, cy - 8), + (cx - 2, cy - 1), + (cx + 3, cy - 1), + (cx - 1, cy + 8), + (cx + 0.5, cy + 1.5), + (cx - 3.5, cy + 1.5), + ] + v = 0.0 + for a, b in zip(pts, pts[1:] + pts[:1]): + v = max(v, line_cov(px, py, a[0], a[1], b[0], b[1], 1.0)) + return v + + +def icon_signal(px: float, py: float, cx: float, cy: float, filled: bool) -> float: + a, b, c = (cx, cy - 7.5), (cx - 7.2, cy + 6), (cx + 7.2, cy + 6) + v = 0.0 + v = max(v, line_cov(px, py, *a, *b, 1.05)) + v = max(v, line_cov(px, py, *b, *c, 1.05)) + v = max(v, line_cov(px, py, *c, *a, 1.05)) + if filled: + # barycentric fill + def area(p, q, r): + return (q[0] - p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0]) + + p = (px, py) + a0 = area(a, b, c) + if abs(a0) > 0.1: + w1 = area(p, b, c) / a0 + w2 = area(a, p, c) / a0 + w3 = area(a, b, p) / a0 + if min(w1, w2, w3) >= -0.02: + v = max(v, 1.0) + return v + + +def icon_esc(px: float, py: float, cx: float, cy: float) -> float: + v = 0.0 + v = max(v, line_cov(px, py, cx, cy + 7, cx, cy - 2, 1.1)) + v = max(v, line_cov(px, py, cx, cy - 2, cx - 6, cy + 4, 1.05)) + v = max(v, line_cov(px, py, cx, cy - 2, cx + 6, cy + 4, 1.05)) + v = max(v, line_cov(px, py, cx - 6, cy + 4, cx + 6, cy + 4, 1.0)) + return v + + +def icon_link(px: float, py: float, cx: float, cy: float) -> float: + v = line_cov(px, py, cx - 8, cy, cx + 5, cy, 1.1) + v = max(v, line_cov(px, py, cx + 5, cy, cx + 1, cy - 4, 1.05)) + v = max(v, line_cov(px, py, cx + 5, cy, cx + 1, cy + 4, 1.05)) + return v + + +def icon_comp(px: float, py: float, cx: float, cy: float) -> float: + v = 0.0 + for ox in (-4.2, 3.2): + a, b, c = (cx + ox + 4, cy), (cx + ox - 3.5, cy - 6), (cx + ox - 3.5, cy + 6) + v = max(v, line_cov(px, py, *a, *b, 1.0)) + v = max(v, line_cov(px, py, *a, *c, 1.0)) + v = max(v, line_cov(px, py, *b, *c, 1.0)) + return v + + +def icon_cond(px: float, py: float, cx: float, cy: float) -> float: + x0, y0, x1, y1 = cx - 6, cy - 8, cx + 6, cy + 8 + v = 0.0 + on = ( + (abs(px - x0) < 1.0 or abs(px - x1) < 1.0) and (y0 <= py <= y1) + ) or ( + (abs(py - y0) < 1.0 or abs(py - y1) < 1.0) and (x0 <= px <= x1) + ) + if on: + v = 1.0 + for yy in (cy - 3, cy, cy + 3): + v = max(v, line_cov(px, py, cx - 3.5, yy, cx + 3.5, yy, 0.8)) + return v + + +def icon_multi(px: float, py: float, cx: float, cy: float) -> float: + r = 7.4 + pts = [] + for i in range(5): + a = -math.pi / 2 + i * 2 * math.pi / 5 + pts.append((cx + r * math.cos(a), cy + r * math.sin(a))) + v = 0.0 + for a, b in zip(pts, pts[1:] + pts[:1]): + v = max(v, line_cov(px, py, *a, *b, 1.0)) + return v + + +def icon_par_multi(px: float, py: float, cx: float, cy: float) -> float: + v = ring(math.hypot(px - cx, py - cy), 7.4, 1.0) + v = max(v, line_cov(px, py, cx, cy - 5, cx, cy + 5, 1.05)) + v = max(v, line_cov(px, py, cx - 5, cy, cx + 5, cy, 1.05)) + return v + + +def icon_cancel(px: float, py: float, cx: float, cy: float) -> float: + v = line_cov(px, py, cx - 6, cy - 6, cx + 6, cy + 6, 1.15) + v = max(v, line_cov(px, py, cx + 6, cy - 6, cx - 6, cy + 6, 1.15)) + return v + + +def icon_user(px: float, py: float, cx: float, cy: float) -> float: + v = disk(math.hypot(px - cx, py - (cy - 3.2)), 3.1, 0.7) + v = max(v, ring(math.hypot(px - cx, py - (cy - 3.2)), 3.1, 0.9)) + # shoulders + v = max(v, line_cov(px, py, cx - 6, cy + 7, cx + 6, cy + 7, 1.1)) + v = max(v, line_cov(px, py, cx - 6, cy + 7, cx - 4, cy + 1.5, 1.0)) + v = max(v, line_cov(px, py, cx + 6, cy + 7, cx + 4, cy + 1.5, 1.0)) + return v + + +def icon_service(px: float, py: float, cx: float, cy: float) -> float: + v = ring(math.hypot(px - cx, py - cy), 4.2, 1.15) + for i in range(8): + a = i * math.pi / 4 + v = max( + v, + line_cov( + px, py, + cx + 3.2 * math.cos(a), cy + 3.2 * math.sin(a), + cx + 7.4 * math.cos(a), cy + 7.4 * math.sin(a), + 1.05, + ), + ) + return v + + +def icon_script(px: float, py: float, cx: float, cy: float) -> float: + return icon_cond(px, py, cx, cy) + + +def icon_manual(px: float, py: float, cx: float, cy: float) -> float: + v = line_cov(px, py, cx - 7, cy + 2, cx + 7, cy + 2, 1.2) + v = max(v, line_cov(px, py, cx - 7, cy + 2, cx - 5, cy - 4, 1.0)) + v = max(v, line_cov(px, py, cx - 2, cy + 2, cx - 1, cy - 6, 1.0)) + v = max(v, line_cov(px, py, cx + 2, cy + 2, cx + 3, cy - 5.5, 1.0)) + v = max(v, line_cov(px, py, cx + 6, cy + 2, cx + 7, cy - 3, 1.0)) + return v + + +def icon_rule(px: float, py: float, cx: float, cy: float) -> float: + v = 0.0 + for yy in (cy - 6, cy, cy + 6): + v = max(v, line_cov(px, py, cx - 7, yy, cx + 7, yy, 0.95)) + for xx in (cx - 7, cx, cx + 7): + v = max(v, line_cov(px, py, xx, cy - 6, xx, cy + 6, 0.95)) + return v + + +def icon_plusbox(px: float, py: float, cx: float, cy: float) -> float: + x0, y0, x1, y1 = cx - 7, cy - 7, cx + 7, cy + 7 + v = 0.0 + on = ( + (abs(px - x0) < 1.05 or abs(px - x1) < 1.05) and (y0 <= py <= y1) + ) or ( + (abs(py - y0) < 1.05 or abs(py - y1) < 1.05) and (x0 <= px <= x1) + ) + if on: + v = 1.0 + v = max(v, line_cov(px, py, cx, cy - 4.5, cx, cy + 4.5, 1.05)) + v = max(v, line_cov(px, py, cx - 4.5, cy, cx + 4.5, cy, 1.05)) + return v + + +ICONS = { + "": None, + "msg": lambda px, py, cx, cy: icon_msg(px, py, cx, cy, False), + "msgf": lambda px, py, cx, cy: icon_msg(px, py, cx, cy, True), + "timer": icon_timer, + "error": icon_error, + "signal": lambda px, py, cx, cy: icon_signal(px, py, cx, cy, False), + "signalf": lambda px, py, cx, cy: icon_signal(px, py, cx, cy, True), + "esc": icon_esc, + "link": icon_link, + "comp": icon_comp, + "cond": icon_cond, + "multi": icon_multi, + "par": icon_par_multi, + "cancel": icon_cancel, + "user": icon_user, + "service": icon_service, + "script": icon_script, + "manual": icon_manual, + "rule": icon_rule, + "plusbox": icon_plusbox, +} + + +def raster(draw) -> list[list[float]]: + acc = [[0.0] * N for _ in range(N)] + big = N * SS + for y in range(big): + for x in range(big): + v = draw((x + 0.5) / SS, (y + 0.5) / SS) + acc[y // SS][x // SS] += v + s = float(SS * SS) + return [[acc[y][x] / s for x in range(N)] for y in range(N)] + + +def to_sprite(name: str, grid: list[list[float]]) -> str: + lines = [f"sprite ${name} [{N}x{N}/16] {{"] + for row in grid: + lines.append("".join(HEX[min(15, int(round(v * 15)))] for v in row)) + lines.append("}") + return "\n".join(lines) + + +def event_grid(kind: str, icon: str, dashed: bool = False) -> list[list[float]]: + cx = cy = (N - 1) / 2.0 + fn = ICONS[icon] + + def draw(px, py): + v = sample_event(px, py, kind=kind, dashed=dashed) + if fn is not None: + v = max(v, fn(px, py, cx, cy)) + return v + + return raster(draw) + + +def gateway_grid(mark: str) -> list[list[float]]: + cx = cy = (N - 1) / 2.0 + r = 21.0 + + def draw(px, py): + m = diamond_m(px, py, cx, cy) + v = ring(m, r, 1.25) + if mark == "x": + v = max(v, line_cov(px, py, cx - 8, cy - 8, cx + 8, cy + 8, 1.25)) + v = max(v, line_cov(px, py, cx + 8, cy - 8, cx - 8, cy + 8, 1.25)) + elif mark == "+": + v = max(v, line_cov(px, py, cx, cy - 9, cx, cy + 9, 1.3)) + v = max(v, line_cov(px, py, cx - 9, cy, cx + 9, cy, 1.3)) + elif mark == "o": + v = max(v, ring(math.hypot(px - cx, py - cy), 7.0, 1.15)) + elif mark == "*": + for i in range(4): + a = i * math.pi / 4 + v = max( + v, + line_cov( + px, py, + cx - 7 * math.cos(a), cy - 7 * math.sin(a), + cx + 7 * math.cos(a), cy + 7 * math.sin(a), + 1.05, + ), + ) + elif mark == "ev": + v = max(v, icon_multi(px, py, cx, cy)) + v = max(v, ring(math.hypot(px - cx, py - cy), 10.5, 0.95)) + elif mark == "pev": + v = max(v, icon_par_multi(px, py, cx, cy)) + return v + + return raster(draw) + + +EVENTS = [ + ("evStart", "start", "", False), + ("evStartMsg", "start", "msg", False), + ("evStartTimer", "start", "timer", False), + ("evStartSignal", "start", "signal", False), + ("evStartCond", "start", "cond", False), + ("evStartError", "start", "error", False), + ("evStartEsc", "start", "esc", False), + ("evStartMulti", "start", "multi", False), + ("evStartPar", "start", "par", False), + ("evStartNI", "start", "", True), + ("evStartMsgNI", "start", "msg", True), + ("evStartTimerNI", "start", "timer", True), + ("evStartSignalNI", "start", "signal", True), + ("evInter", "inter", "", False), + ("evCatchMsg", "inter", "msg", False), + ("evCatchTimer", "inter", "timer", False), + ("evCatchSignal", "inter", "signal", False), + ("evCatchError", "inter", "error", False), + ("evCatchEsc", "inter", "esc", False), + ("evCatchCond", "inter", "cond", False), + ("evCatchLink", "inter", "link", False), + ("evCatchComp", "inter", "comp", False), + ("evCatchCancel", "inter", "cancel", False), + ("evCatchMulti", "inter", "multi", False), + ("evCatchPar", "inter", "par", False), + ("evThrowMsg", "inter", "msgf", False), + ("evThrowSignal", "inter", "signalf", False), + ("evThrowEsc", "inter", "esc", False), + ("evThrowLink", "inter", "link", False), + ("evThrowComp", "inter", "comp", False), + ("evThrowMulti", "inter", "multi", False), + ("evEnd", "end", "", False), + ("evEndMsg", "end", "msgf", False), + ("evEndError", "end", "error", False), + ("evEndEsc", "end", "esc", False), + ("evEndCancel", "end", "cancel", False), + ("evEndComp", "end", "comp", False), + ("evEndSignal", "end", "signalf", False), + ("evEndMulti", "end", "multi", False), + ("evTerminate", "endfill", "", False), + ("evBoundMsg", "inter", "msg", False), + ("evBoundTimer", "inter", "timer", False), + ("evBoundError", "inter", "error", False), + ("evBoundEsc", "inter", "esc", False), + ("evBoundSignal", "inter", "signal", False), + ("evBoundCancel", "inter", "cancel", False), + ("evBoundComp", "inter", "comp", False), + ("evBoundCond", "inter", "cond", False), + ("evBoundMsgNI", "inter", "msg", True), + ("evBoundTimerNI", "inter", "timer", True), + ("evBoundEscNI", "inter", "esc", True), + ("evBoundSignalNI", "inter", "signal", True), + ("evBoundCondNI", "inter", "cond", True), +] + +GATES = [ + ("gwXor", "x"), + ("gwAnd", "+"), + ("gwOr", "o"), + ("gwComplex", "*"), + ("gwEvent", "ev"), + ("gwParEvent", "pev"), +] + +TASK_ICONS = ["user", "service", "script", "manual", "rule", "plusbox", "msg", "msgf"] + + +def main() -> None: + chunks = ["' auto-generated geometric sprites (BPMN 2.0 notation)"] + for name, kind, icon, dashed in EVENTS: + chunks.append(to_sprite(name, event_grid(kind, icon, dashed))) + chunks.append("") + for name, mark in GATES: + chunks.append(to_sprite(name, gateway_grid(mark))) + chunks.append("") + cx = cy = (N - 1) / 2.0 + for icon in TASK_ICONS: + fn = ICONS[icon] + grid = raster(lambda px, py, fn=fn: fn(px, py, cx, cy)) + chunks.append(to_sprite("ic" + icon.capitalize() if icon != "msgf" else "icMsgf", grid)) + chunks.append("") + # plusbox already as icPlusbox; loop/adhoc/mi as simple extra + def loop_draw(px, py): + v = ring(math.hypot(px - cx, py - (cy - 1)), 8.5, 1.1) + v = max(v, line_cov(px, py, cx + 6, cy + 2, cx + 10, cy + 6, 1.05)) + v = max(v, line_cov(px, py, cx + 6, cy + 2, cx + 2, cy + 6, 1.05)) + return v + + chunks.append(to_sprite("icLoop", raster(loop_draw))) + chunks.append("") + Path("_bpmn_sprites.puml").write_text("\n".join(chunks), encoding="utf-8") + print("wrote", len(chunks), "chunks") + + +if __name__ == "__main__": + main() diff --git a/scripts/check_page_fill.bas b/scripts/check_page_fill.bas new file mode 100644 index 0000000..42bc75f --- /dev/null +++ b/scripts/check_page_fill.bas @@ -0,0 +1,85 @@ +Attribute VB_Name = "CheckPageFill" +' Heuristic check for half-empty pages inside an unfinished section (MIREA method guide, app. V). +' Run from Word: Tools → Macro → Macros → CheckPageFill_Run +' Findings may be FALSE POSITIVES (large figures, end of subsection, odd breaks). + +Option Explicit + +Private Const EMPTY_THRESHOLD As Double = 0.4 + +Public Sub CheckPageFill_Run() + Dim doc As Document + Dim i As Long, n As Long + Dim msg As String + Dim issues As Long + Dim secTitle As String, nextTitle As String + Dim emptyFrac As Double + Dim contentBottom As Double + Dim textH As Double, topM As Double, pageH As Double + Dim rng As Range + Dim vpos As Double + + Set doc = ActiveDocument + doc.Repaginate + n = doc.ComputeStatistics(wdStatisticPages) + issues = 0 + msg = "Проверка полупустых страниц (эвристика; возможны ложные срабатывания)" & vbCrLf + + For i = 1 To n + Selection.GoTo What:=wdGoToPage, Which:=wdGoToAbsolute, Count:=i + If Selection.Sections(1).PageSetup.Orientation = wdOrientLandscape Then GoTo NextPage + + pageH = Selection.Sections(1).PageSetup.PageHeight + topM = Selection.Sections(1).PageSetup.TopMargin + textH = pageH - topM - Selection.Sections(1).PageSetup.BottomMargin + If textH <= 0 Then GoTo NextPage + + Set rng = doc.Bookmarks("\page").Range + On Error Resume Next + vpos = rng.Information(wdVerticalPositionRelativeToPage) + On Error GoTo 0 + contentBottom = (vpos - topM) / textH + If contentBottom < 0 Then contentBottom = 0 + If contentBottom > 1 Then contentBottom = 1 + emptyFrac = 1# - contentBottom + + If emptyFrac <= EMPTY_THRESHOLD Then GoTo NextPage + If i = n Then GoTo NextPage + + secTitle = HeadingNear(doc, rng.Start) + Selection.GoTo What:=wdGoToPage, Which:=wdGoToAbsolute, Count:=i + 1 + nextTitle = HeadingNear(doc, Selection.Start) + If StrComp(secTitle, nextTitle, vbTextCompare) <> 0 Then GoTo NextPage + If InStr(1, UCase$(secTitle), "СОДЕРЖАНИЕ", vbTextCompare) > 0 Then GoTo NextPage + + issues = issues + 1 + msg = msg & "стр. " & i & ": пустой низ ~" & Format(emptyFrac, "0%") & _ + ", раздел «" & secTitle & "» продолжается" & vbCrLf +NextPage: + Next i + + If issues = 0 Then + MsgBox msg & vbCrLf & "Замечаний нет.", vbInformation + Else + MsgBox msg & vbCrLf & "Всего: " & issues & " (эвристика).", vbExclamation + End If +End Sub + +Private Function HeadingNear(doc As Document, pos As Long) As String + Dim p As Paragraph + Dim s As String + On Error Resume Next + Set p = doc.Range(pos, pos).Paragraphs(1) + Dim k As Long + For k = 1 To 80 + s = p.Style + If InStr(1, s, "Heading 1", vbTextCompare) > 0 Or InStr(1, s, "Заголовок 1", vbTextCompare) > 0 Then + HeadingNear = Replace(Trim$(p.Range.Text), vbCr, "") + Exit Function + End If + If p.Range.Start <= 1 Then Exit For + Set p = p.Previous + If p Is Nothing Then Exit For + Next k + HeadingNear = "" +End Function diff --git a/scripts/fetch_plantuml.py b/scripts/fetch_plantuml.py new file mode 100644 index 0000000..dbf22a3 --- /dev/null +++ b/scripts/fetch_plantuml.py @@ -0,0 +1,30 @@ +"""Download official plantuml.jar into md2gost/vendor (for exe builds).""" + +from __future__ import annotations + +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from md2gost.diagram_renderer import PLANTUML_JAR_URL, fetch_plantuml_jar, vendor_plantuml_path + + +def main() -> int: + dest = vendor_plantuml_path() + print(f"PlantUML: {PLANTUML_JAR_URL}") + print(f"Куда: {dest}") + if dest.is_file() and dest.stat().st_size > 1000: + print("Уже скачан.") + return 0 + if fetch_plantuml_jar(dest): + print(f"Готово: {dest} ({dest.stat().st_size} байт)") + return 0 + print("Не удалось скачать plantuml.jar (exe соберётся, диаграммы пойдут через kroki.io).") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/md2gost_exe.py b/scripts/md2gost_exe.py new file mode 100644 index 0000000..27558a4 --- /dev/null +++ b/scripts/md2gost_exe.py @@ -0,0 +1,6 @@ +"""PyInstaller entry point: GUI by default, CLI if a .md is passed.""" + +from md2gost.__main__ import main + +if __name__ == "__main__": + main() diff --git a/scripts/pyi_rth_mplbackend.py b/scripts/pyi_rth_mplbackend.py new file mode 100644 index 0000000..2c24357 --- /dev/null +++ b/scripts/pyi_rth_mplbackend.py @@ -0,0 +1,4 @@ +"""Runtime hook: matplotlib only needs Agg for font_manager on a foreign PC.""" +import os + +os.environ.setdefault("MPLBACKEND", "Agg") diff --git a/tests/test_diagram_renderer.py b/tests/test_diagram_renderer.py index f1a674c..e4ca478 100644 --- a/tests/test_diagram_renderer.py +++ b/tests/test_diagram_renderer.py @@ -1,16 +1,35 @@ -"""Tests for diagram renderer and factory routing.""" +"""Tests for diagram renderer, schemes, and include cache.""" from __future__ import annotations +from pathlib import Path from unittest.mock import patch import pytest +from md2gost.diagram_includes import ( + include_cache_index_path, + reset_include_cache, + resolve_url, + rewrite_http_includes_in_source, +) from md2gost.diagram_renderer import ( DIAGRAM_LANGS, + configure_diagrams, + diagram_engine_status, + is_diagram_lang, prepare_source, render_diagram, - configure_diagrams, + resolve_plantuml_jar, +) +from md2gost.diagram_schemes import ( + DiagramScheme, + apply_scheme, + configure_schemes, + ensure_user_schemes, + prepare_with_schemes, + scheme_id_from_lang, + user_schemes_path, ) from md2gost.extended_markdown import markdown from md2gost.extended_markdown.caption import Caption as CapEl @@ -26,16 +45,148 @@ def test_prepare_uml_wraps_startuml(): assert dtype == "plantuml" -def test_prepare_c4_adds_include(): - src, dtype = prepare_source("c4", "Person(user, \"User\")") - assert "!include" in src +def test_prepare_c4_uses_scheme(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + ensure_user_schemes(tmp_path) + configure_schemes(base=tmp_path) + + def fake_resolve(ref, *, base=None, schemes_dir=None): + return str(tmp_path / "C4_Container.puml") + + with patch("md2gost.diagram_schemes.resolve_include_ref", side_effect=fake_resolve): + src, dtype = prepare_with_schemes("c4", 'Person(user, "U")', base=tmp_path) assert dtype == "plantuml" + assert "Person(user" in src + assert "@startuml" in src.lower() + assert "!include" in src -def test_prepare_bpmn(): - src, dtype = prepare_source("bpmn", "start -> end") - assert dtype == "bpmn" - assert "@startbpmn" in src.lower() or "start" in src +def test_prepare_uml_c4_alias(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + ensure_user_schemes(tmp_path) + configure_schemes(base=tmp_path) + + def fake_resolve(ref, *, base=None, schemes_dir=None): + return str(tmp_path / "stub.puml") + + with patch("md2gost.diagram_schemes.resolve_include_ref", side_effect=fake_resolve): + src, dtype = prepare_with_schemes("uml-c4", 'Person(user, "U")', base=tmp_path) + assert dtype == "plantuml" + assert "Person(user" in src + + +def test_unknown_scheme_raises(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + ensure_user_schemes(tmp_path) + configure_schemes(base=tmp_path) + with pytest.raises(ValueError, match="не найдена"): + prepare_with_schemes("uml-nosuch", "A -> B", base=tmp_path) + + +def test_bpmn_scheme_prepare(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + ensure_user_schemes(tmp_path) + configure_schemes(base=tmp_path) + assert is_diagram_lang("bpmn") + assert is_diagram_lang("uml-bpmn") + src, dtype = prepare_with_schemes("bpmn", "Start(s)\nEnd(e)\nFlow(s, e)", base=tmp_path) + assert dtype == "plantuml" + assert "Start(s)" in src + assert "!include" in src + assert "BPMN.puml" in src.replace("\\", "/") + assert src.lower().count("@startuml") == 1 + + +def test_bundled_bpmn_survives_user_file_without_it(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + path = ensure_user_schemes(tmp_path) + path.write_text('{"mine":{"title":"x","prefix":"","postfix":""}}\n', encoding="utf-8") + configure_schemes(base=tmp_path) + assert is_diagram_lang("mine") + assert is_diagram_lang("bpmn") + + +def test_scheme_id_from_lang(): + assert scheme_id_from_lang("uml") is None + assert scheme_id_from_lang("uml-c4") == "c4" + assert scheme_id_from_lang("c4") == "c4" + assert scheme_id_from_lang("bpmn") == "bpmn" + assert scheme_id_from_lang("uml-bpmn") == "bpmn" + + +def test_ensure_user_schemes_once(tmp_path): + path = ensure_user_schemes(tmp_path) + assert path.is_file() + assert "c4" in path.read_text(encoding="utf-8") + path.write_text('{"mine":{"title":"x","prefix":"","postfix":""}}\n', encoding="utf-8") + again = ensure_user_schemes(tmp_path) + assert again == path + assert "mine" in path.read_text(encoding="utf-8") + assert path == user_schemes_path(tmp_path) + + +def test_apply_scheme_no_double_start(): + scheme = DiagramScheme( + id="t", + prefix="@startuml\nskinparam monochrome true\n", + postfix="\n@enduml", + includes=[], + ) + out = apply_scheme(scheme, "@startuml\nA -> B\n@enduml") + assert out.lower().count("@startuml") == 1 + assert "skinparam monochrome true" in out + + +def test_include_cache_index_no_overwrite(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + calls = [] + + def fake_get(url, timeout=60): + calls.append(url) + + class Resp: + content = f"' from {url}\n".encode("utf-8") + + def raise_for_status(self): + return None + + return Resp() + + with patch("md2gost.diagram_includes.requests.get", side_effect=fake_get): + p1 = resolve_url("https://example.com/a.puml", base=tmp_path) + p2 = resolve_url("https://example.com/b.puml", base=tmp_path) + assert p1 != p2 + assert p1.is_file() and p2.is_file() + content1 = p1.read_bytes() + # second resolve same URL — no new download + p1b = resolve_url("https://example.com/a.puml", base=tmp_path) + assert p1b == p1 + assert p1.read_bytes() == content1 + assert calls.count("https://example.com/a.puml") == 1 + assert include_cache_index_path(tmp_path).is_file() + + removed = reset_include_cache(tmp_path) + assert removed >= 2 + assert not include_cache_index_path(tmp_path).is_file() + + +def test_rewrite_http_in_source(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + + def fake_get(url, timeout=60): + class Resp: + content = b"rectangle x\n" + + def raise_for_status(self): + return None + + return Resp() + + with patch("md2gost.diagram_includes.requests.get", side_effect=fake_get): + src = "@startuml\n!include https://example.com/x.puml\nA->B\n@enduml" + out = rewrite_http_includes_in_source(src, base=tmp_path) + assert "https://example.com" not in out + assert "!include " in out def test_caption_plus_listing(): @@ -47,6 +198,45 @@ def test_caption_plus_listing(): assert caps[0].unique_name == "fig" +def test_caption_plus_landscape(): + doc = markdown.parse("%wide Big scheme +landscape\n\n```uml\nA->B\n```\n") + caps = [c for c in doc.children if isinstance(c, CapEl)] + assert caps[0].landscape is True + assert caps[0].with_listing is False + assert caps[0].text == "Big scheme" + + doc2 = markdown.parse("%t1 Широкая +listing +landscape\n\n|a|b|\n|-|-|\n|1|2|\n") + caps2 = [c for c in doc2.children if isinstance(c, CapEl)] + assert caps2[0].landscape is True + assert caps2[0].with_listing is True + assert caps2[0].text == "Широкая" + + +def test_caption_interrupts_paragraph(): + """% line right after text (no blank line) must still be Caption, not plain text.""" + from marko.block import FencedCode, Paragraph + + doc = markdown.parse( + "Пишите так:\n" + "%testuml test +landscape\n" + "\n" + "```uml\nA->B\n```\n" + ) + caps = [c for c in doc.children if isinstance(c, CapEl)] + assert len(caps) == 1 + assert caps[0].landscape is True + assert caps[0].unique_name == "testuml" + joined = "" + for p in doc.children: + if not isinstance(p, Paragraph): + continue + for ch in getattr(p, "children", []) or []: + if hasattr(ch, "children") and isinstance(ch.children, str): + joined += ch.children + assert "%testuml" not in joined + assert any(isinstance(c, FencedCode) and c.lang == "uml" for c in doc.children) + + def test_factory_routes_uml_to_diagram(): from docx import Document from md2gost import package_dir @@ -77,8 +267,8 @@ def test_factory_routes_uml_to_diagram(): def test_render_diagram_uses_cache(tmp_path): png = b"\x89PNG\r\n\x1a\n" + b"\x00" * 20 - def fake_kroki(source, diagram_type, base_url, out_png): - out_png.write_bytes(png) + def fake_kroki(source, diagram_type, base_url, out_path, fmt="png"): + out_path.write_bytes(png if fmt == "png" else b"") return True configure_diagrams(fallback="remote", cache_dir=str(tmp_path)) @@ -86,8 +276,8 @@ def test_render_diagram_uses_cache(tmp_path): patch("md2gost.diagram_renderer._render_kroki", side_effect=fake_kroki): path1 = render_diagram("uml", "A -> B", cache_dir=str(tmp_path)) path2 = render_diagram("uml", "A -> B", cache_dir=str(tmp_path)) - assert path1 == path2 - assert open(path1, "rb").read().startswith(b"\x89PNG") + assert path1.png_path == path2.png_path + assert open(path1.png_path, "rb").read().startswith(b"\x89PNG") def test_render_diagram_off_raises(tmp_path): @@ -98,7 +288,202 @@ def test_render_diagram_off_raises(tmp_path): render_diagram("uml", "A -> B", fallback="off", cache_dir=str(tmp_path)) +def test_prepare_mermaid_no_startuml(): + src, dtype = prepare_source("mermaid", "flowchart LR\nA-->B") + assert "@startuml" not in src.lower() + assert dtype == "mermaid" + assert "flowchart LR" in src + src2, dtype2 = prepare_source("mmd", "sequenceDiagram\nA->>B: hi") + assert dtype2 == "mermaid" + assert "@startuml" not in src2.lower() + + +def test_plantuml_scale_injected_into_render_cache(tmp_path): + from md2gost.diagram_renderer import _ensure_plantuml_png_scale + + boosted = _ensure_plantuml_png_scale("@startuml\nA -> B\n@enduml") + assert "scale 2" in boosted + # author override kept + custom = _ensure_plantuml_png_scale("@startuml\nscale 1.5\nA -> B\n@enduml") + assert custom.count("scale") == 1 + assert "scale 1.5" in custom + + png = b"\x89PNG\r\n\x1a\n" + b"\x00" * 20 + seen = [] + + def fake_kroki(source, diagram_type, base_url, out_path, fmt="png"): + seen.append(source) + out_path.write_bytes(png if fmt == "png" else b"") + return True + + configure_diagrams(fallback="remote", cache_dir=str(tmp_path), diagram_scale=3) + with patch("md2gost.diagram_renderer._render_plantuml_jar", return_value=False), \ + patch("md2gost.diagram_renderer._render_kroki", side_effect=fake_kroki): + result = render_diagram("uml", "A -> B", cache_dir=str(tmp_path)) + assert seen and "scale 3" in seen[0] + assert result.pixel_scale == 3 + + +def test_diagram_scale_one_skips_inject(tmp_path): + png = b"\x89PNG\r\n\x1a\n" + b"\x00" * 20 + seen = [] + + def fake_kroki(source, diagram_type, base_url, out_path, fmt="png"): + seen.append(source) + out_path.write_bytes(png) + return True + + configure_diagrams(fallback="remote", cache_dir=str(tmp_path), diagram_scale=1) + with patch("md2gost.diagram_renderer._render_plantuml_jar", return_value=False), \ + patch("md2gost.diagram_renderer._render_kroki", side_effect=fake_kroki): + result = render_diagram("uml", "A -> B", cache_dir=str(tmp_path), diagram_scale=1) + assert seen and "scale " not in seen[0].split("@startuml", 1)[-1].split("A -> B")[0] + assert result.pixel_scale == 1 + + +def test_is_diagram_lang_mermaid(): + assert is_diagram_lang("mermaid") + assert is_diagram_lang("mmd") + assert "mermaid" in DIAGRAM_LANGS + assert "mmd" in DIAGRAM_LANGS + + +def test_render_mermaid_skips_jar(tmp_path): + png = b"\x89PNG\r\n\x1a\n" + b"\x00" * 20 + calls = {"jar": 0, "kroki": []} + + def fake_jar(*args, **kwargs): + calls["jar"] += 1 + return False + + def fake_kroki(source, diagram_type, base_url, out_path, fmt="png"): + calls["kroki"].append((diagram_type, fmt, base_url)) + if fmt == "png": + out_path.write_bytes(png) + else: + out_path.write_bytes(b"") + return True + + configure_diagrams(fallback="remote", cache_dir=str(tmp_path)) + with patch("md2gost.diagram_renderer._render_plantuml_jar", side_effect=fake_jar), \ + patch("md2gost.diagram_renderer._render_kroki", side_effect=fake_kroki): + result = render_diagram("mermaid", "flowchart LR\nA-->B", cache_dir=str(tmp_path)) + assert calls["jar"] == 0 + assert any(t == "mermaid" and f == "png" for t, f, _ in calls["kroki"]) + assert Path(result.png_path).read_bytes().startswith(b"\x89PNG") + assert result.svg_path is None + + +def test_render_diagram_svg_asks_both_formats(tmp_path): + png = b"\x89PNG\r\n\x1a\n" + b"\x00" * 20 + svg = b'' + fmts = [] + + def fake_kroki(source, diagram_type, base_url, out_path, fmt="png"): + fmts.append(fmt) + out_path.write_bytes(png if fmt == "png" else svg) + return True + + configure_diagrams(fallback="remote", cache_dir=str(tmp_path), diagram_format="svg") + with patch("md2gost.diagram_renderer._render_plantuml_jar", return_value=False), \ + patch("md2gost.diagram_renderer._render_kroki", side_effect=fake_kroki): + result = render_diagram( + "uml", "A -> B", cache_dir=str(tmp_path), diagram_format="svg", + ) + assert "png" in fmts and "svg" in fmts + assert result.svg_path is not None + assert Path(result.svg_path).read_bytes().startswith(b"' + '', + encoding="utf-8", + ) + assert attach_svg_blip(run, picture, svg) + + xml = picture._inline.xml + assert "svgBlip" in xml + assert SVG_BLIP_URI in xml + # two image relationships on the document part + image_rels = [ + r for r in doc.part.rels.values() + if "image" in (r.reltype or "") + ] + assert len(image_rels) >= 2 + + +def test_resolve_plantuml_jar_explicit(tmp_path): + jar = tmp_path / "plantuml.jar" + jar.write_bytes(b"PK" + b"\x00" * 2000) + assert resolve_plantuml_jar(str(jar), download=False) == str(jar) + missing = tmp_path / "nope.jar" + assert resolve_plantuml_jar(str(missing), download=False) is None or ( + resolve_plantuml_jar(str(missing), download=False) != str(missing) + ) + + +def test_diagram_engine_status_mentions_java_or_kroki(): + text = diagram_engine_status() + assert "Java" in text + assert "PlantUML" in text + + def test_diagram_langs(): assert "uml" in DIAGRAM_LANGS assert "c4" in DIAGRAM_LANGS assert "bpmn" in DIAGRAM_LANGS + assert is_diagram_lang("uml") + assert is_diagram_lang("uml-c4") + assert is_diagram_lang("uml-bpmn") + + +def test_render_bpmn_with_jar(tmp_path, monkeypatch): + import shutil + + monkeypatch.chdir(tmp_path) + ensure_user_schemes(tmp_path) + configure_schemes(base=tmp_path) + jar = resolve_plantuml_jar(download=False) + if not shutil.which("java") or not jar: + pytest.skip("нужны Java и plantuml.jar") + configure_diagrams(fallback="off", cache_dir=str(tmp_path), plantuml_jar=jar) + body = "\n".join( + [ + "Start(s)", + "StartMessage(sm, \"msg\")", + "UserTask(t, \"Шаг\")", + "XOR(gw)", + "AND(p)", + "OR(o)", + "End(e)", + "EndTerminate(et)", + "Flow(s, t)", + "Flow(t, gw)", + "CondFlow(gw, e, \"да\")", + "DefaultFlow(gw, et)", + ] + ) + result = render_diagram("bpmn", body, cache_dir=str(tmp_path), fallback="off") + data = Path(result.png_path).read_bytes() + assert data.startswith(b"\x89PNG") + assert len(data) > 500 diff --git a/tests/test_landscape.py b/tests/test_landscape.py new file mode 100644 index 0000000..4354631 --- /dev/null +++ b/tests/test_landscape.py @@ -0,0 +1,200 @@ +"""Tests for landscape section around wide figures/tables.""" + +from __future__ import annotations + +import os +from pathlib import Path + +from docx import Document +from docx.enum.section import WD_ORIENT +from PIL import Image as PILImage + +from md2gost import package_dir +from md2gost.page_geometry import content_size +from md2gost.parser_ import Parser +from md2gost.renderable.caption import CaptionInfo +from md2gost.renderable.image import Image +from md2gost.renderable.table import Table +from md2gost.renderer import Renderer + + +def test_content_size_landscape_wider(): + h_p, w_p = content_size(landscape=False) + h_l, w_l = content_size(landscape=True) + assert w_l > w_p + assert h_l < h_p + + +def test_renderer_landscape_section_for_table(): + doc = Document(os.path.join(package_dir(), "Template.docx")) + body = doc._body._element + for child in list(body): + if child.tag.endswith("}sectPr"): + continue + body.remove(child) + + renderer = Renderer(doc, skip_numbering=False) + table = Table(doc._body, 2, 4, CaptionInfo("t1", "Wide", landscape=True)) + for r in range(2): + for c in range(4): + table.add_paragraph_to_cell(r, c).add_run(f"{r},{c}") + + before = len(doc.sections) + renderer.process([table]) + after = len(doc.sections) + assert after >= before + 2 + + land = [s for s in doc.sections if s.orientation == WD_ORIENT.LANDSCAPE] + assert land, "no landscape section" + assert int(land[0].page_width) > int(land[0].page_height) + + from md2gost.styles import apply_document_styles + apply_document_styles(doc, "mirea") + land2 = [s for s in doc.sections if s.orientation == WD_ORIENT.LANDSCAPE] + assert land2 + assert int(land2[0].page_width) > int(land2[0].page_height) + + assert doc.sections[-1].orientation == WD_ORIENT.PORTRAIT + assert int(doc.sections[-1].page_width) < int(doc.sections[-1].page_height) + + +def test_parser_propagates_landscape_to_table(): + doc = Document(os.path.join(package_dir(), "Template.docx")) + md = "%t1 Широкая +landscape\n\n|A|B|C|\n|---|---|---|\n|1|2|3|\n" + renderables = list(Parser(doc, md).parse()) + assert len(renderables) == 1 + assert isinstance(renderables[0], Table) + assert renderables[0].landscape is True + + +def test_landscape_image_not_queued_for_soft_pagebreak(tmp_path, monkeypatch): + """Portrait fit-check must not queue +landscape images (empty page before figure).""" + monkeypatch.chdir(tmp_path) + png = tmp_path / "wide.png" + # Wide image that is tall when forced into portrait width + PILImage.new("RGB", (2000, 1400), color=(0, 128, 255)).save(png) + os.environ["WORKING_DIR"] = str(tmp_path) + + doc = Document(os.path.join(package_dir(), "Template.docx")) + body = doc._body._element + for child in list(body): + if child.tag.endswith("}sectPr"): + continue + body.remove(child) + + from md2gost.renderable.paragraph import Paragraph as RParagraph + from md2gost.sub_renderable import SubRenderable + + para = RParagraph(doc._body) + para.add_run("Before text that fills some space. " * 20) + img = Image( + doc._body, + str(png), + CaptionInfo("deploy", "Диаграмма", landscape=True), + ) + # Mimic factory: image attached to paragraph + para._images.append(img) + + from md2gost.layout_tracker import LayoutState + from md2gost.page_geometry import content_size + from docx.shared import Length + + max_h, max_w = content_size(landscape=False) + state = LayoutState(max_h, max_w) + # Pretend most of the page is already used so a portrait fit-check would fail + state.add_height(Length(int(max_h) - 100000)) + + infos = list(para.render(None, state)) + subs = [i for i in infos if isinstance(i, SubRenderable)] + assert len(subs) == 1 + assert subs[0].renderable is img + assert subs[0].add_to_new_page is False + + +def test_landscape_places_figure_inside_landscape_section(tmp_path, monkeypatch): + """Content must sit BEFORE body sectPr or Word ignores landscape.""" + monkeypatch.chdir(tmp_path) + png = tmp_path / "x.png" + PILImage.new("RGB", (40, 20), color=(0, 128, 255)).save(png) + + doc = Document(os.path.join(package_dir(), "Template.docx")) + body = doc._body._element + for child in list(body): + if child.tag.endswith("}sectPr"): + continue + body.remove(child) + + os.environ["WORKING_DIR"] = str(tmp_path) + img = Image( + doc._body, + str(png), + CaptionInfo("fig", "wide", landscape=True), + ) + renderer = Renderer(doc, skip_numbering=False) + renderer.process([img]) + + A = "{http://schemas.openxmlformats.org/drawingml/2006/main}" + W = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}" + section_idx = 0 + image_section = None + for child in doc.element.body: + tag = child.tag.split("}")[-1] + if child.findall(f".//{A}blip"): + image_section = section_idx + sect = None + if tag == "sectPr": + sect = child + elif tag == "p": + pPr = child.find(f"{W}pPr") + if pPr is not None: + sect = pPr.find(f"{W}sectPr") + if sect is not None: + section_idx += 1 + + assert any(s.orientation == WD_ORIENT.LANDSCAPE for s in doc.sections) + assert image_section is not None + assert image_section == 1 + + +def test_landscape_listing_deferred_after_section(tmp_path, monkeypatch): + """+landscape +listing → listing must not sit in the landscape section.""" + from unittest.mock import patch + + from md2gost.renderable.caption import CaptionInfo + from md2gost.renderable.diagram import DiagramFigure + from md2gost.renderer import Renderer + from md2gost.styles import apply_mirea_styles + + monkeypatch.chdir(tmp_path) + png = tmp_path / "d.png" + PILImage.new("RGB", (40, 20), color=(0, 128, 255)).save(png) + + doc = Document(os.path.join(package_dir(), "Template.docx")) + body = doc._body._element + for child in list(body): + if child.tag.endswith("}sectPr"): + continue + body.remove(child) + apply_mirea_styles(doc) + + class FakeResult: + png_path = str(png) + svg_path = None + pixel_scale = 1.0 + + fig = DiagramFigure( + doc._body, + "uml", + "@startuml\nA->B\n@enduml", + CaptionInfo("d1", "Схема", with_listing=True, landscape=True), + with_listing=True, + ) + assert fig.listing is not None + renderer = Renderer(doc, skip_numbering=False) + with patch("md2gost.renderable.diagram.render_diagram", return_value=FakeResult()): + renderer.process([fig]) + + assert any(s.orientation == WD_ORIENT.LANDSCAPE for s in doc.sections) + texts = [p.text for p in doc.paragraphs] + listing_caps = [t for t in texts if t.startswith("Листинг")] + assert listing_caps, f"no listing caption in {texts!r}" diff --git a/tests/test_mirea_tz.py b/tests/test_mirea_tz.py index a05aad7..ca82765 100644 --- a/tests/test_mirea_tz.py +++ b/tests/test_mirea_tz.py @@ -378,3 +378,164 @@ def test_apid_biblio_min_seven(): assert any(i.id == "biblio.count" for i in issues) +def test_special_heading_alignment_soderzhanie_vs_vvedenie(): + import docx + from docx.enum.text import WD_ALIGN_PARAGRAPH + from docx.shared import Cm + from md2gost.renderable.heading import Heading + from md2gost.renderer import Renderer + from md2gost.styles import apply_mirea_styles + + doc = docx.Document(r"md2gost/Template.docx") + doc._body.clear_content() + apply_mirea_styles(doc) + renderer = Renderer(doc, skip_numbering=True) + + h_toc = Heading(doc._body, 1, False) + h_toc.add_run("СОДЕРЖАНИЕ") + renderer._handle_heading(h_toc) + assert h_toc._docx_paragraph.alignment == WD_ALIGN_PARAGRAPH.CENTER + assert abs(h_toc._docx_paragraph.paragraph_format.left_indent.cm - 0) < 0.01 + + h_intro = Heading(doc._body, 1, False) + h_intro.add_run("ВВЕДЕНИЕ") + renderer._handle_heading(h_intro) + # Left like Heading 1 (style indent 1.25), not forced center + assert h_intro._docx_paragraph.alignment != WD_ALIGN_PARAGRAPH.CENTER + + +def test_appendix_item_becomes_h3_plus_title(): + import docx + from docx.enum.text import WD_ALIGN_PARAGRAPH + from md2gost.renderable.heading import Heading + from md2gost.renderer import Renderer + from md2gost.styles import apply_mirea_styles + + doc = docx.Document(r"md2gost/Template.docx") + doc._body.clear_content() + apply_mirea_styles(doc) + renderer = Renderer(doc, skip_numbering=True) + + h = Heading(doc._body, 2, True) + h.add_run("Приложение А Листинг модуля") + renderer._handle_heading(h) + assert h.style.name == "Heading 3" + assert h.text == "Приложение А" + assert h._docx_paragraph.alignment == WD_ALIGN_PARAGRAPH.CENTER + assert len(renderer._after_current) == 1 + title_p = renderer._after_current[0] + assert "Листинг модуля" in title_p._docx_paragraph.text + assert title_p._docx_paragraph.alignment == WD_ALIGN_PARAGRAPH.CENTER + + +def test_caption_table_keep_with_next_and_toc_styles(): + import docx + from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING + from md2gost.styles import apply_mirea_styles + + doc = docx.Document(r"md2gost/Template.docx") + apply_mirea_styles(doc) + assert doc.styles["Caption Table"].paragraph_format.keep_with_next is True + assert doc.styles["Table Text"].paragraph_format.alignment == WD_ALIGN_PARAGRAPH.LEFT + bh = doc.styles["Bibliography Heading"] + assert abs(bh.paragraph_format.left_indent.cm - 1.25) < 0.01 + toc1 = doc.styles["toc 1"] + assert toc1.font.all_caps is True + assert toc1.font.bold is False + assert toc1.paragraph_format.line_spacing_rule == WD_LINE_SPACING.ONE_POINT_FIVE + + +def test_emdash_default_false_in_pipeline(): + from md2gost.pipeline import ConvertRequest + assert ConvertRequest().emdash_to_hyphen is False + + +def test_object_ref_unused_warning(): + text = SAMPLE_OK.replace("@Рисунок:arch", "схема") + issues = check_markdown(text, "coursework") + assert any(i.id == "ref.unused" for i in issues) + + +def test_table_continuation_warning(): + issues = check_markdown(SAMPLE_OK, "coursework", table_continuation="off") + assert any(i.id == "table.continuation" for i in issues) + issues2 = check_markdown(SAMPLE_OK, "coursework", table_continuation="caption") + assert not any(i.id == "table.continuation" for i in issues2) + issues3 = check_markdown(SAMPLE_OK, "coursework", table_continuation="word") + assert not any(i.id == "table.continuation" for i in issues3) + + +def test_appendix_list_warning(): + text = SAMPLE_OK + "\n## Приложение Б Ещё\n\nТекст.\n" + # No list between ПРИЛОЖЕНИЯ and first appendix + issues = check_markdown(text, "coursework") + assert any(i.id == "appendix.toc" for i in issues) + + +def test_vkr_biblio_per_section(): + text = """ +# *СОДЕРЖАНИЕ +[TOC] +# *ВВЕДЕНИЕ +Текст. +# 1 Раздел +См. [1.1]. +# *ЗАКЛЮЧЕНИЕ +Выводы. +# *СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ +## Нормативные +[1.1]: ГОСТ 7.32-2017. — М., 2022. +## Научные +[2.1]: Иванов И. И. Книга. — М., 2023. +# *ПРИЛОЖЕНИЯ +## Приложение А Графический материал +Слайды. +""" + issues = check_markdown(text, "vkr") + assert any(i.id == "biblio.count" for i in issues) + + +def test_page_fill_evaluate_heuristic(): + from md2gost.page_fill_check import PageMetric, evaluate_page_fill + + pages = [ + PageMetric(1, 0.4, "1 Анализ", "1 Анализ"), + PageMetric(2, 0.9, "1 Анализ", "2 Проект", is_last_doc_page=False), + PageMetric(3, 0.5, "2 Проект", None, is_last_doc_page=True), + ] + issues = evaluate_page_fill(pages) + assert len(issues) == 1 + assert issues[0].page_index == 1 + assert "heuristic" in issues[0].message + + # Last page of section — no issue + pages2 = [ + PageMetric(1, 0.3, "1 Анализ", "2 Проект"), + PageMetric(2, 0.9, "2 Проект", None, is_last_doc_page=True), + ] + assert evaluate_page_fill(pages2) == [] + + # Landscape skipped + pages3 = [ + PageMetric(1, 0.2, "1 Анализ", "1 Анализ", is_landscape=True), + PageMetric(2, 0.9, "1 Анализ", None, is_last_doc_page=True), + ] + assert evaluate_page_fill(pages3) == [] + + +def test_landscape_valign_center(): + from docx import Document + from docx.oxml.ns import qn + from md2gost import package_dir + import os + from md2gost.page_geometry import apply_section_geometry + + doc = Document(os.path.join(package_dir(), "Template.docx")) + section = doc.sections[0] + apply_section_geometry(section, landscape=True) + valign = section._sectPr.find(qn("w:vAlign")) + assert valign is not None + assert valign.get(qn("w:val")) == "center" + apply_section_geometry(section, landscape=False) + assert section._sectPr.find(qn("w:vAlign")) is None + diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py new file mode 100644 index 0000000..57236e9 --- /dev/null +++ b/tests/test_pipeline.py @@ -0,0 +1,186 @@ +"""CLI/GUI shared pipeline helpers.""" + +import os +import sys + +from md2gost.dnd import first_markdown, normalize_drop_paths, parse_tkdnd_files +from md2gost.pipeline import ConvertRequest, convert, default_output_path, should_launch_gui +from md2gost.__main__ import build_parser, request_from_args + + +def test_should_launch_gui(): + assert should_launch_gui(None, False) is True + assert should_launch_gui("", False) is True + assert should_launch_gui("a.md", True) is True + assert should_launch_gui("a.md", False) is False + + +def test_default_output_path(): + path = default_output_path(r"C:\docs\report.md") + assert path.endswith("report.docx") + + +def test_parse_tkdnd_files(): + raw = r"{C:\My Files\a.md} C:\tmp\b.md" + assert parse_tkdnd_files(raw) == [r"C:\My Files\a.md", r"C:\tmp\b.md"] + + +def test_normalize_and_first_markdown(tmp_path): + md = tmp_path / "note.md" + png = tmp_path / "pic.png" + md.write_text("# hi\n", encoding="utf-8") + png.write_text("x", encoding="utf-8") + paths = normalize_drop_paths([str(png), str(md)]) + assert first_markdown(paths) == os.path.normpath(str(md)) + + +def test_convert_rejects_non_md(tmp_path): + txt = tmp_path / "file.txt" + txt.write_text("nope", encoding="utf-8") + result = convert(ConvertRequest(filename=str(txt))) + assert result.ok is False + assert result.exit_code == 1 + + +def test_convert_missing_file(): + result = convert(ConvertRequest(filename="definitely-missing.md")) + assert result.ok is False + assert result.exit_code == 2 + + +def test_parser_gui_and_optional_file(): + parser = build_parser() + args = parser.parse_args(["--gui", "report.md", "--type", "PIS_custom"]) + assert args.gui is True + assert args.filename == "report.md" + req = request_from_args(args) + assert req.doc_type == "PIS_custom" + assert req.filename == "report.md" + + +def test_win32_drop_api_pointer_width(): + if sys.platform != "win32": + return + import ctypes + from md2gost.dnd import _win32_drop_api + api = _win32_drop_api() + assert api.CallWindowProc.argtypes[0] is ctypes.c_void_p + huge = (1 << 40) | 0x1234 + api.CallWindowProc.argtypes[0](huge) # must not OverflowError + + +def test_win32_drop_hook_survives_window_messages(): + if sys.platform != "win32": + return + import tkinter as tk + from md2gost.dnd import enable_file_drop + root = tk.Tk() + try: + root.geometry("240x160+40+40") + root.update() + kind = enable_file_drop(root, lambda _paths: None) + root.update() + root.event_generate("", x=12, y=12) + root.update() + assert kind in {"win32", "tkdnd", "none"} + finally: + root.destroy() + + +def test_argv_needs_console(): + from md2gost.__main__ import _argv_needs_console + assert _argv_needs_console(["md2gost"]) is False + assert _argv_needs_console(["md2gost", "--gui"]) is False + assert _argv_needs_console(["md2gost", "--help"]) is True + assert _argv_needs_console(["md2gost", "report.md"]) is True + assert _argv_needs_console(["md2gost", "--gui", "report.md"]) is False + + +def test_gui_module_imports(): + from md2gost import gui + assert hasattr(gui, "run_gui") + assert hasattr(gui, "main") + + +def test_hr_pagebreak_becomes_page_break(): + import os + import docx + from md2gost import package_dir + from md2gost.parser_ import Parser + from md2gost.renderable.page_break import PageBreak + + doc = docx.Document(os.path.join(package_dir(), "Template.docx")) + doc._body.clear_content() + text = "До разрыва.\n\n---\n\nПосле разрыва.\n" + items = list(Parser(doc, text, hr_pagebreak=True).parse()) + assert any(isinstance(x, PageBreak) for x in items) + + doc2 = docx.Document(os.path.join(package_dir(), "Template.docx")) + doc2._body.clear_content() + items_off = list(Parser(doc2, text, hr_pagebreak=False).parse()) + assert not any(isinstance(x, PageBreak) for x in items_off) + texts = [] + for item in items_off: + para = getattr(item, "_docx_paragraph", None) + if para is not None: + texts.append(para.text) + assert not any("ThematicBreak" in t for t in texts) + + +def test_prompt_catalog_loads(): + from md2gost.help_content import load_prompt_catalog + catalog = load_prompt_catalog() + names = {name for name, _title, text in catalog} + assert "generate-mirea-report.md" in names + assert "generate-pis-custom-report.md" in names + assert all(text.strip() for _n, _t, text in catalog) + + +def test_listing_continuation_mode(): + import os + import docx + from md2gost import package_dir + from md2gost.profiles import DEFAULT_LISTING_CONTINUATION + from md2gost.renderable.caption import CaptionInfo + from md2gost.renderable.listing import Listing + + doc = docx.Document(os.path.join(package_dir(), "Template.docx")) + doc._body.clear_content() + listing = Listing(doc._body, "python", CaptionInfo("c1", "Код")) + listing.set_text("print(1)\n") + assert listing._continuation_mode == DEFAULT_LISTING_CONTINUATION + listing.set_continuation_mode("caption") + assert listing._continuation_mode == "caption" + try: + listing.set_continuation_mode("nope") + raise AssertionError("expected ValueError") + except ValueError: + pass + + +def test_parser_listing_continuation_flag(): + parser = build_parser() + args = parser.parse_args(["report.md", "--listing-continuation", "legacy"]) + req = request_from_args(args) + assert req.listing_continuation == "legacy" + args2 = parser.parse_args(["report.md"]) + assert request_from_args(args2).listing_continuation == "word" + + +def test_parser_hr_pagebreak_flag(): + parser = build_parser() + args = parser.parse_args(["report.md", "--no-hr-pagebreak"]) + req = request_from_args(args) + assert req.hr_pagebreak is False + args2 = parser.parse_args(["report.md"]) + assert request_from_args(args2).hr_pagebreak is False + assert request_from_args(args2).doc_type == "practice" + args3 = parser.parse_args(["report.md", "--hr-pagebreak"]) + assert request_from_args(args3).hr_pagebreak is True + + +def test_parser_no_file_is_ok(): + parser = build_parser() + args = parser.parse_args([]) + assert args.filename is None + assert args.gui is False diff --git a/tests/test_table_merge.py b/tests/test_table_merge.py index f142f7f..63b70a0 100644 --- a/tests/test_table_merge.py +++ b/tests/test_table_merge.py @@ -157,14 +157,14 @@ def test_checker_merge_ok(): def test_table_continuation_modes(): from md2gost.profiles import TABLE_CONTINUATION_MODES, DEFAULT_TABLE_CONTINUATION - assert DEFAULT_TABLE_CONTINUATION == "off" - assert TABLE_CONTINUATION_MODES == ("off", "legacy", "caption", "soft") + assert DEFAULT_TABLE_CONTINUATION == "word" + assert TABLE_CONTINUATION_MODES == ("off", "legacy", "caption", "soft", "word") doc = Document(os.path.join(package_dir(), "Template.docx")) from md2gost.renderable.table import Table as RTable from md2gost.renderable.caption import CaptionInfo t = RTable(doc._body, 1, 2, CaptionInfo("x", "y")) - assert t._continuation_mode == "off" + assert t._continuation_mode == "word" t.set_continuation_mode("legacy") assert t._continuation_mode == "legacy" t.set_continuation_mode("caption") diff --git a/tests/test_word_fix.py b/tests/test_word_fix.py new file mode 100644 index 0000000..adfce33 --- /dev/null +++ b/tests/test_word_fix.py @@ -0,0 +1,110 @@ +"""Tests for Word COM table/listing continuation post-process (pure + optional COM).""" + +from __future__ import annotations + +import os +import sys +import tempfile + +import pytest + +from md2gost.profiles import LISTING_CONTINUATION_MODES, TABLE_CONTINUATION_MODES +from md2gost.word_fix import ( + continuation_label, + find_page_break_row, + fix_continuations, + parse_caption_text, +) + + +def test_word_mode_in_profiles(): + assert "word" in TABLE_CONTINUATION_MODES + assert "word" in LISTING_CONTINUATION_MODES + + +@pytest.mark.parametrize( + "text, kind, number, cont", + [ + ("Таблица 2.1 — Название", "table", "2.1", False), + ("Таблица 1", "table", "1", False), + ("Продолжение Таблицы 2.1", "table", "2.1", True), + ("Листинг 3 — Код", "listing", "3", False), + ("Продолжение Листинга 3", "listing", "3", True), + ("Таблицы 1.2", "table", "1.2", False), + ], +) +def test_parse_caption_text(text, kind, number, cont): + info = parse_caption_text(text) + assert info is not None + assert info.kind == kind + assert info.number == number + assert info.is_continuation is cont + + +def test_parse_caption_rejects_noise(): + assert parse_caption_text("") is None + assert parse_caption_text("Рисунок 1 — x") is None + assert parse_caption_text("просто текст") is None + + +def test_find_page_break_row(): + assert find_page_break_row([]) is None + assert find_page_break_row([1]) is None + assert find_page_break_row([1, 1, 1]) is None + assert find_page_break_row([1, 1, 2, 2]) == 3 + assert find_page_break_row([2, 2, 3]) == 3 + assert find_page_break_row([1, 2]) == 2 + + +def test_continuation_label(): + assert continuation_label("table", "2.1") == "Продолжение Таблицы 2.1" + assert continuation_label("listing", "4") == "Продолжение Листинга 4" + + +def test_word_paged_modes_include_word(): + from md2gost.renderable.table import _WORD_PAGED_MODES as t + from md2gost.renderable.listing import _WORD_PAGED_MODES as L + assert "word" in t + assert "word" in L + + +def test_fix_continuations_missing_file(): + r = fix_continuations(os.path.join(tempfile.gettempdir(), "md2gost-no-such.docx")) + assert r.ok is False + assert "не найден" in r.message.lower() or "Файл" in r.message + + +@pytest.mark.skipif(sys.platform != "win32", reason="Windows only") +@pytest.mark.skipif(os.environ.get("MD2GOST_TEST_WORD") != "1", reason="set MD2GOST_TEST_WORD=1 to run Word COM smoke") +def test_fix_continuations_com_smoke(tmp_path): + """Build a tall table DOCX via python-docx, then run Word fix (opt-in).""" + try: + import win32com.client # noqa: F401 + except ImportError: + pytest.skip("pywin32 not installed") + + from docx import Document + from docx.shared import Pt + + path = tmp_path / "tall_table.docx" + doc = Document() + p = doc.add_paragraph("Таблица 1 — Длинная") + try: + p.style = "Caption" + except KeyError: + pass + table = doc.add_table(rows=1, cols=2) + table.rows[0].cells[0].text = "A" + table.rows[0].cells[1].text = "B" + for i in range(80): + row = table.add_row() + row.cells[0].text = f"row {i}" + row.cells[1].text = "x" * 20 + for cell in row.cells: + for para in cell.paragraphs: + para.paragraph_format.space_after = Pt(6) + doc.save(str(path)) + + result = fix_continuations(str(path), tables=True, listings=False) + assert result.ok, result.message + assert path.is_file() diff --git a/tmp_listing_word.docx b/tmp_listing_word.docx new file mode 100644 index 0000000000000000000000000000000000000000..700a46a292858b24587694da8ea9835ac6802954 GIT binary patch literal 28898 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 zⅈeVtl@IsJK5EUfNlq`qr)8T<*n(Icb^qyEd!61*M$tyOKnS3KLF3$ZXbDPz!=0w5Gim(&^hBsUSW2ireMzzTEL19Yq}%K2{c>^+ z({cRe;$#tdZ%;C8)&Bj$+=f6~0qZrb)e8!_XlEHq?kM?{t`b^1a9qU7xsSBx%z25p z83!TlQxciJHtlXKP@N}o+NY*C{WyesWdlKk8|B?Q{f*Fe+}{WBuIAm!zQ$AkI4?75CEHe(J94* zmSn~LCHJp>sL89}=pZrvvr%FOC^Y~S$JH+;Mh(u1RRk&|Ca@?b{;VfJA_j#;DaQXt z7k{9*CLE(;Z0J8W|8!9h6CkNV`=)s9_D2r?zoGg!B^Z>p>_f6*l>ZR<185~SP+XhN zsCf4dL4c%$7d9cE@PF9+t2?9OZ@cJ!gB3#gCaEJ=%UzchtiqL|a|&~MBW87+^s0Q$ z|0N+y(MiQZ{nZ^&neWn2aNWfu^;Ph7&>yd##O#;(-S_pB%dkoBiZbU%oBFFQ&v@07 zSd~ISOk<^Go%7x!a>ifR^RtvUqgs|aU1Y@MHXD!OQaK~nb||EI#52`qEy@WJSD1!t#`!#kJh*xNyRy0fZSj5G#Jq-23xtLV!lFBv3?<`-Sxe>q!huSsrmSn zX0c$RbDuDpfjV{N;_`KKNo8Yz2B35Q=>dYo%m+bh7K@@fM_fWFH(1<)Z$|L>fEG=n z*A6Wdu)7wZKf&%y07A1@36WcX4Zv3?Gz17pxaRas$!ojO*TfbsUfT4V+jrN4t;?V$yb{kjynxK>ZF9XfIYCa`!LRS{NY~WI zE*YztwK5!Dg`alt-mAMJNkVBO&kf??e8k}gS<^c+!#=z?M8KKX9YH z5p=?1@DYr%oBrDSc3L5w`C^D_z5uSo6;(C)Ip>m&M>8qafVv}-6e3CL3?rvO2{Th+ zSI|-cXTsF6fKUXDWY91FW;V(j8~QmfAdu{%jeGI1$u#ac1822`?f?RHU6-;oM}GPX zoDiMc9p*ag*L#=mkjGwP2o_YA@T?W$U$R!|85NRcZfL(Z#8pcEnBknw4mTN#6e^T! zt;Qf?NDRJqL9Rk!lR-B$P9lG@5)pXF;)zQxstL}`C~36eqrCBwW9Nx6|yubDIn zu~N5DsZWJGz~W6wccmO!GVK;xyuBy91o~S-DgCSt>Zy@Z&w@7UzNAuenPNc8dnvuS zGOCeODL*Y}M`3=;Cq*Io7G25_{(@ZUTa|d%x==>z4dY_@WG!+k=Oj2V-MOn-U+u+6 z#VfX|T1tjOFNQhY-Xe(8mrg%NIh9u560=L4Jv!5#BryNIsJ!fLgWl6(xU@2L4jHBdHIGrSC$oefQf-fg2M+pBy5zA{t?-!>M1csAm6?shs zS4vg7UqAn#o%9FKi`H!_hix=n5Bu{^-OLe^hct!zZL8O~08`_4);yJh&w&HQBZj@Z zx0|S~SwGa(J`I;lN92&O%dYbXP&y*%)#xaur5hBwJju<-?GUcS%VPdyk;DEhHzIW* zT|tD=aH+hSyUUws5R$mqY{#LvFti(AZp+TzVYSh`0jV}=Bu+jltwiR8%4cUuH%Gh7CNZ2o|riCVs9Q zmlsvV9*p9CerO%x7zLo;U)#QAm9sFUMm)AlN48!W=Pz0_$Uh58G7Nf9%+RKOy*}8R z=gN<%0Qx4%s1c59=+~&_s+>-Y)n-ye5@AnGzxyAJ5R95W} z3T{2XrXmG6);Vkbm)*h1rlQi1*OYsx`Av3KlgC9>=P(51k%h33-);g|EmX}V;v!4T zL6|slWB5)`1;Wz2?eWuz9!rus7Thg^lR01{AIiAGdB((^<{U$nJ;tnGR@K6(6atkq zGj55Gnm5ifJsC5bZZFFg`+D+D-pIKYX|d2X-bTo4Fu3W*@Y$DJnWxnB>ieJrmGa9? zR@n~A>C=n`ULG}YILKz~IWF~9;PB8FV$v-ZIKl%gX<4w#t5@{^eH`-2OlCN1<;l$B zp(d*t-X0{QF${E-#;bb(s%28-GU4B*_j9ls!_rMwG#(bF`I2$QFnv~)7N)Cqk4s2J zKSrLe1 z^!=sMr-AhA9F=XfLgtDV1#=6d5XI}c20YUW|HZg0pX1vBM<-%H?&2q@d68YzrH*1~ z!?%!0%96Wv2SupX1IIE6rWC>UQ-LZ|~Tz?Feyx4)VNxQ_IeHT+i%1sW+mk6CWN!5i<&mMNAxPmuIvWicQ&!GFTa3 zfLV1_lUFB?o%!vRpYP;PivwG}-aJS~kf`N`4Gl$(H*KKiGxXKc+z?|FHP1-y?(=!@ zMp;}3vi!pr$${6)5dtt;mQK&3dxG^n6xqi6{Gb8Y6hCKJ5YaLF&(ErhrWt#XZJE(t zOgR_p6xDEMr$>h}ZlL&SFr}3U(K?l-xVkKNZ(RkqFWzn!5I>|#l4-}9F&tO+t@LKx zyW%ZUBUW9{RpiP^4U|hrYZ=n=^NdVX+3UmNJD@i5A1iljvy90j2n zxRP@|PV)%UiDP516v!K2ha{&_pVv)H!$@X01x5sr&;&O4a}If(>nR+Szk0#BMS9dW|qJoZpNoIDFwzLoJmIzYr-+U|g#@&uo zjI}a&(Ow9u*WE96X+OR=Jqg6TeSa$ywG2Oa7R16fCCz|+8=e4joT-#jn?Ky8c&QG# z!O?N5u5P>87ALFv&A|3Z^4Mw@^#cE{qw=2LN&O-un)wC;d@bc0vGz@AtoLRUh+Lu5 zdgjBytWwSI6}h1tRz=qt$N`X_5>%%7tcjPdNw~|*Bj@dP9M3_&3}o%37YoF-iyU5? zX0C$3cyJkMxpo1=2q!&4B^z0aKYo0aN)&vpjiej;>D&` z`AW{d(2D{^5=J3wP58R-`Nu0w|1Oco)uF5cegSN(q_0U^6a@u>CvxpR#nhNK`hM!U z&lqTDdUDWb(8 z#0q(L*&NKE1+yY5U7?vA%LgpPoOzpm%ficNKP@#TLY(k*!?h-!`Y7PTB~rn}W1H(i z|BM|#DP~3V+OgPQWoPcy2*?wV@{MN{Wdx*N7w`ffl=1ExSkWjuKst=_x<`YlmF>A% z*8Mx8RyyJ1#ec|o6SmiAaSTMM4|RV#L=h$NCsx2uqXX2wB!gK>xpNsQ`dWFFKvG11 z(^&f27q?;qsU_jmO@RP8ng9@wY@i4559>qEr9oX-G%ggjua{uE1g3*g7)IMb_oH8; zUiq`{hmkFoWEhloe(*1rgiLj+dp@+Ou=q7Ea@`gEet&@zLPC6QV@>MAY2~Kk1ThB zewstvknm0As8so}+IY_Fh_Y5u;miYwR(|M`5HK$J5#l(Y^-Dk+XS%ex99ludVa}~s zv95$@L^|fwKwd-{E7ao{>Vl7$B#BYheVD+U;lwoO7M}r2g;iAaPOfqmu96%f8;rhd;dko==wi1VjiXSnK z16Cma4Q$^gP;_qSFVV0+qS_39iOLh#3^Q3!s#bU3@rN{ zizbzkf%C{1SuA>x>2eRiF(5Es?oyTvhCymzyqOY?1K zQR{oXoPKuxxXjJ_%s^$@x)iN>ls#yp%K$T}~n4ln)WJw~@Dzj1V?X{P1{Lyfvg4M)k2LgA+A*CYqd&N@? zNTfvEdF{K!Qv)A(!gcdgu1qz=1eQOKNN1PU*s|Q5y^6Q6QL=a|&Wd9m3m3VF|Hv?} zj1A-{3YlP*6Z;WCG_EqUwId1%^pBV+wxXWrjLT8zdCmFW;~Xah9Y-ocn@lPx3-K~~ zY0q#xkla~ZVGdU|yDo`T-wU2F{mj^7eMNrG=_f-n`FZ+AiK3;9V_~-cuvRUDd7O^R zu_#Cq!JNURBjzt-jHgTT1#-|%7N9iKd<3no$5S-C$>1_Q{WCd%Ny|dC(qU>h+p0WY zOnJtk9+C;NJm1viqYp|r6(g%Lfhj&C`}9*O=7f>CJhiBC#Mo{crmz!e7?Mda}qntp}6Atmj! z>js!?GxOe=Y%AgKnQjZ|>ePOw%`;r4hR2*C8*Sd||47KXm|pjM@_^Rv)7+J+t2&iC z(ok|!4#4Mvla?13$iER75UPuDqBN%QgbO^bsB)7)t43Lf&CWj-|GWm#jw|9_43W;W z09R=_Y}#-&w$@zwSt`)@{wVDF8J5RgE3%~1Co8QYafjJnF}<`KL=Aw1iGKp(2X25N zJ3`lqK>Akv&L#U%_}j$v+R&EtIC|Dw@fMeyqZhURSy7xO2ZS9v1_0P1LV|<>DDS&*8P}tKO9+4(qKpQrghU|3AN9%h zdE|!z$iBH}ze@?t%$&yjR!k)j0R0OZKd5I=Y6Adpi&<6xgjU2^1BCW#76`33g-#{B zH>iMGkXIRWToSw-93vM(6@tZ9%Z1=4*xy76O-o$eYAW8Nb3iEF;8AdLd^#1>R-So9 zt6K2Y`9bZz17M)LgA`#ApvU%4u72gZx0U?G^!5vdF3u2ZzK@}<1zv44Em&h0j&Kkmxy6KgTMO zaRA>!{QYN|Ul~1X{zp>S%h2De~|C7dw^mU*Ind>JjsI{re z0{e$|#~b0TLdn08&HrDJ!L51SPM#{qPK$-y$ILn*U`pkPzxgTzjIcsYl*x}?MiwwI zvwlh2k)Qlbjv~qKV&}KBhyD4Y{pKxdn$UXlds`P|k@Ki*LS8}#{iJH9C#$zz8gAGG zj}TA3b_=JDU69KAMw0YrEPcW$hJ6ABPiPifU}h=M1}_j1G$+OU@~zqU^1BL4f(cjT zd%RuCF5Jx^W}4XCKm-`LvliVUvCk;0-!D;GBf|pjq$8;VOs(GVrbKFCdqa!%3zv-o zKREWnhvkrO$74lzB3>s7o{8US+psd^Kwdkt} zHCLTK+>@X=c9%8;I;q0WG{|66Zr9BQ^415P3YsX5u^6*jZc!JW1ex~23ykf6W-9Yhwm8EUOS}joU(DG~(8P{(c(MSk88><){Sc2PZ3a`-`TM%EZ zg>E$JlWI<+{^j(?PfLIKsS)U>5&gEhW%ab{qJw|zQc^dncnd&&I80bE$2%iPpA&c& zuM5X*UYA@A*o&gZy>PC*ec zbGv0XX#^Ml_AGsnPB?s*aPdT~7<(4c@58C|klDn3f0oWc?tdISa1YSCb(7z0|RR2QhxEz8a@h}Ty{JG+ud!&8qrdt*JjAPV&z08n!%1ty&TxJCsYR-gB-nAThdCM2dYIc%EYNZGWIck zszUyBEM4tEzdqANIJpHmv8hi*h_ckWs{$cmrl}u2LR>)Hv%R>9BCUzcwk24YhB~ns zs;o-4#yNo+^ygH1>im&0q4Dh~(h<`bK_3TUp~Nc6aHwgJzkC+Fyl>h~TVxxrnOBAC5e!{Tx7!sv<!4#W=rLLpkJV^QP5>ho>NI}l{%}AK)_Ee8nCNUzN{$* zr~)H1J`6B28xf59fYEpm^vF_mMvv%HEoz-b_R&aEquZ3IH>Owc7mrmKY6WWkS?Ufc zU+zQs&^C0fefXTu#+1MPI#>7KUI2Xy1u65vTmfD1t*-^628V9F4EW?<`N&V{U#Xdv z#R=C+U!yx^Xu+X6k;u}N=!oI)X9Z6gI=N9oXt@*>fkLb&v}jLathP%AYM{ZlMl0d- zq1D3SAMz)H0<<3a{rFTo2nzUAN;ms)AhnWJ=?`agv@KZD_EI%QTVY%OY+#3s8j&de zwC#ICApYlnWel1t`pu5KO_be~*-nALupBW4GV0?;3*?y%$51sJzhZBFL3~6`qK&BTCpk+QAM_>!S?Xv)CaQ7k@ zZPD9|%1eH6|H?VI_J1ktBG0fY)OePFO-*!smR8xT7y)SQlaA?61z7iD>rOd-ExQWW zTVUy{0KcyXQ3kbi`&16TeEvxl0>xn)<}-xWRe&OdR!arGGq!`CeowZ zWNCj&M1AZy9u-z6pBqi;`F131Z(0$*f|vNA=9zjqZzh8Vhcr7mU|w@_AQIn!5UWji z)aswxf%g)_K+5 z$HLD$l`lw}Ht#g@$ScUYH1{@P>$*jp2JLg?hnnwN`GCVc->x3JnHF_f0(oi5l^IDB z2*8wm?B_$7qsq>t&S&Q3Qc@)Y(QeJf`Zzf({V9WUdUFB7&388cealWlBq2LNv3^GoA}kT;ryt$PB$8TkreP3EAGaM zHMllEfcjs&z_Qqez_ySfl@L6B7g9lGnH^ESxm3MvXlQkl{WxK}RmVD`K;*u|7Z7}O zh~npc%!lBXVx36L#8Fsx3%V?M>GZh16k|aJnFUaZ-uz9>!(_;rTksDtgNnox<{zR@ zD1Q;V=bi@=^DG4td+7%fyHNO>7;WhKsb_5l;NcG|zCWyR|7OK9dbYsuwC$ZQ2p2pWfzl3tGe z>lswbXblk+5|86-JS#Notj5Rz@?SHVNZShVjDkO99>_oalrh__U8ZHZbwix5aE8ii zy-1*e&oWQ|rPpfqW16_@>@7OxlDVvq4s)7{RbI*?Z&&n`m;v90=kCRa?G?fF6L=)= z!;Xfslq2gG&>$f_%Km8586kf?#`aGliBa9jfIFP3Byp)!^D(Oec^nFBR#5b#fIp}BL>%cM&Ebyga)>fU zSpiG7jw5JT+{8TLE5)3Ru3ES`egFkeG}wv3AxJ(>H~z6fM!4q`iPm|Sp^zW=(9SF!a4ZeJXm=W1uBd3*fXY=!MtdlFITZfhQR(ySqjCx zZA+=z9X6qY7aXGU_!L02&4*8MoFnBvPhw`xd$hMS#cxP8wM4ESv^@2w?9DTT%3sc_ zJoT=@tZ)tTxD24mtUzf2yk>qCbuO4Tm&Q6)K8SvLFEtW()T}g9u0vJudd2=nv7X|1 zEBbZezbVEnQ2bvgM$@^&oD6B5XE*5VDov5cNh(WKlEGo#D~i;ght=|_hd~#dFOOT! zY$XQ%GlaT4mEuTKHaT16|6lV^X^fFVid+*%(Aa|tvqI^Pol@Pg-fZD(wP|^34otKY zXkT%vBg2eZQhv)vVt~>&)QXHye}ym>AEj@e4PA7$hV&y*N_CyeT0*1s*?f*i%DYJS-Qn<`l(2;-kKo=b|V2JA&lw7Em94zcQ|E1X&rnyjGG~w524q3Mh2b&LY zg(b_f3`_AJGMQ&oAS@y&szqX_aUrDIukU52d59(*Gi5017))PafgPpCfbe0rgj}DX znPs8z^3|1Ibq7ip{UvSsx3qB~B2Zcz^KWS=z`vwdR*?NSz*h6wg~v>>z77G8=1C{> z!NyE4gmC+YO;`2z@$J+b#^Q%eheJpOrC~CS{MajOq(|4c(CyWMy%>s^aqJljy1Hvk zA`$IZ0s)MF&g6&b5XMa1>7urG>n$1V5!fr#jl_5B4Z3UzivSOpgXzkz?&=%&{>iiY z((Y006%3gZXBd4T^hIY|?kmG<>gA6BKX%KDJ%;r3Mj9hH!Xl+n$&mEKQwX4vE$Qs! z*-+=7yOwZ2*=hFcAChNjjTS7%7(vENS0_!I3OjIT%jgvUrw@pWBF-LW%_Z3!Km^6p zGVly}d`RonjAr=wHN-{blM*IH=Zb2t=<+eqlkS5GoHJJOmG?T;1kF|DG7|hklz-aM zkB@kHiMuT_K0k>~dyyXpplmI#OyiklX7D63J*H7V2l^Vo=B?5;2nmVq@}HbQ@$t;eVy$DO^A|P5>hu%z`_9m zECuJ$fLzk?21+6r9~K~)6}DM~wJ9o|flM`LNJ}=@PdVcm{Aou{EX+rJ$;BDJ5mulc ztC8#_yqa)KX8n8+R}P847)}}eupUR1K^{KO8F#B&rhc+kTL0<70gK2}Ws zk{w$UKg)#%{T0ZcCa*IOHcjytzvU!(8PuF{r=2CZ^*FS)@pYW~ZF&TtU!g6-Q##V8 z&Hd+a!=Nlbd8mf8kaVrW)tmBlLs>ciPo+I6?v3gStmpCp{n8HwlB}~dI1DpZ|BT#L zEpX&eY5B2w#v+Jn(1KX7kMKXO1RJFQDc0LIMm7|PWc+SRZ}Vr#b-~OTf8$L{;A$AW zeGOPKL(Yltfu~|<+I3#_WubvW&xw$pk%h2KpZ}#k-Ly6frk^zROAWZe=G50~2$}SO zTjmbqSbRs$3eZosXgcAg57!*-IO}YTaJv{`z!N5;n9?_=m1>nKJV({6!jfi_U{w^w z3O*}eY>ScPNg1nRMVG2|Q_mC=(CChb5I1A{hOn@62wzmhI1kT@@Pte98ucJNUWyd} zvG9zW@zkSs36X(r+F;chT=Vq@%`f%vhF+xgWUQc?dDSM9HZ{wliJu?JY2i(%5j)pc zUpJWHb}AM!y6HFc+Xr02u9Q89GmwQf-2zhf%UZ*mB7vFdA#zSh(~3cR#i3DrRMVxh zFuHYvmCUz#7p&5CC)#SNvMU#j)*SwJ5*D~WI?5#48=6+f<5yK2F|3;;jB9iH(2pxv zFs!Lo0(W<4+Kr)pX;i%M!qRL^G=?>hOu?4?Oa9qW?~lA#`#M`1`wu*raTP~Q>msb6 zs~ncQzYx1G-&}p|jKKjSPKyirYK`;5DDud2zGSnC1=kwrwzm-GUmD>}t^FEAei3V( zrLzxBe#)0Kdely7V7uYNIGDJ*P%CE+G*Bh2{w?{G5@$> zo_K>JtzzKtMEFK2qqA$>0xjIibxuPG*ZRZt`r(ufD=Q#S#a?xr`CUj_D{a;ALsnCq zvU1b|7e4e@3DNIEq3Drkq7A}A86_-Ef2HsMnYGI{?L|)F)PTz@)w#UC!+FO&`Oa0* z5bNi`pMiV_yk4&{tQ@CUoxy#ax077Zq#p+;a=-)deLn@<7-ZM!S8Id zS$)+tdF!>jnJkxU)&f4Fp0S5HJ}=h}TK$TvyuQ}^>mdDvJywfxP5JbGI7s8s3JYRn{1t5Q`SeiXu4qV)ZR@-|W-N*dvw+fyV9r$}4r2V3e4x}aqzVcT0rmy3A zQ_iC4rp*;|`*FxkrxgM?cArq}wgiGs7?(Id@QHQ3&YH~z!|rv*!cBw!rpC_rSyE*K-xcPLF z{CIkc(Vyn?;p2dTh;jUcjn=kXjoFtKi4zqExGb|+?wZZ4k?6ENgHu{XZ8cr(cZso8pf(XDDn=9GX=VK_mTvGIncMZ<%+Gv~#b zY|nrOEWP4zYcBprhEpvOTLFu-8g!( z_n(q(1tMHCI|>x)PG(}BzyhBg2!TQvK7MidMd>a4oqn5}tqo`i;jyzJCAUQL$3SOW zGiC;p?Kwy@?oI)Nm-F7MqBwdp83l`2^eYSEmj)_#4PAdi6E?AxV8UMZ5H~*ML4)>= zn|G@3gX-8+EHn<5BMMmXqu?K5j3#)VUQXSago+A>6u2QZuFdb1do?)8jY#E-pNQx`RkW0eQ26 z(x)t!?;<{v2&nDEk;Py$hF59_533~6mY452nO#!YPs>En`Uc>A+EGf??6>X1U3T$Z z#8z|-Rj(R7h^+r!tX?`cOa|`3-+XX-E&N5nfu5g#cx;MWeM2l$PHdmgjVmjyzce{_ z6@iRa*rTXjipHe-VRlF21S6Fz4zkIhJK3Q_KNV5x+qXVQ{k9yKQgO^VEYY(%+l=ga zh3ECQT4JGo%s`zo;o2W`d%50+z{&%P?&Y%&U70kHnVW|+{CeS!Dl&KbCjnJ4-WmSA zA}mhW%Gc}>X;|YUY&~;wt)=pL?$|6kiqOWR7Nvqti5lRY9xEdc6Cp(0zev?U{oG4# z;)kt@5tM+k+RS6qr0=8P98chC&WVw|QC`mRbC3e#Ts2?)w;)pMn0*sfM_Q>{mS#RH zIwLm<%l7HPcL(m7ZABk_AMM5GqqxNkPQhHo4J6^e)Be<`D=H2&5B0G!8 z^xi)-ZIP1lZGA&I(t?;B657BVjEI5#f@krC)N6o7T{yg=rPp7mW?5NwO{f?n$a^ix zT^UxkzVU*etWyGSn)@?>xC|z*W)1il$yhY&<{XQE8L6`eGEjhq0JIi2NQjuD0}oRO zTMd~VAF@{IO7g-nAAU#-C~1^ck0X(KEp^7NZ2!LI845erVX*}>jMRkq7ZPi}031(^ zVJL!E5f2CT$YmZ>0L=>yYK&R~1gBgT6+H?1yP+a05mY)wn#}2YsWSH*>V(eDdbn8R zv>ADKhIPNr|-e|a{ z^q^t2s5&wM8vMneig2WCu!{=n+4q-Ah&l_!m#!UKG9x!=cl4;*?H@~qhG$kFM#v~e ztfyu_nKx2F9o<_AgOT{u5vi?t*!h7G@Y@L4Dsko-O5gf5cIyYT5%vz#B`e{rS2pAi ze~`Dd9g#e$=0+2KT@n9uEWP1pkd#gA<)*j4=>Ksfx;`Oxee>f$pzuk{*FhrW@neT7 zUC^opdB2#}_kI&6GL3`nz?1D_?NuI^32 zUs~0DIrY8|({qoVhBHsZh8i2aii^|rxwtQ@B7W%k*5zvKts5)?A@#yiYhSr-g?Gil zP_q+hPszX90Az+enam`-*Y1RU(g{ghX<*v`5Sm!3E^Qew3!%Tmw!CRnj zB)S^{Q=C0)!7LgZK!hqs6lPa35hKY5`OY9(4HIZ1q9hkl3X)F;dZvUK9fGGE>Zpnc ziy4E!L7^*ZY%Lk+a2^gyBrf0m{^Z+SlX55is!b=RkEUo9w}WZHam7K)9{K$$0%NkCMWZ4fB{ zwC5XvYnt8C9e=jxapEC^z?{QCIbbVAFuF;-ll)B7lLk-MVm{JRZG1KastD@4Am5iJ z7TKeYliD$4=Lb}mEh7l7bX=Opid-;k1c}w+O`8=KYBAt&tW3~$rc1FW=e>dwn{iNh zPK?TZFb_SA;?@<#XK%t?NRyNLjIfUjv)ehFmp^Z~GFlO$A-#Is)q?zOuJ!6R#KY6Y zQMdfFJP^?ROKLM4+*eP=DRWhyJ%`Mn$XuGeEw&;V64n&s2ndMrKGR~bmo zW|<5RocA-(jv<1uXpR}r0e$e->8mYTy*!ugOu0#YUPb!<612Eux)HeD=iT5!{ z?vcz8aWRpJ{3`*w4tG>tnw-gxhRu9oWxXX`cIiBg2RbWwYy4eYn_hZzI!!NAV>Ez6QJTXq!PT>y2v?D77%rR_?!*(SvLMI8HqHPim3j(#3iHG`HY75 z0TrFd>AO8JS?Q!*yDY{f-^vnJz2rUK{QL?hw-81b;Z_6+6Pe3Kg~UpEtbk!tA#fyd zkBRJ=Wk$YnDVd?$gQ*jP&T0N@;4gDLC@V68tT{#v@srHQ#fcGlLWitl_GTg4Z>M7$ zM0$RGh|QFU&0_nm=n7C%XQtCgO?`X><{evRb(H&Q38T*0nVi`~WwUE6>9R$@#L0V< zt1ZfA!hQUNHD{6)OF=#rDsKcR48$bJi!WTO=qsPNR1;B|JQureYN2#fhIL7&vZJS5 z7by6RA~|#S37n=?*9nusp98)}TUVCXx)Y1=s?-XFAk!$Z|t@3si(t6_~#$2sl~x zR&(qjx3Jb(!;Npe^+lA0)vY?qaZIhLqyprtMkiQ6HI?cz7vZfJv4sWhIq#`rRM*LK-Mo(EgQ`aEjWu!B8l8 zz!(?IUPnxjAF!+_%Rk%`OF-Doz(pk(WYs&d;lm}=DfB~W3byWcb_}w7K0Uu&ygc8Y zTvTN+9zNay6*3m!s*k~f3Z0F2C|F7><-MfPz>oQlyywxtYpmgw4WhrVVbLVJ%%F)3 zNRsZ3X&8ef)H=+d1>pReq3a+C6>?R7x4SQmP|&vzg^%o-Z+V2tR9JC%RECaC#N>L~U{vvO02vtjyr0B~d_&R_plJIzZY zz1MkgL>vF4c4iv`*ImmduQ;#MrC)DmLHEC(>955UB#m08`0YvtqEooFU$%Bi`KABLKSOR%PNbmB7Ud% zwKrMO--?JNQ~7MW!!%nEto3t7u5VbV9U2%V5r8s5(F63 zXj7W_8sw3SP$dvK$$Z@*FpxxZ3{GwpUSm;zTA6p6nJPh6AYVWd?+mGH>no5`JD>6Y zePqu3g^w2YdQ{$c{rZbT)Wp@<#U7vuuw}AxF$1vtb*!j}5Q6I^K>_JP`z*+LloO1C z4#pp(d^%aSz=1lA$x(3U#qcB?#RkqMYk!i8U1S;#hPQroyuM}XSDq%Kf4%kp5HI z9Qned$!L=4T6=74F_G5#JbBke+D5HHAWnW^WSfse1iMn>nA#pd*lOcD$V6M;hGxPY ze3e|o9@evsJ104g@~keWCR@^`{^4o%3GCnZtXmt}cJXz>YOfQ=`Pan%n(}YD%-=UI zBf96WDT|&2J_gLUku6d*;?qNOHImLw3Ri};H$oh03Z{+hX z=4s7wowS4+;tYamVD6YE54dTH#6aO0cOAFLi}B-hDCbl#J3->VS^8*lcBmxQ6K#}< z4`l#I+7^p3&F42GEU8=0ux7|iTFbQDnW-W@F)j*wBaVY(}!80DJ&lVMM05~Tg#99 z=bGEo)8eRrd?2$fPA>n*8t;lsJ6H+}Ymg!^f;qnmr%&24CY6pJ2g6F5#qU!N8qU3+ z!E>y%VQz!oDd-vDWOo00FSB;;#7Y>U1C(P>JZUgGc{2`c@T|i;bRfAPXD{<^ULkfK z*>3K>pLw1jXDN}DK%@&*izUIk!#FIUP64jj?Dhmer+Qp##8Cl({n6iG<|IlM4UKjE|H83pYQc>c=Es{*y? z&vUhIEjE%!RI`OYOG#JM&cAj;k}t(q3W+%#(B%FQF48FYGXM0p_?d*NrK3fFVzY z0knHIK?4&5P#pVeKAABh{&|p83Jua)ax_?;Qh#LRU4m4(wH|u)0 zI6d3cr^1g#eBAkR{AA=>Jli!p6~9V=tyMs*q35b|SZLLy%{XAK;lAY&ckP-*Y&SKL zpwdZroTSI|cvtPCQDA2vKiX=Qwr2&~ujrVF+bKo~Xp&R&k^Uv3aWhr(=v2H|o_y~( zdpKax(A!i@I9WZ9Tv9r~(o|%l;Hfu%$;3L-g}R?=EorhK&(*@RgGX5445#VyygcblWXdzEAM6Qlr5vnvyK@fME_|0yj}s=_)}~8NTNy$-N15z zvDa&*jM#BW+Cy=f`iJEkE1Fq=`tUS1?$QQLJNMPnXRUNY`ph)NC9{Az_9)<8D|h)e zqd18u=k~2f2B$(+WE9Z|=c0B^x^o@Jqpp3?j_yV5gN}38L(<{6)eGl^NowKr6Zr=X zAkaKnWy&T+K*ur0zQ1exX8U_*`Mz_jG+?-2LqK+q;-~)fVQcI`m9>lB{Ce%yEQVeu z=Y9NRDio<8^ABkeFvK)y2=s2TK2lKV_t%VKVdH3vgP9T;BCq3-pJ zP=pm)Bv@`f(crP1`xaIQQI{1Dm@pAABnX=!o-%IJH9;^a<05X+wLx!2r`KOVMdA>k z#(JugIiNtnU{!2H`;jw?!4(H2zz~3TLm5`RX#Hi?_2aYRL9`z@!%hk~$1#duz*m+# z32Q=D2h9Vyi1N$?1cI@=@UfVUXkT)?osXQTeWYN}cyLgfMNYWfgXH)wt=x3r5-=)J!TYw*2JtHo7@wEKV;*B5r{r=a#^xpM}H%S z_D2}x)gM{|-6ioDZ^c%ELLTVXlxXdefQzyW*#Xh}2(vCU-vcLqB!Yl!3N5ZNYIb@W z>FfNMbJl`FBne7dyAX^*M1VWAN5oCaH=#^1BL753%Gb@-8iJPvzrOCv=c(+Y0fWrW zgJ)W``ZTg8v?+Xwuzv!#j!FoP@9L$9fNTtp;UbXKLyoL|fswnoP;eT8l7TEg3vG64 z*!$COwihS=6a;}Zc&Al>=8FW$_1Y!I>qul2g+3dkZ3^ zgia|EW(tRj>T!N39%BrQ8) z%ef@2r7&O;1CR>sqb!qxTuVJeleyhYa}2%nQi)J*?O7j>Tc-OPRar`bIwJrh%6kiomv2NW-?4Z8Hw#Ad(7=`qK1%#F|f9r(t3lE6*}HoIZ;t>TRjug8KOp@K=~~(NojZU?tD|}Gg7WjsN?WKY0iORB4!2y5 zflgc~n@K8=2(@`lc!ZOT@f@%kLSo;UjDxy*zTAQ;mEmEWK5$(Q(kE`Z5ePpUA{q%u zo8PvJrK0){>ovmai5P;P3LvAdj&O7){D3$L&Wj**J;Wl5}=nG79+DdMLP zixV07#$qBSt~;qsiMjLDSH8S7Q@|Z{3LW<6lH049*M0UwGQxN^Bp)j^*blWv2?K|XMa*pj*Cd)Oo~r*U-}!^f*Os_KyJYbv z!kJawEmeMZ)rLdyQUt`|G2_S)%pwnmJR(t(f|`vRmJ0SxvI*B%71Q~0c5XaZUz(g4 znRT)m-(N${wEJJm7abh~?zRUPZ5;A2JQEe58GnVNJwsc_E$1Llc)4zgmCeCiCDzBz z)=xYAEKgi0^%uBdQu55G zJu4GiBycBLUE)7#ZxfnzCf)%a5RO2DGoOj}3y5g%QZXSvk=zZCKg&+Lry7X3#l9vk7-(QI&cZA(dICaa(38OpN3JfrZmO^1-8l2&zoh*$3(N_P}0 zQo94C*lP$MK-}YW6Ffv>Q!{CMkl>u;CtNK3|kk&~e7 zLw>t+RDHei|X zlBt270yEOsFn3RWk8th#Ak1XO^_u?pv2|&q!)$-wTSdS2prNjjs`eQQ4)^N~)`S(` z$Y6Xlss&fXDOR`66hv~4Ekm2ry(Ys*1I&lDTx4JRX3d{CB~rrTbKpz9;-a~Wn;=K0 zFasqd?c&uzgGqcX)$zlklj!V9fP*}U5RSnE><{hUX9$G+pS>?nck(2nB8i?&vs?Xu zf<>NJ%R4^&cMwl^hl?6fUF^Vu$d{u51)rxacs9ko?j!^Lr>DEABm2deqymfP!o*iCkhn!}*{+-=T)XrS+R z(VR=%Rr2qGZdsFI22I7(&70(b$12l3)4Q>tgtTlw5%Gj{si+`HS1?y?mqiH2WDmK9 zyfIM z8j8C|mF?oIC$3#5zkAE+-r{No*BSQ;l_JVX+Qq~6BO~XI+12MWhL1CWfT!+^n$X0| zPKo#$Jr1MI#2@!f5EqXE^yYC9#e9dnx2Y;ole3nZ}KRmB3r)BgoU0J8q4B;o&)rpCp*2 zALUn1t@wz(54C>z)xS=8Tuda_Q2cTFgeSmC^SK9whl4?!^g7f?t8U!tk$lBvJpX$i zcT3}%>8F}hP2G%~fX0UX89n%q+*Uf|+zdU;(!N=;?-9*~mKymI@I9Ou# zgXXE8*J#V*R?n!KPW& zLfRd4B~MszMa%gPqCsiMsesZ-qY0mIWX4fv03VysXq~OMT*aP^PS1W?i9;%)Cb|Zf zj;?#sG2ev_0IUfi-dhM*_|2paxaWH!c&XGfPA>UfrXu%oxQ}#qX);1a>q)05dnVSy zU~^u{+07)hE(+V-+oBE1plzUSL7!epr!CmDF7QMC?J4Ns%9n%DFd~PM^+=hEC3m$)#@?q`g3b5su)e36)GKElf{e$&&T5=wCH zwp!E(?cI_+2%3Y-7-k*F5G-AycbEe3uEwfX4$QQBpmFHgLuuKpC8m1;>kV^^A|E~m zemrr+)i?f`ZA)5zRj<(@aG^E6fhupqJfGc6;GXyuhHz}d?TUUYSr|W*v%9%r&bjt7 zgXME1-or72fZ}r!@>8cYlulF73^!soOM+R(VA3V=#>ll#T<^H5+B8!0)5qopzW^f3 z4Zej8Pdv?QzL~~sJRi!T*tQNZ5n`D`BwMw~&s&C(*l~8j&{Og{?({q*abby8)nE=w z5K49AszFuGM&%D=sYu~%Hy=`>%x{6NKYxbKB0hOS4r@geCc^UNWJE8;R<%>%M`J8D z$%h5jzRX#n&#wyn>@&;Chm`P2Z2!?Hyg$>SIJ4exR;|f26cx4U-J->$vC4T9p3yWL z=|MUE^2#v|3Wo7Jc=W%;!(N|@-ygoO56b_P5_@alt#;La;2>5uiq=29{jO1@~VOG_Y(bw{=r-PTbB4g_}N#|_}}Mi_jDas%CvCQA_e-);@#lKBD{lSZ>{?8QDTLW)XKYt9o{L8@qN(8;N z^){RG$JXs@rOm(dVSjQfZ>_vdLHw}-tMPxg^B)O`xA?aa-aq(#t-tYa;=OMzyp0|G zu`sFkw}pR3lHS7Kx*vbwVL@=d)HBvfqY$Ye1SXE^j6jA*&G#{OceBTDmuPy&K zqF+icH#}=%h-a7fXm(IG(;wY(vYN7DDfr7Mb_fa2)rKQSv8cO+YU?u8&!5g*9A+Yd zdx;F&0z+uAt4FW5`j3qeu~3Q~%5|8At-IV~nCfwo_`P$IR|TlzEe_)Wu{DNJpT->A z2lHGYA;6P(*4*vEnv<|-?D&uFB1oHwsA?7MI4X=b4ZM6ORpAh1-U*=yk;y<7Sv|j= zqO597sU+GeC75yN5mBurMyWS|m)n0^nl3_xgCpL_s+aHI@KRNGW@;%W^KCTRzqcbsA@AXm#dIlnG2Vcbb%~wS#VnXtauN2HWn-{S zb3Fm>fA1Jup%YxE`rgmHmHed2c^Dez!uvP5kb}%41TDGzup3xBD3Pepfdj%Gy++vB znVPG@_sb&ZN|0Q1y?}dgQ_It?dA&t2eS-i1{QLj|$p1e`ju(g3a`P9tvVS=Y{g>o= zjwaSl^mPB^{~`DPU`GG9Pp^#cvm9W6;lBy^3YhCsSntCqkfk@8-N0A_htQIcM&4Mp zT>1XuUR?#!IWrKOm|sYk^>oS=z zIDR($8Yh;Z91n=lKnAflXEG$Lsz$aYX!c}ewQ&P`)n zTDBC|`VC&rBhnp=+dhry17*rrm%>aN8`HmCuQRB8p3uq^Wl8x&gJsP?PnsELSP6x7 z+c|gcS8M)Z})x&>;@V^KH_*V`8*8ZQJ8j~h$Hy98^UO?aB z!Cp{li)8RaaZo~`jpJm_fwnLcIpQJiK;vz%yu8U#X)3Cu2LU<`#oL{nf5l{Mbj3&D z?{+e@lXx_fK+iVq3AT{FHNV{-18cnYvY5@AQ;Y#}x9N2GxI93_gMN;>^}mCbo9o5X zfqaB1>bd2n&P0Mn$zdI!K)XBBHdS70KsTz>)s$se#xCKfNBq`Ba+_iNIo97JT`#r} zhIK36rkph(1HF_(zA`PEz^|=Sx z%ZSwFPed9ZzsgJw0IFR}m};ilVn!H`#kjz&5gaeOx=Ts5^0iDf-;Aheii(Z>lM`63gkQLi0&(bES9$U~_DCFXe+ zyr}nxwNlP71uCF&BH39L&rC}Xrw@z>t;G4*=VDa*EuG1Cj=XmeLaiPdue84cd3^k& zTbzq1&rBAyn$!Gt8hHSZ5*j;7Q@>pZQFrUeLyU!$Li=@}w6i?-)xE^Da4rQmZiK(XK<|s-}OjtNlCFEcoCi>qths%P- zsFU1VI1QJX3i&zD?o4K@=;YA&@)@87iI`Ky%taQK)pk8AkuHT+^V9GZWW1$+{M3R; zG0q2m57-v#y^TAtMr<0*vo(MOB+<(nLnW1sHmzfAW!_@7Et(^wIBMLql?ndL4&Sb=57R!su=*jgD0mcLB7Ay##vVpWcdt80DJe0xeSP$^GSn9PeTgt@ zBY!9(kL^2Gx3d&O9bgaKLvEzgC~HkvS2iiVMI<%%nhG`+l@=xYM_>aVZw4yl7G+Ao z=3vceH8ZA@hC;mf&yr&R1634S1*YznCS?zs2}r{tBg_2UiL2rJIqRRE%BQ%Uwf-`d zHkE31x-Iw$0fWEG2x?&S&7_twE9={n5>3XiS3^R7NciQ!p3s%}bp51qohA8EO-fBq zLOm6&xh%ra>_oaB`shp+6>=uEJvwJjmsuN)RTfb%aqFHXn9V2di1 zzu|H0MW5-*8Ix5q-IHbadsZ*(w~BWQ8*(}J3AovkhQo1Y{qLc!;GTVCe<~Z9qoEEh z_@O)Ik#CYr_UUr_@wr1(>jv}nl*wvDqHJIu z|Mzs%X{YN}nscnoRJv4GwuBxE>uk*r<#&CT#pc1}D`Frc>&w{wigvv|fv-q4&6>0k zVPW$*q*H!llyieGy=#?zQiNv7R`N$1ve%JFpuvkX={gWUB7q9vQ{OO0fO#T67W#MO zFU>!a1sYFz@+|39V)^NC?GKnbq?pd9yWIl(Iz*MYXOensRPdQ>#Z?lON*-1;PHWO? zcJuF*(kHVaoslCs_!p)4(W&K zg0+|{kzL86Y7QPHY!+dP?kiu!$Ch&?wqvJ?BE8PE{a9$8%Efjl-&Q`B%)4*XJsGI- zYv+rTQJ|7F>Ptk(KxJX(}^WtMSTjf$D!sA%5gX;w~VJ@f|uGvXctE-mavHB85 zi<)jDAEC^mJ-w%=CQQp}G~OAhU5EjeCc?uW{HsUBw!ali3~A?~5!xFb`?p%8ARR6r zDJ>yvpkC}M4dP^7U=L6hWi_1H;k|m1(&=Q>-)>i7QgbtJot0wD&Bq5jsQE5Rs>E8* zT2xwYD{}Do>ul>TTq0oU;ql+BN<%~P$%wMMk7e4aq#&mXohi!EDH$DI;T#{m5rzY8 z&}ce9G>{+>0pRy9%bd^^6NciGE;#&WJ?XLNu(lG4;+|5Fc-cxN0|>-@78(kUQ%x4U zVBtL89gvN$KgB&S%+kEp)2UP&k=!d)B?2`49?pL?(!Ee6GQH3#Z>IFWNmv-#TU5;! zt;qYF1kNwoa#COOyx_4+oXke#3GS+tnNO2}oViH3OSO$y;t|VSZzYLkx3`|_B3?3d z^ry3Znt|*hbuq|Aag*eZ|4G|+IX;t71)tEhb7~c#*JA88Ck6%-ehPg%q844J&TR<>Wh1Ef z#CCLMN?1x?tHLN`fecd+qCN#$S;0vlo7#sOMa)bS<==!5<)T~gB!vRWAVuEPKr|~N zVog20*@$SF@p1n)mILyG+dtDcxz=&z(8h*ihP7bQ`q3IY1A!Id3Y?0fZ8YSEw*(A_ zJ>9y=wDa5otCEtSfvLl?*EzG!UWDilWgpF}!NcAh(n0+Ck^(wxj7mUv(rwGHWXp0!D5!jfj^h-r*V4BF%*Iu z(YuhC#Oh;uHDSoFqJ4)b=_DKe5X4DAV^hgNd7PywL07`5-(BFL10cnBEi<)`;yi2c zpe|vJU%%o@u2rU44l%Y}qG4(9mjiWvU0!^{cRi*vzZ~}quZx?$g!^&!>7;!OKnTw$ zOh7~M6IhcK6xC#rzToYTofG-n-kK3G;6@^NjRtcCxv(U>*A1xc7!{X!%qIN?=T(tE zT)75*Ngw|eJEt0}yOoTiEt-h%n>*QUg9)~7&^_I}Y~C=g&mAN_2MPMXzW$-$0lGEA z74W=+k46>_q(~IJ4?wxoIJ!>ITw8ZQUC^awPk>P00LoA7htyFW;Q$2s95vv3*C7bR zqV;PZB0e=vMqbX+JU4l9umk(&RS2l8*Dl3B3=Y5kP1ZmpYI5vZBSyY4#~-Ep=#oO# z4qTvhMF5y8DC;4e!&r7l&jBh_`i5}c&5n&I0+;6*;AI*H9I;l*zlFaWZBQSpa#*}k z%Mg&svrh6tzPKt#Qik*PdfW}zTVTT5rhbXrjDHI7ZU&-8Jepz!GLxyseI5=q2SA$1 z1`0-QYlx(aaJxxSAxZoRkY@chB1dhPcz%krN;vG zRB;qmw4)__QLIzbZP2nxXhWleQoEeXjD7OF=}Xd5`KRJcwzNS@i@V33e?ROb%GVB!T`MksXN2}9XwPp917tTX{ z9JTS3VSOwmm4m#VE@2z&Q(|Wo&)kyIyMv8BL>*S^^&Y~255=vf4Q0q^LNK{Ba1n{n z3+gNmtmk4QFcwXeaMM+3+&vx-9?TYY&DP8aFO){dqI60DtG2W>6>Wi~>2QCB&f02jy zIh$yhr$o}!L%Ro5>MBn@U#)*!x6~KhNAB?pfq;_?yba$KA2Q`uFRw30U~7Ki?-}aR zAS7rC`*6kl5KuvJYx`n)_W$rBMKTF1auW4KD(1xIQ)PbZ+vZf$pLmPVUq>!CD zmbEOmgjzQ!(MHnAm0KbWlT^#*ClTAC@MPoISQDjlG*^DqU@kkgM|UP4bPr9zlRVWs zeWbW(7EAh7j)qu}<}aM%7X}}l=}fhLzm#A2H38Jza?7qwd}xdgXSTRr=OcuZ>>ovUVM3r8+~}#2=$eQ3sWm8d__(GyBA~&|BOCIBfInDOF?4uqhg30y z>aS?biB(spG5X{->;^9Xgh4oxV-`&o$*(;ttTS-=@%y77t58}cm4T_CwD!_CC*HS9 zI)2gQ@Ht6y4G}|Xyja&2$I^i}pqrrM&;lKebMuaRfhSEF=eeu!OaVi+vp})cwfBbm zcgw*MJH62x`^`D2X&@3*NofsN7^MUcciQ2XFJJKCRC!(J0$`w9RWHMJk5hvBAQ=`g zP5^0(Nwmwu&|@lcm_YL+!EVHhCwa8-ChZw*FvGl9{X?G4cy=DkI+VVhWuer-!1G+cc_k&EBc#iZqE)BG8zDxg)T}N z*caZM5rrEyYZ#KVd`D#X@PK!CF5)Z4Rz>Se;fDOfM{yTs#bFe)>VE2wfH_Z_oQ}+m@a98cB-4cSHKZ2VI z5e3C(&K0lkfz4nF)U<##1@2OET3Y>rW{FM^l${$v94NVBpg@6ZE({>jJ0CYaHCe3? zL>K6E+7P{AoP_f&3Vi}5^Whm+O^&s1BuWtcNeET|HeHcKaSZSjyMGSmZ}*B&Rx;sq z7qv(Ln=LIsXY*RlT(gC$M?KZd*mJupYx0a6BYE#FOQ&Hv`5ZOI%3tM$)UgHQ2hQQ3 zcZZS`4*<5?eRh4FP;r+XFNlER^)Yg@c%g1k4)tF#aq|?`>BKYloKWoQtUV}6&41@J z@n2se-f+R6{q%6fobJR-8=c%_MVzxbWaxyPW1c_~zn4A0pJ>6hcdbFlb36ynN$~Oc z9lBlo5zqN+g?FoO)C&YNPe?xWCmD0evXqgb_^?F*-d$^ub8nXC~6WP z%4(Lfb<+57Pj_JJ3nyer0cfD5odCISfMUh#D>oiEXm+ei}tq`)2N7rW-@(SWp{7d81= zp$CSjJx8Tp%u>T=T$e@~d&}INLsfqLN7RRY7O~0&;gi#BpuK5l5<%uKp2_VnVMx0q7lpBk+FSDSZVYVNtOtVW{k8ArW z$z$vnP^crPYUm2!i4DSZ;`B@`j`Z|QS1hfG&>u+-KKpM)Oj5Q~i5+z3yRi8oZn<|g zn~3M-l415k)j~rrC8lS0Ds~2~th(hx*%uXSs$)&L)d|4j!bFed!yA9bsyKzg%y+JM zo-jV}sS$p&>5vDxcw@D#4XXU=NmzBks4+7JGW;Dc7*q4m& zk7%6OKroN0+^{{%S|dvHkK9LcC%W=SV?x9NvN2Et%-*XPJA;|5(X{7QOv{Af@J)0g z;2|0c0*?M`vcmwD*?y@!!^-&+SDjo{ET9_QaA)U*4iP#iO4SQF(XxRNG!@=}3A(#YnS$S4y< z;I#41^5;33|HimG-(Nu+5jF5yem?-!9a=<*lgp^i0ZbETZoe+jKr2EEm^c4Rr5w%cM9#8E=wFm}ZJi;KfhCOUd zLg&B!L=VvXN|R7>q;h#=@EqR%s7%IJ%7s~Ogs!@b!>+i{VPzp<5_5^}Qcg?p!S@<`&kY>|AP~}*`eW-N6Mknhs`iW zX+hIW`BY81sCm9gz4=iHImVnsy?y6$hLV(4O3M2qp0r?uMk_a}aIKLRL?d^uu^DXK zrg}dDXZf!nAm(cOlaVLoGIhRnYMAv_*k)63@gkx<33owKnmRXW_*g}nMuwn}_{>xX zR5}ihcfHxt?avWpMnirs$ggao7$^2s_zkeQYl4U?OfnSCif|rU!vLEa`yNvpv@#^t zYB5ak1<>wKjxf&KCLc5zX4%>EK9X@rtjmqtvR+|>TU+74s8B<@I{{LM%c=%LJ5Mr9 zvBUk)O;sckB1aNb0!e#b>@ZB6l-k|N`T%^&pjgc9v8r@_ZEC_w4)rCHs}_gaaE~lD z-bD(C0tIE^mhi6Gh|OVL`gf$?wN~Oq$o=%RgW8WIM=^b$J^w5R{rp_wTG?Tb+||&4 zaKzH2(03K3R?+b7i#CIK%syd ztw)*g#Jr_%{eXTW%}U!U5I>LJmuMsk0kPh;!e?%1Xn0sxc^-IE;ELc=a-NG{gHK2z zXSbph`6e)PGIAHlq^yV5c*1a?-&H1t1QR3 zwvk%Vp3}I+!tUGIJ;P&R_N}Y%C1G9#ZMn`LqIW$)Po&e3L2DHBgx1wL1}&=Wcr|$) z-xc41=rb4#Z`l&b5}NxZ(zEI!Hg@(2r)O{SP%lrsUmAX7S&S$X-w>IQ@#J&M)|IpS zL-(-l{xP}n!WU4&W4Ezk+%>89wA{t>llGD>1Bk9INcLyN?&re$hVS{g3-+}~=;@`C zwsh}nYvVrU)5LxCLD$pj@^~YQxDAy4S62{)TqtAOB90n!_@4U;!1#Pv90CKieV+)N zu58Vj1SN{%DzA6k0Vfmpted%ZS#M#zdGgkHiGOqN1312n6Pr2P5?^*d8C3dJ>jD`| z`mmTzoE$hlxpbE{Q+YQyeQBtHkFx~heNqWuXo+*Lg^IaOLlgHl+8&gRa-Je3Hj2gF zCR~x)#Ytaa+KEABFPh|gsSzcWv+dgE-ZRe4xMsw4q(v=OaV%f3#KLZYh*dDQ;bZ>> zMV%tA5({q1+9s0?yTW)*r2w-zgU3uYs6_ULv(WnfI@dtTT#*#-;k$az&0k5`b7%_9Chex2 zs89-GYt~98587*c<@>iR6)8~q0ztAtG&1ut^-FB_`E!?AW?csTtkV41D*dW`b0`Ko zumxlOVc zfh-7WFwegLSs;E)GpA>Qp3`{ZfPB>p08A#b!VNF{E!sD;k3nsnghYblE2%Uu4WCQF zG^@OMa+1mNR64)J`ZmuV#lyAVneF*FHrXRzsA`FBz2%>S{dqcygN8mp1~nbP_;E3C z`b#p;+D*@PE2g1u8F>@}6(4R+!#WPN@JUbno#8p$Bs5@23(wxTjA#+HM^}Q*f4_z_|L< zQ7_1G>Ha%4&C-zM0f22w`v*GD#6!OShCtrgt;~^KqfJPX zkg!sr7jK2d1<)HN?OPg3vNVE({vAvSh@L`)0^q79wEj>>UtDol^nD5xrPdz8uO&{z zPY#co=s0o6)Wsb*as08zOYh0C4UqPzia+IavI)N!)4nl)XT z^BjhrQntUb6>x{YG6@A8@5)edKDUq@@Vn1gVh-kXGcy%%{N7;%Xq7rx|KMA0-AY9% z1y#+!M2kyQDLEIT5zMVX6_lox%fV_GClY)=5}qIGmu8uMeyj0 zR1nO}5fh|TE4shsDg@G_e8w-GhHA_9=)&ILvFe?P2a4t+c^2IvsB6c@ht5Kr{ zpvTVWV;Fg&=B$)c;hAv9jdAf0Yqr7DcuH2!r8xy3YaxL786X5G9k@FZ&o~f;M0q%C zk+57YeC@=3R#DRTn~kuxOc*Tb^}`opILLus-Z!$?oRPP5owaq(RcRt1?z>vxiGT+J zxojk&8;-fhfg<9KMtz6dtq7ofgwKS2MKmRFnbU2yI1xqzi-FY}vfj{pT;w9#Jw-Ah zdJfk5VzJ!5^YanZy5)p)zh4JmsjP;gNpYD1XS6#1s+enG)oNVKWo-Bo#~ z73Hk7oAogDxcxBt4^gR8*C1fPv0KAyMx7i>Wshzx@_n+y-tD+cclZu;ZuV|{k<;Ulo?JO<_WD_KoO13c= zeaM(Ldp5_F@prML#Ph$5O37E{VT#X-D^G2hwv*4}C#kn~hFeD^7-8H{TGu*mzoPdi zyDOe=cG7*+Wb~G%-rSa+6D@+HJu;hpeLim(kGlX6-+`u_>u1=B*OsP^S&Pi6Ny)u=-%$rC`w32Fv}BIH~d%c&3M68KcP zXS{lE0brocUqUI zSDe1zCMqgn-s4Z2!4pR}ALw!7R9w6=Zj~ufTZ(qFAs;E7Dw-_}S)Jb+KC9V2SIn7g zT@X%S)7G5XMYG}ixAC`<m90KHrzJwza`0~M!a+ioeNdV@i*al$;h>xC`5q0Ds`G$hYnZxy(s8{PoC3#8JG z8l>bc`{!T$63H@Qqfet7Sy&P*TV?W;;5N=o7If%+qIXaj!XzYaB{P-<_9LLFI0ndn{% zr*DkFwj1Ik?ZS@aSw4i_5YjxCfAA}fkx>tAP#qO;*{-Gp#^8mBuf{vC9#1VAa> z<@y?mg5bq&}C$;II;GrnwMLqZ7wK2oFk4wLD}1l=Y+JwhiBn1;5pg%9iS4-Qy}C zY%On66s5?~f6>ZeoiG&HPU&nKSUQ@!UZqb4iRbZ1 zN$|A3@vDfHSYF7;4Q&pO05^j6P?Y9yAD4;5ev*MefRo`W3V7pfwyfUHP1c0sCz!Hh zj~LPKCr8{=Nt7q|@m_%P=fqg>e6uRw;Syz4U1H>fag9jypf!I^5%?Z^?SuY%#R0;V z7qMyGSgXpWg+PYD8hOfVBxeK60_YycNvw%=!X+M58pTtdaldK^VVK~Q#I}bwJWwjY z_185|Fs2_V6&UjyGH2)fUJ_U*(dG5K31g((t)nO0!Oh$v!Neix`HG>5@#_uOO$Rb! z77GLm@b>FO$YK@du7=i1y=~?$ja|9=Y(LM1b6R#>mIrV~CyrUL@2Boy?;dOWzC`%_fuNAA`Xsh#(cEnzM;ukOX zZOz5OST=EqB7)&jBpfzj%_M777DcHV;o{*1y7Nf|2G5MzAP?HpC#{ zcxAUVw>%$u?iYg6u~*V0Hm858OE7+o=o!u?D8!}%O$p-Yi`%uxlCXpMN)MOb!N2|? z=;K-IpFzp+l@$pmu}>qHmPiL`uYvQwB1-kCJ%bqwjMwCZ?P}tfE=RUH8o54*UEanr z=}5cdAiX0EuB#VG+luG|G(a3+c(N_6!0R?$>gbIS7z{0vQB^lliUye0NGpk91qXJ*i<4Q{&n!&M77eTqeQo64=nAV z*>&P1p%MZ__9S4NBRf4);S5ddPILW%LH&rMRVsvmU?+jZ%*QBJ7RHt%>W&l(iH1PX zWytPF;66`9je~<2Dd&h`JWRQLdjkxb`3igfQoA7gI2KZHv;jF(@!=3=(nZIk?RnD# zV(&I3qS0lsMOx#fJe-+)HjGR_T(tdQjOWIQga)()XX_9=BzCw=Defa-wJM^ekWN^I z48K&iRBvnMGa2m&KW5PCPAZ?Ml^NP9qaWIdczW;imUeLa3t6#%)+t>3ibsvNru>iEHWEU|i=2nZ>GqT{F5r$tTiR#( zA6H9R#YUs%#!b3nH@=(m(4^{iwco~YDA$M)0PTPb*?rTRtdvrCTLH%|dKm?dlFcMe zV)d>!S!E2WcB6g66(L9cA0Gq1H7ZjgN_H+%LS)X?xx8RL<yhNMWMz;f57v91kV-K zYxT$9F$oMD;Qw}=o7fuL{uM5#f6z0TIA!}+w?=NLpKxieA)Aj}si|d)*v`va%D21$ z!W|+lF+ezEw|)9@u8PAmkL%Hm^pDwp(g0Q6ek*`-6~lt3<(XsV|B-vxM#(3FEw2Kdzji59u9pQ24hsd%4D4wtpMgG-j(Uy;C9w&-<1wKw@Lr+rh+=t zqy>QaMD3^|gq@+ft=EXFID^itSWR7(6Fn@8mic!R=5qJm#&2fF zFsiFTqi zw0GP>5v5H^w&!?jpQJ(tt#4_A@L?_HDQuMlE+#Q<*-1Sb{eGdRcd{k8yz_P3U8CT#Yr`Qh{AY9}W^Y!7IzLqz}RROIM zxWl<>6?vJs*+H{W1>yjW-WX+0oYI_fRQ^@M97$WzTIfm#%B-K~>;#4+qyaVT0A1V* z87A>k0?S}qCHKm|6s*azyXuNn#nAZF2bgN8s&Me?YCNhWlG}D4L5d-f#Xf~4Wx0*b z^B^sg<&NBB&lN-5LJa1(h>-*vx~3}%G3JRmz#y7R>G2VZS}W>!)E>EdyKV(wLY?=b zMc5OF`8iSHCGNrHMF z`zod7)Yx`6W%B0`#NO)7Bs2ri?@AW0TC`$i#pNxuP9Zq;JIrnIe8lx#~!{WxQtvHyIVN@=RNUWjN zX>t2;#`-RvSWuO3{*VwBZ)-6;rLk=5Y}_f8C}wP?1MuFuU!T=Px5TYnWVMQfl#mF6 zUIsx58ytcj-5q~^Bx?Pgbb-rxr!HE~$}l%mUgm|V>st%|&2Icd`M6Cn2Bzcl?{=?$ z$BN2&Y@FWTK==R`008yBu;OfPVq-%8ul!#@&83En-3A**H~gwEfwSEgBA1Lmr^w~% zRdO*bQj1Vbqp*}Ug#y7DNjL13L+>Aht=a*o%jSXFDt_q8`LA?q+-A5_y;Uj2WYP@V zO}3%%D&AF+r_LRGlA{SVS;OGy(AY$B+8WoobHeyn?p%r0d)ueq=*!$BsIowl?t+@z zsp2=|$07LI!`M_ri~{^Qqp+LzL`Cyak|OW5kw+rg@gUi-ZJ~AkWf0VUy(7Ksrb(Vn z?V4eXGTe!~7^~YjaAk7xdJT_kp(g)UQF$g1RRyvoQQCa{hBp(J{DXjb^y{- z2kl$7C0Y}2m;le-wTtJ9gKVBwP0xX5fW{#Yom@+%O$x*4uQiiSV zuj?d$#if#j9W>tgXh)inH;!|_e<0~pSp+OI;|ZSuS4tS;PQiYOesB+mp#`!-&=Fk5 z)1M?aN|SgCC@tOd%KEhec-%dXpv&PGEc}*}z4>zV{P{lX%E47$u}4BH%l7>l+JWa2 z@#XoxehaACbk`XC`Cc&V`FbzHE)DNB)8l(T7%MQ_@%lc2hsU|4^#d1aU7$7rG$zoE z%Evd*EL@co3`4H8z&WiBmpsKbnydw0Av1yBMw^Aw?yV^3APs^iVpLoN+zi`CvQKeZ zKJK?zh)z~{?5BX->K`f%-57ZtCGB8F-npg-$}FZ!&I(Oj+%!UP#EB22D9pJN62vif zqo93rNqP5|;#EF{6S0xbqB~mJB4%c*XMOw5Et8Pt?6 zIWTF{9W@3eIt%L{6*X?9E?ip@FEU8au~ix2ES4d<9WZWQ#7QYq>ik;&RARdJrrtqY z6iNhMXY?ZVfyglf-<1fbL^jI;3AGM?Oh{ZI^10QKs&4`{{{#U%lLqF+MpAN*x1-r^ zuC{QVMc^?Z&OQlb@5mFUfsicN!=%`3ghgYsmx}{0R3a!-?;u$9?Q$dJdiq_q`I6S& zzrxfM`-y1N`uDXx;;dT_m)(Ivy;v>*&g{$7o43>V4Eu@5lh-$`$9m9|f|uhioArKB z$qk{&C4UrV8=cHgvcB$YfCw|A@S6c&lzwU$^`4;L6b589ZyptAs6CXSrKlhU^Kzar zWs3a}!-VYiJK!b$cr`0|iWI+5(*$rwr(&n`vGpVk3;IS_F6xxNh5>cTwzT>bP)edI zbNQ6igp8{sGhZ5eo-M+14T@!j3pExJC^M3Ij}}-$ZRZeziK>xZT1c8sopLLOE9Ju zfeqFxHRfb!nyr{8oT(ZDU0!SjA6Du4)Zd-bl*M;HlPFnj_PjcfkFy~<+WLh~jYkf7 zL~uG3QKdV`wKv!rx#<4&vs8q|dcsyZV{?sm8hFLIyS;hO7NHDX*^T@KQq z>K`Yds9C=HRcpUZsc0Gi(FfO}S;?!nwOb!1ZJQ`JnPR{Fhh>6hibocBcS24JymG{1 zL(!ep7Bo`n;rPuu%!@2kRr@#iK#v^FGIeO8ot#~LLem|dCx*JZ8yXbGR))A8TicoR z@{{^`1}BfdWA_6(2!3N%vlE-D8ACC1ts)Qa0>Tfk#Y$`-RT7iYW|4jvhnm|nL0&|P zHTZhnPkHS?%{HF|Qkx;0x<@r^QIc2Z$Yd&EjLp_N89GHcG3qOgq1hE=wv28n(Rd)I z(B^e+m`q>lESpGyoU>{d%j#a&Pb#6R?Z+XL+MC^@qaD_m7%RFy;QzPi1?unhKZy{4 zWC^NZL;(QcpX2{hBpVZB3j=x!8v`>FCOUgtvoLvCF<2<9e_FvxhzlzM006iDy&i}7 z=f52wl@+FcPgKq#>dr=X_8!(Irp^FjCblMy2F@nN1cn|2=FZOcPMq}g_SOcr&MtrV zIvd&9xK5A%eIal-3uSfZ|GNLLjnD3xQyN>g8*C`uD@yiTbV&MPq@=EyxuaMcinA;h z0jn0^)&srjk+kY$Jc{87u%N+Pm!`ed!MfLgt_!f(Y|i}j?damV269Y_zQy8W4)cwv zfo%JP9^Ek^At$qkz-WS4q6fYD-WUY>*npg{0^$Hq-~$Nmydqz-Z};A`fTkygIl@6Y$FHD3WgUl->c z75Ks#aydA(u*D8v7ZYkZ5OD!rq>rSSCVHtf*tYz|KqS(9ZNq)eYv#z8Lne8Gk71}^ z*}ZPFl#%}O<>={n1ecKS!vR6GW*9;_8&N@_L`b^Qv3;Vv+5EN$y}`4@O8D@-lrX8< z$gdb-!}@~$&Z` zu?Lj#2ArW|5JsQs_~Q#{nGk<6TqHg++IpoYOxqN3MfUQTysKfSo&t7{2Rf1nuyx5& zVdd&IER(n}b!YI2(_yA*VCWjk(@w=@E=_^hsoTQ%%Uw^P0>zPxC!LBk+Hji1r7l$*HuDIN@CJ95dJMs+12VDZ zYE|3Gghc1x%71AXV^Z8pq$%zAnU!i!S_|w5%S-gg3@4<%ipp|BgJa5tDbj?hw2Amp zSG~rgrNhCBXE~sc7`b+!G z3nFc{iWuB_jvscw`^MeodXyT{tDUQk!fW=Af@r1~hAM>O3348A@^Om0(o5KyczBrR z&QDg6i^KlyQBIga@4{=TMxI0x2Wo>ynvBZ&eQnf%h;_jYf>JUi_fqMkxiEYnXltp( zFe*fIox&0X@QTzO_1t1<;h9e#38?uO^@i1COPPb6$-pwPwj(Zzs-R*aG0J=SwwE$K z$cRNd^ki+wmoH($&;n<`d_D`NAql7WDKE`0iuTIaX}}gfpRpV)RY1LX&o6RtXM^UNWKWQ530avLJ7blIua=f@`$j z_Krz(j*B^d=Ev)I!G-t_3UyDnHXvWvjx z;pMN51vSiRG&otit z`Dtr;)oTHP&PTWz>`h%5RBT*z?;9n!T*yV9AUqTC`?x$#iu0GBrfx z?x&*c3tXGVMk($VfFu}pe>Ge|kr7EzNLP9mY|}A^Hnr>O8%~+bN6Erbo;^fIJgogS z*6%L_3*06rGjd&EbE&SKw*toC1*P`AT7S^ODoKo}YMb&Bp1|A%&y8u-TU@GFO=XQ+ zw3BJil(>tFuWhCkU7qhR#Y;GT>ke1v(bP1eF!K=lv8Sx@4w@U77Rfd#pxa?KU}Kb` z+B0+?X(xsw^W98{ci?y`3RY>cI0ef z+4x2;eN6oZVCv;JP|KhOUAvCk#|7EqK5X%6j0K!kOY7E_CRzJx0N_}4xmgm z@r>dM-3XOZAg~#t$}q@hHPSaR!4AB0!|;VK?(8)fa;x-!{`}bg_?%|7O;dA<>U=NJ zGgGS$0UX@B>*M7RN(@V|@3xli=x*=+U%b6#RF-YGHH@t&qI60~r=X;iAR%4SAuTN} zNT_s!(%s!10xBTLMMyVDcjrZZ3%#GcpZ)D;yg%L_uVdUp2REGOk!#I0=Uf+ko@7J* zkBVZys!g(TRIYM!F7jPYaT-<`;T^jX@Vu;RFuY4}Iq^C9J4V8Hn|D5@-t<1hGyg!1 z>2bgv%zEj~X0*%;XCsqUUfw9S*Lj6yC-;0Bi5^@{c%bjl{6#d;N1~hY&6tgH%yskv z_nUXCEC}$;*QdzphAxY?{BUzy42qq(jm$J$Y<_NEp7 z#oq#CO$HZ#qj=tn{PP`Hg8$FH!Pwu=Z+myQthktkjjexhuu!{!?^L2PTff;4J2W)( zjTO&g6Z!@8RL`{Q2`Q&d5(^>7$B*bGBj2hlhu< zF7>`V0)J<)#N~WgC}>DgSY*P`W$+MgVQ6@&hOr5m^j65}-Q{@F>2rT$!H5fPC|w{u<$8{^+AD^K5UzT(@&iGlggcf=x}BL043 zns;}zkk2tTf-qJf5Eup5J5c63d1QQ;{zCyRn%O6ze8iCfY5(2&Z=c$JmjzFM^Vbu``7^to;_s}2~O3vw>fnKJst zXDVM)d8O22zkmS!TJ;LEPoG=#9?rauik=#^G|j z&+BsZB+!Yt&UaOyn6MUIQq(syG1QB z(g{4YiWzxI8It%{uDCmFOms87Ohr9C+MD4+7aJ_ltliz)i#1F>XzlK{8n3b2ZQ7~7 z!&ZLp{{3$YW8{L2D@#jT4zwTt9KW2Pk`iCvTXU^)sUmY2p4vDxES94cfznGu#SbO2 zRt5_*#~<)IZSJu?u65Xu_D)GnZT6h`5jZowZP{=tLTR_$m)!C&Cn`EFPNUL7lY4f0 znw+V!ygXfygM*_&LN(gnhD3^D5RCIyzhAXCtGdZmd_SDJX20`+O-4&s3xz@Vu2RHSAzDXoH)s-)8tK zODSIsxjk2?RoAB6(QY|IJ1LaW}w+r>LOXgGJ- zA2wR(O3WOO<*@8oYYV6M_V)JAeL?IHt(p)Nbhj>Gw^-+3eS9p*NJH}?EXvY6eAw9C z9V&0wmB0&!^00AuyzX7(N8IK~xLrFfv$BY~cLx~}q5*i%YQvg`*;a31Qf~rQe4x`#HqbnonJW{o=CK6Iob0*?yOp(!Z z<;J}tc)Me{%FO9Oni;_?Cwka_BlJsZih0ZOD6KgcU22F4G(i2bSeLhCTN1X>jU|taB!P14lC{J zd3yTKo12@k$ENyHAFW`O{jlkK)*NY`-(RaR?xkX649xvmpYE_e##IpKkFeD(H|c+Zb?3ds#1}5&6oo2l zc0yu8-_V4Fi2}{1Xw|o#q|L33R_@||9UXnQ+?T!EWAz~lBPTwdn6M>oCM1AN$gAi+ z>t&}FnK;gGnd5@D@7`r+VYxpU%JVmqU@)))#1{xB{qEt3_k<}A}PcX z@4Lj-(JE4G!XhF^NMvFTBT&IL}XLG7ChRxLV47p&|!PVycGFK`8 zWp2dbZ8zAgfmUM*kqiwDX$gs3xzy|7yGy;IaY!c3nm!2i5oyEEiQ3`Dmz9;#_$x$p zhPfmcJ#)YQGDi`)(Qxj;P3Nyw4Dq~|IxzI1=<@Qi^|!(HcC(}1CHR?o*Hdo75XOiZ zEc{95oxl`oX=Vl&0o0o}e_ydATb*o390@y@xP0Sp`V$pv4-1Fn+(puv3EN`o^Fu68 zt4{5qsRv85#y*-L9dbFhFs4;Uh?(Yhk2$+~k+=5^|7IsAr!d_3xHzZ1W#Qn@f?Tzq z%wNBz8Dk?nW3imzsgU4!GFxDt8onG5V`v|H)2rE!$CfW_;~W+ay4w- z>G8p^xd{?(x7k5^^katO`D^Mb9?}kWcGxlxl(Xg2yHiCK_~7JZjBn2&6z25~un7qz z4k$H#J0EV6t(A=CyniqhNXP-#gjMfHfa~dz)l7h$otw z(qrS}a6AY|NV>cyol|h~^769S&hA?OO-V`j{1J^_Xfx>gElLGAg^evtP4awqZ{5;y z|CIibkL&RG_}$yLvPowBIpLgx%ZrOqaIU;B@$)ydwu+s#f0^>qf#FlPL|?KAOGvOV zHC>sVRg7wqT#$}=;yV&bLKgqE7Der?3JlmP(KU>L0e zyZMOiTwD0}Le^Fex{Pi)g&BxalpH=^6dC2jb`z-%F zwvUxbVY$wBspm%^A+=1x7@W)CZj}tc2e2%v)z*};Re}7~_nI0XQAx*%vBVdKasZH8 zUtb55_Tj?^XQIxR0s`-olgZ*^TUOfc{MoMef?@9=POi_-{~&3#m~W3(42-2yNq;UP z0?`D~-*5N(>j!Rb?rG<-8oQNbA@9ya{`0RNuCTDMoJ0$Iqcs9v**iE`2%giHm6e5a z0yk`%m1GA_03#z~wyReCXv^R9A}eHNWrc+n$)rigkBH64Fl=x=HSCC)TUn7vys>SU zUH*0G(VdDcHn+T_o}QjXTG^WxkQ^X#(s+7%dG*}5($;eS{(akb=VzyY_W*p$cF1(% zNB*?yd{_fA|LYe?T=V0 zwzj*FMIqs|x3|*}!KF(47p~_g{fBCm7L1)OBO@aKvEm*Z zbp{bXB_}68d@Lai5q&G*Ko;X-du4BIbasl5k4S2AIN6_IddWY8C8nUzhqM9^j_uK- zdkZreCD-_1XBX$@uyJrAK7IP0ct`lsMPQ&{UK^l7`eYjt@`|1ryT^!2NYuhLfstp* zBu-RXYBQ0ntgHkRuq{Ih;&<9zgv50C`Pr?DSqhBwka(wAn35$j_nVInd`zG{pM9n| z*5IbCB31|)9(MkVqs{Hxw;db~0E*yj)0@gZx=4wnI{3a03a8vf0?7R$VZ-gUc8-HS z6N!4Mt`#BO{S|?(NVi)8F+-AH6A}`*n9>H}lw~VqI=y=bz;g|P*U3-WFMkG5GC@_( z`)fl;X=0RXW{#52G^bgl%RgbFLMFt+!*lua<*=HN2UriLS;NR>v@+JYkDJVm!p4_ZPI0u%I9Bekn>mhU zHOf{dcM56xaAQnXCQ+nmBH2te3Z;+Mq_*!Xbg(-G?MeG1<>|y(@bK2O2J%gI1R-y4aN@o7wFPY~np?EAv^qLET3T9|RFaD$<@Axk zXlQ7}#KeR7>JE@q;4JS@yFVvoa_9Dc8@4<1BUYO$Atd-|85@zC{24hpyV*9O9X9ez zh|rF4`Bh)XVZsgZi2H7gcA~N94>5y++a;?XCNlKfNK^}TahRUGAao)=jJ)pG>~?-y zsn>EB7dP_KmFurfP5pPa2DRPH&CTmxcc*;)`W0^1j?u>_WK8Z(PT`;COV;)y^-8bJ zP3$y|IS3QiU^pCGS&LkELlt29@74TNr7g#i+9{$L#D62~;lsJ5Q5Px()_SzX_~^Hv zyTX?1C@0d4PN$YO9w#fiyL}M2mB8a@z4$A+*&kN{D?@;xvjr=%0Xa|T*Ew2gnTgkt zc*_K?MBlIB)~lz{$$~M)?8rXnhQ%!EpU%ARKc=idkN2yI_|=hxkZYM)c@`=mDQ-Ep9F=3&JSnMMMXs)K744!2Nf|D zV2gw8ZEm+SXKPm1>MZ^y16huhkGdZIZD=it8YLS{OvfKS$quI6rMlCf6eKzxTzFM@ zc}9G2R4uJ#H6r*^x_;^cQ(pVWy<-38YHB0@q&J)ScDT33CMJG6#c*xv>P`zA&Iquu zc2AHtXZo@ZaAf#cSy?q5L~bb`UuUYeJupVqzLk`9v4m8%Jj)(<%c&I8;RU@GCsZWl z68$2DUt5%~tmxs2NlHpW?DzI=ftaOUZbI4F0#O|zlC7QH-p)>~%Q3qfRT%5V^P5zM z&5inMZS`?zsQQM-o{tCDkZWH-mHN`NSKEs@H={>8wrF?wHlKJ!Z{d=$#+dv4HkQ!=%CD{UM$C+P(T}v+HymW z-8c5U;8WV~f=&tue$RoGU^jm zyZe+E|3{%0m?8$Um6A<^lD7+=}wT77G)^szV7n?+Q@mn|p0AOSf<&7mpcz{vUwN@a$$Ygh(Zvlw*l8ZH-{u(aM%s$t5>zdG0= zEiR6VhNjuzT9=xt2TWJBbGxDA@k}uPBLtxsFh*kG#|7xmC^oH$-0ldl$u8*Eh1Jlz z-I;xrzLGNLXXUaAbpzCu@Jh!Y0r-Z7h8lbW{r$vT*0Y}|C4l%l+0AaMce(i$c2IFR zzrORM*pur_?l~!)Kb>gpl+WSgXnFr>Mh{GZd*jq?=ckcWMBCZ7l z30dg?4%o?0lx1P!X=_Eru*}TNz`(%lY+9Gu{C{J34<$lsdSdU3)25a07@wiMr%PrM zCk}_5Kq(P>oo*`LetKk|>8w#Ho&URWRFR*)lreZkui@PRtdl7eY6*NUHPO-085tRz zB$dX!KSoBJx>JM!R|DFcmbor#iV=hWx^!1Tio|hmcNc)l{^~F$1_s9c`&-*{5=W61 z3b(?0<#J|bX58^j`XpnQHWY52B1lkVDHpSda*^!7IwzHMwpE6n_07klAM%G(+Mf_Ha! zn~+tsUZhuONb9QN5)z;yFa&0BW20+fT0_o-+Z51s-{nafv3v42#V&K_2=-_!so@oB zB!4&BWj^TOe>vf?SBx9jgR#vLd(9DGD_nPRNrtxU$0&Wi@KQM7`T6-!6zJ*d9&*Or zu*Hdg>U{X-Bs1xHh@b$*FFEYcY4O47jeX&Bhqf{YWwc?A^8zhS3T@SE92I=~!;~Yp zMU9Cyc(;FI<|SMSp_cbUKyky)&W=ef9S|J6;Ql6|ySnc?0PZ(|)lHgzF{ZO|0-5J~ z3bDFWob3h0`?LkNjQr>#;(}Kt+U-{QYFy>8sT)2|Dw?>Mp=5{1YuMY^l$MmxNh%p; zT?6(VqRW;8O7jYGXQ85^B3HRE!@+|;;)c8onXLHVM?Mk7&|EtDZhX#MpVjG78B96f z#Yf*@85wy(n^PVB`)CLSgEG2`2n$R9)hpQ|7QF2&Vv{w2Lj52wh{kQA<0?J(!YO^F=obpo@evKn4U!2eZB@HUT@4aK0ba3(OquEF z(NR$s>_Bjxr$9bFA)%gqHAagoCWrZmqJqK>Ku=y5?!s(AH2Irh*O$BHn)x#yiUhPC zchSngb&m9s%3jgCl0{t&WFlhaDdn?C)y|Rv7SzGun3U^+C!=IbLvxb zw4m0Pw=9m^vw6Ns{3(LmO@m4$Pytan-oyCVP@Lm2y_zN7d>UuJGGGoBVB!f^hsecC z67p{jwp^Ee*W-f-OZ@2Wjs)hVkE$GA5O#H)gQdH_U$frXk*Sf=pV|}XdmKS-p znmHAh3xm;QN5n{(fN$zPZ@qQvmh%YX6O+$sOeeBDG#J|rzAu2?&5(|dnR%zkL+g>U z2?YTy4hJxT=xGE5ielbbmtW3wDH7mB@*l50J`;!-$X7QdWy%e>c%w*n*Aj3(HYZ%z z5Gz*X%jc|I%-+jOU$%1mZvg_U8A#6NNy@N$Qyf;n9#u`YY&XWh?7qC+`u_8$Z}87y zInQaLPamon7@K?L{6BBnj6m*RUr_8WGW-c(#E-ohw&lIRWvk(d30KG@&Yg_pecOTr(H1(b08#vdBBOhS0l}Ep(m5txOSBdzkZrS$fM!<;aIPz3e;54L2mGQCHDeSr$xK&;fK(&fKQ{elbsz&nj zxrC@#Lp0Mfr=Db8F)~UvJScsMvB)jQYlJIh;<<$@(TO;}-ksxM0yG|6W@=<4dM;me zAwvZcur0EC8g3nwRhW+|0WnT@zKvjhYPYOdZEa8kY?qkjjoi)MC53KNjrOr~v6m`$eg1rttI|gnvFqma z`t_olrhA#Q3)TN^JE(x4+towKr1X8-8m`0;6K?`Q zvDoNnxhY_$RCM4f8W@;(f9IfoAtqK-Qj)?AgiD@Bvw`lbSDUNDkCm!G2tZTa#wjqe zv5MBZF@<^CAMp|7NV*eXADcX*9eofU3C#Vfwf2nT_11IZ)z)*1bJO(L(zzS#PZsC; zP6&J{yjxbqYN>SI=fl4lj^c_%YCO_UP68DnmWVl`sno9|UZzpTGai z9}O`vx}6Y>HfnLx@uSY-XSW`p@AUra@s6JIah z5n+EE-xb-Xz_jOURI?J6e4l0&o`Mba$%RYg-!?4ZFIip1<8oxnJpzP(N+6W|U`{ymhh5UpmG zt!c~NOnu*98{KKJHt<+ci5cl2j0sTM?>F$$)N>`vY%Qe6Y>UC#2KwMxCGAsrC(MEk z)@%NVZH0hJ)4`y&#>U1adywds*vs4dqq%IT!kJZyNEM!GBs|jj=w-IcKAL{uXzPgTZ9sc zKvnGR=}7FmTYk%eA`9lW*Z33kVcjEhYW=8tA>*Mb^?uoO{bWpTsQ$=2i^p3 zqd@I$vb09#D=Q>ulf$M4B`g?#(V>iB^xPH?r?bLt1^P3)v&w3QwR9Rtq3~LxWN%SQ z0JK0bMwbkUS1Smyu+M*JHqdD*de28Fi-h|#TyQUP>E+s3_2adPT8BauE7d%aKmtGy ztAZtXym;KpQ&Z1CFgvxXz@OVKiw6C&hV%H79 zEAYD2i%3>PKT?sFo0`f{J+T7j2#hz(@h(2=b0PLaGFn<+3q^=ZxdITh*-BCvaU(~8 z0!@2?*)|Mng3iMPkhg@eL33)6Q4FPon(r|^lT6g354DA$a>;RdzOA5dnu7qQfb2<^ zShBw~yG_IXYE!rWsuR5xS5!kqEWBw#qqn<%cqNDnGatjfXJT7U0t~KW17{|u3!v-@Nc7* zj%&4tL;N&fwKO9k;lVNLt4q_Y9QMO6*cp@F`qflban|^A+b#3Bo$ID+{rmTC$>CqLHET07js3+H73-4? z{QmO{OhDl&^8^y(bK7Qdj+a!K85w~*s7m$)hmWb0jAWWjWp!_BX9v+<^NAOj>|koF zm~Czo-+M*|P}a$u#i17EB$`;L^cRLKp0lv~hHOT%$VxyIO*0)VfTjE0YmwZ|{1y`v z^Q<$8qBZ#2;iFBtEj#hK^bp*X^cQj4MORz&gkZ32BMVbRbO>Q9n^> z)b#*O)fzh({S0*BtdJB(d3pJa*W_F}Ce@O|acx-s7wOk|D5*+Ag@=jv7cX{`lyTQDx%fBzg!mJPrsXr+C)7vK}BC?+qi*598O9g4EEvo~)?5gumw zY|D$e&RDvxEHoc(&CoJ~#ieHr2n9I=I6ic|Q2`E~pX^-dIKbsXy41y{!7Vvy3m|?P zfeJATAkk}FT-+my7q$I4FOMO^s$N4D9xW{|_cSI6qGkv_U0u&n%ofDAg=%oOv&RBb zvB&VRibh6@klfI(UvFTZyS-AX(Q175t5Lu1^19?GHMqKLt*E}QI@uf4E)~tq%oHWz z$$Bm!;qSr`3T~BB(5V65+Qw?(AIW};@S+ujv)vqUEE(I9*lWhxGZ}B&dQ^Kph#%}e zdnInfOh%XK!O+S~osR17vxWrTFBICzO&m_AOPYs|1n1MP)IB{pzE&-$#Pl-lek6qY z8v8YZd-uNHm=GLlaO3~u(z)I&p+d+I`5~AKF2YaClMc&M!IV4W5SAd;oeAKGXDO0V zPHHU7W~wyNksx$M#OWOYC!Rk5--5)GD5R4qIAc@|cs30=-;* z3YeK@PkelQ#n-Q1kutT1N%b?hmi3YDJ#lbwFlcy2G-ua0t@t}pVPdqY^2SlI&%V9o zLO^6r)Wh4=ye@C7hJJmxU@iGCd+?#%EkByyI(>v}qb%>Hmnl5==9m`XchWv|K((gS zp96iHls@u0oFd5Rt*xz#P%AmFGKPRL3+NA%&kck$TB{Ckw662DDg0|`bbm~jYe zb0FU~%X=_e(=@^V^MRMaF7tQ>24lw$tK!Hx8QKqwNcOsI5~iJFyCeq>x6h=?aE@Es z(gXFUtuxFb-h)7Q;Rc)HNX)uMPfu?W*l>kLYxn8UzySNk6%wvC2{L4012SK`y{bxM zx~eK~KWtFWUzE&E+9vFbb9TlLFOzy(N4jlvERkB_;H<=TJSyw4;FSAfY2eugJsk=E zfb?0|^yDJjc}RF=dD6j0w|AtdXdW9@{&{pE@wVta!lJ+M1-<*Wp%exRmwd zIQay-dRun_ctssJE`}KXi0Bg2_Wz{3`meMtU!tW51_=;KVjCh64RQ=j%%!c?Le{JT zAVI`s7(4fhXLOkr+IdMz64&GzGRBp=zI&n`kPFc+m2}m8qzWWV;M-zpu$v14y?XymNg{v-z z)TBI~H@^kYh6R)1^n~nAvtnXl^_80{)6?gEX>V~pIkb|HkjM$%*w4oNhe5(xI2yv4 zcY#X5?R5Hhc%{ITxIm>?u<(JVM=J9JZPSeLu2mzZd1xSpOEuBoxD^cBn@V@HSG4%#MZM`==(bCtq7k8IX-lQ+HP8flFbOfpA{ z9lFjp++IE{@4wnXE0IqO;~*8+{_llViO`)DZr&;Lb{>dWl|6WAzo1`E<~ zm~zy61K0Q<6MsW==X^2B{F7gX)LAglTv^&{^L#2uNm6gE z%r_{k=f6EeuMnu)|>-$TsZAoD06wl<_X{7P&1E!dX*PZ`*5=p-nTzk8P|Uj|%vVG(mMw)qe2Y%0 zio?TQUuN~RXt%NY#P4ZdnBX~>s~)>789Pnt7FJQ|E63e2QP@{%)RI_MJMMrsZc&gL zjdh;rJa^gBEZr3SDPskZ9?*Rt4%`3!dTA>qA><($eJ~!&W!361Ay5HI)Lr7`Z<=?@ z9QH4vC|l9W#H7CZiPjI%K6@+mvHW^l;`fTcl}Hnx>(;YXYX_rZcFP0V%>~Y}wo7TDY_HSA z*x`oVZZJ9D)WM3)bTZB#c{#Uc0POLvt3p3 zsYDkgEuP-Ex3PS7W}kncahxJ^Psl1Ld8)O5dxz4bRFzGM!6rR|Hhv*$WT(!>Bz*;{ zCn&Q(vaGNeS8MG6kOnmP?OV5KJ68H{sSf}6aa|Q3ugM@Jr{z|*ok6PLJ3U33telCG z`}=)z7n@B=-KIiv?6?|USf8g1lyz9RvBP-IwJ;9`#`Y$=U0Zb%{lKbwmblW#rmajo zlm5>9w5%MDy#`4(yw%HI7c}P^ero9$HVzOOSX`(YLV!(d3%St0K86~4v5p(`nhlOS zRGge|0pxjkc?nur^k#I-{`?FyT5QzeS&Z${v%()7lV1|=HV+KosN!D*Sx;wtth1B> z!*dFefn^E^jMNhLX+1*>$Gp ze)492>1Gh|ZdTl%*kqJ8W`J!?#Ak)nc(plYK7GA;)J@r)_M5}&!sB&2tTkyoBbl|6 z@+}8s%(Wp@&~eixBB8Pw0AmOl2hb$4Z+5q~9Kma)t-eM|?+*59Qk{ew8>J1lT?dV} zACjzLcxNw&;eJ=k|2P^lqbM$75X+BY8*v^Nj2a8H*j+c4#LiL5XI*NMn;?vEY>ypC zvl5~+-hAm|Z_K5@#MF!9WGw^o>JkHz4e4b2I3=dh>+WpZ@W5oAC;%}FEv^V!_5MO< zU~5=HT*o!D|jA6NDtVN+;5tccGZNPmO5^MdQ8M>^1Z7YPE0m>6@wgEQ8!5HEbhX{M zj9r31?PshoFFpte56oUC;HL`=2=Exo%gqJfS()3p>x-^wN!4n%^MlD7<)x)UN?t_Z zyeJdq5kInCJ-*!z!t?BSChdvN`%eOzq1+U963cNgipCI*=2E_jBeUkJp1nClWF(`y zp@|9I>^!)I6b4SUJT7%PZ@ssGIQYY4>zZ#$vQ!F1Rflm?z5)3X;QK5a!M7I zm7+>s8W8+uDKpZRy6zY(UA1{6Fc@nx^L-&l`c)9p~hYEJn~SdmW$ zX91Y?Th-O@aNz2Az;z+wC9d5%RPXY%gmo6OQs1}e)~oCk?(Btn%lxDFvly0L^GM{u z*93z7W!aIVA(PTua7jtZt$RLbv7E6nh=7L%Iv{#8Wxxzv7ZUr_cJUF|7=YNWcXThT z`$z$68KZ)W)u=1ZdxOG|2ET7>`6rcHMj$tZ8y>187fie*0{k2~q`bVRKvdnvVOSi< zj}5u-it$>Eae`~mdtHHX$wLbgL|YE8 ztU^v$F<(Vr<(ER+d~zW0%M1`W>guDQiQejV1m7MwNGdJHr%kV9-T9Y2#j5=udy3Md zOO(k4!o+#WW}Q{sC0lKH?#YS}Bh_ovKPcBIT>}Puq|^w#^5_LT0!&i->*Md>szx$v zZ!e&P!L^eWGJ1&VQO&yCI&c!ZflItc5nXsgR<>ZmTzLQIB?TyQ7U# zbLdoEtCRy5kC)xCpX!VJ;C#ARR(Js|lyqD!N4pS3VQuQq&yKa~oIw2D+Nw701sify zbTk(?HxtNaAYkA!Yh4JGqnBwemd&l0dE)JZFOwZ>U!2FjdF8+QH^*1XZb7hS0KH~z z5;y|D&qTsytq*)PFk@i71@6?@Il>rBk-xvW1DR9<`wRvi4{z_xwBNClvTuA4vVJ^{ z(KV2)E92$ta|;jD-^%^5d-<_1Kk75j+L6Ug32HiEq_I!8aaqMsT~ceNq@)Dj0)*OA zI4qy3q*%d2)89WgQyKu~$@utjIN6|(;Smuz9~Ag?*505KBe3Fv^T@@86b?|Q*jSyUwn@1rBBZLS3c3T%ce?oDLcDqN<`Ekkwu~nAukP+-ArNyktKN{ktOvUL zqJ^LWB@H8zc4U2`ZZum=&b^PpO$Jk8C^4=pXXs`ICeY$D3gtYWsbaehVzrTXvJ3}&BS4Stgu&}Ge2p@`iLPA0ybeNe7 zVZ6ekqBH1y>3(6;tj%K}4_WKcp}r^3DL#fH3fA%L^P{1QWi)SUX@L$1b8uRKB_Jp$ z=t9^9Lp@2N!OGj%m>6zOP6m~t-{1x315F6pQYLY-ZZ9>@eGlL=)JwV#F3djPw=QOO z6BH#q{;D<}dTK7%bvgHqOTj4H#z!a(JN&XzYI$b*V=;Uj=P78uu^J9i+=3=0H7 z#M349#(KfbWI0qs27VB<+*U-m;Z`1jEqQzp7+RBD`3i#bgJRRbY{Ux`JuGn*EDad& zz+4g&9bNvq`(tod$p<3158njt-U^l1Fmz{8nKjTJLHvCXgNfdwi)I(BU?|TZyNr#F z#-hzWVrQS9DZNA3dZ8=>tCKSvKvY~@>>S!BM$1(Co}R>QI9yM7c4qIkoax2>p`g(8 z!eq8NLmGem`ZX|M56qdFNDzoJo8~W`fx#2bDkX7E8gAs=4do&szgF~#zkPY(-wkK) zUALx*2~AZkHT>Q~2O&CLVexrAW4}-$8n^xGBA6UVXt=T3px4jb%xrP5`+v3uVqHUG zZVNeCscJBtEhl&p32UP%_7T8<-Y?(lYSl3^%6x5%-R22AGFaH)6&*mILO(?`ICLvt z_TVt62%z_W`!)rh=#yeWF$sy$FOH=!Q(&)!YyZ!Ug(3DDI5KxyybaLMgfY8lP0-+_ z0^yWKjQJ&?>WQ<$MMxoSZ83@l@pou+w6>-O{I#&cCl|3zz1iP-8JZem+eC~nZsf_- z<-ZMzw_FrC!FYv`N#<7kEVu*Ti}d0X91?~!*QqQbbI+1N70lT^Am z2E{iu)w~_Aqn!@|`LZnk;v8l1iARG`0RrqK3>#>1PKX4M9*oetHo$wizudn9FbJG| zXAs}P`o&^E* zX0t9jdN54qV6j^$&fuE)NSV&cJMc!al9G}_@@UyAaD2YzLufgW2M8Gforj1)gl}wY zYb{R8dpwjO7S!aF4sV7lrFCc*b%!RWh!^L`X%k)E|;!9hHKTa&LWH{k5oq!soL!Ks_odc-m~WNlB#8=l3!2TZrhI ztSNX7r#D1`AlSv?1!>|j-C_86ao|GW7urww<1kv=+CE`pGly}u{Ekb9_84gFIERii zK#&yxqCjD&b3Ki*l%S%c^P{^>UDA{XlJt~%x3$-Q=prTJXAG>$d%f=eUG|CPp9W!P zdDwOkQ->;VLAP5sc*TRisWySF_{R^BueiWAedq4odbe}Fr>Bqr87OUN8eo3p$C`Bu z|Dp9%n!rl6dkohz`Srw~j$WGgO-)U}*Mm3#ZKmm9-`$vOa6?2eB00y(jA3>VPDQb4 z&bC`hIG;XkXyrrw+SKIH!=7k=J8&c|qS=8$hTzY>qNLGKQF-M{LDQKA2>lk~Pr=i) z#nf29?LXKNp;lc{iga^;^6zxUJk@bHE*qzL(N zjRrvu1S2;j1o;qb>Au0iU>~2~e?)fhu02Qp{I`_>r7av9q2Ejif~?2#=us3jM}o&p zS5FW5*noh|1Pd3pdUpwX86;X@?m?C<4^USE`~ zOu+DX1Oz2MV=2P7p^XNPk|tPWKASH2?yZd~XX`~uNl77V|CuLB`cRqiXGp4>fpuYF zSoIKQMXJ=4@#Qnc5GBi%$L7uBGR(@qVkQdG8!VPVOKBc9P%e(&7 zx{H!k_U~Sfta7djLIyrz8=%aGuunr@PgnsbD zMs$ALNc^YvW`K~ig{W9qwg4ME;CFQf1d(kB*qcf+3ihgX5MjMFEo^CNiH?r0%+=l9 zEyxB6$NJ)Qvwx-8u=UQol$_p1b4yDpDxh?XNbMS1W5A0IC)@s2n_$F*4gv7*!hWQQ z!ZBipo?7TX@maap1cA_0TMpYrS#b;J!wzcrG^RE`wTtfXYrY5o&mfs$seJzYS@kDe z$kzuTxC_QZv~)dQt0b_dxpCVLudNW8`hKtc+y#c?Q&|=3xbSF&Ins$7I4*2DH|T;r znPQlqpNFYnm|S#Ihr@N#rqL+Yy+)r0?V++_A8JA6B_YHpE-jUik-;_Td6+rqp+)0V zt>W9zz#q=zd?=d*{dp)Tj-sjvfWJR~Wt8j8Xnj+M=39OylhIV3L;jF5aW} zYXHB?b({TwTy;A+ES6nZNOUtK`}A=MHWw(FUO4PZ+we8ierWapWmCu#sHDDC=*FXQ zJl`L+gb1b|4jtgMimu>S05gTEBLu_MwY3HW>+4^nO3+uDogo{@7xdc1q$CF79{T%gWAWtyzAN`++#dwvcHNpkq5KJK708KO{72i#lP9^|WtRRtArr&pTv`w?TH;xei=$uB{M6SkX7ZSB1K zvtD@^h*{-Dc2c=jyu)sU%h;*F;Mi(Mk!pi!u{v%RtDgD%uZBv^o^ln zy?nLuyAOD1uuvBt-tEuy=?VwP?Ax~VJfaAyPvb~FON|EC_}p6;gLcIIPpU^^r&oMJIYbQbc{NlKy;(;wr^0SBryssVMy|-UN&pr2Z8SilDQ;o9U+uLgzNZ+P;Q{(ttub!54jvjqVt9gim0w;NdEysO@pyUsZJ?04)k41IW9&$ncv}Z^0Xw z=Rq)=V9sO}t+vxyVl^WI$xO+nJ&Hw8ij0X_t*jM+$OXF%#OAs;Qy*nN?0`EcB;=k+ z2p|71YM@b%{Nkn2V+&)VtL@j+xD~<4X`dgno(q>ykrmp!>*+4$HLLw!U3N5)^8Y4y zG(XP77SNip2Istg5mxAM)kj)|jMuFN-ECb6s(t7xDyI3#$;tV~T^Ni}gHQlj5sO4I z3Hcyw^+1WpD}wvJi!nQw>&lg~dIpFyIpbbrv`K$XJfDjiCtcDF0Ub4EWn~Rjp?z{^ zXy_$Coo1yxhF+SOAh5We0P_&7%1rFCpM=iqY<@9NSxU!1gg$U4SlcwX;7bg%=Kw40 z_Y~+%*M2z*D6ycR;IU!*BNi5&Jx{AHkGsW!Xu~xAfJNoP;4=~G+|KJaoB6hW`Ct54 z{L{#ik~cV_d`t=+2AKShzw_8Co-KAiR8sIkK#i^IPPxc5TblRAAm)k1*wENmTmod0 zT8KoOjp%t&>sT0&Rz(DFYdfbU6_OD)GA2k|fISpNS=jc^)G{>P@|`xew)&LN^H6qS z4}vOh)z0QeMj8#kaz#bO472k4?EV@M3Yax(nqK1kU_!Xr0aOoMHL-v@P4g1uh^|Eb z#Fr7lh;6+{Xx)}e7QDv&1Wb%d-vc$lH+FeEBjDXaC-GDsG=CIvhb>hXJ~A{6)WqqU zA;aUaAR;~jdnGN0QQLf{-rDL!U2Nf>kAI`1BC+C9V1FYh#P0VST=UT-mK(*sA+-5^ zxx8*?aV7x1lr_X?TuRawS5{(aQJX*`pEJSG3CFY;VAt8Z2S;$~yA6|G;#10<|Ko15~);f*dY}mak(Iwf! zYGtpVu{E5Zy(^%)ShlRBlV$@}!*)Eh?yHSq%xwBA6U|LcH|>U6J)j}lYKMQa#?C)o zo`fW^J&)`QkoMHmCKPmZIiOsvW)<0!R==6LeES<@2&~8j4yJ>ZK^YE%rc{6_C~5WYki)_&o(Eic0k}*1EbW z;3nr(EqTMB)%}o$#>YSkv{o_}P!POa$bvsns*_xhK6x>3m+B>lu~pd@t~Yv#m*NaL z%(sE~3i)Y^)-f0X!SiB{7p!_nkw&7k1{FB&2uhw|F^qJ^u=)2-MXW!yp1nz!nnHHF zcUlOtb8m>J^*(u1b8~-|Tq?u|GBUDhlZ#;`m24A7zJQkSYf8ZTi+={9HvQfSAQDu& zp%P+(66Q@b627s40VNe$wW_|Du(0RUC4x}nU4*0y(Rd_1&D7Ge>n`+Yc=a3Ijtl`x z@1{f3EJh@@@vkH(FVR&xh-R$czU4I4fzJ>qyXfI%u0QXCrv$Ohs$por9m{7_^q-1)TeL%1brym%`L1=`<=`u zg0DWxN?f`6=jy}P$WYp@#?1}cprBm*_n$ubk>x*s^5Z3VM6_oR(o80V-Q&)UeH=>1ll;JRKmAt6W9C%KGo}|U!F^G?@yqzo!&ZJYl$VX;2%3X zo^RkduSL%7H(K)ZV+9rPl4z|*BMhoe`8gUU&Q|stJ7hTcZ>Xb-XuGQNn!+zzcsBa|v980=yr#;2k8OvvouRhi=#9s_S8Cpt zI}}tq>vf#kklNL%Ak!V~Zt`~VxQh-{-t#$S3gUim2mCMBgC zq#Kl$ln!xX|IhQ`JnuW+aXy_f-ZA)qj*Dxp zYp%KGTJtyO{LOjnGx$5)(8naQq7+uE%bSR9XSHL2{FS=wt+YphD4JvTEB#a(hB&%B z2XYs0@1zRp`hSUd+%x~wlLpBo$I_fVJ>s3m_??<9!HVGO0g6SI)zdK<{7sw1$8;F+ ziH&Yn?wn8O3V-KII3EsGnT-yLNtUv;JL#_I>Ie!+Nn02`bXk3r<%BYiUovp^!%NFJ5Q96VbL}a2q0;Mgwn?dEE~~sqCAayJXWaBH_mef!s((l3 zV&Rz>nbNNe`qSIbrzMfw|D;u=_|PLEtx^{wP0t~d69>g4*TZkofOF(^^- z$pu1KygSz*ESXA{>6o`KwzP1}PzoeB=`qpuuC4z4uCRP{7gbj-RwdcVMSW{c+TRUF z)pcOj&sKx2vYWk)3(t~Fcb&jccA&b|jS?}+6QMDZzxzWgE6#T5sZJ*WXl>R^U{ofw zJ&%*_`(bO*_6wcXE?K-~#_zIZBo3KmE^atlnv+8yp+v{#8)6ZSgTuiho#;dYM61GC zP1+~RqPUP%cdIdfF~xyH*4Me_xyl)J$yyx0pj(HseggZ->}Ze4_jRc;)JIw8#U|N& zFV_naK3U4>4`FNSJbts#K#YN1GZr-h*RPem-#)wwBO`zK^~s~|Rwc}rXg=N4MkM_V zd0`q$cT{THwMkKHwWkXQDqWBD8KY5eoO;kYUoBcC0Wc{wwHPX0HziFcd zJqj(M`}>w=77`gP%PV^p;d=3!zH$!RomZT*ctdn~0;yJI$y`&GWywD#-{x>kJ!FqC zVpi&wVDn92qgy7gc}sbxlr7?^Qako|ywrG+Kk|ACdOqc&8fC4IvXrIXcXLNyC57Dc zm5!G(P*?iA%EtYUOTsy!uB${#W1vQHP!{@}F zmUwTGxay;jjPCBAMQc5353pdo`{bqKJ@R;5p|_hrLxfA}Ljt6O(M z_`wT&i*Mm5zrMEm_WL}VEljw4b_o&neShayaNnX6#Ow-!z8c`miqc=k$6DKl;?HZx zC-DI@Ee_s@!KIm z7?yo8Q^?B2MhaHWzEi#50nc(Xs#e1-uFQ)q_;q%ccE9jGc3ECzN1lP z=Pri^(&WgRKd0#{!J{yIBr8Z)f=i7x(mo$fI6prAOYNu{>l*p%xjPjzlS}U`rcr%e zP*`&@X(S|KL#zZ~(9LI?48%hl}9;UJKPl9YfyS)#bdJh^HX$Qx-O5))T1|jI52do89Xn%r68PD zAGtx{D~A2<0G>dD;K+1Q-MYn1f( z1lbQw&qojHX0?wO*3TQ`<`P zVX-^Xnnug&{!}10ellHY2(&BEqMUnTs$Gxy4U~wK#J4IJAZ27i5w%J;uzFXM^WQC< zD3K^)ZcY5-ap%8xSJ|Jw^`_kv^l?KHFkkBJ5Bx4LS0S%1^h+39?~9$>u9fMw|=3CGeLFbTb)MLsfG2rtG5e z!bi@?=yUelPTTJt)O9=5JKJVuoZ7~ltgvkBptVX3NQiE!`X(Abt&8~2&<9G4&jY1Z z-FF)NW^ckS{0zT1vN+6tAlg1T@FXSu63@Oe9qidV&FhHndQ1Dc8e2DfOALB`2Ljm2 zh>>B)NWP)(;edXo?Ii+uAjZ$_k&yaX!+F4?_)e*0U zO+H`05kT^FV~m34sOOMN6@WkZLH^u6)9e2Ky}vLlri^9Jj9IrLdu@_zTF({Db#Gr0 z`b*~~S>>Ts4BLjb1Id*>kgCrjV-e=HnAHy`a63koUQEKUzRunVqx#REZ^a?~icy%$8?UhAiJ*y}8ls!_eFR`6TXas&7-k zd4gj1{A0gA!$2P9!)Ier-)Ymnt*tae4$&?drRnn5}h9Yjq?`_$dt-Lc;b7{M}zkOvM(N=LaG!v-p5+c?zeovN7sn-s98(LJ}Wq+ZDYb{-ZZ1 zre*&KEYHwSc)vA?o0^wt#yM7E4)*L!JPT)p8>F6OHL8caQS~az%@p8e7v`aFS%db+ z9h@|sc6=uYvp%*+{O1*~$^2Zmd6Ol21lw#T-5Fodu{V|1D|z`b(s0)8uGR$32kWM~ z`6#aFvA-bMxd%fwAPYa;YBt_&L+!otyq@)&1%)`KF!l_L=vTvLu zY$MJk%7ujD7KF;D&KAGAajn|_)oZyNkDGX`QIe3rK3YaP^|`=N8b;=3@2<7DCLL#e zg~Yo0hyRXvd0sd%C2zin6Qf=^P>UT;*fCLne%i!mCW<$uo$;TEu^Ajk>IxO@Wkv%8 zGq&zX!{FwjHp!|@j$mad@jrtGP&Rs|`dAClWzkV)uGA#eUM`#Dpq>K_@NM;l%dHID zDN`rjRiFD%NzO{)bF#*OoMRgRY-d?Zf@!hiili*VhL)Clj;!XYb*dVh<^2lbp7!g-u3_D?KB595-Uq&X<`= zet*F*tNvYi5qgg@l!HGU>NuEHrJK5^%~gX6&tA^86SG$GIxu_fwj@y%49u939}3ua z6oJ&>Ne8>WwI+i4(?oI9%aYcrB_?4i)0jO=H)ZY=&s<>=@u@Ulc!U`$#LldJq3OEa zFiQKS_N-@WiuNKV)N6IR0pqx0j3koj9z^mfRXFrOw0XY6rcPq1GlZkAhve3zdR10c zha-d0AX4>bYm;Nw&GB{9m%o(1KN_yeaDI1yc5_k{I&;2&LFLK;PNfMTiqu=Rz1>|h z7;g95uw$atRjUx0(mijX?f{wAQ)sIDokB9SJxcUBV(j3nP*udKC>G?-3Nt78Ecz_Gx`PZolpTp8xp7`%nkY9gKc?nS91& zyDJ~g^TFaStct^xQ1E&+(Q!7u(zrS8vAS)hmbG`%%1_HD@+dxgQKrcr6SY72Um+f( zKcuw%SlULqYJD@6))luYU|n=(pMpyIwkfLhHz&$Y+V&XAUx`g8)HGGi6l4&u@Vccu zswPQHNK{ubTVr%HRpqq7AxT}^CS|p&8`D-!dV#@=`ezNQRfle6;a$4AFj2=%{P01L z^f#wifu`I|X{Yt?4c;XoGpC1jk86(-ANqCu)hXMna2LzdY1hlxfv%a@oju2c#~2S3 zf~4A|r21sK=(;;uI+$pC7z$s@zk2O%tEgn=Eh{Z=U=mrNFrI&PJy*iJzdnwJf%e6w zYvX6n{lA(};4d%>l<>rx-Dhw1SIPoch!amebVV~xIZi?6^L`xxDSiYtQH*TO(}dqZ zlbXg<+nVuO3*TK_1Uj4CNTPH%Higa1yr|B53FLJTVmH%oQS0&d5Dtsjm`&O|+)EPF zXA$DDnn)Yy!chLiUyYtzb&|Zs@9tH#_AxBZWy?Bjz;)rDT~j;%k%8kyQ&Zf`e{Kx2 zyefkgVZ>-tz~FnI6udaKx%)>;i3XIP+uu_J|8RcZx+s$uQ+M`IURhsFaDE0~F%-JO64%m*&|D*BoWT`^u%Sm&di@Tk> z6+rgmKEFn}Y538SYLf?zyIb{r7W*4+3Oy1ohVAq+Pr;|U9J5TluVytQ$nUkF{F zxvzUZUa?D=HuR4q96n>DY)!T`0y``0;c?xiU+ig&sQgwijkkTMx8d%+F(AX7!@MKp z_3^v=P0c{pw0r2(VekpA(6CQzwNuA}1wsAX!Q`QbUZR<7Pu0m0N9fYUxZrq6_5qN3 zw&Rcf8jrZm<22hEg%zxy);fJGPJ1bhYm};2M~R$$^-a42+{6aklq#6+&gZKg)N7f*wmijk?=&bQC%teU@@Lc&y*pW;a-C ztj{FyBlv+Ek=zIBnwz)%o{zj<&0WuT#gv3jys%h5F2N=KQhhz1o;*9HaPLAtBuu(H z-L|Ih^iW_@#ze%-;%)6s#i^gU=opcJuxayN*vjTvTkeqpdV)CxNx&al>75u(WNkaO zCVaq6%0gUrS-LUtX3#>}HaiDp__Mz^7iFy7%DnyIc+9qb_uS1u;SBcqhsKII#5XID zYS70ZMD|V22a3y?(MrQC!Ml0vW_NyUKC5r?*pU(Jc-r@H!pF~&P-;p-rcP(pDH3Op znf<9L&2g}?iJIN+Z(!U!z9^d>&s;9^7SGrzn-9G(bITpH- z!o~^8hDt#J`kVz%=fl>vxdadWl4t8(YhYTp$Am4P0P^+GFXW3}oe=rnv;o(i@e3}$ z9e$Ls&Ss-QU3tB0hxzLj6qd#&KX)QXm2KrY<%PtORzldQ?ug6BX|U_4Z%+n=hPvO0 zEvR8B!BRcUD*9F)AI;}yt|)}!%U0AyWBZ}?3q9bPH7fVc)A8CvVH_uG3xFH%;UnQc zeK?_e=8h*M`wyx7;#5`gSSlKRmF>QM=0I~rC(gEdOnL1#&oSXNfWS(HQW7XLUVR&gKhE`(no|+)3p&)-V_asRmxX#%gq() znSLMPQ|iz6&YiY-uUtjrZc3Lm-l+tg?;oO+sK>7?o^7HA^9_0PX>`huhDi=)4#V>8CrMl@ZjFfX-urB-t($P8&Tj;JQN$3mgc!|O`xGq1B zuV01;WXVzN*1X^E4`e5-kIc7A(4+P|7%gHlk0K?}Y{EnD(6zBjQO$wCKlOR)Ur?O( zEKZXC?A!3Zhi%Hj^n_9mD+SHRW-}XEPyO#COG-I-=uHnDs=)onO22{x88*k&l(3qp ztKz|HJ^_Q$yLoXs-IG8o!re5F**I!1Em*rr#mm&K#A%Z4ls- zKc4Rf$%*pU$}v2cFj;z#$h7CG_VL|(k@lymSpDqCrk>j8@?;rF%`wCkl;Kc0VAHRE zwV@lUSEGtk7qH0o?RMG+=n4TFlH)bv{)7^wp%ZPp@o^+x$o7>$9p1`SX?t1SsK=?P zG>t!^*tfaa$`1`nEeR(3WW71_?!j-Bc2(y zN`J@a@@*eM1#fQ&@sS>yR*utG9NGx%zEUD)g-*H*L#X;1s-lLPY0DlDrCt=X*XFtW z`1ds_16k>HjaW}gyouT#^G+=I0CFu&4vlj)T{@J)hFxAN_9mLG1)S47E z{%&sko30+m;Ip-=im2bIJbFDbD!>Qaa2;AVPsM=^*-sLG#$~7JToq!?DZB_w$Y1f< zjZyFAAqvB?vCwzGSyEhZs@Hy(ZGlCkzdOEq?s~DoWSAbbTJz$Kit~unCD=jH*LK5R za8?USW{d{cl#(;bJ)o+C+;OL3OM}nUZRa*$A~@YjQ?Jy6(NF#1W=l9gyf;w2AQGOI zvv2Pm`F|`}k?l$B(_9>I+*?NP=v@rd4?XE%%>5m?IL)T1mlP}G*|^5yMXNX3q~s3*$lXuYk0TYjeNJdn;O zDI)ai;&sWoexc;hC0;|mCp@M9hRbu=gLI;F(aB~-E~2_7JR@hbfq4f}7BUu%lHO@h z17AvWH8Z@Qth{!W7KW{(q~;dYTh~e5^8hS-fPO0H*qN+y3Zz&vJ*o_xZ#R?UA-e7x z-V*mA+;LQG2r)a)BZtG|2Z|1?)N_jgC$TaDj5Sixsr9i%GqG&;3RjdEWU*Hr5EkYV zE;oNfB;&UY&xFeN;t(IF=rKYWKXc+BAxLC=tep{@%gK+EYPMp&e~O7GO1g0zn_l_@ z;t>g3FXo>eLh|f|#~R68wg8n**uTzD$J(8%xrT?ZMA^QN?~Qm~FVx{tU)*?4@(z;6 z_4`u_ibT-=x)Q+td5Q%dntG&)fBN(tIgp_ur9Ms z3l3VM!-rmWpJc{_jwjd1VJ@E~=|JTq+0(~AJodCfNLB0L1i znJ+4@m^ZDPrmhP*U$FM5jd(xGC@WBG_xZ- zE6i0OCT&KdQ}~>GH1+kMt*PQ$U4&<1AB(U?`OzxX5tTFcpkuJWUArgX0{XKt9Lk^d z5t8Fs#jbSd!o^lqu-7ZG_j6yu9d;}qTRa+mO7;4Cy}pf!<`CJQJAYPEuVDRiC0IRl zx?@v_8WSZ-QsB^dJsHv7$+A~RaC3z4BmNyrDpem5__5V=1+|Wb6EP7eZ26igyy^hD=A90!GB8n@?*#KkuLs| z9nN76UX@}mb5*}dpx)uuH|%|Eu%S9K+^&@vl`FIFq{-4P`C{kyc1vok0)Q6J?op*^ zYY^Z^a9FL8N!8C7;lOToD23VXyS9t&(6mC^Oh;xpMY-J0>n6!crNGC9F~Epd*6oTG zpG-{}kR4Aqv!+W&@Sv^76nGX0LKabOk{lJ+>aDRTuWLoahXY<|J|(v;GQn;+(RfNb z*2tzU=-W!wcr6>5yM&4%8x!i1ZCyUt==L7Y-v|%ko}&rV^TvnRRcVMLbO~o`e~IpR z%Y1+@xo?H+FvUTu=FQUw38r=Dag)yM76OmJsrNV(-pRcgZsNmLc`cUSV0_?Y!6h8L zzQ;OyFk`%A2z{gT{`f~N*DC~vXYo$feB$o@=Ywx(c4$}S^Xt)c9wl8BO$p<_?X`3X zV|YRk3Ez1uMb2`PrW!J(}KgjAFa zr-rUQG8=W7KYu58Fcl9oe|Rf~Fmhqwt=goUc9{!$4|m3qx~z0ltCfk8Ob!d(50Wi% z4*CA0nI0%>^|ACq!AU2Bop)Z%lvFGa?4sI9A^a}O`iuA}=_EEY$*V}WkLlm7-0pPG z26M^`$Vq$p#lcJ|mnoUe;#Ut3S_cj2&n`BDjh?Lr+CY(Fagbpfw-=7V!y} zuytzW&aR_-dh@(DFF2E#WCSw4e)!aFEr=E4!O8ZRL5TYZUppdODoG^g4qt>6@z}d< ziCKMfJ*Yy$Xdg?FyXWUBHH13ku^e*>E6rZEFk1D}pT)H|EW0w+g>ehs#N)j%uzb-r zjrK}6#E0+|N5%tI%Y#E#bK#kHHt#(O%k(=xP<)%)`5c`J%@T{?!Mip*uUSL21sAjO zU8&7}v@v*DdG``fc*KuWL*(WC>3egEz4J0*ZOSVys2MFAUMA6Tm+Pzkl4sB>)qSCc zaLs^pVf%Wk?H#Q*G)B#F-Iu1YQS@8Q52gCFmC8@G$4?jqkz@=hl1Z{Vc02YHPn@>4 z)!7boogo}_cm%xQb7(Kg^yZK)(1Ja)wo$6tj@3PIldtm(vwQ7kdL-vOT8JDTp0a|l zKkK2Bhq_4D-G>Cg6N#njsb?632F4VJ$BdiA^o=f)0=(A8^;GbJ^3z76w!kYP9EUT z-i-Wi0jTK8HeN)8x{jWvhkA_nyl{7HtTI%H!|-yQ0$h$<6~ZiCt@Wz6@4{R&4|O5s zKLlghZG8NMn_wJ*;ZU3Q1%;-ncij$E?5|j#)u7o@j8a_EL^d$D$!hsr_UV_b54IH} zfLNm7yn{FjEOv8&5?jh8YTB(=w;fN`4yt!qYvnr09@Fdw>*YTBgU0cIiYNBS9>LR zB&)o(SPem_7d`bQW1mA=BYyq$l!`y8hq?lUc)0GdH^0*vaHlVa#tNLH=g@SoL6I>3 z;HozI9~i(Mt0qx(*3tbI%irMjLn-5vUP)o!_Ofx8B=GtNNeNx0`|BEFf?a)!i1DQ7 z{B_$U53&EwH8ACWfEnz?6#mZW|MewKCa~L}2Ft8=E|cUT;q7~!IAAx<;|U|Ey}H42 zaW}SJP|sGau>+rTtC&i`bZ|I6NY7l5NN=L-4RM+x?KvRD2GFDdhBECj%ITR9VKgPh@`p+~;6#aQc_hk?)gFi`^dz1+aYf z$9JrB0xk=S{cNJoWs+l zD(vMLTB#kC7CV_j2Qb&V{+5g16a8gm--^ve&20p)+F0aDYTeT22WE@nU#;_d2j^y$ z)a=fz@t30-Gu!yY;NQ+-`wN_ul;`B^rwpx8+6jeS_j2TlkPc&l;KB7TP$ma6=5ZHN znu=-nVAol;J0Wj&+0o}85+TEpWjoB<$6owRX0$)18BtTgmP)&MkX!W;~AYb4ctJ%wa%VoP}%S#fQ_`I1vKfdw*gF|H5f_LGV_TE&x6 z0JWB6HA{Rw?H!Ejlc=xLYS(UlaBTnDRP_i%(-Z-T&TV`JP8`nTwJ!YZUGjx&7qjQ; zk)}%ZfS|98bSHzUU9Y2%vd=w~<|YYVEB?0vvC>IGsMFD{*H~l3>U`5Kn~}L_1Ksr) z2?$0jLdjU3vp9(>2OHpqAHbfM{Kl^dZ;Uw^F@x+(jtSARr^@u+6CY9tcc5d7m-)58 zPO@s8vsnw!;ya~*{LlBkF-(i40zl~{6{~@sp`ClJaIC^Lu`uxxp-^?orlm|d3j&zl z_VE^6EqngClb)1sqDh;cX3e;C6uZ;Vv!6DRAfD$xiG*98>XU_3H0h}vtcl6b?P-fK zCGUy>cgPNxRAn?)rzV2lN}G019xY%NuNkuAWy9z70wM_zZ3=w_TbEmlkS<5nXb=#$ z8d9Q%u8*LX?hT-}!0Uk3I4o!Nsaw=l(8@(Xb3R?5g-Xwh490r;txA@;SaVOWIID8t z55GG~=4vbsAi)!{Y@cr1ilG7uXJiNpoCeN2A zY7pFR9}9lijpMNU-t^DG{Z8{aeK92l`l8DB_8{B2q z%@g=G3AFZnS_7X$Lsk$Mo=r90QwX-Gi&}yx>d9TzSD5Q!D$gikOr1?V!K?{8mW$@0647d5o#9!Z{;eiVKI^_2>2m8fs;cUG zq|?)tWJc8KTqK6Y1@)js!V??mBhCP-UT-)hbD8IghaDU3Xnam~OjNb@v+)@A%q=~j zZ)Il)xHW$kVT)4!%w@uGfUqPs<9(t=90(v&jfw;c9G(~MchY}Tb#ic_P^gSlUmH;4 z7$2*&tcBC?r#xjUnU>1B>I_rl0I=R}w{H6T??k0hdnhuB*5Q=$3z{{T0N9J6BA2c` zEY|JIkxCMxZ4Wl2-6sT&0e|WPNJt#PjzdC2k8*VXnsOa1>tEGeQki*VFFMoj5yf$F zIg_7IVJ#hfpT53PF7d>yy1l2kCEI$+>AM(0*vGsPUk!_bXgz0VVaTCk=p>qAg-={EO<)KL5tQXtF2)^N*HV!(6RSRYXYKPw>kW zSM6Gagtw^C>%8F9-AfyE>{Th(4nMDfkpiK$B@aflR*wf2?CeCrl-G^AeglG{zYwZ} zhB+yqn2-;^I4Dh+X^MAx$R@A+_*xS*UJoE=pSKBcr~HQ#TA+wAk`>fITPh1{6P zUnNUpjgC$u4tMN`TU5r^kypwSIaqFk<@TtQv#+)Cs6WjbWoe149@z=$<(D^|RIF`Z zAF<Se~a6<3SXwH|+oU^~MQ zt%`?}!fj5F%GI|&-yXQ?I+TlP+-L$F_jMfFX=Ljjh7>MMI6C%gzLyA`>=9>-#5+FykEerlKuNCdM^9Tf;Vek~A$>wEl@I0`DW57ezu*9E#Hk^CXuDVp-z5G+a)Un#vYL!2V>yd=t3Z{&1LdPU!i4KWK-$V)Z_x%INk_ z??TiMhPYQY1szQzf=@!De%1`88d5CtrVdnsD6ux@-^_@D8nCp~EKJ#EcnOIvskQT& z;FNxpr>|Ym18#MW9t8r&C4T>8C}6?HE{nRrg(M0?Q>^fxgcmuJnY71?dqMf@he_!& z5gm@neSwltilTo)_z}lAe`ERo*Zfi{i+C`IqIk1%2dUc_2pI{foEymV|4WY6|Nr>^ zE_>~V71YCHYHWUfK22?k5)ek5xe^05Kr2opCh)kLF=)Z(I>Ks?5UZG^e)ht^7pegG zAxEr`v_L{!Aqr(N(840L|M6D|8OoHzM9>3AZ~f=&>}*R56&>Ac8#4QiQ6_!;IVzdh zHw&PmYjJ5wk?d+iHK0Q#P*G74kep}8;R1?>?QK0JrSwXW7b`mO&{bPe(H<0>gYFkY z-$0j5Ts*vjsbNJeEor6|&<7FJ_^mh9*H0tC{6>L8qn6l2EqwR^q?3IWfXt~Z)8 zD=Xd@zI#_%KLV-@eJ&SB3%0gvH*T()ZcgA>3W0WH_WcAe5kIUrL=k}11Cl6k*e_Z> z0;&OT4pMIde}Ou|z*Y{c>#H3fmVCfr#N~Z?io>7=3h|||*+dXdkGv$DG3g&kQ2B2bE6DG@y|oWv9O9p12}&)B5d@ zM)}F-K+>hTwU2?8ApF4#rXirH9KR1g^oq?mxzD--6-kL0=6&g8A(J zL`~lX1?dhRsHmul&TE5I%+VOIPg@QbfHhk2Xxumqwy&<|CLVopaUr9P4+>ouA3l>J zDn|+DDJ6)N_Wb4lF#QVr{Q`kQpWw+mRZucs?X@%8AbtTzg19%kUx3m*MFz_2VEs!G zY*13%c{N0QHJ90>mcu_bM6c39VQphH9g-mrpZa~NIbr0_S-+mYlG2u|q z9StZl1@q-ZpZ92~8=4aegjfXemD3mgexy_dLF#>)W-<3bB=kERl<5+J{Q`Pd-N0yX=<*5_=T~=T!byED_dfMluMXvetv&?;zVlbWq?}gK7EPcxuUoOG z#PlgeNsSO7w`=ZwBKqkGW`zjCXzN(PdmpPpAC{CvWXmGs30nLeH4qL+CqaL|M};%f zGcaT=bJm|Cg40F+ORm#^PP;sn(>hj1_05~7*w`vsTJ(5;ryh0;nBrp!zJLD?7m2H* zgmslY17&9WfP;V)m;wh_nkM9Ctm&GHYwz$dI)QNY(C=~L*NXRlzqg~KocKGz4SI=v5IU@^6@y$=T@bu3=U`(19k zHq~`OIrp>T^dR1LpWsV1(DnS>+`ukNJiErT?Ej{BjC-wTpc@(#>uhq_u}={2W^67k zncCUi%-vkg=}74VW^mO_1n4@2v$ey z5boKhA6e>mKWL~9K7oOv#o%W+hzax5tOeJ1@7|=Ky~CqK*Fu#d(g-n=5@2B1h&NAB zETCHMO=j5nAhE&0rB3+FhB*~uaccYZgjUz%ZD7UDNY2fqlD^Kw*Kef=nFGojWzWaJ z4kF#!!DkR|d>T{Zl|&V#+eKyx8`P-7GK?aCfyQLfzek zt`3LN)n2an`T8Q&m>y8dgL+zRk}xdL!YHdb+>Y)8{FOBl47mUk0{LEKnZ)lZU$+Ie z3usbP2I!tc@mY-k73My64X*bt;{-40Ix7R3+|WC@flA9yJ!mm8F`&X8&;ty`Ffizc{QYYqc@NU(y6vrYFWDM^BsY*i@)HIn`di>24|o8x z3UZGEXTcbWC=kg*aG@LL0LbPEF#Op7vTOi{8y6a7=&B%io#k_Tu^sTCav%ZffB%YX z7-VN~YCAP!^n{>lt)zK$`ZnYGeG)22X*EAYv68(3saqvBUl(gI@RS$yG3?)T`Wzp7 z(&zLSlLF9Z=1s4$UC&bMZCY%lWcyi9n~Yl&4NA|@D7pK?+bKIjXe=F&A*k)g_p|dl_2`&Y|zb_wJaL^RLB4uL+POHl%WKF~Jb@H1nkX@t9om7}#?DeI( z&gb6P@wM$iR=df-!xP7LnoW>UX5}iK^Ju4w)nOrhFsPz<#pisPD9f~BFK=nMaiv;r z%xFmbBC*PLpm^hz7JLXBV@G7C zrFw-3A%yYa9djV{o9=okwg5e;&Upj3imZ|dE%O%i>C^)0KK1yqQdeR$W0&{c$w4Nc zkFeDX1JbI& z+yi0HZ;_AJhS}bN&}V0Y0y02(_dP@V1(S{S2$8 zXO|*?(QR8xK$XgDaYmUNANbN;2GRSeje12+M{#zerB)q9>2E$ycu2SImPMQ07}b%^OMaNp5LBS8SCanf`G3}C zCP%cqVyEZb{G?B)Rv*>FH8&JXCGCfOnnO+L{!}DUks0NiqjuB^rJIMAdO{*ci8s`d&wg#s{&5Ltv1YXZwpKXjxL)!~yCR?e8s^Q`!-d>}1 zL#~CwPGqbZX1&R@1-a)-sKI&bAT_N9zKF2z1SwB7@A0oi5yapk_(zl6oCDhD)|VAf ze3~e&N8Iqf>8js5rxLZ( zaPCEZPd5WDNNElLUd7vlDd}m3YVX&u2OVPu_0J@qIQV_$)}S_BwzhMlxzTG(F?uok znPMA*jb^U|m*lH$LB3t*&>RsT+Af)#i@?u0-00|`DiC^+mgxk*@1Cvzh;jvqNY#si zBja}MNTNFcW-z$+1Yw=JB(eYup#2~ddBNHYhs zpP<4e==kLD3w12Y;O{Hsi{#7&y#+2CAxOJYV@fnt2{s3feNeTJL)c186*d2rdtmdN zWFf`DzPDe768!ZCzC~w76Z6S|4dJ(0$=(7UVbJYPi4&w6^Z)vm#;x%z;Nqi9;Xldj zy}59T|NgqloBhN?S5=EmJ$>lZDpQIA;sXo^0K=^CB#K*euLO>^?>5>gzxUiKosyh> z(+MKGCjaQhf;y=iFi($l>n)*#sj(ZFP-6gxcB)GAau~P@c*IlP9S+ra zv}|#aM-eOZ7h7%?Zly)p>nIqzK^P`d&mRWK&U!3ALi<*6<;2!o(`2_I62hXdkkVVA9zxx$ss(TtGCCm^A{TMe~&{A`0kIt z_&+#U1!XQL_Jj^E@|Yx_^4{))$s)M0jt5m$H5*x-toFy}pi0PKs9YOXa#VPhVI9un zF!kTRN)C(adJ~$uP0?%7r5)LN@irZvdNwPmJuXE;xOKItVQ1r}-UY&UuW#SN!MvwH zRN^kV7^|k@xKNnh*?>3X&QeQBaH#Qgl`ux;Svlgni(@B2*u8vRXZo}?uf~RbHk-&u zZTX6&819-I?pTzr8FWq87Jl-a`uydRF|2_#s)K2x6#R@yw}AnkNtYylnQLoX-$9JQl-rw0HYTh@Am(c&m%~P^;b4ju zawJh%FnwPCl|yzVDuJ~+z97Guq}b2}(KjZYA})NPIqc2~kOV=#l0PD0#3pm|;ws4# z$@XWw$~Kgm?m4x-jm-;20m#Mr<&Voj`mFlQ`g0a`77Km!L805xT9e?GfpSJvAR)+o z8&#iqvx<_IzUa%NE_yp5eRV0cTz`}n1s_+nwZ(uL#p%H-AN~=a03Ujs zL1k;n3U zo6PVQ3fOPSe#4w27yYwN9f}^4vlPd0MX|+ot5w~vJzF}U`8^DI?zSRZ;hO%fy-o3( zG;um|^y#fDwu>DfeaiX!>YffQ;T~BkA*QaZKF#0HQVk1H%V`p4LYozTD3jFtw3EbA z-}5c%-4V=_^bC6yJ+FgUrO@&}3JDTCPSgV4`E~z#L~x9*L2$V%RmSj|dMhuUi%k96 zCBKPw(Vm;GVrq91jcI)-6Q02?ox3|+@z%zpv$V)?T3DgXsVJ(t(7Cs-HDxK5T{k0! zzRqtzhYFqW?_p*vkHA1b$yCgr{$5=_dF8Mym9v}k8Ee`h9;j|u@Vy~3*(nzE-Q&+IckWousyX( zwzdH4l3i-Xy|2}G&+u!dAvfMMl*JdfJJeDPLCzw3eBn5|;esFtwWlQP!*K?W7#3UgIw#;x< z+Er&HlU{d~)Cii7gbQcH&xUxcT(E3Qa7#q zddZxLxgBY2|JEup*`*+OaAwP{L?t!bNHNtYZ_X;rA9L!Leu|>U`$H+Itt?SqS725fJHzC#a`{>rzwBjzF0X3wqLo6l>~P z$g8Ryd|uWbulZeKH-jwenEf^hkAr5+fWiXZ&$PF@w0MvNi~-0n2iQ_rO~T6Wx14u!S2|1DlNAQpiHC-<6?2YO>gsvJbS&D;Tw=F28z zL84zz#hTJ?n%8}-1^*A7&qMOPwE&0*v+CnSl50U0#)46B#lCf96OLK-Ft~JtNtLAn zCOtH(D=T5(lJV}(dFTTx;nBVDEsB-$9DqfK!x4_GA1CSmda=e75J|p2kNxWbFIPf* zJamVt=RSY+N0fi)^iOh_Bm8&}N|jjOdb)j{TXY@&-d?=J4Tm+}v~lb)ZnD%sg6m;1 z&*T~ci|+6!N85I@DVjJzNhEoQx$@MszJgk*{L-M=*~*QqG7!W0;w(XOG-L{k=7h`4 zb-LKM@VCDaAJ;l1W5w{6^tM!-hH1{+R!EKw_qj=6WSVwHo=JnNJH|r9(3pCp6}lg~ z*;q><2Dgs7`|tYYr(8pV9qi;c?h?-$x__LK^DV!!-Uy3tCRWnZCg9q8DI#UW+CWAl za?t1$GQOSwBy!9p`Dey2*=nd%Q*DZD6l&G29!64+HP-O$8zs+GY}S9F+KKk*TY8*0gh8eB zTHYQf8RyYwA65BJdR-?T3ri%lJG>I&nKATCu=IVabvavJPsu0ha?`2rl-*%Yk;>;{ zT7SR>u!Z38>kh_3ouzm#PszW@)EgmJmCu|2-NAG2D6K@+uy8%Vc8=kQ;>D@Te>Ab8#|-%hUqfTX@6)(r zFJ2B9>*O$YBVzxtUMG6EDT*2tUxzP)Tt~$_RNoqYs1UEnis3Zg9WQinWAhgoEP3HB zz>_w6YIIP3DV?>#9&umisAJiEEinm;w6b9|a&qBjkS?K5aNl4$3693;qqM!ef+o!H zR#>0m{$MSwQ(dGCl@sEuc>Y1dTu`8O2d7W8ue>wj0hkpLdrL)S)HMUeA{wUk9~+?d zAt|r8A~fS?o|1Te`hYE1HMH6_ZGuY&8Mj3Sgbs}u>jzv&PDI5ZbUKBnIAK|91J^l8f=U_8Unnj0k&;2HTNCxn9 zMzktkQk#4mffcDDG>|Ha)~hSkxuYnw43fdIkX-QC^Y z-3boC-AO`_g#?!n+#P}wEV#P`4+{zIu2Vekv-j-po0;R7`8)rhdv$fSRozzC34z-Q zSxBA5?qQB~11eW}wT1;!=;85_JXbJQJ3YEoZ|xqwrK4Pre>3Md zcd)AJYAe4DB@VF!Ygfs>&Rt5bLdN**JV_0QaNj^iOYy#wm0>;21e@d|r_E;-b+T`6 zcYoL3G$K%Z>uGe$QYR7L=amR27qqe?Ps1LFm^^;^p!9!>Ly*h#-jYehT#bb5Ob`w^ z3*7(5pQxK+fJToUEFH9Rc?-LNApA&Y6o&7PdD>nHF&pF zbU*v266eYv+Ez=ysS(}w{Wn9Jx)azuywyX-smJd=JZDH~Xo!1paME-0%jRej1)r5bMJ%b+C3bx$rocrgxDxXhgfJqvs)eyT7d z4w*(6j?k2Qo4S>0+~<5qzw{gY{XKcn)59;m;fe_!0r`V6Zc-*!4g;M@>X&}_{uEc;=!PBNg4EB)ekqLP>2 zD9aAtn&S4$PyM$KG5j?2)KPM{|SM^dH*NkjdX_eWLkhqZxk}grwBd6^hmkF z&q;I$(45@@v;vTgJH5Q5It8gJYdl*vRv;#xX9H+LAgh;O3{*JBW`VSKR)qu3qpu%O zTl8xIi8J#!pxSx3*mw%ys5QMHPYK9`CP0E*_B)WTJ|S$ozgPfj!GwJ7L$k9UAmO2| zSPP`!^NYW+Sx(FXMb;1mFZ)UB$X+*oinOUo*d)z{K{iw6{D`FDJQFz4AhNEL7I zXTLNATEc0RnIQROnmyyAw8+%>| z$eD?)ly)&Q02(U6AVTz_@5))L3&@1>3kcNSQozH*Co&q)a@qgvV$yFgW+sdc4-cR4 zvbLs4o1Q|9s0jB?)WeN%;7lhd)P(6!nT8zC@)Hs_odcq37&#LMNqSWz$ws`+sBB>KA_ic=S22fbHx|5bf zdh=!#QRd*pfqUAyvA({ZI`;=NA3Goo1h1-#Hj%*`N%IQ|rnge6IhxvlY-d-R@-z@0 z^~#iL1+pkrtS7ZwI@0_=xc?y!h{#XfnLopgLn?{m)*9c;Rhd5oFq5FMgjJ!rkil(< zajP`g@2)RiBO-2y?&x4<y}-v|5ZYMUf#Ik_J*=h4bP=mFVnsYcADH( zuZWcNOLtxhd|p@S)DM8;zL8ed1=!3*0ld=QD_fCyWntmrjRFJ-QyzH0op=fYonohw zH-H>iqc0FC=PxR=9sEf`285E|vLQLhu)yXRQ35s7dUhTvM#YzBd^AtmzAOR)OUCN4 z@4}+FMFOn|pG`QO6n&Li<8JL8s9xXF4|piXxl*C4pQbxqm5UR zaBW>z0Pva~iZd4)CZ+;Z?-0N*qt~5SkS2mO90MMO1dm*5W=zvv*p?*_1>*JGU)1{W zZ`dh`UZ(>Qut~CJrnf)(n{LpoffV~EA(J?izT#5Us!<>+oTu<%Og6Q&9W#@qX{s_Y z2m$M)eM~x0?n{@o5xqT5BKN*7pP!OxKryKc1QpkN(l}8TeFKp#J>*Jx<5ZT7ycK9& z=Y=U!=}QzaGU@<9`lRGki{tZi4s0b@=wwuMgROgTRP;!ZEKd<9H+S+LJ>^^@ns*Lh z@yu4*4J&;ag`OzsNc#ebUc~RnIirmF(|sBtnlJui-kl0Z2(m`uut}D&8UBF|CtO1T zkC|zNu9VP-7&zhp9Y`J8QEwn0SM7_?BJCii4$%xm#y|oxzxLI&9``)J39k- zv@d%OTW=K~G=EjIpV^0^%26n8a`12~C@OMF$Km-J=1xNTt<7;ioW2j=4H1RThxadH zwDhlg?7wYz8$g^{FIUDa`j>Dy=!&MgtH^TyF%0NY=CkA0P#|u!V}7 zzIOo0aITeV%c!a>>{6VFxB|+A+=Pa>I5Y?7;bFzW(CetlAp0*}1|X|T^&*kG5RaVXh1tw+azN2TekUK z35zPgWq)f<`{cYW4omAdMRbu5+;NDQvbUnkjBUvmnb)8eRN$R`2#$z&YtKYpZji-Y zst5(z#x_AXkjZZrV7Na%KK3Z8Dru%#F%<#A#Fi8+EZE**G}}m59&1wBel%vu;A``Nc&( zG8~w6pl(=YBu?`|?c8UZ4%bjSMa8c-DR`vi@RePI0|Prgwy#@G=`>5ZeJ<#Ew3>MX zRW^JP)wol%0&KEQSwN;Kq^k@BFG>S>Ag5vMs*Sqi)b8%?&GVnAe232~5*h&YqW&+R zt9>{Ig<{GYs*#b={yy`avtk-Ag)DtSLshQQJAk{0UVd-*&f4BvchHW4k z@-6e_nzOT|md0o*-%V@tygJVXx^>T1Kq#ZpM?!LCIVH;XGVgZUBAR4NRbG!6Nr z#o8JgxC5OOR*{A7909cAK;%}hEa3_W+o=Y$+LjW6h%{9!{b02qm5j8(avo5i5a%dsE8A3%amN!(XIna_hUDLL*6fC;>O?M|F2 zR%XO$|2lNjV^**yW%*yNp{K=mPg_ebh5@6AH(4FFbMTAxlsp-0qc14l;Ezv~syHhd z3~Fz0&vpbHfo80m4XmKfotb?VIj_(iD6}K8NI&smsz}W%;dPPGVx9>D1kJaXCo()? z+7oEL7nPT9_EbT?lQ`}8&Xnq8r;f<@T3Yz}HhaQtAw1xV@o6N6@$v8+^T-=IfbcMA zxMOl5ukhQ&V7*5e+9@bhx)PUr9FXhY9$rOOeFZU$7lUd72Yx0C1)>BjKYrD~9(z#M z2UuCjp7F9^u2!byNeW&F{s}>Ee0tsO+bo80Rr=~!u@9}14v*0yi(Fc@()s(ANy7W< z33ODTChW3Ii9^ypSxGeIyH7&pxbu^$5G_s3iEV|&T*U%vL9Q{MJo)y?aMaOHByE#; zioKm|gs^MhgjuW$c+daCq)dK+#_`X_G%L0 zZyGENOh1+Gzqgs4^!UBo9s9^Hnnqo)F`nFN*`T{r>=3yj9@c}fPcO9g7$I}~+ovkNk=KH8jy*PXI`Q@{9~^P zL(4$PJm-I03O!`$X1gZ~9}NS;?NtpD;8*~+K}JT#2BwmBzKysL;lFBwE0KVndeXBo zHea#C18{^}=E`%aXJRic zUz3Ri0sckYBN9IOvLLtDpsL0Z90&4fbM4rT=KL%vAoB8f;iKSoT$ADqb%m;vB zb_;by(>>vC*-FqZ2-=hRWd@(!^Q6Zxn8ahP$ zNTHjwiP2E^uESB_MkQRpArr2bZy_``BR)MsP@*7%|HLsUE{^4*p`$xJK92eR#>mXf zELv4TbE&~8uyuG~p!l%w^*Lod6(2e}dLLrn6?o!d10MVH-L>mUU|=1;+L5eG!nO(^z+nSu6UESVyf1ZOeX*P9iD>k?P`)Nb+ zTWxLaK8F4mXB_tr;THhkxn4XlFaT&jfE?xY%*)H`L|aulbp-JaMvT$DG|m%n(K7s< zot=SaRS0Jna`_KH&FO2IF9X2+kjRQ|Xupzn-O1<) zB!{J|Bx z?IYom9UUDFUliT+JBY~4wj_?9N6Qm1Y)_8grE<6I>-&Qs!QAWQ;l6lrg|Mw6C>z401tz~Gdfq4`hT7R>w(naPcsyE}XGodP-(fVgFua6~-> zz?&XUfbej`5Sbj${XHr8%SmMt$^f_P*1M$FkjXJ6zHfBoitcK~+U>cbDyZ+?88fF_ z0|$eIqx91Vg>NCdI#!TEe8W)%x5aasWJS-jIf?i&;RR!SEV?+Av(3T(WO;$XX#LSJ zh#LJB3=B?0!Fv`FU}+NQgtzk{_G~!waxxZVr06euKizm1Dkn`m8hIa)A(fjYO^at; z)KRX#)Nsy@_3eLIQM3Y-+j6wzq-v+G^!vMSX|PfHq~wG+ICGVDi1bd*8M-9Hsjm&} zLGWOu)lYYc?T+N5pl@g_rK~S7o|?%}YZ|(2kdFN)dQlpi3k&;*5hMS}5pR>hV5RuB zxZ11@ZtcWA&q+Zc z;i`zs;e|9Ndj~$m<~K6>OW#n^OJo!jHZDY=lzF4z;i}N#6O5+u_2Sv|-oC*72so1y za!vmCKiz|~AgEMZuX=8?Pjy5^KWAiQBwMLwUPcyFT1RC^8*(@(@?wHiB1mI{^;IAS zGgbflwagr`!O=TZ4;L4-G-g27fK{6{-CkN#h%Z?IbI3ow2^Je_B-tn%Gkc0D$~$0q zsM_F^b#bmGqAD6g)GAl$8jU3c5?`e z`1=d`$&WLZnzc;mQCWm+iNPi)BRf~;UxKXt;$J(IS(RbGb?4H!248~|5ZeAPTe>ev z5=s-9ecvZPU@V2VlZat6E~{TEN5WHl8%^xw=tJTZhgL!FbiI=&Oy`6P1F)}7Bw{&- zdwaID07*;E*W!ih!S1eTYUoYmNvSKmW@RW%_PfQ0OY3;UM3v`I_qufL_d3YAr_Sc7 zYiX4n+KP3Z#Y8t~2Qq!^dP+wBW6&GDqrxl3h^%tk)lA2Hx26+EW)7}jL)j(nCA?;Q z19!lfCSbdPS0aweOOM*=8$hhUzIqkv#QsfPM_$1XcL#Iz=q|RIe~~=$3|y}7&+Ow? zYHNJy=JTgHmYM4K=*Th9$756A3sN1D|Z2resfSYMg?NUnP?M9S;poKRd`|GVD{jedLP7VA| z6F#EU%uvha*Mur?m4J(*~slub=)AJb93 zrGBwb`Yu@aB$lv>$4Ey$MDGvBqE8KaU-$=u&h!}IWhR@kHpW+Nsk_NcR=+s^i0A*FnmVKb9U=H2llte| z1}MHaFT!t;cQXB1dIip3+Ph%mMzf5mTCCd5rF}fm5gUuTLFpNOgI>a!u#4$?Na2UA z$_Q3{@SQ59Dx#Vw!lF6|{Og&Fd(Ofl=WVZ4Rx(deAikBUEj7l)ZIb&CsTM(pP!zpL z1uql6r?w-)__rWFTFQ#b0NNb*%%arxVQrpuEm+i zn6BxWNiw&jN#ld7w|yJsU>$GoPd(2DOIF+#gR}P%izoIc#TCh#tn8_sDC{jt-kXzdIQi@j)Rse8URL>%JQ;WYxBzHo?8k*@I91P9UXO&%VeE&A zu@s4Z#$1z4zrzTvbpkp8k!;JN*}^}ma{9^4>13a!VtF^77n6tfb@6dj? zhW?GO@XEm7%APDOTvSxi&AgHRY7pz5=a^-^`-N%l)gU@~%MT!mAi`mdt!QeuQ6Tox zu^6h@*XQazDd_D8%(}ai+HkWgmbw!Ol;Dew?^XdeWv`yh!3A0T8VVpK2rQdgq+}q1 z4ob?G%QHWfwR4v_ElA}&18YG*J$kc1Wx(S@8lOjO`}I!tNvZo7_kZl{i;vojCd`KH zGs_Y%xKD>G$jS~pIvg&3&;DJ@eRD6Qq(+PU={dTH2sbHd=tok_&)uhqe?A->o(b+% z%UnhvDjE#!*FIF{#>q^4+pdjs_1?r;*kpfqb zj*d8lN3PgRfXL8$<}KWYi=1cA8jpY8m461>XUy2q-0MiFA)0|*Bp%V+HMrzRje%WR z)5K1F1ms9%un@5WVkavOX?$lmh**gTl*`=R{Q?-$E-o(C9giGfTK6$z1tI7BCGZ>F zVFMXoFE1TQ>8Q<*_s`Bi3jK_Mck*9Hq)9LOJMC`_ zCRvLjnNd06++AI1+a>l+*7Oh`e+mRWUW9Z3Qr4UV^pWn4YcD_wN?-66A6(dT$V$As z?41(mVu>~NF)(%I6%`fb1+Tt6UPeBr*sHg~7u_OFXQq%b%2OampHb!x=pa2bU1JX=HqiUVSQ zJE}nj1pyBZtdlu1nI_@QVK-7K)S5maFAz0wK0qx`YkJEqAVAR8sU%R@*ob4AuuT8h z4H+DiYmq(7FPKMEBKU#CzQ>@6eQGNzV4cuo$K!qIYUb{)X;5r`EIPHib1_d$P5|(S z-9+tUy~ZCt$Q_!-x&#Cm1u-!)j*O0$Y$WQ`s^>+4x92RTp*(Sa-)^*sZlFTIpuB$> z)xbG1S8fDZHo#p@!8yh$?4;`Q1)@Ply3uE4MLxz)#gdm5dzj|M6MLS{&)YnYvm1Uo z-E0oyIaJE8da0XMRh9^E?9_PY44c;^UrVAU1?t;rR%zuXqqxZCw>TGaj?%i3EJx8$MT z3Gn&`2DKN|{idp^EPaqo;^~EL*SiMEq^g3AC)O#E1EW)9yFyo0LQ>|=lp>w``}gfg zHq0OXVviaOUm!(%>Y^x5Dm0T&P^&8j!1k)#!g<3%n%&)9cm0g#M$c3%kg3L#{m9FK zpwZ`_7}?LP4am=yy(~B;ma$rBiaW%fh=XU}wl#AdP_^KSea{02R)8b4g!lBlpT#!B zTgomiEXc+B`VvrWO7NuDvMR((BsLP`IAaQsm;Z3D$EVaPSjAL4qs1G+5`o&7xwU<% zcSd6MQR2f*iukr>{g@5|oMsEKN zr4u4@$!@`>`1FPhYabXLE1F63)DxlwmGAvPh2kAeW zJZH+kk#3&7w|N>OixMoH+)!*P6Di{x#Nra16hb&?4iXdh619x=GaZXjlg`S1f$2Z` z*l1|*fYL#@?26u{%(BG0SmLYNAV$A@SIUTwsu*N+Xh>TkB{SE0R^|1N)GDtO&Ddrv z#$&*A-NkwA!EA!cN|$x{WLfcYaajhvM*ihTZkT5MCaBzAJ3uQy7aJ@XZcRTX+VdNS z>(o-0ho84c-rGAX|GKFE2(vQY2 z3QZy>zMYULiVI6LNbiUfDr8sfSP7<0H1L@ZM5fujk^VV&8rvH>r^fqV%LT0pxA4e+ zZu}Xq=$F=j3ZfpKhbnYDqbNt+3%#oaS-_f_-1efu!F7KG$gSP-JYi^QdAaV0;V7wf z;Q|BS9eVl7Q{W z$-HHnU)OKu+TI&2PbHMf9RoJ|S1HIi^2?kUr0YbAG#(i|4)a5N_Lp;cveiCWxKV?Ka*Zfh%lZyI$$9ebTF zN>Km4$yv4ngjRS-Yp=<2Si-=UaiJgeFwHan3J|3S;K!|PcsbOw16lV*QRU8?s@j;2 zNnLAki)7ut?x5B7e)c|5M}K;DL=dcvd&#zRGG6Qux7KU}1KuMHNTGT_%Hq*#dai|k z#mV)}Wyo7)C@rnOS{PaN)|nkE3X!fg<8k&K?*aEMKTj)-s%h-!a~0C1H5 zfB!J~@x{o`p(zs`d?6>=+9uEy$0Jzo(bZVTLi;AdzJ#gw?jAzEWAT1CVRHH`lIKXmH?fM3 zEeO$-^f)8j{b&7u(wC~kyI(Y&EO=Zh2sBcNs*WUnz3g$L{F2a|ZCmf!>dTn#VoAz3 zM62&2nK<$?W~8ZlCBVn0K00?-SV8gQ=Y9z_#x$TW9zG|Z=rdt_9bEtAxo4+DAm0^w zlTKjwY-?I#v$c~{eNFyS=a9!zACC3a#5Rmx1o~uf;%7PkNt!sAw#F<9oJVYgW8Aht znUTR(lF~^+%ghU zrCI%$GXF|?uARkO@@(rC@rtm%u;|yK%lVG=`f3!))#z(d8UF}Ud>V_BERxG$>6r?d z{$~agWxq_Q8(4@G8o5_k7?0|L9f_IHe$R_b{Y@1~2v}?Ww#cAMD$8xtKw7>#gihQP zlA>=a3BRA8=_d8cY8k3)ecks7S$`HUk1FYFN$a4YdovHJ2;z$$C|H zCOO)nAHS}8G$Sq(ImmrjQI%1u|EOV)LoO>P^v6Q@j7NPP_RNZSEoIsvwYxl5H(7=yx%#%}73V%K( z6Jwks7rFT(+rpRwx%o7VJ~4d6&FVwY>sG91?*~q$JjDftYI2DicC&aNs4Raz3s0%j zQh&`jq61UWUcviaK)`ToKcQT>_UcuFnYsa&m=ivzXUKwkwOr9f@KQ+R1-e%ZhRCXo zKs{*;9t<5HYK|>^2wLt@btA_MCfD}9u%S*f0_~WoOMbou+P?6`;47K+pELK*P|4lD zPemz&a7(@rqDAHVeAiHE!Ty>=;6{Z<;vM`<|9sexN?jcQ+3Xi+l7B8w)E%Xgbex}Q z?$(L_IiugZOMHod#~Rf!m0o|%f5i3yX>*tQO(~Bwl-OMiFZSjWk=}i-AUt#`EZ0!5 ze%_2)o^9o8vul9nH#~`%pN1m#(Ft}5Iq!5#2k(vTMisw~Ev5(ZOFj(Xw)4W9t_;+r z_xf8&vBtw_<~okkLdrrzCVuPYz?__nr3AjnBCS6t&=w)kE-9w|oIB?A9r0bW`>^Qn zTJautRzB;e{^v91c(}Ou0v8=t&^L1omyjxNYi}RcV zS5H`b>Fui>o%3VOEq;QDmx0(dm`f&1KMv${lPb59B5i124KgO6Go`g1pFzF*0(8P7 zccj-Wco>-ZeDr(Ej~gSU}dX|Ku(gzESLx_l$AU*-!r7I7lXuR)1VBljSN#%Apz z!4bB}4d<-vRY%0(5%d#i95wDLY8M#Zr8>0Nh!r3FrHU`%?!H+`e?C~WUU6a*G@Mjb zg2$akTO0ivo#S3+Sw&_LM6+>`oYg_V+49(@y#k5To*L3uEnFEEs}_jp2za|tT=az` zFQ}+-t;0CLW=PCpUeMv_>pDXk^|zVVJsx8SBrF?VdH#}!eL`LhiXCpz@(5gt`P52b z*1?O@HLus+=8Ywu(%*YDw{B*@TRs9or^m;a&z|_r|L35s@_Eqyi1*nuZ7G;%$p1ZP zyL#E0|1)Io`3=XdNxQEsp*#>_`^6%)D4^b@Dp!0q`kj6>v}g6x@-l){#URxph9b6> zL!e+uw+7rp7d$Qo$HQk`^b}{#eq| zbWD9v7t(azNs@b!E4FW4ZzQniA$RF`M0n$mHrC`Zvw-$ZQHj{K&G5h@WNH7{a=G&e zhedUHKaKNt_I^`76PaubK^#X*tN*O#M8{YRL--D!f{P$J-*yadl zn3|RJ|2n4P&nTXC!nAD$`2P)1@&0?_7@?nDuCW2yD-qAXy$C+GGis5;=<9lCNACZu=du?``MZTs6%6+Tc3fV{qi-e($-LqxZHDuAJ}tE5lnBO)g;5@&edY9XyGs zS~l}H{hM7>W7(d2J)gaYRK1&P+o;m;oOccONJrLRI028SljzrGFGhP_I@#OEh1{o? zkqk)>uU(Hzxt1;2?YiGTI32d1Hlumew>fbUD%}_+U^x4Ik;3FJ%^<#?gUR2ba8Z==0N`8RbHz{;;q3$(LO#q zz-`<_XJ`q=%$zw@JH=S}1?8ATX!_g!>RS(ijgSZW7k%q+uqfZ&{|HqRgQ6D%cQGp> zLL5H%n;b`i4R~M!mm-7E%5qs0-X=6hM@p*Aooiw5Zou=vtJ$D!h1c*!w9w*FiuLXX z%fsdsK4cGuHf9DYJU@#q?#ZPc(_I2dc|Mhnf!%LmL(K(-4n(JH?2?Fs4T@*cL%ZV0 z{l65?PQL$6GECW{vT|@d8d~W6o-<{IL&1>aw`)rn_=U5rmZ$d)*PZj3Pm56Iw@<0` zpJ4+w3aVSVS@>7H60fP;^AlD|V-4T9d8d+h&~r6yz~OV zF*Lu04mUBiS+y((Trp2tzC~)^KJZ*z${Alz_|vNjKjh5<3?0Ji_ZA~S;K_Y2e zC;6K7dEOE{BsR&Sy#7Nx_Q2LX)#cjo$XtbEpdQj{~rBLbm!#VG;AMm+t6-m zEd4GrkVJ$X^MEtza^dAB8k=`0^3c|MA*SpBX7AS&Jd%f4x99K**b&%? zrKTl|jRTC)+}7F7L5Dy)oc`@OklrvLtxX zArqiB`_~X!BhbJ=Lm>CqtuZuMcwM+}KSf@1|25@H-AmQDu@muu1rbIW1&IZ&A{i?_ z8jd%W^{8YohV&qpoy`fJZIF7@qGZn!e4UReU)*^!DW7wy$GrNj>|zFQgGwxhVb8^? z9s~bdSpwBq3`6E4qK5o0<6zolWH$95?SqGar1X&Bo=(l?b^VdTGpX=9(^6-&Rc zEevjKIdLZgKLCf2|J$ZcYjUS@_w9Rdyn{(0DaOU-wXP$-u-*}w;Bygeg9)`On$~4 zMxM<#KRCm4_441d0dCz?=L)WL9`W&cO0=m7DTxn;U$*TpvnZB4tyr3~q|E%Pbq=*5 zy2rW#vPle(XDwHE8}xo5O7Y$!`Ev5RpSAKuacAYK+xjB!6CEP@n{KT)u^N3Gb(N@< z$5OA96-b6pNYQ!Ah$kFR(Ha)zT#8V5cjmpBhI1sT3*~lO*h6qJa#CJMr^~WBwinBM zq9~;_r?kn!eT`Ww{hZb&=7stcDGZqkg4vJ+Lo6%UI3PbID)5H>-#vwe67Z7Qn0?9N zy5#0yWBj+mZe>`E|2~%IWmRlMW1)mst!8q7iL>#{j81Fr1i7J<vGW9$JJvL%#+TETy~p%s8U@ z%DFWwuRD@_aJ-@LZy1}j+$~eOm^lo%gtAx zJ=5T~jM>m%<_vGHlyzLDVFu@g@|8)*B~{Zf$fI0bTf>R|dJ5KzKbm>-*09)b@9Iw+ z(B%zd9plLI4Jqu>+LaF`PgszL$J7ZVh#^1v!|@~7;#UFQ7w&c%jcRYID)As2ixnkXAa^YYGQRnSbV^DC>2OpjMqA#7X| zpMI#qHI%+}GuC$@_=Eek=;izN9AUZMiJ*M9Pq{*yOd*P2cbERc`aB<$=X%9-6PuW#&UM za{Ff`KD(KIrsHdcv3NtT$8?SAgja^$RA_f8#c^XT%D-)yO zq8J?RnaKZcUZS~B2{I#frKeKgHg~ta(}?Kmnp*CuEBirk$(e0b>8;{x7$sYo{rmeF zStESySHO@&FIt!JA51}%!!HCti&=(CCv1G3`MLqF_^O5*-PvAcq(D=@?Ib^9l z;23R_CgzZNm)FmH4lb^PW0*f5>8AHX$lnaX`-{l{|A*64 zqI9sJ@vir8K)^%XzqYL<09qoReGm`G3GKwl~s zALP`!^;#e4fp=mlD)JG9#flWf&U2_j(= ze!8q*5_T82xZymVJ4IS_ou&t5O=hsFJFpmFveLgg4{vzL9J&AKf_2oyX|j)L z%c;fqXhmXMO{%XZ@h}Ygx56AKCf02~=Un5KLn-X?WO)5h%--52Qf-G)D~W9F{p5=c z>S*T>??Z}fd*jZPD8wqK?r*5i?c+~VK+ttkD9N69>|x+Ba?w)2(0;hrl>DQNuGPr| zg?|Ze)6=Siud;LFZP+uzD>1#W5Jiu--2$!!1fG{B8S^M>+&(+oYZhym`RlP#T6>Fs z@_la5HHOtPTGZ{5g#2E&Ab5Gx-ER-7`&>_kAp8LT{g50We4wjS&N-1no-M=7xv-8h zOr7!Qefa?&+4dv^xzmkBH{<3gh^FPDGo%DRT;)bDXzzZFcXpxoECv5yt>najW$8VZ z3Zk(7KFXSC5ocy^h^Q>KCMMAUJDh-EW$x=Vl##M6%!9Oz^frXBS&EY!q5es^#O}&S zvt=gRZn=QIPmwzaX^Ps-TK=Zkn0Fd0Te@}+jsp%6m8VEBtu=vnYY-4e3M1U(wme^LBub7kq4jiw$s4y?oKJTF9!= zFBDXQk(31khnLTjIAqW}kYH^vLfWFrVyPwjDebWPl8E(qnc)?sc8NMULd!!^TlVht z8ZUb~^=RcHciwz1=xqja>6T=476V?RdusbIz(C`CJ|lIk)|-Ig=-EiAsQ#D zmYg+Mj=W1F$r@{mr{0CGwO^aJlrK4TgT}hvj6)K;h@?rzXVTDv713UO73hD_xtLp& z@8f+!C#SRjp~)qYORTB24BSe`j6l&tiaFlLS6{5NN39=Tk$Uo_jlS}Q9m9S9=7zZ* z21PrlfOi8fd!%fFLr0=4%z`d!TvI~oQomj?|2JX@|MTO>RR=Xos zn~~Y`fe}(E;PI|vRLCo4n~DkF>-qL%fSunB6H1?*@=uj~q%6*ldv)p;63G;;xN_;I zuxt-V&MFpD7Xh(Rgztq(SEjprO2 zr@r>s`^fFo;KBJ_=Qj(>#a^|0+maf;4>#nfX)$ZlB(y}w|2ng#8U8kL(ma5Sx~j8 zJChMKOgr>uN}kV+0KzIS9wl;V^7tF8(VluG{)`P_k@nq-Im$cq4~DEu`&tKro{-*@ zIPu7yfGP2Vp3u|9{kQg>yQn!nZq<_4OYbMqa=yuNa+noL{oRqKdW9I(e1pfU ziL&$;2g9+4F5fkXi@aDp|Mb*<|CDEpO!LbTcNe=!+2rBJB?p|DcY*& zC{pF2H~l_GyRBOzcgAf_1&)}xqNR8tx!zu?<~=*ZVGxcM z`TRT*>0EpMmAUJLCLvZ69B2|eLPkZp@1df~heoHg%nnpY6GPjJrn#$zjz=DuB@3*z zte!T^KgYyppK^PCpIX#EuI|jel(96lAZJ_UA%0J6C#q?RcynUQMlBe+Piw&Nt zd-_;@sUKN6iC!ZOmWcG4XR#VPor8#dF6@Rp$)2n7Ra&Wktt#;uaBU(aypUw--u0`d z=L~BJV;lW8Bo+!uw!)4wYMJ4OOAyKAm#VYViTz8>X*~duvDY?$$*ThArxNf+qziBO;&Srz`9p{q%-SeF(Q>x5G*eO0@VvdAZ z~%z5uRu6U00&wc3RVLzSV>r|-7q~~ytw50F4@a8Og z(q6Lu)vtQ9>yjTg7qRaaFA(|zYlPgV+v_Xr;IXEYS=9f$a%t>CMoM?zEC{w|Iqzt@ z**ogd7&q*Mbsk?e6S9-kAdci(jPum$Q_w|I zPw-=HNn-Q(rM_2uWVRp0YT;3PMG}!wyP9%}uMR76g8x@@UT4KJ=%$p|%^l62lA61z zNR9Qyd_kCa)s;axHT#DQCJI!u@Aoo-Bazj2NB(U-XZUZa3M0DaFVcsL1+iSkGYKq$pot~JWjGv z7)+^{Q8fl$jShubett6}5wqg<_p_OXuBHSJ3D1sqq56+Tl71u^_e}gBC2?e*ebA*{ zy5?#Gu#Y6tl9I??c!=bHCa^d_6DXjGzoBntx;+kqg99RYNOcktHB`PzfBi{Ft6}?9 zx=JjTfLiMRXzwip;@GxzQQU&N1$TFc1h>YW;O_2?ySrO(4;mbTySpTK2yVdwfpD8- z?Y&mA_j&KU-|rUE-LvcahRm8ZM~$i)_1?uv%)u{=ejF`;e|RQ5wGQ%S1l?}9x!Hho zKh)7S|69?%u0g_tDw(kg8c7OCU6d3$E=h_NkPgY@H_ur6=x1#E*)tYM{7-EA;QtfW z6@=AKc*f?um<~^!xdZ!!rCcN*)WX?tB*y>2`6c|9Rz~cH^ms+zp4!Aw>I> zw2R;@X0QayQDEv4^SVHWcRNEX!VcPTaiAS5_$o(Wpz$5!rGtbW3}j zeiX@)z!zacOq52oqSQ^9M%6l_>-1#p#^XGKF!kt~-*3G}{}cbl$&Eb~5sEimHBKgV zSR=-xLr=}!+l@Q@ko7K!t*xofM56j}6@i%y@l@e&pDb|p?_(K+wF;552IF?n8u}>SI60~unAol$Cnvjf2D>)FdFs{ zb-XPBU7HQff-3841V!^}tzXeTL&!&NS|^DE)VqBhfB}$^vS}IrP;g z5_}pMIij=~T6~o3=RL@z_6G*AgRc*HC(7|t#@`=6OuWm0RnlRRDKCVll{Is%%lGCC zBm+LEj4+swM2n14WCkL{x`Hj$`@96g1vm7dIv!?w|s;Ps`jzOs^&H7g=W*)GRF(s86@pKZwIR@*iIUAgDr%nO(PS+GjdOpyVp?59MmN}%yEP)X*coRk(@WfanGB^stD3D@tEW=!$)ZDq|U z`16i)uf}k0yl1i+@^N=9?zRQ=V*b-Ei4lO4!I!jCt4EIwFW_F4w+jZ*hdf=T7u+3s za|jJhPf3_M51&7FXO@m<#zc;8vPN5tZz=&~CO3u0)l0#{%1uL#sZc5VU>``a{sB3&Z zsau(N5O26LoUE$8@cN^_x$TprRrieb?(5~mY<0Y;Dy00#X%j{CCpP{llnq}MzPBf1 zp>xu}l6~5ky$3l7R>vo*9b71|B*W-guWzrDmy_;;2FoQLqasrxR45CfhmPbgq-O~7 z_LC2nZd(o$2{k8>0o6ilXZ4ncMc=o0S&~mH1y{$3&$eTpwR{BwR zN44;WxcFGVCDAkUIOOllL%}I2r%Ok96^C<=L|G;BZNxVl#V-0MGlMr4Y-*S+le#&V zq2NhM01zn;3`lX@LKk8o)Z*_&Kk^(al%5I-ObakPIka;L5=0a8WTkI^dE<2HHaw{w z`*Qv$J_f`r^otp}=$SdRPz&g*UX(7$A2i+pV#F-u%z@cSuR7qs9i&+cLlHX=DhJi_ zw3_d#ZTx=0Q~^@kt^h$gkjd;sh}P>7D`)oczR8@Keyv6$=o~~2{!IRA^GEQkPoreR z(AAUhA98)&M#(H+{}gJR&v@sG5v9Hu~y zg3+`RpWi_5zQJL*4_1RNz==U%PNE&FP(UQN=9I^Jk&>dy zl-9dk)!+IhJ3a2&Jz(kQ?W^nYu4FfPHR@TcC}EaKkMm1KaUnv>3lT= zOX?a`BdA7xiUD2^Tm4#xQcJ))I{0)$__zLeEA+w*BAT+BfGk<4Nw{pKKn-x7F&ir& zg!$EEba%#+AB6dxEe`4e{<3CrH(u&Fe0D-7IuCttBh=*kR`Onx0CqY*Vgip#XM=K8xp; zVYf?(Ef}s7N{3KNZwPaf8mLJGL&gAhTP8Ffat8@`J?Kq_hR$2~?0}CzjOUi7EN%qu z+5xrzc7`ob1CY`|9nKm)+tN9r8!v!e=9~`9JY!;6VH5FFbk1{2{o1EH&r5tG3?5Mo zp@4?UW(^4QklNJl46Zb|Y&4!is2UfCMWq|aJ0l+!i|OHwYOI(clf$I_`mqJ-RWt4i z9wn@I=O*u>rAw`INM~N_M*9=*ZLY7|#=+`){5)?@{P{WxxYxe+W1?S0$IoPGjE?Hh zsk&C{@_DMZwYt?V$qI@HwPQhuug9p4T_Zr5%GxBqguCtDy9#qvkrS;}cbJ`6xD=r8 zf_Cvb_mfvaWNS7Eov{vixverFCTw_bS~^iujW|EZm_lpRS4BI+nyA1x%QibM#Xh1u zFJZi6I&?yZb(LxC4tW=g-L<#)NsmE3DO!Ev5W7dI6d`I$cLEprv_qPe&`c#_(1w6N}?-q<4r!E1uGBfwiSYU{HN2tR@n;^a-nE+J-%F(>APceDwS#bDdDS!# z&nyQW9Egb{w^*`d-qVJ!s1AEWs!UMGvJ%F`q`mB`RGqet-9q}zugACsn(dC^gS(fb zbl(XxzI3jr#a&;D-r96S!R#~SN?D%3ZzzwyqpInwA67<`G~Nm6La9*{uDSQ|xcnB2 z)7Ksq@zO8HJJe{3cOFRb_CVTa#p@1e|3}3wSU=@5dtEkA$H5GK`!RiJ z%yXzp%Gz;MTV6@V<3UQWRGtXo4&AeZULO)IJ=Cl~mcU@JF{a`zONB7;06(!6<{+Y5 z{WXl(>_ea19OG*5n?T)WaD%mXuOEW+k7D6R>kwRdTNhqQv0NlQnBnpxQvO~J@d3zQ4%dE;1@8MY-V)ke;b6;$z43kyya{#rbeK;zAuMu`EHM(~upWq& z75u?UUzJ7JGhqzzY2x7LHt}!_@lA53f-E^qB-2aEJYJ`lq^6gA9h7t(;s{|6XNh^r z&Pb;!yqkxg^UqR1E3pJCnX#~ej5Abo9b=Wzld+gwAOS$`vKrzG?2rM1sxCcQVnUDM z<}k5P;O6F?PPBGoHmzbUBh66_@t=6vS>+_yKVX!AbMKG?=D+}jGy7u(k1pbCebLVb zVsTl%LW&;>wmgrHVnM2(-0R`MuO|nns0xq(RAd1vp2p*C#+ki%V(Ei^lK?={O!if< zW%q>n15$u|-O=aN-S-WM?88V=7L-LWHV*dz~)Ksei^+p4J{+Yej{P zP^HY6g;+`iwzybNCrW>)YS`0QiKo+Za#YRIGI_k^Qnozj1u6{7-I5t?G|x0?K$s+< z1k;BLt#KLP--9KDYUVJpBqn*d$3)(e84(g#DG<^?-6!1R%~3bHOIZD_zx~&G(gcqU?nJe=ve?o51Up(s4OkLup&s+t!=zgkDpATG}!AD z9a5}TY=qt_n5}B7n>vW+&@r^!a0Jn}q8h4!sA-{AQ-LU)KDT?l8Xw`7Z^FUY#+2 zW+pb#RzKj^7zder^qRc|nWn|eOARPsi-Pq9$aKGlR83WgYfH<4J7mJ1(8D6pv*1l) z;1GfmjHh#H`#%1@bqucB%qV}8ZmIsj2Hlc5z4(^{MFOpFL*;a_w0kPkC3eMBtBvM4 zNERl_Isk)3auB{$(nfhy zXTtG0MTe=ZXCFzjDzw@mLB}gUV6+*m9n)>R|}&YhMlw8nei;i+c$3uS|)f95nb@zYv*79VU1 zB=|2HMlx+Wrb?`uru5R7T;BL#>jKhMdzkJK3=##k$8pS4%>*fXieO9vp^})C;GmKm zKF{VT?5X%P>yZK~KJ-cWrvEOhFl+>#Dgc;{3a|nL;475QeK0zr#d+5wbPf{t@dHTQ z@z==`E2yOR2O_r)<}>0U(z~~!(g%`9~Q4(Ub0 z^H5Zl@tpqB_^L4miCnrQfYG8T_$0c08#QRaA(PD;J^rNMO;h|_zdv;nBJd)$?Ha*Q z`C9kvYhO`Exj(C}F*I%Nkz9~8o)5GF3;ZXIcmrif17&-@jEPA+qw(36exw^JQw+}4 ztYo=G6MJPYHS+W#Ih;0b+9)PE09}`)AvB96YcuRkD_z=kGDz2WcyvIU7%3`1Bw@_E zUUT_k38LJtbF@=B<(64|IV+j{pS@QodJOU?e?R)ON9)6OgcS@eb+%y`Z5kr*U8^QQ z+esc*Rs|Yhw&aEFt1NLG3@Wv0+a`!~6XxUGf)+ZUOrSKTkVcrI4j30|lg6}^wbWCYmDq8WC2mR1lWxyDWGiZtrHv#E@XIdh|+|v1+3gfO$^1E1q`z#F!R)tro_qeRRAk z0SHE#sp8PA!RKlFIz-I@$(qy)zH|{vI<4Bfr3JWchAB`^Gd>1BjsBxXWxXZ6%Z=EB zR&Myn2kN`=cVA_U%lMG#V_h?5&9R5N%^wsUqZtI7i%}5XU+KJOjWSNjE)pGJWf&{i z#uWhrWNV=POF--|0oy+Xxcm|jX$%su4H7V99gS6D;SNRJ3TE(#>>X<7pa%q6?0ij#Wd+LJd4HLQ_eR#wG`?j6RBa#d`%YS|9=QfPVz|=1R0~x;w&?cd(usZp(dSI!e^r%4H}>q4xl&zQH3 zTTxri-&1>#*XPlsju+cfeYz)BD?Jmt@QSqtIWUCCOJ;?h2e=>vD{V!a+^KxC zdVcIWeR9)wp`K$78Ln4++(;M@4P5MwYz~vcf-|j&NgKL0sWD}}q^sA^F$=8MkOi^i z%1HcTDSQ2Imd0jWm$qX=N*L5mnH6hZi0AVevU>txqk7EZL`zAXsRQGc!+MSx@S}P_ z{1y7EWzfJ-sct@7m6M2O=pKc+u%vF+6D80j4x}f_5IPgG+2tP&~aU??OM(;it+ zVGT&jo(g%Df<(qaDG-82Cu_4FZE`^@7Idimj$=)mD-M67PlPe?GT%H?q$p<`7$rDh z)n`bo{na_j(Dk&&PpTO=1|-5mP-UB_xX|ld%sW>UKltRZ$m)$J8rb!og{DP`Gg88( zJILk~1%&F96t39hvo9UijhXN??!7oENB5FGi!6=P>z=rP>kNLY5uwKso|%VZK^7sA zG9C3ZzMv8pG~jkinSPO<7A4HwXXw6|!YmSQEtrqf;s;>iHq_kOHJXuyhoHJrrnbz> zPZRg!%N*sF7=bWK**CRQ2u=Dd1Anaw-&y1?J}=3<`6WD9uXl&ASF!jeiT@ za_<`(C`dzx0_+#d&%PFAzU=AEJiIckBI+Ffa(O4_SK(1w)OCDde!nS^n0yfS)|!e9 z-;+sTZfQ6cz~*By8zPay=F{F@YwP)r{71#zHk%JkM-kx3iS2QHJQI1lQJ{5Er3B?R z4*m<(gPlj~GN20zT%|-GWT@$V!%CH4+FK&>7vXAV_bR~Ej4|+Jn~JMkvd*9~Aun)l zbjmAq-|CEPNDPU?K6|1 z`($R~#VUncPkd)9iD<4&+W!MH*|Hs!(1f&Io&Sho<%7JfR}m z>9599ezNeQjHlGV*Y+}A7Rhnd7FsogdE+(6uBVA1dm6Yy6x_bKGEPHs=q$jRubJwQ ztK`JR+aO1I*??3+CEdaBF4_ri+nEwCnzsCim&IW5#*afVCB#n~%mgjsR?@wIA-bFj zA)=6(Uy#e$6qnP^N%{iwO1bTLFa%SHwm>3X94&>cB3BHQJyN&1diMs9qk!YXFH}BX zzAe5`ZKev%tzTG%a4xvZJzr2Jqr}D{^eZ8ZR6%p_Q z1r^g9kp)(O8rlkVP4K2Y@_|^ijG79LikrPAqt5p(6&^~BH6Tq0`e-Wx*DF&yS8i7e zD%M_V+j@#R9vIE^7FrpJ)RoaynU|E|&sj%SU`Z@gqkuR1s1={7QAdTmoXSvn)&mx# zM~7iT0bTcPau@swh+~0Zg8FN(+Jlvr5+|rTR0Z`*KweR6(K%@v2Tkb~9#ofVDIYX) zmJr(>hS3xPjpsmRG1In&rsWe>$8d^=3y)2M>T2ayVwcWA%A}#x(ZOGIZp&V#+5w^I% zT=rAvisuUgMsdt!xzV`!%2Lm3Erc7tvE$AJ(jBS_3xj%CwddnAdMn4O8v2WJ7R_Fc z)s{K!!a3+=N~hGlB1g%d!H=0#H*M|M9b^Z?IzOm*BupQ$G?Eu&^GgdDHM0UtRpt{A z{(2XLr!2$v>z8~4DKVu}nAcVd!#<3?EdjfLPK3<}`pO7t{R`fBc}37+P=ovWSxy-L z+b^`We9g_|H()cv_X~D@XExe?7uL65n0GIdhtOGbLrZO5e)@i$b#1k47sPCL*1>Xl z#P9z7oum}HBT&$qRgZ@3GzQrI5g0cO@2zou)Nf4o6iNJ1yR>_}Yrq$yD;cfsF??Sk z>Mosm+5O|Jl~mvGA5r(Ujfayae~tbzvA*x6?K~MLbd$FLq5Cah z{6ScrEc-;gnnc7cL2k?5RzFh`ekyFPr)c7mc%H%B{;WyzEzYE2tKRn{&rdaLK1cMo zBc%>sB({UvBhq5vG}6MpQJQM*DD#YLx=@m(DW*$x`0bfju3RE#=e{lUw7 zr~1PLGEwDOkhknt~TSJZ9ksM zOX`>*q}|*3oDq4uFW;NFpV{o#seL7Nf0wA>eVf@qD6nKH(sOpyf5yj@9>Kg7c-J^| z-s{$EnvbwT-*t)jdMj~7(sFR;1-Dz2-_ZD6{HAeZbMDQ360HUpX%T)2k@l$s^~(`6 zhjBO;+;NWrGpD0*_K*}Q-U&|q5iOrA#j;LSxr?hQjkuv0_REqz$_og;Z$t0So$>t8 zJHqQ}<#i?LCCMxKWA;$ZQ@%Qej!A*BhuXk+UwioNoC@x0)Nei@u~TKAPECZ8pjtW# zFUe2@jw(Voc1m@`Yg3!tVEBBiIEEc=X>njzqhY>8Z_|8rV+q*{=D2)x8wZ4ELlm;T zh%27t^{2F8H+;~Lr})NUNrH?`eeijXD{&ilja_p8k8L#4#p%4q4Gw^S4CL z-uM?=dpKjTBkGrXPZtkQxliW@?o&@c9)=b#-*))@`1UaOeDUCqxwyAKPUq}jJ^JA9 zkH0EBxHoH9ue0~{-swTf@F>`@4AECs7zK#^um>L^-yf8eeRpM}h`1*GErMF8at?d< zR$=OCJL2Sa>go9LsdVqvtEa8I($o|^+ReG((zZQGxF^?dcTYH=TVMb4=2*lOiK8G` zFt99nFfjCg-5hJ@YHMWb1gdH9T+-lJM=tgw7v?(7{!2tiy+Q12d-9~$sV8LO_?Q!z zy4EI>n&Cf4Ms7SbOlB~ypw?Wns1@+Jq-MsE9WKim?% zQkoa=yW3yxL_!o9bGE-X7`_Hhb9NT*3Vpir>(CWY`QFLG_Nmwq$kcIgX0Yscw|!KT zYfpW;+8P9ozbwl4$)WywBI^fTbTNuO6p< zXMggiOUS8U!|<#9(00~d--1c+qPZr-Xh!rwgn@)swm>+c629y?JWnAPrM*rZ9*JP4 zps)mEsI$c%>_v0jWJi_YA|qy%Qm0Xv1Hk=o@R(pQ1xTCGf(#YWB*n-lx-{_ZFu?ijbOv>x7i8J#bG@q}bdhq61o96GyO8fKzVZv>(A<4C6av}XMR zD)Jn0BmxX2>=)&k-LI`Y=Oh;}BLEQLG8X;>JAvhEk}T;k(;i`A28?zVpl2Km*4p2{ z_^L4To&3{I)yc3I68H3nwL6xVLFSgCvxF#7gd6hwuHX*Nx}SH5fI<8WS4r#90IHXW z+e&5W%-KFW=$Wa;6cq`d8NEN z6h&U-IAT+BMx1sqdYQJFg;59MVP%Tp1BU`XF*^lc6dkHu)sT?Pj$wOM3(kC#` zXE8q(qDy;w)AnaA3^S21alaPO^3_B*MI6V8(vk%gxU^@E@F)!Y2;Gqa<54;8F~uYI zrPR^W#wUOi;lj>5U;>q)m*6&aa{?}56|8`jUF<Jf(MxyJA<_-e-^*Im6IZrz6uYyUg2aGx+&{34Zab`2P)jjFg+4qm^o){ZhvUo#_ltLX$pA z#-w@g2ofad)CHYA@Hu3`(xXVIqF`Yx=BvszT?580dacvqjFPE^ZIS$UUsiX2Oh|%jOF+{ikeXtY(b-vWMMM$f8romyt2LMg zLN>#$^ICEC%>`U{NFcaPT6^G>o7VVvKLKsK%Gt6ud_@W`GJ?;$t?!z=kRyX=-WuXK zD4s-PP+U8k6P7pRjPWRK^5r->`nodjP{h^+H|ID8+q<&tP=wSaH>WvzJk25J54g1M(!+CHZp@IY}dnFvqI>NBb(>B|v2A8Imtcez9#1)>jO%S+E2 zb|nX6p;96mS_0qTc7&oq6fRSIXp&azNJ*3nw8TjAJtypAY?7j>r>P}U=cJ)r?s}qU zJxG3H#Fe8%;;Z8lQG<$4Y|v%eFIuHiQ{t8pajnZsI)Yt;uPi`lQ$cV^$WyT*R~-nT zu^ihClz(Tdj0Md9jEVE2&G9jhpA_3Xu$t+G0blTtH5d(h=T(a^=ZnojXT7amn?mQf ze()EYI2D6qUN72{o4--i*nRuv??cL<&?V^OaldnLR@(LBZudac9?T0gr&-t79WMwu zO}i*m$zlfG6j`hn*{L8pT=m|MmEa1jJ6Fem$|J$l`}Y)qPU$Q0RFLWO3~XwSij>lZ z7EQn89L<`yj3kYcEY7El>~RU!Q7To@qIo(k$Tac~DY}@;Xt^CAia3&hw6)XW#;+zo zT8arhk`Q&qc389cMr_Z);!|7%FvS8Ce-6i?f3c}#JB{siC(-w#lOkuY#@tp@$63(2 z4_k4mfeZBemS(5g+;*Am)N8SX^szm+@xWqGcWO18XFvI&E%>VKKp($Hju$3dJJ}a$ z8#{b}7!ND5c!L@Gco~1b22>V0Xho+U_guNQlrHr=Xi|Pd=Qt)$MuZykYM<+ zU!chFh*&Je;H#@iBLQ(4z5K016JJZ*h{V@{Hkq85KRby&0z=WOFHs8I^c0G+4q}wh z>;5(t8EUK-I=TPFup!#hph5`q56J&EWxKrM_38yp)ij`L7erui;Kr`bF7~#Xwl>WA zPNp`_%$6>uwye)HH`DV+k^(4jnmjPjy8r)PqNnO2`&bZRHXk?Fdo1j;L659QE0v{n z;g>;f!7>{XMS#EszQbO;tPZRO29S!2SUl_={tV$^tPimZ&28cPkUP3>wKSsbf;SnzY8bF7>B2)bskT zor`x};A=_c$Uy<#xcG{Xf?NDtF@^3eYr|8*-!W39E`1a+wzsAqA(Xu$s73ZDC4(aGMy`R7JJQa}MllSlIZ+WFb> zJu>|)sG(FF>w0gc;kCkNZ=Bq)c{O2&zTA|Y_T{H3h3*UFl4UY*x>Qj2 zY#r4Zuj#okTH;j>`_P_BWxQ%E@}gO#V{S`S+a{g-_KUf*}6MtvAxjnQM8qn<7) z@lz$x?<}|j`R6~w-f$rSpBj))kU`&K{D=4cKRK={ZUUi~1vOZ|_;{_v3zhaUpCnf- zn9f6P&Xz#RO*S#o2S^^Mu<7{0Ff^mUS1Uww0(`s*^xk7XR2B+^v1;x6thQ}tbh55yatklOaV*KB5i3LM*;3GuE*L1#fnlsgre_Y}qpnt=Si|gNs4vCJq`wi#Q6CAaBT6<9B$Qkeq_;#b_ z_`IGj=*6@I4X?xnjR?3@NlW|yZf7oG2rLats*JoWrD3!-%RWn&AG5LRXmV(p_Cnq} zFboe$OJ()AnfX<|f#q6S)%j=v(fiHTS*%8)Lx`p12@CLau^FN2Ze@AKfgA%(lt>H8 zkrfiHDc6-34x#zM2t}McL<1gujd}FW<|4!^M(VvyoS&q1VPS8I(ID$AGnJCARQ3I$ zHwe2t7Mk%9Rwg3BN7*Np>22UWL%BMwVYE7GPH$(n+wyHWJI(sOiyiRY=lkU2Rov}*%fJZ`g-#y9)aJa~ z$CVi3cErc!?OJ2I*YZ;<FgJq3Lj4(o{-$Uk#GvyOS1(|X(1=oKWe!_ z3!JOSrSWS@$|ssnQA^TLD`f|>!b`J}O5>juE4i#@~ta4uZ|>?Oaq1jck4ngHOqw zb{|<#hi)N!U;}_;G6}EHs4cQv_Tv%dA}2ZIw8X@fNe+1Qk~*D3Z_px<8K)`jjY+Ka z@~LIq2$yA!-@{GIumIK*{5BrF-P^z8kX=t^aOsR7BQ&voO)s+Q-mMK1%D!&O9A-}) z2A57TdC6f2b8^<{+sJP>B%{64<72rR=%#37yLjUxia`~=qK=oe8J&XN->Z(|BYah= z)FhrV9KP$5>%@N{8huojJabf5#`7ilB^@6+K-+y|i2+)iQm&VsU4@{gkW1Yw-cme+ zHNHXWg`jXt_88PmV*I8meW6i1(nvBIFKr8vK{NV6=WAQ`00-$jyJa)tQGhhY85v|~7#p#9U}Cl)gQ~^DW@pLP@D^j@au^|POVlA6KpZ502f8Wj zb&SyaU|DlFBJHv{OQH8rFU&W&A0fKY!`YeAFXWe(foEj;;uOKnvF`q2P;XJyXJp9M zPB%!Mpjg8sJJ$shm^ASxR(sGB7LCO|anZOeQqQW9Ph?DHs7_me1*8@x&V_Rokk0!b zsPP#2_P`u9guCel4oKqS-sN#%<AfYPQ%n4)|syZ366pi*omIO1WfXVPzo17p-Y`?u}DBUytUcxAtA$j7i$O z^4i^W_IYNzJeH{oS9?goed6lZ+-nkNwaq*L9cid7j4JT3E=kabQE>aptJZc+T0M$8sZS=~Q;T4mOcQM=w@WkiYt@^Y zu;rh>6kl&PUTGbhQ)J#fa%mtsJg8}Z%%Vd1?sZVz{^1SL;wGWS9?RUV*hstX(tBbj zXYThqP8D6bs~^P1cf3EI>@m+EZ(y9nm!9~J%JEO_dWZgCT~~D9Sz;0Dfa7qPNZ#vs z!!$aQ{l*1Btpe!2iho}UcGU)UP6jr0awIa%+wbLzyE?UCWx?dvig&51e@m&%TdZce zaERH`MpyLl)K|!_8aZvvO$Ci5fyaH-C#M;OSib%A=z|X9!o8-pfS;}VSeo)}<5k@0 zmz!6SuXZ2DdT%|ozMj{7osGWzc+k=)uz)w_s+q!kxiPd8OYptTa_XbpsdKCMVLRok z(EYcK$F7_;U6wOPhw@WX51eXC3e=A+ckf^@Ub~Q$kkg;KW9G8KHRSDi7AqYc`rOD<=T&q%b5CbC?4t$)Fft7q6XZldP zo*xv{k(?03aHiNpI@$VlXp!H=!Gl;w#)zH%B_S>x1{pyZOzdZt;kqc?Fa%^L!k6Jl zyL>#LM?8qU$zJF|Gd?F>`I>|to9N0STAQx}9p)|^h!Nq|th|j@7Z3L4X?jcP>ucEZ z^z<;~IJQiFr&OTD1x-XASj1Y5iw78KKWcxj`21GDcD7{G9`L;f#gG$VdO|b;HVs}~ z?1L9B1Q13(0@hRh7~0mixF0eQm8$-E z1qfIKn@e3vqR%p1G(EnzHGM&N#)uGH8*1ALiUkTyLS=(N1CWvgQ?4G#SqqMEbwjI- z9u!izWUKdi$S979sDYi)!cnk_(UgMC*TR%U36x-~Z#I)XD45{+q8^!K!eCoI3-C7W z;r)0OH#9XjMt}5SDs02=``tyixjk*?Jt*7ypMh9O(M-lyP#}f{1qO!t&p^!3!QubL zVZUOq=l|I%J$3}7sGS;zuc}&xJamU#836Nq6;2lTvg;J)qm!#;3?wA#vo&Y61S+3y z<^?-F*}r>?BOc!uFD;;XD=t<^3uYuDM8AK>_MOwy;)4!@G*$u2yXnR5%nlKfrH4oU zrt8IkI0TUpbgHTmQiic)L3WE(NI;Vv@dbp&HRewEqN0b z5U!&d?6yqHAO8v8nnqk zM5j_BGS@@i9jkfo(x`p;R}pj)DV8`N1Kwm)`gOK_a>?ETqa#=u$~pS){WF_y{t@$e+ZIcOs48f z2N42lHV$&nCiq2b`&O{S#@5>QtMSF+m7YF}##$9V{+x@j!yFc#wd7$7V<0smSB_b* z+!Gp+j99I~rx1FoyR$Uj#}4wQo97mOQ%6JB$v; zJ+u4#XU$Mfq_@w+wEKoGQZBt%Gq$;p*CP7XMyBGPKmpmWaRLs(2%5_NRfAa&Ro0*i zG*WgzC7Ch)-Z1`p_eK;Hwwc-wB8k@SD#-tILA~y!ee^7c_7F z%Ax#Bo%{)$l6UTVE-~5$LV+Gn|BnS4G^T$}w7<}xqbOHfkP`n+!u-jULW}4hPYnh} zpbY-Aj`ZJD5}*+DC(}PU{)At(YNDcmv`>I}-dX%}foA&)Zf0-qV(P^9-|YXN(IOnb zp?@9*|LuXLJCH)fKUiJ-vjpi_d7+A=wB4JrY4q#%$Bx>=B8{+4tD0htoWN^6e@dn*%=J% z`R{j%+?4;MVEvoo??LcqA@aY}1HF{Pga2~GKg*K;>Bm1y;{FZSL-`l{-vx621pj%; z@NY0s<}dJXClLR{|DM168x01=1R4&1=du1aIpVKs{9H6^Z4^`0)MT)Jo?+w=zrk0s)t zOXp#0Lr?$$M3D~w^tb-M)BoTW7);ZZ9$-KTy8-eU1x3^_4b4xds?fJV!?_jo zM#QQ7a>KhOj(B$IfMyS6JN?luFQ+9Zo{qnaVvmsgTw^qHlz_Thq`odo{ru^|&0#Jo zw3ouLEjWahuzK`*Yw*|vkpQLCp;C`&)Vj+vhN+P#h2J|TbybKe@yBsIIHA@E>eGaS z=U|>2Bpi4W&xWTxOluMrjUE5dLlkK<1y#MW9Y>YXwvmtjq&fa+G=lc)8u%%4`uT3LNoHPNQP~hL5VIGh16Jjen!b;k|ts z3Cq_FdQMs*Qf@4>L9~qI zY3?V${qG$UYjlF^4FCI?x6+>sc~2vweE6VdS8|YfgwQ3|A9h2_2W1jfI&eVPqt|F# zdov3)_JHJ`U=rf(1cfS(^=0EPb_$?+1g{@naUuH0V^L;oeY zzLTkqGdwlB`e=wu}%cEB%^;r!tzzEy~e+AEVDX#Zn6w1*X&u(C>fkS9Z${=s7 zTCIG4@vN=_>7E&iPs}eQ&w4p$i@ENm=-y(bslrBeK`y`P4QPLKdH|$`b`~>wEIaJO zWF0@7eT@@KQjP~lYobQYV1XsRMr2I&A|FwSJRg-pTM|*uEE3&^n)_vuTN*DO^EN`ZqOZ6IZtk7inXGAqQSCZpeM~vG^&Ea zy6v1h_bTiu9f}O4f&2{_N&n$vrh}yQeG+wy$i#=FX5^d) ze*PY5hXw$^0e}E>w|6q3{|}Ow*c-dr{?)^Ogz!HJ1Nc`B|JMGWotjc7>^2w>L|;JP z;lW-|X^Um?BXCe6piL5G&w;iuQ#g_!?m(05u6%sSQE4ixWd;E{4kg;1T{z>jHoB6c z@pn5J+DSZHNT6q%_k{kCzO}sF9|LQ?_Oh7ITTqMv^0eu8`MEwoB!Pa8y7#|>R#@mK z(SdwKD(SoDXUxQa#>!(Ipg?=L&^A|HYeF|^(AAb_StTstXGVMLA-T^m{u~?Zk*=3m ziom*;Y*Wq}l7U_+j7k3XMlr1kWf=8Zv=u#dQ&qzjrJj|af0QpMjY6-!+k1Tv-jit9 z26xDm6go+Ks7#rRWz4t+;mMAM37rNaVYS8Er2

1i*(ws}5pi3L+v6R#;^w2LRQnBg`;Y`(sX+gvGePqZyVYx4KJ7weq!0G~a@#WQK~3>#ey$ z!YXs40ty$t8tDonT6^{W!yU#9svpGbtK;A8tzjOeZ?VWkYTRp2Ui|dI0rF6$NQrr# z125(~VxyclOo0lhl0tS?%{%j_hszH}lveV5>~k@;{g%%3J5Rwk6roO^j87&=kvu7B z(ml~tjCUpnTHSem`xkjIuQD1tN^`$`I8k@&$V0rPwPZMK%3kIDig zlCZ@2z?$bK1|D5?QPFX>{_T)s-vVW*{?}g#`R@dRinh=oBnJSHRRRP+{L71f5$L~C z=Tg_(8CksL=SOYpW_E?-&*iQ}OCZAJ{?;H#AXS3`wo{{On|v37LPT%1WvSvOGLVeZZuo+R;$1eWJ#0*j=f_$S--#Ga0{gjh#|+exHvmK1CYLx2DAA`{Vj<&c@Z3 zje7r|KRc6gCDn^E)062Stcmn-(`ZT~? z$-h#5Wj9~CIGI>@MD(EAP@AGfFYdG^_E?~Ns5qBPS2w4%#AutAGaGAE9iNjWcW&T{K9FV}Zta`RVYdUODR z>0zcCl)>M?cBJ|pQI9OqIK@kDOd`Rkb#vrUs+16|*jc+cuUj7)6$;B3=U12Gsw}n4X%zZs+Jx1{#weIgVW&uzRx*Ok!?wzZAETnVC6;%ZE}Em?#U| zsmwCK>L|gQNHcv@A#L2k(9BBXZ=%f{YGwVay4j7%|MxuFs!T)+O7o(UgQIn?50fp5 z1?7oSA$jS2nyripBvF4C!6W2wKrhu9HZd88Ve!eWCPHK3pvZzh$1*#M%i%!c!N|x* zr}W5_q%oOxf6m^Wa5z85q&?d7>gS!LC7m>EoE}TVV8BOGYewGKSjVr+p(=O#GN&7p zb$41#PeM&gxBdpYfO=mgqBse8^LdYq?!#D|*yBy{YyK3A8ic@CJ6BU7=fiJvYYxjfq@|a7ZngWhuNnDL z+Krxf@0A_J+AGpS#*eH z@!9^%0jwGPu;dy=yH>&B~v^zAkb}dMtu@)FILYHo*?^ryFv&Be6h(7v(bbAb~`JmB6R|kxl^f zP=QS}VaNko0aD1CPrnsd605}vGU3`EFm*{WT@JT|!2w(&>)fj7eaFgp9L6&m&FbWi zs>dctnRUA)FB+M0z;_Nzd4APL7xQa08el#$TbJoCWgV!wk%8w-ciiYE*z|JU@mad2 z>i>}XGrBQuW*YjMn9sj*iRLYeO({^#c*UpKz-Au8uIY@Art?uIgca*OopO`@c=cdS zq3bEbi;1GBtuH1C^)X35XOA9A0#BQ!`l4C=UuN;@xAj-Mp%%lm%I_uws`)WMZRx zLg7s?1DQ>u!5Zmkl0*c62ZFhrpA#LcGL2?iE-^&)OTk{j9Iqypb$7~by{IY{h~Rxz zZVm%EZPxKcub@1?UEpL+o`n4uO|rap<4}|uQ2Y{@rNV`S9&gz4kzSzE@peemhYSZl zwJZ!>wQKt$(D+S~Lgu=w`BvA_c97W{h8Lp>L?;#M)KCaPmmR7-<%`CYanV>;x6h0+ zI{W5C5UyFe=0ey{41i8CdRlp62Z#zL!$QY2Q`gwJ!P#LP8Exn+2_@B_)&19CA${=3tHuXhn>1((aPM*MK61rwq9d(m7_{oVqq(Dr>{Mkii}FLu&!Uyo*Txc?Sa%EExd1~Z zA#y1Ibb`X_|4THJaB~bgOoLRlwqrG#5e8i?J#kBFS4;Ek1DnB;D$EXxX{~2aS}ZP_ zXPMOp?exPh(Eo(S)5b-jHSI`#AbYfUyF{}7Cw&Umdr=ydQORqC8#+b zDJ`T;_H*%%duIpcqCe%6A-1{4F3o~P7=6|czf4QPTj-h#V24->!d<0 z^j9p9UwX9)LRG4XW9GCN9?P{dXNzz=8gdt@a#Z$WWUSb*S{@+2xn9I53hN?U;WKvW z2V4~EVpSqXy*Id8J^Hp=T7Z4t72WQ(e1;f%!q-`j9zD^;RB(hzlp%7d%f z7WbQCDuBS{4gxocF(CEtqLkak22EDBNVWZsR(5Kh;j8snq>s(@PdeRDq1wmFmb|Xf(Ax- za!y{he&1FTw8GApqN5QB1uGW>SpGhybBwFd!8+M6QiV4xJQN_bG6c{2Ou|4A2D!#; zKF2ORp1MRr7&lEEVI<}n$Wu`mn3be#XZQ2V4tWC9rxk0)=`CT_4XWDWX!d3H6NKmo zp~N2dblm=<8$t*-JeC6GW%kM-q4WE9wz7bU7kpy3cNe1OloMmrSFRFOtK84~@ zEM6p%D(fXL0;$p{tQfcZ{Ant)oAgx~xB0p;bF~S86=0HbQ4&|)Y|m0u1}!vyt&zLK zWVIKoxOJW#BN&+w-@Q}4IAIrllc|AJtp0K50TK^d<+W_u_e@*Jpv8jemaRoZ={TU%!VLKS5_YX#o7V*o z=Ef>6b?^pfHdi+yEIU)w(4MzjYi&Ap~$fUyt!RMW{|S?E}Jln|;d# zf!MKGUr-kD;9Q2Q05EA|#Vc_u6V1@k_{d7W!8gXh!^Oe7SY18&J}g$2!QRU^L>Irf z$r&Z}>ac-e@qQE3Ocw4S3}6k%>CqXCZzId?Nl=XMFD4eKGm(ObN#HiYkF+>zcc4I! zG8T8qtzim9iSmX@J$89O2>Lf@Yo6+aD#5H%nU^I5Iv&b8!hN5t#(Tli6rO?KfOMV%OS}@^>S1#E-RyTK2$*gO zHdZP=3(hhJ#Ea_pPGfsP;nobUbJe5_E@Ot&8D;uov6lRMR z$0Cw>3n~BI`Xbn8wdf- zw`^Z2F5lZe4(2-lii!;+(N(#=7F<&Jp|mK{jZ=%Fdnhsdr2?*s%x$o|yJ)iYf~CS` zCz!_JceZ=TgagxXs)sMZ!GF7OiDsZZj!l7=k6%r|;{Z^r_9SYA%+gw1<21+#jxf~y4?_6(BZX`LLkC<~z?t_& z^`9nWbU~A^%{FI14n=YCcQz^x3QaA?#x4aowmcIkz%nEw*!=2rMG~YmKu{b99cI4H zlzpSczG$y-l3qF5Y5=ZBw*xxoQB=*Fa|Lje+$YwQn6x=@J+dXufERH(YE3$PD+p_0 z38&RnW5CYV5G$Ns>vQmL^z;=&+COwe1dw5X>lcN$apG`cd2@(Z#W_KPpnL{*)M>Oc zMIcu)Dqy2IzWcN%bYDO0CwN~UV{JE|)$WR2ly(cQWqJ;H3aI?ql~mR3^p2NRlsAX{CxhtZ|U^!{Xlu!llIsRti%vmjpPf($6F7q`7byFayVcDI?i8Ks(bB2}D=mu#gu=^Zg0Uz9NAzd=H5r{-fjPt}p{vuzz3442xXu0I7RTM5y- zW&y{C-Iv4qGh#V~v%Q?CPWjIjW;tXY2Dp%MUW-%+ORoWY*=^X)J?DR^ccg_OrHIYx zjP4r8-w@T@btN`LR*8MyLC2jhxNcKkm5hyoZA|B;{q0Zg^U`kV=ReDCd`3Iq)ps1# z53o}Ivfo52%b#hZtAeY#3bTl_aI)I9uyo?Gw6P`rmfrgueUmm#Sv4T@)t(&!mqLHw zIn#HeS>MRR+DX_*jJlH@TRkY>?6-Lu)=K8yVy`RDu;w+S2hT`SI@V0?fgEm<6hm~z zx|Vz<`9@*EC+_0JpXQfH(sk@nM07Q^>qWAX;R83izndEALpuh>@6f?|^DeI)rPYpc z9-?XSBl}G6{{nj)r~$^wRDB8!fMe*6d@qP)vG+@XQSHn*3%x8gthX|q2zTr8Iw4vpYUzE!^8B`5ICW;1 z3}#DNz?hO+fBeuKhJ&-IEY7V|9pP)M*wbfRh8(5~5ZVV~BLCCt{dH|tr8eqk3yR_p zS>#M-9Q4C6EyFrrW0&d_MR|=>H)kM z(*8FGA??o;4k2N7N5yKQI%V3&_b|0>5f*6AFqj@n_#%=sf~1!=8;}GAA@BYDZ0EY1 zMG%VmF|aF1$AIA^M5%2in+;Y?Zx4waruh_qSP--IQ7KS5eBYRhir7XeIXN>WGZop< z-Zi<$?J?ifoV?sx-Olf4!t1TRH}XzqE5(8YW~fe}ha-q84HWOoINZ zF-h85C>iYqUffx?xy(hh-_Y2kSBq4s#?!#rm(Yb3ZtQ_69UOwK%K{asOH2lIq7_x$ zhc*C9TugydCiWqs#-yVVLjI7d!3%@VOdsFX-tvv}NSV`g^oNf;1 zQ#QCJI(7C&1eTTy9OqKTA70PmHK$x^nH39Hse4L0_2&7?9otn@olNCI*LV9KG0Z4F z1~Os>MHM=}DSOfKOz)dIqk?%R1r;;0rVimx<-*R_LQ`2wV-|JXX=%$Z6Tb@Nnn6wd zfqTrVBO!xZh6iCv7{`_*n?ea3GeS-{dd{;S`NIic{FZUjB%O3jsTH)IzQv-``;kb0M}*i|~h@+e~eL3nyEurnQLLRx^bD6aQJMV5w! zIFoF*tO%f-?-L(UyMn9ZF?3c#Ng)_fK6JbW`!M7tn8!7#=j{&>GFPoHMYXBJMY&$7 znj%sy7=EHSFlg^h$0t_=@6I%zYuX2?#*-oVd*s*}N=!CeTu#xt zt-nxAB*tQ(b{JAj(ayQTyJf9ub|zqNn2_BSJ5U=0s%LKqr1i>J!6pGKKVQDg}ZEeUFJql~#DqHU}m{?PDv zqJ^|g)yECqtcm>c|7Yau=jRgVOCD$BNgtDtlgs9%`}Gmgf)Qf`c9q;_<(n{x_s<{q zstLwYDiljge$x!4Z7Jd=iB@!i7)e5SddPWR}v`_TQZ|FDa_wB5;j)T=98Uv#f zxuA{sUpbx+$5S7SG(k_|qpeYTB_>v+Omv^H~%Ku~eH5_FC=7 zQUpiVHbsC9m79TP$Kf4xu+L8fb>ukOW>xmP^?0DbIx%?sHd9^s^ZNji#3Rmur2Vo_ahAS8&r8;7IT@3 zuyaN(gnr+GYq&;{l8sfETehtYDut_Q7f>2!*x<40L4HIQ32v-~Fs{&6V@-x-yj^F7 z%Iz=s_7nc6SDqVsQ~Ji=Nk5|-w{Bfn4|@CZQyX{q-tzhVbe*dYdIqVcuLWT7+TZ5e zU>|RL?vp=@8-eQb!s;>EVobtrzE_Numq1slf;)u6Q_Bn48AQ3M(TlMEV1SOSFY26sPOHso+F0F@Tz{pXw zXm>sf39e<1mD=$QGXg)p`*H<4)dNrhuY=o_alGARp(|e=kp{Iq!33@@@Ow*x&f3k2 z6)P%9DJEfxm=_zyqbqnfSrcRr3IkfcHEP-)C5pnjbZgb@0ACeE3Q*P&Nu$cSUzKD? z{A6pROPrRLqgE@;$~>o34M=!{9~G$tbb=#{rw98?nJO!mU0ne+#UC>Jro%fLx>(43 zGHT9mgVc4Ed&@B)j3pa!-ILCxUhydaffWQ4%G^eylAFvjJU3XRA5j%}o-3S2a9vs8 zlC{649&O*Mt7?V!SQupUql|LXRgoxw%-Qn5>-Ls`S`oC=7I9wCfJEKXFWi%#xCc>) z`p5fD6-y|g15RaH*t5d0#)B(wZce6P3J~kOJz;M^s4_%TED6-3 z(Jd4Kc`c>glG?8fZ|YK=ybdd8YcXXqo3a4l!#1adEJ@{Gt`5c6a&WVZ2kmNfsZJ@F z&HXt=mgQh6fZCM~=y+F7Qv3M@r2yaGDxub3E_QKK0A@>yBS5R9XT{sIKa$?IR#w>^ z*{Zfz7_yo`# zsq(|HZTFIep(9Vwq})Jj&B_PSARa{}18~mX6pZ9p`TiKZ>8VaZ4PXbr8RWkmI48%2 zkd+bjAp*?Oek#Y7jM@32rI0NXS0B7H@uneo7Iyd1sx!wzR>vUYmUwvZ2N~$yt4rwF zH_9*x6Rm%&`$P2mLKmHc{ee|-1}%Q=puF*d8|@Gn{@n>}l4oh%TNKuD?lt&9267_i zT5W@aLSjyjCv0hhG#E~%Aqbr)Izd6u(}Vm*>#Z#iAfNcj^jm}sRlijtrz-yjI4E5T zNmjurLrxx*?5#l4^+J@?R*;qrO4gapf^CbNssu^&y(zLubRmwozf^25qg0yJU#IGT;3l+z7K{K9@-qqe!?M?Ei+817F*>lbxq);wR+NoNCQ5a0RSWrA;l@? zhR%#jZZJU;-jQfLkKrX^{$qRkyPh#c-FAcpNtMBhzG3C($*-Svk2iSfZeHK7Yaz_8 zAS*hqQVnUd0fLaTbx*+;1J_pUDF8Pq%Xc~>)gyzb5qcHuRAB& z5r?DB4dr^!5m(pbJLwM`*PWVsoM){7h;84{MgX=?$3&-W{vUOM?6lD=5Rg_dYb5DU z(zn1rz%w(BIe%m2)ZS>>GPQ@EK+(>_+96Dl;tblXxGWNAdDkTP0O~;u#U#(22B<&g z(Z`*HDWAMIl$d)t&5Unq?-vw(h0U4^-`!MpS~1Sbx<6(atIw0TfMW8epHNKA zMo=O!SoHkXIPR~`Bw?nmgIQh?_5V;=XZ}82GAaYYzY9fHW0%T-AzCx2OW@Og;0H(2 z3cpZSk_UI_vi*_%iPk`q&2PX5`gU|N3k`-uQ2#MUpA7)aBCTooVGEES0_fx(8z!qg zlFQY1CTTz%Mr~~6M&9Y?0;vp5i=#U!t!cDj!ZinzH)#s}qgh8a?3)HVx7-_M zgrEwvAZ8%?`>y4%Hq6~0`GANV*RZv)7q<=e=ua#2N6UJTxAD6zEAQ{D2m8vaPRp=q z&+Ha|ztjRmx@@cWESVxLSDm0F)PEhGK2e3 zhwq+{5$go17=EBEr$L3YYyyNDCtPpJRtl~o`hPXY6l|O*=>!j@#*1F#`+e!Kds?|j zFej?!5$+g1M=wpmzukUUDM2G~49*N(E`>D3b_%HgBvCGHmOGbJMIyan4Y2r6{7d<- zzT8}C97@5dY^5{>`W8npO(Dg>%`v1TO`*M-Q~i0)ZDc6L5zcL}X-5`brpb@Mn8+D{ zO?pEJJ7niY!9{y-ZNehK+p)Q>4vw?dDdA&mmq*}XGi{jc$vw&?M-NR|j>9V(1kY;P zube={Jcm&3Y&Z7=(f~-4?}HYR@h$9y$a@|_kfJC14J2^&7$c!B^XZnWS=dI{@VGd} z!v;Sqc^rgdgBsB61?Dj)FEidHFMhvpE0&fg!wd~lP&3< zuhN%6{U?5M`>C3qAV>YPWb&)lzb_cvAL_@=QjxMgeL?L0jYoA+(4Tsjv!0NolU1dp zF&pH>llH0IhJ)Jr4dvw0Y^t;SAmUoUd&fdlKbv20T!&!q!JKKTp(i?aBmG7mLO-+w zGB(>#1IlD=@YlP%^bm8%_2A#yKhO=d3|a|RMbNuwFX@l-^MO8m9 z?fFLeKrdbV1Bd#EESlnv`?)SZcL-o=dP>UJ8p=(PngfWxkgiAD#;}bGlMCNIUr(#& zobyIag5}#^;4iv<#a-?#1oH87c?M8u=4*wG@#S{k-8Fk>s3svfmRD5klT?VeZX(ChBDmpk}^A*5l;lrG#*&wFi$G~F<&#=-2Tqd*MIaWi}p_@R^hnZU4#yzmO7 zZk$x3E~#t~0YQ_8)px>S3Xo>x%6~7VP;nHE7@9{>fF&x_--Sd1aDP4t_rKyawIEXa z?x9%oL0==L8|N<32P;>7! zI8W($)FT=Ld`7Szkw6(3i&B%j2#y#l>EI)ubiC_o@KmD1TgvyUZbM zMUlfMc?HN-`E*yNWr-Z{d3%*}V@mu^p5uU-eo<@M0f#G4p%x(t-ZwWwZ=cSxK?gy6QOIL$> zXCGw@qATvjY$nT5F)=ot6wj?~4h)p3Hza!V0*B}C)72EnKBewM0Ksa#ID{(LTFGa( z*A+PzI~k1YNg|r?XuOu!{M|)x$kNZSh;}un0S^;(9IW`sYU2-Wb5`_1<5M_#W`FN& zD+|fGPQ(};fx<20u!dw8*|_u|%XowWPB|Sgwm1{BHsF9*<;;SIpF_35$?e6R_5rA- zSH;_UXzfE9B5y+gm(JJcR12Coi|XhEFLw7SP8+w(;hmbf`jJwyT4J2069qkp>9HSY zhBDFv0`8xP3gkL@Bh_@42G=Q_n>5r*U<5 z%V-@gUsDHy4RVzQjFT6*sKllt&nM8vRtff7$>svA>(PH~r~lng=Rc|Z3isD(mk0&` zfbgGwIx~BF7gHyuf1IdAsnYfb1PCKH)K~5TIMk%8iVK%o^_5igPYSjvX7*hOF~*gh zAS80#U;FNr7SyF}0fuIN58;43zC4a#aVl9dQ3&tC@IX`%G0gSwdplQ7x6nYw^%0E8 zL1DKtus$(<-x*7D#W3pApd&u7P;-gR^*b3r9W;n4)am)*( zKdYe{|vxq455ZVgUdBOGG|jwd@R6Dz-LRrM@A6>w=gVxMWYwbkzWb^0pgCq|HM z?2sQajgD%V$on4jBx2nsDV>_ZYkcyn04VGrV>UlZ1@KzknYTk`AN=UWAKqZtl$|qo zfg3}I@?y!OB~j~_O<$~LCIf?r%?JmPBMK<3s!fyYJdW{LX8mku#wpk4y10{4y-%_s zji1tTZJPYFXnQwsJv439qL1>XCKg&8u5kFkzsaM2Ls{FLi4y5jqBvC-%UD^nniKb6 zJWUFSZuUhNr&oXc7Ww(tmze+6?Einj4&%SE^Kb0@Ut)*(-`M##cK%P;xuSZl`S`15 zVBi4%p9kF3&cyDoYB~P{p2?IcyZ;caCtR9q$d)5FYHGP+w)2WV6#h|!M3hGrr$!6LNn0CYw zGyEuJQ80^%ei1l~!&p2x^~0lGDRP{~%P`PFvR(5th%gq-&v3~CwGgbzI;TYw=~=Pb z%ZSqWJ91_R4~M=DgE6X5ZMsg3RtR&GGw8yK)!bDz(ZjN6Rd6?9A%E{{@@9Svqvo&0muuks zD`O>z!aWsKHm?PV#j`S-Xyv1~ncjjM;Z-|md4U*%rJR?-+$@7AQ)70N_Krt5x~y5* z?i_FJlT_HS_3hUneB>XCbhau2SJQa++%Kh9jGdk51oJEKLI_u;9K|8u`hAi|JimJ- z@9LtKfLpIn@$_l?*=5_a>HM!}OP^FkP|LQe4fC_7oy-0ovNn3tTnU$V_KTkXtwMR= z$1r^UMGDMcq#*s*=;nU|l`C$IP2{@&fQs#+s=aE*2LQ?e#u5XBLw4s^5Py=3vZ$Q% zTJ&A-r2Y2*K=em-RUs_9TEMs>4>`qtlpx_Em$<(l|Mazj39c$=wcs7jRjcUB#LW(x ztr`#qXxzpqbIO#~l#>c)DRT^MWowZe9VoNGZx?4UBwA>@59-~+tvMYF_Iy1AtQVTQ6TZ1=S5{o#(gVftE%v$Alf8Bt`?;L~tvP5%$XE5qkDCkD^-aeb zN}GX%cdTkhU-<5?qTAs__R{i?=BP26e{ERan75ULaWRa_M-quQwmL6vKhD_PB@qj$ z@y{O;!s2Z$Mx{5EZ=H=hXAs4Y&2#|X+w>c-n(CFhSBS1wlaLY;VbIGWNMnOT(4%|c z&yU2ezmqO-yX@4*$y*!cXDi6QFm-)vVIOz#lqCql>T4qe}y!cnzHsAY#80}tNsKo_FstHvO!#;m#bH4C9p_;gyWk; zq-`h^3C>8mVXqu}0}Qw72B0om2I{H>pfBgYGHq~M;7;{drIpf1v+Oq6hN7zZR!N>Z zckoG$Ce-DO!r~$lQpjm*-RjQ?lU{l9C0Fn5p1jeQc}P&@fF?bJw6-%OZpM$p@pXo= zsfZW_1@cB=H}8px=VPTr-|J$IM01lsa$(yd>VwK5r~`XPdfClVy_(y#A{k|QQg$&` zw{hUg_!YiF$w~L>a#4zmz5DUllaI{94<7MD5o!q7%^?_g4NdQaAqzF4` zd<)Qyv|?_Y=70kr>C{*Rt+JB{p8;1&852*zIK@7Ah9l8J*dgc$E|cg_(wbyQdhX^oVCu-w(zL&3AmhPvGHkZfOI-MOzoBO#w{^^kNI}4Yi6^rGz4p zt1NL&Yoer1v5hC|fLF*&;kVIdp>%pH3p+?d;fWZP76CUS_mLdZotKaMEf?a_R3G~( zAh-I5$|5#KUPnngn2~p`DMGVL=+bf`QWiIj5u9+6LMV#z?u3PKOx!7G-(1t*gQWRX zPT@ptWpe1w76d7?N<>yHU>q?!P(nP?eHy*7n>n59cgVy|NmM}1=+Z(`H$70}QQ~s2 z4l+;^SL&m5r0`-w^_^N(5Y7@9;@SZd=S7{BW2Dco4NfJe>uwqxb;O`V;dMtZG9HMW zvhdxAa7yKJERj&_@yCQE6l0!Sov8XIPzz2Fz_V#!UTmeL_V_wl?C0u==2-+ElM@|M zLH3Tka2g58!aPk&%tu%>H+#7`@FFBbv-J_Td${cn6dT0z32F=B~OqQDd%{lAe%tlVaxo z#s1q4VYwE?s?wDj3kj4N$)ZOaEV-_82*FfMTbKw3VZ^DgJJa!`&ATy!!kpTXPcD}s zgG)1gONO<+h(~yY*1k*A-fMKkass>G5;DvY>a*yYB_mXF;vtpw_EIam-eW6AwfWq{ zcX~G|LPerE*3_hYvM?rsRQc;-D(rdS4P)1WY{Ig>LG1&*nSp<3Hfb;6)v9mYC6o7n zSaeVj1*Z@YJJJ`%i`~WzR7I$iy{&{C$Z3NoP_xaUaXrJ=HF*if%rd0WW~J7G3{9&Q z^MoryQ?Sd2t?{k(&d$KMJ2 z!5swN*fs3LX6hzT%-pNU!@Gd+!)pnW8%R~eWVAV?A0`nN4or|2G2)H>KKD~TJ5aMN zCn414$Yvg~ja!uDHF>hx${1s_4bDc+(awwp%42Buh1q{bHXgXoT{lcBqpI&GB9l6p-=m`))|#3qxjo?jm*@rRZ~mV|2tcv~RXCyu0PxT9 z|04Unozv%ecE z7f}rtV|xcr8&fkE0C7`0Qzt_gQxgIsPXY@U7YAo9dU^*NLpvAOzcY`G?QPwr$N%0C zIGm-5hRc8M|JO8RkL)SUExQdilI0s3}y2|Yu3CMEw8i806dri>7_eL~Of`0((P z*+XD7AuO?jUISkY0t0M7E?7YcfG6+)1P?yZuUYcd*WQsYf_nrD`=vO{f+{wcS;@gL z3zZ83HmI?p=a^YVTXzYKQx8803){;zV7&@YJ{$!Ov*zW!zJsO6-V3N*;6V(We9~t| zkB5n)ymb0nzs}FA`=z4^c7J+opknYf3(6R4|L*tad)C^o;GeIH^Nvbs`Zff67R8UD86K9@BM!bYaDmNsQunx0 zV@QmUwb@L1|ApWgrdg@0>`dl<}!5Z_N1RvFP|`G_mV{{>z4mJ16&c|R>n!seg3$nEV$IjCv6l^xE4Wqo z(lo)OxR?B;yc1|%rZZ_HxFez<*&{ogobf6q#{&(HDIcjs6QSBB8c1FJnuL}K2P^u! z`Q{)|sPc6bNFBMgwt@&OTvv_($3V=(=2~}vYK21_HT=F z!3_EqT}wCdrjR&N8$QxxRWm&l0BeELa3&A(_guBKVZ9_&npl#90=aZ^->mI#Ye-Yc}dl=DMIFWRH0=|H}G zi4aBB+LKu)J1+XjMzqILWT?x5yfsO!hkgsK(SAEPrP4Vq<^@_D zuiu3g;XhQ&yeVclO(`Isj8`k1%YOj2`}4yI?Jt^oXEg%kgMJhOk|I9OGe@~CST?bX!v4l9SQ`s%oYQP{ zwzNNe5yxNzt6JhpZwCq2{jmnAGC5tUi^t!lbRfD4L+1hUNPtcJLy;A}|5*|R&3=+; zT%m*WXO3+OiSD@|zl)G`!AfDoW(~`t(5?%&EV#>R)UC8ou!XIvWr;3ON86`C8whkh z+TCz(>cX&c>k_T^AxFlLS>(h)z(L6TWeo>Qc0|dtHH%xeqq~=>F*<)g17%X%Bygjjya;aUC+R1%5*+f4vzBdAujr14e^G1e<4ipHZ6^j z`vRL=ZSA}jFdi>7qwm$`gBDg+FaLsR;+9nicUcx~3^fkUg3q!MFxn@Old(1{`j51UQhVCPslqh8W zn+b^y94{rIYE2i@~s0d zd4h>Q{2>KW5hdF073}<}2pPAuW~xDF9N4LlVso!Qo&xVAnz)-Cd0SYv{&7nmQ{Dhf zy#j{nS=0c`;FC=e!U?ttj)+*)(1F>4%DR?P;4j~vh|Usrk_XShlo_U8vD^_G5z>kT zwnJ1|h6Sv~2BxOiA$RT={_rK8y@o^XRi4nFANwDl)2w#C)SY8H-%It))oa252lwv! z_&9`9B9k4uZDcyS+q)M-30I>T>au#Zc9_`zTyk-o@&Ofit!b?DZi2^%G|jA}&Iq07 zi4b|yVte1h1QkMre`DANk|T$_aK$qLI^6z}m-I2Wy5!}}V6~{KZTdur7{I{;$;C5q zAL*>4ELc#xtoK$gyw<-+;3)4e6FLKc0srVf(}3<21x5Cz6~V=?0_05w7r&x--i`e8 z6WD_P&wjwz-_LJ*cekv#n1zk4e{ir+yMgaiqB2{**$*c)H1y;a@+9*+T&&lk<%-&$9D^N$ai6D^W0%$kaq(a>%B7_x3k!?u*RP+QojEgg;`C>h&UeO3 zFm49YdHwV9t)o3t_(wtl5fz2ccW2>Jyzs&uQa&CQmQYKzOMjOy#A|ogmWU|U7mNBw zOUuqP&aOob6P+fXdyZdz1pIwY&+qwpLj!}CQF-mMXb}++AR3+7Afx_gAeWv!dj_Mp zM&wQ&Jd!+chlIOnGLZPs!$WdlWfrzJmdNakjg6_OsE!Xd7W6F1Wlewl_>mZ=dFRhg zQY`i7DvOASOuC)(V%ix0URinicJmeACT6UYr?|9ut<~?6r0ACzp|uEqDK}s zIZkoz8uX4-_(`HOozK+#(k?O>WfzV{<{yMLJVPzxYh$Ybvjldh8bvpkJ$A zVU|2D8hqb(MXYb8C3tD=SAT!{qZ<96pBlX}_+5^|3d1;Dj`w+8j-Dji_}?ra%akEK zV-o&zbp$SJHMn+^Y%g}F+T3mmdv_ZhoeecCingoUcVc3~tkUiLtgKeAC1|yr^~P;{ zfjLk*AK)$xmu8CWR}qtvYS-GE)2)Kn>eZ{?B+*xH+$n02kxt-&&t~K)Wk?cSx#I4y zG11NRG8OgoXm5rOLu{}>vvzlHFV-;mptZZ(YP`m7w`r&THe31Kd-uLAj6D!!Tv=M$ za-jYA=lJCWm6Z7U-kNKbOBI>J^wh?sVX+*o2$WtLDt;)DwK7*VDajnCK zw0BBsYP09ekHDGfZOevJ5lXw|zT}pNIZ@GZaT=8tn%uL~(+`*`%gfURIXE~fBvhmA zZAhi)8CO|LL!z1`rKB>YV&`XOY}Q7~D=jCtjSP+Zvg9;0i2p2_c)DNnu5Z1xzCr<@JvPeKF?d(Qo{~bgEqM7`fY}!3fx}Q{+M-huIKW&fcYTA@u4k{9XLBgNAdL{b8d8P@|2FahTdZ@iK0cOYq@j5c7G-H3K5Xpn4wX0TO5lYq_BnGKmr?DP(Up;O9;sSb6A3A)ITLX;rpV~Ia^qeR{N1r!W#;rC&5U4{ z6Fr>25&9)H#XRiD?5C)WvFcvMi15KY)kadpEh4sOw+<)j-j&tbF3FGNYu4_=3y?V_ zm&gqe5$5AHY{~^ROx~WJ9np_lzJH%yDQqZEE6?%ran_K2@giBYAk=P(i;u4!7PIWB z=>S7P2H{<%$EGGG*7vwX%||QNCL0sCT07&PuC1;fivDwgFv%$9*T-uYqM~T& zIgEQeO14MJO_6%2ML(awf3(;SR~bEiT)JA$$LX+6OhUqKOQXz08aesJvC-=xnn_<) zHykW&S7f@7_Z>p6W&=Urz&mUfV^J;(g_e``WutkjkIRvri9>rUgB=|mNR7D9a8h|4 zw>|jIB@_ALyQ+nohKIQhx|IJ$6Ldk{^@03RIJnIhhn4pAJU#vA&CN~NV^e*pk5;hC zemL|!>k#=5bE2aWk<6Jw{&>vuZF4i*WyZZ(9{1Z1Cf!Ky-W4#G;k<}3#53EliHLA| z&uXd8`|}+{BbsHe@2yoB z_fj!32Il^(Pj^@!<0^>rN7(9?oAf`yzWv@};tLl^ib9n&I}r(yZ)ifoM1kg0^y*tr z(&pAiD|ZRLj*h-t?#tfovHB2&nG+vRLevsB6B0l!Iwh<-n^TQ3Y^JVf$OW?wt~TeFxk~vjb0ZGX++ec?T8$}0GBh-# zB_wj?Qm=>aF7=AWA(=F5`XJOtqzykOZigFRR#ryiuMpK4=8|0W%>DYy97W_t!?_1H zoxfHw#PeS2z|e=H%gf8w-v-;;&5m}L;Lp^%o^lg~FhSROWNZ7f=^!qWhs%*)LP)*3A+r}1J*wA|9vdHr<3UJD+T}gzoPwK| zmzTwMcE|c}N=mxxk7)ElpTW>?Q7XVKY;0j_lIOc~>z0oDr}U3}T!+WU@7}(ZO)~4x z3FjPKUR;cVbLD-BpTD8CRqU+&%aoT644=9s`jSmpLV|^<>B{V^VpNmlf^^Ii-;q#K z^7w~;Yco$&;z&vga`F$lrw|fc52w8C*G7Cx8K7Vq45L+GHy^Q`YYYEg$lB_q^C+I% zj@xSbs`iWmWOH*py~nnTzdi=G`S|)0u^8wkL91o_OUW4EZ5mC_527VqLxV* zgL4_&t&#!t0W6DZwKZjIRUm)$-KNGzRMK%`Eb)b*98gHDudhRu_Tj?^XX4J60s`-o zlgZ;_TUOd`|Jknhf?@9=POi_-{~&F(m~W3(42-2yNq;UP0?`D~-*5N(>j!Rb?rG<- z8oQNbA@9ya{`0RNuCTDMoJ0$Iqc=jmvUhN>5ImFHUd zmAz>J$pIoKjiuyd{_vA|LWI+DB{^Tiad8qL6Uf+uLb~;Zi03 ztFGrK=l|QAP|!YnI0L1udZk4??>l>Vb#pKNXV|}?p&4OIk4j)TMLqy*S zIFQA>*k0Ki8=alv<0F!q98UHpSYGlEVTmay^dYT43CH&6(cOiajFM}7u(OMEb2zxT z5uZMNPrNOB=^`*tFs}_zA$_t9iFifNjNN0zB_wL$n!w02WfCVUEw!0QS5{Vn3E7q* z1@SxWE<$2D{QT_J#ViFzdPu#~EKJE0nfuL02R0 z4b8#f07?GDq)_s(=oBIPwbvqPa3lai{lo@>jHijDgd#+v8!<`~m%c>Y=Np0Lof^|VHBM8VVg z&bP!Aq)++{Q;kHsLU~o426o4g%2yQS6&3rSlo%fyyN*uS2lKJwv*^$14D`YHmHV`KfPS4B)6LW2kS~j>Gj5|Q|opzRlLuS|> zRSXMC#9@JL=Ey~J&j*oormOvBi|1`a>?o33FG#OZiGs+EdReir{BYuWGv(>TS@7`I zv^|b|UY?@oNw6r=pI$By2kd6sM0PmHGa({7#^qOi9ft`w#3Syx zG1`g7Vm!nO3T~IIewfJ6ZzEAH)WuAzR=Q1zWjfL zHyOxstbEk<@NYwJNz^FWU}8G{@JV(sE%(uh6H8wHv+bM=?Q&)Fd*lg6Vsoh=a6AtKq@+3oG@)VdtAyHSO)UOc}^b=cgfuhv!{mxiiuc&d`(4mUAwv?L z5{$Wn!GH35Y3T(4A==V2rn`~PNaB1Y@BW~FNH26NWxeHy+^+TU;k>pSEeX9Qo>}7& z$PFgzJ8*j*b8rlHci&sk(;??|b2|snV#nw*&HJj`pCY3^QMJ2IdGUV~dVwWkAX_Qf zG$@Hy_$1#}vj}}fkKgT#hlb|$aESpl{3>D?VQ_*dx(r6mBcx%C!@RcIWP7r(P{G>S z`IFSkna=#;z4YcQdeqd^iTrL82!!d$;T9YU4L){OR+x!w8H{@5!F|eN9sF~SGm80d zC^*kFx6buT5poMnF>ao6+{!gfLm3k*dPe&C7x8svAivb8>uf&O(pp2za%;L7su*}s z(rq6EnK{43m!C{CjKdDz{MTizvxG_#UTB8#Uw2e+`tuZfn1S=*qeqW6Beh)#*v(Yn zVa>>>>FxKIu4rgz*sl(K@Z3$phj8G*8{oDRK65OA=%7mcu11k04-aEI&+?ZSox^C% z4BHPcK&yD+?6Rs{s1*$&Ye3XPwlZ6^m?Gi0Gd=CMrLSxr%t;QcY59*+7fy@%|e0Idye)(9Vq1I#@E1R+^96 z?=E&ji^LCu`;&_<`Q4jl6;t7;+zAt2<|Yny`qWN(g~qs#aiuLoCnKnh)Lla-edW6u zT|J+@zJ6L_Vq$J?ZgusA5xN;!1yNN=N$LLm`|vJEGxy!71b#x*k?j;0(pDn2fz&x+ zLFpHW|Ll^Iy52^5Bc}saA>p&(_d~P*TFPvdtnY;kPX(z96A; zh?+xJ#D$Ud7nI5jY1gm}vSu;p+%a4(IALkMrBuU|lYe!vMOs`O6&+o(!L=?mRS%G^ zYUg%C$K#n`{znKRF+hyO!jB6uo>6RC6T96OV3S?YuM4Z8ce_3NDt#qo%+JbY75WC~ zE8&%nKLQ904GlH;2KxI+xU6SCQAz;tce0z^RPS>0E9{`+aDIK~N3kc@ncQPQ*Lgj$$A&40RaGHRIh-IBq53a zaOFl#O$`)y^sKC(_^|{&!bavCwTgHS*l?cRp+|mzXRFGvf3L?78CsA=rPuUM7*oiW zZyqT((c(I1VNp;TFz=NY5)u-|vgytbW@LDBI_@yJw6fyA!vbs9)t9XR`(3C}{VB#T z^eD;V;_(6VajDonw$lwDYD{6tw6CYiQY zR1C|^%nS?+%+98Dna%$#Hl+)80u=M z_NHa7%bH>aApkDjRgfZe+}quSLS=t-7z+~<^WMF!?Kz2~Nbui>_sZqW%*?nGnDj}; zE^R2>JVlVA$Wkt5Rev56AGFjG+2>eE4i(-j-9DDuIvN5fD`PSoHqZSAhDCRZCnsqs zc`F?G(P|s&Qze`_W0dqD#hLA?MsF!eNd^W6C@uH56|(O1r~Pu?{Qaz+JcJ?bZpc;J zJ`FM3^pzXkM)b-BP1D=oT==$e6s@rGgI(-}GbzzDpa|aG-EBfv(Rz_xp&_fQic3g< zj=&I*!Htcsg=q~r7j9FiuKO-e(um!azbST^J4dibV@VCaP$T)f(Ju2r2mi|nkG*2N zz#hzPme^~KP`1K#7nfvc%YKZ~=L;`|1D>Cs4^4rduI?dc+zngY_@~Z?Z%#6ku7?N; zVE&TB37r-noZi?MK6hvic6N3wYUzOB;05kAz?P98 zLquHgszkfpN?(nu91eBE=Sf8q7c-RX5P1!I8=KOS5;{pG!>nt7zC(1`azJTbLGCP6 zR8-_D7iKtk@JHN`mm!xG|NF=%qL`XXN8gRlnd`GUT`Geq=ezjm8!RIuPiS+h!+#$Q zp%V#>Tf~CDoke1@1{wHdTwp6@vI#mj^Wl|tP=UzCauN3`4K{`I7VYxv%L8Yx~SE;K(N+iH5_K+zvJv}-q>H-}IuJaVgCm=HhbNrGXN~V|pp8S?Fv*QAVb#xA$>mq5{i{JX_72ipzz;=&~bXWK4iJ zb)UE1x^>HWgz<^VXEml1Ssoh9?FRV1gkeX9bbQRrJ4GH^kCaVl2xxIR01?DUBP3K5 z^Uk{ba;8g>5I2(lc=hp_K*T`4x+xh`ZotJGMY_9|0Q0dq;lhDfu_9kSXXRq{US9gL zmE(UC5ZKKCayCy=hTWawumbd`YO-a!F$QM$30;dA7ZsbW zw7YCR2)EO{L5E2f2l%P4Uj@+6`~i%*P0SgBI+uArlbio^R{=n3dP1>_f>hzJ8P2gsq*sen_%S71J;WKoXD#+{n8%qTG@Bh7C~cZOz<+jths~Y)GK8@%#5G5kJ)} z+dkb07%?43o@xr5h7_qXKK43=T~`CQ%1Z*MR?%k)eE*ZxNPa$-5EW~PZhGd_ldLO7 zPRWK3tuG1o1IzIm;Yyi!ZsAG{V$QF3<~W$38V@crH8K)Cm#?~@p#lln7TG-ww+_lG z%tw^~7^gemMle6MTUM;LHmCu#OU&{{?&j{2Lbt-X7~p|-Zmj{@qnxi6-bVD~HCdUS zg#}aEGE^e+x9XY!5C+VjoA{P`wVrH?FP*Ujnm>qTo>N;EvCRR|ag z&Av!p#x*Yy_IwhDE#3^V@K)th*pq{1JZ)Lx_Athig7kFZ&?jtrjm$+K0qicMqxTIC z&Ln1Fs&?8l@kGU;dzrHf-9MTgbimK;>Y-&)`aW$9S7L~XHvvkq*yw1vDL|)Gbl@r) z7?^l}=b(QfCRS8ZlEMvuOP)uwf$pnUo2$c*m8yUUKv&(yEikgNiq^U@g@xvi_y}|) z-3g$NO`g$?J_wHl=6=;$d&cp4>pAgi>$%0bX?h&#+zs|8i*tP^L`L;7f3Ap7-%_r^lE;Up`P?gm<6T}0=CxA-+$&02bb+~S?iEyB%p8coPfR#4vGeD`7igktk63SK<7sVWz$jAh;WQ(dKtCcjo z37LUz0|0P%9d1;1Cv(Heg90I$K*~N?Wc{{ggZU<3EJd0VUoYMfVSilT71^hNwC8J7 zvl5kjpJo-Ff(`Y_g-hh$HZ0&TSzX2Ba%9Ur0)T%?Afjn#DC$A|rb#5Nf=E?}HU$mM zPP|gx?A(H)MCp>@f#vK&LiX^H2~SQ#m$gFGAEgKUdnjKaTFoq5)0VxN`o6z5y3=57 z;IX0-Gtxm66QH!;Z{Ve==SrU0T1b!87K6PF@WHc6+NbhPSOpub*ZdLN3IUa-gF$VL zjg3q8K+!F+m$&yvbJv_^L*H zx#^%h63VpU0!}9FI;Zf2!s=?|336wLDDvWNJ!NIa4q(n~5lSQmRIBLIg z$?t2)i!Hm@MLWq=Bz7@CNqO=nwR(Sbs;Y+J1G;kLC2R5zRe7t$2&j`zGzEQ~&Q2T? z`CLlQw_RjzwgKS`X;f){$Uyj>YUl&sPJl6R=oGV*3n$?B0XKo$C{VkbEUl6G$_feG zE+s3_2adPT8BauE7d%aKtiY33( zVS4I-KK#nGK>9=W*3cYqUPk8xE^g`7a2i(GkGYzM3|^Sw*WM0!daKFc-Me@E{QP_i zxS{b+9Y%(Sw;XO!it^F-GQR>q#J(2D*Y6zb;qijl=wD^i#I75HR^WB37qP5}exxET zH#L=^dSV645fE>f;~fIl=R)j<O$`CYh*5A8HGM<&xv_d|N@^GzS4p0oju-v1ET~db>!cQNzHn zGj6!lNQMr1vePxpX7>%=nmyLj!}@qdM>bO8#fy7`w2urN`Y_28!^;h3-J~D0JoZ|) zj3T)5TY=(kBoKj*;i$u0?->~xnItwy(2#2=Sq$V65aGT!cZK@9nSG<=aK*)J9gUCB zh+2{UQ{gH4eV(XkLZXnh^>yh=x1%L$D^G|^xG$Rn0s^v5U;;ixKwp-?LJ&B6x0W$prt$TiDN`Kxmodl@MFJ@>-}m zgW=8OQKWA}<2hCE&Ios)U9UgcM!+eOdm47{sr{-7gq?TeCJZ)7W2JQL#SR!0$iLzyuJUGEX20 z0k>@y=Xgn#nUN9DgR10TaQT=@$w{ZlRaW=5c6Jc$HJ^9^$quH*j@jlm@x5zg0BxPj zSsZFnPNIp0N`GO<;yDYuZ^&jOi>w4h(KOS+0$94=y%x#c%x|%xx?sG>C0sw2fXpUf$lyIf_5Oe;4FbE2`2cH+iY^?T+}A z*>BckLGt7xv0AB^C->Y&gQeC7igbk74XlH#kdT(xOb60L5%m+5MqT&ORjqM?G0uP& z&I(C!l$V#!c>RD&$D~?vIIa!b|04Z54<%J;i2Ug8mdjFVt%{3^5-X&@=>*I^TmwRr z=eFc9H^VO364uAJRuSJ7v_u(QxAh+JA#La)?B@o9f3EIRAs4;!qd1)K_*}iJKdrT( zZVSeR`|qEl%d$c730!F(-bL{VT@;fSSL^T3iw;HE+1ZV3G79K4vFZVPi38H2QK3!eUQOp)3u!U}Lx3k9rQnAPIu!=@Ti;&#V zuU~Iqp1ZwLs?lnE_N!69?(n+gC^fjcY^|uiuR7Tq)Gigx&CC=f<;i+3A>r@B5ejOR zQsAkfytR$hA~=%$7U4xJ2xq%F;8-%YC9&6xwP!Nkw)Lp?e2^g6efCP+h?$Ho)BT~9 znK~WS-)9X8yk97^lbbl4PM0(f9|_KJbf+Pz2!^)>cugm>?Ly)hv; z)ZoVdN2PPUSwe+~A@V~o7hHs&mM0ySr-CWB$0001tUD9H70*&6r=ajyD>*p=4UFtn zqm_NR5dyhcO-3Ji9ZnHs z^w!qaMd+2BR~bV#h;w*2t!bLz z|M|enV3&D31Cz1ihgEUpoDA&;MkIUPHYwB2v0ajbhudc|WjM#JZRvsf)7BYg5$}PZ zyHJBoaU^D4qo=1g32eARqqX~VXkdVS;|eKPn*=#BumPE`-Ck9tF{4F-L=AmWkv3wL2`5iqM$n_FMjWN6V^YM4htjYyr?;4LS z$z%f}_|xHvm-E+eSF&$e-kue2ZJTsGX1s0_K?iT0g`@}Rd^fbw-+1KG#ZMc;6<2d;Qr+Qm=0&qng zI4*{m{)p%j)As+wy!x-SEnlLg2nGreT4Ebw5se3!SXfJ2t%a;v1ptDG%P@BC70>80 zE41^HmL#spGh~b_cYXInJ$N8QyHwIu_mL`)D1mQ_rH#Ly^heug?J-?SKCOo0@=0nL zd`7p-@B5>lxD-XT3vou%%T5P>Ok%9=>C|#CktE-+Ik(R`@fEJR08*3kc;5ULKpz%N zhSL+WJI#uPjonvns!UIx`=!0b`Q*?_LP8=Zcw;{s>mLLOd*NsZcisg$1-H}b2kL=0SZLwc?Mxw?u(@oy=mTROnQZUIJEq3TS-*9{Rl&Atv zyNqcX9RT;sC9Gm>Oh#>nkvn^P>37GV(KGJNKpr0hI}H4`JYSHHPtquW{tou`8L5ii zCMo{81a$<-VS;K;A9}tQPyH9zr|)OvvWmVEAv-{@@aU68W_?Y5;hl-4IAch zM0d^?qs%|~Wk{U`qn;y8DIM19T4mV>K59nbw#=2Ky*AIMf|Mln);j*F#1IWYee<>0yYCs-ERndp={IP7m( zqR@z_I(~hBskJQ$1fAlUd^?Rao_&B6Gx56fUr!>;vXRG24-y{_=fD1-O`SAWkd7$y zi|tfb*}J2rbtusL^N`IuL-d02Mi|V+cTXthT@vOiA{fh-LLk0Hr&Pt^9L!aZU6zcUCUXm`sPvWN?wBa-D>Z6KEUO)NKp(d#NR7rmPjsHUY-yHm zit&`Of>;mWJ|Ktfe}BESm68zhkeogkpXIV@b(j#a043@!@$xs#yJZghmrxY1+DQ*c zWY=!D2uK>Q=+$=F4TLb>`*gI--Yg3XR>U;RYu zhiIR@mHJqIy)E&3Mc_)LiO+TG*{ZdJQ8Byaf$Zi2=UCgNv{1I!X=3bf!_XQ`&bRe2 z9x&(us&50$0^n>Q8{D$I;ZsslUd~p+dV%IwZ}{o{Z(@M6O_elO>a9ktQU_jeVLGL- z1G||_ZK;v;-9g_f*Em!}813hTxW?9{f*;pyZIOjLtio2vL1CsCDI#bRwk%0ykP1U0 zIx!}lr;a5j6>{2x$ z+1}BioTp+n^8@`%iZ(wa{DXVdZTa$*RV>`+WA+bx-uwL*>(DCKb2KExaICN%&Fv_e zVHPx-^D{Ng08+HLz*LwYtv%;z4YsiY@f}ibt_Q+?us^U|LE=pQFy>V}2 z`RvR-|3K$BMdqH6S5Wd)YXSBStx2gWn-YUfdIW9!Le$7kor_8O3Up7L&VuRd+4%q>oKonRq7so%v~5IUair zl5Tjbm%T1%&Nuwj(lKltATqGHP&I@Anb;O`p?`f0HS}T~H|RAR9CxTVIp0E&=jG)k zXkpQt(J}k;Gr(xEQHN(SwoA_ne{f8GNxaiMFo3H{a204ho$;~GQU*-VDMXS}mU@Yd zZJ!)cFDZ(3poNdP$n!y=r=xboXrZM;n~><0^Luo8TX8a!Y$ndGGd=f`H~ULBgGhF> z;{L=YqqH#t9BX1eE2PG&%_;Ng>&>HX%I>t^99|b5uiIg-N#h&Iteuo^IUr-M4WR;$ zn=TOvoy`CUL&!M*CXs!!yS3#AS}Se!H8Og4kWZ88B;432ZLsY+Xte#1WDUbRdqD#C zyITIo(U2KMaS?-9ehk})^SEHtSfIu3y0Iiqj#57BQj6RKQG{cA>_D285S{VnOBZ`% zE(Ip0UR)<@8K75}7?5m8C)>vfmgZ z;>&1FX_C>bO76$a&*Secx3(CJteShv{=ADg8{?hFjGU|sN&!Fni)tHs5Bp&!O3yJr z=kyg&36Ix0K=b{Kr>R;ZlF5X7j$Q3Zs)Eqx~3&ntKH5ICU2CNmI^6(5rgugOqfUf$a?h{ ztsRKx+3`%;6P@>;1T;grDeNSc<6snxAso%6d=*D#%~d^nbBM@DMs-6I6S~=XPzxyx zoE&d7kn+2>4=S0rwuRuqNpNHuwJ1fry%MP*-iYdUL4?aI>$`m7rgp>M78+jB%k32@ z?DEe0^3q$2<1Ae<2>3E{!0-<@r>4m}fpi2M9F*>}rnzZSvFvh66_u5uN?sZe{AMXL z(w4ez8!TP5c_ieEHLsRo5$!|1=b7k6zR5P{V3Y`Ak@mPX~7ako8;D)$nk@ z>UcnPA>t*j-8xk7^0b6?7P3;`x9HZZ>=f?og?h{UqxZ5HmR<8m2Z@y^?kNU-A^I_J8ClN{=p4CKm{kvHSBN$dt5$sR>?;SE{YzW6MKV!PuZ5zTdm`q511i%0rhT<3gLc?AV`cXy+X znEqltQF4yQpaH8aDTx7M7eL#~SFT*Z{Nc!*OMcg;S+g>gX^r9Jp+!ay54>}YIxff( zQl7Tpm;RB6AlLq1@*yvNU2H7MykN%a{ML)nLu&{seC>K?q{l6+>$maA>X#V+g{g$j zxf+1Z!^1dEtLckYP6$Q(Vucx|<$8jNEM4cK7@wf2H*Qgnc5u6+k5Y5!R9&l-0~L># z-LaqQi~Qhxx>#0t0WGw2TrNku5Jh2a>d()Pwd$OJ{N38BHtq!(a#VCQ7dJN(&}Kkj z;4y1m5R{{rX)cz{t(bY@?foy49cy2l$G&;xzxp@DSITZdux0?gW^NKF0zl71%4Mw& zcr_qnAiV|b)Y&=07(|i3zqtdLR0I1A0v-=<@65E{v6HfId=RpJJdV*dkgO}?wh+6FVOi_;uFapc5ms;)3(Y#f20OO}YNkSydVZrB&EiowT+| zxh5i{s;Uay0p~ki{BR-Oym|A8jSWXelloV8cd`(WIhs{($Y0h2+jHDe| zpQszn7L#-DV{nrJRUuBgv5Q%_+LBrd5YAY{#9yEmskT`NBIfF8ZS@K6Vg)@N7;9p9 z9Jis~I5|DF9IFZq^VpycvxhF7S?f#E`%%D#bsD|4iaE}+?wR;m$d;b)UP5{A5f-#p z{L((%G+RPi`VIyL|C`@mLGYa+8Lg|MlU!KX)nY^dO+6725db>O%!M#sVNuZ;jJ|Zg zuxZxjF`$R6_2^LF6Y3Nn!x07Pc=q|x(8V&EH?_2Y1Hv4X79a@-3JSU)c0o{2nrN`{ zHZ~@Po0F44rRX zqSgy$8Bm>^;Q->|;$r8}J~3LR()aYFZo}bv!m~4bXmX|(`-g%;(+iW?<_u~4_3PKb zfIT2*W+FjA%50jycm@JbIIEN-HEDQ}b2pTWg#21DCjR>J!oM5N-o0*36BC-MT59;c zhYmt?y29e~dd7aCM09TZ)kP3FkkW8tw}IEs+{|oouls*m1F^3mv9^VrtW-6a&XyCr zh=sM$75fNbK<}6DcD3pl8D+jU#%c2e92q2R@QMzAPr*+S4GP`Lmp!-)Dgqe&-@Z+O zCiiMmj@H)HfW8)1_~atCsW=A@Q79PyWmMWfffB-uQ!vP2_oMRV zO%OI>PR2v}01g2u?gz6P9UA&I6Z}8KC);lW3I2FBlER#}XMsSy*{q9!5e(BgSnL*x zGq`3xQl_)=4zy9MWMpKJJX*F29G|cG5Lpi7L4}L}=OJPc;Tszp8y@}=Tq#0~Dr#z- zu^hn-?r|6QqHx^>-&pRZO2$=~2$?9h`ojkgAa_C!#{m>gTNvDPtW5Q1pbGG`#7clH z2Mge=j!HpExwk&9{#sN);q%!VR6Qyx_}Xl>Nl9ej^ZOY1Ektxp))X{{(;Ff|5bR>{ zgEaA(ZZmwmIB+5G*UL}%<1$*?+CE`pGly}u{EkZpdkh#m&cSg86=VezQNS?Nxt_*Y zN>I_!`O%?Kmo()8B|W9yZSD0RT%^SOjDb~ouh;#*%RaIEV-R+hhiwNkb*ScOw&QWyD{0|hKOK9a*mZ5!|WiOisI0mZMT$gK7HEI%7^;3 zsmY^s7PyRbWYEUHF!9Lt(Dqne%fC z`|Dgqk#z>qaaHxKb!Kj^si9$o&4Tm=`DWrc+ZuY3iv8D6@m?1b2gks~WMo$XFFdx5 z?l$3**B~5)TEr3vSLsB)jz6AEh2xVe|2X{KPv6DIAIgv-;=?l<1Ue9e+>j9DLvW<~ z1_y(Ee188S*+IMZ9OLs}D+5YfI2fVdObG(5$MWb=6qqAHW2UR8hkR^6$Yz3#hu8A2 zmuUX=Q=z=agpg(iMr4iW7fo;!iyjzlA3S`B#G3uReBt#)$;yNp9-ok~#Ahr;7!7PR zaFjGbBJX1=;gC8RI5_|m=%aGuunr@N1V8v;BL=^1B*9aAGpLZXg{W9q zwxBk+&+qCC6-2fn)ZSE*QLtC7g9z)bX<N*;1B@)F6>8|C>$ep@YI6;#AoGV69ht2 zZ8>ZgWyLL=4?C#gYfNo^Y8USCYrY65o`EvMR{8w-v+7T{kgpGba2Je+Xz6;qR!L}0 za|6u|zpW5VeZNJcnv}l~FReT#7_``Xe4`s8!pNE3t zD5{Ep^7rS@>_@)Qo$*hMG0PuHTU67CX`G!NOwy9v!GH9A4a)Cw-DW=kSKUqyi)9xU z65R~RKYd(+%>_uN7Y@79HvArHKbSp$*%a~wD5-B1+;}vO=li3U5W)1r!2wRI=n8rT z5L2i+LNHuiTWdhDzWzm~1isSj4B0@wpw}iQB{2~9@J!lT=0F1nEgOVC`_13qpd>Qz zgd#dm4Qg{R1Q9{a3_gVm11i*_c^W5uVsI@1Rpb~=Pc4%ubibQ)k&DQ*|JuHUlK=n2 z1?ucmrpYR+R9L34=jV`+B!^$>gWps9gG2{;Tg zAyeVR8%_NeyF|7@qflL2OGH3$?fUg?D^BH0moCAfx3#!mNdFk=kH97vR`L1ynYt9y z9;X^=q%bc}6R3g0ZbE}3!J+u6)M#*x&%JdqXh*#N#Cjw~ZtPoa<3&O!kunZ`0P%}wErH7> zL!;VSu&sR@i2udsIc8>N?WBmGUmSN-JaEKm+V*J=9N+XdCoovha1~P2_BijasyYz@7X^_4=v`f8_|2)epbgCPAe>DwXR?Y`+vzN^nh}9y zrexC|#Udz0&cv)%)`~#ng4_mTbKRS%kFpMMj>CcJhb5Y}@OS&PTqo%B^tf4Bj|G*gxy@aUKtdz&#rHKgwiR%d< z57Da3Brf|&7`)Er7Xy{0bPPo317?D~O@jxz#4vjfkivdX0cX1Q%UP%r3knJz8@4}U zVbR(1wCeJ>Q!I!+Oydu=s9YF)6QR!SyneHpZ|j%;#UG1*8d*~E23M4iNx{Pai~sR= z9$Uq;#qNhn3O)$vv31=k7nx>D^WGT5Jh2!X8XJpCfJ{;gk!Z6KBTs4_8xzv1h#;D_ zb6Qd%IZ-2Hg2Y9!hoUG8+y0qahNfG-)5g|TpAtL|Wf$@w=<-(WY<^^<(Lh zR(_A&Ujsq`vt~`xOWYq!2sb+@)dN>eEZ|PlyaYO;E0I6(WkfJyTQ3r<+j7Z**SMd6 zh*9Z#peE?XE{|sfyj$obnaTt6M-g|}Qgz`YL&HE#+^!jNd=3j@k|U5;(sCHJ&3Edp ztxnX%7XJDC8yyv?6_*108$lsd6U|8#j$(!};W@-G0|Q%{>v(9z`p zbG4dPWKUN8X6iE9H^>m!kqaD52P=azLhR=xqv44Ts)uS*&>^V!KEf4t)d~V3dJ$rF z`|}v61_Rq#0*PAe0WK>buf55rrEu|=6rd<7)z4V#>ZX92oKv;r4TDzqLmC<%11aEI z$ytCw@Nyv!{zR!xdI9?6#k^grmmJ1XWnZ}7=p|l?JLE9m2IMQ`r!88?U<3rui#cAP z>LEoMiOw2SptvI}d4|m}(iy|%-#-P|yLXUS5k}gQ&k@PfEOUtf1;L-5vH$saH0Z8wrL(?o~B#!Z~Bxo-&R62-f ztlz%nG}VD`2$WrTc$w=DKfYWUujNQLI*R*R-9vzSjgQ^wKwwL?A8M}Sb7!hQOG7RV?9eyd71J2@IogVss`M6H|8dNvVPlTj_@}^#TQ9j zAROlr(}Enx4nU5g{=mcH7ZX%W$P;rGZ<+OEdU z4cVZeT>Sf=zWm7YpTGQg2_AVF|Ie2n$x_AA;ZgrrUw*tr{vW>lxTXnTeiXs2Ur=ag zuY2>63X|3^EkMyn?8UIun)%%pbieWMN$J^ADpW5&iLX{%!X3V$x?96hH&jPA;?MWt zRD|RVgDaJs_UC*V;^9~RMx-I%{Z8at&IjIIJd?Jk{d0nwhVz#%zc}9sRjSo+3|b>$9XMsZokozpC3D@fR|Kj zJsM$9b;{4tFmblB-`F9;!GA*?Lqyxv_2g)_V^ZsUZ}!!;^Y+2kgwPa0*}}8Y?~ipI zhUPU@_IqqQBp!Kd$L4yT6KWw-J?#v&1xIf@-n~-uw%nnh+F7sT)P~HiRt1^vXm^vh zi^pAbpz^NIDN_*ldn2DIY-H0L+#t03l0pwn7Ux%|+wGM0d2$wNGuDz(eXgqxS}pvd zT+zYNCBGxrbpXcKAkb%G5COvi)*cGuDRw~^Ec=GO|&_JqW;rIcd<%bi<|e0!jik4`y5yd`%o_5 zB%}EaITj6~%2_I=ynW`U^oEwuOww@F0Xyes?aIdZUBbhf6$W2l5@viU{?x6fSiAL9 zmCk0j!a8PJJ_itMt$Lc+X8yL=qBYL3BsKKqlA{Qy&eZh%-bMAw(NS`%&7{~e8TJI@ zi5r?iVqRtr!u8W2-C$;TC94nZS))?e^InbWXmkY_7LF}l^TaQVWHGZ!avVm`?orMk zlRK$n^$B;r((6?A38_RbtDJBpm$~7m+zick6E#vQe}`vdw>H5+&|-EtHEC0#nH-zZ%MAZ zMrbJ8U)kb9g&g4t(-_X(`Kgr=W4ri7r-SfVTE2t8X z-zAACoHB`A+z5;mM|*!FiS~^*BqAF72LlBQ_ffr%@I@*b-2x=H7clGdOmjch}oOmQ7ja7Sbe+qu`N&?8D6BTUOt%92tI~P-uGb z8Qj8oV9;@Uh10tWMb-i(>ejbmR+y8_&#!222)q@i6Emyc5167@*baR4t`_yNUpdp| zddb8SaPJ;F(oyYv=*ebQ;ukS{M%6p6Yf_`=4>HaQO)~jjuI0sjwv^Ey#L?7w_-4M2 z1QVxfG-4Q`UoCsLZD<8fPI3R+qX%6rN?0#3yt-(NNc$LbLNpd{sZ_OTlc7gCdpn_` zKVm@1grI#x^XkWW^d`fTIFH;rvoSqr?Y83;%9v1}X}tvl8Xb}Ahvp_$QW-7FOFLHK zTJfshQcm0LS1)Gp2kCPJlC4S-xh5@35`Rv-&ElNA&k<(CqSPh9?j6TYzeG{>mg-h9 zd)O1DHk`3osj&iI)U~9?xl|9Tl(jy|QWbmN&K`M{5Ol{|I#$X+=}~qh{7xpqJNK|j z1CpE6j5>IV`h{z1^~_cdb1Lo*C3AFuXxg3UG-&$wpOLs*;=e`Vs*OM~y1jQAsr8`E z&w}apqnC0qn$k18KK3 zmP_w1S|t?d65bDuMe(_F5bJHH`_qKqo1LKT{?E{iF5PkAd(R0hzK5dy`qtvz=k;JF zKknk`1w_>Q!>wO|y$g;IvrEY16+dq_w7wEPw(3?iUtTDm#7C@@7?ey(4F2=<>*pxe z(`U=Kf1}&x=G^6wLJ{H&i1oB@>A?t^`mG%GF+-Tb>aB{|{kIYzc>65c$6!DA>LJDeIQ6T_>%FHBzv9)=L0 zSV21DoT{u*_V{o^`3VSKYDZLASIJ+^-YT1(SbS$Oh3@Ty#+HpmD+wXDzVoXu+=l08 z8y9x8O()8t{zDUf$^|}y_%IX(r_k<~rQUeO4llBk|_M3s> zzPpq?CH2V$U6VzCAY=RQ57=SmJ1=v;TB$7f9=>Fq?ob?h>w^yM#|~cl8YNWf3SFn% zI*V1+!*hCz&usJOr{Ju3O&*WATW{*1fAB;za87trK{%r}e4Wx;4m+0gp5FA`zCvR; zvC65u>f0*S#1BMbJE@=a6!6~B>rnOAlew$2zA=SgFX{Ffwk_R zQ|xzU)1-G72A;cgDrw*q7%>^CcSSZp%E*Pns+F$cwazAIzneSI!qLQB8u-Uz&VKK#a6EbI zNw*>B<$@w$zSz^}|3hH5OkQ2+moSdrSE$^MmFf8i^%QzCYs#h3g{Be;&5?HJ`(NrY z_gU+Cx)|v8jHmY+u^h3mcbZe42hTWte!^jHJ8pjd`{eN8I}P!-`6`jL_hglfjW5$W zor{he^Ni=5pFq2DbBS+zp`cg^{3Qe3NJQ6Am7KXQIj=wWk~1>;lKHm7_D4HS%{I;U zmRZRQZQ~6#c&2s0YPkj^NViyh1B0K=NxU)mo)XhDe`!_M?K+>C>yUFF!>R?sBlyi@8AyzKtI#^5(zvI z<7aj#D1B_9Jm66RDPj~9$469;QBe5M{)fkzLQk%1+0kB2JX^aKK=F2AihyOQXHiJy zfj{_3@yssW{xzc4N&jb=`dTDPEjY>;kP&lb#fZCw)iN@pin}WS?+F^_quXDoJU=r znYI30jxBcQk8pO)mS&O$E#F*XQ^YZvH^h{t9> zDMDi<mU>j>L}|N&i;KRP&9BIV;qvs5#RG zO&rSaY1xvgyggZUVY{-ob!i^fT6Q^U543~nHl*Sirxnez)2gv#+IA7%*6ij}NYqzV zB|S}#{me77SidfLO2^TXQJO;DqvU=fQZH-UWxYZE!#BsKCI1L4$IwT3uO)$-hL?Hz zeYC_Z{OPH92JSF7NIl7@R}Xrl>QR!NF2Ktn%)`*U3hRs6KW;c_|3MgHePj{;&nsS& z`#5j$CQ5V*w%Sa%GCil~Xeg~!^6+7zeNnTsQsqAvsGIEKrMRre@tkz~4jk2hJoIF< z$#|y~z30;XYQ|><6ylghY4LnA3jPtmuy^u(Kg>9$rtV-o;AR+G>%YTX9L;Is5c-hq z)SIn7f+@GPF$-biaF|t14sJ8oR%8|Q=7DXaafizD+;P?TxP>aKp!IfI_z&j9ThIrK zQL0Z}nzuZ$l6xnQ^BMzfrsl!E!n+0Mjw4m(KaRV~BbSNZI{H2$P|2Hp?V+bWn>;sA zH!G{HC-4}|7b_HIOzv~@-HM%$Pkj+u)}&L!L&7fZbC0*PW_1lZxo^GtGqx>PdOuhp zPj>U(eg~|a@lR`${d|?@;b=P(>8hkd6KV|VsZ5tk7?W``jMWRVn#hlfvCmX zvyYp4*928tS2JAIO8@NF!K(9NOWpH{Yhy&UsF2|rQGeV5PNdE~IfAX<69u!fn~LKi zf3<--v8OJxVP|)MjdAKWWyBD7>WiH0{2ms%*md`%xlwb=kPf`tf4--3RiJOO@Y>A^hkhUM%Nr_7+l}iCS9?~5-blU z`Dai+s(SZiFKYq%40@{c<*J10izSmR^fRCV-Ywn;x#j*_CF&$Q>a!oqDcC5zj#n8` zvuyo%=+(x4G7lbs=mNx0D?V3WG(V0-y?l~e~=!+YuLPOEPu?jTi7IkmHzYtB0j?Q(kxiZmF_tOeMPa#&BJ|T2dhL1 zS6BPE$Kx~lrymelV)v#&X$;qet*R@tNdMl9(;!?n3F#g z<}i>^p_{y`%~get$Xv>Xididp?3=xIS(K;<1ZK?02Mz2yN`D&gq&>8EwSlnqBwif- zqNt@}ky)79G-}tN*d-YrKoLgk#+MxdG9 zgtgdI!O!AeBAMl4gq%F;XMc2Ehv~NC}42p)$(@CZp|J%|LKwEfewN@@bSe(;whKyj(jN3M~mC=3QlJt!K;;c zhnd)NbB`x)}9H=jh2t((Y$seOcUM4s~h=WA@8N$r?UN2+)B1$eLb1d8M7f^ zU2tlbgiiLhA)@8?3$*Q&tx>eU5}S&vYN(jbOCwq4bxC?qNg5v)udZUY%H(3I`oad6 zGaJd1*+ zPY!AxRv*US_v!qrQ?{3(PL?NA&KJ@Bozv0VyAJygG4Ck^NVQ2x^~!Y8cXhC~Gt+f5 z=D(JI_1e`|QOV3xR$AV`Bs@=HEcfziwupCcZ43hwfPPwDT(RQ}9g`8cuS zIB}KV)uUqdQ%H={rgcca^ZY-%rh4uZBj@vmhM4L9+!$neRR+pJNH8XW!S_7Qdwybb z`;V3q_bWfMyQ2vH;ralmjP$eJrDdRQe8&lPUPVuJU3+2QoV(xN&ys=GxVgzg=Qcp= zd5P6>GyP-5Ucszcn}qCs+u^x?G(MI*S?FvjVb*rK$bQW0*9bQ)KSn}z;(&2i zi@w)FU)^=STip2&R4@Gmd|Hn7mcvgm+KGpo>&N=}u(j#Cx@Ti$J5(uy|473A3ue;h zL~A{;v%+p}SDpHW?#9US?|D=BTL*gUuAb}tGAvmv+d>|nez;y&^>1B1?O{!m|QoTHkf6-gpu+z`I^k#XeVDqBi{npt1U}Mq2s|9DX0C1`t_~QfofxYW`Uo9_gskOKGIZOzwL8> z;PGnqYOXV?D0uw2#oAF39?93rtFhF?nMs8^=lVe*(p{;xRlO$%0uwSOB4!qEtFOyW ze9T2hi3NmBn|4E%H%?o#4;3EAnNyPb{jrsv@u9@lwv(&E``l!#BqbNc>*H?*ER=0C zv(SdV_<-4Fw)DGZulw_-an3&0m(3!-TY*%AUPdAE?{Z!+yccOLv@8<5 z8%HkCvm^5veUpa{Okl^;eLxUBe42n(RTMONGQCC_KaI-bOG9Oji-Qxxz4%>i$U6=< zO3h>mcR%P*Ah@15%3g@G|5%#%Q~ft+6O!GQ%AD40!E_Oq`;x7E=nG-iGR}ba<;iJV zm3$JgPJ8bp8#KxOZs=G7^PkD?ZZqHLd#rVP^#kQt>5KB~$EoVd1qB$g=H1^Pw6xB~ zx#<@@UF%#0)4Dk(Z21IGuMU5qp7-bk$@ir6J9m$rbNOuZqlI)d84c*l>s{H;T`i-r z);IXL5<@C%%g?CJB^I^fLPm6lojy&0T}N|sGAPv5{Elx%4@nA?>Sj^Vw{rVrJ~w?y zDHL0>tS%be2dka$2G^_+xp(dkS8fVpxEY%O-1vYP4sG;$0oyfqI40e@Pwf+>ay!!ET?JK9J}bsb#$rgr;}#a zyOgBY+Bb|2lNxjHy_er)9ELu#TStRzqkKTDH-!XU9#;^RlVVW$#6+v-vMOOtRg050 z0J~VV)psg13tb&(l|Pq0B$Aq{4x{#@tXrs1zLZ;PDpODQ`GlBMf3|z(xW#+vEE;oN zyrl6?CE#rD0If(pc6s4+13i#$fDGn?C?L{A&e1ndpe3j;qf4PbgjW>3L>{hAv|f4e z#{lhn&lg6~w5}?zTYI&eKTWc#z$}U!2^yxpn)vneuB6fQM-GoPdrWlGeBp?LnYB@Ua)E#{n z?K2fNm+kj!v#pg~E&LRti#ltO9;0P$a{|00+LKvTeKqNuDHylmqrT=%$*$&&`2|_= zp1mgLv|KNoBiszYJ)rtv>sbUtC`@^814fpFb~^joQ)h3evP5UkOZj3kS}l@im7lKg zr7ewA7yY@Ba<&upxley8M*BfC{nZ31LmrfuIAopc;>+0DMUX&-9OX{chrK?34x-xd zTxgsgjr;yc0ke4o8L4Ii{^NFC8>=MMEC}Lrue<&^;0Gw zOsN{PfKWuA}ON^g~cDWs6ZMz@sjX<~%q|nP7bahZwEm2k)gWZTH^WisYyVQZ!}dFBo`p2# zdZ)P~*bb8x%uT8&RiS+5JWtd&6OS0KI#Hp6oc^3#m88bs#Z7SC+3gT`x>`{d@jIDE zuRBTw_<-xqgKOr=xbQ){36d{(9JC!PLTp+2=l*fI%U(NC>ODNfA=ow+`u4bsit~=O z+V3(gu!;3|##YXp&)1m^Qv+72p1)CfKP+_tc2MLMbjSn2WLOZC9VC%y=?MO+}>YnWaDDNpn1_YV&IKj*E;cO~{{&-Xd+ zEIn@TS@72nK5l2q{vEzB#jdHB5G~{0R*ja2X-*cPSiV_u5-xzvte{B;nN5xD*7!}z z$o<>pKK0=TS1fed9BbO*myslSY4eedDe9X`e2yaejGH;19;CSIsYRaocv8XA|a zN;RWO=!$)j|1q}kkkw^xW?dQ(Z9|IMMK8_ewVd-Qbq6Bv!{rkC>9%-d5k$JQ#( z>>ONR3r`JA2nWLXa&|1FqQ{!mAt zbBXAw>7ePp2NvE>Kl#PzsjPAmq);+7q6D08*Artwx~}V<5_cioG1RR{F{t~Y{lU>a zMSC`y*#*DjXc+;fDyhii+USDmXm&e=ODatA=*xBpD@zfVi!U;q>HE5ST=@rah?isJ zD3OehIZ1#JBs?}6YJ}i&^m#!&Q#RK($;=ZWT|b7yApH^ffRw!l>(34$efr#Om2@^! zfLbTyUuUR&_15KV-F! zMWIGxBhUHN@3xE-r6(-orA>FF0(A;RBopvJr&IRVWY*}wK}&pa-vjztW>n~CBBlsf zWt#ng16R|*mkY@bELTW3GMDsI!=@a)Fd&`R>_bk%gOIGbg3_`%)0zq1JDNyMvkS)a zV96iSabU^DlY4Wuo#?*}_Rf9#V#gAOt9mFCP{2<+Gt9HhQUPMpW~4g#&nQNcUk})t zD!$c4y2tmj3Tu=eu23IRzsDJH2o$&teFQF`jrE~0{)|tMEcXfyr2{7}_KLjS9*Nz? zJqcI%k$iOFNazXm>mRlHHYS>b$Z=2yu8n;lV|#i- z3|@Pr48+Upy??D?CpAkE{$6mlW|&CL6Y8*>W||@j4IJ$lSQhCEFQ3g(QQ`yzPL0|F~rHMQs`l>>N5eLhIqLV_?(t5tHT+G!(P`1LlG zF#BESHqmX`7Fet4@XQNQE|;^K3G!ko@NprGaFXRUXu-mx$q57Uqp2pgROv7tjJ2pd z_dG$!0@`(ggW_tfH8#~%wP@&&-z&{06t)E>IL*fzPv}PL*|i0|Td3==WW%!;(J^JC zf}Jw0O9$#*J|Ot(5kcIuv>|$)1Q2M2hB#7}Xr}s?=(eZKN5rD*X3#cs46I_#JavF@ zN_P$~;S@UWe+W*!hrx&r?u}3rFRt<{vD7-_eMbu};lQE*K|kd*8}I?~9#`t3+(oTgCPFeXBzP}Cw%~oxkDpBpKv^q~r04UFI~bv! zIaQNV(LC_;N=Jpz+pKHPV<)8(*v%v_!(Be5{;+bn)io3NLZ)9%+TAAxZc4R8#bOq_ zaIQ|Z(V%v89^TQL6a>OS8KJkt@Vc1s41rF z!W2I8_(t=CsXuQf^HcBg6GcEInSu&r5}-#eM?T{5Q`WXRTmH_|L<9B@fERoQ>mi-m z7_j-QA0zMmXUU+-SqM>C+V8I5I;mbiBv7k z^m%W>7#5kh3>nkPytskM9;S83H9(3I53ilcpX~Lw6l-$PGxN zr*R~!o_ZF?ueXldCsB*zn za5&yeO`n%aK%2JpoLe(|fmj?`1m6CLZQu%+<43yHXOQboZfkb?z#(JyyFnev1e^+! zsHuUD25?HLlC&$Y@IF6;*Z%In)Gwt&??;~(x<2L$V0-P2ZCmO1UF30>hXym)p=jMF zkxFnRCJ3Vbm9f~#nY)O?BNhxt{ssQJ&{BYFxQ&C z=JVg=kVT~g-yX9{9pt?_#XW@i*t?M$!om!cc9*my+|+{|L@ zD}s!S=lHZyhR!JEm=fBx6uvB^!xSgDfAtHL$-#|zT!oY-quSg!be5pU6irUs`uu|; zPtdH zZRYz&cCSrU4?#3d5s>KI#8(g`p*$XI!cX5Np38Q!xUU>)s#Nw1ddo<6Fq%Sp9E6m; z?w~a_Nbp+mzZHmO^$GFcHxM)RD;NLrN zN-P-wN-wFw$_MgB@X9CI&liNV!LKjdUQHU#pPusXpZW-!7dooZ(b31`(J@i>ezWMO zc~0?qD@ZI>`OW#$j|2@%*+2N%#Hf#wszl81hJ~1KW2t`r47o0yYn}ul6tQ9Lh(wE0 z#@v~&m2xUC6p#%tXv#%638p@djYV-$E&1_bT%&e-k@b|cGs7yISsI;Y{iyo*`w|e# zHNdiAwj&(x*NEqT6d;>XkIp%qNxR)T|=H->QpzY&Z@(( zrjC< zGJ4f5YRc&3!eCjS&oP3f=R^jg-F;RhOPs8^rUxI^{k>~CW zRi2Yb_K5Rp!U&p)9o1J@Yho%-so)J<<(|HiI0)CTXc+ zm#Anr)$smA^d{ty2j{Gj9{VHAN@EJM2@!F+bZ6Z3D<3fM-TE~2CM8S`_L`baanWmw zJb=Nh3OSOCbj*e&=;kL)##kZhr|SQV}v8( z>lwmM0jgeWI4N_HesuxJqX;IKZ~$QC4cHPZrD#$ z6rJ`VULytskf}#R`~?oq^7lFzzNhpVslt8tEvmRnXMX!(<#Fc(cpWn6ZI zC~^W=Z>LK)^}`S1;)q=|8Aa<*D*1WMstW+@MNv~oS05DW_GU>X2+_3#8q)0%0mpzp z`5q)FhH%?HuC7}-vTs$n2A=V+YA&iwKd=*>?sJRaJinOEjVrU3j=alITQ3)XY*yLU zUD%vyJ?Z#E3@PkoUQeKgO-a0#wLL#*Up9CgNx4iRMl=zobdIF$6e~!X`y}UrHMc`u zIFTfm$*i9B=8DU`K;xAcHQ(ly9>)qN{Rm-(3GP?7P;c=+gO^92fCH9pOLIBJ`S^;X zIdO~kE_H~N*iI{}db=0Gub#B-qIlr5MB?4|WcyF;3!SzTnQrtG5_z87FpbE6jFJ6m zK0un0)GhC5FRks`!%7Q@Gs|d~s^j*-dHuVLrc1NAsL?1%^-Cn)q7A(rU%JC6vPx4^ z9JjY~TZrTr^?_aPwQs>hK^)c}EwzR_Tb-zgkh&fdlqjx1n}vioX&%>jAZWT4*XcPb zlCJC-^}0U&f}+2Wssj`kUzlA4lg;0h9vLbPawSDp zv-RzIaTPhCo$j9=Rt!9$vSu^iYeCrT@#yN^8iJV{53( zrSY7sH^Fi{bgG%xS~)bIXN)qm#8nTWLVCHS4aa4xTUUo1xIxM~YKB{{{SuiXPcG}s z{4?-B1{%VpIRdvfVp7PNkH2}C@omOb<94paULe^|F-0q45o8FPW0X?$tuHqRuDTA@ zLJBvAK>J-C`!-tHn)^Zdi{lOseVQL6{3p7_nZkKYhd($d9lvNhMmT7(?8>*G8?offtxZ5{i2Y$b;AEPk)BA`|aa`X<2s z?MSh-SEizhaaGtb?*e8Q3oJpmiz`Z}$ zP58xwStpMO1RT8<2*C3`Y9xt($?O4jtJigcEed%l&7f5TI>ifufZ(O!{NNKP5udp~ zF06=f`y48OdGMWbIDmVuxCpLrUjzyEVF!h91eUUB0D^a+Rbgo(rIEF+n!2XP6C4Le z?eoNt?5P5$#;#8Ne4MhS%&(q;h*zapjD6b%e{NGT5NM~r3+htcQZwNY=h6Nr6M>JLq zBpXsL@h10|gDA1~i@%u>c~xL(X;_&v&G6&mosz5P(!nYHAV*(2uN&Oz9Nh8*j*5K# z$xy(Ck6sjXf(uCmrlwf_KM5~#CetYo7j^@3*A5a=Wy0DW5_|n6VUz{`gz&?TaR0{g z|F8L_SQdG208R0F`4&o-F%U8eN+~yx=l_=+t^fb=|6TUlPb-+4+vMon++2#u+*EZyK>m$ti`m@wB(QoEKMc2aOq9Xa_x~gBhjK89y zA|N?WlfwfP4_jM$N=m8aATL(5|Gu-fqM{urI0xM?2ET(Yn|S#6d6PqmT3XV~%b*V; zsPS8CsI8qsf%}XAhej>U1yYoo8^`zF3e>+M$`K)+2Wfyk^{eA9VE(+?@e^(f+O9j+ zlay?|sX>PDk+sE#3W5?fMjJ&@P?UzpswcaQTD4g3aW00cpr8=Gr{Hy_Y?)RA*iZ@` zdx$}EUdz*xMu6btIU&Da4oLRp!a9h-o`1&{&emUHZZ zy57Jd)7}}t2J^j@G=h-pcS)~ML^br;E+_;%Yq(l(NG~sYWBBe}aqTduGW5EbC(GO1 zs$RdoY`8u~U@Q1T$?f_GUm|~6af%`Vs|O@r;Gj>mbQn|vUhgMg`~L!Ug8nU>R#%tX zUaYx*!-&iC;slpb4HV)_;joJ!9m8R(88Kp~ovaO@<0*unpP!H>@E7P%=&#!bp_S@( zwEZS05lnn4e7!H63Z^fNAKz!WRDZsOt=e$0$hx_?Y5C=PS@>ELhaG$|#?2QqMZsRZ zY6NA^y*GW1SopU<%`UDksJOLH0gZ;~sODQ+TLX^()gz9o^J@qoIB1|Fer|xmo7h=W zQj+YtM$eMPibxNjumhD&EVQ7JAytRI`>wbmOT*gDkVd(QXF$@$+0_q$mZ1VI`%m?PJ%D@dG@_^OfP!>;H*|D##rLZN)GU#h z@Xwp}=YTa@@o>yI1iq)P=OP|?ets^aO#ljA7w$imA}&P>z3$mF(R#k)XWV z)MHx$LEgWNqBsWXL|M`&07;B=w{@rj^M6ULQowxRUc=R4QPpMg)d8&{0XFtvF$Zc; zw|GG)3=|KG8&b-~a|KOyLasr_@HIE?z282V_EDiQ&>amZG9}B!c(3P3u?vPH8iYgy z`R#=_;=^#U3X;t8B*kL(o=ETy1Sr!bbTa!Y=vJPM3yCR`;Q9shuCk8F&d})5M?tIix9e4x>MT>z? zagpQZ$rNNimOlsV9+f`=i7QroGLue z9j0+!&~;V@G`V4LbODu?pSv-lqM|^BJ)j2|ctZ(VzW_oQF;&&50VWm}dE)fFNJb-2 zdaNU^Pp67DpA`TswdXEq^Wbqf4CW6^21X056Z|oe%NJ&pZg4tPe;B$3JcNU=WM3&X zd`uC;l{?R-(fQs1e?C)d8(E!#SF;xSDZ_{PMHf@Ni9)Sin{*Hg5`y8X=< zKn#gt5fSriYt;sgx6eQ416BvXOY~LhRyo?gY2qVu8;fJ(lLA;`4ZK8ro-##$ekgpuaFFYKGSb{jG^6&EjMYg z<&tfu-K{b%5wvLCgCi8K_iv``0HL+CM}@3#2VySaja)Z>&aXx8S_jWe8^<~20sp>q zaNb^1{F02F1vsrv8<15E%h!o-HbHicGIv6LZlTB5${MdbqeoY^`x$K}{r8U@+GsaG zLYbAbbk>9IPB#1b)PaDq!ey_sCE^UzvfZ4;q59=YxlyA*@$>i!+y27U-(`uEo5DB3 zuw~rU%@SSF-`j}&wgU2ql}jF0xgW1SX`c5Ys*fI)os#MiB7zXbhPKaw)NlH$#pt}p z2{rH6@hZs6i80b|K%WjRknU4U5G{2nMmu_O*OdZf@_7kcJvSh$7)TtJ1-OcIP%~Cb z=F&yxoP5dLO@FAp_2h#g`ip`bJoGN?ALUeoQV~S^}!nW((8G+yuav z?lg$pORm=|a6F8Gjucz97o@)VGVUhbvTY*p^c}CJ_Nt#eDJR$Qy;^cU1#9(E-(Ph> zvsBW)->W&;km^fK8WEmWx-kN!Q7GOxsLa<%Rw=X*I-WBULOUZ-G@QANAVX# z826p-DgMA`kk=2O7ABh#%uQ$e5_L&v--wbL8ISq%;2QqpgdS?9 zT~jyrvY=y_UP^8ro8<&Udg9IY>$af=t0rN?{E0R(W`j>gzk%JoV9K2G9!UuTU<}_j zPsfk(#i`BOwZr@^afF>dO{NWejM&W!dAG^2%{t^H5?g#k#|`^odzW0seQ9KHqQ(94 zNb7BN`2)-4e^lq%=0wYeIU~FD*BkEK%@?-V-15Ej0ScWqR7QJo)b;K~@K<)U^D~mn zf{t8NYWYGYdk1#L6qac^dU~oze=}|wM zU$8ka8EjsdWh3x<_Ud$fcna2*KRwYxGgyfbC-w9gsTp+67j`6POEc?9q|3`bTSO1c zSp%tQRfq+oT?a^cs`@~1C5j{g7r{T86H4Y{%vB^jH;!xL2{x^Z zhhf&_>l-MHj@tS7tHj=7;=q%5X+4s<4-J=no>}GS9fq^dbGy43@jyzmAMh&P#!X63 zF;;rMhTm%+HK=_m`N-bq3%3T1>5?_nh4xynKFR3$%ooZnOm^DcB0SP>wt2bGj=@=C zK8zi5IVXX}S-i-|!3q$1k(TKI!0)cE0Elu0h)C6nf+OQ*?d09LpvgLiApP?sH*Um=N zyxu&QjS!?wsXi%^x(J7p)-IsZ%RXc|s)C09($&9dRx+P*f6vn=O$qV(Bj3V%CKL0C z{&nHE8Ht_(ZXvL(4vAxwD)ayPmio=H4B+CUO%gmx@3}sAjQ#Pt!jt3JO;=TmT|IU1 z#425i65<662LQvY@FfacvM&V=w{F)%l|Q&|7Eek}z3BjvU6X%wV_u!i1(>IYy0w-t zqU7jxESNEXLpxNZc{vT71>AStEpESf9Ma1J<2o3ua%X(EEE2@3uaMI60qb@8_UvQbLK|M|b<2sI_NeV4 zRMmc&!GHHLkfaUs$I|5g$HUG6Qs(+@6YMn399|Kv!>$-}hf>!T&uBHsHHE_Wb|gU=@_P7~d5-IL~30e8PLP z4<-r_!a8o$6_xDdHL}{Do`EVMgTYd5c+p}0X_|E?kNxC-|0+3b>Z=V{@)l)}MW=Rn z%lX?>MDpp3r1qE;DbeQTf(F#aMZFV*?_S@$g_C7hfw;(3av@qx#bG`_wWAJy(3Q2C zits?=$qG@F%+pfj4=0BX!jL<;y6+iMRz2$L_SkL0!?oqh7NfYUuDPSpI;S6NIyduE zWYy*_6^&x|uTt+%86^>o4w|!a>(#w!o%os3vVLMTEd26+l zk^yAEiceoz+j&Ofs?HMFLUZ%DL>Xo z%_PMJ&xyY?>lAPi2+iVjlz}7&isjs42_try>*tpV?kKj6u_{|I8v186`ZhMtnFJu` zYZpH+1{gAG(`(OIIan?99}fuKjMkEXu=JNRq6P^;u3PB(EE^S6bPNSwA9ON6h4j^> zFtUBoniag9)m9h$rWL17mglW<C+#NGZ@xLUu7UD&G zEErr4w2O+k{LJvw-WWPczy-8_L6#M{Y)+2H@mHm0bnv=F?fI4tE&_&X?I$i`<~{Vj zZfw632@SIhvjxN=D6C5Y=&z3w)8Z_ZqUZ)$T6h0EZZ)$fAT8j@2z<< zxQKUXsf3)owE8@EH$ycfNG+>DoEc+A{Ju;=&yx;POMUmZ=(mTlj#Ja@RP;Rdqm_b7 z|0pC#@HkfUd*{>j>jB}>V-3QK9jOw=*EE|sv0UWpS5CPNbPIOe^ktJf6Bx{EgXxGg z4(aTjp|ZC&ZXLx1hEu`{C5{CVmHF>`dRvkfqd9cbq8Ms?`gN!u6a78RjOCG-kB`$8 zbEke()=pg7FG*$XWPQP&vQIjl@xu@3;w7f!Qs~Nwb=GsO^_*!+6_S&#T{G)0eP|=T zIAMY}BpZlb#DB-mEt<#U%^N;^2w4N8Qcj%RZ#4B+$bavk+g6TSbXhyUPvTr`V)r`Y zOL2!gfURoL-qyI=;1BCeu*~NWx7sJe4-pX~~4wokdlG=EFI@ev(P; zj5(U_f<5A@>0C=^L(&PqoKZ;bvwf$T24?urE5NOK)gE5tnt1$8NR5eaAzVT%MMR))Nho)qU7XWZ;%W0aiQ`5?Ap zL;hEZE)c^^xpE!!gEO5y__W3x!;3RFded2_;Aci8TQF0St)LeoMd?^I{&Ifx!@DCH zMOT9CFK}Rb z6I!H_oN1((Y?L!=72=CE`Aa`Z(e1;5l+@-We_;&!$r6mPwxNW9N+@s0fVdMgG6{Jb zJ_#!53xagvCxz`FJl!vJH@mcOfE4quRm!x32ln+H;>*FNjzI3zZ`*5=Sp%2KcFym;E&E-^ zLQ6@Q!DCW?%(o&^uFEPUj<#FRzh_j9T#6B(rd&a|)K}Xsx5Q0fc&*KKc9->DNrZC! z(V%FVi?sLZKF9}j?j(ECMk(zwarXwKZ*GS3z}PpSGPgU;>q89!I`g<)X{{%pNv$_; zyCxk7YjOWuylg-s0trm)F{SYL#12>4k93*71su(n4XA>|zn+LSq+B7W=?If4OZiQ>X;zk(!@(uv-JkQ&3tq&d zd+uEjE#=-1j|@d19oRliF#PpmjVU3Le19JM*8^TEhj_W^4$;hh`R0o({n+7~U_VRr zX+M}czP9CL>nywAD)xh&c)JTOTdZmQ=tI0jss1?UgF>E(RU|h3!C{uR?M6c+Nt}{M z;vh@;iD_*ajZ*1_LDPFH7xHp{%=hP~agrlJli0M!TxQNwh2HtUeU13I)~J}uhBl?Q zq+&Eovt~DgvTV4|O#H)Bw9|4->YQCM=feg^)x)hG`#d%qZO+H!)=_u;UAy#zYf!MA zgW}p%;%Qyi&l3v1rB~MLA+b#)N_yIaT)Qttq>R|=$Z19P>m36|j^L%>?on(>7<2M* zzMUGk%u7*^MxacEEv6}FXs^0KkToJfd$+k9ZOL9BUVc$MLS~6nj-@E~)c7TP6}4)z zO@WO<5CU=4&XljS7|Z1@ zxlzGytn1_O#p_e|JKpaar8;Yh{PV9U#}`R+(u==yzO4V+98B}xvvl>dMHhrtQ(bko zgE5DqNNx;Y_*@EtF;|7nC;Cq2>`CzstRtt`okicB{ZsvA2k&QV7hidF2Og>zozvYV zBC1Oaq1DrjG6~YM5>EkJd8BC+cTT9hireiHS~A&U{)81Qy_%Z*)ZBW;HDQU`?|c;0 zMISpD^Pr0JyXVMNsTC^x7*+XCCRPksL7xz-7;FT+8n^7kO95k@9Oh0~^gq_?SPw5r zQG@c^&_#grhiK;$<08FN}A_^6g#NeMJU}p1TV0q|BTc?U!CiXDoAs-PJj4 zUvgcIPrxQCuNw)Un7a9U%Il6Y=vzb#lb zjOtZw!V7yDmjy_IRcWE;%wlqXe58?txD6?Ha|O` z;*y$<1>d&f*&S%&vH7LC{)J;n@QX6{MGmrWkvN0xlOAD3@?sK#S_Fx26i~W}1dn_L@#WvK!Tx36I^ zO7eoiEOaX`J^_6kE{a_^`kLQl!YJ{t+#rr|qX+R4gHsww9AOcvD&l3FTFX%I+09l& zlp`QWbORS`ArbQ`p5s&R7K~$hKokR; z#hq(%Mkt-zT*W3~o%Y9?3YbDIejpkO&;6hInSdH z{(o`zmT`IYUcYBs3T>f4ad&rjcXxM+LveRXDGtS@xVsgnSaFIw#mj}eyG^+N_jBes zXU@!L=Iy+Ly{}|vOOmy+R(@ZSa*XGhV3Ty@tmVA2Ui#hL-k-YLCOES1y-hCJszjm( z++yLR{1!H(>6il%lPAv|O81XwC}O$JdlHG5>ydD+3H%{PzTAj~!BUml^#9THc2on| zx){qkI5F&Z22bbF5yfBW{8~m%)nhoZ(ga?9V4Y}B)AVNKG%uE*MlAK{2|*(pVYySS z?QRtA)$JA*Av$?yR$%af=HEwuCf%F{p|(*&T!W2Xqe(b-lpS-lyUxEAR-TkhC$@C! zkaA|5rQsXtQXgWc88hm?Qk%hWH2X>6AaH40SJMSrTPYCAcp z#@|Vnh%>p6VrA>=ZE>Y8njm?e30`~fqmsKb(^`*)QEP+ z!Mh=K?FmdS?wTRPw382?UeYBrHpWTn%Oz_l*I-7M zxbhkNlbXEf?&cHU_)bON(!99aoS+zQu<}Blp!$T{MJDiNZ^n-5Y1gBt%%u*}syDw% znS2wfLFF~NbW8Ag3^8@9?6S#745_|whQ1=z3^0BFb7x?*i+3g~Hj9LfG0?_~)x`a- zu5F4NkfgB`ParB5pmL%Z133lC*|c@_WUIbpS8T;PGGvcCi8!5u-Q%TfGH4P`dZMq{zy%l$|FWL&N1U=iypqGp1KFq!x3?NS70z=kdvqZZFI& zk@{P6>d|0WX-f4B%jw+S>(!}*iP+x#`M?Q~zE{0Sl5n)oftfHIh;l_iie1S0`h4{> z_fZY6?ptl%kui`-F3XktAz}G1mZmd37miw%F4yPog@4v$YBMG_>+((V|A4WveF3ME z_dwLf@*JZ9*Lpgb??NA)-(<}tEa!2wUfi#pBjfTZU_ z8{r(`*|Y$e-bf_k&nxtF(@?&?zH@VPfSY3l-6-;0yom(apvB!mS$X2|;cgn1&wgV7 z&2uFuH+LG=J{SbaGk3s6)SeGOg#tDD<>mk7&q>r!pgFq@Xayh|cXo9} zaRx$FmN?cd%s@;$-wM!#Kvb`w1gLP1%>rrf>`FVVCvP91w&>Fe0%xXiK(+H|vFQxJ zQEU4^oDz@;jerEXbSjXqKE-c;xLg2g!T3C`L$kAPAmE`YUk9Y%3rfDTm`}_CMb>B1 z&9t)J8w6Y+R~|Hx@E52p7Y1-SK)Sg|KzPG>eX4z;YzNt(+T| z0~iq{r5Fp@YVuDSYHCzAfWR9+3FQ%43Nj7iGBX*pV=syTIWw`1+$LrQKtsjoga}^s zUps1a1DQ}>KEAp;GFVvHM0!1H4%?sI47!bmjQFwP;o%b=mX=iM(^K#fmEoR=I@l3* zoatY;2{#h^q6iA9xs!ozLsiWjlXi?R3e zYUh&%&sgD)+10;+D0O^*HO@b`NbJDOc^BiG2^7{X?j^+$-o0CempVMPHK7|Uc23x$yvgg2Bs#zaCwI;B8PbXQhJ z&g8cg;T9Ng+qTK`y-p~|&mWiH*-#Xy<+>8-V>ocaOqaRt6OxdGa^)t&<#v`#`vf@d z8|l^EfX!U&$1Ul(vK^UU9u^+n#D|wK<%R>?iRVS2Gt4y7Mi2vQ@&+R1yv60#gFlH# zfKc*#76dyfCYW3Ua-e3~z{*8IFAsIjL-nlf%f!dGWT+bZAuO6xD8K^$#e~CY@fR{% zMo&QbJjp2qY@O<*?fg*T`7j(HP_B*23^D!+q~9rPRUt5I_WBtCK!q2#on@W>CIBw| zXQLv&9@4t!C2jg2Q6iZ|!~2&f#d`~O?Hlkrfr?z|)bR>p&TZ>*0AABecJ4&Qz)*tGi zjW?*)K#1L6z$gx>zoZPgdK8EX=gWN>lTIt^K+j@oo~lX=gu^)P7?Vtt`Pyx1Kx>PY z$a$d6L*ij3_!X_zRDx&hCU5qzz9v3$eNu9o z*~!HP8>RvbWHKtc(b_dADte??nyZ+dlQVgrmVB-W#WNSMcxJ0?h84b!LQWO5B)x$| zFMKLu?kK(PbiZ1#`m6t#cV~hT{LE2UEaK%X`hOwA2{#Y`d9dg5`1&Tws9sc@<^*8a z&w|e946HO20EB2dcJNw<$N@Wu!&(zcChfjL{XNd+?(P5%HB|3W+nxNQ`mY++bK4MP z88Z1zHZD#%d3kopI2>>NyvfktmZsRB&OV0k1q(yw!+$TLwfz)ZnD#5uD$SWd1`+ZBN!)`)VVvPnSivuyRJ3I>^v!}iXU`q_C~9G28= zj_4*GxMveGW^F^28rzmFHmyZ2ti(D06ciEh-j;#1LNA-MOdbNXjjaN)LMOkQf#Lq- zOK;eD&vqToHn$Ix(;sQ09{U;|7Q0p#QM(u>Qi_6Ob#5Z8lfw*9l6FJNWc5t3= z+TB2G6?zuNs@$YKPU-6E+A{A?;XQm&na~KR7Y$oH&bHxbWb!F%$OZ-m2M3Jz zj`Hc;WYV+=jn#Py9{}zmdikUN2TNN|?LiwddKQ~`R_>Mgq-TJ%cRq-@{o zHPrOjo@qV~35$RYy8J-rPa(dE0bA#e~!VziCoPzu7fX8(^;{;kMFYprL1nkxMbzkaR4pAP zNbMxBa-z_P$-?7q8rAge@xx~*Mt_YSL|UHEB^k&#iYTO0z*ZPJ|vAqGtp8rX{Z z8vb669yu~3WQ;!}y7w=nmO2ZH-~Bj?ITi6gT_?DgY-FtRq26X;F>-&tP6kV0;)qM? zyKnH#yL@fQMw!Td4I!z+=TuBvpo(8Ulsws>D#>2B6eFQtrkUNE8HhUMlhSf1t)4^e z0V5)qxDJoTH~uM)iL<>I@TkBnu;O!t*MJd6H$~?|vsr*6B(0Qs^af_=TB?sSgS z_xpD!q8YHR$z2p|wi&z0B;r68r#T?hE+V>6@^gLvsz08==T$_O-mon}B9N~vC?U+l z=5c@i>L!xe<72(rKg*%ZuR{YiiDYnV@t+4|!jl;&WV?UtH9<%@NSSB&{I!(F~;I8l6VM8xMZ!olz4GI zl>f8cshr`tAOC-MSSU|`nO!30`D1k;IzYa39 z3e~s$udm%bS3pwYRM=%7FjB7z{OWRypRKLUZ<1+GWuss~0DestMkw$v;+_z2NtgM- zlW=JyLEaL$1A-~k5X5mI$N|dp=qS0UOn7n$Qj(s763;ywn8H`gki)}@R>e&KZJq1~ zxPW+SBao4$+YHq1fr&hcL3;woYPa(}Jz`70#!mplpoIqj!)zAni>G_TU2+s4-JvK` z?m#kMcMwQyxzPCS?e5O^cMOiH*Gl~ObgORj5r{QJ1tSum`%L_xg1-(N^D2sG0Rt@r z<~)$H#U}H-02n+=K-TZmDSCaU%t8(N^7y6O(9VIrXJRhk&->fq{~v z{}asop?CQ&BVl1do!MhX)rG4>7UuVIu5J`XaDuPL1;e#xpdu z5&sQW1Wk8Fka(el%@| zy?y_Fo%&Q8z#b1cfZq|7mNogA7Z!L>Qt^vWt=`=7yX<=0Z=inCyTv>EA@2%xF2HK(TyF~lFqxCy#b`0^>i|6gfV#h-kR1M z2M5C&&W4T}yub1uH>h5D=CfX|U!MML4Mi>_A=*%q*%f_s24DL~xOiu0XX96S7u`+* z5|eGQlb6x5cyv3H^9TgNUune}v4th*cFM zP%gglxRTTCB~`M#`}v$${FvZ^Aua|@oYMK`;D4gLz+kletRF~;`WhMVBu$iiROf$pMfpwmpj7-c~9-GYrVNCiSbi|f- zQo2jukupl9SVz#7a{!=JZObA)=wACTRg zoKVsk0a*h^W!89SX-zJ^bOp>I|M(^tEXa{0qb!W9DaJ@2fZ?HRg;n0&bMdn`JL-5* zUvhDBQpK70ZvY3EF`;W7*lwq++w4lm!V(v}3_Bdh37^)(CMe|V%kLvQPG4ryI-x^h z7Q8J2lc0#`SW|EXqV|hhHb}Ed!#?YdWpRz(dMRLO`@d}I{v>e-Rb%8(tREi-KZJB z@VWar82yhyZ}5Qvrvxps+GS5Y1O3CARvd{bxPLW8r#L8B_4r0kzcF>db_1V89EFDt zrNeiCSb=%{I>dqXyQ-F~oDcRc`s(q0YzyxqY2-P$UGJYcCvB9LxRNaw&vPsz#mVup zeSm|#x#MASZ)hIkfm{&pSZ)mM!^j!39X6enhK~i=;pS#9kR9 za*ZDAx#~ciaJZ6hiIQpFR;QO@On{U-?#xPm%7VGV=Ls+;#_$uhL0LOKDe;^u+$I*B zqx2wem-?yYA7kOoOh;O`LOOR3yzC=2tm)X6r;_C3hHwTY@IMhNIc3Ln4cAKpPbWl} zya?&_1^`a@m|Q(mKHKj`hugAP4iC@LaHQ^ZqLo}aEvaKnOYx5K)dBH`Annsw{Aw-( z4Mp`Mt*JuID9TEAq`*@ei8p`H#IG7NDVTA_em=4@AnOmd~aHe+bZi|{Il#D zT)(Vu!ODed8C|(VxramZWS}!P7I}l*J^U86ls#b&-TR2l2UD3I?E2t2Q%I9XHj;-y zw&VNPGZ}ZEg+a{US*faGoFIdLFI87&h>hJW^C?m#f(EWQdXWNFDtupMSBQSAFg{ws zg62nb8q69np}bX95BK)G*87nhE8O!!y?Jub$ndeT2gsaDvJlaoGqRGT?ue7d2UqX< zH!8qB-oBqY?v3Wm*sXf!AH^0=ZBI)olhv77)4GsYTNOP0?@t2t-?E`^5pgetrLGlo zU^h*^xCgoA5GJTp!KXY7r)n3BIdKA>jR8U#2beB-<>kV%u5S=5A^Q}N2BBFT6n z%e;JzenEwAVa({8gS>HmXgN!T9r05Gz3U%S5}{0F4RoZD(r8Lcla%CSNvQSyeo5sj zW(u#{b4vyia)NW|T(OD-4Cculf0&CY91nvgqHU024}2(qMylmOW?~!T4-7!~e8+lI ztLbZBaW5oP1@=WPEAo?#&7Na}{Tdcg=ggMY@*JXBF2s%h9U#`gVFYn+icUX|f zeF64@fPC~8zAC?`$8;XI*p8dsoYOMbG0y+k*_WR+>5UlmS!b5Tps}Be%SlTQJlP#B zrsn*q?2X&8Cd2vz` z-*@WboIN+O7TMZbk4{e=U0m4Yg4+IQh?;9l?K4KqXwG#6}f)Wemqiya+pw&LqZ1& zHc4E^H=!b>LJ$s9SJz8mNIN+>S$00Lfoa`KpBXGU7c7C_XbW|A&QHOfd$K2{%v*jBb!2{G>Y`p=E;eJsl~)K*jBTV_Wk>~ z-$ZTff|Ra}$`Kn^Zfb^?QigBgIwG|#%by4JU}r0MRaOzA|P&H|z_9W}5NY)nnX ziU9^%75Fu=yw{HpUJP<`Q!mtz!kMQ=+>VyPQl5LOuwRK^>>qoT&_RB{g9H0yj*X^? zxN}(zl!`Q_Pe}`f^&Ah8E7F_abMo=wwRb7-RW&tX87C~$KJ|nS4$8Dj9~BhN!zzCnUuK_`@E8jeO1X0@0RYuM+ z#v$yq`sx+DUS@{D7e#p=*Fx z@m;J1-FC;Wt02B88N@xnt02Kn_R6p0U>`1zt)9id(?X*Bek-{Lw$LS(gsG-4Xq7AD zMt(oY&Ea4YcV^?<1QdhX6nXoKIDg?J{w>gws$T~huwR!(E; z58Wi3Uf6NIZxm0eF5Gx#of6p5J4ANKbyp`OW$jMM)3|>8*nwch_-PAsRB!kaA;Q0# ztRkt%L`+Vlz61cF0Zi9DtfyY|W{9+8 zdAC=#1vdwWdEi^bUp}Pz>6Y&TD{M9WH2kzN!G__sj1z)=pYb5zggXEPlWe{nUS_=2AUlM5dc2k_*Fa?|JE?<%29d+dPEm+suy~(x)O|kZdZFQ> zFa!<`?s9&f5$GwX719Q6zFcDTvJahgJ(44-V{U9~@^FQC1yNiYhokMyDCIP|K!RuDoO6f(roXb85SNfBA9zAjg1HX_d zY26q?+fqg_mOHz;(lT z8*xAm-BUSHfNmEKc;OW))#BPj`x3m3oeixs!-H=X{1!#qIHW%}{*G7vmQ;iABOhIa z$aTITt3cilxvv9Jz}nipj^e?=bziv9JDcTs{E)JW3hfd7QDV!Y1v;L|N8FI^hePKE zUJU5*n;VaIvHez7%FsK>MfOt;V34xrBy|ItbMuCN%x-H3|Mf;~W8-z^X?Dih+1W0& zkQxL`mzSME1}EHtp$lni5J(QTKzv_{CtRo-H(qJcPpn)@iGqcNg^8=<<0^?5lc-=P zd;2juuZidDwoY7JxCm@tCngI?>1Gl)i|ICh)(5+KEkqeY@0RMdOLwIowQQBY;S~9> z>AuXOyn51pH|?dBNVgmppA7kg!YVo)qM5O@xL8XP5ZkzJbTKo~qd-J|#~$A(U9aSQ zfOX@;o8Bm1JQFnEW2$h0zN`wd3f+kMv|X`dwN2fLhm?tXU(g+18ni<;ouwZi0HdW3 zx%?Z}{Stc|^hi&e?Yx(Ur;@d#M!D8f0}PIO-|HE~N_^}u+_Mij_tlC0=~yX@1##|V zG^eXRif;!M8pDt|e7sX&oo~%JIhs7jfnq{ni+4v#6pEGjqgKr)z3X;h9gwaayB_cN z(1ArzxS|T_QJacr*19Jbb-6w|(YbEBSV?j}#T4q!bO}o(kKq&~@sw(L+pw8u{a~;> zl~5*g0@&=|BtoydqECK!Q7v1O)$~dJWim82?i$YNe0V2_mzkC}g%o)xceu1?gq_v> zK%PNeC%$WtZnsE-r8c1psV=uk)Zm;N^8&j?$H2X@#4( z?uI0nDGc|BRkEba1b7p1Q6 zi|45->hrrJ2*ucV5O2>Q;l>PhY0J^m<32_UEmHMMSv+3N$TRb;JiWQS3VyE$p{Dj# z2_vc5KDS{;BG9&^KgqeL_n&8pt%`joQ8*cD0EZUyVJ$sWM{=={IcPXD)~#c__=eog zV2mK7n@!ItilV5*h+cJQN?KBP#4+EE5&rCV$k))E$E7Jw5ohCHHg>|Z0m0(6n zird}Zp3q?b=&0``X_4B6`uAd?UxE;|)M4eoox0hACWX zg;YKk-B#IR+%@-Yu4Rl_0;M?XUPMlom=!Du-hyL9FO=_fMuVVVyhs7yTO@GA&Cbz;!O7Iw+0xeBiQe7Dx>j?+ zae@7V&ynntP}deU1e#Qg%`phIrx%~+MD+I?K~mamqL&CSZe!)2TH&H=X|YB)f1m&U zo3T`bq3gQQaCDgJ=_vCxFO3fGC~{BsEsclRob8VHjjrt0h$!_CwXTzR;s{jCNOR4K zpO;rdv?!Hg97>j<<9NJ`d8KM|@~}x01$?5NyA0-M4qaBwj>12~*tBh~2es>R;b=<} zDJ8+*kAB9mj2-7szN^QiEP$tjPP!MjjDv1($|l2lj6jF5`Fh2rCFzMIGvk|onfd@_ z?x>){Rm~qlhE`{-!|5G z?h(gdFUfI5|E8^!>B;^s0+QoQB+=M=OfgQyp>+|FLkVK7_5xBqXQ_r);g7%IFDwRn zZoWDAH?Vz$a``T3g@Tgq1XFmAvo*YKJC3Z>sosU=pl4q2vKZgWF%pX^BK6Bm*xRv| zp1PUS7$}0q3Jz@rdOQukq;%C5F60iZ*AS(3%b4uMt^M$dXr+0SvdZ-s^t}{1B!xV2 zl^xM7Zx}s&mDXy$L6~+@3>^f6kJ0P`uKFyo z4s@t@EMYZg6NR!P&Y^5QyZ1%JZ0$8@jvHSNI6xWseIDdnSNU>N&_i~u?NIQ#Qld4? z@2?efWz6Tn37vhKA9HJo>wG_Wy#_Iq;b0UT9h(WS>`^7f&|1FHm1BFiJ9}f0rF=BY zZPZz`ufWt;*XDir!f8?GT%dn+Cu;4OoIa1N&UeST%9Z$yRU+sCrA#B8wC5IC;8^~G zjHSQ8Z}~zfz>^_8m*Or=L)1=|VtenXYEI9IXRHcjfa1=_WT7#c_glNa^s3KZksK(2 zYZOGq9PWC6L-J;L(OJ6{bsXaDYau06wfookUSVYDC9T!Gz7kO*r#V^+O#80&rL$Ld zgU06T-5v@~soau1GsZJCko-){P)$s!TfU`E>X>V2Dh4Z%s`KEU zjgvbRHq>Q;rx`PK#mlom-5hov zpTq(f!|o(DP=zQpDL&CKOU%t{bHoz~)Da^&*G>HtVEVFDug*G>GgWse+=AfPUPW|9 zDSP0OItnFFGeZ~}@CUwJa&@bTZFdUs{?9JOh0=#Yb<&H|rfG7Kk102<>-l#z1B;Z{ z8BHj!tpdudOK}7Hv2_mLeDZQ}{JMF$yUui~G4pmt3G+_9{(|>XgeNwewu(3~Ts`#S z5fxwBcn^y%Iw^Pt8}%((6I-Sc8kzrvDw%GZO6(AwS8*gBVb(tn-pUv95#l5tz+%#g|H2J`F(aB2Uf|ffg$-Zw zcN_blPW2uGBTCnEiWI`o(OfS9w4(NOx{4q~teVON^p-3GrVUFG>hH~#C=~ljq8d7< zb`AfWgz!va4yDyF9{LRyw&_E+P2wsg9AdRa zyqH-|&%oUxbFcuWLxwv-DEG z?@MYJYd7(k6;*W_9|1#WAts6R?teza28jrfxZb2i3l zlgZ&OahfP!J7n>?i)TAfG5NG*LLk?3J(E|LVwGF6AP|Q_C7z;(Rpii0`>R8cvc(^d z#77?8m6J?Z^Cuwa^z;Pv;+fz4e-7G8Uj}WDxnI1{lz@JL_&)}1XAf)Be}?RRpW(PQ zN!OJnq(=fwpIC%eIpq5^#mX-Re=?4T_AP#zUquir>7|*)kj2)q@f9v9{lUP)%?_7O z?Bc&}A+p?`uF!bID0w1*3Vr>Kkj39SG??MkrAOAyzk9Iz?vc-=?Y^}A@zHnkHnLfu z%xz(ofm`5HyG7H&hHc$)o8JRu!R>NzJbY$#+gfA9tii>g{dyZ;|Ndh1Y0dAtv~U!m zFAC>s?DVZQV&)QqL3<+)J^{bi#coxL(@}cYs4L>(%-ykTL%Z9z<;uIO%ER=B;J)Cb z!KH@)C*74Yj=i-$jU`vF<1cY@dx~>Q%N0q7-VNR@ZVeKyPo=HRCzOZv!Oa(4M0uBa zA_tZY27LQ&GFSG;__w|&W6f?e3n<^^6$qW%^$*>Gmkv(Mm%EO!n3R_f(%J83A2wyP z5J|?+-o8hmj}Xm_b9cFRKk|LbacRHbKf793T5g(ik8hE%@~E+5>SXXelya)m8ClZV za&Y3kZa-{aJ~Ozn+h_1<;KSEe$m9y%@O&6a4V2jRJy7ytnZuu9Xi?DJvQNXEkw5Q( zZr}9t-SSiN+&Z<7(9NjO*u$v27eW+pCwT0gYw;aF2+}ZnBsH34AG!2#w|TlgO|{F7 z^LXf7YP-KI_uJ{sy}J#p@w;(_tXzi)=p4JPhZ4lL|L`YB+enFATg2B>(BM-$3ALSR zvvdv)I8L&|_aWzr>cb=WiuCr8KgNfQaY^N)LiP%b>xR$nvhNQaUwaL^sO2}6230>= z!dHC3Dog8vSN+*-kNXfqo6FIx-ArjhbELO}SA%|2dz(|k3jX5s&F#t`*{7SpjYEgM zhXricWMXTC(w-}stBwZpqsj8M!F8Iapvb3#z6%d~*SnRVZf;%V>boP|J~ygz`PkQ~ z{2`*V?Q0)Aw7G`+x@&6e{9&pGu!}Ti1RttljDi$<1-q(YuoKumkf^K+?i~y~@H2`! zVES93;{~#P*`bVpmzDS^W5^Aw@^Vs1^HuUU;qeYkWo%FBssixGcMs=zGSoK z`tay*)N$5=;?~gaz=5x5%T<~nff)05nUDVv4VP(rr`2okaOq&8N7b4)hacJ6hTzEV zgY#L3N0j*BQbqI}hO(#wEJfhu@mabZ>8IEEZElHodXL2ixU2xTaT}ef!5=eo?oi_p zW8o8+YZRgGYyGQ#Js2iJ7U*B}ufKso`u_1ph>8dVwJ@lgQ64_j&fnMQBoZ9J0S7qb z=>%4m%cF2MA-P%-678-W3;Xv2?uQ;NdhILR`mdq|7LQXb_db~)wXET6Gi;}OaA;c^-q$1%07jK zo&E99LSJg`lm!+UU9QibHGaTX_VzlizI$v}_7`5Q0$Jbv(`diI_-z!{v~e==u6QKg zP`DN(tdzy-zjN_SBkiQ+Xx?~(%W=QC7SMcUI2XJazrSQw)hsxhL%w24 zJkEU6^U#&0Z^hq0mLhsXdTAy)K5_Nc1` zs7(|W&k)3+?T-QsIRlKIZ^^jCk1*~oVC66)FcV2JnP{09jWn4JyA`3M{Q~8`YCf8B zBN^Sy?!NAhR{s2!iIz_U{z=l#gSfluJOZg}f<^qj%qS?uSF=fXbvYyRh%qNn** zq0|%wI4AwLXiDZaa|h8@pSC`C9Y&xc+^r2Q{E`2*sB-POM!VF!WVUgLHk#Kq+cjtx zV1xC0r^eDEfv;V&y`86ic{#o0L5VrQbu*pFFls50FDqgzz+NDHzn)=f`u2oBt*G(4 zk>SqD5G%K<6T{u66FE|IO{GU6=O*FZh*KhNvs2OYKaywiW`#d^Cig!c?h8nweM~M8 zpol0`Z2Opko^G3nkXj&F$YV;)DLa{yTwF^&nEzBS82>F}xaeq>1^?pV1C6qZe{x(( zSiN4cR36qJjzhZ{Uz1=?Ire4C3sFbTaI$gjj59lYH=!l|%T6gjmDw$QNG)F@9TlF; zQ;*uvV9`y{f$bD&?ZdZ}uk}#Mabu^V0}Dd*QgUJo9K})=JXCD&sv3|#8|Fv*HdC=znn+p@ai_V5^z`^>Tnl+%0 zzR3t$oSYqYl?qzi)2%2*{qGI^SL^=0{Cl;=rT+hRHl`r+9xdPMyU!c_3)PB0vwki; z6c=*NG;Z`d@c!ud>OO+%1?|vL#G>YP60tL$yz|`J|KRLSM9A-*3?R z1S`aQjugnq?tRfH5XPRBscG+zd`Ps5_}zSGxrx!_Wv{J3sW_H)ttdw{d`gVUT~0V* ze}>YyDC1O&#JxN3$uOKNPFWjZmsr--SHmBRCeMCr1Kt3Z8< z|Na6qRGZM49@O~*l()<=E8-%zivCK{&=p(NA6fMow+U%8jf4X`E5Cm!$bw3^;{#o2 zHLNLuh>TKpG1a!Tf}D^>gcU5I2lek?(52+iiB&0}bssIH$gP}2)Kor)VFx9~=LBWv zt15MkDVIE-p|ir&jtvFJO>)@XMy$Y{{okDq$|n0>8dRfo^A0Kb3|Lcuxp>5nYU2m1}7EFPSzOUxoU-I(i| z^y&U5BYPVR*5expN9rV$)WsgT;UTSiNdDP}`!!mt@aDmyqJU2YIfo^t!%a4nd&gx= z`DJTI!7SY?c!={h6Z~{UTEV-h~h~OZ$y%{(=f@QssLVR9l9@@0qh9zf9@gT`Oui zO+ydP3lu04k%}v)qmf2AIk$xqY`OE-jz5`ra@R81?(FGK9Ma?uW1L_~^9;%DQQK4u zCQq1=hR4(wJHL_MIqm+X_;dBk+)wGGJ^BJ?tlv4-T$%8Q3Cf!eeToCCkjbCY-}e{` z;=2|bgOu^9@`s#oMW~{z=uIoSl9fRP|9l3%seG`Ej>4ooQmJhVlsP|w?vN`+3h%745w7#g1o^E5joiwv}#}ZK&u1=Lz z4})*V?*F0H&wC_#2Bq>88b#OV=yhJpsQIDUSw4!l*HzYI31Y_=1sqTpvl z=*~!^yld%c`=A!l-95G3TVMVI?}|OgpvqIpTR%#=D(6q?IY|?2-8aCH%OD#w^EeL? zhYhCCvqFp2G}9Fh7cyt9Ge$o3e&Z$f_W{p$)UWA-drRoJa-EQyjP;<3Hpo7;~#K{m()qd-PeSc6u9WL+HQLJ*; z?NMAz6IPLIEF(G|7@h+cCwAT=Zfuf$Goz$|o|s5W+elj`6Cdc%w*6KY;Sx`#saVU} z-c}VCVdW~|{;bQn=aFBC=~C@UQOy@-9FhOe)zc~oT)XU1&A0*i>caxQOM%nVt2Jt! z1&Ca9x%Eeus|QXX>H4y?9Od2pkNU^)rKpi02?}`*XhP8oNMt44yfjIxNFt)cs|<9UlaBgw>jUqJ9Y`R zYCB91NE^*y)O2FdL1$-ta~$6IrY>Eak{7;PM_QRfKppn(;uvo4@6+nfoT6J3Pgx;7 zZe0WEARd}==zF)({ilnYCqm9IrilYnlSosV6|kuodjZx=yG_lmDI4LCAJ^}N(D8KY zP-adhzi?ku*##Ez5`4?J{LGZzqSoFuPFF2Uo_=G2@8wRkx0AN4ZHP8xOZN5(d>SJ) zQY(TFPft2~lTo7rd`~BnNBj>KhUE=f5lRj(srxIg!$>^2>nbQkc&LS9+l*?iCvnj9 ze{YA`kxi^yf62YUu7Hr)*Z$6xBT;}^ zM%7nem($Cast~XHv`Cya@x)EfZRE1Gkgnrszd89wIZc~`5fbkb&ZfIXDNj|`#`~}r z`qv^lVZril?|b;13-R2qj56nu);PU(ch=0-&BnkMu zZH4pjqq(faIN%Ik7?;6h7!D>?g7%8a4~yUU$C$= zraC&o0P7n*{;IsU=}05x+vtbs8yW3zVY6hXxdOi@WfFU;B2AVVtb1hq`u!t!;nL+b zTQq!)G12eUR<^Zm9_5-;x23(B^A*!k$+_s zK2fP5ju6xlY>9s`Xh|!Y5UrP(GKUT|c>{H7RnnHodTVc4gwe|`kKN+tl|L2AC;8G_J=x$SRSFe z#xig#?K1;}kI3e@pI(2p%o(+Od`;}mlRo;|8)gjq>bsiYSCi05+-*CgBnI^>V2ZzghY?iIST-i;u0}WOussNK><5&@)GHZC1tlA8~*Vx1_6^t404Xd}z6AtzkDSkoKoDUHKY5s^u1kTODa>3k%J@m!`lukb|^D zwczZ`_u!0cj70tGF=sccM6`KGc~1X;WaD^I!UwfqTV(CkwDC(yE46xOu7}Y5gsb85 zB4lZ@klWv0$9rwtBlm{w4u$sUdBSBl!Fiq@%BH=$!(pLpt+ECAM3Q;7yeo4z3C#k` zMp%#}Sh&o}4DTa(rB6)`=~qfF)%!$##k~Ldq+p$EZO3`ueCyQ!e)k_Z^Vn7h zuSCFx#kis|?SPYHb)aP8DXvYMaObt*kK`)_$Ectar@u|Ryl>*3>s-`f7}=P89Y&UFp2u_cP@!}L{IMOogIqlUco)uVe)};G{yxpVV%oc#faoI zEs!m_4(%eX<_t8^7_l!Pqf;;F71MF&cauua(Q!jAZPqEw@KfAaYogeEUWwGo&y2RC z7_D3?uZbcu>sC|Fa8+T1PI0%?=e1VM18>U+U0hLY$tgLTi&dCk%@>A=R$uE?P_ll? zWFSK}NqvywABn8FKlW|+I>&uaQ54ZVf0<#ZYk0)erl(0?Jd)IC?rhj}cPo|HuL%1tO2 zRDr<;sz3r&Y=yj=>2W&>3i6BOBGyVsR8#sU`Ryk@wVL%e$!d{UJW7dLXGc+czhIgn zB!8a%@sPwHP95;dHC!@c?iSqL z9TIE^Fa&pZcXxMpCj_^k!6Cuj9RdUmZow1e8?yH~JIOik?|bjx_ck&!Yr3ANs@CdW z)z#hAe47~>5w_3{^L=gD!T*!Nl#>r~AWA*F67XBA)>{$ya(rz^O^oVIUxk}V6V`zFVBb}J_x8)3 zUh-OtAS>W(Wai3pEJX%m>Ab^db+lN?tWAMSQEbM$CpeFYbNV$y9?qKuLn!t<6~YuQ zaW^dYs;TX7qGFbEstu#Lq@N2P-eRtrEA{O)IC_pI0yx1*=2RzI3(eBX%>WbIVOduyGFNI`EQ@PyJ9gCW-9=jzOI^sAG1nLRZ4NHd+ERw9KAVc; zFD7;n6Y_)%T}Hx@f1TGfiyq@q3s)M?NR-}ti=k-e0!2X;qa21RV*tMzW{wzLh6X10e@YTtQt1(JK+D-AK44MmF% zQ)NJ=jMCyw-LVuUkG^(&wIl>Sj2qNvxb{*Z_N@p`hvrNo{yw0{y}X}aO&i2pMa>_k z$bPt=JleUdtz$409lJi6XQ|9SQ;f*jWSTqT@`q7^-@>I$`D9G76q$#JN{szr0G}_V)upAZjA`5bz5u$)`G1peW2@bIQ8*r4pmw-C0`j5sPaoE#;qPk;IinOAb5GJ`1jvbe z>Q2OeFs#SeZ%45j7fB5A6MrDD&cx+_yX1?47WiAFBmv zuJ(Zv9icQuEGqjdePF(;yd+ZD(?r`8=VF;;d9B(>v{kwi2RcSq<2n_I`|$=#gUQOO zbFV)8o7p@`T6RuZ?Yvq{%vQyps6>7jIcY49@x(3=h5E%;iT~~KNa&1|SMeTQ%S>+DLE(=LKGx)u3Zdmu;x}QDzUjL{PaZw~L$uTeoyJ5#torQH3azLl;nO~f zev`a&rG5@YwTltzQ|2DRXo!hWI?}Cjk!xTQCC;BdO4_7ucUrnG8eQ#w>?a1)+XhjTG|7#{;d7XAwv zrSKU!v_QklSG6!*QXpuw9fXKgz?B2Dm0r2eiPulN8ip#qFI)zy@K7^#e24@*7j*1{`*H ztoVJUIw@Ve)DIO%p={DO8;}aEatEjcB?MP2^qSQCoe~99Q7~#*CrSA{ucqi$tL?0o z>WI`kGP6{3PTU5pQ|Xq3-Ggl1d9R_V?}oSNy7!B`K97E;YvDBU;!MtsrcGnS0B17D z2>DUg&m|O12iVqv-qFHk{2r_VosS!Xz>-8aQZ9!?Va4?Z8!;tCnK`X{v9h=2Yj%3v z{adVRw1-}~HSH&24(1}`YL~3j`J9%lPR%Oe4u7_j{a#)TsFK+#2-eh9>IP7a{1kos zF7~?Bc7^7E_w?}T2JmnF@s}7x>P6LM)`3|vP~&jf3V~|iyd&0@UJz!Na}!3|L3A6mrMqdW1b+_C-ZM{#;->|tz0fX^*UwrjL2 zo!m80<2Au@>(NIkof>47RQAPVR1juR%A$=|uaJ?5?k^*~G!>x&J(GPqKX#<+ac z=XFNzDF~ipZ0!Nwl2IFopAloe2^pPN%njJjmUSC87vcAGAurR0_E^(T`B9rHkzF7I zxsn+B>^Zr0U6HC4MXLEidE!sq*}QAiM}L$^0U!&o+cbmlV)<0mkxni8?d?fOA~#sG zq(9JwFDVatLn@6?%CHf|#H79GsZgG@irqlo7SLr{1Z>n{zoV}1s2fy7k~G>1`Gi_6FH-&8$Nl0q7PqG@D&mD-j(4un zr6sZr;{4_JFMwx@=0#7UN(cmSTm}Q?O`T1XjVR<2adA)zCp~@3p95XGpnM zk5;I1M*=2XCrgY_0_Y(v>rjn~B-6zyWOcmN3a@15O_!Afx+l-kd|N2*^J^7Qi95=d zAIy?JB-bo8i}1n>8;P>J_e-e5XzX6-<0CJjXVTFVqve*FAc7ta-?%_Dg*8Fi&H3nL zYM5C?UHs0CJuTdVlbgB6NL{7vT})ycMkWg<+{`NvU^>i1Gu#a6p*JzGfJ~s9Q=$*D zo71J2bB^bGDPJ*Nu1K)O{qATlC;ph~n{=2J>kw91NY)qt1*|(#MY%w*!Z$@xj!YN> z0@^tEnRR?z0|Mimi69G3W6AX5QukNs#i{AV-})t81~@}F!dYXUvNO`D3+`s&XZ*9| z(2Fg=il@x2A>#~GTt?WWb*0V6=SYE&J8T97f?MRkpvnsm)|k*ExEU;LRJfU0$77A1 zn03ooi%2sx1A-?$4mMc;$4AU!aGos+;0zeBU}|qf|G`;ewI}-7Kx}S{m&ox0!4_xH zQLM;y1xkVxi)+vq38k_?FI!oa=yIEHZy5m<|wg4Yr7kgblVh2)8J=!_syFDhUR` z&D>681X%FnWJN$t-5Z`7py%=)LS~RPbp!?gbM!^3kDn-oT-yWm@_LnudjToTXLtY> z(awT&tXEw@l!A)2J=sLsY(0hGl%-a~Gra5vZ1*e-=ZmtSM~w|&n#XTWRPR7-VeIQi zQhqKg$HyrSgF_M{4{=@TDm{KOw_HSf8CGVPpSO2cH)Eir)-ShJ^-c}%b=Y%|VjV}# zp_3$U$sQ@i%7s5bnw^Pcge5-)3lXY(`zl?8OYfAuEUhiN#*!Kbp;D143#o({Y<|9u zUX0;D*`TYT0$;o9_^^t#dHiU@xpZ;H6I2+Mr#Um)aF%&opC}2S0Mmm9t#%RM--RuV zW@M2 z2<%_LsDYw~zzR_G&@~5&9@Z^|(O8?iVMURvT3Y#DKdhKQsd3cG+oxDASqs0FGhNnH zHL;h-p=WHq<_uzJem8&(vrsFP3B+zW_mDALTL&4dp!6!p0k|B153*JpWG##u$l5Nh zFO7y}Z`-2RpRKI|S-U(FYp9S694SYtDpd-BUifHg4}Hq`so$;YIRN>oo;tamJr{o8 zUQPulN;uU<4rTM&wG>3V%wcdRon6ng^2?f?&>0j^qxeg~!k4Ek+bkr;nyUK(YNH^N z4_|RKqtG^+da8H{+Mr^8^3uPgE zsil(ka;0WTlg*|%BR^*<2Z-=%wM>#b?*DLgTuR7D(m}c>88bVI?wTT{u<7(N9HR{C zOKSh>*C&fipJ4F#2p5AJ$e1SRkbSYS;v1qz)&j?z^DyPIUt+YZKXgiUv=>#&!*yT3 z9zgtvTt>hEn7Xg}I0Oy{ZgQL^=pqk!nnuYw3&uKIFPT&3h?=qfZ7Q6AOKgzZYWjgR zt6ZZE5_G)s2&3W57N7v_#Zuu9aB8y}Ny9+rCASw}FXa{`wGSpwyK|mWV>~S-6fF1E zbM^I4=D}tywyGrUbLy~Qr8C<70Z%<8P#_a5^)sibPLS5zHGglN4-h!7AIh|DpD4C$ zoX|~UcGmL2(E+9_cQJoQ&`%W79K|(DH5H=jDugi(gi2yofP+f1-=5Bq+g0*u(j^0y zf9wJHrvJ`W5HbwU!o-dXS(#Qf8nwz72D+EP z;VcBa87|fM&6YLJ^x2tUsdZars3ZcgB*3uVjtN2i;6k*}#uFu9hql4a+O?G(9HDAEgM zaa(!lqL}G{^q(XRpjj>0nqaS6=+mx}LAu7nV*p#l$Wk+K5M{O21lRWOMa&+KyF`~`O zED2mpYL!WwMu>D{mZRMKW_mB_Kq=%wD0pg}d^9-fjX{%-c)vI_g8yF*Rg3R#Sz}B$ zMqUD{kixf>Oy1FPw@554j zu)ftTJHai^r}Io+sFa4N7<{b$$RDqw*qNgH;t(S|f1Li6w(a!XWoMFFgry5LgoWx3 zD}IRU`)P7J z;--LPb(%R}`UnN>7R{ZKe7shJ6e!0jAAO$&|6#+@?&9vn2AqCNR|1rM)t&geZ_-Ak z{3r~uE*aBiI0Kz#_wo+Wj6zLCs0be}wLh>$8KqYT8hBOflUSsh=)*?S z)QH699Ml~u2a<*Bd0~Vnk|K?a_gk2J>M5RV#EYSzNgUZm&Do_>1;}lg{?y z)C$&okJS2E8xxgRlZq)beVl63U6)iQCG>+OC1Ij$$W$I4Wn}H1h2eDGak^fhJdAe1 zBymt=UwS}xQZvN+BHm(q2XQh&df(xZ^{<3s0h~ZK72m^Vpen(u@IzB7N+q8q^tf1l zs`6s9a;1KRarK5cjpSwcoyz2TZb7r-Yeuj5Z7*}Msfy%=R0=e!$)>yaW{}~! zMMn)p0nuLbosmspud(4ws$Yn)U-_l>(pdGD7n&rzfekF{TrpB3D3Fp z$bbq4twVariVx!Xe1^=fAlR@jiv;mPQb%gvXvLteLk9e?t{1@)!{s8{ux{5AY;* zua|9ZB!k{{gXOEyc`?DiaRzjpE9;RacW~|WpfuGTXjM}NnXwQ6xaMEaE;wqa zIZ|oEjhj`Z^#a+xv2^IZNZ6?IFC#S6gEX{s6%r7F>lXS)wmj>8`f~~7PqL*7Cocx# z%E9RG*A6q^!Y)=I%(7j-5tx!;Q&vZ82ep-$UkAKxth3uIBp=2}j}X%Gp5N%+MN)fD zp4k<7i8PyTF1^TIoKYhxRGz23ZaftJ0kT4HUxL}b9`E)gn;NN=6vpNFVT}@DX0Y2g zJU~&bz&p4AZAwr5V0TJG5&r|t{OG7KI$dIp&i;fcD?!TGEooE$hk6Q{vrK>q?VXUV zJ6rz(dlVFJcglxx+g9r+M?+Q(EdR($8KNV(ufQNy-te>sRy0_BveKslK7}CBQBVqm zu))#Fv`dph2%8lH>RtPhy7eWezu^i|OuY21N6KreDSIXWC#>oesg=JfXDNoR#^`Ys z)0aL#cnF$IBQ-Zht+QFjlKe-X999|K(L{aQ?$gk;CvtRr z2el){ybZgE$7L9vQm2t6ak`yj=WrdtZ`C4nIm0vaaLvgh04bAEKjRB(2_bzR*OW=b zcWF@~EIkHp^C>K%;Z{QLaGU*rtULzl8#{(mGVl=8mx?qNdGFFB{P;76c>qHYhADd{ zwsN6K+tTn?%J3b9ZW6PSEbCvxgLS(nxRUC{-7?gXHW-5n(!_iE@TTMoVfG|~D#1Oc z@}&3rPP}df&y16 z)&m)8^1XhkQYh^$F$H3{is^SHujPyp@MP=q%Utq~pi*H^a2^b*OAOztjBF}?wmw5R zyI_+VNy@csP5>uX*${Dpbse>rcpaz%K%E%CtZic_@FpCKMPHP~B@t$gIHAk?W4v8vGIX!>P59;RLkJPpaIy3r zRYy?eGkxOU^cLGE_Luny=NbR*){Yu4L=XLlPYEr&mk=SN!Me<_9YELz5v|<(-rwkc z?N?kc{!`hlYUb(Ze(lR0r;aqzG*P0uOq>#N?+i`r#(hFVvejFTr&=-hq>86fBhd6T zS`^K3(G*@bfYI{o=g?KhlsO69A`Wg_UmB$)J#Z3ad#9dipR3@=%~vl=by1I8OfA*U z_&(YZf76KyKbo%WkdIY={@RaIC?&*C6U-Pr;zrUfpE0_O8X=;9ML>w#$pnwf)=}yl z>r%1xs6PZtfi54AE`gpxUY;us${wj&U%uA@=E&jt2nd(WmTgMRRhcS7^XL_nBAf~B z@XY3yN-J=6Ge(wUsa#RE)^s$}v!IN4tE4E*os5@Ulszh+=a(1Zt8~c*(3V5=>iHI= zSw)rSE;9if%}7`8mEDzPfwVX(ArBr{Dn_2N3Ju+*{OXKf&x_t!d@LU>ZB1XyX(6h7 zT@s0_;+3g@z2YB~PfuA!-%-}xAThULD2VuhuBUrAvOEI5FTZ^9OJu$!u$r!1RUN!> zm!dBgJ)^puv;2Cu(Xiu#bGf@hLp4Yf!XCQvz_p6hj-{LB{PNWonl>I{4*P~v-36A0 zqP3;;m1f1I1T$8V<=B9^Dpc?WAC;n06`H7!7ZVvu&w9Xu^k_Fo$fxhTN&WLTjp9;$SVA@otAZRzm$u=%bN+wLJqI3JT z%xR;!Nu$4N88bJ8D?(VDoAoTPB;!1dav>DY=h?-%U|nEzTfn16;mFhUl!aY=Xs8*E zkK_kEVfxq-M&%5J0HGWiuKaF3UB)IqIZyLGq3;T@yziw`OyI%u#RDEN=zfbbaOG5j zjGFRHk5IPl3nl4F(9kL}9HoIglk)O5ujI#?dbKSvi=gH<<)$n3Kx<`KVJU|NIV?Q_QZnDy^ng+}b5sHD9=V9}6ETunnE`PotU>Mg_h6kO8 zzclr%#$2S~mIH4pkp4hfLCbUc3%5xO# z82wntbkbIj+(33Pt_gsON5b@Yl|=G^Y<^)5qhgwmrOa{+lE2yk$y1f$`1MLYfRvcf zF34-Gf#Dd$*#yAOVG!dmfxa?CUi*qaT2>x(5LEBBcA66=@b)WR4S!P;#WmQ};P*LO zzf)^XzjLdbuPi&~$paW{xuGT2FIIkBWnEeB*aorKp0=}I916JocrW=H!@*0)icOc6 z{3OP!?Xy?hB)qrU*A9>ca9VtR->udLl2)di~cj!5);FKlMXxu6@p1&Q2lfTQ;!Z^*NcRjWuvT@z$C z>}>QhCE+K+X1WT;E=Xn>&FoGaCEwzX8?@;DNb*>zUiCR-xEU(3{|eX)YKusVfm2Hh zyQMNw-%{iqT6d-*Pm@o7-R`$*R?e}z(Y$5@jbDSRwNaud(7Gc*+7Mr<>t=pYnd;>6 z!`<)uz-~#kYjVEoUAji$N4u;8pN)-!_Jc04HSC%Klm~*+H`}z&3ZKmd|@S4FPLm_ zTz1quyIgV`S&U~VROd$(foNRy>G*PNyJ6{;_}YywCEW1N+=q(Y+G(?eYBT97j^3<+ zFx-8%Qg6+pUC&dP1)q2T;^K=t&yf(%tq3z1Gp>vE=(f%0lQ)vurU+@@ZGBFOz11=6dH z#3f0K{w+iv*C@Y%(V6&lqlTv3>+eZ)YG7oA1jWRfCjgokL#FnlaL#z6?)j#Uhoc-J zDX;m)xb%iJe6r+AJCtS5FDKOE24Xlaig&5bA^dI!-k&+)`(d<)*U`Pvkz|mhs1S(R zMKeqJ<`6pa8jK^<8piv|-EZqeXh*GX{T`WvI{Rc|ER+<@!a-y~nlf-$9=f69b$h%f zjqx?6&u#e;>|k@VJ%A_wvDqU~lwg!TxvC`n6g+U+=9h z)C~9hFBTzsigLq1@kcxG0gAnTNtyST*78WJQnwK_!WA<(J2!F@Pn!|PHxo}s4^Jh# zFJC@w+?AxJ=+dpv1edh#O2R$4+}=Imf^L2N&zobBQUHfRuwYB(&)isRxO*>IkE@42k`7(Fs~JyW zUP7%pXH{wDrKRh>!yUwyR!lTmsuNdm%+DL|M<2TBz4>@U{8C|7(C=<%=Nbi~Q- zyZ+#n*Cba*(T?!SrC+;_pwf>HR`!)5125+G{Zsu#zq`%D>RdaTljW8maDqiK{uTSW ztFf#{`pEksbF55;J-m&{+R;wLjvwacp5udUpPGQY%bs11y-xlVPZy9A!3N=%d!cP? z-M;zb-i0%bNYP9f{Rn*tE$o4C!o~dAGx#3D&I-F7xV(U1=Af_yBdF8)M~=c79`eKS zy7%T`vks2s{cZczkZy^k?u~C@a58#lUi&IW_<#LaJ2=X*fZ|NyIG_M#!wEZqtd{!8 zEu^O$*`-R6DvOTQp<9dqN~PBv4^V)CSTo%!bbG9DeQYoIOuFsUjPYJJ z&@FsyNmeM?h=raAx0uk5aEE2_7}y2&yT)a;S0tJ5?q%2CKLE4lfS(y|R&{Z!IJlgt zrX2CQ4C;s4b?}9yO$M^tJ?%R>o9m~d(XNG{^x{Y-do-r~0?P9oZ~*}Z0K54&EN)kp z9y5}2SP?*oaA|XY!mYrvRY}%#m`V4rFnuOlbI>ym`fKdIBYu;c`a!X>Re3zM{e`H2s#P4XFC^yLY3!3i6b$gV8U$!V~}p0 znj5w!8C0Yk+_%s76StM~Mb*}gfHn6Yh{lg=87wo3#TAGNqfOlmX!|@6WamGSjUU&C zb@oktQnS0qkMQ{DAtyv8u(zy;bmQb&1cSi;gFcLuyaf87qLu_Bs$y}2PdwH~B#M>K z!s6rt;+C5NFya0XoH~;Pvkh$xHa)#PrJJ;R-dKf?->Po5Nfx?W<-^DkH=ds ztjWvC-hL%uxCxrt8g`8)PPB z=6NNk;j4~tf;5U7r6B_nrq zcISj>VEITkscB5_<3sI>}oJj z8+B2OEWBPpjRo&u|8%CyJ+IfYVm2_za3r z$zdc^VXz1`%Vou?jy}^T291-VjN*y8TMI5!IB>RaHXJ)pM8^=z-eOcFa)Q*NZ&S;5 zbOPLtE~+{|CnUkOCZKB&zMf!`*4~RjHs+WXM|T_ zoj=FH!PkXli!!!0xGBdW*v^G@i!!7(xhc)T{i)gupC&50;X9@Yk;m|Xem|XmZ7|P7 zbL)26J}(r{g0aY?j2@FplAbJy(}6k*sOU%ZVIal;&Ks#&gHOr9*l1Kp1{PjgcOTx*3s4wt8&p&Eq;1pVB1fAV#1T9N9M2P7FB_Y zPpsEr-YZqk zwe9V#zYiIs+$SL)_wQT#rzM{r?{@aZ?7%!hbDCAP?a`dD4C9eLOtp9y$vMWJHhpq(X;9!TAuaelPL*9iB&wJAmksQ|UFLS#phwoDm&njL3rM515=cq`jYiZhExvsG zTFl_QM;r0eF{&lArjESkQ>Qb^b*?+HrkT}q>6pL!1iqS7h7uIujYlB= zAhaRC9aG@eOyLHPw<8haii?9#Qgd%iA5-4sAT1Ss^}mh+U+#R{ zbx@b8fO-}Wj27&lQSjXV@{V@)PCo|$X+9+wZ64|WZ|7&ncS-lMqJ^FYo}zutl`YU= zl9{2(#JQ$N@ER+&AT&=*EX)r-wT&#f+OxPdO~3Ok7WQ4l?>Iz@aYNdx0xQh<*5N=A z)U<%2=5Lr-l~(VT8E|25ZEJTROzfFq3>4l|n#S1)9lD~#^Hxb#Z7(7^y~*#{;lOP^ zSe9IARJMO~o)}T|h8(s`n-GUlk{$_&FQv9IS-F#F6D0DkVK+gdIPcL<#?gJR+9rMy zxfxK(b$GlqK56aY+oX4iR_zeg2y>w~(x+{@H9(rSkNH|D>1BCLweHkC$nyUTd&7kV zeX2pWeKnE-?FVDM5ZdIR@{MN2R%Z z5$qbf9?MPZDebJwsoa8da9j(DXryw{`*CHv|LTX}aIB?qkPk#aV+xGPU^G;V2aL6JkJUZ-Afzf>Qc)zJqbP67r!qX%h4!DoSv z3b;nj?0g6}3i^!i`It zMhg~PcR7_YSPIQTt0s?`gY(Ab3uy-wWot$Xbjc$}no|uek(PaJJ%Z-#kr#z-;7X5% zTeD}{8(ztnlx$2-sd*nLP|+h#2Q4s%nexk7^gggi!jrMwQVu$A- ztDmU2t{30-wtzi;SYs!9>2zxkqxV>~Sgg(3# zPDvKg!<*47B~4tSa9PY?OXEkO{+|A}+OQoL0Bdk*E{ZZ8enejd{T(J9m!;4)E$9*P zn8@c5bfo*AJA;U*E9YzoFtAq$zZ?Ub+SxiQ8yH&u+zM8bJ8VC*q7B?Y_`n8ukxM7M zM5i&&Zr+PWl8qeal+_TIP$b>w)lKSf3cW^;L}8kwvNIyJ(tSrG?Mk#Lee?ltQkoUG zmf-j0!P~9v2QK;5cm}uj5DG#g+qd*W%g&veAmQw*#>_#E)Io5m6yq111~A8`9li|$ zwgb|dTU|aD%Ym-)hBoupK4O^E;Y+IcN$b%mIKADfs6HZ>B?^raDTCoVKDmwp=VH-^ zrO8u=rKP-IlV8yDV*oYXzAP|8OHj#nb8sjTRu^!qdd6EwWU$58zeW@iY0e&jno5jc zS7sA%gn|<+1m4hSvgy9-6?On=aQtcp~PLD)n?XXh z85qPytnV9}&Pk(b@Uq)lu-CuE9J?4qNZSyzj|P$iy}1M36ZR@b_(QOanJcko>5PT& z2Pj0db)E-^PKIU{n z;^E!paboAxU0P4l85_^NoTb$v|Bj|1A+;T&!1;QOb_0#j+y=^v#Jf7#J34*Y&ePrJISN(l13S zk5&}yKt(FS)^EUnpoXpyCV}cRu$3z1(Q+f8$T-?icA#LL2!?@$RWQtvt2=?(bvHCLGG!wrT-H9<9f!Pa*wI-vbmXR5G zmYqZAdg6oq>b8e0YSbT|`&DfpwTS1}iPUykXKuuY+H@8^kT^Q=eAsd<|CGD@QG9gE z`}6TG%M{8N%;WfyW8Yy}fr%aO&_}j4dAF?vR^fIyPUo@Y-F7YJ;h}6TX9SgUFSlia z?Di(7GSk0~Ug`ZD+h5f2f z(pFuS&{-3B-IjfFnovpJw4EG&)MlFduC6KQXX7@Krg+nE8F%va`eo$HorjU`8xM_d zXVu@Pqi;U%H#Z2*;g7her?6an8Q6*?{LyML@mco7sm1%Cjp}9S-rI&F7q0407E^}@ zZzd-0xl|V9XdasH-os$Nawac+C(P|zP zzgp>R59KUH>JC~1os^6$am>lZp0ax#LLAf}vMxm7xi#4(f&{^6PDIZ;ESN?K0DVp% zXUF_JDh&;*6udV?7={u{o6VcJTB&R=4n`sp_%I<2EBPwU!m8cM@%BLk@-)M0jwRVJwNZFs2q=z3FT#;`_<2E3co5afp6fz4 zIwM>9mV^zu58$O6dgi(?KykOo$zV*tMqpo{i0Rk4m`a*|_cw3sA zw#)aXx+e(V2nmAwi^^t#e7;-e?Z9$_JLPLU^5yS&D5wsJ zXn`HkB2lpN(NscBS0Yrz2~=RqTIV8+1}0`k7!E&9 zgnjY*ad+NnW=GfY{l88@J?gG%-#~E~HWU~b+OKh#fxZ3z3&Z{j!JhwTD|OitlA(2| z9lWe;9&l$GaA5?_@|QcB6UeMlnhlRHmok!)s;*R@)@UiMT+a%1cyRo1A4NL4e!nn> z?kzuGDJ7JVh!FkZJ^K$X4@+S^CMoQE*7uY1otfFB@mi0MFFt!7c1l9O6NSf|-$pCY zQXtp{_e7^o=T6_RJeBC3JS{usr`i}FULXb**6%xuY%m2{yz6k(h>wn}FuK>9njhX~ z5#*r>^UUU&6ksAhtKOFrvamVAA7iw##%fg%Iy=Pw#%HKi8`gQ4IfY(}d#FYEwH`G( zHr)oL8xg-Y!dqjaBpL_m$6mIoqWBSupZxS^{Wf(Wl;R4?PIHa!Gjf6WYTZ6e$uf3h zy<`zYL_rVnz453H+!Oy;+r&Pix*RM5?nS6I_!s_DU!dAx6JXl0>G=fv=g^Hmd_*{a zjdcf<+mXMrT+Jj3QR^0GPmSsFZ$O;707%3v^AYQ7s2XzZUdj8f*V)6tpoX)oBGUv9 zpq%1788kAFp%4QqLUN{;?xJ5RlLU9|7mEW3KMNSt>~m6Bf4@1~Ch$sg{i>$LGGvIW zt1Nhw9nV{mb$C~|5=q|YIw%@tH10GE)i$8z5d-Vp%}_k9-OqGjsB%;KUoJp63dq ztsp7T)9HU%ph4U8&x!UgY0yEGiw#JLf9GKSL_&x%S0|$Z1Cvz*|5--*A1DAQ1pSHh zE5@JVexpjaC?M?h4e~X*i**Tjyvi~>x|7YoGj^Ctz9tHpJxly=(ljitu z+{g5{RX;#3gaJA1xqkLPi!|@w(k8aXwjkYqmS+8v+?^zv^&TXS1QLHfS@qB2465(? z({Im)|Bv!2%kc?%Ag67;0PSi25eB`>6Z~7=+1$j&gyrY|U+f3<%Fl%ijB*a40Cc(s z?D_9^2zIf*A#6;HEeu#JYz)jy*qQBZ&Hl3DABbV7?43m?FtF#p-ys-N{u6@jABcZ8 zf-|~MP0sM*Zd-(B> kJQ!HK-Y*9H9gN7!K!Y|SFfbg@PZFpaezPHHn*{s+048*TQUCw| literal 0 HcmV?d00001 diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..ed00b42 --- /dev/null +++ b/todo.txt @@ -0,0 +1,2 @@ +- Ожидаем внедрение BPMN в mermaid +- Оценки с текстом через сам Word а не предугадываание \ No newline at end of file