From 35a96bde514a8897f6f0fcc41c5833bf63df2e2a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 18:29:01 +0200 Subject: Adding upstream version 1.0.2. Signed-off-by: Daniel Baumann --- share/extensions/.pylintrc | 374 ++ share/extensions/.pytest_cache/CACHEDIR.TAG | 4 + share/extensions/.pytest_cache/README.md | 8 + share/extensions/.pytest_cache/v/cache/nodeids | 667 +++ share/extensions/.pytest_cache/v/cache/stepwise | 1 + share/extensions/LICENSE.txt | 340 ++ share/extensions/MANIFEST.in | 2 + share/extensions/Poly3DObjects/cube.obj | 19 + share/extensions/Poly3DObjects/cuboct.obj | 30 + share/extensions/Poly3DObjects/dodec.obj | 36 + share/extensions/Poly3DObjects/great_dodec.obj | 96 + .../Poly3DObjects/great_rhombicosidodec.obj | 185 + .../Poly3DObjects/great_rhombicuboct.obj | 77 + .../extensions/Poly3DObjects/great_stel_dodec.obj | 96 + share/extensions/Poly3DObjects/icos.obj | 36 + share/extensions/Poly3DObjects/icosidodec.obj | 65 + .../Poly3DObjects/jessens_orthog_icos.obj | 35 + share/extensions/Poly3DObjects/methane.obj | 13 + share/extensions/Poly3DObjects/oct.obj | 17 + share/extensions/Poly3DObjects/rh_axes.obj | 12 + share/extensions/Poly3DObjects/rhomb_dodec.obj | 29 + share/extensions/Poly3DObjects/rhomb_triacont.obj | 65 + .../Poly3DObjects/small_rhombicosidodec.obj | 127 + .../Poly3DObjects/small_rhombicuboct.obj | 54 + .../extensions/Poly3DObjects/small_triam_icos.obj | 95 + share/extensions/Poly3DObjects/snub_cube.obj | 65 + share/extensions/Poly3DObjects/snub_dodec.obj | 156 + share/extensions/Poly3DObjects/szilassi.obj | 24 + share/extensions/Poly3DObjects/tet.obj | 12 + share/extensions/Poly3DObjects/trunc_cube.obj | 42 + share/extensions/Poly3DObjects/trunc_dodec.obj | 96 + share/extensions/Poly3DObjects/trunc_icos.obj | 96 + share/extensions/Poly3DObjects/trunc_oct.obj | 42 + share/extensions/Poly3DObjects/trunc_tet.obj | 24 + share/extensions/README.md | 79 + share/extensions/STYLEGUIDE.md | 15 + .../extensions/__pycache__/addnodes.cpython-39.pyc | Bin 0 -> 2129 bytes .../__pycache__/color_HSL_adjust.cpython-39.pyc | Bin 0 -> 1428 bytes .../__pycache__/color_blackandwhite.cpython-39.pyc | Bin 0 -> 1013 bytes .../__pycache__/color_brighter.cpython-39.pyc | Bin 0 -> 900 bytes .../__pycache__/color_custom.cpython-39.pyc | Bin 0 -> 2114 bytes .../__pycache__/color_darker.cpython-39.pyc | Bin 0 -> 801 bytes .../__pycache__/color_desaturate.cpython-39.pyc | Bin 0 -> 739 bytes .../__pycache__/color_grayscale.cpython-39.pyc | Bin 0 -> 724 bytes .../__pycache__/color_lesshue.cpython-39.pyc | Bin 0 -> 609 bytes .../__pycache__/color_lesslight.cpython-39.pyc | Bin 0 -> 631 bytes .../color_lesssaturation.cpython-39.pyc | Bin 0 -> 646 bytes .../__pycache__/color_list.cpython-39.pyc | Bin 0 -> 1160 bytes .../__pycache__/color_morehue.cpython-39.pyc | Bin 0 -> 616 bytes .../__pycache__/color_morelight.cpython-39.pyc | Bin 0 -> 628 bytes .../color_moresaturation.cpython-39.pyc | Bin 0 -> 659 bytes .../__pycache__/color_negative.cpython-39.pyc | Bin 0 -> 641 bytes .../__pycache__/color_randomize.cpython-39.pyc | Bin 0 -> 2059 bytes .../__pycache__/color_removeblue.cpython-39.pyc | Bin 0 -> 684 bytes .../__pycache__/color_removegreen.cpython-39.pyc | Bin 0 -> 634 bytes .../__pycache__/color_removered.cpython-39.pyc | Bin 0 -> 684 bytes .../__pycache__/color_replace.cpython-39.pyc | Bin 0 -> 993 bytes .../__pycache__/color_rgbbarrel.cpython-39.pyc | Bin 0 -> 692 bytes .../__pycache__/convert2dashes.cpython-39.pyc | Bin 0 -> 2569 bytes .../__pycache__/dhw_input.cpython-39.pyc | Bin 0 -> 3038 bytes .../__pycache__/dimension.cpython-39.pyc | Bin 0 -> 3981 bytes .../extensions/__pycache__/docinfo.cpython-39.pyc | Bin 0 -> 1186 bytes .../__pycache__/dpiswitcher.cpython-39.pyc | Bin 0 -> 8990 bytes .../__pycache__/draw_from_triangle.cpython-39.pyc | Bin 0 -> 10782 bytes .../__pycache__/dxf12_outlines.cpython-39.pyc | Bin 0 -> 2968 bytes .../__pycache__/dxf_input.cpython-39.pyc | Bin 0 -> 23053 bytes .../__pycache__/dxf_outlines.cpython-39.pyc | Bin 0 -> 10954 bytes share/extensions/__pycache__/edge3d.cpython-39.pyc | Bin 0 -> 3630 bytes .../__pycache__/embedimage.cpython-39.pyc | Bin 0 -> 2694 bytes .../__pycache__/export_gimp_palette.cpython-39.pyc | Bin 0 -> 1877 bytes .../__pycache__/extractimage.cpython-39.pyc | Bin 0 -> 2566 bytes .../extensions/__pycache__/extrude.cpython-39.pyc | Bin 0 -> 2269 bytes .../__pycache__/fig_input.cpython-39.pyc | Bin 0 -> 700 bytes .../extensions/__pycache__/flatten.cpython-39.pyc | Bin 0 -> 1106 bytes .../__pycache__/foldablebox.cpython-39.pyc | Bin 0 -> 4143 bytes .../__pycache__/fractalize.cpython-39.pyc | Bin 0 -> 2160 bytes share/extensions/__pycache__/frame.cpython-39.pyc | Bin 0 -> 3765 bytes .../extensions/__pycache__/funcplot.cpython-39.pyc | Bin 0 -> 4896 bytes .../__pycache__/gcodetools.cpython-39.pyc | Bin 0 -> 167323 bytes .../__pycache__/generate_voronoi.cpython-39.pyc | Bin 0 -> 4181 bytes .../extensions/__pycache__/gimp_xcf.cpython-39.pyc | Bin 0 -> 4892 bytes .../__pycache__/grid_cartesian.cpython-39.pyc | Bin 0 -> 5406 bytes .../__pycache__/grid_isometric.cpython-39.pyc | Bin 0 -> 6311 bytes .../__pycache__/grid_polar.cpython-39.pyc | Bin 0 -> 5013 bytes .../__pycache__/guides_creator.cpython-39.pyc | Bin 0 -> 6355 bytes .../__pycache__/guillotine.cpython-39.pyc | Bin 0 -> 5944 bytes .../extensions/__pycache__/handles.cpython-39.pyc | Bin 0 -> 1443 bytes .../extensions/__pycache__/hershey.cpython-39.pyc | Bin 0 -> 34120 bytes .../__pycache__/hpgl_decoder.cpython-39.pyc | Bin 0 -> 2627 bytes .../__pycache__/hpgl_encoder.cpython-39.pyc | Bin 0 -> 8762 bytes .../__pycache__/hpgl_input.cpython-39.pyc | Bin 0 -> 1840 bytes .../__pycache__/hpgl_output.cpython-39.pyc | Bin 0 -> 2070 bytes .../__pycache__/image_attributes.cpython-39.pyc | Bin 0 -> 4370 bytes .../__pycache__/ink2canvas.cpython-39.pyc | Bin 0 -> 2254 bytes .../inkscape_follow_link.cpython-39.pyc | Bin 0 -> 1270 bytes .../__pycache__/inkwebeffect.cpython-39.pyc | Bin 0 -> 1513 bytes share/extensions/__pycache__/interp.cpython-39.pyc | Bin 0 -> 6308 bytes .../__pycache__/interp_att_g.cpython-39.pyc | Bin 0 -> 4688 bytes .../__pycache__/jessyink_autotexts.cpython-39.pyc | Bin 0 -> 1252 bytes .../__pycache__/jessyink_effects.cpython-39.pyc | Bin 0 -> 1803 bytes .../__pycache__/jessyink_export.cpython-39.pyc | Bin 0 -> 1785 bytes .../__pycache__/jessyink_install.cpython-39.pyc | Bin 0 -> 3732 bytes .../jessyink_key_bindings.cpython-39.pyc | Bin 0 -> 5649 bytes .../jessyink_master_slide.cpython-39.pyc | Bin 0 -> 1462 bytes .../jessyink_mouse_handler.cpython-39.pyc | Bin 0 -> 1628 bytes .../__pycache__/jessyink_summary.cpython-39.pyc | Bin 0 -> 4812 bytes .../jessyink_transitions.cpython-39.pyc | Bin 0 -> 1676 bytes .../__pycache__/jessyink_uninstall.cpython-39.pyc | Bin 0 -> 1741 bytes .../__pycache__/jessyink_video.cpython-39.pyc | Bin 0 -> 2297 bytes .../__pycache__/jessyink_view.cpython-39.pyc | Bin 0 -> 1647 bytes .../__pycache__/jitternodes.cpython-39.pyc | Bin 0 -> 2898 bytes .../__pycache__/launch_webbrowser.cpython-39.pyc | Bin 0 -> 1102 bytes .../__pycache__/layer2png.cpython-39.pyc | Bin 0 -> 5843 bytes .../__pycache__/layers2svgfont.cpython-39.pyc | Bin 0 -> 2139 bytes .../__pycache__/layout_nup.cpython-39.pyc | Bin 0 -> 6900 bytes .../__pycache__/lindenmayer.cpython-39.pyc | Bin 0 -> 3355 bytes .../__pycache__/lorem_ipsum.cpython-39.pyc | Bin 0 -> 8889 bytes .../__pycache__/markers_strokepaint.cpython-39.pyc | Bin 0 -> 3072 bytes .../extensions/__pycache__/measure.cpython-39.pyc | Bin 0 -> 6339 bytes .../__pycache__/media_zip.cpython-39.pyc | Bin 0 -> 4696 bytes .../__pycache__/merge_styles.cpython-39.pyc | Bin 0 -> 1361 bytes share/extensions/__pycache__/motion.cpython-39.pyc | Bin 0 -> 3456 bytes .../__pycache__/new_glyph_layer.cpython-39.pyc | Bin 0 -> 1142 bytes .../__pycache__/next_glyph_layer.cpython-39.pyc | Bin 0 -> 1076 bytes .../__pycache__/nicechart.cpython-39.pyc | Bin 0 -> 12136 bytes .../__pycache__/output_scour.cpython-39.pyc | Bin 0 -> 2773 bytes .../__pycache__/param_curves.cpython-39.pyc | Bin 0 -> 3965 bytes .../__pycache__/path_envelope.cpython-39.pyc | Bin 0 -> 3246 bytes .../__pycache__/path_mesh_m2p.cpython-39.pyc | Bin 0 -> 9017 bytes .../__pycache__/path_mesh_p2m.cpython-39.pyc | Bin 0 -> 2424 bytes .../__pycache__/path_number_nodes.cpython-39.pyc | Bin 0 -> 2421 bytes .../__pycache__/path_to_absolute.cpython-39.pyc | Bin 0 -> 834 bytes .../__pycache__/pathalongpath.cpython-39.pyc | Bin 0 -> 8154 bytes .../__pycache__/pathmodifier.cpython-39.pyc | Bin 0 -> 3170 bytes .../__pycache__/pathscatter.cpython-39.pyc | Bin 0 -> 7818 bytes .../extensions/__pycache__/pdflatex.cpython-39.pyc | Bin 0 -> 2336 bytes .../__pycache__/perfectboundcover.cpython-39.pyc | Bin 0 -> 3003 bytes .../__pycache__/perspective.cpython-39.pyc | Bin 0 -> 4104 bytes .../__pycache__/pixelsnap.cpython-39.pyc | Bin 0 -> 12136 bytes .../extensions/__pycache__/plotter.cpython-39.pyc | Bin 0 -> 4057 bytes .../__pycache__/polyhedron_3d.cpython-39.pyc | Bin 0 -> 14006 bytes .../prepare_file_save_as.cpython-39.pyc | Bin 0 -> 934 bytes .../previous_glyph_layer.cpython-39.pyc | Bin 0 -> 706 bytes .../__pycache__/print_win32_vector.cpython-39.pyc | Bin 0 -> 6007 bytes .../__pycache__/printing_marks.cpython-39.pyc | Bin 0 -> 9496 bytes .../extensions/__pycache__/ps_input.cpython-39.pyc | Bin 0 -> 1012 bytes .../__pycache__/render_alphabetsoup.cpython-39.pyc | Bin 0 -> 10497 bytes .../render_alphabetsoup_config.cpython-39.pyc | Bin 0 -> 25154 bytes .../__pycache__/render_barcode.cpython-39.pyc | Bin 0 -> 1296 bytes .../render_barcode_datamatrix.cpython-39.pyc | Bin 0 -> 12112 bytes .../render_barcode_qrcode.cpython-39.pyc | Bin 0 -> 32657 bytes .../__pycache__/render_gear_rack.cpython-39.pyc | Bin 0 -> 2174 bytes .../__pycache__/render_gears.cpython-39.pyc | Bin 0 -> 3413 bytes .../__pycache__/replace_font.cpython-39.pyc | Bin 0 -> 6606 bytes .../extensions/__pycache__/restack.cpython-39.pyc | Bin 0 -> 2796 bytes share/extensions/__pycache__/rtree.cpython-39.pyc | Bin 0 -> 1472 bytes .../__pycache__/rubberstretch.cpython-39.pyc | Bin 0 -> 2124 bytes .../__pycache__/scribus_export_pdf.cpython-39.pyc | Bin 0 -> 4633 bytes .../setup_typography_canvas.cpython-39.pyc | Bin 0 -> 1981 bytes .../__pycache__/spirograph.cpython-39.pyc | Bin 0 -> 2556 bytes .../__pycache__/straightseg.cpython-39.pyc | Bin 0 -> 1740 bytes .../__pycache__/svgcalendar.cpython-39.pyc | Bin 0 -> 9944 bytes .../__pycache__/svgfont2layers.cpython-39.pyc | Bin 0 -> 2224 bytes .../__pycache__/synfig_fileformat.cpython-39.pyc | Bin 0 -> 4620 bytes .../__pycache__/synfig_output.cpython-39.pyc | Bin 0 -> 31010 bytes .../__pycache__/synfig_prepare.cpython-39.pyc | Bin 0 -> 11288 bytes .../__pycache__/tar_layers.cpython-39.pyc | Bin 0 -> 2191 bytes .../extensions/__pycache__/template.cpython-39.pyc | Bin 0 -> 1638 bytes .../__pycache__/template_dvd_cover.cpython-39.pyc | Bin 0 -> 1611 bytes .../template_seamless_pattern.cpython-39.pyc | Bin 0 -> 2815 bytes .../__pycache__/text_braille.cpython-39.pyc | Bin 0 -> 841 bytes .../__pycache__/text_extract.cpython-39.pyc | Bin 0 -> 1944 bytes .../__pycache__/text_flipcase.cpython-39.pyc | Bin 0 -> 654 bytes .../__pycache__/text_lowercase.cpython-39.pyc | Bin 0 -> 591 bytes .../__pycache__/text_merge.cpython-39.pyc | Bin 0 -> 2856 bytes .../__pycache__/text_randomcase.cpython-39.pyc | Bin 0 -> 859 bytes .../__pycache__/text_sentencecase.cpython-39.pyc | Bin 0 -> 925 bytes .../__pycache__/text_split.cpython-39.pyc | Bin 0 -> 3774 bytes .../__pycache__/text_titlecase.cpython-39.pyc | Bin 0 -> 791 bytes .../__pycache__/text_uppercase.cpython-39.pyc | Bin 0 -> 590 bytes .../extensions/__pycache__/triangle.cpython-39.pyc | Bin 0 -> 4757 bytes .../__pycache__/ungroup_deep.cpython-39.pyc | Bin 0 -> 4128 bytes .../extensions/__pycache__/voronoi.cpython-39.pyc | Bin 0 -> 19692 bytes .../__pycache__/voronoi2svg.cpython-39.pyc | Bin 0 -> 5982 bytes .../web_interactive_mockup.cpython-39.pyc | Bin 0 -> 1239 bytes .../__pycache__/web_set_att.cpython-39.pyc | Bin 0 -> 2148 bytes .../__pycache__/web_transmit_att.cpython-39.pyc | Bin 0 -> 1656 bytes .../webslicer_create_group.cpython-39.pyc | Bin 0 -> 1641 bytes .../webslicer_create_rect.cpython-39.pyc | Bin 0 -> 2779 bytes .../__pycache__/webslicer_effect.cpython-39.pyc | Bin 0 -> 1315 bytes .../__pycache__/webslicer_export.cpython-39.pyc | Bin 0 -> 12042 bytes share/extensions/__pycache__/whirl.cpython-39.pyc | Bin 0 -> 1742 bytes .../__pycache__/wireframe_sphere.cpython-39.pyc | Bin 0 -> 4498 bytes share/extensions/addnodes.inx | 20 + share/extensions/addnodes.py | 65 + share/extensions/aisvg.inx | 14 + share/extensions/aisvg.xslt | 36 + share/extensions/alphabet_soup/2.svg | 11 + share/extensions/alphabet_soup/3.svg | 11 + share/extensions/alphabet_soup/6.svg | 10 + share/extensions/alphabet_soup/7.svg | 10 + share/extensions/alphabet_soup/Cblob.svg | 10 + share/extensions/alphabet_soup/Chook.svg | 10 + share/extensions/alphabet_soup/Ctail.svg | 10 + share/extensions/alphabet_soup/Delta.svg | 10 + share/extensions/alphabet_soup/Eb.svg | 10 + share/extensions/alphabet_soup/Eserif.svg | 16 + share/extensions/alphabet_soup/Et.svg | 10 + share/extensions/alphabet_soup/G.svg | 10 + share/extensions/alphabet_soup/IBSerif.svg | 10 + share/extensions/alphabet_soup/ITSerif.svg | 10 + share/extensions/alphabet_soup/Lb.svg | 10 + share/extensions/alphabet_soup/Lt.svg | 10 + share/extensions/alphabet_soup/Ocross.svg | 10 + share/extensions/alphabet_soup/Oterm.svg | 10 + share/extensions/alphabet_soup/P.svg | 10 + share/extensions/alphabet_soup/Q.svg | 10 + share/extensions/alphabet_soup/Rblock.svg | 10 + share/extensions/alphabet_soup/Tb.svg | 16 + share/extensions/alphabet_soup/Tt.svg | 16 + share/extensions/alphabet_soup/U.svg | 10 + share/extensions/alphabet_soup/Vser.svg | 10 + share/extensions/alphabet_soup/Xh.svg | 10 + share/extensions/alphabet_soup/Xne.svg | 10 + share/extensions/alphabet_soup/Xnw.svg | 10 + share/extensions/alphabet_soup/Xvb.svg | 10 + share/extensions/alphabet_soup/Xvt.svg | 10 + share/extensions/alphabet_soup/a.svg | 10 + share/extensions/alphabet_soup/abase.svg | 10 + share/extensions/alphabet_soup/acap.svg | 10 + share/extensions/alphabet_soup/b.svg | 10 + share/extensions/alphabet_soup/bar.svg | 10 + share/extensions/alphabet_soup/bar2.svg | 10 + share/extensions/alphabet_soup/barcap.svg | 10 + share/extensions/alphabet_soup/c.svg | 10 + share/extensions/alphabet_soup/cross.svg | 10 + share/extensions/alphabet_soup/cserif.svg | 10 + share/extensions/alphabet_soup/e.svg | 10 + share/extensions/alphabet_soup/epsilon.svg | 8 + share/extensions/alphabet_soup/f.svg | 16 + share/extensions/alphabet_soup/gamma.svg | 10 + share/extensions/alphabet_soup/h.svg | 10 + share/extensions/alphabet_soup/h2.svg | 10 + share/extensions/alphabet_soup/hcap.svg | 10 + share/extensions/alphabet_soup/idot.svg | 10 + share/extensions/alphabet_soup/j.svg | 10 + share/extensions/alphabet_soup/k.svg | 10 + share/extensions/alphabet_soup/l.svg | 10 + share/extensions/alphabet_soup/lserif.svg | 10 + share/extensions/alphabet_soup/m.svg | 10 + share/extensions/alphabet_soup/mcap.svg | 10 + share/extensions/alphabet_soup/n.svg | 10 + share/extensions/alphabet_soup/o.svg | 10 + share/extensions/alphabet_soup/ocap.svg | 10 + share/extensions/alphabet_soup/question.svg | 10 + share/extensions/alphabet_soup/r.svg | 10 + share/extensions/alphabet_soup/rcap.svg | 10 + share/extensions/alphabet_soup/s.svg | 10 + share/extensions/alphabet_soup/serif.svg | 10 + share/extensions/alphabet_soup/t.svg | 16 + share/extensions/alphabet_soup/tserif.svg | 10 + share/extensions/alphabet_soup/v.svg | 10 + share/extensions/alphabet_soup/vcap.svg | 10 + share/extensions/alphabet_soup/vserl.svg | 10 + share/extensions/alphabet_soup/vserr.svg | 10 + share/extensions/alphabet_soup/x.svg | 10 + share/extensions/alphabet_soup/y.svg | 10 + share/extensions/alphabet_soup/yogh.svg | 10 + share/extensions/alphabet_soup/z.svg | 10 + share/extensions/barcode/Base.py | 166 + share/extensions/barcode/BaseEan.py | 158 + share/extensions/barcode/Code128.py | 158 + share/extensions/barcode/Code25i.py | 69 + share/extensions/barcode/Code39.py | 98 + share/extensions/barcode/Code39Ext.py | 68 + share/extensions/barcode/Code93.py | 116 + share/extensions/barcode/Ean13.py | 43 + share/extensions/barcode/Ean2.py | 39 + share/extensions/barcode/Ean5.py | 39 + share/extensions/barcode/Ean8.py | 38 + share/extensions/barcode/Rm4scc.py | 136 + share/extensions/barcode/Upca.py | 39 + share/extensions/barcode/Upce.py | 100 + share/extensions/barcode/__init__.py | 73 + .../barcode/__pycache__/Base.cpython-39.pyc | Bin 0 -> 4524 bytes .../barcode/__pycache__/BaseEan.cpython-39.pyc | Bin 0 -> 5102 bytes .../barcode/__pycache__/Code128.cpython-39.pyc | Bin 0 -> 3824 bytes .../barcode/__pycache__/Code25i.cpython-39.pyc | Bin 0 -> 1132 bytes .../barcode/__pycache__/Code39.cpython-39.pyc | Bin 0 -> 1650 bytes .../barcode/__pycache__/Code39Ext.cpython-39.pyc | Bin 0 -> 1283 bytes .../barcode/__pycache__/Code93.cpython-39.pyc | Bin 0 -> 2765 bytes .../barcode/__pycache__/Ean13.cpython-39.pyc | Bin 0 -> 946 bytes .../barcode/__pycache__/Ean2.cpython-39.pyc | Bin 0 -> 890 bytes .../barcode/__pycache__/Ean5.cpython-39.pyc | Bin 0 -> 1054 bytes .../barcode/__pycache__/Ean8.cpython-39.pyc | Bin 0 -> 817 bytes .../barcode/__pycache__/Rm4scc.cpython-39.pyc | Bin 0 -> 2502 bytes .../barcode/__pycache__/Upca.cpython-39.pyc | Bin 0 -> 907 bytes .../barcode/__pycache__/Upce.cpython-39.pyc | Bin 0 -> 2333 bytes .../barcode/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 1600 bytes share/extensions/color_HSL_adjust.inx | 34 + share/extensions/color_HSL_adjust.py | 37 + share/extensions/color_blackandwhite.inx | 15 + share/extensions/color_blackandwhite.py | 19 + share/extensions/color_brighter.inx | 14 + share/extensions/color_brighter.py | 24 + share/extensions/color_custom.inx | 36 + share/extensions/color_custom.py | 62 + share/extensions/color_darker.inx | 14 + share/extensions/color_darker.py | 19 + share/extensions/color_desaturate.inx | 14 + share/extensions/color_desaturate.py | 14 + share/extensions/color_grayscale.inx | 14 + share/extensions/color_grayscale.py | 15 + share/extensions/color_lesshue.inx | 14 + share/extensions/color_lesshue.py | 13 + share/extensions/color_lesslight.inx | 14 + share/extensions/color_lesslight.py | 13 + share/extensions/color_lesssaturation.inx | 14 + share/extensions/color_lesssaturation.py | 13 + share/extensions/color_list.inx | 14 + share/extensions/color_list.py | 22 + share/extensions/color_morehue.inx | 14 + share/extensions/color_morehue.py | 13 + share/extensions/color_morelight.inx | 14 + share/extensions/color_morelight.py | 13 + share/extensions/color_moresaturation.inx | 14 + share/extensions/color_moresaturation.py | 13 + share/extensions/color_negative.inx | 14 + share/extensions/color_negative.py | 15 + share/extensions/color_randomize.inx | 25 + share/extensions/color_randomize.py | 52 + share/extensions/color_removeblue.inx | 14 + share/extensions/color_removeblue.py | 12 + share/extensions/color_removegreen.inx | 14 + share/extensions/color_removegreen.py | 12 + share/extensions/color_removered.inx | 14 + share/extensions/color_removered.py | 12 + share/extensions/color_replace.inx | 22 + share/extensions/color_replace.py | 19 + share/extensions/color_rgbbarrel.inx | 14 + share/extensions/color_rgbbarrel.py | 16 + share/extensions/colors.xml | 151 + share/extensions/convert2dashes.inx | 14 + share/extensions/convert2dashes.py | 101 + share/extensions/dhw_input.inx | 14 + share/extensions/dhw_input.py | 118 + share/extensions/dimension.inx | 20 + share/extensions/dimension.py | 145 + share/extensions/docinfo.inx | 14 + share/extensions/docinfo.py | 41 + share/extensions/doxygen-main.dox | 55 + share/extensions/dpi90to96.inx | 17 + share/extensions/dpi96to90.inx | 17 + share/extensions/dpiswitcher.py | 346 ++ share/extensions/draw_from_triangle.inx | 75 + share/extensions/draw_from_triangle.py | 395 ++ share/extensions/dxf12_outlines.inx | 17 + share/extensions/dxf12_outlines.py | 138 + share/extensions/dxf14_footer.txt | 62 + share/extensions/dxf14_header.txt | 198 + share/extensions/dxf14_style.txt | 358 ++ share/extensions/dxf_input.inx | 46 + share/extensions/dxf_input.py | 629 +++ share/extensions/dxf_outlines.inx | 57 + share/extensions/dxf_outlines.py | 332 ++ share/extensions/edge3d.inx | 21 + share/extensions/edge3d.py | 122 + share/extensions/embedimage.inx | 15 + share/extensions/embedimage.py | 119 + share/extensions/embedselectedimages.inx | 13 + share/extensions/eps_input.inx | 19 + share/extensions/export_gimp_palette.inx | 14 + share/extensions/export_gimp_palette.py | 68 + share/extensions/extractimage.inx | 16 + share/extensions/extractimage.py | 99 + share/extensions/extrude.inx | 18 + share/extensions/extrude.py | 94 + share/extensions/fig_input.inx | 16 + share/extensions/fig_input.py | 35 + share/extensions/flatten.inx | 15 + share/extensions/flatten.py | 44 + share/extensions/foldablebox.inx | 26 + share/extensions/foldablebox.py | 246 + share/extensions/fontfix.conf | 55 + share/extensions/fractalize.inx | 16 + share/extensions/fractalize.py | 90 + share/extensions/frame.inx | 30 + share/extensions/frame.py | 127 + share/extensions/funcplot.inx | 55 + share/extensions/funcplot.py | 247 + share/extensions/gcodetools.py | 5921 ++++++++++++++++++++ share/extensions/gcodetools_about.inx | 52 + share/extensions/gcodetools_area.inx | 133 + share/extensions/gcodetools_dxf_points.inx | 79 + share/extensions/gcodetools_engraving.inx | 91 + share/extensions/gcodetools_graffiti.inx | 120 + share/extensions/gcodetools_lathe.inx | 113 + share/extensions/gcodetools_orientation_points.inx | 57 + share/extensions/gcodetools_path_to_gcode.inx | 93 + .../gcodetools_prepare_path_for_plasma.inx | 59 + share/extensions/gcodetools_tools_library.inx | 62 + share/extensions/generate_voronoi.inx | 26 + share/extensions/generate_voronoi.py | 180 + share/extensions/genpofiles.sh | 7 + share/extensions/gimp_xcf.inx | 35 + share/extensions/gimp_xcf.py | 179 + share/extensions/grid_cartesian.inx | 73 + share/extensions/grid_cartesian.py | 216 + share/extensions/grid_isometric.inx | 25 + share/extensions/grid_isometric.py | 357 ++ share/extensions/grid_polar.inx | 43 + share/extensions/grid_polar.py | 155 + share/extensions/guides_creator.inx | 91 + share/extensions/guides_creator.py | 268 + share/extensions/guillotine.inx | 23 + share/extensions/guillotine.py | 185 + share/extensions/handles.inx | 14 + share/extensions/handles.py | 63 + share/extensions/hershey.inx | 131 + share/extensions/hershey.py | 1939 +++++++ share/extensions/hpgl_decoder.py | 100 + share/extensions/hpgl_encoder.py | 344 ++ share/extensions/hpgl_input.inx | 20 + share/extensions/hpgl_input.py | 61 + share/extensions/hpgl_output.inx | 46 + share/extensions/hpgl_output.py | 68 + share/extensions/image_attributes.inx | 76 + share/extensions/image_attributes.py | 124 + share/extensions/ink2canvas.inx | 14 + share/extensions/ink2canvas.py | 89 + share/extensions/ink2canvas_lib/__init__.py | 0 .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 147 bytes .../__pycache__/canvas.cpython-39.pyc | Bin 0 -> 7658 bytes .../ink2canvas_lib/__pycache__/svg.cpython-39.pyc | Bin 0 -> 10331 bytes share/extensions/ink2canvas_lib/canvas.py | 191 + share/extensions/ink2canvas_lib/svg.py | 293 + share/extensions/inkex.py | 11 + share/extensions/inkex/__init__.py | 29 + .../inkex/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 786 bytes .../inkex/__pycache__/base.cpython-39.pyc | Bin 0 -> 11943 bytes .../inkex/__pycache__/bezier.cpython-39.pyc | Bin 0 -> 11855 bytes .../inkex/__pycache__/colors.cpython-39.pyc | Bin 0 -> 13508 bytes .../inkex/__pycache__/command.cpython-39.pyc | Bin 0 -> 6499 bytes .../inkex/__pycache__/deprecated.cpython-39.pyc | Bin 0 -> 15884 bytes .../inkex/__pycache__/extensions.cpython-39.pyc | Bin 0 -> 12533 bytes .../inkex/__pycache__/inx.cpython-39.pyc | Bin 0 -> 6299 bytes .../inkex/__pycache__/localization.cpython-39.pyc | Bin 0 -> 1141 bytes .../inkex/__pycache__/paths.cpython-39.pyc | Bin 0 -> 48895 bytes .../inkex/__pycache__/ports.cpython-39.pyc | Bin 0 -> 4246 bytes .../inkex/__pycache__/styles.cpython-39.pyc | Bin 0 -> 14008 bytes .../inkex/__pycache__/transforms.cpython-39.pyc | Bin 0 -> 34865 bytes .../inkex/__pycache__/turtle.cpython-39.pyc | Bin 0 -> 3808 bytes .../inkex/__pycache__/tween.cpython-39.pyc | Bin 0 -> 1686 bytes .../inkex/__pycache__/units.cpython-39.pyc | Bin 0 -> 2432 bytes .../inkex/__pycache__/utils.cpython-39.pyc | Bin 0 -> 10503 bytes share/extensions/inkex/base.py | 355 ++ share/extensions/inkex/bezier.py | 425 ++ share/extensions/inkex/colors.py | 478 ++ share/extensions/inkex/command.py | 222 + .../extensions/inkex/deprecated-simple/README.rst | 4 + .../__pycache__/bezmisc.cpython-39.pyc | Bin 0 -> 1138 bytes .../__pycache__/cspsubdiv.cpython-39.pyc | Bin 0 -> 348 bytes .../__pycache__/cubicsuperpath.cpython-39.pyc | Bin 0 -> 1118 bytes .../__pycache__/ffgeom.cpython-39.pyc | Bin 0 -> 3171 bytes .../__pycache__/simplepath.cpython-39.pyc | Bin 0 -> 1808 bytes .../__pycache__/simplestyle.cpython-39.pyc | Bin 0 -> 1555 bytes .../__pycache__/simpletransform.cpython-39.pyc | Bin 0 -> 4003 bytes .../extensions/inkex/deprecated-simple/bezmisc.py | 44 + .../inkex/deprecated-simple/cspsubdiv.py | 25 + .../inkex/deprecated-simple/cubicsuperpath.py | 46 + share/extensions/inkex/deprecated-simple/ffgeom.py | 87 + .../inkex/deprecated-simple/run_command.py | 74 + .../inkex/deprecated-simple/simplepath.py | 51 + .../inkex/deprecated-simple/simplestyle.py | 47 + .../inkex/deprecated-simple/simpletransform.py | 106 + share/extensions/inkex/deprecated.py | 388 ++ share/extensions/inkex/elements/__init__.py | 19 + .../elements/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 1541 bytes .../elements/__pycache__/_base.cpython-39.pyc | Bin 0 -> 18505 bytes .../elements/__pycache__/_filters.cpython-39.pyc | Bin 0 -> 10723 bytes .../elements/__pycache__/_groups.cpython-39.pyc | Bin 0 -> 3488 bytes .../elements/__pycache__/_image.cpython-39.pyc | Bin 0 -> 468 bytes .../elements/__pycache__/_meta.cpython-39.pyc | Bin 0 -> 5469 bytes .../elements/__pycache__/_polygons.cpython-39.pyc | Bin 0 -> 9772 bytes .../elements/__pycache__/_selected.cpython-39.pyc | Bin 0 -> 6660 bytes .../inkex/elements/__pycache__/_svg.cpython-39.pyc | Bin 0 -> 8222 bytes .../elements/__pycache__/_text.cpython-39.pyc | Bin 0 -> 6256 bytes .../inkex/elements/__pycache__/_use.cpython-39.pyc | Bin 0 -> 2048 bytes share/extensions/inkex/elements/_base.py | 514 ++ share/extensions/inkex/elements/_filters.py | 276 + share/extensions/inkex/elements/_groups.py | 112 + share/extensions/inkex/elements/_image.py | 27 + share/extensions/inkex/elements/_meta.py | 147 + share/extensions/inkex/elements/_polygons.py | 231 + share/extensions/inkex/elements/_selected.py | 159 + share/extensions/inkex/elements/_svg.py | 211 + share/extensions/inkex/elements/_text.py | 159 + share/extensions/inkex/elements/_use.py | 70 + share/extensions/inkex/extensions.py | 344 ++ share/extensions/inkex/inx.py | 166 + share/extensions/inkex/localization.py | 66 + share/extensions/inkex/paths.py | 1547 +++++ share/extensions/inkex/ports.py | 100 + share/extensions/inkex/styles.py | 380 ++ share/extensions/inkex/tester/__init__.py | 384 ++ .../tester/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 13791 bytes .../tester/__pycache__/decorators.cpython-39.pyc | Bin 0 -> 388 bytes .../tester/__pycache__/filters.cpython-39.pyc | Bin 0 -> 6337 bytes .../inkex/tester/__pycache__/inx.cpython-39.pyc | Bin 0 -> 3340 bytes .../inkex/tester/__pycache__/mock.cpython-39.pyc | Bin 0 -> 13699 bytes .../inkex/tester/__pycache__/svg.cpython-39.pyc | Bin 0 -> 1279 bytes .../inkex/tester/__pycache__/word.cpython-39.pyc | Bin 0 -> 841 bytes .../tester/__pycache__/xmldiff.cpython-39.pyc | Bin 0 -> 3619 bytes share/extensions/inkex/tester/decorators.py | 8 + share/extensions/inkex/tester/filters.py | 139 + share/extensions/inkex/tester/inx.py | 95 + share/extensions/inkex/tester/mock.py | 414 ++ share/extensions/inkex/tester/svg.py | 49 + share/extensions/inkex/tester/word.py | 38 + share/extensions/inkex/tester/xmldiff.py | 113 + share/extensions/inkex/transforms.py | 1084 ++++ share/extensions/inkex/turtle.py | 120 + share/extensions/inkex/tween.py | 79 + share/extensions/inkex/units.py | 107 + share/extensions/inkex/utils.py | 290 + share/extensions/inkscape.extension.rng | 544 ++ share/extensions/inkscape_follow_link.inx | 12 + share/extensions/inkscape_follow_link.py | 28 + share/extensions/inkscape_help_askaquestion.inx | 14 + share/extensions/inkscape_help_commandline.inx | 14 + share/extensions/inkscape_help_faq.inx | 13 + share/extensions/inkscape_help_keys.inx | 14 + share/extensions/inkscape_help_manual.inx | 14 + share/extensions/inkscape_help_relnotes.inx | 14 + share/extensions/inkscape_help_reportabug.inx | 13 + share/extensions/inkscape_help_svgspec.inx | 13 + share/extensions/inkweb.js | 216 + share/extensions/inkwebeffect.py | 56 + share/extensions/interp.inx | 20 + share/extensions/interp.py | 283 + share/extensions/interp_att_g.inx | 59 + share/extensions/interp_att_g.py | 174 + share/extensions/jessyInk.js | 2727 +++++++++ .../jessyInk_core_mouseHandler_noclick.js | 53 + .../jessyInk_core_mouseHandler_zoomControl.js | 434 ++ share/extensions/jessyink_autotexts.inx | 28 + share/extensions/jessyink_autotexts.py | 47 + share/extensions/jessyink_effects.inx | 40 + share/extensions/jessyink_effects.py | 60 + share/extensions/jessyink_export.inx | 29 + share/extensions/jessyink_export.py | 73 + share/extensions/jessyink_install.inx | 21 + share/extensions/jessyink_install.py | 99 + share/extensions/jessyink_key_bindings.inx | 68 + share/extensions/jessyink_key_bindings.py | 167 + share/extensions/jessyink_master_slide.inx | 24 + share/extensions/jessyink_master_slide.py | 51 + share/extensions/jessyink_mouse_handler.inx | 27 + share/extensions/jessyink_mouse_handler.py | 60 + share/extensions/jessyink_summary.inx | 20 + share/extensions/jessyink_summary.py | 149 + share/extensions/jessyink_transitions.inx | 39 + share/extensions/jessyink_transitions.py | 59 + share/extensions/jessyink_uninstall.inx | 29 + share/extensions/jessyink_uninstall.py | 67 + share/extensions/jessyink_video.inx | 20 + share/extensions/jessyink_video.py | 83 + share/extensions/jessyink_video.svg | 596 ++ share/extensions/jessyink_view.inx | 26 + share/extensions/jessyink_view.py | 65 + share/extensions/jitternodes.inx | 31 + share/extensions/jitternodes.py | 95 + share/extensions/launch_webbrowser.py | 43 + share/extensions/layer2png.inx | 36 + share/extensions/layer2png.py | 184 + share/extensions/layers2svgfont.inx | 14 + share/extensions/layers2svgfont.py | 95 + share/extensions/layout_nup.inx | 75 + share/extensions/layout_nup.py | 255 + share/extensions/lindenmayer.inx | 51 + share/extensions/lindenmayer.py | 93 + share/extensions/lorem_ipsum.inx | 24 + share/extensions/lorem_ipsum.py | 248 + share/extensions/markers_strokepaint.inx | 39 + share/extensions/markers_strokepaint.py | 107 + share/extensions/measure.inx | 73 + share/extensions/measure.py | 195 + share/extensions/media_zip.inx | 19 + share/extensions/media_zip.py | 184 + share/extensions/merge_styles.inx | 19 + share/extensions/merge_styles.py | 59 + share/extensions/motion.inx | 16 + share/extensions/motion.py | 117 + share/extensions/new_glyph_layer.inx | 15 + share/extensions/new_glyph_layer.py | 53 + share/extensions/next_glyph_layer.inx | 14 + share/extensions/next_glyph_layer.py | 47 + share/extensions/nicechart.inx | 102 + share/extensions/nicechart.py | 523 ++ share/extensions/output_scour.inx | 129 + share/extensions/output_scour.py | 72 + share/extensions/output_scour.svg | 5 + share/extensions/param_curves.inx | 47 + share/extensions/param_curves.py | 206 + share/extensions/path_envelope.inx | 14 + share/extensions/path_envelope.py | 99 + share/extensions/path_mesh_m2p.inx | 37 + share/extensions/path_mesh_m2p.py | 350 ++ share/extensions/path_mesh_p2m.inx | 32 + share/extensions/path_mesh_p2m.py | 89 + share/extensions/path_number_nodes.inx | 30 + share/extensions/path_number_nodes.py | 74 + share/extensions/path_to_absolute.inx | 14 + share/extensions/path_to_absolute.py | 35 + share/extensions/pathalongpath.inx | 37 + share/extensions/pathalongpath.py | 265 + share/extensions/pathmodifier.py | 118 + share/extensions/pathscatter.inx | 39 + share/extensions/pathscatter.py | 248 + share/extensions/pdflatex.inx | 19 + share/extensions/pdflatex.py | 83 + share/extensions/perfectboundcover.inx | 41 + share/extensions/perfectboundcover.py | 109 + share/extensions/perspective.inx | 14 + share/extensions/perspective.py | 136 + share/extensions/pixelsnap.inx | 20 + share/extensions/pixelsnap.py | 451 ++ share/extensions/plotter.inx | 104 + share/extensions/plotter.py | 126 + share/extensions/polyhedron_3d.inx | 97 + share/extensions/polyhedron_3d.py | 389 ++ share/extensions/prepare_file_save_as.inx | 14 + share/extensions/prepare_file_save_as.py | 47 + share/extensions/previous_glyph_layer.inx | 14 + share/extensions/previous_glyph_layer.py | 31 + share/extensions/print_win32_vector.inx | 15 + share/extensions/print_win32_vector.py | 214 + share/extensions/printing_marks.inx | 49 + share/extensions/printing_marks.py | 401 ++ share/extensions/ps_input.inx | 18 + share/extensions/ps_input.py | 41 + share/extensions/render_alphabetsoup.inx | 18 + share/extensions/render_alphabetsoup.py | 503 ++ share/extensions/render_alphabetsoup_config.py | 902 +++ share/extensions/render_barcode.inx | 32 + share/extensions/render_barcode.py | 50 + share/extensions/render_barcode_datamatrix.inx | 50 + share/extensions/render_barcode_datamatrix.py | 513 ++ share/extensions/render_barcode_qrcode.inx | 61 + share/extensions/render_barcode_qrcode.py | 1081 ++++ share/extensions/render_gear_rack.inx | 19 + share/extensions/render_gear_rack.py | 81 + share/extensions/render_gears.inx | 26 + share/extensions/render_gears.py | 165 + share/extensions/replace_font.inx | 36 + share/extensions/replace_font.py | 236 + share/extensions/restack.inx | 57 + share/extensions/restack.py | 91 + share/extensions/rtree.inx | 17 + share/extensions/rtree.py | 53 + share/extensions/rubberstretch.inx | 16 + share/extensions/rubberstretch.py | 83 + share/extensions/scribus_export_pdf.inx | 41 + share/extensions/scribus_export_pdf.py | 153 + share/extensions/seamless_pattern.svg | 560 ++ share/extensions/setup.cfg | 2 + share/extensions/setup.py | 44 + share/extensions/setup_typography_canvas.inx | 19 + share/extensions/setup_typography_canvas.py | 66 + share/extensions/spirograph.inx | 23 + share/extensions/spirograph.py | 102 + share/extensions/straightseg.inx | 16 + share/extensions/straightseg.py | 62 + share/extensions/svg2fxg.inx | 14 + share/extensions/svg2fxg.xsl | 3008 ++++++++++ share/extensions/svg2xaml.inx | 15 + share/extensions/svg2xaml.xsl | 2987 ++++++++++ share/extensions/svg_fonts/EMSAllure.svg | 235 + share/extensions/svg_fonts/EMSElfin.svg | 236 + share/extensions/svg_fonts/EMSFelix.svg | 235 + share/extensions/svg_fonts/EMSNixish.svg | 235 + share/extensions/svg_fonts/EMSNixishItalic.svg | 235 + share/extensions/svg_fonts/EMSOsmotron.svg | 235 + share/extensions/svg_fonts/EMSReadability.svg | 235 + .../extensions/svg_fonts/EMSReadabilityItalic.svg | 235 + share/extensions/svg_fonts/EMSTech.svg | 236 + share/extensions/svg_fonts/HersheyGothEnglish.svg | 261 + share/extensions/svg_fonts/HersheySans1.svg | 260 + share/extensions/svg_fonts/HersheySansMed.svg | 260 + share/extensions/svg_fonts/HersheyScript1.svg | 261 + share/extensions/svg_fonts/HersheyScriptMed.svg | 261 + share/extensions/svg_fonts/HersheySerifBold.svg | 261 + .../svg_fonts/HersheySerifBoldItalic.svg | 262 + share/extensions/svg_fonts/HersheySerifMed.svg | 260 + .../extensions/svg_fonts/HersheySerifMedItalic.svg | 260 + share/extensions/svg_fonts/OFL.txt | 97 + share/extensions/svgcalendar.inx | 144 + share/extensions/svgcalendar.py | 405 ++ share/extensions/svgfont2layers.inx | 15 + share/extensions/svgfont2layers.py | 105 + share/extensions/synfig_fileformat.py | 244 + share/extensions/synfig_output.inx | 18 + share/extensions/synfig_output.py | 1336 +++++ share/extensions/synfig_prepare.py | 476 ++ share/extensions/tar_layers.inx | 17 + share/extensions/tar_layers.py | 83 + share/extensions/template.py | 59 + share/extensions/template_business_card.inx | 30 + share/extensions/template_desktop.inx | 42 + share/extensions/template_dvd_cover.inx | 29 + share/extensions/template_dvd_cover.py | 58 + share/extensions/template_envelope.inx | 25 + share/extensions/template_generic.inx | 41 + share/extensions/template_icon.inx | 23 + share/extensions/template_page.inx | 43 + share/extensions/template_seamless_pattern.inx | 23 + share/extensions/template_seamless_pattern.py | 84 + share/extensions/template_video.inx | 35 + share/extensions/tests/README.md | 33 + share/extensions/tests/__init__.py | 1 + .../tests/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 138 bytes .../test_addnodes.cpython-39-PYTEST.pyc | Bin 0 -> 1940 bytes .../test_color_HSL_adjust.cpython-39-PYTEST.pyc | Bin 0 -> 1250 bytes .../test_color_blackandwhite.cpython-39-PYTEST.pyc | Bin 0 -> 1149 bytes .../test_color_brighter.cpython-39-PYTEST.pyc | Bin 0 -> 1078 bytes .../test_color_custom.cpython-39-PYTEST.pyc | Bin 0 -> 2107 bytes .../test_color_darker.cpython-39-PYTEST.pyc | Bin 0 -> 1082 bytes .../test_color_desaturate.cpython-39-PYTEST.pyc | Bin 0 -> 1008 bytes .../test_color_grayscale.cpython-39-PYTEST.pyc | Bin 0 -> 1044 bytes .../test_color_lesshue.cpython-39-PYTEST.pyc | Bin 0 -> 1110 bytes .../test_color_lesslight.cpython-39-PYTEST.pyc | Bin 0 -> 1107 bytes ...test_color_lesssaturation.cpython-39-PYTEST.pyc | Bin 0 -> 1127 bytes .../test_color_list.cpython-39-PYTEST.pyc | Bin 0 -> 624 bytes .../test_color_morehue.cpython-39-PYTEST.pyc | Bin 0 -> 1108 bytes .../test_color_morelight.cpython-39-PYTEST.pyc | Bin 0 -> 1102 bytes ...test_color_moresaturation.cpython-39-PYTEST.pyc | Bin 0 -> 1122 bytes .../test_color_negative.cpython-39-PYTEST.pyc | Bin 0 -> 1040 bytes .../test_color_randomize.cpython-39-PYTEST.pyc | Bin 0 -> 1321 bytes .../test_color_removeblue.cpython-39-PYTEST.pyc | Bin 0 -> 1016 bytes .../test_color_removegreen.cpython-39-PYTEST.pyc | Bin 0 -> 1020 bytes .../test_color_removered.cpython-39-PYTEST.pyc | Bin 0 -> 1012 bytes .../test_color_replace.cpython-39-PYTEST.pyc | Bin 0 -> 782 bytes .../test_color_rgbbarrel.cpython-39-PYTEST.pyc | Bin 0 -> 1070 bytes .../test_convert2dashes.cpython-39-PYTEST.pyc | Bin 0 -> 1869 bytes .../test_deprecated_simple.cpython-39-PYTEST.pyc | Bin 0 -> 7149 bytes .../test_dhw_input.cpython-39-PYTEST.pyc | Bin 0 -> 764 bytes .../test_dimension.cpython-39-PYTEST.pyc | Bin 0 -> 629 bytes .../__pycache__/test_docinfo.cpython-39-PYTEST.pyc | Bin 0 -> 626 bytes .../test_dpiswitcher.cpython-39-PYTEST.pyc | Bin 0 -> 659 bytes .../test_draw_from_triangle.cpython-39-PYTEST.pyc | Bin 0 -> 669 bytes .../test_dxf12_outlines.cpython-39-PYTEST.pyc | Bin 0 -> 580 bytes .../test_dxf_input.cpython-39-PYTEST.pyc | Bin 0 -> 1081 bytes .../test_dxf_outlines.cpython-39-PYTEST.pyc | Bin 0 -> 683 bytes .../__pycache__/test_edge3d.cpython-39-PYTEST.pyc | Bin 0 -> 1235 bytes .../test_embedimage.cpython-39-PYTEST.pyc | Bin 0 -> 619 bytes .../test_export_gimp_palette.cpython-39-PYTEST.pyc | Bin 0 -> 619 bytes .../test_extractimage.cpython-39-PYTEST.pyc | Bin 0 -> 1693 bytes .../__pycache__/test_extrude.cpython-39-PYTEST.pyc | Bin 0 -> 740 bytes .../test_fig_input.cpython-39-PYTEST.pyc | Bin 0 -> 607 bytes .../__pycache__/test_flatten.cpython-39-PYTEST.pyc | Bin 0 -> 722 bytes .../test_foldablebox.cpython-39-PYTEST.pyc | Bin 0 -> 699 bytes .../test_fractalize.cpython-39-PYTEST.pyc | Bin 0 -> 604 bytes .../__pycache__/test_frame.cpython-39-PYTEST.pyc | Bin 0 -> 3008 bytes .../test_funcplot.cpython-39-PYTEST.pyc | Bin 0 -> 690 bytes .../test_gcodetools.cpython-39-PYTEST.pyc | Bin 0 -> 2976 bytes .../test_generate_voronoi.cpython-39-PYTEST.pyc | Bin 0 -> 719 bytes .../test_gimp_xcf.cpython-39-PYTEST.pyc | Bin 0 -> 1071 bytes .../test_grid_cartesian.cpython-39-PYTEST.pyc | Bin 0 -> 717 bytes .../test_grid_isometric.cpython-39-PYTEST.pyc | Bin 0 -> 783 bytes .../test_grid_polar.cpython-39-PYTEST.pyc | Bin 0 -> 701 bytes .../test_guides_creator.cpython-39-PYTEST.pyc | Bin 0 -> 1069 bytes .../test_guillotine.cpython-39-PYTEST.pyc | Bin 0 -> 2357 bytes .../__pycache__/test_handles.cpython-39-PYTEST.pyc | Bin 0 -> 629 bytes .../__pycache__/test_hershey.cpython-39-PYTEST.pyc | Bin 0 -> 2466 bytes .../test_hpgl_decoder.cpython-39-PYTEST.pyc | Bin 0 -> 2191 bytes .../test_hpgl_input.cpython-39-PYTEST.pyc | Bin 0 -> 616 bytes .../test_hpgl_output.cpython-39-PYTEST.pyc | Bin 0 -> 675 bytes .../test_image_attributes.cpython-39-PYTEST.pyc | Bin 0 -> 798 bytes .../test_ink2canvas_svg.cpython-39-PYTEST.pyc | Bin 0 -> 741 bytes .../__pycache__/test_inkex.cpython-39-PYTEST.pyc | Bin 0 -> 2797 bytes .../test_inkex_base.cpython-39-PYTEST.pyc | Bin 0 -> 6133 bytes .../test_inkex_bezier.cpython-39-PYTEST.pyc | Bin 0 -> 1517 bytes .../test_inkex_bounding_box.cpython-39-PYTEST.pyc | Bin 0 -> 20927 bytes .../test_inkex_colors.cpython-39-PYTEST.pyc | Bin 0 -> 7695 bytes .../test_inkex_command.cpython-39-PYTEST.pyc | Bin 0 -> 983 bytes .../test_inkex_cubic_paths.cpython-39-PYTEST.pyc | Bin 0 -> 1945 bytes .../test_inkex_deprecated.cpython-39-PYTEST.pyc | Bin 0 -> 1978 bytes .../test_inkex_elements.cpython-39-PYTEST.pyc | Bin 0 -> 25265 bytes .../test_inkex_elements_base.cpython-39-PYTEST.pyc | Bin 0 -> 13100 bytes ...inkex_elements_selections.cpython-39-PYTEST.pyc | Bin 0 -> 5334 bytes .../test_inkex_extensions.cpython-39-PYTEST.pyc | Bin 0 -> 2639 bytes .../test_inkex_inx.cpython-39-PYTEST.pyc | Bin 0 -> 1039 bytes .../test_inkex_paths.cpython-39-PYTEST.pyc | Bin 0 -> 22199 bytes .../test_inkex_styles.cpython-39-PYTEST.pyc | Bin 0 -> 11548 bytes .../test_inkex_svg.cpython-39-PYTEST.pyc | Bin 0 -> 19577 bytes .../test_inkex_tester.cpython-39-PYTEST.pyc | Bin 0 -> 1244 bytes .../test_inkex_transforms.cpython-39-PYTEST.pyc | Bin 0 -> 22661 bytes .../test_inkex_tween.cpython-39-PYTEST.pyc | Bin 0 -> 2449 bytes .../test_inkex_units.cpython-39-PYTEST.pyc | Bin 0 -> 2771 bytes .../test_inkex_utils.cpython-39-PYTEST.pyc | Bin 0 -> 12760 bytes ...test_inkscape_follow_link.cpython-39-PYTEST.pyc | Bin 0 -> 618 bytes .../test_inkwebeffect.cpython-39-PYTEST.pyc | Bin 0 -> 578 bytes .../__pycache__/test_interp.cpython-39-PYTEST.pyc | Bin 0 -> 896 bytes .../test_interp_att_g.cpython-39-PYTEST.pyc | Bin 0 -> 938 bytes .../test_jessyink_autotexts.cpython-39-PYTEST.pyc | Bin 0 -> 638 bytes .../test_jessyink_effects.cpython-39-PYTEST.pyc | Bin 0 -> 685 bytes .../test_jessyink_export.cpython-39-PYTEST.pyc | Bin 0 -> 696 bytes .../test_jessyink_install.cpython-39-PYTEST.pyc | Bin 0 -> 573 bytes ...test_jessyink_keybindings.cpython-39-PYTEST.pyc | Bin 0 -> 750 bytes ...test_jessyink_masterslide.cpython-39-PYTEST.pyc | Bin 0 -> 590 bytes ...est_jessyink_mousehandler.cpython-39-PYTEST.pyc | Bin 0 -> 748 bytes .../test_jessyink_summary.cpython-39-PYTEST.pyc | Bin 0 -> 616 bytes ...test_jessyink_transitions.cpython-39-PYTEST.pyc | Bin 0 -> 633 bytes .../test_jessyink_uninstall.cpython-39-PYTEST.pyc | Bin 0 -> 607 bytes .../test_jessyink_video.cpython-39-PYTEST.pyc | Bin 0 -> 614 bytes .../test_jessyink_view.cpython-39-PYTEST.pyc | Bin 0 -> 632 bytes .../test_jitternodes.cpython-39-PYTEST.pyc | Bin 0 -> 741 bytes .../test_launch_webbrowser.cpython-39-PYTEST.pyc | Bin 0 -> 1236 bytes .../test_layer2png.cpython-39-PYTEST.pyc | Bin 0 -> 1862 bytes .../test_layers2svgfont.cpython-39-PYTEST.pyc | Bin 0 -> 643 bytes .../test_layout_nup.cpython-39-PYTEST.pyc | Bin 0 -> 556 bytes .../test_lindenmayer.cpython-39-PYTEST.pyc | Bin 0 -> 664 bytes .../test_lorem_ipsum.cpython-39-PYTEST.pyc | Bin 0 -> 587 bytes .../test_markers_strokepaint.cpython-39-PYTEST.pyc | Bin 0 -> 1133 bytes .../__pycache__/test_measure.cpython-39-PYTEST.pyc | Bin 0 -> 902 bytes .../test_media_zip.cpython-39-PYTEST.pyc | Bin 0 -> 669 bytes .../test_merge_styles.cpython-39-PYTEST.pyc | Bin 0 -> 645 bytes .../__pycache__/test_motion.cpython-39-PYTEST.pyc | Bin 0 -> 761 bytes .../test_new_glyph_layer.cpython-39-PYTEST.pyc | Bin 0 -> 614 bytes .../test_next_glyph_layer.cpython-39-PYTEST.pyc | Bin 0 -> 608 bytes .../test_nicechart.cpython-39-PYTEST.pyc | Bin 0 -> 1013 bytes .../test_output_scour.cpython-39-PYTEST.pyc | Bin 0 -> 664 bytes .../test_param_curves.cpython-39-PYTEST.pyc | Bin 0 -> 740 bytes .../test_path_envelope.cpython-39-PYTEST.pyc | Bin 0 -> 937 bytes .../test_path_mesh.cpython-39-PYTEST.pyc | Bin 0 -> 1173 bytes .../test_path_number_nodes.cpython-39-PYTEST.pyc | Bin 0 -> 611 bytes .../test_path_to_absolute.cpython-39-PYTEST.pyc | Bin 0 -> 760 bytes .../test_pathalongpath.cpython-39-PYTEST.pyc | Bin 0 -> 767 bytes .../test_pathscatter.cpython-39-PYTEST.pyc | Bin 0 -> 737 bytes .../test_pdflatex.cpython-39-PYTEST.pyc | Bin 0 -> 1632 bytes .../test_perfectboundcover.cpython-39-PYTEST.pyc | Bin 0 -> 652 bytes .../test_perspective.cpython-39-PYTEST.pyc | Bin 0 -> 854 bytes .../test_pixelsnap.cpython-39-PYTEST.pyc | Bin 0 -> 707 bytes .../__pycache__/test_plotter.cpython-39-PYTEST.pyc | Bin 0 -> 882 bytes .../test_polyhedron_3d.cpython-39-PYTEST.pyc | Bin 0 -> 848 bytes ...test_prepare_file_save_as.cpython-39-PYTEST.pyc | Bin 0 -> 628 bytes ...test_previous_glyph_layer.cpython-39-PYTEST.pyc | Bin 0 -> 624 bytes .../test_print_win32_vector.cpython-39-PYTEST.pyc | Bin 0 -> 718 bytes .../test_printing_marks.cpython-39-PYTEST.pyc | Bin 0 -> 802 bytes .../test_ps_input.cpython-39-PYTEST.pyc | Bin 0 -> 741 bytes .../test_render_alphabetsoup.cpython-39-PYTEST.pyc | Bin 0 -> 592 bytes .../test_render_barcode.cpython-39-PYTEST.pyc | Bin 0 -> 4692 bytes ...render_barcode_datamatrix.cpython-39-PYTEST.pyc | Bin 0 -> 773 bytes ...est_render_barcode_qrcode.cpython-39-PYTEST.pyc | Bin 0 -> 1232 bytes .../test_render_gear_rack.cpython-39-PYTEST.pyc | Bin 0 -> 711 bytes .../test_render_gears.cpython-39-PYTEST.pyc | Bin 0 -> 697 bytes .../test_replace_font.cpython-39-PYTEST.pyc | Bin 0 -> 1014 bytes .../__pycache__/test_restack.cpython-39-PYTEST.pyc | Bin 0 -> 639 bytes .../__pycache__/test_rtree.cpython-39-PYTEST.pyc | Bin 0 -> 673 bytes .../test_rubberstretch.cpython-39-PYTEST.pyc | Bin 0 -> 651 bytes .../test_scribus_pdf.cpython-39-PYTEST.pyc | Bin 0 -> 750 bytes ...t_setup_typography_canvas.cpython-39-PYTEST.pyc | Bin 0 -> 646 bytes .../test_spirograph.cpython-39-PYTEST.pyc | Bin 0 -> 703 bytes .../test_straightseg.cpython-39-PYTEST.pyc | Bin 0 -> 754 bytes .../test_svgcalendar.cpython-39-PYTEST.pyc | Bin 0 -> 4730 bytes .../test_svgfont2layers.cpython-39-PYTEST.pyc | Bin 0 -> 646 bytes .../test_synfig_fileformat.cpython-39-PYTEST.pyc | Bin 0 -> 1441 bytes .../test_synfig_output.cpython-39-PYTEST.pyc | Bin 0 -> 580 bytes .../test_synfig_prepare.cpython-39-PYTEST.pyc | Bin 0 -> 578 bytes .../test_tar_layers.cpython-39-PYTEST.pyc | Bin 0 -> 648 bytes .../test_template.cpython-39-PYTEST.pyc | Bin 0 -> 823 bytes .../test_template_dvd_cover.cpython-39-PYTEST.pyc | Bin 0 -> 646 bytes ...template_seamless_pattern.cpython-39-PYTEST.pyc | Bin 0 -> 745 bytes .../test_text_braille.cpython-39-PYTEST.pyc | Bin 0 -> 604 bytes .../test_text_extract.cpython-39-PYTEST.pyc | Bin 0 -> 676 bytes .../test_text_flipcase.cpython-39-PYTEST.pyc | Bin 0 -> 587 bytes .../test_text_lowercase.cpython-39-PYTEST.pyc | Bin 0 -> 2227 bytes .../test_text_merge.cpython-39-PYTEST.pyc | Bin 0 -> 575 bytes .../test_text_randomcase.cpython-39-PYTEST.pyc | Bin 0 -> 595 bytes .../test_text_sentencecase.cpython-39-PYTEST.pyc | Bin 0 -> 603 bytes .../test_text_split.cpython-39-PYTEST.pyc | Bin 0 -> 628 bytes .../test_text_titlecase.cpython-39-PYTEST.pyc | Bin 0 -> 2804 bytes .../test_text_uppercase.cpython-39-PYTEST.pyc | Bin 0 -> 2004 bytes .../test_triangle.cpython-39-PYTEST.pyc | Bin 0 -> 734 bytes .../test_ungroup_deep.cpython-39-PYTEST.pyc | Bin 0 -> 713 bytes .../__pycache__/test_voronoi.cpython-39-PYTEST.pyc | Bin 0 -> 1602 bytes .../test_voronoi2svg.cpython-39-PYTEST.pyc | Bin 0 -> 705 bytes ...st_web_interactive_mockup.cpython-39-PYTEST.pyc | Bin 0 -> 644 bytes .../test_web_set_att.cpython-39-PYTEST.pyc | Bin 0 -> 602 bytes .../test_web_transmit_att.cpython-39-PYTEST.pyc | Bin 0 -> 626 bytes ...st_webslicer_create_group.cpython-39-PYTEST.pyc | Bin 0 -> 635 bytes ...est_webslicer_create_rect.cpython-39-PYTEST.pyc | Bin 0 -> 590 bytes .../test_webslicer_export.cpython-39-PYTEST.pyc | Bin 0 -> 774 bytes .../__pycache__/test_whirl.cpython-39-PYTEST.pyc | Bin 0 -> 755 bytes .../test_wireframe_sphere.cpython-39-PYTEST.pyc | Bin 0 -> 764 bytes share/extensions/tests/add_pylint.py | 113 + share/extensions/tests/data/README.md | 10 + share/extensions/tests/data/batches/barcodes.dat | 512 ++ .../fig2dev/c15e8c5e5e50d45b2579ac22522b007e.msg | 29 + .../cmd/gimp/6c0e5d2fbe380e26e310ebfc206d81ea.msg | 2204 ++++++++ .../cmd/gimp/9de0e6f5cf782a299a1c062b5412dd63.msg | 2918 ++++++++++ .../inkscape/03dd4e9c9ee257c7ae161057ebe7c8a3.msg | 25 + .../inkscape/0ac64d7c051206284e72bf3e0800186d.msg | 1025 ++++ .../inkscape/106011cd22941d59371660c2b4abf75c.msg | 25 + .../inkscape/1b0252977bd9eafe0af2686834decd6e.msg | 28 + .../inkscape/1f50fa6d71e2543dd18dfc807fe56018.msg | 25 + .../inkscape/2911a62f9cfb6cf1e950992909bf7efb.msg | 25 + .../inkscape/2aee6b61724952541d5114e64953ae32.msg | 28 + .../inkscape/37d9cfceb38aade8eda7ad77e4ad0c29.msg | 25 + .../inkscape/581055ce0e3ef5df0c1ab22982a51513.msg | 94 + .../inkscape/67e526c4c1e53207e5e46274e8cdfcc0.msg | 17 + .../inkscape/7e16d346b278485f0e308b7c8eda301d.msg | 25 + .../inkscape/818d4c30d07def36e80e32df12023124.msg | 261 + .../inkscape/82de0e8eb29f071d78cebe7a4116e246.msg | 435 ++ .../inkscape/88e23ddddb7b44e469a8309d48b71594.msg | 25 + .../inkscape/8d81a6ce40b41366bc5784c8b8e3f3e3.msg | 28 + .../inkscape/9216316249e241e47bf118e187386687.msg | 28 + .../inkscape/96e4049adf5290ee08bfbcd580bd8dd1.msg | 19 + .../inkscape/994e3f169976a1af54f6590723d399ee.msg | 28 + .../inkscape/a274e3a1c2d893233fa49b8ad9850098.msg | 18 + .../inkscape/b493ad11b50db40a61c857109f083621.msg | 604 ++ .../inkscape/c1bf37c2aa47000fc9941d96b415218c.msg | 28 + .../inkscape/c23825a5b83e906138ca8bff105b8b29.msg | 1038 ++++ .../inkscape/d475a74656cd1f8ba68e095b0ec7c469.msg | 1025 ++++ .../inkscape/da3b4e4db182a123e61e1317b2e4578b.msg | 25 + .../inkscape/ee1fdaa387bcb73ee6f7a2359fa9bd62.msg | 28 + .../inkscape/f863b882b18ffd8659b3c20cdd549266.msg | 569 ++ .../inkscape/fbabf7cf387f29c12ae11a51af0a9926.msg | 28 + .../pdflatex/7b89a79118ea572d7e2fdafa2e82fc70.msg | 1016 ++++ .../ps2pdf/43caf043a269fc0e77f8633cd2ad4a88.msg | 59 + .../ps2pdf/9f4cd3145afec1e2a294713237712f4c.msg | 60 + .../scribus/a5ed3ec8aa6d61652cb04fa27f921943.msg | 13 + .../scribus/b8a765f070200c63d022a696ac129e35.msg | 261 + share/extensions/tests/data/io/PAGE_001.DHW | Bin 0 -> 12320 bytes share/extensions/tests/data/io/PGLT_161.DHW | Bin 0 -> 456 bytes share/extensions/tests/data/io/PGLT_162.DHW | Bin 0 -> 976 bytes share/extensions/tests/data/io/PGLT_163.DHW | Bin 0 -> 1916 bytes share/extensions/tests/data/io/nicechart_01.csv | 13 + share/extensions/tests/data/io/test.eps | 83 + share/extensions/tests/data/io/test.fig | 15 + share/extensions/tests/data/io/test.hpgl | 1 + share/extensions/tests/data/io/test.ps | 120 + share/extensions/tests/data/io/test_r12.dxf | 5592 ++++++++++++++++++ share/extensions/tests/data/io/test_r14.dxf | 1398 +++++ share/extensions/tests/data/refs/addnodes.out | 0 .../data/refs/addnodes__--id__p1__--id__r3.out | 41 + .../extensions/tests/data/refs/convert2dashes.out | 0 .../tests/data/refs/dhw_input__PAGE_001__DHW.out | 8 + .../tests/data/refs/dhw_input__PGLT_161__DHW.out | 8 + .../tests/data/refs/dhw_input__PGLT_162__DHW.out | 8 + .../tests/data/refs/dhw_input__PGLT_163__DHW.out | 8 + .../data/refs/dimension__--id__p1__--id__r3.out | 41 + ...mension__--id__p1__--id__r3__--type__visual.out | 41 + share/extensions/tests/data/refs/dm2svg.out | 27 + share/extensions/tests/data/refs/docinfo.out | 9 + share/extensions/tests/data/refs/dpiswitcher.out | 41 + .../data/refs/dpiswitcher__--id__p1__--id__r3.out | 41 + .../draw_from_triangle__--id__p1__--id__r3.out | 41 + .../tests/data/refs/dxf_input__test_r12__dxf.out | 2 + .../tests/data/refs/dxf_input__test_r14__dxf.out | 2 + share/extensions/tests/data/refs/dxf_outlines.out | 3412 +++++++++++ .../tests/data/refs/dxf_outlines__--POLY__true.out | 2880 ++++++++++ .../tests/data/refs/dxf_outlines__--ROBO__true.out | 3352 +++++++++++ .../data/refs/dxf_outlines__--id__p1__--id__r3.out | 3412 +++++++++++ .../tests/data/refs/edge3d__--id__p1__--id__r3.out | 44 + share/extensions/tests/data/refs/embedimage.out | 16 + .../tests/data/refs/export_gimp_palette.out | 12 + .../export_gimp_palette__--id__p1__--id__r3.out | 6 + ...ctedonly__False__--filepath__TMP_DIR__img__.out | Bin 0 -> 128 bytes ...embeded_image01__--filepath__TMP_DIR__img__.out | Bin 0 -> 128 bytes .../data/refs/extrude__--id__p1__--id__p2.out | 41 + share/extensions/tests/data/refs/fig_input.out | 22 + share/extensions/tests/data/refs/flatten.out | 0 .../data/refs/flatten__--id__p1__--id__r3.out | 41 + ...dablebox__--proportion__0__5__--guide__true.out | 2 + ...x__--width__20__--height__20__--depth__2__2.out | 2 + .../data/refs/fractalize__--id__p1__--id__p2.out | 41 + .../data/refs/funcplot__--id__p1__--id__r3.out | 40 + ...codetools__06eec9617e749f35cb949d850415f68d.out | 45 + ...codetools__2bf3b298fa730dafb8c6fd51921078f0.out | 40 + ...codetools__4a9fb751baf0533eadd4d394957c966d.out | 0 .../refs/generate_voronoi__--id__r3__--id__p1.out | 41 + share/extensions/tests/data/refs/gimp_xcf.out | Bin 0 -> 70038 bytes .../data/refs/gimp_xcf__-d__true__-r__true.out | Bin 0 -> 126005 bytes .../extensions/tests/data/refs/grid_cartesian.out | 41 + .../refs/grid_cartesian__--id__p1__--id__r3.out | 41 + .../extensions/tests/data/refs/grid_isometric.out | 41 + share/extensions/tests/data/refs/grid_polar.out | 41 + .../data/refs/grid_polar__--id__p1__--id__r3.out | 41 + .../guides_creator__--tab__diagonal_guides.out | 40 + ...om_edges__True__--margins_preset__book_left.out | 40 + ...m_edges__True__--margins_preset__book_right.out | 40 + ..._from_edges__True__--margins_preset__custom.out | 40 + ...ides_preset__5__5__--start_from_edges__True.out | 40 + ...ab__regular_guides__--guides_preset__custom.out | 40 + ...es__--guides_preset__golden__--delete__True.out | 34 + ...--ignore__true__--directory__TMP_DIR__img__.out | Bin 0 -> 133120 bytes ...image__f____oo__--directory__TMP_DIR__img__.out | Bin 0 -> 71680 bytes .../data/refs/handles__--id__curve__--id__quad.out | 10 + share/extensions/tests/data/refs/hershey.out | 52 + .../tests/data/refs/hershey_encoding.out | 13 + .../tests/data/refs/hershey_fonttable.out | 13 + .../tests/data/refs/hershey_glyphtable.out | 13 + .../tests/data/refs/hershey_loadfont.out | 17 + .../tests/data/refs/hershey_partialselection.out | 22 + .../tests/data/refs/hershey_preservetext.out | 22 + share/extensions/tests/data/refs/hpgl_input.out | 2 + .../data/refs/hpgl_output__hpgl_multipen__svg.out | 1 + .../tests/data/refs/hpgl_output__shapes__svg.out | 1 + .../tests/data/refs/image_attributes.out | 18 + ...timizeSpeed__--tab____tab_image_rendering__.out | 18 + ...atio__xMinYMin__--tab____tab_aspect_ratio__.out | 18 + share/extensions/tests/data/refs/ink2canvas.out | 159 + .../tests/data/refs/inkex_extensions_color.out | 32 + .../inkex_extensions_color__--id__color_svg.out | 32 + .../data/refs/inkex_extensions_color__--id__r1.out | 32 + .../inkex_extensions_color__--id__r1__--id__r2.out | 32 + .../data/refs/inkex_extensions_color__--id__r2.out | 32 + .../data/refs/inkex_extensions_color__--id__r3.out | 32 + .../data/refs/inkex_extensions_color__--id__r4.out | 41 + .../tests/data/refs/inkscape_follow_link.out | 0 .../inkscape_follow_link__--id__p1__--id__r3.out | 0 .../interp__2e7c2144ef5878a5c0824e02c83dc243.out | 161 + .../interp__359f83409ebaa8716afca1081eb4987d.out | 161 + ..._--start-val____050505__--end-val____000000.out | 27 + ..._--start-val____181818__--end-val____000000.out | 27 + .../tests/data/refs/interp_att_g__--id__layer1.out | 41 + ...autotexts__--autoText__slideTitle__--id__t1.out | 41 + ...-id__p1__--effectIn__fade__--effectOut__pop.out | 41 + .../refs/jessyink_effects__--id__p1__--id__r3.out | 0 .../data/refs/jessyink_export__--resolution__1.out | Bin 0 -> 960 bytes .../tests/data/refs/jessyink_install.out | 2766 +++++++++ .../refs/jessyink_install__--id__p1__--id__r3.out | 2766 +++++++++ ...drawing_undo__ENTER__--index_nextPage__LEFT.out | 61 + ...__a__--drawing_undo__b__--index_nextPage__c.out | 61 + .../tests/data/refs/jessyink_master_slide.out | 0 .../jessyink_master_slide__--id__p1__--id__r3.out | 0 ...yink_mouse_handler__--mouseSetting__default.out | 0 ...mouse_handler__--mouseSetting__draggingZoom.out | 475 ++ ...yink_mouse_handler__--mouseSetting__noclick.out | 94 + .../tests/data/refs/jessyink_summary.out | 10 + .../jessyink_transitions__--layerName__Slide2.out | 0 .../tests/data/refs/jessyink_uninstall.out | 39 + .../extensions/tests/data/refs/jessyink_video.out | 61 + .../jessyink_view__--id__r3__--viewOrder__1.out | 41 + ...s__--id__p1__--dist__gaussian__--end__false.out | 41 + ...__--id__p1__--dist__lognorm__--radiusx__100.out | 41 + ...s__--id__p1__--dist__pareto__--radiusy__100.out | 41 + ...s__--id__p1__--dist__uniform__--ctrl__false.out | 41 + share/extensions/tests/data/refs/layer2png.out | 0 .../data/refs/layer2png__--id__p1__--id__r3.out | 0 .../extensions/tests/data/refs/layers2svgfont.out | 3 + share/extensions/tests/data/refs/lindenmayer.out | 41 + .../data/refs/lindenmayer__--id__p1__--id__r3.out | 41 + share/extensions/tests/data/refs/lorem_ipsum.out | 41 + ...kepaint__--tab____custom____--id__dimension.out | 26 + ...kepaint__--tab____object____--id__dimension.out | 26 + .../data/refs/measure__--id__p1__--id__p2.out | 41 + ..._presets__--presetFormat__FT_bbox__--id__p2.out | 41 + ...presets__--presetFormat__FT_start__--id__p1.out | 41 + ..._presets__--presetFormat__TaP_end__--id__p2.out | 41 + ...resets__--presetFormat__TaP_start__--id__p1.out | 41 + .../data/refs/measure__--type__area__--id__p1.out | 41 + .../data/refs/measure__--type__cofm__--id__c3.out | 41 + share/extensions/tests/data/refs/media_zip.out | Bin 0 -> 7263 bytes .../data/refs/merge_styles__--id__c2__--id__c3.out | 46 + .../tests/data/refs/motion__--id__c3__--id__p2.out | 41 + .../extensions/tests/data/refs/new_glyph_layer.out | 0 .../refs/new_glyph_layer__--id__p1__--id__r3.out | 0 .../tests/data/refs/next_glyph_layer.out | 0 .../refs/next_glyph_layer__--id__p1__--id__r3.out | 0 share/extensions/tests/data/refs/nicechart.out | 41 + ...art__--file__DAT_DIR__io__nicechart_01__csv.out | 13 + ...DAT_DIR__io__nicechart_01__csv__--type__pie.out | 13 + ...DIR__io__nicechart_01__csv__--type__pie_abs.out | 13 + ...T_DIR__io__nicechart_01__csv__--type__stbar.out | 13 + .../data/refs/nicechart__--id__p1__--id__r3.out | 41 + share/extensions/tests/data/refs/output_scour.out | 40 + share/extensions/tests/data/refs/param_curves.out | 0 .../data/refs/param_curves__--id__p1__--id__r3.out | 40 + .../path_envelope__--id__obj__--id__envelope.out | 20 + .../path_envelope__--id__text__--id__envelope.out | 6 + .../path_mesh_m2p__--id__mesh1__--mode__faces.out | 78 + ...th_mesh_m2p__--id__mesh1__--mode__gridlines.out | 78 + ..._mesh_m2p__--id__mesh1__--mode__meshpatches.out | 78 + ...path_mesh_m2p__--id__mesh1__--mode__outline.out | 78 + .../path_mesh_p2m__--id__path1__--id__path9.out | 78 + .../refs/path_number_nodes__--id__p1__--id__r3.out | 40 + ...h_to_absolute__--id__c1__--id__c2__--id__c3.out | 41 + ...ute__--id__p1__--id__p2__--id__s1__--id__u1.out | 41 + ...id__r1__--id__r2__--id__r3__--id__slicerect.out | 41 + ...d__r1__--id__r2__--id__r3__--id__slicerect1.out | 36 + ...ath__--copymode__Single__--id__p1__--id__p2.out | 41 + .../data/refs/pathscatter__--id__p1__--id__r3.out | 41 + .../pdflatex__cceb2358b6829feda6d763508a98eaf1.out | 120 + .../tests/data/refs/perfectboundcover.out | 41 + .../perspective__--id__obj__--id__envelope.out | 20 + .../data/refs/perspective__--id__p1__--id__p2.out | 41 + .../perspective__--id__text__--id__envelope.out | 6 + .../data/refs/pixelsnap__--id__p1__--id__r3.out | 41 + .../data/refs/plotter__--serialPort____test__.out | 35 + ...rialPort____test____--commandLanguage__DMPL.out | 2 + ...erialPort____test____--commandLanguage__KNK.out | 34 + share/extensions/tests/data/refs/plt_output.out | 1 + ...obj__oct__--r1_ax__z__--r1_ang__45__--th__4.out | 2 + ...__x__--r1_ang__45__--r2_ax__y__--r2_ang__45.out | 2 + ...e__--r1_ax__y__--r1_ang__45__--z_sort__cent.out | 2 + ...be__--r1_ax__z__--r1_ang__45__--z_sort__max.out | 2 + .../polyhedron_3d__--show__vtx__--obj__methane.out | 2 + ...hedron_3d__31c852a9dcfffc92123ff370cba34361.out | 41 + .../tests/data/refs/prepare_file_save_as.out | 115 + .../tests/data/refs/previous_glyph_layer.out | 0 .../previous_glyph_layer__--id__p1__--id__r3.out | 0 .../tests/data/refs/print_win32_vector.out | 0 .../print_win32_vector__--id__p1__--id__r3.out | 0 .../extensions/tests/data/refs/printing_marks.out | 41 + .../refs/printing_marks__--id__p1__--id__r3.out | 41 + .../tests/data/refs/ps_input__test__eps.out | 81 + .../tests/data/refs/ps_input__test__ps.out | Bin 0 -> 2547 bytes ...er_barcode__--type__Code93__--text__3332222.out | 41 + .../render_barcode__--type__Ean2__--text__55.out | 41 + ...ender_barcode__--type__Upce__--text__123456.out | 41 + ...matrix__--symbol__rect8x32__--text__1234Foo.out | 2 + .../render_barcode_datamatrix__--symbol__sq10.out | 2 + ...amatrix__--symbol__sq144__--text__HelloTest.out | 2 + ...atamatrix__--symbol__sq96__--text__Sunshine.out | 2 + ...qrcode__--text__0123456789__--typenumber__0.out | 2 + ...Yard__--typenumber__3__--correctionlevel__1.out | 2 + ...eadRolls__--typenumber__2__--encoding__utf8.out | 2 + ...level__2__--symbolid__AirTransportation_Inv.out | 12 + ...erfall__--typenumber__1__--drawtype__circle.out | 2 + ..._qrcode__--text__groupid__--groupid__testid.out | 2 + .../tests/data/refs/render_gear_rack.out | 41 + .../refs/render_gear_rack__--id__p1__--id__r3.out | 41 + share/extensions/tests/data/refs/render_gears.out | 41 + .../data/refs/render_gears__--id__p1__--id__r3.out | 41 + ...r_find__sans-serif__--fr_replace__monospace.out | 41 + .../refs/replace_font__--action__list_only.out | 1 + ...tack__--tab__positional__--id__p1__--id__r3.out | 41 + ...restack__--tab__z_order__--id__p1__--id__r3.out | 41 + share/extensions/tests/data/refs/rtree.out | 41 + .../refs/rubberstretch__--id__p1__--id__r3.out | 41 + .../tests/data/refs/scribus_export_pdf.out | Bin 0 -> 14059 bytes .../tests/data/refs/setup_typography_canvas.out | 41 + ...setup_typography_canvas__--id__p1__--id__r3.out | 41 + share/extensions/tests/data/refs/sk1_output.out | 126 + share/extensions/tests/data/refs/spirograph.out | 41 + .../data/refs/spirograph__--id__p1__--id__r3.out | 41 + share/extensions/tests/data/refs/straightseg.out | 0 .../data/refs/straightseg__--id__p1__--id__r3.out | 41 + share/extensions/tests/data/refs/svgcalendar.out | 41 + .../tests/data/refs/svgfont2layers__--count__3.out | 20 + share/extensions/tests/data/refs/tar_layers.out | Bin 0 -> 13312 bytes .../data/refs/tar_layers__--id__p1__--id__r3.out | Bin 0 -> 13312 bytes ...50__--grid__true__--orientation__horizontal.out | 2 + ...0x50__--grid__true__--orientation__vertical.out | 2 + ...15mm__--background__black__--noborder__true.out | 2 + ...om__--width__100__--height__100__--unit__in.out | 2 + .../refs/template_dvd_cover__-s__10__-b__10.out | 2 + ...amless_pattern__--width__100__--height__100.out | 99 + .../extensions/tests/data/refs/test_color_list.out | 9 + .../data/refs/test_color_list__--id__color_svg.out | 9 + .../tests/data/refs/test_color_list__--id__r1.out | 3 + .../refs/test_color_list__--id__r1__--id__r2.out | 9 + .../tests/data/refs/test_color_list__--id__r2.out | 6 + .../tests/data/refs/test_color_list__--id__r3.out | 8 + .../tests/data/refs/test_color_list__--id__r4.out | 9 + share/extensions/tests/data/refs/text_braille.out | 41 + .../data/refs/text_extract__--direction__bt.out | 8 + .../data/refs/text_extract__--direction__lr.out | 8 + .../data/refs/text_extract__--direction__rl.out | 8 + .../data/refs/text_extract__--direction__tb.out | 8 + share/extensions/tests/data/refs/text_flipcase.out | 41 + .../extensions/tests/data/refs/text_lowercase.out | 41 + share/extensions/tests/data/refs/text_merge.out | 41 + .../extensions/tests/data/refs/text_randomcase.out | 41 + .../tests/data/refs/text_sentencecase.out | 41 + .../data/refs/text_split__--id__t1__--id__t3.out | 41 + .../extensions/tests/data/refs/text_titlecase.out | 41 + .../extensions/tests/data/refs/text_uppercase.out | 41 + share/extensions/tests/data/refs/triangle.out | 41 + .../data/refs/triangle__--id__p1__--id__r3.out | 41 + share/extensions/tests/data/refs/ungroup_deep.out | 33 + .../tests/data/refs/ungroup_deep__--id__layer2.out | 39 + .../data/refs/voronoi2svg__--id__p1__--id__r3.out | 41 + .../web_interactive_mockup__--id__p1__--id__r3.out | 259 + .../data/refs/web_set_att__--id__p1__--id__r3.out | 259 + .../refs/web_transmit_att__--id__p1__--id__r3.out | 259 + .../webslicer_create_group__--id__slicerect1.out | 41 + .../tests/data/refs/webslicer_create_rect.out | 41 + .../webslicer_create_rect__--id__p1__--id__r3.out | 41 + .../data/refs/webslicer_export__--dir__TMP_DIR.out | 0 .../tests/data/refs/whirl__--id__p1__--id__r3.out | 41 + .../tests/data/refs/wireframe_sphere.out | 41 + .../refs/wireframe_sphere__--id__p1__--id__r3.out | 41 + share/extensions/tests/data/refs/wmf_output.out | Bin 0 -> 4698 bytes share/extensions/tests/data/svg/colors.svg | 33 + .../tests/data/svg/complextransform.test.svg | 70 + share/extensions/tests/data/svg/css.svg | 94 + share/extensions/tests/data/svg/curves.svg | 72 + share/extensions/tests/data/svg/dash.svg | 34 + .../tests/data/svg/default-inkscape-SVG.svg | 37 + .../data/svg/default-inkscape-SVG_scoured.svg | 12 + .../tests/data/svg/default-plain-SVG.svg | 29 + share/extensions/tests/data/svg/diff.svg | 281 + share/extensions/tests/data/svg/edge3d.svg | 31 + share/extensions/tests/data/svg/empty.svg | 13 + share/extensions/tests/data/svg/font.svg | 27 + share/extensions/tests/data/svg/font_layers.svg | 3 + .../tests/data/svg/group_interpolate.svg | 110 + share/extensions/tests/data/svg/guides.svg | 233 + share/extensions/tests/data/svg/hershey_input.svg | 1071 ++++ .../tests/data/svg/hershey_trivial_input.svg | 124 + share/extensions/tests/data/svg/hpgl_multipen.svg | 43 + share/extensions/tests/data/svg/images.svg | 73 + share/extensions/tests/data/svg/img/green.png | Bin 0 -> 128 bytes share/extensions/tests/data/svg/inkweb-debug.js | 367 ++ share/extensions/tests/data/svg/inkwebjs-move.svg | 128 + share/extensions/tests/data/svg/interp_shapes.svg | 210 + share/extensions/tests/data/svg/markers.svg | 55 + share/extensions/tests/data/svg/mesh.svg | 240 + .../tests/data/svg/minimal-blank-prepare.svg | 1 + share/extensions/tests/data/svg/minimal-blank.svg | 1 + .../tests/data/svg/multilayered-test.svg | 156 + share/extensions/tests/data/svg/perspective.svg | 33 + .../tests/data/svg/perspective_groups.svg | 78 + .../extensions/tests/data/svg/shapes-clipboard.svg | 288 + share/extensions/tests/data/svg/shapes.svg | 284 + share/extensions/tests/data/svg/shapes_cmyk.svg | 302 + .../tests/data/svg/simpletransform.test.svg | 8 + share/extensions/tests/data/svg/single_box.svg | 62 + share/extensions/tests/data/svg/slicer.svg | 75 + share/extensions/tests/data/svg/symbol.svg | 46 + share/extensions/tests/data/svg/with-lpe.svg | 30 + share/extensions/tests/dev_requirements.txt | 10 + share/extensions/tests/test_addnodes.py | 20 + share/extensions/tests/test_color_HSL_adjust.py | 32 + share/extensions/tests/test_color_blackandwhite.py | 32 + share/extensions/tests/test_color_brighter.py | 26 + share/extensions/tests/test_color_custom.py | 49 + share/extensions/tests/test_color_darker.py | 27 + share/extensions/tests/test_color_desaturate.py | 25 + share/extensions/tests/test_color_grayscale.py | 25 + share/extensions/tests/test_color_lesshue.py | 27 + share/extensions/tests/test_color_lesslight.py | 27 + .../extensions/tests/test_color_lesssaturation.py | 27 + share/extensions/tests/test_color_list.py | 9 + share/extensions/tests/test_color_morehue.py | 28 + share/extensions/tests/test_color_morelight.py | 27 + .../extensions/tests/test_color_moresaturation.py | 27 + share/extensions/tests/test_color_negative.py | 25 + share/extensions/tests/test_color_randomize.py | 38 + share/extensions/tests/test_color_removeblue.py | 25 + share/extensions/tests/test_color_removegreen.py | 25 + share/extensions/tests/test_color_removered.py | 25 + share/extensions/tests/test_color_replace.py | 15 + share/extensions/tests/test_color_rgbbarrel.py | 26 + share/extensions/tests/test_convert2dashes.py | 16 + share/extensions/tests/test_deprecated_simple.py | 198 + share/extensions/tests/test_dhw_input.py | 18 + share/extensions/tests/test_dimension.py | 10 + share/extensions/tests/test_docinfo.py | 9 + share/extensions/tests/test_dpiswitcher.py | 8 + share/extensions/tests/test_draw_from_triangle.py | 9 + share/extensions/tests/test_dxf12_outlines.py | 7 + share/extensions/tests/test_dxf_input.py | 20 + share/extensions/tests/test_dxf_outlines.py | 13 + share/extensions/tests/test_edge3d.py | 25 + share/extensions/tests/test_embedimage.py | 10 + share/extensions/tests/test_export_gimp_palette.py | 7 + share/extensions/tests/test_extractimage.py | 40 + share/extensions/tests/test_extrude.py | 10 + share/extensions/tests/test_fig_input.py | 10 + share/extensions/tests/test_flatten.py | 8 + share/extensions/tests/test_foldablebox.py | 11 + share/extensions/tests/test_fractalize.py | 7 + share/extensions/tests/test_frame.py | 96 + share/extensions/tests/test_funcplot.py | 11 + share/extensions/tests/test_gcodetools.py | 83 + share/extensions/tests/test_generate_voronoi.py | 9 + share/extensions/tests/test_gimp_xcf.py | 20 + share/extensions/tests/test_grid_cartesian.py | 8 + share/extensions/tests/test_grid_isometric.py | 10 + share/extensions/tests/test_grid_polar.py | 8 + share/extensions/tests/test_guides_creator.py | 20 + share/extensions/tests/test_guillotine.py | 58 + share/extensions/tests/test_handles.py | 11 + share/extensions/tests/test_hershey.py | 57 + share/extensions/tests/test_hpgl_decoder.py | 25 + share/extensions/tests/test_hpgl_input.py | 8 + share/extensions/tests/test_hpgl_output.py | 12 + share/extensions/tests/test_image_attributes.py | 12 + share/extensions/tests/test_ink2canvas_svg.py | 11 + share/extensions/tests/test_inkex.py | 63 + share/extensions/tests/test_inkex_base.py | 136 + share/extensions/tests/test_inkex_bezier.py | 10 + share/extensions/tests/test_inkex_bounding_box.py | 668 +++ share/extensions/tests/test_inkex_colors.py | 180 + share/extensions/tests/test_inkex_command.py | 16 + share/extensions/tests/test_inkex_cubic_paths.py | 59 + share/extensions/tests/test_inkex_deprecated.py | 36 + share/extensions/tests/test_inkex_elements.py | 496 ++ share/extensions/tests/test_inkex_elements_base.py | 322 ++ .../tests/test_inkex_elements_selections.py | 109 + share/extensions/tests/test_inkex_extensions.py | 59 + share/extensions/tests/test_inkex_inx.py | 22 + share/extensions/tests/test_inkex_paths.py | 520 ++ share/extensions/tests/test_inkex_styles.py | 229 + share/extensions/tests/test_inkex_svg.py | 461 ++ share/extensions/tests/test_inkex_tester.py | 28 + share/extensions/tests/test_inkex_transforms.py | 567 ++ share/extensions/tests/test_inkex_tween.py | 21 + share/extensions/tests/test_inkex_units.py | 64 + share/extensions/tests/test_inkex_utils.py | 98 + .../extensions/tests/test_inkscape_follow_link.py | 6 + share/extensions/tests/test_inkwebeffect.py | 7 + share/extensions/tests/test_interp.py | 15 + share/extensions/tests/test_interp_att_g.py | 17 + share/extensions/tests/test_jessyink_autotexts.py | 8 + share/extensions/tests/test_jessyink_effects.py | 12 + share/extensions/tests/test_jessyink_export.py | 9 + share/extensions/tests/test_jessyink_install.py | 6 + .../extensions/tests/test_jessyink_keybindings.py | 10 + .../extensions/tests/test_jessyink_masterslide.py | 6 + .../extensions/tests/test_jessyink_mousehandler.py | 12 + share/extensions/tests/test_jessyink_summary.py | 8 + .../extensions/tests/test_jessyink_transitions.py | 7 + share/extensions/tests/test_jessyink_uninstall.py | 7 + share/extensions/tests/test_jessyink_video.py | 9 + share/extensions/tests/test_jessyink_view.py | 8 + share/extensions/tests/test_jitternodes.py | 13 + share/extensions/tests/test_launch_webbrowser.py | 23 + share/extensions/tests/test_layer2png.py | 43 + share/extensions/tests/test_layers2svgfont.py | 8 + share/extensions/tests/test_layout_nup.py | 7 + share/extensions/tests/test_lindenmayer.py | 8 + share/extensions/tests/test_lorem_ipsum.py | 7 + share/extensions/tests/test_markers_strokepaint.py | 26 + share/extensions/tests/test_measure.py | 18 + share/extensions/tests/test_media_zip.py | 9 + share/extensions/tests/test_merge_styles.py | 8 + share/extensions/tests/test_motion.py | 11 + share/extensions/tests/test_new_glyph_layer.py | 6 + share/extensions/tests/test_next_glyph_layer.py | 6 + share/extensions/tests/test_nicechart.py | 20 + share/extensions/tests/test_output_scour.py | 10 + share/extensions/tests/test_param_curves.py | 8 + share/extensions/tests/test_path_envelope.py | 15 + share/extensions/tests/test_path_mesh.py | 25 + share/extensions/tests/test_path_number_nodes.py | 7 + share/extensions/tests/test_path_to_absolute.py | 13 + share/extensions/tests/test_pathalongpath.py | 9 + share/extensions/tests/test_pathscatter.py | 9 + share/extensions/tests/test_pdflatex.py | 34 + share/extensions/tests/test_perfectboundcover.py | 7 + share/extensions/tests/test_perspective.py | 15 + share/extensions/tests/test_pixelsnap.py | 9 + share/extensions/tests/test_plotter.py | 18 + share/extensions/tests/test_polyhedron_3d.py | 14 + .../extensions/tests/test_prepare_file_save_as.py | 9 + .../extensions/tests/test_previous_glyph_layer.py | 6 + share/extensions/tests/test_print_win32_vector.py | 11 + share/extensions/tests/test_printing_marks.py | 13 + share/extensions/tests/test_ps_input.py | 17 + share/extensions/tests/test_render_alphabetsoup.py | 6 + share/extensions/tests/test_render_barcode.py | 100 + .../tests/test_render_barcode_datamatrix.py | 13 + .../extensions/tests/test_render_barcode_qrcode.py | 24 + share/extensions/tests/test_render_gear_rack.py | 8 + share/extensions/tests/test_render_gears.py | 8 + share/extensions/tests/test_replace_font.py | 18 + share/extensions/tests/test_restack.py | 10 + share/extensions/tests/test_rtree.py | 9 + share/extensions/tests/test_rubberstretch.py | 7 + share/extensions/tests/test_scribus_pdf.py | 13 + .../tests/test_setup_typography_canvas.py | 6 + share/extensions/tests/test_spirograph.py | 8 + share/extensions/tests/test_straightseg.py | 8 + share/extensions/tests/test_svgcalendar.py | 99 + share/extensions/tests/test_svgfont2layers.py | 10 + share/extensions/tests/test_synfig_fileformat.py | 7 + share/extensions/tests/test_synfig_output.py | 6 + share/extensions/tests/test_synfig_prepare.py | 6 + share/extensions/tests/test_tar_layers.py | 8 + share/extensions/tests/test_template.py | 13 + share/extensions/tests/test_template_dvd_cover.py | 8 + .../tests/test_template_seamless_pattern.py | 9 + share/extensions/tests/test_text_braille.py | 8 + share/extensions/tests/test_text_extract.py | 13 + share/extensions/tests/test_text_flipcase.py | 7 + share/extensions/tests/test_text_lowercase.py | 56 + share/extensions/tests/test_text_merge.py | 7 + share/extensions/tests/test_text_randomcase.py | 7 + share/extensions/tests/test_text_sentencecase.py | 7 + share/extensions/tests/test_text_split.py | 8 + share/extensions/tests/test_text_titlecase.py | 67 + share/extensions/tests/test_text_uppercase.py | 44 + share/extensions/tests/test_triangle.py | 8 + share/extensions/tests/test_ungroup_deep.py | 12 + share/extensions/tests/test_voronoi.py | 9 + share/extensions/tests/test_voronoi2svg.py | 9 + .../tests/test_web_interactive_mockup.py | 7 + share/extensions/tests/test_web_set_att.py | 7 + share/extensions/tests/test_web_transmit_att.py | 7 + .../tests/test_webslicer_create_group.py | 7 + .../extensions/tests/test_webslicer_create_rect.py | 6 + share/extensions/tests/test_webslicer_export.py | 11 + share/extensions/tests/test_whirl.py | 10 + share/extensions/tests/test_wireframe_sphere.py | 8 + share/extensions/text_braille.inx | 14 + share/extensions/text_braille.py | 22 + share/extensions/text_extract.inx | 30 + share/extensions/text_extract.py | 70 + share/extensions/text_flipcase.inx | 16 + share/extensions/text_flipcase.py | 13 + share/extensions/text_lowercase.inx | 16 + share/extensions/text_lowercase.py | 12 + share/extensions/text_merge.inx | 32 + share/extensions/text_merge.py | 118 + share/extensions/text_randomcase.inx | 16 + share/extensions/text_randomcase.py | 29 + share/extensions/text_sentencecase.inx | 16 + share/extensions/text_sentencecase.py | 35 + share/extensions/text_split.inx | 27 + share/extensions/text_split.py | 191 + share/extensions/text_titlecase.inx | 16 + share/extensions/text_titlecase.py | 30 + share/extensions/text_uppercase.inx | 16 + share/extensions/text_uppercase.py | 12 + share/extensions/tools/generate_argparse_conf.py | 60 + share/extensions/tox.ini | 12 + share/extensions/triangle.inx | 27 + share/extensions/triangle.py | 188 + share/extensions/ungroup_deep.inx | 18 + share/extensions/ungroup_deep.py | 187 + share/extensions/voronoi.py | 836 +++ share/extensions/voronoi2svg.inx | 39 + share/extensions/voronoi2svg.py | 292 + share/extensions/web_interactive_mockup.inx | 38 + share/extensions/web_interactive_mockup.py | 43 + share/extensions/web_set_att.inx | 50 + share/extensions/web_set_att.py | 69 + share/extensions/web_transmit_att.inx | 48 + share/extensions/web_transmit_att.py | 55 + share/extensions/webslicer_create_group.inx | 37 + share/extensions/webslicer_create_group.py | 56 + share/extensions/webslicer_create_rect.inx | 71 + share/extensions/webslicer_create_rect.py | 101 + share/extensions/webslicer_effect.py | 53 + share/extensions/webslicer_export.inx | 27 + share/extensions/webslicer_export.py | 417 ++ share/extensions/whirl.inx | 16 + share/extensions/whirl.py | 61 + share/extensions/wireframe_sphere.inx | 20 + share/extensions/wireframe_sphere.py | 204 + share/extensions/xaml2svg.inx | 15 + share/extensions/xaml2svg.xsl | 107 + share/extensions/xaml2svg/animation.xsl | 141 + share/extensions/xaml2svg/brushes.xsl | 244 + share/extensions/xaml2svg/canvas.xsl | 80 + share/extensions/xaml2svg/geometry.xsl | 272 + share/extensions/xaml2svg/properties.xsl | 286 + share/extensions/xaml2svg/shapes.xsl | 171 + share/extensions/xaml2svg/transform.xsl | 120 + 1464 files changed, 129781 insertions(+) create mode 100644 share/extensions/.pylintrc create mode 100644 share/extensions/.pytest_cache/CACHEDIR.TAG create mode 100644 share/extensions/.pytest_cache/README.md create mode 100644 share/extensions/.pytest_cache/v/cache/nodeids create mode 100644 share/extensions/.pytest_cache/v/cache/stepwise create mode 100644 share/extensions/LICENSE.txt create mode 100644 share/extensions/MANIFEST.in create mode 100644 share/extensions/Poly3DObjects/cube.obj create mode 100644 share/extensions/Poly3DObjects/cuboct.obj create mode 100644 share/extensions/Poly3DObjects/dodec.obj create mode 100644 share/extensions/Poly3DObjects/great_dodec.obj create mode 100644 share/extensions/Poly3DObjects/great_rhombicosidodec.obj create mode 100644 share/extensions/Poly3DObjects/great_rhombicuboct.obj create mode 100644 share/extensions/Poly3DObjects/great_stel_dodec.obj create mode 100644 share/extensions/Poly3DObjects/icos.obj create mode 100644 share/extensions/Poly3DObjects/icosidodec.obj create mode 100644 share/extensions/Poly3DObjects/jessens_orthog_icos.obj create mode 100644 share/extensions/Poly3DObjects/methane.obj create mode 100644 share/extensions/Poly3DObjects/oct.obj create mode 100644 share/extensions/Poly3DObjects/rh_axes.obj create mode 100644 share/extensions/Poly3DObjects/rhomb_dodec.obj create mode 100644 share/extensions/Poly3DObjects/rhomb_triacont.obj create mode 100644 share/extensions/Poly3DObjects/small_rhombicosidodec.obj create mode 100644 share/extensions/Poly3DObjects/small_rhombicuboct.obj create mode 100644 share/extensions/Poly3DObjects/small_triam_icos.obj create mode 100644 share/extensions/Poly3DObjects/snub_cube.obj create mode 100644 share/extensions/Poly3DObjects/snub_dodec.obj create mode 100644 share/extensions/Poly3DObjects/szilassi.obj create mode 100644 share/extensions/Poly3DObjects/tet.obj create mode 100644 share/extensions/Poly3DObjects/trunc_cube.obj create mode 100644 share/extensions/Poly3DObjects/trunc_dodec.obj create mode 100644 share/extensions/Poly3DObjects/trunc_icos.obj create mode 100644 share/extensions/Poly3DObjects/trunc_oct.obj create mode 100644 share/extensions/Poly3DObjects/trunc_tet.obj create mode 100644 share/extensions/README.md create mode 100644 share/extensions/STYLEGUIDE.md create mode 100644 share/extensions/__pycache__/addnodes.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_HSL_adjust.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_blackandwhite.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_brighter.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_custom.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_darker.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_desaturate.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_grayscale.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_lesshue.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_lesslight.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_lesssaturation.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_list.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_morehue.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_morelight.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_moresaturation.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_negative.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_randomize.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_removeblue.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_removegreen.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_removered.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_replace.cpython-39.pyc create mode 100644 share/extensions/__pycache__/color_rgbbarrel.cpython-39.pyc create mode 100644 share/extensions/__pycache__/convert2dashes.cpython-39.pyc create mode 100644 share/extensions/__pycache__/dhw_input.cpython-39.pyc create mode 100644 share/extensions/__pycache__/dimension.cpython-39.pyc create mode 100644 share/extensions/__pycache__/docinfo.cpython-39.pyc create mode 100644 share/extensions/__pycache__/dpiswitcher.cpython-39.pyc create mode 100644 share/extensions/__pycache__/draw_from_triangle.cpython-39.pyc create mode 100644 share/extensions/__pycache__/dxf12_outlines.cpython-39.pyc create mode 100644 share/extensions/__pycache__/dxf_input.cpython-39.pyc create mode 100644 share/extensions/__pycache__/dxf_outlines.cpython-39.pyc create mode 100644 share/extensions/__pycache__/edge3d.cpython-39.pyc create mode 100644 share/extensions/__pycache__/embedimage.cpython-39.pyc create mode 100644 share/extensions/__pycache__/export_gimp_palette.cpython-39.pyc create mode 100644 share/extensions/__pycache__/extractimage.cpython-39.pyc create mode 100644 share/extensions/__pycache__/extrude.cpython-39.pyc create mode 100644 share/extensions/__pycache__/fig_input.cpython-39.pyc create mode 100644 share/extensions/__pycache__/flatten.cpython-39.pyc create mode 100644 share/extensions/__pycache__/foldablebox.cpython-39.pyc create mode 100644 share/extensions/__pycache__/fractalize.cpython-39.pyc create mode 100644 share/extensions/__pycache__/frame.cpython-39.pyc create mode 100644 share/extensions/__pycache__/funcplot.cpython-39.pyc create mode 100644 share/extensions/__pycache__/gcodetools.cpython-39.pyc create mode 100644 share/extensions/__pycache__/generate_voronoi.cpython-39.pyc create mode 100644 share/extensions/__pycache__/gimp_xcf.cpython-39.pyc create mode 100644 share/extensions/__pycache__/grid_cartesian.cpython-39.pyc create mode 100644 share/extensions/__pycache__/grid_isometric.cpython-39.pyc create mode 100644 share/extensions/__pycache__/grid_polar.cpython-39.pyc create mode 100644 share/extensions/__pycache__/guides_creator.cpython-39.pyc create mode 100644 share/extensions/__pycache__/guillotine.cpython-39.pyc create mode 100644 share/extensions/__pycache__/handles.cpython-39.pyc create mode 100644 share/extensions/__pycache__/hershey.cpython-39.pyc create mode 100644 share/extensions/__pycache__/hpgl_decoder.cpython-39.pyc create mode 100644 share/extensions/__pycache__/hpgl_encoder.cpython-39.pyc create mode 100644 share/extensions/__pycache__/hpgl_input.cpython-39.pyc create mode 100644 share/extensions/__pycache__/hpgl_output.cpython-39.pyc create mode 100644 share/extensions/__pycache__/image_attributes.cpython-39.pyc create mode 100644 share/extensions/__pycache__/ink2canvas.cpython-39.pyc create mode 100644 share/extensions/__pycache__/inkscape_follow_link.cpython-39.pyc create mode 100644 share/extensions/__pycache__/inkwebeffect.cpython-39.pyc create mode 100644 share/extensions/__pycache__/interp.cpython-39.pyc create mode 100644 share/extensions/__pycache__/interp_att_g.cpython-39.pyc create mode 100644 share/extensions/__pycache__/jessyink_autotexts.cpython-39.pyc create mode 100644 share/extensions/__pycache__/jessyink_effects.cpython-39.pyc create mode 100644 share/extensions/__pycache__/jessyink_export.cpython-39.pyc create mode 100644 share/extensions/__pycache__/jessyink_install.cpython-39.pyc create mode 100644 share/extensions/__pycache__/jessyink_key_bindings.cpython-39.pyc create mode 100644 share/extensions/__pycache__/jessyink_master_slide.cpython-39.pyc create mode 100644 share/extensions/__pycache__/jessyink_mouse_handler.cpython-39.pyc create mode 100644 share/extensions/__pycache__/jessyink_summary.cpython-39.pyc create mode 100644 share/extensions/__pycache__/jessyink_transitions.cpython-39.pyc create mode 100644 share/extensions/__pycache__/jessyink_uninstall.cpython-39.pyc create mode 100644 share/extensions/__pycache__/jessyink_video.cpython-39.pyc create mode 100644 share/extensions/__pycache__/jessyink_view.cpython-39.pyc create mode 100644 share/extensions/__pycache__/jitternodes.cpython-39.pyc create mode 100644 share/extensions/__pycache__/launch_webbrowser.cpython-39.pyc create mode 100644 share/extensions/__pycache__/layer2png.cpython-39.pyc create mode 100644 share/extensions/__pycache__/layers2svgfont.cpython-39.pyc create mode 100644 share/extensions/__pycache__/layout_nup.cpython-39.pyc create mode 100644 share/extensions/__pycache__/lindenmayer.cpython-39.pyc create mode 100644 share/extensions/__pycache__/lorem_ipsum.cpython-39.pyc create mode 100644 share/extensions/__pycache__/markers_strokepaint.cpython-39.pyc create mode 100644 share/extensions/__pycache__/measure.cpython-39.pyc create mode 100644 share/extensions/__pycache__/media_zip.cpython-39.pyc create mode 100644 share/extensions/__pycache__/merge_styles.cpython-39.pyc create mode 100644 share/extensions/__pycache__/motion.cpython-39.pyc create mode 100644 share/extensions/__pycache__/new_glyph_layer.cpython-39.pyc create mode 100644 share/extensions/__pycache__/next_glyph_layer.cpython-39.pyc create mode 100644 share/extensions/__pycache__/nicechart.cpython-39.pyc create mode 100644 share/extensions/__pycache__/output_scour.cpython-39.pyc create mode 100644 share/extensions/__pycache__/param_curves.cpython-39.pyc create mode 100644 share/extensions/__pycache__/path_envelope.cpython-39.pyc create mode 100644 share/extensions/__pycache__/path_mesh_m2p.cpython-39.pyc create mode 100644 share/extensions/__pycache__/path_mesh_p2m.cpython-39.pyc create mode 100644 share/extensions/__pycache__/path_number_nodes.cpython-39.pyc create mode 100644 share/extensions/__pycache__/path_to_absolute.cpython-39.pyc create mode 100644 share/extensions/__pycache__/pathalongpath.cpython-39.pyc create mode 100644 share/extensions/__pycache__/pathmodifier.cpython-39.pyc create mode 100644 share/extensions/__pycache__/pathscatter.cpython-39.pyc create mode 100644 share/extensions/__pycache__/pdflatex.cpython-39.pyc create mode 100644 share/extensions/__pycache__/perfectboundcover.cpython-39.pyc create mode 100644 share/extensions/__pycache__/perspective.cpython-39.pyc create mode 100644 share/extensions/__pycache__/pixelsnap.cpython-39.pyc create mode 100644 share/extensions/__pycache__/plotter.cpython-39.pyc create mode 100644 share/extensions/__pycache__/polyhedron_3d.cpython-39.pyc create mode 100644 share/extensions/__pycache__/prepare_file_save_as.cpython-39.pyc create mode 100644 share/extensions/__pycache__/previous_glyph_layer.cpython-39.pyc create mode 100644 share/extensions/__pycache__/print_win32_vector.cpython-39.pyc create mode 100644 share/extensions/__pycache__/printing_marks.cpython-39.pyc create mode 100644 share/extensions/__pycache__/ps_input.cpython-39.pyc create mode 100644 share/extensions/__pycache__/render_alphabetsoup.cpython-39.pyc create mode 100644 share/extensions/__pycache__/render_alphabetsoup_config.cpython-39.pyc create mode 100644 share/extensions/__pycache__/render_barcode.cpython-39.pyc create mode 100644 share/extensions/__pycache__/render_barcode_datamatrix.cpython-39.pyc create mode 100644 share/extensions/__pycache__/render_barcode_qrcode.cpython-39.pyc create mode 100644 share/extensions/__pycache__/render_gear_rack.cpython-39.pyc create mode 100644 share/extensions/__pycache__/render_gears.cpython-39.pyc create mode 100644 share/extensions/__pycache__/replace_font.cpython-39.pyc create mode 100644 share/extensions/__pycache__/restack.cpython-39.pyc create mode 100644 share/extensions/__pycache__/rtree.cpython-39.pyc create mode 100644 share/extensions/__pycache__/rubberstretch.cpython-39.pyc create mode 100644 share/extensions/__pycache__/scribus_export_pdf.cpython-39.pyc create mode 100644 share/extensions/__pycache__/setup_typography_canvas.cpython-39.pyc create mode 100644 share/extensions/__pycache__/spirograph.cpython-39.pyc create mode 100644 share/extensions/__pycache__/straightseg.cpython-39.pyc create mode 100644 share/extensions/__pycache__/svgcalendar.cpython-39.pyc create mode 100644 share/extensions/__pycache__/svgfont2layers.cpython-39.pyc create mode 100644 share/extensions/__pycache__/synfig_fileformat.cpython-39.pyc create mode 100644 share/extensions/__pycache__/synfig_output.cpython-39.pyc create mode 100644 share/extensions/__pycache__/synfig_prepare.cpython-39.pyc create mode 100644 share/extensions/__pycache__/tar_layers.cpython-39.pyc create mode 100644 share/extensions/__pycache__/template.cpython-39.pyc create mode 100644 share/extensions/__pycache__/template_dvd_cover.cpython-39.pyc create mode 100644 share/extensions/__pycache__/template_seamless_pattern.cpython-39.pyc create mode 100644 share/extensions/__pycache__/text_braille.cpython-39.pyc create mode 100644 share/extensions/__pycache__/text_extract.cpython-39.pyc create mode 100644 share/extensions/__pycache__/text_flipcase.cpython-39.pyc create mode 100644 share/extensions/__pycache__/text_lowercase.cpython-39.pyc create mode 100644 share/extensions/__pycache__/text_merge.cpython-39.pyc create mode 100644 share/extensions/__pycache__/text_randomcase.cpython-39.pyc create mode 100644 share/extensions/__pycache__/text_sentencecase.cpython-39.pyc create mode 100644 share/extensions/__pycache__/text_split.cpython-39.pyc create mode 100644 share/extensions/__pycache__/text_titlecase.cpython-39.pyc create mode 100644 share/extensions/__pycache__/text_uppercase.cpython-39.pyc create mode 100644 share/extensions/__pycache__/triangle.cpython-39.pyc create mode 100644 share/extensions/__pycache__/ungroup_deep.cpython-39.pyc create mode 100644 share/extensions/__pycache__/voronoi.cpython-39.pyc create mode 100644 share/extensions/__pycache__/voronoi2svg.cpython-39.pyc create mode 100644 share/extensions/__pycache__/web_interactive_mockup.cpython-39.pyc create mode 100644 share/extensions/__pycache__/web_set_att.cpython-39.pyc create mode 100644 share/extensions/__pycache__/web_transmit_att.cpython-39.pyc create mode 100644 share/extensions/__pycache__/webslicer_create_group.cpython-39.pyc create mode 100644 share/extensions/__pycache__/webslicer_create_rect.cpython-39.pyc create mode 100644 share/extensions/__pycache__/webslicer_effect.cpython-39.pyc create mode 100644 share/extensions/__pycache__/webslicer_export.cpython-39.pyc create mode 100644 share/extensions/__pycache__/whirl.cpython-39.pyc create mode 100644 share/extensions/__pycache__/wireframe_sphere.cpython-39.pyc create mode 100644 share/extensions/addnodes.inx create mode 100755 share/extensions/addnodes.py create mode 100644 share/extensions/aisvg.inx create mode 100644 share/extensions/aisvg.xslt create mode 100644 share/extensions/alphabet_soup/2.svg create mode 100644 share/extensions/alphabet_soup/3.svg create mode 100644 share/extensions/alphabet_soup/6.svg create mode 100644 share/extensions/alphabet_soup/7.svg create mode 100644 share/extensions/alphabet_soup/Cblob.svg create mode 100644 share/extensions/alphabet_soup/Chook.svg create mode 100644 share/extensions/alphabet_soup/Ctail.svg create mode 100644 share/extensions/alphabet_soup/Delta.svg create mode 100644 share/extensions/alphabet_soup/Eb.svg create mode 100644 share/extensions/alphabet_soup/Eserif.svg create mode 100644 share/extensions/alphabet_soup/Et.svg create mode 100644 share/extensions/alphabet_soup/G.svg create mode 100644 share/extensions/alphabet_soup/IBSerif.svg create mode 100644 share/extensions/alphabet_soup/ITSerif.svg create mode 100644 share/extensions/alphabet_soup/Lb.svg create mode 100644 share/extensions/alphabet_soup/Lt.svg create mode 100644 share/extensions/alphabet_soup/Ocross.svg create mode 100644 share/extensions/alphabet_soup/Oterm.svg create mode 100644 share/extensions/alphabet_soup/P.svg create mode 100644 share/extensions/alphabet_soup/Q.svg create mode 100644 share/extensions/alphabet_soup/Rblock.svg create mode 100644 share/extensions/alphabet_soup/Tb.svg create mode 100644 share/extensions/alphabet_soup/Tt.svg create mode 100644 share/extensions/alphabet_soup/U.svg create mode 100644 share/extensions/alphabet_soup/Vser.svg create mode 100644 share/extensions/alphabet_soup/Xh.svg create mode 100644 share/extensions/alphabet_soup/Xne.svg create mode 100644 share/extensions/alphabet_soup/Xnw.svg create mode 100644 share/extensions/alphabet_soup/Xvb.svg create mode 100644 share/extensions/alphabet_soup/Xvt.svg create mode 100644 share/extensions/alphabet_soup/a.svg create mode 100644 share/extensions/alphabet_soup/abase.svg create mode 100644 share/extensions/alphabet_soup/acap.svg create mode 100644 share/extensions/alphabet_soup/b.svg create mode 100644 share/extensions/alphabet_soup/bar.svg create mode 100644 share/extensions/alphabet_soup/bar2.svg create mode 100644 share/extensions/alphabet_soup/barcap.svg create mode 100644 share/extensions/alphabet_soup/c.svg create mode 100644 share/extensions/alphabet_soup/cross.svg create mode 100644 share/extensions/alphabet_soup/cserif.svg create mode 100644 share/extensions/alphabet_soup/e.svg create mode 100644 share/extensions/alphabet_soup/epsilon.svg create mode 100644 share/extensions/alphabet_soup/f.svg create mode 100644 share/extensions/alphabet_soup/gamma.svg create mode 100644 share/extensions/alphabet_soup/h.svg create mode 100644 share/extensions/alphabet_soup/h2.svg create mode 100644 share/extensions/alphabet_soup/hcap.svg create mode 100644 share/extensions/alphabet_soup/idot.svg create mode 100644 share/extensions/alphabet_soup/j.svg create mode 100644 share/extensions/alphabet_soup/k.svg create mode 100644 share/extensions/alphabet_soup/l.svg create mode 100644 share/extensions/alphabet_soup/lserif.svg create mode 100644 share/extensions/alphabet_soup/m.svg create mode 100644 share/extensions/alphabet_soup/mcap.svg create mode 100644 share/extensions/alphabet_soup/n.svg create mode 100644 share/extensions/alphabet_soup/o.svg create mode 100644 share/extensions/alphabet_soup/ocap.svg create mode 100644 share/extensions/alphabet_soup/question.svg create mode 100644 share/extensions/alphabet_soup/r.svg create mode 100644 share/extensions/alphabet_soup/rcap.svg create mode 100644 share/extensions/alphabet_soup/s.svg create mode 100644 share/extensions/alphabet_soup/serif.svg create mode 100644 share/extensions/alphabet_soup/t.svg create mode 100644 share/extensions/alphabet_soup/tserif.svg create mode 100644 share/extensions/alphabet_soup/v.svg create mode 100644 share/extensions/alphabet_soup/vcap.svg create mode 100644 share/extensions/alphabet_soup/vserl.svg create mode 100644 share/extensions/alphabet_soup/vserr.svg create mode 100644 share/extensions/alphabet_soup/x.svg create mode 100644 share/extensions/alphabet_soup/y.svg create mode 100644 share/extensions/alphabet_soup/yogh.svg create mode 100644 share/extensions/alphabet_soup/z.svg create mode 100644 share/extensions/barcode/Base.py create mode 100644 share/extensions/barcode/BaseEan.py create mode 100644 share/extensions/barcode/Code128.py create mode 100644 share/extensions/barcode/Code25i.py create mode 100644 share/extensions/barcode/Code39.py create mode 100644 share/extensions/barcode/Code39Ext.py create mode 100644 share/extensions/barcode/Code93.py create mode 100644 share/extensions/barcode/Ean13.py create mode 100644 share/extensions/barcode/Ean2.py create mode 100644 share/extensions/barcode/Ean5.py create mode 100644 share/extensions/barcode/Ean8.py create mode 100644 share/extensions/barcode/Rm4scc.py create mode 100644 share/extensions/barcode/Upca.py create mode 100644 share/extensions/barcode/Upce.py create mode 100644 share/extensions/barcode/__init__.py create mode 100644 share/extensions/barcode/__pycache__/Base.cpython-39.pyc create mode 100644 share/extensions/barcode/__pycache__/BaseEan.cpython-39.pyc create mode 100644 share/extensions/barcode/__pycache__/Code128.cpython-39.pyc create mode 100644 share/extensions/barcode/__pycache__/Code25i.cpython-39.pyc create mode 100644 share/extensions/barcode/__pycache__/Code39.cpython-39.pyc create mode 100644 share/extensions/barcode/__pycache__/Code39Ext.cpython-39.pyc create mode 100644 share/extensions/barcode/__pycache__/Code93.cpython-39.pyc create mode 100644 share/extensions/barcode/__pycache__/Ean13.cpython-39.pyc create mode 100644 share/extensions/barcode/__pycache__/Ean2.cpython-39.pyc create mode 100644 share/extensions/barcode/__pycache__/Ean5.cpython-39.pyc create mode 100644 share/extensions/barcode/__pycache__/Ean8.cpython-39.pyc create mode 100644 share/extensions/barcode/__pycache__/Rm4scc.cpython-39.pyc create mode 100644 share/extensions/barcode/__pycache__/Upca.cpython-39.pyc create mode 100644 share/extensions/barcode/__pycache__/Upce.cpython-39.pyc create mode 100644 share/extensions/barcode/__pycache__/__init__.cpython-39.pyc create mode 100644 share/extensions/color_HSL_adjust.inx create mode 100755 share/extensions/color_HSL_adjust.py create mode 100644 share/extensions/color_blackandwhite.inx create mode 100755 share/extensions/color_blackandwhite.py create mode 100644 share/extensions/color_brighter.inx create mode 100755 share/extensions/color_brighter.py create mode 100644 share/extensions/color_custom.inx create mode 100755 share/extensions/color_custom.py create mode 100644 share/extensions/color_darker.inx create mode 100755 share/extensions/color_darker.py create mode 100644 share/extensions/color_desaturate.inx create mode 100755 share/extensions/color_desaturate.py create mode 100644 share/extensions/color_grayscale.inx create mode 100755 share/extensions/color_grayscale.py create mode 100644 share/extensions/color_lesshue.inx create mode 100755 share/extensions/color_lesshue.py create mode 100644 share/extensions/color_lesslight.inx create mode 100755 share/extensions/color_lesslight.py create mode 100644 share/extensions/color_lesssaturation.inx create mode 100755 share/extensions/color_lesssaturation.py create mode 100644 share/extensions/color_list.inx create mode 100755 share/extensions/color_list.py create mode 100644 share/extensions/color_morehue.inx create mode 100755 share/extensions/color_morehue.py create mode 100644 share/extensions/color_morelight.inx create mode 100755 share/extensions/color_morelight.py create mode 100644 share/extensions/color_moresaturation.inx create mode 100755 share/extensions/color_moresaturation.py create mode 100644 share/extensions/color_negative.inx create mode 100755 share/extensions/color_negative.py create mode 100644 share/extensions/color_randomize.inx create mode 100755 share/extensions/color_randomize.py create mode 100644 share/extensions/color_removeblue.inx create mode 100755 share/extensions/color_removeblue.py create mode 100644 share/extensions/color_removegreen.inx create mode 100755 share/extensions/color_removegreen.py create mode 100644 share/extensions/color_removered.inx create mode 100755 share/extensions/color_removered.py create mode 100644 share/extensions/color_replace.inx create mode 100755 share/extensions/color_replace.py create mode 100644 share/extensions/color_rgbbarrel.inx create mode 100755 share/extensions/color_rgbbarrel.py create mode 100644 share/extensions/colors.xml create mode 100644 share/extensions/convert2dashes.inx create mode 100755 share/extensions/convert2dashes.py create mode 100644 share/extensions/dhw_input.inx create mode 100755 share/extensions/dhw_input.py create mode 100644 share/extensions/dimension.inx create mode 100755 share/extensions/dimension.py create mode 100644 share/extensions/docinfo.inx create mode 100755 share/extensions/docinfo.py create mode 100644 share/extensions/doxygen-main.dox create mode 100644 share/extensions/dpi90to96.inx create mode 100644 share/extensions/dpi96to90.inx create mode 100755 share/extensions/dpiswitcher.py create mode 100644 share/extensions/draw_from_triangle.inx create mode 100755 share/extensions/draw_from_triangle.py create mode 100644 share/extensions/dxf12_outlines.inx create mode 100755 share/extensions/dxf12_outlines.py create mode 100755 share/extensions/dxf14_footer.txt create mode 100755 share/extensions/dxf14_header.txt create mode 100755 share/extensions/dxf14_style.txt create mode 100644 share/extensions/dxf_input.inx create mode 100755 share/extensions/dxf_input.py create mode 100644 share/extensions/dxf_outlines.inx create mode 100755 share/extensions/dxf_outlines.py create mode 100644 share/extensions/edge3d.inx create mode 100755 share/extensions/edge3d.py create mode 100644 share/extensions/embedimage.inx create mode 100755 share/extensions/embedimage.py create mode 100644 share/extensions/embedselectedimages.inx create mode 100644 share/extensions/eps_input.inx create mode 100644 share/extensions/export_gimp_palette.inx create mode 100755 share/extensions/export_gimp_palette.py create mode 100644 share/extensions/extractimage.inx create mode 100755 share/extensions/extractimage.py create mode 100644 share/extensions/extrude.inx create mode 100755 share/extensions/extrude.py create mode 100644 share/extensions/fig_input.inx create mode 100755 share/extensions/fig_input.py create mode 100644 share/extensions/flatten.inx create mode 100755 share/extensions/flatten.py create mode 100644 share/extensions/foldablebox.inx create mode 100755 share/extensions/foldablebox.py create mode 100644 share/extensions/fontfix.conf create mode 100644 share/extensions/fractalize.inx create mode 100755 share/extensions/fractalize.py create mode 100644 share/extensions/frame.inx create mode 100755 share/extensions/frame.py create mode 100644 share/extensions/funcplot.inx create mode 100755 share/extensions/funcplot.py create mode 100755 share/extensions/gcodetools.py create mode 100644 share/extensions/gcodetools_about.inx create mode 100644 share/extensions/gcodetools_area.inx create mode 100644 share/extensions/gcodetools_dxf_points.inx create mode 100644 share/extensions/gcodetools_engraving.inx create mode 100644 share/extensions/gcodetools_graffiti.inx create mode 100644 share/extensions/gcodetools_lathe.inx create mode 100644 share/extensions/gcodetools_orientation_points.inx create mode 100644 share/extensions/gcodetools_path_to_gcode.inx create mode 100644 share/extensions/gcodetools_prepare_path_for_plasma.inx create mode 100644 share/extensions/gcodetools_tools_library.inx create mode 100644 share/extensions/generate_voronoi.inx create mode 100755 share/extensions/generate_voronoi.py create mode 100755 share/extensions/genpofiles.sh create mode 100644 share/extensions/gimp_xcf.inx create mode 100755 share/extensions/gimp_xcf.py create mode 100644 share/extensions/grid_cartesian.inx create mode 100755 share/extensions/grid_cartesian.py create mode 100644 share/extensions/grid_isometric.inx create mode 100755 share/extensions/grid_isometric.py create mode 100644 share/extensions/grid_polar.inx create mode 100755 share/extensions/grid_polar.py create mode 100644 share/extensions/guides_creator.inx create mode 100755 share/extensions/guides_creator.py create mode 100644 share/extensions/guillotine.inx create mode 100755 share/extensions/guillotine.py create mode 100644 share/extensions/handles.inx create mode 100755 share/extensions/handles.py create mode 100755 share/extensions/hershey.inx create mode 100644 share/extensions/hershey.py create mode 100644 share/extensions/hpgl_decoder.py create mode 100644 share/extensions/hpgl_encoder.py create mode 100644 share/extensions/hpgl_input.inx create mode 100755 share/extensions/hpgl_input.py create mode 100644 share/extensions/hpgl_output.inx create mode 100755 share/extensions/hpgl_output.py create mode 100644 share/extensions/image_attributes.inx create mode 100755 share/extensions/image_attributes.py create mode 100644 share/extensions/ink2canvas.inx create mode 100755 share/extensions/ink2canvas.py create mode 100644 share/extensions/ink2canvas_lib/__init__.py create mode 100644 share/extensions/ink2canvas_lib/__pycache__/__init__.cpython-39.pyc create mode 100644 share/extensions/ink2canvas_lib/__pycache__/canvas.cpython-39.pyc create mode 100644 share/extensions/ink2canvas_lib/__pycache__/svg.cpython-39.pyc create mode 100644 share/extensions/ink2canvas_lib/canvas.py create mode 100644 share/extensions/ink2canvas_lib/svg.py create mode 100644 share/extensions/inkex.py create mode 100644 share/extensions/inkex/__init__.py create mode 100644 share/extensions/inkex/__pycache__/__init__.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/base.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/bezier.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/colors.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/command.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/deprecated.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/extensions.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/inx.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/localization.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/paths.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/ports.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/styles.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/transforms.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/turtle.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/tween.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/units.cpython-39.pyc create mode 100644 share/extensions/inkex/__pycache__/utils.cpython-39.pyc create mode 100644 share/extensions/inkex/base.py create mode 100644 share/extensions/inkex/bezier.py create mode 100644 share/extensions/inkex/colors.py create mode 100644 share/extensions/inkex/command.py create mode 100644 share/extensions/inkex/deprecated-simple/README.rst create mode 100644 share/extensions/inkex/deprecated-simple/__pycache__/bezmisc.cpython-39.pyc create mode 100644 share/extensions/inkex/deprecated-simple/__pycache__/cspsubdiv.cpython-39.pyc create mode 100644 share/extensions/inkex/deprecated-simple/__pycache__/cubicsuperpath.cpython-39.pyc create mode 100644 share/extensions/inkex/deprecated-simple/__pycache__/ffgeom.cpython-39.pyc create mode 100644 share/extensions/inkex/deprecated-simple/__pycache__/simplepath.cpython-39.pyc create mode 100644 share/extensions/inkex/deprecated-simple/__pycache__/simplestyle.cpython-39.pyc create mode 100644 share/extensions/inkex/deprecated-simple/__pycache__/simpletransform.cpython-39.pyc create mode 100644 share/extensions/inkex/deprecated-simple/bezmisc.py create mode 100644 share/extensions/inkex/deprecated-simple/cspsubdiv.py create mode 100644 share/extensions/inkex/deprecated-simple/cubicsuperpath.py create mode 100644 share/extensions/inkex/deprecated-simple/ffgeom.py create mode 100755 share/extensions/inkex/deprecated-simple/run_command.py create mode 100644 share/extensions/inkex/deprecated-simple/simplepath.py create mode 100644 share/extensions/inkex/deprecated-simple/simplestyle.py create mode 100644 share/extensions/inkex/deprecated-simple/simpletransform.py create mode 100644 share/extensions/inkex/deprecated.py create mode 100644 share/extensions/inkex/elements/__init__.py create mode 100644 share/extensions/inkex/elements/__pycache__/__init__.cpython-39.pyc create mode 100644 share/extensions/inkex/elements/__pycache__/_base.cpython-39.pyc create mode 100644 share/extensions/inkex/elements/__pycache__/_filters.cpython-39.pyc create mode 100644 share/extensions/inkex/elements/__pycache__/_groups.cpython-39.pyc create mode 100644 share/extensions/inkex/elements/__pycache__/_image.cpython-39.pyc create mode 100644 share/extensions/inkex/elements/__pycache__/_meta.cpython-39.pyc create mode 100644 share/extensions/inkex/elements/__pycache__/_polygons.cpython-39.pyc create mode 100644 share/extensions/inkex/elements/__pycache__/_selected.cpython-39.pyc create mode 100644 share/extensions/inkex/elements/__pycache__/_svg.cpython-39.pyc create mode 100644 share/extensions/inkex/elements/__pycache__/_text.cpython-39.pyc create mode 100644 share/extensions/inkex/elements/__pycache__/_use.cpython-39.pyc create mode 100644 share/extensions/inkex/elements/_base.py create mode 100644 share/extensions/inkex/elements/_filters.py create mode 100644 share/extensions/inkex/elements/_groups.py create mode 100644 share/extensions/inkex/elements/_image.py create mode 100644 share/extensions/inkex/elements/_meta.py create mode 100644 share/extensions/inkex/elements/_polygons.py create mode 100644 share/extensions/inkex/elements/_selected.py create mode 100644 share/extensions/inkex/elements/_svg.py create mode 100644 share/extensions/inkex/elements/_text.py create mode 100644 share/extensions/inkex/elements/_use.py create mode 100644 share/extensions/inkex/extensions.py create mode 100644 share/extensions/inkex/inx.py create mode 100644 share/extensions/inkex/localization.py create mode 100644 share/extensions/inkex/paths.py create mode 100644 share/extensions/inkex/ports.py create mode 100644 share/extensions/inkex/styles.py create mode 100644 share/extensions/inkex/tester/__init__.py create mode 100644 share/extensions/inkex/tester/__pycache__/__init__.cpython-39.pyc create mode 100644 share/extensions/inkex/tester/__pycache__/decorators.cpython-39.pyc create mode 100644 share/extensions/inkex/tester/__pycache__/filters.cpython-39.pyc create mode 100644 share/extensions/inkex/tester/__pycache__/inx.cpython-39.pyc create mode 100644 share/extensions/inkex/tester/__pycache__/mock.cpython-39.pyc create mode 100644 share/extensions/inkex/tester/__pycache__/svg.cpython-39.pyc create mode 100644 share/extensions/inkex/tester/__pycache__/word.cpython-39.pyc create mode 100644 share/extensions/inkex/tester/__pycache__/xmldiff.cpython-39.pyc create mode 100644 share/extensions/inkex/tester/decorators.py create mode 100644 share/extensions/inkex/tester/filters.py create mode 100644 share/extensions/inkex/tester/inx.py create mode 100644 share/extensions/inkex/tester/mock.py create mode 100644 share/extensions/inkex/tester/svg.py create mode 100644 share/extensions/inkex/tester/word.py create mode 100644 share/extensions/inkex/tester/xmldiff.py create mode 100644 share/extensions/inkex/transforms.py create mode 100644 share/extensions/inkex/turtle.py create mode 100644 share/extensions/inkex/tween.py create mode 100644 share/extensions/inkex/units.py create mode 100644 share/extensions/inkex/utils.py create mode 100644 share/extensions/inkscape.extension.rng create mode 100644 share/extensions/inkscape_follow_link.inx create mode 100755 share/extensions/inkscape_follow_link.py create mode 100644 share/extensions/inkscape_help_askaquestion.inx create mode 100644 share/extensions/inkscape_help_commandline.inx create mode 100644 share/extensions/inkscape_help_faq.inx create mode 100644 share/extensions/inkscape_help_keys.inx create mode 100644 share/extensions/inkscape_help_manual.inx create mode 100644 share/extensions/inkscape_help_relnotes.inx create mode 100644 share/extensions/inkscape_help_reportabug.inx create mode 100644 share/extensions/inkscape_help_svgspec.inx create mode 100644 share/extensions/inkweb.js create mode 100755 share/extensions/inkwebeffect.py create mode 100644 share/extensions/interp.inx create mode 100755 share/extensions/interp.py create mode 100644 share/extensions/interp_att_g.inx create mode 100755 share/extensions/interp_att_g.py create mode 100644 share/extensions/jessyInk.js create mode 100644 share/extensions/jessyInk_core_mouseHandler_noclick.js create mode 100644 share/extensions/jessyInk_core_mouseHandler_zoomControl.js create mode 100644 share/extensions/jessyink_autotexts.inx create mode 100755 share/extensions/jessyink_autotexts.py create mode 100644 share/extensions/jessyink_effects.inx create mode 100755 share/extensions/jessyink_effects.py create mode 100644 share/extensions/jessyink_export.inx create mode 100755 share/extensions/jessyink_export.py create mode 100644 share/extensions/jessyink_install.inx create mode 100755 share/extensions/jessyink_install.py create mode 100644 share/extensions/jessyink_key_bindings.inx create mode 100755 share/extensions/jessyink_key_bindings.py create mode 100644 share/extensions/jessyink_master_slide.inx create mode 100755 share/extensions/jessyink_master_slide.py create mode 100644 share/extensions/jessyink_mouse_handler.inx create mode 100755 share/extensions/jessyink_mouse_handler.py create mode 100644 share/extensions/jessyink_summary.inx create mode 100755 share/extensions/jessyink_summary.py create mode 100644 share/extensions/jessyink_transitions.inx create mode 100755 share/extensions/jessyink_transitions.py create mode 100644 share/extensions/jessyink_uninstall.inx create mode 100755 share/extensions/jessyink_uninstall.py create mode 100644 share/extensions/jessyink_video.inx create mode 100755 share/extensions/jessyink_video.py create mode 100644 share/extensions/jessyink_video.svg create mode 100644 share/extensions/jessyink_view.inx create mode 100755 share/extensions/jessyink_view.py create mode 100644 share/extensions/jitternodes.inx create mode 100755 share/extensions/jitternodes.py create mode 100755 share/extensions/launch_webbrowser.py create mode 100644 share/extensions/layer2png.inx create mode 100755 share/extensions/layer2png.py create mode 100644 share/extensions/layers2svgfont.inx create mode 100755 share/extensions/layers2svgfont.py create mode 100644 share/extensions/layout_nup.inx create mode 100755 share/extensions/layout_nup.py create mode 100644 share/extensions/lindenmayer.inx create mode 100755 share/extensions/lindenmayer.py create mode 100644 share/extensions/lorem_ipsum.inx create mode 100755 share/extensions/lorem_ipsum.py create mode 100644 share/extensions/markers_strokepaint.inx create mode 100755 share/extensions/markers_strokepaint.py create mode 100644 share/extensions/measure.inx create mode 100755 share/extensions/measure.py create mode 100644 share/extensions/media_zip.inx create mode 100755 share/extensions/media_zip.py create mode 100644 share/extensions/merge_styles.inx create mode 100755 share/extensions/merge_styles.py create mode 100644 share/extensions/motion.inx create mode 100755 share/extensions/motion.py create mode 100644 share/extensions/new_glyph_layer.inx create mode 100755 share/extensions/new_glyph_layer.py create mode 100644 share/extensions/next_glyph_layer.inx create mode 100755 share/extensions/next_glyph_layer.py create mode 100644 share/extensions/nicechart.inx create mode 100755 share/extensions/nicechart.py create mode 100644 share/extensions/output_scour.inx create mode 100755 share/extensions/output_scour.py create mode 100644 share/extensions/output_scour.svg create mode 100644 share/extensions/param_curves.inx create mode 100755 share/extensions/param_curves.py create mode 100644 share/extensions/path_envelope.inx create mode 100755 share/extensions/path_envelope.py create mode 100644 share/extensions/path_mesh_m2p.inx create mode 100755 share/extensions/path_mesh_m2p.py create mode 100644 share/extensions/path_mesh_p2m.inx create mode 100755 share/extensions/path_mesh_p2m.py create mode 100644 share/extensions/path_number_nodes.inx create mode 100755 share/extensions/path_number_nodes.py create mode 100644 share/extensions/path_to_absolute.inx create mode 100755 share/extensions/path_to_absolute.py create mode 100644 share/extensions/pathalongpath.inx create mode 100755 share/extensions/pathalongpath.py create mode 100755 share/extensions/pathmodifier.py create mode 100644 share/extensions/pathscatter.inx create mode 100755 share/extensions/pathscatter.py create mode 100644 share/extensions/pdflatex.inx create mode 100755 share/extensions/pdflatex.py create mode 100644 share/extensions/perfectboundcover.inx create mode 100755 share/extensions/perfectboundcover.py create mode 100644 share/extensions/perspective.inx create mode 100755 share/extensions/perspective.py create mode 100644 share/extensions/pixelsnap.inx create mode 100755 share/extensions/pixelsnap.py create mode 100644 share/extensions/plotter.inx create mode 100755 share/extensions/plotter.py create mode 100644 share/extensions/polyhedron_3d.inx create mode 100755 share/extensions/polyhedron_3d.py create mode 100644 share/extensions/prepare_file_save_as.inx create mode 100755 share/extensions/prepare_file_save_as.py create mode 100644 share/extensions/previous_glyph_layer.inx create mode 100755 share/extensions/previous_glyph_layer.py create mode 100644 share/extensions/print_win32_vector.inx create mode 100755 share/extensions/print_win32_vector.py create mode 100644 share/extensions/printing_marks.inx create mode 100755 share/extensions/printing_marks.py create mode 100644 share/extensions/ps_input.inx create mode 100755 share/extensions/ps_input.py create mode 100644 share/extensions/render_alphabetsoup.inx create mode 100755 share/extensions/render_alphabetsoup.py create mode 100755 share/extensions/render_alphabetsoup_config.py create mode 100644 share/extensions/render_barcode.inx create mode 100755 share/extensions/render_barcode.py create mode 100644 share/extensions/render_barcode_datamatrix.inx create mode 100755 share/extensions/render_barcode_datamatrix.py create mode 100644 share/extensions/render_barcode_qrcode.inx create mode 100755 share/extensions/render_barcode_qrcode.py create mode 100644 share/extensions/render_gear_rack.inx create mode 100755 share/extensions/render_gear_rack.py create mode 100644 share/extensions/render_gears.inx create mode 100755 share/extensions/render_gears.py create mode 100644 share/extensions/replace_font.inx create mode 100755 share/extensions/replace_font.py create mode 100644 share/extensions/restack.inx create mode 100755 share/extensions/restack.py create mode 100644 share/extensions/rtree.inx create mode 100755 share/extensions/rtree.py create mode 100644 share/extensions/rubberstretch.inx create mode 100755 share/extensions/rubberstretch.py create mode 100644 share/extensions/scribus_export_pdf.inx create mode 100644 share/extensions/scribus_export_pdf.py create mode 100644 share/extensions/seamless_pattern.svg create mode 100644 share/extensions/setup.cfg create mode 100755 share/extensions/setup.py create mode 100644 share/extensions/setup_typography_canvas.inx create mode 100755 share/extensions/setup_typography_canvas.py create mode 100644 share/extensions/spirograph.inx create mode 100755 share/extensions/spirograph.py create mode 100644 share/extensions/straightseg.inx create mode 100755 share/extensions/straightseg.py create mode 100644 share/extensions/svg2fxg.inx create mode 100644 share/extensions/svg2fxg.xsl create mode 100644 share/extensions/svg2xaml.inx create mode 100644 share/extensions/svg2xaml.xsl create mode 100644 share/extensions/svg_fonts/EMSAllure.svg create mode 100644 share/extensions/svg_fonts/EMSElfin.svg create mode 100644 share/extensions/svg_fonts/EMSFelix.svg create mode 100644 share/extensions/svg_fonts/EMSNixish.svg create mode 100644 share/extensions/svg_fonts/EMSNixishItalic.svg create mode 100644 share/extensions/svg_fonts/EMSOsmotron.svg create mode 100644 share/extensions/svg_fonts/EMSReadability.svg create mode 100644 share/extensions/svg_fonts/EMSReadabilityItalic.svg create mode 100644 share/extensions/svg_fonts/EMSTech.svg create mode 100644 share/extensions/svg_fonts/HersheyGothEnglish.svg create mode 100644 share/extensions/svg_fonts/HersheySans1.svg create mode 100644 share/extensions/svg_fonts/HersheySansMed.svg create mode 100644 share/extensions/svg_fonts/HersheyScript1.svg create mode 100644 share/extensions/svg_fonts/HersheyScriptMed.svg create mode 100644 share/extensions/svg_fonts/HersheySerifBold.svg create mode 100644 share/extensions/svg_fonts/HersheySerifBoldItalic.svg create mode 100644 share/extensions/svg_fonts/HersheySerifMed.svg create mode 100644 share/extensions/svg_fonts/HersheySerifMedItalic.svg create mode 100644 share/extensions/svg_fonts/OFL.txt create mode 100644 share/extensions/svgcalendar.inx create mode 100755 share/extensions/svgcalendar.py create mode 100644 share/extensions/svgfont2layers.inx create mode 100755 share/extensions/svgfont2layers.py create mode 100755 share/extensions/synfig_fileformat.py create mode 100644 share/extensions/synfig_output.inx create mode 100755 share/extensions/synfig_output.py create mode 100755 share/extensions/synfig_prepare.py create mode 100644 share/extensions/tar_layers.inx create mode 100755 share/extensions/tar_layers.py create mode 100755 share/extensions/template.py create mode 100644 share/extensions/template_business_card.inx create mode 100644 share/extensions/template_desktop.inx create mode 100644 share/extensions/template_dvd_cover.inx create mode 100755 share/extensions/template_dvd_cover.py create mode 100644 share/extensions/template_envelope.inx create mode 100644 share/extensions/template_generic.inx create mode 100644 share/extensions/template_icon.inx create mode 100644 share/extensions/template_page.inx create mode 100644 share/extensions/template_seamless_pattern.inx create mode 100755 share/extensions/template_seamless_pattern.py create mode 100644 share/extensions/template_video.inx create mode 100644 share/extensions/tests/README.md create mode 100644 share/extensions/tests/__init__.py create mode 100644 share/extensions/tests/__pycache__/__init__.cpython-39.pyc create mode 100644 share/extensions/tests/__pycache__/test_addnodes.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_HSL_adjust.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_blackandwhite.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_brighter.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_custom.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_darker.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_desaturate.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_grayscale.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_lesshue.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_lesslight.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_lesssaturation.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_list.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_morehue.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_morelight.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_moresaturation.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_negative.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_randomize.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_removeblue.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_removegreen.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_removered.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_replace.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_color_rgbbarrel.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_convert2dashes.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_deprecated_simple.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_dhw_input.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_dimension.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_docinfo.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_dpiswitcher.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_draw_from_triangle.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_dxf12_outlines.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_dxf_input.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_dxf_outlines.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_edge3d.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_embedimage.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_export_gimp_palette.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_extractimage.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_extrude.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_fig_input.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_flatten.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_foldablebox.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_fractalize.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_frame.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_funcplot.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_gcodetools.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_generate_voronoi.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_gimp_xcf.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_grid_cartesian.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_grid_isometric.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_grid_polar.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_guides_creator.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_guillotine.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_handles.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_hershey.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_hpgl_decoder.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_hpgl_input.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_hpgl_output.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_image_attributes.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_ink2canvas_svg.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_base.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_bezier.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_bounding_box.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_colors.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_command.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_cubic_paths.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_deprecated.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_elements.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_elements_base.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_elements_selections.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_extensions.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_inx.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_paths.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_styles.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_svg.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_tester.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_transforms.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_tween.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_units.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkex_utils.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkscape_follow_link.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_inkwebeffect.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_interp.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_interp_att_g.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_jessyink_autotexts.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_jessyink_effects.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_jessyink_export.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_jessyink_install.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_jessyink_keybindings.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_jessyink_masterslide.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_jessyink_mousehandler.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_jessyink_summary.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_jessyink_transitions.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_jessyink_uninstall.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_jessyink_video.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_jessyink_view.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_jitternodes.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_launch_webbrowser.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_layer2png.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_layers2svgfont.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_layout_nup.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_lindenmayer.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_lorem_ipsum.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_markers_strokepaint.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_measure.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_media_zip.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_merge_styles.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_motion.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_new_glyph_layer.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_next_glyph_layer.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_nicechart.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_output_scour.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_param_curves.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_path_envelope.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_path_mesh.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_path_number_nodes.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_path_to_absolute.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_pathalongpath.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_pathscatter.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_pdflatex.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_perfectboundcover.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_perspective.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_pixelsnap.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_plotter.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_polyhedron_3d.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_prepare_file_save_as.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_previous_glyph_layer.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_print_win32_vector.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_printing_marks.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_ps_input.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_render_alphabetsoup.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_render_barcode.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_render_barcode_datamatrix.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_render_barcode_qrcode.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_render_gear_rack.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_render_gears.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_replace_font.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_restack.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_rtree.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_rubberstretch.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_scribus_pdf.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_setup_typography_canvas.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_spirograph.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_straightseg.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_svgcalendar.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_svgfont2layers.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_synfig_fileformat.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_synfig_output.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_synfig_prepare.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_tar_layers.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_template.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_template_dvd_cover.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_template_seamless_pattern.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_text_braille.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_text_extract.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_text_flipcase.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_text_lowercase.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_text_merge.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_text_randomcase.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_text_sentencecase.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_text_split.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_text_titlecase.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_text_uppercase.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_triangle.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_ungroup_deep.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_voronoi.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_voronoi2svg.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_web_interactive_mockup.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_web_set_att.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_web_transmit_att.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_webslicer_create_group.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_webslicer_create_rect.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_webslicer_export.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_whirl.cpython-39-PYTEST.pyc create mode 100644 share/extensions/tests/__pycache__/test_wireframe_sphere.cpython-39-PYTEST.pyc create mode 100755 share/extensions/tests/add_pylint.py create mode 100644 share/extensions/tests/data/README.md create mode 100644 share/extensions/tests/data/batches/barcodes.dat create mode 100644 share/extensions/tests/data/cmd/fig2dev/c15e8c5e5e50d45b2579ac22522b007e.msg create mode 100644 share/extensions/tests/data/cmd/gimp/6c0e5d2fbe380e26e310ebfc206d81ea.msg create mode 100644 share/extensions/tests/data/cmd/gimp/9de0e6f5cf782a299a1c062b5412dd63.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/03dd4e9c9ee257c7ae161057ebe7c8a3.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/0ac64d7c051206284e72bf3e0800186d.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/106011cd22941d59371660c2b4abf75c.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/1b0252977bd9eafe0af2686834decd6e.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/1f50fa6d71e2543dd18dfc807fe56018.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/2911a62f9cfb6cf1e950992909bf7efb.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/2aee6b61724952541d5114e64953ae32.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/37d9cfceb38aade8eda7ad77e4ad0c29.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/581055ce0e3ef5df0c1ab22982a51513.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/67e526c4c1e53207e5e46274e8cdfcc0.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/7e16d346b278485f0e308b7c8eda301d.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/818d4c30d07def36e80e32df12023124.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/82de0e8eb29f071d78cebe7a4116e246.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/88e23ddddb7b44e469a8309d48b71594.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/8d81a6ce40b41366bc5784c8b8e3f3e3.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/9216316249e241e47bf118e187386687.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/96e4049adf5290ee08bfbcd580bd8dd1.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/994e3f169976a1af54f6590723d399ee.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/a274e3a1c2d893233fa49b8ad9850098.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/b493ad11b50db40a61c857109f083621.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/c1bf37c2aa47000fc9941d96b415218c.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/c23825a5b83e906138ca8bff105b8b29.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/d475a74656cd1f8ba68e095b0ec7c469.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/da3b4e4db182a123e61e1317b2e4578b.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/ee1fdaa387bcb73ee6f7a2359fa9bd62.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/f863b882b18ffd8659b3c20cdd549266.msg create mode 100644 share/extensions/tests/data/cmd/inkscape/fbabf7cf387f29c12ae11a51af0a9926.msg create mode 100644 share/extensions/tests/data/cmd/pdflatex/7b89a79118ea572d7e2fdafa2e82fc70.msg create mode 100644 share/extensions/tests/data/cmd/ps2pdf/43caf043a269fc0e77f8633cd2ad4a88.msg create mode 100644 share/extensions/tests/data/cmd/ps2pdf/9f4cd3145afec1e2a294713237712f4c.msg create mode 100644 share/extensions/tests/data/cmd/scribus/a5ed3ec8aa6d61652cb04fa27f921943.msg create mode 100644 share/extensions/tests/data/cmd/scribus/b8a765f070200c63d022a696ac129e35.msg create mode 100644 share/extensions/tests/data/io/PAGE_001.DHW create mode 100644 share/extensions/tests/data/io/PGLT_161.DHW create mode 100644 share/extensions/tests/data/io/PGLT_162.DHW create mode 100644 share/extensions/tests/data/io/PGLT_163.DHW create mode 100644 share/extensions/tests/data/io/nicechart_01.csv create mode 100644 share/extensions/tests/data/io/test.eps create mode 100644 share/extensions/tests/data/io/test.fig create mode 100644 share/extensions/tests/data/io/test.hpgl create mode 100644 share/extensions/tests/data/io/test.ps create mode 100644 share/extensions/tests/data/io/test_r12.dxf create mode 100644 share/extensions/tests/data/io/test_r14.dxf create mode 100644 share/extensions/tests/data/refs/addnodes.out create mode 100644 share/extensions/tests/data/refs/addnodes__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/convert2dashes.out create mode 100644 share/extensions/tests/data/refs/dhw_input__PAGE_001__DHW.out create mode 100644 share/extensions/tests/data/refs/dhw_input__PGLT_161__DHW.out create mode 100644 share/extensions/tests/data/refs/dhw_input__PGLT_162__DHW.out create mode 100644 share/extensions/tests/data/refs/dhw_input__PGLT_163__DHW.out create mode 100644 share/extensions/tests/data/refs/dimension__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/dimension__--id__p1__--id__r3__--type__visual.out create mode 100644 share/extensions/tests/data/refs/dm2svg.out create mode 100644 share/extensions/tests/data/refs/docinfo.out create mode 100644 share/extensions/tests/data/refs/dpiswitcher.out create mode 100644 share/extensions/tests/data/refs/dpiswitcher__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/draw_from_triangle__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/dxf_input__test_r12__dxf.out create mode 100644 share/extensions/tests/data/refs/dxf_input__test_r14__dxf.out create mode 100644 share/extensions/tests/data/refs/dxf_outlines.out create mode 100644 share/extensions/tests/data/refs/dxf_outlines__--POLY__true.out create mode 100644 share/extensions/tests/data/refs/dxf_outlines__--ROBO__true.out create mode 100644 share/extensions/tests/data/refs/dxf_outlines__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/edge3d__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/embedimage.out create mode 100644 share/extensions/tests/data/refs/export_gimp_palette.out create mode 100644 share/extensions/tests/data/refs/export_gimp_palette__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/extractimage__--selectedonly__False__--filepath__TMP_DIR__img__.out create mode 100644 share/extensions/tests/data/refs/extractimage__--selectedonly__True__--id__embeded_image01__--filepath__TMP_DIR__img__.out create mode 100644 share/extensions/tests/data/refs/extrude__--id__p1__--id__p2.out create mode 100644 share/extensions/tests/data/refs/fig_input.out create mode 100644 share/extensions/tests/data/refs/flatten.out create mode 100644 share/extensions/tests/data/refs/flatten__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/foldablebox__--proportion__0__5__--guide__true.out create mode 100644 share/extensions/tests/data/refs/foldablebox__--width__20__--height__20__--depth__2__2.out create mode 100644 share/extensions/tests/data/refs/fractalize__--id__p1__--id__p2.out create mode 100644 share/extensions/tests/data/refs/funcplot__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/gcodetools__06eec9617e749f35cb949d850415f68d.out create mode 100644 share/extensions/tests/data/refs/gcodetools__2bf3b298fa730dafb8c6fd51921078f0.out create mode 100644 share/extensions/tests/data/refs/gcodetools__4a9fb751baf0533eadd4d394957c966d.out create mode 100644 share/extensions/tests/data/refs/generate_voronoi__--id__r3__--id__p1.out create mode 100644 share/extensions/tests/data/refs/gimp_xcf.out create mode 100644 share/extensions/tests/data/refs/gimp_xcf__-d__true__-r__true.out create mode 100644 share/extensions/tests/data/refs/grid_cartesian.out create mode 100644 share/extensions/tests/data/refs/grid_cartesian__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/grid_isometric.out create mode 100644 share/extensions/tests/data/refs/grid_polar.out create mode 100644 share/extensions/tests/data/refs/grid_polar__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/guides_creator__--tab__diagonal_guides.out create mode 100644 share/extensions/tests/data/refs/guides_creator__--tab__margins__--start_from_edges__True__--margins_preset__book_left.out create mode 100644 share/extensions/tests/data/refs/guides_creator__--tab__margins__--start_from_edges__True__--margins_preset__book_right.out create mode 100644 share/extensions/tests/data/refs/guides_creator__--tab__margins__--start_from_edges__True__--margins_preset__custom.out create mode 100644 share/extensions/tests/data/refs/guides_creator__--tab__regular_guides__--guides_preset__5__5__--start_from_edges__True.out create mode 100644 share/extensions/tests/data/refs/guides_creator__--tab__regular_guides__--guides_preset__custom.out create mode 100644 share/extensions/tests/data/refs/guides_creator__--tab__regular_guides__--guides_preset__golden__--delete__True.out create mode 100644 share/extensions/tests/data/refs/guillotine__--ignore__true__--directory__TMP_DIR__img__.out create mode 100644 share/extensions/tests/data/refs/guillotine__--image__f____oo__--directory__TMP_DIR__img__.out create mode 100644 share/extensions/tests/data/refs/handles__--id__curve__--id__quad.out create mode 100755 share/extensions/tests/data/refs/hershey.out create mode 100755 share/extensions/tests/data/refs/hershey_encoding.out create mode 100755 share/extensions/tests/data/refs/hershey_fonttable.out create mode 100755 share/extensions/tests/data/refs/hershey_glyphtable.out create mode 100755 share/extensions/tests/data/refs/hershey_loadfont.out create mode 100755 share/extensions/tests/data/refs/hershey_partialselection.out create mode 100755 share/extensions/tests/data/refs/hershey_preservetext.out create mode 100644 share/extensions/tests/data/refs/hpgl_input.out create mode 100644 share/extensions/tests/data/refs/hpgl_output__hpgl_multipen__svg.out create mode 100644 share/extensions/tests/data/refs/hpgl_output__shapes__svg.out create mode 100644 share/extensions/tests/data/refs/image_attributes.out create mode 100644 share/extensions/tests/data/refs/image_attributes__--id__embeded_image01__--image_rendering__optimizeSpeed__--tab____tab_image_rendering__.out create mode 100644 share/extensions/tests/data/refs/image_attributes__--id__image174__--aspect_ratio__xMinYMin__--tab____tab_aspect_ratio__.out create mode 100644 share/extensions/tests/data/refs/ink2canvas.out create mode 100644 share/extensions/tests/data/refs/inkex_extensions_color.out create mode 100644 share/extensions/tests/data/refs/inkex_extensions_color__--id__color_svg.out create mode 100644 share/extensions/tests/data/refs/inkex_extensions_color__--id__r1.out create mode 100644 share/extensions/tests/data/refs/inkex_extensions_color__--id__r1__--id__r2.out create mode 100644 share/extensions/tests/data/refs/inkex_extensions_color__--id__r2.out create mode 100644 share/extensions/tests/data/refs/inkex_extensions_color__--id__r3.out create mode 100644 share/extensions/tests/data/refs/inkex_extensions_color__--id__r4.out create mode 100644 share/extensions/tests/data/refs/inkscape_follow_link.out create mode 100644 share/extensions/tests/data/refs/inkscape_follow_link__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/interp__2e7c2144ef5878a5c0824e02c83dc243.out create mode 100644 share/extensions/tests/data/refs/interp__359f83409ebaa8716afca1081eb4987d.out create mode 100644 share/extensions/tests/data/refs/interp_att_g__--id__g53__--att__fill__--start-val____050505__--end-val____000000.out create mode 100644 share/extensions/tests/data/refs/interp_att_g__--id__g53__--att__fill__--start-val____181818__--end-val____000000.out create mode 100644 share/extensions/tests/data/refs/interp_att_g__--id__layer1.out create mode 100644 share/extensions/tests/data/refs/jessyink_autotexts__--autoText__slideTitle__--id__t1.out create mode 100644 share/extensions/tests/data/refs/jessyink_effects__--id__p1__--effectIn__fade__--effectOut__pop.out create mode 100644 share/extensions/tests/data/refs/jessyink_effects__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/jessyink_export__--resolution__1.out create mode 100644 share/extensions/tests/data/refs/jessyink_install.out create mode 100644 share/extensions/tests/data/refs/jessyink_install__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/jessyink_key_bindings__--slide_export__SPACE__--drawing_undo__ENTER__--index_nextPage__LEFT.out create mode 100644 share/extensions/tests/data/refs/jessyink_key_bindings__--slide_export__a__--drawing_undo__b__--index_nextPage__c.out create mode 100644 share/extensions/tests/data/refs/jessyink_master_slide.out create mode 100644 share/extensions/tests/data/refs/jessyink_master_slide__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/jessyink_mouse_handler__--mouseSetting__default.out create mode 100644 share/extensions/tests/data/refs/jessyink_mouse_handler__--mouseSetting__draggingZoom.out create mode 100644 share/extensions/tests/data/refs/jessyink_mouse_handler__--mouseSetting__noclick.out create mode 100644 share/extensions/tests/data/refs/jessyink_summary.out create mode 100644 share/extensions/tests/data/refs/jessyink_transitions__--layerName__Slide2.out create mode 100644 share/extensions/tests/data/refs/jessyink_uninstall.out create mode 100644 share/extensions/tests/data/refs/jessyink_video.out create mode 100644 share/extensions/tests/data/refs/jessyink_view__--id__r3__--viewOrder__1.out create mode 100644 share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__gaussian__--end__false.out create mode 100644 share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__lognorm__--radiusx__100.out create mode 100644 share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__pareto__--radiusy__100.out create mode 100644 share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__uniform__--ctrl__false.out create mode 100644 share/extensions/tests/data/refs/layer2png.out create mode 100644 share/extensions/tests/data/refs/layer2png__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/layers2svgfont.out create mode 100644 share/extensions/tests/data/refs/lindenmayer.out create mode 100644 share/extensions/tests/data/refs/lindenmayer__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/lorem_ipsum.out create mode 100644 share/extensions/tests/data/refs/markers_strokepaint__--tab____custom____--id__dimension.out create mode 100644 share/extensions/tests/data/refs/markers_strokepaint__--tab____object____--id__dimension.out create mode 100644 share/extensions/tests/data/refs/measure__--id__p1__--id__p2.out create mode 100644 share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__FT_bbox__--id__p2.out create mode 100644 share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__FT_start__--id__p1.out create mode 100644 share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__TaP_end__--id__p2.out create mode 100644 share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__TaP_start__--id__p1.out create mode 100644 share/extensions/tests/data/refs/measure__--type__area__--id__p1.out create mode 100644 share/extensions/tests/data/refs/measure__--type__cofm__--id__c3.out create mode 100644 share/extensions/tests/data/refs/media_zip.out create mode 100644 share/extensions/tests/data/refs/merge_styles__--id__c2__--id__c3.out create mode 100644 share/extensions/tests/data/refs/motion__--id__c3__--id__p2.out create mode 100644 share/extensions/tests/data/refs/new_glyph_layer.out create mode 100644 share/extensions/tests/data/refs/new_glyph_layer__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/next_glyph_layer.out create mode 100644 share/extensions/tests/data/refs/next_glyph_layer__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/nicechart.out create mode 100644 share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv.out create mode 100644 share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv__--type__pie.out create mode 100644 share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv__--type__pie_abs.out create mode 100644 share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv__--type__stbar.out create mode 100644 share/extensions/tests/data/refs/nicechart__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/output_scour.out create mode 100644 share/extensions/tests/data/refs/param_curves.out create mode 100644 share/extensions/tests/data/refs/param_curves__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/path_envelope__--id__obj__--id__envelope.out create mode 100644 share/extensions/tests/data/refs/path_envelope__--id__text__--id__envelope.out create mode 100644 share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__faces.out create mode 100644 share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__gridlines.out create mode 100644 share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__meshpatches.out create mode 100644 share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__outline.out create mode 100644 share/extensions/tests/data/refs/path_mesh_p2m__--id__path1__--id__path9.out create mode 100644 share/extensions/tests/data/refs/path_number_nodes__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/path_to_absolute__--id__c1__--id__c2__--id__c3.out create mode 100644 share/extensions/tests/data/refs/path_to_absolute__--id__p1__--id__p2__--id__s1__--id__u1.out create mode 100644 share/extensions/tests/data/refs/path_to_absolute__--id__r1__--id__r2__--id__r3__--id__slicerect.out create mode 100644 share/extensions/tests/data/refs/path_to_absolute__--id__r1__--id__r2__--id__r3__--id__slicerect1.out create mode 100644 share/extensions/tests/data/refs/pathalongpath__--copymode__Single__--id__p1__--id__p2.out create mode 100644 share/extensions/tests/data/refs/pathscatter__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/pdflatex__cceb2358b6829feda6d763508a98eaf1.out create mode 100644 share/extensions/tests/data/refs/perfectboundcover.out create mode 100644 share/extensions/tests/data/refs/perspective__--id__obj__--id__envelope.out create mode 100644 share/extensions/tests/data/refs/perspective__--id__p1__--id__p2.out create mode 100644 share/extensions/tests/data/refs/perspective__--id__text__--id__envelope.out create mode 100644 share/extensions/tests/data/refs/pixelsnap__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/plotter__--serialPort____test__.out create mode 100644 share/extensions/tests/data/refs/plotter__--serialPort____test____--commandLanguage__DMPL.out create mode 100644 share/extensions/tests/data/refs/plotter__--serialPort____test____--commandLanguage__KNK.out create mode 100644 share/extensions/tests/data/refs/plt_output.out create mode 100644 share/extensions/tests/data/refs/polyhedron_3d__--show__edg__--obj__oct__--r1_ax__z__--r1_ang__45__--th__4.out create mode 100644 share/extensions/tests/data/refs/polyhedron_3d__--show__fce__--obj__cube__--r1_ax__x__--r1_ang__45__--r2_ax__y__--r2_ang__45.out create mode 100644 share/extensions/tests/data/refs/polyhedron_3d__--show__fce__--obj__cube__--r1_ax__y__--r1_ang__45__--z_sort__cent.out create mode 100644 share/extensions/tests/data/refs/polyhedron_3d__--show__fce__--obj__cube__--r1_ax__z__--r1_ang__45__--z_sort__max.out create mode 100644 share/extensions/tests/data/refs/polyhedron_3d__--show__vtx__--obj__methane.out create mode 100644 share/extensions/tests/data/refs/polyhedron_3d__31c852a9dcfffc92123ff370cba34361.out create mode 100644 share/extensions/tests/data/refs/prepare_file_save_as.out create mode 100644 share/extensions/tests/data/refs/previous_glyph_layer.out create mode 100644 share/extensions/tests/data/refs/previous_glyph_layer__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/print_win32_vector.out create mode 100644 share/extensions/tests/data/refs/print_win32_vector__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/printing_marks.out create mode 100644 share/extensions/tests/data/refs/printing_marks__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/ps_input__test__eps.out create mode 100644 share/extensions/tests/data/refs/ps_input__test__ps.out create mode 100644 share/extensions/tests/data/refs/render_barcode__--type__Code93__--text__3332222.out create mode 100644 share/extensions/tests/data/refs/render_barcode__--type__Ean2__--text__55.out create mode 100644 share/extensions/tests/data/refs/render_barcode__--type__Upce__--text__123456.out create mode 100644 share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__rect8x32__--text__1234Foo.out create mode 100644 share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__sq10.out create mode 100644 share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__sq144__--text__HelloTest.out create mode 100644 share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__sq96__--text__Sunshine.out create mode 100644 share/extensions/tests/data/refs/render_barcode_qrcode__--text__0123456789__--typenumber__0.out create mode 100644 share/extensions/tests/data/refs/render_barcode_qrcode__--text__Blue__Front__Yard__--typenumber__3__--correctionlevel__1.out create mode 100644 share/extensions/tests/data/refs/render_barcode_qrcode__--text__BreadRolls__--typenumber__2__--encoding__utf8.out create mode 100644 share/extensions/tests/data/refs/render_barcode_qrcode__--text__ThingOne__--drawtype__symbol__--correctionlevel__2__--symbolid__AirTransportation_Inv.out create mode 100644 share/extensions/tests/data/refs/render_barcode_qrcode__--text__Waterfall__--typenumber__1__--drawtype__circle.out create mode 100644 share/extensions/tests/data/refs/render_barcode_qrcode__--text__groupid__--groupid__testid.out create mode 100644 share/extensions/tests/data/refs/render_gear_rack.out create mode 100644 share/extensions/tests/data/refs/render_gear_rack__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/render_gears.out create mode 100644 share/extensions/tests/data/refs/render_gears__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/replace_font__--action__find_replace__--fr_find__sans-serif__--fr_replace__monospace.out create mode 100644 share/extensions/tests/data/refs/replace_font__--action__list_only.out create mode 100644 share/extensions/tests/data/refs/restack__--tab__positional__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/restack__--tab__z_order__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/rtree.out create mode 100644 share/extensions/tests/data/refs/rubberstretch__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/scribus_export_pdf.out create mode 100644 share/extensions/tests/data/refs/setup_typography_canvas.out create mode 100644 share/extensions/tests/data/refs/setup_typography_canvas__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/sk1_output.out create mode 100644 share/extensions/tests/data/refs/spirograph.out create mode 100644 share/extensions/tests/data/refs/spirograph__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/straightseg.out create mode 100644 share/extensions/tests/data/refs/straightseg__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/svgcalendar.out create mode 100644 share/extensions/tests/data/refs/svgfont2layers__--count__3.out create mode 100644 share/extensions/tests/data/refs/tar_layers.out create mode 100644 share/extensions/tests/data/refs/tar_layers__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/template__--size__100x50__--grid__true__--orientation__horizontal.out create mode 100644 share/extensions/tests/data/refs/template__--size__100x50__--grid__true__--orientation__vertical.out create mode 100644 share/extensions/tests/data/refs/template__--size__5mmx15mm__--background__black__--noborder__true.out create mode 100644 share/extensions/tests/data/refs/template__--size__custom__--width__100__--height__100__--unit__in.out create mode 100644 share/extensions/tests/data/refs/template_dvd_cover__-s__10__-b__10.out create mode 100644 share/extensions/tests/data/refs/template_seamless_pattern__--width__100__--height__100.out create mode 100644 share/extensions/tests/data/refs/test_color_list.out create mode 100644 share/extensions/tests/data/refs/test_color_list__--id__color_svg.out create mode 100644 share/extensions/tests/data/refs/test_color_list__--id__r1.out create mode 100644 share/extensions/tests/data/refs/test_color_list__--id__r1__--id__r2.out create mode 100644 share/extensions/tests/data/refs/test_color_list__--id__r2.out create mode 100644 share/extensions/tests/data/refs/test_color_list__--id__r3.out create mode 100644 share/extensions/tests/data/refs/test_color_list__--id__r4.out create mode 100644 share/extensions/tests/data/refs/text_braille.out create mode 100644 share/extensions/tests/data/refs/text_extract__--direction__bt.out create mode 100644 share/extensions/tests/data/refs/text_extract__--direction__lr.out create mode 100644 share/extensions/tests/data/refs/text_extract__--direction__rl.out create mode 100644 share/extensions/tests/data/refs/text_extract__--direction__tb.out create mode 100644 share/extensions/tests/data/refs/text_flipcase.out create mode 100644 share/extensions/tests/data/refs/text_lowercase.out create mode 100644 share/extensions/tests/data/refs/text_merge.out create mode 100644 share/extensions/tests/data/refs/text_randomcase.out create mode 100644 share/extensions/tests/data/refs/text_sentencecase.out create mode 100644 share/extensions/tests/data/refs/text_split__--id__t1__--id__t3.out create mode 100644 share/extensions/tests/data/refs/text_titlecase.out create mode 100644 share/extensions/tests/data/refs/text_uppercase.out create mode 100644 share/extensions/tests/data/refs/triangle.out create mode 100644 share/extensions/tests/data/refs/triangle__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/ungroup_deep.out create mode 100644 share/extensions/tests/data/refs/ungroup_deep__--id__layer2.out create mode 100644 share/extensions/tests/data/refs/voronoi2svg__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/web_interactive_mockup__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/web_set_att__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/web_transmit_att__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/webslicer_create_group__--id__slicerect1.out create mode 100644 share/extensions/tests/data/refs/webslicer_create_rect.out create mode 100644 share/extensions/tests/data/refs/webslicer_create_rect__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/webslicer_export__--dir__TMP_DIR.out create mode 100644 share/extensions/tests/data/refs/whirl__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/wireframe_sphere.out create mode 100644 share/extensions/tests/data/refs/wireframe_sphere__--id__p1__--id__r3.out create mode 100644 share/extensions/tests/data/refs/wmf_output.out create mode 100644 share/extensions/tests/data/svg/colors.svg create mode 100644 share/extensions/tests/data/svg/complextransform.test.svg create mode 100644 share/extensions/tests/data/svg/css.svg create mode 100644 share/extensions/tests/data/svg/curves.svg create mode 100644 share/extensions/tests/data/svg/dash.svg create mode 100644 share/extensions/tests/data/svg/default-inkscape-SVG.svg create mode 100644 share/extensions/tests/data/svg/default-inkscape-SVG_scoured.svg create mode 100644 share/extensions/tests/data/svg/default-plain-SVG.svg create mode 100644 share/extensions/tests/data/svg/diff.svg create mode 100644 share/extensions/tests/data/svg/edge3d.svg create mode 100644 share/extensions/tests/data/svg/empty.svg create mode 100644 share/extensions/tests/data/svg/font.svg create mode 100644 share/extensions/tests/data/svg/font_layers.svg create mode 100644 share/extensions/tests/data/svg/group_interpolate.svg create mode 100644 share/extensions/tests/data/svg/guides.svg create mode 100755 share/extensions/tests/data/svg/hershey_input.svg create mode 100755 share/extensions/tests/data/svg/hershey_trivial_input.svg create mode 100644 share/extensions/tests/data/svg/hpgl_multipen.svg create mode 100644 share/extensions/tests/data/svg/images.svg create mode 100644 share/extensions/tests/data/svg/img/green.png create mode 100644 share/extensions/tests/data/svg/inkweb-debug.js create mode 100644 share/extensions/tests/data/svg/inkwebjs-move.svg create mode 100644 share/extensions/tests/data/svg/interp_shapes.svg create mode 100644 share/extensions/tests/data/svg/markers.svg create mode 100644 share/extensions/tests/data/svg/mesh.svg create mode 100644 share/extensions/tests/data/svg/minimal-blank-prepare.svg create mode 100644 share/extensions/tests/data/svg/minimal-blank.svg create mode 100644 share/extensions/tests/data/svg/multilayered-test.svg create mode 100644 share/extensions/tests/data/svg/perspective.svg create mode 100644 share/extensions/tests/data/svg/perspective_groups.svg create mode 100644 share/extensions/tests/data/svg/shapes-clipboard.svg create mode 100644 share/extensions/tests/data/svg/shapes.svg create mode 100644 share/extensions/tests/data/svg/shapes_cmyk.svg create mode 100644 share/extensions/tests/data/svg/simpletransform.test.svg create mode 100644 share/extensions/tests/data/svg/single_box.svg create mode 100644 share/extensions/tests/data/svg/slicer.svg create mode 100644 share/extensions/tests/data/svg/symbol.svg create mode 100644 share/extensions/tests/data/svg/with-lpe.svg create mode 100644 share/extensions/tests/dev_requirements.txt create mode 100644 share/extensions/tests/test_addnodes.py create mode 100644 share/extensions/tests/test_color_HSL_adjust.py create mode 100644 share/extensions/tests/test_color_blackandwhite.py create mode 100644 share/extensions/tests/test_color_brighter.py create mode 100644 share/extensions/tests/test_color_custom.py create mode 100644 share/extensions/tests/test_color_darker.py create mode 100644 share/extensions/tests/test_color_desaturate.py create mode 100644 share/extensions/tests/test_color_grayscale.py create mode 100644 share/extensions/tests/test_color_lesshue.py create mode 100644 share/extensions/tests/test_color_lesslight.py create mode 100644 share/extensions/tests/test_color_lesssaturation.py create mode 100644 share/extensions/tests/test_color_list.py create mode 100644 share/extensions/tests/test_color_morehue.py create mode 100644 share/extensions/tests/test_color_morelight.py create mode 100644 share/extensions/tests/test_color_moresaturation.py create mode 100644 share/extensions/tests/test_color_negative.py create mode 100644 share/extensions/tests/test_color_randomize.py create mode 100644 share/extensions/tests/test_color_removeblue.py create mode 100644 share/extensions/tests/test_color_removegreen.py create mode 100644 share/extensions/tests/test_color_removered.py create mode 100644 share/extensions/tests/test_color_replace.py create mode 100644 share/extensions/tests/test_color_rgbbarrel.py create mode 100644 share/extensions/tests/test_convert2dashes.py create mode 100644 share/extensions/tests/test_deprecated_simple.py create mode 100644 share/extensions/tests/test_dhw_input.py create mode 100644 share/extensions/tests/test_dimension.py create mode 100644 share/extensions/tests/test_docinfo.py create mode 100644 share/extensions/tests/test_dpiswitcher.py create mode 100644 share/extensions/tests/test_draw_from_triangle.py create mode 100644 share/extensions/tests/test_dxf12_outlines.py create mode 100644 share/extensions/tests/test_dxf_input.py create mode 100644 share/extensions/tests/test_dxf_outlines.py create mode 100644 share/extensions/tests/test_edge3d.py create mode 100644 share/extensions/tests/test_embedimage.py create mode 100644 share/extensions/tests/test_export_gimp_palette.py create mode 100644 share/extensions/tests/test_extractimage.py create mode 100644 share/extensions/tests/test_extrude.py create mode 100644 share/extensions/tests/test_fig_input.py create mode 100644 share/extensions/tests/test_flatten.py create mode 100644 share/extensions/tests/test_foldablebox.py create mode 100644 share/extensions/tests/test_fractalize.py create mode 100644 share/extensions/tests/test_frame.py create mode 100644 share/extensions/tests/test_funcplot.py create mode 100644 share/extensions/tests/test_gcodetools.py create mode 100644 share/extensions/tests/test_generate_voronoi.py create mode 100644 share/extensions/tests/test_gimp_xcf.py create mode 100644 share/extensions/tests/test_grid_cartesian.py create mode 100644 share/extensions/tests/test_grid_isometric.py create mode 100644 share/extensions/tests/test_grid_polar.py create mode 100644 share/extensions/tests/test_guides_creator.py create mode 100644 share/extensions/tests/test_guillotine.py create mode 100644 share/extensions/tests/test_handles.py create mode 100644 share/extensions/tests/test_hershey.py create mode 100644 share/extensions/tests/test_hpgl_decoder.py create mode 100644 share/extensions/tests/test_hpgl_input.py create mode 100644 share/extensions/tests/test_hpgl_output.py create mode 100644 share/extensions/tests/test_image_attributes.py create mode 100644 share/extensions/tests/test_ink2canvas_svg.py create mode 100644 share/extensions/tests/test_inkex.py create mode 100644 share/extensions/tests/test_inkex_base.py create mode 100644 share/extensions/tests/test_inkex_bezier.py create mode 100644 share/extensions/tests/test_inkex_bounding_box.py create mode 100644 share/extensions/tests/test_inkex_colors.py create mode 100644 share/extensions/tests/test_inkex_command.py create mode 100644 share/extensions/tests/test_inkex_cubic_paths.py create mode 100644 share/extensions/tests/test_inkex_deprecated.py create mode 100644 share/extensions/tests/test_inkex_elements.py create mode 100644 share/extensions/tests/test_inkex_elements_base.py create mode 100644 share/extensions/tests/test_inkex_elements_selections.py create mode 100644 share/extensions/tests/test_inkex_extensions.py create mode 100644 share/extensions/tests/test_inkex_inx.py create mode 100644 share/extensions/tests/test_inkex_paths.py create mode 100644 share/extensions/tests/test_inkex_styles.py create mode 100644 share/extensions/tests/test_inkex_svg.py create mode 100644 share/extensions/tests/test_inkex_tester.py create mode 100644 share/extensions/tests/test_inkex_transforms.py create mode 100644 share/extensions/tests/test_inkex_tween.py create mode 100644 share/extensions/tests/test_inkex_units.py create mode 100644 share/extensions/tests/test_inkex_utils.py create mode 100644 share/extensions/tests/test_inkscape_follow_link.py create mode 100644 share/extensions/tests/test_inkwebeffect.py create mode 100644 share/extensions/tests/test_interp.py create mode 100644 share/extensions/tests/test_interp_att_g.py create mode 100644 share/extensions/tests/test_jessyink_autotexts.py create mode 100644 share/extensions/tests/test_jessyink_effects.py create mode 100644 share/extensions/tests/test_jessyink_export.py create mode 100644 share/extensions/tests/test_jessyink_install.py create mode 100644 share/extensions/tests/test_jessyink_keybindings.py create mode 100644 share/extensions/tests/test_jessyink_masterslide.py create mode 100644 share/extensions/tests/test_jessyink_mousehandler.py create mode 100644 share/extensions/tests/test_jessyink_summary.py create mode 100644 share/extensions/tests/test_jessyink_transitions.py create mode 100644 share/extensions/tests/test_jessyink_uninstall.py create mode 100644 share/extensions/tests/test_jessyink_video.py create mode 100644 share/extensions/tests/test_jessyink_view.py create mode 100644 share/extensions/tests/test_jitternodes.py create mode 100644 share/extensions/tests/test_launch_webbrowser.py create mode 100644 share/extensions/tests/test_layer2png.py create mode 100644 share/extensions/tests/test_layers2svgfont.py create mode 100644 share/extensions/tests/test_layout_nup.py create mode 100644 share/extensions/tests/test_lindenmayer.py create mode 100644 share/extensions/tests/test_lorem_ipsum.py create mode 100644 share/extensions/tests/test_markers_strokepaint.py create mode 100644 share/extensions/tests/test_measure.py create mode 100644 share/extensions/tests/test_media_zip.py create mode 100644 share/extensions/tests/test_merge_styles.py create mode 100644 share/extensions/tests/test_motion.py create mode 100644 share/extensions/tests/test_new_glyph_layer.py create mode 100644 share/extensions/tests/test_next_glyph_layer.py create mode 100644 share/extensions/tests/test_nicechart.py create mode 100644 share/extensions/tests/test_output_scour.py create mode 100644 share/extensions/tests/test_param_curves.py create mode 100644 share/extensions/tests/test_path_envelope.py create mode 100644 share/extensions/tests/test_path_mesh.py create mode 100644 share/extensions/tests/test_path_number_nodes.py create mode 100644 share/extensions/tests/test_path_to_absolute.py create mode 100644 share/extensions/tests/test_pathalongpath.py create mode 100644 share/extensions/tests/test_pathscatter.py create mode 100644 share/extensions/tests/test_pdflatex.py create mode 100644 share/extensions/tests/test_perfectboundcover.py create mode 100644 share/extensions/tests/test_perspective.py create mode 100644 share/extensions/tests/test_pixelsnap.py create mode 100644 share/extensions/tests/test_plotter.py create mode 100644 share/extensions/tests/test_polyhedron_3d.py create mode 100644 share/extensions/tests/test_prepare_file_save_as.py create mode 100644 share/extensions/tests/test_previous_glyph_layer.py create mode 100644 share/extensions/tests/test_print_win32_vector.py create mode 100644 share/extensions/tests/test_printing_marks.py create mode 100644 share/extensions/tests/test_ps_input.py create mode 100644 share/extensions/tests/test_render_alphabetsoup.py create mode 100644 share/extensions/tests/test_render_barcode.py create mode 100644 share/extensions/tests/test_render_barcode_datamatrix.py create mode 100644 share/extensions/tests/test_render_barcode_qrcode.py create mode 100644 share/extensions/tests/test_render_gear_rack.py create mode 100644 share/extensions/tests/test_render_gears.py create mode 100644 share/extensions/tests/test_replace_font.py create mode 100644 share/extensions/tests/test_restack.py create mode 100644 share/extensions/tests/test_rtree.py create mode 100644 share/extensions/tests/test_rubberstretch.py create mode 100644 share/extensions/tests/test_scribus_pdf.py create mode 100644 share/extensions/tests/test_setup_typography_canvas.py create mode 100644 share/extensions/tests/test_spirograph.py create mode 100644 share/extensions/tests/test_straightseg.py create mode 100644 share/extensions/tests/test_svgcalendar.py create mode 100644 share/extensions/tests/test_svgfont2layers.py create mode 100644 share/extensions/tests/test_synfig_fileformat.py create mode 100644 share/extensions/tests/test_synfig_output.py create mode 100644 share/extensions/tests/test_synfig_prepare.py create mode 100644 share/extensions/tests/test_tar_layers.py create mode 100644 share/extensions/tests/test_template.py create mode 100644 share/extensions/tests/test_template_dvd_cover.py create mode 100644 share/extensions/tests/test_template_seamless_pattern.py create mode 100644 share/extensions/tests/test_text_braille.py create mode 100644 share/extensions/tests/test_text_extract.py create mode 100644 share/extensions/tests/test_text_flipcase.py create mode 100644 share/extensions/tests/test_text_lowercase.py create mode 100644 share/extensions/tests/test_text_merge.py create mode 100644 share/extensions/tests/test_text_randomcase.py create mode 100644 share/extensions/tests/test_text_sentencecase.py create mode 100644 share/extensions/tests/test_text_split.py create mode 100644 share/extensions/tests/test_text_titlecase.py create mode 100644 share/extensions/tests/test_text_uppercase.py create mode 100644 share/extensions/tests/test_triangle.py create mode 100644 share/extensions/tests/test_ungroup_deep.py create mode 100644 share/extensions/tests/test_voronoi.py create mode 100644 share/extensions/tests/test_voronoi2svg.py create mode 100644 share/extensions/tests/test_web_interactive_mockup.py create mode 100644 share/extensions/tests/test_web_set_att.py create mode 100644 share/extensions/tests/test_web_transmit_att.py create mode 100644 share/extensions/tests/test_webslicer_create_group.py create mode 100644 share/extensions/tests/test_webslicer_create_rect.py create mode 100644 share/extensions/tests/test_webslicer_export.py create mode 100644 share/extensions/tests/test_whirl.py create mode 100644 share/extensions/tests/test_wireframe_sphere.py create mode 100644 share/extensions/text_braille.inx create mode 100755 share/extensions/text_braille.py create mode 100644 share/extensions/text_extract.inx create mode 100755 share/extensions/text_extract.py create mode 100644 share/extensions/text_flipcase.inx create mode 100755 share/extensions/text_flipcase.py create mode 100644 share/extensions/text_lowercase.inx create mode 100755 share/extensions/text_lowercase.py create mode 100644 share/extensions/text_merge.inx create mode 100755 share/extensions/text_merge.py create mode 100644 share/extensions/text_randomcase.inx create mode 100755 share/extensions/text_randomcase.py create mode 100644 share/extensions/text_sentencecase.inx create mode 100755 share/extensions/text_sentencecase.py create mode 100644 share/extensions/text_split.inx create mode 100755 share/extensions/text_split.py create mode 100644 share/extensions/text_titlecase.inx create mode 100755 share/extensions/text_titlecase.py create mode 100644 share/extensions/text_uppercase.inx create mode 100755 share/extensions/text_uppercase.py create mode 100644 share/extensions/tools/generate_argparse_conf.py create mode 100644 share/extensions/tox.ini create mode 100644 share/extensions/triangle.inx create mode 100755 share/extensions/triangle.py create mode 100644 share/extensions/ungroup_deep.inx create mode 100755 share/extensions/ungroup_deep.py create mode 100755 share/extensions/voronoi.py create mode 100644 share/extensions/voronoi2svg.inx create mode 100755 share/extensions/voronoi2svg.py create mode 100644 share/extensions/web_interactive_mockup.inx create mode 100755 share/extensions/web_interactive_mockup.py create mode 100644 share/extensions/web_set_att.inx create mode 100755 share/extensions/web_set_att.py create mode 100644 share/extensions/web_transmit_att.inx create mode 100755 share/extensions/web_transmit_att.py create mode 100644 share/extensions/webslicer_create_group.inx create mode 100755 share/extensions/webslicer_create_group.py create mode 100644 share/extensions/webslicer_create_rect.inx create mode 100755 share/extensions/webslicer_create_rect.py create mode 100755 share/extensions/webslicer_effect.py create mode 100644 share/extensions/webslicer_export.inx create mode 100755 share/extensions/webslicer_export.py create mode 100644 share/extensions/whirl.inx create mode 100755 share/extensions/whirl.py create mode 100644 share/extensions/wireframe_sphere.inx create mode 100755 share/extensions/wireframe_sphere.py create mode 100644 share/extensions/xaml2svg.inx create mode 100644 share/extensions/xaml2svg.xsl create mode 100644 share/extensions/xaml2svg/animation.xsl create mode 100644 share/extensions/xaml2svg/brushes.xsl create mode 100644 share/extensions/xaml2svg/canvas.xsl create mode 100644 share/extensions/xaml2svg/geometry.xsl create mode 100644 share/extensions/xaml2svg/properties.xsl create mode 100644 share/extensions/xaml2svg/shapes.xsl create mode 100644 share/extensions/xaml2svg/transform.xsl (limited to 'share/extensions') diff --git a/share/extensions/.pylintrc b/share/extensions/.pylintrc new file mode 100644 index 0000000..a3903f9 --- /dev/null +++ b/share/extensions/.pylintrc @@ -0,0 +1,374 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +init-hook='import sys; sys.path.append("pythonenv/lib/python3.6/site-packages"); sys.path.append("../pythonenv/lib/python3.6/site-packages"); sys.path.append("../../pythonenv/lib/python3.6/site-packages"); sys.path.append("../../../pythonenv/lib/python3.6/site-packages");' + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# Use multiple processes to speed up Pylint. +jobs=1 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist=lxml,numpy + +# Allow optimization of some AST trees. This will activate a peephole AST +# optimizer, which will apply various small optimizations. For instance, it can +# be used to obtain the result of joining multiple strings with the addition +# operator. Joining a lot of strings can lead to a maximum recursion error in +# Pylint and this flag can prevent that. It has one side effect, the resulting +# AST will be different than the one from reality. +optimize-ast=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating,superfluous-parens,missing-super-argument,model-missing-unicode + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=100 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[BASIC] + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,input + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,x,y,z,ex,Run,_,__,js,rx,x1,y1,x2,y2 + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[ELIF] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). This supports can work +# with qualified names. +ignored-classes=SQLObject,WSGIRequest + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members=objects,DoesNotExist,id,pk,_meta,base_fields,context + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/share/extensions/.pytest_cache/CACHEDIR.TAG b/share/extensions/.pytest_cache/CACHEDIR.TAG new file mode 100644 index 0000000..381f03a --- /dev/null +++ b/share/extensions/.pytest_cache/CACHEDIR.TAG @@ -0,0 +1,4 @@ +Signature: 8a477f597d28d172789f06886806bc55 +# This file is a cache directory tag created by pytest. +# For information about cache directory tags, see: +# http://www.bford.info/cachedir/spec.html diff --git a/share/extensions/.pytest_cache/README.md b/share/extensions/.pytest_cache/README.md new file mode 100644 index 0000000..bb78ba0 --- /dev/null +++ b/share/extensions/.pytest_cache/README.md @@ -0,0 +1,8 @@ +# pytest cache directory # + +This directory contains data from the pytest's cache plugin, +which provides the `--lf` and `--ff` options, as well as the `cache` fixture. + +**Do not** commit this to version control. + +See [the docs](https://docs.pytest.org/en/latest/cache.html) for more information. diff --git a/share/extensions/.pytest_cache/v/cache/nodeids b/share/extensions/.pytest_cache/v/cache/nodeids new file mode 100644 index 0000000..3aaefde --- /dev/null +++ b/share/extensions/.pytest_cache/v/cache/nodeids @@ -0,0 +1,667 @@ +[ + "tests/test_addnodes.py::SplitItBasicTest::test_all_comparisons", + "tests/test_addnodes.py::SplitItBasicTest::test_basic", + "tests/test_color_HSL_adjust.py::ColorHSLAdjustTest::test_colors", + "tests/test_color_HSL_adjust.py::ColorBaseCase::test_colors", + "tests/test_color_blackandwhite.py::ColorBlackAndWhiteTest::test_colors", + "tests/test_color_blackandwhite.py::ColorBaseCase::test_colors", + "tests/test_color_brighter.py::ColorBrighterTest::test_colors", + "tests/test_color_brighter.py::ColorBaseCase::test_colors", + "tests/test_color_custom.py::ColorCustomTest::test_bad_syntax", + "tests/test_color_custom.py::ColorCustomTest::test_colors", + "tests/test_color_custom.py::ColorCustomTest::test_evil_fails", + "tests/test_color_custom.py::ColorCustomTest::test_invalid_operator", + "tests/test_color_custom.py::ColorBaseCase::test_colors", + "tests/test_color_darker.py::ColorDarkerTest::test_colors", + "tests/test_color_darker.py::ColorBaseCase::test_colors", + "tests/test_color_desaturate.py::ColorDesaturateTest::test_colors", + "tests/test_color_desaturate.py::ColorBaseCase::test_colors", + "tests/test_color_grayscale.py::ColorGrayscaleTest::test_colors", + "tests/test_color_grayscale.py::ColorBaseCase::test_colors", + "tests/test_color_lesshue.py::ColorLessHueTest::test_colors", + "tests/test_color_lesshue.py::ColorBaseCase::test_colors", + "tests/test_color_lesslight.py::ColorLessLightTest::test_colors", + "tests/test_color_lesslight.py::ColorBaseCase::test_colors", + "tests/test_color_lesssaturation.py::ColorLessSaturationTest::test_colors", + "tests/test_color_lesssaturation.py::ColorBaseCase::test_colors", + "tests/test_color_list.py::ColorListTest::test_all_comparisons", + "tests/test_color_list.py::ColorEffectTest::test_all_comparisons", + "tests/test_color_morehue.py::ColorMoreHueTest::test_colors", + "tests/test_color_morehue.py::ColorBaseCase::test_colors", + "tests/test_color_morelight.py::ColorMoreLightTest::test_colors", + "tests/test_color_morelight.py::ColorBaseCase::test_colors", + "tests/test_color_moresaturation.py::ColorMoreSaturationTest::test_colors", + "tests/test_color_moresaturation.py::ColorBaseCase::test_colors", + "tests/test_color_negative.py::ColorNegativeTest::test_colors", + "tests/test_color_negative.py::ColorBaseCase::test_colors", + "tests/test_color_randomize.py::ColorRandomizeTest::test_bad_opacity", + "tests/test_color_randomize.py::ColorRandomizeTest::test_colors", + "tests/test_color_randomize.py::ColorBaseCase::test_colors", + "tests/test_color_removeblue.py::ColorRemoveBlueTest::test_colors", + "tests/test_color_removeblue.py::ColorBaseCase::test_colors", + "tests/test_color_removegreen.py::ColorRemoveGreenTest::test_colors", + "tests/test_color_removegreen.py::ColorBaseCase::test_colors", + "tests/test_color_removered.py::ColorRemoveRedTest::test_colors", + "tests/test_color_removered.py::ColorBaseCase::test_colors", + "tests/test_color_replace.py::ColorReplaceTest::test_colors", + "tests/test_color_replace.py::ColorBaseCase::test_colors", + "tests/test_color_rgbbarrel.py::ColorBarrelTest::test_colors", + "tests/test_color_rgbbarrel.py::ColorBaseCase::test_colors", + "tests/test_convert2dashes.py::DashitBasicTest::test_all_comparisons", + "tests/test_convert2dashes.py::DashitBasicTest::test_basic", + "tests/test_convert2dashes.py::DashitBasicTest::test_default_settings", + "tests/test_deprecated_simple.py::DeprecatedTest::test_inkex_effect", + "tests/test_deprecated_simple.py::DeprecatedTest::test_inkex_namespace", + "tests/test_deprecated_simple.py::DeprecatedTest::test_namespace_pollution", + "tests/test_deprecated_simple.py::DeprecatedTest::test_simple_imports", + "tests/test_deprecated_simple.py::DeprecatedTest::test_simplepath", + "tests/test_deprecated_simple.py::DeprecatedTest::test_simplestyle", + "tests/test_deprecated_simple.py::DeprecatedTest::test_simpletransform", + "tests/test_dhw_input.py::TestDxfInput::test_all_comparisons", + "tests/test_dimension.py::TestDimensionBasic::test_all_comparisons", + "tests/test_docinfo.py::TestDocInfo::test_all_comparisons", + "tests/test_dpiswitcher.py::TestDPISwitcherBasic::test_all_comparisons", + "tests/test_draw_from_triangle.py::DrawFromTriangleBasicTest::test_all_comparisons", + "tests/test_draw_from_triangle.py::DrawFromTriangleBasicTest::test_default_settings", + "tests/test_dxf12_outlines.py::TestDXF12OutlinesBasic::test_default_settings", + "tests/test_dxf_input.py::TestDxfInputBasic::test_all_comparisons", + "tests/test_dxf_outlines.py::DFXOutlineBasicTest::test_all_comparisons", + "tests/test_dxf_outlines.py::DFXOutlineBasicTest::test_default_settings", + "tests/test_edge3d.py::Edge3dBasicTest::test_all_comparisons", + "tests/test_edge3d.py::Edge3dBasicTest::test_basic", + "tests/test_embedimage.py::EmbedderBasicTest::test_all_comparisons", + "tests/test_export_gimp_palette.py::TestExportGplBasic::test_all_comparisons", + "tests/test_extractimage.py::ExtractImageBasicTest::test_all_comparisons", + "tests/test_extractimage.py::ExtractImageBasicTest::test_default_settings", + "tests/test_extrude.py::ExtrudeBasicTest::test_all_comparisons", + "tests/test_fig_input.py::TestFigInput::test_all_comparisons", + "tests/test_flatten.py::FlattenBasicTest::test_all_comparisons", + "tests/test_flatten.py::FlattenBasicTest::test_default_settings", + "tests/test_foldablebox.py::FoldableBoxArguments::test_all_comparisons", + "tests/test_fractalize.py::PathFractalizeBasicTest::test_all_comparisons", + "tests/test_frame.py::FrameTest::test_default_settings", + "tests/test_frame.py::FrameTest::test_single_frame", + "tests/test_frame.py::FrameTest::test_single_frame_clipped", + "tests/test_frame.py::FrameTest::test_single_frame_grouped", + "tests/test_funcplot.py::FuncPlotBasicTest::test_all_comparisons", + "tests/test_gcodetools.py::TestGcodetoolsBasic::test_all_comparisons", + "tests/test_gcodetools.py::TestGcodetoolsBasic::test_default_settings", + "tests/test_generate_voronoi.py::TestPatternBasic::test_all_comparisons", + "tests/test_gimp_xcf.py::GimpXcfBasicTest::test_all_comparisons", + "tests/test_gimp_xcf.py::GimpXcfGuidesTest::test_all_comparisons", + "tests/test_grid_cartesian.py::GridCartesianBasicTest::test_all_comparisons", + "tests/test_grid_cartesian.py::GridCartesianBasicTest::test_default_settings", + "tests/test_grid_isometric.py::TestGridIsometricBasic::test_all_comparisons", + "tests/test_grid_isometric.py::TestGridIsometricBasic::test_default_settings", + "tests/test_grid_polar.py::GridPolarBasicTest::test_all_comparisons", + "tests/test_grid_polar.py::GridPolarBasicTest::test_default_settings", + "tests/test_guides_creator.py::GuidesCreatorBasicTest::test_all_comparisons", + "tests/test_guides_creator.py::GuidesCreatorBasicTest::test_default_settings", + "tests/test_guillotine.py::TestGuillotineBasic::test_all_comparisons", + "tests/test_handles.py::HandlesBasicTest::test_all_comparisons", + "tests/test_hershey.py::TestHersheyBasic::test_all_comparisons", + "tests/test_hershey.py::TestHersheyBasic::test_default_settings", + "tests/test_hershey.py::TestHersheyTrivialInput::test_all_comparisons", + "tests/test_hershey.py::TestHersheyTrivialInput::test_default_settings", + "tests/test_hershey.py::TestHersheyTables::test_all_comparisons", + "tests/test_hershey.py::TestHersheyTables::test_default_settings", + "tests/test_hpgl_decoder.py::TesthpglDecoderBasic::test_init_values_scale", + "tests/test_hpgl_input.py::TestHpglFileBasic::test_all_comparisons", + "tests/test_hpgl_output.py::HPGLOutputBasicTest::test_all_comparisons", + "tests/test_image_attributes.py::TestSetAttrImageBasic::test_all_comparisons", + "tests/test_ink2canvas_svg.py::Ink2CanvasBasicTest::test_all_comparisons", + "tests/test_inkex.py::TestModuleCollisions::test_inkex", + "tests/test_inkex.py::TestModuleCollisions::test_inkex_elements", + "tests/test_inkex_base.py::InkscapeExtensionTest::test_arg_parser_defaults", + "tests/test_inkex_base.py::InkscapeExtensionTest::test_arg_parser_passed", + "tests/test_inkex_base.py::InkscapeExtensionTest::test_bare_bones", + "tests/test_inkex_base.py::InkscapeExtensionTest::test_compat", + "tests/test_inkex_base.py::InkscapeExtensionTest::test_svg_path", + "tests/test_inkex_base.py::SvgInputOutputTest::test_input_mixin", + "tests/test_inkex_base.py::SvgInputOutputTest::test_no_output", + "tests/test_inkex_base.py::SvgInputOutputTest::test_str_document", + "tests/test_inkex_base.py::SvgInputOutputTest::test_svg_output", + "tests/test_inkex_bezier.py::TestPointDistance::test_points_on_horizontal_line", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_bbox_empty_equivalent_to_none", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_bbox_empty_is_false", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_bbox_empty_is_identity_for_addition", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_bbox_empty_is_zero_for_intersection", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_bbox_nonempty_is_true", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_bbox_nonintersection_is_empty", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_circle_with_cx", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_circle_with_cy", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_circle_with_radius", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_circle_with_stroke", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_circle_with_stroke_scaled", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_circle_without_attributes", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_circle_without_center", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_empty_group", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_empty_group_with_translation", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_empty_path", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_group_nested_transform", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_group_with_number_of_rects", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_group_with_number_of_rects_scaled", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_group_with_number_of_rects_translated", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_group_with_regular_rect", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_Arc_long_sweep_off", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_Arc_long_sweep_on", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_Arc_long_sweep_on_axis_x_25", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_Arc_short_sweep_off", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_Arc_short_sweep_on", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_Curve", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_Horz", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_Line", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_Move", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_TepidQuadratic", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_TepidQuadratic_2", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_Vert", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_Zone", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_combined_1", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_horizontal_line_stroke_butt_cap", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_horizontal_line_stroke_round_cap", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_horizontal_line_stroke_square_cap", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_horz", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_line", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_move", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_straight_line", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_straight_line_scaled", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_two_straight_lines_abosolute", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_two_straight_lines_relative", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_vert", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_path_with_move_commands_only", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_random_path_1", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_rectangle_without_attributes", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_rectangle_without_coordinates", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_rectangle_without_dimensions", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_regular_circle", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_regular_rectangle", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_regular_rectangle_scaled", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_regular_rectangle_with_stroke", + "tests/test_inkex_bounding_box.py::BoundingBoxTest::test_regular_rectangle_with_stroke_scaled", + "tests/test_inkex_colors.py::ColorTest::test_empty", + "tests/test_inkex_colors.py::ColorTest::test_errors", + "tests/test_inkex_colors.py::ColorTest::test_hsl_color", + "tests/test_inkex_colors.py::ColorTest::test_hsl_grey", + "tests/test_inkex_colors.py::ColorTest::test_hsl_to_rgb", + "tests/test_inkex_colors.py::ColorTest::test_int_color", + "tests/test_inkex_colors.py::ColorTest::test_interpolate", + "tests/test_inkex_colors.py::ColorTest::test_is_color", + "tests/test_inkex_colors.py::ColorTest::test_namedcolor", + "tests/test_inkex_colors.py::ColorTest::test_rgb_hex", + "tests/test_inkex_colors.py::ColorTest::test_rgb_int", + "tests/test_inkex_colors.py::ColorTest::test_rgb_percent", + "tests/test_inkex_colors.py::ColorTest::test_rgb_short_hex", + "tests/test_inkex_colors.py::ColorTest::test_rgb_to_hsl", + "tests/test_inkex_colors.py::ColorTest::test_rgba_color", + "tests/test_inkex_colors.py::ColorTest::test_setter_alpha", + "tests/test_inkex_colors.py::ColorTest::test_setter_hsl", + "tests/test_inkex_colors.py::ColorTest::test_setter_rgb", + "tests/test_inkex_command.py::CommandTest::test_binary_call", + "tests/test_inkex_cubic_paths.py::CubicPathTest::test_AZ", + "tests/test_inkex_cubic_paths.py::CubicPathTest::test_CS", + "tests/test_inkex_cubic_paths.py::CubicPathTest::test_LHV", + "tests/test_inkex_cubic_paths.py::CubicPathTest::test_QT", + "tests/test_inkex_deprecated.py::DeprecatedTests::test_traceback", + "tests/test_inkex_deprecated.py::DeprecatedTests::test_warning", + "tests/test_inkex_elements.py::ElementTestCase::test_print", + "tests/test_inkex_elements.py::PathElementTestCase::test_new_path", + "tests/test_inkex_elements.py::PathElementTestCase::test_original_path", + "tests/test_inkex_elements.py::PathElementTestCase::test_print", + "tests/test_inkex_elements.py::PolylineElementTestCase::test_polyline_points", + "tests/test_inkex_elements.py::PolylineElementTestCase::test_print", + "tests/test_inkex_elements.py::PolylineElementTestCase::test_type", + "tests/test_inkex_elements.py::PolygonElementTestCase::test_conversion", + "tests/test_inkex_elements.py::PolygonElementTestCase::test_print", + "tests/test_inkex_elements.py::PolygonElementTestCase::test_type", + "tests/test_inkex_elements.py::LineElementTestCase::test_conversion", + "tests/test_inkex_elements.py::LineElementTestCase::test_new_line", + "tests/test_inkex_elements.py::LineElementTestCase::test_print", + "tests/test_inkex_elements.py::LineElementTestCase::test_type", + "tests/test_inkex_elements.py::PatternTestCase::test_pattern_transform", + "tests/test_inkex_elements.py::PatternTestCase::test_print", + "tests/test_inkex_elements.py::GroupTest::test_bounding_box", + "tests/test_inkex_elements.py::GroupTest::test_get_path", + "tests/test_inkex_elements.py::GroupTest::test_groupmode", + "tests/test_inkex_elements.py::GroupTest::test_new_group", + "tests/test_inkex_elements.py::GroupTest::test_print", + "tests/test_inkex_elements.py::GroupTest::test_transform_property", + "tests/test_inkex_elements.py::RectTest::test_compose_stylesheet", + "tests/test_inkex_elements.py::RectTest::test_compose_transform", + "tests/test_inkex_elements.py::RectTest::test_effetive_stylesheet", + "tests/test_inkex_elements.py::RectTest::test_parse", + "tests/test_inkex_elements.py::RectTest::test_path", + "tests/test_inkex_elements.py::RectTest::test_print", + "tests/test_inkex_elements.py::PathTest::test_apply_transform", + "tests/test_inkex_elements.py::PathTest::test_print", + "tests/test_inkex_elements.py::CircleTest::test_new", + "tests/test_inkex_elements.py::CircleTest::test_parse", + "tests/test_inkex_elements.py::CircleTest::test_path", + "tests/test_inkex_elements.py::CircleTest::test_print", + "tests/test_inkex_elements.py::AnchorTest::test_new", + "tests/test_inkex_elements.py::AnchorTest::test_print", + "tests/test_inkex_elements.py::NamedViewTest::test_guides", + "tests/test_inkex_elements.py::NamedViewTest::test_print", + "tests/test_inkex_elements.py::TextTest::test_append_superscript", + "tests/test_inkex_elements.py::TextTest::test_path", + "tests/test_inkex_elements.py::TextTest::test_print", + "tests/test_inkex_elements.py::UseTest::test_empty_ref", + "tests/test_inkex_elements.py::UseTest::test_path", + "tests/test_inkex_elements.py::UseTest::test_print", + "tests/test_inkex_elements.py::UseTest::test_unlink", + "tests/test_inkex_elements.py::StopTests::test_interpolate", + "tests/test_inkex_elements.py::StopTests::test_print", + "tests/test_inkex_elements.py::GradientTests::test_apply_transform", + "tests/test_inkex_elements.py::GradientTests::test_get_stop_offsets", + "tests/test_inkex_elements.py::GradientTests::test_interpolate", + "tests/test_inkex_elements.py::GradientTests::test_parse", + "tests/test_inkex_elements.py::GradientTests::test_print", + "tests/test_inkex_elements.py::GradientTests::test_stop_styles", + "tests/test_inkex_elements.py::GradientTests::test_stops", + "tests/test_inkex_elements.py::SymbolTest::test_print", + "tests/test_inkex_elements.py::SymbolTest::test_unlink_symbol", + "tests/test_inkex_elements.py::DefsTest::test_defs", + "tests/test_inkex_elements.py::DefsTest::test_print", + "tests/test_inkex_elements.py::StyleTest::test_print", + "tests/test_inkex_elements.py::StyleTest::test_style", + "tests/test_inkex_elements_base.py::OverridenElementTestCase::test_abstract_raises", + "tests/test_inkex_elements_base.py::OverridenElementTestCase::test_add", + "tests/test_inkex_elements_base.py::OverridenElementTestCase::test_creation", + "tests/test_inkex_elements_base.py::OverridenElementTestCase::test_reference_count", + "tests/test_inkex_elements_base.py::OverridenElementTestCase::test_tag_names", + "tests/test_inkex_elements_base.py::OverridenElementTestCase::test_tostring", + "tests/test_inkex_elements_base.py::AttributeHandelingTestCase::test_chained_multiple_attrs", + "tests/test_inkex_elements_base.py::AttributeHandelingTestCase::test_get_element_by_name", + "tests/test_inkex_elements_base.py::AttributeHandelingTestCase::test_in_place_style", + "tests/test_inkex_elements_base.py::AttributeHandelingTestCase::test_pop_regular_attribute", + "tests/test_inkex_elements_base.py::AttributeHandelingTestCase::test_pop_wrapped_attribute", + "tests/test_inkex_elements_base.py::AttributeHandelingTestCase::test_random_id", + "tests/test_inkex_elements_base.py::AttributeHandelingTestCase::test_random_ids", + "tests/test_inkex_elements_base.py::AttributeHandelingTestCase::test_set_id_backlinks", + "tests/test_inkex_elements_base.py::AttributeHandelingTestCase::test_set_wrapped_attribute", + "tests/test_inkex_elements_base.py::AttributeHandelingTestCase::test_update_consistant", + "tests/test_inkex_elements_base.py::TransformationTestCase::test_bounding_box", + "tests/test_inkex_elements_base.py::TransformationTestCase::test_in_place_transforms", + "tests/test_inkex_elements_base.py::TransformationTestCase::test_scale", + "tests/test_inkex_elements_base.py::TransformationTestCase::test_transform", + "tests/test_inkex_elements_base.py::RelationshipTestCase::test_ancestors", + "tests/test_inkex_elements_base.py::RelationshipTestCase::test_copy", + "tests/test_inkex_elements_base.py::RelationshipTestCase::test_descendants", + "tests/test_inkex_elements_base.py::RelationshipTestCase::test_duplicate", + "tests/test_inkex_elements_base.py::RelationshipTestCase::test_findall", + "tests/test_inkex_elements_base.py::RelationshipTestCase::test_luca", + "tests/test_inkex_elements_base.py::RelationshipTestCase::test_replace_with", + "tests/test_inkex_elements_selections.py::ElementListTestCase::test_creation", + "tests/test_inkex_elements_selections.py::ElementListTestCase::test_filtering", + "tests/test_inkex_elements_selections.py::ElementListTestCase::test_get_bounding_box", + "tests/test_inkex_elements_selections.py::ElementListTestCase::test_getitem", + "tests/test_inkex_elements_selections.py::ElementListTestCase::test_getting_recursively", + "tests/test_inkex_elements_selections.py::ElementListTestCase::test_paint_order", + "tests/test_inkex_elements_selections.py::ElementListTestCase::test_pop_items", + "tests/test_inkex_elements_selections.py::ElementListTestCase::test_set_elements", + "tests/test_inkex_elements_selections.py::ElementListTestCase::test_set_ids", + "tests/test_inkex_elements_selections.py::ElementListTestCase::test_set_invalid_ids", + "tests/test_inkex_elements_selections.py::ElementListTestCase::test_set_nothing", + "tests/test_inkex_elements_selections.py::ElementListTestCase::test_set_xpath", + "tests/test_inkex_elements_selections.py::ElementListTestCase::test_svg_selection", + "tests/test_inkex_elements_selections.py::ElementListTestCase::test_to_dict", + "tests/test_inkex_extensions.py::ColorEffectTest::test_all_comparisons", + "tests/test_inkex_extensions.py::ColorBaseCase::test_colors", + "tests/test_inkex_inx.py::InxTestCase::test_inx_files", + "tests/test_inkex_paths.py::SegmentTest::test_absolute_relative", + "tests/test_inkex_paths.py::SegmentTest::test_args", + "tests/test_inkex_paths.py::SegmentTest::test_equals", + "tests/test_inkex_paths.py::SegmentTest::test_to_curves", + "tests/test_inkex_paths.py::SegmentTest::test_to_line", + "tests/test_inkex_paths.py::SegmentTest::test_transformation", + "tests/test_inkex_paths.py::PathTest::test_absolute", + "tests/test_inkex_paths.py::PathTest::test_adding_to_path", + "tests/test_inkex_paths.py::PathTest::test_arc_transformation", + "tests/test_inkex_paths.py::PathTest::test_bounding_box_arcs", + "tests/test_inkex_paths.py::PathTest::test_bounding_box_curves", + "tests/test_inkex_paths.py::PathTest::test_bounding_box_lines", + "tests/test_inkex_paths.py::PathTest::test_chained_conversion", + "tests/test_inkex_paths.py::PathTest::test_control_points", + "tests/test_inkex_paths.py::PathTest::test_copy", + "tests/test_inkex_paths.py::PathTest::test_create_from_points", + "tests/test_inkex_paths.py::PathTest::test_extending", + "tests/test_inkex_paths.py::PathTest::test_inline_transformations", + "tests/test_inkex_paths.py::PathTest::test_invalid", + "tests/test_inkex_paths.py::PathTest::test_list", + "tests/test_inkex_paths.py::PathTest::test_new_empty", + "tests/test_inkex_paths.py::PathTest::test_passthrough", + "tests/test_inkex_paths.py::PathTest::test_relative", + "tests/test_inkex_paths.py::PathTest::test_repr", + "tests/test_inkex_paths.py::PathTest::test_rotate", + "tests/test_inkex_paths.py::PathTest::test_scale", + "tests/test_inkex_paths.py::PathTest::test_scale_relative_after_close", + "tests/test_inkex_paths.py::PathTest::test_single_point_transform", + "tests/test_inkex_paths.py::PathTest::test_subtracting_from_path", + "tests/test_inkex_paths.py::PathTest::test_to_arrays", + "tests/test_inkex_paths.py::PathTest::test_transform", + "tests/test_inkex_paths.py::PathTest::test_transformation_preserve_type", + "tests/test_inkex_paths.py::SuperPathTest::test_closing", + "tests/test_inkex_paths.py::SuperPathTest::test_closing_without_z", + "tests/test_inkex_paths.py::SuperPathTest::test_from_arrays", + "tests/test_inkex_paths.py::SuperPathTest::test_is_line", + "tests/test_inkex_styles.py::StyleTest::test_color_property", + "tests/test_inkex_styles.py::StyleTest::test_composite", + "tests/test_inkex_styles.py::StyleTest::test_inbuilts", + "tests/test_inkex_styles.py::StyleTest::test_interpolate", + "tests/test_inkex_styles.py::StyleTest::test_new_style", + "tests/test_inkex_styles.py::StyleTest::test_set_property", + "tests/test_inkex_styles.py::AttribFallbackTest::test_fallback_read_attrib", + "tests/test_inkex_styles.py::AttribFallbackTest::test_fallback_read_css", + "tests/test_inkex_styles.py::AttribFallbackTest::test_fallback_read_style", + "tests/test_inkex_styles.py::AttribFallbackTest::test_fallback_write_attrib", + "tests/test_inkex_styles.py::AttribFallbackTest::test_fallback_write_css", + "tests/test_inkex_styles.py::AttribFallbackTest::test_fallback_write_move", + "tests/test_inkex_styles.py::AttribFallbackTest::test_fallback_write_style", + "tests/test_inkex_styles.py::AttribFallbackTest::test_no_attr", + "tests/test_inkex_styles.py::StyleSheetTest::test_applied_styles", + "tests/test_inkex_styles.py::StyleSheetTest::test_classes", + "tests/test_inkex_styles.py::StyleSheetTest::test_creation", + "tests/test_inkex_styles.py::StyleSheetTest::test_lookup_and", + "tests/test_inkex_styles.py::StyleSheetTest::test_lookup_by_class", + "tests/test_inkex_styles.py::StyleSheetTest::test_lookup_by_element", + "tests/test_inkex_styles.py::StyleSheetTest::test_lookup_by_id", + "tests/test_inkex_styles.py::StyleSheetTest::test_lookup_or", + "tests/test_inkex_styles.py::StyleSheetTest::test_parsing", + "tests/test_inkex_styles.py::StyleSheetTest::test_string", + "tests/test_inkex_svg.py::BasicSvgTest::test_add_ns", + "tests/test_inkex_svg.py::BasicSvgTest::test_defs", + "tests/test_inkex_svg.py::BasicSvgTest::test_scale", + "tests/test_inkex_svg.py::BasicSvgTest::test_selected_bbox", + "tests/test_inkex_svg.py::BasicSvgTest::test_svg_by_class", + "tests/test_inkex_svg.py::BasicSvgTest::test_svg_by_href", + "tests/test_inkex_svg.py::BasicSvgTest::test_svg_by_url_link", + "tests/test_inkex_svg.py::BasicSvgTest::test_svg_center_position", + "tests/test_inkex_svg.py::BasicSvgTest::test_svg_ids", + "tests/test_inkex_svg.py::BasicSvgTest::test_svg_layers", + "tests/test_inkex_svg.py::BasicSvgTest::test_svg_load", + "tests/test_inkex_svg.py::BasicSvgTest::test_svg_name", + "tests/test_inkex_svg.py::BasicSvgTest::test_svg_nameview", + "tests/test_inkex_svg.py::BasicSvgTest::test_svg_new_id", + "tests/test_inkex_svg.py::BasicSvgTest::test_svg_select_id", + "tests/test_inkex_svg.py::NamedViewTest::test_create_guide", + "tests/test_inkex_svg.py::GetDocumentWidthTest::test_empty_viewbox", + "tests/test_inkex_svg.py::GetDocumentWidthTest::test_empty_width", + "tests/test_inkex_svg.py::GetDocumentWidthTest::test_empty_width_and_viewbox", + "tests/test_inkex_svg.py::GetDocumentWidthTest::test_no_dimensions", + "tests/test_inkex_svg.py::GetDocumentWidthTest::test_non_zero_viewbox_x", + "tests/test_inkex_svg.py::GetDocumentWidthTest::test_only_valid_viewbox", + "tests/test_inkex_svg.py::GetDocumentWidthTest::test_viewbox_only", + "tests/test_inkex_svg.py::GetDocumentWidthTest::test_width_and_viewbox", + "tests/test_inkex_svg.py::GetDocumentWidthTest::test_width_only", + "tests/test_inkex_svg.py::GetDocumentHeightTest::test_empty_height", + "tests/test_inkex_svg.py::GetDocumentHeightTest::test_empty_height_viewbox", + "tests/test_inkex_svg.py::GetDocumentHeightTest::test_empty_viewbox", + "tests/test_inkex_svg.py::GetDocumentHeightTest::test_height_and_viewbox", + "tests/test_inkex_svg.py::GetDocumentHeightTest::test_height_only", + "tests/test_inkex_svg.py::GetDocumentHeightTest::test_no_dimensions", + "tests/test_inkex_svg.py::GetDocumentHeightTest::test_no_height_valid_viewbox", + "tests/test_inkex_svg.py::GetDocumentHeightTest::test_non_zero_viewbox_y", + "tests/test_inkex_svg.py::GetDocumentHeightTest::test_viewbox_only", + "tests/test_inkex_svg.py::GetDocumentUnitTest::test_bad_viewbox_entry", + "tests/test_inkex_svg.py::GetDocumentUnitTest::test_bad_width_number", + "tests/test_inkex_svg.py::GetDocumentUnitTest::test_height_only", + "tests/test_inkex_svg.py::GetDocumentUnitTest::test_height_width_and_viewbox", + "tests/test_inkex_svg.py::GetDocumentUnitTest::test_height_with_viewbox", + "tests/test_inkex_svg.py::GetDocumentUnitTest::test_large_error_reverts_to_px", + "tests/test_inkex_svg.py::GetDocumentUnitTest::test_no_dimensions", + "tests/test_inkex_svg.py::GetDocumentUnitTest::test_unitless_width_and_viewbox", + "tests/test_inkex_svg.py::GetDocumentUnitTest::test_viewbox_only", + "tests/test_inkex_svg.py::GetDocumentUnitTest::test_width_and_viewbox_in", + "tests/test_inkex_svg.py::GetDocumentUnitTest::test_width_and_viewbox_px", + "tests/test_inkex_svg.py::GetDocumentUnitTest::test_width_only", + "tests/test_inkex_svg.py::UserUnitTest::test_adddocumentunit_common", + "tests/test_inkex_svg.py::UserUnitTest::test_adddocumentunit_non_float", + "tests/test_inkex_svg.py::UserUnitTest::test_unittouu_bad_input_number", + "tests/test_inkex_svg.py::UserUnitTest::test_unittouu_bad_input_unit", + "tests/test_inkex_svg.py::UserUnitTest::test_unittouu_empty_input", + "tests/test_inkex_svg.py::UserUnitTest::test_unittouu_identity", + "tests/test_inkex_svg.py::UserUnitTest::test_unittouu_in_to_cm", + "tests/test_inkex_svg.py::UserUnitTest::test_unittouu_parsing", + "tests/test_inkex_svg.py::UserUnitTest::test_unittouu_unitless_input", + "tests/test_inkex_svg.py::UserUnitTest::test_unittouu_yd_to_m", + "tests/test_inkex_svg.py::UserUnitTest::test_uutounit_cm_to_in", + "tests/test_inkex_svg.py::UserUnitTest::test_uutounit_identity", + "tests/test_inkex_svg.py::UserUnitTest::test_uutounit_m_to_yd", + "tests/test_inkex_svg.py::UserUnitTest::test_uutounit_unknown_unit", + "tests/test_inkex_tester.py::TesterTest::test_xmldiff", + "tests/test_inkex_transforms.py::ImmutableVector2dTest::test_assign", + "tests/test_inkex_transforms.py::ImmutableVector2dTest::test_binary_operators", + "tests/test_inkex_transforms.py::ImmutableVector2dTest::test_getitem", + "tests/test_inkex_transforms.py::ImmutableVector2dTest::test_ioperators", + "tests/test_inkex_transforms.py::ImmutableVector2dTest::test_representations", + "tests/test_inkex_transforms.py::ImmutableVector2dTest::test_unary_operators", + "tests/test_inkex_transforms.py::ImmutableVector2dTest::test_vector_creation", + "tests/test_inkex_transforms.py::Vector2dTest::test_assign", + "tests/test_inkex_transforms.py::Vector2dTest::test_binary_operators", + "tests/test_inkex_transforms.py::Vector2dTest::test_getitem", + "tests/test_inkex_transforms.py::Vector2dTest::test_ioperators", + "tests/test_inkex_transforms.py::Vector2dTest::test_representations", + "tests/test_inkex_transforms.py::Vector2dTest::test_unary_operators", + "tests/test_inkex_transforms.py::Vector2dTest::test_vector_creation", + "tests/test_inkex_transforms.py::TransformTest::test_add_transform", + "tests/test_inkex_transforms.py::TransformTest::test_apply_to_point", + "tests/test_inkex_transforms.py::TransformTest::test_construction_order", + "tests/test_inkex_transforms.py::TransformTest::test_interpolate", + "tests/test_inkex_transforms.py::TransformTest::test_invalid_creation_matrix", + "tests/test_inkex_transforms.py::TransformTest::test_invalid_creation_string", + "tests/test_inkex_transforms.py::TransformTest::test_is_rotation", + "tests/test_inkex_transforms.py::TransformTest::test_is_scale", + "tests/test_inkex_transforms.py::TransformTest::test_is_translate", + "tests/test_inkex_transforms.py::TransformTest::test_is_unity", + "tests/test_inkex_transforms.py::TransformTest::test_matrix_inversion", + "tests/test_inkex_transforms.py::TransformTest::test_new_empty", + "tests/test_inkex_transforms.py::TransformTest::test_new_from_matrix_str", + "tests/test_inkex_transforms.py::TransformTest::test_new_from_rotate", + "tests/test_inkex_transforms.py::TransformTest::test_new_from_scale", + "tests/test_inkex_transforms.py::TransformTest::test_new_from_sextlet", + "tests/test_inkex_transforms.py::TransformTest::test_new_from_skew", + "tests/test_inkex_transforms.py::TransformTest::test_new_from_translate", + "tests/test_inkex_transforms.py::TransformTest::test_new_from_triples", + "tests/test_inkex_transforms.py::TransformTest::test_repr", + "tests/test_inkex_transforms.py::TransformTest::test_rotate", + "tests/test_inkex_transforms.py::TransformTest::test_rotation_degrees", + "tests/test_inkex_transforms.py::TransformTest::test_scale", + "tests/test_inkex_transforms.py::TransformTest::test_translate", + "tests/test_inkex_transforms.py::ScaleTest::test_center", + "tests/test_inkex_transforms.py::ScaleTest::test_combine", + "tests/test_inkex_transforms.py::ScaleTest::test_creation", + "tests/test_inkex_transforms.py::ScaleTest::test_errors", + "tests/test_inkex_transforms.py::ScaleTest::test_neg", + "tests/test_inkex_transforms.py::ScaleTest::test_size", + "tests/test_inkex_transforms.py::BoundingBoxTest::test_bbox", + "tests/test_inkex_transforms.py::BoundingBoxTest::test_bbox_anchor_custom", + "tests/test_inkex_transforms.py::BoundingBoxTest::test_bbox_anchor_left_right", + "tests/test_inkex_transforms.py::BoundingBoxTest::test_bbox_anchor_radial", + "tests/test_inkex_transforms.py::BoundingBoxTest::test_bbox_anchor_top_bottom", + "tests/test_inkex_transforms.py::BoundingBoxTest::test_bbox_neg", + "tests/test_inkex_transforms.py::BoundingBoxTest::test_bbox_scale", + "tests/test_inkex_transforms.py::BoundingBoxTest::test_bbox_sum", + "tests/test_inkex_transforms.py::SegmentTest::test_segment_creation", + "tests/test_inkex_transforms.py::SegmentTest::test_segment_maths", + "tests/test_inkex_transforms.py::ExtremaTest::test_cubic_extrema_1", + "tests/test_inkex_transforms.py::ExtremaTest::test_quadratic_extrema_1", + "tests/test_inkex_transforms.py::ExtremaTest::test_quadratic_extrema_2", + "tests/test_inkex_tween.py::TweenTest::test_interpcoord", + "tests/test_inkex_tween.py::TweenTest::test_interppoints", + "tests/test_inkex_units.py::UnitsTest::test_convert_unit", + "tests/test_inkex_units.py::UnitsTest::test_discover_unit", + "tests/test_inkex_units.py::UnitsTest::test_near", + "tests/test_inkex_units.py::UnitsTest::test_number_parsing", + "tests/test_inkex_units.py::UnitsTest::test_parse_unit", + "tests/test_inkex_units.py::UnitsTest::test_render_unit", + "tests/test_inkex_utils.py::TestInkexBasic::test_boolean", + "tests/test_inkex_utils.py::TestInkexBasic::test_debug", + "tests/test_inkex_utils.py::TestInkexBasic::test_to", + "tests/test_inkex_utils.py::TestInkexBasic::test_filename", + "tests/test_inkex_utils.py::TestInkexBasic::test_add_ns", + "tests/test_inkex_utils.py::TestInkexBasic::test_strargs", + "tests/test_inkex_utils.py::TestInkexBasic::test_ascii", + "tests/test_inkex_utils.py::TestInkexBasic::test_nonunicode_latin1", + "tests/test_inkex_utils.py::TestInkexBasic::test_unicode_latin1", + "tests/test_inkscape_follow_link.py::TestFollowLinkBasic::test_all_comparisons", + "tests/test_inkscape_follow_link.py::TestFollowLinkBasic::test_default_settings", + "tests/test_inkwebeffect.py::InkWebEffectBasicTest::test_default_settings", + "tests/test_interp.py::InterpBasicTest::test_all_comparisons", + "tests/test_interp_att_g.py::InterpAttGBasicTest::test_all_comparisons", + "tests/test_interp_att_g.py::InterpAttGColorRoundingTest::test_all_comparisons", + "tests/test_jessyink_autotexts.py::JessyInkAutoTextsBasicTest::test_all_comparisons", + "tests/test_jessyink_effects.py::JessyInkEffectsTest::test_all_comparisons", + "tests/test_jessyink_export.py::JessyInkExportBasicTest::test_all_comparisons", + "tests/test_jessyink_install.py::JessyInkInstallBasicTest::test_all_comparisons", + "tests/test_jessyink_keybindings.py::JessyInkCustomKeyBindingsBasicTest::test_all_comparisons", + "tests/test_jessyink_masterslide.py::JessyInkMasterSlideBasicTest::test_all_comparisons", + "tests/test_jessyink_mousehandler.py::JessyInkAddMouseHandlerTest::test_all_comparisons", + "tests/test_jessyink_summary.py::JessyInkSummaryTest::test_all_comparisons", + "tests/test_jessyink_transitions.py::JessyInkTransitionsBasicTest::test_all_comparisons", + "tests/test_jessyink_uninstall.py::JessyInkUninstallBasicTest::test_all_comparisons", + "tests/test_jessyink_video.py::JessyInkEffectsBasicTest::test_all_comparisons", + "tests/test_jessyink_view.py::JessyInkEffectsBasicTest::test_all_comparisons", + "tests/test_jitternodes.py::JitterNodesBasicTest::test_all_comparisons", + "tests/test_launch_webbrowser.py::TestWebsiteOpen::test_open", + "tests/test_layer2png.py::Layer2PNGTest::test_all_comparisons", + "tests/test_layer2png.py::Layer2PNGTest::test_bad_slice_layer", + "tests/test_layer2png.py::Layer2PNGTest::test_color", + "tests/test_layer2png.py::Layer2PNGTest::test_get_layers", + "tests/test_layers2svgfont.py::TestLayers2SVGFontBasic::test_all_comparisons", + "tests/test_layout_nup.py::TestNupBasic::test_default_settings", + "tests/test_lindenmayer.py::LSystemBasicTest::test_all_comparisons", + "tests/test_lorem_ipsum.py::LorumIpsumBasicTest::test_all_comparisons", + "tests/test_markers_strokepaint.py::MarkerStrokePaintBasicTest::test_all_comparisons", + "tests/test_markers_strokepaint.py::MarkerStrokePaintBasicTest::test_basic", + "tests/test_measure.py::LengthBasicTest::test_all_comparisons", + "tests/test_media_zip.py::CmoBasicTest::test_all_comparisons", + "tests/test_merge_styles.py::TestMergeStylesBasic::test_all_comparisons", + "tests/test_motion.py::MotionBasicTest::test_all_comparisons", + "tests/test_motion.py::MotionBasicTest::test_default_settings", + "tests/test_new_glyph_layer.py::TestNewGlyphLayerBasic::test_all_comparisons", + "tests/test_new_glyph_layer.py::TestNewGlyphLayerBasic::test_default_settings", + "tests/test_next_glyph_layer.py::TestNextLayerBasic::test_all_comparisons", + "tests/test_next_glyph_layer.py::TestNextLayerBasic::test_default_settings", + "tests/test_nicechart.py::TestNiceChartBasic::test_all_comparisons", + "tests/test_output_scour.py::ScourBasicTests::test_all_comparisons", + "tests/test_output_scour.py::ScourBasicTests::test_default_settings", + "tests/test_param_curves.py::TestParamCurvesBasic::test_all_comparisons", + "tests/test_param_curves.py::TestParamCurvesBasic::test_default_settings", + "tests/test_path_envelope.py::PathEnvelopeTest::test_all_comparisons", + "tests/test_path_envelope.py::PathEnvelopeGroupTest::test_all_comparisons", + "tests/test_path_mesh.py::PathToMeshTest::test_all_comparisons", + "tests/test_path_mesh.py::MeshToPathTest::test_all_comparisons", + "tests/test_path_number_nodes.py::NumberNodesTest::test_all_comparisons", + "tests/test_path_to_absolute.py::PathToAbsoluteTest::test_all_comparisons", + "tests/test_pathalongpath.py::TestPathAlongPathBasic::test_all_comparisons", + "tests/test_pathscatter.py::TestPathScatterBasic::test_all_comparisons", + "tests/test_pathscatter.py::TestPathScatterBasic::test_default_settings", + "tests/test_pdflatex.py::PdfLatexTest::test_all_comparisons", + "tests/test_perfectboundcover.py::PerfectBoundCoverBasicTest::test_all_comparisons", + "tests/test_perfectboundcover.py::PerfectBoundCoverBasicTest::test_default_settings", + "tests/test_perspective.py::PerspectiveBasicTest::test_all_comparisons", + "tests/test_perspective.py::PerspectiveGroupTest::test_all_comparisons", + "tests/test_pixelsnap.py::TestPixelSnapEffectBasic::test_all_comparisons", + "tests/test_plotter.py::TestPlotter::test_all_comparisons", + "tests/test_polyhedron_3d.py::Poly3DBasicTest::test_all_comparisons", + "tests/test_prepare_file_save_as.py::TestPrepareFileSaveBasic::test_all_comparisons", + "tests/test_previous_glyph_layer.py::TestPreviousLayerBasic::test_all_comparisons", + "tests/test_previous_glyph_layer.py::TestPreviousLayerBasic::test_default_settings", + "tests/test_print_win32_vector.py::TestPrintWin32VectorBasic::test_default_settings", + "tests/test_printing_marks.py::PrintingMarksBasicTest::test_all_comparisons", + "tests/test_printing_marks.py::PrintingMarksBasicTest::test_default_settings", + "tests/test_ps_input.py::TestPostscriptInput::test_all_comparisons", + "tests/test_render_alphabetsoup.py::AlphabetSoupBasicTest::test_default_settings", + "tests/test_render_barcode.py::BarcodeBasicTest::test_all_comparisons", + "tests/test_render_barcode.py::GetBarcodeTest::test_render_barcode_code128", + "tests/test_render_barcode.py::GetBarcodeTest::test_render_barcode_code25i", + "tests/test_render_barcode.py::GetBarcodeTest::test_render_barcode_code39", + "tests/test_render_barcode.py::GetBarcodeTest::test_render_barcode_code39ext", + "tests/test_render_barcode.py::GetBarcodeTest::test_render_barcode_ean2", + "tests/test_render_barcode.py::GetBarcodeTest::test_render_barcode_ian13", + "tests/test_render_barcode.py::GetBarcodeTest::test_render_barcode_ian5", + "tests/test_render_barcode.py::GetBarcodeTest::test_render_barcode_ian8", + "tests/test_render_barcode.py::GetBarcodeTest::test_render_barcode_royal_mail", + "tests/test_render_barcode.py::GetBarcodeTest::test_render_barcode_upca", + "tests/test_render_barcode.py::GetBarcodeTest::test_render_barcode_upce", + "tests/test_render_barcode_datamatrix.py::TestDataMatrixBasic::test_all_comparisons", + "tests/test_render_barcode_qrcode.py::TestQRCodeInkscapeBasic::test_all_comparisons", + "tests/test_render_barcode_qrcode.py::TestQRCodeInkscapeSymbol::test_all_comparisons", + "tests/test_render_gear_rack.py::TestRackGearBasic::test_all_comparisons", + "tests/test_render_gear_rack.py::TestRackGearBasic::test_default_settings", + "tests/test_render_gears.py::GearsBasicTest::test_all_comparisons", + "tests/test_render_gears.py::GearsBasicTest::test_default_settings", + "tests/test_replace_font.py::TestReplaceFontBasic::test_all_comparisons", + "tests/test_replace_font.py::TestReplaceFontBasic::test_default_settings", + "tests/test_replace_font.py::TestFontList::test_all_comparisons", + "tests/test_restack.py::RestackBasicTest::test_all_comparisons", + "tests/test_rtree.py::RTreeTurtleBasicTest::test_all_comparisons", + "tests/test_rubberstretch.py::TestRubberStretchBasic::test_all_comparisons", + "tests/test_rubberstretch.py::TestRubberStretchBasic::test_default_settings", + "tests/test_scribus_pdf.py::ScribusBasicTest::test_all_comparisons", + "tests/test_setup_typography_canvas.py::TestSetupTypographyCanvasBasic::test_all_comparisons", + "tests/test_setup_typography_canvas.py::TestSetupTypographyCanvasBasic::test_default_settings", + "tests/test_spirograph.py::SpirographBasicTest::test_all_comparisons", + "tests/test_spirograph.py::SpirographBasicTest::test_default_settings", + "tests/test_straightseg.py::SegmentStraightenerBasicTest::test_all_comparisons", + "tests/test_straightseg.py::SegmentStraightenerBasicTest::test_default_settings", + "tests/test_svgcalendar.py::CalendarArguments::test_all_comparisons", + "tests/test_svgcalendar.py::CalendarArguments::test_configuring_week_start_mon", + "tests/test_svgcalendar.py::CalendarArguments::test_configuring_week_start_sun", + "tests/test_svgcalendar.py::CalendarArguments::test_converted_year_thousand", + "tests/test_svgcalendar.py::CalendarArguments::test_converted_year_zero", + "tests/test_svgcalendar.py::CalendarArguments::test_default_names_list", + "tests/test_svgcalendar.py::CalendarArguments::test_inner_extra_spaces", + "tests/test_svgcalendar.py::CalendarArguments::test_modifyed_names_list", + "tests/test_svgcalendar.py::CalendarArguments::test_recognize_a_weekend", + "tests/test_svgcalendar.py::CalendarArguments::test_starting_names_list", + "tests/test_svgfont2layers.py::TestSVGFont2LayersBasic::test_all_comparisons", + "tests/test_synfig_fileformat.py::TestSynFigDefaultLayerVersion::test_layer_version", + "tests/test_synfig_output.py::TestSynfigExportBasic::test_default_settings", + "tests/test_synfig_prepare.py::TestSynfigPrepBasic::test_default_settings", + "tests/test_tar_layers.py::LayersOutputBasicTest::test_all_comparisons", + "tests/test_template.py::TemplateTestCase::test_all_comparisons", + "tests/test_template_dvd_cover.py::TestDvdCoverBasic::test_all_comparisons", + "tests/test_template_seamless_pattern.py::SeamlessPatternBasicTest::test_all_comparisons", + "tests/test_text_braille.py::TestBrailleBasic::test_all_comparisons", + "tests/test_text_extract.py::TestExtractBasic::test_all_comparisons", + "tests/test_text_flipcase.py::TestFlipCaseBasic::test_all_comparisons", + "tests/test_text_lowercase.py::LowerCase::test_all_comparisons", + "tests/test_text_lowercase.py::LowerCase::test_lowercase", + "tests/test_text_lowercase.py::LowerCase::test_numbers_before", + "tests/test_text_lowercase.py::LowerCase::test_punctuation_before", + "tests/test_text_lowercase.py::LowerCase::test_sentencecase", + "tests/test_text_lowercase.py::LowerCase::test_titlecase", + "tests/test_text_lowercase.py::LowerCase::test_uppercase", + "tests/test_text_merge.py::TestMergeBasic::test_all_comparisons", + "tests/test_text_randomcase.py::TestRandomCaseBasic::test_all_comparisons", + "tests/test_text_sentencecase.py::TestSentenceCaseBasic::test_all_comparisons", + "tests/test_text_split.py::TestSplitBasic::test_all_comparisons", + "tests/test_text_titlecase.py::TitleCaseTest::test_all_comparisons", + "tests/test_text_titlecase.py::TitleCaseTest::test_check_strings", + "tests/test_text_titlecase.py::TitleCaseTest::test_lowercase", + "tests/test_text_titlecase.py::TitleCaseTest::test_numbers_before", + "tests/test_text_titlecase.py::TitleCaseTest::test_punctuation_before", + "tests/test_text_titlecase.py::TitleCaseTest::test_sentencecase", + "tests/test_text_titlecase.py::TitleCaseTest::test_uppercase", + "tests/test_text_uppercase.py::UpperCase::test_all_comparisons", + "tests/test_text_uppercase.py::UpperCase::test_lowercase", + "tests/test_text_uppercase.py::UpperCase::test_numbers_before", + "tests/test_text_uppercase.py::UpperCase::test_punctuation_before", + "tests/test_text_uppercase.py::UpperCase::test_sentencecase", + "tests/test_text_uppercase.py::UpperCase::test_titlecase", + "tests/test_triangle.py::TriangleBasicTest::test_all_comparisons", + "tests/test_triangle.py::TriangleBasicTest::test_default_settings", + "tests/test_ungroup_deep.py::TestUngroupBasic::test_all_comparisons", + "tests/test_voronoi.py::TestVoronoiSiteBasic::test_site_basic", + "tests/test_voronoi2svg.py::TestVoronoi2svgBasic::test_all_comparisons", + "tests/test_web_interactive_mockup.py::TestInkWebInteractiveMockupBasic::test_all_comparisons", + "tests/test_web_set_att.py::SetAttributeBasic::test_all_comparisons", + "tests/test_web_transmit_att.py::TestInkWebTransmitAttBasic::test_all_comparisons", + "tests/test_webslicer_create_group.py::TestWebSlicerCreateGroupBasic::test_all_comparisons", + "tests/test_webslicer_create_rect.py::TestWebSlicerCreateRectBasic::test_all_comparisons", + "tests/test_webslicer_export.py::TestWebSlicerExportBasic::test_all_comparisons", + "tests/test_whirl.py::WhirlBasicTest::test_all_comparisons", + "tests/test_whirl.py::WhirlBasicTest::test_default_settings", + "tests/test_wireframe_sphere.py::TestWireframeSphereBasic::test_all_comparisons", + "tests/test_wireframe_sphere.py::TestWireframeSphereBasic::test_default_settings" +] \ No newline at end of file diff --git a/share/extensions/.pytest_cache/v/cache/stepwise b/share/extensions/.pytest_cache/v/cache/stepwise new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/share/extensions/.pytest_cache/v/cache/stepwise @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/share/extensions/LICENSE.txt b/share/extensions/LICENSE.txt new file mode 100644 index 0000000..b83f24b --- /dev/null +++ b/share/extensions/LICENSE.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/share/extensions/MANIFEST.in b/share/extensions/MANIFEST.in new file mode 100644 index 0000000..2f29cde --- /dev/null +++ b/share/extensions/MANIFEST.in @@ -0,0 +1,2 @@ +include *.py +include *.inx diff --git a/share/extensions/Poly3DObjects/cube.obj b/share/extensions/Poly3DObjects/cube.obj new file mode 100644 index 0000000..b577ce1 --- /dev/null +++ b/share/extensions/Poly3DObjects/cube.obj @@ -0,0 +1,19 @@ +#Name:Cube +#Type:Face-specified +#Direction:Clockwise + +v -0.5 -0.5 -0.5 +v -0.5 -0.5 0.5 +v -0.5 0.5 -0.5 +v -0.5 0.5 0.5 +v 0.5 -0.5 -0.5 +v 0.5 -0.5 0.5 +v 0.5 0.5 -0.5 +v 0.5 0.5 0.5 + +f 8 4 2 6 +f 8 6 5 7 +f 8 7 3 4 +f 4 3 1 2 +f 1 3 7 5 +f 2 1 5 6 \ No newline at end of file diff --git a/share/extensions/Poly3DObjects/cuboct.obj b/share/extensions/Poly3DObjects/cuboct.obj new file mode 100644 index 0000000..709606b --- /dev/null +++ b/share/extensions/Poly3DObjects/cuboct.obj @@ -0,0 +1,30 @@ +#Name:Cuboctahedron +#Type:Face_specified + +v -1. 0 0 +v -0.5 -0.5 -0.70710678 +v -0.5 -0.5 0.70710678 +v -0.5 0.5 -0.70710678 +v -0.5 0.5 0.70710678 +v 0 -1. 0 +v 0 1. 0 +v 0.5 -0.5 -0.70710678 +v 0.5 -0.5 0.70710678 +v 0.5 0.5 -0.70710678 +v 0.5 0.5 0.70710678 +v 1. 0 0 + +f 12 11 9 +f 3 5 1 +f 6 9 3 +f 5 11 7 +f 8 10 12 +f 1 4 2 +f 2 8 6 +f 7 10 4 +f 4 10 8 2 +f 3 9 11 5 +f 9 6 8 12 +f 3 1 2 6 +f 5 7 4 1 +f 11 12 10 7 diff --git a/share/extensions/Poly3DObjects/dodec.obj b/share/extensions/Poly3DObjects/dodec.obj new file mode 100644 index 0000000..5b66f09 --- /dev/null +++ b/share/extensions/Poly3DObjects/dodec.obj @@ -0,0 +1,36 @@ +#NameDodecahedron +#Type:Face_specified + +v 0 0 1.4012585 +v 0 0 -1.4012585 +v 0.17841104 -1.3090170 0.46708618 +v 0.17841104 1.3090170 0.46708618 +v 0.46708618 -0.80901699 -1.0444364 +v 0.46708618 0.80901699 -1.0444364 +v 1.0444364 -0.80901699 0.46708618 +v 1.0444364 0.80901699 0.46708618 +v -1.2228475 -0.5 0.46708618 +v -1.2228475 0.5 0.46708618 +v 1.2228475 -0.5 -0.46708618 +v 1.2228475 0.5 -0.46708618 +v -0.93417236 0 -1.0444364 +v -0.46708618 -0.80901699 1.0444364 +v -0.46708618 0.80901699 1.0444364 +v 0.93417236 0 1.0444364 +v -1.0444364 -0.80901699 -0.46708618 +v -1.0444364 0.80901699 -0.46708618 +v -0.17841104 -1.3090170 -0.46708618 +v -0.17841104 1.3090170 -0.46708618 + +f 15 10 9 14 1 +f 2 6 12 11 5 +f 5 11 7 3 19 +f 11 12 8 16 7 +f 12 6 20 4 8 +f 6 2 13 18 20 +f 2 5 19 17 13 +f 4 20 18 10 15 +f 18 13 17 9 10 +f 17 19 3 14 9 +f 3 7 16 1 14 +f 16 8 4 15 1 diff --git a/share/extensions/Poly3DObjects/great_dodec.obj b/share/extensions/Poly3DObjects/great_dodec.obj new file mode 100644 index 0000000..2d12f02 --- /dev/null +++ b/share/extensions/Poly3DObjects/great_dodec.obj @@ -0,0 +1,96 @@ +#Name:Great Dodecahedron +#Type:Face_specified + +v 0. 0. -0.951057 +v 0. 0. 0.951057 +v -0.425325 -0.309017 -0.100406 +v -0.425325 0.309017 -0.100406 +v 0.425325 -0.309017 0.100406 +v 0.425325 0.309017 0.100406 +v -0.688191 -0.5 0.425325 +v -0.688191 0.5 0.425325 +v 0.688191 -0.5 -0.425325 +v 0.688191 0.5 -0.425325 +v -0.850651 0. -0.425325 +v 0.850651 0. 0.425325 +v -0.100406 -0.309017 0.425325 +v -0.100406 0.309017 0.425325 +v 0.100406 -0.309017 -0.425325 +v 0.100406 0.309017 -0.425325 +v -0.32492 0. 0.425325 +v -0.16246 -0.5 0.100406 +v -0.16246 0.5 0.100406 +v 0.16246 -0.5 -0.100406 +v 0.16246 0.5 -0.100406 +v 0.32492 0. -0.425325 +v -0.525731 0. 0.100406 +v -0.262866 -0.809017 -0.425325 +v -0.262866 0.190983 -0.425325 +v -0.262866 -0.190983 -0.425325 +v -0.262866 0.809017 -0.425325 +v 0.262866 -0.809017 0.425325 +v 0.262866 0.190983 0.425325 +v 0.262866 -0.190983 0.425325 +v 0.262866 0.809017 0.425325 +v 0.525731 0. -0.100406 + +f 14 2 31 +f 14 31 8 +f 14 8 2 +f 17 2 8 +f 17 8 7 +f 17 7 2 +f 13 2 7 +f 13 7 28 +f 13 28 2 +f 30 2 28 +f 30 28 12 +f 30 12 2 +f 29 2 12 +f 29 12 31 +f 29 31 2 +f 15 9 24 +f 15 24 1 +f 15 1 9 +f 22 10 9 +f 22 9 1 +f 22 1 10 +f 16 27 10 +f 16 10 1 +f 16 1 27 +f 25 11 27 +f 25 27 1 +f 25 1 11 +f 26 24 11 +f 26 11 1 +f 26 1 24 +f 19 31 27 +f 19 27 8 +f 19 8 31 +f 23 8 11 +f 23 11 7 +f 23 7 8 +f 18 7 24 +f 18 24 28 +f 18 28 7 +f 5 28 9 +f 5 9 12 +f 5 12 28 +f 6 12 10 +f 6 10 31 +f 6 31 12 +f 20 9 28 +f 20 28 24 +f 20 24 9 +f 32 10 12 +f 32 12 9 +f 32 9 10 +f 21 27 31 +f 21 31 10 +f 21 10 27 +f 4 11 8 +f 4 8 27 +f 4 27 11 +f 3 24 7 +f 3 7 11 +f 3 11 24 diff --git a/share/extensions/Poly3DObjects/great_rhombicosidodec.obj b/share/extensions/Poly3DObjects/great_rhombicosidodec.obj new file mode 100644 index 0000000..88bdccc --- /dev/null +++ b/share/extensions/Poly3DObjects/great_rhombicosidodec.obj @@ -0,0 +1,185 @@ +#Name:Great Rhombicosidodecahedron +#Type:face_specified +v -1. -1.30902 -3.42705 +v -1. -1.30902 3.42705 +v -1. 1.30902 -3.42705 +v -1. 1.30902 3.42705 +v -0.5 -0.5 -3.73607 +v -0.5 -0.5 3.73607 +v -0.5 0.5 -3.73607 +v -0.5 0.5 3.73607 +v -0.5 -3.73607 -0.5 +v -0.5 -3.73607 0.5 +v -0.5 -2.11803 -3.11803 +v -0.5 -2.11803 3.11803 +v -0.5 3.73607 -0.5 +v -0.5 3.73607 0.5 +v -0.5 2.11803 -3.11803 +v -0.5 2.11803 3.11803 +v 0.5 -0.5 -3.73607 +v 0.5 -0.5 3.73607 +v 0.5 0.5 -3.73607 +v 0.5 0.5 3.73607 +v 0.5 -3.73607 -0.5 +v 0.5 -3.73607 0.5 +v 0.5 -2.11803 -3.11803 +v 0.5 -2.11803 3.11803 +v 0.5 3.73607 -0.5 +v 0.5 3.73607 0.5 +v 0.5 2.11803 -3.11803 +v 0.5 2.11803 3.11803 +v 1. -1.30902 -3.42705 +v 1. -1.30902 3.42705 +v 1. 1.30902 -3.42705 +v 1. 1.30902 3.42705 +v -3.42705 -1. -1.30902 +v -3.42705 -1. 1.30902 +v -3.42705 1. -1.30902 +v -3.42705 1. 1.30902 +v -2.92705 -1.80902 -1.61803 +v -2.92705 -1.80902 1.61803 +v -2.92705 1.80902 -1.61803 +v -2.92705 1.80902 1.61803 +v -1.80902 -1.61803 -2.92705 +v -1.80902 -1.61803 2.92705 +v -1.80902 1.61803 -2.92705 +v -1.80902 1.61803 2.92705 +v -1.30902 -3.42705 -1. +v -1.30902 -3.42705 1. +v -1.30902 -2.42705 -2.61803 +v -1.30902 -2.42705 2.61803 +v -1.30902 2.42705 -2.61803 +v -1.30902 2.42705 2.61803 +v -1.30902 3.42705 -1. +v -1.30902 3.42705 1. +v -2.61803 -1.30902 -2.42705 +v -2.61803 -1.30902 2.42705 +v -2.61803 1.30902 -2.42705 +v -2.61803 1.30902 2.42705 +v -3.73607 -0.5 -0.5 +v -3.73607 -0.5 0.5 +v -3.73607 0.5 -0.5 +v -3.73607 0.5 0.5 +v -1.61803 -2.92705 -1.80902 +v -1.61803 -2.92705 1.80902 +v -1.61803 2.92705 -1.80902 +v -1.61803 2.92705 1.80902 +v -3.11803 -0.5 -2.11803 +v -3.11803 -0.5 2.11803 +v -3.11803 0.5 -2.11803 +v -3.11803 0.5 2.11803 +v -2.11803 -3.11803 -0.5 +v -2.11803 -3.11803 0.5 +v -2.11803 3.11803 -0.5 +v -2.11803 3.11803 0.5 +v -2.42705 -2.61803 -1.30902 +v -2.42705 -2.61803 1.30902 +v -2.42705 2.61803 -1.30902 +v -2.42705 2.61803 1.30902 +v 1.61803 -2.92705 -1.80902 +v 1.61803 -2.92705 1.80902 +v 1.61803 2.92705 -1.80902 +v 1.61803 2.92705 1.80902 +v 2.42705 -2.61803 -1.30902 +v 2.42705 -2.61803 1.30902 +v 2.42705 2.61803 -1.30902 +v 2.42705 2.61803 1.30902 +v 3.73607 -0.5 -0.5 +v 3.73607 -0.5 0.5 +v 3.73607 0.5 -0.5 +v 3.73607 0.5 0.5 +v 2.11803 -3.11803 -0.5 +v 2.11803 -3.11803 0.5 +v 2.11803 3.11803 -0.5 +v 2.11803 3.11803 0.5 +v 1.30902 -3.42705 -1. +v 1.30902 -3.42705 1. +v 1.30902 -2.42705 -2.61803 +v 1.30902 -2.42705 2.61803 +v 1.30902 2.42705 -2.61803 +v 1.30902 2.42705 2.61803 +v 1.30902 3.42705 -1. +v 1.30902 3.42705 1. +v 2.61803 -1.30902 -2.42705 +v 2.61803 -1.30902 2.42705 +v 2.61803 1.30902 -2.42705 +v 2.61803 1.30902 2.42705 +v 3.11803 -0.5 -2.11803 +v 3.11803 -0.5 2.11803 +v 3.11803 0.5 -2.11803 +v 3.11803 0.5 2.11803 +v 1.80902 -1.61803 -2.92705 +v 1.80902 -1.61803 2.92705 +v 1.80902 1.61803 -2.92705 +v 1.80902 1.61803 2.92705 +v 2.92705 -1.80902 -1.61803 +v 2.92705 -1.80902 1.61803 +v 2.92705 1.80902 -1.61803 +v 2.92705 1.80902 1.61803 +v 3.42705 -1. -1.30902 +v 3.42705 -1. 1.30902 +v 3.42705 1. -1.30902 +v 3.42705 1. 1.30902 + +f 2 6 8 4 44 56 68 66 54 42 +f 109 29 17 19 31 111 103 107 105 101 +f 24 30 18 6 2 12 +f 7 3 15 27 31 19 +f 58 57 33 37 73 69 70 74 38 34 +f 84 116 120 88 87 119 115 83 91 92 +f 90 89 81 113 117 85 86 118 114 82 +f 36 40 76 72 71 75 39 35 59 60 +f 5 17 29 23 11 1 +f 4 8 20 32 28 16 +f 67 55 43 3 7 5 1 41 53 65 +f 18 30 110 102 106 108 104 112 32 20 +f 79 83 115 103 111 97 +f 38 74 62 48 42 54 +f 4 16 50 44 +f 23 29 109 95 +f 96 110 30 24 +f 43 49 15 3 +f 53 41 47 61 73 37 +f 98 112 104 116 84 80 +f 69 45 9 10 46 70 +f 26 100 92 91 99 25 +f 82 114 102 110 96 78 +f 55 39 75 63 49 43 +f 1 11 47 41 +f 28 32 112 98 +f 61 47 11 23 95 77 93 21 9 45 +f 50 16 28 98 80 100 26 14 52 64 +f 97 111 31 27 +f 42 48 12 2 +f 44 50 64 76 40 56 +f 77 95 109 101 113 81 +f 63 51 13 25 99 79 97 27 15 49 +f 46 10 22 94 78 96 24 12 48 62 +f 52 14 13 51 71 72 +f 22 21 93 89 90 94 +f 115 119 107 103 +f 34 38 54 66 +f 71 51 63 75 +f 94 90 82 78 +f 114 118 106 102 +f 35 39 55 67 +f 70 46 62 74 +f 99 91 83 79 +f 65 53 37 33 +f 104 108 120 116 +f 77 81 89 93 +f 76 64 52 72 +f 59 35 67 65 33 57 +f 106 118 86 88 120 108 +f 68 56 40 36 +f 101 105 117 113 +f 80 84 92 100 +f 73 61 45 69 +f 34 66 68 36 60 58 +f 105 107 119 87 85 117 +f 7 19 17 5 +f 6 18 20 8 +f 14 26 25 13 +f 9 21 22 10 +f 58 60 59 57 +f 85 87 88 86 diff --git a/share/extensions/Poly3DObjects/great_rhombicuboct.obj b/share/extensions/Poly3DObjects/great_rhombicuboct.obj new file mode 100644 index 0000000..1ef7b4c --- /dev/null +++ b/share/extensions/Poly3DObjects/great_rhombicuboct.obj @@ -0,0 +1,77 @@ +#Name:Great Rhombicuboctahedron +#Type:face_specified +v -0.5 1.20711 -1.91421 +v -0.5 1.20711 1.91421 +v -0.5 -1.20711 -1.91421 +v -0.5 -1.20711 1.91421 +v -0.5 -1.91421 1.20711 +v -0.5 -1.91421 -1.20711 +v -0.5 1.91421 1.20711 +v -0.5 1.91421 -1.20711 +v 0.5 1.20711 -1.91421 +v 0.5 1.20711 1.91421 +v 0.5 -1.20711 -1.91421 +v 0.5 -1.20711 1.91421 +v 0.5 -1.91421 1.20711 +v 0.5 -1.91421 -1.20711 +v 0.5 1.91421 1.20711 +v 0.5 1.91421 -1.20711 +v 1.20711 -0.5 -1.91421 +v 1.20711 -0.5 1.91421 +v 1.20711 0.5 -1.91421 +v 1.20711 0.5 1.91421 +v 1.20711 -1.91421 -0.5 +v 1.20711 -1.91421 0.5 +v 1.20711 1.91421 -0.5 +v 1.20711 1.91421 0.5 +v -1.20711 -0.5 -1.91421 +v -1.20711 -0.5 1.91421 +v -1.20711 0.5 -1.91421 +v -1.20711 0.5 1.91421 +v -1.20711 -1.91421 -0.5 +v -1.20711 -1.91421 0.5 +v -1.20711 1.91421 -0.5 +v -1.20711 1.91421 0.5 +v -1.91421 -0.5 1.20711 +v -1.91421 -0.5 -1.20711 +v -1.91421 0.5 1.20711 +v -1.91421 0.5 -1.20711 +v -1.91421 1.20711 -0.5 +v -1.91421 1.20711 0.5 +v -1.91421 -1.20711 -0.5 +v -1.91421 -1.20711 0.5 +v 1.91421 -0.5 1.20711 +v 1.91421 -0.5 -1.20711 +v 1.91421 0.5 1.20711 +v 1.91421 0.5 -1.20711 +v 1.91421 1.20711 -0.5 +v 1.91421 1.20711 0.5 +v 1.91421 -1.20711 -0.5 +v 1.91421 -1.20711 0.5 + +f 44 42 17 19 +f 14 6 3 11 +f 34 36 27 25 +f 8 16 9 1 +f 20 18 41 43 +f 12 4 5 13 +f 26 28 35 33 +f 2 10 15 7 +f 45 23 24 46 +f 39 29 30 40 +f 48 22 21 47 +f 38 32 31 37 +f 9 19 17 11 3 25 27 1 +f 2 28 26 4 12 18 20 10 +f 41 48 47 42 44 45 46 43 +f 35 38 37 36 34 39 40 33 +f 15 24 23 16 8 31 32 7 +f 5 30 29 6 14 21 22 13 +f 46 24 15 10 20 43 +f 35 28 2 7 32 38 +f 41 18 12 13 22 48 +f 40 30 5 4 26 33 +f 44 19 9 16 23 45 +f 37 31 8 1 27 36 +f 47 21 14 11 17 42 +f 34 25 3 6 29 39 diff --git a/share/extensions/Poly3DObjects/great_stel_dodec.obj b/share/extensions/Poly3DObjects/great_stel_dodec.obj new file mode 100644 index 0000000..b0e5429 --- /dev/null +++ b/share/extensions/Poly3DObjects/great_stel_dodec.obj @@ -0,0 +1,96 @@ +#Name:Great Stellated Dodecahedron +#Type:face_specified + +v 0. 0. -0.951057 +v 0. 0. 0.951057 +v -0.425325 -1.30902 1.80171 +v -0.425325 1.30902 1.80171 +v 0.425325 -1.30902 -1.80171 +v 0.425325 1.30902 -1.80171 +v -0.688191 -0.5 0.425325 +v -0.688191 0.5 0.425325 +v -0.688191 -2.11803 0.425325 +v -0.688191 2.11803 0.425325 +v 0.688191 -0.5 -0.425325 +v 0.688191 0.5 -0.425325 +v 0.688191 -2.11803 -0.425325 +v 0.688191 2.11803 -0.425325 +v -0.850651 0. -0.425325 +v 0.850651 0. 0.425325 +v -1.11352 -0.809017 -1.80171 +v -1.11352 0.809017 -1.80171 +v 1.11352 -0.809017 1.80171 +v 1.11352 0.809017 1.80171 +v -1.80171 -1.30902 -0.425325 +v -1.80171 1.30902 -0.425325 +v 1.80171 -1.30902 0.425325 +v 1.80171 1.30902 0.425325 +v -2.22703 0. 0.425325 +v 2.22703 0. -0.425325 +v -0.262866 -0.809017 -0.425325 +v -0.262866 0.809017 -0.425325 +v 0.262866 -0.809017 0.425325 +v 0.262866 0.809017 0.425325 +v -1.37638 0. 1.80171 +v 1.37638 0. -1.80171 + +f 4 2 30 +f 4 30 8 +f 4 8 2 +f 31 2 8 +f 31 8 7 +f 31 7 2 +f 3 2 7 +f 3 7 29 +f 3 29 2 +f 19 2 29 +f 19 29 16 +f 19 16 2 +f 20 2 16 +f 20 16 30 +f 20 30 2 +f 5 11 27 +f 5 27 1 +f 5 1 11 +f 32 12 11 +f 32 11 1 +f 32 1 12 +f 6 28 12 +f 6 12 1 +f 6 1 28 +f 18 15 28 +f 18 28 1 +f 18 1 15 +f 17 27 15 +f 17 15 1 +f 17 1 27 +f 10 30 28 +f 10 28 8 +f 10 8 30 +f 25 8 15 +f 25 15 7 +f 25 7 8 +f 9 7 27 +f 9 27 29 +f 9 29 7 +f 23 29 11 +f 23 11 16 +f 23 16 29 +f 24 16 12 +f 24 12 30 +f 24 30 16 +f 13 11 29 +f 13 29 27 +f 13 27 11 +f 26 12 16 +f 26 16 11 +f 26 11 12 +f 14 28 30 +f 14 30 12 +f 14 12 28 +f 22 15 8 +f 22 8 28 +f 22 28 15 +f 21 27 7 +f 21 7 15 +f 21 15 27 diff --git a/share/extensions/Poly3DObjects/icos.obj b/share/extensions/Poly3DObjects/icos.obj new file mode 100644 index 0000000..ed55ea4 --- /dev/null +++ b/share/extensions/Poly3DObjects/icos.obj @@ -0,0 +1,36 @@ +#Name:Icosahedron +#Type:face_specified + +v 0 0 -0.95105652 +v 0 0 0.95105652 +v -0.85065081 0 -0.42532540 +v 0.85065081 0 0.42532540 +v 0.68819096 -0.50000000 -0.42532540 +v 0.68819096 0.50000000 -0.42532540 +v -0.68819096 -0.50000000 0.42532540 +v -0.68819096 0.50000000 0.42532540 +v -0.26286556 -0.80901699 -0.42532540 +v -0.26286556 0.80901699 -0.42532540 +v 0.26286556 -0.80901699 0.42532540 +v 0.26286556 0.80901699 0.42532540 + +f 2 12 8 +f 2 8 7 +f 2 7 11 +f 2 11 4 +f 2 4 12 +f 5 9 1 +f 6 5 1 +f 10 6 1 +f 3 10 1 +f 9 3 1 +f 12 10 8 +f 8 3 7 +f 7 9 11 +f 11 5 4 +f 4 6 12 +f 5 11 9 +f 6 4 5 +f 10 12 6 +f 3 8 10 +f 9 7 3 diff --git a/share/extensions/Poly3DObjects/icosidodec.obj b/share/extensions/Poly3DObjects/icosidodec.obj new file mode 100644 index 0000000..961bec2 --- /dev/null +++ b/share/extensions/Poly3DObjects/icosidodec.obj @@ -0,0 +1,65 @@ +#Name:Icosidodecahedron +#Type:face_specified +v 0. -1.61803 0. +v 0. 1.61803 0. +v 0.262866 -0.809017 -1.37638 +v 0.262866 0.809017 -1.37638 +v 0.425325 -1.30902 0.850651 +v 0.425325 1.30902 0.850651 +v 0.688191 -0.5 1.37638 +v 0.688191 0.5 1.37638 +v 1.11352 -0.809017 -0.850651 +v 1.11352 0.809017 -0.850651 +v -1.37638 0. -0.850651 +v -0.688191 -0.5 -1.37638 +v -0.688191 0.5 -1.37638 +v 1.37638 0. 0.850651 +v 0.951057 -1.30902 0. +v 0.951057 1.30902 0. +v 0.850651 0. -1.37638 +v -0.951057 -1.30902 0. +v -0.951057 1.30902 0. +v -1.53884 -0.5 0. +v -1.53884 0.5 0. +v 1.53884 -0.5 0. +v 1.53884 0.5 0. +v -0.850651 0. 1.37638 +v -1.11352 -0.809017 0.850651 +v -1.11352 0.809017 0.850651 +v -0.425325 -1.30902 -0.850651 +v -0.425325 1.30902 -0.850651 +v -0.262866 -0.809017 1.37638 +v -0.262866 0.809017 1.37638 + +f 30 24 29 7 8 +f 26 24 30 +f 25 29 24 +f 5 7 29 +f 14 8 7 +f 6 30 8 +f 16 2 6 +f 19 21 26 +f 20 18 25 +f 1 15 5 +f 22 23 14 +f 2 19 26 30 6 +f 21 20 25 24 26 +f 18 1 5 29 25 +f 15 22 14 7 5 +f 23 16 6 8 14 +f 12 13 4 17 3 +f 3 17 9 +f 17 4 10 +f 4 13 28 +f 13 12 11 +f 12 3 27 +f 27 1 18 +f 9 22 15 +f 10 16 23 +f 28 19 2 +f 11 20 21 +f 27 3 9 15 1 +f 9 17 10 23 22 +f 10 4 28 2 16 +f 28 13 11 21 19 +f 11 12 27 18 20 diff --git a/share/extensions/Poly3DObjects/jessens_orthog_icos.obj b/share/extensions/Poly3DObjects/jessens_orthog_icos.obj new file mode 100644 index 0000000..41aff7a --- /dev/null +++ b/share/extensions/Poly3DObjects/jessens_orthog_icos.obj @@ -0,0 +1,35 @@ +#Name:Jessen's Orthogonal Icosahedron +#Type:face_specified +v 0. -0.809017 0.5 +v 0. -0.809017 -0.5 +v 0. 0.809017 0.5 +v 0. 0.809017 -0.5 +v 0.5 0. -0.809017 +v 0.5 0. 0.809017 +v -0.5 0. -0.809017 +v -0.5 0. 0.809017 +v -0.809017 0.5 0. +v -0.809017 -0.5 0. +v 0.809017 0.5 0. +v 0.809017 -0.5 0. + +f 3 1 6 +f 6 1 12 +f 6 12 5 +f 11 3 6 +f 6 5 11 +f 12 1 10 +f 12 10 2 +f 5 12 2 +f 3 11 9 +f 1 3 8 +f 8 10 1 +f 7 2 10 +f 10 8 7 +f 3 9 8 +f 7 8 9 +f 5 2 4 +f 2 7 4 +f 7 9 4 +f 4 9 11 +f 5 4 11 \ No newline at end of file diff --git a/share/extensions/Poly3DObjects/methane.obj b/share/extensions/Poly3DObjects/methane.obj new file mode 100644 index 0000000..0b2d744 --- /dev/null +++ b/share/extensions/Poly3DObjects/methane.obj @@ -0,0 +1,13 @@ +#Name:Methane Molecule +#Type:edge_specified + +v 0 0 0 +v 0 0 0.61237244 +v -0.28867513 -0.50000000 -0.20412415 +v -0.28867513 0.50000000 -0.20412415 +v 0.57735027 0 -0.20412415 + +l 1 2 +l 1 3 +l 1 4 +l 1 5 diff --git a/share/extensions/Poly3DObjects/oct.obj b/share/extensions/Poly3DObjects/oct.obj new file mode 100644 index 0000000..f356b69 --- /dev/null +++ b/share/extensions/Poly3DObjects/oct.obj @@ -0,0 +1,17 @@ +#Name:Octahedron +#Type:face_specified +v -0.5 -0.5 0 +v -0.5 0.5 0 +v 0 0 -0.70710678 +v 0 0 0.70710678 +v 0.5 -0.5 0 +v 0.5 0.5 0 + +f 4 5 6 +f 4 6 2 +f 4 2 1 +f 4 1 5 +f 5 1 3 +f 5 3 6 +f 3 1 2 +f 6 3 2 diff --git a/share/extensions/Poly3DObjects/rh_axes.obj b/share/extensions/Poly3DObjects/rh_axes.obj new file mode 100644 index 0000000..cc6623f --- /dev/null +++ b/share/extensions/Poly3DObjects/rh_axes.obj @@ -0,0 +1,12 @@ +#Name:Right Handed Coordinate Axes +#Type:Edge_specified + +v 0 0 0 +v 1 0 0 +v 0 1 0 +v 0 0 1 + +l 1 2 +l 1 3 +l 1 4 + diff --git a/share/extensions/Poly3DObjects/rhomb_dodec.obj b/share/extensions/Poly3DObjects/rhomb_dodec.obj new file mode 100644 index 0000000..9fde9d6 --- /dev/null +++ b/share/extensions/Poly3DObjects/rhomb_dodec.obj @@ -0,0 +1,29 @@ +#Name:Rhombic Dodecahedron +#Type:face_specified +v -0.816497 -0.816497 0. +v -0.816497 0. -0.57735 +v -0.816497 0. 0.57735 +v -0.816497 0.816497 0. +v 0. -0.816497 -0.57735 +v 0. -0.816497 0.57735 +v 0. 0. -1.1547 +v 0. 0. 1.1547 +v 0. 0.816497 -0.57735 +v 0. 0.816497 0.57735 +v 0.816497 -0.816497 0. +v 0.816497 0. -0.57735 +v 0.816497 0. 0.57735 +v 0.816497 0.816497 0. + +f 2 1 3 4 +f 1 2 7 5 +f 6 8 3 1 +f 2 4 9 7 +f 8 10 4 3 +f 11 6 1 5 +f 9 4 10 14 +f 5 7 12 11 +f 11 13 8 6 +f 7 9 14 12 +f 13 14 10 8 +f 14 13 11 12 \ No newline at end of file diff --git a/share/extensions/Poly3DObjects/rhomb_triacont.obj b/share/extensions/Poly3DObjects/rhomb_triacont.obj new file mode 100644 index 0000000..70acebf --- /dev/null +++ b/share/extensions/Poly3DObjects/rhomb_triacont.obj @@ -0,0 +1,65 @@ +#Name:Rhombic Triacontahedron +#Type:face_specified +v 0. 0. -1.61803 +v 0. 0. 1.61803 +v 0.276393 -0.850651 1.17082 +v 0.276393 0.850651 1.17082 +v 0.894427 0. 1.17082 +v 1.17082 -0.850651 0.723607 +v 1.17082 -0.850651 -0.276393 +v 1.17082 0.850651 0.723607 +v 1.17082 0.850651 -0.276393 +v -0.894427 0. -1.17082 +v -0.447214 -1.37638 0.723607 +v -0.447214 -1.37638 -0.276393 +v -0.447214 1.37638 0.723607 +v -0.447214 1.37638 -0.276393 +v 0.447214 -1.37638 0.276393 +v 0.447214 -1.37638 -0.723607 +v 0.447214 1.37638 0.276393 +v 0.447214 1.37638 -0.723607 +v -1.44721 0. 0.723607 +v -1.44721 0. -0.276393 +v -0.723607 -0.525731 1.17082 +v -0.723607 0.525731 1.17082 +v 0.723607 -0.525731 -1.17082 +v 0.723607 0.525731 -1.17082 +v 1.44721 0. 0.276393 +v 1.44721 0. -0.723607 +v -1.17082 -0.850651 0.276393 +v -1.17082 -0.850651 -0.723607 +v -1.17082 0.850651 0.276393 +v -1.17082 0.850651 -0.723607 +v -0.276393 -0.850651 -1.17082 +v -0.276393 0.850651 -1.17082 + +f 16 15 11 12 +f 14 13 17 18 +f 10 28 20 30 +f 8 5 6 25 +f 12 28 31 16 +f 32 30 14 18 +f 6 3 11 15 +f 8 17 13 4 +f 11 21 19 27 +f 13 29 19 22 +f 7 16 23 26 +f 24 18 9 26 +f 12 11 27 28 +f 30 29 13 14 +f 7 6 15 16 +f 18 17 8 9 +f 2 22 19 21 +f 23 1 24 26 +f 3 2 21 11 +f 4 13 22 2 +f 16 31 1 23 +f 1 32 18 24 +f 31 28 10 1 +f 10 30 32 1 +f 6 5 2 3 +f 8 4 2 5 +f 28 27 19 20 +f 20 19 29 30 +f 26 25 6 7 +f 9 8 25 26 diff --git a/share/extensions/Poly3DObjects/small_rhombicosidodec.obj b/share/extensions/Poly3DObjects/small_rhombicosidodec.obj new file mode 100644 index 0000000..a209ba2 --- /dev/null +++ b/share/extensions/Poly3DObjects/small_rhombicosidodec.obj @@ -0,0 +1,127 @@ +#Name:Small Rhombicosidodecahedron +#Type:face_specified + +v -0.5 -0.5 -2.11803 +v -0.5 -0.5 2.11803 +v -0.5 0.5 -2.11803 +v -0.5 0.5 2.11803 +v -0.5 -2.11803 -0.5 +v -0.5 -2.11803 0.5 +v -0.5 2.11803 -0.5 +v -0.5 2.11803 0.5 +v 0. -1.30902 -1.80902 +v 0. -1.30902 1.80902 +v 0. 1.30902 -1.80902 +v 0. 1.30902 1.80902 +v 0.5 -0.5 -2.11803 +v 0.5 -0.5 2.11803 +v 0.5 0.5 -2.11803 +v 0.5 0.5 2.11803 +v 0.5 -2.11803 -0.5 +v 0.5 -2.11803 0.5 +v 0.5 2.11803 -0.5 +v 0.5 2.11803 0.5 +v -1.80902 0. -1.30902 +v -1.80902 0. 1.30902 +v -0.809017 -1.61803 -1.30902 +v -0.809017 -1.61803 1.30902 +v -0.809017 1.61803 -1.30902 +v -0.809017 1.61803 1.30902 +v -1.61803 -1.30902 -0.809017 +v -1.61803 -1.30902 0.809017 +v -1.61803 1.30902 -0.809017 +v -1.61803 1.30902 0.809017 +v -2.11803 -0.5 -0.5 +v -2.11803 -0.5 0.5 +v -2.11803 0.5 -0.5 +v -2.11803 0.5 0.5 +v -1.30902 -1.80902 0. +v -1.30902 -0.809017 -1.61803 +v -1.30902 -0.809017 1.61803 +v -1.30902 0.809017 -1.61803 +v -1.30902 0.809017 1.61803 +v -1.30902 1.80902 0. +v 0.809017 -1.61803 -1.30902 +v 0.809017 -1.61803 1.30902 +v 0.809017 1.61803 -1.30902 +v 0.809017 1.61803 1.30902 +v 1.61803 -1.30902 -0.809017 +v 1.61803 -1.30902 0.809017 +v 1.61803 1.30902 -0.809017 +v 1.61803 1.30902 0.809017 +v 2.11803 -0.5 -0.5 +v 2.11803 -0.5 0.5 +v 2.11803 0.5 -0.5 +v 2.11803 0.5 0.5 +v 1.30902 -1.80902 0. +v 1.30902 -0.809017 -1.61803 +v 1.30902 -0.809017 1.61803 +v 1.30902 0.809017 -1.61803 +v 1.30902 0.809017 1.61803 +v 1.30902 1.80902 0. +v 1.80902 0. -1.30902 +v 1.80902 0. 1.30902 + +f 36 23 27 +f 37 28 24 +f 40 8 7 +f 35 5 6 +f 38 29 25 +f 39 26 30 +f 10 14 2 +f 9 1 13 +f 12 4 16 +f 11 15 3 +f 54 45 41 +f 55 42 46 +f 58 19 20 +f 53 18 17 +f 56 43 47 +f 57 48 44 +f 34 32 22 +f 33 21 31 +f 59 51 49 +f 60 50 52 +f 27 31 21 36 +f 23 36 1 9 +f 10 2 37 24 +f 37 22 32 28 +f 8 40 30 26 +f 25 29 40 7 +f 35 27 23 5 +f 6 24 28 35 +f 3 38 25 11 +f 21 33 29 38 +f 39 30 34 22 +f 12 26 39 4 +f 55 14 10 42 +f 41 9 13 54 +f 57 44 12 16 +f 15 11 43 56 +f 45 54 59 49 +f 50 60 55 46 +f 48 58 20 44 +f 43 19 58 47 +f 53 17 41 45 +f 46 42 18 53 +f 59 56 47 51 +f 52 48 57 60 +f 31 32 34 33 +f 17 18 6 5 +f 1 3 15 13 +f 14 16 4 2 +f 7 8 20 19 +f 51 52 50 49 +f 3 1 36 21 38 +f 22 37 2 4 39 +f 29 33 34 30 40 +f 27 35 28 32 31 +f 42 10 24 6 18 +f 41 17 5 23 9 +f 20 8 26 12 44 +f 11 25 7 19 43 +f 56 59 54 13 15 +f 57 16 14 55 60 +f 58 48 52 51 47 +f 49 50 46 53 45 + diff --git a/share/extensions/Poly3DObjects/small_rhombicuboct.obj b/share/extensions/Poly3DObjects/small_rhombicuboct.obj new file mode 100644 index 0000000..2d064ab --- /dev/null +++ b/share/extensions/Poly3DObjects/small_rhombicuboct.obj @@ -0,0 +1,54 @@ +#Name:Small Rhombicuboctahedron +#Type:face_specified + +v -0.5 -0.5 -1.20711 +v -0.5 -0.5 1.20711 +v -0.5 0.5 -1.20711 +v -0.5 0.5 1.20711 +v -0.5 -1.20711 -0.5 +v -0.5 -1.20711 0.5 +v -0.5 1.20711 -0.5 +v -0.5 1.20711 0.5 +v 0.5 -0.5 -1.20711 +v 0.5 -0.5 1.20711 +v 0.5 0.5 -1.20711 +v 0.5 0.5 1.20711 +v 0.5 -1.20711 -0.5 +v 0.5 -1.20711 0.5 +v 0.5 1.20711 -0.5 +v 0.5 1.20711 0.5 +v -1.20711 -0.5 -0.5 +v -1.20711 -0.5 0.5 +v -1.20711 0.5 -0.5 +v -1.20711 0.5 0.5 +v 1.20711 -0.5 -0.5 +v 1.20711 -0.5 0.5 +v 1.20711 0.5 -0.5 +v 1.20711 0.5 0.5 + +f 3 11 9 1 +f 2 10 12 4 +f 24 22 21 23 +f 19 17 18 20 +f 5 13 14 6 +f 8 16 15 7 +f 13 21 22 14 +f 16 24 23 15 +f 6 18 17 5 +f 7 19 20 8 +f 6 14 10 2 +f 4 12 16 8 +f 22 24 12 10 +f 2 4 20 18 +f 1 9 13 5 +f 7 15 11 3 +f 9 11 23 21 +f 17 19 3 1 +f 22 10 14 +f 16 12 24 +f 6 2 18 +f 20 4 8 +f 13 9 21 +f 23 11 15 +f 17 1 5 +f 7 3 19 diff --git a/share/extensions/Poly3DObjects/small_triam_icos.obj b/share/extensions/Poly3DObjects/small_triam_icos.obj new file mode 100644 index 0000000..1d366ff --- /dev/null +++ b/share/extensions/Poly3DObjects/small_triam_icos.obj @@ -0,0 +1,95 @@ +#Name:Small Triambic Icosahedron +#Type:face_specified +v 0. 0. -0.951057 +v 0. 0. 0.951057 +v 0.262866 -0.809017 0.425325 +v 0.262866 0.809017 0.425325 +v 0.688191 -0.5 -0.425325 +v 0.688191 0.5 -0.425325 +v 0.995959 0. -0.190211 +v -0.688191 -0.5 0.425325 +v -0.688191 0.5 0.425325 +v -0.49798 -0.361803 -0.805748 +v -0.49798 0.361803 -0.805748 +v 0.49798 -0.361803 0.805748 +v 0.49798 0.361803 0.805748 +v 0.190211 -0.58541 -0.805748 +v 0.190211 0.58541 -0.805748 +v 0.850651 0. 0.425325 +v -0.190211 -0.58541 0.805748 +v -0.190211 0.58541 0.805748 +v -0.615537 0. 0.805748 +v -0.307768 -0.947214 0.190211 +v -0.307768 0.947214 0.190211 +v 0.307768 -0.947214 -0.190211 +v 0.307768 0.947214 -0.190211 +v 0.615537 0. -0.805748 +v 0.805748 -0.58541 0.190211 +v 0.805748 0.58541 0.190211 +v -0.850651 0. -0.425325 +v -0.262866 -0.809017 -0.425325 +v -0.262866 0.809017 -0.425325 +v -0.995959 0. 0.190211 +v -0.805748 -0.58541 -0.190211 +v -0.805748 0.58541 -0.190211 + +f 18 2 4 +f 18 4 9 +f 18 9 2 +f 19 2 9 +f 19 9 8 +f 19 8 2 +f 17 2 8 +f 17 8 3 +f 17 3 2 +f 12 2 3 +f 12 3 16 +f 12 16 2 +f 13 2 16 +f 13 16 4 +f 13 4 2 +f 14 5 28 +f 14 28 1 +f 14 1 5 +f 24 6 5 +f 24 5 1 +f 24 1 6 +f 15 29 6 +f 15 6 1 +f 15 1 29 +f 11 27 29 +f 11 29 1 +f 11 1 27 +f 10 28 27 +f 10 27 1 +f 10 1 28 +f 21 4 29 +f 21 29 9 +f 21 9 4 +f 30 9 27 +f 30 27 8 +f 30 8 9 +f 20 8 28 +f 20 28 3 +f 20 3 8 +f 25 3 5 +f 25 5 16 +f 25 16 3 +f 26 16 6 +f 26 6 4 +f 26 4 16 +f 22 5 3 +f 22 3 28 +f 22 28 5 +f 7 6 16 +f 7 16 5 +f 7 5 6 +f 23 29 4 +f 23 4 6 +f 23 6 29 +f 32 27 9 +f 32 9 29 +f 32 29 27 +f 31 28 8 +f 31 8 27 +f 31 27 28 diff --git a/share/extensions/Poly3DObjects/snub_cube.obj b/share/extensions/Poly3DObjects/snub_cube.obj new file mode 100644 index 0000000..de018af --- /dev/null +++ b/share/extensions/Poly3DObjects/snub_cube.obj @@ -0,0 +1,65 @@ +#Name:Snub Cube +#Type:face_specified +v -1.1426135 -0.33775397 -0.62122641 +v -1.1426135 0.33775397 0.62122641 +v -1.1426135 -0.62122641 0.33775397 +v -1.1426135 0.62122641 -0.33775397 +v 1.1426135 -0.33775397 0.62122641 +v 1.1426135 0.33775397 -0.62122641 +v 1.1426135 -0.62122641 -0.33775397 +v 1.1426135 0.62122641 0.33775397 +v -0.33775397 -1.1426135 0.62122641 +v -0.33775397 1.1426135 -0.62122641 +v -0.33775397 -0.62122641 -1.1426135 +v -0.33775397 0.62122641 1.1426135 +v 0.33775397 -1.1426135 -0.62122641 +v 0.33775397 1.1426135 0.62122641 +v 0.33775397 -0.62122641 1.1426135 +v 0.33775397 0.62122641 -1.1426135 +v -0.62122641 -1.1426135 -0.33775397 +v -0.62122641 1.1426135 0.33775397 +v -0.62122641 -0.33775397 1.1426135 +v -0.62122641 0.33775397 -1.1426135 +v 0.62122641 -1.1426135 0.33775397 +v 0.62122641 1.1426135 -0.33775397 +v 0.62122641 -0.33775397 -1.1426135 +v 0.62122641 0.33775397 1.1426135 + +f 3 1 17 +f 3 17 9 +f 3 19 2 +f 3 9 19 +f 1 4 20 +f 1 20 11 +f 1 11 17 +f 2 19 12 +f 2 18 4 +f 2 12 18 +f 4 18 10 +f 4 10 20 +f 17 11 13 +f 19 9 15 +f 18 12 14 +f 20 10 16 +f 9 21 15 +f 11 23 13 +f 12 24 14 +f 10 22 16 +f 13 23 7 +f 13 7 21 +f 15 21 5 +f 15 5 24 +f 16 22 6 +f 16 6 23 +f 14 24 8 +f 14 8 22 +f 21 7 5 +f 23 6 7 +f 24 5 8 +f 22 8 6 +f 1 3 2 4 +f 21 9 17 13 +f 24 12 19 15 +f 10 18 14 22 +f 11 20 16 23 +f 8 5 7 6 diff --git a/share/extensions/Poly3DObjects/snub_dodec.obj b/share/extensions/Poly3DObjects/snub_dodec.obj new file mode 100644 index 0000000..d830978 --- /dev/null +++ b/share/extensions/Poly3DObjects/snub_dodec.obj @@ -0,0 +1,156 @@ +#Name:Snub Dodecahedron +#Type:face_specified + +v -2.0502159 -0.64302961 0.17539263 +v 2.0502159 -0.64302961 -0.17539263 +v -1.6450691 0.64302961 1.2360806 +v 1.6450691 0.64302961 -1.2360806 +v -2.0927544 0.33092102 0.39812710 +v 2.0927544 0.33092102 -0.39812710 +v -1.3329632 1.6469179 -0.39812710 +v 1.3329632 1.6469179 0.39812710 +v -1.8252651 -0.33092102 1.0984232 +v 1.8252651 -0.33092102 -1.0984232 +v -0.62604653 1.7461864 -1.0984232 +v 0.62604653 1.7461864 1.0984232 +v -1.0622158 1.4540242 1.1853886 +v 1.0622158 1.4540242 -1.1853886 +v -1.9321359 0.84755005 -0.44288192 +v 1.9321359 0.84755005 0.44288192 +v -1.1448745 -0.84755005 1.6181953 +v 1.1448745 -0.84755005 -1.6181953 +v -1.5819879 -1.4540242 -0.17539263 +v 1.5819879 -1.4540242 0.17539263 +v -1.0574124 0.37482166 -1.8409298 +v 1.0574124 0.37482166 1.8409298 +v -0.43913786 -0.37482166 -2.0770897 +v 0.43913786 -0.37482166 2.0770897 +v -1.5624104 -1.2495038 0.80327387 +v 1.5624104 -1.2495038 -0.80327387 +v -1.8633072 -0.72833518 -0.80327387 +v 1.8633072 -0.72833518 0.80327387 +v -1.7000678 1.2495038 0.44288192 +v 1.7000678 1.2495038 -0.44288192 +v -0.72811404 -1.6469179 1.1853886 +v 0.72811404 -1.6469179 -1.1853886 +v -0.26565458 -1.7461864 -1.2360806 +v 0.26565458 -1.7461864 1.2360806 +v -0.75979117 -1.9778390 -0.39812710 +v 0.75979117 -1.9778390 0.39812710 +v -1.1992186 -1.4152654 -1.0984232 +v 1.1992186 -1.4152654 1.0984232 +v -1.7903298 0.19289371 -1.1853886 +v 1.7903298 0.19289371 1.1853886 +v -1.3064371 -0.56771537 -1.6181953 +v 1.3064371 -0.56771537 1.6181953 +v -0.85331128 0.72833518 1.8409298 +v 0.85331128 0.72833518 -1.8409298 +v -1.3794145 1.1031568 -1.2360806 +v 1.3794145 1.1031568 1.2360806 +v -0.10503615 0.56771537 -2.0770897 +v 0.10503615 0.56771537 2.0770897 +v -0.46822796 2.0970538 -0.17539263 +v 0.46822796 2.0970538 0.17539263 +v -0.30089684 1.9778390 0.80327387 +v 0.30089684 1.9778390 -0.80327387 +v -0.16156263 1.4152654 1.6181953 +v 0.16156263 1.4152654 -1.6181953 +v -0.54417401 -0.19289371 2.0770897 +v 0.54417401 -0.19289371 -2.0770897 +v -0.23206810 -2.0970538 0.44288192 +v 0.23206810 -2.0970538 -0.44288192 +v -0.20410113 -1.1031568 1.8409298 +v 0.20410113 -1.1031568 -1.8409298 + +f 5 1 9 +f 5 9 3 +f 5 29 15 +f 5 3 29 +f 1 27 19 +f 1 19 25 +f 1 25 9 +f 15 29 7 +f 15 45 39 +f 15 7 45 +f 27 39 41 +f 27 41 37 +f 27 37 19 +f 9 25 17 +f 39 45 21 +f 39 21 41 +f 29 3 13 +f 3 43 13 +f 19 37 35 +f 25 31 17 +f 45 7 11 +f 7 49 11 +f 41 21 23 +f 37 33 35 +f 17 31 59 +f 17 59 55 +f 13 43 53 +f 13 53 51 +f 21 47 23 +f 43 55 48 +f 43 48 53 +f 35 33 58 +f 35 58 57 +f 31 57 34 +f 31 34 59 +f 11 49 52 +f 11 52 54 +f 55 59 24 +f 55 24 48 +f 49 51 50 +f 49 50 52 +f 23 47 56 +f 23 56 60 +f 51 53 12 +f 51 12 50 +f 33 60 32 +f 33 32 58 +f 57 58 36 +f 57 36 34 +f 47 54 44 +f 47 44 56 +f 48 24 22 +f 54 52 14 +f 54 14 44 +f 60 56 18 +f 60 18 32 +f 34 36 38 +f 24 42 22 +f 50 12 8 +f 12 46 8 +f 32 18 26 +f 36 20 38 +f 44 14 4 +f 22 42 40 +f 22 40 46 +f 14 30 4 +f 18 10 26 +f 38 20 28 +f 38 28 42 +f 42 28 40 +f 8 46 16 +f 8 16 30 +f 46 40 16 +f 26 10 2 +f 26 2 20 +f 20 2 28 +f 4 30 6 +f 4 6 10 +f 30 16 6 +f 10 6 2 +f 39 27 1 5 15 +f 3 9 17 55 43 +f 51 49 7 29 13 +f 57 31 25 19 35 +f 47 21 45 11 54 +f 33 37 41 23 60 +f 42 24 59 34 38 +f 46 12 53 48 22 +f 36 58 32 26 20 +f 14 52 50 8 30 +f 44 4 10 18 56 +f 16 40 28 2 6 diff --git a/share/extensions/Poly3DObjects/szilassi.obj b/share/extensions/Poly3DObjects/szilassi.obj new file mode 100644 index 0000000..9dbce05 --- /dev/null +++ b/share/extensions/Poly3DObjects/szilassi.obj @@ -0,0 +1,24 @@ +#Face:Szilassi Polyhedron +#Type:face_specified +v -4.8 0. 4.8 +v -2.8 -1. 0.8 +v -2.8 0. 0.8 +v -1.8 1. 0.8 +v -1.5 -1.5 -1.2 +v -0.8 2. -3.2 +v 0. -5.04 -4.8 +v 0. 5.04 -4.8 +v 0.8 -2. -3.2 +v 1.5 1.5 -1.2 +v 1.8 -1. 0.8 +v 2.8 0. 0.8 +v 2.8 1. 0.8 +v 4.8 0. 4.8 + +f 4 10 6 1 14 13 +f 3 2 1 6 8 7 +f 5 10 4 3 7 9 +f 10 5 11 12 8 6 +f 12 13 14 9 7 8 +f 11 5 9 14 1 2 +f 13 12 11 2 3 4 diff --git a/share/extensions/Poly3DObjects/tet.obj b/share/extensions/Poly3DObjects/tet.obj new file mode 100644 index 0000000..3bd8f0e --- /dev/null +++ b/share/extensions/Poly3DObjects/tet.obj @@ -0,0 +1,12 @@ +#Name:Tetrahedron +#Type:face_specified + +v 0 0 0.61237244 +v -0.28867513 -0.50000000 -0.20412415 +v -0.28867513 0.50000000 -0.20412415 +v 0.57735027 0 -0.20412415 + +f 2 3 4 +f 3 2 1 +f 4 1 2 +f 1 4 3 diff --git a/share/extensions/Poly3DObjects/trunc_cube.obj b/share/extensions/Poly3DObjects/trunc_cube.obj new file mode 100644 index 0000000..1dbcfa6 --- /dev/null +++ b/share/extensions/Poly3DObjects/trunc_cube.obj @@ -0,0 +1,42 @@ +#Name:Truncated Cube +#Type:face_specified + +v -0.5 1.2071068 1.2071068 +v -0.5 1.2071068 -1.2071068 +v -0.5 -1.2071068 1.2071068 +v -0.5 -1.2071068 -1.2071068 +v 0.5 1.2071068 1.2071068 +v 0.5 1.2071068 -1.2071068 +v 0.5 -1.2071068 1.2071068 +v 0.5 -1.2071068 -1.2071068 +v 1.2071068 -0.5 1.2071068 +v 1.2071068 -0.5 -1.2071068 +v 1.2071068 0.5 1.2071068 +v 1.2071068 0.5 -1.2071068 +v 1.2071068 1.2071068 -0.5 +v 1.2071068 1.2071068 0.5 +v 1.2071068 -1.2071068 -0.5 +v 1.2071068 -1.2071068 0.5 +v -1.2071068 -0.5 1.2071068 +v -1.2071068 -0.5 -1.2071068 +v -1.2071068 0.5 1.2071068 +v -1.2071068 0.5 -1.2071068 +v -1.2071068 1.2071068 -0.5 +v -1.2071068 1.2071068 0.5 +v -1.2071068 -1.2071068 -0.5 +v -1.2071068 -1.2071068 0.5 + +f 6 12 10 8 4 18 20 2 +f 1 19 17 3 7 9 11 5 +f 3 24 23 4 8 15 16 7 +f 5 14 13 6 2 21 22 1 +f 9 16 15 10 12 13 14 11 +f 19 22 21 20 18 23 24 17 +f 16 9 7 +f 5 11 14 +f 3 17 24 +f 22 19 1 +f 8 10 15 +f 13 12 6 +f 23 18 4 +f 2 20 21 diff --git a/share/extensions/Poly3DObjects/trunc_dodec.obj b/share/extensions/Poly3DObjects/trunc_dodec.obj new file mode 100644 index 0000000..9a5743a --- /dev/null +++ b/share/extensions/Poly3DObjects/trunc_dodec.obj @@ -0,0 +1,96 @@ +#Name:Truncated Dodecahedron +#Type:face_specified + +v 0 -1.6180340 2.4898983 +v 0 -1.6180340 -2.4898983 +v 0 1.6180340 2.4898983 +v 0 1.6180340 -2.4898983 +v 0.42532540 -2.9270510 0.26286556 +v 0.42532540 2.9270510 0.26286556 +v 0.68819096 -2.1180340 1.9641672 +v 0.68819096 2.1180340 1.9641672 +v -2.7527638 0 -1.1135164 +v -2.0645729 -2.1180340 0.26286556 +v -2.0645729 2.1180340 0.26286556 +v -1.3763819 -2.6180340 -0.26286556 +v -1.3763819 2.6180340 -0.26286556 +v -0.68819096 -2.1180340 -1.9641672 +v -0.68819096 2.1180340 -1.9641672 +v 1.3763819 -2.6180340 0.26286556 +v 1.3763819 2.6180340 0.26286556 +v 2.7527638 0 1.1135164 +v 1.8017073 -1.3090170 -1.9641672 +v 1.8017073 1.3090170 -1.9641672 +v 2.0645729 -2.1180340 -0.26286556 +v 2.0645729 2.1180340 -0.26286556 +v 2.2270327 0 1.9641672 +v 2.2270327 -1.6180340 -1.1135164 +v 2.2270327 1.6180340 -1.1135164 +v -2.6523581 -1.3090170 0.26286556 +v -2.6523581 1.3090170 0.26286556 +v 2.6523581 -1.3090170 -0.26286556 +v 2.6523581 1.3090170 -0.26286556 +v 2.9152237 -0.5 0.26286556 +v 2.9152237 0.5 0.26286556 +v -2.9152237 -0.5 -0.26286556 +v -2.9152237 0.5 -0.26286556 +v 0.95105652 -1.3090170 2.4898983 +v 0.95105652 -1.3090170 -2.4898983 +v 0.95105652 1.3090170 2.4898983 +v 0.95105652 1.3090170 -2.4898983 +v 0.85065081 -2.6180340 1.1135164 +v 0.85065081 2.6180340 1.1135164 +v -0.95105652 -1.3090170 2.4898983 +v -0.95105652 -1.3090170 -2.4898983 +v -0.95105652 1.3090170 2.4898983 +v -0.95105652 1.3090170 -2.4898983 +v -1.5388418 -0.5 2.4898983 +v -1.5388418 -0.5 -2.4898983 +v -1.5388418 0.5 2.4898983 +v -1.5388418 0.5 -2.4898983 +v 1.5388418 -0.5 2.4898983 +v 1.5388418 -0.5 -2.4898983 +v 1.5388418 0.5 2.4898983 +v 1.5388418 0.5 -2.4898983 +v -2.2270327 0 -1.9641672 +v -2.2270327 -1.6180340 1.1135164 +v -2.2270327 1.6180340 1.1135164 +v -0.85065081 -2.6180340 -1.1135164 +v -0.85065081 2.6180340 -1.1135164 +v -1.8017073 -1.3090170 1.9641672 +v -1.8017073 1.3090170 1.9641672 +v -0.42532540 -2.9270510 -0.26286556 +v -0.42532540 2.9270510 -0.26286556 + +f 3 42 46 44 40 1 34 48 50 36 +f 47 43 4 37 51 49 35 2 41 45 +f 2 35 19 24 21 16 5 59 55 14 +f 49 51 20 25 29 31 30 28 24 19 +f 37 4 15 56 60 6 17 22 25 20 +f 43 47 52 9 33 27 11 13 56 15 +f 45 41 14 55 12 10 26 32 9 52 +f 6 60 13 11 54 58 42 3 8 39 +f 27 33 32 26 53 57 44 46 58 54 +f 10 12 59 5 38 7 1 40 57 53 +f 16 21 28 30 18 23 48 34 7 38 +f 31 29 22 17 39 8 36 50 23 18 +f 9 32 33 +f 18 30 31 +f 47 45 52 +f 50 48 23 +f 10 53 26 +f 27 54 11 +f 21 24 28 +f 29 25 22 +f 40 44 57 +f 58 46 42 +f 35 49 19 +f 20 51 37 +f 12 55 59 +f 60 56 13 +f 41 2 14 +f 15 4 43 +f 34 1 7 +f 8 3 36 +f 38 5 16 +f 17 6 39 diff --git a/share/extensions/Poly3DObjects/trunc_icos.obj b/share/extensions/Poly3DObjects/trunc_icos.obj new file mode 100644 index 0000000..4b535ba --- /dev/null +++ b/share/extensions/Poly3DObjects/trunc_icos.obj @@ -0,0 +1,96 @@ +#Name:Truncated Icosahedron +#Type:Face_specified + +v -0.16245985 -2.1180340 1.2759762 +v -0.16245985 2.1180340 1.2759762 +v 0.16245985 -2.1180340 -1.2759762 +v 0.16245985 2.1180340 -1.2759762 +v -0.26286556 -0.80901699 -2.3274384 +v -0.26286556 -2.4270510 -0.42532540 +v -0.26286556 0.80901699 -2.3274384 +v -0.26286556 2.4270510 -0.42532540 +v 0.26286556 -0.80901699 2.3274384 +v 0.26286556 -2.4270510 0.42532540 +v 0.26286556 0.80901699 2.3274384 +v 0.26286556 2.4270510 0.42532540 +v 0.68819096 -0.5 -2.3274384 +v 0.68819096 0.5 -2.3274384 +v 1.2139221 -2.1180340 0.42532540 +v 1.2139221 2.1180340 0.42532540 +v -2.0645729 -0.5 1.2759762 +v -2.0645729 0.5 1.2759762 +v -1.3763819 -1.0 1.8017073 +v -1.3763819 1.0 1.8017073 +v -1.3763819 -1.6180340 -1.2759762 +v -1.3763819 1.6180340 -1.2759762 +v -0.68819096 -0.5 2.3274384 +v -0.68819096 0.5 2.3274384 +v 1.3763819 -1.0 -1.8017073 +v 1.3763819 1.0 -1.8017073 +v 1.3763819 -1.6180340 1.2759762 +v 1.3763819 1.6180340 1.2759762 +v -1.7013016 0 -1.8017073 +v 1.7013016 0 1.8017073 +v -1.2139221 -2.1180340 -0.42532540 +v -1.2139221 2.1180340 -0.42532540 +v -1.9641672 -0.80901699 -1.2759762 +v -1.9641672 0.80901699 -1.2759762 +v 2.0645729 -0.5 -1.2759762 +v 2.0645729 0.5 -1.2759762 +v 2.2270327 -1.0 -0.42532540 +v 2.2270327 1.0 -0.42532540 +v 2.3894926 -0.5 0.42532540 +v 2.3894926 0.5 0.42532540 +v -1.1135164 -1.8090170 1.2759762 +v -1.1135164 1.8090170 1.2759762 +v 1.1135164 -1.8090170 -1.2759762 +v 1.1135164 1.8090170 -1.2759762 +v -2.3894926 -0.5 -0.42532540 +v -2.3894926 0.5 -0.42532540 +v -1.6392475 -1.8090170 0.42532540 +v -1.6392475 1.8090170 0.42532540 +v 1.6392475 -1.8090170 -0.42532540 +v 1.6392475 1.8090170 -0.42532540 +v 1.9641672 -0.80901699 1.2759762 +v 1.9641672 0.80901699 1.2759762 +v 0.85065081 0 2.3274384 +v -2.2270327 -1.0 0.42532540 +v -2.2270327 1.0 0.42532540 +v -0.85065081 0 -2.3274384 +v -0.52573111 -1.6180340 -1.8017073 +v -0.52573111 1.6180340 -1.8017073 +v 0.52573111 -1.6180340 1.8017073 +v 0.52573111 1.6180340 1.8017073 + +f 53 11 24 23 9 +f 51 39 40 52 30 +f 60 28 16 12 2 +f 20 42 48 55 18 +f 19 17 54 47 41 +f 1 10 15 27 59 +f 36 26 44 50 38 +f 4 58 22 32 8 +f 34 29 33 45 46 +f 21 57 3 6 31 +f 37 49 43 25 35 +f 13 5 56 7 14 +f 9 59 27 51 30 53 +f 53 30 52 28 60 11 +f 11 60 2 42 20 24 +f 24 20 18 17 19 23 +f 23 19 41 1 59 9 +f 13 25 43 3 57 5 +f 5 57 21 33 29 56 +f 56 29 34 22 58 7 +f 7 58 4 44 26 14 +f 14 26 36 35 25 13 +f 40 38 50 16 28 52 +f 16 50 44 4 8 12 +f 12 8 32 48 42 2 +f 48 32 22 34 46 55 +f 55 46 45 54 17 18 +f 54 45 33 21 31 47 +f 47 31 6 10 1 41 +f 10 6 3 43 49 15 +f 15 49 37 39 51 27 +f 39 37 35 36 38 40 diff --git a/share/extensions/Poly3DObjects/trunc_oct.obj b/share/extensions/Poly3DObjects/trunc_oct.obj new file mode 100644 index 0000000..0c7c8ad --- /dev/null +++ b/share/extensions/Poly3DObjects/trunc_oct.obj @@ -0,0 +1,42 @@ +#Name:Truncated Octahedron +#Type:face_specified + +v -1.5 -0.5 0 +v -1.5 0.5 0 +v -1. -1. -0.70710678 +v -1. -1. 0.70710678 +v -1. 1. -0.70710678 +v -1. 1. 0.70710678 +v -0.5 -1.5 0 +v -0.5 -0.5 -1.4142136 +v -0.5 -0.5 1.4142136 +v -0.5 0.5 -1.4142136 +v -0.5 0.5 1.4142136 +v -0.5 1.5 0 +v 0.5 -1.5 0 +v 0.5 -0.5 -1.4142136 +v 0.5 -0.5 1.4142136 +v 0.5 0.5 -1.4142136 +v 0.5 0.5 1.4142136 +v 0.5 1.5 0 +v 1. -1. -0.70710678 +v 1. -1. 0.70710678 +v 1. 1. -0.70710678 +v 1. 1. 0.70710678 +v 1.5 -0.5 0 +v 1.5 0.5 0 + +f 17 11 9 15 +f 14 8 10 16 +f 22 24 21 18 +f 12 5 2 6 +f 13 19 23 20 +f 4 1 3 7 +f 19 13 7 3 8 14 +f 15 9 4 7 13 20 +f 16 10 5 12 18 21 +f 22 18 12 6 11 17 +f 20 23 24 22 17 15 +f 14 16 21 24 23 19 +f 9 11 6 2 1 4 +f 3 1 2 5 10 8 \ No newline at end of file diff --git a/share/extensions/Poly3DObjects/trunc_tet.obj b/share/extensions/Poly3DObjects/trunc_tet.obj new file mode 100644 index 0000000..d2c9ceb --- /dev/null +++ b/share/extensions/Poly3DObjects/trunc_tet.obj @@ -0,0 +1,24 @@ +#Name:Truncated Tetrahedron +#Type:face_specified + +v 0 -1. -0.61237244 +v 0 1. -0.61237244 +v -0.57735027 -1. 0.20412415 +v -0.57735027 1. 0.20412415 +v -0.28867513 -0.5 1.0206207 +v -0.28867513 0.5 1.0206207 +v 0.57735027 0 1.0206207 +v 1.1547005 0 0.20412415 +v -0.86602540 -0.5 -0.61237244 +v -0.86602540 0.5 -0.61237244 +v 0.86602540 -0.5 -0.61237244 +v 0.86602540 0.5 -0.61237244 + +f 11 12 8 +f 3 9 1 +f 2 10 4 +f 6 5 7 +f 11 8 7 5 3 1 +f 2 4 6 7 8 12 +f 9 3 5 6 4 10 +f 2 12 11 1 9 10 \ No newline at end of file diff --git a/share/extensions/README.md b/share/extensions/README.md new file mode 100644 index 0000000..4f7c6b1 --- /dev/null +++ b/share/extensions/README.md @@ -0,0 +1,79 @@ +# Inkscape Extensions + +This folder contains the stock Inkscape extensions, i.e. the scripts that +implement some commands that you can use from within Inkscape. Most of +these commands are in the Extensions menu. + +## Installation + +These scripts should be installed with an Inkscape package already (if you have +installed Inkscape). For packagers or people testing newer releases, you can +install the files into /usr/share/inkscape/extensions or +~/.config/inkscape/extensions . + +## Testing + +These extensions are designed to have good test coverage as well as python 2.7 +and python 3.6 support. +Testing can be run using the setup.py command or pytest directly: + + ./setup.py test + python2 -m pytest + python3 -m pytest + +The latest coverage report for master branch can be found at +https://inkscape.gitlab.io/extensions/coverage/. + +## Testing Options + +Tests can be run with these options that are provided as environment variables: + + FAIL_ON_DEPRECATION=1 - Will instantly fail any use of deprecated APIs + EXPORT_COMPARE=1 - Generate output files from comparisions. This is useful for manually checking the output as well as updating the comparison data. + NO_MOCK_COMMANDS=1 - Instead of using the mock data, actually call commands. This will also generate the msg files similar to export compare. + INKSCAPE_COMMAND=/other/inkscape - Use a different Inkscape (for example development version) while running commands. Works outside of tests too. + XML_DIFF=1 - Attempt to output an XML diff file, this can be useful for debugging to see differences in context. + DEBUG_KEY=1 - Export mock file keys for debugging. This is a highly specialised option for debuging key generation. + +## Extension description + +Each *.inx file describes an extension, listing its name, purpose, +prerequisites, location within the menu, etc. These files are read by +Inkscape on launch. Other files are the scripts themselves (Perl, +Python, and Ruby are supported, as well as shell scripts). + +## Development + +Development of both the core inkex modules, tests and each of the extensions +contained within the core inkscape extensions repository should follow these +basic rules of quality assurance: + + * Use pylint to ensure code is written consistantly + * Have tests so that each line of an extension is covered in the coverage report + * Not cross streams between extensions, so your extension should import from + a module and not from another extension. + * Use translations on text for display to users using get text. + * Should not require external programs to work (with some exceptions) + +Also join the community on chat.inkscape.org channel #inkscape_extensions with any +doubts or problems. + +## Building Docs + +You may wish to compile to docs for use outside of the Inkscape docs, this can +be done with these commands: + + sphinx-apidoc -F -o source inkex + ./setup.py build_sphinx -s source + firefox ./build/sphinx/html/inkex.html + +All documentation should be included INSIDE of each python module. + +The latest documentation for master branch can be found at +https://inkscape.gitlab.io/extensions/documentation/. + +## License Requirements + +Only include extensions here which are GPL-compatible. This includes +Apache-2, MPL 1.1, certain Creative Commons licenses, and more. See +https://www.gnu.org/licenses/license-list.html for guidance. diff --git a/share/extensions/STYLEGUIDE.md b/share/extensions/STYLEGUIDE.md new file mode 100644 index 0000000..f3bd501 --- /dev/null +++ b/share/extensions/STYLEGUIDE.md @@ -0,0 +1,15 @@ +1. Follow PEP8 +2. For Python 2 specific code use `if sys.version_info[0] == 2:` and include a Python3 alternative in the else clause. + + + For example: + +```python +if sys.version_info[0] == 2: + import urllib + import urlparse +else: + import urllib.request as urllib + import urllib.parse as urlparse +``` + diff --git a/share/extensions/__pycache__/addnodes.cpython-39.pyc b/share/extensions/__pycache__/addnodes.cpython-39.pyc new file mode 100644 index 0000000..539600e Binary files /dev/null and b/share/extensions/__pycache__/addnodes.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_HSL_adjust.cpython-39.pyc b/share/extensions/__pycache__/color_HSL_adjust.cpython-39.pyc new file mode 100644 index 0000000..5a52612 Binary files /dev/null and b/share/extensions/__pycache__/color_HSL_adjust.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_blackandwhite.cpython-39.pyc b/share/extensions/__pycache__/color_blackandwhite.cpython-39.pyc new file mode 100644 index 0000000..054d618 Binary files /dev/null and b/share/extensions/__pycache__/color_blackandwhite.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_brighter.cpython-39.pyc b/share/extensions/__pycache__/color_brighter.cpython-39.pyc new file mode 100644 index 0000000..56a31ac Binary files /dev/null and b/share/extensions/__pycache__/color_brighter.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_custom.cpython-39.pyc b/share/extensions/__pycache__/color_custom.cpython-39.pyc new file mode 100644 index 0000000..1b71e88 Binary files /dev/null and b/share/extensions/__pycache__/color_custom.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_darker.cpython-39.pyc b/share/extensions/__pycache__/color_darker.cpython-39.pyc new file mode 100644 index 0000000..bb7fa3f Binary files /dev/null and b/share/extensions/__pycache__/color_darker.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_desaturate.cpython-39.pyc b/share/extensions/__pycache__/color_desaturate.cpython-39.pyc new file mode 100644 index 0000000..25c1c67 Binary files /dev/null and b/share/extensions/__pycache__/color_desaturate.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_grayscale.cpython-39.pyc b/share/extensions/__pycache__/color_grayscale.cpython-39.pyc new file mode 100644 index 0000000..8ca351c Binary files /dev/null and b/share/extensions/__pycache__/color_grayscale.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_lesshue.cpython-39.pyc b/share/extensions/__pycache__/color_lesshue.cpython-39.pyc new file mode 100644 index 0000000..e62daf6 Binary files /dev/null and b/share/extensions/__pycache__/color_lesshue.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_lesslight.cpython-39.pyc b/share/extensions/__pycache__/color_lesslight.cpython-39.pyc new file mode 100644 index 0000000..16b0a76 Binary files /dev/null and b/share/extensions/__pycache__/color_lesslight.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_lesssaturation.cpython-39.pyc b/share/extensions/__pycache__/color_lesssaturation.cpython-39.pyc new file mode 100644 index 0000000..0c56d22 Binary files /dev/null and b/share/extensions/__pycache__/color_lesssaturation.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_list.cpython-39.pyc b/share/extensions/__pycache__/color_list.cpython-39.pyc new file mode 100644 index 0000000..dc5ffbf Binary files /dev/null and b/share/extensions/__pycache__/color_list.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_morehue.cpython-39.pyc b/share/extensions/__pycache__/color_morehue.cpython-39.pyc new file mode 100644 index 0000000..406cc99 Binary files /dev/null and b/share/extensions/__pycache__/color_morehue.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_morelight.cpython-39.pyc b/share/extensions/__pycache__/color_morelight.cpython-39.pyc new file mode 100644 index 0000000..8723273 Binary files /dev/null and b/share/extensions/__pycache__/color_morelight.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_moresaturation.cpython-39.pyc b/share/extensions/__pycache__/color_moresaturation.cpython-39.pyc new file mode 100644 index 0000000..cf4d402 Binary files /dev/null and b/share/extensions/__pycache__/color_moresaturation.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_negative.cpython-39.pyc b/share/extensions/__pycache__/color_negative.cpython-39.pyc new file mode 100644 index 0000000..d13649f Binary files /dev/null and b/share/extensions/__pycache__/color_negative.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_randomize.cpython-39.pyc b/share/extensions/__pycache__/color_randomize.cpython-39.pyc new file mode 100644 index 0000000..9664462 Binary files /dev/null and b/share/extensions/__pycache__/color_randomize.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_removeblue.cpython-39.pyc b/share/extensions/__pycache__/color_removeblue.cpython-39.pyc new file mode 100644 index 0000000..d210377 Binary files /dev/null and b/share/extensions/__pycache__/color_removeblue.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_removegreen.cpython-39.pyc b/share/extensions/__pycache__/color_removegreen.cpython-39.pyc new file mode 100644 index 0000000..074daeb Binary files /dev/null and b/share/extensions/__pycache__/color_removegreen.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_removered.cpython-39.pyc b/share/extensions/__pycache__/color_removered.cpython-39.pyc new file mode 100644 index 0000000..6ba2958 Binary files /dev/null and b/share/extensions/__pycache__/color_removered.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_replace.cpython-39.pyc b/share/extensions/__pycache__/color_replace.cpython-39.pyc new file mode 100644 index 0000000..8d9c919 Binary files /dev/null and b/share/extensions/__pycache__/color_replace.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/color_rgbbarrel.cpython-39.pyc b/share/extensions/__pycache__/color_rgbbarrel.cpython-39.pyc new file mode 100644 index 0000000..d5f5ca4 Binary files /dev/null and b/share/extensions/__pycache__/color_rgbbarrel.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/convert2dashes.cpython-39.pyc b/share/extensions/__pycache__/convert2dashes.cpython-39.pyc new file mode 100644 index 0000000..5d9616b Binary files /dev/null and b/share/extensions/__pycache__/convert2dashes.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/dhw_input.cpython-39.pyc b/share/extensions/__pycache__/dhw_input.cpython-39.pyc new file mode 100644 index 0000000..7560597 Binary files /dev/null and b/share/extensions/__pycache__/dhw_input.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/dimension.cpython-39.pyc b/share/extensions/__pycache__/dimension.cpython-39.pyc new file mode 100644 index 0000000..c56b3d6 Binary files /dev/null and b/share/extensions/__pycache__/dimension.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/docinfo.cpython-39.pyc b/share/extensions/__pycache__/docinfo.cpython-39.pyc new file mode 100644 index 0000000..5eb1f1e Binary files /dev/null and b/share/extensions/__pycache__/docinfo.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/dpiswitcher.cpython-39.pyc b/share/extensions/__pycache__/dpiswitcher.cpython-39.pyc new file mode 100644 index 0000000..1a8896a Binary files /dev/null and b/share/extensions/__pycache__/dpiswitcher.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/draw_from_triangle.cpython-39.pyc b/share/extensions/__pycache__/draw_from_triangle.cpython-39.pyc new file mode 100644 index 0000000..030c33f Binary files /dev/null and b/share/extensions/__pycache__/draw_from_triangle.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/dxf12_outlines.cpython-39.pyc b/share/extensions/__pycache__/dxf12_outlines.cpython-39.pyc new file mode 100644 index 0000000..42f7f36 Binary files /dev/null and b/share/extensions/__pycache__/dxf12_outlines.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/dxf_input.cpython-39.pyc b/share/extensions/__pycache__/dxf_input.cpython-39.pyc new file mode 100644 index 0000000..d4a8541 Binary files /dev/null and b/share/extensions/__pycache__/dxf_input.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/dxf_outlines.cpython-39.pyc b/share/extensions/__pycache__/dxf_outlines.cpython-39.pyc new file mode 100644 index 0000000..490cd2e Binary files /dev/null and b/share/extensions/__pycache__/dxf_outlines.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/edge3d.cpython-39.pyc b/share/extensions/__pycache__/edge3d.cpython-39.pyc new file mode 100644 index 0000000..b636e51 Binary files /dev/null and b/share/extensions/__pycache__/edge3d.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/embedimage.cpython-39.pyc b/share/extensions/__pycache__/embedimage.cpython-39.pyc new file mode 100644 index 0000000..ff42c4d Binary files /dev/null and b/share/extensions/__pycache__/embedimage.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/export_gimp_palette.cpython-39.pyc b/share/extensions/__pycache__/export_gimp_palette.cpython-39.pyc new file mode 100644 index 0000000..0412511 Binary files /dev/null and b/share/extensions/__pycache__/export_gimp_palette.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/extractimage.cpython-39.pyc b/share/extensions/__pycache__/extractimage.cpython-39.pyc new file mode 100644 index 0000000..39a0ed2 Binary files /dev/null and b/share/extensions/__pycache__/extractimage.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/extrude.cpython-39.pyc b/share/extensions/__pycache__/extrude.cpython-39.pyc new file mode 100644 index 0000000..e3caab5 Binary files /dev/null and b/share/extensions/__pycache__/extrude.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/fig_input.cpython-39.pyc b/share/extensions/__pycache__/fig_input.cpython-39.pyc new file mode 100644 index 0000000..d9017c2 Binary files /dev/null and b/share/extensions/__pycache__/fig_input.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/flatten.cpython-39.pyc b/share/extensions/__pycache__/flatten.cpython-39.pyc new file mode 100644 index 0000000..1c32b74 Binary files /dev/null and b/share/extensions/__pycache__/flatten.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/foldablebox.cpython-39.pyc b/share/extensions/__pycache__/foldablebox.cpython-39.pyc new file mode 100644 index 0000000..3bcc1fe Binary files /dev/null and b/share/extensions/__pycache__/foldablebox.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/fractalize.cpython-39.pyc b/share/extensions/__pycache__/fractalize.cpython-39.pyc new file mode 100644 index 0000000..47608ff Binary files /dev/null and b/share/extensions/__pycache__/fractalize.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/frame.cpython-39.pyc b/share/extensions/__pycache__/frame.cpython-39.pyc new file mode 100644 index 0000000..5491890 Binary files /dev/null and b/share/extensions/__pycache__/frame.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/funcplot.cpython-39.pyc b/share/extensions/__pycache__/funcplot.cpython-39.pyc new file mode 100644 index 0000000..d9b54bc Binary files /dev/null and b/share/extensions/__pycache__/funcplot.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/gcodetools.cpython-39.pyc b/share/extensions/__pycache__/gcodetools.cpython-39.pyc new file mode 100644 index 0000000..a645d25 Binary files /dev/null and b/share/extensions/__pycache__/gcodetools.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/generate_voronoi.cpython-39.pyc b/share/extensions/__pycache__/generate_voronoi.cpython-39.pyc new file mode 100644 index 0000000..c2ed296 Binary files /dev/null and b/share/extensions/__pycache__/generate_voronoi.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/gimp_xcf.cpython-39.pyc b/share/extensions/__pycache__/gimp_xcf.cpython-39.pyc new file mode 100644 index 0000000..e1ea19d Binary files /dev/null and b/share/extensions/__pycache__/gimp_xcf.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/grid_cartesian.cpython-39.pyc b/share/extensions/__pycache__/grid_cartesian.cpython-39.pyc new file mode 100644 index 0000000..b6bd276 Binary files /dev/null and b/share/extensions/__pycache__/grid_cartesian.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/grid_isometric.cpython-39.pyc b/share/extensions/__pycache__/grid_isometric.cpython-39.pyc new file mode 100644 index 0000000..ce90410 Binary files /dev/null and b/share/extensions/__pycache__/grid_isometric.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/grid_polar.cpython-39.pyc b/share/extensions/__pycache__/grid_polar.cpython-39.pyc new file mode 100644 index 0000000..c49f6b5 Binary files /dev/null and b/share/extensions/__pycache__/grid_polar.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/guides_creator.cpython-39.pyc b/share/extensions/__pycache__/guides_creator.cpython-39.pyc new file mode 100644 index 0000000..1c0596f Binary files /dev/null and b/share/extensions/__pycache__/guides_creator.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/guillotine.cpython-39.pyc b/share/extensions/__pycache__/guillotine.cpython-39.pyc new file mode 100644 index 0000000..ed97da5 Binary files /dev/null and b/share/extensions/__pycache__/guillotine.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/handles.cpython-39.pyc b/share/extensions/__pycache__/handles.cpython-39.pyc new file mode 100644 index 0000000..a8ad69c Binary files /dev/null and b/share/extensions/__pycache__/handles.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/hershey.cpython-39.pyc b/share/extensions/__pycache__/hershey.cpython-39.pyc new file mode 100644 index 0000000..1b8bdf4 Binary files /dev/null and b/share/extensions/__pycache__/hershey.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/hpgl_decoder.cpython-39.pyc b/share/extensions/__pycache__/hpgl_decoder.cpython-39.pyc new file mode 100644 index 0000000..6b13b95 Binary files /dev/null and b/share/extensions/__pycache__/hpgl_decoder.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/hpgl_encoder.cpython-39.pyc b/share/extensions/__pycache__/hpgl_encoder.cpython-39.pyc new file mode 100644 index 0000000..9a8677a Binary files /dev/null and b/share/extensions/__pycache__/hpgl_encoder.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/hpgl_input.cpython-39.pyc b/share/extensions/__pycache__/hpgl_input.cpython-39.pyc new file mode 100644 index 0000000..cd3b372 Binary files /dev/null and b/share/extensions/__pycache__/hpgl_input.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/hpgl_output.cpython-39.pyc b/share/extensions/__pycache__/hpgl_output.cpython-39.pyc new file mode 100644 index 0000000..b807e75 Binary files /dev/null and b/share/extensions/__pycache__/hpgl_output.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/image_attributes.cpython-39.pyc b/share/extensions/__pycache__/image_attributes.cpython-39.pyc new file mode 100644 index 0000000..4614f79 Binary files /dev/null and b/share/extensions/__pycache__/image_attributes.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/ink2canvas.cpython-39.pyc b/share/extensions/__pycache__/ink2canvas.cpython-39.pyc new file mode 100644 index 0000000..af02748 Binary files /dev/null and b/share/extensions/__pycache__/ink2canvas.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/inkscape_follow_link.cpython-39.pyc b/share/extensions/__pycache__/inkscape_follow_link.cpython-39.pyc new file mode 100644 index 0000000..c5e9a08 Binary files /dev/null and b/share/extensions/__pycache__/inkscape_follow_link.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/inkwebeffect.cpython-39.pyc b/share/extensions/__pycache__/inkwebeffect.cpython-39.pyc new file mode 100644 index 0000000..763e0fd Binary files /dev/null and b/share/extensions/__pycache__/inkwebeffect.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/interp.cpython-39.pyc b/share/extensions/__pycache__/interp.cpython-39.pyc new file mode 100644 index 0000000..c12d1e9 Binary files /dev/null and b/share/extensions/__pycache__/interp.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/interp_att_g.cpython-39.pyc b/share/extensions/__pycache__/interp_att_g.cpython-39.pyc new file mode 100644 index 0000000..a2063b7 Binary files /dev/null and b/share/extensions/__pycache__/interp_att_g.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/jessyink_autotexts.cpython-39.pyc b/share/extensions/__pycache__/jessyink_autotexts.cpython-39.pyc new file mode 100644 index 0000000..4a493e4 Binary files /dev/null and b/share/extensions/__pycache__/jessyink_autotexts.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/jessyink_effects.cpython-39.pyc b/share/extensions/__pycache__/jessyink_effects.cpython-39.pyc new file mode 100644 index 0000000..c9021e2 Binary files /dev/null and b/share/extensions/__pycache__/jessyink_effects.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/jessyink_export.cpython-39.pyc b/share/extensions/__pycache__/jessyink_export.cpython-39.pyc new file mode 100644 index 0000000..fb2e9dc Binary files /dev/null and b/share/extensions/__pycache__/jessyink_export.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/jessyink_install.cpython-39.pyc b/share/extensions/__pycache__/jessyink_install.cpython-39.pyc new file mode 100644 index 0000000..ac7c706 Binary files /dev/null and b/share/extensions/__pycache__/jessyink_install.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/jessyink_key_bindings.cpython-39.pyc b/share/extensions/__pycache__/jessyink_key_bindings.cpython-39.pyc new file mode 100644 index 0000000..3fb0533 Binary files /dev/null and b/share/extensions/__pycache__/jessyink_key_bindings.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/jessyink_master_slide.cpython-39.pyc b/share/extensions/__pycache__/jessyink_master_slide.cpython-39.pyc new file mode 100644 index 0000000..68718f3 Binary files /dev/null and b/share/extensions/__pycache__/jessyink_master_slide.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/jessyink_mouse_handler.cpython-39.pyc b/share/extensions/__pycache__/jessyink_mouse_handler.cpython-39.pyc new file mode 100644 index 0000000..9c7b7c7 Binary files /dev/null and b/share/extensions/__pycache__/jessyink_mouse_handler.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/jessyink_summary.cpython-39.pyc b/share/extensions/__pycache__/jessyink_summary.cpython-39.pyc new file mode 100644 index 0000000..cf11724 Binary files /dev/null and b/share/extensions/__pycache__/jessyink_summary.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/jessyink_transitions.cpython-39.pyc b/share/extensions/__pycache__/jessyink_transitions.cpython-39.pyc new file mode 100644 index 0000000..9b2c382 Binary files /dev/null and b/share/extensions/__pycache__/jessyink_transitions.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/jessyink_uninstall.cpython-39.pyc b/share/extensions/__pycache__/jessyink_uninstall.cpython-39.pyc new file mode 100644 index 0000000..9e96704 Binary files /dev/null and b/share/extensions/__pycache__/jessyink_uninstall.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/jessyink_video.cpython-39.pyc b/share/extensions/__pycache__/jessyink_video.cpython-39.pyc new file mode 100644 index 0000000..530fa58 Binary files /dev/null and b/share/extensions/__pycache__/jessyink_video.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/jessyink_view.cpython-39.pyc b/share/extensions/__pycache__/jessyink_view.cpython-39.pyc new file mode 100644 index 0000000..1238c4b Binary files /dev/null and b/share/extensions/__pycache__/jessyink_view.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/jitternodes.cpython-39.pyc b/share/extensions/__pycache__/jitternodes.cpython-39.pyc new file mode 100644 index 0000000..e5d496c Binary files /dev/null and b/share/extensions/__pycache__/jitternodes.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/launch_webbrowser.cpython-39.pyc b/share/extensions/__pycache__/launch_webbrowser.cpython-39.pyc new file mode 100644 index 0000000..a1cf78b Binary files /dev/null and b/share/extensions/__pycache__/launch_webbrowser.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/layer2png.cpython-39.pyc b/share/extensions/__pycache__/layer2png.cpython-39.pyc new file mode 100644 index 0000000..de56592 Binary files /dev/null and b/share/extensions/__pycache__/layer2png.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/layers2svgfont.cpython-39.pyc b/share/extensions/__pycache__/layers2svgfont.cpython-39.pyc new file mode 100644 index 0000000..e7bc581 Binary files /dev/null and b/share/extensions/__pycache__/layers2svgfont.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/layout_nup.cpython-39.pyc b/share/extensions/__pycache__/layout_nup.cpython-39.pyc new file mode 100644 index 0000000..1bcfffc Binary files /dev/null and b/share/extensions/__pycache__/layout_nup.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/lindenmayer.cpython-39.pyc b/share/extensions/__pycache__/lindenmayer.cpython-39.pyc new file mode 100644 index 0000000..5550ba9 Binary files /dev/null and b/share/extensions/__pycache__/lindenmayer.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/lorem_ipsum.cpython-39.pyc b/share/extensions/__pycache__/lorem_ipsum.cpython-39.pyc new file mode 100644 index 0000000..2e8d2a67 Binary files /dev/null and b/share/extensions/__pycache__/lorem_ipsum.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/markers_strokepaint.cpython-39.pyc b/share/extensions/__pycache__/markers_strokepaint.cpython-39.pyc new file mode 100644 index 0000000..cffbc3c Binary files /dev/null and b/share/extensions/__pycache__/markers_strokepaint.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/measure.cpython-39.pyc b/share/extensions/__pycache__/measure.cpython-39.pyc new file mode 100644 index 0000000..b1d7592 Binary files /dev/null and b/share/extensions/__pycache__/measure.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/media_zip.cpython-39.pyc b/share/extensions/__pycache__/media_zip.cpython-39.pyc new file mode 100644 index 0000000..b96498e Binary files /dev/null and b/share/extensions/__pycache__/media_zip.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/merge_styles.cpython-39.pyc b/share/extensions/__pycache__/merge_styles.cpython-39.pyc new file mode 100644 index 0000000..b92e01a Binary files /dev/null and b/share/extensions/__pycache__/merge_styles.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/motion.cpython-39.pyc b/share/extensions/__pycache__/motion.cpython-39.pyc new file mode 100644 index 0000000..a303112 Binary files /dev/null and b/share/extensions/__pycache__/motion.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/new_glyph_layer.cpython-39.pyc b/share/extensions/__pycache__/new_glyph_layer.cpython-39.pyc new file mode 100644 index 0000000..1bfd6f6 Binary files /dev/null and b/share/extensions/__pycache__/new_glyph_layer.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/next_glyph_layer.cpython-39.pyc b/share/extensions/__pycache__/next_glyph_layer.cpython-39.pyc new file mode 100644 index 0000000..4db853c Binary files /dev/null and b/share/extensions/__pycache__/next_glyph_layer.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/nicechart.cpython-39.pyc b/share/extensions/__pycache__/nicechart.cpython-39.pyc new file mode 100644 index 0000000..5936674 Binary files /dev/null and b/share/extensions/__pycache__/nicechart.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/output_scour.cpython-39.pyc b/share/extensions/__pycache__/output_scour.cpython-39.pyc new file mode 100644 index 0000000..e71e418 Binary files /dev/null and b/share/extensions/__pycache__/output_scour.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/param_curves.cpython-39.pyc b/share/extensions/__pycache__/param_curves.cpython-39.pyc new file mode 100644 index 0000000..e988350 Binary files /dev/null and b/share/extensions/__pycache__/param_curves.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/path_envelope.cpython-39.pyc b/share/extensions/__pycache__/path_envelope.cpython-39.pyc new file mode 100644 index 0000000..4ff20d8 Binary files /dev/null and b/share/extensions/__pycache__/path_envelope.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/path_mesh_m2p.cpython-39.pyc b/share/extensions/__pycache__/path_mesh_m2p.cpython-39.pyc new file mode 100644 index 0000000..32d76cc Binary files /dev/null and b/share/extensions/__pycache__/path_mesh_m2p.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/path_mesh_p2m.cpython-39.pyc b/share/extensions/__pycache__/path_mesh_p2m.cpython-39.pyc new file mode 100644 index 0000000..6175078 Binary files /dev/null and b/share/extensions/__pycache__/path_mesh_p2m.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/path_number_nodes.cpython-39.pyc b/share/extensions/__pycache__/path_number_nodes.cpython-39.pyc new file mode 100644 index 0000000..470d0f2 Binary files /dev/null and b/share/extensions/__pycache__/path_number_nodes.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/path_to_absolute.cpython-39.pyc b/share/extensions/__pycache__/path_to_absolute.cpython-39.pyc new file mode 100644 index 0000000..981500c Binary files /dev/null and b/share/extensions/__pycache__/path_to_absolute.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/pathalongpath.cpython-39.pyc b/share/extensions/__pycache__/pathalongpath.cpython-39.pyc new file mode 100644 index 0000000..91586d5 Binary files /dev/null and b/share/extensions/__pycache__/pathalongpath.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/pathmodifier.cpython-39.pyc b/share/extensions/__pycache__/pathmodifier.cpython-39.pyc new file mode 100644 index 0000000..9fa1e59 Binary files /dev/null and b/share/extensions/__pycache__/pathmodifier.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/pathscatter.cpython-39.pyc b/share/extensions/__pycache__/pathscatter.cpython-39.pyc new file mode 100644 index 0000000..3edba6c Binary files /dev/null and b/share/extensions/__pycache__/pathscatter.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/pdflatex.cpython-39.pyc b/share/extensions/__pycache__/pdflatex.cpython-39.pyc new file mode 100644 index 0000000..41b76ff Binary files /dev/null and b/share/extensions/__pycache__/pdflatex.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/perfectboundcover.cpython-39.pyc b/share/extensions/__pycache__/perfectboundcover.cpython-39.pyc new file mode 100644 index 0000000..d66296d Binary files /dev/null and b/share/extensions/__pycache__/perfectboundcover.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/perspective.cpython-39.pyc b/share/extensions/__pycache__/perspective.cpython-39.pyc new file mode 100644 index 0000000..2ab5c8b Binary files /dev/null and b/share/extensions/__pycache__/perspective.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/pixelsnap.cpython-39.pyc b/share/extensions/__pycache__/pixelsnap.cpython-39.pyc new file mode 100644 index 0000000..2069294 Binary files /dev/null and b/share/extensions/__pycache__/pixelsnap.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/plotter.cpython-39.pyc b/share/extensions/__pycache__/plotter.cpython-39.pyc new file mode 100644 index 0000000..236251f Binary files /dev/null and b/share/extensions/__pycache__/plotter.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/polyhedron_3d.cpython-39.pyc b/share/extensions/__pycache__/polyhedron_3d.cpython-39.pyc new file mode 100644 index 0000000..d210178 Binary files /dev/null and b/share/extensions/__pycache__/polyhedron_3d.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/prepare_file_save_as.cpython-39.pyc b/share/extensions/__pycache__/prepare_file_save_as.cpython-39.pyc new file mode 100644 index 0000000..81abd46 Binary files /dev/null and b/share/extensions/__pycache__/prepare_file_save_as.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/previous_glyph_layer.cpython-39.pyc b/share/extensions/__pycache__/previous_glyph_layer.cpython-39.pyc new file mode 100644 index 0000000..3b1656d Binary files /dev/null and b/share/extensions/__pycache__/previous_glyph_layer.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/print_win32_vector.cpython-39.pyc b/share/extensions/__pycache__/print_win32_vector.cpython-39.pyc new file mode 100644 index 0000000..a7d5da2 Binary files /dev/null and b/share/extensions/__pycache__/print_win32_vector.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/printing_marks.cpython-39.pyc b/share/extensions/__pycache__/printing_marks.cpython-39.pyc new file mode 100644 index 0000000..0c8bc04 Binary files /dev/null and b/share/extensions/__pycache__/printing_marks.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/ps_input.cpython-39.pyc b/share/extensions/__pycache__/ps_input.cpython-39.pyc new file mode 100644 index 0000000..84c4665 Binary files /dev/null and b/share/extensions/__pycache__/ps_input.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/render_alphabetsoup.cpython-39.pyc b/share/extensions/__pycache__/render_alphabetsoup.cpython-39.pyc new file mode 100644 index 0000000..c8ca5fd Binary files /dev/null and b/share/extensions/__pycache__/render_alphabetsoup.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/render_alphabetsoup_config.cpython-39.pyc b/share/extensions/__pycache__/render_alphabetsoup_config.cpython-39.pyc new file mode 100644 index 0000000..eaa0d96 Binary files /dev/null and b/share/extensions/__pycache__/render_alphabetsoup_config.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/render_barcode.cpython-39.pyc b/share/extensions/__pycache__/render_barcode.cpython-39.pyc new file mode 100644 index 0000000..743766c Binary files /dev/null and b/share/extensions/__pycache__/render_barcode.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/render_barcode_datamatrix.cpython-39.pyc b/share/extensions/__pycache__/render_barcode_datamatrix.cpython-39.pyc new file mode 100644 index 0000000..53bcb3d Binary files /dev/null and b/share/extensions/__pycache__/render_barcode_datamatrix.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/render_barcode_qrcode.cpython-39.pyc b/share/extensions/__pycache__/render_barcode_qrcode.cpython-39.pyc new file mode 100644 index 0000000..e325d29 Binary files /dev/null and b/share/extensions/__pycache__/render_barcode_qrcode.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/render_gear_rack.cpython-39.pyc b/share/extensions/__pycache__/render_gear_rack.cpython-39.pyc new file mode 100644 index 0000000..4c30bc6 Binary files /dev/null and b/share/extensions/__pycache__/render_gear_rack.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/render_gears.cpython-39.pyc b/share/extensions/__pycache__/render_gears.cpython-39.pyc new file mode 100644 index 0000000..55f2e64 Binary files /dev/null and b/share/extensions/__pycache__/render_gears.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/replace_font.cpython-39.pyc b/share/extensions/__pycache__/replace_font.cpython-39.pyc new file mode 100644 index 0000000..4b44d80 Binary files /dev/null and b/share/extensions/__pycache__/replace_font.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/restack.cpython-39.pyc b/share/extensions/__pycache__/restack.cpython-39.pyc new file mode 100644 index 0000000..89dae54 Binary files /dev/null and b/share/extensions/__pycache__/restack.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/rtree.cpython-39.pyc b/share/extensions/__pycache__/rtree.cpython-39.pyc new file mode 100644 index 0000000..82ed7e7 Binary files /dev/null and b/share/extensions/__pycache__/rtree.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/rubberstretch.cpython-39.pyc b/share/extensions/__pycache__/rubberstretch.cpython-39.pyc new file mode 100644 index 0000000..a8fb13a Binary files /dev/null and b/share/extensions/__pycache__/rubberstretch.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/scribus_export_pdf.cpython-39.pyc b/share/extensions/__pycache__/scribus_export_pdf.cpython-39.pyc new file mode 100644 index 0000000..9069718 Binary files /dev/null and b/share/extensions/__pycache__/scribus_export_pdf.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/setup_typography_canvas.cpython-39.pyc b/share/extensions/__pycache__/setup_typography_canvas.cpython-39.pyc new file mode 100644 index 0000000..c6fb3c4 Binary files /dev/null and b/share/extensions/__pycache__/setup_typography_canvas.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/spirograph.cpython-39.pyc b/share/extensions/__pycache__/spirograph.cpython-39.pyc new file mode 100644 index 0000000..65d5f44 Binary files /dev/null and b/share/extensions/__pycache__/spirograph.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/straightseg.cpython-39.pyc b/share/extensions/__pycache__/straightseg.cpython-39.pyc new file mode 100644 index 0000000..b26c4a7 Binary files /dev/null and b/share/extensions/__pycache__/straightseg.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/svgcalendar.cpython-39.pyc b/share/extensions/__pycache__/svgcalendar.cpython-39.pyc new file mode 100644 index 0000000..5cc65e2 Binary files /dev/null and b/share/extensions/__pycache__/svgcalendar.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/svgfont2layers.cpython-39.pyc b/share/extensions/__pycache__/svgfont2layers.cpython-39.pyc new file mode 100644 index 0000000..d287329 Binary files /dev/null and b/share/extensions/__pycache__/svgfont2layers.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/synfig_fileformat.cpython-39.pyc b/share/extensions/__pycache__/synfig_fileformat.cpython-39.pyc new file mode 100644 index 0000000..be90d2d Binary files /dev/null and b/share/extensions/__pycache__/synfig_fileformat.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/synfig_output.cpython-39.pyc b/share/extensions/__pycache__/synfig_output.cpython-39.pyc new file mode 100644 index 0000000..ce73aee Binary files /dev/null and b/share/extensions/__pycache__/synfig_output.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/synfig_prepare.cpython-39.pyc b/share/extensions/__pycache__/synfig_prepare.cpython-39.pyc new file mode 100644 index 0000000..9a7278f Binary files /dev/null and b/share/extensions/__pycache__/synfig_prepare.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/tar_layers.cpython-39.pyc b/share/extensions/__pycache__/tar_layers.cpython-39.pyc new file mode 100644 index 0000000..13bf1bc Binary files /dev/null and b/share/extensions/__pycache__/tar_layers.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/template.cpython-39.pyc b/share/extensions/__pycache__/template.cpython-39.pyc new file mode 100644 index 0000000..5c5d030 Binary files /dev/null and b/share/extensions/__pycache__/template.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/template_dvd_cover.cpython-39.pyc b/share/extensions/__pycache__/template_dvd_cover.cpython-39.pyc new file mode 100644 index 0000000..8704649 Binary files /dev/null and b/share/extensions/__pycache__/template_dvd_cover.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/template_seamless_pattern.cpython-39.pyc b/share/extensions/__pycache__/template_seamless_pattern.cpython-39.pyc new file mode 100644 index 0000000..fa7c83c Binary files /dev/null and b/share/extensions/__pycache__/template_seamless_pattern.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/text_braille.cpython-39.pyc b/share/extensions/__pycache__/text_braille.cpython-39.pyc new file mode 100644 index 0000000..ca4d08f Binary files /dev/null and b/share/extensions/__pycache__/text_braille.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/text_extract.cpython-39.pyc b/share/extensions/__pycache__/text_extract.cpython-39.pyc new file mode 100644 index 0000000..74a51ba Binary files /dev/null and b/share/extensions/__pycache__/text_extract.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/text_flipcase.cpython-39.pyc b/share/extensions/__pycache__/text_flipcase.cpython-39.pyc new file mode 100644 index 0000000..280468e Binary files /dev/null and b/share/extensions/__pycache__/text_flipcase.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/text_lowercase.cpython-39.pyc b/share/extensions/__pycache__/text_lowercase.cpython-39.pyc new file mode 100644 index 0000000..7f28b57 Binary files /dev/null and b/share/extensions/__pycache__/text_lowercase.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/text_merge.cpython-39.pyc b/share/extensions/__pycache__/text_merge.cpython-39.pyc new file mode 100644 index 0000000..c0ac4c1 Binary files /dev/null and b/share/extensions/__pycache__/text_merge.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/text_randomcase.cpython-39.pyc b/share/extensions/__pycache__/text_randomcase.cpython-39.pyc new file mode 100644 index 0000000..e15589f Binary files /dev/null and b/share/extensions/__pycache__/text_randomcase.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/text_sentencecase.cpython-39.pyc b/share/extensions/__pycache__/text_sentencecase.cpython-39.pyc new file mode 100644 index 0000000..b90171b Binary files /dev/null and b/share/extensions/__pycache__/text_sentencecase.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/text_split.cpython-39.pyc b/share/extensions/__pycache__/text_split.cpython-39.pyc new file mode 100644 index 0000000..acf333e Binary files /dev/null and b/share/extensions/__pycache__/text_split.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/text_titlecase.cpython-39.pyc b/share/extensions/__pycache__/text_titlecase.cpython-39.pyc new file mode 100644 index 0000000..8e2e460 Binary files /dev/null and b/share/extensions/__pycache__/text_titlecase.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/text_uppercase.cpython-39.pyc b/share/extensions/__pycache__/text_uppercase.cpython-39.pyc new file mode 100644 index 0000000..0295ea9 Binary files /dev/null and b/share/extensions/__pycache__/text_uppercase.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/triangle.cpython-39.pyc b/share/extensions/__pycache__/triangle.cpython-39.pyc new file mode 100644 index 0000000..245411a Binary files /dev/null and b/share/extensions/__pycache__/triangle.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/ungroup_deep.cpython-39.pyc b/share/extensions/__pycache__/ungroup_deep.cpython-39.pyc new file mode 100644 index 0000000..0fd99bc Binary files /dev/null and b/share/extensions/__pycache__/ungroup_deep.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/voronoi.cpython-39.pyc b/share/extensions/__pycache__/voronoi.cpython-39.pyc new file mode 100644 index 0000000..2626464 Binary files /dev/null and b/share/extensions/__pycache__/voronoi.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/voronoi2svg.cpython-39.pyc b/share/extensions/__pycache__/voronoi2svg.cpython-39.pyc new file mode 100644 index 0000000..1fc415b Binary files /dev/null and b/share/extensions/__pycache__/voronoi2svg.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/web_interactive_mockup.cpython-39.pyc b/share/extensions/__pycache__/web_interactive_mockup.cpython-39.pyc new file mode 100644 index 0000000..e95c69d Binary files /dev/null and b/share/extensions/__pycache__/web_interactive_mockup.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/web_set_att.cpython-39.pyc b/share/extensions/__pycache__/web_set_att.cpython-39.pyc new file mode 100644 index 0000000..647ac99 Binary files /dev/null and b/share/extensions/__pycache__/web_set_att.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/web_transmit_att.cpython-39.pyc b/share/extensions/__pycache__/web_transmit_att.cpython-39.pyc new file mode 100644 index 0000000..86bb971 Binary files /dev/null and b/share/extensions/__pycache__/web_transmit_att.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/webslicer_create_group.cpython-39.pyc b/share/extensions/__pycache__/webslicer_create_group.cpython-39.pyc new file mode 100644 index 0000000..5f000c7 Binary files /dev/null and b/share/extensions/__pycache__/webslicer_create_group.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/webslicer_create_rect.cpython-39.pyc b/share/extensions/__pycache__/webslicer_create_rect.cpython-39.pyc new file mode 100644 index 0000000..e00e37e Binary files /dev/null and b/share/extensions/__pycache__/webslicer_create_rect.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/webslicer_effect.cpython-39.pyc b/share/extensions/__pycache__/webslicer_effect.cpython-39.pyc new file mode 100644 index 0000000..98510e6 Binary files /dev/null and b/share/extensions/__pycache__/webslicer_effect.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/webslicer_export.cpython-39.pyc b/share/extensions/__pycache__/webslicer_export.cpython-39.pyc new file mode 100644 index 0000000..25c95b3 Binary files /dev/null and b/share/extensions/__pycache__/webslicer_export.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/whirl.cpython-39.pyc b/share/extensions/__pycache__/whirl.cpython-39.pyc new file mode 100644 index 0000000..ca0e99a Binary files /dev/null and b/share/extensions/__pycache__/whirl.cpython-39.pyc differ diff --git a/share/extensions/__pycache__/wireframe_sphere.cpython-39.pyc b/share/extensions/__pycache__/wireframe_sphere.cpython-39.pyc new file mode 100644 index 0000000..9c88b7d Binary files /dev/null and b/share/extensions/__pycache__/wireframe_sphere.cpython-39.pyc differ diff --git a/share/extensions/addnodes.inx b/share/extensions/addnodes.inx new file mode 100644 index 0000000..c681125 --- /dev/null +++ b/share/extensions/addnodes.inx @@ -0,0 +1,20 @@ + + + Add Nodes + org.inkscape.filter.add_nodes + + + + + 10.0 + 2 + + path + + + + + + diff --git a/share/extensions/addnodes.py b/share/extensions/addnodes.py new file mode 100755 index 0000000..5587bea --- /dev/null +++ b/share/extensions/addnodes.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005, 2007 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" +This extension either adds nodes to a path so that + + No segment is longer than a maximum value OR that each segment is divided + into a given number of equal segments. + +""" +import math +import inkex + +from inkex import bezier, PathElement, CubicSuperPath + + +class AddNodes(inkex.EffectExtension): + """Extension to split a path by adding nodes to it""" + def add_arguments(self, pars): + pars.add_argument("--segments", type=int, default=2, + help="Number of segments to divide the path into") + pars.add_argument("--max", type=float, default=2.0, + help="Number of segments to divide the path into") + pars.add_argument("--method", help="The kind of division to perform") + + def effect(self): + for node in self.svg.selection.filter(PathElement).values(): + new = [] + for sub in node.path.to_superpath(): + new.append([sub[0][:]]) + i = 1 + while i <= len(sub) - 1: + length = bezier.cspseglength(new[-1][-1], sub[i]) + + if self.options.method == 'bynum': + splits = self.options.segments + else: + splits = math.ceil(length / self.options.max) + + for sel in range(int(splits), 1, -1): + result = bezier.cspbezsplitatlength(new[-1][-1], sub[i], 1.0 / sel) + better_result = [[list(el) for el in elements] for elements in result] + new[-1][-1], nxt, sub[i] = better_result + new[-1].append(nxt[:]) + new[-1].append(sub[i]) + i += 1 + node.path = CubicSuperPath(new).to_path(curves_only=True) + +if __name__ == '__main__': + AddNodes().run() diff --git a/share/extensions/aisvg.inx b/share/extensions/aisvg.inx new file mode 100644 index 0000000..46f4ed0 --- /dev/null +++ b/share/extensions/aisvg.inx @@ -0,0 +1,14 @@ + + + AI SVG Input + org.inkscape.input.aisvg + + .ai.svg + text/xml+svg + Adobe Illustrator SVG (*.ai.svg) + Cleans the cruft out of Adobe Illustrator SVGs before opening + + + aisvg.xslt + + diff --git a/share/extensions/aisvg.xslt b/share/extensions/aisvg.xslt new file mode 100644 index 0000000..15397da --- /dev/null +++ b/share/extensions/aisvg.xslt @@ -0,0 +1,36 @@ + + + + + + + + + + + + layer + + + + + + + + + + + + + + diff --git a/share/extensions/alphabet_soup/2.svg b/share/extensions/alphabet_soup/2.svg new file mode 100644 index 0000000..d198939 --- /dev/null +++ b/share/extensions/alphabet_soup/2.svg @@ -0,0 +1,11 @@ + + + + diff --git a/share/extensions/alphabet_soup/3.svg b/share/extensions/alphabet_soup/3.svg new file mode 100644 index 0000000..3c6d97d --- /dev/null +++ b/share/extensions/alphabet_soup/3.svg @@ -0,0 +1,11 @@ + + + + diff --git a/share/extensions/alphabet_soup/6.svg b/share/extensions/alphabet_soup/6.svg new file mode 100644 index 0000000..d05c508 --- /dev/null +++ b/share/extensions/alphabet_soup/6.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/7.svg b/share/extensions/alphabet_soup/7.svg new file mode 100644 index 0000000..e921bb5 --- /dev/null +++ b/share/extensions/alphabet_soup/7.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Cblob.svg b/share/extensions/alphabet_soup/Cblob.svg new file mode 100644 index 0000000..b5b104e --- /dev/null +++ b/share/extensions/alphabet_soup/Cblob.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Chook.svg b/share/extensions/alphabet_soup/Chook.svg new file mode 100644 index 0000000..2e3cf99 --- /dev/null +++ b/share/extensions/alphabet_soup/Chook.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Ctail.svg b/share/extensions/alphabet_soup/Ctail.svg new file mode 100644 index 0000000..54d55de --- /dev/null +++ b/share/extensions/alphabet_soup/Ctail.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Delta.svg b/share/extensions/alphabet_soup/Delta.svg new file mode 100644 index 0000000..82e60c3 --- /dev/null +++ b/share/extensions/alphabet_soup/Delta.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Eb.svg b/share/extensions/alphabet_soup/Eb.svg new file mode 100644 index 0000000..490671b --- /dev/null +++ b/share/extensions/alphabet_soup/Eb.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Eserif.svg b/share/extensions/alphabet_soup/Eserif.svg new file mode 100644 index 0000000..51e46b9 --- /dev/null +++ b/share/extensions/alphabet_soup/Eserif.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/share/extensions/alphabet_soup/Et.svg b/share/extensions/alphabet_soup/Et.svg new file mode 100644 index 0000000..49a3a5f --- /dev/null +++ b/share/extensions/alphabet_soup/Et.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/G.svg b/share/extensions/alphabet_soup/G.svg new file mode 100644 index 0000000..f7cd3cc --- /dev/null +++ b/share/extensions/alphabet_soup/G.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/IBSerif.svg b/share/extensions/alphabet_soup/IBSerif.svg new file mode 100644 index 0000000..dfb6987 --- /dev/null +++ b/share/extensions/alphabet_soup/IBSerif.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/ITSerif.svg b/share/extensions/alphabet_soup/ITSerif.svg new file mode 100644 index 0000000..492aff5 --- /dev/null +++ b/share/extensions/alphabet_soup/ITSerif.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Lb.svg b/share/extensions/alphabet_soup/Lb.svg new file mode 100644 index 0000000..3fa9f97 --- /dev/null +++ b/share/extensions/alphabet_soup/Lb.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Lt.svg b/share/extensions/alphabet_soup/Lt.svg new file mode 100644 index 0000000..1f4c18f --- /dev/null +++ b/share/extensions/alphabet_soup/Lt.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Ocross.svg b/share/extensions/alphabet_soup/Ocross.svg new file mode 100644 index 0000000..63b1caa --- /dev/null +++ b/share/extensions/alphabet_soup/Ocross.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Oterm.svg b/share/extensions/alphabet_soup/Oterm.svg new file mode 100644 index 0000000..e167a57 --- /dev/null +++ b/share/extensions/alphabet_soup/Oterm.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/P.svg b/share/extensions/alphabet_soup/P.svg new file mode 100644 index 0000000..0344082 --- /dev/null +++ b/share/extensions/alphabet_soup/P.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Q.svg b/share/extensions/alphabet_soup/Q.svg new file mode 100644 index 0000000..c60ea42 --- /dev/null +++ b/share/extensions/alphabet_soup/Q.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Rblock.svg b/share/extensions/alphabet_soup/Rblock.svg new file mode 100644 index 0000000..a2f4e9d --- /dev/null +++ b/share/extensions/alphabet_soup/Rblock.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Tb.svg b/share/extensions/alphabet_soup/Tb.svg new file mode 100644 index 0000000..2a75ac5 --- /dev/null +++ b/share/extensions/alphabet_soup/Tb.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/share/extensions/alphabet_soup/Tt.svg b/share/extensions/alphabet_soup/Tt.svg new file mode 100644 index 0000000..04d2a18 --- /dev/null +++ b/share/extensions/alphabet_soup/Tt.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/share/extensions/alphabet_soup/U.svg b/share/extensions/alphabet_soup/U.svg new file mode 100644 index 0000000..e8b8244 --- /dev/null +++ b/share/extensions/alphabet_soup/U.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Vser.svg b/share/extensions/alphabet_soup/Vser.svg new file mode 100644 index 0000000..a06b247 --- /dev/null +++ b/share/extensions/alphabet_soup/Vser.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Xh.svg b/share/extensions/alphabet_soup/Xh.svg new file mode 100644 index 0000000..c60f304 --- /dev/null +++ b/share/extensions/alphabet_soup/Xh.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Xne.svg b/share/extensions/alphabet_soup/Xne.svg new file mode 100644 index 0000000..9dc54f1 --- /dev/null +++ b/share/extensions/alphabet_soup/Xne.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Xnw.svg b/share/extensions/alphabet_soup/Xnw.svg new file mode 100644 index 0000000..98cd8bc --- /dev/null +++ b/share/extensions/alphabet_soup/Xnw.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Xvb.svg b/share/extensions/alphabet_soup/Xvb.svg new file mode 100644 index 0000000..16e80b4 --- /dev/null +++ b/share/extensions/alphabet_soup/Xvb.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/Xvt.svg b/share/extensions/alphabet_soup/Xvt.svg new file mode 100644 index 0000000..3a5c5dc --- /dev/null +++ b/share/extensions/alphabet_soup/Xvt.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/a.svg b/share/extensions/alphabet_soup/a.svg new file mode 100644 index 0000000..284de78 --- /dev/null +++ b/share/extensions/alphabet_soup/a.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/abase.svg b/share/extensions/alphabet_soup/abase.svg new file mode 100644 index 0000000..1da2bb1 --- /dev/null +++ b/share/extensions/alphabet_soup/abase.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/acap.svg b/share/extensions/alphabet_soup/acap.svg new file mode 100644 index 0000000..dfc7ede --- /dev/null +++ b/share/extensions/alphabet_soup/acap.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/b.svg b/share/extensions/alphabet_soup/b.svg new file mode 100644 index 0000000..fd01a26 --- /dev/null +++ b/share/extensions/alphabet_soup/b.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/bar.svg b/share/extensions/alphabet_soup/bar.svg new file mode 100644 index 0000000..8b477e3 --- /dev/null +++ b/share/extensions/alphabet_soup/bar.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/bar2.svg b/share/extensions/alphabet_soup/bar2.svg new file mode 100644 index 0000000..39ed825 --- /dev/null +++ b/share/extensions/alphabet_soup/bar2.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/barcap.svg b/share/extensions/alphabet_soup/barcap.svg new file mode 100644 index 0000000..5831e83 --- /dev/null +++ b/share/extensions/alphabet_soup/barcap.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/c.svg b/share/extensions/alphabet_soup/c.svg new file mode 100644 index 0000000..c6261a0 --- /dev/null +++ b/share/extensions/alphabet_soup/c.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/cross.svg b/share/extensions/alphabet_soup/cross.svg new file mode 100644 index 0000000..89b67fe --- /dev/null +++ b/share/extensions/alphabet_soup/cross.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/cserif.svg b/share/extensions/alphabet_soup/cserif.svg new file mode 100644 index 0000000..14669a0 --- /dev/null +++ b/share/extensions/alphabet_soup/cserif.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/e.svg b/share/extensions/alphabet_soup/e.svg new file mode 100644 index 0000000..03106dd --- /dev/null +++ b/share/extensions/alphabet_soup/e.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/epsilon.svg b/share/extensions/alphabet_soup/epsilon.svg new file mode 100644 index 0000000..a2dd33e --- /dev/null +++ b/share/extensions/alphabet_soup/epsilon.svg @@ -0,0 +1,8 @@ + + + diff --git a/share/extensions/alphabet_soup/f.svg b/share/extensions/alphabet_soup/f.svg new file mode 100644 index 0000000..c0f94c3 --- /dev/null +++ b/share/extensions/alphabet_soup/f.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/share/extensions/alphabet_soup/gamma.svg b/share/extensions/alphabet_soup/gamma.svg new file mode 100644 index 0000000..feb6b36 --- /dev/null +++ b/share/extensions/alphabet_soup/gamma.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/h.svg b/share/extensions/alphabet_soup/h.svg new file mode 100644 index 0000000..23be380 --- /dev/null +++ b/share/extensions/alphabet_soup/h.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/h2.svg b/share/extensions/alphabet_soup/h2.svg new file mode 100644 index 0000000..636b4df --- /dev/null +++ b/share/extensions/alphabet_soup/h2.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/hcap.svg b/share/extensions/alphabet_soup/hcap.svg new file mode 100644 index 0000000..a6048b6 --- /dev/null +++ b/share/extensions/alphabet_soup/hcap.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/idot.svg b/share/extensions/alphabet_soup/idot.svg new file mode 100644 index 0000000..d3d0dad --- /dev/null +++ b/share/extensions/alphabet_soup/idot.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/j.svg b/share/extensions/alphabet_soup/j.svg new file mode 100644 index 0000000..07c6805 --- /dev/null +++ b/share/extensions/alphabet_soup/j.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/k.svg b/share/extensions/alphabet_soup/k.svg new file mode 100644 index 0000000..314f2b0 --- /dev/null +++ b/share/extensions/alphabet_soup/k.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/l.svg b/share/extensions/alphabet_soup/l.svg new file mode 100644 index 0000000..00beccf --- /dev/null +++ b/share/extensions/alphabet_soup/l.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/lserif.svg b/share/extensions/alphabet_soup/lserif.svg new file mode 100644 index 0000000..7ab2c0d --- /dev/null +++ b/share/extensions/alphabet_soup/lserif.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/m.svg b/share/extensions/alphabet_soup/m.svg new file mode 100644 index 0000000..ce3cd05 --- /dev/null +++ b/share/extensions/alphabet_soup/m.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/mcap.svg b/share/extensions/alphabet_soup/mcap.svg new file mode 100644 index 0000000..25c8c8a --- /dev/null +++ b/share/extensions/alphabet_soup/mcap.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/n.svg b/share/extensions/alphabet_soup/n.svg new file mode 100644 index 0000000..420042a --- /dev/null +++ b/share/extensions/alphabet_soup/n.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/o.svg b/share/extensions/alphabet_soup/o.svg new file mode 100644 index 0000000..3c08f07 --- /dev/null +++ b/share/extensions/alphabet_soup/o.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/ocap.svg b/share/extensions/alphabet_soup/ocap.svg new file mode 100644 index 0000000..1c50fd2 --- /dev/null +++ b/share/extensions/alphabet_soup/ocap.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/question.svg b/share/extensions/alphabet_soup/question.svg new file mode 100644 index 0000000..ddecb15 --- /dev/null +++ b/share/extensions/alphabet_soup/question.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/r.svg b/share/extensions/alphabet_soup/r.svg new file mode 100644 index 0000000..bb2273a --- /dev/null +++ b/share/extensions/alphabet_soup/r.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/rcap.svg b/share/extensions/alphabet_soup/rcap.svg new file mode 100644 index 0000000..e835f50 --- /dev/null +++ b/share/extensions/alphabet_soup/rcap.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/s.svg b/share/extensions/alphabet_soup/s.svg new file mode 100644 index 0000000..dc7c6fc --- /dev/null +++ b/share/extensions/alphabet_soup/s.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/serif.svg b/share/extensions/alphabet_soup/serif.svg new file mode 100644 index 0000000..d2bf55e --- /dev/null +++ b/share/extensions/alphabet_soup/serif.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/t.svg b/share/extensions/alphabet_soup/t.svg new file mode 100644 index 0000000..7940a68 --- /dev/null +++ b/share/extensions/alphabet_soup/t.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/share/extensions/alphabet_soup/tserif.svg b/share/extensions/alphabet_soup/tserif.svg new file mode 100644 index 0000000..f04cfcf --- /dev/null +++ b/share/extensions/alphabet_soup/tserif.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/v.svg b/share/extensions/alphabet_soup/v.svg new file mode 100644 index 0000000..9175807 --- /dev/null +++ b/share/extensions/alphabet_soup/v.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/vcap.svg b/share/extensions/alphabet_soup/vcap.svg new file mode 100644 index 0000000..792989e --- /dev/null +++ b/share/extensions/alphabet_soup/vcap.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/vserl.svg b/share/extensions/alphabet_soup/vserl.svg new file mode 100644 index 0000000..10e32d3 --- /dev/null +++ b/share/extensions/alphabet_soup/vserl.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/vserr.svg b/share/extensions/alphabet_soup/vserr.svg new file mode 100644 index 0000000..964ae97 --- /dev/null +++ b/share/extensions/alphabet_soup/vserr.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/x.svg b/share/extensions/alphabet_soup/x.svg new file mode 100644 index 0000000..f40b0a9 --- /dev/null +++ b/share/extensions/alphabet_soup/x.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/y.svg b/share/extensions/alphabet_soup/y.svg new file mode 100644 index 0000000..089cec9 --- /dev/null +++ b/share/extensions/alphabet_soup/y.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/yogh.svg b/share/extensions/alphabet_soup/yogh.svg new file mode 100644 index 0000000..6e6f19d --- /dev/null +++ b/share/extensions/alphabet_soup/yogh.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/alphabet_soup/z.svg b/share/extensions/alphabet_soup/z.svg new file mode 100644 index 0000000..7826d59 --- /dev/null +++ b/share/extensions/alphabet_soup/z.svg @@ -0,0 +1,10 @@ + + + + diff --git a/share/extensions/barcode/Base.py b/share/extensions/barcode/Base.py new file mode 100644 index 0000000..a4680a7 --- /dev/null +++ b/share/extensions/barcode/Base.py @@ -0,0 +1,166 @@ +# coding=utf-8 +# +# Copyright (C) 2010 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Base module for rendering barcodes for Inkscape. +""" + +import itertools +import sys + +from inkex import Group, TextElement, Rectangle + +(TEXT_POS_BOTTOM, TEXT_POS_TOP) = range(2) +(WHITE_BAR, BLACK_BAR, TALL_BAR) = range(3) +TEXT_TEMPLATE = 'font-size:%dpx;text-align:center;text-anchor:middle;' + +try: + from typing import Optional +except ImportError: + pass + +class Barcode(object): + """Provide a base class for all barcode renderers""" + default_height = 30 + font_size = 9 + name = None # type: Optional[str] + + def error(self, text, msg): + """Cause an error to be reported""" + sys.stderr.write( + "Error encoding '{}' as {} barcode: {}\n".format(text, self.name, msg)) + return "ERROR" + + def encode(self, text): + """ + Replace this with the encoding function, it should return + a string of ones and zeros + """ + raise NotImplementedError("You need to write an encode() function.") + + def __init__(self, param): + param = param or {} + self.document = param.get('document', None) + self.known_ids = [] + self._extra = [] + + self.pos_x = int(param.get('x', 0)) + self.pos_y = int(param.get('y', 0)) + self.text = param.get('text', None) + self.scale = param.get('scale', 1) + self.height = param.get('height', self.default_height) + self.pos_text = param.get('text_pos', TEXT_POS_BOTTOM) + + if self.document: + self.known_ids = list(self.document.xpath('//@id')) + + if not self.text: + raise ValueError("No string specified for barcode.") + + def get_id(self, name='element'): + """Get the next useful id (and claim it)""" + index = 0 + while name in self.known_ids: + index += 1 + name = 'barcode{:d}'.format(index) + self.known_ids.append(name) + return name + + def add_extra_barcode(self, barcode, **kw): + """Add an extra barcode along side this one, used for ean13 extras""" + from . import get_barcode + kw['height'] = self.height + kw['document'] = self.document + kw['scale'] = None + self._extra.append(get_barcode(barcode, **kw).generate()) + + def generate(self): + """Generate the actual svg from the coding""" + string = self.encode(self.text) + + if string == 'ERROR': + return + + name = self.get_id('barcode') + + # use an svg group element to contain the barcode + barcode = Group() + barcode.set('id', name) + barcode.set('style', 'fill: black;') + + barcode.transform.add_translate(self.pos_x, self.pos_y) + if self.scale: + barcode.transform.add_scale(self.scale) + + bar_id = 1 + bar_offset = 0 + tops = set() + + for datum in self.graphical_array(string): + # Datum 0 tells us what style of bar is to come next + style = self.get_style(int(datum[0])) + # Datum 1 tells us what width in units, + # style tells us how wide a unit is + width = int(datum[1]) * int(style['width']) + + if style['write']: + tops.add(style['top']) + rect = Rectangle() + rect.set('x', str(bar_offset)) + rect.set('y', str(style['top'])) + if self.pos_text == TEXT_POS_TOP: + rect.set('y', str(style['top'] + self.font_size)) + rect.set('id', "{}_bar{:d}".format(name, bar_id)) + rect.set('width', str(width)) + rect.set('height', str(style['height'])) + barcode.append(rect) + bar_offset += width + bar_id += 1 + + for extra in self._extra: + if extra is not None: + barcode.append(extra) + + bar_width = bar_offset + # Add text at the bottom of the barcode + text = TextElement() + text.set('x', str(int(bar_width / 2))) + text.set('y', str(min(tops) + self.font_size - 1)) + if self.pos_text == TEXT_POS_BOTTOM: + text.set('y', str(self.height + max(tops) + self.font_size)) + text.set('style', TEXT_TEMPLATE % self.font_size) + text.set('xml:space', 'preserve') + text.set('id', '{}_text'.format(name)) + text.text = str(self.text) + barcode.append(text) + return barcode + + def graphical_array(self, code): + """Converts black and white markets into a space array""" + return [(x, len(list(y))) for x, y in itertools.groupby(code)] + + def get_style(self, index): + """Returns the styles that should be applied to each bar""" + result = {'width': 1, 'top': 0, 'write': True} + if index == BLACK_BAR: + result['height'] = int(self.height) + if index == TALL_BAR: + result['height'] = int(self.height) + int(self.font_size / 2) + if index == WHITE_BAR: + result['write'] = False + return result diff --git a/share/extensions/barcode/BaseEan.py b/share/extensions/barcode/BaseEan.py new file mode 100644 index 0000000..d1c4352 --- /dev/null +++ b/share/extensions/barcode/BaseEan.py @@ -0,0 +1,158 @@ +# coding=utf-8 +# +# Copyright (C) 2010 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Some basic common code shared between EAN and UCP generators. +""" + +from .Base import Barcode, TEXT_POS_TOP + +try: + from typing import Optional, List, Dict +except ImportError: + pass + +MAPPING = [ + # Left side of barcode Family '0' + ["0001101", "0011001", "0010011", "0111101", "0100011", + "0110001", "0101111", "0111011", "0110111", "0001011"], + # Left side of barcode Family '1' and flipped to right side. + ["0100111", "0110011", "0011011", "0100001", "0011101", + "0111001", "0000101", "0010001", "0001001", "0010111"], +] +# This chooses which of the two encodings above to use. +FAMILIES = ('000000', '001011', '001101', '001110', '010011', + '011001', '011100', '010101', '010110', '011010') + + +class EanBarcode(Barcode): + """Simple base class for all EAN type barcodes""" + lengths = None # type: Optional[List[int]] + length = None # type: Optional[int] + checks = [] # type: List[int] + extras = {} # type: Dict[int, str] + magic = 10 + guard_bar = '202' + center_bar = '02020' + + def intarray(self, number): + """Convert a string of digits into an array of ints""" + return [int(i) for i in number] + + def encode_interleaved(self, family, number, fams=FAMILIES): + """Encode any side of the barcode, interleaved""" + result = [] + encset = self.intarray(fams[family]) + for i in range(len(number)): + thismap = MAPPING[encset[i]] + result.append(thismap[number[i]]) + return result + + def encode_right(self, number): + """Encode the right side of the barcode, non-interleaved""" + result = [] + for num in number: + # The right side is always the reverse of the left's family '1' + result.append(MAPPING[1][num][::-1]) + return result + + def encode_left(self, number): + """Encode the left side of the barcode, non-interleaved""" + result = [] + for num in number: + result.append(MAPPING[0][num]) + return result + + def space(self, *spacing): + """Space out an array of numbers""" + result = '' + for space in spacing: + if isinstance(space, list): + for i in space: + result += str(i) + elif isinstance(space, int): + result += ' ' * space + return result + + def get_lengths(self): + """Return a list of acceptable lengths""" + if self.length: + return [self.length] + return self.lengths[:] + + def encode(self, code): + """Encode any EAN barcode""" + code = code.replace(' ', '').strip() + guide = code.endswith('>') + code = code.strip('>') + + if not code.isdigit(): + return self.error(code, 'Not a Number, must be digits 0-9 only') + lengths = self.get_lengths() + self.checks + + # Allow extra barcodes after the first one + if len(code) not in lengths: + for extra in self.extras: + sep = len(code) - extra + if sep in lengths: + # Generate a barcode along side this one. + self.add_extra_barcode(self.extras[extra], text=code[sep:], + x=self.pos_x + 400 * self.scale, text_pos=TEXT_POS_TOP) + code = code[:sep] + + if len(code) not in lengths: + return self.error(code, 'Wrong size {:d}, must be {} digits'.format(len(code), ', '.join([str(length) for length in lengths]))) + + if self.checks: + if len(code) not in self.checks: + code = self.append_checksum(code) + elif not self.verify_checksum(code): + return self.error(code, 'Checksum failed, omit for new sum') + return self._encode(self.intarray(code), guide=guide) + + def _encode(self, num, guide=False): + """ + Write your EAN encoding function, it's passed in an array of int and + it should return a string on 1 and 0 for black and white parts + """ + raise NotImplementedError("_encode should be provided by parent EAN") + + def enclose(self, left, right=()): + """Standard Enclosure""" + parts = [self.guard_bar] + left + parts.append(self.center_bar) + parts += list(right) + [self.guard_bar] + return ''.join(parts) + + def get_checksum(self, num): + """Generate a UPCA/EAN13/EAN8 Checksum""" + # Left to right,checksum based on first digits. + total = sum([int(n) * (3, 1)[x % 2] for x, n in enumerate(num[::-1])]) + # Modulous result to a single digit checksum + checksum = self.magic - (total % self.magic) + if checksum < 0 or checksum >= self.magic: + return '0' + return str(checksum) + + def append_checksum(self, number): + """Apply the checksum to a short number""" + return number + self.get_checksum(number) + + def verify_checksum(self, number): + """Verify any checksum""" + return self.get_checksum(number[:-1]) == number[-1] diff --git a/share/extensions/barcode/Code128.py b/share/extensions/barcode/Code128.py new file mode 100644 index 0000000..20876b3 --- /dev/null +++ b/share/extensions/barcode/Code128.py @@ -0,0 +1,158 @@ +# coding=utf-8 +# +# Authored by Martin Owens +# Debugged by Ralf Heinecke & Martin Siepmann 2007-09-07 +# Horst Schottky 2010-02-27 +# +# Copyright (C) 2007 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Renderer for Code128/EAN128 codes. Designed for use with Inkscape. +""" + +import re + +from .Base import Barcode + +CODE_MAP = [ + '11011001100', '11001101100', '11001100110', '10010011000', '10010001100', + '10001001100', '10011001000', '10011000100', '10001100100', '11001001000', + '11001000100', '11000100100', '10110011100', '10011011100', '10011001110', + '10111001100', '10011101100', '10011100110', '11001110010', '11001011100', + '11001001110', '11011100100', '11001110100', '11101101110', '11101001100', + '11100101100', '11100100110', '11101100100', '11100110100', '11100110010', + '11011011000', '11011000110', '11000110110', '10100011000', '10001011000', + '10001000110', '10110001000', '10001101000', '10001100010', '11010001000', + '11000101000', '11000100010', '10110111000', '10110001110', '10001101110', + '10111011000', '10111000110', '10001110110', '11101110110', '11010001110', + '11000101110', '11011101000', '11011100010', '11011101110', '11101011000', + '11101000110', '11100010110', '11101101000', '11101100010', '11100011010', + '11101111010', '11001000010', '11110001010', '10100110000', '10100001100', + '10010110000', '10010000110', '10000101100', '10000100110', '10110010000', + '10110000100', '10011010000', '10011000010', '10000110100', '10000110010', + '11000010010', '11001010000', '11110111010', '11000010100', '10001111010', + '10100111100', '10010111100', '10010011110', '10111100100', '10011110100', + '10011110010', '11110100100', '11110010100', '11110010010', '11011011110', + '11011110110', '11110110110', '10101111000', '10100011110', '10001011110', + '10111101000', '10111100010', '11110101000', '11110100010', '10111011110', + '10111101110', '11101011110', '11110101110', '11010000100', '11010010000', + '11010011100', '11000111010', '11'] + + +def map_extra(data, chars): + """Maps the data into the chars""" + result = list(data) + for char in chars: + result.append(chr(char)) + result.append('FNC3') + result.append('FNC2') + result.append('SHIFT') + return result + + +# The map_extra method is used to slim down the amount +# of pre code and instead we generate the lists +CHAR_AB = list(" !\"#$%&\'()*+,-./0123456789:;<=>?@" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_") +CHAR_A = map_extra(CHAR_AB, range(0, 31)) # Offset 64 +CHAR_B = map_extra(CHAR_AB, range(96, 125)) # Offset -32 + + +class Code128(Barcode): + """Main barcode object, generates the encoding bits here""" + + def encode(self, text): + blocks = [] + block = '' + + # Split up into sections of numbers, or characters + # This makes sure that all the characters are encoded + # In the best way possible for Code128 + for datum in re.findall(r'(?:(?:\d\d){2,})|(?:^\d\d)|.', text): + if len(datum) == 1: + block = block + datum + else: + if block: + blocks.append(self.best_block(block)) + block = '' + blocks.append(['C', datum]) + + if block: + blocks.append(self.best_block(block)) + block = '' + + return self.encode_blocks(blocks) + + def best_block(self, block): + """If this has lower case then select B over A""" + if block.upper() == block: + return ['A', block] + return ['B', block] + + def encode_blocks(self, blocks): + """Encode the given blocks into A, B or C codes""" + encode = '' + total = 0 + pos = 0 + + for block in blocks: + b_set = block[0] + datum = block[1] + + # POS : 0, 1 + # A : 101, 103 + # B : 100, 104 + # C : 99, 105 + num = 0 + if b_set == 'A': + num = 103 + elif b_set == 'B': + num = 104 + elif b_set == 'C': + num = 105 + + i = pos + if pos: + num = 204 - num + else: + i = 1 + + total = total + num * i + encode = encode + CODE_MAP[num] + pos += 1 + + if b_set == 'A' or b_set == 'B': + chars = CHAR_B + if b_set == 'A': + chars = CHAR_A + + for char in datum: + total = total + (chars.index(char) * pos) + encode = encode + CODE_MAP[chars.index(char)] + pos += 1 + else: + for char in (datum[i:i + 2] for i in range(0, len(datum), 2)): + total = total + (int(char) * pos) + encode = encode + CODE_MAP[int(char)] + pos += 1 + + checksum = total % 103 + encode = encode + CODE_MAP[checksum] + encode = encode + CODE_MAP[106] + encode = encode + CODE_MAP[107] + + return encode diff --git a/share/extensions/barcode/Code25i.py b/share/extensions/barcode/Code25i.py new file mode 100644 index 0000000..e655f6c --- /dev/null +++ b/share/extensions/barcode/Code25i.py @@ -0,0 +1,69 @@ +# coding=utf-8 +# +# Copyright (C) 2010 Geoffrey Mosini +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Generate barcodes for Code25-interleaved 2 of 5, for Inkscape. +""" + +from .Base import Barcode + +# 1 means thick, 0 means thin +ENCODE = { + '0': '00110', + '1': '10001', + '2': '01001', + '3': '11000', + '4': '00101', + '5': '10100', + '6': '01100', + '7': '00011', + '8': '10010', + '9': '01010', +} + + +class Code25i(Barcode): + """Convert a text into string binary of black and white markers""" + + # Start and stop code are already encoded into white (0) and black(1) bars + def encode(self, number): + if not number.isdigit(): + return self.error(number, "CODE25 can only encode numbers.") + + # Number of figures to encode must be even, + # a 0 is added to the left in case it's odd. + if len(number) % 2 > 0: + number = '0' + number + + # Number is encoded by pairs of 2 figures + size = len(number) // 2 + encoded = '1010' + for i in range(size): + # First in the pair is encoded in black (1), second in white (0) + black = ENCODE[number[i * 2]] + white = ENCODE[number[i * 2 + 1]] + for j in range(5): + if black[j] == '1': + encoded += '11' + else: + encoded += '1' + if white[j] == '1': + encoded += '00' + else: + encoded += '0' + return encoded + '1101' diff --git a/share/extensions/barcode/Code39.py b/share/extensions/barcode/Code39.py new file mode 100644 index 0000000..a0ee2e2 --- /dev/null +++ b/share/extensions/barcode/Code39.py @@ -0,0 +1,98 @@ +# coding=utf-8 +# +# Copyright (C) 2007 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Python barcode renderer for Code39 barcodes. Designed for use with Inkscape. +""" + +from .Base import Barcode + +ENCODE = { + '0': '000110100', + '1': '100100001', + '2': '001100001', + '3': '101100000', + '4': '000110001', + '5': '100110000', + '6': '001110000', + '7': '000100101', + '8': '100100100', + '9': '001100100', + 'A': '100001001', + 'B': '001001001', + 'C': '101001000', + 'D': '000011001', + 'E': '100011000', + 'F': '001011000', + 'G': '000001101', + 'H': '100001100', + 'I': '001001100', + 'J': '000011100', + 'K': '100000011', + 'L': '001000011', + 'M': '101000010', + 'N': '000010011', + 'O': '100010010', + 'P': '001010010', + 'Q': '000000111', + 'R': '100000110', + 'S': '001000110', + 'T': '000010110', + 'U': '110000001', + 'V': '011000001', + 'W': '111000000', + 'X': '010010001', + 'Y': '110010000', + 'Z': '011010000', + '-': '010000101', + '*': '010010100', + '+': '010001010', + '$': '010101000', + '%': '000101010', + '/': '010100010', + '.': '110000100', + ' ': '011000100', +} + + +class Code39(Barcode): + """Convert a text into string binary of black and white markers""" + + def encode(self, text): + self.text = text.upper() + result = '' + # It is possible for us to encode code39 + # into full ascii, but this feature is + # not enabled here + for char in '*' + self.text + '*': + if char not in ENCODE: + char = '-' + result = result + ENCODE[char] + '0' + + # Now we need to encode the code39, best read + # the code to understand what it's up to: + encoded = '' + colour = '1' # 1 = Black, 0 = White + for data in result: + if data == '1': + encoded = encoded + colour + colour + else: + encoded = encoded + colour + colour = colour == '1' and '0' or '1' + + return encoded diff --git a/share/extensions/barcode/Code39Ext.py b/share/extensions/barcode/Code39Ext.py new file mode 100644 index 0000000..e9e804a --- /dev/null +++ b/share/extensions/barcode/Code39Ext.py @@ -0,0 +1,68 @@ +# coding=utf-8 +# +# Copyright (C) 2007 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Python barcode renderer for Code39 Extended barcodes. Designed for Inkscape. +""" + +from .Code39 import Code39 + +encode = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') + +map = {} + +i = 0 +for char in encode: + map[char] = i + i += 1 + + +# Extended encoding maps for full ASCII Code93 +def getMap(array): + result = {} + y = 0 + for x in array: + result[chr(x)] = encode[y] + y += 1 + + return result + + +# MapA is eclectic, but B, C, D are all ASCII ranges +mapA = getMap([27, 28, 29, 30, 31, 59, 60, 61, 62, 63, 91, 92, 93, 94, 95, 123, 124, 125, 126, 127, 0, 64, 96, 127, 127, 127]) # % +mapB = getMap(range(1, 26)) # $ +mapC = getMap(range(33, 58)) # / +mapD = getMap(range(97, 122)) # + + + +class Code39Ext(Code39): + def encode(self, text): + # We are only going to extend the Code39 barcodes + result = '' + for char in text: + if char in mapA: + char = '%' + mapA[char] + elif char in mapB: + char = '$' + mapB[char] + elif char in mapC: + char = '/' + mapC[char] + elif char in mapD: + char = '+' + mapD[char] + result = result + char + + return Code39.encode(self, result) diff --git a/share/extensions/barcode/Code93.py b/share/extensions/barcode/Code93.py new file mode 100644 index 0000000..e7af072 --- /dev/null +++ b/share/extensions/barcode/Code93.py @@ -0,0 +1,116 @@ +# coding=utf-8 +# +# Copyright (C) 2007 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Python barcode renderer for Code93 barcodes. Designed for use with Inkscape. +""" + +from .Base import Barcode + +PALLET = list('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%') +PALLET.append('($)') +PALLET.append('(/)') +PALLET.append('(+)') +PALLET.append('(%)') +PALLET.append('MARKER') + +MAP = dict((PALLET[i], i) for i in range(len(PALLET))) + + +def get_map(array): + """Extended ENCODE maps for full ASCII Code93""" + result = {} + pos = 10 + for char in array: + result[chr(char)] = PALLET[pos] + pos += 1 + return result + + +# MapA is eclectic, but B, C, D are all ASCII ranges +MAP_A = get_map([27, 28, 29, 30, 31, 59, 60, 61, 62, 63, 91, 92, 93, 94, 95, + 123, 124, 125, 126, 127, 0, 64, 96, 127, 127, 127]) # % +MAP_B = get_map(range(1, 26)) # $ +MAP_C = get_map(range(33, 58)) # / +MAP_D = get_map(range(97, 122)) # + + +ENCODE = [ + '100010100', '101001000', '101000100', '101000010', '100101000', + '100100100', '100100010', '101010000', '100010010', '100001010', + '110101000', '110100100', '110100010', '110010100', '110010010', + '110001010', '101101000', '101100100', '101100010', '100110100', + '100011010', '101011000', '101001100', '101000110', '100101100', + '100010110', '110110100', '110110010', '110101100', '110100110', + '110010110', '110011010', '101101100', '101100110', '100110110', + '100111010', '100101110', '111010100', '111010010', '111001010', + '101101110', '101110110', '110101110', '100100110', '111011010', + '111010110', '100110010', '101011110', '' +] + + +class Code93(Barcode): + def encode(self, text): + # start marker + bits = ENCODE[MAP.get('MARKER', -1)] + + # Extend to ASCII charset ( return Array ) + text = self.encode_ascii(text) + + # Calculate the checksums + text.append(self.checksum(text, 20)) # C + text.append(self.checksum(text, 15)) # K + + # Now convert text into the ENCODE bits (black and white stripes) + for char in text: + bits = bits + ENCODE[MAP.get(char, -1)] + + # end marker and termination bar + return bits + ENCODE[MAP.get('MARKER', -1)] + '1' + + def checksum(self, text, mod): + """Generate a code 93 checksum""" + weight = len(text) % mod + check = 0 + for char in text: + check = check + (MAP[char] * weight) + # Reset the weight is required + weight -= 1 + if weight == 0: + weight = mod + + return PALLET[check % 47] + + # Some characters need re-ENCODE into the code93 specification + def encode_ascii(self, text): + result = [] + for char in text: + if char in MAP: + result.append(char) + elif char in MAP_A: + result.append('(%)') + result.append(MAP_A[char]) + elif char in MAP_B: + result.append('($)') + result.append(MAP_B[char]) + elif char in MAP_C: + result.append('(/)') + result.append(MAP_C[char]) + elif char in MAP_D: + result.append('(+)') + result.append(MAP_D[char]) + return result diff --git a/share/extensions/barcode/Ean13.py b/share/extensions/barcode/Ean13.py new file mode 100644 index 0000000..2f41a9c --- /dev/null +++ b/share/extensions/barcode/Ean13.py @@ -0,0 +1,43 @@ +# coding=utf-8 +# +# Copyright (C) 2010 Martin Owens +# +# Thanks to Lineaire Chez of Inkbar ( www.inkbar.lineaire.net ) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Python barcode renderer for EAN13 barcodes. Designed for use with Inkscape. +""" + +from .BaseEan import EanBarcode + + +class Ean13(EanBarcode): + """Provide an Ean13 barcode generator""" + name = 'ean13' + extras = {2: 'Ean2', 5: 'Ean5'} + checks = [13] + lengths = [12] + + def _encode(self, num, guide=False): + """Encode an ean13 barcode""" + self.text = self.space(num[0:1], 4, num[1:7], 5, num[7:], 7) + if guide: + self.text = self.text[:-4] + '>' + return self.enclose( + self.encode_interleaved(num[0], num[1:7]), + self.encode_right(num[7:]) + ) diff --git a/share/extensions/barcode/Ean2.py b/share/extensions/barcode/Ean2.py new file mode 100644 index 0000000..08c990a --- /dev/null +++ b/share/extensions/barcode/Ean2.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# +# Copyright (C) 2016 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Python barcode renderer for EAN2 barcodes. Designed for use with Inkscape. +""" + +from .BaseEan import EanBarcode + +FAMS = ['00', '01', '10', '11'] +START = '01011' + + +class Ean2(EanBarcode): + """Provide an Ean5 barcode generator""" + length = 2 + name = 'ean5' + + def _encode(self, num, guide=False): + if len(num) != 2: + num = ([0, 0] + num)[-2:] + self.text = ' '.join(self.space(num)) + family = ((num[0] * 10) + num[1]) % 4 + return START + '01'.join(self.encode_interleaved(family, num, FAMS)) diff --git a/share/extensions/barcode/Ean5.py b/share/extensions/barcode/Ean5.py new file mode 100644 index 0000000..074c970 --- /dev/null +++ b/share/extensions/barcode/Ean5.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# +# Copyright (C) 2009 Aaron C Spike +# 2010 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Python barcode renderer for EAN5 barcodes. Designed for use with Inkscape. +""" + +from .BaseEan import EanBarcode + +FAMS = ['11000', '10100', '10010', '10001', '01100', + '00110', '00011', '01010', '01001', '00101'] +START = '01011' + + +class Ean5(EanBarcode): + """Provide an Ean5 barcode generator""" + name = 'ean5' + length = 5 + + def _encode(self, num, guide=False): + self.text = ' '.join(self.space(num)) + family = sum([int(n) * int(m) for n, m in zip(num, '39393')]) % 10 + return START + '01'.join(self.encode_interleaved(family, num, FAMS)) diff --git a/share/extensions/barcode/Ean8.py b/share/extensions/barcode/Ean8.py new file mode 100644 index 0000000..d8fb2d8 --- /dev/null +++ b/share/extensions/barcode/Ean8.py @@ -0,0 +1,38 @@ +# coding=utf-8 +# +# Copyright (C) 2010 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Python barcode renderer for EAN8 barcodes. Designed for use with Inkscape. +""" + +from .BaseEan import EanBarcode + + +class Ean8(EanBarcode): + """Provide an EAN8 barcode generator""" + name = 'ean8' + checks = [8] + lengths = [7] + + def _encode(self, num, guide=False): + """Encode an ean8 barcode""" + self.text = self.space(num[:4], 3, num[4:]) + return self.enclose( + self.encode_left(num[:4]), + self.encode_right(num[4:]) + ) diff --git a/share/extensions/barcode/Rm4scc.py b/share/extensions/barcode/Rm4scc.py new file mode 100644 index 0000000..5687f63 --- /dev/null +++ b/share/extensions/barcode/Rm4scc.py @@ -0,0 +1,136 @@ +# coding=utf-8 +# +# Copyright (C) 2007 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Python barcode renderer for RM4CC barcodes. Designed for use with Inkscape. +""" + +from .Base import Barcode + +map = { + '(': '25', + ')': '3', + '0': '05053535', + '1': '05152535', + '2': '05153525', + '3': '15052535', + '4': '15053525', + '5': '15152525', + '6': '05251535', + '7': '05350535', + '8': '05351525', + '9': '15250535', + 'A': '15251525', + 'B': '15350525', + 'C': '05253515', + 'D': '05352515', + 'E': '05353505', + 'F': '15252515', + 'G': '15253505', + 'H': '15352505', + 'I': '25051535', + 'J': '25150535', + 'K': '25151525', + 'L': '35050535', + 'M': '35051525', + 'N': '35150525', + 'O': '25053525', + 'P': '25152515', + 'Q': '25153505', + 'R': '35052515', + 'S': '35053505', + 'T': '35152505', + 'U': '25251515', + 'V': '25350515', + 'W': '25351505', + 'X': '35250515', + 'Y': '35251505', + 'Z': '35350505', +} + +check = ['ZUVWXY', '501234', 'B6789A', 'HCDEFG', 'NIJKLM', 'TOPQRS'] +(BAR_TRACK, BAR_DOWN, BAR_UP, BAR_FULL, BAR_NONE, WHITE_SPACE) = range(6) + + +class Rm4scc(Barcode): + default_height = 18 + + def encode(self, text): + result = '' + + text = text.upper() + text.replace('(', '') + text.replace(')', '') + + text = '(' + text + self.checksum(text) + ')' + + i = 0 + for char in text: + if char in map: + result = result + map[char] + i += 1 + + return result + + # given a string of data, return the check character + def checksum(self, text): + total_lower = 0 + total_upper = 0 + for char in text: + if char in map: + bars = map[char][0:8:2] + lower = 0 + upper = 0 + + if int(bars[0]) & 1: + lower += 4 + if int(bars[1]) & 1: + lower += 2 + if int(bars[2]) & 1: + lower += 1 + if int(bars[0]) & 2: + upper += 4 + if int(bars[1]) & 2: + upper += 2 + if int(bars[2]) & 2: + upper += 1 + total_lower += lower % 6 + total_upper += upper % 6 + + total_lower = total_upper % 6 + total_upper %= 6 + + checkchar = check[total_upper][total_lower] + return checkchar + + def get_style(self, index): + """Royal Mail Barcodes use a completely different style""" + result = {'width': 2, 'write': True, 'top': 0} + if index == BAR_TRACK: # Track Bar + result['top'] = 6 + result['height'] = 5 + elif index == BAR_DOWN: # Decender Bar + result['top'] = 6 + result['height'] = 11 + elif index == BAR_UP: # Accender Bar + result['height'] = 11 + elif index == BAR_FULL: # Full Bar + result['height'] = 17 + elif index == WHITE_SPACE: # White Space + result['write'] = False + return result diff --git a/share/extensions/barcode/Upca.py b/share/extensions/barcode/Upca.py new file mode 100644 index 0000000..77cb415 --- /dev/null +++ b/share/extensions/barcode/Upca.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# +# Copyright (C) 2007 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Python barcode renderer for UPCA barcodes. Designed for use with Inkscape. +""" + +from .BaseEan import EanBarcode + + +class Upca(EanBarcode): + """Provides a renderer for EAN12 aka UPC-A Barcodes""" + name = 'upca' + font_size = 10 + lengths = [11] + checks = [12] + + def _encode(self, num, guide=False): + """Encode for a UPC-A Barcode""" + self.text = self.space(num[0:1], 3, num[1:6], 4, num[6:11], 3, num[11:]) + return self.enclose( + self.encode_left(num[0:6]), + self.encode_right(num[6:12]), + ) diff --git a/share/extensions/barcode/Upce.py b/share/extensions/barcode/Upce.py new file mode 100644 index 0000000..e341bdd --- /dev/null +++ b/share/extensions/barcode/Upce.py @@ -0,0 +1,100 @@ +# coding=utf-8 +# +# Copyright (C) 2010 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Python barcode renderer for UPCE barcodes. Designed for use with Inkscape. +""" + +from .BaseEan import EanBarcode + +# This is almost exactly the same as the standard FAMILIES +# But flipped around and with the first 111000 instead of 000000. +FAMS = ['111000', '110100', '110010', '110001', '101100', + '100110', '100011', '101010', '101001', '100101'] + + +class Upce(EanBarcode): + """Generate EAN6/UPC-E barcode generator""" + name = 'upce' + font_size = 10 + lengths = [6, 11] + checks = [7, 12] + center_bar = '020' + + def _encode(self, num, guide=False): + """Generate a UPC-E Barcode""" + self.text = self.space(['0'], 2, num[:6], 2, num[-1]) + code = self.encode_interleaved(num[-1], num[:6], FAMS) + return self.enclose(code) + + def append_checksum(self, number): + """Generate a UPCE Checksum""" + if len(number) == 6: + number = self.convert_e2a(number) + result = self.get_checksum(number) + return self.convert_a2e(number) + result + + def convert_a2e(self, number): + """Converting UPC-A to UPC-E, may cause errors.""" + # All UPC-E Numbers use number system 0 + if number[0] != '0' or len(number) != 11: + # If not then the code is invalid + raise ValueError("Invalid UPC Number") + + # Most of the conversions deal + # with the specific code parts + maker = number[1:6] + product = number[6:11] + + # There are 4 cases to convert: + if maker[2:] == '000' or maker[2:] == '100' or maker[2:] == '200': + # Maximum number product code digits can be encoded + if product[:2] == '00': + return maker[:2] + product[2:] + maker[2] + elif maker[3:5] == '00': + # Now only 2 product code digits can be used + if product[:3] == '000': + return maker[:3] + product[3:] + '3' + elif maker[4] == '0': + # With even more maker code we have less room for product code + if product[:4] == '0000': + return maker[0:4] + product[4] + '4' + elif product[:4] == '0000' and int(product[4]) > 4: + # The last recorse is to try and squeeze it in the last 5 numbers + # so long as the product is 00005-00009 so as not to conflict with + # the 0-4 used above. + return maker + product[4] + else: + # Invalid UPC-A Numbe + raise ValueError("Invalid UPC Number") + + def convert_e2a(self, number): + """Convert UPC-E to UPC-A by padding with zeros""" + # It's more likly to convert this without fault + # But we still must be mindful of the 4 conversions + if len(number) != 6: + return None + + if number[5] in ['0', '1', '2']: + return '0' + number[:2] + number[5] + '0000' + number[2:5] + elif number[5] == '3': + return '0' + number[:3] + '00000' + number[3:5] + elif number[5] == '4': + return '0' + number[:4] + '00000' + number[4] + else: + return '0' + number[:5] + '0000' + number[5] diff --git a/share/extensions/barcode/__init__.py b/share/extensions/barcode/__init__.py new file mode 100644 index 0000000..f68d947 --- /dev/null +++ b/share/extensions/barcode/__init__.py @@ -0,0 +1,73 @@ +# coding=utf-8 +# +# Copyright (C) 2014 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=no-self-use +""" +Renderer for barcodes, SVG extension for Inkscape. + +For supported barcodes see Barcode module directory. +""" + + +# This lists all known Barcodes missing from this package +# ===== UPC-Based Extensions ====== # +# Code11 +# ========= Code25-Based ========== # +# Codabar +# Postnet +# ITF25 +# ========= Alpha-numeric ========= # +# Code39Mod +# USPS128 +# =========== 2D Based ============ # +# PDF417 +# PDF417-Macro +# PDF417-Truncated +# PDF417-GLI + +class NoBarcode(object): + """Simple class for no barcode""" + + def __init__(self, msg): + self.msg = msg + + def encode(self, text): + """Encode the text into a barcode pattern""" + raise ValueError("No barcode encoder: {}".format(self.msg)) + + def generate(self): + """Generate actual svg from the barcode pattern""" + return None + + +def get_barcode(code, **kw): + """Gets a barcode from a list of available barcode formats""" + if not code: + return NoBarcode("No barcode format given.") + + code = str(code).replace('-', '').strip() + module = 'barcode.' + code + lst = ['barcode'] + try: + return getattr(__import__(module, fromlist=lst), code)(kw) + except ImportError as err: + if code in str(err): + return NoBarcode("Invalid type of barcode: {}.{}".format(module, code)) + raise + except AttributeError: + return NoBarcode("Barcode module is missing barcode class: {}.{}".format(module, code)) diff --git a/share/extensions/barcode/__pycache__/Base.cpython-39.pyc b/share/extensions/barcode/__pycache__/Base.cpython-39.pyc new file mode 100644 index 0000000..aa8da7a Binary files /dev/null and b/share/extensions/barcode/__pycache__/Base.cpython-39.pyc differ diff --git a/share/extensions/barcode/__pycache__/BaseEan.cpython-39.pyc b/share/extensions/barcode/__pycache__/BaseEan.cpython-39.pyc new file mode 100644 index 0000000..28ad0ad Binary files /dev/null and b/share/extensions/barcode/__pycache__/BaseEan.cpython-39.pyc differ diff --git a/share/extensions/barcode/__pycache__/Code128.cpython-39.pyc b/share/extensions/barcode/__pycache__/Code128.cpython-39.pyc new file mode 100644 index 0000000..da22eb5 Binary files /dev/null and b/share/extensions/barcode/__pycache__/Code128.cpython-39.pyc differ diff --git a/share/extensions/barcode/__pycache__/Code25i.cpython-39.pyc b/share/extensions/barcode/__pycache__/Code25i.cpython-39.pyc new file mode 100644 index 0000000..fc11498 Binary files /dev/null and b/share/extensions/barcode/__pycache__/Code25i.cpython-39.pyc differ diff --git a/share/extensions/barcode/__pycache__/Code39.cpython-39.pyc b/share/extensions/barcode/__pycache__/Code39.cpython-39.pyc new file mode 100644 index 0000000..fd4bab3 Binary files /dev/null and b/share/extensions/barcode/__pycache__/Code39.cpython-39.pyc differ diff --git a/share/extensions/barcode/__pycache__/Code39Ext.cpython-39.pyc b/share/extensions/barcode/__pycache__/Code39Ext.cpython-39.pyc new file mode 100644 index 0000000..dac67f3 Binary files /dev/null and b/share/extensions/barcode/__pycache__/Code39Ext.cpython-39.pyc differ diff --git a/share/extensions/barcode/__pycache__/Code93.cpython-39.pyc b/share/extensions/barcode/__pycache__/Code93.cpython-39.pyc new file mode 100644 index 0000000..d03fac4 Binary files /dev/null and b/share/extensions/barcode/__pycache__/Code93.cpython-39.pyc differ diff --git a/share/extensions/barcode/__pycache__/Ean13.cpython-39.pyc b/share/extensions/barcode/__pycache__/Ean13.cpython-39.pyc new file mode 100644 index 0000000..e69a658 Binary files /dev/null and b/share/extensions/barcode/__pycache__/Ean13.cpython-39.pyc differ diff --git a/share/extensions/barcode/__pycache__/Ean2.cpython-39.pyc b/share/extensions/barcode/__pycache__/Ean2.cpython-39.pyc new file mode 100644 index 0000000..d8e9cf9 Binary files /dev/null and b/share/extensions/barcode/__pycache__/Ean2.cpython-39.pyc differ diff --git a/share/extensions/barcode/__pycache__/Ean5.cpython-39.pyc b/share/extensions/barcode/__pycache__/Ean5.cpython-39.pyc new file mode 100644 index 0000000..9d24001 Binary files /dev/null and b/share/extensions/barcode/__pycache__/Ean5.cpython-39.pyc differ diff --git a/share/extensions/barcode/__pycache__/Ean8.cpython-39.pyc b/share/extensions/barcode/__pycache__/Ean8.cpython-39.pyc new file mode 100644 index 0000000..8dad384 Binary files /dev/null and b/share/extensions/barcode/__pycache__/Ean8.cpython-39.pyc differ diff --git a/share/extensions/barcode/__pycache__/Rm4scc.cpython-39.pyc b/share/extensions/barcode/__pycache__/Rm4scc.cpython-39.pyc new file mode 100644 index 0000000..bbc1a35 Binary files /dev/null and b/share/extensions/barcode/__pycache__/Rm4scc.cpython-39.pyc differ diff --git a/share/extensions/barcode/__pycache__/Upca.cpython-39.pyc b/share/extensions/barcode/__pycache__/Upca.cpython-39.pyc new file mode 100644 index 0000000..74d648e Binary files /dev/null and b/share/extensions/barcode/__pycache__/Upca.cpython-39.pyc differ diff --git a/share/extensions/barcode/__pycache__/Upce.cpython-39.pyc b/share/extensions/barcode/__pycache__/Upce.cpython-39.pyc new file mode 100644 index 0000000..888e4a7 Binary files /dev/null and b/share/extensions/barcode/__pycache__/Upce.cpython-39.pyc differ diff --git a/share/extensions/barcode/__pycache__/__init__.cpython-39.pyc b/share/extensions/barcode/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..3b09d89 Binary files /dev/null and b/share/extensions/barcode/__pycache__/__init__.cpython-39.pyc differ diff --git a/share/extensions/color_HSL_adjust.inx b/share/extensions/color_HSL_adjust.inx new file mode 100644 index 0000000..d5ac33d --- /dev/null +++ b/share/extensions/color_HSL_adjust.inx @@ -0,0 +1,34 @@ + + + HSL Adjust + org.inkscape.color.hsl_adjust + + + 0 + false + 0 + false + 0 + false + + + + + + + all + + + + + + diff --git a/share/extensions/color_HSL_adjust.py b/share/extensions/color_HSL_adjust.py new file mode 100755 index 0000000..303b1f3 --- /dev/null +++ b/share/extensions/color_HSL_adjust.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +"""Adjust all the HSL values""" + +import random +import inkex + +class HslAdjust(inkex.ColorExtension): + """Modify the HSL levels of each color""" + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("-x", "--hue", type=int, default=0, help="Adjust hue") + pars.add_argument("-s", "--saturation", type=int, default=0, help="Adjust saturation") + pars.add_argument("-l", "--lightness", type=int, default=0, help="Adjust lightness") + pars.add_argument("--random_h", type=inkex.Boolean, dest="random_hue") + pars.add_argument("--random_s", type=inkex.Boolean, dest="random_saturation") + pars.add_argument("--random_l", type=inkex.Boolean, dest="random_lightness") + + def modify_color(self, name, color): + if self.options.random_hue: + color.hue = int(random.random() * 255.0) + elif self.options.hue: + color.hue += (self.options.hue * 2.55) + + if self.options.random_saturation: + color.saturation = int(random.random() * 255.0) + elif self.options.saturation: + color.saturation += (self.options.saturation * 2.55) + + if self.options.random_lightness: + color.lightness = int(random.random() * 255.0) + elif self.options.lightness: + color.lightness += (self.options.lightness * 2.55) + + return color + +if __name__ == '__main__': + HslAdjust().run() diff --git a/share/extensions/color_blackandwhite.inx b/share/extensions/color_blackandwhite.inx new file mode 100644 index 0000000..71dec01 --- /dev/null +++ b/share/extensions/color_blackandwhite.inx @@ -0,0 +1,15 @@ + + + Black and White + org.inkscape.color.black_and_white + 127 + + all + + + + + + diff --git a/share/extensions/color_blackandwhite.py b/share/extensions/color_blackandwhite.py new file mode 100755 index 0000000..2af6a61 --- /dev/null +++ b/share/extensions/color_blackandwhite.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +"""To black and white""" + +import inkex + +class BlackAndWhite(inkex.ColorExtension): + """Convert colours to black and white""" + def add_arguments(self, pars): + pars.add_argument("-t", "--threshold", type=int, default=127, help="Threshold Color Level") + + def modify_color(self, name, color): + # ITU-R Recommendation BT.709 (NTSC and PAL) + # l = 0.2125 * r + 0.7154 * g + 0.0721 * b + lum = 0.299 * color.red + 0.587 * color.green + 0.114 * color.blue + grey = 255 if lum > self.options.threshold else 0 + return inkex.Color((grey, grey, grey)) + +if __name__ == '__main__': + BlackAndWhite().run() diff --git a/share/extensions/color_brighter.inx b/share/extensions/color_brighter.inx new file mode 100644 index 0000000..6571e7c --- /dev/null +++ b/share/extensions/color_brighter.inx @@ -0,0 +1,14 @@ + + + Brighter + org.inkscape.color.brighter + + all + + + + + + diff --git a/share/extensions/color_brighter.py b/share/extensions/color_brighter.py new file mode 100755 index 0000000..be70897 --- /dev/null +++ b/share/extensions/color_brighter.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +"""Lighten colours of selected objects""" + +import inkex + +class Brighter(inkex.ColorExtension): + """Make colours brighter""" + def modify_color(self, name, color): + factor = 0.9 + contra = int(1 / (1 - factor)) + if color.space == 'hsl': + color.lightness = min(int(round(color.lightness / factor)), 255) + elif color.red == 0 and color.green == 0 and color.blue == 0: + color.red = contra + color.green = contra + color.blue = contra + else: + color.red = min(int(round(color.red / factor)), 255) + color.green = min(int(round(color.green / factor)), 255) + color.blue = min(int(round(color.blue / factor)), 255) + return color + +if __name__ == '__main__': + Brighter().run() diff --git a/share/extensions/color_custom.inx b/share/extensions/color_custom.inx new file mode 100644 index 0000000..77328a6 --- /dev/null +++ b/share/extensions/color_custom.inx @@ -0,0 +1,36 @@ + + + Custom + org.inkscape.color.custom + + + r + g + b + + + + + + + + + + + all + + + + + + diff --git a/share/extensions/color_custom.py b/share/extensions/color_custom.py new file mode 100755 index 0000000..43d3204 --- /dev/null +++ b/share/extensions/color_custom.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +import ast +import operator as op +import inkex + +OPS = {ast.Add: op.add, + ast.Sub: op.sub, + ast.Mult: op.mul, + ast.Div: op.truediv, + ast.Pow: op.pow, + ast.BitXor: op.xor, + ast.USub: op.neg} + +def eval_expr(expr, namespace): + """ + >>> eval_expr('2^6') + 4 + >>> eval_expr('2**6') + 64 + >>> eval_expr('1 + 2*3**(4^5) / (6 + -7)') + -5.0 + """ + return _eval(ast.parse(expr, mode='eval').body, namespace) + +def _eval(node, namespace): + if isinstance(node, ast.Num): # + return node.n + elif isinstance(node, ast.Name): # (must be in namespace) + return namespace[node.id] + elif isinstance(node, ast.BinOp): # + return OPS[type(node.op)](_eval(node.left, namespace), _eval(node.right, namespace)) + elif isinstance(node, ast.UnaryOp): # e.g., -1 + return OPS[type(node.op)](_eval(node.operand, namespace)) + else: + raise TypeError(node) + +class Custom(inkex.ColorExtension): + """Custom colour functions per channel""" + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("-r", "--r", default="r", help="red channel function") + pars.add_argument("-g", "--g", default="g", help="green channel function") + pars.add_argument("-b", "--b", default="b", help="blue channel function") + pars.add_argument("-s", "--scale", type=float, default=1.0, help="The input (r,g,b) range") + + def modify_color(self, name, color): + opt = self.options + factor = 255.0 / opt.scale + # add stuff to be accessible from within the custom color function here. + namespace = { + 'r': float(color.red) / factor, + 'g': float(color.green) / factor, + 'b': float(color.blue) / factor} + + color.red = int(eval_expr(opt.r.strip(), namespace) * factor) + color.green = int(eval_expr(opt.g.strip(), namespace) * factor) + color.blue = int(eval_expr(opt.b.strip(), namespace) * factor) + return color + +if __name__ == '__main__': + Custom().run() diff --git a/share/extensions/color_darker.inx b/share/extensions/color_darker.inx new file mode 100644 index 0000000..eb2d8ce --- /dev/null +++ b/share/extensions/color_darker.inx @@ -0,0 +1,14 @@ + + + Darker + org.inkscape.color.darker + + all + + + + + + diff --git a/share/extensions/color_darker.py b/share/extensions/color_darker.py new file mode 100755 index 0000000..6c57d78 --- /dev/null +++ b/share/extensions/color_darker.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +"""Darken colours of selected objects""" + +import inkex + +class Darker(inkex.ColorExtension): + """Make the colours darker""" + def modify_color(self, name, color): + factor = 0.9 + if color.space == 'hsl': + color.lightness = int(round(max(color.lightness * factor, 0))) + else: + color.red = int(round(max(color.red * factor, 0))) + color.green = int(round(max(color.green * factor, 0))) + color.blue = int(round(max(color.blue * factor, 0))) + return color + +if __name__ == '__main__': + Darker().run() diff --git a/share/extensions/color_desaturate.inx b/share/extensions/color_desaturate.inx new file mode 100644 index 0000000..3f52ca1 --- /dev/null +++ b/share/extensions/color_desaturate.inx @@ -0,0 +1,14 @@ + + + Desaturate + org.inkscape.color.desaturate + + all + + + + + + diff --git a/share/extensions/color_desaturate.py b/share/extensions/color_desaturate.py new file mode 100755 index 0000000..fa79d8a --- /dev/null +++ b/share/extensions/color_desaturate.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +"""Remove colors""" + +import inkex + +class Desaturate(inkex.ColorExtension): + """Remove color but maintain intesity""" + def modify_color(self, name, color): + lum = (max(color.red, color.green, color.blue) \ + + min(color.red, color.green, color.blue)) // 2 + return inkex.Color((int(round(lum)), int(round(lum)), int(round(lum)))) + +if __name__ == '__main__': + Desaturate().run() diff --git a/share/extensions/color_grayscale.inx b/share/extensions/color_grayscale.inx new file mode 100644 index 0000000..0d0a98a --- /dev/null +++ b/share/extensions/color_grayscale.inx @@ -0,0 +1,14 @@ + + + Grayscale + org.inkscape.color.grayscale + + all + + + + + + diff --git a/share/extensions/color_grayscale.py b/share/extensions/color_grayscale.py new file mode 100755 index 0000000..367b4e1 --- /dev/null +++ b/share/extensions/color_grayscale.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +"""Convert to grey""" + +import inkex + +class Grayscale(inkex.ColorExtension): + """Make all colours grayscale""" + def modify_color(self, name, color): + # ITU-R Recommendation BT.709 (NTSC and PAL) + # l = 0.2125 * r + 0.7154 * g + 0.0721 * b + lum = 0.299 * color.red + 0.587 * color.green + 0.114 * color.blue + return inkex.Color((int(round(lum)), int(round(lum)), int(round(lum)))) + +if __name__ == '__main__': + Grayscale().run() diff --git a/share/extensions/color_lesshue.inx b/share/extensions/color_lesshue.inx new file mode 100644 index 0000000..487e972 --- /dev/null +++ b/share/extensions/color_lesshue.inx @@ -0,0 +1,14 @@ + + + Less Hue + org.inkscape.color.less_hue + + all + + + + + + diff --git a/share/extensions/color_lesshue.py b/share/extensions/color_lesshue.py new file mode 100755 index 0000000..62c2d8d --- /dev/null +++ b/share/extensions/color_lesshue.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +"""Reduce hue""" + +import inkex + +class LessHue(inkex.ColorExtension): + """Remove Hue from the color""" + def modify_color(self, name, color): + color.hue -= int(0.05 * 255) + return color + +if __name__ == '__main__': + LessHue().run() diff --git a/share/extensions/color_lesslight.inx b/share/extensions/color_lesslight.inx new file mode 100644 index 0000000..5f24539 --- /dev/null +++ b/share/extensions/color_lesslight.inx @@ -0,0 +1,14 @@ + + + Less Light + org.inkscape.color.less_light + + all + + + + + + diff --git a/share/extensions/color_lesslight.py b/share/extensions/color_lesslight.py new file mode 100755 index 0000000..10a42fe --- /dev/null +++ b/share/extensions/color_lesslight.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +"""Reduce lightness""" + +import inkex + +class LessLight(inkex.ColorExtension): + """Reduce the light of the color""" + def modify_color(self, name, color): + color.lightness -= int(0.05 * 255) + return color + +if __name__ == '__main__': + LessLight().run() diff --git a/share/extensions/color_lesssaturation.inx b/share/extensions/color_lesssaturation.inx new file mode 100644 index 0000000..6e31086 --- /dev/null +++ b/share/extensions/color_lesssaturation.inx @@ -0,0 +1,14 @@ + + + Less Saturation + org.inkscape.color.less_saturation + + all + + + + + + diff --git a/share/extensions/color_lesssaturation.py b/share/extensions/color_lesssaturation.py new file mode 100755 index 0000000..924efd3 --- /dev/null +++ b/share/extensions/color_lesssaturation.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +"""Reduce saturation""" + +import inkex + +class LessSaturation(inkex.ColorExtension): + """Make colours less saturated""" + def modify_color(self, name, color): + color.saturation -= int(0.05 * 255) + return color + +if __name__ == '__main__': + LessSaturation().run() diff --git a/share/extensions/color_list.inx b/share/extensions/color_list.inx new file mode 100644 index 0000000..f5c5d4a --- /dev/null +++ b/share/extensions/color_list.inx @@ -0,0 +1,14 @@ + + + List All + org.inkscape.color.list_colours + + all + + + + + + diff --git a/share/extensions/color_list.py b/share/extensions/color_list.py new file mode 100755 index 0000000..f86db22 --- /dev/null +++ b/share/extensions/color_list.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""List all colors used in an svg document""" + +from collections import defaultdict +import inkex + +class ListColours(inkex.ColorExtension): + """Make the colours darker""" + _counts = defaultdict(int) + + def effect(self): + super(ListColours, self).effect() + items = sorted(self._counts.items(), key=lambda v: -v[1]) + for color, count in items: + self.msg("{count}: {color}".format(color=color, count=count)) + + def modify_color(self, name, color): + self._counts[color] += 1 + return color + +if __name__ == '__main__': + ListColours().run() diff --git a/share/extensions/color_morehue.inx b/share/extensions/color_morehue.inx new file mode 100644 index 0000000..d10de1b --- /dev/null +++ b/share/extensions/color_morehue.inx @@ -0,0 +1,14 @@ + + + More Hue + org.inkscape.color.more_hue + + all + + + + + + diff --git a/share/extensions/color_morehue.py b/share/extensions/color_morehue.py new file mode 100755 index 0000000..85f4e15 --- /dev/null +++ b/share/extensions/color_morehue.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +"""Add more hue""" + +import inkex + +class MoreHue(inkex.ColorExtension): + """Add hue to any selected object""" + def modify_color(self, name, color): + color.hue += int(0.05 * 255.0) + return color + +if __name__ == '__main__': + MoreHue().run() diff --git a/share/extensions/color_morelight.inx b/share/extensions/color_morelight.inx new file mode 100644 index 0000000..5f5630d --- /dev/null +++ b/share/extensions/color_morelight.inx @@ -0,0 +1,14 @@ + + + More Light + org.inkscape.color.more_light + + all + + + + + + diff --git a/share/extensions/color_morelight.py b/share/extensions/color_morelight.py new file mode 100755 index 0000000..4143640 --- /dev/null +++ b/share/extensions/color_morelight.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +"""Increase lightness""" + +import inkex + +class MoreLight(inkex.ColorExtension): + """Lighten selected objects""" + def modify_color(self, name, color): + color.lightness += int(0.05 * 255.0) + return color + +if __name__ == '__main__': + MoreLight().run() diff --git a/share/extensions/color_moresaturation.inx b/share/extensions/color_moresaturation.inx new file mode 100644 index 0000000..3305728 --- /dev/null +++ b/share/extensions/color_moresaturation.inx @@ -0,0 +1,14 @@ + + + More Saturation + org.inkscape.color.more_saturation + + all + + + + + + diff --git a/share/extensions/color_moresaturation.py b/share/extensions/color_moresaturation.py new file mode 100755 index 0000000..d1afa5d --- /dev/null +++ b/share/extensions/color_moresaturation.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +"""Increase satuation""" + +import inkex + +class MoreSaturation(inkex.ColorExtension): + """Increase saturation of selected objects""" + def modify_color(self, name, color): + color.saturation += int(0.05 * 255.0) + return color + +if __name__ == '__main__': + MoreSaturation().run() diff --git a/share/extensions/color_negative.inx b/share/extensions/color_negative.inx new file mode 100644 index 0000000..9017b4e --- /dev/null +++ b/share/extensions/color_negative.inx @@ -0,0 +1,14 @@ + + + Negative + org.inkscape.color.negative + + all + + + + + + diff --git a/share/extensions/color_negative.py b/share/extensions/color_negative.py new file mode 100755 index 0000000..60dc84e --- /dev/null +++ b/share/extensions/color_negative.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +"""Reverse the colors""" + +import inkex + +class Negative(inkex.ColorExtension): + """Make the colour oposite""" + def modify_color(self, name, color): + # Support any colour space + for i, channel in enumerate(color): + color[i] = 255 - channel + return color + +if __name__ == '__main__': + Negative().run() diff --git a/share/extensions/color_randomize.inx b/share/extensions/color_randomize.inx new file mode 100644 index 0000000..8610b96 --- /dev/null +++ b/share/extensions/color_randomize.inx @@ -0,0 +1,25 @@ + + + Randomize + org.inkscape.color.randomize + + + 100 + 100 + 100 + 0 + + + + + + + all + + + + + + diff --git a/share/extensions/color_randomize.py b/share/extensions/color_randomize.py new file mode 100755 index 0000000..f07c113 --- /dev/null +++ b/share/extensions/color_randomize.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +"""Randomise the selected item's colours using hsl colorspace""" + +from random import randrange, uniform +import inkex + +def _rand(limit, value, roof=255, method=randrange): + limit = roof * float(limit) / 100 + limit /= 2 + max_ = type(roof)((value * roof) + limit) + min_ = type(roof)((value * roof) - limit) + if max_ > roof: + min_ -= max_ - roof + max_ = roof + if min_ < 0: + max_ -= min_ + min_ = 0 + return method(min_, max_) + +class Randomize(inkex.ColorExtension): + """Randomize the colours of all objects""" + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("-y", "--hue_range", type=int, default=0, help="Hue range") + pars.add_argument("-t", "--saturation_range", type=int, default=0, help="Saturation range") + pars.add_argument("-m", "--lightness_range", type=int, default=0, help="Lightness range") + pars.add_argument("-o", "--opacity_range", type=int, default=0, help="Opacity range") + + + def modify_color(self, name, color): + hsl = color.to_hsl() + if self.options.hue_range > 0: + hsl.hue = int(_rand(self.options.hue_range, hsl.hue)) + if self.options.saturation_range > 0: + hsl.saturation = int(_rand(self.options.saturation_range, hsl.saturation)) + if self.options.lightness_range > 0: + hsl.lightness = int(_rand(self.options.lightness_range, hsl.lightness)) + return hsl.to_rgb() + + def modify_opacity(self, name, opacity): + try: + opacity = float(opacity) + except ValueError: + self.msg("Ignoring unusual opacity value: {}".format(opacity)) + return opacity + orange = self.options.opacity_range + if orange > 0: + return _rand(orange, opacity, roof=100.0, method=uniform) / 100 + return opacity + +if __name__ == '__main__': + Randomize().run() diff --git a/share/extensions/color_removeblue.inx b/share/extensions/color_removeblue.inx new file mode 100644 index 0000000..f1ebdda --- /dev/null +++ b/share/extensions/color_removeblue.inx @@ -0,0 +1,14 @@ + + + Remove Blue + org.inkscape.color.remove_blue + + all + + + + + + diff --git a/share/extensions/color_removeblue.py b/share/extensions/color_removeblue.py new file mode 100755 index 0000000..c19fd33 --- /dev/null +++ b/share/extensions/color_removeblue.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +"""Extension to remove the blue colour from selected shapes""" + +import inkex + +class RemoveBlue(inkex.ColorExtension): + """Remove blue color from selected objects""" + def modify_color(self, name, color): + return inkex.Color([color.red, color.green, 0]) + +if __name__ == '__main__': + RemoveBlue().run() diff --git a/share/extensions/color_removegreen.inx b/share/extensions/color_removegreen.inx new file mode 100644 index 0000000..8a2c32e --- /dev/null +++ b/share/extensions/color_removegreen.inx @@ -0,0 +1,14 @@ + + + Remove Green + org.inkscape.color.remove_green + + all + + + + + + diff --git a/share/extensions/color_removegreen.py b/share/extensions/color_removegreen.py new file mode 100755 index 0000000..2eacf95 --- /dev/null +++ b/share/extensions/color_removegreen.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +"""Remove green color from selected objects""" + +import inkex + +class RemoveGreen(inkex.ColorExtension): + """Remove green color from selected objects""" + def modify_color(self, name, color): + return inkex.Color([color.red, 0, color.blue]) + +if __name__ == '__main__': + RemoveGreen().run() diff --git a/share/extensions/color_removered.inx b/share/extensions/color_removered.inx new file mode 100644 index 0000000..fab0bd4 --- /dev/null +++ b/share/extensions/color_removered.inx @@ -0,0 +1,14 @@ + + + Remove Red + org.inkscape.color.remove_red + + all + + + + + + diff --git a/share/extensions/color_removered.py b/share/extensions/color_removered.py new file mode 100755 index 0000000..d8353f2 --- /dev/null +++ b/share/extensions/color_removered.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +"""Extension for removing the colour red from selected objects""" + +import inkex + +class RemoveRed(inkex.ColorExtension): + """Remove red color from selected objects""" + def modify_color(self, name, color): + return inkex.Color([0, color.green, color.blue]) + +if __name__ == '__main__': + RemoveRed().run() diff --git a/share/extensions/color_replace.inx b/share/extensions/color_replace.inx new file mode 100644 index 0000000..9f038bc --- /dev/null +++ b/share/extensions/color_replace.inx @@ -0,0 +1,22 @@ + + + Replace color + org.inkscape.color.replace_color + + + + + + + + + + all + + + + + + diff --git a/share/extensions/color_replace.py b/share/extensions/color_replace.py new file mode 100755 index 0000000..6ed569a --- /dev/null +++ b/share/extensions/color_replace.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +"""Replace color extension""" + +import inkex + +class ReplaceColor(inkex.ColorExtension): + """Replace color in SVG with another""" + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument('-f', "--from_color",\ + default=inkex.Color("black"), type=inkex.Color, help="Replace color") + pars.add_argument('-t', "--to_color",\ + default=inkex.Color("red"), type=inkex.Color, help="By color") + + def modify_color(self, name, color): + return self.options.to_color if color == self.options.from_color else color + +if __name__ == '__main__': + ReplaceColor().run() diff --git a/share/extensions/color_rgbbarrel.inx b/share/extensions/color_rgbbarrel.inx new file mode 100644 index 0000000..a035ab7 --- /dev/null +++ b/share/extensions/color_rgbbarrel.inx @@ -0,0 +1,14 @@ + + + RGB Barrel + org.inkscape.color.rgb_barrel + + all + + + + + + diff --git a/share/extensions/color_rgbbarrel.py b/share/extensions/color_rgbbarrel.py new file mode 100755 index 0000000..5458265 --- /dev/null +++ b/share/extensions/color_rgbbarrel.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +"""Rotate the colors in the selected elements""" + +import inkex + +class RgbBarrel(inkex.ColorExtension): + """ + Cycle colors RGB -> BRG + + aka Do a Barrel Roll! + """ + def modify_color(self, name, color): + return inkex.Color((color.blue, color.red, color.green)) + +if __name__ == '__main__': + RgbBarrel().run() diff --git a/share/extensions/colors.xml b/share/extensions/colors.xml new file mode 100644 index 0000000..7ed8592 --- /dev/null +++ b/share/extensions/colors.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/convert2dashes.inx b/share/extensions/convert2dashes.inx new file mode 100644 index 0000000..5fd2c44 --- /dev/null +++ b/share/extensions/convert2dashes.inx @@ -0,0 +1,14 @@ + + + Convert to Dashes + org.inkscape.filter.dashit + + path + + + + + + diff --git a/share/extensions/convert2dashes.py b/share/extensions/convert2dashes.py new file mode 100755 index 0000000..ca9e13c --- /dev/null +++ b/share/extensions/convert2dashes.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005,2007 Aaron Spike, aaron@ekips.org +# Copyright (C) 2009 Alvin Penner, penner@vaxxine.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This extension converts a path into a dashed line using 'stroke-dasharray' +It is a modification of the file addnodes.py +""" +import inkex +from inkex import bezier, CubicSuperPath, Group, PathElement + + +class Dashit(inkex.EffectExtension): + """Extension to convert paths into dash-array line""" + def __init__(self): + super(Dashit, self).__init__() + self.not_converted = [] + + def effect(self): + for node in self.svg.selection.values(): + self.convert2dash(node) + if self.not_converted: + inkex.errormsg(_('Total number of objects not converted: {}\n').format( + len(self.not_converted))) + # return list of IDs in case the user needs to find a specific object + inkex.debug(self.not_converted) + + def convert2dash(self, node): + """Convert each selected node's dash array""" + if isinstance(node, Group): + for child in node: + self.convert2dash(child) + elif isinstance(node, PathElement): + self._convert(node) + else: + self.not_converted.append(node.get('id')) + + @staticmethod + def _convert(node): + dashes = [] + offset = 0 + style = node.style + if 'stroke-dasharray' in style: + if style['stroke-dasharray'].find(',') > 0: + dashes = [float(dash) for dash in style['stroke-dasharray'].split(',')] + if 'stroke-dashoffset' in style: + offset = style['stroke-dashoffset'] + if not dashes: + return + new = [] + for sub in node.path.to_superpath(): + idash = 0 + dash = dashes[0] + length = float(offset) + while dash < length: + length = length - dash + idash = (idash + 1) % len(dashes) + dash = dashes[idash] + new.append([sub[0][:]]) + i = 1 + while i < len(sub): + dash = dash - length + length = bezier.cspseglength(new[-1][-1], sub[i]) + while dash < length: + new[-1][-1], nxt, sub[i] = \ + bezier.cspbezsplitatlength(new[-1][-1], sub[i], dash/length) + if idash % 2: # create a gap + new.append([nxt[:]]) + else: # splice the curve + new[-1].append(nxt[:]) + length = length - dash + idash = (idash + 1) % len(dashes) + dash = dashes[idash] + if idash % 2: + new.append([sub[i]]) + else: + new[-1].append(sub[i]) + i += 1 + style.pop('stroke-dasharray') + node.pop('sodipodi:type') + node.path = CubicSuperPath(new) + node.style = style + +if __name__ == '__main__': + Dashit().run() diff --git a/share/extensions/dhw_input.inx b/share/extensions/dhw_input.inx new file mode 100644 index 0000000..5968540 --- /dev/null +++ b/share/extensions/dhw_input.inx @@ -0,0 +1,14 @@ + + + DHW file input + org.inkscape.input.dhw_input + + .dhw + application/x-extension-DHW + ACECAD Digimemo File (*.dhw) + Open files from ACECAD Digimemo + + + diff --git a/share/extensions/dhw_input.py b/share/extensions/dhw_input.py new file mode 100755 index 0000000..7b5ca9f --- /dev/null +++ b/share/extensions/dhw_input.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2009 Kevin Lindsey, https://github.com/thelonious/DM2SVG +# 2011 Nikita Kitaev, https://github.com/nikitakit/DM2SVG +# Chris Morgan, https://gist.github.com/1471691 +# 2019 Martin Owens, +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Import a DHW file from ACECAD DigiMemo, a hardware based digitiser +""" + +import struct + +import inkex +from inkex.utils import NSS +from inkex import AbortExtension, errormsg, Group, Polyline + +NSS['dm'] = 'http://github.com/nikitakit/DM2SVG' + +class DhwInput(inkex.InputExtension): + """Open DHW files and convert to svg on the fly""" + template = """ + + + + + + +""" + + def load(self, stream): + """Load the steam as if it were an open DHW file""" + header = list(struct.unpack('<32sBHHBxx', stream.read(40))) + doc = header.pop(0).decode() + if doc != 'ACECAD_DIGIMEMO_HANDWRITING_____': + raise AbortExtension('Could not load file, not a ACECAD DHW file!') + + height = int(header[2]) + doc = self.get_template(**dict(zip(('v', 'w', 'h', 'p'), header))) + svg = doc.getroot() + + timestamp = 0 + layer = svg.getElementById('layer1') + + while True: + tag = stream.read(1) + if tag == b'': + break + + if ord(tag) <= 128: + errormsg('Unsupported tag: {}\n'.format(tag)) + continue + + if tag == b'\x90': + # New Layer element + timestamp = 0 + name = 'layer{:d}'.format(ord(stream.read(1)) + 1) + layer = svg.add(Group(inkscape_groupmode="layer", id=name)) + elif tag == b'\x88': + # Read the timestamp next + timestamp += ord(stream.read(1)) * 20 + else: + # Pen down + coords = [p for p in iter(lambda: read_point(stream, height), None)] + # Pen up + coords.append(read_point(stream, height)) + + poly = layer.add(Polyline()) + poly.path = coords + poly.set('dm:timestamp', timestamp) + + return doc + + +def read_point(stream, ymax): + """If the next byte is a stop byte, return None. Otherwise read 4 bytes + (in total) and return a 2D point. + """ + # read first byte, it might be a stop byte + x1 = struct.unpack('B', stream.read(1))[0] + + if x1 >= 0x80: + return None + + x2, y1, y2 = struct.unpack('BBB', stream.read(3)) + + x = x1 | x2 << 7 + y = y1 | y2 << 7 + + return x, ymax - y + +if __name__ == '__main__': + DhwInput().run() diff --git a/share/extensions/dimension.inx b/share/extensions/dimension.inx new file mode 100644 index 0000000..ccd4c0d --- /dev/null +++ b/share/extensions/dimension.inx @@ -0,0 +1,20 @@ + + + Dimensions + se.lewerin.filter.dimension + 50 + 50 + + + + + + path + + + + + + diff --git a/share/extensions/dimension.py b/share/extensions/dimension.py new file mode 100755 index 0000000..5b15407 --- /dev/null +++ b/share/extensions/dimension.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2007 Peter Lewerin, peter.lewerin@tele2.se +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +An Inkscape effect for adding CAD style dimensions to selected objects +in a drawing. + +It uses the selection's bounding box, so if the bounding box has empty +space in the x- or y-direction (such as with some stars) the results +will look strange. Strokes might also overlap the edge of the +bounding box. + +The dimension arrows aren't measured: use the "Visualize Path/Measure +Path" effect to add measurements. + +This code contains snippets from existing effects in the Inkscape +extensions library, and marker data from markers.svg. +""" + +import inkex +from inkex import Group, Marker, PathElement + +import pathmodifier + +class Dimension(pathmodifier.PathModifier): + """Add dimensions as a path modifier""" + def add_arguments(self, pars): + pars.add_argument("--xoffset", type=float, default=100.0,\ + help="x offset of the vertical dimension arrow") + pars.add_argument("--yoffset", type=float, default=100.0,\ + help="y offset of the horizontal dimension arrow") + pars.add_argument("--type", default="geometric", help="Bounding box type") + + def add_marker(self, name, rotate): + """Create a marker in the defs of the svg""" + marker = Marker() + marker.set('id', name) + marker.set('orient', 'auto') + marker.set('refX', '0.0') + marker.set('refY', '0.0') + marker.set('style', 'overflow:visible') + marker.set('inkscape:stockid', name) + self.svg.defs.append(marker) + + arrow = PathElement(d='M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z ') + if rotate: + arrow.set('transform', 'scale(0.8) rotate(180) translate(12.5,0)') + else: + arrow.set('transform', 'scale(0.8) translate(12.5,0)') + arrow.set('style', 'fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none') + marker.append(arrow) + + def horz_line(self, y, xlat, bbox): + """Create a horzontal line""" + line = PathElement() + x1 = bbox.left - xlat[0] * self.options.xoffset + x2 = bbox.right + y1 = y - xlat[1] * self.options.yoffset + line.set('d', 'M %f %f H %f' % (x1, y1, x2)) + return line + + def vert_line(self, x, xlat, bbox): + """Create a vertical line""" + line = PathElement() + x = x - xlat[0] * self.options.xoffset + y1 = bbox.top - xlat[1] * self.options.yoffset + y2 = bbox.bottom + line.set('d', 'M %f %f V %f' % (x, y1, y2)) + return line + + def effect(self): + scale = self.svg.unittouu('1px') # convert to document units + self.options.xoffset *= scale + self.options.yoffset *= scale + + if not self.svg.selected: + raise inkex.AbortExtension("Please select an object") + if self.options.type == "geometric": + bbox = self.svg.selection.bounding_box() + else: + bbox = self.svg.selection.first().bounding_box() + + layer = self.svg.get_current_layer() + + self.add_marker('Arrow1Lstart', False) + self.add_marker('Arrow1Lend', True) + + group = Group() + layer.append(group) + group.set('fill', 'none') + group.set('stroke', 'black') + + line = self.horz_line(bbox.top, [0, 1], bbox) + line.set('marker-start', 'url(#Arrow1Lstart)') + line.set('marker-end', 'url(#Arrow1Lend)') + line.set('stroke-width', str(scale)) + group.append(line) + + line = self.vert_line(bbox.left, [0, 2], bbox) + line.set('stroke-width', str(0.5 * scale)) + group.append(line) + + line = self.vert_line(bbox.right, [0, 2], bbox) + line.set('stroke-width', str(0.5 * scale)) + group.append(line) + + line = self.vert_line(bbox.left, [1, 0], bbox) + line.set('marker-start', 'url(#Arrow1Lstart)') + line.set('marker-end', 'url(#Arrow1Lend)') + line.set('stroke-width', str(scale)) + group.append(line) + + line = self.horz_line(bbox.top, [2, 0], bbox) + line.set('stroke-width', str(0.5 * scale)) + group.append(line) + + line = self.horz_line(bbox.bottom, [2, 0], bbox) + line.set('stroke-width', str(0.5 * scale)) + group.append(line) + + for node in self.svg.selected.values(): + group.append(node) + + layer.append(group) + return None + + +if __name__ == '__main__': + Dimension().run() diff --git a/share/extensions/docinfo.inx b/share/extensions/docinfo.inx new file mode 100644 index 0000000..095036f --- /dev/null +++ b/share/extensions/docinfo.inx @@ -0,0 +1,14 @@ + + + DOC Info + org.inkscape.doc_info + + all + + + + + + diff --git a/share/extensions/docinfo.py b/share/extensions/docinfo.py new file mode 100755 index 0000000..1c39b90 --- /dev/null +++ b/share/extensions/docinfo.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2012 Jabiertxo Arraiza, jabier.arraiza@marker.es +# Copyright (C) 2016 su_v, +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +"""Document information""" + +import inkex + +class DocInfo(inkex.EffectExtension): + """Show document information""" + def effect(self): + namedview = self.svg.namedview + self.msg(":::SVG document related info:::") + self.msg("version: " + self.svg.get('inkscape:version', 'New Document (unsaved)')) + self.msg("width: {}".format(self.svg.width)) + self.msg("height: {}".format(self.svg.height)) + self.msg("viewbox: {}".format(str(self.svg.get_viewbox()))) + self.msg("document-units: {}".format(namedview.get('inkscape:document-units', 'None'))) + self.msg("units: " + namedview.get('units', 'None')) + self.msg("Document has " + str(len(namedview.get_guides())) + " guides") + for i, grid in enumerate(namedview.findall('inkscape:grid')): + self.msg("Grid number {}: Units: {}".format(i + 1, grid.get("units", 'None'))) + +if __name__ == '__main__': + DocInfo().run() diff --git a/share/extensions/doxygen-main.dox b/share/extensions/doxygen-main.dox new file mode 100644 index 0000000..759b0a7 --- /dev/null +++ b/share/extensions/doxygen-main.dox @@ -0,0 +1,55 @@ +/** \mainpage Inkscape Extensions Source Code Documentation + * This documentation contains API documentation for Inkscape extensions. + * + * It describes in more detail common libraries for creating extensions + * and some extensions that are good examples for inspiration. + * + * \section groups Main directory documentation + * - \subpage CommonClasses - Common classes used for writing extensions. + * - \subpage ExampleExtensions - Links to selected extensions. + * - \subpage Testing - How to write tests for extensions. + * - \subpage CommandLine - Command-line usage + * + * \section extlinks Links to external documentation + * + * \subpage ExtensionGuides - Extension guides + */ + +/** + * \page CommonClasses Common classes + * + * \section inkex + * [\ref inkex.py] + * The inkex module exposes the primary interface for writing extensions. + * \subsection effect inkex.Effect + * inkex.Effect is a class from which all extensions are derived. It provides the pipeline that directs the original svg document to the extension, and then reads the resulting svg and inserts it into the document. It also manages effect options, and provides tools for working with them. There are also functions for operating on svgs, converting units, working with guides, writing debug messages to Inkscape's debug window, etc. + * \subsection inkoption inkex.InkOption + * inkex.InkOption is a thin wrapper around optparse that adds the "inkbool" option + * + * \section simplepath + * [\ref simplepath.py] + * + * Utility module for working with paths + * - parse and serialize svg paths + * - transform paths + * + * \section simpletransform + * [\ref simpletransform.py] + * + * Utility module for working with transforms + * - matrices + * - points + * - bounding boxes + * + * \section simplestyle + * [\ref simplestyle.py] + * + * Utility module for working with styles + * - parse and serialize stylesheets + * - parse and format colors + */ +/** \page ExtensionGuides Extension writing guides on the wiki + * - Script_extensions + * - PythonEffectTutorial + * - Category Developer_Documentation + */ \ No newline at end of file diff --git a/share/extensions/dpi90to96.inx b/share/extensions/dpi90to96.inx new file mode 100644 index 0000000..fbb471b --- /dev/null +++ b/share/extensions/dpi90to96.inx @@ -0,0 +1,17 @@ + + + DPI 90 to 96 + org.inkscape.dpi90to96 + + + + + all + + + + + + diff --git a/share/extensions/dpi96to90.inx b/share/extensions/dpi96to90.inx new file mode 100644 index 0000000..e3ac574 --- /dev/null +++ b/share/extensions/dpi96to90.inx @@ -0,0 +1,17 @@ + + + DPI 96 to 90 + org.inkscape.dpi96to90 + + + + + all + + + + + + diff --git a/share/extensions/dpiswitcher.py b/share/extensions/dpiswitcher.py new file mode 100755 index 0000000..b1cf2ec --- /dev/null +++ b/share/extensions/dpiswitcher.py @@ -0,0 +1,346 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2012 Jabiertxo Arraiza, jabier.arraiza@marker.es +# Copyright (C) 2016 su_v, +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +""" +Version 0.6 - DPI Switcher + +This extension scales a document to fit different SVG DPI -90/96- + +Changes since v0.5: + - transform all top-level containers and graphics elements + - support scientific notation in SVG lengths + - fix scaling with existing matrix() + - support different units for document width, height attributes + - improve viewBox support (syntax, offset) + - support common cases of text-put-on-path in SVG root + - support common cases of references in SVG root + - examples from http://tavmjong.free.fr/INKSCAPE/UNITS/ tested + +TODO: + - check grids/guides created with 0.91: + http://tavmjong.free.fr/INKSCAPE/UNITS/units_mm_nv_90dpi.svg + - check instances + - check more and text-on-path cases (reverse scaling needed?) + - scale perspective of 3dboxes + +""" + +import re +import math +import inkex +from inkex import Use, TextElement + +# globals +SKIP_CONTAINERS = [ + 'defs', + 'glyph', + 'marker', + 'mask', + 'missing-glyph', + 'pattern', + 'symbol', +] +CONTAINER_ELEMENTS = [ + 'a', + 'g', + 'switch', +] +GRAPHICS_ELEMENTS = [ + 'circle', + 'ellipse', + 'image', + 'line', + 'path', + 'polygon', + 'polyline', + 'rect', + 'text', + 'use', +] + + +def is_3dbox(element): + """Check whether element is an Inkscape 3dbox type.""" + return element.get('sodipodi:type') == 'inkscape:box3d' + + +def is_text_on_path(element): + """Check whether text element is put on a path.""" + if isinstance(element, TextElement): + text_path = element.find('svg:textPath') + if text_path is not None and len(text_path): + return True + return False + + +def is_sibling(element1, element2): + """Check whether element1 and element2 are siblings of same parent.""" + return element2 in element1.getparent() + + +def is_in_defs(doc, element): + """Check whether element is in defs.""" + if element is not None: + defs = doc.find('defs') + if defs is not None: + return element in defs.iterdescendants() + return False + + +def check_3dbox(svg, element, scale_x, scale_y): + """Check transformation for 3dbox element.""" + skip = False + if skip: + # 3dbox elements ignore preserved transforms + # FIXME: manually update geometry of 3dbox? + pass + return skip + + +def check_text_on_path(svg, element, scale_x, scale_y): + """Check whether to skip scaling a text put on a path.""" + skip = False + path = element.find('textPath').href + if not is_in_defs(svg, path): + if is_sibling(element, path): + # skip common element scaling if both text and path are siblings + skip = True + # scale offset + if 'transform' in element.attrib: + element.transform.add_scale(scale_x, scale_y) + # scale font size + mat = inkex.Transform('scale({},{})'.format(scale_x, scale_y)).matrix + det = abs(mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]) + descrim = math.sqrt(abs(det)) + prop = 'font-size' + # outer text + sdict = dict(inkex.Style.parse_str(element.get('style'))) + if prop in sdict: + sdict[prop] = float(sdict[prop]) * descrim + element.set('style', str(inkex.Style(sdict))) + # inner tspans + for child in element.iterdescendants(): + if isinstance(element, inkex.Tspan): + sdict = dict(inkex.Style.parse_str(child.get('style'))) + if prop in sdict: + sdict[prop] = float(sdict[prop]) * descrim + child.set('style', str(inkex.Style(sdict))) + return skip + + +def check_use(svg, element, scale_x, scale_y): + """Check whether to skip scaling an instantiated element ().""" + skip = False + path = element.href + if not is_in_defs(svg, path): + if is_sibling(element, path): + skip = True + # scale offset + if 'transform' in element.attrib: + element.transform.add_scale(scale_x, scale_y) + return skip + + +class DPISwitcher(inkex.EffectExtension): + multi_inx = True + factor_a = 90.0 / 96.0 + factor_b = 96.0 / 90.0 + units = "px" + + def add_arguments(self, pars): + pars.add_argument("--switcher", type=str, default="0", + help="Select the DPI switch you want") + + # dictionaries of unit to user unit conversion factors + __uuconvLegacy = { + 'in': 90.0, + 'pt': 1.25, + 'px': 1.0, + 'mm': 3.5433070866, + 'cm': 35.433070866, + 'm': 3543.3070866, + 'km': 3543307.0866, + 'pc': 15.0, + 'yd': 3240.0, + 'ft': 1080.0, + } + __uuconv = { + 'in': 96.0, + 'pt': 1.33333333333, + 'px': 1.0, + 'mm': 3.77952755913, + 'cm': 37.7952755913, + 'm': 3779.52755913, + 'km': 3779527.55913, + 'pc': 16.0, + 'yd': 3456.0, + 'ft': 1152.0, + } + + def parse_length(self, length, percent=False): + """Parse SVG length.""" + if self.options.switcher == "0": # dpi90to96 + known_units = list(self.__uuconvLegacy) + else: # dpi96to90 + known_units = list(self.__uuconv) + if percent: + unitmatch = re.compile('(%s)$' % '|'.join(known_units + ['%'])) + else: + unitmatch = re.compile('(%s)$' % '|'.join(known_units)) + param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)') + p = param.match(length) + u = unitmatch.search(length) + val = 100 # fallback: assume default length of 100 + unit = 'px' # fallback: assume 'px' unit + if p: + val = float(p.string[p.start():p.end()]) + if u: + unit = u.string[u.start():u.end()] + return val, unit + + def convert_length(self, val, unit): + """Convert length to self.units if unit differs.""" + doc_unit = self.units or 'px' + if unit != doc_unit: + if self.options.switcher == "0": # dpi90to96 + val_px = val * self.__uuconvLegacy[unit] + val = val_px / (self.__uuconvLegacy[doc_unit] / self.__uuconvLegacy['px']) + unit = doc_unit + else: # dpi96to90 + val_px = val * self.__uuconv[unit] + val = val_px / (self.__uuconv[doc_unit] / self.__uuconv['px']) + unit = doc_unit + return val, unit + + def check_attr_unit(self, element, attr, unit_list): + """Check unit of attribute value, match to units in *unit_list*.""" + if attr in element.attrib: + unit = self.parse_length(element.get(attr), percent=True)[1] + return unit in unit_list + + def scale_attr_val(self, element, attr, unit_list, factor): + """Scale attribute value if unit matches one in *unit_list*.""" + if attr in element.attrib: + val, unit = self.parse_length(element.get(attr), percent=True) + if unit in unit_list: + element.set(attr, '{}{}'.format(val * factor, unit)) + + def scale_root(self, unit_exponent=1.0): + """Scale all top-level elements in SVG root.""" + + # update viewport + width_num = self.parse_length(self.svg.get('width'))[0] + height_num = self.convert_length(*self.parse_length(self.svg.get('height')))[0] + width_doc = width_num * self.factor_a * unit_exponent + height_doc = height_num * self.factor_a * unit_exponent + + svg = self.svg + if svg.get('height'): + svg.set('height', str(height_doc)) + if svg.get('width'): + svg.set('width', str(width_doc)) + + # update viewBox + if svg.get('viewBox'): + viewboxstring = re.sub(' +|, +|,', ' ', svg.get('viewBox')) + viewboxlist = [float(i) for i in viewboxstring.strip().split(' ', 4)] + svg.set('viewBox', '{} {} {} {}'.format(*[(val * self.factor_a) for val in viewboxlist])) + + # update guides, grids + if self.options.switcher == "1": + # FIXME: dpi96to90 only? + self.scale_guides() + self.scale_grid() + + for element in svg: # iterate all top-level elements of SVGRoot + + # init variables + tag = element.TAG + width_scale = self.factor_a + height_scale = self.factor_a + + if tag in GRAPHICS_ELEMENTS or tag in CONTAINER_ELEMENTS: + + # test for specific elements to skip from scaling + if is_3dbox(element): + if check_3dbox(svg, element, width_scale, height_scale): + continue + if is_text_on_path(element): + if check_text_on_path(svg, element, width_scale, height_scale): + continue + if isinstance(element, Use): + if check_use(svg, element, width_scale, height_scale): + continue + + # relative units ('%') in presentation attributes + for attr in ['width', 'height']: + self.scale_attr_val(element, attr, ['%'], 1.0 / self.factor_a) + for attr in ['x', 'y']: + self.scale_attr_val(element, attr, ['%'], 1.0 / self.factor_a) + + # set preserved transforms on top-level elements + if width_scale != 1.0 and height_scale != 1.0: + element.transform.add_scale(width_scale, height_scale) + + def scale_element(self, elem): + pass # TODO: optionally scale graphics elements only? + + def scale_guides(self): + """Scale the guidelines""" + for guide in self.svg.namedview.get_guides(): + point = guide.get("position").split(",") + guide.set("position", str(float(point[0].strip()) * self.factor_a) + "," + str(float(point[1].strip()) * self.factor_a)) + + def scale_grid(self): + """Scale the inkscape grid""" + grids = self.svg.xpath('//inkscape:grid') + for grid in grids: + grid.set("units", "px") + if grid.get("spacingx"): + spacingx = str(float(re.sub("[a-zA-Z]", "", grid.get("spacingx"))) * self.factor_a) + "px" + grid.set("spacingx", str(spacingx)) + if grid.get("spacingy"): + spacingy = str(float(re.sub("[a-zA-Z]", "", grid.get("spacingy"))) * self.factor_a) + "px" + grid.set("spacingy", str(spacingy)) + if grid.get("originx"): + originx = str(float(re.sub("[a-zA-Z]", "", grid.get("originx"))) * self.factor_a) + "px" + grid.set("originx", str(originx)) + if grid.get("originy"): + originy = str(float(re.sub("[a-zA-Z]", "", grid.get("originy"))) * self.factor_a) + "px" + grid.set("originy", str(originy)) + + def effect(self): + svg = self.svg + if self.options.switcher == "0": + self.factor_a = 96.0 / 90.0 + self.factor_b = 90.0 / 96.0 + svg.namedview.set('inkscape:document-units', "px") + self.units = self.parse_length(svg.get('width'))[1] + unit_exponent = 1.0 + if self.units and self.units != "px" and self.units != "" and self.units != "%": + if self.options.switcher == "0": + unit_exponent = 1.0 / (self.factor_a / self.__uuconv[self.units]) + else: + unit_exponent = 1.0 / (self.factor_a / self.__uuconvLegacy[self.units]) + self.scale_root(unit_exponent) + + +if __name__ == '__main__': + DPISwitcher().run() diff --git a/share/extensions/draw_from_triangle.inx b/share/extensions/draw_from_triangle.inx new file mode 100644 index 0000000..116e3f5 --- /dev/null +++ b/share/extensions/draw_from_triangle.inx @@ -0,0 +1,75 @@ + + + Draw From Triangle + org.inkscape.render.draw_from_triangle + + + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + + + + + + + cos(a_a):cos(a_b):cos(a_c) + false + false + s_a*s_b*s_c/(4*area) + false + false + + + + + + + all + + + + + + diff --git a/share/extensions/draw_from_triangle.py b/share/extensions/draw_from_triangle.py new file mode 100755 index 0000000..32de00a --- /dev/null +++ b/share/extensions/draw_from_triangle.py @@ -0,0 +1,395 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2007 John Beard john.j.beard@gmail.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This extension allows you to draw various triangle constructions +It requires a path to be selected +It will use the first three nodes of this path + +Dimensions of a triangle__ + + /`__ + / a_c``--__ + / ``--__ s_a +s_b / ``--__ + /a_a a_b`--__ + /--------------------------------``B + A s_b +""" + +from math import acos, cos, pi, sin, sqrt, tan + +import inkex +from inkex import PathElement, Circle + +(X, Y) = range(2) + +# DRAWING ROUTINES + +# draw an SVG triangle given in trilinar coords +def draw_SVG_circle(rad, centre, params, style, name, parent): # draw an SVG circle with a given radius as trilinear coordinates + if rad == 0: # we want a dot + r = style.d_rad # get the dot width from the style + circ_style = {'stroke': style.d_col, 'stroke-width': str(style.d_th), 'fill': style.d_fill} + else: + r = rad # use given value + circ_style = {'stroke': style.c_col, 'stroke-width': str(style.c_th), 'fill': style.c_fill} + + cx, cy = get_cartesian_pt(centre, params) + circ_attribs = {'cx': str(cx), 'cy': str(cy), 'r': str(r)} + elem = parent.add(Circle(**circ_attribs)) + elem.style = circ_style + elem.label = name + + +# draw an SVG triangle given in trilinar coords +def draw_SVG_tri(vert_mat, params, style, name, parent): + p1, p2, p3 = get_cartesian_tri(vert_mat, params) # get the vertex matrix in cartesian points + elem = parent.add(PathElement()) + elem.path = 'M ' + str(p1[0]) + ',' + str(p1[1]) +\ + ' L ' + str(p2[0]) + ',' + str(p2[1]) +\ + ' L ' + str(p3[0]) + ',' + str(p3[1]) +\ + ' L ' + str(p1[0]) + ',' + str(p1[1]) + ' z' + elem.style = {'stroke': style.l_col, 'stroke-width': str(style.l_th), 'fill': style.l_fill} + elem.label = name + + +# draw an SVG line segment between the given (raw) points +def draw_SVG_line(a, b, style, name, parent): + (x1, y1) = a + (x2, y2) = b + line = parent.add(PathElement()) + line.style = {'stroke': style.l_col, 'stroke-width': str(style.l_th), 'fill': style.l_fill} + line.path = 'M ' + str(x1) + ',' + str(y1) + ' L ' + str(x2) + ',' + str(y2) + line.lavel = name + + +# lines from each vertex to a corresponding point in trilinears +def draw_vertex_lines(vert_mat, params, width, name, parent): + for i in range(3): + oppositepoint = get_cartesian_pt(vert_mat[i], params) + draw_SVG_line(params[3][-i % 3], oppositepoint, width, name + ':' + str(i), parent) + + +# MATHEMATICAL ROUTINES + +def distance(a, b): + """find the pythagorean distance""" + (x0, y0) = a + (x1, y1) = b + return sqrt((x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1)) + + +def vector_from_to(a, b): + """get the vector from (x0,y0) to (x1,y1)""" + return b[X] - a[X], b[Y], a[Y] + + +def get_cartesian_pt(t, p): # get the cartesian coordinates from a trilinear set + denom = p[0][0] * t[0] + p[0][1] * t[1] + p[0][2] * t[2] + c1 = p[0][1] * t[1] / denom + c2 = p[0][2] * t[2] / denom + return c1 * p[2][1][0] + c2 * p[2][0][0], c1 * p[2][1][1] + c2 * p[2][0][1] + + +def get_cartesian_tri(arg, params): + """get the cartesian points from a trilinear vertex matrix""" + (t11, t12, t13), (t21, t22, t23), (t31, t32, t33) = arg + p1 = get_cartesian_pt((t11, t12, t13), params) + p2 = get_cartesian_pt((t21, t22, t23), params) + p3 = get_cartesian_pt((t31, t32, t33), params) + return p1, p2, p3 + + +def angle_from_3_sides(a, b, c): # return the angle opposite side c + cosx = (a * a + b * b - c * c) / (2 * a * b) # use the cosine rule + return acos(cosx) + + +def translate_string(string, os): # translates s_a, a_a, etc to params[x][y], with cyclic offset + string = string.replace('s_a', 'params[0][' + str((os + 0) % 3) + ']') # replace with ref. to the relvant values, + string = string.replace('s_b', 'params[0][' + str((os + 1) % 3) + ']') # cycled by i + string = string.replace('s_c', 'params[0][' + str((os + 2) % 3) + ']') + string = string.replace('a_a', 'params[1][' + str((os + 0) % 3) + ']') + string = string.replace('a_b', 'params[1][' + str((os + 1) % 3) + ']') + string = string.replace('a_c', 'params[1][' + str((os + 2) % 3) + ']') + string = string.replace('area', 'params[4][0]') + string = string.replace('semiperim', 'params[4][1]') + return string + + +def pt_from_tcf(tcf, params): # returns a trilinear triplet from a triangle centre function + trilin_pts = [] # will hold the final points + for i in range(3): + temp = tcf # read in the tcf + temp = translate_string(temp, i) + func = eval('lambda params: ' + temp.strip('"')) # the function leading to the trilinar element + trilin_pts.append(func(params)) # evaluate the function for the first trilinear element + return trilin_pts + + +# SVG DATA PROCESSING + +def get_n_points_from_path(node, n): + """returns a list of first n points (x,y) in an SVG path-representing node""" + points = list(node.path.control_points) + if len(points) < 3: + return [] + return points[:3] + +# EXTRA MATHS FUNCTIONS +def sec(x): # secant(x) + if x == pi / 2 or x == -pi / 2 or x == 3 * pi / 2 or x == -3 * pi / 2: # sec(x) is undefined + return 100000000000 + else: + return 1 / cos(x) + + +def csc(x): # cosecant(x) + if x == 0 or x == pi or x == 2 * pi or x == -2 * pi: # csc(x) is undefined + return 100000000000 + else: + return 1 / sin(x) + + +def cot(x): # cotangent(x) + if x == 0 or x == pi or x == 2 * pi or x == -2 * pi: # cot(x) is undefined + return 100000000000 + else: + return 1 / tan(x) + + +class Style(object): # container for style information + def __init__(self, svg): + # dot markers + self.d_rad = svg.unittouu('4px') # dot marker radius + self.d_th = svg.unittouu('2px') # stroke width + self.d_fill = '#aaaaaa' # fill colour + self.d_col = '#000000' # stroke colour + + # lines + self.l_th = svg.unittouu('2px') + self.l_fill = 'none' + self.l_col = '#000000' + + # circles + self.c_th = svg.unittouu('2px') + self.c_fill = 'none' + self.c_col = '#000000' + + +class DrawFromTriangle(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("--tab") + # PRESET POINT OPTIONS + pars.add_argument("--circumcircle", type=inkex.Boolean, default=False) + pars.add_argument("--circumcentre", type=inkex.Boolean, default=False) + pars.add_argument("--incircle", type=inkex.Boolean, default=False) + pars.add_argument("--incentre", type=inkex.Boolean, default=False) + pars.add_argument("--contact_tri", type=inkex.Boolean, default=False) + pars.add_argument("--excircles", type=inkex.Boolean, default=False) + pars.add_argument("--excentres", type=inkex.Boolean, default=False) + pars.add_argument("--extouch_tri", type=inkex.Boolean, default=False) + pars.add_argument("--excentral_tri", type=inkex.Boolean, default=False) + pars.add_argument("--orthocentre", type=inkex.Boolean, default=False) + pars.add_argument("--orthic_tri", type=inkex.Boolean, default=False) + pars.add_argument("--altitudes", type=inkex.Boolean, default=False) + pars.add_argument("--anglebisectors", type=inkex.Boolean, default=False) + pars.add_argument("--centroid", type=inkex.Boolean, default=False) + pars.add_argument("--ninepointcentre", type=inkex.Boolean, default=False) + pars.add_argument("--ninepointcircle", type=inkex.Boolean, default=False) + pars.add_argument("--symmedians", type=inkex.Boolean, default=False) + pars.add_argument("--sym_point", type=inkex.Boolean, default=False) + pars.add_argument("--sym_tri", type=inkex.Boolean, default=False) + pars.add_argument("--gergonne_pt", type=inkex.Boolean, default=False) + pars.add_argument("--nagel_pt", type=inkex.Boolean, default=False) + # CUSTOM POINT OPTIONS + pars.add_argument("--mode", default='trilin') + pars.add_argument("--cust_str", default='s_a') + pars.add_argument("--cust_pt", type=inkex.Boolean, default=False) + pars.add_argument("--cust_radius", type=inkex.Boolean, default=False) + pars.add_argument("--radius", default='s_a') + pars.add_argument("--isogonal_conj", type=inkex.Boolean, default=False) + pars.add_argument("--isotomic_conj", type=inkex.Boolean, default=False) + + def effect(self): + so = self.options # shorthand + + pts = [] # initialise in case nothing is selected and following loop is not executed + for node in self.svg.selection.filter(inkex.PathElement).values(): + # find the (x,y) coordinates of the first 3 points of the path + pts = get_n_points_from_path(node, 3) + + if len(pts) == 3: # if we have right number of nodes, else skip and end program + st = Style(self.svg) # style for dots, lines and circles + + # CREATE A GROUP TO HOLD ALL GENERATED ELEMENTS IN + # Hold relative to point A (pt[0]) + layer = self.svg.get_current_layer().add(inkex.Group.new('TriangleElements')) + layer.transform = 'translate(' + str(pts[0][0]) + ',' + str(pts[0][1]) + ')' + + # GET METRICS OF THE TRIANGLE + # vertices in the local coordinates (set pt[0] to be the origin) + vtx = [[0, 0], + [pts[1][0] - pts[0][0], pts[1][1] - pts[0][1]], + [pts[2][0] - pts[0][0], pts[2][1] - pts[0][1]]] + + s_a = distance(vtx[1], vtx[2]) # get the scalar side lengths + s_b = distance(vtx[0], vtx[1]) + s_c = distance(vtx[0], vtx[2]) + sides = (s_a, s_b, s_c) # side list for passing to functions easily and for indexing + + a_a = angle_from_3_sides(s_b, s_c, s_a) # angles in radians + a_b = angle_from_3_sides(s_a, s_c, s_b) + a_c = angle_from_3_sides(s_a, s_b, s_c) + angles = (a_a, a_b, a_c) + + ab = vector_from_to(vtx[0], vtx[1]) # vector from a to b + ac = vector_from_to(vtx[0], vtx[2]) # vector from a to c + bc = vector_from_to(vtx[1], vtx[2]) # vector from b to c + vecs = (ab, ac) # vectors for finding cartesian point from trilinears + + semiperim = (s_a + s_b + s_c) / 2.0 # semiperimeter + area = sqrt(semiperim * (semiperim - s_a) * (semiperim - s_b) * (semiperim - s_c)) # area of the triangle by heron's formula + uvals = (area, semiperim) # useful values + + params = (sides, angles, vecs, vtx, uvals) # all useful triangle parameters in one object + + # BEGIN DRAWING + if so.circumcentre or so.circumcircle: + r = s_a * s_b * s_c / (4 * area) + pt = (cos(a_a), cos(a_b), cos(a_c)) + if so.circumcentre: + draw_SVG_circle(0, pt, params, st, 'Circumcentre', layer) + if so.circumcircle: + draw_SVG_circle(r, pt, params, st, 'Circumcircle', layer) + + if so.incentre or so.incircle: + pt = [1, 1, 1] + if so.incentre: + draw_SVG_circle(0, pt, params, st, 'Incentre', layer) + if so.incircle: + r = area / semiperim + draw_SVG_circle(r, pt, params, st, 'Incircle', layer) + + if so.contact_tri: + t1 = s_b * s_c / (-s_a + s_b + s_c) + t2 = s_a * s_c / (s_a - s_b + s_c) + t3 = s_a * s_b / (s_a + s_b - s_c) + v_mat = ((0, t2, t3), (t1, 0, t3), (t1, t2, 0)) + draw_SVG_tri(v_mat, params, st, 'ContactTriangle', layer) + + if so.extouch_tri: + t1 = (-s_a + s_b + s_c) / s_a + t2 = (s_a - s_b + s_c) / s_b + t3 = (s_a + s_b - s_c) / s_c + v_mat = ((0, t2, t3), (t1, 0, t3), (t1, t2, 0)) + draw_SVG_tri(v_mat, params, st, 'ExtouchTriangle', layer) + + if so.orthocentre: + pt = pt_from_tcf('cos(a_b)*cos(a_c)', params) + draw_SVG_circle(0, pt, params, st, 'Orthocentre', layer) + + if so.orthic_tri: + v_mat = [[0, sec(a_b), sec(a_c)], [sec(a_a), 0, sec(a_c)], [sec(a_a), sec(a_b), 0]] + draw_SVG_tri(v_mat, params, st, 'OrthicTriangle', layer) + + if so.centroid: + pt = [1 / s_a, 1 / s_b, 1 / s_c] + draw_SVG_circle(0, pt, params, st, 'Centroid', layer) + + if so.ninepointcentre or so.ninepointcircle: + pt = [cos(a_b - a_c), cos(a_c - a_a), cos(a_a - a_b)] + if so.ninepointcentre: + draw_SVG_circle(0, pt, params, st, 'NinePointCentre', layer) + if so.ninepointcircle: + r = s_a * s_b * s_c / (8 * area) + draw_SVG_circle(r, pt, params, st, 'NinePointCircle', layer) + + if so.altitudes: + v_mat = [[0, sec(a_b), sec(a_c)], [sec(a_a), 0, sec(a_c)], [sec(a_a), sec(a_b), 0]] + draw_vertex_lines(v_mat, params, st, 'Altitude', layer) + + if so.anglebisectors: + v_mat = ((0, 1, 1), (1, 0, 1), (1, 1, 0)) + draw_vertex_lines(v_mat, params, st, 'AngleBisectors', layer) + + if so.excircles or so.excentres or so.excentral_tri: + v_mat = ((-1, 1, 1), (1, -1, 1), (1, 1, -1)) + if so.excentral_tri: + draw_SVG_tri(v_mat, params, st, 'ExcentralTriangle', layer) + for i in range(3): + if so.excircles: + r = area / (semiperim - sides[i]) + draw_SVG_circle(r, v_mat[i], params, st, 'Excircle:' + str(i), layer) + if so.excentres: + draw_SVG_circle(0, v_mat[i], params, st, 'Excentre:' + str(i), layer) + + if so.sym_tri or so.symmedians: + v_mat = ((0, s_b, s_c), (s_a, 0, s_c), (s_a, s_b, 0)) + if so.sym_tri: + draw_SVG_tri(v_mat, params, st, 'SymmedialTriangle', layer) + if so.symmedians: + draw_vertex_lines(v_mat, params, st, 'Symmedian', layer) + + if so.sym_point: + pt = (s_a, s_b, s_c) + draw_SVG_circle(0, pt, params, st, 'SymmmedianPoint', layer) + + if so.gergonne_pt: + pt = pt_from_tcf('1/(s_a*(s_b+s_c-s_a))', params) + draw_SVG_circle(0, pt, params, st, 'GergonnePoint', layer) + + if so.nagel_pt: + pt = pt_from_tcf('(s_b+s_c-s_a)/s_a', params) + draw_SVG_circle(0, pt, params, st, 'NagelPoint', layer) + + if so.cust_pt or so.cust_radius or so.isogonal_conj or so.isotomic_conj: + pt = [] # where we will store the point in trilinears + if so.mode == 'trilin': # if we are receiving from trilinears + for i in range(3): + strings = so.cust_str.split(':') # get split string + strings[i] = translate_string(strings[i], 0) + func = eval('lambda params: ' + strings[i].strip('"')) # the function leading to the trilinar element + pt.append(func(params)) # evaluate the function for the trilinear element + else: # we need a triangle function + string = so.cust_str # don't need to translate, as the pt_from_tcf function does that for us + pt = pt_from_tcf(string, params) # get the point from the tcf directly + + if so.cust_pt: # draw the point + draw_SVG_circle(0, pt, params, st, 'CustomTrilinearPoint', layer) + if so.cust_radius: # draw the circle with given radius + strings = translate_string(so.radius, 0) + func = eval('lambda params: ' + strings.strip('"')) # the function leading to the radius + r = func(params) + draw_SVG_circle(r, pt, params, st, 'CustomTrilinearCircle', layer) + if so.isogonal_conj: + isogonal = [0, 0, 0] + for i in range(3): + isogonal[i] = 1 / pt[i] + draw_SVG_circle(0, isogonal, params, st, 'CustomIsogonalConjugate', layer) + if so.isotomic_conj: + isotomic = [0, 0, 0] + for i in range(3): + isotomic[i] = 1 / (params[0][i] * params[0][i] * pt[i]) + draw_SVG_circle(0, isotomic, params, st, 'CustomIsotomicConjugate', layer) + + +if __name__ == '__main__': + DrawFromTriangle().run() diff --git a/share/extensions/dxf12_outlines.inx b/share/extensions/dxf12_outlines.inx new file mode 100644 index 0000000..51115b7 --- /dev/null +++ b/share/extensions/dxf12_outlines.inx @@ -0,0 +1,17 @@ + + + Desktop Cutting Plotter R12 + org.inkscape.output.dxf_twelve + org.inkscape.output.svg.inkscape + + .dxf + image/dxf + Desktop Cutting Plotter (AutoCAD DXF R12) (*.dxf) + DXF R12 Output + true + + + diff --git a/share/extensions/dxf12_outlines.py b/share/extensions/dxf12_outlines.py new file mode 100755 index 0000000..2bc001f --- /dev/null +++ b/share/extensions/dxf12_outlines.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005,2007 Aaron Spike, aaron@ekips.org +# - template dxf_outlines.dxf added Feb 2008 by Alvin Penner, penner@vaxxine.com +# - layers, transformation, flattening added April 2008 by Bob Cook, bob@bobcookdev.com +# - added support for dxf R12, Nov. 2008 by Switcher +# - brought together to replace ps2edit version 2018 by Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +from __future__ import absolute_import, print_function, unicode_literals + +import re +import inkex +from inkex.bezier import cspsubdiv + +r12_header = ''' 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1009 + 9 +$EXTMIN + 10 + 0 + 20 + 0 + 9 +$EXTMAX + 10 + 8.5 + 20 + 11 + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES +''' + +r12_footer = ''' 0 +ENDSEC + 0 +EOF''' + + +class DxfTwelve(inkex.OutputExtension): + """Create dxf12 output from the svg""" + def __init__(self): + super(DxfTwelve, self).__init__() + self.handle = 255 + self.flatness = 0.1 + + def dxf_add(self, line): + self._stream.write(line.encode('utf-8')) + + def dxf_insert_code(self, code, value): + self.dxf_add(code + "\n" + value + "\n") + + def dxf_line(self, layer, csp): + self.dxf_insert_code('0', 'LINE') + self.dxf_insert_code('8', layer) + # self.dxf_insert_code( '62', '1' ) #Change the Line Color + self.dxf_insert_code('10', '{:f}'.format(csp[0][0])) + self.dxf_insert_code('20', '{:f}'.format(csp[0][1])) + self.dxf_insert_code('11', '{:f}'.format(csp[1][0])) + self.dxf_insert_code('21', '{:f}'.format(csp[1][1])) + + def dxf_path_to_lines(self, layer, p): + f = self.flatness + is_flat = 0 + while is_flat < 1: + try: + cspsubdiv(p, self.flatness) + is_flat = 1 + except: + f += 0.1 + + for sub in p: + for i in range(len(sub) - 1): + self.handle += 1 + s = sub[i] + e = sub[i + 1] + self.dxf_line(layer, [s[1], e[1]]) + + def dxf_path_to_point(self, layer, p): + bbox = inkex.Path(p).bounding_box() or inkex.BoundingBox(0, 0) + x, y = bbox.center + self.dxf_point(layer, x, y) + + def save(self, stream): + self._stream = stream + self.dxf_insert_code('999', '"DXF R12 Output" (www.mydxf.blogspot.com)') + self.dxf_add(r12_header) + + scale = 25.4 / 90.0 + h = self.svg.height + + path = '//svg:path' + for node in self.svg.xpath(path): + + layer = node.getparent().label + if layer is None: + layer = 'Layer 1' + + node.transform *= inkex.Transform([[scale, 0, 0], [0, -scale, h * scale]]) + node.apply_transform() + path = node.path.to_superpath() + + if re.search('drill$', layer, re.I) is None: + # if layer == 'Brackets Drill': + self.dxf_path_to_lines(layer, path) + else: + self.dxf_path_to_point(layer, path) + + self.dxf_add(r12_footer) + + +if __name__ == '__main__': + DxfTwelve().run() diff --git a/share/extensions/dxf14_footer.txt b/share/extensions/dxf14_footer.txt new file mode 100755 index 0000000..dc37964 --- /dev/null +++ b/share/extensions/dxf14_footer.txt @@ -0,0 +1,62 @@ + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +C +330 +0 +100 +AcDbDictionary + 3 +ACAD_GROUP +350 +D + 3 +ACAD_MLINESTYLE +350 +17 + 0 +DICTIONARY + 5 +D +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +1A +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +17 +330 +C +100 +AcDbDictionary + 3 +STANDARD +350 +18 + 0 +DICTIONARY + 5 +19 +330 +C +100 +AcDbDictionary + 0 +ENDSEC + 0 +EOF diff --git a/share/extensions/dxf14_header.txt b/share/extensions/dxf14_header.txt new file mode 100755 index 0000000..f0f8bc3 --- /dev/null +++ b/share/extensions/dxf14_header.txt @@ -0,0 +1,198 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1014 + 9 +$HANDSEED + 5 +FFFF + 9 +$MEASUREMENT + 70 + 1 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 + 4 + 0 +VPORT + 5 +2E +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*ACTIVE + 70 + 0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +210.0 + 22 +148.5 + 13 +0.0 + 23 +0.0 + 14 +10.0 + 24 +10.0 + 15 +10.0 + 25 +10.0 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +341.0 + 41 +1.24 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 + 0 + 72 + 100 + 73 + 1 + 74 + 3 + 75 + 0 + 76 + 0 + 77 + 0 + 78 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +LTYPE + 5 +14 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYBLOCK + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +15 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYLAYER + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +16 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CONTINUOUS + 70 + 0 + 3 +Solid line + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE diff --git a/share/extensions/dxf14_style.txt b/share/extensions/dxf14_style.txt new file mode 100755 index 0000000..03bdf6b --- /dev/null +++ b/share/extensions/dxf14_style.txt @@ -0,0 +1,358 @@ + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +STYLE + 5 +11 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +STANDARD + 70 + 0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 + 0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 + 2 + 0 +APPID + 5 +12 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +A +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +DIMSTYLE +105 +27 +330 +A +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +ISO-25 + 70 + 0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874016 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 + 71 + 0 + 72 + 0 + 73 + 0 + 74 + 0 + 75 + 0 + 76 + 0 + 77 + 1 + 78 + 8 +170 + 0 +171 + 3 +172 + 1 +173 + 0 +174 + 0 +175 + 0 +176 + 0 +177 + 0 +178 + 0 +270 + 2 +271 + 2 +272 + 2 +273 + 2 +274 + 3 +340 +11 +275 + 0 +280 + 0 +281 + 0 +282 + 0 +283 + 0 +284 + 8 +285 + 0 +286 + 0 +287 + 3 +288 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +BLOCK_RECORD + 5 +1F +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*MODEL_SPACE + 0 +BLOCK_RECORD + 5 +1B +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*PAPER_SPACE + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +20 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*MODEL_SPACE + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*MODEL_SPACE + 1 + + 0 +ENDBLK + 5 +21 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockBegin + 2 +*PAPER_SPACE + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES diff --git a/share/extensions/dxf_input.inx b/share/extensions/dxf_input.inx new file mode 100644 index 0000000..e3372d0 --- /dev/null +++ b/share/extensions/dxf_input.inx @@ -0,0 +1,46 @@ + + + DXF Input + org.inkscape.input.dxf_input + + + + + + + + 1.0 + 0.0 + 0.0 + false + + + + + + + + + Arial + + + + + + + .dxf + image/x-svgz + AutoCAD DXF R13 (*.dxf) + Import AutoCAD's Document Exchange Format + + + diff --git a/share/extensions/dxf_input.py b/share/extensions/dxf_input.py new file mode 100755 index 0000000..fdd4cf2 --- /dev/null +++ b/share/extensions/dxf_input.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2008, 2009 Alvin Penner, penner@vaxxine.com +# Copyright (C) 2009 Christian Mayer, inkscape@christianmayer.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Input a DXF file >= (AutoCAD Release 13 == AC1012) +""" +# thanks to Aaron Spike for inkex without which this would not have been possible + +from __future__ import absolute_import, unicode_literals + +import os +import re +import sys +import math + +from lxml import etree + +import inkex + +if sys.version_info[0] < 3: + from urllib import quote +else: + from urllib.parse import quote + + unichr = chr + + +def re_hex2unichar(m): + return unichr(int(m.group(1), 16)) + + +def formatStyle(style): + return str(inkex.Style(style)) + + +def export_MTEXT(): + # mandatory group codes : (1 or 3, 10, 20) (text, x, y) + if (vals[groups['1']] or vals[groups['3']]) and vals[groups['10']] and vals[groups['20']]: + x = vals[groups['10']][0] + y = vals[groups['20']][0] + # optional group codes : (21, 40, 50) (direction, text height mm, text angle) + size = 12 # default fontsize in px + if vals[groups['40']] and vals[groups['40']][0]: + size = scale * vals[groups['40']][0] + attribs = {'x': '%f' % x, 'y': '%f' % y, 'style': 'font-size: %.1fpx; fill: %s; font-family: %s' % (size, color, options.font)} + angle = 0 # default angle in degrees + if vals[groups['50']]: + angle = vals[groups['50']][0] + attribs.update({'transform': 'rotate (%f %f %f)' % (-angle, x, y)}) + elif vals[groups['21']]: + if vals[groups['21']][0] == 1.0: + attribs.update({'transform': 'rotate (%f %f %f)' % (-90, x, y)}) + elif vals[groups['21']][0] == -1.0: + attribs.update({'transform': 'rotate (%f %f %f)' % (90, x, y)}) + node = layer.add(inkex.Text(**attribs)) + node.set('sodipodi:linespacing', '125%') + text = '' + if vals[groups['3']]: + for i in range(0, len(vals[groups['3']])): + text += vals[groups['3']][i] + if vals[groups['1']]: + text += vals[groups['1']][0] + found = text.find(r'\P') # new line + while found > -1: + tspan = node.add(inkex.Tspan()) + tspan.set('sodipodi:role', 'line') + tspan.text = text[:found] + text = text[(found + 2):] + found = text.find(r'\P') + tspan = node.add(inkex.Tspan()) + tspan.set('sodipodi:role', 'line') + tspan.text = text + + +def export_POINT(w): + # mandatory group codes : (10, 20) (x, y) + if vals[groups['10']] and vals[groups['20']]: + if options.gcodetoolspoints: + generate_gcodetools_point(vals[groups['10']][0], vals[groups['20']][0]) + else: + generate_ellipse(vals[groups['10']][0], vals[groups['20']][0], w / 2, 0.0, 1.0, 0.0, 0.0) + + +def export_LINE(): + # mandatory group codes : (10, 11, 20, 21) (x1, x2, y1, y2) + if vals[groups['10']] and vals[groups['11']] and vals[groups['20']] and vals[groups['21']]: + path = 'M %f,%f %f,%f' % (vals[groups['10']][0], vals[groups['20']][0], scale * (extrude * vals[groups['11']][0] - xmin), height - scale * (vals[groups['21']][0] - ymin)) + attribs = {'d': path, 'style': style} + etree.SubElement(layer, 'path', attribs) + + +def export_SPLINE(): + # see : http://www.mactech.com/articles/develop/issue_25/schneider.html + # mandatory group codes : (10, 20, 40, 70) (x[], y[], knots[], flags) + if vals[groups['70']] and len(vals[groups['10']]) == len(vals[groups['20']]) and vals[groups['10']] and vals[groups['20']] and vals[groups['40']]: + knots = len(vals[groups['40']]) + ctrls = len(vals[groups['10']]) + if ctrls > 3 and knots == ctrls + 4: # cubic + if ctrls > 4: + for i in range(knots - 5, 3, -1): + if vals[groups['40']][i] != vals[groups['40']][i - 1] and vals[groups['40']][i] != vals[groups['40']][i + 1]: + a0 = (vals[groups['40']][i] - vals[groups['40']][i - 2]) / (vals[groups['40']][i + 1] - vals[groups['40']][i - 2]) + a1 = (vals[groups['40']][i] - vals[groups['40']][i - 1]) / (vals[groups['40']][i + 2] - vals[groups['40']][i - 1]) + vals[groups['10']].insert(i - 1, (1.0 - a1) * vals[groups['10']][i - 2] + a1 * vals[groups['10']][i - 1]) + vals[groups['20']].insert(i - 1, (1.0 - a1) * vals[groups['20']][i - 2] + a1 * vals[groups['20']][i - 1]) + vals[groups['10']][i - 2] = (1.0 - a0) * vals[groups['10']][i - 3] + a0 * vals[groups['10']][i - 2] + vals[groups['20']][i - 2] = (1.0 - a0) * vals[groups['20']][i - 3] + a0 * vals[groups['20']][i - 2] + vals[groups['40']].insert(i, vals[groups['40']][i]) + knots = len(vals[groups['40']]) + for i in range(knots - 6, 3, -2): + if vals[groups['40']][i] != vals[groups['40']][i + 2] and vals[groups['40']][i - 1] != vals[groups['40']][i + 1] and vals[groups['40']][i - 2] != vals[groups['40']][i]: + a1 = (vals[groups['40']][i] - vals[groups['40']][i - 1]) / (vals[groups['40']][i + 2] - vals[groups['40']][i - 1]) + vals[groups['10']].insert(i - 1, (1.0 - a1) * vals[groups['10']][i - 2] + a1 * vals[groups['10']][i - 1]) + vals[groups['20']].insert(i - 1, (1.0 - a1) * vals[groups['20']][i - 2] + a1 * vals[groups['20']][i - 1]) + ctrls = len(vals[groups['10']]) + path = 'M %f,%f' % (vals[groups['10']][0], vals[groups['20']][0]) + for i in range(0, (ctrls - 1) // 3): + path += ' C %f,%f %f,%f %f,%f' % (vals[groups['10']][3 * i + 1], vals[groups['20']][3 * i + 1], vals[groups['10']][3 * i + 2], vals[groups['20']][3 * i + 2], vals[groups['10']][3 * i + 3], vals[groups['20']][3 * i + 3]) + if vals[groups['70']][0] & 1: # closed path + path += ' z' + attribs = {'d': path, 'style': style} + etree.SubElement(layer, 'path', attribs) + if ctrls == 3 and knots == 6: # quadratic + path = 'M %f,%f Q %f,%f %f,%f' % (vals[groups['10']][0], vals[groups['20']][0], vals[groups['10']][1], vals[groups['20']][1], vals[groups['10']][2], vals[groups['20']][2]) + attribs = {'d': path, 'style': style} + etree.SubElement(layer, 'path', attribs) + if ctrls == 5 and knots == 8: # spliced quadratic + path = 'M %f,%f Q %f,%f %f,%f Q %f,%f %f,%f' % (vals[groups['10']][0], vals[groups['20']][0], vals[groups['10']][1], vals[groups['20']][1], vals[groups['10']][2], vals[groups['20']][2], vals[groups['10']][3], vals[groups['20']][3], vals[groups['10']][4], vals[groups['20']][4]) + attribs = {'d': path, 'style': style} + etree.SubElement(layer, 'path', attribs) + + +def export_CIRCLE(): + # mandatory group codes : (10, 20, 40) (x, y, radius) + if vals[groups['10']] and vals[groups['20']] and vals[groups['40']]: + generate_ellipse(vals[groups['10']][0], vals[groups['20']][0], scale * vals[groups['40']][0], 0.0, 1.0, 0.0, 0.0) + + +def export_ARC(): + # mandatory group codes : (10, 20, 40, 50, 51) (x, y, radius, angle1, angle2) + if vals[groups['10']] and vals[groups['20']] and vals[groups['40']] and vals[groups['50']] and vals[groups['51']]: + generate_ellipse(vals[groups['10']][0], vals[groups['20']][0], scale * vals[groups['40']][0], 0.0, 1.0, vals[groups['50']][0] * math.pi / 180.0, vals[groups['51']][0] * math.pi / 180.0) + + +def export_ELLIPSE(): + # mandatory group codes : (10, 11, 20, 21, 40, 41, 42) (xc, xm, yc, ym, width ratio, angle1, angle2) + if vals[groups['10']] and vals[groups['11']] and vals[groups['20']] and vals[groups['21']] and vals[groups['40']] and vals[groups['41']] and vals[groups['42']]: + generate_ellipse(vals[groups['10']][0], vals[groups['20']][0], scale * vals[groups['11']][0], scale * vals[groups['21']][0], vals[groups['40']][0], vals[groups['41']][0], vals[groups['42']][0]) + + +def export_LEADER(): + # mandatory group codes : (10, 20) (x, y) + if vals[groups['10']] and vals[groups['20']]: + if len(vals[groups['10']]) > 1 and len(vals[groups['20']]) == len(vals[groups['10']]): + path = 'M %f,%f' % (vals[groups['10']][0], vals[groups['20']][0]) + for i in range(1, len(vals[groups['10']])): + path += ' %f,%f' % (vals[groups['10']][i], vals[groups['20']][i]) + attribs = {'d': path, 'style': style} + etree.SubElement(layer, 'path', attribs) + + +def export_LWPOLYLINE(): + # mandatory group codes : (10, 20, 70) (x, y, flags) + if vals[groups['10']] and vals[groups['20']] and vals[groups['70']]: + if len(vals[groups['10']]) > 1 and len(vals[groups['20']]) == len(vals[groups['10']]): + # optional group codes : (42) (bulge) + iseqs = 0 + ibulge = 0 + if vals[groups['70']][0] & 1: # closed path + seqs.append('20') + vals[groups['10']].append(vals[groups['10']][0]) + vals[groups['20']].append(vals[groups['20']][0]) + while seqs[iseqs] != '20': + iseqs += 1 + path = 'M %f,%f' % (vals[groups['10']][0], vals[groups['20']][0]) + xold = vals[groups['10']][0] + yold = vals[groups['20']][0] + for i in range(1, len(vals[groups['10']])): + bulge = 0 + iseqs += 1 + while seqs[iseqs] != '20': + if seqs[iseqs] == '42': + bulge = vals[groups['42']][ibulge] + ibulge += 1 + iseqs += 1 + if bulge: + sweep = 0 # sweep CCW + if bulge < 0: + sweep = 1 # sweep CW + bulge = -bulge + large = 0 # large-arc-flag + if bulge > 1: + large = 1 + r = math.sqrt((vals[groups['10']][i] - xold) ** 2 + (vals[groups['20']][i] - yold) ** 2) + r = 0.25 * r * (bulge + 1.0 / bulge) + path += ' A %f,%f 0.0 %d %d %f,%f' % (r, r, large, sweep, vals[groups['10']][i], vals[groups['20']][i]) + else: + path += ' L %f,%f' % (vals[groups['10']][i], vals[groups['20']][i]) + xold = vals[groups['10']][i] + yold = vals[groups['20']][i] + if vals[groups['70']][0] & 1: # closed path + path += ' z' + attribs = {'d': path, 'style': style} + etree.SubElement(layer, 'path', attribs) + + +def export_HATCH(): + # mandatory group codes : (10, 20, 70, 72, 92, 93) (x, y, fill, Edge Type, Path Type, Number of edges) + if vals[groups['10']] and vals[groups['20']] and vals[groups['70']] and vals[groups['72']] and vals[groups['92']] and vals[groups['93']]: + if len(vals[groups['10']]) > 1 and len(vals[groups['20']]) == len(vals[groups['10']]): + # optional group codes : (11, 21, 40, 50, 51, 73) (x, y, r, angle1, angle2, CCW) + i10 = 1 # count start points + i11 = 0 # count line end points + i40 = 0 # count circles + i72 = 0 # count edge type flags + path = '' + for i in range(0, len(vals[groups['93']])): + xc = vals[groups['10']][i10] + yc = vals[groups['20']][i10] + if vals[groups['72']][i72] == 2: # arc + rm = scale * vals[groups['40']][i40] + a1 = vals[groups['50']][i40] + path += 'M %f,%f ' % (xc + rm * math.cos(a1 * math.pi / 180.0), yc + rm * math.sin(a1 * math.pi / 180.0)) + else: + a1 = 0 + path += 'M %f,%f ' % (xc, yc) + for j in range(0, vals[groups['93']][i]): + if vals[groups['92']][i] & 2: # polyline + if j > 0: + path += 'L %f,%f ' % (vals[groups['10']][i10], vals[groups['20']][i10]) + if j == vals[groups['93']][i] - 1: + i72 += 1 + elif vals[groups['72']][i72] == 2: # arc + xc = vals[groups['10']][i10] + yc = vals[groups['20']][i10] + rm = scale * vals[groups['40']][i40] + a2 = vals[groups['51']][i40] + diff = (a2 - a1 + 360) % 360 + sweep = 1 - vals[groups['73']][i40] # sweep CCW + large = 0 # large-arc-flag + if diff: + path += 'A %f,%f 0.0 %d %d %f,%f ' % (rm, rm, large, sweep, xc + rm * math.cos(a2 * math.pi / 180.0), yc + rm * math.sin(a2 * math.pi / 180.0)) + else: + path += 'A %f,%f 0.0 %d %d %f,%f ' % (rm, rm, large, sweep, xc + rm * math.cos((a1 + 180.0) * math.pi / 180.0), yc + rm * math.sin((a1 + 180.0) * math.pi / 180.0)) + path += 'A %f,%f 0.0 %d %d %f,%f ' % (rm, rm, large, sweep, xc + rm * math.cos(a1 * math.pi / 180.0), yc + rm * math.sin(a1 * math.pi / 180.0)) + i40 += 1 + i72 += 1 + elif vals[groups['72']][i72] == 1: # line + path += 'L %f,%f ' % (scale * (extrude * vals[groups['11']][i11] - xmin), height - scale * (vals[groups['21']][i11] - ymin)) + i11 += 1 + i72 += 1 + i10 += 1 + path += "z " + if vals[groups['70']][0]: + style = formatStyle({'fill': '%s' % color}) + else: + style = formatStyle({'fill': 'url(#Hatch)', 'fill-opacity': '1.0'}) + attribs = {'d': path, 'style': style} + etree.SubElement(layer, 'path', attribs) + + +def export_DIMENSION(): + # mandatory group codes : (10, 11, 13, 14, 20, 21, 23, 24) (x1..4, y1..4) + if vals[groups['10']] and vals[groups['11']] and vals[groups['13']] and vals[groups['14']] and vals[groups['20']] and vals[groups['21']] and vals[groups['23']] and vals[groups['24']]: + dx = abs(vals[groups['10']][0] - vals[groups['13']][0]) + dy = abs(vals[groups['20']][0] - vals[groups['23']][0]) + if (vals[groups['10']][0] == vals[groups['14']][0]) and dx > 0.00001: + d = dx / scale + dy = 0 + path = 'M %f,%f %f,%f' % (vals[groups['10']][0], vals[groups['20']][0], vals[groups['13']][0], vals[groups['20']][0]) + elif (vals[groups['20']][0] == vals[groups['24']][0]) and dy > 0.00001: + d = dy / scale + dx = 0 + path = 'M %f,%f %f,%f' % (vals[groups['10']][0], vals[groups['20']][0], vals[groups['10']][0], vals[groups['23']][0]) + else: + return + attribs = {'d': path, 'style': style + '; marker-start: url(#DistanceX); marker-end: url(#DistanceX); stroke-width: 0.25px'} + etree.SubElement(layer, 'path', attribs) + x = scale * (extrude * vals[groups['11']][0] - xmin) + y = height - scale * (vals[groups['21']][0] - ymin) + size = 12 # default fontsize in px + if vals[groups['3']]: + if vals[groups['3']][0] in DIMTXT: + size = scale * DIMTXT[vals[groups['3']][0]] + if size < 2: + size = 2 + attribs = {'x': '%f' % x, 'y': '%f' % y, 'style': 'font-size: %.1fpx; fill: %s; font-family: %s; text-anchor: middle; text-align: center' % (size, color, options.font)} + if dx == 0: + attribs.update({'transform': 'rotate (%f %f %f)' % (-90, x, y)}) + node = etree.SubElement(layer, 'text', attribs) + tspan = node.add(inkex.Tspan()) + tspan.set('sodipodi:role', 'line') + tspan.text = str(float('%.2f' % d)) + + +def export_INSERT(): + # mandatory group codes : (2, 10, 20) (block name, x, y) + if vals[groups['2']] and vals[groups['10']] and vals[groups['20']]: + x = vals[groups['10']][0] + scale * xmin + y = vals[groups['20']][0] - scale * ymin - height + elem = layer.add(inkex.Use()) + elem.set('xlink:href', '#' + quote(vals[groups['2']][0].replace(" ", "_").encode("utf-8"))) + elem.transform = 'translate(%f, %f)' % (x, y) + if vals[groups['41']] and vals[groups['42']]: + elem.transform.add_scale(vals[groups['41']][0], vals[groups['42']][0]) + + +def export_BLOCK(): + # mandatory group codes : (2) (block name) + if vals[groups['2']]: + global block + block = etree.SubElement(defs, 'symbol', {'id': vals[groups['2']][0].replace(" ", "_")}) + + +def export_ENDBLK(): + global block + block = defs # initiallize with dummy + + +def export_ATTDEF(): + # mandatory group codes : (1, 2) (default, tag) + if vals[groups['1']] and vals[groups['2']]: + vals[groups['1']][0] = vals[groups['2']][0] + export_MTEXT() + + +def generate_ellipse(xc, yc, xm, ym, w, a1, a2): + rm = math.sqrt(xm * xm + ym * ym) + a = math.atan2(ym, xm) + diff = (a2 - a1 + 2 * math.pi) % (2 * math.pi) + if abs(diff) > 0.0000001 and abs(diff - 2 * math.pi) > 0.0000001: # open arc + large = 0 # large-arc-flag + if diff > math.pi: + large = 1 + xt = rm * math.cos(a1) + yt = w * rm * math.sin(a1) + x1 = xt * math.cos(a) - yt * math.sin(a) + y1 = xt * math.sin(a) + yt * math.cos(a) + xt = rm * math.cos(a2) + yt = w * rm * math.sin(a2) + x2 = xt * math.cos(a) - yt * math.sin(a) + y2 = xt * math.sin(a) + yt * math.cos(a) + path = 'M %f,%f A %f,%f %f %d 0 %f,%f' % (xc + x1, yc - y1, rm, w * rm, -180.0 * a / math.pi, large, xc + x2, yc - y2) + else: # closed arc + path = 'M %f,%f A %f,%f %f 1 0 %f,%f %f,%f %f 1 0 %f,%f z' % (xc + xm, yc - ym, rm, w * rm, -180.0 * a / math.pi, xc - xm, yc + ym, rm, w * rm, -180.0 * a / math.pi, xc + xm, yc - ym) + attribs = {'d': path, 'style': style} + etree.SubElement(layer, 'path', attribs) + + +def generate_gcodetools_point(xc, yc): + elem = layer.add(inkex.PathElement()) + elem.style = 'stroke:none;fill:#ff0000' + elem.set('inkscape:dxfpoint', '1') + elem.path = 'm %s,%s 2.9375,-6.34375 0.8125,1.90625 6.84375,-6.84375 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.8125 z' % (xc, yc) + + +# define DXF Entities and specify which Group Codes to monitor + +entities = {'MTEXT': export_MTEXT, 'TEXT': export_MTEXT, 'POINT': export_POINT, 'LINE': export_LINE, 'SPLINE': export_SPLINE, 'CIRCLE': export_CIRCLE, 'ARC': export_ARC, 'ELLIPSE': export_ELLIPSE, 'LEADER': export_LEADER, 'LWPOLYLINE': export_LWPOLYLINE, 'HATCH': export_HATCH, 'DIMENSION': export_DIMENSION, 'INSERT': export_INSERT, 'BLOCK': export_BLOCK, 'ENDBLK': export_ENDBLK, 'ATTDEF': export_ATTDEF, 'VIEWPORT': False, 'ENDSEC': False} +groups = {'1': 0, '2': 1, '3': 2, '6': 3, '8': 4, '10': 5, '11': 6, '13': 7, '14': 8, '20': 9, '21': 10, '23': 11, '24': 12, '40': 13, '41': 14, '42': 15, '50': 16, '51': 17, '62': 18, '70': 19, '72': 20, '73': 21, '92': 22, '93': 23, '230': 24, '370': 25} +colors = {1: '#FF0000', 2: '#FFFF00', 3: '#00FF00', 4: '#00FFFF', 5: '#0000FF', 6: '#FF00FF', 7: '#000000', 8: '#808080', + 9: '#C0C0C0', 10: '#FF0000', 11: '#FF7F7F', 12: '#CC0000', 13: '#CC6666', 14: '#990000', 15: '#994C4C', 16: '#7F0000', 17: '#7F3F3F', + 18: '#4C0000', 19: '#4C2626', 20: '#FF3F00', 21: '#FF9F7F', 22: '#CC3300', 23: '#CC7F66', 24: '#992600', 25: '#995F4C', 26: '#7F1F00', + 27: '#7F4F3F', 28: '#4C1300', 29: '#4C2F26', 30: '#FF7F00', 31: '#FFBF7F', 32: '#CC6600', 33: '#CC9966', 34: '#994C00', 35: '#99724C', + 36: '#7F3F00', 37: '#7F5F3F', 38: '#4C2600', 39: '#4C3926', 40: '#FFBF00', 41: '#FFDF7F', 42: '#CC9900', 43: '#CCB266', 44: '#997200', + 45: '#99854C', 46: '#7F5F00', 47: '#7F6F3F', 48: '#4C3900', 49: '#4C4226', 50: '#FFFF00', 51: '#FFFF7F', 52: '#CCCC00', 53: '#CCCC66', + 54: '#989800', 55: '#98984C', 56: '#7F7F00', 57: '#7F7F3F', 58: '#4C4C00', 59: '#4C4C26', 60: '#BFFF00', 61: '#DFFF7F', 62: '#99CC00', + 63: '#B2CC66', 64: '#729800', 65: '#85984C', 66: '#5F7F00', 67: '#6F7F3F', 68: '#394C00', 69: '#424C26', 70: '#7FFF00', 71: '#BFFF7F', + 72: '#66CC00', 73: '#99CC66', 74: '#4C9800', 75: '#72984C', 76: '#3F7F00', 77: '#5F7F3F', 78: '#264C00', 79: '#394C26', 80: '#3FFF00', + 81: '#9FFF7F', 82: '#33CC00', 83: '#7FCC66', 84: '#269800', 85: '#5F984C', 86: '#1F7F00', 87: '#4F7F3F', 88: '#134C00', 89: '#2F4C26', + 90: '#00FF00', 91: '#7FFF7F', 92: '#00CC00', 93: '#66CC66', 94: '#009800', 95: '#4C984C', 96: '#007F00', 97: '#3F7F3F', 98: '#004C00', + 99: '#264C26', 100: '#00FF3F', 101: '#7FFF9F', 102: '#00CC33', 103: '#66CC7F', 104: '#009826', 105: '#4C985F', 106: '#007F1F', + 107: '#3F7F4F', 108: '#004C13', 109: '#264C2F', 110: '#00FF7F', 111: '#7FFFBF', 112: '#00CC66', 113: '#66CC99', 114: '#00984C', + 115: '#4C9872', 116: '#007F3F', 117: '#3F7F5F', 118: '#004C26', 119: '#264C39', 120: '#00FFBF', 121: '#7FFFDF', 122: '#00CC99', + 123: '#66CCB2', 124: '#009872', 125: '#4C9885', 126: '#007F5F', 127: '#3F7F6F', 128: '#004C39', 129: '#264C42', 130: '#00FFFF', + 131: '#7FFFFF', 132: '#00CCCC', 133: '#66CCCC', 134: '#009898', 135: '#4C9898', 136: '#007F7F', 137: '#3F7F7F', 138: '#004C4C', + 139: '#264C4C', 140: '#00BFFF', 141: '#7FDFFF', 142: '#0099CC', 143: '#66B2CC', 144: '#007298', 145: '#4C8598', 146: '#005F7F', + 147: '#3F6F7F', 148: '#00394C', 149: '#26424C', 150: '#007FFF', 151: '#7FBFFF', 152: '#0066CC', 153: '#6699CC', 154: '#004C98', + 155: '#4C7298', 156: '#003F7F', 157: '#3F5F7F', 158: '#00264C', 159: '#26394C', 160: '#003FFF', 161: '#7F9FFF', 162: '#0033CC', + 163: '#667FCC', 164: '#002698', 165: '#4C5F98', 166: '#001F7F', 167: '#3F4F7F', 168: '#00134C', 169: '#262F4C', 170: '#0000FF', + 171: '#7F7FFF', 172: '#0000CC', 173: '#6666CC', 174: '#000098', 175: '#4C4C98', 176: '#00007F', 177: '#3F3F7F', 178: '#00004C', + 179: '#26264C', 180: '#3F00FF', 181: '#9F7FFF', 182: '#3300CC', 183: '#7F66CC', 184: '#260098', 185: '#5F4C98', 186: '#1F007F', + 187: '#4F3F7F', 188: '#13004C', 189: '#2F264C', 190: '#7F00FF', 191: '#BF7FFF', 192: '#6600CC', 193: '#9966CC', 194: '#4C0098', + 195: '#724C98', 196: '#3F007F', 197: '#5F3F7F', 198: '#26004C', 199: '#39264C', 200: '#BF00FF', 201: '#DF7FFF', 202: '#9900CC', + 203: '#B266CC', 204: '#720098', 205: '#854C98', 206: '#5F007F', 207: '#6F3F7F', 208: '#39004C', 209: '#42264C', 210: '#FF00FF', + 211: '#FF7FFF', 212: '#CC00CC', 213: '#CC66CC', 214: '#980098', 215: '#984C98', 216: '#7F007F', 217: '#7F3F7F', 218: '#4C004C', + 219: '#4C264C', 220: '#FF00BF', 221: '#FF7FDF', 222: '#CC0099', 223: '#CC66B2', 224: '#980072', 225: '#984C85', 226: '#7F005F', + 227: '#7F3F6F', 228: '#4C0039', 229: '#4C2642', 230: '#FF007F', 231: '#FF7FBF', 232: '#CC0066', 233: '#CC6699', 234: '#98004C', + 235: '#984C72', 236: '#7F003F', 237: '#7F3F5F', 238: '#4C0026', 239: '#4C2639', 240: '#FF003F', 241: '#FF7F9F', 242: '#CC0033', + 243: '#CC667F', 244: '#980026', 245: '#984C5F', 246: '#7F001F', 247: '#7F3F4F', 248: '#4C0013', 249: '#4C262F', 250: '#333333', + 251: '#5B5B5B', 252: '#848484', 253: '#ADADAD', 254: '#D6D6D6', 255: '#FFFFFF'} + + +class DxfInput(inkex.InputExtension): + def add_arguments(self, pars): + pars.add_argument("--tab", default="Options") + pars.add_argument("--scalemethod", default="manual") + pars.add_argument("--scale", default="1.0") + pars.add_argument("--xmin", default="0.0") + pars.add_argument("--ymin", default="0.0") + pars.add_argument("--gcodetoolspoints", default=True, type=inkex.Boolean) + pars.add_argument("--encoding", dest="input_encode", default="latin_1") + pars.add_argument("--font", default="Arial") + + def load(self, stream): + return stream + + def effect(self): + global options + global defs + global entity + global vals + global seqs + global style + global layer + global scale + global color + global extrude + global xmin + global ymin + global height + + options = self.options + + doc = self.get_template(width=210 * 96 / 25.4, height=297 * 96 / 25.4) + svg = doc.getroot() + defs = svg.defs + marker = etree.SubElement(defs, 'marker', {'id': 'DistanceX', 'orient': 'auto', 'refX': '0.0', 'refY': '0.0', 'style': 'overflow:visible'}) + etree.SubElement(marker, 'path', {'d': 'M 3,-3 L -3,3 M 0,-5 L 0,5', 'style': 'stroke:#000000; stroke-width:0.5'}) + pattern = etree.SubElement(defs, 'pattern', {'id': 'Hatch', 'patternUnits': 'userSpaceOnUse', 'width': '8', 'height': '8', 'x': '0', 'y': '0'}) + etree.SubElement(pattern, 'path', {'d': 'M8 4 l-4,4', 'stroke': '#000000', 'stroke-width': '0.25', 'linecap': 'square'}) + etree.SubElement(pattern, 'path', {'d': 'M6 2 l-4,4', 'stroke': '#000000', 'stroke-width': '0.25', 'linecap': 'square'}) + etree.SubElement(pattern, 'path', {'d': 'M4 0 l-4,4', 'stroke': '#000000', 'stroke-width': '0.25', 'linecap': 'square'}) + + def _get_line(): + return self.document.readline().strip().decode(options.input_encode) + + def get_line(): + return _get_line(), _get_line() + + def get_group(group): + line = get_line() + if line[0] == group: + return float(line[1]) + return 0.0 + + xmax = xmin = ymin = 0.0 + height = 297.0 * 96.0 / 25.4 # default A4 height in pixels + measurement = 0 # default inches + line = get_line() + polylines = 0 + flag = 0 # (0, 1, 2, 3) = (none, LAYER, LTYPE, DIMTXT) + layer_colors = {} # store colors by layer + layer_nodes = {} # store nodes by layer + linetypes = {} # store linetypes by name + DIMTXT = {} # store DIMENSION text sizes + + while line[0] and line[1] != 'BLOCKS': + line = get_line() + if options.scalemethod == 'file': + if line[1] == '$MEASUREMENT': + measurement = get_group('70') + elif options.scalemethod == 'auto': + if line[1] == '$EXTMIN': + xmin = get_group('10') + ymin = get_group('20') + if line[1] == '$EXTMAX': + xmax = get_group('10') + if flag == 1 and line[0] == '2': + layername = line[1] + layer_nodes[layername] = svg.add(inkex.Layer.new(layername)) + if flag == 2 and line[0] == '2': + linename = line[1] + linetypes[linename] = [] + if flag == 3 and line[0] == '2': + stylename = line[1] + if line[0] == '2' and line[1] == 'LAYER': + flag = 1 + if line[0] == '2' and line[1] == 'LTYPE': + flag = 2 + if line[0] == '2' and line[1] == 'DIMSTYLE': + flag = 3 + if flag == 1 and line[0] == '62': + layer_colors[layername] = int(line[1]) + if flag == 2 and line[0] == '49': + linetypes[linename].append(float(line[1])) + if flag == 3 and line[0] == '140': + DIMTXT[stylename] = float(line[1]) + if line[0] == '0' and line[1] == 'ENDTAB': + flag = 0 + + if options.scalemethod == 'file': + scale = 25.4 # default inches + if measurement == 1.0: + scale = 1.0 # use mm + elif options.scalemethod == 'auto': + scale = 1.0 + if xmax > xmin: + scale = 210.0 / (xmax - xmin) # scale to A4 width + else: + scale = float(options.scale) # manual scale factor + xmin = float(options.xmin) + ymin = float(options.ymin) + svg.description('%s - scale = %f, origin = (%f, %f), method = %s' % ( + os.path.basename(options.input_file), scale, xmin, ymin, options.scalemethod)) + scale *= 96.0 / 25.4 # convert from mm to pixels + + if '0' not in layer_nodes: + layer_nodes['0'] = svg.add(inkex.Layer.new('0')) + + layer_colors['0'] = 7 + + for linename in linetypes.keys(): # scale the dashed lines + linetype = '' + for length in linetypes[linename]: + if length == 0: # test for dot + linetype += ' 0.5,' + else: + linetype += '%.4f,' % math.fabs(length * scale) + if linetype == '': + linetypes[linename] = 'stroke-linecap: round' + else: + linetypes[linename] = 'stroke-dasharray:' + linetype + + entity = '' + inENTITIES = False + block = defs # initiallize with dummy + while line[0] and (line[1] != 'ENDSEC' or not inENTITIES): + line = get_line() + if line[1] == 'ENTITIES': + inENTITIES = True + elif line[1] == 'POLYLINE': + polylines += 1 + if entity and line[0] in groups: + seqs.append(line[0]) # list of group codes + if line[0] in ('1', '2', '3', '6', '8'): # text value + val = line[1].replace(r'\~', ' ') + val = re.sub(r'\\A.*;', '', val) + val = re.sub(r'\\H.*;', '', val) + val = re.sub(r'\^I', '', val) + val = re.sub(r'{\\L', '', val) + val = re.sub(r'}', '', val) + val = re.sub(r'\\S.*;', '', val) + val = re.sub(r'\\W.*;', '', val) + val = val + val = re.sub(r'\\U\+([0-9A-Fa-f]{4})', re_hex2unichar, val) + elif line[0] == '62' or line[0] == '70' or line[0] == '92' or line[0] == '93': + val = int(line[1]) + else: # unscaled float value + val = float(line[1]) + vals[groups[line[0]]].append(val) + elif line[1] in entities: + if entity in entities: + if block != defs: # in a BLOCK + layer = block + elif vals[groups['8']]: # use Common Layer Name + if not vals[groups['8']][0]: + vals[groups['8']][0] = '0' # use default name + if vals[groups['8']][0] not in layer_nodes: + layer_nodes[vals[groups['8']][0]] = svg.add(inkex.Layer.new(vals[groups['8']][0])) + layer = layer_nodes[vals[groups['8']][0]] + color = '#000000' # default color + if vals[groups['8']]: + if vals[groups['8']][0] in layer_colors: + if layer_colors[vals[groups['8']][0]] in colors: + color = colors[layer_colors[vals[groups['8']][0]]] + if vals[groups['62']]: # Common Color Number + if vals[groups['62']][0] in colors: + color = colors[vals[groups['62']][0]] + style = formatStyle({'stroke': '%s' % color, 'fill': 'none'}) + w = 0.5 # default lineweight for POINT + if vals[groups['370']]: # Common Lineweight + if vals[groups['370']][0] > 0: + w = 96.0 / 25.4 * vals[groups['370']][0] / 100.0 + if w < 0.5: + w = 0.5 + style = formatStyle({'stroke': '%s' % color, 'fill': 'none', 'stroke-width': '%.1f' % w}) + if vals[groups['6']]: # Common Linetype + if vals[groups['6']][0] in linetypes: + style += ';' + linetypes[vals[groups['6']][0]] + extrude = 1.0 + if vals[groups['230']]: + extrude = float(vals[groups['230']][0]) + for xgrp in ['10', '13', '14']: # scale/reflect x values + if vals[groups[xgrp]]: + for i in range(0, len(vals[groups[xgrp]])): + vals[groups[xgrp]][i] = scale * (extrude * vals[groups[xgrp]][i] - xmin) + for ygrp in ['20', '23', '24']: # scale y values + if vals[groups[ygrp]]: + for i in range(0, len(vals[groups[ygrp]])): + vals[groups[ygrp]][i] = height - scale * (vals[groups[ygrp]][i] - ymin) + if extrude == -1.0: # reflect angles + if vals[groups['50']] and vals[groups['51']]: + temp = vals[groups['51']][0] + vals[groups['51']][0] = 180.0 - vals[groups['50']][0] + vals[groups['50']][0] = 180.0 - temp + if entities[entity]: + if entity == 'POINT': + entities[entity](w) + else: + entities[entity]() + entity = line[1] + vals = [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []] + seqs = [] + + if polylines: + inkex.errormsg(_('%d ENTITIES of type POLYLINE encountered and ignored. Please try to convert to Release 13 format using QCad.') % polylines) + self.document = doc + + +if __name__ == '__main__': + DxfInput().run() diff --git a/share/extensions/dxf_outlines.inx b/share/extensions/dxf_outlines.inx new file mode 100644 index 0000000..6773667 --- /dev/null +++ b/share/extensions/dxf_outlines.inx @@ -0,0 +1,57 @@ + + + Desktop Cutting Plotter R14 + org.ekips.output.dxf_outlines + org.inkscape.output.svg.inkscape + + + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .dxf + image/dxf + Desktop Cutting Plotter (AutoCAD DXF R14) (*.dxf) + Desktop Cutting Plotter + true + + + diff --git a/share/extensions/dxf_outlines.py b/share/extensions/dxf_outlines.py new file mode 100755 index 0000000..7217e75 --- /dev/null +++ b/share/extensions/dxf_outlines.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005,2007,2008 Aaron Spike, aaron@ekips.org +# Copyright (C) 2008,2010 Alvin Penner, penner@vaxxine.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This file output script for Inkscape creates a AutoCAD R14 DXF file. +The spec can be found here: http://www.autodesk.com/techpubs/autocad/acadr14/dxf/index.htm. + + File history: + - template dxf_outlines.dxf added Feb 2008 by Alvin Penner +- ROBO-Master output option added Aug 2008 +- ROBO-Master multispline output added Sept 2008 +- LWPOLYLINE output modification added Dec 2008 +- toggle between LINE/LWPOLYLINE added Jan 2010 +- support for transform elements added July 2010 +- support for layers added July 2010 +- support for rectangle added Dec 2010 +""" + +from __future__ import print_function + +import inkex +from inkex import colors, bezier, Transform, Group, Layer, Use, PathElement, \ + Rectangle, Line, Circle, Ellipse + + +def get_matrix(u, i, j): + if j == i + 2: + return (u[i]-u[i-1])*(u[i]-u[i-1])/(u[i+2]-u[i-1])/(u[i+1]-u[i-1]) + elif j == i + 1: + return ((u[i]-u[i-1])*(u[i+2]-u[i])/(u[i+2]-u[i-1]) \ + + (u[i+1]-u[i])*(u[i]-u[i-2])/(u[i+1]-u[i-2]))/(u[i+1]-u[i-1]) + elif j == i: + return (u[i+1]-u[i])*(u[i+1]-u[i])/(u[i+1]-u[i-2])/(u[i+1]-u[i-1]) + else: + return 0 + +def get_fit(u, csp, col): + return (1-u)**3*csp[0][col] + 3*(1-u)**2*u*csp[1][col] \ + + 3*(1-u)*u**2*csp[2][col] + u**3*csp[3][col] + +class DxfOutlines(inkex.OutputExtension): + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("-R", "--ROBO", type=inkex.Boolean, default=False) + pars.add_argument("-P", "--POLY", type=inkex.Boolean, default=False) + pars.add_argument("--units", default="72./96") # Points + pars.add_argument("--encoding", dest="char_encode", default="latin_1") + pars.add_argument("--layer_option", default="all") + pars.add_argument("--layer_name") + + self.dxf = [] + self.handle = 255 # handle for DXF ENTITY + self.layers = ['0'] + self.layer = '0' # mandatory layer + self.layernames = [] + self.csp_old = [[0.0, 0.0]] * 4 # previous spline + self.d = [0.0] # knot vector + self.poly = [[0.0, 0.0]] # LWPOLYLINE data + + def save(self, stream): + stream.write(b''.join(self.dxf)) + + def dxf_add(self, str): + self.dxf.append(str.encode(self.options.char_encode)) + + def dxf_line(self, csp): + """Draw a line in the DXF format""" + self.handle += 1 + self.dxf_add(" 0\nLINE\n 5\n%x\n100\nAcDbEntity\n 8\n%s\n 62\n%d\n100\nAcDbLine\n" % (self.handle, self.layer, self.color)) + self.dxf_add(" 10\n%f\n 20\n%f\n 30\n0.0\n 11\n%f\n 21\n%f\n 31\n0.0\n" % (csp[0][0], csp[0][1], csp[1][0], csp[1][1])) + + def LWPOLY_line(self, csp): + if (abs(csp[0][0] - self.poly[-1][0]) > .0001 + or abs(csp[0][1] - self.poly[-1][1]) > .0001 + or self.color_LWPOLY != self.color): # THIS LINE IS NEW + self.LWPOLY_output() # terminate current polyline + self.poly = [csp[0]] # initiallize new polyline + self.color_LWPOLY = self.color + self.layer_LWPOLY = self.layer + self.poly.append(csp[1]) + + def LWPOLY_output(self): + if len(self.poly) == 1: + return + self.handle += 1 + closed = 1 + if (abs(self.poly[0][0] - self.poly[-1][0]) > .0001 + or abs(self.poly[0][1] - self.poly[-1][1]) > .0001): + closed = 0 + self.dxf_add(" 0\nLWPOLYLINE\n 5\n%x\n100\nAcDbEntity\n 8\n%s\n 62\n%d\n100\nAcDbPolyline\n 90\n%d\n 70\n%d\n" % (self.handle, self.layer_LWPOLY, self.color_LWPOLY, len(self.poly) - closed, closed)) + for i in range(len(self.poly) - closed): + self.dxf_add(" 10\n%f\n 20\n%f\n 30\n0.0\n" % (self.poly[i][0], self.poly[i][1])) + + def dxf_spline(self, csp): + knots = 8 + ctrls = 4 + self.handle += 1 + self.dxf_add(" 0\nSPLINE\n 5\n%x\n100\nAcDbEntity\n 8\n%s\n 62\n%d\n100\nAcDbSpline\n" % (self.handle, self.layer, self.color)) + self.dxf_add(" 70\n8\n 71\n3\n 72\n%d\n 73\n%d\n 74\n0\n" % (knots, ctrls)) + for i in range(2): + for j in range(4): + self.dxf_add(" 40\n%d\n" % i) + for i in csp: + self.dxf_add(" 10\n%f\n 20\n%f\n 30\n0.0\n" % (i[0], i[1])) + + def ROBO_spline(self, csp): + """this spline has zero curvature at the endpoints, as in ROBO-Master""" + if (abs(csp[0][0] - self.csp_old[3][0]) > .0001 + or abs(csp[0][1] - self.csp_old[3][1]) > .0001 + or abs((csp[1][1] - csp[0][1]) * (self.csp_old[3][0] - self.csp_old[2][0]) - (csp[1][0] - csp[0][0]) * (self.csp_old[3][1] - self.csp_old[2][1])) > .001): + self.ROBO_output() # terminate current spline + self.xfit = [csp[0][0]] # initiallize new spline + self.yfit = [csp[0][1]] + self.d = [0.0] + self.color_ROBO = self.color + self.layer_ROBO = self.layer + self.xfit += 3 * [0.0] + self.yfit += 3 * [0.0] + self.d += 3 * [0.0] + for i in range(1, 4): + j = len(self.d) + i - 4 + self.xfit[j] = get_fit(i / 3.0, csp, 0) + self.yfit[j] = get_fit(i / 3.0, csp, 1) + self.d[j] = self.d[j - 1] + bezier.pointdistance((self.xfit[j - 1], self.yfit[j - 1]), (self.xfit[j], self.yfit[j])) + self.csp_old = csp + + def ROBO_output(self): + try: + import numpy + from numpy.linalg import solve + except ImportError: + inkex.errormsg("Failed to import the numpy or numpy.linalg modules. These modules are required by the ROBO option. Please install them and try again.") + return + + if len(self.d) == 1: + return + fits = len(self.d) + ctrls = fits + 2 + knots = ctrls + 4 + self.xfit += 2 * [0.0] # pad with 2 endpoint constraints + self.yfit += 2 * [0.0] + self.d += 6 * [0.0] # pad with 3 duplicates at each end + self.d[fits + 2] = self.d[fits + 1] = self.d[fits] = self.d[fits - 1] + + solmatrix = numpy.zeros((ctrls, ctrls), dtype=float) + for i in range(fits): + solmatrix[i, i] = get_matrix(self.d, i, i) + solmatrix[i, i + 1] = get_matrix(self.d, i, i + 1) + solmatrix[i, i + 2] = get_matrix(self.d, i, i + 2) + solmatrix[fits, 0] = self.d[2] / self.d[fits - 1] # curvature at start = 0 + solmatrix[fits, 1] = -(self.d[1] + self.d[2]) / self.d[fits - 1] + solmatrix[fits, 2] = self.d[1] / self.d[fits - 1] + solmatrix[fits + 1, fits - 1] = (self.d[fits - 1] - self.d[fits - 2]) / self.d[fits - 1] # curvature at end = 0 + solmatrix[fits + 1, fits] = (self.d[fits - 3] + self.d[fits - 2] - 2 * self.d[fits - 1]) / self.d[fits - 1] + solmatrix[fits + 1, fits + 1] = (self.d[fits - 1] - self.d[fits - 3]) / self.d[fits - 1] + xctrl = solve(solmatrix, self.xfit) + yctrl = solve(solmatrix, self.yfit) + self.handle += 1 + self.dxf_add(" 0\nSPLINE\n 5\n%x\n100\nAcDbEntity\n 8\n%s\n 62\n%d\n100\nAcDbSpline\n" % (self.handle, self.layer_ROBO, self.color_ROBO)) + self.dxf_add(" 70\n0\n 71\n3\n 72\n%d\n 73\n%d\n 74\n%d\n" % (knots, ctrls, fits)) + for i in range(knots): + self.dxf_add(" 40\n%f\n" % self.d[i - 3]) + for i in range(ctrls): + self.dxf_add(" 10\n%f\n 20\n%f\n 30\n0.0\n" % (xctrl[i], yctrl[i])) + for i in range(fits): + self.dxf_add(" 11\n%f\n 21\n%f\n 31\n0.0\n" % (self.xfit[i], self.yfit[i])) + + def process_shape(self, node, mat): + rgb = (0, 0, 0) + style = node.get('style') + if style: + style = dict(inkex.Style.parse_str(style)) + if 'stroke' in style: + if style['stroke'] and style['stroke'] != 'none' and style['stroke'][0:3] != 'url': + rgb = inkex.Color(style['stroke']).to_rgb() + hsl = colors.rgb_to_hsl(rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0) + self.color = 7 # default is black + if hsl[2]: + self.color = 1 + (int(6 * hsl[0] + 0.5) % 6) # use 6 hues + + if not isinstance(node, (PathElement, Rectangle, Line, Circle, Ellipse)): + return + + # Transforming /after/ superpath is more reliable than before + # because of some issues with arcs in transformations + for sub in node.path.to_superpath().transform(Transform(mat) * node.transform): + for i in range(len(sub) - 1): + s = sub[i] + e = sub[i + 1] + if s[1] == s[2] and e[0] == e[1]: + if self.options.POLY: + self.LWPOLY_line([s[1], e[1]]) + else: + self.dxf_line([s[1], e[1]]) + elif self.options.ROBO: + self.ROBO_spline([s[1], s[2], e[0], e[1]]) + else: + self.dxf_spline([s[1], s[2], e[0], e[1]]) + + def process_clone(self, node): + """Process a clone node, looking for internal paths""" + trans = node.get('transform') + x = node.get('x') + y = node.get('y') + mat = Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) + if trans: + mat *= Transform(trans) + if x: + mat *= Transform([[1.0, 0.0, float(x)], [0.0, 1.0, 0.0]]) + if y: + mat *= Transform([[1.0, 0.0, 0.0], [0.0, 1.0, float(y)]]) + # push transform + if trans or x or y: + self.groupmat.append(Transform(self.groupmat[-1]) * mat) + # get referenced node + refid = node.get('xlink:href') + refnode = self.svg.getElementById(refid[1:]) + if refnode is not None: + if isinstance(refnode, Group): + self.process_group(refnode) + elif isinstance(refnode, Use): + self.process_clone(refnode) + else: + self.process_shape(refnode, self.groupmat[-1]) + # pop transform + if trans or x or y: + self.groupmat.pop() + + def process_group(self, group): + """Process group elements""" + if isinstance(group, Layer): + style = group.style + if style.get('display', '') == 'none' and self.options.layer_option and self.options.layer_option == 'visible': + return + layer = group.label + if self.options.layer_name and self.options.layer_option == 'name': + if not layer.lower() in self.options.layer_name: + return + + layer = layer.replace(' ', '_') + if layer in self.layers: + self.layer = layer + trans = group.get('transform') + if trans: + self.groupmat.append(Transform(self.groupmat[-1]) * Transform(trans)) + for node in group: + if isinstance(node, Group): + self.process_group(node) + elif isinstance(node, Use): + self.process_clone(node) + else: + self.process_shape(node, self.groupmat[-1]) + if trans: + self.groupmat.pop() + + def effect(self): + # Warn user if name match field is empty + if self.options.layer_option and self.options.layer_option == 'name' and not self.options.layer_name: + return inkex.errormsg("Error: Field 'Layer match name' must be filled when using 'By name match' option") + + # Split user layer data into a list: "layerA,layerb,LAYERC" becomes ["layera", "layerb", "layerc"] + if self.options.layer_name: + self.options.layer_name = self.options.layer_name.lower().split(',') + + # References: Minimum Requirements for Creating a DXF File of a 3D Model By Paul Bourke + # NURB Curves: A Guide for the Uninitiated By Philip J. Schneider + # The NURBS Book By Les Piegl and Wayne Tiller (Springer, 1995) + # self.dxf_add("999\nDXF created by Inkscape\n") # Some programs do not take comments in DXF files (KLayout 0.21.12 for example) + with open(self.get_resource('dxf14_header.txt'), 'r') as fhl: + self.dxf_add(fhl.read()) + for node in self.svg.xpath('//svg:g'): + if isinstance(node, Layer): + layer = node.label + self.layernames.append(layer.lower()) + if self.options.layer_name and self.options.layer_option and self.options.layer_option == 'name' and not layer.lower() in self.options.layer_name: + continue + layer = layer.replace(' ', '_') + if layer and layer not in self.layers: + self.layers.append(layer) + self.dxf_add(" 2\nLAYER\n 5\n2\n100\nAcDbSymbolTable\n 70\n%s\n" % len(self.layers)) + for i in range(len(self.layers)): + self.dxf_add(" 0\nLAYER\n 5\n%x\n100\nAcDbSymbolTableRecord\n100\nAcDbLayerTableRecord\n 2\n%s\n 70\n0\n 6\nCONTINUOUS\n" % (i + 80, self.layers[i])) + with open(self.get_resource('dxf14_style.txt'), 'r') as fhl: + self.dxf_add(fhl.read()) + + scale = eval(self.options.units) + if not scale: + scale = 25.4 / 96 # if no scale is specified, assume inch as baseunit + scale /= self.svg.unittouu('1px') + h = self.svg.height + doc = self.document.getroot() + # process viewBox height attribute to correct page scaling + viewBox = doc.get('viewBox') + if viewBox: + viewBox2 = viewBox.split(',') + if len(viewBox2) < 4: + viewBox2 = viewBox.split(' ') + scale *= h / self.svg.unittouu(self.svg.add_unit(viewBox2[3])) + self.groupmat = [[[scale, 0.0, 0.0], [0.0, -scale, h * scale]]] + self.process_group(doc) + if self.options.ROBO: + self.ROBO_output() + if self.options.POLY: + self.LWPOLY_output() + with open(self.get_resource('dxf14_footer.txt'), 'r') as fhl: + self.dxf_add(fhl.read()) + # Warn user if layer data seems wrong + if self.options.layer_name and self.options.layer_option and self.options.layer_option == 'name': + for layer in self.options.layer_name: + if layer not in self.layernames: + inkex.errormsg("Warning: Layer '%s' not found!" % layer) + + +if __name__ == '__main__': + DxfOutlines().run() diff --git a/share/extensions/edge3d.inx b/share/extensions/edge3d.inx new file mode 100644 index 0000000..dedfb65 --- /dev/null +++ b/share/extensions/edge3d.inx @@ -0,0 +1,21 @@ + + + Edge 3D + org.inkscape.effects.edge3d + 45 + 2 + false + 10 + 5 + 2 + 2 + + path + + + + + + diff --git a/share/extensions/edge3d.py b/share/extensions/edge3d.py new file mode 100755 index 0000000..cded66f --- /dev/null +++ b/share/extensions/edge3d.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2007 Terry Brown, terry_n_brown@yahoo.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +from math import atan2, degrees + +import inkex +from inkex import ClipPath, Filter + + +class Edge3D(inkex.EffectExtension): + """Generate a 3d edge""" + def add_arguments(self, pars): + pars.add_argument('--angle', type=float, default=45.0, + help='angle of illumination, clockwise, 45 = upper right') + pars.add_argument('--stddev', type=float, default=5.0, help='Gaussian Blur stdDeviation') + pars.add_argument('--blurheight', type=float, default=2.0, help='Gaussian Blur height') + pars.add_argument('--blurwidth', type=float, default=2.0, help='Gaussian Blur width') + pars.add_argument('--shades', type=int, default=2, help="Number of shades") + pars.add_argument('--bw', type=inkex.Boolean, help="Black and white") + pars.add_argument('--thick', type=float, default=10.0, help='stroke-width for pieces') + + def angle_between(self, start, end, angle): + """Return true if angle (degrees, clockwise, 0 = up/north) is between + angles start and end""" + + def f(x): + """Add 360 to x if x is less than 0""" + if x < 0: + return x + 360. + return x + + # rotate all inputs by start, => start = 0 + a = f(f(angle) - f(start)) + e = f(f(end) - f(start)) + return a < e + + def effect(self): + """Check each internode to see if it's in one of the wedges + for the current shade. shade is a floating point 0-1 white-black""" + # size of a wedge for shade i, wedges come in pairs + delta = 360. / self.options.shades / 2. + for node in self.svg.selection.filter(inkex.PathElement).values(): + array = node.path.to_arrays() + group = None + filt = None + for shade in range(0, self.options.shades): + if self.options.bw and 0 < shade < self.options.shades - 1: + continue + start = [self.options.angle - delta * (shade + 1)] + end = [self.options.angle - delta * shade] + start.append(self.options.angle + delta * shade) + end.append(self.options.angle + delta * (shade + 1)) + level = float(shade) / float(self.options.shades - 1) + last = [] + result = [] + for cmd, params in array: + if cmd == 'Z': + last = [] + continue + if last: + if cmd == 'V': + point = [last[0], params[-2:][0]] + elif cmd == 'H': + point = [params[-2:][0], last[1]] + else: + point = params[-2:] + ang = degrees(atan2(point[0] - last[0], point[1] - last[1])) + if (self.angle_between(start[0], end[0], ang) or \ + self.angle_between(start[1], end[1], ang)): + result.append(('M', last)) + result.append((cmd, params)) + ref = point + else: + ref = params[-2:] + last = ref + if result: + if group is None: + group, filt = self.get_group(node) + new_node = group.add(node.copy()) + new_node.path = result + col = 255 - int(255. * level) + new_node.style = 'fill:none;stroke:#%02x%02x%02x;stroke-opacity:1;stroke-width:10;%s' % ((col,) * 3 + (filt,)) + + def get_group(self, node): + """ + make a clipped group, clip with clone of original, clipped group + include original and group of paths. + """ + defs = self.svg.defs + clip = defs.add(ClipPath()) + new_node = clip.add(node.copy()) + clip_group = node.getparent().add(inkex.Group()) + group = clip_group.add(inkex.Group()) + clip_group.set('clip-path', 'url(#' + clip.get_id() + ')') + + # make a blur filter reference by the style of each path + filt = defs.add(Filter(x='-0.5', y='-0.5',\ + height=str(self.options.blurheight),\ + width=str(self.options.blurwidth))) + + filt.add_primitive('feGaussianBlur', stdDeviation=self.options.stddev) + return group, 'filter:url(#%s);' % filt.get_id() + +if __name__ == '__main__': + Edge3D().run() diff --git a/share/extensions/embedimage.inx b/share/extensions/embedimage.inx new file mode 100644 index 0000000..70c0a63 --- /dev/null +++ b/share/extensions/embedimage.inx @@ -0,0 +1,15 @@ + + + Embed Images + org.inkscape.filter.embed_image + false + + all + + + + + + diff --git a/share/extensions/embedimage.py b/share/extensions/embedimage.py new file mode 100755 index 0000000..5dfb93d --- /dev/null +++ b/share/extensions/embedimage.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005,2007 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=ungrouped-imports +""" +Embed images so they are base64 encoded data inside the svg. +""" + +from __future__ import unicode_literals + +import os + +import inkex +from inkex import Image + +try: + import urllib.request as urllib + import urllib.parse as urlparse + from base64 import encodebytes +except ImportError: + # python2 compatibility, remove when python3 only. + import urllib + import urlparse + from base64 import encodestring as encodebytes + +class EmbedImage(inkex.EffectExtension): + """Allow selected image tags to become embeded image tags""" + def add_arguments(self, pars): + pars.add_argument("--selectedonly", type=inkex.Boolean, help="embed only selected images") + + def effect(self): + # if slectedonly is enabled and there is a selection + # only embed selected images. otherwise embed all images + if self.options.selectedonly: + images = self.svg.selection.get(Image) + else: + images = self.svg.xpath('//svg:image') + + for node in images: + self.embed_image(node) + + def embed_image(self, node): + """Embed the data of the selected Image Tag element""" + xlink = node.get('xlink:href') + if xlink and xlink[:5] == 'data:': + # No need, data alread embedded + return + + url = urlparse.urlparse(xlink) + href = urllib.url2pathname(url.path) + + # Primary location always the filename itself. + path = self.absolute_href(href or '') + + # Backup directory where we can find the image + if not os.path.isfile(path): + path = node.get('sodipodi:absref', path) + + if not os.path.isfile(path): + inkex.errormsg(_('File not found "{}". Unable to embed image.').format(path)) + return + + with open(path, "rb") as handle: + # Don't read the whole file to check the header + file_type = get_type(path, handle.read(10)) + handle.seek(0) + + if file_type: + # Future: Change encodestring to encodebytes when python3 only + node.set('xlink:href', 'data:{};base64,{}'.format( + file_type, encodebytes(handle.read()).decode('ascii'))) + node.pop('sodipodi:absref') + else: + inkex.errormsg(_("%s is not of type image/png, image/jpeg, "\ + "image/bmp, image/gif, image/tiff, or image/x-icon") % path) + + +def get_type(path, header): + """Basic magic header checker, returns mime type""" + for head, mime in ( + (b'\x89PNG', 'image/png'), + (b'\xff\xd8', 'image/jpeg'), + (b'BM', 'image/bmp'), + (b'GIF87a', 'image/gif'), + (b'GIF89a', 'image/gif'), + (b'MM\x00\x2a', 'image/tiff'), + (b'II\x2a\x00', 'image/tiff'), + ): + if header.startswith(head): + return mime + + # ico files lack any magic... therefore we check the filename instead + for ext, mime in ( + # official IANA registered MIME is 'image/vnd.microsoft.icon' tho + ('.ico', 'image/x-icon'), + ('.svg', 'image/svg+xml'), + ): + if path.endswith(ext): + return mime + return None + +if __name__ == '__main__': + EmbedImage().run() diff --git a/share/extensions/embedselectedimages.inx b/share/extensions/embedselectedimages.inx new file mode 100644 index 0000000..85d6ff6 --- /dev/null +++ b/share/extensions/embedselectedimages.inx @@ -0,0 +1,13 @@ + + + Embed Selected Images + org.inkscape.filter.selected.embed_image + true + + all + + + diff --git a/share/extensions/eps_input.inx b/share/extensions/eps_input.inx new file mode 100644 index 0000000..3c44fe8 --- /dev/null +++ b/share/extensions/eps_input.inx @@ -0,0 +1,19 @@ + + + EPS Input + org.inkscape.input.eps + org.inkscape.input.pdf + ps2pdf + true + + .eps + image/x-encapsulated-postscript + Encapsulated PostScript (*.eps) + Encapsulated PostScript + org.inkscape.output.eps + + + diff --git a/share/extensions/export_gimp_palette.inx b/share/extensions/export_gimp_palette.inx new file mode 100644 index 0000000..e98ae64 --- /dev/null +++ b/share/extensions/export_gimp_palette.inx @@ -0,0 +1,14 @@ + + + Export as GIMP Palette + com.kaioa.export_gimp_palette + + .gpl + text/plain + GIMP Palette (*.gpl) + Exports the colors of this document as GIMP Palette + + + diff --git a/share/extensions/export_gimp_palette.py b/share/extensions/export_gimp_palette.py new file mode 100755 index 0000000..812756b --- /dev/null +++ b/share/extensions/export_gimp_palette.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (c) 2009 - Jos Hirth, kaioa.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +""" +Export a gimp pallet file (.gpl) +""" + +import inkex +from inkex import ShapeElement, ColorIdError, ColorError + + +class ExportGimpPalette(inkex.OutputExtension): + """Export all colors in a document to a gimp pallet""" + select_all = (ShapeElement,) + + def save(self, stream): + name = self.svg.name.replace('.svg', '') + stream.write('GIMP Palette\nName: {}\n#\n'.format(name).encode('utf-8')) + + for key, value in sorted(list(set(self.get_colors()))): + stream.write("{} {}\n".format(key, value).encode('utf-8')) + + def get_colors(self): + """Get all the colors from the selected elements""" + for elem in self.svg.selection.filter(ShapeElement).values(): + for color in self.process_element(elem): + if str(color).upper() == 'NONE': + continue + yield ("{:3d} {:3d} {:3d}".format(*color.to_rgb()), str(color).upper()) + + def process_element(self, elem): + """Recursively process elements for colors""" + style = elem.fallback_style(move=False) + for name in inkex.Style.color_props: + try: + yield inkex.Color(style.get(name)) + except ColorIdError: + gradient = self.svg.getElementById(style.get(name)) + for item in self.process_element(gradient): + yield item + if gradient.href is not None: + for item in self.process_element(gradient.href): + yield item + except ColorError: + pass # Bad color + + if elem.href is not None: # Capture second level gradient colors + for color in self.process_element(elem.href): + yield color + +if __name__ == '__main__': + ExportGimpPalette().run() diff --git a/share/extensions/extractimage.inx b/share/extensions/extractimage.inx new file mode 100644 index 0000000..4f9f238 --- /dev/null +++ b/share/extensions/extractimage.inx @@ -0,0 +1,16 @@ + + + Extract Image + org.inkscape.filter.extract_image + true + ./images/ + + all + + + + + + diff --git a/share/extensions/extractimage.py b/share/extensions/extractimage.py new file mode 100755 index 0000000..810c6f4 --- /dev/null +++ b/share/extensions/extractimage.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Extract embedded images. +""" + +import os +import inkex +from inkex import Image + +try: + from base64 import decodebytes +except ImportError: + from base64 import decodestring as decodebytes + +class ExtractImage(inkex.EffectExtension): + """Extract images and save to filenames""" + def add_arguments(self, pars): + pars.add_argument("-s", "--selectedonly", type=inkex.Boolean,\ + help="Extract only selected images", default=True) + pars.add_argument("--filepath", default='.',\ + help="Location to save the images.") + + def effect(self): + elems = self.svg.selection.filter(Image).values() \ + if self.options.selectedonly else self.svg.xpath('//svg:image') + + for elem in elems: + self.extract_image(elem) + + @staticmethod + def mime_to_ext(mime): + """Return an extension based on the mime type""" + # Most extensions are automatic (i.e. extension is same as minor part of mime type) + part = mime.split('/', 1)[1].split('+')[0] + return '.' + { + # These are the non-matching ones. + 'svg+xml' : '.svg', + 'jpeg' : '.jpg', + 'icon' : '.ico', + }.get(part, part) + + def extract_image(self, node): + """Extract the node as if it were an image.""" + xlink = node.get('xlink:href') + if not xlink.startswith('data:'): + return # Not embedded image data + + save_to = self.absolute_href(self.options.filepath) + # Make the target directory if it doesn't exist yet. + if not os.path.isdir(save_to): + os.makedirs(save_to) + + try: + data = xlink[5:] + (mimetype, data) = data.split(';', 1) + (base, data) = data.split(',', 1) + except ValueError: + inkex.errormsg("Invalid image format found") + return + + if base != 'base64': + inkex.errormsg("Can't decode encoding: {}".format(base)) + return + + file_ext = self.mime_to_ext(mimetype) + + pathwext = os.path.join(save_to, node.get("id") + file_ext) + if os.path.isfile(pathwext): + inkex.errormsg("Can't extract image, filename already used: {}".format(pathwext)) + return + + self.msg('Image extracted to: {}'.format(pathwext)) + + with open(pathwext, 'wb') as fhl: + fhl.write(decodebytes(data.encode('utf-8'))) + + # absolute for making in-mem cycles work + node.set('xlink:href', os.path.realpath(pathwext)) + +if __name__ == '__main__': + ExtractImage().run() diff --git a/share/extensions/extrude.inx b/share/extensions/extrude.inx new file mode 100644 index 0000000..de65872 --- /dev/null +++ b/share/extensions/extrude.inx @@ -0,0 +1,18 @@ + + + Extrude + org.greygreen.inkscape.effects.extrude + + + + + + path + + + + + + diff --git a/share/extensions/extrude.py b/share/extensions/extrude.py new file mode 100755 index 0000000..169d50c --- /dev/null +++ b/share/extensions/extrude.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2007 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +"""Join paths with lines or polygons""" + + +import inkex + +class Extrude(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("--mode", default="Lines", help="Join paths with lines or polygons") + + def effect(self): + paths = [] + for node in self.svg.selection.filter(inkex.PathElement).values(): + paths.append(node) + if len(paths) < 2: + raise inkex.AbortExtension("Need at least 2 paths selected") + + for path in paths: + path.apply_transform() + + pts = [node.path.to_superpath() for node in paths] + + for n1 in range(0, len(paths)): + for n2 in range(n1 + 1, len(paths)): + verts = [] + for i in range(0, min(map(len, pts))): + comp = [] + for j in range(0, min(len(pts[n1][i]), len(pts[n2][i]))): + comp.append([pts[n1][i][j][1][-2:], pts[n2][i][j][1][-2:]]) + verts.append(comp) + + if self.options.mode.lower() == 'lines': + line = [] + for comp in verts: + for n, v in enumerate(comp): + line += [('M', v[0])] + line += [('L', v[1])] + ele = inkex.PathElement() + paths[0].xpath('..')[0].append(ele) + ele.set('d', str(inkex.Path(line))) + style = { + 'fill': 'none', + 'stroke': '#000000', + 'stroke-opacity': 1, + 'stroke-width': self.svg.unittouu('1px'), + } + ele.set('style', str(inkex.Style(style))) + elif self.options.mode.lower() == 'polygons': + g = inkex.Group() + style = { + 'fill': '#000000', + 'fill-opacity': 0.3, + 'stroke': '#000000', + 'stroke-opacity': 0.6, + 'stroke-width': self.svg.unittouu('2px'), + } + g.set('style', str(inkex.Style(style))) + paths[0].xpath('..')[0].append(g) + for comp in verts: + for n, v in enumerate(comp): + nn = n + 1 + if nn == len(comp): + nn = 0 + line = [] + line += [('M', comp[n][0])] + line += [('L', comp[n][1])] + line += [('L', comp[nn][1])] + line += [('L', comp[nn][0])] + line += [('L', comp[n][0])] + ele = inkex.PathElement() + g.append(ele) + ele.set('d', str(inkex.Path(line))) + + +if __name__ == '__main__': + Extrude().run() diff --git a/share/extensions/fig_input.inx b/share/extensions/fig_input.inx new file mode 100644 index 0000000..00eae65 --- /dev/null +++ b/share/extensions/fig_input.inx @@ -0,0 +1,16 @@ + + + XFIG Input + org.inkscape.input.fig_input + fig2dev + + .fig + image/x-xfig + XFIG Graphics File (*.fig) + Open files saved with XFIG + org.inkscape.output.fig + + + diff --git a/share/extensions/fig_input.py b/share/extensions/fig_input.py new file mode 100755 index 0000000..e5d4c0f --- /dev/null +++ b/share/extensions/fig_input.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2008 Stephen Silver +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +# +""" +Simple wrapper around fig2dev +""" + +import inkex +from inkex.command import call + +class FigInput(inkex.CallExtension): + """Load FIG Files by calling fig2dev program""" + input_ext = 'fig' + + def call(self, input_file, output_file): + call('fig2dev', '-L', 'svg', input_file, output_file) + +if __name__ == '__main__': + FigInput().run() diff --git a/share/extensions/flatten.inx b/share/extensions/flatten.inx new file mode 100644 index 0000000..e0d4c9a --- /dev/null +++ b/share/extensions/flatten.inx @@ -0,0 +1,15 @@ + + + Flatten Beziers + org.ekips.filter.flatten + 10.0 + + path + + + + + + diff --git a/share/extensions/flatten.py b/share/extensions/flatten.py new file mode 100755 index 0000000..d9173bc --- /dev/null +++ b/share/extensions/flatten.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2006 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +import inkex +from inkex import bezier + +class Flatten(inkex.EffectExtension): + """Flattern a path""" + def add_arguments(self, pars): + pars.add_argument("--flatness", type=float, default=10.0, help="Minimum flattness") + + def effect(self): + for node in self.svg.selection.filter(inkex.PathElement).values(): + path = node.path.to_superpath() + bezier.cspsubdiv(path, self.options.flatness) + newpath = [] + for subpath in path: + first = True + for csp in subpath: + cmd = 'L' + if first: + cmd = 'M' + first = False + newpath.append([cmd, [csp[1][0], csp[1][1]]]) + node.path = newpath + +if __name__ == '__main__': + Flatten().run() diff --git a/share/extensions/foldablebox.inx b/share/extensions/foldablebox.inx new file mode 100644 index 0000000..088ccd5 --- /dev/null +++ b/share/extensions/foldablebox.inx @@ -0,0 +1,26 @@ + + + Foldable Box + org.inkscape.render.foldable_box + 10.0 + 15.0 + 3.0 + 0.6 + + + + + + + + true + + all + + + + + + diff --git a/share/extensions/foldablebox.py b/share/extensions/foldablebox.py new file mode 100755 index 0000000..06e3e54 --- /dev/null +++ b/share/extensions/foldablebox.py @@ -0,0 +1,246 @@ +#! /usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2009 Aurelio A. Heckert +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +__version__ = "0.2" + +import inkex + +class FoldableBox(inkex.EffectExtension): + """Foldable Box generation.""" + def add_arguments(self, pars): + pars.add_argument("--width", type=float, default=10.0, help="The Box Width") + pars.add_argument("--height", type=float, default=15.0, help="The Box Height") + pars.add_argument("--depth", type=float, default=3.0, help="The Box Depth (z dimention)") + pars.add_argument("--unit", default="cm", help="The unit of the box dimensions") + pars.add_argument("--proportion", type=float, default=0.6, help="Inner tab proportion") + pars.add_argument("--guide", type=inkex.Boolean, default=False, help="Add guide lines") + + def guide(self, value, orient): + """Create a guideline conditionally""" + if self.options.guide: + self.svg.namedview.new_guide(value, orient) + + def effect(self): + doc_w = self.svg.unittouu(self.document.getroot().get('width')) + doc_h = self.svg.unittouu(self.document.getroot().get('height')) + + box_w = self.svg.unittouu(str(self.options.width) + self.options.unit) + box_h = self.svg.unittouu(str(self.options.height) + self.options.unit) + box_d = self.svg.unittouu(str(self.options.depth) + self.options.unit) + tab_h = box_d * self.options.proportion + + box_id = self.svg.get_unique_id('box') + group = self.svg.get_current_layer().add(inkex.Group(id=box_id)) + + line_style = {'stroke': '#000000', 'fill': 'none', + 'stroke-width': str(self.svg.unittouu('1px'))} + + self.guide(doc_h, True) + + # Inner Close Tab + line = group.add(inkex.PathElement(id=box_id + '-inner-close-tab')) + line.path = [ + ['M', [box_w - (tab_h * 0.7), 0]], + ['C', [box_w - (tab_h * 0.25), 0, box_w, tab_h * 0.3, box_w, tab_h * 0.9]], + ['L', [box_w, tab_h]], + ['L', [0, tab_h]], + ['L', [0, tab_h * 0.9]], + ['C', [0, tab_h * 0.3, tab_h * 0.25, 0, tab_h * 0.7, 0]], + ['Z', []] + ] + line.style = line_style + + lower_pos = box_d + tab_h + left_pos = 0 + + self.guide(doc_h - tab_h, True) + + # Upper Close Tab + line = group.add(inkex.PathElement(id=box_id + '-upper-close-tab')) + line.path = [ + ['M', [left_pos, tab_h]], + ['L', [left_pos + box_w, tab_h]], + ['L', [left_pos + box_w, lower_pos]], + ['L', [left_pos + 0, lower_pos]], + ['Z', []] + ] + line.style = line_style + + left_pos += box_w + + # Upper Right Tab + side_tab_h = lower_pos - (box_w / 2) + if side_tab_h < tab_h: + side_tab_h = tab_h + + line = group.add(inkex.PathElement(id=box_id + '-upper-right-tab')) + line.path = [ + ['M', [left_pos, side_tab_h]], + ['L', [left_pos + (box_d * 0.8), side_tab_h]], + ['L', [left_pos + box_d, ((lower_pos * 3) - side_tab_h) / 3]], + ['L', [left_pos + box_d, lower_pos]], + ['L', [left_pos + 0, lower_pos]], + ['Z', []] + ] + line.style = line_style + + left_pos += box_w + box_d + + # Upper Left Tab + line = group.add(inkex.PathElement(id=box_id + '-upper-left-tab')) + line.path = [ + ['M', [left_pos + box_d, side_tab_h]], + ['L', [left_pos + (box_d * 0.2), side_tab_h]], + ['L', [left_pos, ((lower_pos * 3) - side_tab_h) / 3]], + ['L', [left_pos, lower_pos]], + ['L', [left_pos + box_d, lower_pos]], + ['Z', []] + ] + line.style = line_style + + left_pos = 0 + + self.guide(doc_h - tab_h - box_d, True) + + # Right Tab + line = group.add(inkex.PathElement(id=box_id + '-left-tab')) + line.path = [ + ['M', [left_pos, lower_pos]], + ['L', [left_pos - (box_d / 2), lower_pos + (box_d / 4)]], + ['L', [left_pos - (box_d / 2), lower_pos + box_h - (box_d / 4)]], + ['L', [left_pos, lower_pos + box_h]], + ['Z', []] + ] + line.style = line_style + + # Front + line = group.add(inkex.PathElement(id=box_id + '-front')) + line.path = [ + ['M', [left_pos, lower_pos]], + ['L', [left_pos + box_w, lower_pos]], + ['L', [left_pos + box_w, lower_pos + box_h]], + ['L', [left_pos, lower_pos + box_h]], + ['Z', []] + ] + line.style = line_style + + left_pos += box_w + + # Right + line = group.add(inkex.PathElement(id=box_id + '-right')) + line.path = [ + ['M', [left_pos, lower_pos]], + ['L', [left_pos + box_d, lower_pos]], + ['L', [left_pos + box_d, lower_pos + box_h]], + ['L', [left_pos, lower_pos + box_h]], + ['Z', []] + ] + line.style = line_style + + left_pos += box_d + + # Back + line = group.add(inkex.PathElement(id=box_id + '-back')) + line.path = [ + ['M', [left_pos, lower_pos]], + ['L', [left_pos + box_w, lower_pos]], + ['L', [left_pos + box_w, lower_pos + box_h]], + ['L', [left_pos, lower_pos + box_h]], + ['Z', []] + ] + line.style = line_style + + left_pos += box_w + + # Left + line = group.add(inkex.PathElement(id=box_id + '-line')) + line.path = [ + ['M', [left_pos, lower_pos]], + ['L', [left_pos + box_d, lower_pos]], + ['L', [left_pos + box_d, lower_pos + box_h]], + ['L', [left_pos, lower_pos + box_h]], + ['Z', []] + ] + line.style = line_style + + lower_pos += box_h + left_pos = 0 + b_tab = lower_pos + box_d + if b_tab > box_w / 2.5: + b_tab = box_w / 2.5 + + # Bottom Front Tab + line = group.add(inkex.PathElement(id=box_id + '-bottom-front-tab')) + line.path = [ + ['M', [left_pos, lower_pos]], + ['L', [left_pos, lower_pos + (box_d / 2)]], + ['L', [left_pos + box_w, lower_pos + (box_d / 2)]], + ['L', [left_pos + box_w, lower_pos]], + ['Z', []] + ] + line.style = line_style + + left_pos += box_w + + # Bottom Right Tab + line = group.add(inkex.PathElement(id=box_id + '-bottom-right-tab')) + line.path = [ + ['M', [left_pos, lower_pos]], + ['L', [left_pos, lower_pos + b_tab]], + ['L', [left_pos + box_d, lower_pos + b_tab]], + ['L', [left_pos + box_d, lower_pos]], + ['Z', []] + ] + line.style = line_style + + left_pos += box_d + + # Bottom Back Tab + line = group.add(inkex.PathElement(id=box_id + '-bottom-back-tab')) + line.path = [ + ['M', [left_pos, lower_pos]], + ['L', [left_pos, lower_pos + (box_d / 2)]], + ['L', [left_pos + box_w, lower_pos + (box_d / 2)]], + ['L', [left_pos + box_w, lower_pos]], + ['Z', []] + ] + line.style = line_style + + left_pos += box_w + + # Bottom Left Tab + line = group.add(inkex.PathElement(id=box_id + '-bottom-left-tab')) + line.path = [ + ['M', [left_pos, lower_pos]], + ['L', [left_pos, lower_pos + b_tab]], + ['L', [left_pos + box_d, lower_pos + b_tab]], + ['L', [left_pos + box_d, lower_pos]], + ['Z', []] + ] + line.style = line_style + + left_pos += box_d + lower_pos += b_tab + + group.transform = inkex.Transform(translate=((doc_w - left_pos) / 2, (doc_h - lower_pos) / 2)) + + +if __name__ == '__main__': # pragma: no cover + FoldableBox().run() diff --git a/share/extensions/fontfix.conf b/share/extensions/fontfix.conf new file mode 100644 index 0000000..4f66174 --- /dev/null +++ b/share/extensions/fontfix.conf @@ -0,0 +1,55 @@ +# This file contains correction factors for the PowerPoint compensation method +# when files are saved to EMF. PowerPoint applies some odd offsets when it ungroups +# fonts from within an EMF. This file contains compensating factors so that the +# imported, ungrouped characters end up in the desired location. +# +# Format(s) +# +# # a comment +# +# f1 f2 f3 FontName +# where +# f1: vertical (rotating) correction factor (a double) +# f2: vertical (nonrotating) correction factor (a double) +# f3: horizontal (nonrotating) correction factor (a double) +# FontName: Case sensitive, may contain spaces. Example: Times New Roman +# +# The first font will listed will be used as the default for any font which +# is later requested but was not explicitly listed. +# +# If f1 specifies a multiplicative correction factor. It is multiplied by the font size +# and then the character is offset parallel to the (original) vertical direction of the character, +# that is, along the long part of a capital L. +# +# If f2 or f3 is nonzero then for angles <1 degree from 0,90,180,270 degrees +# the angle is snapped to n*90 and f2 is used for the vertical displacements, f3 +# for the horizontal displacements (that is, for 90 degrees, f3 is used). +# +# There is are one special type of fontname: Convert To FontName, +# for instance "Convert To Symbol". It is used when EMF print converts +# from unicode to Symbol, Wingdings, or Zapf Dingbats. +# +# Positive values lower the letter, negative raise it +# +# File history: +# 1.0.0 03/26/2012, David Mathog, initial values +##################################################################### +0.05 -0.055 -0.065 Arial +0.05 -0.055 -0.065 Times New Roman +-0.025 -0.055 -0.065 Lucida Sans +0.05 -0.055 -0.065 Sans +-0.05 -0.055 -0.065 Microsoft Sans Serif +0.05 -0.055 -0.065 Serif +0.05 -0.055 -0.065 Garamond +0.25 0.025 0.025 Century Schoolbook +0.025 0.0 0.0 Verdana +0.045 0.025 0.025 Tahoma +0.025 0.0 0.0 Symbol +0.05 0.0 0.0 Wingdings +0.025 0.0 0.0 Zapf Dingbats +0.025 0.0 0.0 Convert To Symbol +0.05 0.0 0.0 Convert To Wingdings +0.025 0.0 0.0 Convert To Zapf Dingbats +0.1 0.0 0.0 Sylfaen +0.175 0.125 0.125 Palatino Linotype +0.1 0.0 0.0 Segoe UI diff --git a/share/extensions/fractalize.inx b/share/extensions/fractalize.inx new file mode 100644 index 0000000..03f9e6c --- /dev/null +++ b/share/extensions/fractalize.inx @@ -0,0 +1,16 @@ + + + Fractalize + org.inkscape.filter.fractalize + 6 + 4.0 + + path + + + + + + diff --git a/share/extensions/fractalize.py b/share/extensions/fractalize.py new file mode 100755 index 0000000..f9cf111 --- /dev/null +++ b/share/extensions/fractalize.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005 Carsten Goetze c.goetze@tu-bs.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +import math +import random +import inkex +from inkex.paths import Move, Line + +def calculate_subdivision(smoothness, x1, y1, x2, y2): + # Calculate the vector from (x1,y1) to (x2,y2) + x3 = x2 - x1 + y3 = y2 - y1 + # Calculate the point half-way between the two points + hx = x1 + x3 / 2 + hy = y1 + y3 / 2 + # Calculate normalized vector perpendicular to the vector (x3,y3) + length = math.sqrt(x3 * x3 + y3 * y3) + if length != 0: + nx = -y3 / length + ny = x3 / length + else: + nx = 1 + ny = 0 + # Scale perpendicular vector by random factor """ + r = random.uniform(-length / (1 + smoothness), length / (1 + smoothness)) + nx = nx * r + ny = ny * r + # add scaled perpendicular vector to the half-way point to get the final displaced subdivision point + x = hx + nx + y = hy + ny + return (x, y) + + +class Fractalize(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("-s", "--subdivs", type=int, default="6", + help="Number of subdivisons") + pars.add_argument("-f", "--smooth", type=float, default="4.0", + help="Smoothness of the subdivision") + + def effect(self): + for node in self.svg.selection.filter(inkex.PathElement).values(): + path = node.path.to_absolute() + result = [] + for cmd_proxy in path.proxy_iterator(): # type: inkex.Path.PathCommandProxy + prev = cmd_proxy.previous_end_point + end = cmd_proxy.end_point + if cmd_proxy.letter == 'M': + result.append(Move(*cmd_proxy.args)) + else: + for seg in self.fractalize((prev.x, prev.y, end.x, end.y), self.options.subdivs, + self.options.smooth): + result.append(Line(*seg)) + result.append(Line(end.x, end.y)) + + node.path = result + + def fractalize(self, coords, subdivs, smooth): + """recursively subdivide the segments left and right of the subdivision""" + subdiv_point = calculate_subdivision(smooth, *coords) + + if subdivs: + # recursively subdivide the segment left of the subdivision point + for left_seg in self.fractalize(coords[:2] + subdiv_point[-2:], subdivs - 1, smooth): + yield left_seg + + yield subdiv_point + + # recursively subdivide the segment right of the subdivision point + for right_seg in self.fractalize(subdiv_point[-2:] + coords[-2:], subdivs - 1, smooth): + yield right_seg + +if __name__ == '__main__': + Fractalize().run() diff --git a/share/extensions/frame.inx b/share/extensions/frame.inx new file mode 100644 index 0000000..004359b --- /dev/null +++ b/share/extensions/frame.inx @@ -0,0 +1,30 @@ + + + Frame + frame + + + 255 + + + 0 + + + + + + + false + false + 2 + 0 + + all + + + + + + diff --git a/share/extensions/frame.py b/share/extensions/frame.py new file mode 100755 index 0000000..013e195 --- /dev/null +++ b/share/extensions/frame.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2016 Richard White, rwhite8282@gmail.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +""" +An Inkscape extension that creates a frame around a selected object. +""" + +import inkex +from inkex import Group, PathElement, ClipPath + +def size_box(box, delta): + """ Returns a box with an altered size. + delta -- The amount the box should grow. + Returns a box with an altered size. + """ + return (box.x.minimum - delta, box.x.maximum + delta, + box.y.minimum - delta, box.y.maximum + delta) + + +# Frame maker Inkscape effect extension +class Frame(inkex.EffectExtension): + """ + An Inkscape extension that creates a frame around a selected object. + """ + def add_arguments(self, pars): + # Parse the options. + pars.add_argument('--tab', default='object') + pars.add_argument('--clip', type=inkex.Boolean, default=False) + pars.add_argument('--corner_radius', type=int, default=0) + pars.add_argument('--fill_color', type=inkex.Color, default=inkex.Color(0)) + pars.add_argument('--group', type=inkex.Boolean, default=False) + pars.add_argument('--position', default='outside') + pars.add_argument('--stroke_color', type=inkex.Color, default=inkex.Color(0)) + pars.add_argument('--width', type=float, default=2.0) + + def add_clip(self, node, clip_path): + """ Adds a new clip path node to the defs and sets + the clip-path on the node. + node -- The node that will be clipped. + clip_path -- The clip path object. + """ + clip = ClipPath() + clip.append(PathElement(d=str(clip_path.path))) + clip_id = self.svg.get_unique_id('clipPath') + clip.set('id', clip_id) + self.svg.defs.append(clip) + node.set('clip-path', 'url(#{})'.format(str(clip_id))) + + def add_frame(self, name, box, style, radius=0): + """ + name -- The name of the new frame object. + box -- The boundary box of the node. + style -- The style used to draw the path. + radius -- The corner radius of the frame. + returns a new frame node. + """ + r = min([radius, (abs(box[1] - box[0]) / 2), (abs(box[3] - box[2]) / 2)]) + if radius > 0: + d = ' '.join(str(x) for x in + ['M', box[0], (box[2] + r), + 'A', r, r, '0 0 1', (box[0] + r), box[2], + 'L', (box[1] - r), box[2], + 'A', r, r, '0 0 1', box[1], (box[2] + r), + 'L', box[1], (box[3] - r), + 'A', r, r, '0 0 1', (box[1] - r), box[3], + 'L', (box[0] + r), box[3], + 'A', r, r, '0 0 1', box[0], (box[3] - r), + 'Z']) + else: + d = ' '.join(str(x) for x in + ['M', box[0], box[2], + 'L', box[1], box[2], + 'L', box[1], box[3], + 'L', box[0], box[3], + 'Z']) + + elem = PathElement() + elem.style = style + elem.label = name + elem.path = d + return elem + + def effect(self): + """Performs the effect.""" + # Determine common properties. + width = self.options.width + style = inkex.Style({'stroke-width': width}) + style.set_color(self.options.fill_color, 'fill') + style.set_color(self.options.stroke_color, 'stroke') + layer = self.svg.get_current_layer() + + for node in self.svg.selected.values(): + box = node.bounding_box() + if self.options.position == 'outside': + box = size_box(box, (width / 2)) + else: + box = size_box(box, -(width / 2)) + + frame = self.add_frame("Frame", box, style, self.options.corner_radius) + if self.options.clip: + self.add_clip(node, frame) + if self.options.group: + group = layer.add(Group()) + group.append(node) + group.append(frame) + else: + layer.append(frame) + return None + +if __name__ == '__main__': + Frame().run() diff --git a/share/extensions/funcplot.inx b/share/extensions/funcplot.inx new file mode 100644 index 0000000..6263515 --- /dev/null +++ b/share/extensions/funcplot.inx @@ -0,0 +1,55 @@ + + + Function Plotter + org.inkscape.effect.func_plot + + + 0.0 + 1.0 + false + 0.0 + 1.0 + 8 + false + + true + + + + + + + + + exp(-x*x) + true + x + false + true + false + false + + rect + + + + + + diff --git a/share/extensions/funcplot.py b/share/extensions/funcplot.py new file mode 100755 index 0000000..4b24191 --- /dev/null +++ b/share/extensions/funcplot.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2007 Tavmjong Bah, tavmjong@free.fr +# Copyright (C) 2006 Georg Wiora, xorx@quarkbox.de +# Copyright (C) 2006 Johan Engelen, johan@shouraizou.nl +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Changes: +# * This program is a modified version of wavy.py by Aaron Spike. +# * 22-Dec-2006: Wiora : Added axis and isotropic scaling +# * 21-Jun-2007: Tavmjong: Added polar coordinates +# +import random +import math +from math import cos, pi, sin + +import inkex +from inkex import ClipPath, Rectangle + +EVAL_GLOBALS = {} +EVAL_GLOBALS.update(random.__dict__) +EVAL_GLOBALS.update(math.__dict__) + + +def drawfunction(xstart, xend, ybottom, ytop, samples, width, height, left, bottom, + fx="sin(x)", fpx="cos(x)", fponum=True, times2pi=False, polar=False, isoscale=True, drawaxis=True, endpts=False): + if times2pi: + xstart = 2 * pi * xstart + xend = 2 * pi * xend + + # coords and scales based on the source rect + if xstart == xend: + inkex.errormsg("x-interval cannot be zero. Please modify 'Start X value' or 'End X value'") + return [] + scalex = width / (xend - xstart) + xoff = left + coordx = lambda x: (x - xstart) * scalex + xoff # convert x-value to coordinate + if polar: # Set scale so that left side of rectangle is -1, right side is +1. + # (We can't use xscale for both range and scale.) + centerx = left + width / 2.0 + polar_scalex = width / 2.0 + coordx = lambda x: x * polar_scalex + centerx # convert x-value to coordinate + + if ytop == ybottom: + inkex.errormsg("y-interval cannot be zero. Please modify 'Y value of rectangle's top' or 'Y value of rectangle's bottom'") + return [] + scaley = height / (ytop - ybottom) + yoff = bottom + coordy = lambda y: (ybottom - y) * scaley + yoff # convert y-value to coordinate + + # Check for isotropic scaling and use smaller of the two scales, correct ranges + if isoscale and not polar: + if scaley < scalex: + # compute zero location + xzero = coordx(0) + # set scale + scalex = scaley + # correct x-offset + xstart = (left - xzero) / scalex + xend = (left + width - xzero) / scalex + else: + # compute zero location + yzero = coordy(0) + # set scale + scaley = scalex + # correct x-offset + ybottom = (yzero - bottom) / scaley + ytop = (bottom + height - yzero) / scaley + + # functions specified by the user + try: + if fx != "": + f = eval('lambda x: ' + fx, EVAL_GLOBALS, {}) + if fpx != "": + fp = eval('lambda x: ' + fpx, EVAL_GLOBALS, {}) + # handle incomplete/invalid function gracefully + except SyntaxError: + return [] + + # step is the distance between nodes on x + step = (xend - xstart) / (samples - 1) + third = step / 3.0 + ds = step * 0.001 # Step used in calculating derivatives + + a = [] # path array + # add axis + if drawaxis: + # check for visibility of x-axis + if ybottom <= 0 <= ytop: + # xaxis + a.append(['M', [left, coordy(0)]]) + a.append(['l', [width, 0]]) + # check for visibility of y-axis + if xstart <= 0 <= xend: + # xaxis + a.append(['M', [coordx(0), bottom]]) + a.append(['l', [0, -height]]) + + # initialize function and derivative for 0; + # they are carried over from one iteration to the next, to avoid extra function calculations. + x0 = xstart + y0 = f(xstart) + if polar: + xp0 = y0 * cos(x0) + yp0 = y0 * sin(x0) + x0 = xp0 + y0 = yp0 + if fponum or polar: # numerical derivative, using 0.001*step as the small differential + x1 = xstart + ds # Second point AFTER first point (Good for first point) + y1 = f(x1) + if polar: + xp1 = y1 * cos(x1) + yp1 = y1 * sin(x1) + x1 = xp1 + y1 = yp1 + dx0 = (x1 - x0) / ds + dy0 = (y1 - y0) / ds + else: # derivative given by the user + dx0 = 1 # Only works for rectangular coordinates + dy0 = fp(xstart) + + # Start curve + if endpts: + a.append(['M', [left, coordy(0)]]) + a.append(['L', [coordx(x0), coordy(y0)]]) + else: + a.append(['M', [coordx(x0), coordy(y0)]]) # initial moveto + + for i in range(int(samples - 1)): + x1 = (i + 1) * step + xstart + x2 = x1 - ds # Second point BEFORE first point (Good for last point) + y1 = f(x1) + y2 = f(x2) + if polar: + xp1 = y1 * cos(x1) + yp1 = y1 * sin(x1) + xp2 = y2 * cos(x2) + yp2 = y2 * sin(x2) + x1 = xp1 + y1 = yp1 + x2 = xp2 + y2 = yp2 + if fponum or polar: # numerical derivative + dx1 = (x1 - x2) / ds + dy1 = (y1 - y2) / ds + else: # derivative given by the user + dx1 = 1 # Only works for rectangular coordinates + dy1 = fp(x1) + # create curve + a.append(['C', + [coordx(x0 + (dx0 * third)), coordy(y0 + (dy0 * third)), + coordx(x1 - (dx1 * third)), coordy(y1 - (dy1 * third)), + coordx(x1), coordy(y1)] + ]) + x0 = x1 # Next segment's start is this segments end + y0 = y1 + dx0 = dx1 # Assume the function is smooth everywhere, so carry over the derivative too + dy0 = dy1 + if endpts: + a.append(['L', [left + width, coordy(0)]]) + return a + + +class FuncPlot(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("--xstart", type=float, default=0.0, help="Start x-value") + pars.add_argument("--xend", type=float, default=1.0, help="End x-value") + pars.add_argument("--times2pi", type=inkex.Boolean, default=True, help="* x-range by 2*pi") + pars.add_argument("--polar", type=inkex.Boolean, default=False, help="Use polar coords") + pars.add_argument("--ybottom", type=float, default=-1.0, help="y-value of rect's bottom") + pars.add_argument("--ytop", type=float, default=1.0, help="y-value of rectangle's top") + pars.add_argument("--samples", type=int, default=8, help="Samples") + pars.add_argument("--fofx", default="sin(x)", help="f(x) for plotting") + pars.add_argument("--fponum", type=inkex.Boolean, default=True, help="Numerical 1st deriv") + pars.add_argument("--fpofx", default="cos(x)", help="f'(x) for plotting") + pars.add_argument("--clip", type=inkex.Boolean, default=False, help="Clip with source rect") + pars.add_argument("--remove", type=inkex.Boolean, default=True, help="Remove source rect") + pars.add_argument("--isoscale", type=inkex.Boolean, default=True, help="Isotropic scaling") + pars.add_argument("--drawaxis", type=inkex.Boolean, default=True, help="Draw axis") + pars.add_argument("--endpts", type=inkex.Boolean, default=False, help="Add end points") + + def effect(self): + newpath = None + for node in self.svg.selected.values(): + if isinstance(node, Rectangle): + # create new path with basic dimensions of selected rectangle + newpath = inkex.PathElement() + x = float(node.get('x')) + y = float(node.get('y')) + w = float(node.get('width')) + h = float(node.get('height')) + + # copy attributes of rect + newpath.style = node.style + newpath.transform = node.transform + + # top and bottom were exchanged + newpath.path = \ + drawfunction(self.options.xstart, + self.options.xend, + self.options.ybottom, + self.options.ytop, + self.options.samples, + w, h, x, y + h, + self.options.fofx, + self.options.fpofx, + self.options.fponum, + self.options.times2pi, + self.options.polar, + self.options.isoscale, + self.options.drawaxis, + self.options.endpts) + newpath.set('title', self.options.fofx) + + # add path into SVG structure + node.getparent().append(newpath) + # option whether to clip the path with rect or not. + if self.options.clip: + clip = self.svg.defs.add(ClipPath()) + clip.set_random_id() + clip.append(node.copy()) + newpath.set('clip-path', 'url(#' + clip.get_id() + ')') + # option whether to remove the rectangle or not. + if self.options.remove: + node.getparent().remove(node) + if newpath is None: + inkex.errormsg("Please select a rectangle") + + +if __name__ == '__main__': + FuncPlot().run() diff --git a/share/extensions/gcodetools.py b/share/extensions/gcodetools.py new file mode 100755 index 0000000..90a41a7 --- /dev/null +++ b/share/extensions/gcodetools.py @@ -0,0 +1,5921 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org (super paths et al) +# 2007 hugomatic... (gcode.py) +# 2009 Nick Drobchenko, nick@cnc-club.ru (main developer) +# 2011 Chris Lusby Taylor, clusbytaylor@enterprise.net (engraving functions) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Comments starting "#LT" or "#CLT" are by Chris Lusby Taylor who rewrote the engraving function in 2011. +History of CLT changes to engraving and other functions it uses: +9 May 2011 Changed test of tool diameter to square it +10 May Note that there are many unused functions, including: + bound_to_bound_distance, csp_curvature_radius_at_t, + csp_special_points, csplength, rebuild_csp, csp_slope, + csp_simple_bound_to_point_distance, csp_bound_to_point_distance, + bez_at_t, bez_to_point_distance, bez_normalized_slope, matrix_mul, transpose + Fixed csp_point_inside_bound() to work if x outside bounds +20 May Now encoding the bisectors of angles. +23 May Using r/cos(a) instead of normalised normals for bisectors of angles. +23 May Note that Z values generated for engraving are in pixels, not mm. + Removed the biarc curves - straight lines are better. +24 May Changed Bezier slope calculation to be less sensitive to tiny differences in points. + Added use of self.options.engraving_newton_iterations to control accuracy +25 May Big restructure and new recursive function. + Changed the way I treat corners - I now find if the centre of a proposed circle is + within the area bounded by the line being tested and the two angle bisectors at + its ends. See get_radius_to_line(). +29 May Eliminating redundant points. If A,B,C colinear, drop B +30 May Eliminating redundant lines in divided Beziers. Changed subdivision of lines + 7Jun Try to show engraving in 3D + 8 Jun Displaying in stereo 3D. + Fixed a bug in bisect - it could go wrong due to rounding errors if + 1+x1.x2+y1.y2<0 which should never happen. BTW, I spotted a non-normalised normal + returned by csp_normalized_normal. Need to check for that. + 9 Jun Corrected spelling of 'definition' but still match previous 'defention' and 'defenition' if found in file + Changed get_tool to find 1.6.04 tools or new tools with corrected spelling +10 Jun Put 3D into a separate layer called 3D, created unless it already exists + Changed csp_normalized_slope to reject lines shorter than 1e-9. +10 Jun Changed all dimensions seen by user to be mm/inch, not pixels. This includes + tool diameter, maximum engraving distance, tool shape and all Z values. +12 Jun ver 208 Now scales correctly if orientation points moved or stretched. +12 Jun ver 209. Now detect if engraving toolshape not a function of radius + Graphics now indicate Gcode toolpath, limited by min(tool diameter/2,max-dist) +24 Jan 2017 Removed hard-coded scale values from orientation point calculation +TODO Change line division to be recursive, depending on what line is touched. See line_divide +""" + +__version__ = '1.7' + +import cmath +import copy +import math +import os +import re +import sys +import time +from functools import partial + +import numpy + +import inkex +from inkex.bezier import bezierlength, bezierparameterize, beziertatlength +from inkex import Transform, PathElement, TextElement, Tspan, Group, Layer, Marker, CubicSuperPath, Style + +if sys.version_info[0] > 2: + xrange = range + unicode = str + +def ireplace(self, old, new, count=0): + pattern = re.compile(re.escape(old), re.I) + return re.sub(pattern, new, self, count) + + +################################################################################ +# +# Styles and additional parameters +# +################################################################################ + +TAU = math.pi * 2 +STRAIGHT_TOLERANCE = 0.0001 +STRAIGHT_DISTANCE_TOLERANCE = 0.0001 +ENGRAVING_TOLERANCE = 0.0001 +LOFT_LENGTHS_TOLERANCE = 0.0000001 + +EMC_TOLERANCE_EQUAL = 0.00001 + +options = {} +defaults = { + 'header': """% +(Header) +(Generated by gcodetools from Inkscape.) +(Using default header. To add your own header create file "header" in the output dir.) +M3 +(Header end.) +""", + 'footer': """ +(Footer) +M5 +G00 X0.0000 Y0.0000 +M2 +(Using default footer. To add your own footer create file "footer" in the output dir.) +(end) +%""" +} + +INTERSECTION_RECURSION_DEPTH = 10 +INTERSECTION_TOLERANCE = 0.00001 + +def marker_style(stroke, marker='DrawCurveMarker', width=1): + """Set a marker style with some basic defaults""" + return Style(stroke=stroke, fill='none', stroke_width=width, + marker_end='url(#{})'.format(marker)) + +MARKER_STYLE = { + "in_out_path_style": marker_style('#0072a7', 'InOutPathMarker'), + "loft_style": { + 'main curve': marker_style('#88f', 'Arrow2Mend'), + }, + "biarc_style": { + 'biarc0': marker_style('#88f'), + 'biarc1': marker_style('#8f8'), + 'line': marker_style('#f88'), + 'area': marker_style('#777', width=0.1), + }, + "biarc_style_dark": { + 'biarc0': marker_style('#33a'), + 'biarc1': marker_style('#3a3'), + 'line': marker_style('#a33'), + 'area': marker_style('#222', width=0.3), + }, + "biarc_style_dark_area": { + 'biarc0': marker_style('#33a', width=0.1), + 'biarc1': marker_style('#3a3', width=0.1), + 'line': marker_style('#a33', width=0.1), + 'area': marker_style('#222', width=0.3), + }, + "biarc_style_i": { + 'biarc0': marker_style('#880'), + 'biarc1': marker_style('#808'), + 'line': marker_style('#088'), + 'area': marker_style('#999', width=0.3), + }, + "biarc_style_dark_i": { + 'biarc0': marker_style('#dd5'), + 'biarc1': marker_style('#d5d'), + 'line': marker_style('#5dd'), + 'area': marker_style('#aaa', width=0.3), + }, + "biarc_style_lathe_feed": { + 'biarc0': marker_style('#07f', width=0.4), + 'biarc1': marker_style('#0f7', width=0.4), + 'line': marker_style('#f44', width=0.4), + 'area': marker_style('#aaa', width=0.3), + }, + "biarc_style_lathe_passing feed": { + 'biarc0': marker_style('#07f', width=0.4), + 'biarc1': marker_style('#0f7', width=0.4), + 'line': marker_style('#f44', width=0.4), + 'area': marker_style('#aaa', width=0.3), + }, + "biarc_style_lathe_fine feed": { + 'biarc0': marker_style('#7f0', width=0.4), + 'biarc1': marker_style('#f70', width=0.4), + 'line': marker_style('#744', width=0.4), + 'area': marker_style('#aaa', width=0.3), + }, + "area artefact": Style(stroke='#ff0000', fill='#ffff00', stroke_width=1), + "area artefact arrow": Style(stroke='#ff0000', fill='#ffff00', stroke_width=1), + "dxf_points": Style(stroke="#ff0000", fill="#ff0000"), +} + + +################################################################################ +# Gcode additional functions +################################################################################ + +def gcode_comment_str(s, replace_new_line=False): + if replace_new_line: + s = re.sub(r"[\n\r]+", ".", s) + res = "" + if s[-1] == "\n": + s = s[:-1] + for a in s.split("\n"): + if a != "": + res += "(" + re.sub(r"[\(\)\\\n\r]", ".", a) + ")\n" + else: + res += "\n" + return res + + +################################################################################ +# Cubic Super Path additional functions +################################################################################ + + +def csp_from_polyline(line): + return [[[point[:] for _ in range(3)] for point in subline] for subline in line] + + +def csp_remove_zero_segments(csp, tolerance=1e-7): + res = [] + for subpath in csp: + if len(subpath) > 0: + res.append([subpath[0]]) + for sp1, sp2 in zip(subpath, subpath[1:]): + if point_to_point_d2(sp1[1], sp2[1]) <= tolerance and point_to_point_d2(sp1[2], sp2[1]) <= tolerance and point_to_point_d2(sp1[1], sp2[0]) <= tolerance: + res[-1][-1][2] = sp2[2] + else: + res[-1].append(sp2) + return res + + +def point_inside_csp(p, csp, on_the_path=True): + # we'll do the raytracing and see how many intersections are there on the ray's way. + # if number of intersections is even then point is outside. + # ray will be x=p.x and y=>p.y + # you can assign any value to on_the_path, by default if point is on the path + # function will return thai it's inside the path. + x, y = p + ray_intersections_count = 0 + for subpath in csp: + + for i in range(1, len(subpath)): + sp1 = subpath[i - 1] + sp2 = subpath[i] + ax, ay, bx, by, cx, cy, dx, dy = csp_parameterize(sp1, sp2) + if ax == 0 and bx == 0 and cx == 0 and dx == x: + # we've got a special case here + b = csp_true_bounds([[sp1, sp2]]) + if b[1][1] <= y <= b[3][1]: + # points is on the path + return on_the_path + else: + # we can skip this segment because it won't influence the answer. + pass + else: + for t in csp_line_intersection([x, y], [x, y + 5], sp1, sp2): + if t == 0 or t == 1: + # we've got another special case here + x1, y1 = csp_at_t(sp1, sp2, t) + if y1 == y: + # the point is on the path + return on_the_path + # if t == 0 we should have considered this case previously. + if t == 1: + # we have to check the next segment if it is on the same side of the ray + st_d = csp_normalized_slope(sp1, sp2, 1)[0] + if st_d == 0: + st_d = csp_normalized_slope(sp1, sp2, 0.99)[0] + + for j in range(1, len(subpath) + 1): + if (i + j) % len(subpath) == 0: + continue # skip the closing segment + sp11 = subpath[(i - 1 + j) % len(subpath)] + sp22 = subpath[(i + j) % len(subpath)] + ax1, ay1, bx1, by1, cx1, cy1, dx1, dy1 = csp_parameterize(sp1, sp2) + if ax1 == 0 and bx1 == 0 and cx1 == 0 and dx1 == x: + continue # this segment parallel to the ray, so skip it + en_d = csp_normalized_slope(sp11, sp22, 0)[0] + if en_d == 0: + en_d = csp_normalized_slope(sp11, sp22, 0.01)[0] + if st_d * en_d <= 0: + ray_intersections_count += 1 + break + else: + x1, y1 = csp_at_t(sp1, sp2, t) + if y1 == y: + # the point is on the path + return on_the_path + else: + if y1 > y and 3 * ax * t ** 2 + 2 * bx * t + cx != 0: # if it's 0 the path only touches the ray + ray_intersections_count += 1 + return ray_intersections_count % 2 == 1 + + +def csp_close_all_subpaths(csp, tolerance=0.000001): + for i in range(len(csp)): + if point_to_point_d2(csp[i][0][1], csp[i][-1][1]) > tolerance ** 2: + csp[i][-1][2] = csp[i][-1][1][:] + csp[i] += [[csp[i][0][1][:] for _ in range(3)]] + else: + if csp[i][0][1] != csp[i][-1][1]: + csp[i][-1][1] = csp[i][0][1][:] + return csp + + +def csp_simple_bound(csp): + minx = None + miny = None + maxx = None + maxy = None + + for subpath in csp: + for sp in subpath: + for p in sp: + minx = min(minx, p[0]) if minx is not None else p[0] + miny = min(miny, p[1]) if miny is not None else p[1] + maxx = max(maxx, p[0]) if maxx is not None else p[0] + maxy = max(maxy, p[1]) if maxy is not None else p[1] + return minx, miny, maxx, maxy + + +def csp_segment_to_bez(sp1, sp2): + return sp1[1:] + sp2[:2] + + +def csp_to_point_distance(csp, p, dist_bounds=(0, 1e100)): + min_dist = [1e100, 0, 0, 0] + for j in range(len(csp)): + for i in range(1, len(csp[j])): + d = csp_seg_to_point_distance(csp[j][i - 1], csp[j][i], p, sample_points=5) + if d[0] < dist_bounds[0]: + return [d[0], j, i, d[1]] + else: + if d[0] < min_dist[0]: + min_dist = [d[0], j, i, d[1]] + return min_dist + + +def csp_seg_to_point_distance(sp1, sp2, p, sample_points=5): + ax, ay, bx, by, cx, cy, dx, dy = csp_parameterize(sp1, sp2) + dx = dx - p[0] + dy = dy - p[1] + if sample_points < 2: + sample_points = 2 + d = min([(p[0] - sp1[1][0]) ** 2 + (p[1] - sp1[1][1]) ** 2, 0.], [(p[0] - sp2[1][0]) ** 2 + (p[1] - sp2[1][1]) ** 2, 1.]) + for k in range(sample_points): + t = float(k) / (sample_points - 1) + i = 0 + while i == 0 or abs(f) > 0.000001 and i < 20: + t2 = t ** 2 + t3 = t ** 3 + f = (ax * t3 + bx * t2 + cx * t + dx) * (3 * ax * t2 + 2 * bx * t + cx) + (ay * t3 + by * t2 + cy * t + dy) * (3 * ay * t2 + 2 * by * t + cy) + df = (6 * ax * t + 2 * bx) * (ax * t3 + bx * t2 + cx * t + dx) + (3 * ax * t2 + 2 * bx * t + cx) ** 2 + (6 * ay * t + 2 * by) * (ay * t3 + by * t2 + cy * t + dy) + (3 * ay * t2 + 2 * by * t + cy) ** 2 + if df != 0: + t = t - f / df + else: + break + i += 1 + if 0 <= t <= 1: + p1 = csp_at_t(sp1, sp2, t) + d1 = (p1[0] - p[0]) ** 2 + (p1[1] - p[1]) ** 2 + if d1 < d[0]: + d = [d1, t] + return d + + +def csp_seg_to_csp_seg_distance(sp1, sp2, sp3, sp4, dist_bounds=(0, 1e100), sample_points=5, tolerance=.01): + # check the ending points first + dist = csp_seg_to_point_distance(sp1, sp2, sp3[1], sample_points) + dist += [0.] + if dist[0] <= dist_bounds[0]: + return dist + d = csp_seg_to_point_distance(sp1, sp2, sp4[1], sample_points) + if d[0] < dist[0]: + dist = d + [1.] + if dist[0] <= dist_bounds[0]: + return dist + d = csp_seg_to_point_distance(sp3, sp4, sp1[1], sample_points) + if d[0] < dist[0]: + dist = [d[0], 0., d[1]] + if dist[0] <= dist_bounds[0]: + return dist + d = csp_seg_to_point_distance(sp3, sp4, sp2[1], sample_points) + if d[0] < dist[0]: + dist = [d[0], 1., d[1]] + if dist[0] <= dist_bounds[0]: + return dist + sample_points -= 2 + if sample_points < 1: + sample_points = 1 + ax1, ay1, bx1, by1, cx1, cy1, dx1, dy1 = csp_parameterize(sp1, sp2) + ax2, ay2, bx2, by2, cx2, cy2, dx2, dy2 = csp_parameterize(sp3, sp4) + # try to find closes points using Newtons method + for k in range(sample_points): + for j in range(sample_points): + t1 = float(k + 1) / (sample_points + 1) + t2 = float(j) / (sample_points + 1) + + t12 = t1 * t1 + t13 = t1 * t1 * t1 + t22 = t2 * t2 + t23 = t2 * t2 * t2 + i = 0 + + F1 = [0, 0] + F2 = [[0, 0], [0, 0]] + F = 1e100 + x = ax1 * t13 + bx1 * t12 + cx1 * t1 + dx1 - (ax2 * t23 + bx2 * t22 + cx2 * t2 + dx2) + y = ay1 * t13 + by1 * t12 + cy1 * t1 + dy1 - (ay2 * t23 + by2 * t22 + cy2 * t2 + dy2) + while i < 2 or abs(F - Flast) > tolerance and i < 30: + f1x = 3 * ax1 * t12 + 2 * bx1 * t1 + cx1 + f1y = 3 * ay1 * t12 + 2 * by1 * t1 + cy1 + f2x = 3 * ax2 * t22 + 2 * bx2 * t2 + cx2 + f2y = 3 * ay2 * t22 + 2 * by2 * t2 + cy2 + F1[0] = 2 * f1x * x + 2 * f1y * y + F1[1] = -2 * f2x * x - 2 * f2y * y + F2[0][0] = 2 * (6 * ax1 * t1 + 2 * bx1) * x + 2 * f1x * f1x + 2 * (6 * ay1 * t1 + 2 * by1) * y + 2 * f1y * f1y + F2[0][1] = -2 * f1x * f2x - 2 * f1y * f2y + F2[1][0] = -2 * f2x * f1x - 2 * f2y * f1y + F2[1][1] = -2 * (6 * ax2 * t2 + 2 * bx2) * x + 2 * f2x * f2x - 2 * (6 * ay2 * t2 + 2 * by2) * y + 2 * f2y * f2y + F2 = inv_2x2(F2) + if F2 is not None: + t1 -= (F2[0][0] * F1[0] + F2[0][1] * F1[1]) + t2 -= (F2[1][0] * F1[0] + F2[1][1] * F1[1]) + t12 = t1 * t1 + t13 = t1 * t1 * t1 + t22 = t2 * t2 + t23 = t2 * t2 * t2 + x = ax1 * t13 + bx1 * t12 + cx1 * t1 + dx1 - (ax2 * t23 + bx2 * t22 + cx2 * t2 + dx2) + y = ay1 * t13 + by1 * t12 + cy1 * t1 + dy1 - (ay2 * t23 + by2 * t22 + cy2 * t2 + dy2) + Flast = F + F = x * x + y * y + else: + break + i += 1 + if F < dist[0] and 0 <= t1 <= 1 and 0 <= t2 <= 1: + dist = [F, t1, t2] + if dist[0] <= dist_bounds[0]: + return dist + return dist + + +def csp_to_csp_distance(csp1, csp2, dist_bounds=(0, 1e100), tolerance=.01): + dist = [1e100, 0, 0, 0, 0, 0, 0] + for i1 in range(len(csp1)): + for j1 in range(1, len(csp1[i1])): + for i2 in range(len(csp2)): + for j2 in range(1, len(csp2[i2])): + d = csp_seg_bound_to_csp_seg_bound_max_min_distance(csp1[i1][j1 - 1], csp1[i1][j1], csp2[i2][j2 - 1], csp2[i2][j2]) + if d[0] >= dist_bounds[1]: + continue + if d[1] < dist_bounds[0]: + return [d[1], i1, j1, 1, i2, j2, 1] + d = csp_seg_to_csp_seg_distance(csp1[i1][j1 - 1], csp1[i1][j1], csp2[i2][j2 - 1], csp2[i2][j2], dist_bounds, tolerance=tolerance) + if d[0] < dist[0]: + dist = [d[0], i1, j1, d[1], i2, j2, d[2]] + if dist[0] <= dist_bounds[0]: + return dist + if dist[0] >= dist_bounds[1]: + return dist + return dist + + +def csp_split(sp1, sp2, t=.5): + [x1, y1] = sp1[1] + [x2, y2] = sp1[2] + [x3, y3] = sp2[0] + [x4, y4] = sp2[1] + x12 = x1 + (x2 - x1) * t + y12 = y1 + (y2 - y1) * t + x23 = x2 + (x3 - x2) * t + y23 = y2 + (y3 - y2) * t + x34 = x3 + (x4 - x3) * t + y34 = y3 + (y4 - y3) * t + x1223 = x12 + (x23 - x12) * t + y1223 = y12 + (y23 - y12) * t + x2334 = x23 + (x34 - x23) * t + y2334 = y23 + (y34 - y23) * t + x = x1223 + (x2334 - x1223) * t + y = y1223 + (y2334 - y1223) * t + return [sp1[0], sp1[1], [x12, y12]], [[x1223, y1223], [x, y], [x2334, y2334]], [[x34, y34], sp2[1], sp2[2]] + + +def csp_true_bounds(csp): + # Finds minx,miny,maxx,maxy of the csp and return their (x,y,i,j,t) + minx = [float("inf"), 0, 0, 0] + maxx = [float("-inf"), 0, 0, 0] + miny = [float("inf"), 0, 0, 0] + maxy = [float("-inf"), 0, 0, 0] + for i in range(len(csp)): + for j in range(1, len(csp[i])): + ax, ay, bx, by, cx, cy, x0, y0 = bezierparameterize((csp[i][j - 1][1], csp[i][j - 1][2], csp[i][j][0], csp[i][j][1])) + roots = cubic_solver(0, 3 * ax, 2 * bx, cx) + [0, 1] + for root in roots: + if type(root) is complex and abs(root.imag) < 1e-10: + root = root.real + if type(root) is not complex and 0 <= root <= 1: + y = ay * (root ** 3) + by * (root ** 2) + cy * root + y0 + x = ax * (root ** 3) + bx * (root ** 2) + cx * root + x0 + maxx = max([x, y, i, j, root], maxx) + minx = min([x, y, i, j, root], minx) + + roots = cubic_solver(0, 3 * ay, 2 * by, cy) + [0, 1] + for root in roots: + if type(root) is complex and root.imag == 0: + root = root.real + if type(root) is not complex and 0 <= root <= 1: + y = ay * (root ** 3) + by * (root ** 2) + cy * root + y0 + x = ax * (root ** 3) + bx * (root ** 2) + cx * root + x0 + maxy = max([y, x, i, j, root], maxy) + miny = min([y, x, i, j, root], miny) + maxy[0], maxy[1] = maxy[1], maxy[0] + miny[0], miny[1] = miny[1], miny[0] + + return minx, miny, maxx, maxy + + +############################################################################ +# csp_segments_intersection(sp1,sp2,sp3,sp4) +# +# Returns array containing all intersections between two segments of cubic +# super path. Results are [ta,tb], or [ta0, ta1, tb0, tb1, "Overlap"] +# where ta, tb are values of t for the intersection point. +############################################################################ +def csp_segments_intersection(sp1, sp2, sp3, sp4): + a = csp_segment_to_bez(sp1, sp2) + b = csp_segment_to_bez(sp3, sp4) + + def polish_intersection(a, b, ta, tb, tolerance=INTERSECTION_TOLERANCE): + ax, ay, bx, by, cx, cy, dx, dy = bezierparameterize(a) + ax1, ay1, bx1, by1, cx1, cy1, dx1, dy1 = bezierparameterize(b) + i = 0 + F = [.0, .0] + F1 = [[.0, .0], [.0, .0]] + while i == 0 or (abs(F[0]) ** 2 + abs(F[1]) ** 2 > tolerance and i < 10): + ta3 = ta ** 3 + ta2 = ta ** 2 + tb3 = tb ** 3 + tb2 = tb ** 2 + F[0] = ax * ta3 + bx * ta2 + cx * ta + dx - ax1 * tb3 - bx1 * tb2 - cx1 * tb - dx1 + F[1] = ay * ta3 + by * ta2 + cy * ta + dy - ay1 * tb3 - by1 * tb2 - cy1 * tb - dy1 + F1[0][0] = 3 * ax * ta2 + 2 * bx * ta + cx + F1[0][1] = -3 * ax1 * tb2 - 2 * bx1 * tb - cx1 + F1[1][0] = 3 * ay * ta2 + 2 * by * ta + cy + F1[1][1] = -3 * ay1 * tb2 - 2 * by1 * tb - cy1 + det = F1[0][0] * F1[1][1] - F1[0][1] * F1[1][0] + if det != 0: + F1 = [[F1[1][1] / det, -F1[0][1] / det], [-F1[1][0] / det, F1[0][0] / det]] + ta = ta - (F1[0][0] * F[0] + F1[0][1] * F[1]) + tb = tb - (F1[1][0] * F[0] + F1[1][1] * F[1]) + else: + break + i += 1 + + return ta, tb + + def recursion(a, b, ta0, ta1, tb0, tb1, depth_a, depth_b): + global bezier_intersection_recursive_result + if a == b: + bezier_intersection_recursive_result += [[ta0, tb0, ta1, tb1, "Overlap"]] + return + tam = (ta0 + ta1) / 2 + tbm = (tb0 + tb1) / 2 + if depth_a > 0 and depth_b > 0: + a1, a2 = bez_split(a, 0.5) + b1, b2 = bez_split(b, 0.5) + if bez_bounds_intersect(a1, b1): + recursion(a1, b1, ta0, tam, tb0, tbm, depth_a - 1, depth_b - 1) + if bez_bounds_intersect(a2, b1): + recursion(a2, b1, tam, ta1, tb0, tbm, depth_a - 1, depth_b - 1) + if bez_bounds_intersect(a1, b2): + recursion(a1, b2, ta0, tam, tbm, tb1, depth_a - 1, depth_b - 1) + if bez_bounds_intersect(a2, b2): + recursion(a2, b2, tam, ta1, tbm, tb1, depth_a - 1, depth_b - 1) + elif depth_a > 0: + a1, a2 = bez_split(a, 0.5) + if bez_bounds_intersect(a1, b): + recursion(a1, b, ta0, tam, tb0, tb1, depth_a - 1, depth_b) + if bez_bounds_intersect(a2, b): + recursion(a2, b, tam, ta1, tb0, tb1, depth_a - 1, depth_b) + elif depth_b > 0: + b1, b2 = bez_split(b, 0.5) + if bez_bounds_intersect(a, b1): + recursion(a, b1, ta0, ta1, tb0, tbm, depth_a, depth_b - 1) + if bez_bounds_intersect(a, b2): + recursion(a, b2, ta0, ta1, tbm, tb1, depth_a, depth_b - 1) + else: # Both segments have been subdivided enough. Let's get some intersections :). + intersection, t1, t2 = straight_segments_intersection([a[0]] + [a[3]], [b[0]] + [b[3]]) + if intersection: + if intersection == "Overlap": + t1 = (max(0, min(1, t1[0])) + max(0, min(1, t1[1]))) / 2 + t2 = (max(0, min(1, t2[0])) + max(0, min(1, t2[1]))) / 2 + bezier_intersection_recursive_result += [[ta0 + t1 * (ta1 - ta0), tb0 + t2 * (tb1 - tb0)]] + + global bezier_intersection_recursive_result + bezier_intersection_recursive_result = [] + recursion(a, b, 0., 1., 0., 1., INTERSECTION_RECURSION_DEPTH, INTERSECTION_RECURSION_DEPTH) + intersections = bezier_intersection_recursive_result + for i in range(len(intersections)): + if len(intersections[i]) < 5 or intersections[i][4] != "Overlap": + intersections[i] = polish_intersection(a, b, intersections[i][0], intersections[i][1]) + return intersections + + +def csp_segments_true_intersection(sp1, sp2, sp3, sp4): + intersections = csp_segments_intersection(sp1, sp2, sp3, sp4) + res = [] + for intersection in intersections: + if ( + (len(intersection) == 5 and intersection[4] == "Overlap" and (0 <= intersection[0] <= 1 or 0 <= intersection[1] <= 1) and (0 <= intersection[2] <= 1 or 0 <= intersection[3] <= 1)) + or (0 <= intersection[0] <= 1 and 0 <= intersection[1] <= 1) + ): + res += [intersection] + return res + + +def csp_get_t_at_curvature(sp1, sp2, c, sample_points=16): + # returns a list containing [t1,t2,t3,...,tn], 0<=ti<=1... + if sample_points < 2: + sample_points = 2 + tolerance = .0000000001 + res = [] + ax, ay, bx, by, cx, cy, dx, dy = csp_parameterize(sp1, sp2) + for k in range(sample_points): + t = float(k) / (sample_points - 1) + i = 0 + F = 1e100 + while i < 2 or abs(F) > tolerance and i < 17: + try: # some numerical calculation could exceed the limits + t2 = t * t + # slopes... + f1x = 3 * ax * t2 + 2 * bx * t + cx + f1y = 3 * ay * t2 + 2 * by * t + cy + f2x = 6 * ax * t + 2 * bx + f2y = 6 * ay * t + 2 * by + f3x = 6 * ax + f3y = 6 * ay + d = (f1x ** 2 + f1y ** 2) ** 1.5 + F1 = ( + ((f1x * f3y - f3x * f1y) * d - (f1x * f2y - f2x * f1y) * 3. * (f2x * f1x + f2y * f1y) * ((f1x ** 2 + f1y ** 2) ** .5)) / + ((f1x ** 2 + f1y ** 2) ** 3) + ) + F = (f1x * f2y - f1y * f2x) / d - c + t -= F / F1 + except: + break + i += 1 + if 0 <= t <= 1 and F <= tolerance: + if len(res) == 0: + res.append(t) + for i in res: + if abs(t - i) <= 0.001: + break + if not abs(t - i) <= 0.001: + res.append(t) + return res + + +def csp_max_curvature(sp1, sp2): + ax, ay, bx, by, cx, cy, dx, dy = csp_parameterize(sp1, sp2) + tolerance = .0001 + F = 0. + i = 0 + while i < 2 or F - Flast < tolerance and i < 10: + t = .5 + f1x = 3 * ax * t ** 2 + 2 * bx * t + cx + f1y = 3 * ay * t ** 2 + 2 * by * t + cy + f2x = 6 * ax * t + 2 * bx + f2y = 6 * ay * t + 2 * by + f3x = 6 * ax + f3y = 6 * ay + d = pow(f1x ** 2 + f1y ** 2, 1.5) + if d != 0: + Flast = F + F = (f1x * f2y - f1y * f2x) / d + F1 = ( + (d * (f1x * f3y - f3x * f1y) - (f1x * f2y - f2x * f1y) * 3. * (f2x * f1x + f2y * f1y) * pow(f1x ** 2 + f1y ** 2, .5)) / + (f1x ** 2 + f1y ** 2) ** 3 + ) + i += 1 + if F1 != 0: + t -= F / F1 + else: + break + else: + break + return t + + +def csp_curvature_at_t(sp1, sp2, t, depth=3): + ax, ay, bx, by, cx, cy, dx, dy = bezierparameterize(csp_segment_to_bez(sp1, sp2)) + + # curvature = (x'y''-y'x'') / (x'^2+y'^2)^1.5 + + f1x = 3 * ax * t ** 2 + 2 * bx * t + cx + f1y = 3 * ay * t ** 2 + 2 * by * t + cy + f2x = 6 * ax * t + 2 * bx + f2y = 6 * ay * t + 2 * by + d = (f1x ** 2 + f1y ** 2) ** 1.5 + if d != 0: + return (f1x * f2y - f1y * f2x) / d + else: + t1 = f1x * f2y - f1y * f2x + if t1 > 0: + return 1e100 + if t1 < 0: + return -1e100 + # Use the Lapitals rule to solve 0/0 problem for 2 times... + t1 = 2 * (bx * ay - ax * by) * t + (ay * cx - ax * cy) + if t1 > 0: + return 1e100 + if t1 < 0: + return -1e100 + t1 = bx * ay - ax * by + if t1 > 0: + return 1e100 + if t1 < 0: + return -1e100 + if depth > 0: + # little hack ;^) hope it won't influence anything... + return csp_curvature_at_t(sp1, sp2, t * 1.004, depth - 1) + return 1e100 + + +def csp_subpath_ccw(subpath): + # Remove all zero length segments + s = 0 + if (P(subpath[-1][1]) - P(subpath[0][1])).l2() > 1e-10: + subpath[-1][2] = subpath[-1][1] + subpath[0][0] = subpath[0][1] + subpath += [[subpath[0][1], subpath[0][1], subpath[0][1]]] + pl = subpath[-1][2] + for sp1 in subpath: + for p in sp1: + s += (p[0] - pl[0]) * (p[1] + pl[1]) + pl = p + return s < 0 + + +def csp_at_t(sp1, sp2, t): + ax = sp1[1][0] + bx = sp1[2][0] + cx = sp2[0][0] + dx = sp2[1][0] + + ay = sp1[1][1] + by = sp1[2][1] + cy = sp2[0][1] + dy = sp2[1][1] + + x1 = ax + (bx - ax) * t + y1 = ay + (by - ay) * t + + x2 = bx + (cx - bx) * t + y2 = by + (cy - by) * t + + x3 = cx + (dx - cx) * t + y3 = cy + (dy - cy) * t + + x4 = x1 + (x2 - x1) * t + y4 = y1 + (y2 - y1) * t + + x5 = x2 + (x3 - x2) * t + y5 = y2 + (y3 - y2) * t + + x = x4 + (x5 - x4) * t + y = y4 + (y5 - y4) * t + + return [x, y] + + +def csp_at_length(sp1, sp2, l=0.5, tolerance=0.01): + bez = (sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:]) + t = beziertatlength(bez, l, tolerance) + return csp_at_t(sp1, sp2, t) + + +def cspseglength(sp1, sp2, tolerance=0.01): + bez = (sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:]) + return bezierlength(bez, tolerance) + + +def csp_line_intersection(l1, l2, sp1, sp2): + dd = l1[0] + cc = l2[0] - l1[0] + bb = l1[1] + aa = l2[1] - l1[1] + if aa == cc == 0: + return [] + if aa: + coef1 = cc / aa + coef2 = 1 + else: + coef1 = 1 + coef2 = aa / cc + bez = (sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:]) + ax, ay, bx, by, cx, cy, x0, y0 = bezierparameterize(bez) + a = coef1 * ay - coef2 * ax + b = coef1 * by - coef2 * bx + c = coef1 * cy - coef2 * cx + d = coef1 * (y0 - bb) - coef2 * (x0 - dd) + roots = cubic_solver(a, b, c, d) + retval = [] + for i in roots: + if type(i) is complex and abs(i.imag) < 1e-7: + i = i.real + if type(i) is not complex and -1e-10 <= i <= 1. + 1e-10: + retval.append(i) + return retval + + +def csp_split_by_two_points(sp1, sp2, t1, t2): + if t1 > t2: + t1, t2 = t2, t1 + if t1 == t2: + sp1, sp2, sp3 = csp_split(sp1, sp2, t1) + return [sp1, sp2, sp2, sp3] + elif t1 <= 1e-10 and t2 >= 1. - 1e-10: + return [sp1, sp1, sp2, sp2] + elif t1 <= 1e-10: + sp1, sp2, sp3 = csp_split(sp1, sp2, t2) + return [sp1, sp1, sp2, sp3] + elif t2 >= 1. - 1e-10: + sp1, sp2, sp3 = csp_split(sp1, sp2, t1) + return [sp1, sp2, sp3, sp3] + else: + sp1, sp2, sp3 = csp_split(sp1, sp2, t1) + sp2, sp3, sp4 = csp_split(sp2, sp3, (t2 - t1) / (1 - t1)) + return [sp1, sp2, sp3, sp4] + + +def csp_seg_split(sp1, sp2, points): + # points is float=t or list [t1, t2, ..., tn] + if type(points) is float: + points = [points] + points.sort() + res = [sp1, sp2] + last_t = 0 + for t in points: + if 1e-10 < t < 1. - 1e-10: + sp3, sp4, sp5 = csp_split(res[-2], res[-1], (t - last_t) / (1 - last_t)) + last_t = t + res[-2:] = [sp3, sp4, sp5] + return res + + +def csp_subpath_split_by_points(subpath, points): + # points are [[i,t]...] where i-segment's number + points.sort() + points = [[1, 0.]] + points + [[len(subpath) - 1, 1.]] + parts = [] + for int1, int2 in zip(points, points[1:]): + if int1 == int2: + continue + if int1[1] == 1.: + int1[0] += 1 + int1[1] = 0. + if int1 == int2: + continue + if int2[1] == 0.: + int2[0] -= 1 + int2[1] = 1. + if int1[0] == 0 and int2[0] == len(subpath) - 1: # and small(int1[1]) and small(int2[1]-1) : + continue + if int1[0] == int2[0]: # same segment + sp = csp_split_by_two_points(subpath[int1[0] - 1], subpath[int1[0]], int1[1], int2[1]) + if sp[1] != sp[2]: + parts += [[sp[1], sp[2]]] + else: + sp5, sp1, sp2 = csp_split(subpath[int1[0] - 1], subpath[int1[0]], int1[1]) + sp3, sp4, sp5 = csp_split(subpath[int2[0] - 1], subpath[int2[0]], int2[1]) + if int1[0] == int2[0] - 1: + parts += [[sp1, [sp2[0], sp2[1], sp3[2]], sp4]] + else: + parts += [[sp1, sp2] + subpath[int1[0] + 1:int2[0] - 1] + [sp3, sp4]] + return parts + + +def arc_from_s_r_n_l(s, r, n, l): + if abs(n[0] ** 2 + n[1] ** 2 - 1) > 1e-10: + n = normalize(n) + return arc_from_c_s_l([s[0] + n[0] * r, s[1] + n[1] * r], s, l) + + +def arc_from_c_s_l(c, s, l): + r = point_to_point_d(c, s) + if r == 0: + return [] + alpha = l / r + cos_ = math.cos(alpha) + sin_ = math.sin(alpha) + e = [c[0] + (s[0] - c[0]) * cos_ - (s[1] - c[1]) * sin_, c[1] + (s[0] - c[0]) * sin_ + (s[1] - c[1]) * cos_] + n = [c[0] - s[0], c[1] - s[1]] + slope = rotate_cw(n) if l > 0 else rotate_ccw(n) + return csp_from_arc(s, e, c, r, slope) + + +def csp_from_arc(start, end, center, r, slope_st): + # Creates csp that approximise specified arc + r = abs(r) + alpha = (atan2(end[0] - center[0], end[1] - center[1]) - atan2(start[0] - center[0], start[1] - center[1])) % TAU + + sectors = int(abs(alpha) * 2 / math.pi) + 1 + alpha_start = atan2(start[0] - center[0], start[1] - center[1]) + cos_ = math.cos(alpha_start) + sin_ = math.sin(alpha_start) + k = (4. * math.tan(alpha / sectors / 4.) / 3.) + if dot(slope_st, [- sin_ * k * r, cos_ * k * r]) < 0: + if alpha > 0: + alpha -= TAU + else: + alpha += TAU + if abs(alpha * r) < 0.001: + return [] + + sectors = int(abs(alpha) * 2 / math.pi) + 1 + k = (4. * math.tan(alpha / sectors / 4.) / 3.) + result = [] + for i in range(sectors + 1): + cos_ = math.cos(alpha_start + alpha * i / sectors) + sin_ = math.sin(alpha_start + alpha * i / sectors) + sp = [[], [center[0] + cos_ * r, center[1] + sin_ * r], []] + sp[0] = [sp[1][0] + sin_ * k * r, sp[1][1] - cos_ * k * r] + sp[2] = [sp[1][0] - sin_ * k * r, sp[1][1] + cos_ * k * r] + result += [sp] + result[0][0] = result[0][1][:] + result[-1][2] = result[-1][1] + + return result + + +def point_to_arc_distance(p, arc): + # Distance calculattion from point to arc + P0, P2, c, a = arc + p = P(p) + r = (P0 - c).mag() + if r > 0: + i = c + (p - c).unit() * r + alpha = ((i - c).angle() - (P0 - c).angle()) + if a * alpha < 0: + if alpha > 0: + alpha = alpha - TAU + else: + alpha = TAU + alpha + if between(alpha, 0, a) or min(abs(alpha), abs(alpha - a)) < STRAIGHT_TOLERANCE: + return (p - i).mag(), [i.x, i.y] + else: + d1 = (p - P0).mag() + d2 = (p - P2).mag() + if d1 < d2: + return d1, [P0.x, P0.y] + else: + return d2, [P2.x, P2.y] + + +def csp_to_arc_distance(sp1, sp2, arc1, arc2, tolerance=0.01): # arc = [start,end,center,alpha] + n = 10 + i = 0 + d = (0, [0, 0]) + d1 = (0, [0, 0]) + dl = 0 + while i < 1 or (abs(d1[0] - dl[0]) > tolerance and i < 4): + i += 1 + dl = d1 * 1 + for j in range(n + 1): + t = float(j) / n + p = csp_at_t(sp1, sp2, t) + d = min(point_to_arc_distance(p, arc1), point_to_arc_distance(p, arc2)) + d1 = max(d1, d) + n = n * 2 + return d1[0] + + +def csp_point_inside_bound(sp1, sp2, p): + bez = [sp1[1], sp1[2], sp2[0], sp2[1]] + x, y = p + c = 0 + # CLT added test of x in range + xmin = 1e100 + xmax = -1e100 + for i in range(4): + [x0, y0] = bez[i - 1] + [x1, y1] = bez[i] + xmin = min(xmin, x0) + xmax = max(xmax, x0) + if x0 - x1 != 0 and (y - y0) * (x1 - x0) >= (x - x0) * (y1 - y0) and x > min(x0, x1) and x <= max(x0, x1): + c += 1 + return xmin <= x <= xmax and c % 2 == 0 + + +def line_line_intersect(p1, p2, p3, p4): # Return only true intersection. + if (p1[0] == p2[0] and p1[1] == p2[1]) or (p3[0] == p4[0] and p3[1] == p4[1]): + return False + x = (p2[0] - p1[0]) * (p4[1] - p3[1]) - (p2[1] - p1[1]) * (p4[0] - p3[0]) + if x == 0: # Lines are parallel + if (p3[0] - p1[0]) * (p2[1] - p1[1]) == (p3[1] - p1[1]) * (p2[0] - p1[0]): + if p3[0] != p4[0]: + t11 = (p1[0] - p3[0]) / (p4[0] - p3[0]) + t12 = (p2[0] - p3[0]) / (p4[0] - p3[0]) + t21 = (p3[0] - p1[0]) / (p2[0] - p1[0]) + t22 = (p4[0] - p1[0]) / (p2[0] - p1[0]) + else: + t11 = (p1[1] - p3[1]) / (p4[1] - p3[1]) + t12 = (p2[1] - p3[1]) / (p4[1] - p3[1]) + t21 = (p3[1] - p1[1]) / (p2[1] - p1[1]) + t22 = (p4[1] - p1[1]) / (p2[1] - p1[1]) + return "Overlap" if (0 <= t11 <= 1 or 0 <= t12 <= 1) and (0 <= t21 <= 1 or 0 <= t22 <= 1) else False + else: + return False + else: + return ( + 0 <= ((p4[0] - p3[0]) * (p1[1] - p3[1]) - (p4[1] - p3[1]) * (p1[0] - p3[0])) / x <= 1 and + 0 <= ((p2[0] - p1[0]) * (p1[1] - p3[1]) - (p2[1] - p1[1]) * (p1[0] - p3[0])) / x <= 1) + + +def line_line_intersection_points(p1, p2, p3, p4): # Return only points [ (x,y) ] + if (p1[0] == p2[0] and p1[1] == p2[1]) or (p3[0] == p4[0] and p3[1] == p4[1]): + return [] + x = (p2[0] - p1[0]) * (p4[1] - p3[1]) - (p2[1] - p1[1]) * (p4[0] - p3[0]) + if x == 0: # Lines are parallel + if (p3[0] - p1[0]) * (p2[1] - p1[1]) == (p3[1] - p1[1]) * (p2[0] - p1[0]): + if p3[0] != p4[0]: + t11 = (p1[0] - p3[0]) / (p4[0] - p3[0]) + t12 = (p2[0] - p3[0]) / (p4[0] - p3[0]) + t21 = (p3[0] - p1[0]) / (p2[0] - p1[0]) + t22 = (p4[0] - p1[0]) / (p2[0] - p1[0]) + else: + t11 = (p1[1] - p3[1]) / (p4[1] - p3[1]) + t12 = (p2[1] - p3[1]) / (p4[1] - p3[1]) + t21 = (p3[1] - p1[1]) / (p2[1] - p1[1]) + t22 = (p4[1] - p1[1]) / (p2[1] - p1[1]) + res = [] + if (0 <= t11 <= 1 or 0 <= t12 <= 1) and (0 <= t21 <= 1 or 0 <= t22 <= 1): + if 0 <= t11 <= 1: + res += [p1] + if 0 <= t12 <= 1: + res += [p2] + if 0 <= t21 <= 1: + res += [p3] + if 0 <= t22 <= 1: + res += [p4] + return res + else: + return [] + else: + t1 = ((p4[0] - p3[0]) * (p1[1] - p3[1]) - (p4[1] - p3[1]) * (p1[0] - p3[0])) / x + t2 = ((p2[0] - p1[0]) * (p1[1] - p3[1]) - (p2[1] - p1[1]) * (p1[0] - p3[0])) / x + if 0 <= t1 <= 1 and 0 <= t2 <= 1: + return [[p1[0] * (1 - t1) + p2[0] * t1, p1[1] * (1 - t1) + p2[1] * t1]] + else: + return [] + + +def point_to_point_d2(a, b): + return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + + +def point_to_point_d(a, b): + return math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) + + +def point_to_line_segment_distance_2(p1, p2, p3): + # p1 - point, p2,p3 - line segment + # draw_pointer(p1) + w0 = [p1[0] - p2[0], p1[1] - p2[1]] + v = [p3[0] - p2[0], p3[1] - p2[1]] + c1 = w0[0] * v[0] + w0[1] * v[1] + if c1 <= 0: + return w0[0] * w0[0] + w0[1] * w0[1] + c2 = v[0] * v[0] + v[1] * v[1] + if c2 <= c1: + return (p1[0] - p3[0]) ** 2 + (p1[1] - p3[1]) ** 2 + return (p1[0] - p2[0] - v[0] * c1 / c2) ** 2 + (p1[1] - p2[1] - v[1] * c1 / c2) + + +def line_to_line_distance_2(p1, p2, p3, p4): + if line_line_intersect(p1, p2, p3, p4): + return 0 + return min( + point_to_line_segment_distance_2(p1, p3, p4), + point_to_line_segment_distance_2(p2, p3, p4), + point_to_line_segment_distance_2(p3, p1, p2), + point_to_line_segment_distance_2(p4, p1, p2)) + + +def csp_seg_bound_to_csp_seg_bound_max_min_distance(sp1, sp2, sp3, sp4): + bez1 = csp_segment_to_bez(sp1, sp2) + bez2 = csp_segment_to_bez(sp3, sp4) + min_dist = 1e100 + max_dist = 0. + for i in range(4): + if csp_point_inside_bound(sp1, sp2, bez2[i]) or csp_point_inside_bound(sp3, sp4, bez1[i]): + min_dist = 0. + break + for i in range(4): + for j in range(4): + d = line_to_line_distance_2(bez1[i - 1], bez1[i], bez2[j - 1], bez2[j]) + if d < min_dist: + min_dist = d + d = (bez2[j][0] - bez1[i][0]) ** 2 + (bez2[j][1] - bez1[i][1]) ** 2 + if max_dist < d: + max_dist = d + return min_dist, max_dist + + +def csp_reverse(csp): + for i in range(len(csp)): + n = [] + for j in csp[i]: + n = [[j[2][:], j[1][:], j[0][:]]] + n + csp[i] = n[:] + return csp + + +def csp_normalized_slope(sp1, sp2, t): + ax, ay, bx, by, cx, cy, dx, dy = bezierparameterize((sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:])) + if sp1[1] == sp2[1] == sp1[2] == sp2[0]: + return [1., 0.] + f1x = 3 * ax * t * t + 2 * bx * t + cx + f1y = 3 * ay * t * t + 2 * by * t + cy + if abs(f1x * f1x + f1y * f1y) > 1e-9: # LT changed this from 1e-20, which caused problems + l = math.sqrt(f1x * f1x + f1y * f1y) + return [f1x / l, f1y / l] + + if t == 0: + f1x = sp2[0][0] - sp1[1][0] + f1y = sp2[0][1] - sp1[1][1] + if abs(f1x * f1x + f1y * f1y) > 1e-9: # LT changed this from 1e-20, which caused problems + l = math.sqrt(f1x * f1x + f1y * f1y) + return [f1x / l, f1y / l] + else: + f1x = sp2[1][0] - sp1[1][0] + f1y = sp2[1][1] - sp1[1][1] + if f1x * f1x + f1y * f1y != 0: + l = math.sqrt(f1x * f1x + f1y * f1y) + return [f1x / l, f1y / l] + elif t == 1: + f1x = sp2[1][0] - sp1[2][0] + f1y = sp2[1][1] - sp1[2][1] + if abs(f1x * f1x + f1y * f1y) > 1e-9: + l = math.sqrt(f1x * f1x + f1y * f1y) + return [f1x / l, f1y / l] + else: + f1x = sp2[1][0] - sp1[1][0] + f1y = sp2[1][1] - sp1[1][1] + if f1x * f1x + f1y * f1y != 0: + l = math.sqrt(f1x * f1x + f1y * f1y) + return [f1x / l, f1y / l] + else: + return [1., 0.] + + +def csp_normalized_normal(sp1, sp2, t): + nx, ny = csp_normalized_slope(sp1, sp2, t) + return [-ny, nx] + + +def csp_parameterize(sp1, sp2): + return bezierparameterize(csp_segment_to_bez(sp1, sp2)) + + +def csp_concat_subpaths(*s): + def concat(s1, s2): + if not s1: + return s2 + if not s2: + return s1 + if (s1[-1][1][0] - s2[0][1][0]) ** 2 + (s1[-1][1][1] - s2[0][1][1]) ** 2 > 0.00001: + return s1[:-1] + [[s1[-1][0], s1[-1][1], s1[-1][1]], [s2[0][1], s2[0][1], s2[0][2]]] + s2[1:] + else: + return s1[:-1] + [[s1[-1][0], s2[0][1], s2[0][2]]] + s2[1:] + + if len(s) == 0: + return [] + if len(s) == 1: + return s[0] + result = s[0] + for s1 in s[1:]: + result = concat(result, s1) + return result + + +def csp_subpaths_end_to_start_distance2(s1, s2): + return (s1[-1][1][0] - s2[0][1][0]) ** 2 + (s1[-1][1][1] - s2[0][1][1]) ** 2 + + +def csp_clip_by_line(csp, l1, l2): + result = [] + for i in range(len(csp)): + s = csp[i] + intersections = [] + for j in range(1, len(s)): + intersections += [[j, int_] for int_ in csp_line_intersection(l1, l2, s[j - 1], s[j])] + splitted_s = csp_subpath_split_by_points(s, intersections) + for s in splitted_s[:]: + clip = False + for p in csp_true_bounds([s]): + if (l1[1] - l2[1]) * p[0] + (l2[0] - l1[0]) * p[1] + (l1[0] * l2[1] - l2[0] * l1[1]) < -0.01: + clip = True + break + if clip: + splitted_s.remove(s) + result += splitted_s + return result + + +def csp_subpath_line_to(subpath, points, prepend=False): + # Appends subpath with line or polyline. + if len(points) > 0: + if not prepend: + if len(subpath) > 0: + subpath[-1][2] = subpath[-1][1][:] + if type(points[0]) == type([1, 1]): + for p in points: + subpath += [[p[:], p[:], p[:]]] + else: + subpath += [[points, points, points]] + else: + if len(subpath) > 0: + subpath[0][0] = subpath[0][1][:] + if type(points[0]) == type([1, 1]): + for p in points: + subpath = [[p[:], p[:], p[:]]] + subpath + else: + subpath = [[points, points, points]] + subpath + return subpath + + +def csp_join_subpaths(csp): + result = csp[:] + done_smf = True + joined_result = [] + while done_smf: + done_smf = False + while len(result) > 0: + s1 = result[-1][:] + del (result[-1]) + j = 0 + joined_smf = False + while j < len(joined_result): + if csp_subpaths_end_to_start_distance2(joined_result[j], s1) < 0.000001: + joined_result[j] = csp_concat_subpaths(joined_result[j], s1) + done_smf = True + joined_smf = True + break + if csp_subpaths_end_to_start_distance2(s1, joined_result[j]) < 0.000001: + joined_result[j] = csp_concat_subpaths(s1, joined_result[j]) + done_smf = True + joined_smf = True + break + j += 1 + if not joined_smf: + joined_result += [s1[:]] + if done_smf: + result = joined_result[:] + joined_result = [] + return joined_result + + +def triangle_cross(a, b, c): + return (a[0] - b[0]) * (c[1] - b[1]) - (c[0] - b[0]) * (a[1] - b[1]) + + +def csp_segment_convex_hull(sp1, sp2): + a = sp1[1][:] + b = sp1[2][:] + c = sp2[0][:] + d = sp2[1][:] + + abc = triangle_cross(a, b, c) + abd = triangle_cross(a, b, d) + bcd = triangle_cross(b, c, d) + cad = triangle_cross(c, a, d) + if abc == 0 and abd == 0: + return [min(a, b, c, d), max(a, b, c, d)] + if abc == 0: + return [d, min(a, b, c), max(a, b, c)] + if abd == 0: + return [c, min(a, b, d), max(a, b, d)] + if bcd == 0: + return [a, min(b, c, d), max(b, c, d)] + if cad == 0: + return [b, min(c, a, d), max(c, a, d)] + + m1 = abc * abd > 0 + m2 = abc * bcd > 0 + m3 = abc * cad > 0 + + if m1 and m2 and m3: + return [a, b, c] + if m1 and m2 and not m3: + return [a, b, c, d] + if m1 and not m2 and m3: + return [a, b, d, c] + if not m1 and m2 and m3: + return [a, d, b, c] + if m1 and not (m2 and m3): + return [a, b, d] + if not (m1 and m2) and m3: + return [c, a, d] + if not (m1 and m3) and m2: + return [b, c, d] + + raise ValueError("csp_segment_convex_hull happened which is something that shouldn't happen!") + + +################################################################################ +# Bezier additional functions +################################################################################ + +def bez_bounds_intersect(bez1, bez2): + return bounds_intersect(bez_bound(bez2), bez_bound(bez1)) + + +def bez_bound(bez): + return [ + min(bez[0][0], bez[1][0], bez[2][0], bez[3][0]), + min(bez[0][1], bez[1][1], bez[2][1], bez[3][1]), + max(bez[0][0], bez[1][0], bez[2][0], bez[3][0]), + max(bez[0][1], bez[1][1], bez[2][1], bez[3][1]), + ] + + +def bounds_intersect(a, b): + return not ((a[0] > b[2]) or (b[0] > a[2]) or (a[1] > b[3]) or (b[1] > a[3])) + + +def tpoint(xy1, xy2, t): + (x1, y1) = xy1 + (x2, y2) = xy2 + return [x1 + t * (x2 - x1), y1 + t * (y2 - y1)] + + +def bez_split(a, t=0.5): + a1 = tpoint(a[0], a[1], t) + at = tpoint(a[1], a[2], t) + b2 = tpoint(a[2], a[3], t) + a2 = tpoint(a1, at, t) + b1 = tpoint(b2, at, t) + a3 = tpoint(a2, b1, t) + return [a[0], a1, a2, a3], [a3, b1, b2, a[3]] + + +################################################################################ +# Some vector functions +################################################################################ + +def normalize(xy): + (x, y) = xy + l = math.sqrt(x ** 2 + y ** 2) + if l == 0: + return [0., 0.] + else: + return [x / l, y / l] + + +def cross(a, b): + return a[1] * b[0] - a[0] * b[1] + + +def dot(a, b): + return a[0] * b[0] + a[1] * b[1] + + +def rotate_ccw(d): + return [-d[1], d[0]] + + +def rotate_cw(d): + return [d[1], -d[0]] + + +def vectors_ccw(a, b): + return a[0] * b[1] - b[0] * a[1] < 0 + + +################################################################################ +# Common functions +################################################################################ + +def inv_2x2(a): # invert matrix 2x2 + det = a[0][0] * a[1][1] - a[1][0] * a[0][1] + if det == 0: + return None + return [ + [a[1][1] / det, -a[0][1] / det], + [-a[1][0] / det, a[0][0] / det] + ] + + +def small(a): + global small_tolerance + return abs(a) < small_tolerance + + +def atan2(*arg): + if len(arg) == 1 and (type(arg[0]) == type([0., 0.]) or type(arg[0]) == type((0., 0.))): + return (math.pi / 2 - math.atan2(arg[0][0], arg[0][1])) % TAU + elif len(arg) == 2: + return (math.pi / 2 - math.atan2(arg[0], arg[1])) % TAU + else: + raise ValueError("Bad argumets for atan! ({})".format(*arg)) + + +def draw_text(text, x, y, group=None, style=None, font_size=10, gcodetools_tag=None): + if style is None: + style = "font-family:DejaVu Sans;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:DejaVu Sans;fill:#000000;fill-opacity:1;stroke:none;" + style += "font-size:{:f}px;".format(font_size) + attributes = {'x': str(x), 'y': str(y), 'style': style} + if gcodetools_tag is not None: + attributes["gcodetools"] = str(gcodetools_tag) + + if group is None: + group = options.doc_root + + text_elem = group.add(TextElement(**attributes)) + text_elem.set("xml:space", "preserve") + text = str(text).split("\n") + for string in text: + span = text_elem.add(Tspan(x=str(x), y=str(y))) + span.set('sodipodi:role', 'line') + y += font_size + span.text = str(string) + + +def draw_csp(csp, stroke="#f00", fill="none", comment="", width=0.354, group=None, style=None): + if group is None: + group = options.doc_root + node = group.add(PathElement()) + + node.style = style if style is not None else \ + {'fill': fill, 'fill-opacity': 1, 'stroke': stroke, 'stroke-width': width} + + node.path = CubicSuperPath(csp) + + if comment != '': + node.set('comment', comment) + + return node + + +def draw_pointer(x, color="#f00", figure="cross", group=None, comment="", fill=None, width=.1, size=10., text=None, font_size=None, pointer_type=None, attrib=None): + size = size / 2 + if attrib is None: + attrib = {} + if pointer_type is None: + pointer_type = "Pointer" + attrib["gcodetools"] = pointer_type + if group is None: + group = options.self.svg.get_current_layer() + if text is not None: + if font_size is None: + font_size = 7 + group = group.add(Group(gcodetools=pointer_type + " group")) + draw_text(text, x[0] + size * 2.2, x[1] - size, group=group, font_size=font_size) + if figure == "line": + s = "" + for i in range(1, len(x) / 2): + s += " {}, {} ".format(x[i * 2], x[i * 2 + 1]) + attrib.update({"d": "M {},{} L {}".format(x[0], x[1], s), "style": "fill:none;stroke:{};stroke-width:{:f};".format(color, width), "comment": str(comment)}) + elif figure == "arrow": + if fill is None: + fill = "#12b3ff" + fill_opacity = "0.8" + d = "m {},{} ".format(x[0], x[1]) + re.sub("([0-9\\-.e]+)", (lambda match: str(float(match.group(1)) * size * 2.)), "0.88464,-0.40404 c -0.0987,-0.0162 -0.186549,-0.0589 -0.26147,-0.1173 l 0.357342,-0.35625 c 0.04631,-0.039 0.0031,-0.13174 -0.05665,-0.12164 -0.0029,-1.4e-4 -0.0058,-1.4e-4 -0.0087,0 l -2.2e-5,2e-5 c -0.01189,0.004 -0.02257,0.0119 -0.0305,0.0217 l -0.357342,0.35625 c -0.05818,-0.0743 -0.102813,-0.16338 -0.117662,-0.26067 l -0.409636,0.88193 z") + attrib.update({"d": d, "style": "fill:{};stroke:none;fill-opacity:{};".format(fill, fill_opacity), "comment": str(comment)}) + else: + attrib.update({"d": "m {},{} l {:f},{:f} {:f},{:f} {:f},{:f} {:f},{:f} , {:f},{:f}".format(x[0], x[1], size, size, -2 * size, -2 * size, size, size, size, -size, -2 * size, 2 * size), "style": "fill:none;stroke:{};stroke-width:{:f};".format(color, width), "comment": str(comment)}) + group.add(PathElement(**attrib)) + + +def straight_segments_intersection(a, b, true_intersection=True): # (True intersection means check ta and tb are in [0,1]) + ax = a[0][0] + bx = a[1][0] + cx = b[0][0] + dx = b[1][0] + ay = a[0][1] + by = a[1][1] + cy = b[0][1] + dy = b[1][1] + if (ax == bx and ay == by) or (cx == dx and cy == dy): + return False, 0, 0 + if (bx - ax) * (dy - cy) - (by - ay) * (dx - cx) == 0: # Lines are parallel + ta = (ax - cx) / (dx - cx) if cx != dx else (ay - cy) / (dy - cy) + tb = (bx - cx) / (dx - cx) if cx != dx else (by - cy) / (dy - cy) + tc = (cx - ax) / (bx - ax) if ax != bx else (cy - ay) / (by - ay) + td = (dx - ax) / (bx - ax) if ax != bx else (dy - ay) / (by - ay) + return ("Overlap" if 0 <= ta <= 1 or 0 <= tb <= 1 or 0 <= tc <= 1 or 0 <= td <= 1 or not true_intersection else False), (ta, tb), (tc, td) + else: + ta = ((ay - cy) * (dx - cx) - (ax - cx) * (dy - cy)) / ((bx - ax) * (dy - cy) - (by - ay) * (dx - cx)) + tb = (ax - cx + ta * (bx - ax)) / (dx - cx) if dx != cx else (ay - cy + ta * (by - ay)) / (dy - cy) + return (0 <= ta <= 1 and 0 <= tb <= 1 or not true_intersection), ta, tb + + +def between(c, x, y): + return x - STRAIGHT_TOLERANCE <= c <= y + STRAIGHT_TOLERANCE or y - STRAIGHT_TOLERANCE <= c <= x + STRAIGHT_TOLERANCE + + +def cubic_solver_real(a, b, c, d): + # returns only real roots of a cubic equation. + roots = cubic_solver(a, b, c, d) + res = [] + for root in roots: + if type(root) is complex: + if -1e-10 < root.imag < 1e-10: + res.append(root.real) + else: + res.append(root) + return res + + +def cubic_solver(a, b, c, d): + if a != 0: + # Monics formula see http://en.wikipedia.org/wiki/Cubic_function#Monic_formula_of_roots + a, b, c = (b / a, c / a, d / a) + m = 2 * a ** 3 - 9 * a * b + 27 * c + k = a ** 2 - 3 * b + n = m ** 2 - 4 * k ** 3 + w1 = -.5 + .5 * cmath.sqrt(3) * 1j + w2 = -.5 - .5 * cmath.sqrt(3) * 1j + if n >= 0: + t = m + math.sqrt(n) + m1 = pow(t / 2, 1. / 3) if t >= 0 else -pow(-t / 2, 1. / 3) + t = m - math.sqrt(n) + n1 = pow(t / 2, 1. / 3) if t >= 0 else -pow(-t / 2, 1. / 3) + else: + m1 = pow(complex((m + cmath.sqrt(n)) / 2), 1. / 3) + n1 = pow(complex((m - cmath.sqrt(n)) / 2), 1. / 3) + x1 = -1. / 3 * (a + m1 + n1) + x2 = -1. / 3 * (a + w1 * m1 + w2 * n1) + x3 = -1. / 3 * (a + w2 * m1 + w1 * n1) + return [x1, x2, x3] + elif b != 0: + det = c ** 2 - 4 * b * d + if det > 0: + return [(-c + math.sqrt(det)) / (2 * b), (-c - math.sqrt(det)) / (2 * b)] + elif d == 0: + return [-c / (b * b)] + else: + return [(-c + cmath.sqrt(det)) / (2 * b), (-c - cmath.sqrt(det)) / (2 * b)] + elif c != 0: + return [-d / c] + else: + return [] + + +################################################################################ +# print_ prints any arguments into specified log file +################################################################################ + +def print_(*arg): + with open(options.log_filename, "ab") as f: + for s in arg: + s = unicode(s).encode('unicode_escape') + b" " + f.write(s) + f.write(b"\n") + + +################################################################################ +# Point (x,y) operations +################################################################################ +class P(object): + def __init__(self, x, y=None): + if not y is None: + self.x = float(x) + self.y = float(y) + else: + self.x = float(x[0]) + self.y = float(x[1]) + + def __add__(self, other): + return P(self.x + other.x, self.y + other.y) + + def __sub__(self, other): + return P(self.x - other.x, self.y - other.y) + + def __neg__(self): + return P(-self.x, -self.y) + + def __mul__(self, other): + if isinstance(other, P): + return self.x * other.x + self.y * other.y + return P(self.x * other, self.y * other) + + __rmul__ = __mul__ + + def __div__(self, other): + return P(self.x / other, self.y / other) + + def __truediv__(self, other): + return self.__div__(other) + + def mag(self): + return math.hypot(self.x, self.y) + + def unit(self): + h_mag = self.mag() + if h_mag: + return self / h_mag + return P(0, 0) + + def dot(self, other): + return self.x * other.x + self.y * other.y + + def rot(self, theta): + c = math.cos(theta) + s = math.sin(theta) + return P(self.x * c - self.y * s, self.x * s + self.y * c) + + def angle(self): + return math.atan2(self.y, self.x) + + def __repr__(self): + return '{:f},{:f}'.format(self.x, self.y) + + def pr(self): + return "{:.2f},{:.2f}".format(self.x, self.y) + + def to_list(self): + return [self.x, self.y] + + def ccw(self): + return P(-self.y, self.x) + + def l2(self): + return self.x * self.x + self.y * self.y + + +class Line(object): + def __init__(self, st, end): + if st.__class__ == P: + st = st.to_list() + if end.__class__ == P: + end = end.to_list() + self.st = P(st) + self.end = P(end) + self.l = self.length() + if self.l != 0: + self.n = ((self.end - self.st) / self.l).ccw() + else: + self.n = [0, 1] + + def offset(self, r): + self.st -= self.n * r + self.end -= self.n * r + + def l2(self): + return (self.st - self.end).l2() + + def length(self): + return (self.st - self.end).mag() + + def draw(self, group, style, layer, transform, num=0, reverse_angle=1): + st = gcodetools.transform(self.st.to_list(), layer, True) + end = gcodetools.transform(self.end.to_list(), layer, True) + + attr = {'style': style['line'], + 'd': 'M {},{} L {},{}'.format(st[0], st[1], end[0], end[1]), + "gcodetools": "Preview", + } + if transform: + attr["transform"] = transform + group.add(PathElement(**attr)) + + def intersect(self, b): + if b.__class__ == Line: + if self.l < 10e-8 or b.l < 10e-8: + return [] + v1 = self.end - self.st + v2 = b.end - b.st + x = v1.x * v2.y - v2.x * v1.y + if x == 0: + # lines are parallel + res = [] + + if (self.st.x - b.st.x) * v1.y - (self.st.y - b.st.y) * v1.x == 0: + # lines are the same + if v1.x != 0: + if 0 <= (self.st.x - b.st.x) / v2.x <= 1: + res.append(self.st) + if 0 <= (self.end.x - b.st.x) / v2.x <= 1: + res.append(self.end) + if 0 <= (b.st.x - self.st.x) / v1.x <= 1: + res.append(b.st) + if 0 <= (b.end.x - b.st.x) / v1.x <= 1: + res.append(b.end) + else: + if 0 <= (self.st.y - b.st.y) / v2.y <= 1: + res.append(self.st) + if 0 <= (self.end.y - b.st.y) / v2.y <= 1: + res.append(self.end) + if 0 <= (b.st.y - self.st.y) / v1.y <= 1: + res.append(b.st) + if 0 <= (b.end.y - b.st.y) / v1.y <= 1: + res.append(b.end) + return res + else: + t1 = (-v1.x * (b.end.y - self.end.y) + v1.y * (b.end.x - self.end.x)) / x + t2 = (-v1.y * (self.st.x - b.st.x) + v1.x * (self.st.y - b.st.y)) / x + + gcodetools.error(str((x, t1, t2))) + if 0 <= t1 <= 1 and 0 <= t2 <= 1: + return [self.st + v1 * t1] + else: + return [] + else: + return [] + + +################################################################################ +# +# Offset function +# +# This function offsets given cubic super path. +# It's based on src/livarot/PathOutline.cpp from Inkscape's source code. +# +# +################################################################################ +def csp_offset(csp, r): + offset_tolerance = 0.05 + offset_subdivision_depth = 10 + time_ = time.time() + time_start = time_ + print_("Offset start at {}".format(time_)) + print_("Offset radius {}".format(r)) + + def csp_offset_segment(sp1, sp2, r): + result = [] + t = csp_get_t_at_curvature(sp1, sp2, 1 / r) + if len(t) == 0: + t = [0., 1.] + t.sort() + if t[0] > .00000001: + t = [0.] + t + if t[-1] < .99999999: + t.append(1.) + for st, end in zip(t, t[1:]): + c = csp_curvature_at_t(sp1, sp2, (st + end) / 2) + sp = csp_split_by_two_points(sp1, sp2, st, end) + if sp[1] != sp[2]: + if c > 1 / r and r < 0 or c < 1 / r and r > 0: + offset = offset_segment_recursion(sp[1], sp[2], r, offset_subdivision_depth, offset_tolerance) + else: # This part will be clipped for sure... TODO Optimize it... + offset = offset_segment_recursion(sp[1], sp[2], r, offset_subdivision_depth, offset_tolerance) + + if not result: + result = offset[:] + else: + if csp_subpaths_end_to_start_distance2(result, offset) < 0.0001: + result = csp_concat_subpaths(result, offset) + else: + + intersection = csp_get_subapths_last_first_intersection(result, offset) + if intersection: + i, t1, j, t2 = intersection + sp1_, sp2_, sp3_ = csp_split(result[i - 1], result[i], t1) + result = result[:i - 1] + [sp1_, sp2_] + sp1_, sp2_, sp3_ = csp_split(offset[j - 1], offset[j], t2) + result = csp_concat_subpaths(result, [sp2_, sp3_] + offset[j + 1:]) + else: + pass # ??? + return result + + def create_offset_segment(sp1, sp2, r): + # See Gernot Hoffmann "Bezier Curves" p.34 -> 7.1 Bezier Offset Curves + p0 = P(sp1[1]) + p1 = P(sp1[2]) + p2 = P(sp2[0]) + p3 = P(sp2[1]) + + s0 = p1 - p0 + s1 = p2 - p1 + s3 = p3 - p2 + + n0 = s0.ccw().unit() if s0.l2() != 0 else P(csp_normalized_normal(sp1, sp2, 0)) + n3 = s3.ccw().unit() if s3.l2() != 0 else P(csp_normalized_normal(sp1, sp2, 1)) + n1 = s1.ccw().unit() if s1.l2() != 0 else (n0.unit() + n3.unit()).unit() + + q0 = p0 + r * n0 + q3 = p3 + r * n3 + c = csp_curvature_at_t(sp1, sp2, 0) + q1 = q0 + (p1 - p0) * (1 - (r * c if abs(c) < 100 else 0)) + c = csp_curvature_at_t(sp1, sp2, 1) + q2 = q3 + (p2 - p3) * (1 - (r * c if abs(c) < 100 else 0)) + + return [[q0.to_list(), q0.to_list(), q1.to_list()], [q2.to_list(), q3.to_list(), q3.to_list()]] + + def csp_get_subapths_last_first_intersection(s1, s2): + _break = False + for i in range(1, len(s1)): + sp11 = s1[-i - 1] + sp12 = s1[-i] + for j in range(1, len(s2)): + sp21 = s2[j - 1] + sp22 = s2[j] + intersection = csp_segments_true_intersection(sp11, sp12, sp21, sp22) + if intersection: + _break = True + break + if _break: + break + if _break: + intersection = max(intersection) + return [len(s1) - i, intersection[0], j, intersection[1]] + else: + return [] + + def csp_join_offsets(prev, next, sp1, sp2, sp1_l, sp2_l, r): + if len(next) > 1: + if (P(prev[-1][1]) - P(next[0][1])).l2() < 0.001: + return prev, [], next + intersection = csp_get_subapths_last_first_intersection(prev, next) + if intersection: + i, t1, j, t2 = intersection + sp1_, sp2_, sp3_ = csp_split(prev[i - 1], prev[i], t1) + sp3_, sp4_, sp5_ = csp_split(next[j - 1], next[j], t2) + return prev[:i - 1] + [sp1_, sp2_], [], [sp4_, sp5_] + next[j + 1:] + + # Offsets do not intersect... will add an arc... + start = (P(csp_at_t(sp1_l, sp2_l, 1.)) + r * P(csp_normalized_normal(sp1_l, sp2_l, 1.))).to_list() + end = (P(csp_at_t(sp1, sp2, 0.)) + r * P(csp_normalized_normal(sp1, sp2, 0.))).to_list() + arc = csp_from_arc(start, end, sp1[1], r, csp_normalized_slope(sp1_l, sp2_l, 1.)) + if not arc: + return prev, [], next + else: + # Clip prev by arc + if csp_subpaths_end_to_start_distance2(prev, arc) > 0.00001: + intersection = csp_get_subapths_last_first_intersection(prev, arc) + if intersection: + i, t1, j, t2 = intersection + sp1_, sp2_, sp3_ = csp_split(prev[i - 1], prev[i], t1) + sp3_, sp4_, sp5_ = csp_split(arc[j - 1], arc[j], t2) + prev = prev[:i - 1] + [sp1_, sp2_] + arc = [sp4_, sp5_] + arc[j + 1:] + # Clip next by arc + if not next: + return prev, [], arc + if csp_subpaths_end_to_start_distance2(arc, next) > 0.00001: + intersection = csp_get_subapths_last_first_intersection(arc, next) + if intersection: + i, t1, j, t2 = intersection + sp1_, sp2_, sp3_ = csp_split(arc[i - 1], arc[i], t1) + sp3_, sp4_, sp5_ = csp_split(next[j - 1], next[j], t2) + arc = arc[:i - 1] + [sp1_, sp2_] + next = [sp4_, sp5_] + next[j + 1:] + + return prev, arc, next + + def offset_segment_recursion(sp1, sp2, r, depth, tolerance): + sp1_r, sp2_r = create_offset_segment(sp1, sp2, r) + err = max( + csp_seg_to_point_distance(sp1_r, sp2_r, (P(csp_at_t(sp1, sp2, .25)) + P(csp_normalized_normal(sp1, sp2, .25)) * r).to_list())[0], + csp_seg_to_point_distance(sp1_r, sp2_r, (P(csp_at_t(sp1, sp2, .50)) + P(csp_normalized_normal(sp1, sp2, .50)) * r).to_list())[0], + csp_seg_to_point_distance(sp1_r, sp2_r, (P(csp_at_t(sp1, sp2, .75)) + P(csp_normalized_normal(sp1, sp2, .75)) * r).to_list())[0], + ) + + if err > tolerance ** 2 and depth > 0: + if depth > offset_subdivision_depth - 2: + t = csp_max_curvature(sp1, sp2) + t = max(.1, min(.9, t)) + else: + t = .5 + sp3, sp4, sp5 = csp_split(sp1, sp2, t) + r1 = offset_segment_recursion(sp3, sp4, r, depth - 1, tolerance) + r2 = offset_segment_recursion(sp4, sp5, r, depth - 1, tolerance) + return r1[:-1] + [[r1[-1][0], r1[-1][1], r2[0][2]]] + r2[1:] + else: + return [sp1_r, sp2_r] + + ############################################################################ + # Some small definitions + ############################################################################ + csp_len = len(csp) + + ############################################################################ + # Prepare the path + ############################################################################ + # Remove all small segments (segment length < 0.001) + + for i in xrange(len(csp)): + for j in xrange(len(csp[i])): + sp = csp[i][j] + if (P(sp[1]) - P(sp[0])).mag() < 0.001: + csp[i][j][0] = sp[1] + if (P(sp[2]) - P(sp[0])).mag() < 0.001: + csp[i][j][2] = sp[1] + for i in xrange(len(csp)): + for j in xrange(1, len(csp[i])): + if cspseglength(csp[i][j - 1], csp[i][j]) < 0.001: + csp[i] = csp[i][:j] + csp[i][j + 1:] + if cspseglength(csp[i][-1], csp[i][0]) > 0.001: + csp[i][-1][2] = csp[i][-1][1] + csp[i] += [[csp[i][0][1], csp[i][0][1], csp[i][0][1]]] + + # TODO Get rid of self intersections. + + original_csp = csp[:] + # Clip segments which has curvature>1/r. Because their offset will be self-intersecting and very nasty. + + print_("Offset prepared the path in {}".format(time.time() - time_)) + print_("Path length = {}".format(sum([len(i) for i in csp]))) + time_ = time.time() + + ############################################################################ + # Offset + ############################################################################ + # Create offsets for all segments in the path. And join them together inside each subpath. + unclipped_offset = [[] for i in xrange(csp_len)] + + intersection = [[] for i in xrange(csp_len)] + for i in xrange(csp_len): + subpath = csp[i] + subpath_offset = [] + for sp1, sp2 in zip(subpath, subpath[1:]): + segment_offset = csp_offset_segment(sp1, sp2, r) + if not subpath_offset: + subpath_offset = segment_offset + + prev_l = len(subpath_offset) + else: + prev, arc, next = csp_join_offsets(subpath_offset[-prev_l:], segment_offset, sp1, sp2, sp1_l, sp2_l, r) + + subpath_offset = csp_concat_subpaths(subpath_offset[:-prev_l + 1], prev, arc, next) + prev_l = len(next) + sp1_l = sp1[:] + sp2_l = sp2[:] + + # Join last and first offsets togother to close the curve + + prev, arc, next = csp_join_offsets(subpath_offset[-prev_l:], subpath_offset[:2], subpath[0], subpath[1], sp1_l, sp2_l, r) + subpath_offset[:2] = next[:] + subpath_offset = csp_concat_subpaths(subpath_offset[:-prev_l + 1], prev, arc) + + # Collect subpath's offset and save it to unclipped offset list. + unclipped_offset[i] = subpath_offset[:] + + print_("Offsetted path in {}".format(time.time() - time_)) + time_ = time.time() + + ############################################################################ + # Now to the clipping. + ############################################################################ + # First of all find all intersection's between all segments of all offset subpaths, including self intersections. + + # TODO define offset tolerance here + global small_tolerance + small_tolerance = 0.01 + summ = 0 + summ1 = 0 + for subpath_i in xrange(csp_len): + for subpath_j in xrange(subpath_i, csp_len): + subpath = unclipped_offset[subpath_i] + subpath1 = unclipped_offset[subpath_j] + for i in xrange(1, len(subpath)): + # If subpath_i==subpath_j we are looking for self intersections, so + # we'll need search intersections only for xrange(i,len(subpath1)) + for j in (xrange(i, len(subpath1)) if subpath_i == subpath_j else xrange(len(subpath1))): + if subpath_i == subpath_j and j == i: + # Find self intersections of a segment + sp1, sp2, sp3 = csp_split(subpath[i - 1], subpath[i], .5) + intersections = csp_segments_intersection(sp1, sp2, sp2, sp3) + summ += 1 + for t in intersections: + summ1 += 1 + if not (small(t[0] - 1) and small(t[1])) and 0 <= t[0] <= 1 and 0 <= t[1] <= 1: + intersection[subpath_i] += [[i, t[0] / 2], [j, t[1] / 2 + .5]] + else: + intersections = csp_segments_intersection(subpath[i - 1], subpath[i], subpath1[j - 1], subpath1[j]) + summ += 1 + for t in intersections: + summ1 += 1 + # TODO tolerance dependence to cpsp_length(t) + if len(t) == 2 and 0 <= t[0] <= 1 and 0 <= t[1] <= 1 and not ( + subpath_i == subpath_j and ( + (j - i - 1) % (len(subpath) - 1) == 0 and small(t[0] - 1) and small(t[1]) or + (i - j - 1) % (len(subpath) - 1) == 0 and small(t[1] - 1) and small(t[0]))): + intersection[subpath_i] += [[i, t[0]]] + intersection[subpath_j] += [[j, t[1]]] + + elif len(t) == 5 and t[4] == "Overlap": + intersection[subpath_i] += [[i, t[0]], [i, t[1]]] + intersection[subpath_j] += [[j, t[1]], [j, t[3]]] + + print_("Intersections found in {}".format(time.time() - time_)) + print_("Examined {} segments".format(summ)) + print_("found {} intersections".format(summ1)) + time_ = time.time() + + ######################################################################## + # Split unclipped offset by intersection points into splitted_offset + ######################################################################## + splitted_offset = [] + for i in xrange(csp_len): + subpath = unclipped_offset[i] + if len(intersection[i]) > 0: + parts = csp_subpath_split_by_points(subpath, intersection[i]) + # Close parts list to close path (The first and the last parts are joined together) + if [1, 0.] not in intersection[i]: + parts[0][0][0] = parts[-1][-1][0] + parts[0] = csp_concat_subpaths(parts[-1], parts[0]) + splitted_offset += parts[:-1] + else: + splitted_offset += parts[:] + else: + splitted_offset += [subpath[:]] + + print_("Split in {}".format(time.time() - time_)) + time_ = time.time() + + ######################################################################## + # Clipping + ######################################################################## + result = [] + for subpath_i in range(len(splitted_offset)): + clip = False + s1 = splitted_offset[subpath_i] + for subpath_j in range(len(splitted_offset)): + s2 = splitted_offset[subpath_j] + if (P(s1[0][1]) - P(s2[-1][1])).l2() < 0.0001 and ((subpath_i + 1) % len(splitted_offset) != subpath_j): + if dot(csp_normalized_normal(s2[-2], s2[-1], 1.), csp_normalized_slope(s1[0], s1[1], 0.)) * r < -0.0001: + clip = True + break + if (P(s2[0][1]) - P(s1[-1][1])).l2() < 0.0001 and ((subpath_j + 1) % len(splitted_offset) != subpath_i): + if dot(csp_normalized_normal(s2[0], s2[1], 0.), csp_normalized_slope(s1[-2], s1[-1], 1.)) * r > 0.0001: + clip = True + break + + if not clip: + result += [s1[:]] + elif options.offset_draw_clippend_path: + draw_csp([s1], width=.1) + draw_pointer(csp_at_t(s2[-2], s2[-1], 1.) + + (P(csp_at_t(s2[-2], s2[-1], 1.)) + P(csp_normalized_normal(s2[-2], s2[-1], 1.)) * 10).to_list(), "Green", "line") + draw_pointer(csp_at_t(s1[0], s1[1], 0.) + + (P(csp_at_t(s1[0], s1[1], 0.)) + P(csp_normalized_slope(s1[0], s1[1], 0.)) * 10).to_list(), "Red", "line") + + # Now join all together and check closure and orientation of result + joined_result = csp_join_subpaths(result) + # Check if each subpath from joined_result is closed + + for s in joined_result[:]: + if csp_subpaths_end_to_start_distance2(s, s) > 0.001: + # Remove open parts + if options.offset_draw_clippend_path: + draw_csp([s], width=1) + draw_pointer(s[0][1], comment=csp_subpaths_end_to_start_distance2(s, s)) + draw_pointer(s[-1][1], comment=csp_subpaths_end_to_start_distance2(s, s)) + joined_result.remove(s) + else: + # Remove small parts + minx, miny, maxx, maxy = csp_true_bounds([s]) + if (minx[0] - maxx[0]) ** 2 + (miny[1] - maxy[1]) ** 2 < 0.1: + joined_result.remove(s) + print_("Clipped and joined path in {}".format(time.time() - time_)) + + ######################################################################## + # Now to the Dummy clipping: remove parts from split offset if their + # centers are closer to the original path than offset radius. + ######################################################################## + + if abs(r * .01) < 1: + r1 = (0.99 * r) ** 2 + r2 = (1.01 * r) ** 2 + else: + r1 = (abs(r) - 1) ** 2 + r2 = (abs(r) + 1) ** 2 + + for s in joined_result[:]: + dist = csp_to_point_distance(original_csp, s[int(len(s) / 2)][1], dist_bounds=[r1, r2]) + if not r1 < dist[0] < r2: + joined_result.remove(s) + if options.offset_draw_clippend_path: + draw_csp([s], comment=math.sqrt(dist[0])) + draw_pointer(csp_at_t(csp[dist[1]][dist[2] - 1], csp[dist[1]][dist[2]], dist[3]) + s[int(len(s) / 2)][1], "blue", "line", comment=[math.sqrt(dist[0]), i, j, sp]) + + print_("-----------------------------") + print_("Total offset time {}".format(time.time() - time_start)) + print_() + return joined_result + + +################################################################################ +# +# Biarc function +# +# Calculates biarc approximation of cubic super path segment +# splits segment if needed or approximates it with straight line +# +################################################################################ +def biarc(sp1, sp2, z1, z2, depth=0): + def biarc_split(sp1, sp2, z1, z2, depth): + if depth < options.biarc_max_split_depth: + sp1, sp2, sp3 = csp_split(sp1, sp2) + l1 = cspseglength(sp1, sp2) + l2 = cspseglength(sp2, sp3) + if l1 + l2 == 0: + zm = z1 + else: + zm = z1 + (z2 - z1) * l1 / (l1 + l2) + return biarc(sp1, sp2, z1, zm, depth + 1) + biarc(sp2, sp3, zm, z2, depth + 1) + else: + return [[sp1[1], 'line', 0, 0, sp2[1], [z1, z2]]] + + P0 = P(sp1[1]) + P4 = P(sp2[1]) + TS = (P(sp1[2]) - P0) + TE = -(P(sp2[0]) - P4) + v = P0 - P4 + tsa = TS.angle() + tea = TE.angle() + va = v.angle() + if TE.mag() < STRAIGHT_DISTANCE_TOLERANCE and TS.mag() < STRAIGHT_DISTANCE_TOLERANCE: + # Both tangents are zero - line straight + return [[sp1[1], 'line', 0, 0, sp2[1], [z1, z2]]] + if TE.mag() < STRAIGHT_DISTANCE_TOLERANCE: + TE = -(TS + v).unit() + r = TS.mag() / v.mag() * 2 + elif TS.mag() < STRAIGHT_DISTANCE_TOLERANCE: + TS = -(TE + v).unit() + r = 1 / (TE.mag() / v.mag() * 2) + else: + r = TS.mag() / TE.mag() + TS = TS.unit() + TE = TE.unit() + tang_are_parallel = ((tsa - tea) % math.pi < STRAIGHT_TOLERANCE or math.pi - (tsa - tea) % math.pi < STRAIGHT_TOLERANCE) + if (tang_are_parallel and + ((v.mag() < STRAIGHT_DISTANCE_TOLERANCE or TE.mag() < STRAIGHT_DISTANCE_TOLERANCE or TS.mag() < STRAIGHT_DISTANCE_TOLERANCE) or + 1 - abs(TS * v / (TS.mag() * v.mag())) < STRAIGHT_TOLERANCE)): + # Both tangents are parallel and start and end are the same - line straight + # or one of tangents still smaller then tolerance + + # Both tangents and v are parallel - line straight + return [[sp1[1], 'line', 0, 0, sp2[1], [z1, z2]]] + + c = v * v + b = 2 * v * (r * TS + TE) + a = 2 * r * (TS * TE - 1) + if v.mag() == 0: + return biarc_split(sp1, sp2, z1, z2, depth) + asmall = abs(a) < 10 ** -10 + bsmall = abs(b) < 10 ** -10 + csmall = abs(c) < 10 ** -10 + if asmall and b != 0: + beta = -c / b + elif csmall and a != 0: + beta = -b / a + elif not asmall: + discr = b * b - 4 * a * c + if discr < 0: + raise ValueError(a, b, c, discr) + disq = discr ** .5 + beta1 = (-b - disq) / 2 / a + beta2 = (-b + disq) / 2 / a + if beta1 * beta2 > 0: + raise ValueError(a, b, c, disq, beta1, beta2) + beta = max(beta1, beta2) + elif asmall and bsmall: + return biarc_split(sp1, sp2, z1, z2, depth) + alpha = beta * r + ab = alpha + beta + P1 = P0 + alpha * TS + P3 = P4 - beta * TE + P2 = (beta / ab) * P1 + (alpha / ab) * P3 + + def calculate_arc_params(P0, P1, P2): + D = (P0 + P2) / 2 + if (D - P1).mag() == 0: + return None, None + R = D - ((D - P0).mag() ** 2 / (D - P1).mag()) * (P1 - D).unit() + p0a = (P0 - R).angle() % (2 * math.pi) + p1a = (P1 - R).angle() % (2 * math.pi) + p2a = (P2 - R).angle() % (2 * math.pi) + alpha = (p2a - p0a) % (2 * math.pi) + if (p0a < p2a and (p1a < p0a or p2a < p1a)) or (p2a < p1a < p0a): + alpha = -2 * math.pi + alpha + if abs(R.x) > 1000000 or abs(R.y) > 1000000 or (R - P0).mag() < options.min_arc_radius ** 2: + return None, None + else: + return R, alpha + + R1, a1 = calculate_arc_params(P0, P1, P2) + R2, a2 = calculate_arc_params(P2, P3, P4) + if R1 is None or R2 is None or (R1 - P0).mag() < STRAIGHT_TOLERANCE or (R2 - P2).mag() < STRAIGHT_TOLERANCE: + return [[sp1[1], 'line', 0, 0, sp2[1], [z1, z2]]] + + d = csp_to_arc_distance(sp1, sp2, [P0, P2, R1, a1], [P2, P4, R2, a2]) + if d > options.biarc_tolerance and depth < options.biarc_max_split_depth: + return biarc_split(sp1, sp2, z1, z2, depth) + else: + if R2.mag() * a2 == 0: + zm = z2 + else: + zm = z1 + (z2 - z1) * (abs(R1.mag() * a1)) / (abs(R2.mag() * a2) + abs(R1.mag() * a1)) + + l = (P0 - P2).l2() + if l < EMC_TOLERANCE_EQUAL ** 2 or l < EMC_TOLERANCE_EQUAL ** 2 * R1.l2() / 100: + # arc should be straight otherwise it could be treated as full circle + arc1 = [sp1[1], 'line', 0, 0, [P2.x, P2.y], [z1, zm]] + else: + arc1 = [sp1[1], 'arc', [R1.x, R1.y], a1, [P2.x, P2.y], [z1, zm]] + + l = (P4 - P2).l2() + if l < EMC_TOLERANCE_EQUAL ** 2 or l < EMC_TOLERANCE_EQUAL ** 2 * R2.l2() / 100: + # arc should be straight otherwise it could be treated as full circle + arc2 = [[P2.x, P2.y], 'line', 0, 0, [P4.x, P4.y], [zm, z2]] + else: + arc2 = [[P2.x, P2.y], 'arc', [R2.x, R2.y], a2, [P4.x, P4.y], [zm, z2]] + + return [arc1, arc2] + + +class Postprocessor(object): + def __init__(self, error_function_handler): + self.error = error_function_handler + self.functions = { + "remap": self.remap, + "remapi": self.remapi, + "scale": self.scale, + "move": self.move, + "flip": self.flip_axis, + "flip_axis": self.flip_axis, + "round": self.round_coordinates, + "parameterize": self.parameterize, + "regex": self.re_sub_on_gcode_lines + } + + def process(self, command): + command = re.sub(r"\\\\", ":#:#:slash:#:#:", command) + command = re.sub(r"\\;", ":#:#:semicolon:#:#:", command) + command = command.split(";") + for s in command: + s = re.sub(":#:#:slash:#:#:", "\\\\", s) + s = re.sub(":#:#:semicolon:#:#:", "\\;", s) + s = s.strip() + if s != "": + self.parse_command(s) + + def parse_command(self, command): + r = re.match(r"([A-Za-z0-9_]+)\s*\(\s*(.*)\)", command) + if not r: + self.error("Parse error while postprocessing.\n(Command: '{}')".format(command), "error") + function = r.group(1).lower() + parameters = r.group(2) + if function in self.functions: + print_("Postprocessor: executing function {}({})".format(function, parameters)) + self.functions[function](parameters) + else: + self.error("Unrecognized function '{}' while postprocessing.\n(Command: '{}')".format(function, command), "error") + + def re_sub_on_gcode_lines(self, parameters): + gcode = self.gcode.split("\n") + self.gcode = "" + try: + for line in gcode: + self.gcode += eval("re.sub({},line)".format(parameters)) + "\n" + + except Exception as ex: + self.error("Bad parameters for regexp. " + "They should be as re.sub pattern and replacement parameters! " + "For example: r\"G0(\\d)\", r\"G\\1\" \n" + "(Parameters: '{}')\n {}".format(parameters, ex), "error") + + def remapi(self, parameters): + self.remap(parameters, case_sensitive=True) + + def remap(self, parameters, case_sensitive=False): + # remap parameters should be like "x->y,y->x" + parameters = parameters.replace("\\,", ":#:#:coma:#:#:") + parameters = parameters.split(",") + pattern = [] + remap = [] + for s in parameters: + s = s.replace(":#:#:coma:#:#:", "\\,") + r = re.match("""\\s*(\'|\")(.*)\\1\\s*->\\s*(\'|\")(.*)\\3\\s*""", s) + if not r: + self.error("Bad parameters for remap.\n(Parameters: '{}')".format(parameters), "error") + pattern += [r.group(2)] + remap += [r.group(4)] + + for i in range(len(pattern)): + if case_sensitive: + self.gcode = ireplace(self.gcode, pattern[i], ":#:#:remap_pattern{}:#:#:".format(i)) + else: + self.gcode = self.gcode.replace(pattern[i], ":#:#:remap_pattern{}:#:#:".format(i)) + + for i in range(len(remap)): + self.gcode = self.gcode.replace(":#:#:remap_pattern{}:#:#:".format(i), remap[i]) + + def transform(self, move, scale): + axis = ["xi", "yj", "zk", "a"] + flip = scale[0] * scale[1] * scale[2] < 0 + gcode = "" + warned = [] + r_scale = scale[0] + plane = "g17" + for s in self.gcode.split("\n"): + # get plane selection: + s_wo_comments = re.sub(r"\([^\)]*\)", "", s) + r = re.search(r"(?i)(G17|G18|G19)", s_wo_comments) + if r: + plane = r.group(1).lower() + if plane == "g17": + r_scale = scale[0] # plane XY -> scale x + if plane == "g18": + r_scale = scale[0] # plane XZ -> scale x + if plane == "g19": + r_scale = scale[1] # plane YZ -> scale y + # Raise warning if scale factors are not the game for G02 and G03 + if plane not in warned: + r = re.search(r"(?i)(G02|G03)", s_wo_comments) + if r: + if plane == "g17" and scale[0] != scale[1]: + self.error("Post-processor: Scale factors for X and Y axis are not the same. G02 and G03 codes will be corrupted.") + if plane == "g18" and scale[0] != scale[2]: + self.error("Post-processor: Scale factors for X and Z axis are not the same. G02 and G03 codes will be corrupted.") + if plane == "g19" and scale[1] != scale[2]: + self.error("Post-processor: Scale factors for Y and Z axis are not the same. G02 and G03 codes will be corrupted.") + warned += [plane] + # Transform + for i in range(len(axis)): + if move[i] != 0 or scale[i] != 1: + for a in axis[i]: + r = re.search(r"(?i)(" + a + r")\s*(-?)\s*(\d*\.?\d*)", s) + if r and r.group(3) != "": + s = re.sub(r"(?i)(" + a + r")\s*(-?)\s*(\d*\.?\d*)", r"\1 {:f}".format(float(r.group(2) + r.group(3)) * scale[i] + (move[i] if a not in ["i", "j", "k"] else 0)), s) + # scale radius R + if r_scale != 1: + r = re.search(r"(?i)(r)\s*(-?\s*(\d*\.?\d*))", s) + if r and r.group(3) != "": + try: + s = re.sub(r"(?i)(r)\s*(-?)\s*(\d*\.?\d*)", r"\1 {:f}".format(float(r.group(2) + r.group(3)) * r_scale), s) + except: + pass + + gcode += s + "\n" + + self.gcode = gcode + if flip: + self.remapi("'G02'->'G03', 'G03'->'G02'") + + def parameterize(self, parameters): + planes = [] + feeds = {} + coords = [] + gcode = "" + coords_def = {"x": "x", "y": "y", "z": "z", "i": "x", "j": "y", "k": "z", "a": "a"} + for s in self.gcode.split("\n"): + s_wo_comments = re.sub(r"\([^\)]*\)", "", s) + # get Planes + r = re.search(r"(?i)(G17|G18|G19)", s_wo_comments) + if r: + plane = r.group(1).lower() + if plane not in planes: + planes += [plane] + # get Feeds + r = re.search(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", s_wo_comments) + if r: + feed = float(r.group(2) + r.group(3)) + if feed not in feeds: + feeds[feed] = "#" + str(len(feeds) + 20) + + # Coordinates + for c in "xyzijka": + r = re.search(r"(?i)(" + c + r")\s*(-?)\s*(\d*\.?\d*)", s_wo_comments) + if r: + c = coords_def[r.group(1).lower()] + if c not in coords: + coords += [c] + # Add offset parametrization + offset = {"x": "#6", "y": "#7", "z": "#8", "a": "#9"} + for c in coords: + gcode += "{} = 0 ({} axis offset)\n".format(offset[c], c.upper()) + + # Add scale parametrization + if not planes: + planes = ["g17"] + if len(planes) > 1: # have G02 and G03 in several planes scale_x = scale_y = scale_z required + gcode += "#10 = 1 (Scale factor)\n" + scale = {"x": "#10", "i": "#10", "y": "#10", "j": "#10", "z": "#10", "k": "#10", "r": "#10"} + else: + gcode += "#10 = 1 ({} Scale factor)\n".format({"g17": "XY", "g18": "XZ", "g19": "YZ"}[planes[0]]) + gcode += "#11 = 1 ({} Scale factor)\n".format({"g17": "Z", "g18": "Y", "g19": "X"}[planes[0]]) + scale = {"x": "#10", "i": "#10", "y": "#10", "j": "#10", "z": "#10", "k": "#10", "r": "#10"} + if "g17" in planes: + scale["z"] = "#11" + scale["k"] = "#11" + if "g18" in planes: + scale["y"] = "#11" + scale["j"] = "#11" + if "g19" in planes: + scale["x"] = "#11" + scale["i"] = "#11" + # Add a scale + if "a" in coords: + gcode += "#12 = 1 (A axis scale)\n" + scale["a"] = "#12" + + # Add feed parametrization + for f in feeds: + gcode += "{} = {:f} (Feed definition)\n".format(feeds[f], f) + + # Parameterize Gcode + for s in self.gcode.split("\n"): + # feed replace : + r = re.search(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", s) + if r and len(r.group(3)) > 0: + s = re.sub(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", "F [{}]".format(feeds[float(r.group(2) + r.group(3))]), s) + # Coords XYZA replace + for c in "xyza": + r = re.search(r"(?i)((" + c + r")\s*(-?)\s*(\d*\.?\d*))", s) + if r and len(r.group(4)) > 0: + s = re.sub(r"(?i)(" + c + r")\s*((-?)\s*(\d*\.?\d*))", r"\1[\2*{}+{}]".format(scale[c], offset[c]), s) + + # Coords IJKR replace + for c in "ijkr": + r = re.search(r"(?i)((" + c + r")\s*(-?)\s*(\d*\.?\d*))", s) + if r and len(r.group(4)) > 0: + s = re.sub(r"(?i)(" + c + r")\s*((-?)\s*(\d*\.?\d*))", r"\1[\2*{}]".format(scale[c]), s) + + gcode += s + "\n" + + self.gcode = gcode + + def round_coordinates(self, parameters): + try: + round_ = int(parameters) + except: + self.error("Bad parameters for round. Round should be an integer! \n(Parameters: '{}')".format(parameters), "error") + gcode = "" + for s in self.gcode.split("\n"): + for a in "xyzijkaf": + r = re.search(r"(?i)(" + a + r")\s*(-?\s*(\d*\.?\d*))", s) + if r: + + if r.group(2) != "": + s = re.sub( + r"(?i)(" + a + r")\s*(-?)\s*(\d*\.?\d*)", + (r"\1 %0." + str(round_) + "f" if round_ > 0 else r"\1 %d") % round(float(r.group(2)), round_), + s) + gcode += s + "\n" + self.gcode = gcode + + def scale(self, parameters): + parameters = parameters.split(",") + scale = [1., 1., 1., 1.] + try: + for i in range(len(parameters)): + if float(parameters[i]) == 0: + self.error("Bad parameters for scale. Scale should not be 0 at any axis! \n(Parameters: '{}')".format(parameters), "error") + scale[i] = float(parameters[i]) + except: + self.error("Bad parameters for scale.\n(Parameters: '{}')".format(parameters), "error") + self.transform([0, 0, 0, 0], scale) + + def move(self, parameters): + parameters = parameters.split(",") + move = [0., 0., 0., 0.] + try: + for i in range(len(parameters)): + move[i] = float(parameters[i]) + except: + self.error("Bad parameters for move.\n(Parameters: '{}')".format(parameters), "error") + self.transform(move, [1., 1., 1., 1.]) + + def flip_axis(self, parameters): + parameters = parameters.lower() + axis = {"x": 1., "y": 1., "z": 1., "a": 1.} + for p in parameters: + if p in [",", " ", " ", "\r", "'", '"']: + continue + if p not in ["x", "y", "z", "a"]: + self.error("Bad parameters for flip_axis. Parameter should be string consists of 'xyza' \n(Parameters: '{}')".format(parameters), "error") + axis[p] = -axis[p] + self.scale("{:f},{:f},{:f},{:f}".format(axis["x"], axis["y"], axis["z"], axis["a"])) + + +################################################################################ +# Polygon class +################################################################################ +class Polygon(object): + def __init__(self, polygon=None): + self.polygon = [] if polygon is None else polygon[:] + + def move(self, x, y): + for i in range(len(self.polygon)): + for j in range(len(self.polygon[i])): + self.polygon[i][j][0] += x + self.polygon[i][j][1] += y + + def bounds(self): + minx = 1e400 + miny = 1e400 + maxx = -1e400 + maxy = -1e400 + for poly in self.polygon: + for p in poly: + if minx > p[0]: + minx = p[0] + if miny > p[1]: + miny = p[1] + if maxx < p[0]: + maxx = p[0] + if maxy < p[1]: + maxy = p[1] + return minx * 1, miny * 1, maxx * 1, maxy * 1 + + def width(self): + b = self.bounds() + return b[2] - b[0] + + def rotate_(self, sin, cos): + self.polygon = [ + [ + [point[0] * cos - point[1] * sin, point[0] * sin + point[1] * cos] for point in subpoly + ] + for subpoly in self.polygon + ] + + def rotate(self, a): + cos = math.cos(a) + sin = math.sin(a) + self.rotate_(sin, cos) + + def drop_into_direction(self, direction, surface): + # Polygon is a list of simple polygons + # Surface is a polygon + line y = 0 + # Direction is [dx,dy] + if len(self.polygon) == 0 or len(self.polygon[0]) == 0: + return + if direction[0] ** 2 + direction[1] ** 2 < 1e-10: + return + direction = normalize(direction) + sin = direction[0] + cos = -direction[1] + self.rotate_(-sin, cos) + surface.rotate_(-sin, cos) + self.drop_down(surface, zerro_plane=False) + self.rotate_(sin, cos) + surface.rotate_(sin, cos) + + def centroid(self): + centroids = [] + sa = 0 + for poly in self.polygon: + cx = 0 + cy = 0 + a = 0 + for i in range(len(poly)): + [x1, y1] = poly[i - 1] + [x2, y2] = poly[i] + cx += (x1 + x2) * (x1 * y2 - x2 * y1) + cy += (y1 + y2) * (x1 * y2 - x2 * y1) + a += (x1 * y2 - x2 * y1) + a *= 3. + if abs(a) > 0: + cx /= a + cy /= a + sa += abs(a) + centroids += [[cx, cy, a]] + if sa == 0: + return [0., 0.] + cx = 0 + cy = 0 + for c in centroids: + cx += c[0] * c[2] + cy += c[1] * c[2] + cx /= sa + cy /= sa + return [cx, cy] + + def drop_down(self, surface, zerro_plane=True): + # Polygon is a list of simple polygons + # Surface is a polygon + line y = 0 + # Down means min y (0,-1) + if len(self.polygon) == 0 or len(self.polygon[0]) == 0: + return + # Get surface top point + top = surface.bounds()[3] + if zerro_plane: + top = max(0, top) + # Get polygon bottom point + bottom = self.bounds()[1] + self.move(0, top - bottom + 10) + # Now get shortest distance from surface to polygon in positive x=0 direction + # Such distance = min(distance(vertex, edge)...) where edge from surface and + # vertex from polygon and vice versa... + dist = 1e300 + for poly in surface.polygon: + for i in range(len(poly)): + for poly1 in self.polygon: + for i1 in range(len(poly1)): + st = poly[i - 1] + end = poly[i] + vertex = poly1[i1] + if st[0] <= vertex[0] <= end[0] or end[0] <= vertex[0] <= st[0]: + if st[0] == end[0]: + d = min(vertex[1] - st[1], vertex[1] - end[1]) + else: + d = vertex[1] - st[1] - (end[1] - st[1]) * (vertex[0] - st[0]) / (end[0] - st[0]) + if dist > d: + dist = d + # and vice versa just change the sign because vertex now under the edge + st = poly1[i1 - 1] + end = poly1[i1] + vertex = poly[i] + if st[0] <= vertex[0] <= end[0] or end[0] <= vertex[0] <= st[0]: + if st[0] == end[0]: + d = min(- vertex[1] + st[1], -vertex[1] + end[1]) + else: + d = - vertex[1] + st[1] + (end[1] - st[1]) * (vertex[0] - st[0]) / (end[0] - st[0]) + if dist > d: + dist = d + + if zerro_plane and dist > 10 + top: + dist = 10 + top + self.move(0, -dist) + + def draw(self, color="#075", width=.1, group=None): + csp = [csp_subpath_line_to([], poly + [poly[0]]) for poly in self.polygon] + draw_csp(csp, width=width, group=group) + + def add(self, add): + if type(add) == type([]): + self.polygon += add[:] + else: + self.polygon += add.polygon[:] + + def point_inside(self, p): + inside = False + for poly in self.polygon: + for i in range(len(poly)): + st = poly[i - 1] + end = poly[i] + if p == st or p == end: + return True # point is a vertex = point is on the edge + if st[0] > end[0]: + st, end = end, st # This will be needed to check that edge if open only at right end + c = (p[1] - st[1]) * (end[0] - st[0]) - (end[1] - st[1]) * (p[0] - st[0]) + if st[0] <= p[0] < end[0]: + if c < 0: + inside = not inside + elif c == 0: + return True # point is on the edge + elif st[0] == end[0] == p[0] and (st[1] <= p[1] <= end[1] or end[1] <= p[1] <= st[1]): # point is on the edge + return True + return inside + + def hull(self): + # Add vertices at all self intersection points. + hull = [] + for i1 in range(len(self.polygon)): + poly1 = self.polygon[i1] + poly_ = [] + for j1 in range(len(poly1)): + s = poly1[j1 - 1] + e = poly1[j1] + poly_ += [s] + + # Check self intersections + for j2 in range(j1 + 1, len(poly1)): + s1 = poly1[j2 - 1] + e1 = poly1[j2] + int_ = line_line_intersection_points(s, e, s1, e1) + for p in int_: + if point_to_point_d2(p, s) > 0.000001 and point_to_point_d2(p, e) > 0.000001: + poly_ += [p] + # Check self intersections with other polys + for i2 in range(len(self.polygon)): + if i1 == i2: + continue + poly2 = self.polygon[i2] + for j2 in range(len(poly2)): + s1 = poly2[j2 - 1] + e1 = poly2[j2] + int_ = line_line_intersection_points(s, e, s1, e1) + for p in int_: + if point_to_point_d2(p, s) > 0.000001 and point_to_point_d2(p, e) > 0.000001: + poly_ += [p] + hull += [poly_] + # Create the dictionary containing all edges in both directions + edges = {} + for poly in self.polygon: + for i in range(len(poly)): + s = tuple(poly[i - 1]) + e = tuple(poly[i]) + if point_to_point_d2(e, s) < 0.000001: + continue + break_s = False + break_e = False + for p in edges: + if point_to_point_d2(p, s) < 0.000001: + break_s = True + s = p + if point_to_point_d2(p, e) < 0.000001: + break_e = True + e = p + if break_s and break_e: + break + l = point_to_point_d(s, e) + if not break_s and not break_e: + edges[s] = [[s, e, l]] + edges[e] = [[e, s, l]] + else: + if e in edges: + for edge in edges[e]: + if point_to_point_d2(edge[1], s) < 0.000001: + break + if point_to_point_d2(edge[1], s) > 0.000001: + edges[e] += [[e, s, l]] + else: + edges[e] = [[e, s, l]] + if s in edges: + for edge in edges[s]: + if point_to_point_d2(edge[1], e) < 0.000001: + break + if point_to_point_d2(edge[1], e) > 0.000001: + edges[s] += [[s, e, l]] + else: + edges[s] = [[s, e, l]] + + def angle_quadrant(sin, cos): + # quadrants are (0,pi/2], (pi/2,pi], (pi,3*pi/2], (3*pi/2, 2*pi], i.e. 0 is in the 4-th quadrant + if sin > 0 and cos >= 0: + return 1 + if sin >= 0 and cos < 0: + return 2 + if sin < 0 and cos <= 0: + return 3 + if sin <= 0 and cos > 0: + return 4 + + def angle_is_less(sin, cos, sin1, cos1): + # 0 = 2*pi is the largest angle + if [sin1, cos1] == [0, 1]: + return True + if [sin, cos] == [0, 1]: + return False + if angle_quadrant(sin, cos) > angle_quadrant(sin1, cos1): + return False + if angle_quadrant(sin, cos) < angle_quadrant(sin1, cos1): + return True + if sin >= 0 and cos > 0: + return sin < sin1 + if sin > 0 and cos <= 0: + return sin > sin1 + if sin <= 0 and cos < 0: + return sin > sin1 + if sin < 0 and cos >= 0: + return sin < sin1 + + def get_closes_edge_by_angle(edges, last): + # Last edge is normalized vector of the last edge. + min_angle = [0, 1] + next = last + last_edge = [(last[0][0] - last[1][0]) / last[2], (last[0][1] - last[1][1]) / last[2]] + for p in edges: + + cur = [(p[1][0] - p[0][0]) / p[2], (p[1][1] - p[0][1]) / p[2]] + cos = dot(cur, last_edge) + sin = cross(cur, last_edge) + + if angle_is_less(sin, cos, min_angle[0], min_angle[1]): + min_angle = [sin, cos] + next = p + + return next + + # Join edges together into new polygon cutting the vertexes inside new polygon + self.polygon = [] + len_edges = sum([len(edges[p]) for p in edges]) + loops = 0 + + while len(edges) > 0: + poly = [] + if loops > len_edges: + raise ValueError("Hull error") + loops += 1 + # Find left most vertex. + start = (1e100, 1) + for edge in edges: + start = min(start, min(edges[edge])) + last = [(start[0][0] - 1, start[0][1]), start[0], 1] + first_run = True + loops1 = 0 + while last[1] != start[0] or first_run: + first_run = False + if loops1 > len_edges: + raise ValueError("Hull error") + loops1 += 1 + next = get_closes_edge_by_angle(edges[last[1]], last) + + last = next + poly += [list(last[0])] + self.polygon += [poly] + # Remove all edges that are intersects new poly (any vertex inside new poly) + poly_ = Polygon([poly]) + for p in edges.keys()[:]: + if poly_.point_inside(list(p)): + del edges[p] + self.draw(color="Green", width=1) + + +################################################################################ +# +# Gcodetools class +# +################################################################################ + +class Gcodetools(inkex.EffectExtension): + multi_inx = True # XXX Remove this after refactoring + + def export_gcode(self, gcode, no_headers=False): + if self.options.postprocessor != "" or self.options.postprocessor_custom != "": + postprocessor = Postprocessor(self.error) + postprocessor.gcode = gcode + if self.options.postprocessor != "": + postprocessor.process(self.options.postprocessor) + if self.options.postprocessor_custom != "": + postprocessor.process(self.options.postprocessor_custom) + + if not no_headers: + postprocessor.gcode = self.header + postprocessor.gcode + self.footer + + with open(os.path.join(self.options.directory, self.options.file), "w") as f: + f.write(postprocessor.gcode) + + ################################################################################ + # In/out paths: + # TODO move it to the bottom + ################################################################################ + def plasma_prepare_path(self): + self.get_info_plus() + + def add_arc(sp1, sp2, end=False, l=10., r=10.): + if not end: + n = csp_normalized_normal(sp1, sp2, 0.) + return csp_reverse([arc_from_s_r_n_l(sp1[1], r, n, -l)])[0] + else: + n = csp_normalized_normal(sp1, sp2, 1.) + return arc_from_s_r_n_l(sp2[1], r, n, l) + + def add_normal(sp1, sp2, end=False, l=10., r=10.): + # r is needed only for be compatible with add_arc + if not end: + n = csp_normalized_normal(sp1, sp2, 0.) + p = [n[0] * l + sp1[1][0], n[1] * l + sp1[1][1]] + return csp_subpath_line_to([], [p, sp1[1]]) + else: + n = csp_normalized_normal(sp1, sp2, 1.) + p = [n[0] * l + sp2[1][0], n[1] * l + sp2[1][1]] + return csp_subpath_line_to([], [sp2[1], p]) + + def add_tangent(sp1, sp2, end=False, l=10., r=10.): + # r is needed only for be compatible with add_arc + if not end: + n = csp_normalized_slope(sp1, sp2, 0.) + p = [-n[0] * l + sp1[1][0], -n[1] * l + sp1[1][1]] + return csp_subpath_line_to([], [p, sp1[1]]) + else: + n = csp_normalized_slope(sp1, sp2, 1.) + p = [n[0] * l + sp2[1][0], n[1] * l + sp2[1][1]] + return csp_subpath_line_to([], [sp2[1], p]) + + if not self.options.in_out_path and not self.options.plasma_prepare_corners and self.options.in_out_path_do_not_add_reference_point: + self.error("Warning! Extension is not said to do anything! Enable one of Create in-out paths or Prepare corners checkboxes or disable Do not add in-out reference point!") + return + + # Add in-out-reference point if there is no one yet. + if ((len(self.in_out_reference_points) == 0 and self.options.in_out_path + or not self.options.in_out_path and not self.options.plasma_prepare_corners) + and not self.options.in_out_path_do_not_add_reference_point): + self.options.orientation_points_count = "in-out reference point" + self.orientation() + + if self.options.in_out_path or self.options.plasma_prepare_corners: + self.set_markers() + add_func = {"Round": add_arc, "Perpendicular": add_normal, "Tangent": add_tangent}[self.options.in_out_path_type] + if self.options.in_out_path_type == "Round" and self.options.in_out_path_len > self.options.in_out_path_radius * 3 / 2 * math.pi: + self.error("In-out len is to big for in-out radius will cropp it to be r*3/2*pi!") + + if self.selected_paths == {} and self.options.auto_select_paths: + self.selected_paths = self.paths + self.error("No paths are selected! Trying to work on all available paths.") + + if self.selected_paths == {}: + self.error("Nothing is selected. Please select something.") + a = self.options.plasma_prepare_corners_tolerance + corner_tolerance = cross([1., 0.], [math.cos(a), math.sin(a)]) + + for layer in self.layers: + if layer in self.selected_paths: + max_dist = self.transform_scalar(self.options.in_out_path_point_max_dist, layer, reverse=True) + l = self.transform_scalar(self.options.in_out_path_len, layer, reverse=True) + plasma_l = self.transform_scalar(self.options.plasma_prepare_corners_distance, layer, reverse=True) + r = self.transform_scalar(self.options.in_out_path_radius, layer, reverse=True) + l = min(l, r * 3 / 2 * math.pi) + + for path in self.selected_paths[layer]: + csp = self.apply_transforms(path, path.path.to_superpath()) + csp = csp_remove_zero_segments(csp) + res = [] + + for subpath in csp: + # Find closes point to in-out reference point + # If subpath is open skip this step + if self.options.in_out_path: + # split and reverse path for further add in-out points + if point_to_point_d2(subpath[0][1], subpath[-1][1]) < 1.e-10: + d = [1e100, 1, 1, 1.] + for p in self.in_out_reference_points: + d1 = csp_to_point_distance([subpath], p, dist_bounds=[0, max_dist]) + if d1[0] < d[0]: + d = d1[:] + p_ = p + if d[0] < max_dist ** 2: + # Lets find is there any angles near this point to put in-out path in + # the angle if it's possible + # remove last node to make iterations easier + subpath[0][0] = subpath[-1][0] + del subpath[-1] + max_cross = [-1e100, None] + for j in range(len(subpath)): + sp1 = subpath[j - 2] + sp2 = subpath[j - 1] + sp3 = subpath[j] + if point_to_point_d2(sp2[1], p_) < max_dist ** 2: + s1 = csp_normalized_slope(sp1, sp2, 1.) + s2 = csp_normalized_slope(sp2, sp3, 0.) + max_cross = max(max_cross, [cross(s1, s2), j - 1]) + # return back last point + subpath.append(subpath[0]) + if max_cross[1] is not None and max_cross[0] > corner_tolerance: + # there's an angle near the point + j = max_cross[1] + if j < 0: + j -= 1 + if j != 0: + subpath = csp_concat_subpaths(subpath[j:], subpath[:j + 1]) + else: + # have to cut path's segment + d, i, j, t = d + sp1, sp2, sp3 = csp_split(subpath[j - 1], subpath[j], t) + subpath = csp_concat_subpaths([sp2, sp3], subpath[j:], subpath[:j], [sp1, sp2]) + + if self.options.plasma_prepare_corners: + # prepare corners + # find corners and add some nodes + # corner at path's start/end is ignored + res_ = [subpath[0]] + for sp2, sp3 in zip(subpath[1:], subpath[2:]): + sp1 = res_[-1] + s1 = csp_normalized_slope(sp1, sp2, 1.) + s2 = csp_normalized_slope(sp2, sp3, 0.) + if cross(s1, s2) > corner_tolerance: + # got a corner to process + S1 = P(s1) + S2 = P(s2) + N = (S1 - S2).unit() * plasma_l + SP2 = P(sp2[1]) + P1 = (SP2 + N) + res_ += [ + [sp2[0], sp2[1], (SP2 + S1 * plasma_l).to_list()], + [(P1 - N.ccw() / 2).to_list(), P1.to_list(), (P1 + N.ccw() / 2).to_list()], + [(SP2 - S2 * plasma_l).to_list(), sp2[1], sp2[2]] + ] + else: + res_ += [sp2] + res_ += [sp3] + subpath = res_ + if self.options.in_out_path: + # finally add let's add in-out paths... + subpath = csp_concat_subpaths( + add_func(subpath[0], subpath[1], False, l, r), + subpath, + add_func(subpath[-2], subpath[-1], True, l, r) + ) + + res += [subpath] + + if self.options.in_out_path_replace_original_path: + path.path = CubicSuperPath(self.apply_transforms(path, res, True)) + else: + draw_csp(res, width=1, style=MARKER_STYLE["in_out_path_style"]) + + def add_arguments(self, pars): + add_argument = pars.add_argument + add_argument("-d", "--directory", default="/home/", help="Directory for gcode file") + add_argument("-f", "--filename", dest="file", default="-1.0", help="File name") + add_argument("--add-numeric-suffix-to-filename", type=inkex.Boolean, default=True, help="Add numeric suffix to filename") + add_argument("--Zscale", type=float, default="1.0", help="Scale factor Z") + add_argument("--Zoffset", type=float, default="0.0", help="Offset along Z") + add_argument("-s", "--Zsafe", type=float, default="0.5", help="Z above all obstacles") + add_argument("-z", "--Zsurface", type=float, default="0.0", help="Z of the surface") + add_argument("-c", "--Zdepth", type=float, default="-0.125", help="Z depth of cut") + add_argument("--Zstep", type=float, default="-0.125", help="Z step of cutting") + add_argument("-p", "--feed", type=float, default="4.0", help="Feed rate in unit/min") + + add_argument("--biarc-tolerance", type=float, default="1", help="Tolerance used when calculating biarc interpolation.") + add_argument("--biarc-max-split-depth", type=int, default="4", help="Defines maximum depth of splitting while approximating using biarcs.") + add_argument("--path-to-gcode-order", default="path by path", help="Defines cutting order path by path or layer by layer.") + add_argument("--path-to-gcode-depth-function", default="zd", help="Path to gcode depth function.") + add_argument("--path-to-gcode-sort-paths", type=inkex.Boolean, default=True, help="Sort paths to reduce rapid distance.") + add_argument("--comment-gcode", default="", help="Comment Gcode") + add_argument("--comment-gcode-from-properties", type=inkex.Boolean, default=False, help="Get additional comments from Object Properties") + + add_argument("--tool-diameter", type=float, default="3", help="Tool diameter used for area cutting") + add_argument("--max-area-curves", type=int, default="100", help="Maximum area curves for each area") + add_argument("--area-inkscape-radius", type=float, default="0", help="Area curves overlapping (depends on tool diameter [0, 0.9])") + add_argument("--area-tool-overlap", type=float, default="-10", help="Radius for preparing curves using inkscape") + add_argument("--unit", default="G21 (All units in mm)", help="Units") + add_argument("--active-tab", type=self.arg_method('tab'), default=self.tab_help, help="Defines which tab is active") + + add_argument("--area-fill-angle", type=float, default="0", help="Fill area with lines heading this angle") + add_argument("--area-fill-shift", type=float, default="0", help="Shift the lines by tool d * shift") + add_argument("--area-fill-method", default="zig-zag", help="Filling method either zig-zag or spiral") + + add_argument("--area-find-artefacts-diameter", type=float, default="1", help="Artefacts seeking radius") + add_argument("--area-find-artefacts-action", default="mark with an arrow", help="Artefacts action type") + + add_argument("--auto_select_paths", type=inkex.Boolean, default=True, help="Select all paths if nothing is selected.") + + add_argument("--loft-distances", default="10", help="Distances between paths.") + add_argument("--loft-direction", default="crosswise", help="Direction of loft's interpolation.") + add_argument("--loft-interpolation-degree", type=float, default="2", help="Which interpolation use to loft the paths smooth interpolation or staright.") + + add_argument("--min-arc-radius", type=float, default=".1", help="All arc having radius less than minimum will be considered as straight line") + + add_argument("--engraving-sharp-angle-tollerance", type=float, default="150", help="All angles thar are less than engraving-sharp-angle-tollerance will be thought sharp") + add_argument("--engraving-max-dist", type=float, default="10", help="Distance from original path where engraving is not needed (usually it's cutting tool diameter)") + add_argument("--engraving-newton-iterations", type=int, default="4", help="Number of sample points used to calculate distance") + add_argument("--engraving-draw-calculation-paths", type=inkex.Boolean, default=False, help="Draw additional graphics to debug engraving path") + add_argument("--engraving-cutter-shape-function", default="w", help="Cutter shape function z(w). Ex. cone: w. ") + + add_argument("--lathe-width", type=float, default=10., help="Lathe width") + add_argument("--lathe-fine-cut-width", type=float, default=1., help="Fine cut width") + add_argument("--lathe-fine-cut-count", type=int, default=1., help="Fine cut count") + add_argument("--lathe-create-fine-cut-using", default="Move path", help="Create fine cut using") + add_argument("--lathe-x-axis-remap", default="X", help="Lathe X axis remap") + add_argument("--lathe-z-axis-remap", default="Z", help="Lathe Z axis remap") + + add_argument("--lathe-rectangular-cutter-width", type=float, default="4", help="Rectangular cutter width") + + add_argument("--create-log", type=inkex.Boolean, dest="log_create_log", default=False, help="Create log files") + add_argument("--log-filename", default='', help="Create log files") + + add_argument("--orientation-points-count", default="2", help="Orientation points count") + add_argument("--tools-library-type", default='cylinder cutter', help="Create tools definition") + + add_argument("--dxfpoints-action", default='replace', help="dxfpoint sign toggle") + + add_argument("--help-language", default='http://www.cnc-club.ru/forum/viewtopic.php?f=33&t=35', help="Open help page in webbrowser.") + + add_argument("--offset-radius", type=float, default=10., help="Offset radius") + add_argument("--offset-step", type=float, default=10., help="Offset step") + add_argument("--offset-draw-clippend-path", type=inkex.Boolean, default=False, help="Draw clipped path") + add_argument("--offset-just-get-distance", type=inkex.Boolean, default=False, help="Don't do offset just get distance") + + add_argument("--postprocessor", default='', help="Postprocessor command.") + add_argument("--postprocessor-custom", default='', help="Postprocessor custom command.") + + add_argument("--graffiti-max-seg-length", type=float, default=1., help="Graffiti maximum segment length.") + add_argument("--graffiti-min-radius", type=float, default=10., help="Graffiti minimal connector's radius.") + add_argument("--graffiti-start-pos", default="(0;0)", help="Graffiti Start position (x;y).") + add_argument("--graffiti-create-linearization-preview", type=inkex.Boolean, default=True, help="Graffiti create linearization preview.") + add_argument("--graffiti-create-preview", type=inkex.Boolean, default=True, help="Graffiti create preview.") + add_argument("--graffiti-preview-size", type=int, default=800, help="Graffiti preview's size.") + add_argument("--graffiti-preview-emmit", type=int, default=800, help="Preview's paint emmit (pts/s).") + + add_argument("--in-out-path", type=inkex.Boolean, default=True, help="Create in-out paths") + add_argument("--in-out-path-do-not-add-reference-point", type=inkex.Boolean, default=False, help="Just add reference in-out point") + add_argument("--in-out-path-point-max-dist", type=float, default=10., help="In-out path max distance to reference point") + add_argument("--in-out-path-type", default="Round", help="In-out path type") + add_argument("--in-out-path-len", type=float, default=10., help="In-out path length") + add_argument("--in-out-path-replace-original-path", type=inkex.Boolean, default=False, help="Replace original path") + add_argument("--in-out-path-radius", type=float, default=10., help="In-out path radius for round path") + + add_argument("--plasma-prepare-corners", type=inkex.Boolean, default=True, help="Prepare corners") + add_argument("--plasma-prepare-corners-distance", type=float, default=10., help="Stepout distance for corners") + add_argument("--plasma-prepare-corners-tolerance", type=float, default=10., help="Maximum angle for corner (0-180 deg)") + + def __init__(self): + super(Gcodetools, self).__init__() + self.default_tool = { + "name": "Default tool", + "id": "default tool", + "diameter": 10., + "shape": "10", + "penetration angle": 90., + "penetration feed": 100., + "depth step": 1., + "feed": 400., + "in trajectotry": "", + "out trajectotry": "", + "gcode before path": "", + "gcode after path": "", + "sog": "", + "spinlde rpm": "", + "CW or CCW": "", + "tool change gcode": " ", + "4th axis meaning": " ", + "4th axis scale": 1., + "4th axis offset": 0., + "passing feed": "800", + "fine feed": "800", + } + self.tools_field_order = [ + 'name', + 'id', + 'diameter', + 'feed', + 'shape', + 'penetration angle', + 'penetration feed', + "passing feed", + 'depth step', + "in trajectotry", + "out trajectotry", + "gcode before path", + "gcode after path", + "sog", + "spinlde rpm", + "CW or CCW", + "tool change gcode", + ] + + def parse_curve(self, p, layer, w=None, f=None): + c = [] + if len(p) == 0: + return [] + p = self.transform_csp(p, layer) + + # Sort to reduce Rapid distance + k = list(range(1, len(p))) + keys = [0] + while len(k) > 0: + end = p[keys[-1]][-1][1] + dist = None + for i in range(len(k)): + start = p[k[i]][0][1] + dist = max((-((end[0] - start[0]) ** 2 + (end[1] - start[1]) ** 2), i), dist) + keys += [k[dist[1]]] + del k[dist[1]] + for k in keys: + subpath = p[k] + c += [[[subpath[0][1][0], subpath[0][1][1]], 'move', 0, 0]] + for i in range(1, len(subpath)): + sp1 = [[subpath[i - 1][j][0], subpath[i - 1][j][1]] for j in range(3)] + sp2 = [[subpath[i][j][0], subpath[i][j][1]] for j in range(3)] + c += biarc(sp1, sp2, 0, 0) if w is None else biarc(sp1, sp2, -f(w[k][i - 1]), -f(w[k][i])) + c += [[[subpath[-1][1][0], subpath[-1][1][1]], 'end', 0, 0]] + return c + + ################################################################################ + # Draw csp + ################################################################################ + + def draw_csp(self, csp, layer=None, group=None, fill='none', stroke='#178ade', width=0.354, style=None): + if layer is not None: + csp = self.transform_csp(csp, layer, reverse=True) + if group is None and layer is None: + group = self.document.getroot() + elif group is None and layer is not None: + group = layer + csp = self.apply_transforms(group, csp, reverse=True) + if style is not None: + return draw_csp(csp, group=group, style=style) + else: + return draw_csp(csp, group=group, fill=fill, stroke=stroke, width=width) + + def draw_curve(self, curve, layer, group=None, style=MARKER_STYLE["biarc_style"]): + self.set_markers() + + for i in [0, 1]: + sid = 'biarc{}_r'.format(i) + style[sid] = style['biarc{}'.format(i)].copy() + style[sid]["marker-start"] = "url(#DrawCurveMarker_r)" + del style[sid]["marker-end"] + + if group is None: + group = self.layers[min(1, len(self.layers) - 1)].add(Group(gcodetools="Preview group")) + if not hasattr(self, "preview_groups"): + self.preview_groups = {layer: group} + elif layer not in self.preview_groups: + self.preview_groups[layer] = group + group = self.preview_groups[layer] + + s = '' + arcn = 0 + + transform = self.get_transforms(group) + if transform: + transform = self.reverse_transform(transform) + transform = str(Transform(transform)) + + a = [0., 0.] + b = [1., 0.] + c = [0., 1.] + k = (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]) + a = self.transform(a, layer, True) + b = self.transform(b, layer, True) + c = self.transform(c, layer, True) + if ((b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1])) * k > 0: + reverse_angle = 1 + else: + reverse_angle = -1 + for sk in curve: + si = sk[:] + si[0] = self.transform(si[0], layer, True) + si[2] = self.transform(si[2], layer, True) if type(si[2]) == type([]) and len(si[2]) == 2 else si[2] + + if s != '': + if s[1] == 'line': + elem = group.add(PathElement(gcodetools="Preview")) + elem.transform = transform + elem.style = style['line'] + elem.path = 'M {},{} L {},{}'.format(s[0][0], s[0][1], si[0][0], si[0][1]) + elif s[1] == 'arc': + arcn += 1 + sp = s[0] + c = s[2] + s[3] = s[3] * reverse_angle + + a = ((P(si[0]) - P(c)).angle() - (P(s[0]) - P(c)).angle()) % TAU # s[3] + if s[3] * a < 0: + if a > 0: + a = a - TAU + else: + a = TAU + a + r = math.sqrt((sp[0] - c[0]) ** 2 + (sp[1] - c[1]) ** 2) + a_st = (math.atan2(sp[0] - c[0], - (sp[1] - c[1])) - math.pi / 2) % (math.pi * 2) + if a > 0: + a_end = a_st + a + st = style['biarc{}'.format(arcn % 2)] + else: + a_end = a_st * 1 + a_st = a_st + a + st = style['biarc{}_r'.format(arcn % 2)] + + elem = group.add(PathElement.arc(c, r, start=a_st, end=a_end, + open=True, gcodetools="Preview")) + elem.transform = transform + elem.style = st + + s = si + + def check_dir(self): + print_("Checking directory: '{}'".format(self.options.directory)) + if os.path.isdir(self.options.directory): + if os.path.isfile(os.path.join(self.options.directory, 'header')): + with open(os.path.join(self.options.directory, 'header')) as f: + self.header = f.read() + else: + self.header = defaults['header'] + if os.path.isfile(os.path.join(self.options.directory, 'footer')): + with open(os.path.join(self.options.directory, 'footer')) as f: + self.footer = f.read() + else: + self.footer = defaults['footer'] + self.header += self.options.unit + "\n" + else: + self.error("Directory does not exist! Please specify existing directory at Preferences tab!", "error") + return False + + if self.options.add_numeric_suffix_to_filename: + dir_list = os.listdir(self.options.directory) + if "." in self.options.file: + r = re.match(r"^(.*)(\..*)$", self.options.file) + ext = r.group(2) + name = r.group(1) + else: + ext = "" + name = self.options.file + max_n = 0 + for s in dir_list: + r = re.match(r"^{}_0*(\d+){}$".format(re.escape(name), re.escape(ext)), s) + if r: + max_n = max(max_n, int(r.group(1))) + filename = name + "_" + ("0" * (4 - len(str(max_n + 1))) + str(max_n + 1)) + ext + self.options.file = filename + + try: + with open(os.path.join(self.options.directory, self.options.file), "w") as f: + pass + except: + self.error("Can not write to specified file!\n{}".format(os.path.join(self.options.directory, self.options.file)), "error") + return False + return True + + ################################################################################ + # + # Generate Gcode + # Generates Gcode on given curve. + # + # Curve definition [start point, type = {'arc','line','move','end'}, arc center, arc angle, end point, [zstart, zend]] + # + ################################################################################ + def generate_gcode(self, curve, layer, depth): + Zauto_scale = self.Zauto_scale[layer] + tool = self.tools[layer][0] + g = "" + + def c(c): + c = [c[i] if i < len(c) else None for i in range(6)] + if c[5] == 0: + c[5] = None + s = [" X", " Y", " Z", " I", " J", " K"] + s1 = ["", "", "", "", "", ""] + m = [1, 1, self.options.Zscale * Zauto_scale, 1, 1, self.options.Zscale * Zauto_scale] + a = [0, 0, self.options.Zoffset, 0, 0, 0] + r = '' + for i in range(6): + if c[i] is not None: + r += s[i] + ("{:f}".format(c[i] * m[i] + a[i])) + s1[i] + return r + + def calculate_angle(a, current_a): + return min( + [abs(a - current_a % TAU + TAU), a + current_a - current_a % TAU + TAU], + [abs(a - current_a % TAU - TAU), a + current_a - current_a % TAU - TAU], + [abs(a - current_a % TAU), a + current_a - current_a % TAU])[1] + + if len(curve) == 0: + return "" + + try: + self.last_used_tool is None + except: + self.last_used_tool = None + print_("working on curve") + print_(curve) + + if tool != self.last_used_tool: + g += ("(Change tool to {})\n".format(re.sub("\"'\\(\\)\\\\", " ", tool["name"]))) + tool["tool change gcode"] + "\n" + + lg = 'G00' + zs = self.options.Zsafe + f = " F{:f}".format(tool['feed']) + current_a = 0 + go_to_safe_distance = "G00" + c([None, None, zs]) + "\n" + penetration_feed = " F{}".format(tool['penetration feed']) + for i in range(1, len(curve)): + # Creating Gcode for curve between s=curve[i-1] and si=curve[i] start at s[0] end at s[4]=si[0] + s = curve[i - 1] + si = curve[i] + feed = f if lg not in ['G01', 'G02', 'G03'] else '' + if s[1] == 'move': + g += go_to_safe_distance + "G00" + c(si[0]) + "\n" + tool['gcode before path'] + "\n" + lg = 'G00' + elif s[1] == 'end': + g += go_to_safe_distance + tool['gcode after path'] + "\n" + lg = 'G00' + elif s[1] == 'line': + if tool['4th axis meaning'] == "tangent knife": + a = atan2(si[0][0] - s[0][0], si[0][1] - s[0][1]) + a = calculate_angle(a, current_a) + g += "G01 A{}\n".format(a * tool['4th axis scale'] + tool['4th axis offset']) + current_a = a + if lg == "G00": + g += "G01" + c([None, None, s[5][0] + depth]) + penetration_feed + "(Penetrate)\n" + g += "G01" + c(si[0] + [s[5][1] + depth]) + feed + "\n" + lg = 'G01' + elif s[1] == 'arc': + r = [(s[2][0] - s[0][0]), (s[2][1] - s[0][1])] + if tool['4th axis meaning'] == "tangent knife": + if s[3] < 0: # CW + a1 = atan2(s[2][1] - s[0][1], -s[2][0] + s[0][0]) + math.pi + else: # CCW + a1 = atan2(-s[2][1] + s[0][1], s[2][0] - s[0][0]) + math.pi + a = calculate_angle(a1, current_a) + g += "G01 A{}\n".format(a * tool['4th axis scale'] + tool['4th axis offset']) + current_a = a + axis4 = " A{}".format((current_a + s[3]) * tool['4th axis scale'] + tool['4th axis offset']) + current_a = current_a + s[3] + else: + axis4 = "" + if lg == "G00": + g += "G01" + c([None, None, s[5][0] + depth]) + penetration_feed + "(Penetrate)\n" + if (r[0] ** 2 + r[1] ** 2) > self.options.min_arc_radius ** 2: + r1 = (P(s[0]) - P(s[2])) + r2 = (P(si[0]) - P(s[2])) + if abs(r1.mag() - r2.mag()) < 0.001: + g += ("G02" if s[3] < 0 else "G03") + c(si[0] + [s[5][1] + depth, (s[2][0] - s[0][0]), (s[2][1] - s[0][1])]) + feed + axis4 + "\n" + else: + r = (r1.mag() + r2.mag()) / 2 + g += ("G02" if s[3] < 0 else "G03") + c(si[0] + [s[5][1] + depth]) + " R{:f}".format(r) + feed + axis4 + "\n" + lg = 'G02' + else: + if tool['4th axis meaning'] == "tangent knife": + a = atan2(si[0][0] - s[0][0], si[0][1] - s[0][1]) + math.pi + a = calculate_angle(a, current_a) + g += "G01 A{}\n".format(a * tool['4th axis scale'] + tool['4th axis offset']) + current_a = a + g += "G01" + c(si[0] + [s[5][1] + depth]) + feed + "\n" + lg = 'G01' + if si[1] == 'end': + g += go_to_safe_distance + tool['gcode after path'] + "\n" + return g + + def get_transforms(self, g): + root = self.document.getroot() + trans = [] + while g != root: + if 'transform' in g.keys(): + t = g.get('transform') + t = Transform(t).matrix + trans = (Transform(t) * Transform(trans)).matrix if trans != [] else t + + print_(trans) + g = g.getparent() + return trans + + def reverse_transform(self, transform): + trans = numpy.array(transform + ([0, 0, 1],)) + if numpy.linalg.det(trans) != 0: + trans = numpy.linalg.inv(trans).tolist()[:2] + return trans + else: + return transform + + def apply_transforms(self, g, csp, reverse=False): + trans = self.get_transforms(g) + if trans: + if not reverse: + # TODO: This was applyTransformToPath but was deprecated. Candidate for refactoring. + for comp in csp: + for ctl in comp: + for pt in ctl: + pt[0], pt[1] = Transform(trans).apply_to_point(pt) + + else: + # TODO: This was applyTransformToPath but was deprecated. Candidate for refactoring. + for comp in csp: + for ctl in comp: + for pt in ctl: + pt[0], pt[1] = Transform(self.reverse_transform(trans)).apply_to_point(pt) + return csp + + def transform_scalar(self, x, layer, reverse=False): + return self.transform([x, 0], layer, reverse)[0] - self.transform([0, 0], layer, reverse)[0] + + def transform(self, source_point, layer, reverse=False): + if layer not in self.transform_matrix: + for i in range(self.layers.index(layer), -1, -1): + if self.layers[i] in self.orientation_points: + break + if self.layers[i] not in self.orientation_points: + self.error("Orientation points for '{}' layer have not been found! Please add orientation points using Orientation tab!".format(layer.label), "error") + elif self.layers[i] in self.transform_matrix: + self.transform_matrix[layer] = self.transform_matrix[self.layers[i]] + self.Zcoordinates[layer] = self.Zcoordinates[self.layers[i]] + else: + orientation_layer = self.layers[i] + if len(self.orientation_points[orientation_layer]) > 1: + self.error("There are more than one orientation point groups in '{}' layer".format(orientation_layer.label)) + points = self.orientation_points[orientation_layer][0] + if len(points) == 2: + points += [[[(points[1][0][1] - points[0][0][1]) + points[0][0][0], -(points[1][0][0] - points[0][0][0]) + points[0][0][1]], [-(points[1][1][1] - points[0][1][1]) + points[0][1][0], points[1][1][0] - points[0][1][0] + points[0][1][1]]]] + if len(points) == 3: + print_("Layer '{orientation_layer.label}' Orientation points: ") + for point in points: + print_(point) + # Zcoordinates definition taken from Orientatnion point 1 and 2 + self.Zcoordinates[layer] = [max(points[0][1][2], points[1][1][2]), min(points[0][1][2], points[1][1][2])] + matrix = numpy.array([ + [points[0][0][0], points[0][0][1], 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, points[0][0][0], points[0][0][1], 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, points[0][0][0], points[0][0][1], 1], + [points[1][0][0], points[1][0][1], 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, points[1][0][0], points[1][0][1], 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, points[1][0][0], points[1][0][1], 1], + [points[2][0][0], points[2][0][1], 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, points[2][0][0], points[2][0][1], 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, points[2][0][0], points[2][0][1], 1] + ]) + + if numpy.linalg.det(matrix) != 0: + m = numpy.linalg.solve(matrix, + numpy.array( + [[points[0][1][0]], [points[0][1][1]], [1], [points[1][1][0]], [points[1][1][1]], [1], [points[2][1][0]], [points[2][1][1]], [1]] + ) + ).tolist() + self.transform_matrix[layer] = [[m[j * 3 + i][0] for i in range(3)] for j in range(3)] + + else: + self.error("Orientation points are wrong! (if there are two orientation points they should not be the same. If there are three orientation points they should not be in a straight line.)", "error") + else: + self.error("Orientation points are wrong! (if there are two orientation points they should not be the same. If there are three orientation points they should not be in a straight line.)", "error") + + self.transform_matrix_reverse[layer] = numpy.linalg.inv(self.transform_matrix[layer]).tolist() + print_("\n Layer '{}' transformation matrixes:".format(layer.label)) + print_(self.transform_matrix) + print_(self.transform_matrix_reverse) + + # Zautoscale is obsolete + self.Zauto_scale[layer] = 1 + print_("Z automatic scale = {} (computed according orientation points)".format(self.Zauto_scale[layer])) + + x = source_point[0] + y = source_point[1] + if not reverse: + t = self.transform_matrix[layer] + else: + t = self.transform_matrix_reverse[layer] + return [t[0][0] * x + t[0][1] * y + t[0][2], t[1][0] * x + t[1][1] * y + t[1][2]] + + def transform_csp(self, csp_, layer, reverse=False): + csp = [[[csp_[i][j][0][:], csp_[i][j][1][:], csp_[i][j][2][:]] for j in range(len(csp_[i]))] for i in range(len(csp_))] + for i in xrange(len(csp)): + for j in xrange(len(csp[i])): + for k in xrange(len(csp[i][j])): + csp[i][j][k] = self.transform(csp[i][j][k], layer, reverse) + return csp + + def error(self, s, msg_type="warning"): + """ + Errors handling function + warnings are printed into log file and warning message is displayed but + extension continues working, + errors causes log and execution is halted + """ + if msg_type == "warning": + print_(s) + inkex.errormsg(s + "\n") + + elif msg_type == "error": + print_(s) + raise inkex.AbortExtension(s) + + else: + print_("Unknown message type: {}".format(msg_type)) + print_(s) + raise inkex.AbortExtension(s) + + ################################################################################ + # Set markers + ################################################################################ + def set_markers(self): + """Make sure all markers are available""" + def ensure_marker(elem_id, x=-4, polA='', polB='-', fill='#000044'): + if self.svg.getElementById(elem_id) is None: + marker = self.svg.defs.add(Marker( + id=elem_id, orient="auto", refX=str(x), refY="-1.687441", + style="overflow:visible")) + path = marker.add(PathElement( + d="m {0}4.588864,-1.687441 0.0,0.0 L {0}9.177728,0.0 "\ + "c {1}0.73311,-0.996261 {1}0.728882,-2.359329 0.0,-3.374882"\ + .format(polA, polB))) + path.style = "fill:{};fill-rule:evenodd;stroke:none;".format(fill) + + ensure_marker("CheckToolsAndOPMarker") + ensure_marker("DrawCurveMarker") + ensure_marker("DrawCurveMarker_r", x=4, polA='-', polB='') + ensure_marker("InOutPathMarker", fill='#0072a7') + + def get_info(self): + """Get Gcodetools info from the svg""" + self.selected_paths = {} + self.paths = {} + self.tools = {} + self.orientation_points = {} + self.graffiti_reference_points = {} + self.layers = [self.document.getroot()] + self.Zcoordinates = {} + self.transform_matrix = {} + self.transform_matrix_reverse = {} + self.Zauto_scale = {} + self.in_out_reference_points = [] + self.my3Dlayer = None + + def recursive_search(g, layer, selected=False): + items = g.getchildren() + items.reverse() + for i in items: + if selected: + self.svg.selected[i.get("id")] = i + if isinstance(i, Layer): + if i.label == '3D': + self.my3Dlayer = i + else: + self.layers += [i] + recursive_search(i, i) + + elif i.get('gcodetools') == "Gcodetools orientation group": + points = self.get_orientation_points(i) + if points is not None: + self.orientation_points[layer] = self.orientation_points[layer] + [points[:]] if layer in self.orientation_points else [points[:]] + print_("Found orientation points in '{}' layer: {}".format(layer.label, points)) + else: + self.error("Warning! Found bad orientation points in '{}' layer. Resulting Gcode could be corrupt!".format(layer.label)) + + # Need to recognise old files ver 1.6.04 and earlier + elif i.get("gcodetools") == "Gcodetools tool definition" or i.get("gcodetools") == "Gcodetools tool definition": + tool = self.get_tool(i) + self.tools[layer] = self.tools[layer] + [tool.copy()] if layer in self.tools else [tool.copy()] + print_("Found tool in '{}' layer: {}".format(layer.label, tool)) + + elif i.get("gcodetools") == "Gcodetools graffiti reference point": + point = self.get_graffiti_reference_points(i) + if point: + self.graffiti_reference_points[layer] = self.graffiti_reference_points[layer] + [point[:]] if layer in self.graffiti_reference_points else [point] + else: + self.error("Warning! Found bad graffiti reference point in '{}' layer. Resulting Gcode could be corrupt!".format(layer.label)) + + elif isinstance(i, inkex.PathElement): + if "gcodetools" not in i.keys(): + self.paths[layer] = self.paths[layer] + [i] if layer in self.paths else [i] + if i.get("id") in self.svg.selected.ids: + self.selected_paths[layer] = self.selected_paths[layer] + [i] if layer in self.selected_paths else [i] + + elif i.get("gcodetools") == "In-out reference point group": + items_ = i.getchildren() + items_.reverse() + for j in items_: + if j.get("gcodetools") == "In-out reference point": + self.in_out_reference_points.append(self.apply_transforms(j, j.path.to_superpath())[0][0][1]) + + elif isinstance(i, inkex.Group): + recursive_search(i, layer, (i.get("id") in self.svg.selected)) + + elif i.get("id") in self.svg.selected: + # xgettext:no-pango-format + self.error("This extension works with Paths and Dynamic Offsets and groups of them only! " + "All other objects will be ignored!\n" + "Solution 1: press Path->Object to path or Shift+Ctrl+C.\n" + "Solution 2: Path->Dynamic offset or Ctrl+J.\n" + "Solution 3: export all contours to PostScript level 2 (File->Save As->.ps) and File->Import this file.") + + recursive_search(self.document.getroot(), self.document.getroot()) + + if len(self.layers) == 1: + self.error("Document has no layers! Add at least one layer using layers panel (Ctrl+Shift+L)", "error") + root = self.document.getroot() + + if root in self.selected_paths or root in self.paths: + self.error("Warning! There are some paths in the root of the document, but not in any layer! Using bottom-most layer for them.") + + if root in self.selected_paths: + if self.layers[-1] in self.selected_paths: + self.selected_paths[self.layers[-1]] += self.selected_paths[root][:] + else: + self.selected_paths[self.layers[-1]] = self.selected_paths[root][:] + del self.selected_paths[root] + + if root in self.paths: + if self.layers[-1] in self.paths: + self.paths[self.layers[-1]] += self.paths[root][:] + else: + self.paths[self.layers[-1]] = self.paths[root][:] + del self.paths[root] + + def get_orientation_points(self, g): + items = g.getchildren() + items.reverse() + p2 = [] + p3 = [] + p = None + for i in items: + if isinstance(i, inkex.Group): + if i.get("gcodetools") == "Gcodetools orientation point (2 points)": + p2 += [i] + if i.get("gcodetools") == "Gcodetools orientation point (3 points)": + p3 += [i] + if len(p2) == 2: + p = p2 + elif len(p3) == 3: + p = p3 + if p is None: + return None + points = [] + for i in p: + point = [[], []] + for node in i: + if node.get('gcodetools') == "Gcodetools orientation point arrow": + csp = node.path.transform(node.composed_transform()).to_superpath() + point[0] = csp[0][0][1] + if node.get('gcodetools') == "Gcodetools orientation point text": + r = re.match(r'(?i)\s*\(\s*(-?\s*\d*(?:,|\.)*\d*)\s*;\s*(-?\s*\d*(?:,|\.)*\d*)\s*;\s*(-?\s*\d*(?:,|\.)*\d*)\s*\)\s*', node.get_text()) + point[1] = [float(r.group(1)), float(r.group(2)), float(r.group(3))] + if point[0] != [] and point[1] != []: + points += [point] + if len(points) == len(p2) == 2 or len(points) == len(p3) == 3: + return points + else: + return None + + def get_graffiti_reference_points(self, g): + point = [[], ''] + for node in g: + if node.get('gcodetools') == "Gcodetools graffiti reference point arrow": + point[0] = self.apply_transforms(node, node.path.to_superpath())[0][0][1] + if node.get('gcodetools') == "Gcodetools graffiti reference point text": + point[1] = node.get_text() + if point[0] != [] and point[1] != '': + return point + else: + return [] + + def get_tool(self, g): + tool = self.default_tool.copy() + tool["self_group"] = g + for i in g: + # Get parameters + if i.get("gcodetools") == "Gcodetools tool background": + tool["style"] = dict(inkex.Style.parse_str(i.get("style"))) + elif i.get("gcodetools") == "Gcodetools tool parameter": + key = None + value = None + for j in i: + # need to recognise old tools from ver 1.6.04 + if j.get("gcodetools") == "Gcodetools tool definition field name" or j.get("gcodetools") == "Gcodetools tool defention field name": + key = j.get_text() + if j.get("gcodetools") == "Gcodetools tool definition field value" or j.get("gcodetools") == "Gcodetools tool defention field value": + value = j.get_text() + if value == "(None)": + value = "" + if value is None or key is None: + continue + if key in self.default_tool.keys(): + try: + tool[key] = type(self.default_tool[key])(value) + except: + tool[key] = self.default_tool[key] + self.error("Warning! Tool's and default tool's parameter's ({}) types are not the same ( type('{}') != type('{}') ).".format(key, value, self.default_tool[key])) + else: + tool[key] = value + self.error("Warning! Tool has parameter that default tool has not ( '{}': '{}' ).".format(key, value)) + return tool + + def set_tool(self, layer): + for i in range(self.layers.index(layer), -1, -1): + if self.layers[i] in self.tools: + break + if self.layers[i] in self.tools: + if self.layers[i] != layer: + self.tools[layer] = self.tools[self.layers[i]] + if len(self.tools[layer]) > 1: + label = self.layers[i].label + self.error("Layer '{}' contains more than one tool!".format(label)) + return self.tools[layer] + else: + self.error("Can not find tool for '{}' layer! Please add one with Tools library tab!".format(layer.label), "error") + + ################################################################################ + # + # Path to Gcode + # + ################################################################################ + def tab_path_to_gcode(self): + self.get_info_plus() + def get_boundaries(points): + minx = None + miny = None + maxx = None + maxy = None + out = [[], [], [], []] + for p in points: + if minx == p[0]: + out[0] += [p] + if minx is None or p[0] < minx: + minx = p[0] + out[0] = [p] + + if miny == p[1]: + out[1] += [p] + if miny is None or p[1] < miny: + miny = p[1] + out[1] = [p] + + if maxx == p[0]: + out[2] += [p] + if maxx is None or p[0] > maxx: + maxx = p[0] + out[2] = [p] + + if maxy == p[1]: + out[3] += [p] + if maxy is None or p[1] > maxy: + maxy = p[1] + out[3] = [p] + return out + + def remove_duplicates(points): + i = 0 + out = [] + for p in points: + for j in xrange(i, len(points)): + if p == points[j]: + points[j] = [None, None] + if p != [None, None]: + out += [p] + i += 1 + return out + + def get_way_len(points): + l = 0 + for i in xrange(1, len(points)): + l += math.sqrt((points[i][0] - points[i - 1][0]) ** 2 + (points[i][1] - points[i - 1][1]) ** 2) + return l + + def sort_dxfpoints(points): + points = remove_duplicates(points) + ways = [ + # l=0, d=1, r=2, u=3 + [3, 0], # ul + [3, 2], # ur + [1, 0], # dl + [1, 2], # dr + [0, 3], # lu + [0, 1], # ld + [2, 3], # ru + [2, 1], # rd + ] + minimal_way = [] + minimal_len = None + for w in ways: + tpoints = points[:] + cw = [] + for j in xrange(0, len(points)): + p = get_boundaries(get_boundaries(tpoints)[w[0]])[w[1]] + tpoints.remove(p[0]) + cw += p + curlen = get_way_len(cw) + if minimal_len is None or curlen < minimal_len: + minimal_len = curlen + minimal_way = cw + + return minimal_way + + def sort_lines(lines): + if len(lines) == 0: + return [] + lines = [[key] + lines[key] for key in range(len(lines))] + keys = [0] + end_point = lines[0][3:] + print_("!!!", lines, "\n", end_point) + del lines[0] + while len(lines) > 0: + dist = [[point_to_point_d2(end_point, lines[i][1:3]), i] for i in range(len(lines))] + i = min(dist)[1] + keys.append(lines[i][0]) + end_point = lines[i][3:] + del lines[i] + return keys + + def sort_curves(curves): + lines = [] + for curve in curves: + lines += [curve[0][0][0] + curve[-1][-1][0]] + return sort_lines(lines) + + def print_dxfpoints(points): + gcode = "" + for point in points: + gcode += "(drilling dxfpoint)\nG00 Z{:f}\nG00 X{:f} Y{:f}\nG01 Z{:f} F{:f}\nG04 P{:f}\nG00 Z{:f}\n".format(self.options.Zsafe, point[0], point[1], self.Zcoordinates[layer][1], self.tools[layer][0]["penetration feed"], 0.2, self.options.Zsafe) + return gcode + + def get_path_properties(node): + res = {} + done = False + while not done and node != self.svg: + for i in node.getchildren(): + if isinstance(i, inkex.Desc): + res["Description"] = i.text + elif isinstance(i, inkex.Title): + res["Title"] = i.text + done = True + node = node.getparent() + return res + + if self.selected_paths == {} and self.options.auto_select_paths: + paths = self.paths + self.error("No paths are selected! Trying to work on all available paths.") + else: + paths = self.selected_paths + self.check_dir() + gcode = "" + + parent = list(self.selected_paths)[0] if self.selected_paths else self.layers[0] + biarc_group = parent.add(Group()) + print_(("self.layers=", self.layers)) + print_(("paths=", paths)) + colors = {} + for layer in self.layers: + if layer in paths: + print_(("layer", layer)) + # transform simple path to get all var about orientation + self.transform_csp([[[[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]]]], layer) + + self.set_tool(layer) + curves = [] + dxfpoints = [] + + try: + depth_func = eval('lambda c,d,s: ' + self.options.path_to_gcode_depth_function.strip('"')) + except: + self.error("Bad depth function! Enter correct function at Path to Gcode tab!") + + for path in paths[layer]: + if "d" not in path.keys(): + self.error("Warning: One or more paths do not have 'd' parameter, try to Ungroup (Ctrl+Shift+G) and Object to Path (Ctrl+Shift+C)!") + continue + csp = path.path.to_superpath() + csp = self.apply_transforms(path, csp) + id_ = path.get("id") + + def set_comment(match, path): + if match.group(1) in path.keys(): + return path.get(match.group(1)) + else: + return "None" + + if self.options.comment_gcode != "": + comment = re.sub("\\[([A-Za-z_\\-\\:]+)\\]", partial(set_comment, path=path), self.options.comment_gcode) + comment = comment.replace(":newline:", "\n") + comment = gcode_comment_str(comment) + else: + comment = "" + if self.options.comment_gcode_from_properties: + tags = get_path_properties(path) + for tag in tags: + comment += gcode_comment_str("{}: {}".format(tag, tags[tag])) + + style = dict(inkex.Style.parse_str(path.get("style"))) + colors[id_] = inkex.Color(style['stroke'] if "stroke" in style and style['stroke'] != 'none' else "#000").to_rgb() + if path.get("dxfpoint") == "1": + tmp_curve = self.transform_csp(csp, layer) + x = tmp_curve[0][0][0][0] + y = tmp_curve[0][0][0][1] + print_("got dxfpoint (scaled) at ({:f},{:f})".format(x, y)) + dxfpoints += [[x, y]] + else: + + zd = self.Zcoordinates[layer][1] + zs = self.Zcoordinates[layer][0] + c = 1. - float(sum(colors[id_])) / 255 / 3 + curves += [ + [ + [id_, depth_func(c, zd, zs), comment], + [self.parse_curve([subpath], layer) for subpath in csp] + ] + ] + dxfpoints = sort_dxfpoints(dxfpoints) + gcode += print_dxfpoints(dxfpoints) + + for curve in curves: + for subcurve in curve[1]: + self.draw_curve(subcurve, layer) + + if self.options.path_to_gcode_order == 'subpath by subpath': + curves_ = [] + for curve in curves: + curves_ += [[curve[0], [subcurve]] for subcurve in curve[1]] + curves = curves_ + + self.options.path_to_gcode_order = 'path by path' + + if self.options.path_to_gcode_order == 'path by path': + if self.options.path_to_gcode_sort_paths: + keys = sort_curves([curve[1] for curve in curves]) + else: + keys = range(len(curves)) + for key in keys: + d = curves[key][0][1] + for step in range(0, 1 + int(math.ceil(abs((zs - d) / self.tools[layer][0]["depth step"])))): + z = max(d, zs - abs(self.tools[layer][0]["depth step"] * (step + 1))) + + gcode += gcode_comment_str("\nStart cutting path id: {}".format(curves[key][0][0])) + if curves[key][0][2] != "()": + gcode += curves[key][0][2] # add comment + + for curve in curves[key][1]: + gcode += self.generate_gcode(curve, layer, z) + + gcode += gcode_comment_str("End cutting path id: {}\n\n".format(curves[key][0][0])) + + else: # pass by pass + mind = min([curve[0][1] for curve in curves]) + for step in range(0, 1 + int(math.ceil(abs((zs - mind) / self.tools[layer][0]["depth step"])))): + z = zs - abs(self.tools[layer][0]["depth step"] * step) + curves_ = [] + for curve in curves: + if curve[0][1] < z: + curves_.append(curve) + + z = zs - abs(self.tools[layer][0]["depth step"] * (step + 1)) + gcode += "\n(Pass at depth {})\n".format(z) + + if self.options.path_to_gcode_sort_paths: + keys = sort_curves([curve[1] for curve in curves_]) + else: + keys = range(len(curves_)) + for key in keys: + + gcode += gcode_comment_str("Start cutting path id: {}".format(curves[key][0][0])) + if curves[key][0][2] != "()": + gcode += curves[key][0][2] # add comment + + for subcurve in curves_[key][1]: + gcode += self.generate_gcode(subcurve, layer, max(z, curves_[key][0][1])) + + gcode += gcode_comment_str("End cutting path id: {}\n\n".format(curves[key][0][0])) + + self.export_gcode(gcode) + + ################################################################################ + # + # dxfpoints + # + ################################################################################ + def tab_dxfpoints(self): + self.get_info_plus() + if self.selected_paths == {}: + self.error("Nothing is selected. Please select something to convert to drill point (dxfpoint) or clear point sign.") + for layer in self.layers: + if layer in self.selected_paths: + for path in self.selected_paths[layer]: + if self.options.dxfpoints_action == 'replace': + + path.set("dxfpoint", "1") + r = re.match("^\\s*.\\s*(\\S+)", path.get("d")) + if r is not None: + print_(("got path=", r.group(1))) + path.set("d", "m {} 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000001 z".format(r.group(1))) + path.set("style", MARKER_STYLE["dxf_points"]) + + if self.options.dxfpoints_action == 'save': + path.set("dxfpoint", "1") + + if self.options.dxfpoints_action == 'clear' and path.get("dxfpoint") == "1": + path.set("dxfpoint", "0") + + ################################################################################ + # + # Artefacts + # + ################################################################################ + def tab_area_artefacts(self): + self.get_info_plus() + if self.selected_paths == {} and self.options.auto_select_paths: + paths = self.paths + self.error("No paths are selected! Trying to work on all available paths.") + else: + paths = self.selected_paths + for layer in paths: + for path in paths[layer]: + parent = path.getparent() + if "d" not in path.keys(): + self.error("Warning: One or more paths do not have 'd' parameter, try to Ungroup (Ctrl+Shift+G) and Object to Path (Ctrl+Shift+C)!") + continue + csp = path.path.to_superpath() + remove = [] + for i in range(len(csp)): + subpath = [[point[:] for point in points] for points in csp[i]] + subpath = self.apply_transforms(path, [subpath])[0] + bounds = csp_simple_bound([subpath]) + if (bounds[2] - bounds[0]) ** 2 + (bounds[3] - bounds[1]) ** 2 < self.options.area_find_artefacts_diameter ** 2: + if self.options.area_find_artefacts_action == "mark with an arrow": + arrow = Path('m {},{} 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000001 z'.format(subpath[0][1][0], subpath[0][1][1])).to_superpath() + arrow = self.apply_transforms(path, arrow, True) + node = parent.add(PathElement()) + node.path = CubicSuperPath(arrow) + node.style = MARKER_STYLE["area artefact arrow"] + node.set('gcodetools', 'area artefact arrow') + elif self.options.area_find_artefacts_action == "mark with style": + node = parent.add(PathElement()) + node.path = CubicSuperPath(csp[i]) + node.style = MARKER_STYLE["area artefact"] + remove.append(i) + elif self.options.area_find_artefacts_action == "delete": + remove.append(i) + print_("Deleted artefact {}".format(subpath)) + remove.reverse() + for i in remove: + del csp[i] + if len(csp) == 0: + parent.remove(path) + else: + path.path = CubicSuperPath(csp) + + return + + def tab_area(self): + """Calculate area curves""" + self.get_info_plus() + if len(self.selected_paths) <= 0: + self.error("This extension requires at least one selected path.") + return + for layer in self.layers: + if layer in self.selected_paths: + self.set_tool(layer) + if self.tools[layer][0]['diameter'] <= 0: + self.error("Tool diameter must be > 0 but tool's diameter on '{}' layer is not!".format(layer.label), "error") + + for path in self.selected_paths[layer]: + print_(("doing path", path.get("style"), path.get("d"))) + + area_group = path.getparent().add(Group()) + + csp = path.path.to_superpath() + print_(csp) + if not csp: + print_("omitting non-path") + self.error("Warning: omitting non-path") + continue + + if path.get('sodipodi:type') != "inkscape:offset": + print_("Path {} is not an offset. Preparation started.".format(path.get("id"))) + # Path is not offset. Preparation will be needed. + # Finding top most point in path (min y value) + + min_x, min_y, min_i, min_j, min_t = csp_true_bounds(csp)[1] + + # Reverse path if needed. + if min_y != float("-inf"): + # Move outline subpath to the beginning of csp + subp = csp[min_i] + del csp[min_i] + j = min_j + # Split by the topmost point and join again + if min_t in [0, 1]: + if min_t == 0: + j = j - 1 + subp[-1][2], subp[0][0] = subp[-1][1], subp[0][1] + subp = [[subp[j][1], subp[j][1], subp[j][2]]] + subp[j + 1:] + subp[:j] + [[subp[j][0], subp[j][1], subp[j][1]]] + else: + sp1, sp2, sp3 = csp_split(subp[j - 1], subp[j], min_t) + subp[-1][2], subp[0][0] = subp[-1][1], subp[0][1] + subp = [[sp2[1], sp2[1], sp2[2]]] + [sp3] + subp[j + 1:] + subp[:j - 1] + [sp1] + [[sp2[0], sp2[1], sp2[1]]] + csp = [subp] + csp + # reverse path if needed + if csp_subpath_ccw(csp[0]): + for i in range(len(csp)): + n = [] + for j in csp[i]: + n = [[j[2][:], j[1][:], j[0][:]]] + n + csp[i] = n[:] + + # What the absolute fudge is this doing? Closing paths? Ugh. + d = str(CubicSuperPath(csp)) + print_(("original d=", d)) + d = re.sub(r'(?i)(m[^mz]+)', r'\1 Z ', d) + d = re.sub(r'(?i)\s*z\s*z\s*', r' Z ', d) + d = re.sub(r'(?i)\s*([A-Za-z])\s*', r' \1 ', d) + print_(("formatted d=", d)) + p0 = self.transform([0, 0], layer) + p1 = self.transform([0, 1], layer) + scale = (P(p0) - P(p1)).mag() + if scale == 0: + scale = 1. + else: + scale = 1. / scale + print_(scale) + tool_d = self.tools[layer][0]['diameter'] * scale + r = self.options.area_inkscape_radius * scale + sign = 1 if r > 0 else -1 + print_("Tool diameter = {}, r = {}".format(tool_d, r)) + + # avoiding infinite loops + if self.options.area_tool_overlap > 0.9: + self.options.area_tool_overlap = .9 + + for i in range(self.options.max_area_curves): + radius = - tool_d * (i * (1 - self.options.area_tool_overlap) + 0.5) * sign + if abs(radius) > abs(r): + radius = -r + + elem = area_group.add(PathElement(style=MARKER_STYLE["biarc_style_i"]['area'])) + elem.set('sodipodi:type', 'inkscape:offset') + elem.set('inkscape:radius', radius) + elem.set('inkscape:original', d) + print_(("adding curve", area_group, d, MARKER_STYLE["biarc_style_i"]['area'])) + if radius == -r: + break + + def tab_area_fill(self): + """Fills area with lines""" + self.get_info_plus() + # convert degrees into rad + self.options.area_fill_angle = self.options.area_fill_angle * math.pi / 180 + if len(self.selected_paths) <= 0: + self.error("This extension requires at least one selected path.") + return + for layer in self.layers: + if layer in self.selected_paths: + self.set_tool(layer) + if self.tools[layer][0]['diameter'] <= 0: + self.error("Tool diameter must be > 0 but tool's diameter on '{}' layer is not!".format(layer.label), "error") + tool = self.tools[layer][0] + for path in self.selected_paths[layer]: + lines = [] + print_(("doing path", path.get("style"), path.get("d"))) + area_group = path.getparent().add(Group()) + csp = path.path.to_superpath() + if not csp: + print_("omitting non-path") + self.error("Warning: omitting non-path") + continue + csp = self.apply_transforms(path, csp) + csp = csp_close_all_subpaths(csp) + csp = self.transform_csp(csp, layer) + + # rotate the path to get bounds in defined direction. + a = - self.options.area_fill_angle + rotated_path = [[[[point[0] * math.cos(a) - point[1] * math.sin(a), point[0] * math.sin(a) + point[1] * math.cos(a)] for point in sp] for sp in subpath] for subpath in csp] + bounds = csp_true_bounds(rotated_path) + + # Draw the lines + # Get path's bounds + b = [0.0, 0.0, 0.0, 0.0] # [minx,miny,maxx,maxy] + for k in range(4): + i = bounds[k][2] + j = bounds[k][3] + t = bounds[k][4] + + b[k] = csp_at_t(rotated_path[i][j - 1], rotated_path[i][j], t)[k % 2] + + # Zig-zag + r = tool['diameter'] * (1 - self.options.area_tool_overlap) + if r <= 0: + self.error('Tools diameter must be greater than 0!', 'error') + return + + lines += [[]] + + if self.options.area_fill_method == 'zig-zag': + i = b[0] - self.options.area_fill_shift * r + top = True + last_one = True + while i < b[2] or last_one: + if i >= b[2]: + last_one = False + if not lines[-1]: + lines[-1] += [[i, b[3]]] + + if top: + lines[-1] += [[i, b[1]], [i + r, b[1]]] + + else: + lines[-1] += [[i, b[3]], [i + r, b[3]]] + + top = not top + i += r + else: + + w = b[2] - b[0] + self.options.area_fill_shift * r + h = b[3] - b[1] + self.options.area_fill_shift * r + x = b[0] - self.options.area_fill_shift * r + y = b[1] - self.options.area_fill_shift * r + lines[-1] += [[x, y]] + stage = 0 + start = True + while w > 0 and h > 0: + stage = (stage + 1) % 4 + if stage == 0: + y -= h + h -= r + elif stage == 1: + x += w + if not start: + w -= r + start = False + elif stage == 2: + y += h + h -= r + elif stage == 3: + x -= w + w -= r + + lines[-1] += [[x, y]] + + stage = (stage + 1) % 4 + if w <= 0 and h > 0: + y = y - h if stage == 0 else y + h + if h <= 0 and w > 0: + x = x - w if stage == 3 else x + w + lines[-1] += [[x, y]] + # Rotate created paths back + a = self.options.area_fill_angle + lines = [[[point[0] * math.cos(a) - point[1] * math.sin(a), point[0] * math.sin(a) + point[1] * math.cos(a)] for point in subpath] for subpath in lines] + + # get the intersection points + + splitted_line = [[lines[0][0]]] + intersections = {} + for l1, l2, in zip(lines[0], lines[0][1:]): + ints = [] + + if l1[0] == l2[0] and l1[1] == l2[1]: + continue + for i in range(len(csp)): + for j in range(1, len(csp[i])): + sp1 = csp[i][j - 1] + sp2 = csp[i][j] + roots = csp_line_intersection(l1, l2, sp1, sp2) + for t in roots: + p = tuple(csp_at_t(sp1, sp2, t)) + if l1[0] == l2[0]: + t1 = (p[1] - l1[1]) / (l2[1] - l1[1]) + else: + t1 = (p[0] - l1[0]) / (l2[0] - l1[0]) + if 0 <= t1 <= 1: + ints += [[t1, p[0], p[1], i, j, t]] + if p in intersections: + intersections[p] += [[i, j, t]] + else: + intersections[p] = [[i, j, t]] + + ints.sort() + for i in ints: + splitted_line[-1] += [[i[1], i[2]]] + splitted_line += [[[i[1], i[2]]]] + splitted_line[-1] += [l2] + i = 0 + print_(splitted_line) + while i < len(splitted_line): + # check if the middle point of the first lines segment is inside the path. + # and remove the subline if not. + l1 = splitted_line[i][0] + l2 = splitted_line[i][1] + p = [(l1[0] + l2[0]) / 2, (l1[1] + l2[1]) / 2] + if not point_inside_csp(p, csp): + del splitted_line[i] + else: + i += 1 + + # and apply back transrormations to draw them + csp_line = csp_from_polyline(splitted_line) + csp_line = self.transform_csp(csp_line, layer, True) + + self.draw_csp(csp_line, group=area_group) + + ################################################################################ + # + # Engraving + # + # LT Notes to self: See wiki.inkscape.org/wiki/index.php/PythonEffectTutorial + # To create anything in the Inkscape document, look at the XML editor for + # details of how such an element looks in XML, then follow this model. + # layer number n appears in XML as + # + # to create it, use + # Mylayer = self.svg.add(Layer.new('layername')) + # + # group appears in XML as where nnnnn is a number + # + # to create it, use + # Mygroup = parent.add(Group(gcodetools="My group label") + # where parent may be the layer or a parent group. To get the parent group, you can use + # parent = self.selected_paths[layer][0].getparent() + ################################################################################ + def tab_engraving(self): + self.get_info_plus() + global cspm + global wl + global nlLT + global i + global j + global gcode_3Dleft + global gcode_3Dright + global max_dist # minimum of tool radius and user's requested maximum distance + global eye_dist + eye_dist = 100 # 3D constant. Try varying it for your eyes + + def bisect(nxy1, nxy2): + """LT Find angle bisecting the normals n1 and n2 + + Parameters: Normalised normals + Returns: nx - Normal of bisector, normalised to 1/cos(a) + ny - + sinBis2 - sin(angle turned/2): positive if turning in + Note that bisect(n1,n2) and bisect(n2,n1) give opposite sinBis2 results + If sinturn is less than the user's requested angle tolerance, I return 0 + """ + (nx1, ny1) = nxy1 + (nx2, ny2) = nxy2 + cosBis = math.sqrt(max(0, (1.0 + nx1 * nx2 - ny1 * ny2) / 2.0)) + # We can get correct sign of the sin, assuming cos is positive + if (abs(ny1 - ny2) < ENGRAVING_TOLERANCE) or (abs(cosBis) < ENGRAVING_TOLERANCE): + if abs(nx1 - nx2) < ENGRAVING_TOLERANCE: + return nx1, ny1, 0.0 + sinBis = math.copysign(1, ny1) + else: + sinBis = cosBis * (nx2 - nx1) / (ny1 - ny2) + # We can correct signs by noting that the dot product + # of bisector and either normal must be >0 + costurn = cosBis * nx1 + sinBis * ny1 + if costurn == 0: + return ny1 * 100, -nx1 * 100, 1 # Path doubles back on itself + sinturn = sinBis * nx1 - cosBis * ny1 + if costurn < 0: + sinturn = -sinturn + if 0 < sinturn * 114.6 < (180 - self.options.engraving_sharp_angle_tollerance): + sinturn = 0 # set to zero if less than the user wants to see. + return cosBis / costurn, sinBis / costurn, sinturn + # end bisect + + def get_radius_to_line(xy1, n_xy1, n_xy2, xy2, n_xy23, xy3, n_xy3): + """LT find biggest circle we can engrave here, if constrained by line 2-3 + + Parameters: + x1,y1,nx1,ny1 coordinates and normal of the line we're currently engraving + nx2,ny2 angle bisector at point 2 + x2,y2 coordinates of first point of line 2-3 + nx23,ny23 normal to the line 2-3 + x3,y3 coordinates of the other end + nx3,ny3 angle bisector at point 3 + Returns: + radius or self.options.engraving_max_dist if line doesn't limit radius + This function can be used in three ways: + - With nx1=ny1=0 it finds circle centred at x1,y1 + - with nx1,ny1 normalised, it finds circle tangential at x1,y1 + - with nx1,ny1 scaled by 1/cos(a) it finds circle centred on an angle bisector + where a is the angle between the bisector and the previous/next normals + + If the centre of the circle tangential to the line 2-3 is outside the + angle bisectors at its ends, ignore this line. + + Note that it handles corners in the conventional manner of letter cutting + by mitering, not rounding. + Algorithm uses dot products of normals to find radius + and hence coordinates of centre + """ + (x1, y1) = xy1 + (nx1, ny1) = n_xy1 + (nx2, ny2) = n_xy2 + (x2, y2) = xy2 + (nx23, ny23) = n_xy23 + (x3, y3) = xy3 + (nx3, ny3) = n_xy3 + global max_dist + + # Start by converting coordinates to be relative to x1,y1 + x2, y2 = x2 - x1, y2 - y1 + x3, y3 = x3 - x1, y3 - y1 + + # The logic uses vector arithmetic. + # The dot product of two vectors gives the product of their lengths + # multiplied by the cos of the angle between them. + # So, the perpendicular distance from x1y1 to the line 2-3 + # is equal to the dot product of its normal and x2y2 or x3y3 + # It is also equal to the projection of x1y1-xcyc on the line's normal + # plus the radius. But, as the normal faces inside the path we must negate it. + + # Make sure the line in question is facing x1,y1 and vice versa + dist = -x2 * nx23 - y2 * ny23 + if dist < 0: + return max_dist + denom = 1. - nx23 * nx1 - ny23 * ny1 + if denom < ENGRAVING_TOLERANCE: + return max_dist + + # radius and centre are: + r = dist / denom + cx = r * nx1 + cy = r * ny1 + # if c is not between the angle bisectors at the ends of the line, ignore + # Use vector cross products. Not sure if I need the .0001 safety margins: + if (x2 - cx) * ny2 > (y2 - cy) * nx2 + 0.0001: + return max_dist + if (x3 - cx) * ny3 < (y3 - cy) * nx3 - 0.0001: + return max_dist + return min(r, max_dist) + # end of get_radius_to_line + + def get_radius_to_point(xy1, n_xy, xy2): + """LT find biggest circle we can engrave here, constrained by point x2,y2 + + This function can be used in three ways: + - With nx=ny=0 it finds circle centred at x1,y1 + - with nx,ny normalised, it finds circle tangential at x1,y1 + - with nx,ny scaled by 1/cos(a) it finds circle centred on an angle bisector + where a is the angle between the bisector and the previous/next normals + + Note that I wrote this to replace find_cutter_centre. It is far less + sophisticated but, I hope, far faster. + It turns out that finding a circle touching a point is harder than a circle + touching a line. + """ + (x1, y1) = xy1 + (nx, ny) = n_xy + (x2, y2) = xy2 + global max_dist + + # Start by converting coordinates to be relative to x1,y1 + x2 = x2 - x1 + y2 = y2 - y1 + denom = nx ** 2 + ny ** 2 - 1 + if denom <= ENGRAVING_TOLERANCE: # Not a corner bisector + if denom == -1: # Find circle centre x1,y1 + return math.sqrt(x2 ** 2 + y2 ** 2) + # if x2,y2 not in front of the normal... + if x2 * nx + y2 * ny <= 0: + return max_dist + return (x2 ** 2 + y2 ** 2) / (2 * (x2 * nx + y2 * ny)) + # It is a corner bisector, so.. + discriminator = (x2 * nx + y2 * ny) ** 2 - denom * (x2 ** 2 + y2 ** 2) + if discriminator < 0: + return max_dist # this part irrelevant + r = (x2 * nx + y2 * ny - math.sqrt(discriminator)) / denom + return min(r, max_dist) + # end of get_radius_to_point + + def bez_divide(a, b, c, d): + """LT recursively divide a Bezier. + + Divides until difference between each + part and a straight line is less than some limit + Note that, as simple as this code is, it is mathematically correct. + Parameters: + a,b,c and d are each a list of x,y real values + Bezier end points a and d, control points b and c + Returns: + a list of Beziers. + Each Bezier is a list with four members, + each a list holding a coordinate pair + Note that the final point of one member is the same as + the first point of the next, and the control points + there are smooth and symmetrical. I use this fact later. + """ + bx = b[0] - a[0] + by = b[1] - a[1] + cx = c[0] - a[0] + cy = c[1] - a[1] + dx = d[0] - a[0] + dy = d[1] - a[1] + limit = 8 * math.hypot(dx, dy) / self.options.engraving_newton_iterations + # LT This is the only limit we get from the user currently + if abs(dx * by - bx * dy) < limit and abs(dx * cy - cx * dy) < limit: + return [[a, b, c, d]] + abx = (a[0] + b[0]) / 2.0 + aby = (a[1] + b[1]) / 2.0 + bcx = (b[0] + c[0]) / 2.0 + bcy = (b[1] + c[1]) / 2.0 + cdx = (c[0] + d[0]) / 2.0 + cdy = (c[1] + d[1]) / 2.0 + abcx = (abx + bcx) / 2.0 + abcy = (aby + bcy) / 2.0 + bcdx = (bcx + cdx) / 2.0 + bcdy = (bcy + cdy) / 2.0 + m = [(abcx + bcdx) / 2.0, (abcy + bcdy) / 2.0] + return bez_divide(a, [abx, aby], [abcx, abcy], m) + bez_divide(m, [bcdx, bcdy], [cdx, cdy], d) + # end of bez_divide + + def get_biggest(nxy1, nxy2): + """LT Find biggest circle we can draw inside path at point x1,y1 normal nx,ny + + Parameters: + point - either on a line or at a reflex corner + normal - normalised to 1 if on a line, to 1/cos(a) at a corner + Returns: + tuple (j,i,r) + ..where j and i are indices of limiting segment, r is radius + """ + (x1, y1) = nxy1 + (nx, ny) = nxy2 + global max_dist + global nlLT + global i + global j + + n1 = nlLT[j][i - 1] # current node + jjmin = -1 + iimin = -1 + r = max_dist + # set limits within which to look for lines + xmin = x1 + r * nx - r + xmax = x1 + r * nx + r + ymin = y1 + r * ny - r + ymax = y1 + r * ny + r + for jj in xrange(0, len(nlLT)): # for every subpath of this object + for ii in xrange(0, len(nlLT[jj])): # for every point and line + if nlLT[jj][ii - 1][2]: # if a point + if jj == j: # except this one + if abs(ii - i) < 3 or abs(ii - i) > len(nlLT[j]) - 3: + continue + t1 = get_radius_to_point((x1, y1), (nx, ny), nlLT[jj][ii - 1][0]) + else: # doing a line + if jj == j: # except this one + if abs(ii - i) < 2 or abs(ii - i) == len(nlLT[j]) - 1: + continue + if abs(ii - i) == 2 and nlLT[j][(ii + i) / 2 - 1][3] <= 0: + continue + if (abs(ii - i) == len(nlLT[j]) - 2) and nlLT[j][-1][3] <= 0: + continue + nx2, ny2 = nlLT[jj][ii - 2][1] + x2, y2 = nlLT[jj][ii - 1][0] + nx23, ny23 = nlLT[jj][ii - 1][1] + x3, y3 = nlLT[jj][ii][0] + nx3, ny3 = nlLT[jj][ii][1] + if nlLT[jj][ii - 2][3] > 0: # acute, so use normal, not bisector + nx2 = nx23 + ny2 = ny23 + if nlLT[jj][ii][3] > 0: # acute, so use normal, not bisector + nx3 = nx23 + ny3 = ny23 + x23min = min(x2, x3) + x23max = max(x2, x3) + y23min = min(y2, y3) + y23max = max(y2, y3) + # see if line in range + if n1[2] == False and (x23max < xmin or x23min > xmax or y23max < ymin or y23min > ymax): + continue + t1 = get_radius_to_line((x1, y1), (nx, ny), (nx2, ny2), (x2, y2), (nx23, ny23), (x3, y3), (nx3, ny3)) + if 0 <= t1 < r: + r = t1 + iimin = ii + jjmin = jj + xmin = x1 + r * nx - r + xmax = x1 + r * nx + r + ymin = y1 + r * ny - r + ymax = y1 + r * ny + r + # next ii + # next jj + return jjmin, iimin, r + # end of get_biggest + + def line_divide(xy0, j0, i0, xy1, j1, i1, n_xy, length): + """LT recursively divide a line as much as necessary + + NOTE: This function is not currently used + By noting which other path segment is touched by the circles at each end, + we can see if anything is to be gained by a further subdivision, since + if they touch the same bit of path we can move linearly between them. + Also, we can handle points correctly. + Parameters: + end points and indices of limiting path, normal, length + Returns: + list of toolpath points + each a list of 3 reals: x, y coordinates, radius + + """ + (x0, y0) = xy0 + (x1, y1) = xy1 + (nx, ny) = n_xy + global nlLT + global i + global j + global lmin + x2 = (x0 + x1) / 2 + y2 = (y0 + y1) / 2 + j2, i2, r2 = get_biggest((x2, y2), (nx, ny)) + if length < lmin: + return [[x2, y2, r2]] + if j2 == j0 and i2 == i0: # Same as left end. Don't subdivide this part any more + return [[x2, y2, r2], line_divide((x2, y2), j2, i2, (x1, y1), j1, i1, (nx, ny), length / 2)] + if j2 == j1 and i2 == i1: # Same as right end. Don't subdivide this part any more + return [line_divide((x0, y0), j0, i0, (x2, y2), j2, i2, (nx, ny), length / 2), [x2, y2, r2]] + return [line_divide((x0, y0), j0, i0, (x2, y2), j2, i2, (nx, ny), length / 2), line_divide((x2, y2), j2, i2, (x1, y1), j1, i1, (nx, ny), length / 2)] + # end of line_divide() + + def save_point(xy, w, i, j, ii, jj): + """LT Save this point and delete previous one if linear + + The point is, we generate tons of points but many may be in a straight 3D line. + There is no benefit in saving the intermediate points. + """ + (x, y) = xy + global wl + global cspm + x = round(x, 4) # round to 4 decimals + y = round(y, 4) # round to 4 decimals + w = round(w, 4) # round to 4 decimals + if len(cspm) > 1: + xy1a, xy1, xy1b, i1, j1, ii1, jj1 = cspm[-1] + w1 = wl[-1] + if i == i1 and j == j1 and ii == ii1 and jj == jj1: # one match + xy1a, xy2, xy1b, i1, j1, ii1, jj1 = cspm[-2] + w2 = wl[-2] + if i == i1 and j == j1 and ii == ii1 and jj == jj1: # two matches. Now test linearity + length1 = math.hypot(xy1[0] - x, xy1[1] - y) + length2 = math.hypot(xy2[0] - x, xy2[1] - y) + length12 = math.hypot(xy2[0] - xy1[0], xy2[1] - xy1[1]) + # get the xy distance of point 1 from the line 0-2 + if length2 > length1 and length2 > length12: # point 1 between them + xydist = abs((xy2[0] - x) * (xy1[1] - y) - (xy1[0] - x) * (xy2[1] - y)) / length2 + if xydist < ENGRAVING_TOLERANCE: # so far so good + wdist = w2 + (w - w2) * length1 / length2 - w1 + if abs(wdist) < ENGRAVING_TOLERANCE: + cspm.pop() + wl.pop() + cspm += [[[x, y], [x, y], [x, y], i, j, ii, jj]] + wl += [w] + # end of save_point + + def draw_point(xy0, xy, w, t): + """LT Draw this point as a circle with a 1px dot in the middle (x,y) + and a 3D line from (x0,y0) down to x,y. 3D line thickness should be t/2 + + Note that points that are subsequently erased as being unneeded do get + displayed, but this helps the user see the total area covered. + """ + (x0, y0) = xy0 + (x, y) = xy + global gcode_3Dleft + global gcode_3Dright + if self.options.engraving_draw_calculation_paths: + elem = engraving_group.add(PathElement.arc((x, y), 1)) + elem.set('gcodetools', "Engraving calculation toolpath") + elem.style = "fill:#ff00ff; fill-opacity:0.46; stroke:#000000; stroke-width:0.1;" + + # Don't draw zero radius circles + if w: + elem = engraving_group.add(PathElement.arc((x, y), w)) + elem.set('gcodetools', "Engraving calculation paths") + elem.style = "fill:none; fill-opacity:0.46; stroke:#000000; stroke-width:0.1;" + + # Find slope direction for shading + s = math.atan2(y - y0, x - x0) # -pi to pi + # convert to 2 hex digits as a shade of red + s2 = "#{0:x}0000".format(int(101 * (1.5 - math.sin(s + 0.5)))) + style = "stroke:{}; stroke-opacity:1;stroke-width:{};fill:none".format(s2, t/2) + right = gcode_3Dleft.add(PathElement(style=style, gcodetools="Gcode G1R")) + right.path = "M {:f},{:f} L {:f},{:f}".format( + x0 - eye_dist, y0, x - eye_dist - 0.14 * w, y) + left = gcode_3Dright.add(PathElement(style=style, gcodetools="Gcode G1L")) + left.path = "M {:f},{:f} L {:f},{:f}".format( + x0 + eye_dist, y0, x + eye_dist + 0.14 * r, y) + + # end of subfunction definitions. engraving() starts here: + gcode = '' + r = 0 # theoretical and tool-radius-limited radii in pixels + w = 0 + wmax = 0 + cspe = [] + we = [] + if not self.selected_paths: + self.error("Please select at least one path to engrave and run again.") + return + if not self.check_dir(): + return + # Find what units the user uses + unit = " mm" + if self.options.unit == "G20 (All units in inches)": + unit = " inches" + elif self.options.unit != "G21 (All units in mm)": + self.error("Unknown unit selected. mm assumed") + print_("engraving_max_dist mm/inch", self.options.engraving_max_dist) + + # LT See if we can use this parameter for line and Bezier subdivision: + bitlen = 20 / self.options.engraving_newton_iterations + + for layer in self.layers: + if layer in self.selected_paths and layer in self.orientation_points: + # Calculate scale in pixels per user unit (mm or inch) + p1 = self.orientation_points[layer][0][0] + p2 = self.orientation_points[layer][0][1] + ol = math.hypot(p1[0][0] - p2[0][0], p1[0][1] - p2[0][1]) + oluu = math.hypot(p1[1][0] - p2[1][0], p1[1][1] - p2[1][1]) + print_("Orientation2 p1 p2 ol oluu", p1, p2, ol, oluu) + orientation_scale = ol / oluu + + self.set_tool(layer) + shape = self.tools[layer][0]['shape'] + if re.search('w', shape): + toolshape = eval('lambda w: ' + shape.strip('"')) + else: + self.error("Tool '{}' has no shape. 45 degree cone assumed!".format(self.tools[layer][0]['name'])) + toolshape = lambda w: w + # Get tool radius in pixels + toolr = self.tools[layer][0]['diameter'] * orientation_scale / 2 + print_("tool radius in pixels=", toolr) + # max dist from path to engrave in user's units + max_distuu = min(self.tools[layer][0]['diameter'] / 2, self.options.engraving_max_dist) + max_dist = max_distuu * orientation_scale + print_("max_dist pixels", max_dist) + + engraving_group = self.selected_paths[layer][0].getparent().add(Group()) + if self.options.engraving_draw_calculation_paths and (self.my3Dlayer is None): + self.svg.add(Layer.new("3D")) + # Create groups for left and right eyes + if self.options.engraving_draw_calculation_paths: + gcode_3Dleft = self.my3Dlayer.add(Group(gcodetools="Gcode 3D L")) + gcode_3Dright = self.my3Dlayer.add(Group(gcodetools="Gcode 3D R")) + + for node in self.selected_paths[layer]: + if isinstance(node, inkex.PathElement): + cspi = node.path.to_superpath() + # LT: Create my own list. n1LT[j] is for subpath j + nlLT = [] + for j in xrange(len(cspi)): # LT For each subpath... + # Remove zero length segments, assume closed path + i = 0 # LT was from i=1 + while i < len(cspi[j]): + if abs(cspi[j][i - 1][1][0] - cspi[j][i][1][0]) < ENGRAVING_TOLERANCE and abs(cspi[j][i - 1][1][1] - cspi[j][i][1][1]) < ENGRAVING_TOLERANCE: + cspi[j][i - 1][2] = cspi[j][i][2] + del cspi[j][i] + else: + i += 1 + for csp in cspi: # LT6a For each subpath... + # Create copies in 3D layer + print_("csp is zz ", csp) + cspl = [] + cspr = [] + # create list containing lines and points, starting with a point + # line members: [x,y],[nx,ny],False,i + # x,y is start of line. Normal on engraved side. + # Normal is normalised (unit length) + # Note that Y axis increases down the page + # corner members: [x,y],[nx,ny],True,sin(halfangle) + # if halfangle>0: radius 0 here. normal is bisector + # if halfangle<0. reflex angle. normal is bisector + # corner normals are divided by cos(halfangle) + # so that they will engrave correctly + print_("csp is", csp) + nlLT.append([]) + for i in range(0, len(csp)): # LT for each point + sp0 = csp[i - 2] + sp1 = csp[i - 1] + sp2 = csp[i] + if self.options.engraving_draw_calculation_paths: + # Copy it to 3D layer objects + spl = [] + spr = [] + for j in range(0, 3): + pl = [sp2[j][0] - eye_dist, sp2[j][1]] + pr = [sp2[j][0] + eye_dist, sp2[j][1]] + spl += [pl] + spr += [pr] + cspl += [spl] + cspr += [spr] + # LT find angle between this and previous segment + x0, y0 = sp1[1] + nx1, ny1 = csp_normalized_normal(sp1, sp2, 0) + # I don't trust this function, so test result + if abs(1 - math.hypot(nx1, ny1)) > 0.00001: + print_("csp_normalised_normal error t=0", nx1, ny1, sp1, sp2) + self.error("csp_normalised_normal error. See log.") + + nx0, ny0 = csp_normalized_normal(sp0, sp1, 1) + if abs(1 - math.hypot(nx0, ny0)) > 0.00001: + print_("csp_normalised_normal error t=1", nx0, ny0, sp1, sp2) + self.error("csp_normalised_normal error. See log.") + bx, by, s = bisect((nx0, ny0), (nx1, ny1)) + # record x,y,normal,ifCorner, sin(angle-turned/2) + nlLT[-1] += [[[x0, y0], [bx, by], True, s]] + + # LT now do the line + if sp1[1] == sp1[2] and sp2[0] == sp2[1]: # straightline + nlLT[-1] += [[sp1[1], [nx1, ny1], False, i]] + else: # Bezier. First, recursively cut it up: + nn = bez_divide(sp1[1], sp1[2], sp2[0], sp2[1]) + first = True # Flag entry to divided Bezier + for bLT in nn: # save as two line segments + for seg in range(3): + if seg > 0 or first: + nx1 = bLT[seg][1] - bLT[seg + 1][1] + ny1 = bLT[seg + 1][0] - bLT[seg][0] + l1 = math.hypot(nx1, ny1) + if l1 < ENGRAVING_TOLERANCE: + continue + nx1 = nx1 / l1 # normalise them + ny1 = ny1 / l1 + nlLT[-1] += [[bLT[seg], [nx1, ny1], False, i]] + first = False + if seg < 2: # get outgoing bisector + nx0 = nx1 + ny0 = ny1 + nx1 = bLT[seg + 1][1] - bLT[seg + 2][1] + ny1 = bLT[seg + 2][0] - bLT[seg + 1][0] + l1 = math.hypot(nx1, ny1) + if l1 < ENGRAVING_TOLERANCE: + continue + nx1 = nx1 / l1 # normalise them + ny1 = ny1 / l1 + # bisect + bx, by, s = bisect((nx0, ny0), (nx1, ny1)) + nlLT[-1] += [[bLT[seg + 1], [bx, by], True, 0.]] + # LT for each segment - ends here. + print_(("engraving_draw_calculation_paths=", self.options.engraving_draw_calculation_paths)) + if self.options.engraving_draw_calculation_paths: + # Copy complete paths to 3D layer + cspl += [cspl[0]] # Close paths + cspr += [cspr[0]] # Close paths + style = "stroke:#808080; stroke-opacity:1; stroke-width:0.6; fill:none" + elem = gcode_3Dleft.add(PathElement(style=style, gcodetools="G1L outline")) + elem.path = CubicSuperPath([cspl]) + elem = gcode_3Dright.add(Pathelement(style=style, gcodetools="G1R outline")) + elem.path = CubicSuperPath([cspr]) + + for p in nlLT[-1]: # For last sub-path + if p[2]: + elem = engraving_group.add(PathElement(gcodetools="Engraving normals")) + elem.path = "M {:f},{:f} L {:f},{:f}".format(p[0][0], p[0][1], + p[0][0] + p[1][0] * 10, p[0][1] + p[1][1] * 10) + elem.style = "stroke:#f000af; stroke-opacity:0.46; stroke-width:0.1; fill:none" + else: + elem = engraving_group.add(PathElement(gcodetools="Engraving bisectors")) + elem.path = "M {:f},{:f} L {:f},{:f}".format(p[0][0], p[0][1], + p[0][0] + p[1][0] * 10, p[0][1] + p[1][1] * 10) + elem.style = "stroke:#0000ff; stroke-opacity:0.46; stroke-width:0.1; fill:none" + + # LT6a build nlLT[j] for each subpath - ends here + # Calculate offset points + reflex = False + for j in xrange(len(nlLT)): # LT6b for each subpath + cspm = [] # Will be my output. List of csps. + wl = [] # Will be my w output list + w = r = 0 # LT initial, as first point is an angle + for i in xrange(len(nlLT[j])): # LT for each node + # LT Note: Python enables wrapping of array indices + # backwards to -1, -2, but not forwards. Hence: + n0 = nlLT[j][i - 2] # previous node + n1 = nlLT[j][i - 1] # current node + n2 = nlLT[j][i] # next node + # if n1[2] == True and n1[3]==0 : # A straight angle + # continue + x1a, y1a = n1[0] # this point/start of this line + nx, ny = n1[1] + x1b, y1b = n2[0] # next point/end of this line + if n1[2]: # We're at a corner + bits = 1 + bit0 = 0 + # lastr=r #Remember r from last line + lastw = w # Remember w from last line + w = max_dist + if n1[3] > 0: # acute. Limit radius + len1 = math.hypot((n0[0][0] - n1[0][0]), (n0[0][1] - n1[0][1])) + if i < (len(nlLT[j]) - 1): + len2 = math.hypot((nlLT[j][i + 1][0][0] - n1[0][0]), (nlLT[j][i + 1][0][1] - n1[0][1])) + else: + len2 = math.hypot((nlLT[j][0][0][0] - n1[0][0]), (nlLT[j][0][0][1] - n1[0][1])) + # set initial r value, not to be exceeded + w = math.sqrt(min(len1, len2)) / n1[3] + else: # line. Cut it up if long. + if n0[3] > 0 and not self.options.engraving_draw_calculation_paths: + bit0 = r * n0[3] # after acute corner + else: + bit0 = 0.0 + length = math.hypot((x1b - x1a), (y1a - y1b)) + bit0 = (min(length, bit0)) + bits = int((length - bit0) / bitlen) + # split excess evenly at both ends + bit0 += (length - bit0 - bitlen * bits) / 2 + for b in xrange(bits): # divide line into bits + x1 = x1a + ny * (b * bitlen + bit0) + y1 = y1a - nx * (b * bitlen + bit0) + jjmin, iimin, w = get_biggest((x1, y1), (nx, ny)) + print_("i,j,jjmin,iimin,w", i, j, jjmin, iimin, w) + wmax = max(wmax, w) + if reflex: # just after a reflex corner + reflex = False + if w < lastw: # need to adjust it + draw_point((x1, y1), (n0[0][0] + n0[1][0] * w, n0[0][1] + n0[1][1] * w), w, (lastw - w) / 2) + save_point((n0[0][0] + n0[1][0] * w, n0[0][1] + n0[1][1] * w), w, i, j, iimin, jjmin) + if n1[2]: # We're at a corner + if n1[3] > 0: # acute + save_point((x1 + nx * w, y1 + ny * w), w, i, j, iimin, jjmin) + draw_point((x1, y1), (x1, y1), 0, 0) + save_point((x1, y1), 0, i, j, iimin, jjmin) + elif n1[3] < 0: # reflex + if w > lastw: + draw_point((x1, y1), (x1 + nx * lastw, y1 + ny * lastw), w, (w - lastw) / 2) + wmax = max(wmax, w) + save_point((x1 + nx * w, y1 + ny * w), w, i, j, iimin, jjmin) + elif b > 0 and n2[3] > 0 and not self.options.engraving_draw_calculation_paths: # acute corner coming up + if jjmin == j and iimin == i + 2: + break + draw_point((x1, y1), (x1 + nx * w, y1 + ny * w), w, bitlen) + save_point((x1 + nx * w, y1 + ny * w), w, i, j, iimin, jjmin) + + # LT end of for each bit of this line + if n1[2] == True and n1[3] < 0: # reflex angle + reflex = True + lastw = w # remember this w + # LT next i + cspm += [cspm[0]] + print_("cspm", cspm) + wl += [wl[0]] + print_("wl", wl) + # Note: Original csp_points was a list, each element + # being 4 points, with the first being the same as the + # last of the previous set. + # Each point is a list of [cx,cy,r,w] + # I have flattened it to a flat list of points. + + if self.options.engraving_draw_calculation_paths: + node = engraving_group.add(PathElement( + gcodetools="Engraving calculation paths", + style=MARKER_STYLE["biarc_style_i"]['biarc1'])) + node.path = CubicSuperPath([cspm]) + for i in xrange(len(cspm)): + elem = engraving_group.add(PathElement.arc(cspm[i][1], wl[i])) + elem.set('gcodetools', "Engraving calculation paths") + elem.style = "fill:none;fill-opacity:0.46;stroke:#000000;stroke-width:0.1;" + cspe += [cspm] + wluu = [] # width list in user units: mm/inches + for w in wl: + wluu += [w / orientation_scale] + print_("wl in pixels", wl) + print_("wl in user units", wluu) + # LT previously, we was in pixels so gave wrong depth + we += [wluu] + # LT6b For each subpath - ends here + # LT5 if it is a path - ends here + # LT4 for each selected object in this layer - ends here + + if cspe: + curve = self.parse_curve(cspe, layer, we, toolshape) # convert to lines + self.draw_curve(curve, layer, engraving_group) + gcode += self.generate_gcode(curve, layer, self.options.Zsurface) + + # LT3 for layers loop ends here + if gcode != '': + self.header += "(Tool diameter should be at least " + str(2 * wmax / orientation_scale) + unit + ")\n" + self.header += "(Depth, as a function of radius w, must be " + self.tools[layer][0]['shape'] + ")\n" + self.header += "(Rapid feeds use safe Z=" + str(self.options.Zsafe) + unit + ")\n" + self.header += "(Material surface at Z=" + str(self.options.Zsurface) + unit + ")\n" + self.export_gcode(gcode) + else: + self.error("No need to engrave sharp angles.") + + ################################################################################ + # + # Orientation + # + ################################################################################ + def tab_orientation(self, layer=None): + self.get_info() + + if layer is None: + layer = self.svg.get_current_layer() if self.svg.get_current_layer() is not None else self.document.getroot() + + transform = self.get_transforms(layer) + if transform: + transform = self.reverse_transform(transform) + transform = str(Transform(transform)) + + if self.options.orientation_points_count == "graffiti": + print_(self.graffiti_reference_points) + print_("Inserting graffiti points") + if layer in self.graffiti_reference_points: + graffiti_reference_points_count = len(self.graffiti_reference_points[layer]) + else: + graffiti_reference_points_count = 0 + axis = ["X", "Y", "Z", "A"][graffiti_reference_points_count % 4] + attr = {'gcodetools': "Gcodetools graffiti reference point"} + if transform: + attr["transform"] = transform + group = layer.add(Group(**attr)) + elem = group.add(PathElement(style="stroke:none;fill:#00ff00;")) + elem.set('gcodetools', "Gcodetools graffiti reference point arrow") + elem.path = 'm {},{} 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,'\ + '-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.8125000000'\ + '01 z z'.format(graffiti_reference_points_count * 100, 0) + + draw_text(axis, graffiti_reference_points_count * 100 + 10, -10, group=g, gcodetools_tag="Gcodetools graffiti reference point text") + + elif self.options.orientation_points_count == "in-out reference point": + draw_pointer(group=self.svg.get_current_layer(), x=self.svg.namedview.center, figure="arrow", pointer_type="In-out reference point", text="In-out point") + + else: + print_("Inserting orientation points") + + if layer in self.orientation_points: + self.error("Active layer already has orientation points! Remove them or select another layer!", "error") + + attr = {"gcodetools": "Gcodetools orientation group"} + if transform: + attr["transform"] = transform + + orientation_group = layer.add(Group(**attr)) + doc_height = self.svg.unittouu(self.document.getroot().get('height')) + if self.document.getroot().get('height') == "100%": + doc_height = 1052.3622047 + print_("Overriding height from 100 percents to {}".format(doc_height)) + if self.options.unit == "G21 (All units in mm)": + points = [[0., 0., self.options.Zsurface], [100., 0., self.options.Zdepth], [0., 100., 0.]] + elif self.options.unit == "G20 (All units in inches)": + points = [[0., 0., self.options.Zsurface], [5., 0., self.options.Zdepth], [0., 5., 0.]] + if self.options.orientation_points_count == "2": + points = points[:2] + for i in points: + name = "Gcodetools orientation point ({} points)".format( + self.options.orientation_points_count) + grp = orientation_group.add(Group(gcodetools=name)) + elem = grp.add(PathElement(style="stroke:none;fill:#000000;")) + elem.set('gcodetools', "Gcodetools orientation point arrow") + elem.path = 'm {},{} 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,'\ + '-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000'\ + '001 z'.format(i[0], -i[1] + doc_height) + + draw_text("({}; {}; {})".format(i[0], i[1], i[2]), (i[0] + 10), (-i[1] - 10 + doc_height), group=grp, gcodetools_tag="Gcodetools orientation point text") + + ################################################################################ + # + # Tools library + # + ################################################################################ + def tab_tools_library(self, layer=None): + self.get_info() + + if self.options.tools_library_type == "check": + return self.check_tools_and_op() + + # Add a tool to the drawing + if layer is None: + layer = self.svg.get_current_layer() if self.svg.get_current_layer() is not None else self.document.getroot() + if layer in self.tools: + self.error("Active layer already has a tool! Remove it or select another layer!", "error") + + if self.options.tools_library_type == "cylinder cutter": + tool = { + "name": "Cylindrical cutter", + "id": "Cylindrical cutter 0001", + "diameter": 10, + "penetration angle": 90, + "feed": "400", + "penetration feed": "100", + "depth step": "1", + "tool change gcode": " " + } + elif self.options.tools_library_type == "lathe cutter": + tool = { + "name": "Lathe cutter", + "id": "Lathe cutter 0001", + "diameter": 10, + "penetration angle": 90, + "feed": "400", + "passing feed": "800", + "fine feed": "100", + "penetration feed": "100", + "depth step": "1", + "tool change gcode": " " + } + elif self.options.tools_library_type == "cone cutter": + tool = { + "name": "Cone cutter", + "id": "Cone cutter 0001", + "diameter": 10, + "shape": "w", + "feed": "400", + "penetration feed": "100", + "depth step": "1", + "tool change gcode": " " + } + elif self.options.tools_library_type == "tangent knife": + tool = { + "name": "Tangent knife", + "id": "Tangent knife 0001", + "feed": "400", + "penetration feed": "100", + "depth step": "100", + "4th axis meaning": "tangent knife", + "4th axis scale": 1., + "4th axis offset": 0, + "tool change gcode": " " + } + + elif self.options.tools_library_type == "plasma cutter": + tool = { + "name": "Plasma cutter", + "id": "Plasma cutter 0001", + "diameter": 10, + "penetration feed": 100, + "feed": 400, + "gcode before path": """G31 Z-100 F500 (find metal) +G92 Z0 (zero z) +G00 Z10 F500 (going up) +M03 (turn on plasma) +G04 P0.2 (pause) +G01 Z1 (going to cutting z)\n""", + "gcode after path": "M05 (turn off plasma)\n", + } + elif self.options.tools_library_type == "graffiti": + tool = { + "name": "Graffiti", + "id": "Graffiti 0001", + "diameter": 10, + "penetration feed": 100, + "feed": 400, + "gcode before path": """M03 S1(Turn spray on)\n """, + "gcode after path": "M05 (Turn spray off)\n ", + "tool change gcode": "(Add G00 here to change sprayer if needed)\n", + + } + + else: + tool = self.default_tool + + tool_num = sum([len(self.tools[i]) for i in self.tools]) + colors = ["00ff00", "0000ff", "ff0000", "fefe00", "00fefe", "fe00fe", "fe7e00", "7efe00", "00fe7e", "007efe", "7e00fe", "fe007e"] + + tools_group = layer.add(Group(gcodetools="Gcodetools tool definition")) + bg = tools_group.add(PathElement(gcodetools="Gcodetools tool background")) + bg.style = "fill-opacity:0.5;stroke:#444444;" + bg.style['fill'] = colors[tool_num % len(colors)] + + y = 0 + keys = [] + for key in self.tools_field_order: + if key in tool: + keys += [key] + for key in tool: + if key not in keys: + keys += [key] + for key in keys: + g = tools_group.add(Group(gcodetools="Gcodetools tool parameter")) + draw_text(key, 0, y, group=g, gcodetools_tag="Gcodetools tool definition field name", font_size=10 if key != 'name' else 20) + param = tool[key] + if type(param) == str and re.match("^\\s*$", param): + param = "(None)" + draw_text(param, 150, y, group=g, gcodetools_tag="Gcodetools tool definition field value", font_size=10 if key != 'name' else 20) + v = str(param).split("\n") + y += 15 * len(v) if key != 'name' else 20 * len(v) + + bg.set('d', "m -20,-20 l 400,0 0,{:f} -400,0 z ".format(y + 50)) + tools_group.transform.add_translate(*self.svg.namedview.center) + tools_group.transform.add_translate(-150, 0) + + ################################################################################ + # + # Check tools and OP assignment + # + ################################################################################ + def check_tools_and_op(self): + if len(self.svg.selected) <= 0: + self.error("Selection is empty! Will compute whole drawing.") + paths = self.paths + else: + paths = self.selected_paths + # Set group + parent = self.selected_paths.keys()[0] if len(self.selected_paths.keys()) > 0 else self.layers[0] + group = parent.add(Group()) + trans_ = [[1, 0.3, 0], [0, 0.5, 0]] + + self.set_markers() + + bounds = [float('inf'), float('inf'), float('-inf'), float('-inf')] + tools_bounds = {} + for layer in self.layers: + if layer in paths: + self.set_tool(layer) + tool = self.tools[layer][0] + tools_bounds[layer] = tools_bounds[layer] if layer in tools_bounds else [float("inf"), float("-inf")] + for path in paths[layer]: + group.insert(0, PathElement(**path.attrib)) + new = group.getchildren()[0] + new.style = Style( + stroke='#000044', stroke_width=1, + marker_mid='url(#CheckToolsAndOPMarker)', + fill=tool["style"].get('fill', '#00ff00'), + fill_opacity=tool["style"].get('fill-opacity', 0.5)) + + trans = trans_ * self.get_transforms(path) + csp = path.path.transform(trans).to_superpath() + + path_bounds = csp_simple_bound(csp) + trans = str(Transform(trans)) + bounds = [min(bounds[0], path_bounds[0]), min(bounds[1], path_bounds[1]), max(bounds[2], path_bounds[2]), max(bounds[3], path_bounds[3])] + tools_bounds[layer] = [min(tools_bounds[layer][0], path_bounds[1]), max(tools_bounds[layer][1], path_bounds[3])] + + new.set("transform", trans) + trans_[1][2] += 20 + trans_[1][2] += 100 + + for layer in self.layers: + if layer in self.tools: + if layer in tools_bounds: + tool = self.tools[layer][0] + g = copy.deepcopy(tool["self_group"]) + g.attrib["gcodetools"] = "Check tools and OP assignment" + trans = [[1, 0.3, bounds[2]], [0, 0.5, tools_bounds[layer][0]]] + g.set("transform", str(Transform(trans))) + group.insert(0, g) + + ################################################################################ + # TODO Launch browser on help tab + ################################################################################ + def tab_help(self): + self.error("Tutorials, manuals and support can be found at\n" + " English support forum:\n" + " http://www.cnc-club.ru/gcodetools\n" + "and Russian support forum:\n" + " http://www.cnc-club.ru/gcodetoolsru") + return + + def tab_about(self): + return self.tab_help() + + def tab_preferences(self): + return self.tab_help() + + ################################################################################ + # Lathe + ################################################################################ + def generate_lathe_gcode(self, subpath, layer, feed_type): + if len(subpath) < 2: + return "" + feed = " F {:f}".format(self.tool[feed_type]) + x = self.options.lathe_x_axis_remap + z = self.options.lathe_z_axis_remap + flip_angle = -1 if x.lower() + z.lower() in ["xz", "yx", "zy"] else 1 + alias = {"X": "I", "Y": "J", "Z": "K", "x": "i", "y": "j", "z": "k"} + i_ = alias[x] + k_ = alias[z] + c = [[subpath[0][1], "move", 0, 0, 0]] + for sp1, sp2 in zip(subpath, subpath[1:]): + c += biarc(sp1, sp2, 0, 0) + for i in range(1, len(c)): # Just in case check end point of each segment + c[i - 1][4] = c[i][0][:] + c += [[subpath[-1][1], "end", 0, 0, 0]] + self.draw_curve(c, layer, style=MARKER_STYLE["biarc_style_lathe_{}".format(feed_type)]) + + gcode = ("G01 {} {:f} {} {:f}".format(x, c[0][4][0], z, c[0][4][1])) + feed + "\n" # Just in case move to the start... + for s in c: + if s[1] == 'line': + gcode += ("G01 {} {:f} {} {:f}".format(x, s[4][0], z, s[4][1])) + feed + "\n" + elif s[1] == 'arc': + r = [(s[2][0] - s[0][0]), (s[2][1] - s[0][1])] + if (r[0] ** 2 + r[1] ** 2) > self.options.min_arc_radius ** 2: + r1 = (P(s[0]) - P(s[2])) + r2 = (P(s[4]) - P(s[2])) + if abs(r1.mag() - r2.mag()) < 0.001: + gcode += ("G02" if s[3] * flip_angle < 0 else "G03") + (" {} {:f} {} {:f} {} {:f} {} {:f}".format(x, s[4][0], z, s[4][1], i_, (s[2][0] - s[0][0]), k_, (s[2][1] - s[0][1]))) + feed + "\n" + else: + r = (r1.mag() + r2.mag()) / 2 + gcode += ("G02" if s[3] * flip_angle < 0 else "G03") + (" {} {:f} {} {:f}".format(x, s[4][0], z, s[4][1])) + " R{:f}".format(r) + feed + "\n" + return gcode + + def tab_lathe(self): + self.get_info_plus() + if not self.check_dir(): + return + x = self.options.lathe_x_axis_remap + z = self.options.lathe_z_axis_remap + x = re.sub("^\\s*([XYZxyz])\\s*$", r"\1", x) + z = re.sub("^\\s*([XYZxyz])\\s*$", r"\1", z) + if x not in ["X", "Y", "Z", "x", "y", "z"] or z not in ["X", "Y", "Z", "x", "y", "z"]: + self.error("Lathe X and Z axis remap should be 'X', 'Y' or 'Z'. Exiting...") + return + if x.lower() == z.lower(): + self.error("Lathe X and Z axis remap should be the same. Exiting...") + return + if x.lower() + z.lower() in ["xy", "yx"]: + gcode_plane_selection = "G17 (Using XY plane)\n" + if x.lower() + z.lower() in ["xz", "zx"]: + gcode_plane_selection = "G18 (Using XZ plane)\n" + if x.lower() + z.lower() in ["zy", "yz"]: + gcode_plane_selection = "G19 (Using YZ plane)\n" + self.options.lathe_x_axis_remap = x + self.options.lathe_z_axis_remap = z + + paths = self.selected_paths + self.tool = [] + gcode = "" + for layer in self.layers: + if layer in paths: + self.set_tool(layer) + if self.tool != self.tools[layer][0]: + self.tool = self.tools[layer][0] + self.tool["passing feed"] = float(self.tool["passing feed"] if "passing feed" in self.tool else self.tool["feed"]) + self.tool["feed"] = float(self.tool["feed"]) + self.tool["fine feed"] = float(self.tool["fine feed"] if "fine feed" in self.tool else self.tool["feed"]) + gcode += ("(Change tool to {})\n".format(re.sub("\"'\\(\\)\\\\", " ", self.tool["name"]))) + self.tool["tool change gcode"] + "\n" + + for path in paths[layer]: + csp = self.transform_csp(path.path.to_superpath(), layer) + + for subpath in csp: + # Offset the path if fine cut is defined. + fine_cut = subpath[:] + if self.options.lathe_fine_cut_width > 0: + r = self.options.lathe_fine_cut_width + if self.options.lathe_create_fine_cut_using == "Move path": + subpath = [[[i2[0], i2[1] + r] for i2 in i1] for i1 in subpath] + else: + # Close the path to make offset correct + bound = csp_simple_bound([subpath]) + minx, miny, maxx, maxy = csp_true_bounds([subpath]) + offsetted_subpath = csp_subpath_line_to(subpath[:], [[subpath[-1][1][0], miny[1] - r * 10], [subpath[0][1][0], miny[1] - r * 10], [subpath[0][1][0], subpath[0][1][1]]]) + left = subpath[-1][1][0] + right = subpath[0][1][0] + if left > right: + left, right = right, left + offsetted_subpath = csp_offset([offsetted_subpath], r if not csp_subpath_ccw(offsetted_subpath) else -r) + offsetted_subpath = csp_clip_by_line(offsetted_subpath, [left, 10], [left, 0]) + offsetted_subpath = csp_clip_by_line(offsetted_subpath, [right, 0], [right, 10]) + offsetted_subpath = csp_clip_by_line(offsetted_subpath, [0, miny[1] - r], [10, miny[1] - r]) + # Join offsetted_subpath together + # Hope there won't be any circles + subpath = csp_join_subpaths(offsetted_subpath)[0] + + # Create solid object from path and lathe_width + bound = csp_simple_bound([subpath]) + top_start = [subpath[0][1][0], self.options.lathe_width + self.options.Zsafe + self.options.lathe_fine_cut_width] + top_end = [subpath[-1][1][0], self.options.lathe_width + self.options.Zsafe + self.options.lathe_fine_cut_width] + + gcode += ("G01 {} {:f} F {:f} \n".format(z, top_start[1], self.tool["passing feed"])) + gcode += ("G01 {} {:f} {} {:f} F {:f} \n".format(x, top_start[0], z, top_start[1], self.tool["passing feed"])) + + subpath = csp_concat_subpaths(csp_subpath_line_to([], [top_start, subpath[0][1]]), subpath) + subpath = csp_subpath_line_to(subpath, [top_end, top_start]) + + width = max(0, self.options.lathe_width - max(0, bound[1])) + step = self.tool['depth step'] + steps = int(math.ceil(width / step)) + for i in range(steps + 1): + current_width = self.options.lathe_width - step * i + intersections = [] + for j in range(1, len(subpath)): + sp1 = subpath[j - 1] + sp2 = subpath[j] + intersections += [[j, k] for k in csp_line_intersection([bound[0] - 10, current_width], [bound[2] + 10, current_width], sp1, sp2)] + intersections += [[j, k] for k in csp_line_intersection([bound[0] - 10, current_width + step], [bound[2] + 10, current_width + step], sp1, sp2)] + parts = csp_subpath_split_by_points(subpath, intersections) + for part in parts: + minx, miny, maxx, maxy = csp_true_bounds([part]) + y = (maxy[1] + miny[1]) / 2 + if y > current_width + step: + gcode += self.generate_lathe_gcode(part, layer, "passing feed") + elif current_width <= y <= current_width + step: + gcode += self.generate_lathe_gcode(part, layer, "feed") + else: + # full step cut + part = csp_subpath_line_to([], [part[0][1], part[-1][1]]) + gcode += self.generate_lathe_gcode(part, layer, "feed") + + top_start = [fine_cut[0][1][0], self.options.lathe_width + self.options.Zsafe + self.options.lathe_fine_cut_width] + top_end = [fine_cut[-1][1][0], self.options.lathe_width + self.options.Zsafe + self.options.lathe_fine_cut_width] + gcode += "\n(Fine cutting start)\n(Calculating fine cut using {})\n".format(self.options.lathe_create_fine_cut_using) + for i in range(int(self.options.lathe_fine_cut_count)): + width = self.options.lathe_fine_cut_width * (1 - float(i + 1) / self.options.lathe_fine_cut_count) + if width == 0: + current_pass = fine_cut + else: + if self.options.lathe_create_fine_cut_using == "Move path": + current_pass = [[[i2[0], i2[1] + width] for i2 in i1] for i1 in fine_cut] + else: + minx, miny, maxx, maxy = csp_true_bounds([fine_cut]) + offsetted_subpath = csp_subpath_line_to(fine_cut[:], [[fine_cut[-1][1][0], miny[1] - r * 10], [fine_cut[0][1][0], miny[1] - r * 10], [fine_cut[0][1][0], fine_cut[0][1][1]]]) + left = fine_cut[-1][1][0] + right = fine_cut[0][1][0] + if left > right: + left, right = right, left + offsetted_subpath = csp_offset([offsetted_subpath], width if not csp_subpath_ccw(offsetted_subpath) else -width) + offsetted_subpath = csp_clip_by_line(offsetted_subpath, [left, 10], [left, 0]) + offsetted_subpath = csp_clip_by_line(offsetted_subpath, [right, 0], [right, 10]) + offsetted_subpath = csp_clip_by_line(offsetted_subpath, [0, miny[1] - r], [10, miny[1] - r]) + current_pass = csp_join_subpaths(offsetted_subpath)[0] + + gcode += "\n(Fine cut {:d}-th cicle start)\n".format(i + 1) + gcode += ("G01 {} {:f} {} {:f} F {:f} \n".format(x, top_start[0], z, top_start[1], self.tool["passing feed"])) + gcode += ("G01 {} {:f} {} {:f} F {:f} \n".format(x, current_pass[0][1][0], z, current_pass[0][1][1] + self.options.lathe_fine_cut_width, self.tool["passing feed"])) + gcode += ("G01 {} {:f} {} {:f} F {:f} \n".format(x, current_pass[0][1][0], z, current_pass[0][1][1], self.tool["fine feed"])) + + gcode += self.generate_lathe_gcode(current_pass, layer, "fine feed") + gcode += ("G01 {} {:f} F {:f} \n".format(z, top_start[1], self.tool["passing feed"])) + gcode += ("G01 {} {:f} {} {:f} F {:f} \n".format(x, top_start[0], z, top_start[1], self.tool["passing feed"])) + + self.export_gcode(gcode) + + ################################################################################ + # + # Lathe modify path + # Modifies path to fit current cutter. As for now straight rect cutter. + # + ################################################################################ + + def tab_lathe_modify_path(self): + self.get_info() + if self.selected_paths == {} and self.options.auto_select_paths: + paths = self.paths + self.error("No paths are selected! Trying to work on all available paths.") + else: + paths = self.selected_paths + + for layer in self.layers: + if layer in paths: + width = self.options.lathe_rectangular_cutter_width + for path in paths[layer]: + csp = self.transform_csp(path.path.to_superpath(), layer) + new_csp = [] + for subpath in csp: + orientation = subpath[-1][1][0] > subpath[0][1][0] + new_subpath = [] + + # Split segment at x' and y' == 0 + for sp1, sp2 in zip(subpath[:], subpath[1:]): + ax, ay, bx, by, cx, cy, dx, dy = csp_parameterize(sp1, sp2) + roots = cubic_solver_real(0, 3 * ax, 2 * bx, cx) + roots += cubic_solver_real(0, 3 * ay, 2 * by, cy) + new_subpath = csp_concat_subpaths(new_subpath, csp_seg_split(sp1, sp2, roots)) + subpath = new_subpath + new_subpath = [] + first_seg = True + for sp1, sp2 in zip(subpath[:], subpath[1:]): + n = csp_normalized_normal(sp1, sp2, 0) + a = math.atan2(n[0], n[1]) + if a == 0 or a == math.pi: + n = csp_normalized_normal(sp1, sp2, 1) + a = math.atan2(n[0], n[1]) + if a != 0 and a != math.pi: + o = 0 if 0 < a <= math.pi / 2 or -math.pi < a < -math.pi / 2 else 1 + if not orientation: + o = 1 - o + + # Add first horizontal straight line if needed + if not first_seg and new_subpath == []: + new_subpath = [[[subpath[0][i][0] - width * o, subpath[0][i][1]] for i in range(3)]] + + new_subpath = csp_concat_subpaths( + new_subpath, + [ + [[sp1[i][0] - width * o, sp1[i][1]] for i in range(3)], + [[sp2[i][0] - width * o, sp2[i][1]] for i in range(3)] + ] + ) + first_seg = False + + # Add last horizontal straight line if needed + if a == 0 or a == math.pi: + new_subpath += [[[subpath[-1][i][0] - width * o, subpath[-1][i][1]] for i in range(3)]] + + new_csp += [new_subpath] + self.draw_csp(new_csp, layer) + + ################################################################################ + # Graffiti function generates Gcode for graffiti drawer + ################################################################################ + def tab_graffiti(self): + self.get_info_plus() + # Get reference points. + + def get_gcode_coordinates(point, layer): + gcode = '' + pos = [] + for ref_point in self.graffiti_reference_points[layer]: + c = math.sqrt((point[0] - ref_point[0][0]) ** 2 + (point[1] - ref_point[0][1]) ** 2) + gcode += " {} {:f}".format(ref_point[1], c) + pos += [c] + return pos, gcode + + def graffiti_preview_draw_point(x1, y1, color, radius=.5): + self.graffiti_preview = self.graffiti_preview + r, g, b, a_ = color + for x in range(int(x1 - 1 - math.ceil(radius)), int(x1 + 1 + math.ceil(radius) + 1)): + for y in range(int(y1 - 1 - math.ceil(radius)), int(y1 + 1 + math.ceil(radius) + 1)): + if x >= 0 and y >= 0 and y < len(self.graffiti_preview) and x * 4 < len(self.graffiti_preview[0]): + d = math.sqrt((x1 - x) ** 2 + (y1 - y) ** 2) + a = float(a_) * (max(0, (1 - (d - radius))) if d > radius else 1) / 256 + self.graffiti_preview[y][x * 4] = int(r * a + (1 - a) * self.graffiti_preview[y][x * 4]) + self.graffiti_preview[y][x * 4 + 1] = int(g * a + (1 - a) * self.graffiti_preview[y][x * 4 + 1]) + self.graffiti_preview[y][x * 4 + 2] = int(g * b + (1 - a) * self.graffiti_preview[y][x * 4 + 2]) + self.graffiti_preview[y][x * 4 + 3] = min(255, int(self.graffiti_preview[y][x * 4 + 3] + a * 256)) + + def graffiti_preview_transform(x, y): + tr = self.graffiti_preview_transform + d = max(tr[2] - tr[0] + 2, tr[3] - tr[1] + 2) + return [(x - tr[0] + 1) * self.options.graffiti_preview_size / d, self.options.graffiti_preview_size - (y - tr[1] + 1) * self.options.graffiti_preview_size / d] + + def draw_graffiti_segment(layer, start, end, feed, color=(0, 255, 0, 40), emmit=1000): + # Emit = dots per second + l = math.sqrt(sum([(start[i] - end[i]) ** 2 for i in range(len(start))])) + time_ = l / feed + c1 = self.graffiti_reference_points[layer][0][0] + c2 = self.graffiti_reference_points[layer][1][0] + d = math.sqrt((c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2) + if d == 0: + raise ValueError("Error! Reference points should not be the same!") + for i in range(int(time_ * emmit + 1)): + t = i / (time_ * emmit) + r1 = start[0] * (1 - t) + end[0] * t + r2 = start[1] * (1 - t) + end[1] * t + a = (r1 ** 2 - r2 ** 2 + d ** 2) / (2 * d) + h = math.sqrt(r1 ** 2 - a ** 2) + xa = c1[0] + a * (c2[0] - c1[0]) / d + ya = c1[1] + a * (c2[1] - c1[1]) / d + + x1 = xa + h * (c2[1] - c1[1]) / d + x2 = xa - h * (c2[1] - c1[1]) / d + y1 = ya - h * (c2[0] - c1[0]) / d + y2 = ya + h * (c2[0] - c1[0]) / d + + x = x1 if y1 < y2 else x2 + y = min(y1, y2) + x, y = graffiti_preview_transform(x, y) + graffiti_preview_draw_point(x, y, color) + + def create_connector(p1, p2, t1, t2): + P1 = P(p1) + P2 = P(p2) + N1 = P(rotate_ccw(t1)) + N2 = P(rotate_ccw(t2)) + r = self.options.graffiti_min_radius + C1 = P1 + N1 * r + C2 = P2 + N2 * r + # Get closest possible centers of arcs, also we define that arcs are both ccw or both not. + dc, N1, N2, m = ( + ( + (((P2 - N1 * r) - (P1 - N2 * r)).l2(), -N1, -N2, 1) + if vectors_ccw(t1, t2) else + (((P2 + N1 * r) - (P1 + N2 * r)).l2(), N1, N2, -1) + ) + if vectors_ccw((P1 - C1).to_list(), t1) == vectors_ccw((P2 - C2).to_list(), t2) else + ( + (((P2 + N1 * r) - (P1 - N2 * r)).l2(), N1, -N2, 1) + if vectors_ccw(t1, t2) else + (((P2 - N1 * r) - (P1 + N2 * r)).l2(), -N1, N2, 1) + ) + ) + dc = math.sqrt(dc) + C1 = P1 + N1 * r + C2 = P2 + N2 * r + Dc = C2 - C1 + + if dc == 0: + # can be joined by one arc + return csp_from_arc(p1, p2, C1.to_list(), r, t1) + + cos = Dc.x / dc + sin = Dc.y / dc + + p1_end = [C1.x - r * sin * m, C1.y + r * cos * m] + p2_st = [C2.x - r * sin * m, C2.y + r * cos * m] + if point_to_point_d2(p1, p1_end) < 0.0001 and point_to_point_d2(p2, p2_st) < 0.0001: + return [[p1, p1, p1], [p2, p2, p2]] + + arc1 = csp_from_arc(p1, p1_end, C1.to_list(), r, t1) + arc2 = csp_from_arc(p2_st, p2, C2.to_list(), r, [cos, sin]) + return csp_concat_subpaths(arc1, arc2) + + if not self.check_dir(): + return + if self.selected_paths == {} and self.options.auto_select_paths: + paths = self.paths + self.error("No paths are selected! Trying to work on all available paths.") + else: + paths = self.selected_paths + self.tool = [] + gcode = """(Header) +(Generated by gcodetools from Inkscape.) +(Using graffiti extension.) +(Header end.)""" + + minx = float("inf") + miny = float("inf") + maxx = float("-inf") + maxy = float("-inf") + # Get all reference points and path's bounds to make preview + + for layer in self.layers: + if layer in paths: + # Set reference points + if layer not in self.graffiti_reference_points: + reference_points = None + for i in range(self.layers.index(layer), -1, -1): + if self.layers[i] in self.graffiti_reference_points: + reference_points = self.graffiti_reference_points[self.layers[i]] + self.graffiti_reference_points[layer] = self.graffiti_reference_points[self.layers[i]] + break + if reference_points is None: + self.error('There are no graffiti reference points for layer {}'.format(layer), "error") + + # Transform reference points + for i in range(len(self.graffiti_reference_points[layer])): + self.graffiti_reference_points[layer][i][0] = self.transform(self.graffiti_reference_points[layer][i][0], layer) + point = self.graffiti_reference_points[layer][i] + gcode += "(Reference point {:f};{:f} for {} axis)\n".format(point[0][0], point[0][1], point[1]) + + if self.options.graffiti_create_preview: + for point in self.graffiti_reference_points[layer]: + minx = min(minx, point[0][0]) + miny = min(miny, point[0][1]) + maxx = max(maxx, point[0][0]) + maxy = max(maxy, point[0][1]) + for path in paths[layer]: + csp = path.path.to_superpath() + csp = self.apply_transforms(path, csp) + csp = self.transform_csp(csp, layer) + bounds = csp_simple_bound(csp) + minx = min(minx, bounds[0]) + miny = min(miny, bounds[1]) + maxx = max(maxx, bounds[2]) + maxy = max(maxy, bounds[3]) + + if self.options.graffiti_create_preview: + self.graffiti_preview = list([[255] * (4 * self.options.graffiti_preview_size) for _ in range(self.options.graffiti_preview_size)]) + self.graffiti_preview_transform = [minx, miny, maxx, maxy] + + for layer in self.layers: + if layer in paths: + + r = re.match("\\s*\\(\\s*([0-9\\-,.]+)\\s*;\\s*([0-9\\-,.]+)\\s*\\)\\s*", self.options.graffiti_start_pos) + if r: + start_point = [float(r.group(1)), float(r.group(2))] + else: + start_point = [0., 0.] + last_sp1 = [[start_point[0], start_point[1] - 10] for _ in range(3)] + last_sp2 = [start_point for _ in range(3)] + + self.set_tool(layer) + self.tool = self.tools[layer][0] + # Change tool every layer. (Probably layer = color so it'll be + # better to change it even if the tool has not been changed) + gcode += ("(Change tool to {})\n".format(re.sub("\"'\\(\\)\\\\", " ", self.tool["name"]))) + self.tool["tool change gcode"] + "\n" + + subpaths = [] + for path in paths[layer]: + # Rebuild the paths to polyline. + csp = path.path.to_superpath() + csp = self.apply_transforms(path, csp) + csp = self.transform_csp(csp, layer) + subpaths += csp + polylines = [] + while len(subpaths) > 0: + i = min([(point_to_point_d2(last_sp2[1], subpaths[i][0][1]), i) for i in range(len(subpaths))])[1] + subpath = subpaths[i][:] + del subpaths[i] + polylines += [ + ['connector', create_connector( + last_sp2[1], + subpath[0][1], + csp_normalized_slope(last_sp1, last_sp2, 1.), + csp_normalized_slope(subpath[0], subpath[1], 0.), + )] + ] + polyline = [] + spl = None + + # remove zerro length segments + i = 0 + while i < len(subpath) - 1: + if cspseglength(subpath[i], subpath[i + 1]) < 0.00000001: + subpath[i][2] = subpath[i + 1][2] + del subpath[i + 1] + else: + i += 1 + + for sp1, sp2 in zip(subpath, subpath[1:]): + if spl is not None and abs(cross(csp_normalized_slope(spl, sp1, 1.), csp_normalized_slope(sp1, sp2, 0.))) > 0.1: # TODO add coefficient into inx + # We've got sharp angle at sp1. + polyline += [sp1] + polylines += [['draw', polyline[:]]] + polylines += [ + ['connector', create_connector( + sp1[1], + sp1[1], + csp_normalized_slope(spl, sp1, 1.), + csp_normalized_slope(sp1, sp2, 0.), + )] + ] + polyline = [] + # max_segment_length + polyline += [sp1] + print_(polyline) + print_(sp1) + + spl = sp1 + polyline += [sp2] + polylines += [['draw', polyline[:]]] + + last_sp1 = sp1 + last_sp2 = sp2 + + # Add return to start_point + if not polylines: + continue + polylines += [["connect1", [[polylines[-1][1][-1][1] for _ in range(3)], [start_point for _ in range(3)]]]] + + # Make polylines from polylines. They are still csp. + for i in range(len(polylines)): + polyline = [] + l = 0 + print_("polylines", polylines) + print_(polylines[i]) + for sp1, sp2 in zip(polylines[i][1], polylines[i][1][1:]): + print_(sp1, sp2) + l = cspseglength(sp1, sp2) + if l > 0.00000001: + polyline += [sp1[1]] + parts = int(math.ceil(l / self.options.graffiti_max_seg_length)) + for j in range(1, parts): + polyline += [csp_at_length(sp1, sp2, float(j) / parts)] + if l > 0.00000001: + polyline += [sp2[1]] + print_(i) + polylines[i][1] = polyline + + t = 0 + last_state = None + for polyline_ in polylines: + polyline = polyline_[1] + # Draw linearization + if self.options.graffiti_create_linearization_preview: + t += 1 + csp = [[polyline[i], polyline[i], polyline[i]] for i in range(len(polyline))] + draw_csp(self.transform_csp([csp], layer, reverse=True)) + + # Export polyline to gcode + # we are making transform from XYZA coordinates to R1...Rn + # where R1...Rn are radius vectors from graffiti reference points + # to current (x,y) point. Also we need to assign custom feed rate + # for each segment. And we'll use only G01 gcode. + last_real_pos, g = get_gcode_coordinates(polyline[0], layer) + last_pos = polyline[0] + if polyline_[0] == "draw" and last_state != "draw": + gcode += self.tool['gcode before path'] + "\n" + for point in polyline: + real_pos, g = get_gcode_coordinates(point, layer) + real_l = sum([(real_pos[i] - last_real_pos[i]) ** 2 for i in range(len(last_real_pos))]) + l = (last_pos[0] - point[0]) ** 2 + (last_pos[1] - point[1]) ** 2 + if l != 0: + feed = self.tool['feed'] * math.sqrt(real_l / l) + gcode += "G01 " + g + " F {:f}\n".format(feed) + if self.options.graffiti_create_preview: + draw_graffiti_segment(layer, real_pos, last_real_pos, feed, color=(0, 0, 255, 200) if polyline_[0] == "draw" else (255, 0, 0, 200), emmit=self.options.graffiti_preview_emmit) + last_real_pos = real_pos + last_pos = point[:] + if polyline_[0] == "draw" and last_state != "draw": + gcode += self.tool['gcode after path'] + "\n" + last_state = polyline_[0] + self.export_gcode(gcode, no_headers=True) + if self.options.graffiti_create_preview: + try: + # Draw reference points + for layer in self.graffiti_reference_points: + for point in self.graffiti_reference_points[layer]: + x, y = graffiti_preview_transform(point[0][0], point[0][1]) + graffiti_preview_draw_point(x, y, (0, 255, 0, 255), radius=5) + + import png + writer = png.Writer(width=self.options.graffiti_preview_size, height=self.options.graffiti_preview_size, size=None, greyscale=False, alpha=True, bitdepth=8, palette=None, transparent=None, background=None, gamma=None, compression=None, interlace=False, bytes_per_sample=None, planes=None, colormap=None, maxval=None, chunk_limit=1048576) + with open(os.path.join(self.options.directory, self.options.file + ".png"), 'wb') as f: + writer.write(f, self.graffiti_preview) + + except: + self.error("Png module have not been found!") + + def get_info_plus(self): + """Like get_info(), but checks some of the values""" + self.get_info() + if self.orientation_points == {}: + self.error("Orientation points have not been defined! A default set of orientation points has been automatically added.") + self.tab_orientation(self.layers[min(1, len(self.layers) - 1)]) + self.get_info() + if self.tools == {}: + self.error("Cutting tool has not been defined! A default tool has been automatically added.") + self.options.tools_library_type = "default" + self.tab_tools_library(self.layers[min(1, len(self.layers) - 1)]) + self.get_info() + + ################################################################################ + # + # Effect + # + # Main function of Gcodetools class + # + ################################################################################ + def effect(self): + start_time = time.time() + global options + options = self.options + options.self = self + options.doc_root = self.document.getroot() + + # define print_ function + global print_ + if self.options.log_create_log: + try: + if os.path.isfile(self.options.log_filename): + os.remove(self.options.log_filename) + with open(self.options.log_filename, "a") as fhl: + fhl.write("""Gcodetools log file. +Started at {}. +{} +""".format(time.strftime("%d.%m.%Y %H:%M:%S"), options.log_filename)) + except: + print_ = lambda *x: None + else: + print_ = lambda *x: None + + # This automatically calls any `tab_{tab_name_in_inx}` which in this + # extension is A LOT of different functions. So see all method prefixed + # with tab_ to find out what's supported here. + self.options.active_tab() + + print_("------------------------------------------") + print_("Done in {:f} seconds".format(time.time() - start_time)) + print_("End at {}.".format(time.strftime("%d.%m.%Y %H:%M:%S"))) + + + def tab_offset(self): + self.get_info() + if self.options.offset_just_get_distance: + for layer in self.selected_paths: + if len(self.selected_paths[layer]) == 2: + csp1 = self.selected_paths[layer][0].path.to_superpath() + csp2 = self.selected_paths[layer][1].path.to_superpath() + dist = csp_to_csp_distance(csp1, csp2) + print_(dist) + draw_pointer(list(csp_at_t(csp1[dist[1]][dist[2] - 1], csp1[dist[1]][dist[2]], dist[3])) + + list(csp_at_t(csp2[dist[4]][dist[5] - 1], csp2[dist[4]][dist[5]], dist[6])), "red", "line", comment=math.sqrt(dist[0])) + return + if self.options.offset_step == 0: + self.options.offset_step = self.options.offset_radius + if self.options.offset_step * self.options.offset_radius < 0: + self.options.offset_step *= -1 + time_ = time.time() + offsets_count = 0 + for layer in self.selected_paths: + for path in self.selected_paths[layer]: + + offset = self.options.offset_step / 2 + while abs(offset) <= abs(self.options.offset_radius): + offset_ = csp_offset(path.path.to_superpath(), offset) + offsets_count += 1 + if offset_: + for iii in offset_: + draw_csp([iii], width=1) + else: + print_("------------Reached empty offset at radius {}".format(offset)) + break + offset += self.options.offset_step + print_() + print_("-----------------------------------------------------------------------------------") + print_("-----------------------------------------------------------------------------------") + print_("-----------------------------------------------------------------------------------") + print_() + print_("Done in {}".format(time.time() - time_)) + print_("Total offsets count {}".format(offsets_count)) + + +if __name__ == '__main__': + Gcodetools().run() diff --git a/share/extensions/gcodetools_about.inx b/share/extensions/gcodetools_about.inx new file mode 100644 index 0000000..72f2b99 --- /dev/null +++ b/share/extensions/gcodetools_about.inx @@ -0,0 +1,52 @@ + + + + About + ru.cnc-club.filter.gcodetools_about_no_options_no_preferences + + + + + + + + + + + + + + path + + + + + + diff --git a/share/extensions/gcodetools_area.inx b/share/extensions/gcodetools_area.inx new file mode 100644 index 0000000..6a500e2 --- /dev/null +++ b/share/extensions/gcodetools_area.inx @@ -0,0 +1,133 @@ + + + + Area + ru.cnc-club.filter.gcodetools_area_area_fill_area_artefacts_ptg + + + + 100 + -10 + 0 + + + + + + 0 + 0 + 0 + + + + + + + + 5.0 + + + + + + + + + + 1 + 4 + + + + + + + d + true + + + + + + 1 + 0.0 + true + 0.05 + + false + + + + + output.ngc + true + + /home + + 5 + + + + + + + + + + + + + + + false + + + + + + + + + + + + path + + + + + + diff --git a/share/extensions/gcodetools_dxf_points.inx b/share/extensions/gcodetools_dxf_points.inx new file mode 100644 index 0000000..8cd782a --- /dev/null +++ b/share/extensions/gcodetools_dxf_points.inx @@ -0,0 +1,79 @@ + + + + DXF Points + ru.cnc-club.filter.gcodetools_dxfpoints_no_options + + + + + + + + + + + + + + output.ngc + true + + /home + + 5 + + + + + + + + + + + + + + + false + + + + + + + + + + + + path + + + + + + diff --git a/share/extensions/gcodetools_engraving.inx b/share/extensions/gcodetools_engraving.inx new file mode 100644 index 0000000..daf2d4a --- /dev/null +++ b/share/extensions/gcodetools_engraving.inx @@ -0,0 +1,91 @@ + + + + Engraving + ru.cnc-club.filter.gcodetools_engraving + + + + 175 + 10 + 4 + false + + + + + + 1 + 0.0 + true + 0.05 + + false + + + + + output.ngc + true + + /home + + 5 + + + + + + + + + + + + + + + false + + + + + + + + + + + + path + + + + + + diff --git a/share/extensions/gcodetools_graffiti.inx b/share/extensions/gcodetools_graffiti.inx new file mode 100644 index 0000000..ea428b7 --- /dev/null +++ b/share/extensions/gcodetools_graffiti.inx @@ -0,0 +1,120 @@ + + + + Graffiti + ru.cnc-club.filter.gcodetools_graffiti_orientation + + + + 10 + 10 + (0.0;0.0) + true + true + 800 + 1000 + + + + + + + + + + + + 0 + -1 + + + + + + + + + + 1 + 0.0 + true + 0.05 + + false + + + + + output.ngc + true + + /home + + 5 + + + + + + + + + + + + + + + false + + + + + + + + + + + + path + + + + + + diff --git a/share/extensions/gcodetools_lathe.inx b/share/extensions/gcodetools_lathe.inx new file mode 100644 index 0000000..ac486bb --- /dev/null +++ b/share/extensions/gcodetools_lathe.inx @@ -0,0 +1,113 @@ + + + + Lathe + ru.cnc-club.filter.gcodetools_lathe_lathe_modify_path_ptg + + + + 10 + 1 + 1 + + + + + X + Z + + + + + 4 + + + + + 1 + 4 + + + + + + + d + true + + + + + + 1 + 0.0 + true + 0.05 + + false + + + + + output.ngc + true + + /home + + 5 + + + + + + + + + + + + + + + false + + + + + + + + + + + + path + + + + + + diff --git a/share/extensions/gcodetools_orientation_points.inx b/share/extensions/gcodetools_orientation_points.inx new file mode 100644 index 0000000..41aeeb6 --- /dev/null +++ b/share/extensions/gcodetools_orientation_points.inx @@ -0,0 +1,57 @@ + + + + Orientation points + ru.cnc-club.filter.gcodetools_orientation_no_options_no_preferences + + + + + + + + + + 0 + -1 + + + + + + + + + + + + + + + + + path + + + + + + diff --git a/share/extensions/gcodetools_path_to_gcode.inx b/share/extensions/gcodetools_path_to_gcode.inx new file mode 100644 index 0000000..9c6365c --- /dev/null +++ b/share/extensions/gcodetools_path_to_gcode.inx @@ -0,0 +1,93 @@ + + + + Path to Gcode + ru.cnc-club.filter.gcodetools_ptg + + + + 1 + 4 + + + + + + + d + true + + + + + + 1 + 0.0 + true + 0.05 + + false + + + + + output.ngc + true + + /home + + 5 + + + + + + + + + + + + + + + false + + + + + + + + + + + + path + + + + + + diff --git a/share/extensions/gcodetools_prepare_path_for_plasma.inx b/share/extensions/gcodetools_prepare_path_for_plasma.inx new file mode 100644 index 0000000..53e6035 --- /dev/null +++ b/share/extensions/gcodetools_prepare_path_for_plasma.inx @@ -0,0 +1,59 @@ + + + + Prepare path for plasma + ru.cnc-club.filter.gcodetools_plasma-prepare-path_no_options_no_preferences + + + + true + 10 + 10 + + + + + + 10 + false + false + + + true + 10 + 140 + + + + + + + + + + + + path + + + + + + diff --git a/share/extensions/gcodetools_tools_library.inx b/share/extensions/gcodetools_tools_library.inx new file mode 100644 index 0000000..ca78a0c --- /dev/null +++ b/share/extensions/gcodetools_tools_library.inx @@ -0,0 +1,62 @@ + + + + Tools library + ru.cnc-club.filter.gcodetools_tools_library_no_options_no_preferences + + + + + + + + + + + + + + + + + + + + + + + + + + + + + path + + + + + + diff --git a/share/extensions/generate_voronoi.inx b/share/extensions/generate_voronoi.inx new file mode 100644 index 0000000..f907f41 --- /dev/null +++ b/share/extensions/generate_voronoi.inx @@ -0,0 +1,26 @@ + + + Voronoi Pattern + com.inkscape.generate.generate_voronoi + voronoi.py + + + 10 + 0 + + + + + + + all + + + + + + diff --git a/share/extensions/generate_voronoi.py b/share/extensions/generate_voronoi.py new file mode 100755 index 0000000..b2ac9dd --- /dev/null +++ b/share/extensions/generate_voronoi.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2010 Alvin Penner, penner@vaxxine.com +# +# - Voronoi Diagram algorithm and C code by Steven Fortune, 1987, http://ect.bell-labs.com/who/sjf/ +# - Python translation to file voronoi.py by Bill Simons, 2005, http://www.oxfish.com/ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import random + +import inkex +from inkex import PathElement, Pattern + +import voronoi + +def clip_line(x1, y1, x2, y2, w, h): + if x1 < 0 and x2 < 0: + return [0, 0, 0, 0] + if x1 > w and x2 > w: + return [0, 0, 0, 0] + if x1 < 0: + y1 = (y1 * x2 - y2 * x1) / (x2 - x1) + x1 = 0 + if x2 < 0: + y2 = (y1 * x2 - y2 * x1) / (x2 - x1) + x2 = 0 + if x1 > w: + y1 = y1 + (w - x1) * (y2 - y1) / (x2 - x1) + x1 = w + if x2 > w: + y2 = y1 + (w - x1) * (y2 - y1) / (x2 - x1) + x2 = w + if y1 < 0 and y2 < 0: + return [0, 0, 0, 0] + if y1 > h and y2 > h: + return [0, 0, 0, 0] + if x1 == x2 and y1 == y2: + return [0, 0, 0, 0] + if y1 < 0: + x1 = (x1 * y2 - x2 * y1) / (y2 - y1) + y1 = 0 + if y2 < 0: + x2 = (x1 * y2 - x2 * y1) / (y2 - y1) + y2 = 0 + if y1 > h: + x1 = x1 + (h - y1) * (x2 - x1) / (y2 - y1) + y1 = h + if y2 > h: + x2 = x1 + (h - y1) * (x2 - x1) / (y2 - y1) + y2 = h + return [x1, y1, x2, y2] + + +class GenerateVoronoi(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("--size", type=int, default=10, help="Average size of cell (px)") + pars.add_argument("--border", type=int, default=0, help="Size of Border (px)") + + def effect(self): + if not self.options.ids: + return inkex.errormsg(_("Please select an object")) + scale = self.svg.unittouu('1px') # convert to document units + self.options.size *= scale + self.options.border *= scale + obj = self.svg.selection.first() + bbox = obj.bounding_box() + mat = obj.composed_transform().matrix + pattern = self.svg.defs.add(Pattern()) + pattern.set_random_id('Voronoi') + pattern.set('width', str(bbox.width)) + pattern.set('height', str(bbox.height)) + pattern.set('patternUnits', 'userSpaceOnUse') + pattern.patternTransform.add_translate(bbox.left - mat[0][2], bbox.top - mat[1][2]) + + # generate random pattern of points + c = voronoi.Context() + pts = [] + b = float(self.options.border) # width of border + for i in range(int(bbox.width * bbox.height / self.options.size / self.options.size)): + x = random.random() * bbox.width + y = random.random() * bbox.height + if b > 0: # duplicate border area + pts.append(voronoi.Site(x, y)) + if x < b: + pts.append(voronoi.Site(x + bbox.width, y)) + if y < b: + pts.append(voronoi.Site(x + bbox.width, y + bbox.height)) + if y > bbox.height - b: + pts.append(voronoi.Site(x + bbox.width, y - bbox.height)) + if x > bbox.width - b: + pts.append(voronoi.Site(x - bbox.width, y)) + if y < b: + pts.append(voronoi.Site(x - bbox.width, y + bbox.height)) + if y > bbox.height - b: + pts.append(voronoi.Site(x - bbox.width, y - bbox.height)) + if y < b: + pts.append(voronoi.Site(x, y + bbox.height)) + if y > bbox.height - b: + pts.append(voronoi.Site(x, y - bbox.height)) + elif x > -b and y > -b and x < bbox.width + b and y < bbox.height + b: + pts.append(voronoi.Site(x, y)) # leave border area blank + # dot = pattern.add(inkex.Rectangle()) + # dot.set('x', str(x-1)) + # dot.set('y', str(y-1)) + # dot.set('width', '2') + # dot.set('height', '2') + if len(pts) < 3: + return inkex.errormsg("Please choose a larger object, or smaller cell size") + + # plot Voronoi diagram + sl = voronoi.SiteList(pts) + voronoi.voronoi(sl, c) + path = "" + for edge in c.edges: + if edge[1] >= 0 and edge[2] >= 0: # two vertices + [x1, y1, x2, y2] = clip_line(c.vertices[edge[1]][0], c.vertices[edge[1]][1], c.vertices[edge[2]][0], c.vertices[edge[2]][1], bbox.width, bbox.height) + elif edge[1] >= 0: # only one vertex + if c.lines[edge[0]][1] == 0: # vertical line + xtemp = c.lines[edge[0]][2] / c.lines[edge[0]][0] + if c.vertices[edge[1]][1] > bbox.height / 2: + ytemp = bbox.height + else: + ytemp = 0 + else: + xtemp = bbox.width + ytemp = (c.lines[edge[0]][2] - bbox.width * c.lines[edge[0]][0]) / c.lines[edge[0]][1] + [x1, y1, x2, y2] = clip_line(c.vertices[edge[1]][0], c.vertices[edge[1]][1], xtemp, ytemp, bbox.width, bbox.height) + elif edge[2] >= 0: # only one vertex + if edge[0] >= len(c.lines): + xtemp = 0 + ytemp = 0 + elif c.lines[edge[0]][1] == 0: # vertical line + xtemp = c.lines[edge[0]][2] / c.lines[edge[0]][0] + if c.vertices[edge[2]][1] > bbox.height / 2: + ytemp = bbox.height + else: + ytemp = 0 + else: + xtemp = 0 + ytemp = c.lines[edge[0]][2] / c.lines[edge[0]][1] + [x1, y1, x2, y2] = clip_line(xtemp, ytemp, c.vertices[edge[2]][0], c.vertices[edge[2]][1], bbox.width, bbox.height) + if x1 or x2 or y1 or y2: + path += 'M %.3f,%.3f %.3f,%.3f ' % (x1, y1, x2, y2) + + patternstyle = {'stroke': '#000000', 'stroke-width': str(scale)} + attribs = {'d': path, 'style': str(inkex.Style(patternstyle))} + pattern.append(PathElement(**attribs)) + + # link selected object to pattern + style = {} + if 'style' in obj.attrib: + style = dict(inkex.Style.parse_str(obj.attrib['style'])) + style['fill'] = 'url(#%s)' % pattern.get('id') + obj.attrib['style'] = str(inkex.Style(style)) + if isinstance(obj, inkex.Group): + for node in obj: + style = {} + if 'style' in node.attrib: + style = dict(inkex.Style.parse_str(node.attrib['style'])) + style['fill'] = 'url(#%s)' % pattern.get('id') + node.attrib['style'] = str(inkex.Style(style)) + +if __name__ == '__main__': + GenerateVoronoi().run() diff --git a/share/extensions/genpofiles.sh b/share/extensions/genpofiles.sh new file mode 100755 index 0000000..5106c52 --- /dev/null +++ b/share/extensions/genpofiles.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +OLDPATH=`pwd` + +cd ../.. +find share/extensions -name "*.inx" | sort | xargs -n 1 printf "[type: gettext/xml] %s\n" +cd ${OLDPATH} diff --git a/share/extensions/gimp_xcf.inx b/share/extensions/gimp_xcf.inx new file mode 100644 index 0000000..7bfc40d --- /dev/null +++ b/share/extensions/gimp_xcf.inx @@ -0,0 +1,35 @@ + + + GIMP XCF + org.ekips.output.gimp_xcf + org.inkscape.output.svg.inkscape + gimp + + + false + false + false + 96 + + + + + + + .xcf + application/x-xcf + GIMP XCF maintaining layers (*.xcf) + GIMP XCF maintaining layers (*.xcf) + true + + + diff --git a/share/extensions/gimp_xcf.py b/share/extensions/gimp_xcf.py new file mode 100755 index 0000000..826b12e --- /dev/null +++ b/share/extensions/gimp_xcf.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2006 Aaron Spike, aaron@ekips.org +# Copyright (C) 2010-2012 Nicolas Dufour, nicoduf@yahoo.fr +# (Windows support and various fixes) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Export to Gimp's XCF file format including Grids and Guides. +""" + +import os +from collections import OrderedDict + +import inkex +from inkex.base import TempDirMixin +from inkex.command import take_snapshot, call +from inkex.localization import inkex_gettext as _ + +class GimpXcf(TempDirMixin, inkex.OutputExtension): + """ + Provide a quick and dirty way of using gimp to output an xcf from Inkscape. + + Both Inkscape and Gimp must be installed for this extension to work. + """ + dir_prefix = 'gimp-out-' + + def add_arguments(self, pars): + pars.add_argument("--tab", dest="tab") + pars.add_argument("-d", "--guides", type=inkex.Boolean, help="Save the Guides in the XCF") + pars.add_argument("-r", "--grid", type=inkex.Boolean, help="Save the Grid with the .XCF") + pars.add_argument("-b", "--background", type=inkex.Boolean, help="Add background color") + pars.add_argument("-i", "--dpi", type=float, default=96.0, help="File resolution") + + def get_guides(self): + """Generate a list of horzontal and vertical only guides""" + horz_guides = [] + vert_guides = [] + # Grab all guide tags in the namedview tag + for guide in self.svg.namedview.get_guides(): + if guide.is_horizontal: + # GIMP doesn't like guides that are outside of the image + if 0 < guide.point.y < self.svg.height: + # The origin is at the top in GIMP land + horz_guides.append(str(guide.point.y)) + elif guide.is_vertical: + # GIMP doesn't like guides that are outside of the image + if 0 < guide.point.x < self.svg.width: + vert_guides.append(str(guide.point.x)) + + return ('h', ' '.join(horz_guides)), ('v', ' '.join(vert_guides)) + + def get_grid(self): + """Get the grid if asked for and return as gimpfu script""" + scale = (self.svg.scale) * (self.options.dpi / 96.0) + # GIMP only allows one rectangular grid + xpath = "sodipodi:namedview/inkscape:grid[@type='xygrid' and (not(@units) or @units='px')]" + if self.svg.xpath(xpath): + node = self.svg.getElement(xpath) + for attr, default, target in (('spacing', 1, 'spacing'), ('origin', 0, 'offset')): + fmt = {'target': target} + for dim in 'xy': + # These attributes could be nonexistent + unit = float(node.get(attr + dim, default)) + unit = self.svg.uutounit(unit, "px") * scale + fmt[dim] = int(round(float(unit))) + yield '(gimp-image-grid-set-{target} img {x} {y})'.format(**fmt) + + @property + def docname(self): + """Get the document name suitable for export""" + return self.svg.get('sodipodi:docname') or 'document' + + def save(self, stream): + + pngs = OrderedDict() + valid = False + + for node in self.svg.xpath("/svg:svg/*[name()='g' or @style][@id]"): + if not len(node): # pylint: disable=len-as-condition + # Ignore empty layers + continue + + valid = True + node_id = node.get('id') + name = node.get("inkscape:label", node_id) + + pngs[name] = take_snapshot( + self.document, + dirname=self.tempdir, + name=name, + dpi=int(self.options.dpi), + export_id=node_id, + export_id_only=True, + export_area_page=True, + export_background_opacity=int(bool(self.options.background)) + ) + + if not valid: + inkex.errormsg(_('This extension requires at least one non empty layer.')) + return + + xcf = os.path.join(self.tempdir, "{}.xcf".format(self.docname)) + script_fu = """ +(tracing 1) +(define + (png-to-layer img png_filename layer_name) + (let* + ( + (png (car (file-png-load RUN-NONINTERACTIVE png_filename png_filename))) + (png_layer (car (gimp-image-get-active-layer png))) + (xcf_layer (car (gimp-layer-new-from-drawable png_layer img))) + ) + (gimp-image-add-layer img xcf_layer -1) + (gimp-drawable-set-name xcf_layer layer_name) + ) +) +(let* + ( + (img (car (gimp-image-new 200 200 RGB))) + ) + (gimp-image-set-resolution img {dpi} {dpi}) + (gimp-image-undo-disable img) + (for-each + (lambda (names) + (png-to-layer img (car names) (cdr names)) + ) + (map cons '("{files}") '("{names}")) + ) + + (gimp-image-resize-to-layers img) +""".format( + dpi=self.options.dpi, + files='" "'.join(pngs.values()), + names='" "'.join(list(pngs)) +) + + if self.options.guides: + for dim, guides in self.get_guides(): + script_fu += """ + (for-each + (lambda ({d}Guide) + (gimp-image-add-{d}guide img {d}Guide) + ) + '({g}) + )""".format(d=dim, g=guides) + + # Grid + if self.options.grid: + for fu_let in self.get_grid(): + script_fu += "\n" + fu_let + "\n" + + script_fu += """ + (gimp-image-undo-enable img) + (gimp-file-save RUN-NONINTERACTIVE img (car (gimp-image-get-active-layer img)) "{xcf}" "{xcf}")) +(gimp-quit 0) + """.format(xcf=xcf) + + call('gimp', "-b", "-", i=True, batch_interpreter="plug-in-script-fu-eval", stdin=script_fu) + + with open(xcf, 'rb') as fhl: + stream.write(fhl.read()) + +if __name__ == '__main__': + GimpXcf().run() diff --git a/share/extensions/grid_cartesian.inx b/share/extensions/grid_cartesian.inx new file mode 100644 index 0000000..d8e6143 --- /dev/null +++ b/share/extensions/grid_cartesian.inx @@ -0,0 +1,73 @@ + + + Cartesian Grid + org.inkscape.render.grid_cartesian + + 1 + + + + + + + + + 6 + 5 + + + + + + + 1 + false + 1 + 1 + 1 + 0.5 + 0.3 + + + + + + + + + 5 + 5 + + + + + + + 1 + false + 5 + 1 + 1 + 0.5 + 0.3 + + + + + + + + + + + all + + + + + + + + diff --git a/share/extensions/grid_cartesian.py b/share/extensions/grid_cartesian.py new file mode 100755 index 0000000..6e3946c --- /dev/null +++ b/share/extensions/grid_cartesian.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2007 John Beard john.j.beard@gmail.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This extension allows you to draw a Cartesian grid in Inkscape. + +There is a wide range of options including subdivision, subsubdivions and +logarithmic scales. Custom line widths are also possible. + +All elements are grouped with similar elements (eg all x-subdivs) +""" + +from math import log + +import inkex +from inkex import Group, PathElement, Rectangle + +def draw_line(x1, y1, x2, y2, width, name, parent): + """Draw an SVG line""" + line = parent.add(PathElement()) + line.style = {'stroke': '#000000', 'stroke-width': str(width), 'fill': 'none'} + line.path = 'M {},{} L {},{}'.format(x1, y1, x2, y2) + line.label = name + + +def draw_rect(x, y, w, h, width, fill, name, parent): + """Draw an SVG Rectangle""" + rect = parent.add(Rectangle(x=str(x), y=str(y), width=str(w), height=str(h))) + rect.style = {'stroke': '#000000', 'stroke-width': str(width), 'fill': fill} + rect.label = name + + +class GridCartesian(inkex.GenerateExtension): + def add_arguments(self, pars): + pars.add_argument("--border_th", type=float, default=3) + pars.add_argument("--border_th_unit", default="cm") + pars.add_argument("--tab", default="x_tab") + pars.add_argument("--x_divs", type=int, default=6) + pars.add_argument("--dx", type=float, default=100.0) + pars.add_argument("--dx_unit", default="cm") + pars.add_argument("--x_subdivs", type=int, default=2) + pars.add_argument("--x_log", type=inkex.Boolean, default="false") + pars.add_argument("--x_subsubdivs", type=int, default=5) + pars.add_argument("--x_half_freq", type=int, default=4) + pars.add_argument("--x_divs_th", type=float, default=2) + pars.add_argument("--x_subdivs_th", type=float, default=1) + pars.add_argument("--x_subsubdivs_th", type=float, default=0.3) + pars.add_argument("--x_div_unit", default="cm") + pars.add_argument("--y_divs", type=int, default=5) + pars.add_argument("--dy", type=float, default=100.0) + pars.add_argument("--dy_unit", default="cm") + pars.add_argument("--y_subdivs", type=int, default=1) + pars.add_argument("--y_log", type=inkex.Boolean, default="false") + pars.add_argument("--y_subsubdivs", type=int, default=5) + pars.add_argument("--y_half_freq", type=int, default=4) + pars.add_argument("--y_divs_th", type=float, default=2) + pars.add_argument("--y_subdivs_th", type=float, default=1) + pars.add_argument("--y_subsubdivs_th", type=float, default=0.3) + pars.add_argument("--y_div_unit", default="cm") + + def generate(self): + self.options.border_th = self.svg.unittouu(str(self.options.border_th) + self.options.border_th_unit) + + self.options.dx = self.svg.unittouu(str(self.options.dx) + self.options.dx_unit) + self.options.x_divs_th = self.svg.unittouu(str(self.options.x_divs_th) + self.options.x_div_unit) + self.options.x_subdivs_th = self.svg.unittouu(str(self.options.x_subdivs_th) + self.options.x_div_unit) + self.options.x_subsubdivs_th = self.svg.unittouu(str(self.options.x_subsubdivs_th) + self.options.x_div_unit) + + self.options.dy = self.svg.unittouu(str(self.options.dy) + self.options.dy_unit) + self.options.y_divs_th = self.svg.unittouu(str(self.options.y_divs_th) + self.options.y_div_unit) + self.options.y_subdivs_th = self.svg.unittouu(str(self.options.y_subdivs_th) + self.options.y_div_unit) + self.options.y_subsubdivs_th = self.svg.unittouu(str(self.options.y_subsubdivs_th) + self.options.y_div_unit) + + # find the pixel dimensions of the overall grid + ymax = self.options.dy * self.options.y_divs + xmax = self.options.dx * self.options.x_divs + + # Embed grid in group + # Put in in the centre of the current view + + grid = Group.new("GridCartesian:X{0.x_divs}:Y{0.y_divs}".format(self.options)) + + (pos_x, pos_y) = self.svg.namedview.center + grid.transform.add_translate(pos_x - xmax / 2.0, pos_y - ymax / 2.0) + + # Group for major x gridlines + majglx = grid.add(Group.new("MajorXGridlines")) + # Group for major y gridlines + majgly = grid.add(Group.new("MajorYGridlines")) + + # Group for minor x gridlines + if self.options.x_subdivs > 1: # if there are any minor x gridlines + minglx = grid.add(Group.new("MinorXGridlines")) + + # Group for subminor x gridlines + if self.options.x_subsubdivs > 1: # if there are any minor minor x gridlines + mminglx = grid.add(Group.new("SubMinorXGridlines")) + + # Group for minor y gridlines + if self.options.y_subdivs > 1: # if there are any minor y gridlines + mingly = grid.add(Group.new("MinorYGridlines")) + + # Group for subminor y gridlines + if self.options.y_subsubdivs > 1: # if there are any minor minor x gridlines + mmingly = grid.add(Group.new("SubMinorYGridlines")) + + draw_rect(0, 0, xmax, ymax, self.options.border_th, + 'none', 'Border', grid) # border rectangle + + # DO THE X DIVISIONS====================================== + sd = self.options.x_subdivs # sub divs per div + ssd = self.options.x_subsubdivs # subsubdivs per subdiv + + for i in range(0, self.options.x_divs): # Major x divisions + if i > 0: # don't draw first line (we made a proper border) + draw_line(self.options.dx * i, 0, + self.options.dx * i, ymax, + self.options.x_divs_th, + 'MajorXDiv' + str(i), majglx) + + if self.options.x_log: # log x subdivs + for j in range(1, sd): + if j > 1: # the first loop is only for subsubdivs + draw_line(self.options.dx * (i + log(j, sd)), 0, + self.options.dx * (i + log(j, sd)), ymax, + self.options.x_subdivs_th, + 'MinorXDiv' + str(i) + ':' + str(j), minglx) + + for k in range(1, ssd): # subsub divs + if (j <= self.options.x_half_freq) or (k % 2 == 0): # only draw half the subsubdivs past the half-freq point + if (ssd % 2 > 0) and (j > self.options.y_half_freq): # half frequency won't work with odd numbers of subsubdivs, + ssd2 = ssd + 1 # make even + else: + ssd2 = ssd # no change + draw_line(self.options.dx * (i + log(j + k / float(ssd2), sd)), 0, + self.options.dx * (i + log(j + k / float(ssd2), sd)), ymax, + self.options.x_subsubdivs_th, 'SubminorXDiv' + str(i) + ':' + str(j) + ':' + str(k), mminglx) + + else: # linear x subdivs + for j in range(0, sd): + if j > 0: # not for the first loop (this loop is for the subsubdivs before the first subdiv) + draw_line(self.options.dx * (i + j / float(sd)), 0, + self.options.dx * (i + j / float(sd)), ymax, + self.options.x_subdivs_th, + 'MinorXDiv' + str(i) + ':' + str(j), minglx) + + for k in range(1, ssd): # subsub divs + draw_line(self.options.dx * (i + (j * ssd + k) / (float(sd) * ssd)), 0, + self.options.dx * (i + (j * ssd + k) / (float(sd) * ssd)), ymax, + self.options.x_subsubdivs_th, + 'SubminorXDiv' + str(i) + ':' + str(j) + ':' + str(k), mminglx) + + # DO THE Y DIVISIONS======================================== + sd = self.options.y_subdivs # sub divs per div + ssd = self.options.y_subsubdivs # subsubdivs per subdiv + + for i in range(0, self.options.y_divs): # Major y divisions + if i > 0: # don't draw first line (we will make a border) + draw_line(0, self.options.dy * i, + xmax, self.options.dy * i, + self.options.y_divs_th, + 'MajorYDiv' + str(i), majgly) + + if self.options.y_log: # log y subdivs + for j in range(1, sd): + if j > 1: # the first loop is only for subsubdivs + draw_line(0, self.options.dy * (i + 1 - log(j, sd)), + xmax, self.options.dy * (i + 1 - log(j, sd)), + self.options.y_subdivs_th, + 'MinorXDiv' + str(i) + ':' + str(j), mingly) + + for k in range(1, ssd): # subsub divs + if (j <= self.options.y_half_freq) or (k % 2 == 0): # only draw half the subsubdivs past the half-freq point + if (ssd % 2 > 0) and (j > self.options.y_half_freq): # half frequency won't work with odd numbers of subsubdivs, + ssd2 = ssd + 1 + else: + ssd2 = ssd # no change + draw_line(0, self.options.dx * (i + 1 - log(j + k / float(ssd2), sd)), + xmax, self.options.dx * (i + 1 - log(j + k / float(ssd2), sd)), + self.options.y_subsubdivs_th, + 'SubminorXDiv' + str(i) + ':' + str(j) + ':' + str(k), mmingly) + else: # linear y subdivs + for j in range(0, self.options.y_subdivs): + if j > 0: # not for the first loop (this loop is for the subsubdivs before the first subdiv) + draw_line(0, self.options.dy * (i + j / float(sd)), + xmax, self.options.dy * (i + j / float(sd)), + self.options.y_subdivs_th, + 'MinorXYiv' + str(i) + ':' + str(j), mingly) + + for k in range(1, ssd): # subsub divs + draw_line(0, self.options.dy * (i + (j * ssd + k) / (float(sd) * ssd)), + xmax, self.options.dy * (i + (j * ssd + k) / (float(sd) * ssd)), + self.options.y_subsubdivs_th, + 'SubminorXDiv' + str(i) + ':' + str(j) + ':' + str(k), mmingly) + + return grid + + +if __name__ == '__main__': + GridCartesian().run() diff --git a/share/extensions/grid_isometric.inx b/share/extensions/grid_isometric.inx new file mode 100644 index 0000000..7e82032 --- /dev/null +++ b/share/extensions/grid_isometric.inx @@ -0,0 +1,25 @@ + + + Isometric Grid + org.inkscape.render.grid_isometric + 5 + 5 + 50.0 + 2 + 5 + 2 + 0.5 + 0.1 + 3 + + all + + + + + + + + diff --git a/share/extensions/grid_isometric.py b/share/extensions/grid_isometric.py new file mode 100755 index 0000000..9cf94bb --- /dev/null +++ b/share/extensions/grid_isometric.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2010 Jean-Luc JOULIN "JeanJouX" jean-luc.joulin@laposte.net +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This extension allow you to draw a isometric grid with inkscape +There is some options including subdivision, subsubdivions and custom line width +All elements are grouped with similar elements +These grid are used for isometric view in mechanical drawing or piping schematic +!!! Y Divisions can't be smaller than half the X Divions +""" + +import inkex +from inkex import Rectangle +from inkex.paths import Move, Line + +def draw_line(x1, y1, x2, y2, width, name, parent): + elem = parent.add(inkex.PathElement()) + elem.style = {'stroke': '#000000', 'stroke-width': str(width), 'fill': 'none'} + elem.set('inkscape:label', name) + elem.path = [Move(x1, y1), Line(x2, y2)] + +def draw_rect(x, y, w, h, width, fill, name): + elem = Rectangle(x=str(x), y=str(y), width=str(w), height=str(h)) + elem.style = {'stroke': '#000000', 'stroke-width': str(width), 'fill': fill} + elem.set('inkscape:label', name) + return elem + + +class GridIsometric(inkex.GenerateExtension): + def add_arguments(self, pars): + pars.add_argument("--x_divs", type=int, dest="x_divs", default=5, + help="Major X Divisions") + pars.add_argument("--y_divs", type=int, dest="y_divs", default=5, + help="Major Y Divisions") + pars.add_argument("--dx", type=float, dest="dx", default=10.0, + help="Major X division Spacing") + pars.add_argument("--subdivs", type=int, dest="subdivs", default=2, + help="Subdivisions per Major X division") + pars.add_argument("--subsubdivs", type=int, dest="subsubdivs", default=5, + help="Subsubdivisions per Minor X division") + pars.add_argument("--divs_th", type=float, dest="divs_th", default=2, + help="Major X Division Line thickness") + pars.add_argument("--subdivs_th", type=float, dest="subdivs_th", default=1, + help="Minor X Division Line thickness") + pars.add_argument("--subsubdivs_th", type=float, dest="subsubdivs_th", + default=0.3, help="Subminor X Division Line thickness") + pars.add_argument("--border_th", type=float, dest="border_th", default=3, + help="Border Line thickness") + + @property + def container_label(self): + """Generate label from options""" + return 'Grid_Polar:X{0.x_divs}:Y{0.y_divs}'.format(self.options) # pylint: disable=missing-format-attribute + + def generate(self): + self.options.dx = self.svg.unittouu(str(self.options.dx) + 'px') + self.options.divs_th = self.svg.unittouu(str(self.options.divs_th) + 'px') + self.options.subdivs_th = self.svg.unittouu(str(self.options.subdivs_th) + 'px') + self.options.subsubdivs_th = self.svg.unittouu(str(self.options.subsubdivs_th) + 'px') + self.options.border_th = self.svg.unittouu(str(self.options.border_th) + 'px') + + # Can't generate a grid too flat + # If the Y dimension is smallest than half the X dimension, fix it. + if self.options.y_divs < ((self.options.x_divs + 1) / 2): + self.options.y_divs = int((self.options.x_divs + 1) / 2) + + # Find the pixel dimensions of the overall grid + xmax = self.options.dx * (2 * self.options.x_divs) + ymax = self.options.dx * (2 * self.options.y_divs) / 0.866025 + + # Group for major x gridlines + majglx = inkex.Group.new('MajorXGridlines') + yield majglx + + # Group for major y gridlines + majgly = inkex.Group.new('MajorYGridlines') + yield majgly + + # Group for major z gridlines + majglz = inkex.Group.new('MajorZGridlines') + yield majglz + + # Group for minor x gridlines + if self.options.subdivs > 1: # if there are any minor x gridlines + minglx = inkex.Group.new('MinorXGridlines') + yield minglx + # Group for subminor x gridlines, if there are any minor minor x gridlines + if self.options.subsubdivs > 1: + mminglx = inkex.Group.new('SubMinorXGridlines') + yield mminglx + # Group for minor y gridlines, if there are any minor y gridlines + if self.options.subdivs > 1: + mingly = inkex.Group.new('MinorYGridlines') + yield mingly + # Group for subminor y gridlines, if there are any minor minor x gridlines + if self.options.subsubdivs > 1: + mmingly = inkex.Group.new('SubMinorYGridlines') + yield mmingly + # Group for minor z gridlines, if there are any minor y gridlines + if self.options.subdivs > 1: + minglz = inkex.Group.new('MinorZGridlines') + yield minglz + # Group for subminor z gridlines, if there are any minor minor x gridlines + if self.options.subsubdivs > 1: + mminglz = inkex.Group.new('SubMinorZGridlines') + yield mminglz + + # Border of grid + yield draw_rect(0, 0, xmax, ymax, self.options.border_th, 'none', 'Border') + + # X DIVISION + # Shortcuts for divisions + sd = self.options.subdivs + ssd = self.options.subsubdivs + + # Initializing variable + cpt_div = 0 + cpt_subdiv = 0 + cpt_subsubdiv = 0 + com_div = 0 + com_subdiv = 0 + com_subsubdiv = 0 + + for i in range(1, (2 * self.options.x_divs * sd * ssd)): + cpt_subsubdiv += 1 + com_subsubdiv = 1 + if cpt_subsubdiv == self.options.subsubdivs: + cpt_subsubdiv = 0 + cpt_subdiv += 1 + com_subsubdiv = 0 + com_subdiv = 1 + com_div = 0 + + if cpt_subdiv == self.options.subdivs: + cpt_subdiv = 0 + com_subsubdiv = 0 + com_subdiv = 0 + com_div = 1 + + if com_subsubdiv == 1: + draw_line(self.options.dx * i / sd / ssd, 0, + self.options.dx * i / sd / ssd, ymax, + self.options.subsubdivs_th, + 'MajorXDiv' + str(i), mminglx) + if com_subdiv == 1: + com_subdiv = 0 + draw_line(self.options.dx * i / sd / ssd, 0, + self.options.dx * i / sd / ssd, ymax, + self.options.subdivs_th, + 'MajorXDiv' + str(i), minglx) + if com_div == 1: + com_div = 0 + draw_line(self.options.dx * i / sd / ssd, 0, + self.options.dx * i / sd / ssd, ymax, + self.options.divs_th, + 'MajorXDiv' + str(i), majglx) + + # Y DIVISIONS + # Shortcuts for divisions + sd = self.options.subdivs + ssd = self.options.subsubdivs + + taille = self.options.dx / sd / ssd # Size of unity + nb_ligne = (self.options.x_divs + self.options.y_divs) * self.options.subdivs * self.options.subsubdivs # Global number of lines + nb_ligne_x = self.options.x_divs * self.options.subdivs * self.options.subsubdivs # Number of lines X + nb_ligne_y = self.options.y_divs * self.options.subdivs * self.options.subsubdivs # Number of lines Y + + # Initializing variable + cpt_div = 0 + cpt_subdiv = 0 + cpt_subsubdiv = 0 + com_div = 0 + com_subdiv = 0 + com_subsubdiv = 0 + + for l in range(1, int(nb_ligne * 4)): + cpt_subsubdiv += 1 + com_subsubdiv = 1 + if cpt_subsubdiv == self.options.subsubdivs: + cpt_subsubdiv = 0 + cpt_subdiv += 1 + com_subsubdiv = 0 + com_subdiv = 1 + com_div = 0 + + if cpt_subdiv == self.options.subdivs: + cpt_subdiv = 0 + com_subsubdiv = 0 + com_subdiv = 0 + com_div = 1 + + if ((2 * l) - 1) < (2 * nb_ligne_x): + txa = taille * ((2 * l) - 1) + tya = ymax + txb = 0 + tyb = ymax - taille / (2 * 0.866025) - (taille * (l - 1) / 0.866025) + + if com_subsubdiv == 1: + draw_line(txa, tya, + txb, tyb, + self.options.subsubdivs_th, + 'MajorYDiv' + str(i), mmingly) + draw_line(xmax - txa, tya, + xmax - txb, tyb, + self.options.subsubdivs_th, + 'MajorZDiv' + str(l), mminglz) + if com_subdiv == 1: + com_subdiv = 0 + draw_line(txa, tya, + txb, tyb, + self.options.subdivs_th, + 'MajorYDiv' + str(i), mingly) + draw_line(xmax - txa, tya, + xmax - txb, tyb, + self.options.subdivs_th, + 'MajorZDiv' + str(l), minglz) + if com_div == 1: + com_div = 0 + draw_line(txa, tya, + txb, tyb, + self.options.divs_th, + 'MajorYDiv' + str(i), majgly) + draw_line(xmax - txa, tya, + xmax - txb, tyb, + self.options.divs_th, + 'MajorZDiv' + str(l), majglz) + + if ((2 * l) - 1) == (2 * nb_ligne_x): + txa = taille * ((2 * l) - 1) + tya = ymax + txb = 0 + tyb = ymax - taille / (2 * 0.866025) - (taille * (l - 1) / 0.866025) + + if com_subsubdiv == 1: + draw_line(txa, tya, + txb, tyb, + self.options.subsubdivs_th, + 'MajorYDiv' + str(i), mmingly) + draw_line(xmax - txa, tya, + xmax - txb, tyb, + self.options.subsubdivs_th, + 'MajorZDiv' + str(l), mminglz) + if com_subdiv == 1: + com_subdiv = 0 + draw_line(txa, tya, + txb, tyb, + self.options.subdivs_th, + 'MajorYDiv' + str(i), mingly) + draw_line(xmax - txa, tya, + xmax - txb, tyb, + self.options.subdivs_th, + 'MajorZDiv' + str(l), minglz) + if com_div == 1: + com_div = 0 + draw_line(txa, tya, + txb, tyb, + self.options.divs_th, + 'MajorYDiv' + str(i), majgly) + draw_line(xmax - txa, tya, + xmax - txb, tyb, + self.options.divs_th, + 'MajorZDiv' + str(l), majglz) + + if ((2 * l) - 1) > (2 * nb_ligne_x): + txa = xmax + tya = ymax - taille / (2 * 0.866025) - (taille * (l - 1 - ((2 * nb_ligne_x) / 2)) / 0.866025) + txb = 0 + tyb = ymax - taille / (2 * 0.866025) - (taille * (l - 1) / 0.866025) + + if tyb <= 0: + txa = xmax + tya = ymax - taille / (2 * 0.866025) - (taille * (l - 1 - ((2 * nb_ligne_x) / 2)) / 0.866025) + txb = taille * (2 * (l - (2 * nb_ligne_y)) - 1) + tyb = 0 + + if txb < xmax: + if com_subsubdiv == 1: + draw_line(txa, tya, + txb, tyb, + self.options.subsubdivs_th, + 'MajorYDiv' + str(i), mmingly) + draw_line(xmax - txa, tya, + xmax - txb, tyb, + self.options.subsubdivs_th, + 'MajorZDiv' + str(l), mminglz) + if com_subdiv == 1: + com_subdiv = 0 + draw_line(txa, tya, + txb, tyb, + self.options.subdivs_th, + 'MajorYDiv' + str(i), mingly) + draw_line(xmax - txa, tya, + xmax - txb, tyb, + self.options.subdivs_th, + 'MajorZDiv' + str(l), minglz) + if com_div == 1: + com_div = 0 + draw_line(txa, tya, + txb, tyb, + self.options.divs_th, + 'MajorYDiv' + str(i), majgly) + draw_line(xmax - txa, tya, + xmax - txb, tyb, + self.options.divs_th, + 'MajorZDiv' + str(l), majglz) + + else: + if txb < xmax: + if com_subsubdiv == 1: + draw_line(txa, tya, + txb, tyb, + self.options.subsubdivs_th, + 'MajorYDiv' + str(i), mmingly) + draw_line(xmax - txa, tya, + xmax - txb, tyb, + self.options.subsubdivs_th, + 'MajorZDiv' + str(l), mminglz) + if com_subdiv == 1: + com_subdiv = 0 + draw_line(txa, tya, + txb, tyb, + self.options.subdivs_th, + 'MajorYDiv' + str(i), mingly) + draw_line(xmax - txa, tya, + xmax - txb, tyb, + self.options.subdivs_th, + 'MajorZDiv' + str(l), minglz) + if com_div == 1: + com_div = 0 + draw_line(txa, tya, + txb, tyb, + self.options.divs_th, + 'MajorYDiv' + str(i), majgly) + draw_line(xmax - txa, tya, + xmax - txb, tyb, + self.options.divs_th, + 'MajorZDiv' + str(l), majglz) + + +if __name__ == '__main__': + GridIsometric().run() diff --git a/share/extensions/grid_polar.inx b/share/extensions/grid_polar.inx new file mode 100644 index 0000000..90fa80e --- /dev/null +++ b/share/extensions/grid_polar.inx @@ -0,0 +1,43 @@ + + + Polar Grid + org.inkscape.generate.grid_polar + + 5.0 + + + + + 18 + 24 + + + 5 + 50.0 + 3 + false + 2 + 1 + + + 24 + 4 + 1 + 2 + 2 + 1 + + + + + all + + + + + + + + diff --git a/share/extensions/grid_polar.py b/share/extensions/grid_polar.py new file mode 100755 index 0000000..fe1da42 --- /dev/null +++ b/share/extensions/grid_polar.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2007 John Beard john.j.beard@gmail.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This extension allows you to draw a polar grid in Inkscape. +There is a wide range of options including subdivision and labels. +""" + +from math import cos, log, pi, sin + +import inkex +from inkex import Group, Circle, TextElement + +def draw_circle(r, cx, cy, width, fill, name, parent): + """Draw an SVG circle""" + circle = parent.add(Circle(cx=str(cx), cy=str(cy), r=str(r))) + circle.style = {'stroke': '#000000', 'stroke-width': str(width), 'fill': fill} + circle.label = name + +def draw_line(x1, y1, x2, y2, width, name, parent): + """Draw an SVG line""" + line = parent.add(inkex.PathElement()) + line.style = {'stroke': '#000000', 'stroke-width': str(width), 'fill': 'none'} + line.path = 'M {},{} L {},{}'.format(x1, y1, x2, y2) + line.label = name + +def draw_label(x, y, string, font_size, name, parent): + """Draw a centered label""" + label = parent.add(TextElement(x=str(x), y=str(y))) + label.style = {'text-align': 'center', 'vertical-align': 'top', + 'text-anchor': 'middle', 'font-size': str(font_size) + 'px', + 'fill-opacity': '1.0', 'stroke': 'none', + 'font-weight': 'normal', 'font-style': 'normal', 'fill': '#000000'} + label.text = string + label.label = name + + +class GridPolar(inkex.GenerateExtension): + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("--r_divs", type=int, default=5, help="Circular Divisions") + pars.add_argument("--dr", type=float, default=50, help="Circular Division Spacing") + pars.add_argument("--r_subdivs", type=int, default=3, help="Subdivisions per Major div") + pars.add_argument("--r_log", type=inkex.Boolean, default=False, help="Logarithmic subdiv") + pars.add_argument("--r_divs_th", type=float, default=2, help="Major Line thickness") + pars.add_argument("--r_subdivs_th", type=float, default=1, help="Minor Line thickness") + pars.add_argument("--a_divs", type=int, default=24, help="Angle Divisions") + pars.add_argument("--a_divs_cent", type=int, default=4, help="Angle Divisions at Centre") + pars.add_argument("--a_subdivs", type=int, default=1, help="Angcular Subdivisions") + pars.add_argument("--a_subdivs_cent", type=int, default=1, help="Angular Subdivisions end") + pars.add_argument("--a_divs_th", type=float, default=2, help="Major Angular thickness") + pars.add_argument("--a_subdivs_th", type=float, default=1, help="Minor Angular thickness") + pars.add_argument("--c_dot_dia", type=float, default=5.0, help="Diameter of Centre Dot") + pars.add_argument("--a_labels", default='deg', help="The kind of labels to apply") + pars.add_argument("--a_label_size", type=int, default=18, help="Pixel size of the labels") + pars.add_argument("--a_label_outset", type=float, default=24, help="Label Radial outset") + + def generate(self): + self.options.dr = self.svg.unittouu(str(self.options.dr) + 'px') + self.options.r_divs_th = self.svg.unittouu(str(self.options.r_divs_th) + 'px') + self.options.r_subdivs_th = self.svg.unittouu(str(self.options.r_subdivs_th) + 'px') + self.options.a_divs_th = self.svg.unittouu(str(self.options.a_divs_th) + 'px') + self.options.a_subdivs_th = self.svg.unittouu(str(self.options.a_subdivs_th) + 'px') + self.options.c_dot_dia = self.svg.unittouu(str(self.options.c_dot_dia) + 'px') + self.options.a_label_size = self.svg.unittouu(str(self.options.a_label_size) + 'px') + self.options.a_label_outset = self.svg.unittouu(str(self.options.a_label_outset) + 'px') + + # Embed grid in group + grid = Group.new("GridPolar:R{0.r_divs}:A{0.a_divs}".format(self.options)) + + (pos_x, pos_y) = self.svg.namedview.center + grid.transform.add_translate(pos_x, pos_y) + + dr = self.options.dr # Distance between neighbouring circles + dtheta = 2 * pi / self.options.a_divs_cent # Angular change between adjacent radial lines at centre + rmax = self.options.r_divs * dr + + # Create SVG circles + for i in range(1, self.options.r_divs + 1): + draw_circle(i * dr, 0, 0, # major div circles + self.options.r_divs_th, 'none', + 'MajorDivCircle' + str(i) + ':R' + str(i * dr), grid) + + if self.options.r_log: # logarithmic subdivisions + for j in range(2, self.options.r_subdivs): + draw_circle(i * dr - (1 - log(j, self.options.r_subdivs)) * dr, # minor div circles + 0, 0, self.options.r_subdivs_th, 'none', + 'MinorDivCircle' + str(i) + ':Log' + str(j), grid) + else: # linear subdivs + for j in range(1, self.options.r_subdivs): + draw_circle(i * dr - j * dr / self.options.r_subdivs, # minor div circles + 0, 0, self.options.r_subdivs_th, 'none', + 'MinorDivCircle' + str(i) + ':R' + str(i * dr), grid) + + if self.options.a_divs == self.options.a_divs_cent: # the lines can go from the centre to the edge + for i in range(0, self.options.a_divs): + draw_line(0, 0, rmax * sin(i * dtheta), rmax * cos(i * dtheta), + self.options.a_divs_th, 'RadialGridline' + str(i), grid) + + else: # we need separate lines + for i in range(0, self.options.a_divs_cent): # lines that go to the first circle + draw_line(0, 0, dr * sin(i * dtheta), dr * cos(i * dtheta), + self.options.a_divs_th, 'RadialGridline' + str(i), grid) + + dtheta = 2 * pi / self.options.a_divs # work out the angle change for outer lines + + for i in range(0, self.options.a_divs): # lines that go from there to the edge + draw_line(dr * sin(i * dtheta + pi / 2.0), dr * cos(i * dtheta + pi / 2.0), + rmax * sin(i * dtheta + pi / 2.0), rmax * cos(i * dtheta + pi / 2.0), + self.options.a_divs_th, 'RadialGridline' + str(i), grid) + + if self.options.a_subdivs > 1: # draw angular subdivs + for i in range(0, self.options.a_divs): # for each major division + for j in range(1, self.options.a_subdivs): # draw the subdivisions + angle = i * dtheta - j * dtheta / self.options.a_subdivs + pi / 2.0 # the angle of the subdivion line + draw_line(dr * self.options.a_subdivs_cent * sin(angle), + dr * self.options.a_subdivs_cent * cos(angle), + rmax * sin(angle), rmax * cos(angle), + self.options.a_subdivs_th, 'RadialMinorGridline' + str(i), grid) + + if self.options.c_dot_dia != 0: # if a non-zero diameter, draw the centre dot + draw_circle(self.options.c_dot_dia / 2.0, + 0, 0, 0, '#000000', 'CentreDot', grid) + + if self.options.a_labels == 'deg': + label_radius = rmax + self.options.a_label_outset # radius of label centres + label_size = self.options.a_label_size + numeral_size = 0.73 * label_size # numerals appear to be 0.73 the height of the nominal pixel size of the font in "Sans" + + for i in range(0, self.options.a_divs): # self.options.a_divs): #radial line labels + draw_label(sin(i * dtheta + pi / 2.0) * label_radius, # 0 at the RHS, mathematical style + cos(i * dtheta + pi / 2.0) * label_radius + numeral_size / 2.0, # centre the text vertically + str(i * 360 / self.options.a_divs), + label_size, 'Label' + str(i), grid) + + return grid + +if __name__ == '__main__': + GridPolar().run() diff --git a/share/extensions/guides_creator.inx b/share/extensions/guides_creator.inx new file mode 100644 index 0000000..e7f5e8a --- /dev/null +++ b/share/extensions/guides_creator.inx @@ -0,0 +1,91 @@ + + + Guides creator + org.inkscape.effect.guides_creator + + + + + + + + 2 + 3 + + + false + false + false + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 3 + + + false + false + + all + + + + + + + diff --git a/share/extensions/guides_creator.py b/share/extensions/guides_creator.py new file mode 100755 index 0000000..7f4e332 --- /dev/null +++ b/share/extensions/guides_creator.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2008 Jonas Termeau - jonas.termeau **AT** gmail.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Thanks to: +# +# Bernard Gray - bernard.gray **AT** gmail.com (python helping) +# Jamie Heames (english translation issues) +# ~suv (bug report in v2.3) +# http://www.gutenberg.eu.org/publications/ (9x9 margins settings) +# +""" +This basic extension allows you to automatically draw guides in inkscape. +""" + +from math import cos, sin, sqrt + +import inkex +from inkex import Guide + + +class GuidesCreator(inkex.EffectExtension): + """Create a set of guides based on the given options""" + def add_arguments(self, pars): + pars.add_argument("--tab", type=self.arg_method('generate'), default="regular_guides",\ + help="Type of guides to create.") + pars.add_argument('--guides_preset', default='custom', help='Preset') + pars.add_argument('--vertical_guides', type=int, default=3, help='Vertical guides') + pars.add_argument('--horizontal_guides', type=int, default=3, help='Horizontal guides') + pars.add_argument('--start_from_edges', type=inkex.Boolean, help='Start from edges') + pars.add_argument('--ul', type=inkex.Boolean, default=True, help='Upper left corner') + pars.add_argument('--ur', type=inkex.Boolean, default=True, help='Upper right corner') + pars.add_argument('--ll', type=inkex.Boolean, default=True, help='Lower left corner') + pars.add_argument('--lr', type=inkex.Boolean, default=True, help='Lower right corner') + pars.add_argument('--margins_preset', default='custom', help='Margins preset') + pars.add_argument('--vert', type=int, default=0, help='Vert subdivisions') + pars.add_argument('--horz', type=int, default=0, help='Horz subdivisions') + pars.add_argument('--header_margin', default=6, help='Header margin') + pars.add_argument('--footer_margin', default=6, help='Footer margin') + pars.add_argument('--left_margin', default=6, help='Left margin') + pars.add_argument('--right_margin', default=6, help='Right margin') + pars.add_argument('--delete', type=inkex.Boolean, help='Delete existing guides') + + def effect(self): + # getting the width and height attributes of the canvas + self.width = float(self.svg.width) + self.height = float(self.svg.height) + + # getting edges coordinates + self.h_orientation = '0,' + str(round(self.width, 4)) + self.v_orientation = str(round(self.height, 4)) + ',0' + + if self.options.delete: + for guide in self.svg.namedview.get_guides(): + guide.delete() + + return self.options.tab() + + def generate_regular_guides(self): + """Generate a regular set of guides""" + preset = self.options.guides_preset + from_edges = self.options.start_from_edges + if preset == 'custom': + h_division = self.options.horizontal_guides + v_division = self.options.vertical_guides + if from_edges: + v_division = v_division or 1 + h_division = h_division or 1 + + self.draw_guides(v_division, from_edges, vert=True) + self.draw_guides(h_division, from_edges, vert=False) + + elif preset == 'golden': + gold = (1 + sqrt(5)) / 2 + + # horizontal golden guides + position1 = '0,' + str(self.height / gold) + position2 = '0,' + str(self.height - (self.height / gold)) + + self.draw_guide(position1, self.h_orientation) + self.draw_guide(position2, self.h_orientation) + + # vertical golden guides + position1 = str(self.width / gold) + ',0' + position2 = str(self.width - (self.width / gold)) + ',0' + + self.draw_guide(position1, self.v_orientation) + self.draw_guide(position2, self.v_orientation) + + if from_edges: + # horizontal borders + self.draw_guide('0,' + str(self.height), self.h_orientation) + self.draw_guide(str(self.height) + ',0', self.h_orientation) + + # vertical borders + self.draw_guide('0,' + str(self.width), self.v_orientation) + self.draw_guide(str(self.width) + ',0', self.v_orientation) + + elif ';' in preset: + v_division = int(preset.split(';')[0]) + h_division = int(preset.split(';')[1]) + self.draw_guides(v_division, from_edges, vert=True) + self.draw_guides(h_division, from_edges, vert=False) + else: + raise inkex.AbortExtension("Unknown guide guide preset: {}".format(preset)) + + def generate_diagonal_guides(self): + """Generate diagonal guides""" + # Dimentions + left, bottom = (0, 0) + right, top = (self.width, self.height) + + # Diagonal angle + angle = 45 + + if self.options.ul: + ul_corner = str(top) + ',' + str(left) + from_ul_to_lr = str(cos(angle)) + ',' + str(cos(angle)) + self.draw_guide(ul_corner, from_ul_to_lr) + + if self.options.ur: + ur_corner = str(right) + ',' + str(top) + from_ur_to_ll = str(-sin(angle)) + ',' + str(sin(angle)) + self.draw_guide(ur_corner, from_ur_to_ll) + + if self.options.ll: + ll_corner = str(bottom) + ',' + str(left) + from_ll_to_ur = str(-cos(angle)) + ',' + str(cos(angle)) + self.draw_guide(ll_corner, from_ll_to_ur) + + if self.options.lr: + lr_corner = str(bottom) + ',' + str(right) + from_lr_to_ul = str(-sin(angle)) + ',' + str(-sin(angle)) + self.draw_guide(lr_corner, from_lr_to_ul) + + def generate_margins(self): + """Generate margin guides""" + header_margin = int(self.options.header_margin) + footer_margin = int(self.options.footer_margin) + left_margin = int(self.options.left_margin) + right_margin = int(self.options.right_margin) + h_subdiv = int(self.options.horz) + v_subdiv = int(self.options.vert) + + if self.options.start_from_edges: + # horizontal borders + self.draw_guide('0,' + str(self.height), self.h_orientation) + self.draw_guide(str(self.height) + ',0', self.h_orientation) + + # vertical borders + self.draw_guide('0,' + str(self.width), self.v_orientation) + self.draw_guide(str(self.width) + ',0', self.v_orientation) + + if self.options.margins_preset == 'custom': + y_header = self.height + y_footer = 0 + x_left = 0 + x_right = self.width + + if header_margin != 0: + y_header = (self.height / header_margin) * (header_margin - 1) + self.draw_guide('0,' + str(y_header), self.h_orientation) + + if footer_margin != 0: + y_footer = self.height / footer_margin + self.draw_guide('0,' + str(y_footer), self.h_orientation) + + if left_margin != 0: + x_left = self.width / left_margin + self.draw_guide(str(x_left) + ',0', self.v_orientation) + + if right_margin != 0: + x_right = (self.width / right_margin) * (right_margin - 1) + self.draw_guide(str(x_right) + ',0', self.v_orientation) + + elif self.options.margins_preset == 'book_left': + # 1/9th header + y_header = (self.height / 9) * 8 + self.draw_guide('0,' + str(y_header), self.h_orientation) + + # 2/9th footer + y_footer = (self.height / 9) * 2 + self.draw_guide('0,' + str(y_footer), self.h_orientation) + + # 2/9th left margin + x_left = (self.width / 9) * 2 + self.draw_guide(str(x_left) + ',0', self.v_orientation) + + # 1/9th right margin + x_right = (self.width / 9) * 8 + self.draw_guide(str(x_right) + ',0', self.v_orientation) + + elif self.options.margins_preset == 'book_right': + # 1/9th header + y_header = (self.height / 9) * 8 + self.draw_guide('0,' + str(y_header), self.h_orientation) + + # 2/9th footer + y_footer = (self.height / 9) * 2 + self.draw_guide('0,' + str(y_footer), self.h_orientation) + + # 2/9th left margin + x_left = (self.width / 9) + self.draw_guide(str(x_left) + ',0', self.v_orientation) + + # 1/9th right margin + x_right = (self.width / 9) * 7 + self.draw_guide(str(x_right) + ',0', self.v_orientation) + + # setting up properties of the rectangle created between guides + rectangle_height = y_header - y_footer + rectangle_width = x_right - x_left + + if h_subdiv != 0: + begin_from = y_footer + # creating horizontal guides + self._draw_guides( + (rectangle_width, rectangle_height), h_subdiv, + edges=0, shift=begin_from, vert=False) + + if v_subdiv != 0: + begin_from = x_left + # creating vertical guides + self._draw_guides( + (rectangle_width, rectangle_height), v_subdiv, + edges=0, shift=begin_from, vert=True) + + def draw_guides(self, division, edges, vert=False): + """Draw a vertical or horizontal lines""" + return self._draw_guides((self.width, self.height), division, edges, vert=vert) + + def _draw_guides(self, vector, division, edges, shift=0, vert=False): + if division <= 0: + return + + # Vert controls both ort template and vector calculation + ort = '{},0' if vert else '0,{}' + var = int(bool(edges)) + for x in range(0, division - 1 + 2 * var): + div = vector[not bool(vert)] / division + position = str(round(div + (x - var) * div + shift, 4)) + orientation = str(round(vector[bool(vert)], 4)) + self.draw_guide(ort.format(position), ort.format(orientation)) + + def draw_guide(self, position, orientation): + """Create a guide directly into the namedview""" + if isinstance(position, tuple): + x, y = position + elif isinstance(position, str): + x, y = position.split(',') + self.svg.namedview.add(Guide().move_to(float(x), float(y), orientation)) + +if __name__ == '__main__': + GuidesCreator().run() diff --git a/share/extensions/guillotine.inx b/share/extensions/guillotine.inx new file mode 100644 index 0000000..fccab53 --- /dev/null +++ b/share/extensions/guillotine.inx @@ -0,0 +1,23 @@ + + + Guillotine + org.inkscape.guillotine + + org.inkscape.output.svg.inkscape + + ~/ + guillotined + false + + + all + + + + + + + + diff --git a/share/extensions/guillotine.py b/share/extensions/guillotine.py new file mode 100755 index 0000000..1e2c988 --- /dev/null +++ b/share/extensions/guillotine.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2010 Craig Marshall, craig9 [at] gmail.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +""" +This script slices an inkscape drawing along the guides, similarly to +the GIMP plugin called "guillotine". It can optionally export to the +same directory as the SVG file with the same name, but with a number +suffix. e.g. + +/home/foo/drawing.svg + +will export to: + +/home/foo/drawing0.png +/home/foo/drawing1.png +/home/foo/drawing2.png +/home/foo/drawing3.png + +etc. + +""" + +import os +import locale + +import inkex +from inkex.command import inkscape + +class Guillotine(inkex.EffectExtension): + """Exports slices made using guides""" + def add_arguments(self, pars): + pars.add_argument("--directory", type=str, dest="directory") + pars.add_argument("--image", type=str, dest="image") + pars.add_argument("--ignore", type=inkex.Boolean, dest="ignore") + + def get_all_horizontal_guides(self): + """ + Returns all horizontal guides as a list of floats stored as + strings. Each value is the position from 0 in pixels. + """ + for guide in self.svg.namedview.get_guides(): + if guide.is_horizontal: + yield guide.point.y + + def get_all_vertical_guides(self): + """ + Returns all vertical guides as a list of floats stored as + strings. Each value is the position from 0 in pixels. + """ + for guide in self.svg.namedview.get_guides(): + if guide.is_vertical: + yield guide.point.x + + def get_horizontal_slice_positions(self): + """ + Make a sorted list of all horizontal guide positions, + including 0 and the document height, but not including + those outside of the canvas + """ + horizontals = [0.0] + height = float(self.svg.height) + for y in self.get_all_horizontal_guides(): + if 0.0 < y <= height: + horizontals.append(y) + horizontals.append(height) + return sorted(horizontals) + + def get_vertical_slice_positions(self): + """ + Make a sorted list of all vertical guide positions, + including 0 and the document width, but not including + those outside of the canvas. + """ + verticals = [0.0] + width = float(self.svg.width) + for x in self.get_all_vertical_guides(): + if 0.0 < x <= width: + verticals.append(x) + verticals.append(width) + return sorted(verticals) + + def get_slices(self): + """ + Returns a list of all "slices" as denoted by the guides + on the page. Each slice is really just a 4 element list of + floats (stored as strings), consisting of the X and Y start + position and the X and Y end position. + """ + hs = self.get_horizontal_slice_positions() + vs = self.get_vertical_slice_positions() + slices = [] + for i in range(len(hs) - 1): + for j in range(len(vs) - 1): + slices.append([vs[j], hs[i], vs[j + 1], hs[i + 1]]) + return slices + + def get_filename_parts(self): + """ + Attempts to get directory and image as passed in by the inkscape + dialog. If the boolean ignore flag is set, then it will ignore + these settings and try to use the settings from the export + filename. + """ + + if not self.options.ignore: + if self.options.image == "" or self.options.image is None: + raise inkex.AbortExtension("Please enter an image name") + return self.options.directory, self.options.image + else: + ''' + First get the export-filename from the document, if the + document has been exported before (TODO: Will not work if it + hasn't been exported yet), then uses this to return a tuple + consisting of the directory to export to, and the filename + without extension. + ''' + try: + export_file = self.svg.get('inkscape:export-filename') + except KeyError: + raise inkex.AbortExtension( + "To use the export hints option, you " + "need to have previously exported the document. " + "Otherwise no export hints exist!") + dirname, filename = os.path.split(export_file) + filename = filename.rsplit(".", 1)[0] # Without extension + return dirname, filename + + def get_localised_string(self, name): + return locale.format_string("%.f", float(name), 0) + + def export_slice(self, sli, filename): + """ + Runs inkscape's command line interface and exports the image + slice from the 4 coordinates in s, and saves as the filename + given. + """ + coords = ":".join([self.get_localised_string(dim) for dim in sli]) + inkscape(self.options.input_file, export_area=coords, export_filename=filename) + + def export_slices(self, slices): + """ + Takes the slices list and passes each one with a calculated + filename/directory into export_slice. + """ + dirname, filename = self.get_filename_parts() + # Remove some crusty extensions from name template + if filename.endswith('.svg') or filename.endswith('.png'): + filename = filename.rsplit('.', 1)[0] + if '{' not in filename: + filename += '_{}' + + dirname = os.path.abspath(os.path.expanduser(os.path.expandvars(dirname or './'))) + if not os.path.isdir(dirname): + os.makedirs(dirname) + + output_files = [] + for i, slico in enumerate(slices): + fname = os.path.join(dirname, filename.format(i) + '.png') + output_files.append(fname) + self.export_slice(slico, fname) + + self.debug("The sliced bitmaps have been saved as:" + "\n\n" + "\n".join(output_files)) + + def effect(self): + self.export_slices(self.get_slices()) + + +if __name__ == "__main__": + Guillotine().run() diff --git a/share/extensions/handles.inx b/share/extensions/handles.inx new file mode 100644 index 0000000..259656e --- /dev/null +++ b/share/extensions/handles.inx @@ -0,0 +1,14 @@ + + + Draw Handles + org.ekips.filter.handles + + path + + + + + + diff --git a/share/extensions/handles.py b/share/extensions/handles.py new file mode 100755 index 0000000..3e34926 --- /dev/null +++ b/share/extensions/handles.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Draws handles of selected paths. +""" + +import inkex +from inkex.paths import Path, Curve, Move, Line, Quadratic +from inkex.transforms import Vector2d + +class Handles(inkex.EffectExtension): + """ + Renders the handle lines for the selected curves onto the canvas. + """ + def effect(self): + for node in self.svg.selection.filter(inkex.PathElement).values(): + result = Path() + prev = Vector2d() + start = None + for seg in node.path.to_absolute(): + if start is None: + start = seg.end_point(start, prev) + if isinstance(seg, Curve): + result += [ + Move(seg.x2, seg.y2), Line(prev.x, prev.y), + Move(seg.x3, seg.y3), Line(seg.x4, seg.y4), + ] + elif isinstance(seg, Quadratic): + result += [ + Move(seg.x2, seg.y2), Line(prev.x, prev.y), + Move(seg.x2, seg.y2), Line(seg.x3, seg.y3) + ] + prev = seg.end_point(start, prev) + + if not result: + continue + + elem = node.getparent().add(inkex.PathElement()) + elem.path = result + elem.style = {'stroke-linejoin': 'miter', 'stroke-width': '1.0px', + 'stroke-opacity': '1.0', 'fill-opacity': '1.0', + 'stroke': '#000000', 'stroke-linecap': 'butt', + 'fill': 'none'} + +if __name__ == '__main__': + Handles().run() diff --git a/share/extensions/hershey.inx b/share/extensions/hershey.inx new file mode 100755 index 0000000..e891b82 --- /dev/null +++ b/share/extensions/hershey.inx @@ -0,0 +1,131 @@ + + + Hershey Text + org.evilmad.text.hershey + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +HersheySans1 + +false + + + + + + + + + + + + + +The Quick Brown Fox Jumps Over a Lazy Dog + + + + + + + + + + + + + + + + + all + + + + + + diff --git a/share/extensions/hershey.py b/share/extensions/hershey.py new file mode 100644 index 0000000..0fa447b --- /dev/null +++ b/share/extensions/hershey.py @@ -0,0 +1,1939 @@ +# coding=utf-8 +# +# Copyright(C) 2020 - Windell H. Oskay, www.evilmadscientist.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +''' +Hershey Text 3.0.4, 2020-06-01 + +Copyright 2020, Windell H. Oskay, www.evilmadscientist.com + +Major revisions in Hershey Text 3.0: + +1. Migrate font format to use SVG fonts. + - SVG fonts support unicode, meaning that we can use a full range of + characters. We are no longer limited to the ASCII range that the + historical Hershey font formats used. + - Arbitrary curves are supported within glyphs; we are no longer limited to + the straight line segments used in the historical Hershey format. + - The set of fonts can now be expanded. + +2. Add a mechanism for adding your own SVG fonts, either within the + folder containing the default fonts, or from an external file or directory. + This is particularly important for installations where one does not + have access to edit the contents of the Inkscape extensions directory. + +3. Support font mapping: If a given font face is used for a given block of + text, check first to see if a matching SVG font is present. If not, + substitute with the default (selected) stroke font from the list of + included fonts. + +4. Instead of entering text (one line at a time) in the extension, + this script now converts text (either all text, or all selected text) + in the document, replacing it in place. While not every possible + method of formatting text is supported, many are. + +''' + +import os +import math + +from copy import deepcopy + +import inkex +from inkex import Transform, Style, units + +from inkex import load_svg, Group, TextElement, FlowPara, SVGfont, FontFace,\ + FlowSpan, Glyph, MissingGlyph, Tspan, FlowRoot, Rectangle, Use, PathElement, Defs + + +class Hershey(inkex.Effect): + + ''' + An extension for use with Inkscape 1.0 + ''' + + def __init__(self): + super(Hershey, self).__init__() + + self.arg_parser.add_argument("--tab", \ + dest="mode", \ + default="render", help="The active tab or mode when Apply was pressed") + + self.arg_parser.add_argument("--fontface", \ + dest="fontface", \ + default="HersheySans1", help="The selected font face when Apply was pressed") + + self.arg_parser.add_argument("--otherfont", \ + dest="otherfont", \ + default="", help="Optional other font name or path to use") + + self.arg_parser.add_argument("--preserve", \ + type=inkex.Boolean, dest="preserve_text", \ + default=False, help="Preserve original text") + + self.arg_parser.add_argument("--action", \ + dest="util_mode", \ + default="sample", help="The utility option selected") + + self.arg_parser.add_argument("--text", \ + dest="sample_text", \ + default="sample", help="Text to use for font table") + + self.font_file_list = dict() + self.font_load_fail = False + + self.svg_height = None + self.svg_width = None + + self.output_generated = False + + self.warn_unflow = False + self.warn_textpath = False # For future use: Give warning about text attached to path. + self.font_dict = dict() # Font dictionary - Dictionary of loaded fonts + + self.nodes_to_delete = [] # List of font elements to remove + + self.vb_scale_factor = 0.0104166666 + + self.text_string = "" + self.text_families = [] # List of font family for characters in the string + self.text_heights = [] # List of font heights + self.text_spacings = [] # List of vertical line heights + self.text_aligns = [] # List of horizontal alignment values + self.text_x = [] #List; x-coordinate of text line start + self.text_y = [] #List; y-coordinate of text line start + self.line_number = 0 + self.new_line = True + self.render_width = 1 + + PX_PER_INCH = 96.0 + + help_text = '''====== Hershey Text Help ====== + +The Hershey Text extension is designed to replace text in your document (either +selected text or all text) with specialized "stroke" or "engraving" fonts +designed for plotters. + +Whereas regular "outline" fonts (e.g., TrueType) work by filling in the region +inside an invisible outline, stroke fonts are composed only of individual lines +or strokes with finite width; much like human handwriting when using a physical +pen. + +Stroke fonts are most often used for creating text-like paths that computer +controlled drawing and cutting machines (from pen plotters to CNC routers) can +efficiently follow. + +A full user guide for Hershey Text is available to download from + http://wiki.evilmadscientist.com/hershey + + + ==== Basic operation ==== + +To use Hershey Text, start with a document that contains text objects. Select +the "Render" tab of Hershey Text, and choose a font face from the pop-up menu. + +When you click Apply, it will render all text elements on your page with the +selected stroke-based typeface. If you would like to convert only certain text +elements, click Apply with just those elements selected. + +If the "Preserve original text" box is checked, then the original text elements +on the page will be preserved even when you click Apply. If it is unchecked, +then the original font elements will be removed once rendered. + +You can generate a list of available SVG fonts or a list of all glyphs available +in a given font by using the tools available on the "Utilities" tab. + + + ==== How Hershey Text works ==== + +Hershey Text works by performing font substitution, starting with the text in +your document and replacing it with paths generated from the characters in the +selected SVG font. + +Hershey Text uses fonts in the SVG font format. While SVG fonts are one of the +few types that support stroke-based characters, it is important to note that +converting an outline font to SVG format does not convert it to a stroke based +font. Indeed, most SVG fonts are actually outline fonts. + +This extension *does not* convert outline fonts into stroke fonts, nor does it +convert other fonts into SVG format. Its sole function is to replace the text +in your document with paths from the selected SVG font. + + + ==== Using an external SVG font ==== + +To use an external SVG font -- one not included with the distribution -- select +"Other" for the name of the font in the pop-up menu on the "Render" tab. Then, +do one of the following: + +(1) Add your SVG font file (perhaps "example.svg") to the "svg_fonts" directory +within your Inkscape extensions directory, and enter the name of the font +("example") in the "Other SVG font name or path" box on the "Render" tab. + +or + +(2) Place your SVG font file anywhere on your computer, and enter the full path +to the file in the "Other SVG font name or path" box on the "Render" tab. +A full path might, for example, look like: + /Users/Robin/Documents/AxiDraw/fonts/path_handwriting.svg + + + ==== Using SVG fonts: Advanced methods ==== + +In addition to using a single SVG font for substitution, you can also use +font name mapping to automatically use particular stroke fonts in place of +specific font faces, to support various automated workflows and to support +the rapid use of multiple stroke font faces within the same document. + +Several SVG fonts are included with this distribution, including both +single-stroke and multi-stroke fonts. These fonts are included within the +"svg_fonts" directory within your Inkscape extensions directory. + +You can select the font that you would like to use from the pop-up menu on the +"Render" Tab. You can also make use of your own SVG fonts. + +Order of preference for SVG fonts: + +(1) If there is an SVG font with name matching that of the font for a given +piece of text, that font will be used. For example, if the original text is in +font "FancyScript" and there is a file in svg_fonts with name FancyScript.svg, +then FancyScript.svg will be used to render the text. + +(2) Otherwise (if there is no SVG font available matching the name of the font +for a given block of text), the face selected from the "Font face" pop-up menu +will be used as the default font when rendering text with Hershey Text. + +(3) You can also enter text in the "Name/Path" box, which can represent one of +the following: (i) a font name (for a font located in the svg_fonts directory), +(ii) the path to a font file elsewhere on your computer, or (iii) the path to a +directory containing (one or more) font files. + +(3a) Using a font name: +If you move a custom SVG font file into your svg_fonts directory, then you can +enter the name of the SVG font in the "Name/Path" text box and select "Other" +from the pop-up menu. Then, the named font will be used as the default. + +(3b) Using a file path: +If you enter the path to an SVG font file in the "Name/Path" text box and +select "Other" from the pop-up menu. Then, that font will be used as the +default. All SVG fonts located in the same directory as that font file will +also be available for name-based font substitution. If there are multiple +font-name matches, files in an external directory take precedence over ones in +the svg_fonts directory. + +(3c) Using a directory path: +If you enter the path to a directory containing SVG font files in the +"Name/Path" text box, then all SVG font files files in that directory will be +available for name-based font substitution. If there are multiple font-name +matches, files in an external directory take precedence over ones in the +svg_fonts directory. + + + +Tips about using these methods with your own custom fonts: + +(A) These methods can be used to render different text elements with different +SVG font faces. You can even rename a font -- either your own custom one or one +of the bundled ones -- to match the name of a font that you're using. For +example, if you rename a script font you name a font to "Helvetica.svg", +then all text in Helvetica will be replaced with that SVG font. + +(B) Using a directory path (3c) is a particularly helpful method if you do +not have access to modify the svg_fonts directory. + + + + ==== Limitations ==== + +This extension renders text into non-editable paths, generated from the +character geometry of SVG fonts. Once you have rendered the text, the resulting +paths can be edited with path editing tools, but not text editing tools. + +Since this extension works by a process of font substitution, text spanning a +single line will generally stay that way, whereas text flowed in a box (that +may span multiple lines) will be re-flowed from scratch. Style information such +as text size and line spacing can be lost in some cases. + +We recommend that you use the live preview option to achieve best results with +this extension. + + +(c) 2020 Windell H. Oskay +Evil Mad Scientist Laboratories +''' + + def getlength_inch(self, name): + """ + Get the attribute with name "name", and parse it as a length, + into a value and associated units. Return value in inches. + + This may cause scaling issues in some circumstances. Note, for + example, that Adobe Illustrator uses 72 px per inch, and Inkscape + used 90 px per inch prior to version 0.92. + """ + string_to_parse = self.document.getroot().get(name) + if string_to_parse: + value, unit = units.parse_unit(string_to_parse) + if value is None: + return None + bad_units = {'%', 'ex', 'em'} # Unsupported units + if unit in bad_units: + return None + + return units.convert_unit(string_to_parse, 'in') + return None + + + def units_to_userunits(self, input_string): + """ + Custom replacement for the old "unittouu" routine + + Parse the attribute into a value and associated units. + Return value in user units (typically "px"). + Importantly, return None for malformed inputs. + """ + + value, _ = units.parse_unit(input_string) + if value is None: + return None + + return units.convert_unit(input_string, '') + + + def vb_scale(self, viewbox, p_a_r, doc_width, doc_height): + """" + Parse SVG viewbox and generate scaling parameters. + Reference documentation: https://www.w3.org/TR/SVG11/coords.html + + Inputs: + viewbox: Contents of SVG viewbox attribute + p_a_r: Contents of SVG preserveAspectRatio attribute + doc_width: Width of SVG document + doc_height: Height of SVG document + + Output: s_x, s_y, o_x, o_y + Scale parameters (s_x, s_y) and offset parameters (o_x, o_y) + + """ + if viewbox is None: + return 1, 1, 0, 0 # No viewbox; return default transform + vb_array = viewbox.strip().replace(', ', ' ').split() + + if len(vb_array) < 4: + return 1, 1, 0, 0 # invalid viewbox; return default transform + + min_x = float(vb_array[0]) # Viewbox offset: x + min_y = float(vb_array[1]) # Viewbox offset: y + width = float(vb_array[2]) # Viewbox width + height = float(vb_array[3]) # Viewbox height + + if width <= 0 or height <= 0: + return 1, 1, 0, 0 # invalid viewbox; return default transform + + d_width = float(doc_width) + d_height = float(doc_height) + + if d_width <= 0 or d_height <= 0: + return 1, 1, 0, 0 # invalid document size; return default transform + + ar_doc = d_height / d_width # Document aspect ratio + ar_vb = height / width # Viewbox aspect ratio + + # Default values of the two preserveAspectRatio parameters: + par_align = "xmidymid" # "align" parameter(lowercased) + par_mos = "meet" # "meetOrSlice" parameter + + if p_a_r is not None: + par_array = p_a_r.strip().replace(', ', ' ').lower().split() + if len(par_array) > 0: + par0 = par_array[0] + if par0 == "defer": + if len(par_array) > 1: + par_align = par_array[1] + if len(par_array) > 2: + par_mos = par_array[2] + else: + par_align = par0 + if len(par_array) > 1: + par_mos = par_array[1] + + if par_align == "none": + # Scale document to fill page. Do not preserve aspect ratio. + # This is not default behavior, nor what happens if par_align + # is not given; the "none" value must be _explicitly_ specified. + + s_x = d_width/ width + s_y = d_height / height + o_x = -min_x + o_y = -min_y + return s_x, s_y, o_x, o_y + + """ + Other than "none", all situations fall into two classes: + + 1) (ar_doc >= ar_vb AND par_mos == "meet") + or (ar_doc < ar_vb AND par_mos == "slice") + -> In these cases, scale document up until VB fills doc in X. + + 2) All other cases, i.e., + (ar_doc < ar_vb AND par_mos == "meet") + or (ar_doc >= ar_vb AND par_mos == "slice") + -> In these cases, scale document up until VB fills doc in Y. + + Note in cases where the scaled viewbox exceeds the document + (page) boundaries (all "slice" cases and many "meet" cases where + an offset value is given) that this routine does not perform + any clipping, but subsequent clipping to the page boundary + is appropriate. + + Besides "none", there are 9 possible values of par_align: + xminymin xmidymin xmaxymin + xminymid xmidymid xmaxymid + xminymax xmidymax xmaxymax + """ + + if(((ar_doc >= ar_vb) and(par_mos == "meet")) + or((ar_doc < ar_vb) and(par_mos == "slice"))): + # Case 1: Scale document up until VB fills doc in X. + + s_x = d_width / width + s_y = s_x # Uniform aspect ratio + o_x = -min_x + + scaled_vb_height = ar_doc * width + excess_height = scaled_vb_height - height + + if par_align in {"xminymin", "xmidymin", "xmaxymin"}: + # Case: Y-Min: Align viewbox to minimum Y of the viewport. + o_y = -min_y + # OK: tested with Tall-Meet, Wide-Slice + + elif par_align in {"xminymax", "xmidymax", "xmaxymax"}: + # Case: Y-Max: Align viewbox to maximum Y of the viewport. + o_y = -min_y + excess_height + # OK: tested with Tall-Meet, Wide-Slice + + else: # par_align in {"xminymid", "xmidymid", "xmaxymid"}: + # Default case: Y-Mid: Center viewbox on page in Y + o_y = -min_y + excess_height / 2 + # OK: Tested with Tall-Meet, Wide-Slice + + return s_x, s_y, o_x, o_y + + # Case 2: Scale document up until VB fills doc in Y. + + s_y = d_height / height + s_x = s_y # Uniform aspect ratio + o_y = -min_y + + scaled_vb_width = height / ar_doc + excess_width = scaled_vb_width - width + + if par_align in {"xminymin", "xminymid", "xminymax"}: + # Case: X-Min: Align viewbox to minimum X of the viewport. + o_x = -min_x + # OK: Tested with Tall-Slice, Wide-Meet + + elif par_align in {"xmaxymin", "xmaxymid", "xmaxymax"}: + # Case: X-Max: Align viewbox to maximum X of the viewport. + o_x = -min_x + excess_width + # Need test: Tall-Slice, Wide-Meet + + else: # par_align in {"xmidymin", "xmidymid", "xmidymax"}: + # Default case: X-Mid: Center viewbox on page in X + o_x = -min_x + excess_width / 2 + # OK: Tested with Tall-Slice, Wide-Meet + + return s_x, s_y, o_x, o_y + + + def strip_quotes(self, fontname): + ''' + A multi-word font name may have a leading and trailing + single or double quotes, depending on the source. + If so, remove those quotes. + ''' + + if fontname.startswith("'") and fontname.endswith("'"): + return fontname[1:-1] + if fontname.startswith('"') and fontname.endswith('"'): + return fontname[1:-1] + return fontname + + def parse_svg_font(self, node_list): + ''' + Parse an input svg, searching for an SVG font. If an + SVG font is found, parse it and return a "digest" containing + structured information from the font. See below for more + about the digest format. + + If the font is not found cannot be parsed, return none. + + Notable limitations: + + (1) This function only parses the first font face found within the + tree. We may, in the future, support discovering multiple fonts + within an SVG file. + + (2) We are only processing left-to-right and horizontal text, + not vertical text nor RTL. + + (3) This function currently performs only certain recursive searches, + within the element. It will not discover fonts nested within + groups or other elements. So far as we know, that is not a limitation + in practice. (If you have a counterexample please contact Evil Mad + Scientist tech support and let us know!) + + (4) Kerning details are not implemented yet. + ''' + + digest = None + + if node_list is None: + return None + + for node in node_list: + if isinstance(node, Defs): + return self.parse_svg_font(node) # Recursive call + + if isinstance(node, SVGfont): + ''' + === Internal structure for storing font information === + + We parse the SVG font file and create a keyed "digest" + from it that we can use while rendering text on the page. + + This "digest" will be added to a dictionary that maps + each font family name to a single digest. + + The digest itself is a dictionary with the following + keys, some of which may have empty values. This format + will allow us to add additional keys at a later date, + to support additional SVG font features. + + font_id (a string) + + font_family (a string) + + glyphs + A dictionary mapping unicode points to a specific + dictionary for each point. See below for more about + the key format. + The dictionary for a given point will include keys: + glyph_name(string) + horiz_adv_x(numeric) + d(string) + + missing_glyph + A dictionary for a single code point, with keys: + horiz_adv_x(numeric) + d(string) + + geometry + A dictionary containing geometric data + Keys will include: + horiz_adv_x(numeric) -- Default value + units_per_em(numeric) + ascent(numeric) + descent(numeric) + x_height(numeric) + cap_height(numeric) + bbox (string) + underline_position(numeric) + scale + A numeric scaling factor computed from the + units_per_em value, which gives the overall scale + ''' + + digest = dict() + geometry = dict() + glyphs = dict() + missing_glyph = dict() + + digest['font_id'] = node.get('id') + + horiz_adv_x = node.get('horiz-adv-x') + + if horiz_adv_x is not None: + geometry['horiz_adv_x'] = float(horiz_adv_x) + # Note: case of no horiz_adv_x value is not handled. + + for element in node: + + if isinstance(element, Glyph): + # First, because it is the most common element + try: + uni_text = element.get('unicode') + except: + # Can't use this point if no unicode mapping. + continue + + if uni_text is None: + continue + + if uni_text in glyphs: + # Skip if that unicode point is already in the + # list of glyphs.(There is not currently support + # for alternate glyphs in the font.) + continue + + glyph_dict = dict() + glyph_dict['glyph_name'] = element.get('glyph-name') + + horiz_adv_x = element.get('horiz-adv-x') + + if horiz_adv_x is not None: + glyph_dict['horiz_adv_x'] = float(horiz_adv_x) + else: + glyph_dict['horiz_adv_x'] = geometry['horiz_adv_x'] + + glyph_dict['d'] = element.get('d') # SVG path data + glyphs[uni_text] = glyph_dict + + elif isinstance(element, FontFace): + digest['font_family'] = element.get('font-family') + units_per_em = element.get('units-per-em') + + if units_per_em is None: + # Default: 1000, per SVG specification. + geometry['units_per_em'] = 1000.0 + else: + geometry['units_per_em'] = float(units_per_em) + + ascent = element.get('ascent') + if ascent is not None: + geometry['ascent'] = float(ascent) + + descent = element.get('descent') + if descent is not None: + geometry['descent'] = float(descent) + + ''' + # Skip these attributes that we are not currently using + geometry['x_height'] = element.get('x-height') + geometry['cap_height'] = element.get('cap-height') + geometry['bbox'] = element.get('bbox') + geometry['underline_position'] = element.get('underline-position') + ''' + + elif isinstance(element, MissingGlyph): + horiz_adv_x = element.get('horiz-adv-x') + + if horiz_adv_x is not None: + missing_glyph['horiz_adv_x'] = float(horiz_adv_x) + else: + missing_glyph['horiz_adv_x'] = geometry['horiz_adv_x'] + + missing_glyph['d'] = element.get('d') # SVG path data + digest['missing_glyph'] = missing_glyph + + + # Main scaling factor + digest['scale'] = 1.0 / geometry['units_per_em'] + + digest['glyphs'] = glyphs + digest['geometry'] = geometry + + return digest + return None + + def load_font(self, fontname): + ''' + Attempt to load an SVG font from a file in our list + of (likely) SVG font files. + If we can, add the contents to the font library. + Otherwise, add a "None" entry to the font library. + ''' + + if fontname is None: + return + + if fontname in self.font_dict: + return # Awesome: The font is already loaded. + + if fontname in self.font_file_list: + the_path = self.font_file_list[fontname] + else: + self.font_dict[fontname] = None + return # Font not located. + try: + ''' + Check to see if there is an SVG font file for us to read. + + At present, only one font file will be read per font family; + the name of the file must be FONT_NAME.svg, where FONT_NAME + is the name of the font family. + + Only the first font found in the font file will be read. + Multiple weights and styles within a font family are not + presently supported. + ''' + font_svg = load_svg(the_path) + self.font_dict[fontname] = self.parse_svg_font(font_svg.getroot()) + + except IOError: + self.font_dict[fontname] = None + except: + inkex.errormsg('Error parsing SVG font at ' + str(the_path)) + self.font_dict[fontname] = None + + + def font_table(self): + ''' + Generate display table of all available SVG fonts + ''' + + self.options.preserve_text = False + + # Embed text in group to make manipulation easier: + group = self.svg.get_current_layer().add(Group()) + for fontname in self.font_file_list: + self.load_font(fontname) + + font_size = 0.2 # in inches -- will be scaled by viewbox factor. + font_size_text = str(font_size / self.vb_scale_factor) + 'px' + + labeltext_style = str(Style({'stroke' : 'none', \ + 'font-size':font_size_text, 'fill' : 'black', \ + 'font-family' : 'sans-serif', 'text-anchor': 'end'})) + + x_offset = font_size / self.vb_scale_factor + y_offset = 1.5 * x_offset + y = y_offset + + for fontname in sorted(self.font_dict): + if self.font_dict[fontname] is None: + continue # If the SVG file did NOT contain a font, skip it. + + text_attribs = {'x':'0', 'y': str(y), 'hershey-ignore':'true'} + textline = group.add(TextElement(**text_attribs)) + textline.text = fontname + textline.style = labeltext_style + text_attribs = {'x':str(x_offset), 'y': str(y)} + + sampletext_style = {'stroke' : 'none', \ + 'font-size':font_size_text, \ + 'fill' : 'black', 'font-family' : fontname, \ + 'text-anchor': 'start'} + sampleline = group.add(TextElement(**text_attribs)) + + try: # python 2 + sampleline.text = self.options.sample_text.decode('utf-8') + except AttributeError: # python 3 + sampleline.text = self.options.sample_text + + sampleline.style = sampletext_style + y += y_offset + self.recursively_traverse_svg(group, self.doc_transform) + + + def glyph_table(self): + ''' + Generate display table of glyphs within the current SVG font. Sorted display of + all printable characters in the font _except_ missing glyph. + ''' + + self.options.preserve_text = False + + fontname = self.font_load_wrapper('not_a_font_name') # force load of default + + if self.font_load_fail: + inkex.errormsg('Font not found; Unable to generate glyph table.') + return + + # Embed in group to make manipulation easier: + group = self.svg.get_current_layer().add(Group()) + + # missing_glyph = self.font_dict[fontname]['missing_glyph'] + + glyph_count = 0 + for glyph in self.font_dict[fontname]['glyphs']: + if self.font_dict[fontname]['glyphs'][glyph]['d'] is not None: + glyph_count += 1 + + columns = int(math.floor(math.sqrt(glyph_count))) + + font_size = 0.4 # in inches -- will be scaled by viewbox factor. + font_size_text = str(font_size / self.vb_scale_factor) + 'px' + + glyph_style = str(Style({'stroke' : 'none', \ + 'font-size':font_size_text, 'fill' : 'black', \ + 'font-family' : fontname, 'text-anchor': 'start'})) + + x_offset = 1.5 * font_size / self.vb_scale_factor + y_offset = x_offset + x = x_offset + y = y_offset + + draw_position = 0 + + for glyph in sorted(self.font_dict[fontname]['glyphs']): + if self.font_dict[fontname]['glyphs'][glyph]['d'] is None: + continue + y_pos, x_pos = divmod(draw_position, columns) + x = x_offset *(x_pos + 1) + y = y_offset *(y_pos + 1) + text_attribs = {'x':str(x), 'y': str(y)} + sampleline = group.add(TextElement(**text_attribs)) + sampleline.text = glyph + sampleline.style = glyph_style + draw_position = draw_position + 1 + + self.recursively_traverse_svg(group, self.doc_transform) + + + def find_font_files(self): + ''' + Create list of "plausible" SVG font files + + List items in primary svg_fonts directory, typically located in the + directory where this script is being executed from. + + If there is text given in the "Other name/path" input, that text may + represent one of the following: + + (A) The name of a font file, located in the svg_fonts directory. + - This may be given with or without the .svg suffix. + - If it is a font file, and the font face selected is "other", + then use this as the default font face. + + (B) The path to a font file, located elsewhere. + - If it is a font file, and the font face selected is "other", + then use this as the default font face. + - ALSO: Search the directory where that file is located for + any other SVG fonts. + + (C) The path to a directory + - It may or may not have a trailing separator + - Search that directory for SVG fonts. + + This function will create a list of available files that + appear to be SVG(SVG font) files. It does not parse the files. + We will format it as a dictionary, that maps each file name + (without extension) to a path. + ''' + + self.font_file_list = dict() + + # List contents of primary font directory: + font_directory_name = 'svg_fonts' + + font_dir = os.path.realpath( + os.path.join(os.getcwd(), font_directory_name)) + for dir_item in os.listdir(font_dir): + if dir_item.endswith((".svg", ".SVG")): + file_path = os.path.join(font_dir, dir_item) + if os.path.isfile(file_path): # i.e., if not a directory + root, _ = os.path.splitext(dir_item) + self.font_file_list[root] = file_path + + # split off file extension(e.g., ".svg") + root, _ = os.path.splitext(self.options.otherfont) + + # Check for case "(A)": Input text is the name + # of an item in the primary font directory. + if root in self.font_file_list: + # If we already have that name in our font_file_list, + # and "other" is selected, this is now + # our default font face. + if self.options.fontface == "other": + self.options.fontface = root + return + + test_path = os.path.realpath(self.options.otherfont) + + # Check for case "(B)": A file, not in primary font directory + if os.path.isfile(test_path): + directory, file_name = os.path.split(test_path) + root, ext = os.path.splitext(file_name) + self.font_file_list[root] = test_path + + if self.options.fontface == "other": + self.options.fontface = root + + # Also search the directory where that file + # was located for other SVG files(which may be fonts) + + for dir_item in os.listdir(directory): + if dir_item.endswith((".svg", ".SVG")): + file_path = os.path.join(directory, dir_item) + if os.path.isfile(file_path): # i.e., if not a directory + root, _ = os.path.splitext(dir_item) + self.font_file_list[root] = file_path + return + + # Check for case "(C)": A directory name + if os.path.isdir(test_path): + for dir_item in os.listdir(test_path): + if dir_item.endswith((".svg", ".SVG")): + file_path = os.path.join(test_path, dir_item) + if os.path.isfile(file_path): # i.e., if not a directory + root, _ = os.path.splitext(dir_item) + self.font_file_list[root] = file_path + + + def font_load_wrapper(self, fontname): + ''' + + This implements the following logic: + + * Check to see if the font name is in our lookup table of fonts, + self.font_dict + + * If the font is not listed in font_dict[]: + * Check to see if there is a corresponding SVG font file that + can be opened and parsed. + + * If the font can be opened and parsed: + * Add that font to font_dict. + * Otherwise + * Add the font name to font_dict as None. + + * If the font has value None in font_dict: + * Try to load fallback font. + + * Fallback font: + * If an SVG font matching that in the SVG is not available, + check to see if the default font is available. That font + is given by self.options.fontface + + * If a font is loaded and available, return the font name. + Otherwise, return none. + + ''' + + self.load_font(fontname) # Load the font if available + + ''' + It *may* be worth building one stroke font (e.g., Hershey Sans 1-stroke) as a + variable defined in this file so that it can be used even if no external + SVG font files are available. + ''' + + if self.font_dict[fontname] is None: + + # If we were not able to load the requested font:: + fontname = self.options.fontface # Fallback + if fontname not in self.font_dict: + self.load_font(fontname) + else: + pass + + if self.font_dict[fontname] is None: + self.font_load_fail = True # Set a flag so that we only generate one copy of this error. + return None + return fontname + + + def get_font_char(self, fontname, char): + ''' + Given a font face name and a character(unicode point), + return an SVG path, horizontal advance value, + and scaling factor. + + If the font is not available by name, use the default font. + ''' + + fontname = self.font_load_wrapper(fontname) # Load the font if available + + if fontname is None: + return None + + try: + scale_factor = self.font_dict[fontname]['scale'] + except: + scale_factor = 0.001 # Default: 1/1000 + + try: + if char not in self.font_dict[fontname]['glyphs']: + x_adv = self.font_dict[fontname]['missing_glyph']['horiz_adv_x'] + + return self.font_dict[fontname]['missing_glyph']['d'], \ + x_adv, scale_factor + x_adv = self.font_dict[fontname]['glyphs'][char]['horiz_adv_x'] + + return self.font_dict[fontname]['glyphs'][char]['d'], \ + x_adv, scale_factor + except: + return None + + + def handle_viewbox(self): + ''' + Wrapper function for processing viewbox information + ''' + + self.svg_height = self.getlength_inch('height') + self.svg_width = self.getlength_inch('width') + + self.svg = self.document.getroot() + viewbox = self.svg.get('viewBox') + if viewbox: + p_a_r = self.svg.get('preserveAspectRatio') + s_x, s_y, o_x, o_y = self.vb_scale(viewbox, p_a_r, self.svg_width, self.svg_height) + else: + s_x = 1.0 / float(self.PX_PER_INCH) # Handle case of no viewbox + s_y = s_x + o_x = 0.0 + o_y = 0.0 + + # Initial transform of document is based on viewbox, if present: + self.doc_transform = Transform(scale=(s_x, s_y), translate=(o_x, o_y)) + + self.vb_scale_factor = (s_x + s_y) / 2.0 + # In case of non-square aspect ratio, use average value. + + + def draw_svg_text(self, chardata, parent): + ''' + Render an individual svg glyph + ''' + char = chardata['char'] + font_family = chardata['font_family'] + offset = chardata['offset'] + vertoffset = chardata['vertoffset'] + font_height = chardata['font_height'] + font_scale = 1.0 + + # Stroke scale factor, including external transformations: + stroke_scale = chardata['stroke_scale'] * self.vb_scale_factor + + try: + path_string, adv_x, scale_factor = self.get_font_char(font_family, char) + except: + adv_x = 0 + path_string = None + scale_factor = 1.0 + + if self.font_load_fail: + return 0 + + font_scale *= scale_factor * font_height + + h_offset = 0 + v_offset = 0 + + # SVG fonts use inverted Y axis; mirror vertically + scale_transform = Transform(scale=(font_scale, -font_scale)) + + # Combine scales of external transformations with the scaling + # applied by this function: + _scale = font_scale * stroke_scale + if _scale == 0: + _scale = 1 + stroke_width = self.render_width / _scale + + # Stroke-width is a css style element; cannot use scientific notation. + # Thus, use variable width for encoding the stroke width factor: + + log_ten = math.log10(stroke_width) + if log_ten > 0: # For stroke_width > 1 + width_string = "{0:.3f}in".format(stroke_width) + else: + prec = int(math.ceil(-log_ten) + 3) + width_string = "{0:.{1}f}in".format(stroke_width, prec) + + p_style = {'stroke-width': width_string} + + the_transform = Transform(translate=(offset + h_offset, vertoffset + v_offset)) + the_transform *= scale_transform + + if path_string is not None: + path_element = parent.add(PathElement()) + path_element.set_path(path_string) + path_element.style = p_style + path_element.transform = the_transform + self.output_generated = True + + return offset + float(adv_x) * font_scale # new horizontal offset value + + + def recursive_get_encl_transform(self, node): + + ''' + Determine the cumulative transform which node inherits from + its chain of ancestors. + ''' + node = node.getparent() + if node is not None: + parent_transform = self.recursive_get_encl_transform(node) + node_transform = node.get('transform', None) + if node_transform is None: + return parent_transform + trans = Transform(node_transform).matrix + + if parent_transform is None: + return trans + return Transform(parent_transform) * Transform(trans) + return self.doc_transform + + + def recursively_parse_flowroot(self, node_list, parent_info): + ''' + Parse a flowroot node and its children + ''' + + # By default, inherit these values from parent: + font_height_local = parent_info['font_height'] + font_family_local = parent_info['font_family'] + line_spacing_local = parent_info['line_spacing'] + text_align_local = parent_info['align'] + + for node in node_list: + try: + node_style = node.style + except ValueError: + pass + + try: + font_height = node_style['font-size'] + font_height_local = self.units_to_userunits(font_height) + except KeyError: + pass + + try: + font_family_local = self.strip_quotes(node_style['font-family']) + except: + pass + + try: + line_spacing = node_style['line-height'] + if "%" in line_spacing: # Handle percentage line spacing(e.g., 125%) + line_spacing_local = float(line_spacing.rstrip("%")) / 100.0 + else: + line_spacing_local = self.units_to_userunits(line_spacing) + except KeyError: + pass + + try: + text_align_local = node_style['text-align'] # Use text-anchor in text nodes + except KeyError: + pass + + if node.text is not None: + self.text_string += node.text + + for _ in node.text: + self.text_families.append(font_family_local) + self.text_heights.append(font_height_local) + self.text_spacings.append(line_spacing_local) + self.text_aligns.append(text_align_local) + + if isinstance(node, (FlowPara, FlowSpan)): + the_style = dict() + the_style['font_height'] = font_height_local + the_style['font_family'] = font_family_local + the_style['line_spacing'] = line_spacing_local + the_style['align'] = text_align_local + + self.recursively_parse_flowroot(node, the_style) + + if node.tail is not None: + # By default, inherit these values from parent: + font_height_local = parent_info['font_height'] + font_family_local = parent_info['font_family'] + line_spacing_local = parent_info['line_spacing'] + + text_align_local = parent_info['align'] + self.text_string += node.tail + for _ in node.tail: + self.text_families.append(font_family_local) + self.text_heights.append(font_height_local) + self.text_spacings.append(line_spacing_local) + self.text_aligns.append(text_align_local) + + if isinstance(node, FlowPara): + self.text_string += "\n" # Conclude every flowpara with a return + self.text_families.append(font_family_local) + self.text_heights.append(font_height_local) + self.text_spacings.append(line_spacing_local) + self.text_aligns.append(text_align_local) + + def recursively_parse_text(self, node, parent_info): + ''' + parse a text node and its children + ''' + + # By default, inherit these values from parent: + font_height_local = parent_info['font_height'] + font_family_local = parent_info['font_family'] + anchor_local = parent_info['anchor'] + x_local = parent_info['x_pos'] + y_local = parent_info['y_pos'] + parent_line_spacing = parent_info['line_spacing'] + + try: + node_style = node.style + except: + pass + + try: + font_height = node_style['font-size'] + font_height_local = self.units_to_userunits(font_height) + except KeyError: + pass + + try: + font_family_local = self.strip_quotes(node_style['font-family']) + except KeyError: + pass + + try: + anchor_local = node_style['text-anchor'] # Use text-anchor in text nodes + except KeyError: + pass + + try: + x_temp = node.get('x') + if x_temp is not None: + x_local = x_temp + except ValueError: + pass + + try: + y_temp = node.get('y') + if y_temp is not None: + y_local = y_temp + else: + # Special case, to handle multi-line text given by tspan + # elements that do not have y values + if y_local is None: + y_local = 0 + y_local = float(y_local) + \ + self.line_number * parent_line_spacing * font_height_local + except ValueError: + pass + + if node.text is not None: + self.text_string += node.text + + for _ in node.text: + self.text_families.append(font_family_local) + self.text_heights.append(font_height_local) + self.text_aligns.append(anchor_local) + self.text_x.append(x_local) + self.text_y.append(y_local) + + + for sub_node in node: + # If text is located within a sub_node of this node, + # process that sub_node, with this very routine. + + if isinstance(sub_node, Tspan): + # Note: There may be additional types of text tags that + # we should recursively search as well. + node_info = dict() + node_info['font_height'] = font_height_local + node_info['font_family'] = font_family_local + node_info['anchor'] = anchor_local + node_info['x_pos'] = x_local + node_info['y_pos'] = y_local + node_info['line_spacing'] = parent_line_spacing + + adv_line = False + role = sub_node.get('sodipodi:role') + if role == "line": + adv_line = True + + self.recursively_parse_text(sub_node, node_info) + + # Increment line after tspan if it is labeled as a line + if adv_line: + self.line_number = self.line_number + 1 + + if node.tail is not None: + _stripped_tail = node.tail.strip() + if _stripped_tail is not None: + # By default, inherit these values from parent: + font_height_local = parent_info['font_height'] + font_family_local = parent_info['font_family'] + text_align_local = parent_info['anchor'] + x_local = parent_info['x_pos'] + y_local = parent_info['y_pos'] + self.text_string += _stripped_tail + for _ in _stripped_tail: + self.text_heights.append(font_height_local) + self.text_families.append(font_family_local) + self.text_aligns.append(text_align_local) + self.text_x.append(x_local) + self.text_y.append(y_local) + + def recursively_traverse_svg(self, anode_list, + mat_current=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], + parent_visibility='visible'): + ''' + recursively parse the full document and its children, + looking for nodes that may contain text + ''' + + for node in anode_list: + + # Ignore invisible nodes + vis = node.get('visibility', parent_visibility) + if vis == 'inherit': + vis = parent_visibility + if vis in ('hidden', 'collapse'): + continue + + # First apply the current matrix transform to this node's tranform + _matrix = node.transform + mat_new = Transform(mat_current) * Transform(_matrix) + + if isinstance(node, Group): + + recurse_group = True + ink_label = node.get('inkscape:label') + + if not ink_label: + pass + else: + if(ink_label == 'Hershey Text'): + recurse_group = False # Do not traverse groups of rendered text. + if recurse_group: + self.recursively_traverse_svg(node, mat_new, vis) + + elif isinstance(node, Use): + # A element refers to another SVG element via an xlink:href="#blah" + # attribute. We will handle the element by doing an XPath search through + # the document, looking for the element with the matching id="blah" + # attribute. We then recursively process that element after applying + # any necessary(x, y) translation. + # + # Notes: + # 1. We ignore the height and width attributes as they do not apply to + # path-like elements, and + # 2. Even if the use element has visibility="hidden", SVG still calls + # for processing the referenced element. The referenced element is + # hidden only if its visibility is "inherit" or "hidden". + + refnode = node.href + if refnode is None: + continue # missing reference + + local_transform = Transform(_matrix) + x = float(node.get('x', '0')) + y = float(node.get('y', '0')) + # Note: the transform has already been applied + if(x != 0) or(y != 0): + _trans_string = 'translate({0:.6E}, {1:.6E})'.format(x, y) + ref_transform = Transform(_matrix) * Transform(_trans_string) + else: + ref_transform = local_transform + + try: + ref_group = anode_list.add(Group())# Add a subgroup + except AttributeError: + inkex.errormsg('Unable to process text. Consider unlinking cloned text.') + continue + + # Tests are not using the preset seed for this atm + #if 'id' not in ref_group.attrib: + # ref_group.set_random_id('') + + ref_group.set('transform', ref_transform) + + ref_group.append(deepcopy(refnode)) + + for sub_node in ref_group: + # The copied text elements should be removed at the end, + # or they will persist if original elements are preserved. + self.nodes_to_delete.append(sub_node) + + #Preserve original element? + if not self.options.preserve_text: + self.nodes_to_delete.append(node) + + + elif isinstance(node, (TextElement, FlowRoot)): + + # Flag for when we start a new line of text, for use with indents: + self.new_line = True + + start_x = 0 # Defaults; Fail gracefully in case xy position is not given. + start_y = 0 + + # Default line spacing and font height: 125%, 16 px + line_spacing = self.units_to_userunits("1.25") + font_height = self.units_to_userunits("16px") + + start_x = node.get('x') # XY Position of element + start_y = node.get('y') + + bounding_rect = False + #rect_height = 100 #default size of bounding rectangle for flowroot object + rect_width = 100 #default size of bounding rectangle for flowroot object + transform = "" #transform(scale, translate, matrix, etc.) + text_align = "start" + + try: + hershey_ignore = node.get('hershey-ignore') + if hershey_ignore is not None: + continue # If the attribute is present, skip this node. + except ValueError: + pass + + try: + node_style = node.style + except ValueError: + pass + + font_height = 16 + try: + font_height_temp = node_style['font-size'] + font_height = self.units_to_userunits(font_height_temp) + except KeyError: + pass + + font_family = 'sans-serif' + try: + font_family = self.strip_quotes(node_style['font-family']) + except KeyError: + pass + + try: + line_spacing_temp = node_style['line-height'] + if "%" in line_spacing_temp: # Handle percentage line spacing(e.g., 125%) + line_spacing = float(line_spacing_temp.rstrip("%")) / 100.0 + else: + line_spacing = self.units_to_userunits(line_spacing_temp) + except KeyError: + pass + + try: + transform = node.transform + except ValueError: + pass + + if(transform is not None): + transform2 = Transform(transform).matrix + + ''' + Compute estimate of transformation scale applied to + this element, for purposes of calculating the + stroke width to apply. When all transforms are applied + and our elements are displayed on the page, we want the + final visible stroke width to be reasonable. + Transformation matrix is [[a c e][b d f]] + scale_x = sqrt(a * a + b * b), + scale_y = sqrt(c * c + d * d) + Take estimated scale as the mean of the two. + ''' + + scale_x = math.sqrt(transform2[0][0] * transform2[0][0] + + transform2[1][0] * transform2[1][0]) + scale_y = math.sqrt(transform2[0][1] * transform2[0][1] + + transform2[1][1] * transform2[1][1]) + + scale_r = (scale_x + scale_y) / 2.0 # Average. ¯\_(ツ)_/¯ + else: + scale_r = 1.0 + + the_id = node.get('id') + + #Initialize text attribute lists for each top-level text object: + self.text_string = "" + self.text_families = [] # Lis of font family for characters in the string + self.text_heights = [] # List of font heights + self.text_spacings = [] # List of vertical line heights + self.text_aligns = [] # List of horizontal alignment values + self.text_x = [] #List; x-coordinate of text line start + self.text_y = [] #List; y-coordinate of text line start + + # Group generated paths together, to make the rendered letters + # easier to manipulate in Inkscape once generated: + parent = node.getparent() + + group = parent.add(Group()) + group.label = 'Hershey Text' + + style = {'stroke' : '#000000', 'fill' : 'none', \ + 'stroke-linecap' : 'round', 'stroke-linejoin' : 'round'} + + # Apply rounding to ends to improve final engraved text appearance. + group.style = style + # Some common variables used in both cases A and B: + str_pos = 0 # Position through the full string that we are rendering + i = 0 # Dummy(index) variable for looping over letters in string + w = 0 # Initial spacing offset + w_temp = 0 # Temporary variable for horizontal spacing offset + width_this_line = 0 # Estimated width of characters to be stored on this line + + ''' + CASE A: Handle flowed text nodes + ''' + + if isinstance(node, FlowRoot): + + try: + text_align = node_style['text-align'] + # Use text-align, not text-anchor, in flowroot + except KeyError: + pass + + #selects the flowRegion's child(svg:rect) to get @X and @Y + flowref = \ + self.svg.getElement('/svg:svg//*[@id="%s"]/svg:flowRegion[1]' % the_id)[0] + + if isinstance(flowref, Rectangle): + start_x = flowref.left + start_y = flowref.top + rect_height = flowref.height + rect_width = flowref.width + bounding_rect = True + + elif isinstance(flowref, Use): + pass + + # A element refers to another SVG element via an xlink:href="#blah" + # attribute. We will handle the element by doing an XPath search through + # the document, looking for the element with the matching id="blah" + # attribute. We then recursively process that element after applying + # any necessary(x, y) translation. + # + # Notes: + # 1. We ignore the height and width attributes as they do not apply to + # path-like elements, and + # 2. Even if the use element has visibility="hidden", SVG still calls + # for processing the referenced element. The referenced element is + # hidden only if its visibility is "inherit" or "hidden". + # 3. We may be able to unlink clones using the code in pathmodifier.py + + # The following code can render text flowed into a rectangle object. + # HOWEVER, it does not handle the various transformations that could occur + # be present on those objects, and does not handle more general cases, such + # as a rotated rectangle -- for which text *should* flow in a diamond shape. + # For the time being, we skip these and issue a warning. + # + # refid = flowref.get('xlink:href') + # if refid is not None: + # # [1:] to ignore leading '#' in reference + # path = '//*[@id="%s"]' % refid[1:] + # refnode = flowref.xpath(path) + # if refnode is not None: + # refnode = refnode[0] + # if isinstance(refnode, Rectangle): + # start_x = refnode.get('x") + # start_y = refnode.get('y") + # rect_height = refnode.get('height") + # rect_width = refnode.get('width") + # bounding_rect = True + + if not bounding_rect: + self.warn_unflow = True + continue + + ''' + Recursively loop through content of the flowroot object, + looping through text, flowpara, and other things. + + Create multiple lists: One of text content, + others of style that should be applied to that content. + + then, loop through those lists, one line at a time, + finding how many words fit on a line, etc. + ''' + + the_style = dict() + the_style['font_height'] = font_height + the_style['font_family'] = font_family + the_style['line_spacing'] = line_spacing + the_style['align'] = text_align + + self.recursively_parse_flowroot(node, the_style) + + if(self.text_string == ""): + continue # No convertable text in this SVG element. + + if(self.text_string.isspace()): + continue # No convertable text in this SVG element. + + # Initial vertical offset for the flowed text block: + v = 0 + + # Record that we are on the first line of the paragraph + # for setting the v position of the first line. + first_line = True + + # Keep track of text height on first line, for moving entire text box: + y_offs_overall = 0 + + # Split text by lines AND make a list of how long each + # line is, including the newline characters. + # We need to keep track of this to match up styling + # information to the printable characters. + + text_lines = self.text_string.splitlines() + extd_text_lines = self.text_string.splitlines(True) + str_pos_eol = 0 # str_pos after end of previous text_line. + + nbsp = u'\xa0' # Unicode non-breaking space character + + for line_number, text_line in enumerate(text_lines): + + line_length = len(text_line) + extd_line_length = len(extd_text_lines[line_number]) + + i = 0 # Position within this text_line. + + # A given text_line may take more than one strip + # to render, if it overflows our box width. + + line_start = 0 # Value of i when the current strip started. + + if line_length == 0: + str_pos_temp = str_pos_eol + char_height = float(self.text_heights[str_pos_temp]) + charline_spacing = float(self.text_spacings[str_pos_temp]) + char_v_spacing = charline_spacing * char_height + v = v + char_v_spacing + else: + while(i < line_length): + + word_start = i # Value of i at beginning of the current word. + + while(i < line_length): # Step through the line + # until we reach the end of the line or word. + #(i.e., until we reach whitespace) + character = text_line[i] # character is unicode(not byte string) + str_pos_temp = str_pos_eol + i + + char_height = self.text_heights[str_pos_temp] + char_family = self.text_families[str_pos_temp] + + try: + _, x_adv, scale_factor = \ + self.get_font_char(char_family, character) + except: + x_adv = 0 + scale_factor = 1 + + w_temp += x_adv * scale_factor * char_height + + i += 1 + if character.isspace() and not character == nbsp: + break # Break at space, except non-breaking + + render_line = False + if w_temp > rect_width: # If the word will overflow the box + if word_start == line_start: + # This is the first word in the strip, so this + # word(alone) is wider than the box. Render it. + render_line = True + else: # Not the first word in the strip. + # Render the line up UNTIL this word. + render_line = True + i = word_start + elif i >= line_length: + # Render at end of text_line, if not overflowing. + render_line = True + + if render_line: + # Create group for rendering a strip of text: + line_group = group.add(Group()) + + w_temp = 0 + w = 0 + + self.new_line = True + width_this_line = 0 + line_max_v_spacing = 0 + + j = line_start + + while(j < i): # Calculate max height for the strip: + str_pos_temp = str_pos_eol + j + char_height = float(self.text_heights[str_pos_temp]) + charline_spacing = float(self.text_spacings[str_pos_temp]) + char_v_spacing = charline_spacing * char_height + if(char_v_spacing > line_max_v_spacing): + line_max_v_spacing = char_v_spacing + j = j + 1 + + v = v + line_max_v_spacing + + char_data = dict() + char_data['vertoffset'] = v + char_data['stroke_scale'] = scale_r + + j = line_start + while(j < i): # Render the strip on the page + str_pos = str_pos_eol + j + + char_height = self.text_heights[str_pos] + char_family = self.text_families[str_pos] + text_align = self.text_aligns[str_pos] + + char_data['char'] = text_line[j] + char_data['font_height'] = char_height + char_data['font_family'] = char_family + char_data['offset'] = w + + w = self.draw_svg_text(char_data, line_group) + + width_this_line = w + + j = j + 1 + str_pos = str_pos + 1 + + line_start = i + + # Alignment for the strip: + + the_transform = None + if(text_align == "center"): # when using text-align + the_transform = Transform(translate=\ + ((float(rect_width) - width_this_line)/2)) + elif(text_align == "end"): + the_transform = Transform(translate=\ + (float(rect_width) - width_this_line)) + if the_transform is not None: + line_group.transform = the_transform + + if first_line: + y_offs_overall = line_max_v_spacing / 3 # Heuristic + first_line = False + + str_pos_eol = str_pos_eol + extd_line_length + str_pos = str_pos_eol + + the_transform = Transform(translate=(start_x, float(start_y) - y_offs_overall)) + + else: # If this is a text object, rather than a flowroot object: + ''' + CASE B: Handle regular(non-flowroot) text nodes + ''' + + try: + # Use text-anchor, not text-align, in text(not flowroot) elements + text_align = node_style["text-anchor"] + except KeyError: + pass + + ''' + Recursively loop through content of the text object, + looping through text, tspan, and other things as necessary. + (A recursive search since style elements may be nested.) + + Create multiple lists: One of text content, others of the + style that should be applied to that content. + + For each line, want to record the plain text, font size + per character, text alignment, and x, y start values + for that line) + + (We may need to eventually handle additional text types and + tags, as when importing from other SVG sources. We should + try to eventually support additional formulations + of x, y, dx, dy, etc. + https://www.w3.org/TR/SVG/text.html#TSpanElement) + + then, loop through those lists, one line at a time, + rendering text onto lines. If the x or y values changed, + assume we've started a new line. + + Note: A text element creates a single line + of text; it does not create multiline text by including + line returns within the text itself. Multiple lines of text + are created with multiple text or tspan elements. + ''' + + node_info = dict() + node_info['font_height'] = font_height + node_info['font_family'] = font_family + node_info['anchor'] = text_align + node_info['x_pos'] = start_x + node_info['y_pos'] = start_y + node_info['line_spacing'] = line_spacing + + # Keep track of line number. Used in cases where daughter + # tspan elements do not have Y positions given. + # Reset to zero on each text element. + self.line_number = 0 + + self.recursively_parse_text(node, node_info) + # self.recursively_parse_text(node, font_height, text_align, start_x, start_y) + + if(self.text_string == ""): + continue # No convertable text in this SVG element. + if(self.text_string.isspace()): + continue # No convertable text in this SVG element. + + letter_vals = [q for q in self.text_string] + str_len = len(letter_vals) + + # Use a group for each line. This starts the first: + line_group = group.add(Group()) + + i = 0 + while(i < str_len): # Loop through the entire text of the string. + + x_start_line = float(self.text_x[i]) # We are starting a new line here. + y_start_line = float(self.text_y[i]) + + while(i < str_len): + # Inner while loop, that we will break out of, + # back to the outer while loop. + + q_val = letter_vals[i] + charfont_height = self.text_heights[i] + + char_data = dict() + char_data['char'] = q_val + char_data['font_family'] = self.text_families[i] + + char_data['font_height'] = charfont_height + char_data['offset'] = w + char_data['vertoffset'] = 0 + char_data['stroke_scale'] = scale_r + + w = self.draw_svg_text(char_data, line_group) + width_this_line = w + w_temp = w + + # Set the alignment if(A) this is the last character in the string + # or if the next piece of the string is at a different position + + set_alignment = False + i_next = i + 1 + if(i_next >= str_len): # End of the string; last character. + set_alignment = True + elif((float(self.text_x[i_next]) != x_start_line) or \ + (float(self.text_y[i_next]) != y_start_line)): + set_alignment = True + + if set_alignment: + text_align = self.text_aligns[i] + # Not currently supporting text alignment that changes in the span; + # Use the text alignment as of the last character. + + # Left(or "start") alignment is default. + # if(text_align == "middle"): Center alignment + # if(text_align == "end"): Right alignment + # + # Strategy: Align every row (left, center, or right) + # as it is created. + + x_shift = 0 + if(text_align == "middle"): # when using text-anchor + x_shift = x_start_line -(width_this_line / 2) + elif(text_align == "end"): + x_shift = x_start_line - width_this_line + else: + x_shift = x_start_line + + y_shift = y_start_line + + the_transform = Transform(translate=(x_shift, y_shift)) + + line_group.transform = the_transform + + line_group = group.add(Group()) # Create new group for this line + + self.new_line = True # Used for managing indent defects + w = 0 + i += 1 + break + i += 1 # Only executed when set_alignment is false. + + the_transform = Transform() + + if len(line_group) == 0: + parent = line_group.getparent() + parent.remove(line_group) + + #End cases A & B. Apply transform to text/flowroot object: + + if(transform is not None): + result = Transform(transform) * the_transform + else: + result = the_transform + + group.transform = result + + if not self.output_generated: + parent = group.getparent() + parent.remove(group) #remove empty group + + #Preserve original element? + if not self.options.preserve_text and self.output_generated: + self.nodes_to_delete.append(node) + + + def effect(self): + ''' + Main entry point; Execute the extension's function. + ''' + + # Input sanitization: + self.options.mode = self.options.mode.strip("\"") + self.options.fontface = self.options.fontface.strip("\"") + self.options.otherfont = self.options.otherfont.strip("\"") + self.options.util_mode = self.options.util_mode.strip("\"") + self.options.sample_text = self.options.sample_text.strip("\"") + + self.doc_transform = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] + + self.find_font_files() + + self.handle_viewbox() + + # Calculate "ideal" effective width of rendered strokes: + # Default: 1/800 of page width or height, whichever is smaller + + _rendered_stroke_scale = 1 /(self.PX_PER_INCH * 800.0) + + if self.svg_width is not None: + if self.svg_width < self.svg_height: + self.render_width = self.svg_width * _rendered_stroke_scale + else: + self.render_width = self.svg_height * _rendered_stroke_scale + + if self.options.mode == "help": + inkex.errormsg(self.help_text) + elif self.options.mode == "utilities": + + if self.options.util_mode == "sample": + self.font_table() + else: + self.glyph_table() + else: + if self.options.ids: + # Traverse selected objects + for id_ref in self.options.ids: + transform = self.recursive_get_encl_transform(self.svg.selected[id_ref]) + self.recursively_traverse_svg([self.svg.selected[id_ref]], transform) + else: # Traverse entire document + self.recursively_traverse_svg(self.document.getroot(), self.doc_transform) + + for element_to_remove in self.nodes_to_delete: + if element_to_remove is not None: + parent = element_to_remove.getparent() + if parent is not None: + parent.remove(element_to_remove) + + if self.font_load_fail: + inkex.errormsg('Warning: unable to load SVG stroke fonts.') + + if self.warn_unflow: + inkex.errormsg('Warning: unable to convert text flowed into a frame.\n' + + 'Please use Text > Unflow to convert it prior to use.\n' + + 'If you are unable to identify the object in question, ' + + 'please contact technical support for help.') + +if __name__ == '__main__': + Hershey().run() diff --git a/share/extensions/hpgl_decoder.py b/share/extensions/hpgl_decoder.py new file mode 100644 index 0000000..1e1577e --- /dev/null +++ b/share/extensions/hpgl_decoder.py @@ -0,0 +1,100 @@ +# coding=utf-8 +# +# Copyright (C) 2013 Public Domain +# 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# standard libraries +from __future__ import unicode_literals + +# local library +import inkex +from inkex.localization import inkex_gettext as _ +from inkex.base import SvgOutputMixin + +class hpglDecoder(SvgOutputMixin): + def __init__(self, hpglString, options): + """ options: + "resolutionX":float + "resolutionY":float + "showMovements":bool + """ + self.hpglString = hpglString + self.options = options + self.scaleX = options.resolutionX / 25.4 # dots/inch to dots/mm + self.scaleY = options.resolutionY / 25.4 # dots/inch to dots/mm + self.warning = '' + self.textMovements = _("Movements") + self.textPenNumber = _("Pen ") + self.layers = {} + self.oldCoordinates = (0.0, 297.0) + + def get_svg(self): + """Generate an svg document from hgpl data""" + actual_layer = 0 + # prepare document + doc = self.get_template(width=210.0, height=297.0, unit='mm') + svg = doc.getroot() + svg.namedview.set('inkscape:document-units', 'mm') + + if self.options.showMovements: + self.layers[0] = svg.add(inkex.Layer(self.textMovements)) + + # cut stream into commands + hpgl_data = self.hpglString.split(';') + # if number of commands is under needed minimum, no data was found + if len(hpgl_data) < 3: + raise Exception('NO_HPGL_DATA') + # decode commands into svg data + for command in hpgl_data: + if command.strip() != '': + if command[:2] == 'IN' or command[:2] == 'FS' or command[:2] == 'VS': + # if Initialize, force or speed command ignore it + pass + elif command[:2] == 'SP': + # if Select Pen command + actual_layer = int(command[2:]) + elif command[:2] == 'PU': + # if Pen Up command + self.parameters_to_path(svg, command[2:], 0, True) + elif command[:2] == 'PD': + # if Pen Down command + self.parameters_to_path(svg, command[2:], actual_layer + 1, False) + else: + self.warning = 'UNKNOWN_COMMANDS' + return doc, self.warning + + def parameters_to_path(self, svg, parameters, layerNum, isPU): + """split params and sanity check them""" + parameters = parameters.strip().split(',') + if parameters and len(parameters) % 2 == 0: + for i, param in enumerate(parameters): + # convert params to document units + if i % 2 == 0: + parameters[i] = str(float(param) / self.scaleX) + else: + parameters[i] = str(297.0 - (float(param) / self.scaleY)) + # create path and add it to the corresponding layer + if not isPU or (self.options.showMovements and isPU): + # create layer if it does not exist + if layerNum not in self.layers: + label = self.textPenNumber + str(layerNum - 1) + self.layers[layerNum] = svg.add(inkex.Layer.new(label)) + + path = 'M %f,%f L %s' % (self.oldCoordinates[0], self.oldCoordinates[1], ','.join(parameters)) + style = 'stroke:#' + ('ff0000' if isPU else '000000') + '; stroke-width:0.2; fill:none;' + self.layers[layerNum].add(inkex.PathElement(d=path, style=style)) + self.oldCoordinates = (float(parameters[-2]), float(parameters[-1])) diff --git a/share/extensions/hpgl_encoder.py b/share/extensions/hpgl_encoder.py new file mode 100644 index 0000000..0739580 --- /dev/null +++ b/share/extensions/hpgl_encoder.py @@ -0,0 +1,344 @@ +# coding=utf-8 +# +# Copyright (C) 2008 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Base class for HGPL Encoding +""" + +import re +import math + +import inkex +from inkex.transforms import Transform +from inkex.bezier import cspsubdiv +from inkex.utils import fullmatch + +class NoPathError(ValueError): + """Raise that paths not selected""" + +# Find the pen number in the layer number +FIND_PEN = re.compile(r'\s*pen\s*(\d+)\s*', re.IGNORECASE) + +class hpglEncoder(object): + """HPGL Encoder, used by others""" + def __init__(self, effect): + """ options: + "resolutionX":float + "resolutionY":float + "pen":int + "force:int + "speed:int + "orientation":string // "0", "90", "-90", "180" + "mirrorX":bool + "mirrorY":bool + "center":bool + "flat":float + "overcut":float + "toolOffset":float + "precut":bool + "autoAlign":bool + """ + self.options = effect.options + self.doc = effect.svg + self.docWidth = effect.svg.unittouu(effect.svg.get('width')) + self.docHeight = effect.svg.unittouu(effect.svg.get('height')) + self.hpgl = '' + self.divergenceX = 'False' + self.divergenceY = 'False' + self.sizeX = 'False' + self.sizeY = 'False' + self.dryRun = True + self.lastPoint = [0, 0, 0] + self.lastPen = -1 + self.offsetX = 0 + self.offsetY = 0 + self.scaleX = self.options.resolutionX / effect.svg.unittouu("1.0in") # dots per inch to dots per user unit + self.scaleY = self.options.resolutionY / effect.svg.unittouu("1.0in") # dots per inch to dots per user unit + scaleXY = (self.scaleX + self.scaleY) / 2 + self.overcut = effect.svg.unittouu(str(self.options.overcut) + "mm") * scaleXY # mm to dots (plotter coordinate system) + self.toolOffset = effect.svg.unittouu(str(self.options.toolOffset) + "mm") * scaleXY # mm to dots + self.flat = self.options.flat / (1016 / ((self.options.resolutionX + self.options.resolutionY) / 2)) # scale flatness to resolution + if self.toolOffset > 0.0: + self.toolOffsetFlat = self.flat / self.toolOffset * 4.5 # scale flatness to offset + else: + self.toolOffsetFlat = 0.0 + self.mirrorX = -1.0 if self.options.mirrorX else 1.0 + self.mirrorY = 1.0 if self.options.mirrorY else -1.0 + # process viewBox attribute to correct page scaling + self.viewBoxTransformX = 1 + self.viewBoxTransformY = 1 + viewBox = effect.svg.get_viewbox() + if viewBox and viewBox[2] and viewBox[3]: + self.viewBoxTransformX = self.docWidth / effect.svg.unittouu(effect.svg.add_unit(viewBox[2])) + self.viewBoxTransformY = self.docHeight / effect.svg.unittouu(effect.svg.add_unit(viewBox[3])) + + def getHpgl(self): + """Return the HPGL instructions""" + # dryRun to find edges + transform = Transform([ + [self.mirrorX * self.scaleX * self.viewBoxTransformX, 0.0, 0.0], + [0.0, self.mirrorY * self.scaleY * self.viewBoxTransformY, 0.0]] + ) + transform.add_rotate(int(self.options.orientation)) + + self.vData = [['', 'False', 0, 0], ['', 'False', 0, 0], ['', 'False', 0, 0], ['', 'False', 0, 0]] + self.process_group(self.doc, transform) + if self.divergenceX == 'False' or self.divergenceY == 'False' or self.sizeX == 'False' or self.sizeY == 'False': + raise NoPathError("No paths found") + # live run + self.dryRun = False + # move drawing according to various modifiers + if self.options.autoAlign: + if self.options.center: + self.offsetX -= (self.sizeX - self.divergenceX) / 2 + self.offsetY -= (self.sizeY - self.divergenceY) / 2 + else: + self.divergenceX = 0.0 + self.divergenceY = 0.0 + if self.options.center: + if self.options.orientation == '0': + self.offsetX -= (self.docWidth * self.scaleX) / 2 + self.offsetY += (self.docHeight * self.scaleY) / 2 + if self.options.orientation == '90': + self.offsetY += (self.docWidth * self.scaleX) / 2 + self.offsetX += (self.docHeight * self.scaleY) / 2 + if self.options.orientation == '180': + self.offsetX += (self.docWidth * self.scaleX) / 2 + self.offsetY -= (self.docHeight * self.scaleY) / 2 + if self.options.orientation == '270': + self.offsetY -= (self.docWidth * self.scaleX) / 2 + self.offsetX -= (self.docHeight * self.scaleY) / 2 + else: + if self.options.orientation == '0': + self.offsetY += self.docHeight * self.scaleY + if self.options.orientation == '90': + self.offsetY += self.docWidth * self.scaleX + self.offsetX += self.docHeight * self.scaleY + if self.options.orientation == '180': + self.offsetX += self.docWidth * self.scaleX + if not self.options.center and self.toolOffset > 0.0: + self.offsetX += self.toolOffset + self.offsetY += self.toolOffset + + # initialize transformation matrix and cache + transform = Transform([ + [self.mirrorX * self.scaleX * self.viewBoxTransformX, + 0.0, + -float(self.divergenceX) + self.offsetX], + [0.0, + self.mirrorY * self.scaleY * self.viewBoxTransformY, + -float(self.divergenceY) + self.offsetY] + ]) + transform.add_rotate(int(self.options.orientation)) + self.vData = [['', 'False', 0, 0], ['', 'False', 0, 0], ['', 'False', 0, 0], ['', 'False', 0, 0]] + # add move to zero point and precut + if self.toolOffset > 0.0 and self.options.precut: + if self.options.center: + # position precut outside of drawing plus one time the tooloffset + if self.offsetX >= 0.0: + precutX = self.offsetX + self.toolOffset + else: + precutX = self.offsetX - self.toolOffset + if self.offsetY >= 0.0: + precutY = self.offsetY + self.toolOffset + else: + precutY = self.offsetY - self.toolOffset + self.processOffset('PU', precutX, precutY, self.options.pen) + self.processOffset('PD', precutX, precutY + self.toolOffset * 8, self.options.pen) + else: + self.processOffset('PU', 0, 0, self.options.pen) + self.processOffset('PD', 0, self.toolOffset * 8, self.options.pen) + # start conversion + self.process_group(self.doc, transform) + # shift an empty node in in order to process last node in cache + if self.toolOffset > 0.0 and not self.dryRun: + self.processOffset('PU', 0, 0, 0) + return self.hpgl + + def process_group(self, group, transform): + """flatten layers and groups to avoid recursion""" + for child in group: + if not isinstance(child, inkex.ShapeElement): + continue + if child.is_visible(): + if isinstance(child, inkex.Group): + self.process_group(child, transform) + elif isinstance(child, inkex.PathElement): + self.process_path(child, transform) + else: + # This only works for shape elements (not text yet!) + new_elem = child.replace_with(child.to_path_element()) + # Element is given composed transform b/c it's not added back to doc + new_elem.transform = child.composed_transform() + self.process_path(new_elem, transform) + + def get_pen_number(self, node): + """Get pen number for node label (usually group)""" + for parent in [node] + list(node.ancestors().values()): + match = fullmatch(FIND_PEN, parent.label or '') + if match: + return int(match.group(1)) + return int(self.options.pen) + + def process_path(self, node, transform): + """Process the given element into a plotter path""" + pen = self.get_pen_number(node) + path = node.path.to_absolute()\ + .transform(node.composed_transform())\ + .transform(transform)\ + .to_superpath() + if path: + cspsubdiv(path, self.flat) + # path to HPGL commands + oldPosX = 0.0 + oldPosY = 0.0 + for singlePath in path: + cmd = 'PU' + for singlePathPoint in singlePath: + posX, posY = singlePathPoint[1] + # check if point is repeating, if so, ignore + if int(round(posX)) != int(round(oldPosX)) or int(round(posY)) != int(round(oldPosY)): + self.processOffset(cmd, posX, posY, pen) + cmd = 'PD' + oldPosX = posX + oldPosY = posY + # perform overcut + if self.overcut > 0.0 and not self.dryRun: + # check if last and first points are the same, otherwise the path is not closed and no overcut can be performed + if int(round(oldPosX)) == int(round(singlePath[0][1][0])) and int(round(oldPosY)) == int(round(singlePath[0][1][1])): + overcutLength = 0 + for singlePathPoint in singlePath: + posX, posY = singlePathPoint[1] + # check if point is repeating, if so, ignore + if int(round(posX)) != int(round(oldPosX)) or int(round(posY)) != int(round(oldPosY)): + overcutLength += self.getLength(oldPosX, oldPosY, posX, posY) + if overcutLength >= self.overcut: + newLength = self.changeLength(oldPosX, oldPosY, posX, posY, - (overcutLength - self.overcut)) + self.processOffset(cmd, newLength[0], newLength[1], pen) + break + else: + self.processOffset(cmd, posX, posY, pen) + oldPosX = posX + oldPosY = posY + + def getLength(self, x1, y1, x2, y2, absolute=True): + """calc absolute or relative length between two points""" + length = math.sqrt((x2 - x1) ** 2.0 + (y2 - y1) ** 2.0) + if absolute: + length = math.fabs(length) + return length + + def changeLength(self, x1, y1, x2, y2, offset): + """change length of line""" + if offset < 0: + offset = max( - self.getLength(x1, y1, x2, y2), offset) + x = x2 + (x2 - x1) / self.getLength(x1, y1, x2, y2, False) * offset + y = y2 + (y2 - y1) / self.getLength(x1, y1, x2, y2, False) * offset + return [x, y] + + def processOffset(self, cmd, posX, posY, pen): + # calculate offset correction (or don't) + if self.toolOffset == 0.0 or self.dryRun: + self.storePoint(cmd, posX, posY, pen) + else: + # insert data into cache + self.vData.pop(0) + self.vData.insert(3, [cmd, posX, posY, pen]) + # decide if enough data is available + if self.vData[2][1] != 'False': + if self.vData[1][1] == 'False': + self.storePoint(self.vData[2][0], self.vData[2][1], self.vData[2][2], self.vData[2][3]) + else: + # perform tool offset correction (It's a *tad* complicated, if you want to understand it draw the data as lines on paper) + if self.vData[2][0] == 'PD': # If the 3rd entry in the cache is a pen down command make the line longer by the tool offset + pointThree = self.changeLength(self.vData[1][1], self.vData[1][2], self.vData[2][1], self.vData[2][2], self.toolOffset) + self.storePoint('PD', pointThree[0], pointThree[1], self.vData[2][3]) + elif self.vData[0][1] != 'False': + # Elif the 1st entry in the cache is filled with data and the 3rd entry is a pen up command shift + # the 3rd entry by the current tool offset position according to the 2nd command + pointThree = self.changeLength(self.vData[0][1], self.vData[0][2], self.vData[1][1], self.vData[1][2], self.toolOffset) + pointThree[0] = self.vData[2][1] - (self.vData[1][1] - pointThree[0]) + pointThree[1] = self.vData[2][2] - (self.vData[1][2] - pointThree[1]) + self.storePoint('PU', pointThree[0], pointThree[1], self.vData[2][3]) + else: + # Else just write the 3rd entry + pointThree = [self.vData[2][1], self.vData[2][2]] + self.storePoint('PU', pointThree[0], pointThree[1], self.vData[2][3]) + if self.vData[3][0] == 'PD': + # If the 4th entry in the cache is a pen down command guide tool to next line with a circle between the prolonged 3rd and 4th entry + if self.getLength(self.vData[2][1], self.vData[2][2], self.vData[3][1], self.vData[3][2]) >= self.toolOffset: + pointFour = self.changeLength(self.vData[3][1], self.vData[3][2], self.vData[2][1], self.vData[2][2], - self.toolOffset) + else: + pointFour = self.changeLength(self.vData[2][1], self.vData[2][2], self.vData[3][1], self.vData[3][2], + (self.toolOffset - self.getLength(self.vData[2][1], self.vData[2][2], self.vData[3][1], self.vData[3][2]))) + # get angle start and angle vector + angleStart = math.atan2(pointThree[1] - self.vData[2][2], pointThree[0] - self.vData[2][1]) + angleVector = math.atan2(pointFour[1] - self.vData[2][2], pointFour[0] - self.vData[2][1]) - angleStart + # switch direction when arc is bigger than 180° + if angleVector > math.pi: + angleVector -= math.pi * 2 + elif angleVector < - math.pi: + angleVector += math.pi * 2 + # draw arc + if angleVector >= 0: + angle = angleStart + self.toolOffsetFlat + while angle < angleStart + angleVector: + self.storePoint('PD', self.vData[2][1] + math.cos(angle) * self.toolOffset, self.vData[2][2] + math.sin(angle) * self.toolOffset, self.vData[2][3]) + angle += self.toolOffsetFlat + else: + angle = angleStart - self.toolOffsetFlat + while angle > angleStart + angleVector: + self.storePoint('PD', self.vData[2][1] + math.cos(angle) * self.toolOffset, self.vData[2][2] + math.sin(angle) * self.toolOffset, self.vData[2][3]) + angle -= self.toolOffsetFlat + self.storePoint('PD', pointFour[0], pointFour[1], self.vData[3][3]) + + def storePoint(self, command, x, y, pen): + x = int(round(x)) + y = int(round(y)) + # skip when no change in movement + if self.lastPoint[0] == command and self.lastPoint[1] == x and self.lastPoint[2] == y: + return + if self.dryRun: + # find edges + if self.divergenceX == 'False' or x < self.divergenceX: + self.divergenceX = x + if self.divergenceY == 'False' or y < self.divergenceY: + self.divergenceY = y + if self.sizeX == 'False' or x > self.sizeX: + self.sizeX = x + if self.sizeY == 'False' or y > self.sizeY: + self.sizeY = y + else: + # store point + if not self.options.center: + # only positive values are allowed (usually) + if x < 0: + x = 0 + if y < 0: + y = 0 + # select correct pen + if self.lastPen != pen: + self.hpgl += ';PU;SP%d' % pen + # do not repeat command + if command == 'PD' and self.lastPoint[0] == 'PD' and self.lastPen == pen: + self.hpgl += ',%d,%d' % (x, y) + else: + self.hpgl += ';%s%d,%d' % (command, x, y) + self.lastPen = pen + self.lastPoint = [command, x, y] + diff --git a/share/extensions/hpgl_input.inx b/share/extensions/hpgl_input.inx new file mode 100644 index 0000000..279fd0a --- /dev/null +++ b/share/extensions/hpgl_input.inx @@ -0,0 +1,20 @@ + + + HPGL Input + org.inkscape.input.hpgl_input + hpgl_decoder.py + + + 1016.0 + 1016.0 + false + + + diff --git a/share/extensions/hpgl_input.py b/share/extensions/hpgl_input.py new file mode 100755 index 0000000..c4cd05f --- /dev/null +++ b/share/extensions/hpgl_input.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2013 Public Domain +# 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import sys +import hpgl_decoder +import inkex +from inkex.localization import inkex_gettext as _ + +class HpglInput(inkex.InputExtension): + def add_arguments(self, pars): + pars.add_argument('--resolutionX', type=float, default=1016.0, help='Resolution X (dpi)') + pars.add_argument('--resolutionY', type=float, default=1016.0, help='Resolution Y (dpi)') + pars.add_argument('--showMovements', type=inkex.Boolean, default=False, + help='Show Movements between paths') + + def load(self, stream): + return b';'.join(line.strip() for line in stream).decode() + + def effect(self): + # interpret HPGL data + myHpglDecoder = hpgl_decoder.hpglDecoder(self.document, self.options) + self.document = None + + try: + doc, warnings = myHpglDecoder.get_svg() + except Exception as inst: + if inst.args[0] == 'NO_HPGL_DATA': + # issue error if no hpgl data found + inkex.errormsg(_("No HPGL data found.")) + exit(1) + else: + type, value, traceback = sys.exc_info() + raise ValueError("", type, value).with_traceback(traceback) + + # issue warning if unknown commands where found + if 'UNKNOWN_COMMANDS' in warnings: + inkex.errormsg(_("The HPGL data contained unknown (unsupported) commands, there is a possibility that the drawing is missing some content.")) + + # deliver document to inkscape + self.document = doc + +if __name__ == '__main__': + HpglInput().run() diff --git a/share/extensions/hpgl_output.inx b/share/extensions/hpgl_output.inx new file mode 100644 index 0000000..c8dc92c --- /dev/null +++ b/share/extensions/hpgl_output.inx @@ -0,0 +1,46 @@ + + + HPGL Output + org.ekips.output.hpgl_output + org.inkscape.output.svg.inkscape + hpgl_encoder.py + + + + 1016.0 + 1016.0 + 1 + 0 + 0 + + + + + + + false + false + false + + + + + 1.00 + 0.25 + true + 1.2 + true + + + + + .hpgl + image/hpgl + HP Graphics Language file (*.hpgl) + Export an HP Graphics Language file + true + + + diff --git a/share/extensions/hpgl_output.py b/share/extensions/hpgl_output.py new file mode 100755 index 0000000..648d241 --- /dev/null +++ b/share/extensions/hpgl_output.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2013 +# 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from __future__ import print_function + +import inkex +from inkex.localization import inkex_gettext as _ + +import hpgl_encoder + +class HpglOutput(inkex.OutputExtension): + """Save as HPGL Output""" + def add_arguments(self, pars): + pars.add_argument('--tab') + pars.add_argument('--resolutionX', type=float, default=1016.0, help='Resolution X (dpi)') + pars.add_argument('--resolutionY', type=float, default=1016.0, help='Resolution Y (dpi)') + pars.add_argument('--pen', type=int, default=1, help='Pen number') + pars.add_argument('--force', type=int, default=24, help='Pen force (g)') + pars.add_argument('--speed', type=int, default=20, help='Pen speed (cm/s)') + pars.add_argument('--orientation', default='90', help='Rotation (Clockwise)') + pars.add_argument('--mirrorX', type=inkex.Boolean, default=False, help='Mirror X axis') + pars.add_argument('--mirrorY', type=inkex.Boolean, default=False, help='Mirror Y axis') + pars.add_argument('--center', type=inkex.Boolean, default=False, help='Center zero point') + pars.add_argument('--overcut', type=float, default=1.0, help='Overcut (mm)') + pars.add_argument('--precut', type=inkex.Boolean, default=True, help='Use precut') + pars.add_argument('--flat', type=float, default=1.2, help='Curve flatness') + pars.add_argument('--autoAlign', type=inkex.Boolean, default=True, help='Auto align') + pars.add_argument('--toolOffset', type=float, default=0.25,\ + help='Tool (Knife) offset correction (mm)') + + def save(self, stream): + self.options.debug = False + # get hpgl data + encoder = hpgl_encoder.hpglEncoder(self) + try: + hpgl = encoder.getHpgl() + except hpgl_encoder.NoPathError: + raise inkex.AbortExtension( + _("No paths were found. Please convert objects you want into paths.")) + # convert raw HPGL to HPGL + hpgl_init = 'IN' + if self.options.force > 0: + hpgl_init += ';FS%d' % self.options.force + if self.options.speed > 0: + hpgl_init += ';VS%d' % self.options.speed + hpgl = hpgl_init + hpgl + ';SP0;PU0,0;IN; ' + stream.write(hpgl.encode('utf-8')) + + +if __name__ == '__main__': + HpglOutput().run() diff --git a/share/extensions/image_attributes.inx b/share/extensions/image_attributes.inx new file mode 100644 index 0000000..72abed0 --- /dev/null +++ b/share/extensions/image_attributes.inx @@ -0,0 +1,76 @@ + + + + Set Image Attributes + org.inkscape.effect.image_attributes + + + + + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + + + + + + + + diff --git a/share/extensions/image_attributes.py b/share/extensions/image_attributes.py new file mode 100755 index 0000000..7645b93 --- /dev/null +++ b/share/extensions/image_attributes.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2015, ~suv +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +image_attributes.py - adjust image attributes which don't have global +GUI options yet + +Tool for Inkscape 0.91 to adjust rendering of drawings with linked +or embedded bitmap images created with older versions of Inkscape +or third-party applications. +""" + +import inkex +from inkex import Image + +class ImageAttributes(inkex.EffectExtension): + """Set attributes in images""" + def effect(self): + self.options.tab_main() + + def add_arguments(self, pars): + pars.add_argument("--tab_main", type=self.arg_method(), default=self.method_tab_basic) + pars.add_argument("--fix_scaling", type=inkex.Boolean, default=True) + pars.add_argument("--fix_rendering", type=inkex.Boolean, default=False) + pars.add_argument("--aspect_ratio", default="none",\ + help="Value for attribute 'preserveAspectRatio'") + pars.add_argument("--aspect_clip", default="unset",\ + help="optional 'meetOrSlice' value") + pars.add_argument("--aspect_ratio_scope", type=self.arg_method("change"),\ + default="selected_only", help="When to edit 'preserveAspectRatio' attribute") + pars.add_argument("--image_rendering", default="unset",\ + help="Value for attribute 'image-rendering'") + pars.add_argument("--image_rendering_scope", type=self.arg_method("change"),\ + default="selected_only", help="When to edit 'image-rendering' attribute") + + def change_attribute(self, node, attribute): + for key, value in attribute.items(): + if key == 'preserveAspectRatio': + # set presentation attribute + if value != "unset": + node.set(key, str(value)) + else: + if node.get(key): + del node.attrib[key] + elif key == 'image-rendering': + node_style = dict(inkex.Style.parse_str(node.get('style'))) + if key not in node_style: + # set presentation attribute + if value != "unset": + node.set(key, str(value)) + else: + if node.get(key): + del node.attrib[key] + else: + # set style property + if value != "unset": + node_style[key] = str(value) + else: + del node_style[key] + node.set('style', str(inkex.Style(node_style))) + else: + pass + + def change_all_images(self, node, attribute): + for img in node.xpath('descendant-or-self::svg:image'): + self.change_attribute(img, attribute) + + def change_selected_only(self, selected, attribute): + for node in selected.values(): + if isinstance(node, Image): + self.change_attribute(node, attribute) + + def change_in_selection(self, selected, attribute): + for node in selected.values(): + self.change_all_images(node, attribute) + + def change_in_document(self, selected, attribute): + self.change_all_images(self.document.getroot(), attribute) + + def change_on_parent_group(self, selected, attribute): + for node in selected.values(): + self.change_attribute(node.getparent(), attribute) + + def change_on_root_only(self, selected, attribute): + self.change_attribute(self.document.getroot(), attribute) + + def method_tab_basic(self): + """Render all bitmap images like in older Inskcape versions""" + self.change_in_document(self.svg.selected, { + 'preserveAspectRatio': ("none" if self.options.fix_scaling else "unset"), + 'image-rendering': ("optimizeSpeed" if self.options.fix_rendering else "unset"), + }) + + def method_tab_aspect_ratio(self): + """Image Aspect Ratio""" + attr_val = [self.options.aspect_ratio] + if self.options.aspect_clip != "unset": + attr_val.append(self.options.aspect_clip) + self.options.aspect_ratio_scope(self.svg.selected,\ + {'preserveAspectRatio': ' '.join(attr_val)}) + + def method_tab_image_rendering(self): + """Image Rendering Quality""" + self.options.image_rendering_scope(self.svg.selected,\ + {'image-rendering': self.options.image_rendering}) + +if __name__ == '__main__': + ImageAttributes().run() diff --git a/share/extensions/ink2canvas.inx b/share/extensions/ink2canvas.inx new file mode 100644 index 0000000..caa83e7 --- /dev/null +++ b/share/extensions/ink2canvas.inx @@ -0,0 +1,14 @@ + + + Convert to html5 canvas + org.inkscape.output.html5canvas + + .html + text/html + HTML 5 canvas (*.html) + HTML 5 canvas code + + + diff --git a/share/extensions/ink2canvas.py b/share/extensions/ink2canvas.py new file mode 100755 index 0000000..d14fb01 --- /dev/null +++ b/share/extensions/ink2canvas.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# coding=utf-8 +# Copyright (C) 2011 Karlisson Bezerra +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Save an SVG file into an html canvas file. +""" + +import inkex + +import ink2canvas_lib.svg as svg +from ink2canvas_lib.canvas import Canvas + +class Html5Canvas(inkex.OutputExtension): + """Creates a canvas output""" + def save(self, stream): + svg_root = self.document.getroot() + width = self.svg.unittouu(svg_root.get("width")) + height = self.svg.unittouu(svg_root.get("height")) + canvas = Canvas(self, width, height) + self.walk_tree(svg_root, canvas) + stream.write(canvas.output().encode('utf-8')) + + def get_gradient_defs(self, elem): + """Return the gradient information""" + url_id = elem.get_gradient_href() + # get the gradient element + gradient = self.svg.getElementById(url_id) + # get the color stops + gstops = gradient.href + colors = [] + for stop in gstops: + colors.append(stop.get("style")) + if gradient.get("r"): + return svg.RadialGradientDef(gradient, colors) + return svg.LinearGradientDef(gradient, colors) + + @staticmethod + def _shape_from_node(node, canvas): + """ + Make a canvas shape object for the given node. Returns `None` if + the node is not an SVG shape element. + @rtype svg.AbstractShape or NoneType + """ + prefix, _brace_, command = node.tag.partition('}') + if prefix != '{http://www.w3.org/2000/svg': + return None + + # makes pylint happy + assert _brace_ == '}' + + cls = getattr(svg, command.capitalize(), None) + + if not (isinstance(cls, type) and issubclass(cls, svg.AbstractShape)): + return None + + return cls(command, node, canvas) + + def walk_tree(self, root, canvas): + """Walk throug the whole svg tree""" + for node in root: + elem = self._shape_from_node(node, canvas) + if elem is None: + continue + gradient = None + if elem.has_gradient(): + gradient = self.get_gradient_defs(elem) + elem.start(gradient) + elem.draw() + self.walk_tree(node, canvas) + elem.end() + + +if __name__ == "__main__": + Html5Canvas().run() diff --git a/share/extensions/ink2canvas_lib/__init__.py b/share/extensions/ink2canvas_lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/ink2canvas_lib/__pycache__/__init__.cpython-39.pyc b/share/extensions/ink2canvas_lib/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..b59cdcb Binary files /dev/null and b/share/extensions/ink2canvas_lib/__pycache__/__init__.cpython-39.pyc differ diff --git a/share/extensions/ink2canvas_lib/__pycache__/canvas.cpython-39.pyc b/share/extensions/ink2canvas_lib/__pycache__/canvas.cpython-39.pyc new file mode 100644 index 0000000..d774ae0 Binary files /dev/null and b/share/extensions/ink2canvas_lib/__pycache__/canvas.cpython-39.pyc differ diff --git a/share/extensions/ink2canvas_lib/__pycache__/svg.cpython-39.pyc b/share/extensions/ink2canvas_lib/__pycache__/svg.cpython-39.pyc new file mode 100644 index 0000000..c58117c Binary files /dev/null and b/share/extensions/ink2canvas_lib/__pycache__/svg.cpython-39.pyc differ diff --git a/share/extensions/ink2canvas_lib/canvas.py b/share/extensions/ink2canvas_lib/canvas.py new file mode 100644 index 0000000..c219132 --- /dev/null +++ b/share/extensions/ink2canvas_lib/canvas.py @@ -0,0 +1,191 @@ +# coding=utf-8 +# +# Copyright (C) 2011 Karlisson Bezerra +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Convas module for ink2canvas extension +""" + +from inkex import Color + +class Canvas(object): + """Canvas API helper class""" + + def __init__(self, parent, width, height, context="ctx"): + self.obj = context + self.code = [] # stores the code + self.style = {} + self.styleCache = {} # stores the previous style applied + self.parent = parent + self.width = width + self.height = height + + def write(self, text): + self.code.append("\t" + text.replace("ctx", self.obj) + "\n") + + def output(self): + from textwrap import dedent + html = """ + + + + Inkscape Output + + + + + + + """ + return dedent(html) % (self.width, self.height, self.obj, "".join(self.code)) + + def equalStyle(self, style, key): + """Checks if the last style used is the same or there's no style yet""" + if key in self.styleCache: + return True + if key not in style: + return True + return style[key] == self.styleCache[key] + + def beginPath(self): + self.write("ctx.beginPath();") + + def createLinearGradient(self, href, x1, y1, x2, y2): + data = (href, x1, y1, x2, y2) + self.write("var %s = \ + ctx.createLinearGradient(%f,%f,%f,%f);" % data) + + def createRadialGradient(self, href, cx1, cy1, rx, cx2, cy2, ry): + data = (href, cx1, cy1, rx, cx2, cy2, ry) + self.write("var %s = ctx.createRadialGradient\ + (%f,%f,%f,%f,%f,%f);" % data) + + def addColorStop(self, href, pos, color): + self.write("%s.addColorStop(%f, %s);" % (href, pos, color)) + + def getColor(self, rgb, alpha): + return "'{}'".format(str(Color(rgb).to_rgba(alpha))) + + def setGradient(self, href): + """ + for stop in gstops: + style = simplestyle.parseStyle(stop.get("style")) + stop_color = style["stop-color"] + opacity = style["stop-opacity"] + color = self.getColor(stop_color, opacity) + pos = float(stop.get("offset")) + self.addColorStop(href, pos, color) + """ + return None # href + + def setOpacity(self, value): + self.write("ctx.globalAlpha = %.1f;" % float(value)) + + def setFill(self, value): + try: + alpha = self.style["fill-opacity"] + except: + alpha = 1 + if not value.startswith("url("): + fill = self.getColor(value, alpha) + self.write("ctx.fillStyle = %s;" % fill) + + def setStroke(self, value): + try: + alpha = self.style["stroke-opacity"] + except: + alpha = 1 + self.write("ctx.strokeStyle = %s;" % self.getColor(value, alpha)) + + def setStrokeWidth(self, value): + self.write("ctx.lineWidth = %f;" % self.parent.svg.unittouu(value)) + + def setStrokeLinecap(self, value): + self.write("ctx.lineCap = '%s';" % value) + + def setStrokeLinejoin(self, value): + self.write("ctx.lineJoin = '%s';" % value) + + def setStrokeMiterlimit(self, value): + self.write("ctx.miterLimit = %s;" % value) + + def setFont(self, value): + self.write("ctx.font = \"%s\";" % value) + + def moveTo(self, x, y): + self.write("ctx.moveTo(%f, %f);" % (x, y)) + + def lineTo(self, x, y): + self.write("ctx.lineTo(%f, %f);" % (x, y)) + + def quadraticCurveTo(self, cpx, cpy, x, y): + data = (cpx, cpy, x, y) + self.write("ctx.quadraticCurveTo(%f, %f, %f, %f);" % data) + + def bezierCurveTo(self, x1, y1, x2, y2, x, y): + data = (x1, y1, x2, y2, x, y) + self.write("ctx.bezierCurveTo(%f, %f, %f, %f, %f, %f);" % data) + + def rect(self, x, y, w, h, rx=0, ry=0): + if rx or ry: + # rounded rectangle, starts top-left anticlockwise + self.moveTo(x, y + ry) + self.lineTo(x, y + h - ry) + self.quadraticCurveTo(x, y + h, x + rx, y + h) + self.lineTo(x + w - rx, y + h) + self.quadraticCurveTo(x + w, y + h, x + w, y + h - ry) + self.lineTo(x + w, y + ry) + self.quadraticCurveTo(x + w, y, x + w - rx, y) + self.lineTo(x + rx, y) + self.quadraticCurveTo(x, y, x, y + ry) + else: + self.write("ctx.rect(%f, %f, %f, %f);" % (x, y, w, h)) + + def arc(self, x, y, r, a1, a2, flag): + data = (x, y, r, a1, a2, flag) + self.write("ctx.arc(%f, %f, %f, %f, %.8f, %d);" % data) + + def fillText(self, text, x, y): + self.write("ctx.fillText(\"%s\", %f, %f);" % (text, x, y)) + + def translate(self, cx, cy): + self.write("ctx.translate(%f, %f);" % (cx, cy)) + + def rotate(self, angle): + self.write("ctx.rotate(%f);" % angle) + + def scale(self, rx, ry): + self.write("ctx.scale(%f, %f);" % (rx, ry)) + + def transform(self, m11, m12, m21, m22, dx, dy): + data = (m11, m12, m21, m22, dx, dy) + self.write("ctx.transform(%f, %f, %f, %f, %f, %f);" % data) + + def save(self): + self.write("ctx.save();") + + def restore(self): + self.write("ctx.restore();") + + def closePath(self): + if "fill" in self.style and self.style["fill"] != "none": + self.write("ctx.fill();") + if "stroke" in self.style and self.style["stroke"] != "none": + self.write("ctx.stroke();") diff --git a/share/extensions/ink2canvas_lib/svg.py b/share/extensions/ink2canvas_lib/svg.py new file mode 100644 index 0000000..75796fc --- /dev/null +++ b/share/extensions/ink2canvas_lib/svg.py @@ -0,0 +1,293 @@ +# coding=utf-8 +# +# Copyright (C) 2011 Karlisson Bezerra +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Element parsing and context for ink2canvas extensions +""" + +from __future__ import unicode_literals + +import inkex + +class Element(object): + """Base Element""" + def __init__(self, node): + self.node = node + + def attr(self, val): + """Get attribute""" + try: + attr = float(self.node.get(val)) + except: + attr = self.node.get(val) + return attr + + +class GradientDef(Element): + def __init__(self, node, stops): + self.node = node + self.stops = stops + + +class LinearGradientDef(GradientDef): + def get_data(self): + x1 = self.attr("x1") + y1 = self.attr("y1") + x2 = self.attr("x2") + y2 = self.attr("y2") + # self.createLinearGradient(href, x1, y1, x2, y2) + + def draw(self): + pass + + +class RadialGradientDef(GradientDef): + def get_data(self): + cx = self.attr("cx") + cy = self.attr("cy") + r = self.attr("r") + # self.createRadialGradient(href, cx, cy, r, cx, cy, r) + + def draw(self): + pass + + +class AbstractShape(Element): + def __init__(self, command, node, ctx): + self.node = node + self.command = command + self.ctx = ctx + + def get_data(self): + return + + def get_style(self): + return self.node.style + + def set_style(self, style): + """Translates style properties names into method calls""" + self.ctx.style = style + for key in style: + tmp_list = [s.capitalize() for s in key.split("-")] + method = "set" + "".join(tmp_list) + if hasattr(self.ctx, method) and style[key] != "none": + getattr(self.ctx, method)(style[key]) + # saves style to compare in next iteration + self.ctx.style_cache = style + + def has_transform(self): + return bool(self.attr("transform")) + + def get_transform(self): + return self.node.transform.to_hexad() + + def has_gradient(self): + style = self.get_style() + if "fill" in style: + fill = style["fill"] + return fill.startswith("url(#linear") or fill.startswith("url(#radial") + return False + + def get_gradient_href(self): + style = self.get_style() + if "fill" in style: + return style["fill"][5:-1] + return + + def has_clip(self): + return bool(self.attr("clip-path")) + + def start(self, gradient): + self.gradient = gradient + self.ctx.write("\n// #%s" % self.attr("id")) + if self.has_transform() or self.has_clip(): + self.ctx.save() + + def draw(self): + data = self.get_data() + style = self.get_style() + self.ctx.beginPath() + if self.has_transform(): + trans_matrix = self.get_transform() + self.ctx.transform(*trans_matrix) # unpacks argument list + if self.has_gradient(): + self.gradient.draw() + self.set_style(style) + # unpacks "data" in parameters to given method + getattr(self.ctx, self.command)(*data) + self.ctx.closePath() + + def end(self): + if self.has_transform() or self.has_clip(): + self.ctx.restore() + + +class G(AbstractShape): + def draw(self): + # get layer label, if exists + if self.has_transform(): + trans_matrix = self.get_transform() + self.ctx.transform(*trans_matrix) + + +class Rect(AbstractShape): + def get_data(self): + x = self.attr("x") + y = self.attr("y") + w = self.attr("width") + h = self.attr("height") + rx = self.attr("rx") or 0 + ry = self.attr("ry") or 0 + return x, y, w, h, rx, ry + + +class Circle(AbstractShape): + def __init__(self, command, node, ctx): + AbstractShape.__init__(self, command, node, ctx) + self.command = "arc" + + def get_data(self): + import math + cx = self.attr("cx") + cy = self.attr("cy") + r = self.attr("r") + return cx, cy, r, 0, math.pi * 2, True + + +class Ellipse(AbstractShape): + def get_data(self): + cx = self.attr("cx") + cy = self.attr("cy") + rx = self.attr("rx") + ry = self.attr("ry") + return cx, cy, rx, ry + + def draw(self): + import math + cx, cy, rx, ry = self.get_data() + style = self.get_style() + self.ctx.beginPath() + if self.has_transform(): + trans_matrix = self.get_transform() + self.ctx.transform(*trans_matrix) # unpacks argument list + self.set_style(style) + + KAPPA = 4 * ((math.sqrt(2) - 1) / 3) + self.ctx.moveTo(cx, cy - ry) + self.ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy) + self.ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry) + self.ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy) + self.ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry) + self.ctx.closePath() + + +class Path(AbstractShape): + def pathMoveTo(self, data): + self.ctx.moveTo(data[0], data[1]) + self.currentPosition = data[0], data[1] + + def pathLineTo(self, data): + self.ctx.lineTo(data[0], data[1]) + self.currentPosition = data[0], data[1] + + def pathCurveTo(self, data): + x1, y1, x2, y2 = data[0], data[1], data[2], data[3] + x, y = data[4], data[5] + self.ctx.bezierCurveTo(x1, y1, x2, y2, x, y) + self.currentPosition = x, y + + def draw(self): + """Gets the node type and calls the given method""" + style = self.get_style() + self.ctx.beginPath() + if self.has_transform(): + trans_matrix = self.get_transform() + self.ctx.transform(*trans_matrix) # unpacks argument list + self.set_style(style) + + # Draws path commands + path_command = {"M": self.pathMoveTo, + "L": self.pathLineTo, + "C": self.pathCurveTo} + # Make sure we only have Lines and curves (no arcs etc) + for comm, data in self.node.path.to_superpath().to_path().to_arrays(): + if comm in path_command: + path_command[comm](data) + + self.ctx.closePath() + + +class Line(Path): + def get_data(self): + x1 = self.attr("x1") + y1 = self.attr("y1") + x2 = self.attr("x2") + y2 = self.attr("y2") + return ("M", (x1, y1)), ("L", (x2, y2)) + + +class Polygon(Path): + def get_data(self): + points = self.attr("points").strip().split(" ") + points = map(lambda x: x.split(","), points) + comm = [] + for pt in points: # creating path command similar + pt = list(map(float, pt)) + comm.append(["L", pt]) + comm[0][0] = "M" # first command must be a 'M' => moveTo + return comm + + +class Polyline(Polygon): + pass + + +class Text(AbstractShape): + def text_helper(self, tspan): + if not len(tspan): + return tspan.text + for ts in tspan: + return ts.text + self.text_helper(ts) + ts.tail + + def set_text_style(self, style): + keys = ("font-style", "font-weight", "font-size", "font-family") + text = [] + for key in keys: + if key in style: + text.append(style[key]) + self.ctx.setFont(" ".join(text)) + + def get_data(self): + x = self.attr("x") + y = self.attr("y") + return x, y + + def draw(self): + x, y = self.get_data() + style = self.get_style() + if self.has_transform(): + trans_matrix = self.get_transform() + self.ctx.transform(*trans_matrix) # unpacks argument list + self.set_style(style) + self.set_text_style(style) + + for tspan in self.node: + text = self.text_helper(tspan) + _x = float(tspan.get("x").split()[0]) + _y = float(tspan.get("y").split()[0]) + self.ctx.fillText(text, _x, _y) diff --git a/share/extensions/inkex.py b/share/extensions/inkex.py new file mode 100644 index 0000000..3253271 --- /dev/null +++ b/share/extensions/inkex.py @@ -0,0 +1,11 @@ +# coding=utf-8 +""" +This file only exists to not break extensions which have + + inkex.py + +This module should never be imported, Python should automatically find +"inkex/__init__.py" before finding this file. +""" + +raise Exception("FIXME: Python imported " + __file__) diff --git a/share/extensions/inkex/__init__.py b/share/extensions/inkex/__init__.py new file mode 100644 index 0000000..36884fc --- /dev/null +++ b/share/extensions/inkex/__init__.py @@ -0,0 +1,29 @@ +# coding=utf-8 +""" +This describes the core API for the inkex core modules. + +This provides the basis from which you can develop your inkscape extension. +""" + +# pylint: disable=wildcard-import +from __future__ import print_function + +from .extensions import * +from .utils import * +from .styles import * +from .paths import Path, CubicSuperPath # Path commands are not exported +from .colors import * +from .transforms import * +from .elements import * + +# legacy proxies +from .deprecated import Effect +from .deprecated import optparse +from .deprecated import InkOption +from .deprecated import etree +from .deprecated import localize +from .deprecated import debug + +# legacy functions +from .deprecated import are_near_relative +from .deprecated import unittouu diff --git a/share/extensions/inkex/__pycache__/__init__.cpython-39.pyc b/share/extensions/inkex/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..3e3e90f Binary files /dev/null and b/share/extensions/inkex/__pycache__/__init__.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/base.cpython-39.pyc b/share/extensions/inkex/__pycache__/base.cpython-39.pyc new file mode 100644 index 0000000..0fad73c Binary files /dev/null and b/share/extensions/inkex/__pycache__/base.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/bezier.cpython-39.pyc b/share/extensions/inkex/__pycache__/bezier.cpython-39.pyc new file mode 100644 index 0000000..c29a13e Binary files /dev/null and b/share/extensions/inkex/__pycache__/bezier.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/colors.cpython-39.pyc b/share/extensions/inkex/__pycache__/colors.cpython-39.pyc new file mode 100644 index 0000000..54725f1 Binary files /dev/null and b/share/extensions/inkex/__pycache__/colors.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/command.cpython-39.pyc b/share/extensions/inkex/__pycache__/command.cpython-39.pyc new file mode 100644 index 0000000..544fd56 Binary files /dev/null and b/share/extensions/inkex/__pycache__/command.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/deprecated.cpython-39.pyc b/share/extensions/inkex/__pycache__/deprecated.cpython-39.pyc new file mode 100644 index 0000000..778ef50 Binary files /dev/null and b/share/extensions/inkex/__pycache__/deprecated.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/extensions.cpython-39.pyc b/share/extensions/inkex/__pycache__/extensions.cpython-39.pyc new file mode 100644 index 0000000..07ba857 Binary files /dev/null and b/share/extensions/inkex/__pycache__/extensions.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/inx.cpython-39.pyc b/share/extensions/inkex/__pycache__/inx.cpython-39.pyc new file mode 100644 index 0000000..3ffedb9 Binary files /dev/null and b/share/extensions/inkex/__pycache__/inx.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/localization.cpython-39.pyc b/share/extensions/inkex/__pycache__/localization.cpython-39.pyc new file mode 100644 index 0000000..6bfaac4 Binary files /dev/null and b/share/extensions/inkex/__pycache__/localization.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/paths.cpython-39.pyc b/share/extensions/inkex/__pycache__/paths.cpython-39.pyc new file mode 100644 index 0000000..c47438d Binary files /dev/null and b/share/extensions/inkex/__pycache__/paths.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/ports.cpython-39.pyc b/share/extensions/inkex/__pycache__/ports.cpython-39.pyc new file mode 100644 index 0000000..039e6d9 Binary files /dev/null and b/share/extensions/inkex/__pycache__/ports.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/styles.cpython-39.pyc b/share/extensions/inkex/__pycache__/styles.cpython-39.pyc new file mode 100644 index 0000000..d99135a Binary files /dev/null and b/share/extensions/inkex/__pycache__/styles.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/transforms.cpython-39.pyc b/share/extensions/inkex/__pycache__/transforms.cpython-39.pyc new file mode 100644 index 0000000..3be6188 Binary files /dev/null and b/share/extensions/inkex/__pycache__/transforms.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/turtle.cpython-39.pyc b/share/extensions/inkex/__pycache__/turtle.cpython-39.pyc new file mode 100644 index 0000000..c2f622d Binary files /dev/null and b/share/extensions/inkex/__pycache__/turtle.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/tween.cpython-39.pyc b/share/extensions/inkex/__pycache__/tween.cpython-39.pyc new file mode 100644 index 0000000..0199ec1 Binary files /dev/null and b/share/extensions/inkex/__pycache__/tween.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/units.cpython-39.pyc b/share/extensions/inkex/__pycache__/units.cpython-39.pyc new file mode 100644 index 0000000..2619f10 Binary files /dev/null and b/share/extensions/inkex/__pycache__/units.cpython-39.pyc differ diff --git a/share/extensions/inkex/__pycache__/utils.cpython-39.pyc b/share/extensions/inkex/__pycache__/utils.cpython-39.pyc new file mode 100644 index 0000000..a095603 Binary files /dev/null and b/share/extensions/inkex/__pycache__/utils.cpython-39.pyc differ diff --git a/share/extensions/inkex/base.py b/share/extensions/inkex/base.py new file mode 100644 index 0000000..09d51c1 --- /dev/null +++ b/share/extensions/inkex/base.py @@ -0,0 +1,355 @@ +# coding=utf-8 +# +# Copyright (c) 2018 - Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +The ultimate base functionality for every Inkscape extension. +""" +from __future__ import absolute_import, print_function, unicode_literals + +import os +import sys +import copy +import shutil + +from argparse import ArgumentParser, Namespace +from lxml import etree + +from .utils import PY3, filename_arg, AbortExtension, ABORT_STATUS, errormsg, do_nothing +from .elements._base import load_svg, BaseElement # pylint: disable=unused-import +from .localization import localize + +stdout = sys.stdout + +try: + from typing import (List, Tuple, Type, Optional, Callable, Any, Union, IO, + TYPE_CHECKING, cast) +except ImportError: + cast = lambda x, y: y + TYPE_CHECKING = False + +if PY3: + unicode = str # pylint: disable=redefined-builtin,invalid-name + basestring = str # pylint: disable=redefined-builtin,invalid-name + stdout = sys.stdout.buffer # type: ignore + + +class InkscapeExtension(object): + """ + The base class extension, provides argument parsing and basic + variable handling features. + """ + multi_inx = False # Set to true if this class is used by multiple inx files. + + def __init__(self): + # type: () -> None + self.file_io = None # type: Optional[IO] + self.options = Namespace() + self.document = None # type: Union[None, bytes, str, unicode, etree] + self.arg_parser = ArgumentParser(description=self.__doc__) + + self.arg_parser.add_argument( + "input_file", nargs="?", metavar="INPUT_FILE", type=filename_arg, + help="Filename of the input file (default is stdin)", default=None) + + self.arg_parser.add_argument( + "--output", type=str, default=None, + help="Optional output filename for saving the result (default is stdout).") + + self.add_arguments(self.arg_parser) + + localize() + + def add_arguments(self, pars): + # type: (ArgumentParser) -> None + """Add any extra arguments to your extension handle, use: + + def add_arguments(self, pars): + pars.add_argument("--num-cool-things", type=int, default=3) + pars.add_argument("--pos-in-doc", type=str, default="doobry") + """ + pass # No extra arguments by default so super is not required + + def parse_arguments(self, args): + # type: (List[str]) -> None + """Parse the given arguments and set 'self.options'""" + self.options = self.arg_parser.parse_args(args) + + def arg_method(self, prefix='method'): + # type: (str) -> Callable[[str], Callable[[Any], Any]] + """Used by add_argument to match a tab selection with an object method + + pars.add_argument("--tab", type=self.arg_method(), default="foo") + ... + self.options.tab(arguments) + ... + def method_foo(self, arguments): + # do something + """ + def _inner(value): + name = '{}_{}'.format(prefix, value.strip('"').lower()).replace('-', '_') + try: + return getattr(self, name) + except AttributeError: + if name.startswith('_'): + return do_nothing + raise AbortExtension("Can not find method {}".format(name)) + return _inner + + def debug(self, msg): + # type: (str) -> None + """Write a debug message""" + errormsg("DEBUG<{}> {}\n".format(type(self).__name__, msg)) + + @staticmethod + def msg(msg): + # type: (str) -> None + """Write a non-error message""" + errormsg(msg) + + def run(self, args=None, output=stdout): + # type: (Optional[List[str]], Union[str, IO]) -> None + """Main entrypoint for any Inkscape Extension""" + try: + if args is None: + args = sys.argv[1:] + + self.parse_arguments(args) + if self.options.input_file is None: + self.options.input_file = sys.stdin + + if self.options.output is None: + # assert output + self.options.output = output + + self.load_raw() + self.save_raw(self.effect()) + except AbortExtension as err: + err.write() + sys.exit(ABORT_STATUS) + finally: + self.clean_up() + + def load_raw(self): + # type: () -> None + """Load the input stream or filename, save everything to self""" + if isinstance(self.options.input_file, (str, unicode)): + self.file_io = open(self.options.input_file, 'rb') + document = self.load(self.file_io) + else: + document = self.load(self.options.input_file) + self.document = document + + def save_raw(self, ret): + # type: (Any) -> None + """Save to the output stream, use everything from self""" + if self.has_changed(ret): + if isinstance(self.options.output, (str, unicode)): + with open(self.options.output, 'wb') as stream: + self.save(stream) + else: + self.save(self.options.output) + + def load(self, stream): + # type: (IO) -> str + """Takes the input stream and creates a document for parsing""" + raise NotImplementedError("No input handle for {}".format(self.name)) + + def save(self, stream): + # type: (IO) -> None + """Save the given document to the output file""" + raise NotImplementedError("No output handle for {}".format(self.name)) + + def effect(self): + # type: () -> Any + """Apply some effects on the document or local context""" + raise NotImplementedError("No effect handle for {}".format(self.name)) + + def has_changed(self, ret): # pylint: disable=no-self-use + # type: (Any) -> bool + """Return true if the output should be saved""" + return ret is not False + + def clean_up(self): + # type: () -> None + """Clean up any open handles and other items""" + if self.file_io is not None: + self.file_io.close() + + def svg_path(self): + # type: () -> Optional[str] + """ + Return the folder the svg is contained in. + Returns None if there is no file. + """ + if self.options.input_file: + return os.path.dirname(self.options.input_file) + return None + + @classmethod + def ext_path(cls): + # type: () -> str + """Return the folder the extension script is in""" + return os.path.dirname(sys.modules[cls.__module__].__file__) + + @classmethod + def get_resource(cls, name, abort_on_fail=True): + # type: (str, bool) -> str + """Return the full filename of the resource in the extension's dir""" + filename = os.path.join(cls.ext_path(), name) + if abort_on_fail and not os.path.isfile(filename): + raise AbortExtension("Could not find resource file: {}".format(filename)) + return filename + + def absolute_href(self, filename, default='~/'): + # type: (str, str) -> str + """ + Process the filename such that it's turned into an absolute filename + with the working directory being the directory of the loaded svg. + + User's home folder is also resolved. So '~/a.png` will be `/home/bob/a.png` + + Default is a fallback directory to use if the svg's filename is not available. + """ + filename = os.path.expanduser(filename) + if not os.path.isabs(filename): + path = self.svg_path() or default + filename = os.path.join(path, filename) + return os.path.realpath(os.path.expanduser(filename)) + + @property + def name(self): + # type: () -> str + """Return a fixed name for this extension""" + return type(self).__name__ + + +if TYPE_CHECKING: + _Base = InkscapeExtension +else: + _Base = object + + +class TempDirMixin(_Base): + """ + Provide a temporary directory for extensions to stash files. + """ + dir_suffix = '' + dir_prefix = 'inktmp' + + def __init__(self, *args, **kwargs): + self.tempdir = None + super(TempDirMixin, self).__init__(*args, **kwargs) + + def load_raw(self): + # type: () -> None + """Create the temporary directory""" + from tempfile import mkdtemp + self.tempdir = mkdtemp(self.dir_suffix, self.dir_prefix, None) + super(TempDirMixin, self).load_raw() + + def clean_up(self): + # type: () -> None + """Delete the temporary directory""" + if self.tempdir and os.path.isdir(self.tempdir): + shutil.rmtree(self.tempdir) + super(TempDirMixin, self).clean_up() + + +class SvgInputMixin(_Base): # pylint: disable=too-few-public-methods + """ + Expects the file input to be an svg document and will parse it. + """ + # Select all objects if none are selected + select_all = () # type: Tuple[Type[BaseElement], ...] + + def __init__(self): + super(SvgInputMixin, self).__init__() + + self.arg_parser.add_argument( + "--id", action="append", type=str, dest="ids", default=[], + help="id attribute of object to manipulate") + + self.arg_parser.add_argument( + "--selected-nodes", action="append", type=str, dest="selected_nodes", default=[], + help="id:subpath:position of selected nodes, if any") + + def load(self, stream): + # type: (IO) -> etree + """Load the stream as an svg xml etree and make a backup""" + document = load_svg(stream) + self.original_document = copy.deepcopy(document) + self.svg = document.getroot() + self.svg.selection.set(*self.options.ids) + if not self.svg.selection and self.select_all: + self.svg.selection = self.svg.descendants().filter(*self.select_all) + return document + + +class SvgOutputMixin(_Base): # pylint: disable=too-few-public-methods + """ + Expects the output document to be an svg document and will write an etree xml. + + A template can be specified to kick off the svg document building process. + """ + template = """ + """ + + @classmethod + def get_template(cls, **kwargs): + """ + Opens a template svg document for building, the kwargs + MUST include all the replacement values in the template, the + default template has 'width' and 'height' of the document. + """ + kwargs.setdefault('unit', '') + return load_svg(str(cls.template.format(**kwargs))) + + def save(self, stream): + # type: (IO) -> None + """Save the svg document to the given stream""" + if isinstance(self.document, (bytes, str, unicode)): + document = self.document + elif 'Element' in type(self.document).__name__: + # isinstance can't be used here because etree is broken + doc = cast(etree, self.document) + document = doc.getroot().tostring() + else: + raise ValueError("Unknown type of document: {} can not save."\ + .format(type(self.document).__name__)) + + try: + stream.write(document) + except TypeError: + # we hope that this happens only when document needs to be encoded + stream.write(document.encode('utf-8')) # type: ignore + +class SvgThroughMixin(SvgInputMixin, SvgOutputMixin): + """ + Combine the input and output svg document handling (usually for effects). + """ + + def has_changed(self, ret): # pylint: disable=unused-argument + # type: (Any) -> bool + """Return true if the svg document has changed""" + original = etree.tostring(self.original_document) + result = etree.tostring(self.document) + return original != result diff --git a/share/extensions/inkex/bezier.py b/share/extensions/inkex/bezier.py new file mode 100644 index 0000000..a95b56c --- /dev/null +++ b/share/extensions/inkex/bezier.py @@ -0,0 +1,425 @@ +# coding=utf-8 +# +# Copyright (C) 2010 Nick Drobchenko, nick@cnc-club.ru +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=invalid-name,too-many-locals +# +""" +Bezier calculations +""" + +import cmath +import math + +import numpy + +from .transforms import DirectedLineSegment +from .localization import inkex_gettext as _ + +# bez = ((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) + +def pointdistance(point_a, point_b): + """The straight line distance between two points""" + return math.sqrt(((point_b[0] - point_a[0]) ** 2) + ((point_b[1] - point_a[1]) ** 2)) + + +def between_point(point_a, point_b, time=0.5): + """Returns the point between point a and point b""" + return point_a[0] + time * (point_b[0] - point_a[0]),\ + point_a[1] + time * (point_b[1] - point_a[1]) + + +def percent_point(point_a, point_b, percent=50.0): + """Returns between_point but takes percent instead of 0.0-1.0""" + return between_point(point_a, point_b, percent / 100.0) + + +def root_wrapper(root_a, root_b, root_c, root_d): + """Get the Cubic function, moic formular of roots, simple root""" + if root_a: + # Monics formula see http://en.wikipedia.org/wiki/Cubic_function#Monic_formula_of_roots + mono_a, mono_b, mono_c = (root_b / root_a, root_c / root_a, root_d / root_a) + m = 2.0 * mono_a ** 3 - 9.0 * mono_a * mono_b + 27.0 * mono_c + k = mono_a ** 2 - 3.0 * mono_b + n = m ** 2 - 4.0 * k ** 3 + w1 = -.5 + .5 * cmath.sqrt(-3.0) + w2 = -.5 - .5 * cmath.sqrt(-3.0) + if n < 0: + m1 = pow(complex((m + cmath.sqrt(n)) / 2), 1. / 3) + n1 = pow(complex((m - cmath.sqrt(n)) / 2), 1. / 3) + else: + if m + math.sqrt(n) < 0: + m1 = -pow(-(m + math.sqrt(n)) / 2, 1. / 3) + else: + m1 = pow((m + math.sqrt(n)) / 2, 1. / 3) + if m - math.sqrt(n) < 0: + n1 = -pow(-(m - math.sqrt(n)) / 2, 1. / 3) + else: + n1 = pow((m - math.sqrt(n)) / 2, 1. / 3) + return (-1. / 3 * (mono_a + m1 + n1), + -1. / 3 * (mono_a + w1 * m1 + w2 * n1), + -1. / 3 * (mono_a + w2 * m1 + w1 * n1)) + elif root_b: + det = root_c ** 2.0 - 4.0 * root_b * root_d + if det: + return ( + (-root_c + cmath.sqrt(det)) / (2.0 * root_b), + (-root_c - cmath.sqrt(det)) / (2.0 * root_b)) + return (-root_c / (2.0 * root_b),) + elif root_c: + return (1.0 * (-root_d / root_c),) + return () + + +def bezlenapprx(sp1, sp2): + """Return the aproximate length between two beziers""" + return pointdistance(sp1[1], sp1[2]) \ + + pointdistance(sp1[2], sp2[0]) \ + + pointdistance(sp2[0], sp2[1]) + + +def cspbezsplit(sp1, sp2, time=0.5): + """Split a cubic bezier at the time period""" + m1 = tpoint(sp1[1], sp1[2], time) + m2 = tpoint(sp1[2], sp2[0], time) + m3 = tpoint(sp2[0], sp2[1], time) + m4 = tpoint(m1, m2, time) + m5 = tpoint(m2, m3, time) + m = tpoint(m4, m5, time) + return [[sp1[0][:], sp1[1][:], m1], [m4, m, m5], [m3, sp2[1][:], sp2[2][:]]] + + +def cspbezsplitatlength(sp1, sp2, length=0.5, tolerance=0.001): + """Split a cubic bezier at length""" + bez = (sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:]) + time = beziertatlength(bez, length, tolerance) + return cspbezsplit(sp1, sp2, time) + + +def cspseglength(sp1, sp2, tolerance=0.001): + """Get cubic bezier segment length""" + bez = (sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:]) + return bezierlength(bez, tolerance) + + +def csplength(csp): + """Get cubic bezier length""" + total = 0 + lengths = [] + for sp in csp: + lengths.append([]) + for i in range(1, len(sp)): + l = cspseglength(sp[i - 1], sp[i]) + lengths[-1].append(l) + total += l + return lengths, total + + +def bezierparameterize(bez): + """Return the bezier parameter size""" + ((bx0, by0), (bx1, by1), (bx2, by2), (bx3, by3)) = bez + # parametric bezier + x0 = bx0 + y0 = by0 + cx = 3 * (bx1 - x0) + bx = 3 * (bx2 - bx1) - cx + ax = bx3 - x0 - cx - bx + cy = 3 * (by1 - y0) + by = 3 * (by2 - by1) - cy + ay = by3 - y0 - cy - by + + return ax, ay, bx, by, cx, cy, x0, y0 + + +def linebezierintersect(arg_a, bez): + """Where a line and bezier intersect""" + ((lx1, ly1), (lx2, ly2)) = arg_a + # parametric line + dd = lx1 + cc = lx2 - lx1 + bb = ly1 + aa = ly2 - ly1 + + if aa: + coef1 = cc / aa + coef2 = 1 + else: + coef1 = 1 + coef2 = aa / cc + + ax, ay, bx, by, cx, cy, x0, y0 = bezierparameterize(bez) + # cubic intersection coefficients + a = coef1 * ay - coef2 * ax + b = coef1 * by - coef2 * bx + c = coef1 * cy - coef2 * cx + d = coef1 * (y0 - bb) - coef2 * (x0 - dd) + + roots = root_wrapper(a, b, c, d) + retval = [] + for i in roots: + if isinstance(i, complex) and i.imag == 0: + i = i.real + if not isinstance(i, complex) and 0 <= i <= 1: + retval.append(bezierpointatt(bez, i)) + return retval + + +def bezierpointatt(bez, t): + """Get coords at the given time point along a bezier curve""" + ax, ay, bx, by, cx, cy, x0, y0 = bezierparameterize(bez) + x = ax * (t ** 3) + bx * (t ** 2) + cx * t + x0 + y = ay * (t ** 3) + by * (t ** 2) + cy * t + y0 + return x, y + + +def bezierslopeatt(bez, t): + """Get slope at the given time point along a bezier curve""" + ax, ay, bx, by, cx, cy, _, _ = bezierparameterize(bez) + dx = 3 * ax * (t ** 2) + 2 * bx * t + cx + dy = 3 * ay * (t ** 2) + 2 * by * t + cy + return dx, dy + + +def beziertatslope(bez, d): + """Reverse; get time from slope along a bezier curve""" + ax, ay, bx, by, cx, cy, _, _ = bezierparameterize(bez) + (dy, dx) = d + # quadratic coefficients of slope formula + if dx: + slope = 1.0 * (dy / dx) + a = 3 * ay - 3 * ax * slope + b = 2 * by - 2 * bx * slope + c = cy - cx * slope + elif dy: + slope = 1.0 * (dx / dy) + a = 3 * ax - 3 * ay * slope + b = 2 * bx - 2 * by * slope + c = cx - cy * slope + else: + return [] + + roots = root_wrapper(0, a, b, c) + retval = [] + for i in roots: + if isinstance(i, complex) and i.imag == 0: + i = i.real + if not isinstance(i, complex) and 0 <= i <= 1: + retval.append(i) + return retval + + +def tpoint(p1, p2, t): + """Linearly interpolate between p1 and p2. + + t = 0.0 returns p1, t = 1.0 returns p2. + + :return: Interpolated point + :rtype: tuple + + :param p1: First point as sequence of two floats + :param p2: Second point as sequence of two floats + :param t: Number between 0.0 and 1.0 + :type t: float + """ + x1, y1 = p1 + x2, y2 = p2 + return x1 + t * (x2 - x1), y1 + t * (y2 - y1) + + +def beziersplitatt(bez, t): + """Split bezier at given time""" + ((bx0, by0), (bx1, by1), (bx2, by2), (bx3, by3)) = bez + m1 = tpoint((bx0, by0), (bx1, by1), t) + m2 = tpoint((bx1, by1), (bx2, by2), t) + m3 = tpoint((bx2, by2), (bx3, by3), t) + m4 = tpoint(m1, m2, t) + m5 = tpoint(m2, m3, t) + m = tpoint(m4, m5, t) + + return ((bx0, by0), m1, m4, m), (m, m5, m3, (bx3, by3)) + + +def addifclose(bez, l, error=0.001): + """Gravesen, Add if the line is closed, in-place addition to array l""" + box = 0 + for i in range(1, 4): + box += pointdistance(bez[i - 1], bez[i]) + chord = pointdistance(bez[0], bez[3]) + if (box - chord) > error: + first, second = beziersplitatt(bez, 0.5) + addifclose(first, l, error) + addifclose(second, l, error) + else: + l[0] += (box / 2.0) + (chord / 2.0) + + +# balfax, balfbx, balfcx, balfay, balfby, balfcy = 0, 0, 0, 0, 0, 0 + + +def balf(t, args): + """Bezier Arc Length Function""" + ax, bx, cx, ay, by, cy = args + retval = (ax * (t ** 2) + bx * t + cx) ** 2 + (ay * (t ** 2) + by * t + cy) ** 2 + return math.sqrt(retval) + + +def simpson(a, b, n_limit, tolerance, balarg): + """It's not known what this function does...""" + n = 2 + multiplier = (b - a) / 6.0 + endsum = balf(a, balarg) + balf(b, balarg) + interval = (b - a) / 2.0 + asum = 0.0 + bsum = balf(a + interval, balarg) + est1 = multiplier * (endsum + (2.0 * asum) + (4.0 * bsum)) + est0 = 2.0 * est1 + # print(multiplier, endsum, interval, asum, bsum, est1, est0) + while n < n_limit and abs(est1 - est0) > tolerance: + n *= 2 + multiplier /= 2.0 + interval /= 2.0 + asum += bsum + bsum = 0.0 + est0 = est1 + for i in range(1, n, 2): + bsum += balf(a + (i * interval), balarg) + est1 = multiplier * (endsum + (2.0 * asum) + (4.0 * bsum)) + # print(multiplier, endsum, interval, asum, bsum, est1, est0) + return est1 + + +def bezierlength(bez, tolerance=0.001, time=1.0): + """Get length of bezier curve""" + ax, ay, bx, by, cx, cy, _, _ = bezierparameterize(bez) + return simpson(0.0, time, 4096, tolerance, [3 * ax, 2 * bx, cx, 3 * ay, 2 * by, cy]) + + +def beziertatlength(bez, l=0.5, tolerance=0.001): + """Get bezier curve time at the length specified""" + curlen = bezierlength(bez, tolerance, 1.0) + time = 1.0 + tdiv = time + targetlen = l * curlen + diff = curlen - targetlen + while abs(diff) > tolerance: + tdiv /= 2.0 + if diff < 0: + time += tdiv + else: + time -= tdiv + curlen = bezierlength(bez, tolerance, time) + diff = curlen - targetlen + return time + +def maxdist(bez): + """Get maximum distance within bezier curve""" + seg = DirectedLineSegment(bez[0], bez[3]) + return max(seg.distance_to_point(*bez[1]), seg.distance_to_point(*bez[2])) + +def cspsubdiv(csp, flat): + """Sub-divide cubic sub-paths""" + for sp in csp: + subdiv(sp, flat) + + +def subdiv(sp, flat, i=1): + """sub divide bezier curve""" + while i < len(sp): + p0 = sp[i - 1][1] + p1 = sp[i - 1][2] + p2 = sp[i][0] + p3 = sp[i][1] + + bez = (p0, p1, p2, p3) + mdist = maxdist(bez) + if mdist <= flat: + i += 1 + else: + one, two = beziersplitatt(bez, 0.5) + sp[i - 1][2] = one[1] + sp[i][0] = two[2] + p = [one[2], one[3], two[1]] + sp[i:1] = [p] + + +def csparea(csp): + """Get area in cubic sub-path""" + MAT_AREA = numpy.array([[0, 2, 1, -3], + [-2, 0, 1, 1], + [-1, -1, 0, 2], + [3, -1, -2, 0]]) + area = 0.0 + for sp in csp: + if len(sp) < 2: + continue + for x, coord in enumerate(sp): # calculate polygon area + area += 0.5 * sp[x - 1][1][0] * (coord[1][1] - sp[x - 2][1][1]) + for i in range(1, len(sp)): # add contribution from cubic Bezier + vec_x = numpy.array([sp[i - 1][1][0], sp[i - 1][2][0], sp[i][0][0], sp[i][1][0]]) + vec_y = numpy.array([sp[i - 1][1][1], sp[i - 1][2][1], sp[i][0][1], sp[i][1][1]]) + vex = numpy.matmul(vec_x, MAT_AREA) + area += 0.15 * numpy.matmul(vex, vec_y.T) + return -area + + +def cspcofm(csp): + """Get cubic sub-path coefficient""" + MAT_COFM_0 = numpy.array([[0, 35, 10, -45], + [-35, 0, 12, 23], + [-10, -12, 0, 22], + [45, -23, -22, 0]]) + + MAT_COFM_1 = numpy.array([[0, 15, 3, -18], + [-15, 0, 9, 6], + [-3, -9, 0, 12], + [18, -6, -12, 0]]) + + MAT_COFM_2 = numpy.array([[0, 12, 6, -18], + [-12, 0, 9, 3], + [-6, -9, 0, 15], + [18, -3, -15, 0]]) + + MAT_COFM_3 = numpy.array([[0, 22, 23, -45], + [-22, 0, 12, 10], + [-23, -12, 0, 35], + [45, -10, -35, 0]]) + area = csparea(csp) + xc = 0.0 + yc = 0.0 + if abs(area) < 1.e-8: + raise ValueError(_("Area is zero, cannot calculate Center of Mass")) + for sp in csp: + for x, coord in enumerate(sp): # calculate polygon moment + xc += sp[x - 1][1][1] * (sp[x - 2][1][0] - coord[1][0]) \ + * (sp[x - 2][1][0] + sp[x - 1][1][0] + coord[1][0]) / 6 + yc += sp[x - 1][1][0] * (coord[1][1] - sp[x - 2][1][1]) \ + * (sp[x - 2][1][1] + sp[x - 1][1][1] + coord[1][1]) / 6 + for i in range(1, len(sp)): # add contribution from cubic Bezier + vec_x = numpy.array([sp[i - 1][1][0], sp[i - 1][2][0], sp[i][0][0], sp[i][1][0]]) + vec_y = numpy.array([sp[i - 1][1][1], sp[i - 1][2][1], sp[i][0][1], sp[i][1][1]]) + def _mul(MAT): + return numpy.matmul(numpy.matmul(vec_x, MAT), vec_y.T) + vec_t = numpy.array([ + _mul(MAT_COFM_0), + _mul(MAT_COFM_1), + _mul(MAT_COFM_2), + _mul(MAT_COFM_3) + ]) + xc += numpy.matmul(vec_x, vec_t.T) / 280 + yc += numpy.matmul(vec_y, vec_t.T) / 280 + return -xc / area, -yc / area diff --git a/share/extensions/inkex/colors.py b/share/extensions/inkex/colors.py new file mode 100644 index 0000000..5ae9403 --- /dev/null +++ b/share/extensions/inkex/colors.py @@ -0,0 +1,478 @@ +# coding=utf-8 +# +# Copyright (C) 2006 Jos Hirth, kaioa.com +# Copyright (C) 2007 Aaron C. Spike +# Copyright (C) 2009 Monash University +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Basic color controls +""" + +from .utils import PY3 +from .tween import interpcoord + +# All the names that get added to the inkex API itself. +__all__ = ('Color', 'ColorError', 'ColorIdError') + +if PY3: + unicode = str # pylint: disable=redefined-builtin,invalid-name + +SVG_COLOR = { + 'aliceblue': '#f0f8ff', + 'antiquewhite': '#faebd7', + 'aqua': '#00ffff', + 'aquamarine': '#7fffd4', + 'azure': '#f0ffff', + 'beige': '#f5f5dc', + 'bisque': '#ffe4c4', + 'black': '#000000', + 'blanchedalmond': '#ffebcd', + 'blue': '#0000ff', + 'blueviolet': '#8a2be2', + 'brown': '#a52a2a', + 'burlywood': '#deb887', + 'cadetblue': '#5f9ea0', + 'chartreuse': '#7fff00', + 'chocolate': '#d2691e', + 'coral': '#ff7f50', + 'cornflowerblue': '#6495ed', + 'cornsilk': '#fff8dc', + 'crimson': '#dc143c', + 'cyan': '#00ffff', + 'darkblue': '#00008b', + 'darkcyan': '#008b8b', + 'darkgoldenrod': '#b8860b', + 'darkgray': '#a9a9a9', + 'darkgreen': '#006400', + 'darkgrey': '#a9a9a9', + 'darkkhaki': '#bdb76b', + 'darkmagenta': '#8b008b', + 'darkolivegreen': '#556b2f', + 'darkorange': '#ff8c00', + 'darkorchid': '#9932cc', + 'darkred': '#8b0000', + 'darksalmon': '#e9967a', + 'darkseagreen': '#8fbc8f', + 'darkslateblue': '#483d8b', + 'darkslategray': '#2f4f4f', + 'darkslategrey': '#2f4f4f', + 'darkturquoise': '#00ced1', + 'darkviolet': '#9400d3', + 'deeppink': '#ff1493', + 'deepskyblue': '#00bfff', + 'dimgray': '#696969', + 'dimgrey': '#696969', + 'dodgerblue': '#1e90ff', + 'firebrick': '#b22222', + 'floralwhite': '#fffaf0', + 'forestgreen': '#228b22', + 'fuchsia': '#ff00ff', + 'gainsboro': '#dcdcdc', + 'ghostwhite': '#f8f8ff', + 'gold': '#ffd700', + 'goldenrod': '#daa520', + 'gray': '#808080', + 'grey': '#808080', + 'green': '#008000', + 'greenyellow': '#adff2f', + 'honeydew': '#f0fff0', + 'hotpink': '#ff69b4', + 'indianred': '#cd5c5c', + 'indigo': '#4b0082', + 'ivory': '#fffff0', + 'khaki': '#f0e68c', + 'lavender': '#e6e6fa', + 'lavenderblush': '#fff0f5', + 'lawngreen': '#7cfc00', + 'lemonchiffon': '#fffacd', + 'lightblue': '#add8e6', + 'lightcoral': '#f08080', + 'lightcyan': '#e0ffff', + 'lightgoldenrodyellow': '#fafad2', + 'lightgray': '#d3d3d3', + 'lightgreen': '#90ee90', + 'lightgrey': '#d3d3d3', + 'lightpink': '#ffb6c1', + 'lightsalmon': '#ffa07a', + 'lightseagreen': '#20b2aa', + 'lightskyblue': '#87cefa', + 'lightslategray': '#778899', + 'lightslategrey': '#778899', + 'lightsteelblue': '#b0c4de', + 'lightyellow': '#ffffe0', + 'lime': '#00ff00', + 'limegreen': '#32cd32', + 'linen': '#faf0e6', + 'magenta': '#ff00ff', + 'maroon': '#800000', + 'mediumaquamarine': '#66cdaa', + 'mediumblue': '#0000cd', + 'mediumorchid': '#ba55d3', + 'mediumpurple': '#9370db', + 'mediumseagreen': '#3cb371', + 'mediumslateblue': '#7b68ee', + 'mediumspringgreen': '#00fa9a', + 'mediumturquoise': '#48d1cc', + 'mediumvioletred': '#c71585', + 'midnightblue': '#191970', + 'mintcream': '#f5fffa', + 'mistyrose': '#ffe4e1', + 'moccasin': '#ffe4b5', + 'navajowhite': '#ffdead', + 'navy': '#000080', + 'oldlace': '#fdf5e6', + 'olive': '#808000', + 'olivedrab': '#6b8e23', + 'orange': '#ffa500', + 'orangered': '#ff4500', + 'orchid': '#da70d6', + 'palegoldenrod': '#eee8aa', + 'palegreen': '#98fb98', + 'paleturquoise': '#afeeee', + 'palevioletred': '#db7093', + 'papayawhip': '#ffefd5', + 'peachpuff': '#ffdab9', + 'peru': '#cd853f', + 'pink': '#ffc0cb', + 'plum': '#dda0dd', + 'powderblue': '#b0e0e6', + 'purple': '#800080', + 'rebeccapurple': '#663399', + 'red': '#ff0000', + 'rosybrown': '#bc8f8f', + 'royalblue': '#4169e1', + 'saddlebrown': '#8b4513', + 'salmon': '#fa8072', + 'sandybrown': '#f4a460', + 'seagreen': '#2e8b57', + 'seashell': '#fff5ee', + 'sienna': '#a0522d', + 'silver': '#c0c0c0', + 'skyblue': '#87ceeb', + 'slateblue': '#6a5acd', + 'slategray': '#708090', + 'slategrey': '#708090', + 'snow': '#fffafa', + 'springgreen': '#00ff7f', + 'steelblue': '#4682b4', + 'tan': '#d2b48c', + 'teal': '#008080', + 'thistle': '#d8bfd8', + 'tomato': '#ff6347', + 'turquoise': '#40e0d0', + 'violet': '#ee82ee', + 'wheat': '#f5deb3', + 'white': '#ffffff', + 'whitesmoke': '#f5f5f5', + 'yellow': '#ffff00', + 'yellowgreen': '#9acd32', + 'none': None, +} +COLOR_SVG = dict([(value, name) for name, value in SVG_COLOR.items()]) + +def is_color(color): + """Determine if it is a color that we can use. If not, leave it unchanged.""" + try: + return bool(Color(color)) + except ColorError: + return False + +def constrain(minim, value, maxim, channel): + """Returns the value so long as it is between min and max values""" + if channel == 'h': # Hue + return value % maxim # Wrap around hue value + return min([maxim, max([minim, value])]) + +class ColorError(KeyError): + """Specific color parsing error""" + +class ColorIdError(ColorError): + """Special color error for gradient and color stop ids""" + +class Color(list): + """An RGB array for the color""" + red = property(lambda self: self.to_rgb()[0]) + red = red.setter(lambda self, value: self._set(0, value)) + green = property(lambda self: self.to_rgb()[1]) + green = green.setter(lambda self, value: self._set(1, value)) + blue = property(lambda self: self.to_rgb()[2]) + blue = blue.setter(lambda self, value: self._set(2, value)) + alpha = property(lambda self: self.to_rgba()[3]) + alpha = alpha.setter(lambda self, value: self._set(3, value, ('rgba',))) + hue = property(lambda self: self.to_hsl()[0]) + hue = hue.setter(lambda self, value: self._set(0, value, ('hsl',))) + saturation = property(lambda self: self.to_hsl()[1]) + saturation = saturation.setter(lambda self, value: self._set(1, value, ('hsl',))) + lightness = property(lambda self: self.to_hsl()[2]) + lightness = lightness.setter(lambda self, value: self._set(2, value, ('hsl',))) + + def __init__(self, color=None, space='rgb'): + super(Color, self).__init__() + if isinstance(color, Color): + space, color = color.space, list(color) + + if isinstance(color, (str, unicode)): + # String from xml or css attributes + space, color = self.parse_str(color.strip()) + + if isinstance(color, int): + # Number from arg parser colour value + space, color = self.parse_int(color) + + # Empty list means 'none', or no color + if color is None: + color = [] + + if not isinstance(color, (list, tuple)): + raise ColorError("Not a known a color value") + + self.space = space + try: + for val in color: + self.append(val) + except ValueError: + raise ColorError("Bad color list") + + def __hash__(self): + """Allow colors to be hashable""" + return tuple(self.to_rgba()).__hash__() + + def _set(self, index, value, spaces=('rgb', 'rgba')): + """Set the color value in place, limits setter to specific color space""" + # Named colors are just rgb, so dump name memory + if self.space == 'named': + self.space = 'rgb' + if not self.space in spaces: + if index == 3 and self.space == 'rgb': + # Special, add alpha, don't convert back to rgb + self.space = 'rgba' + self.append(constrain(0.0, float(value), 1.0, 'a')) + return + # Set in other colour space and convert back and forth + target = self.to(spaces[0]) + target[index] = constrain(0, int(value), 255, spaces[0][index]) + self[:] = target.to(self.space) + return + self[index] = constrain(0, int(value), 255, spaces[0][index]) + + def append(self, val): + """Append a value to the local list""" + if len(self) == len(self.space): + raise ValueError("Can't add any more values to color.") + + if isinstance(val, (unicode, str)): + val = val.strip() + if val.endswith('%'): + val = float(val.strip('%')) / 100 + else: + val = float(val) + + end_type = int + if len(self) == 3: # Alpha value + val = min([1.0, val]) + end_type = float + elif isinstance(val, float) and val <= 1.0: + val *= 255 + + if isinstance(val, (int, float)): + super(Color, self).append(max(end_type(val), 0)) + + @staticmethod + def parse_str(color): + """Creates a rgb int array""" + # Handle pre-defined svg color values + if color and color.lower() in SVG_COLOR: + return 'named', Color.parse_str(SVG_COLOR[color.lower()])[1] + + if color is None: + return 'rgb', None + + if color.startswith('url('): + raise ColorIdError("Color references other element id, e.g. a gradient") + + # Next handle short colors (css: #abc -> #aabbcc) + if color.startswith('#'): + # Remove any icc or ilab directives + # FUTURE: We could use icc or ilab information + col = color.split(' ')[0] + if len(col) == 4: + col = '#{1}{1}{2}{2}{3}{3}'.format(*col) + + # Convert hex to integers + try: + return 'rgb', (int(col[1:3], 16), int(col[3:5], 16), int(col[5:], 16)) + except ValueError: + raise ColorError("Bad RGB hex color value {}".format(col)) + + # Handle other css color values + elif '(' in color and ')' in color: + space, values = color.lower().strip().strip(')').split('(') + return space, values.split(',') + + try: + return Color.parse_int(int(color)) + except ValueError: + pass + + raise ColorError("Unknown color format: {}".format(color)) + + @staticmethod + def parse_int(color): + """Creates an rgb or rgba from a long int""" + space = 'rgb' + color = [ + ((color >> 24) & 255), # red + ((color >> 16) & 255), # green + ((color >> 8) & 255), # blue + ((color & 255) / 255.), # opacity + ] + if color[-1] == 1.0: + color.pop() + else: + space = 'rgba' + return space, color + + def __str__(self): + """int array to #rrggbb""" + if not self: + return 'none' + if self.space == 'named': + rgbhex = '#{0:02x}{1:02x}{2:02x}'.format(*self) + if rgbhex in COLOR_SVG: + return COLOR_SVG[rgbhex] + self.space = 'rgb' + if self.space == 'rgb': + return '#{0:02x}{1:02x}{2:02x}'.format(*self) + if self.space == 'rgba': + if self[3] == 1.0: + return 'rgb({:g}, {:g}, {:g})'.format(*self[:3]) + return 'rgba({:g}, {:g}, {:g}, {:g})'.format(*self) + elif self.space == 'hsl': + return 'hsl({0:g}, {1:g}, {2:g})'.format(*self) + raise ColorError("Can't print colour space '{}'".format(self.space)) + + def __int__(self): + """int array to large integer""" + if not self: + return -1 + color = self.to_rgba() + return (color[0] << 24) + (color[1] << 16) + (color[2] << 8) + (int(color[3] * 255)) + + def to(self, space): + """Dynamic caller for to_hsl, to_rgb, etc""" + return getattr(self, 'to_' + space)() + + def to_hsl(self): + """Turn this color into a Hue/Saturation/Lightness colour space""" + if not self and self.space in ('rgb', 'named'): + return self.to_rgb().to_hsl() + if self.space == 'hsl': + return self + elif self.space == 'rgb': + return Color(rgb_to_hsl(*self.to_floats()), space='hsl') + raise ColorError("Unknown color conversion {}->hsl".format(self.space)) + + def to_rgb(self): + """Turn this color into a Red/Green/Blue colour space""" + if not self and self.space in ('rgb', 'named'): + return Color([0, 0, 0]) + if self.space == 'rgb': + return self + if self.space in ('rgba', 'named'): + return Color(self[:3], space='rgb') + elif self.space == 'hsl': + return Color(hsl_to_rgb(*self.to_floats()), space='rgb') + raise ColorError("Unknown color conversion {}->rgb".format(self.space)) + + def to_rgba(self, alpha=1.0): + """Turn this color isn't an RGB with Alpha colour space""" + if self.space == 'rgba': + return self + return Color(self.to_rgb() + [alpha], 'rgba') + + def to_floats(self): + """Returns the colour values as percentage floats (0.0 - 1.0)""" + return [val / 255.0 for val in self] + + def to_named(self): + """Convert this color to a named color if possible""" + if not self: + return Color() + return Color(COLOR_SVG.get(str(self), str(self))) + + def interpolate(self, other, fraction): + """Iterpolate two colours by the given fraction""" + return Color( + [interpcoord(c1, c2, fraction) + for (c1, c2) in zip(self.to_floats(), other.to_floats())] + ) + + +def rgb_to_hsl(red, green, blue): + """RGB to HSL colour conversion""" + rgb_max = max(red, green, blue) + rgb_min = min(red, green, blue) + delta = rgb_max - rgb_min + hsl = [0.0, 0.0, (rgb_max + rgb_min) / 2.0] + if delta != 0: + if hsl[2] <= 0.5: + hsl[1] = delta / (rgb_max + rgb_min) + else: + hsl[1] = delta / (2 - rgb_max - rgb_min) + + if red == rgb_max: + hsl[0] = (green - blue) / delta + elif green == rgb_max: + hsl[0] = 2.0 + (blue - red) / delta + elif blue == rgb_max: + hsl[0] = 4.0 + (red - green) / delta + + hsl[0] /= 6.0 + if hsl[0] < 0: + hsl[0] += 1 + if hsl[0] > 1: + hsl[0] -= 1 + return hsl + + +def hsl_to_rgb(hue, sat, light): + """HSL to RGB Color Conversion""" + if sat == 0: + return [light, light, light] # Gray + + if light < 0.5: + val2 = light * (1 + sat) + else: + val2 = light + sat - light * sat + val1 = 2 * light - val2 + return [_hue_to_rgb(val1, val2, hue * 6 + 2.0), + _hue_to_rgb(val1, val2, hue * 6), + _hue_to_rgb(val1, val2, hue * 6 - 2.0)] + + +def _hue_to_rgb(val1, val2, hue): + if hue < 0: + hue += 6.0 + if hue > 6: + hue -= 6.0 + if hue < 1: + return val1 + (val2 - val1) * hue + if hue < 3: + return val2 + if hue < 4: + return val1 + (val2 - val1) * (4 - hue) + return val1 diff --git a/share/extensions/inkex/command.py b/share/extensions/inkex/command.py new file mode 100644 index 0000000..0377002 --- /dev/null +++ b/share/extensions/inkex/command.py @@ -0,0 +1,222 @@ +# coding=utf-8 +# +# Copyright (C) 2019 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +This API provides methods for calling Inkscape to execute a given +Inkscape command. This may be needed for various compiling options +(e.g., png), running other extensions or performing other options only +available via the shell API. + +Best practice is to avoid using this API except when absolutely necessary, +since it is resource-intensive to invoke a new Inkscape instance. + +However, in any circumstance when it is necessary to call Inkscape, it +is strongly recommended that you do so through this API, rather than calling +it yourself, to take advantage of the security settings and testing functions. + +""" + +import os +from subprocess import Popen, PIPE +from lxml.etree import ElementTree + +from .utils import TemporaryDirectory, PY3 +from .elements import SvgDocumentElement + +INKSCAPE_EXECUTABLE_NAME = os.environ.get('INKSCAPE_COMMAND', 'inkscape') + +class CommandNotFound(IOError): + """Command is not found""" + pass + +class ProgramRunError(ValueError): + """Command returned non-zero output""" + pass + +def which(program): + """ + Attempt different methods of trying to find if the program exists. + """ + if os.path.isabs(program) and os.path.isfile(program): + return program + try: + # Python2 and python3, but must have distutils and may not always + # work on windows versions (depending on the version) + from distutils.spawn import find_executable + prog = find_executable(program) + if prog: + return prog + except ImportError: + pass + + try: + # Python3 only version of which + from shutil import which as warlock + prog = warlock(program) + if prog: + return prog + except ImportError: + pass # python2 + + # There may be other methods for doing a `which` command for other + # operating systems; These should go here as they are discovered. + + raise CommandNotFound("Can not find the command: '{}'".format(program)) + +def write_svg(svg, *filename): + """Writes an svg to the given filename""" + filename = os.path.join(*filename) + if os.path.isfile(filename): + return filename + with open(filename, 'wb') as fhl: + if isinstance(svg, SvgDocumentElement): + svg = ElementTree(svg) + if hasattr(svg, 'write'): + # XML document + svg.write(fhl) + elif isinstance(svg, bytes): + fhl.write(svg) + else: + raise ValueError("Not sure what type of SVG data this is.") + return filename + + +def to_arg(arg, oldie=False): + """Convert a python argument to a command line argument""" + if isinstance(arg, (tuple, list)): + (arg, val) = arg + arg = '-' + arg + if len(arg) > 2 and not oldie: + arg = '-' + arg + if val is True: + return arg + if val is False: + return None + return '{}={}'.format(arg, str(val)) + return str(arg) + +def to_args(prog, *positionals, **arguments): + """ + Convert positional arguments and key word arguments + into a list of strings which Popen will understand. + + Values can be: + + args = *[ + 'strait_up_string', + '--or_manual_kwarg=1', + ('ordered list', 'version of kwargs (as below)'), + ... + ] + kwargs = **{ + 'name': 'val', # --name="val"' + 'name': ['foo', 'bar'], # --name=foo --name=bar + 'name': True, # --name + 'n': 'v', # -n=v + 'n': True, # -n + } + + All args appear after the kwargs, so if you need args before, + use the ordered list tuple and don't use kwargs. + """ + args = [prog] + oldie = arguments.pop('oldie', False) + for arg, value in arguments.items(): + arg = arg.replace('_', '-').strip() + + if isinstance(value, tuple): + value = list(value) + elif not isinstance(value, list): + value = [value] + + for val in value: + args.append(to_arg((arg, val), oldie)) + + args += [to_arg(pos, oldie) for pos in positionals if pos is not None] + # Filter out empty non-arguments + return [arg for arg in args if arg is not None] + +def _call(program, *args, **kwargs): + stdin = kwargs.pop('stdin', None) + if PY3 and isinstance(stdin, str): + stdin = stdin.encode('utf-8') + inpipe = PIPE if stdin else None + + args = to_args(which(program), *args, **kwargs) + process = Popen( + args, + shell=False, # Never have shell=True + stdin=inpipe, # StdIn not used (yet) + stdout=PIPE, # Grab any output (return it) + stderr=PIPE, # Take all errors, just incase + ) + (stdout, stderr) = process.communicate(input=stdin) + if process.returncode == 0: + return stdout + raise ProgramRunError("Return Code: {}: {}\n{}\nargs: {}".format( + process.returncode, stderr, stdout, args)) + +def call(program, *args, **kwargs): + """ + Generic caller to open any program and return its stdout. + + stdout = call('executable', arg1, arg2, dash_dash_arg='foo', d=True, ...) + + Will raise ProgramRunError() if return code is not 0. + """ + return _call(program, *args, **kwargs) + +def inkscape(svg_file, *args, **kwargs): + """ + Call Inkscape with the given svg_file and the given arguments + """ + return call(INKSCAPE_EXECUTABLE_NAME, svg_file, *args, **kwargs) + +def inkscape_command(svg, select=None, verbs=()): + """ + Executes a list of commands, a mixture of verbs, selects etc. + + inkscape_command('', ('verb', 'VerbName'), ...) + """ + with TemporaryDirectory(prefix='inkscape-command') as dirname: + svg_file = write_svg(svg, dirname, 'input.svg') + select = ('select', select) if select else None + verbs += ('FileSave', 'FileQuit') + inkscape(svg_file, select, batch_process=True, verb=';'.join(verbs)) + with open(svg_file, 'rb') as fhl: + return fhl.read() + +def take_snapshot(svg, dirname, name='snapshot', ext='png', dpi=96, **kwargs): + """ + Take a snapshot of the given svg file. + + Resulting filename is yielded back, after generator finishes, the + file is deleted so you must deal with the file inside the for loop. + """ + svg_file = write_svg(svg, dirname, name + '.svg') + ext_file = os.path.join(dirname, name + '.' + str(ext).lower()) + inkscape(svg_file, export_dpi=dpi, export_filename=ext_file, export_type=ext, **kwargs) + return ext_file + + +def is_inkscape_available(): + """Return true if the Inkscape executable is available.""" + try: + return bool(which(INKSCAPE_EXECUTABLE_NAME)) + except CommandNotFound: + return False diff --git a/share/extensions/inkex/deprecated-simple/README.rst b/share/extensions/inkex/deprecated-simple/README.rst new file mode 100644 index 0000000..df3e2dc --- /dev/null +++ b/share/extensions/inkex/deprecated-simple/README.rst @@ -0,0 +1,4 @@ +# coding=utf-8This directory contains compatibility layers for all the `simple` modules, such as `simplepath` and `simplestyle` + +This directory IS NOT a module path, to denote this we are using a dash in the name and there is no '__init__.py' + diff --git a/share/extensions/inkex/deprecated-simple/__pycache__/bezmisc.cpython-39.pyc b/share/extensions/inkex/deprecated-simple/__pycache__/bezmisc.cpython-39.pyc new file mode 100644 index 0000000..db8842a Binary files /dev/null and b/share/extensions/inkex/deprecated-simple/__pycache__/bezmisc.cpython-39.pyc differ diff --git a/share/extensions/inkex/deprecated-simple/__pycache__/cspsubdiv.cpython-39.pyc b/share/extensions/inkex/deprecated-simple/__pycache__/cspsubdiv.cpython-39.pyc new file mode 100644 index 0000000..26ec318 Binary files /dev/null and b/share/extensions/inkex/deprecated-simple/__pycache__/cspsubdiv.cpython-39.pyc differ diff --git a/share/extensions/inkex/deprecated-simple/__pycache__/cubicsuperpath.cpython-39.pyc b/share/extensions/inkex/deprecated-simple/__pycache__/cubicsuperpath.cpython-39.pyc new file mode 100644 index 0000000..0e063ba Binary files /dev/null and b/share/extensions/inkex/deprecated-simple/__pycache__/cubicsuperpath.cpython-39.pyc differ diff --git a/share/extensions/inkex/deprecated-simple/__pycache__/ffgeom.cpython-39.pyc b/share/extensions/inkex/deprecated-simple/__pycache__/ffgeom.cpython-39.pyc new file mode 100644 index 0000000..b00b896 Binary files /dev/null and b/share/extensions/inkex/deprecated-simple/__pycache__/ffgeom.cpython-39.pyc differ diff --git a/share/extensions/inkex/deprecated-simple/__pycache__/simplepath.cpython-39.pyc b/share/extensions/inkex/deprecated-simple/__pycache__/simplepath.cpython-39.pyc new file mode 100644 index 0000000..d85fe9d Binary files /dev/null and b/share/extensions/inkex/deprecated-simple/__pycache__/simplepath.cpython-39.pyc differ diff --git a/share/extensions/inkex/deprecated-simple/__pycache__/simplestyle.cpython-39.pyc b/share/extensions/inkex/deprecated-simple/__pycache__/simplestyle.cpython-39.pyc new file mode 100644 index 0000000..17bbfa5 Binary files /dev/null and b/share/extensions/inkex/deprecated-simple/__pycache__/simplestyle.cpython-39.pyc differ diff --git a/share/extensions/inkex/deprecated-simple/__pycache__/simpletransform.cpython-39.pyc b/share/extensions/inkex/deprecated-simple/__pycache__/simpletransform.cpython-39.pyc new file mode 100644 index 0000000..f28b4e5 Binary files /dev/null and b/share/extensions/inkex/deprecated-simple/__pycache__/simpletransform.cpython-39.pyc differ diff --git a/share/extensions/inkex/deprecated-simple/bezmisc.py b/share/extensions/inkex/deprecated-simple/bezmisc.py new file mode 100644 index 0000000..1992440 --- /dev/null +++ b/share/extensions/inkex/deprecated-simple/bezmisc.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=invalid-name,unused-argument +"""Deprecated bezmisc API""" + +from inkex.deprecated import deprecate +from inkex import bezier + +bezierparameterize = deprecate(bezier.bezierparameterize) +linebezierintersect = deprecate(bezier.linebezierintersect) +bezierpointatt = deprecate(bezier.bezierpointatt) +bezierslopeatt = deprecate(bezier.bezierslopeatt) +beziertatslope = deprecate(bezier.beziertatslope) +tpoint = deprecate(bezier.tpoint) +beziersplitatt = deprecate(bezier.beziersplitatt) +pointdistance = deprecate(bezier.pointdistance) +Gravesen_addifclose = deprecate(bezier.addifclose) +balf = deprecate(bezier.balf) +bezierlengthSimpson = deprecate(bezier.bezierlength) +beziertatlength = deprecate(bezier.beziertatlength) +bezierlength = bezierlengthSimpson + +@deprecate +def Simpson(func, a, b, n_limit, tolerance): + """bezier.simpson(a, b, n_limit, tolerance, balf_arguments)""" + raise AttributeError( + """Because bezmisc.Simpson used global variables, it's not possible to + call the replacement code automatically. In fact it's unlikely you were + using the code or functionality you think you were since it's a highly + broken way of writing python.""") diff --git a/share/extensions/inkex/deprecated-simple/cspsubdiv.py b/share/extensions/inkex/deprecated-simple/cspsubdiv.py new file mode 100644 index 0000000..91b2237 --- /dev/null +++ b/share/extensions/inkex/deprecated-simple/cspsubdiv.py @@ -0,0 +1,25 @@ +# coding=utf-8 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=invalid-name +"""Deprecated cspsubdiv API""" + +from inkex.deprecated import deprecate +from inkex import bezier + +maxdist = deprecate(bezier.maxdist) +cspsubdiv = deprecate(bezier.cspsubdiv) +subdiv = deprecate(bezier.subdiv) diff --git a/share/extensions/inkex/deprecated-simple/cubicsuperpath.py b/share/extensions/inkex/deprecated-simple/cubicsuperpath.py new file mode 100644 index 0000000..990da31 --- /dev/null +++ b/share/extensions/inkex/deprecated-simple/cubicsuperpath.py @@ -0,0 +1,46 @@ +# coding=utf-8 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=invalid-name +"""Deprecated cubic super path API""" + +from inkex.deprecated import deprecate +from inkex import paths + +@deprecate +def ArcToPath(p1, params): + return paths.arc_to_path(p1, params) + +@deprecate +def CubicSuperPath(simplepath): + return paths.Path(simplepath).to_superpath() + +@deprecate +def unCubicSuperPath(csp): + return paths.CubicSuperPath(csp).to_path().to_arrays() + +@deprecate +def parsePath(d): + return paths.CubicSuperPath(paths.Path(d)) + +@deprecate +def formatPath(p): + return str(paths.Path(unCubicSuperPath(p))) + +matprod = deprecate(paths.matprod) +rotmat = deprecate(paths.rotmat) +applymat = deprecate(paths.applymat) +norm = deprecate(paths.norm) diff --git a/share/extensions/inkex/deprecated-simple/ffgeom.py b/share/extensions/inkex/deprecated-simple/ffgeom.py new file mode 100644 index 0000000..bef3ba4 --- /dev/null +++ b/share/extensions/inkex/deprecated-simple/ffgeom.py @@ -0,0 +1,87 @@ +# coding=utf-8 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=invalid-name,missing-docstring +"""Deprecated ffgeom API""" + +from collections import namedtuple + +from inkex.deprecated import deprecate +from inkex.transforms import DirectedLineSegment as NewSeg + +try: + NaN = float('NaN') +except ValueError: + PosInf = 1e300000 + NaN = PosInf/PosInf + +class Point(namedtuple('Point', 'x y')): + __slots__ = () + def __getitem__(self, key): + if isinstance(key, str): + key = 'xy'.index(key) + return super(Point, self).__getitem__(key) + +class Segment(NewSeg): + @deprecate + def __init__(self, e0, e1): + """inkex.transforms.Segment(((x1, y1), (x2, y2)))""" + if isinstance(e0, dict): + e0 = (e0['x'], e0['y']) + if isinstance(e1, dict): + e1 = (e1['x'], e1['y']) + super(Segment, self).__init__((e0, e1)) + + def __getitem__(self, key): + if key: + return {'x': self.x.maximum, 'y': self.y.maximum} + return {'x': self.x.minimum, 'y': self.y.minimum} + + delta_x = lambda self: self.width + delta_y = lambda self: self.height + run = delta_x + rise = delta_y + + def distanceToPoint(self, p): + return self.distance_to_point(p['x'], p['y']) + + def perpDistanceToPoint(self, p): + return self.perp_distance(p['x'], p['y']) + + def angle(self): + return super(Segment, self).angle + + def length(self): + return super(Segment, self).length + + def pointAtLength(self, length): + return self.point_at_length(length) + + def pointAtRatio(self, ratio): + return self.point_at_ratio(ratio) + + def createParallel(self, p): + self.parallel(p['x'], p['y']) + +@deprecate +def intersectSegments(s1, s2): + """transforms.Segment(s1).intersect(s2)""" + return Point(*s1.intersect(s2)) + +@deprecate +def dot(s1, s2): + """transforms.Segment(s1).dot(s2)""" + return s1.dot(s2) diff --git a/share/extensions/inkex/deprecated-simple/run_command.py b/share/extensions/inkex/deprecated-simple/run_command.py new file mode 100755 index 0000000..7190750 --- /dev/null +++ b/share/extensions/inkex/deprecated-simple/run_command.py @@ -0,0 +1,74 @@ +# coding=utf-8 +# +# Copyright (C) 2008 Stephen Silver +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +# +""" +Deprecated module for running SVG-generating commands in Inkscape extensions +""" +import os +import sys +import tempfile +from subprocess import Popen, PIPE + +from inkex.deprecated import deprecate + +def run(command_format, prog_name): + """inkex.commands.call(...)""" + svgfile = tempfile.mktemp(".svg") + command = command_format % svgfile + msg = None + # ps2pdf may attempt to write to the current directory, which may not + # be writeable, so we switch to the temp directory first. + try: + os.chdir(tempfile.gettempdir()) + except IOError: + pass + + try: + proc = Popen(command, shell=True, stdout=PIPE, stderr=PIPE) + return_code = proc.wait() + out = proc.stdout.read() + err = proc.stderr.read() + + if msg is None: + if return_code: + msg = "{} failed:\n{}\n{}\n".format(prog_name, out, err) + elif err: + sys.stderr.write("{} executed but logged the following error:\n{}\n{}\n".format(prog_name, out, err)) + except Exception as inst: + msg = "Error attempting to run {}: {}".format(prog_name, str(inst)) + + # If successful, copy the output file to stdout. + if msg is None: + if os.name == 'nt': # make stdout work in binary on Windows + import msvcrt + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + try: + with open(svgfile, "rb") as fhl: + sys.stdout.write(fhl.read().decode(sys.stdout.encoding)) + except IOError as inst: + msg = "Error reading temporary file: {}".format(str(inst)) + + try: + # Clean up. + os.remove(svgfile) + except (IOError, OSError): + pass + + # Output error message (if any) and exit. + return msg + diff --git a/share/extensions/inkex/deprecated-simple/simplepath.py b/share/extensions/inkex/deprecated-simple/simplepath.py new file mode 100644 index 0000000..97748f7 --- /dev/null +++ b/share/extensions/inkex/deprecated-simple/simplepath.py @@ -0,0 +1,51 @@ +# coding=utf-8 +# COPYRIGHT +# +# pylint: disable=invalid-name +# +""" +Depreicated simplepath replacements with documentation +""" + +import math +from inkex.deprecated import deprecate, DeprecatedDict +from inkex.transforms import Transform +from inkex.paths import Path + +pathdefs = DeprecatedDict({ + 'M':['L', 2, [float, float], ['x', 'y']], + 'L':['L', 2, [float, float], ['x', 'y']], + 'H':['H', 1, [float], ['x']], + 'V':['V', 1, [float], ['y']], + 'C':['C', 6, [float, float, float, float, float, float], ['x', 'y', 'x', 'y', 'x', 'y']], + 'S':['S', 4, [float, float, float, float], ['x', 'y', 'x', 'y']], + 'Q':['Q', 4, [float, float, float, float], ['x', 'y', 'x', 'y']], + 'T':['T', 2, [float, float], ['x', 'y']], + 'A':['A', 7, [float, float, float, int, int, float, float], ['r', 'r', 'a', 0, 's', 'x', 'y']], + 'Z':['L', 0, [], []] +}) + +@deprecate +def parsePath(d): + """element.path.to_arrays()""" + return Path(d).to_arrays() + +@deprecate +def formatPath(a): + """str(element.path) or str(Path(array))""" + return str(Path(a)) + +@deprecate +def translatePath(p, x, y): + """Path(array).translate(x, y)""" + p[:] = Path(p).translate(x, y).to_arrays() + +@deprecate +def scalePath(p, x, y): + """Path(array).scale(x, y)""" + p[:] = Path(p).scale(x, y).to_arrays() + +@deprecate +def rotatePath(p, a, cx=0, cy=0): + """Path(array).rotate(angle_degrees, (center_x, center_y))""" + p[:] = Path(p).rotate(math.degrees(a), (cx, cy)).to_arrays() diff --git a/share/extensions/inkex/deprecated-simple/simplestyle.py b/share/extensions/inkex/deprecated-simple/simplestyle.py new file mode 100644 index 0000000..3312143 --- /dev/null +++ b/share/extensions/inkex/deprecated-simple/simplestyle.py @@ -0,0 +1,47 @@ +# coding=utf-8 +# COPYRIGHT +"""DOCSTRING""" + +import inkex +from inkex.colors import SVG_COLOR as svgcolors +from inkex.deprecated import deprecate + +@deprecate +def parseStyle(s): + """dict(inkex.Style.parse_str(s))""" + return dict(inkex.Style.parse_str(s)) + +@deprecate +def formatStyle(a): + """str(inkex.Style(a))""" + return str(inkex.Style(a)) + +@deprecate +def isColor(c): + """inkex.colors.is_color(c)""" + return inkex.colors.is_color(c) + +@deprecate +def parseColor(c): + """inkex.Color(c).to_rgb()""" + return tuple(inkex.Color(c).to_rgb()) + +@deprecate +def formatColoria(a): + """str(inkex.Color(a))""" + return str(inkex.Color(a)) + +@deprecate +def formatColorfa(a): + """str(inkex.Color(a))""" + return str(inkex.Color(a)) + +@deprecate +def formatColor3i(r,g,b): + """str(inkex.Color((r, g, b)))""" + return str(inkex.Color((r, g, b))) + +@deprecate +def formatColor3f(r,g,b): + """str(inkex.Color((r, g, b)))""" + return str(inkex.Color((r, g, b))) diff --git a/share/extensions/inkex/deprecated-simple/simpletransform.py b/share/extensions/inkex/deprecated-simple/simpletransform.py new file mode 100644 index 0000000..1130c30 --- /dev/null +++ b/share/extensions/inkex/deprecated-simple/simpletransform.py @@ -0,0 +1,106 @@ +# coding=utf-8 +# +# pylint: disable=invalid-name +# +""" +Depreicated simpletransform replacements with documentation +""" + +import warnings + +from inkex.deprecated import deprecate +from inkex.transforms import Transform, BoundingBox, cubic_extrema +from inkex.paths import Path + +import inkex, cubicsuperpath + +def _lists(mat): + return [list(row) for row in mat] + +@deprecate +def parseTransform(transf, mat=None): + """Transform(str).matrix""" + t = Transform(transf) + if mat is not None: + t = Transform(mat) * t + return _lists(t.matrix) + +@deprecate +def formatTransform(mat): + """str(Transform(mat))""" + if len(mat) == 3: + warnings.warn("3x3 matrices not suported") + mat = mat[:2] + return str(Transform(mat)) + +@deprecate +def invertTransform(mat): + """-Transform(mat)""" + return _lists((-Transform(mat)).matrix) + +@deprecate +def composeTransform(mat1, mat2): + """Transform(M1) * Transform(M2)""" + return _lists((Transform(mat1) * Transform(mat2)).matrix) + +@deprecate +def composeParents(node, mat): + """elem.composed_transform() or elem.transform * Transform(mat)""" + return (node.transform * Transform(mat)).matrix + +@deprecate +def applyTransformToNode(mat, node): + """elem.transform = Transform(mat) * elem.transform """ + node.transform = Transform(mat) * node.transform + +@deprecate +def applyTransformToPoint(mat, pt): + """Transform(mat).apply_to_point(pt)""" + pt2 = Transform(mat).apply_to_point(pt) + # Apply in place as original method was modifying arrays in place. + # but don't do this in your code! This is not good code design. + pt[0] = pt2[0] + pt[1] = pt2[1] + +@deprecate +def applyTransformToPath(mat, path): + """Path(path).transform(mat)""" + return Path(path).transform(Transform(mat)).to_arrays() + +@deprecate +def fuseTransform(node): + """node.apply_transform()""" + return node.apply_transform() + +@deprecate +def boxunion(b1, b2): + """list(BoundingBox(b1) + BoundingBox(b2))""" + bbox = BoundingBox(b1[:2], b1[2:]) + BoundingBox(b2[:2], b2[2:]) + return bbox.x.minimum, bbox.x.maximum, bbox.y.minimum, bbox.y.maximum + +@deprecate +def roughBBox(path): + """list(Path(path)).bounding_box())""" + bbox = Path(path).bounding_box() + return bbox.x.minimum, bbox.x.maximum, bbox.y.minimum, bbox.y.maximum + +@deprecate +def refinedBBox(path): + """list(Path(path)).bounding_box())""" + bbox = Path(path).bounding_box() + return bbox.x.minimum, bbox.x.maximum, bbox.y.minimum, bbox.y.maximum + +@deprecate +def cubicExtrema(y0, y1, y2, y3): + """from inkex.transforms import cubic_extrema""" + return cubic_extrema(y0, y1, y2, y3) + +@deprecate +def computeBBox(aList, mat=[[1,0,0],[0,1,0]]): + """sum([node.bounding_box() for node in aList])""" + return sum([node.bounding_box() for node in aList], None) + +@deprecate +def computePointInNode(pt, node, mat=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]): + """(-Transform(node.transform * mat)).apply_to_point(pt)""" + return (-Transform(node.transform * mat)).apply_to_point(pt) diff --git a/share/extensions/inkex/deprecated.py b/share/extensions/inkex/deprecated.py new file mode 100644 index 0000000..b7c73ef --- /dev/null +++ b/share/extensions/inkex/deprecated.py @@ -0,0 +1,388 @@ +# coding=utf-8 +# +# Copyright (C) 2018 - Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Provide some documentation to existing extensions about why they're failing. +""" +# +# We ignore a lot of pylint warnings here: +# +# pylint: disable=invalid-name,unused-argument,missing-docstring,too-many-public-methods +# + +import os +import sys +import traceback +import warnings +import argparse +from argparse import ArgumentParser + +import inkex +import inkex.utils +import inkex.units +from inkex.base import SvgThroughMixin, InkscapeExtension +from inkex.localization import inkex_gettext as _ + +warnings.simplefilter("default") +# To load each of the deprecated sub-modules (the ones without a namespace) +# we will add the directory to our pythonpath so older scripts can find them + +INKEX_DIR = os.path.abspath(os.path.dirname(__file__)) +SIMPLE_DIR = os.path.join(INKEX_DIR, 'deprecated-simple') + +if os.path.isdir(SIMPLE_DIR): + sys.path.append(SIMPLE_DIR) + +try: + DEPRECATION_LEVEL = int(os.environ.get('INKEX_DEPRECATION_LEVEL', 1)) +except ValueError: + DEPRECATION_LEVEL = 1 + +def _deprecated(msg, stack=2, level=DEPRECATION_LEVEL): + """Internal method for raising a deprecation warning""" + if level > 1: + msg += ' ; '.join(traceback.format_stack()) + if level: + warnings.warn(msg, category=DeprecationWarning, stacklevel=stack + 1) + +class DeprecatedEffect(object): + """An Inkscape effect, takes SVG in and outputs SVG, providing a deprecated layer""" + + def __init__(self): + super(DeprecatedEffect, self).__init__() + + self._doc_ids = None + + # These are things we reference in the deprecated code, they are provided + # by the new effects code, but we want to keep this as a Mixin so these + # items will keep pylint happy and let use check our code as we write. + if not hasattr(self, 'svg'): + from .elements import SvgDocumentElement + self.svg = SvgDocumentElement() + if not hasattr(self, 'arg_parser'): + self.arg_parser = ArgumentParser() + if not hasattr(self, 'run'): + self.run = self.affect + + @classmethod + def _deprecated(cls, name, msg=_('{} is deprecated and should be removed'), stack=3): + """Give the user a warning about their extension using a deprecated API""" + _deprecated( + msg.format('Effect.' + name, cls=cls.__module__ + '.' + cls.__name__), + stack=stack) + + @property + def OptionParser(self): + self._deprecated( + 'OptionParser', + _('{} or `optparse` has been deprecated and replaced with `argparser`.' + 'You must change `self.OptionParser.add_option` to ' + '`self.arg_parser.add_argument`; the arguments are similar.')) + return self + + def add_option(self, *args, **kw): + # Convert type string into type method as needed + if 'type' in kw: + kw['type'] = { + 'string': str, + 'int': int, + 'float': float, + 'inkbool': inkex.utils.Boolean, + }.get(kw['type']) + if kw.get('action', None) == 'store': + # Default store action not required, removed. + kw.pop('action') + args = [arg for arg in args if arg != ""] + self.arg_parser.add_argument(*args, **kw) + + def effect(self): + self._deprecated('effect', _('{} method is now a required method. It should ' + 'be created on {cls}, even if it does nothing.')) + + @property + def current_layer(self): + self._deprecated('current_layer',\ + _('{} is now a method in the svg. Use `self.svg.get_current_layer()` instead.')) + return self.svg.get_current_layer() + + @property + def view_center(self): + self._deprecated('view_center',\ + _('{} is now a method in the svg. Use `self.svg.namedview.center` instead.')) + return self.svg.namedview.center + + @property + def selected(self): + self._deprecated('selected', _('{} is now a dict in the svg. Use `self.svg.selected`.')) + return dict([(elem.get('id'), elem) for elem in self.svg.selected.values()]) + + @property + def doc_ids(self): + self._deprecated('doc_ids', _('{} is now a method in the svg ' + 'document. Use `self.svg.get_ids()` instead.')) + if self._doc_ids is None: + self._doc_ids = dict.fromkeys(self.svg.get_ids()) + return self._doc_ids + + def getdocids(self): + self._deprecated('getdocids', _('Use `self.svg.get_ids()` instead of {} and `doc_ids`.')) + self._doc_ids = None + self.svg.ids.clear() + + def getselected(self): + self._deprecated('getselected', _('{} has been removed')) + + def getElementById(self, eid): + self._deprecated('getElementById',\ + _('{} is now a method in the svg. Use `self.svg.getElementById(eid)` instead.')) + return self.svg.getElementById(eid) + + def xpathSingle(self, xpath): + self._deprecated('xpathSingle', _('{} is now a new method in the svg ' + 'document. Use `self.svg.getElement(path)` instead.`')) + return self.svg.getElement(xpath) + + def getParentNode(self, node): + self._deprecated('getParentNode',\ + _('{} is no longer in use. Use the lxml .getparent() method instead.')) + return node.getparent() + + def getNamedView(self): + self._deprecated('getNamedView',\ + _('{} is now a property of the svg. Use `self.svg.namedview` to access this element')) + return self.svg.namedview + + def createGuide(self, posX, posY, angle): + from .elements import Guide + self._deprecated('createGuide',\ + _('{} is now a method of the namedview element object. ' + 'Use `self.svg.namedview.add(Guide().move_to(x, y, a))` instead')) + return self.svg.namedview.add(Guide().move_to(posX, posY, angle)) + + def affect(self, args=sys.argv[1:], output=True): # pylint: disable=dangerous-default-value + # We need a list as the default value to preserve backwards compatibility + self._deprecated('affect', _('{} is now `Effect.run()`. The `output` argument has changed.')) + self._args = args[-1:] + return self.run(args=args) + + @property + def args(self): + self._deprecated('args', _('self.args[-1] is now self.options.input_file')) + return self._args + + @property + def svg_file(self): + self._deprecated('svg_file', _('self.svg_file is now self.options.input_file')) + return self.options.input_file + + def save_raw(self, ret): + # Derived class may implement "output()" + # Attention: 'cubify.py' implements __getattr__ -> hasattr(self, 'output') returns True + if hasattr(self.__class__, 'output'): + self._deprecated('output', 'Use `save()` or `save_raw()` instead.', stack=5) + return getattr(self, 'output')() + return inkex.base.InkscapeExtension.save_raw(self, ret) + + def uniqueId(self, old_id, make_new_id=True): + self._deprecated('uniqueId', _('{} is now a method in the svg document. ' + ' Use `self.svg.get_unique_id(old_id)` instead.')) + return self.svg.get_unique_id(old_id) + + def getDocumentWidth(self): + self._deprecated('getDocumentWidth', _('{} is now a property of the svg ' + 'document. Use `self.svg.width` instead.')) + return self.svg.get('width') + + def getDocumentHeight(self): + self._deprecated('getDocumentHeight', _('{} is now a property of the svg ' + 'document. Use `self.svg.height` instead.')) + return self.svg.get('height') + + def getDocumentUnit(self): + self._deprecated('getDocumentUnit', _('{} is now a property of the svg ' + 'document. Use `self.svg.unit` instead.')) + return self.svg.unit + + def unittouu(self, string): + self._deprecated('unittouu', _('{} is now a method in the svg ' + 'document. Use `self.svg.unittouu(str)` instead.')) + return self.svg.unittouu(string) + + def uutounit(self, val, unit): + self._deprecated('uutounit', _('{} is now a method in the svg ' + 'document. Use `self.svg.uutounit(value, unit)` instead.')) + return self.svg.uutounit(val, unit) + + def addDocumentUnit(self, value): + self._deprecated('addDocumentUnit', _('{} is now a method in the svg ' + 'document. Use `self.svg.add_unit(value)` instead.')) + return self.svg.add_unit(value) + +class Effect(SvgThroughMixin, DeprecatedEffect, InkscapeExtension): + """An Inkscape effect, takes SVG in and outputs SVG""" + pass + +def deprecate(func): + """Function decorator for deprecation functions which have a one-liner + equivalent in the new API. The one-liner has to passed as a string + to the decorator. + + >>> @deprecate + >>> def someOldFunction(*args): + >>> '''Example replacement code someNewFunction('foo', ...)''' + >>> someNewFunction('foo', *args) + + Or if the args API is the same: + + >>> someOldFunction = deprecate(someNewFunction) + + """ + + def _inner(*args, **kwargs): + _deprecated('{0.__module__}.{0.__name__} -> {0.__doc__}'.format(func), stack=2) + return func(*args, **kwargs) + _inner.__name__ = func.__name__ + if func.__doc__: + _inner.__doc__ = "Deprecated -> " + func.__doc__ + return _inner + +class DeprecatedDict(dict): + @deprecate + def __getitem__(self, key): + return super(DeprecatedDict, self).__getitem__(key) + + @deprecate + def __iter__(self): + return super(DeprecatedDict, self).__iter__() + +# legacy inkex members + +class lazyproxy(object): + """Proxy, use as decorator on a function with provides the wrapped object. + The decorated function is called when a member is accessed on the proxy. + """ + def __init__(self, getwrapped): + ''' + :param getwrapped: Callable which returns the wrapped object + ''' + self._getwrapped = getwrapped + + def __getattr__(self, name): + return getattr(self._getwrapped(), name) + + def __call__(self, *args, **kwargs): + return self._getwrapped()(*args, **kwargs) + +@lazyproxy +def optparse(): + _deprecated('inkex.optparse was removed, use "import optparse"', stack=3) + import optparse as wrapped + return wrapped + +@lazyproxy +def etree(): + _deprecated('inkex.etree was removed, use "from lxml import etree"', stack=3) + from lxml import etree as wrapped + return wrapped + +@lazyproxy +def InkOption(): + import optparse + class wrapped(optparse.Option): + TYPES = optparse.Option.TYPES + ("inkbool", ) + TYPE_CHECKER = dict(optparse.Option.TYPE_CHECKER) + TYPE_CHECKER["inkbool"] = lambda _1, _2, v: str(v).capitalize() == 'True' + return wrapped + +@lazyproxy +def localize(): + _deprecated('inkex.localize was moved to inkex.localization.localize', stack=3) + from .localization import localize as wrapped + return wrapped + +def are_near_relative(a, b, eps): + _deprecated('inkex.are_near_relative was moved to ' + 'inkex.units.are_near_relative', stack=2) + return inkex.units.are_near_relative(a, b, eps) + +def debug(what): + _deprecated('inkex.debug was moved to inkex.utils.debug', stack=2) + return inkex.utils.debug(what) + +# legacy inkex members <= 0.48.x + +def unittouu(string): + _deprecated('inkex.unittouu is now a method in the svg ' + 'document. Use `self.svg.unittouu(str)` instead.', stack=2) + return inkex.units.convert_unit(string, 'px') + +# optparse.Values.ensure_value + +def ensure_value(self, attr, value): + _deprecated('Effect().options.ensure_value was removed', stack=2) + if getattr(self, attr, None) is None: + setattr(self, attr, value) + return getattr(self, attr) + +argparse.Namespace.ensure_value = ensure_value # type: ignore + +@deprecate +def zSort(inNode, idList): + """self.svg.get_z_selected()""" + sortedList = [] + theid = inNode.get("id") + if theid in idList: + sortedList.append(theid) + for child in inNode: + if len(sortedList) == len(idList): + break + sortedList += zSort(child, idList) + return sortedList + +class DeprecatedSvgMixin(object): + """Mixin which adds deprecated API elements to the SvgDocumentElement""" + @property + def selected(self): + """svg.selection""" + return self.selection + + def set_selected(self, *ids): + """svg.selection.set(*ids)""" + return self.selection.set(*ids) + + def get_z_selected(self): + """svg.selection.paint_order()""" + return self.selection.paint_order() + + def get_selected(self, *types): + """svg.selection.filter(*types).values()""" + return self.selection.filter(*types).values() + + def get_selected_or_all(self, *types): + """Set select_all = True in extension class""" + if not self.selection: + self.selection.set_all() + return self.selection.filter(*types) + + def get_selected_bbox(self): + """selection.bounding_box()""" + return self.selection.bounding_box() + + def get_first_selected(self, *types): + """selection.filter(*types).first() or [0] if you'd like an error""" + return self.selection.filter(*types).first() diff --git a/share/extensions/inkex/elements/__init__.py b/share/extensions/inkex/elements/__init__.py new file mode 100644 index 0000000..f5f0d65 --- /dev/null +++ b/share/extensions/inkex/elements/__init__.py @@ -0,0 +1,19 @@ +""" +Element based interface provides the bulk of features that allow you to +interact directly with the SVG xml interface. + +See the documentation for each of the elements for details on how it works. +""" + +from ._base import SVG_PARSER, load_svg, ShapeElement, BaseElement +from ._svg import SvgDocumentElement +from ._groups import Group, Layer, Anchor, Marker, ClipPath +from ._polygons import PathElement, Polyline, Polygon, Line, Rectangle, Circle, Ellipse +from ._text import FlowRegion, FlowRoot, FlowPara, FlowDiv, FlowSpan, TextElement, \ + TextPath, Tspan, SVGfont, FontFace, Glyph, MissingGlyph +from ._use import Symbol, Use +from ._meta import Defs, StyleElement, Script, Desc, Title, NamedView, Guide, \ + Metadata, ForeignObject, Switch, Grid +from ._filters import Filter, Pattern, Gradient, LinearGradient, RadialGradient, \ + PathEffect, Stop, MeshGradient, MeshRow, MeshPatch +from ._image import Image diff --git a/share/extensions/inkex/elements/__pycache__/__init__.cpython-39.pyc b/share/extensions/inkex/elements/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..718ece7 Binary files /dev/null and b/share/extensions/inkex/elements/__pycache__/__init__.cpython-39.pyc differ diff --git a/share/extensions/inkex/elements/__pycache__/_base.cpython-39.pyc b/share/extensions/inkex/elements/__pycache__/_base.cpython-39.pyc new file mode 100644 index 0000000..06dd762 Binary files /dev/null and b/share/extensions/inkex/elements/__pycache__/_base.cpython-39.pyc differ diff --git a/share/extensions/inkex/elements/__pycache__/_filters.cpython-39.pyc b/share/extensions/inkex/elements/__pycache__/_filters.cpython-39.pyc new file mode 100644 index 0000000..a1d3a70 Binary files /dev/null and b/share/extensions/inkex/elements/__pycache__/_filters.cpython-39.pyc differ diff --git a/share/extensions/inkex/elements/__pycache__/_groups.cpython-39.pyc b/share/extensions/inkex/elements/__pycache__/_groups.cpython-39.pyc new file mode 100644 index 0000000..35305d8 Binary files /dev/null and b/share/extensions/inkex/elements/__pycache__/_groups.cpython-39.pyc differ diff --git a/share/extensions/inkex/elements/__pycache__/_image.cpython-39.pyc b/share/extensions/inkex/elements/__pycache__/_image.cpython-39.pyc new file mode 100644 index 0000000..1c2849c Binary files /dev/null and b/share/extensions/inkex/elements/__pycache__/_image.cpython-39.pyc differ diff --git a/share/extensions/inkex/elements/__pycache__/_meta.cpython-39.pyc b/share/extensions/inkex/elements/__pycache__/_meta.cpython-39.pyc new file mode 100644 index 0000000..807de7b Binary files /dev/null and b/share/extensions/inkex/elements/__pycache__/_meta.cpython-39.pyc differ diff --git a/share/extensions/inkex/elements/__pycache__/_polygons.cpython-39.pyc b/share/extensions/inkex/elements/__pycache__/_polygons.cpython-39.pyc new file mode 100644 index 0000000..270e082 Binary files /dev/null and b/share/extensions/inkex/elements/__pycache__/_polygons.cpython-39.pyc differ diff --git a/share/extensions/inkex/elements/__pycache__/_selected.cpython-39.pyc b/share/extensions/inkex/elements/__pycache__/_selected.cpython-39.pyc new file mode 100644 index 0000000..6272318 Binary files /dev/null and b/share/extensions/inkex/elements/__pycache__/_selected.cpython-39.pyc differ diff --git a/share/extensions/inkex/elements/__pycache__/_svg.cpython-39.pyc b/share/extensions/inkex/elements/__pycache__/_svg.cpython-39.pyc new file mode 100644 index 0000000..800e778 Binary files /dev/null and b/share/extensions/inkex/elements/__pycache__/_svg.cpython-39.pyc differ diff --git a/share/extensions/inkex/elements/__pycache__/_text.cpython-39.pyc b/share/extensions/inkex/elements/__pycache__/_text.cpython-39.pyc new file mode 100644 index 0000000..1e898f2 Binary files /dev/null and b/share/extensions/inkex/elements/__pycache__/_text.cpython-39.pyc differ diff --git a/share/extensions/inkex/elements/__pycache__/_use.cpython-39.pyc b/share/extensions/inkex/elements/__pycache__/_use.cpython-39.pyc new file mode 100644 index 0000000..7fb1511 Binary files /dev/null and b/share/extensions/inkex/elements/__pycache__/_use.cpython-39.pyc differ diff --git a/share/extensions/inkex/elements/_base.py b/share/extensions/inkex/elements/_base.py new file mode 100644 index 0000000..e922908 --- /dev/null +++ b/share/extensions/inkex/elements/_base.py @@ -0,0 +1,514 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Sergei Izmailov +# Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=arguments-differ +""" +Provide extra utility to each svg element type specific to its type. + +This is useful for having a common interface for each element which can +give path, transform, and property access easily. +""" + +from collections import defaultdict +from copy import deepcopy +from lxml import etree + +from ..paths import Path +from ..styles import Style, AttrFallbackStyle, Classes +from ..transforms import Transform, BoundingBox +from ..utils import PY3, NSS, addNS, removeNS, splitNS, FragmentError +from ..utils import InitSubClassPy3 + +try: + from typing import overload, DefaultDict, Type, Any, List, Tuple, Union, Optional # pylint: disable=unused-import +except ImportError: + overload = lambda x: x + +class NodeBasedLookup(etree.PythonElementClassLookup): + """ + We choose what kind of Elements we should return for each element, providing useful + SVG based API to our extensions system. + """ + # (ns,tag) -> list(cls) ; ascending priority + lookup_table = defaultdict(list) # type: DefaultDict[str, List[Any]] + + @classmethod + def register_class(cls, klass): + """Register the given class using it's attached tag name""" + cls.lookup_table[splitNS(klass.tag_name)].append(klass) + + def lookup(self, doc, element): # pylint: disable=unused-argument + """Lookup called by lxml when assigning elements their object class""" + try: + for cls in reversed(self.lookup_table[splitNS(element.tag)]): + if cls._is_class_element(element): # pylint: disable=protected-access + return cls + except TypeError: + # Handle non-element proxies case + # The documentation implies that it's not possible + # Didn't found a reliable way to check whether proxy corresponds to element or not + # Look like lxml issue to me. + # The troubling element is "" + return None + return BaseElement + + +SVG_PARSER = etree.XMLParser(huge_tree=True, strip_cdata=False) +SVG_PARSER.set_element_class_lookup(NodeBasedLookup()) + +def load_svg(stream): + """Load SVG file using the SVG_PARSER""" + if (isinstance(stream, str) and stream.lstrip().startswith('<'))\ + or (isinstance(stream, bytes) and stream.lstrip().startswith(b'<')): + return etree.ElementTree(etree.fromstring(stream, parser=SVG_PARSER)) + return etree.parse(stream, parser=SVG_PARSER) + +class BaseElement(etree.ElementBase): + """Provide automatic namespaces to all calls""" + # Included for python2 support (this branch is for 1.0.x only) + __metaclass__ = InitSubClassPy3 + @classmethod + def __init_subclass__(cls): + if cls.tag_name: + NodeBasedLookup.register_class(cls) + + @classmethod + def _is_class_element(cls, el): # type: (etree.Element) -> bool + """Hook to do more restrictive check in addition to (ns,tag) match""" + return True + + tag_name = '' + + @property + def TAG(self): # pylint: disable=invalid-name + """Return the tag_name without NS""" + if not self.tag_name: + return removeNS(super(etree.ElementBase, self).tag)[-1] + return removeNS(self.tag_name)[-1] + + @classmethod + def new(cls, *children, **attrs): + """Create a new element, converting attrs values to strings.""" + obj = cls(*children) + obj.update(**attrs) + return obj + + NAMESPACE = property(lambda self: splitNS(self.tag_name)[0]) + PARSER = SVG_PARSER + WRAPPED_ATTRS = ( + # (prop_name, [optional: attr_name], cls) + ('transform', Transform), + ('style', Style), + ('classes', 'class', Classes), + ) # type: Tuple[Tuple[Any, ...], ...] + + # We do this because python2 and python3 have different ways + # of combining two dictionaries that are incompatible. + # This allows us to update these with inheritance. + @property + def wrapped_attrs(self): + """Map attributes to property name and wrapper class""" + return dict([(row[-2], (row[0], row[-1])) for row in self.WRAPPED_ATTRS]) + + @property + def wrapped_props(self): + """Map properties to attribute name and wrapper class""" + return dict([(row[0], (row[-2], row[-1])) for row in self.WRAPPED_ATTRS]) + + typename = property(lambda self: type(self).__name__) + xml_path = property(lambda self: self.getroottree().getpath(self)) + + def __getattr__(self, name): + """Get the attribute, but load it if it is not available yet""" + if name in self.wrapped_props: + (attr, cls) = self.wrapped_props[name] + # The reason we do this here and not in _init is because lxml + # is inconsistant about when elements are initialised. + # So we make this a lazy property. + def _set_attr(new_item): + if new_item: + self.set(attr, str(new_item)) + else: + self.attrib.pop(attr, None) # pylint: disable=no-member + + # pylint: disable=no-member + value = cls(self.attrib.get(attr, None), callback=_set_attr) + setattr(self, name, value) + return value + raise AttributeError("Can't find attribute {}.{}".format(self.typename, name)) + + def __setattr__(self, name, value): + """Set the attribute, update it if needed""" + if name in self.wrapped_props: + (attr, cls) = self.wrapped_props[name] + # Don't call self.set or self.get (infinate loop) + if value: + if not isinstance(value, cls): + value = cls(value) + self.attrib[attr] = str(value) + else: + self.attrib.pop(attr, None) # pylint: disable=no-member + else: + super(BaseElement, self).__setattr__(name, value) + + def get(self, attr, default=None): + """Get element attribute named, with addNS support.""" + if attr in self.wrapped_attrs: + (prop, _) = self.wrapped_attrs[attr] + value = getattr(self, prop, None) + # We check the boolean nature of the value, because empty + # transformations and style attributes are equiv to not-existing + ret = str(value) if value else (default or None) + return ret + return super(BaseElement, self).get(addNS(attr), default) + + def set(self, attr, value): + """Set element attribute named, with addNS support""" + if attr in self.wrapped_attrs: + # Always keep the local wrapped class up to date. + (prop, cls) = self.wrapped_attrs[attr] + setattr(self, prop, cls(value)) + value = getattr(self, prop) + if not value: + return + if value is None: + self.attrib.pop(addNS(attr), None) # pylint: disable=no-member + else: + value = str(value) if PY3 else unicode(value) # pylint: disable=undefined-variable + super(BaseElement, self).set(addNS(attr), value) + + def update(self, **kwargs): + """ + Update element attributes using keyword arguments + + Note: double underscore is used as namespace separator, + i.e. "namespace__attr" argument name will be treated as "namespace:attr" + + :param kwargs: dict with name=value pairs + :return: self + """ + for name, value in kwargs.items(): + self.set(name, value) + return self + + def pop(self, attr, default=None): + """Delete/remove the element attribute named, with addNS support.""" + if attr in self.wrapped_attrs: + # Always keep the local wrapped class up to date. + (prop, cls) = self.wrapped_attrs[attr] + value = getattr(self, prop) + setattr(self, prop, cls(None)) + return value + return self.attrib.pop(addNS(attr), default) # pylint: disable=no-member + + def add(self, *children): + """ + Like append, but will do multiple children and will return + children or only child + """ + for child in children: + self.append(child) + return children if len(children) != 1 else children[0] + + def tostring(self): + """Return this element as it would appear in an svg document""" + # This kind of hack is pure maddness, but etree provides very little + # in the way of fragment printing, prefering to always output valid xml + from ..base import SvgOutputMixin + svg = SvgOutputMixin.get_template(width=0, height=0).getroot() + svg.append(self.copy()) + return svg.tostring().split(b'>\n ', 1)[-1][:-6] + + def description(self, text): + """Set the desc element with text""" + from ._meta import Desc + desc = self.add(Desc()) + desc.text = text + + def set_random_id(self, prefix=None, size=4, backlinks=False): + """Sets the id attribute if it is not already set.""" + prefix = str(self) if prefix is None else prefix + self.set_id(self.root.get_unique_id(prefix, size=size), backlinks=backlinks) + + def set_random_ids(self, prefix=None, levels=-1, backlinks=False): + """Same as set_random_id, but will apply also to children""" + self.set_random_id(prefix=prefix, backlinks=backlinks) + if levels != 0: + for child in self: + if hasattr(child, 'set_random_ids'): + child.set_random_ids(prefix=prefix, levels=levels-1, backlinks=backlinks) + + def get_id(self): + """Get the id for the element, will set a new unique id if not set""" + if 'id' not in self.attrib: + self.set_random_id(self.TAG) + return self.get('id') + + def set_id(self, new_id, backlinks=False): + """Set the id and update backlinks to xlink and style urls if needed""" + old_id = self.get('id', None) + self.set('id', new_id) + if backlinks and old_id: + for elem in self.root.getElementsByHref(old_id): + elem.href = self + for elem in self.root.getElementsByStyleUrl(old_id): + elem.style.update_urls(old_id, new_id) + + @property + def root(self): + """Get the root document element from any element descendent""" + if self.getparent() is not None: + return self.getparent().root + from ._svg import SvgDocumentElement + if not isinstance(self, SvgDocumentElement): + raise FragmentError("Element fragment does not have a document root!") + return self + + def get_or_create(self, xpath, nodeclass, prepend=False): + """Get or create the given xpath, pre/append new node if not found.""" + node = self.findone(xpath) + if node is None: + node = nodeclass() + if prepend: + self.insert(0, node) + else: + self.append(node) + return node + + def descendants(self): + """Walks the element tree and yields all elements, parent first""" + from ._selected import ElementList + return ElementList(self.root, self._descendants()) + + def _descendants(self): + yield self + for child in self: + if hasattr(child, '_descendants'): + for item in child._descendants(): # pylint: disable=protected-access + yield item + + def ancestors(self, elem=None, stop_at=()): + """ + Walk the parents and yield all the ancestor elements, parent first + + If elem is provided, it will stop at the last common ancestor. + If stop_at is provided, it will stop at the first parent that is in this list. + """ + from ._selected import ElementList + return ElementList(self.root, self._ancestors(elem=elem, stop_at=stop_at)) + + def _ancestors(self, elem, stop_at): + if isinstance(elem, BaseElement): + stop_at = list(elem.ancestors().values()) + parent = self.getparent() + if parent is not None: + yield parent + if parent not in stop_at: + for item in parent._ancestors(elem=elem, stop_at=stop_at): # pylint: disable=protected-access + yield item + + def backlinks(self, *types): + """Get elements which link back to this element, like ancestors but via xlinks""" + if not types or isinstance(self, types): + yield self + my_id = self.get('id') + if my_id is not None: + elems = list(self.root.getElementsByHref(my_id)) \ + + list(self.root.getElementsByStyleUrl(my_id)) + for elem in elems: + if hasattr(elem, 'backlinks'): + for child in elem.backlinks(*types): + yield child + + def xpath(self, pattern, namespaces=NSS): # pylint: disable=dangerous-default-value + """Wrap xpath call and add svg namespaces""" + return super(BaseElement, self).xpath(pattern, namespaces=namespaces) + + def findall(self, pattern, namespaces=NSS): # pylint: disable=dangerous-default-value + """Wrap findall call and add svg namespaces""" + return super(BaseElement, self).findall(pattern, namespaces=namespaces) + + def findone(self, xpath): + """Gets a single element from the given xpath or returns None""" + el_list = self.xpath(xpath) + return el_list[0] if el_list else None + + def delete(self): + """Delete this node from it's parent node""" + if self.getparent() is not None: + self.getparent().remove(self) + + def remove_all(self, *types): + """Remove all children or child types""" + for child in self: + if not types or isinstance(child, types): + self.remove(child) + + def replace_with(self, elem): + """Replace this element with the given element""" + self.addnext(elem) + if not elem.get('id') and self.get('id'): + elem.set('id', self.get('id')) + if not elem.label and self.label: + elem.label = self.label + self.delete() + return elem + + def copy(self): + """Make a copy of the element and return it""" + elem = deepcopy(self) + elem.set('id', None) + return elem + + def duplicate(self): + """Like copy(), but the copy stays in the tree and sets a random id""" + elem = self.copy() + self.addnext(elem) + elem.set_random_id() + return elem + + def __str__(self): + # We would do more here, but lxml is VERY unpleseant when it comes to + # namespaces, basically over printing details and providing no + # supression mechanisms to turn off xml's over engineering. + return str(self.tag).split('}')[-1] + + @property + def href(self): + """Returns the referred-to element if available""" + ref = self.get('xlink:href') + if not ref: + return None + return self.root.getElementById(ref.strip('#')) + + @href.setter + def href(self, elem): + """Set the href object""" + if isinstance(elem, BaseElement): + elem = elem.get_id() + self.set('xlink:href', '#' + elem) + + def fallback_style(self, move=False): + """Get styles falling back to element attributes""" + return AttrFallbackStyle(self, move=move) + + @property + def label(self): + """Returns the inkscape label""" + return self.get('inkscape:label', None) + + label = label.setter(lambda self, value: self.set('inkscape:label', str(value))) # type: ignore + + +class ShapeElement(BaseElement): + """Elements which have a visible representation on the canvas""" + @property + def path(self): + """Gets the outline or path of the element, this may be a simple bounding box""" + return Path(self.get_path()) + + @path.setter + def path(self, path): + self.set_path(path) + + @property + def clip(self): + """Gets the clip path element (if any)""" + ref = self.get('clip-path') + if not ref: + return None + return self.root.getElementById(ref) + + @clip.setter + def clip(self, elem): + self.set('clip-path', 'url(#' + elem.get_id() + ')') + + def get_path(self): + """Generate a path for this object which can inform the bounding box""" + raise NotImplementedError("Path should be provided by svg elem {}.".format(self.typename)) + + def set_path(self, path): + """Set the path for this object (if possible)""" + raise AttributeError( + "Path can not be set on this element: {} <- {}.".format(self.typename, path)) + + def to_path_element(self): + """Replace this element with a path element""" + from ._polygons import PathElement + elem = PathElement() + elem.path = self.path + elem.style = self.effective_style() + elem.transform = self.transform + return elem + + def composed_transform(self, other=None): + """Calculate every transform down to the other element + if none specified the transform is to the root document element""" + parent = self.getparent() + if parent is not None and isinstance(parent, ShapeElement): + return parent.composed_transform() * self.transform + return self.transform + + def composed_style(self): + """Calculate the final styles applied to this element""" + parent = self.getparent() + if parent is not None and isinstance(parent, ShapeElement): + return parent.composed_style() + self.style + return self.style + + def cascaded_style(self): + """Add all cascaded styles, do not write to this Style object""" + ret = Style() + for style in self.root.stylesheets.lookup(self.get('id')): + ret += style + return ret + self.style + + def effective_style(self): + """Without parent styles, what is the effective style is""" + return self.style + + def bounding_box(self, transform=None): + # type: (Optional[Transform]) -> Optional[BoundingBox] + """BoundingBox of the shape (adjusted for its clip path if applicable)""" + shape_box = self.shape_box(transform) + clip = self.clip + if clip is None or shape_box is None: + return shape_box + return shape_box & clip.bounding_box(Transform(transform) * self.transform) + + def shape_box(self, transform=None): + # type: (Optional[Transform]) -> Optional[BoundingBox] + """BoundingBox of the unclipped shape""" + path = self.path.to_absolute() + if transform is True: + path = path.transform(self.composed_transform()) + else: + path = path.transform(self.transform) + if transform: # apply extra transformation + path = path.transform(transform) + return path.bounding_box() + + def is_visible(self): + """Returns false if the css says this object is invisible""" + if self.style.get('display', '') == 'none': + return False + if not float(self.style.get('opacity', 1.0)): + return False + return True diff --git a/share/extensions/inkex/elements/_filters.py b/share/extensions/inkex/elements/_filters.py new file mode 100644 index 0000000..ff6883f --- /dev/null +++ b/share/extensions/inkex/elements/_filters.py @@ -0,0 +1,276 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Sergei Izmailov +# Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=arguments-differ +""" +Element interface for patterns, filters, gradients and path effects. +""" + +from lxml import etree +from copy import deepcopy + +from ..utils import addNS +from ..transforms import Transform +from ..tween import interpcoord, interp +from ..units import convert_unit + +from ..styles import Style +from ._base import BaseElement + + +try: + from typing import overload, Iterable, List, Tuple, Union, Optional # pylint: disable=unused-import +except ImportError: + overload = lambda x: x + + +class Filter(BaseElement): + """A filter (usually in defs)""" + tag_name = 'filter' + + def add_primitive(self, fe_type, **args): + """Create a filter primitive with the given arguments""" + elem = etree.SubElement(self, addNS(fe_type, 'svg')) + elem.update(**args) + return elem + + class Primitive(BaseElement): + pass + + class Blend(Primitive): + tag_name = 'feBlend' + + class ColorMatrix(Primitive): + tag_name = 'feColorMatrix' + + class ComponentTransfer(Primitive): + tag_name = 'feComponentTransfer' + + class Composite(Primitive): + tag_name = 'feComposite' + + class ConvolveMatrix(Primitive): + tag_name = 'feConvolveMatrix' + + class DiffuseLighting(Primitive): + tag_name = 'feDiffuseLighting' + + class DisplacementMap(Primitive): + tag_name = 'feDisplacementMap' + + class Flood(Primitive): + tag_name = 'feFlood' + + class GaussianBlur(Primitive): + tag_name = 'feGaussianBlur' + + class Image(Primitive): + tag_name = 'feImage' + + class Merge(Primitive): + tag_name = 'feMerge' + + class Morphology(Primitive): + tag_name = 'feMorphology' + + class Offset(Primitive): + tag_name = 'feOffset' + + class SpecularLighting(Primitive): + tag_name = 'feSpecularLighting' + + class Tile(Primitive): + tag_name = 'feTile' + + class Turbulence(Primitive): + tag_name = 'feTurbulence' + + +class Stop(BaseElement): + tag_name = 'stop' + + @property + def offset(self): + # type: () -> float + return self.get('offset') + + @offset.setter + def offset(self, number): + self.set('offset', number) + + def interpolate(self, other, fraction): + newstop = Stop() + newstop.style = self.style.interpolate(other.style, fraction) + newstop.offset = interpcoord(float(self.offset), float(other.offset), fraction) + return newstop + + +class Pattern(BaseElement): + """Pattern element which is used in the def to control repeating fills""" + tag_name = 'pattern' + WRAPPED_ATTRS = BaseElement.WRAPPED_ATTRS + (('patternTransform', Transform),) + + +class Gradient(BaseElement): + """A gradient instruction usually in the defs""" + WRAPPED_ATTRS = BaseElement.WRAPPED_ATTRS + (('gradientTransform', Transform),) + + orientation_attributes = () # type: Tuple[str, ...] + + @property + def stops(self): + """Return an ordered list of own or linked stop nodes""" + gradcolor = self.href if isinstance(self.href, LinearGradient) else self + return sorted([child for child in gradcolor if isinstance(child, Stop)] + , key=lambda x: float(x.offset)) + + @property + def stop_offsets(self): + # type: () -> List[float] + """Return a list of own or linked stop offsets""" + return [child.offset for child in self.stops] + + @property + def stop_styles(self): # type: () -> List[Style] + """Return a list of own or linked offset styles""" + return [child.style for child in self.stops] + + def remove_orientation(self): + """Remove all orientation attributes from this element""" + for attr in self.orientation_attributes: + self.pop(attr) + + def interpolate(self, other, fraction): # type: (LinearGradient, float) -> LinearGradient + """Interpolate with another gradient.""" + if self.tag_name != other.tag_name: + return self + newgrad = self.copy() + + # interpolate transforms + newtransform = self.gradientTransform.interpolate(other.gradientTransform, fraction) + newgrad.gradientTransform = newtransform + + # interpolate orientation + for attr in self.orientation_attributes: + newattr = interpcoord(convert_unit(self.get(attr), 'px'), convert_unit(other.get(attr), 'px'), fraction) + newgrad.set(attr, newattr) + + # interpolate stops + if self.href is not None and self.href is other.href: + # both gradients link to the same stops + pass + else: + # gradients might have different stops + newoffsets = sorted(self.stop_offsets + other.stop_offsets[1:-1]) + func = lambda x,y,f: x.interpolate(y, f) + sstops = interp(self.stop_offsets, list(self.stops), newoffsets, func) + ostops = interp(other.stop_offsets, list(other.stops), newoffsets, func) + newstops = [s1.interpolate(s2, fraction) for s1, s2 in zip(sstops, ostops)] + newgrad.remove_all(Stop) + newgrad.add(*newstops) + return newgrad + + def stops_and_orientation(self): + """Return a copy of all the stops in this gradient""" + stops = self.copy() + stops.remove_orientation() + orientation = self.copy() + orientation.remove_all(Stop) + return stops, orientation + + +class LinearGradient(Gradient): + tag_name = 'linearGradient' + orientation_attributes = ('x1', 'y1', 'x2', 'y2') + + def apply_transform(self): # type: () -> None + """Apply transform to orientation points and set it to identity.""" + trans = self.pop('gradientTransform') + p1 = (convert_unit(self.get('x1'), 'px'), convert_unit(self.get('y1'), 'px')) + p2 = (convert_unit(self.get('x2'), 'px'), convert_unit(self.get('y2'), 'px')) + p1t = trans.apply_to_point(p1) + p2t = trans.apply_to_point(p2) + self.update(x1=p1t[0], y1=p1t[1], x2=p2t[0], y2=p2t[1]) + + +class RadialGradient(Gradient): + tag_name = 'radialGradient' + orientation_attributes = ('cx', 'cy', 'fx', 'fy', 'r') + + def apply_transform(self): # type: () -> None + """Apply transform to orientation points and set it to identity.""" + trans = self.pop('gradientTransform') + p1 = (convert_unit(self.get('cx'), 'px'), convert_unit(self.get('cy'), 'px')) + p2 = (convert_unit(self.get('fx'), 'px'), convert_unit(self.get('fy'), 'px')) + p1t = trans.apply_to_point(p1) + p2t = trans.apply_to_point(p2) + self.update(cx=p1t[0], cy=p1t[1], fx=p2t[0], fy=p2t[1]) + +class PathEffect(BaseElement): + """Inkscape LPE element""" + tag_name = 'inkscape:path-effect' + + +class MeshGradient(Gradient): + """Usable MeshGradient XML base class""" + tag_name = 'meshgradient' + + @classmethod + def new_mesh(cls, pos=None, rows=1, cols=1, autocollect=True): + """Return skeleton of 1x1 meshgradient definition.""" + # initial point + if pos is None or len(pos) != 2: + pos = [0.0, 0.0] + # create nested elements for rows x cols mesh + meshgradient = cls() + for _ in range(rows): + meshrow = meshgradient.add(MeshRow()) + for _ in range(cols): + meshrow.append(MeshPatch()) + # set meshgradient attributes + meshgradient.set('gradientUnits', 'userSpaceOnUse') + meshgradient.set('x', pos[0]) + meshgradient.set('y', pos[1]) + if autocollect: + meshgradient.set('inkscape:collect', 'always') + return meshgradient + + +class MeshRow(BaseElement): + """Each row of a mesh gradient""" + tag_name = 'meshrow' + +class MeshPatch(BaseElement): + """Each column or 'patch' in a mesh gradient""" + tag_name = 'meshpatch' + + def stops(self, edges, colors): + """Add or edit meshpatch stops with path and stop-color.""" + # iterate stops based on number of edges (path data) + for i, edge in enumerate(edges): + if i < len(self): + stop = self[i] + else: + stop = self.add(Stop()) + + # set edge path data + stop.set('path', str(edge)) + # set stop color + stop.style['stop-color'] = str(colors[i % 2]) diff --git a/share/extensions/inkex/elements/_groups.py b/share/extensions/inkex/elements/_groups.py new file mode 100644 index 0000000..52bd073 --- /dev/null +++ b/share/extensions/inkex/elements/_groups.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Sergei Izmailov +# Ryan Jarvis +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=arguments-differ +""" +Interface for all group based elements such as Groups, Use, Markers etc. +""" + +from lxml import etree # pylint: disable=unused-import + +from ..paths import Path +from ..utils import addNS +from ..transforms import Transform + +from ._base import ShapeElement + +try: + from typing import Optional # pylint: disable=unused-import +except ImportError: + pass + +class GroupBase(ShapeElement): + """Base Group element""" + def get_path(self): + ret = Path() + for child in self: + if isinstance(child, ShapeElement): + ret += child.path.transform(child.transform) + return ret + + def shape_box(self, transform=None): + bbox = None + effective_transform = Transform(transform) * self.transform + for child in self: + if isinstance(child, ShapeElement): + child_bbox = child.bounding_box(transform=effective_transform) + if child_bbox is not None: + bbox += child_bbox + return bbox + + +class Group(GroupBase): + """Any group element (layer or regular group)""" + tag_name = 'g' + + @classmethod + def new(cls, label, *children, **attrs): + attrs['inkscape:label'] = label + return super(Group, cls).new(*children, **attrs) + + + def effective_style(self): + """A blend of each child's style mixed together (last child wins)""" + style = self.style + for child in self: + style.update(child.effective_style()) + return style + + @property + def groupmode(self): + """Return the type of group this is""" + return self.get('inkscape:groupmode', 'group') + + +class Layer(Group): + """Inkscape extension of svg:g""" + + def _init(self): + self.set('inkscape:groupmode', 'layer') + + @classmethod + def _is_class_element(cls, el): + # type: (etree.Element) -> bool + return el.attrib.get(addNS('inkscape:groupmode'), None) == "layer" + + +class Anchor(GroupBase): + """An anchor or link tag""" + tag_name = 'a' + + @classmethod + def new(cls, href, *children, **attrs): + attrs['xlink:href'] = href + return super(Anchor, cls).new(*children, **attrs) + + +class ClipPath(GroupBase): + """A path used to clip objects""" + tag_name = 'clipPath' + + +class Marker(GroupBase): + """The element defines the graphic that is to be used for drawing arrowheads + or polymarkers on a given , , or element.""" + tag_name = 'marker' diff --git a/share/extensions/inkex/elements/_image.py b/share/extensions/inkex/elements/_image.py new file mode 100644 index 0000000..efd00d3 --- /dev/null +++ b/share/extensions/inkex/elements/_image.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 - Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Image element interface. +""" + +from ._polygons import RectangleBase + +class Image(RectangleBase): + """Provide a useful extension for image elements""" + tag_name = 'image' diff --git a/share/extensions/inkex/elements/_meta.py b/share/extensions/inkex/elements/_meta.py new file mode 100644 index 0000000..f8107ad --- /dev/null +++ b/share/extensions/inkex/elements/_meta.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Maren Hachmann +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=arguments-differ +""" +Provide extra utility to each svg element type specific to its type. + +This is useful for having a common interface for each element which can +give path, transform, and property access easily. +""" + +import math + +from lxml import etree + +from ..styles import StyleSheet +from ..transforms import Vector2d + +from ._base import BaseElement + +class Defs(BaseElement): + """A header defs element, one per document""" + tag_name = 'defs' + +class StyleElement(BaseElement): + """A CSS style element containing multiple style definitions""" + tag_name = 'style' + + def set_text(self, content): + """Sets the style content text as a CDATA section""" + self.text = etree.CDATA(str(content)) + + def stylesheet(self): + """Return the StyleSheet() object for the style tag""" + return StyleSheet(self.text, callback=self.set_text) + +class Script(BaseElement): + """A javascript tag in SVG""" + tag_name = 'script' + + def set_text(self, content): + """Sets the style content text as a CDATA section""" + self.text = etree.CDATA(str(content)) + +class Desc(BaseElement): + """Description element""" + tag_name = 'desc' + +class Title(BaseElement): + """Title element""" + tag_name = 'title' + +class NamedView(BaseElement): + """The NamedView element is Inkscape specific metadata about the file""" + tag_name = 'sodipodi:namedview' + + current_layer = property(lambda self: self.get('inkscape:current-layer')) + + @property + def center(self): + """Returns view_center in terms of document units""" + return Vector2d(self.root.unittouu(self.get('inkscape:cx') or 0), + self.root.unittouu(self.get('inkscape:cy') or 0)) + + def get_guides(self): + """Returns a list of guides""" + return self.findall('sodipodi:guide') + + def new_guide(self, position, orient=True, name=None): + """Creates a new guide in this namedview""" + if orient is True: + elem = Guide().move_to(0, position, (0, 1)) + elif orient is False: + elem = Guide().move_to(position, 0, (1, 0)) + if name: + elem.set('inkscape:label', str(name)) + return self.add(elem) + + +class Guide(BaseElement): + """An inkscape guide""" + tag_name = 'sodipodi:guide' + + is_horizontal = property(lambda self: self.get('orientation').startswith('0,') and not + self.get('orientation') == '0,0') + is_vertical = property(lambda self: self.get('orientation').endswith(',0')) + point = property(lambda self: Vector2d(self.get('position'))) + + @classmethod + def new(cls, pos_x, pos_y, angle, **attrs): + guide = super(Guide, cls).new(**attrs) + guide.move_to(pos_x, pos_y, angle=angle) + return guide + + def move_to(self, pos_x, pos_y, angle=None): + """ + Move this guide to the given x,y position, + + Angle may be a float or integer, which will change the orientation. Alternately, + it may be a pair of numbers (tuple) which will set the orientation directly. + """ + self.set('position', "{:g},{:g}".format(float(pos_x), float(pos_y))) + if isinstance(angle, str): + if ',' not in angle: + angle = float(angle) + + if isinstance(angle, (float, int)): + # Generate orientation from angle + angle = (math.sin(math.radians(angle)), -math.cos(math.radians(angle))) + + if isinstance(angle, (tuple, list)) and len(angle) == 2: + angle = "{:g},{:g}".format(*angle) + + self.set('orientation', angle) + return self + +class Metadata(BaseElement): + """Inkscape Metadata element""" + tag_name = 'metadata' + +class ForeignObject(BaseElement): + """SVG foreignObject element""" + tag_name = 'foreignObject' + +class Switch(BaseElement): + """A switch element""" + tag_name = 'switch' + +class Grid(BaseElement): + """A namedview grid child""" + tag_name = 'inkscape:grid' diff --git a/share/extensions/inkex/elements/_polygons.py b/share/extensions/inkex/elements/_polygons.py new file mode 100644 index 0000000..0bcbc38 --- /dev/null +++ b/share/extensions/inkex/elements/_polygons.py @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Sergei Izmailov +# Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=arguments-differ +""" +Interface for all shapes/polygons such as lines, paths, rectangles, circles etc. +""" + +from ..paths import Path +from ..transforms import Transform, ImmutableVector2d, Vector2d +from ..utils import addNS +from ..units import convert_unit + +from ._base import ShapeElement + +class PathElementBase(ShapeElement): + """Base element for path based shapes""" + get_path = lambda self: self.get('d') + + @classmethod + def new(cls, path, **attrs): + return super(PathElementBase, cls).new(d=Path(path), **attrs) + + def set_path(self, path): + """Set the given data as a path as the 'd' attribute""" + self.set('d', str(Path(path))) + + def apply_transform(self): + """Apply the internal transformation to this node and delete""" + if 'transform' in self.attrib: + self.path = self.path.transform(self.transform) + self.set('transform', Transform()) + + @property + def original_path(self): + """Returns the original path if this is a LPE, or the path if not""" + return Path(self.get('inkscape:original-d', self.path)) + + @original_path.setter + def original_path(self, path): + if addNS('inkscape:original-d') in self.attrib: + self.set('inkscape:original-d', str(Path(path))) + else: + self.path = path + + +class PathElement(PathElementBase): + """Provide a useful extension for path elements""" + tag_name = 'path' + + @classmethod + def arc(cls, center, rx, ry=None, **kw): # pylint: disable=invalid-name + """Generate a sodipodi arc (special type)""" + others = [(name, kw.pop(name, None)) for name in ('start', 'end', 'open')] + elem = cls(**kw) + elem.set('sodipodi:cx', center[0]) + elem.set('sodipodi:cy', center[1]) + elem.set('sodipodi:rx', rx) + elem.set('sodipodi:ry', ry or rx) + elem.set('sodipodi:type', 'arc') + for name, value in others: + if value is not None: + elem.set('sodipodi:'+name, str(value).lower()) + return elem + + @classmethod + def star(cls, center, radi, sides, rounded=None): + """Generate a sodipodi start (special type)""" + elem = cls() + elem.set('sodipodi:cx', center[0]) + elem.set('sodipodi:cy', center[1]) + elem.set('sodipodi:r1', radi[0]) + elem.set('sodipodi:r2', radi[1]) + elem.set('sodipodi:arg1', 0.85) + elem.set('sodipodi:arg2', 1.3) + elem.set('sodipodi:sides', sides) + elem.set('inkscape:rounded', rounded) + elem.set('sodipodi:type', 'star') + return elem + + +class Polyline(ShapeElement): + """Like a path, but made up of straight line segments only""" + tag_name = 'polyline' + + def get_path(self): + return Path('M' + self.get('points')) + + def set_path(self, path): + points = ['{:g},{:g}'.format(x, y) for x, y in Path(path).end_points] + self.set('points', ' '.join(points)) + + +class Polygon(ShapeElement): + """A closed polyline""" + tag_name = 'polygon' + get_path = lambda self: 'M' + self.get('points') + ' Z' + + +class Line(ShapeElement): + """A line segment connecting two points""" + tag_name = 'line' + get_path = lambda self: 'M{0[x1]},{0[y1]} L{0[x2]},{0[y2]} Z'.format(self.attrib) + + @classmethod + def new(cls, start, end, **attrs): + start = Vector2d(start) + end = Vector2d(end) + return super(Line, cls).new(x1=start.x, y1=start.y, + x2=end.x, y2=end.y, **attrs) + + +class RectangleBase(ShapeElement): + """Provide a useful extension for rectangle elements""" + left = property(lambda self: convert_unit(self.get('x', '0'), 'px')) + top = property(lambda self: convert_unit(self.get('y', '0'), 'px')) + right = property(lambda self: self.left + self.width) + bottom = property(lambda self: self.top + self.height) + width = property(lambda self: convert_unit(self.get('width', '0'), 'px')) + height = property(lambda self: convert_unit(self.get('height', '0'), 'px')) + rx = property(lambda self: convert_unit(self.get('rx', self.get('ry', 0.0)), 'px')) + ry = property(lambda self: convert_unit(self.get('ry', self.get('rx', 0.0)), 'px')) # pylint: disable=invalid-name + + def get_path(self): + """Calculate the path as the box around the rect""" + if self.rx: + rx, ry = self.rx, self.ry # pylint: disable=invalid-name + return 'M {1},{0.top}'\ + 'L {2},{0.top} A {0.rx},{0.ry} 0 0 1 {0.right},{3}'\ + 'L {0.right},{4} A {0.rx},{0.ry} 0 0 1 {2},{0.bottom}'\ + 'L {1},{0.bottom} A {0.rx},{0.ry} 0 0 1 {0.left},{4}'\ + 'L {0.left},{3} A {0.rx},{0.ry} 0 0 1 {1},{0.top} z'\ + .format(self, self.left + rx, self.right - rx, self.top + ry, self.bottom - ry) + + return 'M {0.left},{0.top} h{0.width}v{0.height}h{1} z'.format(self, -self.width) + + +class Rectangle(RectangleBase): + """Provide a useful extension for rectangle elements""" + tag_name = 'rect' + + @classmethod + def new(cls, left, top, width, height, **attrs): + return super(Rectangle, cls).new(x=left, y=top, width=width, height=height, **attrs) + + +class EllipseBase(ShapeElement): + """Absorbs common part of Circle and Ellipse classes""" + + def get_path(self): + """Calculate the arc path of this circle""" + rx, ry = self._rxry() + cx, y = self.center.x, self.center.y - ry + return ('M {cx},{y} ' + 'a {rx},{ry} 0 1 0 {rx}, {ry} ' + 'a {rx},{ry} 0 0 0 -{rx}, -{ry} z' + ).format(cx=cx, y=y, rx=rx, ry=ry) + + @property + def center(self): + return ImmutableVector2d(convert_unit(self.get('cx', '0'), 'px'), convert_unit(self.get('cy', '0'), 'px')) + + @center.setter + def center(self, value): + value = Vector2d(value) + self.set("cx", value.x) + self.set("cy", value.y) + + def _rxry(self): + # type: () -> Vector2d + """Helper function """ + raise NotImplementedError() + + @classmethod + def new(cls, center, radius, **attrs): + circle = super(EllipseBase, cls).new(**attrs) + circle.center = center + circle.radius = radius + return circle + + +class Circle(EllipseBase): + """Provide a useful extension for circle elements""" + tag_name = 'circle' + + @property + def radius(self): + return convert_unit(self.get('r', '0'), 'px') + + @radius.setter + def radius(self, value): + self.set("r", value) + + def _rxry(self): + r = self.radius + return Vector2d(r, r) + + +class Ellipse(EllipseBase): + """Provide a similar extension to the Circle interface""" + tag_name = 'ellipse' + + @property + def radius(self): + return ImmutableVector2d(convert_unit(self.get('rx', '0'), 'px'), convert_unit(self.get('ry', '0'), 'px')) + + @radius.setter + def radius(self, value): + value = Vector2d(value) + self.set("rx", str(value.x)) + self.set("ry", str(value.y)) + + def _rxry(self): + return self.radius diff --git a/share/extensions/inkex/elements/_selected.py b/share/extensions/inkex/elements/_selected.py new file mode 100644 index 0000000..c09c526 --- /dev/null +++ b/share/extensions/inkex/elements/_selected.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc.,Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +When elements are selected, these structures provide an advanced API. +""" + +from collections import OrderedDict + +class ElementList(OrderedDict): + """ + A list of elements, selected by id, iterator or xpath + + This may look like a dictionary, but it is really a list of elements. + The default iterator is the element objects themselves (not keys) and it is + possible to key elements by their numerical index. + + It is also possible to look up items by their id and the element object itself. + """ + def __init__(self, svg, _iter=None): + self.svg = svg + self.ids = OrderedDict() + super(ElementList, self).__init__() + if _iter: + self.set(*list(_iter)) + + def __getitem__(self, key): + return super(ElementList, self).__getitem__(self._to_key(key)) + + def __contains__(self, key): + return super(ElementList, self).__contains__(self._to_key(key)) + + def __setitem__(self, orig_key, elem): + from ._base import BaseElement + if orig_key != elem and orig_key != elem.get('id'): + raise ValueError("Refusing to set bad key in ElementList {}".format(orig_key)) + if isinstance(elem, str): + key = elem + elem = self.svg.getElementById(elem) + if elem is None: + return + if isinstance(elem, BaseElement): + # Selection is a list of elements to select + key = elem.xml_path + element_id = elem.get('id') + if element_id is not None: + self.ids[element_id] = key + super(ElementList, self).__setitem__(key, elem) + else: + kind = type(elem).__name__ + raise ValueError("Unknown element type: {}".format(kind)) + + def _to_key(self, key, default=None): + """Takes a key (id, element, etc) and returns an xml_path key""" + from ._base import BaseElement + if self and key is None: + key = default + if isinstance(key, int): + return list(self.keys())[key] + elif isinstance(key, BaseElement): + return key.xml_path + elif isinstance(key, str) and key[0] != '/': + return self.ids.get(key, key) + return key + + def clear(self): + """Also clear ids""" + self.ids.clear() + super(ElementList, self).clear() + + def set(self, *ids): + """ + Sets the currently selected elements to these ids, any existing + selection is cleared. + + Arguments a list of element ids, element objects or + a single xpath expression starting with "//". + + All element objects must have an id to be correctly set. + + >>> selection.set("rect123", "path456", "text789") + >>> selection.set(elem1, elem2, elem3) + >>> selection.set("//rect") + """ + self.clear() + self.add(*ids) + + def pop(self, key=None): + """Remove the key item or remove the last item selected""" + item = super(ElementList, self).pop(self._to_key(key, default=-1)) + self.ids.pop(item.get('id')) + return item + + def add(self, *ids): + """Like set() but does not clear first""" + # Allow selecting of xpath elements directly + if len(ids) == 1 and isinstance(ids[0], str) and ids[0].startswith('//'): + ids = self.svg.xpath(ids[0]) + + for elem in ids: + self[elem] = elem # This doesn't matter + + def paint_order(self): + """Get the selected elements, but ordered by their appearance in the document""" + new_list = ElementList(self.svg) + new_list.set(*[elem for _, elem in sorted(self.items(), key=lambda x: x[0])]) + return new_list + + def filter(self, *types): + """Filter selected elements of the given type, returns a new SelectedElements object""" + return ElementList(self.svg, [e for e in self.values() if not types or isinstance(e, types)]) + + def get(self, *types): + """Like filter, but will enter each element searching for any child of the given types""" + def _recurse(elem): + if not types or isinstance(elem, types): + yield elem + for child in elem: + for item in _recurse(child): + yield item + return ElementList(self.svg, [r for e in self.values() for r in _recurse(e)]) + + def id_dict(self): + """For compatibility, return regular dictionary of id -> element pairs""" + return OrderedDict([(eid, self[xid]) for eid, xid in self.ids.items()]) + + def bounding_box(self): + """ + Gets a :class:`inkex.transforms.BoundingBox` object for the selected items. + + Text objects have a bounding box without width or height that only + reflects the coordinate of their anchor. If a text object is a part of + the selection's boundary, the bounding box may be inaccurate. + + When no object is selected or when the object's location cannot be + determined (e.g. empty group or layer), all coordinates will be None. + """ + return sum([elem.bounding_box() for elem in self.values()], None) + + def first(self): + """Returns the first item in the selected list""" + for elem in self.values(): + return elem + return None diff --git a/share/extensions/inkex/elements/_svg.py b/share/extensions/inkex/elements/_svg.py new file mode 100644 index 0000000..3e1af09 --- /dev/null +++ b/share/extensions/inkex/elements/_svg.py @@ -0,0 +1,211 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Thomas Holder +# Sergei Izmailov +# Windell Oskay +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=attribute-defined-outside-init +# +""" +Provide a way to load lxml attributes with an svg API on top. +""" + +import random +from lxml import etree + +from ..deprecated import DeprecatedSvgMixin +from ..units import discover_unit, convert_unit, render_unit +from ._selected import ElementList +from ..transforms import BoundingBox +from ..styles import StyleSheets + +from ._base import BaseElement +from ._meta import NamedView, Defs, StyleElement, Metadata + +if False: # pylint: disable=using-constant-test + import typing # pylint: disable=unused-import + + +class SvgDocumentElement(DeprecatedSvgMixin, BaseElement): + """Provide access to the document level svg functionality""" + tag_name = 'svg' + + def _init(self): + self.current_layer = None + self.view_center = (0.0, 0.0) + self.selection = ElementList(self) + self.ids = {} + + def tostring(self): + """Convert document to string""" + return etree.tostring(etree.ElementTree(self)) + + def get_ids(self): + """Returns a set of unique document ids""" + if not self.ids: + self.ids = set(self.xpath('//@id')) + return self.ids + + def get_unique_id(self, prefix, size=4): + """Generate a new id from an existing old_id""" + ids = self.get_ids() + new_id = None + _from = 10 ** size - 1 + _to = 10 ** size + while new_id is None or new_id in ids: + # Do not use randint because py2/3 incompatibility + new_id = prefix + str(int(random.random() * _from - _to) + _to) + self.ids.add(new_id) + return new_id + + def get_page_bbox(self): + """Gets the page dimensions as a bbox""" + return BoundingBox((0, float(self.width)), (0, float(self.height))) + + def get_current_layer(self): + """Returns the currently selected layer""" + layer = self.getElementById(self.namedview.current_layer, 'svg:g') + if layer is None: + return self + return layer + + def getElement(self, xpath): # pylint: disable=invalid-name + """Gets a single element from the given xpath or returns None""" + return self.findone(xpath) + + def getElementById(self, eid, elm='*'): # pylint: disable=invalid-name + """Get an element in this svg document by it's ID attribute""" + if eid is not None: + eid = eid.strip()[4:-1] if eid.startswith('url(') else eid + eid = eid.lstrip('#') + return self.getElement('//{elm}[@id="{eid}"]'.format(elm=elm, eid=eid)) + + def getElementByName(self, name, elm='*'): # pylint: disable=invalid-name + """Get an element by it's inkscape:label (aka name)""" + return self.getElement('//{elm}[@inkscape:label="{name}"]'.format(elm=elm, name=name)) + + def getElementsByClass(self, class_name): # pylint: disable=invalid-name + """Get elements by it's class name""" + from inkex.styles import ConditionalRule + return self.xpath(ConditionalRule(".{}".format(class_name)).to_xpath()) + + def getElementsByHref(self, eid): # pylint: disable=invalid-name + """Get elements by their href xlink attribute""" + return self.xpath('//*[@xlink:href="#{}"]'.format(eid)) + + def getElementsByStyleUrl(self, eid, style=None): # pylint: disable=invalid-name + """Get elements by a style attribute url""" + url = "url(#{})".format(eid) + if style is not None: + url = style + ":" + url + return self.xpath('//*[contains(@style,"{}")]'.format(url)) + + @property + def name(self): + """Returns the Document Name""" + return self.get('sodipodi:docname', '') + + @property + def namedview(self): + """Return the sp namedview meta information element""" + return self.get_or_create('//sodipodi:namedview', NamedView, True) + + @property + def metadata(self): + """Return the svg metadata meta element container""" + return self.get_or_create('//svg:metadata', Metadata, True) + + @property + def defs(self): + """Return the svg defs meta element container""" + return self.get_or_create('//svg:defs', Defs, True) + + def get_viewbox(self): + """Parse and return the document's viewBox attribute""" + try: + ret = [float(unit) for unit in self.get('viewBox', '0').split()] + except ValueError: + ret = '' + if len(ret) != 4: + return [0, 0, 0, 0] + return ret + + @property + def width(self): # getDocumentWidth(self): + """Fault tolerance for lazily defined SVG""" + return self.unittouu(self.get('width')) or self.get_viewbox()[2] + + @property + def height(self): # getDocumentHeight(self): + """Returns a string corresponding to the height of the document, as + defined in the SVG file. If it is not defined, returns the height + as defined by the viewBox attribute. If viewBox is not defined, + returns the string '0'.""" + return self.unittouu(self.get('height')) or self.get_viewbox()[3] + + @property + def scale(self): + """Return the ratio between the page width and the viewBox width""" + try: + scale_x = float(self.width) / float(self.get_viewbox()[2]) + scale_y = float(self.height) / float(self.get_viewbox()[3]) + return max([scale_x, scale_y]) + except (ValueError, ZeroDivisionError): + return 1.0 + + @property + def unit(self): + """Returns the unit used for in the SVG document. + In the case the SVG document lacks an attribute that explicitly + defines what units are used for SVG coordinates, it tries to calculate + the unit from the SVG width and viewBox attributes. + Defaults to 'px' units.""" + viewbox = self.get_viewbox() + if viewbox and set(viewbox) != {0}: + return discover_unit(self.get('width'), viewbox[2], default='px') + return 'px' # Default is px + + def unittouu(self, value): + """Convert a unit value into the document's units""" + return convert_unit(value, self.unit) + + def uutounit(self, value, to_unit): + """Convert from the document's units to the given unit""" + return convert_unit(render_unit(value, self.unit), to_unit) + + def add_unit(self, value): + """Add document unit when no unit is specified in the string """ + return render_unit(value, self.unit) + + @property + def stylesheets(self): + """Get all the stylesheets, bound together to one, (for reading)""" + sheets = StyleSheets(self) + for node in self.xpath('//svg:style'): + sheets.append(node.stylesheet()) + return sheets + + @property + def stylesheet(self): + """Return the first stylesheet or create one if needed (for writing)""" + for sheet in self.stylesheets: + return sheet + + style_node = StyleElement() + self.defs.append(style_node) + return style_node.stylesheet() diff --git a/share/extensions/inkex/elements/_text.py b/share/extensions/inkex/elements/_text.py new file mode 100644 index 0000000..766344c --- /dev/null +++ b/share/extensions/inkex/elements/_text.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=arguments-differ +""" +Provide text based element classes interface. + +Because text is not rendered at all, no information about a text's path +size or actual location can be generated yet. +""" + +from ..paths import Path +from ..transforms import Transform, BoundingBox +from ..units import convert_unit + +from ._base import BaseElement, ShapeElement +from ._polygons import PathElementBase + +class FlowRegion(ShapeElement): + """SVG Flow Region (SVG 2.0)""" + tag_name = 'flowRegion' + + def get_path(self): + # This ignores flowRegionExcludes + return sum([child.path for child in self], Path()) + +class FlowRoot(ShapeElement): + """SVG Flow Root (SVG 2.0)""" + tag_name = 'flowRoot' + + @property + def region(self): + """Return the first flowRegion in this flowRoot""" + return self.findone('svg:flowRegion') + + def get_path(self): + region = self.region + return region.get_path() if region is not None else Path() + +class FlowPara(ShapeElement): + """SVG Flow Paragraph (SVG 2.0)""" + tag_name = 'flowPara' + + def get_path(self): + # XXX: These empty paths mean the bbox for text elements will be nothing. + return Path() + +class FlowDiv(ShapeElement): + """SVG Flow Div (SVG 2.0)""" + tag_name = 'flowDiv' + + def get_path(self): + # XXX: These empty paths mean the bbox for text elements will be nothing. + return Path() + +class FlowSpan(ShapeElement): + """SVG Flow Span (SVG 2.0)""" + tag_name = 'flowSpan' + + def get_path(self): + # XXX: These empty paths mean the bbox for text elements will be nothing. + return Path() + +class TextElement(ShapeElement): + """A Text element""" + tag_name = 'text' + x = property(lambda self: convert_unit(self.get('x', 0), 'px')) + y = property(lambda self: convert_unit(self.get('y', 0), 'px')) + + def get_path(self): + return Path() + + def tspans(self): + """Returns all children that are tspan elements""" + return self.findall('svg:tspan') + + def get_text(self, sep="\n"): + """Return the text content including tspans""" + nodes = [self] + list(self.tspans()) + return sep.join([elem.text for elem in nodes if elem.text is not None]) + + def shape_box(self, transform=None): + """ + Returns a horrible bounding box that just contains the coord points + of the text without width or height (which is impossible to calculate) + """ + effective_transform = Transform(transform) * self.transform + x, y = effective_transform.apply_to_point((self.x, self.y)) + bbox = BoundingBox(x, y) + for tspan in self.tspans(): + bbox += tspan.bounding_box(effective_transform) + return bbox + +class TextPath(ShapeElement): + """A textPath element""" + tag_name = 'textPath' + + def get_path(self): + return Path() + +class Tspan(ShapeElement): + """A tspan text element""" + tag_name = 'tspan' + x = property(lambda self: convert_unit(self.get('x', 0), 'px')) + y = property(lambda self: convert_unit(self.get('y', 0), 'px')) + + @classmethod + def superscript(cls, text): + """Adds a superscript tspan element""" + return cls(text, style="font-size:65%;baseline-shift:super") + + def get_path(self): + return Path() + + def shape_box(self, transform=None): + """ + Returns a horrible bounding box that just contains the coord points + of the text without width or height (which is impossible to calculate) + """ + effective_transform = Transform(transform) * self.transform + x1, y1 = effective_transform.apply_to_point((self.x, self.y)) + fontsize = convert_unit(self.style.get('font-size', '1em'), 'px') + x2 = self.x + 0 # XXX This is impossible to calculate! + y2 = self.y + float(fontsize) + x2, y2 = effective_transform.apply_to_point((x2, y2)) + return BoundingBox((x1, x2), (y1, y2)) + + +class SVGfont(BaseElement): + """An svg font element""" + tag_name = 'font' + +class FontFace(BaseElement): + """An svg font font-face element""" + tag_name = 'font-face' + +class Glyph(PathElementBase): + """An svg font glyph element""" + tag_name = 'glyph' + +class MissingGlyph(BaseElement): + """An svg font missing-glyph element""" + tag_name = 'missing-glyph' diff --git a/share/extensions/inkex/elements/_use.py b/share/extensions/inkex/elements/_use.py new file mode 100644 index 0000000..b5a38c5 --- /dev/null +++ b/share/extensions/inkex/elements/_use.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Martin Owens +# Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Interface for the Use and Symbol elements +""" + +from ..transforms import Transform + +from ._groups import Group, GroupBase +from ._base import ShapeElement + +class Symbol(GroupBase): + """SVG symbol element""" + tag_name = 'symbol' + +class Use(ShapeElement): + """A 'use' element that links to another in the document""" + tag_name = 'use' + + @classmethod + def new(cls, elem, x, y, **attrs): # pylint: disable=arguments-differ + ret = super(Use, cls).new(x=x, y=y, **attrs) + ret.href = elem + return ret + + def get_path(self): + """Returns the path of the cloned href plus any transformation""" + path = self.href.path + path.transform(self.href.transform) + return path + + def effective_style(self): + """Href's style plus this object's own styles""" + style = self.href.effective_style() + style.update(self.style) + return style + + def unlink(self): + """Unlink this clone, replacing it with a copy of the original""" + copy = self.href.copy() + if isinstance(copy, Symbol): + group = Group(**copy.attrib) + group.extend(copy) + copy = group + copy.transform *= self.transform + copy.style = self.style + copy.style + self.replace_with(copy) + copy.set_random_ids() + return copy + + def shape_box(self, transform=None): + effective_transform = Transform(transform) * self.transform + return self.href.bounding_box(effective_transform) diff --git a/share/extensions/inkex/extensions.py b/share/extensions/inkex/extensions.py new file mode 100644 index 0000000..10f30a2 --- /dev/null +++ b/share/extensions/inkex/extensions.py @@ -0,0 +1,344 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +A helper module for creating Inkscape effect extensions + +This provides the basic generic types of extensions which most writers should +use in their code. See below for the different types. +""" + +import os +import re +import sys +import types + +from .utils import errormsg, Boolean, CloningVat, PY3 +from .colors import Color, ColorIdError, ColorError +from .elements import load_svg, BaseElement, ShapeElement, Group, Layer, Grid, \ + TextElement, FlowPara, FlowDiv +from .base import InkscapeExtension, SvgThroughMixin, SvgInputMixin, SvgOutputMixin, TempDirMixin +from .transforms import Transform + +# All the names that get added to the inkex API itself. +__all__ = ('EffectExtension', 'GenerateExtension', 'InputExtension', 'OutputExtension', + 'CallExtension', 'TemplateExtension', 'ColorExtension', 'TextExtension') + +stdout = sys.stdout +if PY3: + unicode = str # pylint: disable=redefined-builtin,invalid-name + +class EffectExtension(SvgThroughMixin, InkscapeExtension): + """ + Takes the SVG from Inkscape, modifies the selection or the document + and returns an SVG to Inkscape. + """ + pass + +class OutputExtension(SvgInputMixin, InkscapeExtension): + """ + Takes the SVG from Inkscape and outputs it to something that's not an SVG. + + Used in functions for `Save As` + """ + def effect(self): + """Effect isn't needed for a lot of Output extensions""" + pass + + def save(self, stream): + """But save certainly is, we give a more exact message here""" + raise NotImplementedError("Output extensions require a save(stream) method!") + +class InputExtension(SvgOutputMixin, InkscapeExtension): + """ + Takes any type of file as input and outputs SVG which Inkscape can read. + + Used in functions for `Open` + """ + def effect(self): + """Effect isn't needed for a lot of Input extensions""" + pass + + def load(self, stream): + """But load certainly is, we give a more exact message here""" + raise NotImplementedError("Input extensions require a load(stream) method!") + +class CallExtension(TempDirMixin, InputExtension): + """Call an external program to get the output""" + input_ext = 'svg' + output_ext = 'svg' + + def load(self, stream): + pass # Not called (load_raw instead) + + def load_raw(self): + # Don't call InputExtension.load_raw + TempDirMixin.load_raw(self) + input_file = self.options.input_file + + if not isinstance(input_file, (unicode, str)): + data = input_file.read() + input_file = os.path.join(self.tempdir, 'input.' + self.input_ext) + with open(input_file, 'wb') as fhl: + fhl.write(data) + + output_file = os.path.join(self.tempdir, 'output.' + self.output_ext) + document = self.call(input_file, output_file) or output_file + if isinstance(document, (str, unicode)): + if not os.path.isfile(document): + raise IOError("Can't find generated document: {}".format(document)) + + if self.output_ext == 'svg': + with open(document, 'r') as fhl: + document = fhl.read() + if '<' in document: + document = load_svg(document) + else: + with open(document, 'rb') as fhl: + document = fhl.read() + + self.document = document + + def call(self, input_file, output_file): + """Call whatever programs are needed to get the desired result.""" + raise NotImplementedError("Call extensions require a call(in, out) method!") + +class GenerateExtension(EffectExtension): + """ + Does not need any SVG, but instead just outputs an SVG fragment which is + inserted into Inkscape, centered on the selection. + """ + container_label = '' + container_layer = False + + def generate(self): + """ + Return an SVG fragment to be inserted into the selected layer of the document + OR yield multiple elements which will be grouped into a container group + element which will be given an automatic label and transformation. + """ + raise NotImplementedError("Generate extensions must provide generate()") + + def container_transform(self): + """ + Generate the transformation for the container group, the default is + to return the center position of the svg document or view port. + """ + (pos_x, pos_y) = self.svg.namedview.center + if pos_x is None: + pos_x = 0 + if pos_y is None: + pos_y = 0 + return Transform(translate=(pos_x, pos_y)) + + def effect(self): + layer = self.svg.get_current_layer() + fragment = self.generate() + if isinstance(fragment, types.GeneratorType): + container = (Layer if self.container_layer else Group).new(self.container_label) + if self.container_layer: + self.svg.append(container) + else: + container.transform = self.container_transform() + layer.append(container) + for child in fragment: + if isinstance(child, BaseElement): + container.append(child) + elif isinstance(fragment, BaseElement): + layer.append(fragment) + else: + errormsg("Nothing was generated\n") + + +class TemplateExtension(EffectExtension): + """ + Provide a standard way of creating templates. + """ + size_rex = re.compile(r'([\d.]*)(\w\w)?x([\d.]*)(\w\w)?') + template_id = "SVGRoot" + + def __init__(self): + super(TemplateExtension, self).__init__() + # Arguments added on after add_arguments so it can be overloaded cleanly. + self.arg_parser.add_argument("--size", type=self.arg_size(), dest="size") + self.arg_parser.add_argument("--width", type=int, default=800) + self.arg_parser.add_argument("--height", type=int, default=600) + self.arg_parser.add_argument("--orientation", default=None) + self.arg_parser.add_argument("--unit", default="px") + self.arg_parser.add_argument("--grid", type=Boolean) + + def get_template(self): + """Can be over-ridden with custom svg loading here""" + return self.document + + def arg_size(self, unit='px'): + """Argument is a string of the form X[unit]xY[unit], default units apply when missing""" + def _inner(value): + try: + value = float(value) + return (value, unit, value, unit) + except ValueError: + pass + match = self.size_rex.match(str(value)) + if match is not None: + size = match.groups() + return (float(size[0]), size[1] or unit, float(size[2]), size[3] or unit) + return None + return _inner + + def get_size(self): + """Get the size of the new template (defaults to size options)""" + size = self.options.size + if self.options.size is None: + size = (self.options.width, self.options.unit, + self.options.height, self.options.unit) + if self.options.orientation == "horizontal" and size[0] < size[2] \ + or self.options.orientation == "vertical" and size[0] > size[2]: + size = size[2:4] + size[0:2] + return size + + def effect(self): + """Creates a template, do not over-ride""" + (width, width_unit, height, height_unit) = self.get_size() + width_px = int(self.svg.uutounit(width, 'px')) + height_px = int(self.svg.uutounit(height, 'px')) + + self.document = self.get_template() + self.svg = self.document.getroot() + self.svg.set("id", self.template_id) + self.svg.set("width", str(width) + width_unit) + self.svg.set("height", str(height) + height_unit) + self.svg.set("viewBox", "0 0 {} {}".format(width, height)) + self.set_namedview(width_px, height_px, width_unit) + + def set_namedview(self, width, height, unit): + """Setup the document namedview""" + self.svg.namedview.set('inkscape:document-units', unit) + self.svg.namedview.set('inkscape:zoom', '0.25') + self.svg.namedview.set('inkscape:cx', str(width / 2.0)) + self.svg.namedview.set('inkscape:cy', str(height / 2.0)) + if self.options.grid: + self.svg.namedview.set('showgrid', "true") + self.svg.namedview.add(Grid(type="xygrid")) + + +class ColorExtension(EffectExtension): + """ + A standard way to modify colours in an svg document. + """ + process_none = False # should we call modify_color for the "none" color. + select_all = (ShapeElement,) + + def effect(self): + # Limiting to shapes ignores Gradients (and other things) from the select_all + # this prevents defs from being processed twice. + self._renamed = {} + gradients = CloningVat(self.svg) + for elem in self.svg.selection.get(ShapeElement).values(): + self.process_element(elem, gradients) + gradients.process(self.process_elements, types=(ShapeElement,)) + + def process_elements(self, elem): + """Process multiple elements (gradients)""" + for child in elem.descendants().values(): + self.process_element(child) + + def process_element(self, elem, gradients=None): + """Process one of the selected elements""" + style = elem.fallback_style(move=False) + # Colours first + for name in elem.style.color_props: + value = style.get(name) + if value is not None: + try: + style[name] = self._modify_color(name, Color(value)) + except ColorIdError: + gradient = self.svg.getElementById(value) + gradients.track(gradient, elem, self._ref_cloned, style=style, name=name) + if gradient.href is not None: + gradients.track(gradient.href, elem, self._xlink_cloned, linker=gradient) + except ColorError: + pass # bad color value, don't touch. + # Then opacities (usually does nothing) + for name in elem.style.opacity_props: + value = style.get(name) + if value is not None: + style[name] = self.modify_opacity(name, value) + + def _ref_cloned(self, old_id, new_id, style, name): + self._renamed[old_id] = new_id + style[name] = "url(#{})".format(new_id) + + def _xlink_cloned(self, old_id, new_id, linker): + lid = linker.get('id') + linker = self.svg.getElementById(self._renamed.get(lid, lid)) + linker.set('xlink:href', '#' + new_id) + + def _modify_color(self, name, color): + """Pre-process color value to filter out bad colors""" + if color or self.process_none: + return self.modify_color(name, color) + return color + + def modify_color(self, name, color): + """Replace this method with your colour modifier method""" + raise NotImplementedError("Provide a modify_color method.") + + def modify_opacity(self, name, opacity): + """Optional opacity modification""" + return opacity + +class TextExtension(EffectExtension): + """ + A base effect for changing text in a document. + """ + newline = True + newpar = True + + def effect(self): + nodes = self.svg.selected.values() or {None: self.document.getroot()} + for elem in nodes.values(): + self.process_element(elem) + + def process_element(self, node): + """Reverse the node text""" + if node.get('sodipodi:role') == 'line': + self.newline = True + elif isinstance(node, (TextElement, FlowPara, FlowDiv)): + self.newline = True + self.newpar = True + + if node.text is not None: + node.text = self.process_chardata(node.text) + self.newline = False + self.newpar = False + + for child in node: + self.process_element(child) + + if node.tail is not None: + node.tail = self.process_chardata(node.tail) + + def process_chardata(self, text): + """Replaceable chardata method for processing the text""" + return ''.join(map(self.map_char, text)) + + @staticmethod + def map_char(char): + """Replaceable map_char method for processing each letter""" + raise NotImplementedError("Please provide a process_chardata or map_char static method.") diff --git a/share/extensions/inkex/inx.py b/share/extensions/inkex/inx.py new file mode 100644 index 0000000..76d2d9c --- /dev/null +++ b/share/extensions/inkex/inx.py @@ -0,0 +1,166 @@ +# coding=utf-8 +# +# Copyright (C) 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Parsing inx files for checking and generating. +""" + +import os +from lxml import etree + +try: + from inspect import isclass + from importlib import util +except ImportError: + util = None # type: ignore + +from .base import InkscapeExtension +from .utils import Boolean + +NSS = { + 'inx': 'http://www.inkscape.org/namespace/inkscape/extension', +} + +class InxLookup(etree.CustomElementClassLookup): + """Custom inx xml file lookup""" + def lookup(self, node_type, document, namespace, name): # pylint: disable=unused-argument + if name == 'param': + return ParamElement + return None + +INX_PARSER = etree.XMLParser() +INX_PARSER.set_element_class_lookup(InxLookup()) + +class InxFile(object): + """Open an INX file and provide useful functions""" + name = property(lambda self: self._text('inx:name')) + ident = property(lambda self: self._text('inx:id')) + slug = property(lambda self: self.ident.split('.')[-1].title().replace('_', '')) + kind = property(lambda self: self.metadata['type']) + + def __init__(self, filename): + self.filename = os.path.basename(filename) + self.doc = etree.parse(filename, parser=INX_PARSER) + self.root = self.doc.getroot() + + def __repr__(self): + return "".format(self) + + def xpath(self, xpath): + """Namespace specific xpath searches""" + return self.root.xpath(xpath, namespaces=NSS) + + def find_one(self, name): + """Return the first element matching the given name""" + for elem in self.xpath(name): + return elem + return None + + def _text(self, name, default=None): + elem = self.find_one(name) + if elem is not None and elem.text: + return elem.text + return default + + @property + def script(self): + """Returns information about the called script""" + command = self.find_one('inx:script/inx:command') + if command is None: + return {} + return { + 'interpreter': command.get('interpreter', None), + 'location': command.get('location', None), + 'script': command.text, + } + + @property + def extension_class(self): + """Attempt to get the extension class""" + script = self.script.get('script', None) + if script is not None and util is not None: + name = script[:-3].replace('/', '.') + spec = util.spec_from_file_location(name, script) + mod = util.module_from_spec(spec) + spec.loader.exec_module(mod) + for value in mod.__dict__.values(): + if 'Base' not in name and isclass(value) and value.__module__ == name \ + and issubclass(value, InkscapeExtension): + return value + return None + + @property + def metadata(self): + """Returns information about what type of extension this is""" + effect = self.find_one('inx:effect') + output = self.find_one('inx:output') + data = {} + if effect is not None: + data['type'] = 'effect' + data['preview'] = Boolean(effect.get('needs-live-preview', 'true')) + data['objects'] = self._text('inx:effect/inx:object-type', 'all') + elif self.find_one('inx:input') is not None: + data['type'] = 'input' + data['extension'] = self._text('inx:input/inx:extension') + data['mimetype'] = self._text('inx:input/inx:mimetype') + data['name'] = self._text('inx:input/inx:filetypename') + data['tooltip'] = self._text('inx:input/inx:filetypetooltip') + elif output is not None: + data['type'] = 'output' + data['dataloss'] = Boolean(self._text('inx:output/inx:dataloss', 'false')) + data['extension'] = self._text('inx:output/inx:extension') + data['mimetype'] = self._text('inx:output/inx:mimetype') + data['name'] = self._text('inx:output/inx:filetypename') + data['tooltip'] = self._text('inx:output/inx:filetypetooltip') + return data + + @property + def menu(self): + """Return the menu this effect ends up in""" + def _recurse_menu(parent): + for child in parent.xpath('inx:submenu', namespaces=NSS): + yield child.get('name') + for subchild in _recurse_menu(child): + yield subchild + break # Not more than one menu chain? + menu = self.find_one('inx:effect/inx:effects-menu') + return list(_recurse_menu(menu)) + [self.name] + + @property + def params(self): + """Get all params at all levels""" + # Returns any params at any levels + return list(self.xpath('//inx:param')) + + +class ParamElement(etree.ElementBase): + """ + A param in an inx file. + """ + name = property(lambda self: self.get('name')) + param_type = property(lambda self: self.get('type', 'string')) + + @property + def options(self): + """Return a list of option values""" + if self.param_type == 'notebook': + return [option.get('name') for option in self.xpath('inx:page', namespaces=NSS)] + return [option.get('value') for option in self.xpath('inx:option', namespaces=NSS)] + + def __repr__(self): + return "".format(self) diff --git a/share/extensions/inkex/localization.py b/share/extensions/inkex/localization.py new file mode 100644 index 0000000..7695b69 --- /dev/null +++ b/share/extensions/inkex/localization.py @@ -0,0 +1,66 @@ +# coding=utf-8 +# +# Copyright (C) 2010 Nick Drobchenko, nick@cnc-club.ru +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Allow extensions to translate messages. +""" + +import gettext +import os + +# Get gettext domain and matching locale directory for translation of extensions strings +# (both environment variables are set by Inkscape) +GETTEXT_DOMAIN = os.environ.get('INKEX_GETTEXT_DOMAIN') +GETTEXT_DIRECTORY = os.environ.get('INKEX_GETTEXT_DIRECTORY') + +# INKSCAPE_LOCALEDIR can be used to override the default locale directory Inkscape uses +INKSCAPE_LOCALEDIR = os.environ.get('INKSCAPE_LOCALEDIR') + +def localize(domain=GETTEXT_DOMAIN, localedir=GETTEXT_DIRECTORY): + """Configure gettext and install _() function into builtins namespace for easy access""" + + # Do not enable translation if GETTEXT_DOMAIN is unset. + # This is the case when translationdomain="none", but also when no catalog was found. + # Install a NullTranslation just to be sure (so we do not get errors about undefined '_') + if domain is None: + gettext.NullTranslations().install() + return + + # Use the default system locale by default, + # but prefer LANGUAGE environment variable (which is set by Inkscape according to UI language) + languages = None + + trans = gettext.translation(domain, localedir, languages, fallback=True) + trans.install() + + + +def inkex_localize(): + """ + Return internal Translations instance for translation of the inkex module itself + Those will always use the 'inkscape' domain and attempt to lookup the same catalog Inkscape uses + """ + + domain = 'inkscape' + localedir = INKSCAPE_LOCALEDIR + languages = None + + return gettext.translation(domain, localedir, languages, fallback=True) + +inkex_gettext = inkex_localize().gettext # pylint: disable=invalid-name diff --git a/share/extensions/inkex/paths.py b/share/extensions/inkex/paths.py new file mode 100644 index 0000000..b3620e8 --- /dev/null +++ b/share/extensions/inkex/paths.py @@ -0,0 +1,1547 @@ +# coding=utf-8 +# +# Copyright (C) 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +functions for digesting paths into a simple list structure +""" + +import re +import copy + +from math import atan2, cos, pi, sin, sqrt, acos, tan + +from .transforms import Transform, BoundingBox, Vector2d +from .utils import classproperty, strargs + +try: # pylint: disable=using-constant-test + from typing import overload, Any, Type, Dict, Optional, Union, Tuple, List, Iterator, Generator # pylint: disable=unused-import + from typing import TypeVar + Pathlike = TypeVar('Pathlike', bound="PathCommand") + AbsolutePathlike = TypeVar('AbsolutePathlike', bound="AbsolutePathCommand") +except ImportError: + overload = lambda x: x + +# All the names that get added to the inkex API itself. +__all__ = ( + 'Path', 'CubicSuperPath', + # Path commands: + 'Line', 'line', + 'Move', 'move', + 'ZoneClose', 'zoneClose', + 'Horz', 'horz', + 'Vert', 'vert', + 'Curve', 'curve', + 'Smooth', 'smooth', + 'Quadratic', 'quadratic', + 'TepidQuadratic', 'tepidQuadratic', + 'Arc', 'arc', + # errors + 'InvalidPath' +) + +LEX_REX = re.compile(r'([MLHVCSQTAZmlhvcsqtaz])([^MLHVCSQTAZmlhvcsqtaz]*)') +NONE = lambda obj: obj is not None + + +class InvalidPath(ValueError): + """Raised when given an invalid path string""" + + +class PathCommand(object): + """ + Base class of all path commands + """ + + # Number of arguments that follow this path commands letter + nargs = -1 + + # The full name of the segment (i.e. Line, Arc, etc) + name = classproperty(lambda cls: cls.__name__) + + # The single letter representation of this command (i.e. L, A, etc) + letter = classproperty(lambda cls: cls.name[0]) + + # The implicit next command. This is for automatic chains where the next command + # isn't given, just a bunch on numbers which we automatically parse. + @classproperty + def next_command(self): + return self + + @property + def is_relative(self): # type: () -> bool + raise NotImplementedError + + @property + def is_absolute(self): # type: () -> bool + raise NotImplementedError + + def to_relative(self, prev): # type: (Vector2d) -> RelativePathCommand + """Return absolute counterpart for absolute commands or copy for relative""" + raise NotImplementedError + + def to_absolute(self, prev): # type: (Vector2d) -> AbsolutePathCommand + """Return relative counterpart for relative commands or copy for absolute""" + raise NotImplementedError + + # The precision of the numbers when converting to string + number_template = "{:.6g}" + + # Maps single letter path command to corresponding class + # (filled at the bottom of file, when all classes already defined) + _letter_to_class = {} # type: Dict[str, Type[Any]] + + @staticmethod + def letter_to_class(letter): + """Returns class for given path command letter""" + return PathCommand._letter_to_class[letter] + + @property + def args(self): # type: () -> List[float] + """Returns path command arguments as tuple of floats""" + raise NotImplementedError() + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Union[List[Vector2d], Generator[Vector2d, None, None]] + """Returns list of path command control points""" + raise NotImplementedError + + @classmethod + def _argt(cls, sep): + return sep.join([cls.number_template] * cls.nargs) + + def __str__(self): + return "{} {}".format(self.letter, self._argt(" ").format(*self.args)).strip() + + def __repr__(self): + return "{{}}({})".format(self._argt(", ")).format(self.name, *self.args) + + def __eq__(self, other): + previous = Vector2d() + if type(self) == type(other): # pylint: disable=unidiomatic-typecheck + return self.args == other.args + if isinstance(other, tuple): + return self.args == other + if not isinstance(other, PathCommand): + raise ValueError("Can't compare types") + try: + if self.is_relative == other.is_relative: + return self.to_curve(previous) == other.to_curve(previous) + except ValueError: + pass + return False + + def end_point(self, first, prev): # type: (Vector2d, Vector2d) -> Vector2d + """Returns last control point of path command""" + raise NotImplementedError() + + def update_bounding_box(self, first, last_two_points, bbox): + # type: (Vector2d, List[Vector2d], BoundingBox) -> None + # pylint: disable=unused-argument + """ + Enlarges given bbox to contain path element. + + :param (tuple of float) first: first point of path. Required to calculate Z segment + :param (list of tuple) last_two_points: list with last two control points in abs coords. + :param (BoundingBox) bbox: bounding box to update + """ + raise NotImplementedError("Bounding box is not implemented for {}".format(self.name)) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> Curve + """Convert command to :py:class:`Curve` + Curve().to_curve() returns a copy + """ + raise NotImplementedError("To curve not supported for {}".format(self.name)) + + def to_curves(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> List[Curve] + """Convert command to list of :py:class:`Curve` commands """ + return [self.to_curve(prev, prev_prev)] + + def to_line(self, prev): + # type: (Vector2d) -> Line + """Converts this segment to a line (copies if already a line)""" + return Line(*self.end_point(Vector2d(), prev)) + + +class RelativePathCommand(PathCommand): + """ + Abstract base class for relative path commands. + + Implements most of methods of :py:class:`PathCommand` through + conversion to :py:class:`AbsolutePathCommand` + """ + + @property + def is_relative(self): + return True + + @property + def is_absolute(self): + return False + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Union[List[Vector2d], Generator[Vector2d, None, None]] + return self.to_absolute(prev).control_points(first, prev, prev_prev) + + def to_relative(self, prev): + # type: (Pathlike, Vector2d) -> Pathlike + return self.__class__(*self.args) + + def update_bounding_box(self, first, last_two_points, bbox): + self.to_absolute(last_two_points[-1]).update_bounding_box(first, last_two_points, bbox) + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return self.to_absolute(prev).end_point(first, prev) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> Curve + return self.to_absolute(prev).to_curve(prev, prev_prev) + + def to_curves(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> List[Curve] + return self.to_absolute(prev).to_curves(prev, prev_prev) + + +class AbsolutePathCommand(PathCommand): + """ + Absolute path command. Unlike :py:class:`RelativePathCommand` can be transformed directly. + """ + + @property + def is_relative(self): + return False + + @property + def is_absolute(self): + return True + + def to_absolute(self, previous): # type: (AbsolutePathlike, Vector2d) -> AbsolutePathlike + return self.__class__(*self.args) + + def transform(self, transform): # type: (AbsolutePathlike, Transform) -> AbsolutePathlike + """Returns new transformed segment + + :param transform: a transformation to apply + """ + raise NotImplementedError() + + def rotate(self, degrees, center): # type: (AbsolutePathlike, float, Vector2d) -> AbsolutePathlike + """ + Returns new transformed segment + + :param degrees: rotation angle in degrees + :param center: invariant point of rotation + """ + return self.transform(Transform(rotate=(degrees, center[0], center[1]))) + + def translate(self, dr): # type: (AbsolutePathlike, Vector2d) -> AbsolutePathlike + """Translate or scale this path command by dr""" + return self.transform(Transform(translate=dr)) + + def scale(self, factor): # type: (AbsolutePathlike, Union[float, Tuple[float,float]]) -> AbsolutePathlike + """Returns new transformed segment + + :param factor: scale or (scale_x, scale_y) + """ + return self.transform(Transform(scale=factor)) + + +class Line(AbsolutePathCommand): + """Line segment""" + + nargs = 2 + + @property + def args(self): + return self.x, self.y + + def __init__(self, x, y): + self.x = x + self.y = y + + def update_bounding_box(self, first, last_two_points, bbox): + bbox += BoundingBox((last_two_points[-1].x, self.x), (last_two_points[-1].y, self.y)) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield Vector2d(self.x, self.y) + + def to_relative(self, prev): + # type: (Vector2d) -> line + return line(self.x - prev.x, self.y - prev.y) + + def transform(self, transform): + # type: (Line, Transform) -> Line + return Line(*transform.apply_to_point((self.x, self.y))) + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return Vector2d(self.x, self.y) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Optional[Vector2d]) -> Curve + return Curve(prev.x, prev.y, self.x, self.y, self.x, self.y) + + + +class line(RelativePathCommand): # pylint: disable=invalid-name + """Relative line segment""" + + nargs = 2 + + @property + def args(self): + return self.dx, self.dy + + def __init__(self, dx, dy): + self.dx = dx + self.dy = dy + + def to_absolute(self, prev): # type: (Vector2d) -> Line + return Line(prev.x + self.dx, prev.y + self.dy) + + +class Move(AbsolutePathCommand): + """Move pen segment without a line""" + + nargs = 2 + next_command = Line + + @property + def args(self): + return self.x, self.y + + def __init__(self, x, y): + self.x = x + self.y = y + + def update_bounding_box(self, first, last_two_points, bbox): + bbox += BoundingBox(self.x, self.y) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield Vector2d(self.x, self.y) + + def to_relative(self, prev): + # type: (Vector2d) -> move + return move(self.x - prev.x, self.y - prev.y) + + def transform(self, transform): + # type: (Transform) -> Move + return Move(*transform.apply_to_point((self.x, self.y))) + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return Vector2d(self.x, self.y) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Optional[Vector2d]) -> Curve + raise ValueError("Move segments can not be changed into curves.") + + +class move(RelativePathCommand): # pylint: disable=invalid-name + """Relative move segment""" + + nargs = 2 + next_command = line + + @property + def args(self): + return self.dx, self.dy + + def __init__(self, dx, dy): + self.dx = dx + self.dy = dy + + def to_absolute(self, prev): # type: (Vector2d) -> Move + return Move(prev.x + self.dx, prev.y + self.dy) + + +class ZoneClose(AbsolutePathCommand): + """Close segment to finish a path""" + nargs = 0 + next_command = Move + + @property + def args(self): + return () + + def update_bounding_box(self, first, last_two_points, bbox): + pass + + def transform(self, transform): + # type: (Transform) -> ZoneClose + return ZoneClose() + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield first + + def to_relative(self, prev): + # type: (Vector2d) -> zoneClose + return zoneClose() + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return first + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Optional[Vector2d]) -> Curve + raise ValueError("ZoneClose segments can not be changed into curves.") + + +class zoneClose(RelativePathCommand): # pylint: disable=invalid-name + """Same as above (svg says no difference)""" + + nargs = 0 + next_command = Move + + @property + def args(self): + return () + + def to_absolute(self, prev): + return ZoneClose() + + +class Horz(AbsolutePathCommand): + """Horizontal Line segment""" + nargs = 1 + + @property + def args(self): + return self.x, + + def __init__(self, x): + self.x = x + + def update_bounding_box(self, first, last_two_points, bbox): + bbox += BoundingBox((last_two_points[-1].x, self.x), last_two_points[-1].y) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield Vector2d(self.x, prev.y) + + def to_relative(self, prev): + # type: (Vector2d) -> horz + return horz(self.x - prev.x) + + def transform(self, transformation): + # type: (Pathlike, Transform) -> Pathlike + raise ValueError("Horizontal lines can't be transformed directly.") + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return Vector2d(self.x, prev.y) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Optional[Vector2d]) -> Curve + """Convert a horizontal line into a curve""" + return self.to_line(prev).to_curve(prev) + + def to_line(self, prev): + # type: (Vector2d) -> Line + """Return this path command as a Line instead""" + return Line(self.x, prev.y) + + +class horz(RelativePathCommand): # pylint: disable=invalid-name + """Relative horz line segment""" + + nargs = 1 + + @property + def args(self): + return self.dx, + + def __init__(self, dx): + self.dx = dx + + def to_absolute(self, prev): # type: (Vector2d) -> Horz + return Horz(prev.x + self.dx) + + def to_line(self, prev): # type: (Vector2d) -> Line + """Return this path command as a Line instead""" + return Line(prev.x + self.dx, prev.y) + + +class Vert(AbsolutePathCommand): + """Vertical Line segment""" + + nargs = 1 + + @property + def args(self): + return self.y, + + def __init__(self, y): + self.y = y + + def update_bounding_box(self, first, last_two_points, bbox): + bbox += BoundingBox(last_two_points[-1].x, (last_two_points[-1].y, self.y)) + + def transform(self, transform): # type: (Pathlike, Transform) -> Pathlike + raise ValueError("Vertical lines can't be transformed directly.") + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield Vector2d(prev.x, self.y) + + def to_relative(self, prev): + # type: (Vector2d) -> vert + return vert(self.y - prev.y) + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return Vector2d(prev.x, self.y) + + def to_line(self, prev): + # type: (Vector2d) -> Line + """Return this path command as a line instead""" + return Line(prev.x, self.y) + + def to_curve(self, prev, prev_prev=Vector2d()): # type: (Vector2d, Optional[Vector2d]) -> Curve + """Convert a horizontal line into a curve""" + return self.to_line(prev).to_curve(prev) + + +class vert(RelativePathCommand): # pylint: disable=invalid-name + """Relative vertical line segment""" + + nargs = 1 + + @property + def args(self): + return self.dy, + + def __init__(self, dy): + self.dy = dy + + def to_absolute(self, prev): # type: (Vector2d) -> Vert + return Vert(prev.y + self.dy) + + def to_line(self, prev): # type: (Vector2d) -> Line + """Return this path command as a line instead""" + return Line(prev.x, prev.y + self.dy) + + +class Curve(AbsolutePathCommand): + """Absolute Curved Line segment""" + nargs = 6 + + @property + def args(self): + return self.x2, self.y2, self.x3, self.y3, self.x4, self.y4 + + def __init__(self, x2, y2, x3, y3, x4, y4): + self.x2 = x2 + self.y2 = y2 + + self.x3 = x3 + self.y3 = y3 + + self.x4 = x4 + self.y4 = y4 + + def update_bounding_box(self, first, last_two_points, bbox): + from .transforms import cubic_extrema + + x1, x2, x3, x4 = last_two_points[-1].x, self.x2, self.x3, self.x4 + y1, y2, y3, y4 = last_two_points[-1].y, self.y2, self.y3, self.y4 + + if not (x1 in bbox.x and + x2 in bbox.x and + x3 in bbox.x and + x4 in bbox.x): + bbox.x += cubic_extrema(x1, x2, x3, x4) + + if not (y1 in bbox.y and + y2 in bbox.y and + y3 in bbox.y and + y4 in bbox.y): + bbox.y += cubic_extrema(y1, y2, y3, y4) + + def transform(self, transform): + # type: (Transform) -> Curve + x2, y2 = transform.apply_to_point((self.x2, self.y2)) + x3, y3 = transform.apply_to_point((self.x3, self.y3)) + x4, y4 = transform.apply_to_point((self.x4, self.y4)) + return Curve(x2, y2, x3, y3, x4, y4) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield Vector2d(self.x2, self.y2) + yield Vector2d(self.x3, self.y3) + yield Vector2d(self.x4, self.y4) + + def to_relative(self, prev): # type: (Vector2d) -> curve + return curve( + self.x2 - prev.x, self.y2 - prev.y, + self.x3 - prev.x, self.y3 - prev.y, + self.x4 - prev.x, self.y4 - prev.y + ) + + def end_point(self, first, prev): + return Vector2d(self.x4, self.y4) + + def to_curve(self, prev, prev_prev=Vector2d()): # type: (Vector2d, Optional[Vector2d]) -> Curve + """No conversion needed, pass-through, returns self""" + return Curve(*self.args) + + def to_bez(self): + """Returns the list of coords for SuperPath""" + return [list(self.args[:2]), list(self.args[2:4]), list(self.args[4:6])] + +class curve(RelativePathCommand): # pylint: disable=invalid-name + """Relative curved line segment""" + nargs = 6 + + @property + def args(self): + return self.dx2, self.dy2, self.dx3, self.dy3, self.dx4, self.dy4 + + def __init__(self, dx2, dy2, dx3, dy3, dx4, dy4): + self.dx2 = dx2 + self.dy2 = dy2 + + self.dx3 = dx3 + self.dy3 = dy3 + + self.dx4 = dx4 + self.dy4 = dy4 + + def to_absolute(self, prev): # type: (Vector2d) -> Curve + return Curve( + self.dx2 + prev.x, self.dy2 + prev.y, + self.dx3 + prev.x, self.dy3 + prev.y, + self.dx4 + prev.x, self.dy4 + prev.y + ) + + +class Smooth(AbsolutePathCommand): + """Absolute Smoothed Curved Line segment""" + nargs = 4 + + @property + def args(self): + return self.x3, self.y3, self.x4, self.y4 + + def __init__(self, x3, y3, x4, y4): + + self.x3 = x3 + self.y3 = y3 + + self.x4 = x4 + self.y4 = y4 + + def update_bounding_box(self, first, last_two_points, bbox): + self.to_curve(last_two_points[-1], last_two_points[-2]).update_bounding_box(first, last_two_points, bbox) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + + x1, x2, x3, x4 = prev_prev.x, prev.x, self.x3, self.x4 + y1, y2, y3, y4 = prev_prev.y, prev.y, self.y3, self.y4 + + # infer reflected point + x2 = 2 * x2 - x1 + y2 = 2 * y2 - y1 + + yield Vector2d(x2, y2) + yield Vector2d(x3, y3) + yield Vector2d(x4, y4) + + def to_relative(self, prev): # type: (Vector2d) -> smooth + return smooth( + self.x3 - prev.x, self.y3 - prev.y, + self.x4 - prev.x, self.y4 - prev.y + ) + + def transform(self, transform): + # type: (Transform) -> Smooth + x3, y3 = transform.apply_to_point((self.x3, self.y3)) + x4, y4 = transform.apply_to_point((self.x4, self.y4)) + return Smooth(x3, y3, x4, y4) + + def end_point(self, first, prev): + return Vector2d(self.x4, self.y4) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> Curve + """ + Convert this Smooth curve to a regular curve by creating a mirror + set of nodes based on the previous node. Previous should be a curve. + """ + (x2, y2), (x3, y3), (x4, y4) = self.control_points(prev, prev, prev_prev) + return Curve(x2, y2, x3, y3, x4, y4) + + +class smooth(RelativePathCommand): # pylint: disable=invalid-name + """Relative smoothed curved line segment""" + nargs = 4 + + @property + def args(self): + return self.dx3, self.dy3, self.dx4, self.dy4 + + def __init__(self, dx3, dy3, dx4, dy4): + self.dx3 = dx3 + self.dy3 = dy3 + + self.dx4 = dx4 + self.dy4 = dy4 + + def to_absolute(self, prev): # type: (Vector2d) -> Smooth + return Smooth( + self.dx3 + prev.x, self.dy3 + prev.y, + self.dx4 + prev.x, self.dy4 + prev.y + ) + + +class Quadratic(AbsolutePathCommand): + """Absolute Quadratic Curved Line segment""" + nargs = 4 + + @property + def args(self): + return self.x2, self.y2, self.x3, self.y3 + + def __init__(self, x2, y2, x3, y3): + + self.x2 = x2 + self.y2 = y2 + + self.x3 = x3 + self.y3 = y3 + + def update_bounding_box(self, first, last_two_points, bbox): + from .transforms import quadratic_extrema + + x1, x2, x3 = last_two_points[-1].x, self.x2, self.x3 + y1, y2, y3 = last_two_points[-1].y, self.y2, self.y3 + + if not (x1 in bbox.x and + x2 in bbox.x and + x3 in bbox.x): + bbox.x += quadratic_extrema(x1, x2, x3) + + if not (y1 in bbox.y and + y2 in bbox.y and + y3 in bbox.y): + bbox.y += quadratic_extrema(y1, y2, y3) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield Vector2d(self.x2, self.y2) + yield Vector2d(self.x3, self.y3) + + def to_relative(self, prev): + # type: (Vector2d) -> quadratic + return quadratic( + self.x2 - prev.x, self.y2 - prev.y, + self.x3 - prev.x, self.y3 - prev.y + ) + + def transform(self, transform): + # type: (Transform) -> Quadratic + x2, y2 = transform.apply_to_point((self.x2, self.y2)) + x3, y3 = transform.apply_to_point((self.x3, self.y3)) + return Quadratic(x2, y2, x3, y3) + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return Vector2d(self.x3, self.y3) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> Curve + """Attempt to convert a quadratic to a curve""" + prev = Vector2d(prev) + x1 = 1. / 3 * prev.x + 2. / 3 * self.x2 + x2 = 2. / 3 * self.x2 + 1. / 3 * self.x3 + y1 = 1. / 3 * prev.y + 2. / 3 * self.y2 + y2 = 2. / 3 * self.y2 + 1. / 3 * self.y3 + return Curve(x1, y1, x2, y2, self.x3, self.y3) + + +class quadratic(RelativePathCommand): # pylint: disable=invalid-name + """Relative quadratic line segment""" + nargs = 4 + + @property + def args(self): + return self.dx2, self.dy2, self.dx3, self.dy3 + + def __init__(self, dx2, dy2, dx3, dy3): + self.dx2 = dx2 + self.dx3 = dx3 + self.dy2 = dy2 + self.dy3 = dy3 + + def to_absolute(self, prev): # type: (Vector2d) -> Quadratic + return Quadratic( + self.dx2 + prev.x, self.dy2 + prev.y, + self.dx3 + prev.x, self.dy3 + prev.y + ) + + +class TepidQuadratic(AbsolutePathCommand): + """Continued Quadratic Line segment""" + nargs = 2 + + @property + def args(self): + return self.x3, self.y3 + + def __init__(self, x3, y3): + self.x3 = x3 + self.y3 = y3 + + def update_bounding_box(self, first, last_two_points, bbox): + self.to_quadratic(last_two_points[-1], last_two_points[-2]).update_bounding_box(first, last_two_points, bbox) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + + x1, x2, x3 = prev_prev.x, prev.x, self.x3 + y1, y2, y3 = prev_prev.y, prev.y, self.y3 + + # infer reflected point + x2 = 2 * x2 - x1 + y2 = 2 * y2 - y1 + + yield Vector2d(x2, y2) + yield Vector2d(x3, y3) + + def to_relative(self, prev): # type: (Vector2d) -> tepidQuadratic + return tepidQuadratic( + self.x3 - prev.x, self.y3 - prev.y + ) + + def transform(self, transform): + # type: (Transform) -> TepidQuadratic + x3, y3 = transform.apply_to_point((self.x3, self.y3)) + return TepidQuadratic(x3, y3) + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return Vector2d(self.x3, self.y3) + + def to_curve(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> Curve + return self.to_quadratic(prev, prev_prev).to_curve(prev) + + def to_quadratic(self, prev, prev_prev): + # type: (Vector2d, Vector2d) -> Quadratic + """ + Convert this continued quadratic into a full quadratic + """ + (x2, y2), (x3, y3) = self.control_points(prev, prev, prev_prev) + return Quadratic(x2, y2, x3, y3) + + +class tepidQuadratic(RelativePathCommand): # pylint: disable=invalid-name + """Relative continued quadratic line segment""" + nargs = 2 + + @property + def args(self): + return self.dx3, self.dy3 + + def __init__(self, dx3, dy3): + self.dx3 = dx3 + self.dy3 = dy3 + + def to_absolute(self, prev): + # type: (Vector2d) -> TepidQuadratic + return TepidQuadratic( + self.dx3 + prev.x, self.dy3 + prev.y + ) + + +class Arc(AbsolutePathCommand): + """Special Arc segment""" + nargs = 7 + + @property + def args(self): + return self.rx, self.ry, self.x_axis_rotation, self.large_arc, self.sweep, self.x, self.y + + def __init__(self, rx, ry, x_axis_rotation, large_arc, sweep, x, y): + self.rx = rx + self.ry = ry + self.x_axis_rotation = x_axis_rotation + self.large_arc = large_arc + self.sweep = sweep + self.x = x + self.y = y + + def update_bounding_box(self, first, last_two_points, bbox): + prev = last_two_points[-1] + for seg in self.to_curves(prev=prev): + seg.update_bounding_box(first, [None, prev], bbox) + prev = seg.end_point(first, prev) + + def control_points(self, first, prev, prev_prev): + # type: (Vector2d, Vector2d, Vector2d) -> Generator[Vector2d, None, None] + yield Vector2d(self.x, self.y) + + def to_curves(self, prev, prev_prev=Vector2d()): + # type: (Vector2d, Vector2d) -> List[Curve] + """Convert this arc into bezier curves""" + path = CubicSuperPath([arc_to_path(list(prev), self.args)]).to_path(curves_only=True) + # Ignore the first move command from to_path() + return list(path)[1:] + + def transform(self, transform): + # type: (Transform) -> Arc + x_, y_ = transform.apply_to_point((self.x, self.y)) + + T = transform # type: Transform + if self.x_axis_rotation != 0: + T = T * Transform(rotate=self.x_axis_rotation) + a, c, b, d, _, _ = list(T.to_hexad()) + # T = | a b | + # | c d | + + detT = a * d - b * c + detT2 = detT ** 2 + + rx = float(self.rx) + ry = float(self.ry) + + if rx == 0.0 or ry == 0.0 or detT2 == 0.0: + # invalid Arc parameters + # transform only last point + return Arc(self.rx, self.ry, self.x_axis_rotation, self.large_arc, self.sweep, x_, y_) + + A = (d ** 2 / rx ** 2 + c ** 2 / ry ** 2) / detT2 + B = - (d * b / rx ** 2 + c * a / ry ** 2) / detT2 + D = (b ** 2 / rx ** 2 + a ** 2 / ry ** 2) / detT2 + + theta = atan2(-2 * B, D - A) / 2 + theta_deg = theta * 180.0 / pi + DA = (D - A) + l2 = 4 * B ** 2 + DA ** 2 + + if l2 == 0: + delta = 0.0 + else: + delta = 0.5 * (-DA ** 2 - 4 * B ** 2) / sqrt(l2) + + half = (A + D) / 2 + + rx_ = 1.0 / sqrt(half + delta) + ry_ = 1.0 / sqrt(half - delta) + + x_, y_ = transform.apply_to_point((self.x, self.y)) + + if detT > 0: + sweep = self.sweep + else: + sweep = 0 if self.sweep>0 else 1 + + return Arc(rx_, ry_, theta_deg, self.large_arc, sweep, x_, y_) + + def to_relative(self, prev): + # type: (Vector2d) -> arc + return arc(self.rx, self.ry, self.x_axis_rotation, self.large_arc, self.sweep, self.x - prev.x, self.y - prev.y) + + def end_point(self, first, prev): + # type: (Vector2d, Vector2d) -> Vector2d + return Vector2d(self.x, self.y) + + +class arc(RelativePathCommand): # pylint: disable=invalid-name + """Relative Arc line segment""" + + nargs = 7 + + @property + def args(self): + return self.rx, self.ry, self.x_axis_rotation, self.large_arc, self.sweep, self.dx, self.dy + + def __init__(self, rx, ry, x_axis_rotation, large_arc, sweep, dx, dy): + self.rx = rx + self.ry = ry + self.x_axis_rotation = x_axis_rotation + self.large_arc = large_arc + self.sweep = sweep + self.dx = dx + self.dy = dy + + def to_absolute(self, prev): # type: (Vector2d) -> "Arc" + x1, y1 = prev + return Arc(self.rx, self.ry, self.x_axis_rotation, self.large_arc, self.sweep, self.dx + x1, self.dy + y1) + + +PathCommand._letter_to_class = { + "M": Move, + "L": Line, + "V": Vert, + "H": Horz, + "A": Arc, + "C": Curve, + "S": Smooth, + "Z": ZoneClose, + "Q": Quadratic, + "T": TepidQuadratic, + "m": move, + "l": line, + "v": vert, + "h": horz, + "a": arc, + "c": curve, + "s": smooth, + "z": zoneClose, + "q": quadratic, + "t": tepidQuadratic +} + + +class Path(list): + """A list of segment commands which combine to draw a shape""" + + class PathCommandProxy(object): + """ + A handy class for Path traverse and coordinate access + + Reduces number of arguments in user code compared to bare :py:class:`PathCommand` methods + """ + + def __init__(self, command, first_point, previous_end_point, prev2_control_point): + self.command = command # type: PathCommand + self.first_point = first_point # type: Vector2d + self.previous_end_point = previous_end_point # type: Vector2d + self.prev2_control_point = prev2_control_point # type: Vector2d + + @property + def name(self): + return self.command.name + + @property + def letter(self): + return self.command.letter + + @property + def next_command(self): + return self.command.next_command + + @property + def is_relative(self): + return self.command.is_relative + + @property + def is_absolute(self): + return self.command.is_absolute + + @property + def args(self): + return self.command.args + + @property + def control_points(self): + return self.command.control_points(self.first_point, self.previous_end_point, self.prev2_control_point) + + @property + def end_point(self): + return self.command.end_point(self.first_point, self.previous_end_point) + + def to_curve(self): + return self.command.to_curve(self.previous_end_point, self.prev2_control_point) + + def to_curves(self): + return self.command.to_curves(self.previous_end_point, self.prev2_control_point) + + def __str__(self): + return str(self.command) + + def __repr__(self): + return "<" + self.__class__.__name__ + ">" + repr(self.command) + + def __init__(self, path_d=None): + super(Path, self).__init__() + if isinstance(path_d, str): + # Returns a generator returning PathCommand objects + path_d = self.parse_string(path_d) + elif isinstance(path_d, CubicSuperPath): + path_d = path_d.to_path() + + for item in (path_d or ()): + if isinstance(item, PathCommand): + self.append(item) + elif isinstance(item, (list, tuple)) and len(item) == 2: + if isinstance(item[1], (list, tuple)): + self.append(PathCommand.letter_to_class(item[0])(*item[1])) + else: + self.append(Line(*item)) + else: + raise TypeError("Bad path type: {}({}, ...): {}".format( + type(path_d).__name__, type(item).__name__, item)) + + @classmethod + def parse_string(cls, path_d): + """Parse a path string and generate segment objects""" + for cmd, numbers in LEX_REX.findall(path_d): + args = list(strargs(numbers)) + cmd = PathCommand.letter_to_class(cmd) + i = 0 + while i < len(args) or cmd.nargs == 0: + seg = cmd(*args[i:i + cmd.nargs]) + i += cmd.nargs + cmd = seg.next_command + yield seg + + def bounding_box(self): + # type: () -> Optional[BoundingBox] + """Return bounding box of the Path""" + if not self: + return None + iterator = self.proxy_iterator() + proxy = next(iterator) + bbox = BoundingBox(proxy.first_point.x, proxy.first_point.y) + try: + while True: + proxy = next(iterator) + proxy.command.update_bounding_box(proxy.first_point, [ + proxy.prev2_control_point, + proxy.previous_end_point, + ], bbox) + except StopIteration: + return bbox + + def append(self, cmd): + """Append a command to this path including any chained commands""" + if isinstance(cmd, list): + self.extend(cmd) + elif isinstance(cmd, PathCommand): + super(Path, self).append(cmd) + + def translate(self, x, y, inplace=False): # pylint: disable=invalid-name + """Move all coords in this path by the given amount""" + return self.transform(Transform(translate=(x, y)), inplace=inplace) + + def scale(self, x, y, inplace=False): # pylint: disable=invalid-name + """Scale all coords in this path by the given amounts""" + return self.transform(Transform(scale=(x, y)), inplace=inplace) + + def rotate(self, deg, center=None, inplace=False): + """Rotate the path around the given point""" + if center is None: + # Default center is center of bbox + bbox = self.bounding_box() + if bbox: + center = bbox.center + else: + center = Vector2d() + center = Vector2d(center) + return self.transform(Transform(rotate=(deg, center.x, center.y)), inplace=inplace) + + @property + def control_points(self): + + prev = Vector2d() + prev_prev = Vector2d() + first = Vector2d() + + for i, seg in enumerate(self): # type: PathCommand + if i == 0: + first = seg.end_point(first, prev) + for cp in seg.control_points(first, prev, prev_prev): + prev_prev = prev + prev = cp + yield cp + + @property + def end_points(self): + prev = Vector2d() + first = Vector2d() + + for i, seg in enumerate(self): # type: PathCommand + if i == 0: + first = seg.end_point(first, prev) + end_point = seg.end_point(first, prev) + prev = end_point + yield end_point + + def transform(self, transform, inplace=False): + """Convert to new path""" + result = Path() + previous = Vector2d() + previous_new = Vector2d() + first = Vector2d() + first_new = Vector2d() + + for i, seg in enumerate(self): # type: PathCommand + if i == 0: + first = seg.end_point(first, previous) + + if isinstance(seg, (horz, Horz, Vert, vert)): + seg = seg.to_line(previous) + + if seg.is_relative: + new_seg = seg.to_absolute(previous).transform(transform).to_relative(previous_new) + else: + new_seg = seg.transform(transform) + + if i == 0: + first_new = new_seg.end_point(first_new, previous_new) + + if inplace: + self[i] = new_seg + else: + result.append(new_seg) + previous = seg.end_point(first, previous) + previous_new = new_seg.end_point(first_new, previous_new) + if inplace: + return self + return result + + def reverse(self): + """Returns a reversed path""" + pass + + def close(self): + """Attempt to close the last path segment""" + if self and not isinstance(self[-1], (zoneClose, ZoneClose)): + self.append(ZoneClose()) + + def proxy_iterator(self): + """ + Yields :py:class:`AugmentedPathIterator` + + :rtype: Iterator[ Path.PathCommandProxy ] + """ + + previous = Vector2d() + prev_prev = Vector2d() + first = Vector2d() + + for i, seg in enumerate(self): # type: PathCommand + if i == 0: + prev_prev = previous = first = seg.end_point(first, previous) + yield Path.PathCommandProxy(seg, first, previous, prev_prev) + if isinstance(seg, (curve, tepidQuadratic, quadratic, smooth, + Curve, TepidQuadratic, Quadratic, Smooth)): + prev_prev = list(seg.control_points(first, previous, prev_prev))[-2] + previous = seg.end_point(first, previous) + + def to_absolute(self): + """Convert this path to use only absolute coordinates""" + abspath = Path() + + previous = Vector2d() + first = Vector2d() + + for seg in self: # type: PathCommand + if isinstance(seg, (move, Move)): + first = seg.end_point(first, previous) + + abspath.append(seg.to_absolute(previous)) + previous = seg.end_point(first, previous) + + return abspath + + def to_relative(self): + """Convert this path to use only relative coordinates""" + abspath = Path() + + previous = Vector2d() + first = Vector2d() + + for seg in self: # type: PathCommand + if isinstance(seg, (move, Move)): + first = seg.end_point(first, previous) + + abspath.append(seg.to_relative(previous)) + previous = seg.end_point(first, previous) + + return abspath + + def __str__(self): + return " ".join([str(seg) for seg in self]) + + def __add__(self, other): + acopy = copy.deepcopy(self) + if isinstance(other, str): + other = Path(other) + if isinstance(other, list): + acopy.extend(other) + return acopy + + def to_arrays(self): + """Returns path in format of parsePath output, returning arrays of absolute command data + + .. deprecated:: 1.0 + This is compatibility function for older API. Should not be used in new code + + """ + return [[seg.letter, list(seg.args)] for seg in self.to_absolute()] + + def to_superpath(self): + """Convert this path into a cubic super path""" + return CubicSuperPath(self) + + def copy(self): + """Make a copy""" + return copy.deepcopy(self) + + +class CubicSuperPath(list): + """ + A conversion of a path into a predictable list of cubic curves which + can be operated on as a list of simplified instructions. + + When converting back into a path, all lines, arcs etc will be converted + to curve instructions. + + Structure is held as [SubPath[(point_a, bezier, point_b), ...]], ...] + """ + + def __init__(self, items): + super(CubicSuperPath, self).__init__() + self._closed = True + self._prev = Vector2d() + self._prev_prev = Vector2d() + + + if isinstance(items, str): + items = Path(items) + + if isinstance(items, Path): + items = items.to_absolute() + + for item in items: + self.append(item) + + def __str__(self): + return str(self.to_path()) + + def append(self, item): + """Accept multiple different formats for the data""" + if isinstance(item, list) and len(item) == 2 and isinstance(item[0], str): + item = PathCommand.letter_to_class(item[0])(*item[1]) + is_quadratic = False + if isinstance(item, PathCommand): + if isinstance(item, Move): + if self._closed is False: + super(CubicSuperPath, self).append([]) + item = [list(item.args), list(item.args), list(item.args)] + elif isinstance(item, ZoneClose) and self and self[-1]: + # This duplicates the first segment to 'close' the path, it's appended directly + # because we don't want to last coord to change for the final segment. + self[-1].append([self[-1][0][0][:], self[-1][0][1][:], self[-1][0][2][:]]) + # Then adds a new subpath for the next shape (if any) + self._closed = True + self._prev.assign(self._first) + return + elif isinstance(item, Arc): + # Arcs are made up of three curves (approximated) + for arc_curve in item.to_curves(self._prev, self._prev_prev): + x2, y2, x3, y3, x4, y4 = arc_curve.args + self.append([[x2, y2], [x3, y3], [x4, y4]]) + self._prev_prev.assign(x3, y3) + return + else: + is_quadratic = isinstance(item, (Quadratic, TepidQuadratic, quadratic, tepidQuadratic)) + if isinstance(item, (Horz, Vert)): + item = item.to_line(self._prev) + pp = self._prev_prev + if is_quadratic: + self._prev_prev = list(item.control_points(self._first, self._prev, pp))[-2:-1][0] + item = item.to_curve(self._prev, pp) + + if isinstance(item, Curve): + # Curves are cut into three tuples for the super path. + item = item.to_bez() + + if not isinstance(item, list): + raise ValueError("Unknown super curve item type: {}".format(item)) + + if len(item) != 3 or not all([len(bit) == 2 for bit in item]): + # The item is already a subpath (usually from some other process) + if len(item[0]) == 3 and all([len(bit) == 2 for bit in item[0]]): + super(CubicSuperPath, self).append(self._clean(item)) + self._prev_prev = Vector2d(self[-1][-1][0]) + self._prev = Vector2d(self[-1][-1][1]) + return + raise ValueError("Unknown super curve list format: {}".format(item)) + + if self._closed: + # Closed means that the previous segment is closed so we need a new one + # We always append to the last open segment. CSP starts out closed. + self._closed = False + super(CubicSuperPath, self).append([]) + + if self[-1]: + # The last tuple is replaced, it's the coords of where the next segment will land. + self[-1][-1][-1] = item[0][:] + # The last coord is duplicated, but is expected to be replaced + self[-1].append(item[1:] + copy.deepcopy(item)[-1:]) + + self._prev = Vector2d(self[-1][-1][1]) + if not is_quadratic: + self._prev_prev = Vector2d(self[-1][-1][0]) + + def _clean(self, lst): + """Recursively clean lists so they have the same type""" + if isinstance(lst, (tuple, list)): + return [self._clean(child) for child in lst] + return lst + + @property + def _first(self): + try: + return Vector2d(self[-1][0][0]) + except IndexError: + return Vector2d() + + def to_path(self, curves_only=False): + """Convert the super path back to an svg path""" + return Path(list(self.to_segments(curves_only))) + + def to_segments(self, curves_only=False): + """Generate a set of segments for this cubic super path""" + for subpath in self: + previous = [] + for segment in subpath: + if not previous: + yield Move(*segment[1][:]) + elif self.is_line(previous, segment) and not curves_only: + if segment is subpath[-1] and Vector2d(segment[1]).is_close(subpath[0][1]): + yield ZoneClose() + else: + yield Line(*segment[1][:]) + else: + yield Curve(*(previous[2][:] + segment[0][:] + segment[1][:])) + previous = segment + + def transform(self, transform): + """Apply a transformation matrix to this super path""" + return self.to_path().transform(transform).to_superpath() + + @staticmethod + def is_line(previous, segment): + """Check whether csp segment (two points) has retracted handles.""" + return Vector2d(previous[1]).is_close(previous[2]) and \ + Vector2d(segment[0]).is_close(segment[1]) + +def arc_to_path(point, params): + """Approximates an arc with cubic bezier segments. + + Arguments: + point: Starting point (absolute coords) + params: Arcs parameters as per + https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands + + Returns a list of triplets of points : [control_point_before, node, control_point_after] + (first and last returned triplets are [p1, p1, *] and [*, p2, p2]) + """ + A = point[:] + rx, ry, teta, longflag, sweepflag, x2, y2 = params[:] + teta = teta * pi / 180.0 + B = [x2, y2] + # Degenerate ellipse + if rx == 0 or ry == 0 or A == B: + return [[A[:], A[:], A[:]], [B[:], B[:], B[:]]] + + # turn coordinates so that the ellipse morph into a *unit circle* (not 0-centered) + mat = matprod((rotmat(teta), [[1.0 / rx, 0.0], [0.0, 1.0 / ry]], rotmat(-teta))) + applymat(mat, A) + applymat(mat, B) + + k = [-(B[1] - A[1]), B[0] - A[0]] + d = k[0] * k[0] + k[1] * k[1] + k[0] /= sqrt(d) + k[1] /= sqrt(d) + d = sqrt(max(0, 1 - d / 4.0)) + # k is the unit normal to AB vector, pointing to center O + # d is distance from center to AB segment (distance from O to the midpoint of AB) + # for the last line, remember this is a unit circle, and kd vector is ortogonal to AB (Pythagorean thm) + + if longflag == sweepflag: # top-right ellipse in SVG example https://www.w3.org/TR/SVG/images/paths/arcs02.svg + d *= -1 + + O = [(B[0] + A[0]) / 2.0 + d * k[0], (B[1] + A[1]) / 2.0 + d * k[1]] + OA = [A[0] - O[0], A[1] - O[1]] + OB = [B[0] - O[0], B[1] - O[1]] + start = acos(OA[0] / norm(OA)) + if OA[1] < 0: + start *= -1 + end = acos(OB[0] / norm(OB)) + if OB[1] < 0: + end *= -1 + # start and end are the angles from center of the circle to A and to B respectively + + if sweepflag and start > end: + end += 2 * pi + if (not sweepflag) and start < end: + end -= 2 * pi + + NbSectors = int(abs(start - end) * 2 / pi) + 1 + dTeta = (end - start) / NbSectors + v = 4 * tan(dTeta / 4.) / 3. + # I would use v = tan(dTeta/2)*4*(sqrt(2)-1)/3 ? + p = [] + for i in range(0, NbSectors + 1, 1): + angle = start + i * dTeta + v1 = [O[0] + cos(angle) - (-v) * sin(angle), O[1] + sin(angle) + (-v) * cos(angle)] + pt = [O[0] + cos(angle), O[1] + sin(angle)] + v2 = [O[0] + cos(angle) - v * sin(angle), O[1] + sin(angle) + v * cos(angle)] + p.append([v1, pt, v2]) + p[0][0] = p[0][1][:] + p[-1][2] = p[-1][1][:] + + # go back to the original coordinate system + mat = matprod((rotmat(teta), [[rx, 0], [0, ry]], rotmat(-teta))) + for pts in p: + applymat(mat, pts[0]) + applymat(mat, pts[1]) + applymat(mat, pts[2]) + return p + + +def matprod(mlist): + """Get the product of the mat""" + prod = mlist[0] + for mat in mlist[1:]: + a00 = prod[0][0] * mat[0][0] + prod[0][1] * mat[1][0] + a01 = prod[0][0] * mat[0][1] + prod[0][1] * mat[1][1] + a10 = prod[1][0] * mat[0][0] + prod[1][1] * mat[1][0] + a11 = prod[1][0] * mat[0][1] + prod[1][1] * mat[1][1] + prod = [[a00, a01], [a10, a11]] + return prod + + +def rotmat(teta): + """Rotate the mat""" + return [[cos(teta), -sin(teta)], [sin(teta), cos(teta)]] + + +def applymat(mat, point): + """Apply the given mat""" + x = mat[0][0] * point[0] + mat[0][1] * point[1] + y = mat[1][0] * point[0] + mat[1][1] * point[1] + point[0] = x + point[1] = y + + +def norm(point): + """Normalise""" + return sqrt(point[0] * point[0] + point[1] * point[1]) diff --git a/share/extensions/inkex/ports.py b/share/extensions/inkex/ports.py new file mode 100644 index 0000000..f7c5f5d --- /dev/null +++ b/share/extensions/inkex/ports.py @@ -0,0 +1,100 @@ +# coding=utf-8 +# +# Copyright (C) 2019 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Common access to serial and other computer ports. +""" + +import os +import sys +import time +from .utils import DependencyError, AbortExtension + +try: + import serial + from serial.tools import list_ports +except ImportError: + serial = None + +class Serial(object): + """ + Attempt to get access to the computer's serial port. + + with Serial(port_name, ...) as com: + com.write(...) + + Provides access to the debug/testing ports which are pretend ports + able to accept the same input but allow for debugging. + """ + def __init__(self, port, baud=9600, timeout=0.1, **options): + self.test = port == '[test]' + if self.test: + import pty # This does not work on windows + self.controller, self.peripheral = pty.openpty() + port = os.ttyname(self.peripheral) + + self.has_serial() + self.com = serial.Serial() + self.com.port = port + self.com.baudrate = int(baud) + self.com.timeout = timeout + self.set_options(**options) + + def set_options(self, stop=1, size=8, flow=None, parity=None): + """Set further options on the serial port""" + size = {5: 'five', 6: 'six', 7: 'seven', 8: 'eight'}.get(size, size) + stop = {'onepointfive': 1.5}.get(stop.lower(), stop) + stop = {1: 'one', 1.5: 'one_point_five', 2: 'two'}.get(stop, stop) + self.com.bytesize = getattr(serial, str(str(size).upper()) + 'BITS') + self.com.stopbits = getattr(serial, 'STOPBITS_' + str(stop).upper()) + self.com.parity = getattr(serial, 'PARITY_' + str(parity).upper()) + # set flow control + self.com.xonxoff = flow == 'xonxoff' + self.com.rtscts = flow in ('rtscts', 'dsrdtrrtscts') + self.com.dsrdtr = flow == 'dsrdtrrtscts' + + def __enter__(self): + try: + # try to establish connection + self.com.open() + except serial.SerialException: + raise AbortExtension("Could not open serial port. Please check your device"\ + " is running, connected and the settings are correct") + return self.com + + def __exit__(self, exc, value, traceback): + if not traceback and self.test: + output = ' ' * 1024 + while len(output) == 1024: + time.sleep(0.01) + output = os.read(self.controller, 1024) + sys.stderr.write(output.decode('utf8')) + #self.com.read(2) + self.com.close() + + @staticmethod + def has_serial(): + """Late importing of pySerial module""" + if serial is None: + raise DependencyError("pySerial is required to open serial ports.") + + @staticmethod + def list_ports(): + """Return a list of available serial ports""" + Serial.has_serial() # Cause DependencyError error + return [hw.name for hw in list_ports.comports(True)] diff --git a/share/extensions/inkex/styles.py b/share/extensions/inkex/styles.py new file mode 100644 index 0000000..4d9e2f7 --- /dev/null +++ b/share/extensions/inkex/styles.py @@ -0,0 +1,380 @@ +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Two simple functions for working with inline css +and some color handling on top. +""" + +import re +from collections import OrderedDict + +from .utils import PY3 +from .colors import Color, ColorIdError +from .tween import interpcoord, interpunit + +if PY3: + unicode = str # pylint: disable=redefined-builtin,invalid-name + +class Classes(list): + """A list of classes applied to an element (used in css and js)""" + def __init__(self, classes=None, callback=None): + self.callback = None + if isinstance(classes, (str, unicode)): + classes = classes.split() + super(Classes, self).__init__(classes or ()) + self.callback = callback + + def __str__(self): + return " ".join(self) + + def _callback(self): + if self.callback is not None: + self.callback(self) + + def __setitem__(self, index, value): + super(Classes, self).__setitem__(index, value) + self._callback() + + def append(self, value): + value = str(value) + if value not in self: + super(Classes, self).append(value) + self._callback() + + def remove(self, value): + value = str(value) + if value in self: + super(Classes, self).remove(value) + self._callback() + + def toggle(self, value): + """If exists, remove it, if not, add it""" + value = str(value) + if value in self: + return self.remove(value) + return self.append(value) + +class Style(OrderedDict): + """A list of style directives""" + color_props = ('stroke', 'fill', 'stop-color', 'flood-color', 'lighting-color') + opacity_props = ('stroke-opacity', 'fill-opacity', 'opacity', 'stop-opacity') + unit_props = ('stroke-width') + + def __init__(self, style=None, callback=None, **kw): + # This callback is set twice because this is 'pre-initial' data (no callback) + self.callback = None + # Either a string style or kwargs (with dashes as underscores). + style = style or [(k.replace('_', '-'), v) for k, v in kw.items()] + if isinstance(style, (str, unicode)): + style = self.parse_str(style) + # Order raw dictionaries so tests can be made reliable + if isinstance(style, dict) and not isinstance(style, OrderedDict): + style = [(name, style[name]) for name in sorted(style)] + # Should accept dict, Style, parsed string, list etc. + super(Style, self).__init__(style) + # Now after the initial data, the callback makes sense. + self.callback = callback + + @staticmethod + def parse_str(style): + """Create a dictionary from the value of an inline style attribute""" + if style is None: + style = "" + for directive in style.split(';'): + if ':' in directive: + (name, value) = directive.split(':', 1) + # FUTURE: Parse value here for extra functionality + yield (name.strip().lower(), value.strip()) + + def __str__(self): + """Format an inline style attribute from a dictionary""" + return self.to_str() + + def to_str(self, sep=";"): + """Convert to string using a custom delimiter""" + return sep.join(["{0}:{1}".format(*seg) for seg in self.items()]) + + def __add__(self, other): + """Add two styles together to get a third, composing them""" + ret = self.copy() + ret.update(Style(other)) + return ret + + def __iadd__(self, other): + """Add style to this style, the same as style.update(dict)""" + self.update(other) + return self + + def __sub__(self, other): + """Remove keys and return copy""" + ret = self.copy() + ret.__isub__(other) + return ret + + def __isub__(self, other): + """Remove keys from this style, list of keys or other style dictionary""" + for key in other: + self.pop(key, None) + return self + + def __eq__(self, other): + """Not equals, prefer to overload 'in' but that doesn't seem possible""" + if not isinstance(other, Style): + other = Style(other) + for arg in set(self) | set(other): + if self.get(arg, None) != other.get(arg, None): + return False + return True + __ne__ = lambda self, other: not self.__eq__(other) + + def update(self, other): + """Make sure callback is called when updating""" + super(Style, self).update(Style(other)) + if self.callback is not None: + self.callback(self) + + def __setitem__(self, key, value): + super(Style, self).__setitem__(key, value) + if self.callback is not None: + self.callback(self) + + def get_color(self, name='fill'): + """Get the color AND opacity as one Color object""" + color = Color(self.get(name, 'none')) + return color.to_rgba(self.get(name + '-opacity', 1.0)) + + def set_color(self, color, name='fill'): + """Sets the given color AND opacity as rgba to the fill or stroke style properties.""" + color = Color(color) + if color.space == 'rgba': + self[name + '-opacity'] = color.alpha + self[name] = str(color.to_rgb()) + + def update_urls(self, old_id, new_id): + """Find urls in this style and replace them with the new id""" + for (name, value) in self.items(): + if value == 'url(#{})'.format(old_id): + self[name] = 'url(#{})'.format(new_id) + + def interpolate_prop(self, other, fraction, prop, svg=None): + """Interpolate specific property.""" + a1 = self[prop] + a2 = other.get(prop, None) + if a2 is None: + val = a1 + else: + if prop in self.color_props: + if isinstance(a1, Color): + val = a1.interpolate(Color(a2), fraction) + elif a1.startswith('url(') or a2.startswith('url('): + # gradient requires changes to the whole svg + # and needs to be handled externally + val = a1 + else: + val = Color(a1).interpolate(Color(a2), fraction) + elif prop in self.opacity_props: + val = interpcoord(float(a1), float(a2), fraction) + elif prop in self.unit_props: + val = interpunit(a1, a2, fraction) + else: + val = a1 + return val + + def interpolate(self, other, fraction): + # type: (Style, float) -> Style + """Interpolate all properties.""" + style = Style() + for prop, value in self.items(): + style[prop] = self.interpolate_prop(other, fraction, prop) + return style + + +class AttrFallbackStyle(object): + """ + A container for a style and an element that may have competing styles + + If move is set to true, any new values are set to the style attribute + and removed from the element attributes list. + """ + # TODO: This doesn't cover iterating over styles, because we don't + # have a list of known styles to check attribs for. + def __init__(self, elem, move=False): + self.elem = elem + self.styles = [elem.style] + self.styles.extend(elem.root.stylesheets.lookup(elem.get('id'))) + self.move = move + + def __getitem__(self, name): + # Style is more improtant, followed by the element + for style in self.styles: + if name in style: + return style[name] + return self.elem.attrib.get(name, None) + + def __setitem__(self, name, value): + # Set the item back into the attribs, or move it if requested. + if name in self.elem.attrib: + # The other reason to unset the attrib is if it's already in + # the style dictionary so isn't needed here anyway. + if not self.move and name not in self.styles[0]: + self.elem.set(name, value) + return + self.elem.set(name, None) + for style in self.styles: + if name in style: + style[name] = value + return + # Not set before (anywhere), so set to element style + self.styles[0][name] = value + + def get(self, name, default=None): + """Get with default""" + try: + return self[name] + except KeyError: + return default + + def set(self, name, value): + """Set, nothing fancy""" + self[name] = value + +class StyleSheets(list): + """ + Special mechanism which contains all the stylesheets for an svg document + while also caching lookups for specific elements. + + This caching is needed because data can't be attached to elements as they are + re-created on the fly by lxml so lookups have to be centralised. + """ + def __init__(self, svg=None): + super(StyleSheets, self).__init__() + self.svg = svg + + def lookup(self, element_id, svg=None): + """ + Find all styles for this element. + """ + # This is aweful, but required because we can't know for sure + # what might have changed in the xml tree. + if svg is None: + svg = self.svg + for sheet in self: + for style in sheet.lookup(element_id, svg=svg): + yield style + +class StyleSheet(list): + """ + A style sheet, usually the CDATA contents of a style tag, but also + a css file used with a css. Will yield multiple Style() classes. + """ + comment_strip = re.compile(r"//.*?\n") + + def __init__(self, content=None, callback=None): + super(StyleSheet, self).__init__() + self.callback = None + # Remove comments + content = self.comment_strip.sub('', (content or '')) + # Parse rules + for block in content.split('}'): + if block: + self.append(block) + self.callback = callback + + def __str__(self): + return '\n' + '\n'.join([str(style) for style in self]) + '\n' + + def _callback(self, style=None): # pylint: disable=unused-argument + if self.callback is not None: + self.callback(self) + + def add(self, rule, style): + """Append a rule and style combo to this stylesheet""" + self.append(ConditionalStyle(rules=rule, style=str(style), callback=self._callback)) + + def append(self, other): + """Make sure callback is called when updating""" + if isinstance(other, str): + if '{' not in other: + return # Warning? + rules, style = other.strip('}').split('{', 1) + other = ConditionalStyle(rules=rules, style=style.strip(), callback=self._callback) + super(StyleSheet, self).append(other) + self._callback() + + def lookup(self, element_id, svg): + """Lookup the element_id against all the styles in this sheet""" + for style in self: + for elem in svg.xpath(style.to_xpath()): + if elem.get('id', None) == element_id: + yield style + +class ConditionalStyle(Style): + """ + Just like a Style object, but includes one or more + conditional rules which places this style in a stylesheet + rather than being an attribute style. + """ + def __init__(self, rules='*', style=None, callback=None, **kwargs): + super(ConditionalStyle, self).__init__(style=style, callback=callback, **kwargs) + self.rules = [ConditionalRule(rule) for rule in rules.split(',')] + + def __str__(self): + """Return this style as a css entry with class""" + content = self.to_str(";\n ") + rules = ",\n".join(str(rule) for rule in self.rules) + if content: + return "{0} {{\n {1};\n}}".format(rules, content) + return "{0} {{}}".format(rules) + + def to_xpath(self): + """Convert all rules to an xpath""" + # This can be converted to cssselect.CSSSelector (lxml.cssselect) later if we have + # coverage problems. The main reason we're not is that cssselect is doing exactly + # this xpath transform and provides no extra functionality for reverse lookups. + return '|'.join([rule.to_xpath() for rule in self.rules]) + +class ConditionalRule(object): + """A single css rule""" + step_to_xpath = [ + (re.compile(r'\[(\w+)\^=([^\]]+)\]'), r'[starts-with(@\1,\2)]'), # Starts With + (re.compile(r'\[(\w+)\$=([^\]]+)\]'), r'[ends-with(@\1,\2)]'), # Ends With + (re.compile(r'\[(\w+)\*=([^\]]+)\]'), r'[contains(@\1,\2)]'), # Contains + (re.compile(r'\[([^@\(\)\]]+)\]'), r'[@\1]'), # Attribute (start) + (re.compile(r'#(\w+)'), r"[@id='\1']"), # Id Match + (re.compile(r'\s*>\s*([^\s>~\+]+)'), r'/\1'), # Direct child match + #(re.compile(r'\s*~\s*([^\s>~\+]+)'), r'/following-sibling::\1'), + #(re.compile(r'\s*\+\s*([^\s>~\+]+)'), r'/following-sibling::\1[1]'), + (re.compile(r'\s*([^\s>~\+]+)'), r'//\1'), # Decendant match + (re.compile(r'\.(\w+)'), r"[contains(concat(' ', normalize-space(@class), ' '), ' \1 ')]"), + (re.compile(r'//\['), r'//*['), # Attribute only match + (re.compile(r'//(\w+)'), r'//svg:\1'), # SVG namespace addition + ] + + def __init__(self, rule): + self.rule = rule.strip() + + def __str__(self): + return self.rule + + def to_xpath(self): + """Attempt to convert the rule into a simplified xpath""" + ret = self.rule + for matcher, replacer in self.step_to_xpath: + ret = matcher.sub(replacer, ret) + return ret diff --git a/share/extensions/inkex/tester/__init__.py b/share/extensions/inkex/tester/__init__.py new file mode 100644 index 0000000..f33f695 --- /dev/null +++ b/share/extensions/inkex/tester/__init__.py @@ -0,0 +1,384 @@ +# coding=utf-8 +# +# Copyright (C) 2018-2019 Martin Owens +# 2019 Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +All Inkscape extensions should come with tests. This package provides you with +the tools needed to create tests and thus ensure that your extension continues +to work with future versions of Inkscape, the "inkex" python modules, and other +python and non-python tools you may use. + +Make sure your extension is a python extension and is using the `inkex.generic` +base classes. These provide the greatest amount of functionality for testing. + +You should start by creating a folder in your repository called `tests` with +an empty file inside called `__init__.py` to turn it into a module folder. + +For each of your extensions, you should create a file called +`test_{extension_name}.py` where the name reflects the name of your extension. + +There are two types of tests: + + 1. Full-process Comparison tests - These are tests which invoke your + extension with various arguments and attempt to compare the + output to a known good reference. These are useful for testing + that your extension would work if it was used in Inkscape. + + Good example of writing comparison tests can be found in the + Inkscape core repository, each test which inherits from + the ComparisonMixin class is running comparison tests. + + 2. Unit tests - These are individual test functions which call out to + specific functions within your extension. These are typical + python unit tests and many good python documents exist + to describe how to write them well. For examples here you + can find the tests that test the inkex modules themselves + to be the most instructive. + +When running a test, it will cause a certain fraction of the code within the +extension to execute. This fraction called it's **coverage** and a higher +coverage score indicates that your test is better at exercising the various +options, features, and branches within your code. + +Generating comparison output can be done using the EXPORT_COMPARE environment +variable when calling pytest. For example: + + EXPORT_COMPARE=1 pytest tests/test_my_specific_test.py + +This will create files in `tests/data/refs/*.out.export` and these files should +be manually checked to make sure they are correct before being renamed and stripped +of the `.export` suffix. pytest should then be re-run to confirm before +committing to the repository. +""" + +from __future__ import absolute_import, print_function, unicode_literals + +import os +import re +import sys +import shutil +import tempfile +import hashlib +import random +import uuid + +from io import BytesIO, StringIO +import xml.etree.ElementTree as xml + +from unittest import TestCase as BaseCase +from inkex.base import InkscapeExtension + +from ..utils import PY3, to_bytes +from .xmldiff import xmldiff +from .mock import MockCommandMixin, Capture + +if False: # pylint: disable=using-constant-test + from typing import Type, List + from .filters import Compare + + +class NoExtension(InkscapeExtension): # pylint: disable=too-few-public-methods + """Test case must specify 'self.effect_class' to assertEffect.""" + + def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called + raise NotImplementedError(self.__doc__) + + def run(self, args=None, output=None): + """Fake run""" + pass + + +class TestCase(MockCommandMixin, BaseCase): + """ + Base class for all effects tests, provides access to data_files and test_without_parameters + """ + effect_class = NoExtension # type: Type[InkscapeExtension] + effect_name = property(lambda self: self.effect_class.__module__) + + # If set to true, the output is not expected to be the stdout SVG document, but rather + # text or a message sent to the stderr, this is highly weird. But sometimes happens. + stderr_output = False + stdout_protect = True + stderr_protect = True + python3_only = False + + def __init__(self, *args, **kw): + super(TestCase, self).__init__(*args, **kw) + self._temp_dir = None + self._effect = None + + def setUp(self): # pylint: disable=invalid-name + """Make sure every test is seeded the same way""" + self._effect = None + super(TestCase, self).setUp() + if self.python3_only and not PY3: + self.skipTest("No available in python2") + try: + # python3, with version 1 to get the same numbers + # as in python2 during tests. + random.seed(0x35f, version=1) + except TypeError: + # But of course this kwarg doesn't exist in python2 + random.seed(0x35f) + + def tearDown(self): + super(TestCase, self).tearDown() + if self._temp_dir and os.path.isdir(self._temp_dir): + shutil.rmtree(self._temp_dir) + + @classmethod + def __file__(cls): + """Create a __file__ property which acts much like the module version""" + return os.path.abspath(sys.modules[cls.__module__].__file__) + + @classmethod + def _testdir(cls): + """Get's the folder where the test exists (so data can be found)""" + return os.path.dirname(cls.__file__()) + + @classmethod + def rootdir(cls): + """Return the full path to the extensions directory""" + return os.path.dirname(cls._testdir()) + + @classmethod + def datadir(cls): + """Get the data directory (can be over-ridden if needed)""" + return os.path.join(cls._testdir(), 'data') + + @property + def tempdir(self): + """Generate a temporary location to store files""" + if self._temp_dir is None: + self._temp_dir = tempfile.mkdtemp(prefix='inkex-tests-') + if not os.path.isdir(self._temp_dir): + raise IOError("The temporary directory has disappeared!") + return self._temp_dir + + def temp_file(self, prefix='file-', template='{prefix}{name}{suffix}', suffix='.tmp'): + """Generate the filename of a temporary file""" + filename = template.format(prefix=prefix, suffix=suffix, name=uuid.uuid4().hex) + return os.path.join(self.tempdir, filename) + + @classmethod + def data_file(cls, filename, *parts): + """Provide a data file from a filename, can accept directories as arguments.""" + if os.path.isabs(filename): + # Absolute root was passed in, so we trust that (it might be a tempdir) + full_path = os.path.join(filename, *parts) + else: + # Otherwise we assume it's relative to the test data dir. + full_path = os.path.join(cls.datadir(), filename, *parts) + + if not os.path.isfile(full_path): + raise IOError("Can't find test data file: {}".format(full_path)) + return full_path + + @property + def empty_svg(self): + """Returns a common minimal svg file""" + return self.data_file('svg', 'default-inkscape-SVG.svg') + + def assertAlmostTuple(self, found, expected, precision=8): # pylint: disable=invalid-name + """ + Floating point results may vary with computer architecture; use + assertAlmostEqual to allow a tolerance in the result. + """ + self.assertEqual(len(found), len(expected)) + for fon, exp in zip(found, expected): + self.assertAlmostEqual(fon, exp, precision) + + def assertEffectEmpty(self, effect, **kwargs): # pylint: disable=invalid-name + """Assert calling effect without any arguments""" + self.assertEffect(effect=effect, **kwargs) + + def assertEffect(self, *filename, **kwargs): # pylint: disable=invalid-name + """Assert an effect, capturing the output to stdout. + + filename should point to a starting svg document, default is empty_svg + """ + effect = kwargs.pop('effect', self.effect_class)() + + args = [self.data_file(*filename)] if filename else [self.empty_svg] # pylint: disable=no-value-for-parameter + args += kwargs.pop('args', []) + args += ['--{}={}'.format(*kw) for kw in kwargs.items()] + + # Output is redirected to this string io buffer + if self.stderr_output: + with Capture('stderr') as stderr: + effect.run(args, output=BytesIO()) + effect.test_output = stderr + else: + output = BytesIO() + with Capture('stdout', kwargs.get('stdout_protect', self.stdout_protect)) as stdout: + with Capture('stderr', kwargs.get('stderr_protect', self.stderr_protect)) as stderr: + effect.run(args, output=output) + self.assertEqual('', stdout.getvalue(), "Extra print statements detected") + self.assertEqual('', stderr.getvalue(), "Extra error or warnings detected") + effect.test_output = output + + if os.environ.get('FAIL_ON_DEPRECATION', False): + warnings = getattr(effect, 'warned_about', set()) + effect.warned_about = set() # reset for next test + self.assertFalse(warnings, "Deprecated API is still being used!") + + return effect + + def assertDeepAlmostEqual(self, first, second, places=None, msg=None, delta=None): + if delta is None and places is None: + places = 7 + if isinstance(first, (list, tuple)): + assert len(first) == len(second) + for (f, s) in zip(first, second): + self.assertDeepAlmostEqual(f, s, places, msg, delta) + else: + self.assertAlmostEqual(first, second, places, msg, delta) + + @property + def effect(self): + """Generate an effect object""" + if self._effect is None: + self._effect = self.effect_class() + return self._effect + +class InkscapeExtensionTestMixin(object): + """Automatically setup self.effect for each test and test with an empty svg""" + def setUp(self): # pylint: disable=invalid-name + """Check if there is an effect_class set and create self.effect if it is""" + super(InkscapeExtensionTestMixin, self).setUp() + if self.effect_class is None: + self.skipTest('self.effect_class is not defined for this this test') + + def test_default_settings(self): + """Extension works with empty svg file""" + self.effect.run([self.empty_svg]) + +class ComparisonMixin(object): + """ + Add comparison tests to any existing test suite. + """ + # This input svg file sent to the extension (if any) + compare_file = 'svg/shapes.svg' + # The ways in which the output is filtered for comparision (see filters.py) + compare_filters = [] # type: List[Compare] + # If true, the filtered output will be saved and only applied to the + # extension output (and not to the reference file) + compare_filter_save = False + # A list of comparison runs, each entry will cause the extension to be run. + comparisons = [ + (), + ('--id=p1', '--id=r3'), + ] + + def test_all_comparisons(self): + """Testing all comparisons""" + if not isinstance(self.compare_file, (list, tuple)): + self._test_comparisons(self.compare_file) + else: + for compare_file in self.compare_file: + self._test_comparisons( + compare_file, + addout=os.path.basename(compare_file) + ) + + def _test_comparisons(self, compare_file, addout=None): + for args in self.comparisons: + self.assertCompare( + compare_file, + self.get_compare_outfile(args, addout), + args, + ) + + def assertCompare(self, infile, outfile, args): #pylint: disable=invalid-name + """ + Compare the output of a previous run against this one. + + - infile: The filename of the pre-processed svg (or other type of file) + - outfile: The filename of the data we expect to get, if not set + the filename will be generated from the effect name and kwargs. + - args: All the arguments to be passed to the effect run + + """ + effect = self.assertEffect(infile, args=args) + + if outfile is None: + outfile = self.get_compare_outfile(args) + + if not os.path.isfile(outfile): + raise IOError("Comparison file {} not found".format(outfile)) + + data_a = effect.test_output.getvalue() + if os.environ.get('EXPORT_COMPARE', False): + with open(outfile + '.export', 'wb') as fhl: + if sys.version_info[0] == 3 and isinstance(data_a, str): + data_a = data_a.encode('utf-8') + fhl.write(self._apply_compare_filters(data_a, True)) + print("Written output: {}.export".format(outfile)) + + data_a = self._apply_compare_filters(data_a) + + with open(outfile, 'rb') as fhl: + data_b = self._apply_compare_filters(fhl.read(), False) + + if isinstance(data_a, bytes) and isinstance(data_b, bytes) \ + and data_a.startswith(b'<') and data_b.startswith(b'<'): + # Late importing + diff_xml, delta = xmldiff(data_a, data_b) + if not delta and not os.environ.get('EXPORT_COMPARE', False): + print('The XML is different, you can save the output using the EXPORT_COMPARE=1'\ + ' envionment variable. This will save the compared file as a ".output" file'\ + ' next to the reference file used in the test.\n') + diff = 'SVG Differences: {}\n\n'.format(outfile) + if os.environ.get('XML_DIFF', False): + diff = '<- ' + diff_xml + else: + for x, (value_a, value_b) in enumerate(delta): + try: + # Take advantage of better text diff in testcase's own asserts. + self.assertEqual(value_a, value_b) + except AssertionError as err: + diff += " {}. {}\n".format(x, str(err)) + self.assertTrue(delta, diff) + else: + # compare any content (non svg) + self.assertEqual(data_a, data_b) + + def _apply_compare_filters(self, data, is_saving=None): + data = to_bytes(data) + # Applying filters flips depending if we are saving the filtered content + # to disk, or filtering during the test run. This is because some filters + # are destructive others are useful for diagnostics. + if is_saving is self.compare_filter_save or is_saving is None: + for cfilter in self.compare_filters: + data = cfilter(data) + return data + + def get_compare_outfile(self, args, addout=None): + """Generate an output file for the arguments given""" + if addout is not None: + args = list(args) + [str(addout)] + opstr = '__'.join(args)\ + .replace(self.tempdir, 'TMP_DIR')\ + .replace(self.datadir(), 'DAT_DIR') + opstr = re.sub(r'[^\w-]', '__', opstr) + if opstr: + if len(opstr) > 127: + # avoid filename-too-long error + opstr = hashlib.md5(opstr.encode('latin1')).hexdigest() + opstr = '__' + opstr + return self.data_file("refs", "{}{}.out".format(self.effect_name, opstr)) diff --git a/share/extensions/inkex/tester/__pycache__/__init__.cpython-39.pyc b/share/extensions/inkex/tester/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..7d6722e Binary files /dev/null and b/share/extensions/inkex/tester/__pycache__/__init__.cpython-39.pyc differ diff --git a/share/extensions/inkex/tester/__pycache__/decorators.cpython-39.pyc b/share/extensions/inkex/tester/__pycache__/decorators.cpython-39.pyc new file mode 100644 index 0000000..6c98af5 Binary files /dev/null and b/share/extensions/inkex/tester/__pycache__/decorators.cpython-39.pyc differ diff --git a/share/extensions/inkex/tester/__pycache__/filters.cpython-39.pyc b/share/extensions/inkex/tester/__pycache__/filters.cpython-39.pyc new file mode 100644 index 0000000..b460938 Binary files /dev/null and b/share/extensions/inkex/tester/__pycache__/filters.cpython-39.pyc differ diff --git a/share/extensions/inkex/tester/__pycache__/inx.cpython-39.pyc b/share/extensions/inkex/tester/__pycache__/inx.cpython-39.pyc new file mode 100644 index 0000000..7f8204a Binary files /dev/null and b/share/extensions/inkex/tester/__pycache__/inx.cpython-39.pyc differ diff --git a/share/extensions/inkex/tester/__pycache__/mock.cpython-39.pyc b/share/extensions/inkex/tester/__pycache__/mock.cpython-39.pyc new file mode 100644 index 0000000..82c6234 Binary files /dev/null and b/share/extensions/inkex/tester/__pycache__/mock.cpython-39.pyc differ diff --git a/share/extensions/inkex/tester/__pycache__/svg.cpython-39.pyc b/share/extensions/inkex/tester/__pycache__/svg.cpython-39.pyc new file mode 100644 index 0000000..439198f Binary files /dev/null and b/share/extensions/inkex/tester/__pycache__/svg.cpython-39.pyc differ diff --git a/share/extensions/inkex/tester/__pycache__/word.cpython-39.pyc b/share/extensions/inkex/tester/__pycache__/word.cpython-39.pyc new file mode 100644 index 0000000..cd16936 Binary files /dev/null and b/share/extensions/inkex/tester/__pycache__/word.cpython-39.pyc differ diff --git a/share/extensions/inkex/tester/__pycache__/xmldiff.cpython-39.pyc b/share/extensions/inkex/tester/__pycache__/xmldiff.cpython-39.pyc new file mode 100644 index 0000000..5e61017 Binary files /dev/null and b/share/extensions/inkex/tester/__pycache__/xmldiff.cpython-39.pyc differ diff --git a/share/extensions/inkex/tester/decorators.py b/share/extensions/inkex/tester/decorators.py new file mode 100644 index 0000000..92d6b4d --- /dev/null +++ b/share/extensions/inkex/tester/decorators.py @@ -0,0 +1,8 @@ +""" +Useful decorators for tests. +""" +import pytest +from inkex.command import is_inkscape_available + +requires_inkscape = pytest.mark.skipif( # pylint: disable=invalid-name + not is_inkscape_available(), reason="Test requires inkscape, but it's not available") diff --git a/share/extensions/inkex/tester/filters.py b/share/extensions/inkex/tester/filters.py new file mode 100644 index 0000000..f23d124 --- /dev/null +++ b/share/extensions/inkex/tester/filters.py @@ -0,0 +1,139 @@ +# +# Copyright (C) 2019 Thomas Holder +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# pylint: disable=too-few-public-methods +# +""" +Comparison filters for use with the ComparisonMixin. + +Each filter should be initialised in the list of +filters that are being used. + +compare_filters = [ + CompareNumericFuzzy(), + CompareOrderIndependentLines(option=yes), +] +""" + +import re +from ..utils import to_bytes + +class Compare(object): + """ + Comparison base class, this acts as a passthrough unless + the filter staticmethod is overwritten. + """ + def __init__(self, **options): + self.options = options + + def __call__(self, content): + return self.filter(content) + + @staticmethod + def filter(contents): + """Replace this filter method with your own filtering""" + return contents + +class CompareNumericFuzzy(Compare): + """ + Turn all numbers into shorter standard formats + + 1.2345678 -> 1.2346 + 1.2300 -> 1.23, 50.0000 -> 50.0 + 50.0 -> 50 + """ + @staticmethod + def filter(contents): + func = lambda m: b'%.3f' % (float(m.group(0))) + contents = re.sub(br'\d+\.\d+', func, contents) + contents = re.sub(br'(\d\.\d+?)0+\b', br'\1', contents) + contents = re.sub(br'(\d)\.0+(?=\D|\b)', br'\1', contents) + return contents + +class CompareWithoutIds(Compare): + """Remove all ids from the svg""" + @staticmethod + def filter(contents): + return re.sub(br' id="([^"]*)"', b'', contents) + +class CompareWithPathSpace(Compare): + """Make sure that path segment commands have spaces around them""" + @staticmethod + def filter(contents): + def func(match): + """We've found a path command, process it""" + new = re.sub(br'\s*([LZMHVCSQTAatqscvhmzl])\s*', br' \1 ', match.group(1)) + return b' d="' + new.replace(b',', b' ') + b'"' + return re.sub(br' d="([^"]*)"', func, contents) + +class CompareSize(Compare): + """Compare the length of the contents instead of the contents""" + @staticmethod + def filter(contents): + return len(contents) + +class CompareOrderIndependentBytes(Compare): + """Take all the bytes and sort them""" + @staticmethod + def filter(contents): + return b"".join([bytes(i) for i in sorted(contents)]) + +class CompareOrderIndependentLines(Compare): + """Take all the lines and sort them""" + @staticmethod + def filter(contents): + return b"\n".join(sorted(contents.splitlines())) + +class CompareOrderIndependentStyle(Compare): + """Take all styles and sort the results""" + @staticmethod + def filter(contents): + contents = CompareNumericFuzzy.filter(contents) + def func(match): + """Search and replace function for sorting""" + sty = b';'.join(sorted(match.group(1).split(b';'))) + return b'style="%s"' % (sty,) + return re.sub(br'style="([^"]*)"', func, contents) + +class CompareOrderIndependentStyleAndPath(Compare): + """Take all styles and paths and sort them both""" + @staticmethod + def filter(contents): + contents = CompareOrderIndependentStyle.filter(contents) + def func(match): + """Search and replace function for sorting""" + path = b'X'.join(sorted(re.split(br'[A-Z]', match.group(1)))) + return b'd="%s"' % (path,) + return re.sub(br'\bd="([^"]*)"', func, contents) + +class CompareOrderIndependentTags(Compare): + """Sorts all the XML tags""" + @staticmethod + def filter(contents): + return b"\n".join(sorted(re.split(br'>\s*<', contents))) + +class CompareReplacement(Compare): + """Replace pieces to make output more comparable""" + def __init__(self, *replacements): + self.deltas = replacements + super(CompareReplacement, self).__init__() + + def filter(self, contents): + contents = to_bytes(contents) + for _from, _to in self.deltas: + contents = contents.replace(to_bytes(_from), to_bytes(_to)) + return contents diff --git a/share/extensions/inkex/tester/inx.py b/share/extensions/inkex/tester/inx.py new file mode 100644 index 0000000..a30eb7e --- /dev/null +++ b/share/extensions/inkex/tester/inx.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# coding=utf-8 +""" +Test elements extra logic from svg xml lxml custom classes. +""" + +from ..utils import PY3 +from ..inx import InxFile + +INTERNAL_ARGS = ('help', 'output', 'id', 'selected-nodes') +ARG_TYPES = { + 'Boolean': 'bool', + 'Color': 'color', + 'str': 'string', + 'int': 'int', + 'float': 'float', +} + +class InxMixin(object): + """Tools for Testing INX files, use as a mixin class: + + class MyTests(InxMixin, TestCase): + def test_inx_file(self): + self.assertInxIsGood("some_inx_file.inx") + """ + def assertInxIsGood(self, inx_file): # pylint: disable=invalid-name + """Test the inx file for consistancy and correctness""" + self.assertTrue(PY3, "INX files can only be tested in python3") + + inx = InxFile(inx_file) + if 'help' in inx.ident or inx.script.get('interpreter', None) != 'python': + return + cls = inx.extension_class + # Check class can be matched in python file + self.assertTrue(cls, 'Can not find class for {}'.format(inx.filename)) + # Check name is reasonable for the class + if not cls.multi_inx: + self.assertEqual( + cls.__name__, inx.slug, + "Name of extension class {}.{} is different from ident {}".format( + cls.__module__, cls.__name__, inx.slug)) + self.assertParams(inx, cls) + + def assertParams(self, inx, cls): # pylint: disable=invalid-name + """Confirm the params in the inx match the python script""" + params = dict([(param.name, self.parse_param(param)) for param in inx.params]) + args = dict(self.introspect_arg_parser(cls().arg_parser)) + mismatch_a = list(set(params) ^ set(args) & set(params)) + mismatch_b = list(set(args) ^ set(params) & set(args)) + self.assertFalse(mismatch_a, "{}: Inx params missing from arg parser".format(inx.filename)) + self.assertFalse(mismatch_b, "{}: Script args missing from inx xml".format(inx.filename)) + + for param in args: + if params[param]['type'] and args[param]['type']: + self.assertEqual( + params[param]['type'], + args[param]['type'], + "Type is not the same for {}:param:{}".format(inx.filename, param)) + + def introspect_arg_parser(self, arg_parser): + """Pull apart the arg parser to find out what we have in it""" + for action in arg_parser._optionals._actions: # pylint: disable=protected-access + for opt in action.option_strings: + # Ignore params internal to inkscape (thus not in the inx) + if opt.startswith('--') and opt[2:] not in INTERNAL_ARGS: + yield (opt[2:], self.introspect_action(action)) + + @staticmethod + def introspect_action(action): + """Pull apart a single action to get at the juicy insides""" + return { + 'type': ARG_TYPES.get((action.type or str).__name__, 'string'), + 'default': action.default, + 'choices': action.choices, + 'help': action.help, + } + + @staticmethod + def parse_param(param): + """Pull apart the param element in the inx file""" + if param.param_type in ('optiongroup', 'notebook'): + options = param.options + return { + 'type': None, + 'choices': options, + 'default': options and options[0] or None, + } + param_type = param.param_type + if param.param_type in ('path',): + param_type = 'string' + return { + 'type': param_type, + 'default': param.text, + 'choices': None, + } diff --git a/share/extensions/inkex/tester/mock.py b/share/extensions/inkex/tester/mock.py new file mode 100644 index 0000000..2df8791 --- /dev/null +++ b/share/extensions/inkex/tester/mock.py @@ -0,0 +1,414 @@ +# coding=utf-8 +# +# Copyright (C) 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +# pylint: disable=protected-access,too-few-public-methods +""" +Any mocking utilities required by testing. Mocking is when you need the test +to exercise a piece of code, but that code may or does call on something +outside of the target code that either takes too long to run, isn't available +during the test running process or simply shouldn't be running at all. +""" + +import io +import os +import sys +import logging +import hashlib +import tempfile + +from email.mime.application import MIMEApplication +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.parser import Parser as EmailParser + +import inkex.command + +if False: # pylint: disable=using-constant-test + from typing import List, Tuple, Callable, Any # pylint: disable=unused-import + +FIXED_BOUNDARY = '--CALLDATA--//--CALLDATA--' + +class Capture(object): + """Capture stdout or stderr. Used as `with Capture('stdout') as stream:`""" + def __init__(self, io_name='stdout', swap=True): + self.io_name = io_name + self.original = getattr(sys, io_name) + self.stream = io.StringIO() + self.swap = swap + + def __enter__(self): + # We can't control python2 correctly (unicode vs. bytes-like) but + # we don't need it, so we're ignore python2 as if it doesn't exist. + if self.swap: + setattr(sys, self.io_name, self.stream) + return self.stream + + def __exit__(self, exc, value, traceback): + if exc is not None and self.swap: + # Dump content back to original if there was an error. + self.original.write(self.stream.getvalue()) + setattr(sys, self.io_name, self.original) + +class ManualVerbosity(object): + """Change the verbosity of the test suite manually""" + result = property(lambda self: self.test._current_result) + + def __init__(self, test, okay=True, dots=False): + self.test = test + self.okay = okay + self.dots = dots + + def flip(self, exc_type=None, exc_val=None, exc_tb=None): # pylint: disable=unused-argument + """Swap the stored verbosity with the original""" + self.okay, self.result.showAll = self.result.showAll, self.okay + self.dots, self.result.dots = self.result.dots, self.okay + + __enter__ = flip + __exit__ = flip + + +class MockMixin(object): + """ + Add mocking ability to any test base class, will set up mock on setUp + and remove it on tearDown. + + Mocks are stored in an array attached to the test class (not instance!) which + ensures that mocks can only ever be setUp once and can never be reset over + themselves. (just in case this looks weird at first glance) + + class SomeTest(MockingMixin, TestBase): + mocks = [(sys, 'exit', NoSystemExit("Nope!")] + """ + mocks = [] # type: List[Tuple[Any, str, Any]] + + def setUpMock(self, owner, name, new): # pylint: disable=invalid-name + """Setup the mock here, taking name and function and returning (name, old)""" + old = getattr(owner, name) + if isinstance(new, str): + if hasattr(self, new): + new = getattr(self, new) + if isinstance(new, Exception): + def _error_function(*args2, **kw2): # pylint: disable=unused-argument + raise type(new)(str(new)) + setattr(owner, name, _error_function) + elif new is None or isinstance(new, (str, int, float, list, tuple)): + def _value_function(*args, **kw): # pylint: disable=unused-argument + return new + setattr(owner, name, _value_function) + else: + setattr(owner, name, new) + # When we start, mocks contains length 3 tuples, when we're finished, it contains + # length 4, this stops remocking and reunmocking from taking place. + return (owner, name, old, False) + + def setUp(self): # pylint: disable=invalid-name + """For each mock instruction, set it up and store the return""" + super(MockMixin, self).setUp() + for x, mock in enumerate(self.mocks): + if len(mock) == 4: + logging.error("Mock was already set up, so it wasn't cleared previously!") + continue + self.mocks[x] = self.setUpMock(*mock) + + def tearDown(self): # pylint: disable=invalid-name + """For each returned stored, tear it down and restore mock instruction""" + super(MockMixin, self).tearDown() + try: + for x, (owner, name, old, _) in enumerate(self.mocks): + self.mocks[x] = (owner, name, getattr(owner, name)) + setattr(owner, name, old) + except ValueError: + logging.warning("Was never mocked, did something go wrong?") + + def old_call(self, name): + """Get the original caller""" + for arg in self.mocks: + if arg[1] == name: + return arg[2] + return lambda: None + +class MockCommandMixin(MockMixin): + """ + Replace all the command functions with testable replacements. + + This stops the pipeline and people without the programs, running into problems. + """ + mocks = [ + (inkex.command, '_call', 'mock_call'), + (tempfile, 'mkdtemp', 'record_tempdir'), + ] + recorded_tempdirs = [] # type:List[str] + + def setUp(self): # pylint: disable=invalid-name + super(MockCommandMixin, self).setUp() + # This is a the daftest thing I've ever seen, when in the middle + # of a mock, the 'self' variable magically turns from a FooTest + # into a TestCase, this makes it impossible to find the datadir. + from . import TestCase + TestCase._mockdatadir = self.datadir() + + @classmethod + def cmddir(cls): + """Returns the location of all the mocked command results""" + from . import TestCase + return os.path.join(TestCase._mockdatadir, 'cmd') + + def record_tempdir(self, *args, **kwargs): + """Record any attempts to make tempdirs""" + newdir = self.old_call('mkdtemp')(*args, **kwargs) + self.recorded_tempdirs.append(newdir) + return newdir + + def clean_paths(self, data, files): + """Clean a string of any files or tempdirs""" + try: + for fdir in self.recorded_tempdirs: + data = data.replace(fdir, '.') + files = [fname.replace(fdir, '.') for fname in files] + for fname in files: + data = data.replace(fname, os.path.basename(fname)) + except (UnicodeDecodeError, TypeError): + pass + return data + + def get_all_tempfiles(self): + """Returns a set() of all files currently in any of the tempdirs""" + ret = set([]) + for fdir in self.recorded_tempdirs: + if not os.path.isdir(fdir): + continue + for fname in os.listdir(fdir): + if fname in ('.', '..'): + continue + path = os.path.join(fdir, fname) + # We store the modified time so if a program modifies + # the input file in-place, it will look different. + ret.add(path + ';{}'.format(os.path.getmtime(path))) + + return ret + + def ignore_command_mock(self, program, arglst): + """Return true if the mock is ignored""" + if self and program and arglst: + return os.environ.get('NO_MOCK_COMMANDS') + return False + + def mock_call(self, program, *args, **kwargs): + """ + Replacement for the inkex.command.call() function, instead of calling + an external program, will compile all arguments into a hash and use the + hash to find a command result. + """ + # Remove stdin first because it needs to NOT be in the Arguments list. + stdin = kwargs.pop('stdin', None) + args = list(args) + + # We use email + msg = MIMEMultipart(boundary=FIXED_BOUNDARY) + msg['Program'] = self.get_program_name(program) + + # Gather any output files and add any input files to msg, args and kwargs + # may be modified to strip out filename directories (which change) + inputs, outputs = self.add_call_files(msg, args, kwargs) + + arglst = inkex.command.to_args(program, *args, **kwargs)[1:] + arglst.sort() + argstr = ' '.join(arglst) + argstr = self.clean_paths(argstr, inputs + outputs) + msg['Arguments'] = argstr.strip() + + if stdin is not None: + # The stdin is counted as the msg body + cleanin = self.clean_paths(stdin, inputs + outputs) + msg.attach(MIMEText(cleanin, 'plain', 'utf-8')) + + keystr = msg.as_string() + # There is a difference between python2 and python3 output + keystr = keystr.replace('\n\n', '\n') + keystr = keystr.replace('\n ', ' ') + if 'verb' in keystr: + # Verbs seperated by colons cause diff in py2/3 + keystr = keystr.replace('; ', ';') + # Generate a unique key for this call based on _all_ it's inputs + key = hashlib.md5(keystr.encode('utf-8')).hexdigest() + + if self.ignore_command_mock(program, arglst): + # Call original code. This is so programmers can run the test suite + # against the external programs too, to see how their fair. + if stdin is not None: + kwargs['stdin'] = stdin + + before = self.get_all_tempfiles() + stdout = self.old_call('_call')(program, *args, **kwargs) + outputs += list(self.get_all_tempfiles() - before) + # Remove the modified time from the call + outputs = [out.rsplit(';', 1)[0] for out in outputs] + + # After the program has run, we collect any file outputs and store + # them, then store any stdout or stderr created during the run. + # A developer can then use this to build new test cases. + reply = MIMEMultipart(boundary=FIXED_BOUNDARY) + reply['Program'] = self.get_program_name(program) + reply['Arguments'] = argstr + self.save_call(program, key, stdout, outputs, reply) + self.save_key(program, key, keystr, 'key') + return stdout + + try: + return self.load_call(program, key, outputs) + except IOError: + self.save_key(program, key, keystr, 'bad-key') + raise IOError("Problem loading call: {}/{} use the environment variable "\ + "NO_MOCK_COMMANDS=1 to call out to the external program and generate "\ + "the mock call file.".format(program, key)) + + def add_call_files(self, msg, args, kwargs): + """ + Gather all files, adding input files to the msg (for hashing) and + output files to the returned files list (for outputting in debug) + """ + # Gather all possible string arguments together. + loargs = sorted(kwargs.items(), key=lambda i: i[0]) + values = [] + for arg in args: + if isinstance(arg, (tuple, list)): + loargs.append(arg) + else: + values.append(str(arg)) + + for (_, value) in loargs: + if isinstance(value, (tuple, list)): + for val in value: + if val is not True: + values.append(str(val)) + elif value is not True: + values.append(str(value)) + + # See if any of the strings could be filenames, either going to be + # or are existing files on the disk. + files = [[], []] + for value in values: + if os.path.isfile(value): # Input file + files[0].append(value) + self.add_call_file(msg, value) + elif os.path.isdir(os.path.dirname(value)): # Output file + files[1].append(value) + return files + + def add_call_file(self, msg, filename): + """Add a single file to the given mime message""" + fname = os.path.basename(filename) + with open(filename, "rb") as fhl: + if filename.endswith('.svg'): + value = self.clean_paths(fhl.read().decode('utf8'), []) + else: + value = fhl.read() + part = MIMEApplication(value, Name=fname) + # After the file is closed + part['Content-Disposition'] = 'attachment' + part['Filename'] = fname + msg.attach(part) + + def get_call_filename(self, program, key, create=False): + """ + Get the filename for the call testing information. + """ + path = self.get_call_path(program, create=create) + fname = os.path.join(path, key + '.msg') + if not create and not os.path.isfile(fname): + raise IOError("Attempted to find call test data {}".format(key)) + return fname + + def get_program_name(self, program): + """Takes a program and returns a program name""" + if program == inkex.command.INKSCAPE_EXECUTABLE_NAME: + return 'inkscape' + return program + + def get_call_path(self, program, create=True): + """Get where this program would store it's test data""" + command_dir = os.path.join(self.cmddir(), self.get_program_name(program)) + if not os.path.isdir(command_dir): + if create: + os.makedirs(command_dir) + else: + raise IOError("A test is attempting to use an external program in a test:"\ + " {}; but there is not a command data directory which should"\ + " contain the results of the command here: {}"\ + .format(program, command_dir)) + return command_dir + + def load_call(self, program, key, files): + """ + Load the given call + """ + fname = self.get_call_filename(program, key, create=False) + with open(fname, 'rb') as fhl: + msg = EmailParser().parsestr(fhl.read().decode('utf-8')) + + stdout = None + for part in msg.walk(): + if 'attachment' in part.get("Content-Disposition", ''): + base_name = part['Filename'] + for out_file in files: + if out_file.endswith(base_name): + with open(out_file, 'wb') as fhl: + fhl.write(part.get_payload(decode=True)) + part = None + if part is not None: + # Was not caught by any normal outputs, so we will + # save the file to EVERY tempdir in the hopes of + # hitting on of them. + for fdir in self.recorded_tempdirs: + if os.path.isdir(fdir): + with open(os.path.join(fdir, base_name), 'wb') as fhl: + fhl.write(part.get_payload(decode=True)) + elif part.get_content_type() == "text/plain": + stdout = part.get_payload(decode=True) + + return stdout + + def save_call(self, program, key, stdout, files, msg, ext='output'): # pylint: disable=too-many-arguments + """ + Saves the results from the call into a debug output file, the resulting files + should be a Mime msg file format with each attachment being one of the input + files as well as any stdin and arguments used in the call. + """ + if stdout is not None and stdout.strip(): + # The stdout is counted as the msg body here + msg.attach(MIMEText(stdout.decode('utf-8'), 'plain', 'utf-8')) + + for fname in set(files): + if os.path.isfile(fname): + #print("SAVING FILE INTO MSG: {}".format(fname)) + self.add_call_file(msg, fname) + else: + part = MIMEText("Missing File", 'plain', 'utf-8') + part.add_header('Filename', os.path.basename(fname)) + msg.attach(part) + + fname = self.get_call_filename(program, key, create=True) + '.' + ext + with open(fname, 'wb') as fhl: + fhl.write(msg.as_string().encode('utf-8')) + + def save_key(self, program, key, keystr, ext='key'): + """Save the key file if we are debugging the key data""" + if os.environ.get('DEBUG_KEY'): + fname = self.get_call_filename(program, key, create=True) + '.' + ext + with open(fname, 'wb') as fhl: + fhl.write(keystr.encode('utf-8')) diff --git a/share/extensions/inkex/tester/svg.py b/share/extensions/inkex/tester/svg.py new file mode 100644 index 0000000..d7ef569 --- /dev/null +++ b/share/extensions/inkex/tester/svg.py @@ -0,0 +1,49 @@ +# coding=utf-8 +# +# Copyright (C) 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +SVG specific utilities for tests. +""" + +from lxml import etree + +from inkex import SVG_PARSER + +def svg(svg_attrs=''): + """Returns xml etree based on a simple SVG element. + + svg_attrs: A string containing attributes to add to the + root element of a minimal SVG document. + """ + return etree.fromstring(str.encode( + '' + ''.format(svg_attrs)), parser=SVG_PARSER) + + +def uu_svg(user_unit): + """Same as svg, but takes a user unit for the new document. + + It's based on the ratio between the SVG width and the viewBox width. + """ + return svg('width="1{}" viewBox="0 0 1 1"'.format(user_unit)) + +def svg_file(filename): + """Parse an svg file and return it's document root""" + with open(filename, 'r') as fhl: + doc = etree.parse(fhl, parser=SVG_PARSER) + return doc.getroot() diff --git a/share/extensions/inkex/tester/word.py b/share/extensions/inkex/tester/word.py new file mode 100644 index 0000000..cab7d04 --- /dev/null +++ b/share/extensions/inkex/tester/word.py @@ -0,0 +1,38 @@ +# coding=utf-8 +# +# Unknown author +# +""" +Generate words for testing. +""" + +import string +import random + +def word_generator(text_length): + """ + Generate a word of text_length size + """ + word = "" + + for _ in range(0, text_length): + word += random.choice(string.ascii_lowercase + \ + string.ascii_uppercase + \ + string.digits + \ + string.punctuation) + + return word + +def sentencecase(word): + """Make a word standace case""" + word_new = "" + lower_letters = list(string.ascii_lowercase) + first = True + for letter in word: + if letter in lower_letters and first is True: + word_new += letter.upper() + first = False + else: + word_new += letter + + return word_new diff --git a/share/extensions/inkex/tester/xmldiff.py b/share/extensions/inkex/tester/xmldiff.py new file mode 100644 index 0000000..17f43a4 --- /dev/null +++ b/share/extensions/inkex/tester/xmldiff.py @@ -0,0 +1,113 @@ +# +# Copyright 2011 (c) Ian Bicking +# 2019 (c) Martin Owens +# +# Taken from http://formencode.org under the GPL compatible PSF License. +# Modified to produce more output as a diff. +# +""" +Allow two xml files/lxml etrees to be compared, returning their differences. +""" +import xml.etree.ElementTree as xml +from io import BytesIO + +def text_compare(test1, test2): + """ + Compare two text strings while allowing for '*' to match + anything on either lhs or rhs. + """ + if not test1 and not test2: + return True + if test1 == '*' or test2 == '*': + return True + return (test1 or '').strip() == (test2 or '').strip() + +class DeltaLogger(list): + """A record keeper of the delta between two svg files""" + def append_tag(self, tag_a, tag_b): + """Record a tag difference""" + if tag_a: + tag_a = "<{}.../>".format(tag_a) + if tag_b: + tag_b = "<{}.../>".format(tag_b) + self.append((tag_a, tag_b)) + + def append_attr(self, attr, value_a, value_b): + """Record an attribute difference""" + def _prep(val): + if val: + if attr == 'd': + from inkex.paths import Path + return [attr] + Path(val).to_arrays() + return (attr, val) + return val + self.append((_prep(value_a), _prep(value_b))) + + def append_text(self, text_a, text_b): + """Record a text difference""" + self.append((text_a, text_b)) + + def __bool__(self): + """Returns True if there's no log, i.e. the delta is clean""" + return not self.__len__() + __nonzero__ = __bool__ + + def __repr__(self): + if self: + return "No differences detected" + return "{} xml differences".format(len(self)) + +def to_xml(data): + """Convert string or bytes to xml parsed root node""" + if isinstance(data, str): + data = data.encode('utf8') + if isinstance(data, bytes): + return xml.parse(BytesIO(data)).getroot() + return data + +def xmldiff(data1, data2): + """Create an xml difference, will modify the first xml structure with a diff""" + xml1, xml2 = to_xml(data1), to_xml(data2) + delta = DeltaLogger() + _xmldiff(xml1, xml2, delta) + return xml.tostring(xml1).decode('utf-8'), delta + +def _xmldiff(xml1, xml2, delta): + if xml1.tag != xml2.tag: + xml1.tag = '{}XXX{}'.format(xml1.tag, xml2.tag) + delta.append_tag(xml1.tag, xml2.tag) + for name, value in xml1.attrib.items(): + if name not in xml2.attrib: + delta.append_attr(name, xml1.attrib[name], None) + xml1.attrib[name] += "XXX" + elif xml2.attrib.get(name) != value: + delta.append_attr(name, xml1.attrib.get(name), xml2.attrib.get(name)) + xml1.attrib[name] = "{}XXX{}".format(xml1.attrib.get(name), xml2.attrib.get(name)) + for name, value in xml2.attrib.items(): + if name not in xml1.attrib: + delta.append_attr(name, None, value) + xml1.attrib[name] = "XXX" + value + if not text_compare(xml1.text, xml2.text): + delta.append_text(xml1.text, xml2.text) + xml1.text = "{}XXX{}".format(xml1.text, xml2.text) + if not text_compare(xml1.tail, xml2.tail): + delta.append_text(xml1.tail, xml2.tail) + xml1.tail = "{}XXX{}".format(xml1.tail, xml2.tail) + + # Get children and pad with nulls + children_a = list(xml1) + children_b = list(xml2) + children_a += [None] * (len(children_a) - len(children_b)) + children_b += [None] * (len(children_b) - len(children_a)) + + for child_a, child_b in zip(children_a, children_b): + if child_a is None: # child_b exists + child_c = child_b.clone() + delta.append_tag(child_c.tag, None) + child_c.tag = 'XXX' + child_c.tag + xml1.append(child_c) + elif child_b is None: # child_a exists + delta.append_tag(None, child_a.tag) + child_a.tag += 'XXX' + else: + _xmldiff(child_a, child_b, delta) diff --git a/share/extensions/inkex/transforms.py b/share/extensions/inkex/transforms.py new file mode 100644 index 0000000..89c8563 --- /dev/null +++ b/share/extensions/inkex/transforms.py @@ -0,0 +1,1084 @@ +# coding=utf-8 +# +# Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr +# Copyright (C) 2010 Alvin Penner, penner@vaxxine.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# barraud@math.univ-lille1.fr +# +# This code defines several functions to make handling of transform +# attribute easier. +# +""" +Provide transformation parsing to extensions +""" + +import re +import sys +from decimal import Decimal +from math import cos, radians, sin, sqrt, tan, fabs, atan2, hypot, pi + +try: + from math import isfinite +except ImportError: + isfinite = lambda n: isinstance(n, (int, float)) and n not in (float('+inf'), float('-inf')) + +from .tween import interpcoord +from .utils import strargs, KeyDict, PY3 + +try: + from typing import overload, cast, List, Any, Callable, Generator, Iterator, Tuple, Union, Optional, Sequence # pylint: disable=unused-import + + VectorLike = Union["ImmutableVector2d", Tuple[float, float]] # pylint: disable=invalid-name + MatrixLike = Union[str, Tuple[Tuple[float,float,float], Tuple[float,float,float]], Tuple[float,float,float,float,float,float], "Transform"] + BoundingIntervalArgs = Union['BoundingInterval', Tuple[float, float], float] # pylint: disable=invalid-name +except ImportError: + overload = lambda x: x + cast = lambda x, y: y + +# All the names that get added to the inkex API itself. +__all__ = ( + 'BoundingBox', + 'DirectedLineSegment', + 'ImmutableVector2d', + 'Transform', + 'Vector2d', +) + +if PY3: + unicode = str # pylint: disable=redefined-builtin,invalid-name + +# Old settings, supported because users click 'ok' without looking. +XAN = KeyDict({'l': 'left', 'r': 'right', 'm': 'center_x'}) +YAN = KeyDict({'t': 'top', 'b': 'bottom', 'm': 'center_y'}) +# Anchoring objects with given directions (see inx options) +CUSTOM_DIRECTION = {270: 'tb', 90: 'bt', 0: 'lr', 360: 'lr', 180: 'rl'} +DIRECTION = ['tb', 'bt', 'lr', 'rl', 'ro', 'ri'] + + +class ImmutableVector2d(object): + _x = 0.0 + _y = 0.0 + + x = property(lambda self: self._x) # type: property + y = property(lambda self: self._y) # type: property + + @overload + def __init__(self): + # type: () -> None + pass + + @overload + def __init__(self, v): + # type: (Union[VectorLike, str]) -> None + pass + + @overload + def __init__(self, x, y): + # type: (float, float) -> None + pass + + def __init__(self, *args): + if len(args) == 0: + x, y = 0.0, 0.0 + elif len(args) == 1: + x, y = self._parse(args[0]) + elif len(args) == 2: + x, y = map(float, args) + else: + raise ValueError("too many arguments") + self._x, self._y = float(x), float(y) + + @staticmethod + def _parse(point): + # type: (Union[VectorLike, str]) -> Tuple[float, float] + if isinstance(point, ImmutableVector2d): + x, y = point.x, point.y + elif isinstance(point, (tuple, list)) and len(point) == 2: + x, y = map(float, point) + elif isinstance(point, str) and point.count(',') == 1: + x, y = map(float, point.split(',')) + else: + raise ValueError("Can't parse {}".format(repr(point))) + return x, y + + def __add__(self, other): + # type: (VectorLike) -> Vector2d + other = Vector2d(other) + return Vector2d(self.x + other.x, self.y + other.y) + + def __radd__(self, other): + # type: (VectorLike) -> Vector2d + other = Vector2d(other) + return Vector2d(self.x + other.x, self.y + other.y) + + def __sub__(self, other): + # type: (VectorLike) -> Vector2d + other = Vector2d(other) + return Vector2d(self.x - other.x, self.y - other.y) + + def __rsub__(self, other): + # type: (VectorLike) -> Vector2d + other = Vector2d(other) + return Vector2d(-self.x + other.x, -self.y + other.y) + + def __neg__(self): + # type: () -> Vector2d + return Vector2d(-self.x, -self.y) + + def __pos__(self): + # type: () -> Vector2d + return Vector2d(self.x, self.y) + + def __floordiv__(self, factor): + # type: (float) -> Vector2d + return Vector2d(self.x / float(factor), self.y / float(factor)) + + def __truediv__(self, factor): + # type: (float) -> Vector2d + return Vector2d(self.x / float(factor), self.y / float(factor)) + + def __div__(self, factor): + # type: (float) -> Vector2d + return Vector2d(self.x / float(factor), self.y / float(factor)) + + def __mul__(self, factor): + # type: (float) -> Vector2d + return Vector2d(self.x * factor, self.y * factor) + + def __abs__(self): + # type: () -> float + return self.length + + def __rmul__(self, factor): + # type: (float) -> VectorLike + return Vector2d(self.x * factor, self.y * factor) + + def __repr__(self): + # type: () -> str + return "Vector2d({:.6g}, {:.6g})".format(self.x, self.y) + + def __str__(self): + # type: () -> str + return "{:.6g}, {:.6g}".format(self.x, self.y) + + def __iter__(self): + # type: () -> Generator[float, None, None] + yield self.x + yield self.y + + def __len__(self): + # type: () -> int + return 2 + + def __getitem__(self, item): + # type: (int) -> float + return (self.x, self.y)[item] + + def to_tuple(self): + # type : () -> Tuple[float, float] + return self.x, self.y + + def dot(self, other): + # type: (VectorLike) -> float + other = Vector2d(other) + return self.x * other.x + self.y * other.y + + def is_close(self, other, rtol=1e-5, atol=1e-8): + # type: (Union[VectorLike, str, Tuple[float,float]], float, float) -> float + other = Vector2d(other) + delta = (self - other).length + return delta < (atol + rtol * other.length) + + @property + def length(self): + # type: () -> float + return sqrt(fabs(self.dot(self))) + + +class Vector2d(ImmutableVector2d): + """ + Represents an element of 2-dimensional Euclidean space + """ + + @ImmutableVector2d.x.setter + def x(self, value): + # type: (Union[float, int, str]) -> None + self._x = float(value) + + @ImmutableVector2d.y.setter + def y(self, value): + # type: (Union[float, int, str]) -> None + self._y = float(value) + + def __iadd__(self, other): + # type: (VectorLike) -> Vector2d + other = Vector2d(other) + self.x += other.x + self.y += other.y + return self + + def __isub__(self, other): + # type: (VectorLike) -> Vector2d + other = Vector2d(other) + self.x -= other.x + self.y -= other.y + return self + + def __imul__(self, factor): + # type: (float) -> Vector2d + self.x *= factor + self.y *= factor + return self + + def __idiv__(self, factor): + # type: (float) -> Vector2d + self.x /= factor + self.y /= factor + return self + + def __itruediv__(self, factor): + # type: (float) -> Vector2d + self.x /= factor + self.y /= factor + return self + + def __ifloordiv__(self, factor): + # type: (float) -> Vector2d + self.x /= factor + self.y /= factor + return self + + @overload + def assign(self, x, y): + # type: (float, float) -> None + pass + + @overload + def assign(self, other): + # type: (VectorLike, str) -> None + pass + + def assign(self, *args): + self.x, self.y = Vector2d(*args) + + + +class Transform(object): + """A transformation object which will always reduce to a matrix and can + then be used in combination with other transformations for reducing + finding a point and printing svg ready output. + + Use with svg transform attribute input: + + tr = Transform("scale(45, 32)") + + Use with triad matrix input (internal representation): + + tr = Transform(((1.0, 0.0, 0.0), (0.0, 1.0, 0.0))) + + Use with hexad matrix input (i.e. svg matrix(...)): + + tr = Transform((1.0, 0.0, 0.0, 1.0, 0.0, 0.0)) + + Once you have a transformation you can operate tr * tr to compose, + any of the above inputs are also valid operators for composing. + """ + TRM = re.compile(r'(translate|scale|rotate|skewX|skewY|matrix)\s*\(([^)]*)\)\s*,?') + absolute_tolerance = 1e-5 # type: float + + + def __init__( + self, + matrix=None, # type: Optional[MatrixLike] + callback=None, # type: Optional[Callable[[Transform], Transform]] + **extra): + # type: (...) -> None + self.callback = None + self.matrix = ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0)) + if matrix is not None: + self._set_matrix(matrix) + + self.add_kwargs(**extra) + # Set callback last, so it doesn't kick off just setting up the internal value + self.callback = callback + + def _set_matrix(self, matrix): + # type: (MatrixLike) -> None + """Parse a given string as an svg transformation instruction.""" + if isinstance(matrix, (str, unicode)): + for func, values in self.TRM.findall(matrix.strip()): + getattr(self, 'add_' + func.lower())(*strargs(values)) + elif isinstance(matrix, Transform): + self.matrix = matrix.matrix + elif isinstance(matrix, (tuple, list)) and len(matrix) == 2: + row1 = matrix[0] + row2 = matrix[1] + if isinstance(row1, (tuple, list)) and isinstance(row2, (tuple, list)): + if len(row1) == 3 and len(row2) == 3: + row1 = cast("Tuple[float, float, float]", tuple(map(float, row1))) + row2 = cast("Tuple[float, float, float]", tuple(map(float, row2))) + self.matrix = row1, row2 + else: + raise ValueError("Matrix '{}' is not a valid transformation matrix".format(matrix)) + else: + raise ValueError("Matrix '{}' is not a valid transformation matrix".format(matrix)) + elif isinstance(matrix, (list, tuple)) and len(matrix) == 6: + tmatrix = cast("Union[List[float], Tuple[float,float,float,float,float,float]]", matrix) + row1 = (float(tmatrix[0]), float(tmatrix[2]), float(tmatrix[4])) + row2 = (float(tmatrix[1]), float(tmatrix[3]), float(tmatrix[5])) + self.matrix = row1, row2 + elif not isinstance(matrix, (list, tuple)): + raise ValueError("Invalid transform type: {}".format(type(matrix).__name__)) + else: + raise ValueError("Matrix '{}' is not a valid transformation matrix".format(matrix)) + + + # These provide quick access to the svg matrix: + # + # [ a, c, e ] + # [ b, d, f ] + # + a = property(lambda self: self.matrix[0][0]) # pylint: disable=invalid-name + b = property(lambda self: self.matrix[1][0]) # pylint: disable=invalid-name + c = property(lambda self: self.matrix[0][1]) # pylint: disable=invalid-name + d = property(lambda self: self.matrix[1][1]) # pylint: disable=invalid-name + e = property(lambda self: self.matrix[0][2]) # pylint: disable=invalid-name + f = property(lambda self: self.matrix[1][2]) # pylint: disable=invalid-name + + def __bool__(self): + # type: () -> bool + return not self.__eq__(Transform()) + + __nonzero__ = __bool__ + + @overload + def add_matrix(self, a): + # type: (MatrixLike) -> None + pass + + @overload + def add_matrix(self, a, b, c, d, e, f): + # type: (float, float, float, float, float, float) -> None + pass + + @overload + def add_matrix(self, a, b): + # type: (Tuple[float, float, float], Tuple[float, float, float]) -> None + pass + + def add_matrix(self, *args): + """Add matrix in order they appear in the svg hexad""" + if len(args) == 1: + self.__imul__(Transform(args[0])) + elif len(args) == 2 or len(args) == 6: + self.__imul__(Transform(args)) + else: + raise ValueError("Invalid number of arguments {}".format(args)) + + def add_kwargs(self, **kwargs): + """Add translations, scales, rotations etc using key word arguments""" + for key, value in reversed(list(kwargs.items())): + func = getattr(self, 'add_' + key) + if isinstance(value, tuple): + func(*value) + elif value is not None: + func(value) + + @overload + def add_translate(self, dr): + # type: (VectorLike) -> None + pass + + @overload + def add_translate(self, tr_x, tr_y=0.0): + # type: (float, Optional[float]) -> None + pass + + def add_translate(self, *args): + if len(args) == 1 and isinstance(args[0], (int, float)): + tr_x, tr_y = args[0], 0.0 + else: + tr_x, tr_y = Vector2d(*args) + self.__imul__(((1.0, 0.0, tr_x), (0.0, 1.0, tr_y))) + + def add_scale(self, sc_x, sc_y=None): + """Add scale to this transformation""" + sc_y = sc_x if sc_y is None else sc_y + self.__imul__(((sc_x, 0.0, 0.0), (0.0, sc_y, 0.0))) + + @overload + def add_rotate(self, deg, center): + # type: (float, VectorLike) -> None + pass + + @overload + def add_rotate(self, deg, center_x, center_y): + # type: (float, float, float) -> None + pass + + @overload + def add_rotate(self, deg): + # type: (float) -> None + pass + + @overload + def add_rotate(self, deg, a): + # type: (float, Union[VectorLike, str]) -> None + pass + + @overload + def add_rotate(self, deg, a, b): + # type: (float, float, float) -> None + pass + + def add_rotate(self, deg, *args): + """Add rotation to this transformation""" + center_x, center_y = Vector2d(*args) + _cos, _sin = cos(radians(deg)), sin(radians(deg)) + self.__imul__(((_cos, -_sin, center_x), (_sin, _cos, center_y))) + self.__imul__(((1.0, 0.0, -center_x), (0.0, 1.0, -center_y))) + + def add_skewx(self, deg): + # type: (float) -> None + """Add skew x to this transformation""" + self.__imul__(((1.0, tan(radians(deg)), 0.0), (0.0, 1.0, 0.0))) + + def add_skewy(self, deg): + # type: (float) -> None + """Add skew y to this transformation""" + self.__imul__(((1.0, 0.0, 0.0), (tan(radians(deg)), 1.0, 0.0))) + + def to_hexad(self): + # type: () -> Iterator[float] + """Returns the transform as a hexad matrix (used in svg)""" + return (val for lst in zip(*self.matrix) for val in lst) + + def is_translate(self, exactly=False): + # type: (bool) -> bool + """Returns True if this transformation is ONLY translate""" + tol = self.absolute_tolerance if not exactly else 0.0 + return fabs(self.a - 1) <= tol and abs(self.d - 1) <= tol and fabs(self.b) <= tol and fabs(self.c) <= tol + + def is_scale(self, exactly=False): + # type: (bool) -> bool + """Returns True if this transformation is ONLY scale""" + tol = self.absolute_tolerance if not exactly else 0.0 + return (fabs(self.e) <= tol and fabs(self.f) <= tol and + fabs(self.b) <= tol and fabs(self.c) <= tol) + + def is_rotate(self, exactly=False): + # type: (bool) -> bool + """Returns True if this transformation is ONLY rotate""" + tol = self.absolute_tolerance if not exactly else 0.0 + return self._is_URT(exactly=exactly) and \ + fabs(self.e) <= tol and fabs(self.f) <= tol and fabs(self.a ** 2 + self.b ** 2 - 1) <= tol + + def rotation_degrees(self): + # type: () -> float + """Return the amount of rotation in this transform""" + if not self._is_URT(exactly=False): + raise ValueError("Rotation angle is undefined for non-uniformly scaled or skewed matrices") + return atan2(self.b, self.a) * 180 / pi + + def __str__(self): + # type: () -> str + """Format the given matrix into a string representation for svg""" + hexad = tuple(self.to_hexad()) + if self.is_translate(): + if not self: + return "" + return "translate({:.6g}, {:.6g})".format(self.e, self.f) + elif self.is_scale(): + return "scale({:.6g}, {:.6g})".format(self.a, self.d) + elif self.is_rotate(): + return "rotate({:.6g})".format(self.rotation_degrees()) + return "matrix({})".format(" ".join(format(var, '.6g') for var in hexad)) + + def __repr__(self): + # type: () -> str + """String representation of this object""" + return "{}((({}), ({})))".format( + type(self).__name__, + ', '.join(format(var, '.6g') for var in self.matrix[0]), + ', '.join(format(var, '.6g') for var in self.matrix[1])) + + def __eq__(self, matrix): + # typing this requires writing a proof for mypy that matrix is really + # MatrixLike + """Test if this transformation is equal to the given matrix""" + if isinstance(matrix, (str, tuple, list, Transform)): + val = all(fabs(l - r) <= self.absolute_tolerance + for l, r in zip(self.to_hexad(), Transform(matrix).to_hexad())) + else: + val = False + return val + + def __mul__(self, matrix): + # type: (MatrixLike) -> Transform + """Combine this transform's internal matrix with the given matrix""" + # Conform the input to a known quantity (and convert if needed) + other = Transform(matrix) + # Return a transformation as the combined result + return Transform(( + self.a * other.a + self.c * other.b, + self.b * other.a + self.d * other.b, + self.a * other.c + self.c * other.d, + self.b * other.c + self.d * other.d, + self.a * other.e + self.c * other.f + self.e, + self.b * other.e + self.d * other.f + self.f)) + + def __imul__(self, matrix): + # type: (MatrixLike) -> Transform + """In place multiplication of transform matrices""" + self.matrix = (self * matrix).matrix + if self.callback is not None: + self.callback(self) + return self + + def __neg__(self): + # type: () -> Transform + """Returns an inverted transformation""" + det = (self.a * self.d) - (self.c * self.b) + # invert the rotation/scaling part + new_a = self.d / det + new_d = self.a / det + new_c = -self.c / det + new_b = -self.b / det + # invert the translational part + new_e = -(new_a * self.e + new_c * self.f) + new_f = -(new_b * self.e + new_d * self.f) + return Transform((new_a, new_b, new_c, new_d, new_e, new_f)) + + def apply_to_point(self, point): + # type: (VectorLike) -> Vector2d + """Transform a tuple (X, Y)""" + if isinstance(point, str): + raise ValueError("Will not transform string '{}'".format(point)) + point = Vector2d(point) + return Vector2d(self.a * point.x + self.c * point.y + self.e, + self.b * point.x + self.d * point.y + self.f) + + def _is_URT(self, exactly=False): + # type: (bool) -> bool + """ + Checks that transformation can be decomposed into product of + Uniform scale (U), Rotation around origin (R) and translation (T) + + :return: decomposition as U*R*T is possible + """ + tol = self.absolute_tolerance if not exactly else 0.0 + return (fabs(self.a - self.d) <= tol) and (fabs(self.b + self.c) <= tol) + + def interpolate(self, other, fraction): + # type: (Transform, float) -> Transform + """Interpolate with another Transform.""" + return Transform(( + interpcoord(self.a, other.a, fraction), + interpcoord(self.b, other.b, fraction), + interpcoord(self.c, other.c, fraction), + interpcoord(self.d, other.d, fraction), + interpcoord(self.e, other.e, fraction), + interpcoord(self.f, other.f, fraction))) + + +class BoundingInterval(object): # pylint: disable=too-few-public-methods + """A pair of numbers that represent the minimum and maximum values.""" + + @overload + def __init__(self, other=None): + # type: (Optional[BoundingInterval]) -> None + pass + + @overload + def __init__(self, pair): + # type: (Tuple[float, float]) -> None + pass + + @overload + def __init__(self, value): + # type: (float) -> None + pass + + @overload + def __init__(self, x, y): + # type: (float, float) -> None + pass + + def __init__(self, x=None, y=None): + if y is not None: + if isinstance(x, (int, float, Decimal)) and isinstance(y, (int, float, Decimal)): + self.minimum = x + self.maximum = y + else: + raise ValueError("Not a number for scaling: {} ({},{})" + .format(str((x, y)), type(x).__name__, type(y).__name__)) + + else: + value = x + if value is None: + # identity for addition, zero for intersection + self.minimum, self.maximum = float('+inf'), float('-inf') + elif isinstance(value, BoundingInterval): + self.minimum = value.minimum + self.maximum = value.maximum + elif isinstance(value, (tuple, list)) and len(value) == 2: + self.minimum, self.maximum = min(value), max(value) + elif isinstance(value, (int, float, Decimal)): + self.minimum = self.maximum = value + else: + raise ValueError("Not a number for scaling: {} ({})" + .format(str(value), type(value).__name__)) + + def __bool__(self): + # type: () -> bool + return (isfinite(self.minimum) and isfinite(self.maximum)) + + __nonzero__ = __bool__ + + def __neg__(self): + # type: () -> BoundingInterval + return BoundingInterval((-self.maximum, -self.minimum)) + + def __add__(self, other): + # type: (BoundingInterval) -> BoundingInterval + """Calculate the bounding interval that covers both given bounding intervals""" + new = BoundingInterval(self) + if other is not None: + new += other + return new + + def __iadd__(self, other): + # type: (BoundingInterval) -> BoundingInterval + other = BoundingInterval(other) + self.minimum = min((self.minimum, other.minimum)) + self.maximum = max((self.maximum, other.maximum)) + return self + + def __radd__(self, other): + # type: (BoundingInterval) -> BoundingInterval + if other is None: + return BoundingInterval(self) + return self + other + + def __and__(self, other): + # type: (BoundingInterval) -> BoundingInterval + """Calculate the bounding interval where both given bounding intervals overlap""" + new = BoundingInterval(self) + if other is not None: + new &= other + return new + + def __iand__(self, other): + # type: (BoundingInterval) -> BoundingInterval + other = BoundingInterval(other) + self.minimum = max((self.minimum, other.minimum)) + self.maximum = min((self.maximum, other.maximum)) + if self.minimum > self.maximum: + self.minimum, self.maximum = float('+inf'), float('-inf') + return self + + def __rand__(self, other): + # type: (BoundingInterval) -> BoundingInterval + if other is None: + return BoundingInterval(self) + return self & other + + def __mul__(self, other): + # type: (BoundingInterval) -> BoundingInterval + new = BoundingInterval(self) + if other is not None: + new *= other + return new + + def __imul__(self, other): + # type: (BoundingInterval) -> BoundingInterval + self.minimum *= other + self.maximum *= other + return self + + def __iter__(self): + # type: () -> Generator[BoundingInterval, None, None] + yield self.minimum + yield self.maximum + + def __eq__(self, other): + # type (object) -> bool + return tuple(self) == tuple(BoundingInterval(other)) + + def __contains__(self, value): + # type: (float) -> bool + return self.minimum <= value <= self.maximum + + def __repr__(self): + # type: () -> str + return "BoundingInterval({}, {})".format(self.minimum, self.maximum) + + @property + def center(self): + # type: () -> float + """Pick the middle of the line""" + return self.minimum + ((self.maximum - self.minimum) / 2) + + @property + def size(self): + # type: () -> float + """Return the size difference minimum and maximum""" + return self.maximum - self.minimum + + +class BoundingBox(object): # pylint: disable=too-few-public-methods + """ + Some functions to compute a rough bbox of a given list of objects. + + BoundingBox(other) + BoundingBox(x, y) + BoundingBox((x1, x2), (y1, y2)) + """ + + width = property(lambda self: self.x.size) + height = property(lambda self: self.y.size) + top = property(lambda self: self.y.minimum) + left = property(lambda self: self.x.minimum) + bottom = property(lambda self: self.y.maximum) + right = property(lambda self: self.x.maximum) + center_x = property(lambda self: self.x.center) + center_y = property(lambda self: self.y.center) + + @overload + def __init__(self, other=None): + # type: (Optional[BoundingBox]) -> None + pass + + @overload + def __init__(self, x, y): + # type: (BoundingIntervalArgs, BoundingIntervalArgs) -> None + pass + + def __init__(self, x=None, y=None): + if y is None: + if x is None: + # identity for addition, zero for intersection + pass + elif isinstance(x, BoundingBox): + x, y = x.x, x.y + else: + raise ValueError("Not a number for scaling: {} ({})" + .format(str(x), type(x).__name__)) + self.x = BoundingInterval(x) + self.y = BoundingInterval(y) + + def __bool__(self): + # type: () -> bool + return bool(self.x) and bool(self.y) + + __nonzero__ = __bool__ + + def __neg__(self): + # type: () -> BoundingBox + return BoundingBox(-self.x, -self.y) + + def __add__(self, other): + # type: (Optional[BoundingBox]) -> BoundingBox + """Calculate the bounding box that covers both given bounding boxes""" + new = BoundingBox(self) + new += BoundingBox(other) + return new + + def __iadd__(self, other): + # type: (Optional[BoundingBox]) -> BoundingBox + other = BoundingBox(other) + self.x += other.x + self.y += other.y + return self + + def __radd__(self, other): + # type: (Optional[BoundingBox]) -> BoundingBox + return self + other + + def __and__(self, other): + # type: (Optional[BoundingBox]) -> BoundingBox + """Calculate the bounding box where both given bounding boxes overlap""" + new = BoundingBox(self) + new &= BoundingBox(other) + return new + + def __iand__(self, other): + # type: (Optional[BoundingBox]) -> BoundingBox + new = BoundingBox(self) + other = BoundingBox(other) + self.x = self.x & other.x + self.y = self.y & other.y + if not self.x or not self.y: + self.x, self.y = BoundingInterval(), BoundingInterval() + return self + + def __rand__(self, other): + # type: (Optional[BoundingBox]) -> BoundingBox + return self & other + + def __mul__(self, factor): + # type: (float) -> BoundingBox + new = BoundingBox(self) + new *= factor + return new + + def __imul__(self, factor): + # type: (float) -> BoundingBox + self.x *= factor + self.y *= factor + return self + + def __eq__(self, other): + # type (object) -> bool + if isinstance(other, BoundingBox): + return tuple(self) == tuple(other) + return False + + def __iter__(self): + # type: () -> Generator[BoundingBox, None, None] + yield self.x + yield self.y + + @property + def minimum(self): + # type: () -> Vector2d + """Return the minimum x,y coords""" + return Vector2d(self.x.minimum, self.y.minimum) + + @property + def maximum(self): + # type: () -> Vector2d + """Return the maximum x,y coords""" + return Vector2d(self.x.maximum, self.y.maximum) + + def __repr__(self): + # type: () -> str + return "BoundingBox({},{})".format(tuple(self.x), tuple(self.y)) + + @property + def center(self): + # type: () -> Vector2d + """Returns the middle of the bounding box""" + return Vector2d(self.x.center, self.y.center) + + def get_anchor(self, xanchor, yanchor, direction=0, selbox=None): + # type: (str, str, Union[int, str], Optional[BoundingBox]) -> float + """Calls get_distance with the given anchor options""" + return self.anchor_distance( + getattr(self, XAN[xanchor]), + getattr(self, YAN[yanchor]), + direction=direction, + selbox=selbox) + + @staticmethod + def anchor_distance(x, y, direction=0, selbox=None): + # type: (float, float, Union[int, str], Optional[BoundingBox]) -> float + """Using the x,y returns a single sortable value based on direction and angle + + direction - int (custom angle), tb/bt (top/bottom), lr/rl (left/right), ri/ro (radial) + selbox - The bounding box of the whole selection for radial anchors + """ + rot = 0.0 + if isinstance(direction, int): # Angle + if direction not in CUSTOM_DIRECTION: + return hypot(x, y) * (cos(radians(-direction) - atan2(y, x))) + direction = CUSTOM_DIRECTION[direction] + + if direction in ('ro', 'ri'): + if selbox is None: + raise ValueError("Radial distance not available without selection bounding box") + rot = hypot(selbox.x.center - x, selbox.y.center - y) + + return [y, -y, x, -x, rot, -rot][DIRECTION.index(direction)] + + +class DirectedLineSegment(object): + """ + A directed line segment + + DirectedLineSegment(((x0, y0), (x1, y1))) + """ + + start = Vector2d() # start point of segment + end = Vector2d() # end point of segment + + x0 = property(lambda self: self.start.x) # pylint: disable=invalid-name + y0 = property(lambda self: self.start.y) # pylint: disable=invalid-name + x1 = property(lambda self: self.end.x) + y1 = property(lambda self: self.end.y) + dx = property(lambda self: self.x1 - self.x0) # pylint: disable=invalid-name + dy = property(lambda self: self.y1 - self.y0) # pylint: disable=invalid-name + + @overload + def __init__(self): + # type: () -> None + pass + + @overload + def __init__(self, other): + # type: (DirectedLineSegment) -> None + pass + + @overload + def __init__(self, start, end): + # type: (VectorLike, VectorLike) -> None + pass + + def __init__(self, *args): + if not args: # overload 0 + start, end = Vector2d(), Vector2d() + elif len(args) == 1: # overload 1 + other, = args + start, end = other.start, other.end + elif len(args) == 2: # overload 2 + start, end = args + else: + raise ValueError("DirectedLineSegment() can't be constructed from {}".format(args)) + + self.start = Vector2d(start) + self.end = Vector2d(end) + + def __eq__(self, other): + # type: (object) -> bool + if isinstance(other, (tuple, DirectedLineSegment)): + return tuple(self) == tuple(other) + return False + + def __iter__(self): + # type: () -> Generator[DirectedLineSegment, None, None] + yield self.x0 + yield self.x1 + yield self.y0 + yield self.y1 + + @property + def length(self): + # type: () -> float + """Get the length from the top left to the bottom right of the line""" + return sqrt((self.dx ** 2) + (self.dy ** 2)) + + @property + def angle(self): + # type: () -> float + """Get the angle of the line created by this segment""" + return pi * (atan2(self.dy, self.dx)) / 180 + + def distance_to_point(self, x, y): + # type: (float, float) -> Union[DirectedLineSegment, Optional[float]] + """Get the distance to the given point (x, y)""" + segment2 = DirectedLineSegment(self.start, (x, y)) + dot2 = segment2.dot(self) + if dot2 <= 0: + return DirectedLineSegment((x, y), self.start).length + if self.dot(self) <= dot2: + return DirectedLineSegment((x, y), self.end).length + return self.perp_distance(x, y) + + def perp_distance(self, x, y): + # type: (float, float) -> Optional[float] + """Perpendicular distance to the given point""" + if self.length == 0: + return None + return fabs((self.dx * (self.y0 - y)) - ((self.x0 - x) * self.dy)) / self.length + + def dot(self, other): + # type: (DirectedLineSegment) -> float + """Get the dot product with the segment with another""" + return self.dx * other.dx + self.dy * other.dy + + def point_at_ratio(self, ratio): + # type: (float) -> Tuple[float, float] + """Get the point at the given ratio along the line""" + return self.x0 + ratio * self.dx, self.y0 + ratio * self.dy + + def point_at_length(self, length): + # type: (float) -> Tuple[float, float] + """Get the point as the length along the line""" + return self.point_at_ratio(length / self.length) + + def parallel(self, x, y): + # type: (float, float) -> DirectedLineSegment + """Create parallel Segment""" + return DirectedLineSegment((x + self.dx, y + self.dy), (x, y)) + + def intersect(self, other): + # type: (DirectedLineSegment) -> Optional[Vector2d] + """Get the intersection between two segments""" + other = DirectedLineSegment(other) + denom = (other.dy * self.dx) - (other.dx * self.dy) + num = (other.dx * (self.y0 - other.y0)) - (other.dy * (self.x0 - other.x0)) + # num2 = (self.width * (self.top - other.top)) - (self.height * (self.left - other.left)) + + if denom != 0: + return Vector2d( + self.x0 + ((num / denom) * self.dx), + self.y0 + ((num / denom) * self.dy) + ) + return None + + def __repr__(self): + # type: () -> str + return "DirectedLineSegment(({0.start}), ({0.end}))".format(self) + + +def cubic_extrema(py0, py1, py2, py3): + # type: (float, float, float, float) -> Tuple[float, float] + """Returns the extreme value, given a set of bezier coordinates""" + + atol = 1e-9 + cmin, cmax = min(py0, py3), max(py0, py3) + pd1 = py1 - py0 + pd2 = py2 - py1 + pd3 = py3 - py2 + + def _is_bigger(point): + if (point > 0) and (point < 1): + pyx = py0 * (1 - point) * (1 - point) * (1 - point) + \ + 3 * py1 * point * (1 - point) * (1 - point) + \ + 3 * py2 * point * point * (1 - point) + \ + py3 * point * point * point + return min(cmin, pyx), max(cmax, pyx) + return cmin, cmax + + if fabs(pd1 - 2 * pd2 + pd3) > atol: + if pd2 * pd2 > pd1 * pd3: + pds = sqrt(pd2 * pd2 - pd1 * pd3) + cmin, cmax = _is_bigger((pd1 - pd2 + pds) / (pd1 - 2 * pd2 + pd3)) + cmin, cmax = _is_bigger((pd1 - pd2 - pds) / (pd1 - 2 * pd2 + pd3)) + + elif fabs(pd2 - pd1) > atol: + cmin, cmax = _is_bigger(-pd1 / (2 * (pd2 - pd1))) + + return cmin, cmax + + +def quadratic_extrema(py0, py1, py2): + # type: (float, float, float) -> Tuple[float, float] + atol = 1e-9 + cmin, cmax = min(py0, py2), max(py0, py2) + + def _is_bigger(point): + if (point > 0) and (point < 1): + pyx = py0 * (1 - point) * (1 - point) + \ + 2 * py1 * point * (1 - point) + \ + py2 * point * point + return min(cmin, pyx), max(cmax, pyx) + return cmin, cmax + + if fabs(py0 + py2 - 2 * py1) > atol: + cmin, cmax = _is_bigger((py0 - py1) / (py0 + py2 - 2 * py1)) + + return cmin, cmax diff --git a/share/extensions/inkex/turtle.py b/share/extensions/inkex/turtle.py new file mode 100644 index 0000000..cee7dad --- /dev/null +++ b/share/extensions/inkex/turtle.py @@ -0,0 +1,120 @@ +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import math +import random + + +class pTurtle(object): + """A Python path turtle""" + + def __init__(self, home=(0, 0)): + self.__home = [home[0], home[1]] + self.__pos = self.__home[:] + self.__heading = -90 + self.__path = "" + self.__draw = True + self.__new = True + + def forward(self, mag): + self.setpos((self.__pos[0] + math.cos(math.radians(self.__heading)) * mag, + self.__pos[1] + math.sin(math.radians(self.__heading)) * mag)) + + def backward(self, mag): + self.setpos((self.__pos[0] - math.cos(math.radians(self.__heading)) * mag, + self.__pos[1] - math.sin(math.radians(self.__heading)) * mag)) + + def right(self, deg): + self.__heading -= deg + + def left(self, deg): + self.__heading += deg + + def penup(self): + self.__draw = False + self.__new = False + + def pendown(self): + if not self.__draw: + self.__new = True + self.__draw = True + + def pentoggle(self): + if self.__draw: + self.penup() + else: + self.pendown() + + def home(self): + self.setpos(self.__home) + + def clean(self): + self.__path = '' + + def clear(self): + self.clean() + self.home() + + def setpos(self, arg): + if self.__new: + self.__path += "M" + ",".join([str(i) for i in self.__pos]) + self.__new = False + self.__pos = arg + if self.__draw: + self.__path += "L" + ",".join([str(i) for i in self.__pos]) + + def getpos(self): + return self.__pos[:] + + def setheading(self, deg): + self.__heading = deg + + def getheading(self): + return self.__heading + + def sethome(self, arg): + self.__home = list(arg) + + def getPath(self): + return self.__path + + def rtree(self, size, minimum, pt=False): + if size < minimum: + return + self.fd(size) + turn = random.uniform(20, 40) + self.lt(turn) + self.rtree(size * random.uniform(0.5, 0.9), minimum, pt) + self.rt(turn) + turn = random.uniform(20, 40) + self.rt(turn) + self.rtree(size * random.uniform(0.5, 0.9), minimum, pt) + self.lt(turn) + if pt: + self.pu() + self.bk(size) + if pt: + self.pd() + + fd = forward + bk = backward + rt = right + lt = left + pu = penup + pd = pendown diff --git a/share/extensions/inkex/tween.py b/share/extensions/inkex/tween.py new file mode 100644 index 0000000..31c165b --- /dev/null +++ b/share/extensions/inkex/tween.py @@ -0,0 +1,79 @@ +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import math + +from bisect import bisect_left +from .utils import X, Y +from .units import convert_unit, parse_unit, render_unit + +try: + from typing import Union, Tuple, List, TypeVar, Callable, overload + hasTypes = True + Value = TypeVar('Value') + Number = TypeVar('Number', int, float) +except ImportError: + pass + + +def interpcoord( + coord_a, # type: Number + coord_b, # type: Number + time # type: float +): + # type: (...) -> float + """Interpolate single coordinate by the amount of time""" + return coord_a + ((coord_b - coord_a) * time) + + +def interp( + positions, # type: List[float] + values, # type: List[Value] + newpositions, # type: List[float] + func # type: (Callable[[Value, Value, float], Value]) +): + # type: (...) -> List[Value] + """Interpolate list with arbitrary interpolation function.""" + newvalues = [] + positions = list(map(float, positions)) + newpositions = list(map(float, newpositions)) + for pos in newpositions: + idxl = max(0, bisect_left(positions, pos) - 1) + idxr = min(len(positions)-1, idxl + 1) + fraction = (pos - positions[idxl]) / (positions[idxr] - positions[idxl]) + vall = values[idxl] + valr = values[idxr] + newval = func(vall, valr, fraction) + newvalues.append(newval) + return newvalues + + +def interppoints(point1, point2, time): + # type: (Tuple[float, float], Tuple[float, float], float) -> Tuple[float, float] + """Interpolate coordinate points by amount of time""" + return (interpcoord(point1[X], point2[X], time), interpcoord(point1[Y], point2[Y], time)) + + +def interpunit(start, end, fraction): + # type: (str, str, float) -> str + """Interpolate float attributes with unit.""" + # moved here so we can call 'unittouu' + sp, unit = parse_unit(start) + ep = convert_unit(end, unit) + return render_unit(interpcoord(sp, ep, fraction), unit) diff --git a/share/extensions/inkex/units.py b/share/extensions/inkex/units.py new file mode 100644 index 0000000..cd69a39 --- /dev/null +++ b/share/extensions/inkex/units.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) Aaron Spike +# Aurélio A. Heckert +# Bulia Byak +# Nicolas Dufour, nicoduf@yahoo.fr +# Peter J. R. Moulder +# Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Convert to and from various units and find the closest matching unit. +""" + +import re + +# a dictionary of unit to user unit conversion factors +CONVERSIONS = { + 'in': 96.0, + 'pt': 1.3333333333333333, + 'px': 1.0, + 'mm': 3.779527559055118, + 'cm': 37.79527559055118, + 'm': 3779.527559055118, + 'km': 3779527.559055118, + 'Q': 0.94488188976378, + 'pc': 16.0, + 'yd': 3456.0, + 'ft': 1152.0, + '': 1.0, # Default px +} + +# allowed unit types, including percentages, relative units, and others +# that are not suitable for direct conversion to a length. +# Note that this is _not_ an exhaustive list of allowed unit types. +UNITS = ['in', 'pt', 'px', 'mm', 'cm', 'm', 'km', 'Q', 'pc', 'yd', 'ft', '',\ + '%', 'em', 'ex', 'ch', 'rem', 'vw', 'vh', 'vmin', 'vmax',\ + 'deg', 'grad', 'rad', 'turn', 's', 'ms', 'Hz', 'kHz',\ + 'dpi', 'dpcm', 'dppx'] + +UNIT_MATCH = re.compile(r'({})'.format('|'.join(UNITS))) +NUMBER_MATCH = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)') +BOTH_MATCH = re.compile(r'^\s*{}\s*{}\s*$'.format(NUMBER_MATCH.pattern, UNIT_MATCH.pattern)) + + +def parse_unit(value, default_unit='px', default_value=None): + """ + Takes a value such as 55.32px and returns (55.32, 'px') + Returns default (None) if no match can be found + """ + ret = BOTH_MATCH.match(str(value)) + if ret: + return float(ret.groups()[0]), ret.groups()[-1] or default_unit + return (default_value, default_unit) if default_value is not None else None + + +def are_near_relative(point_a, point_b, eps=0.01): + """Return true if the points are near to eps""" + return (point_a - point_b <= point_a * eps) and (point_a - point_b >= -point_a * eps) + + +def discover_unit(value, viewbox, default='px'): + """Attempt to detect the unit being used based on the viewbox""" + # Default 100px when width can't be parsed + (value, unit) = parse_unit(value, default_value=100.0) + if unit not in CONVERSIONS: + return default + this_factor = CONVERSIONS[unit] * value / viewbox + + # try to find the svgunitfactor in the list of units known. If we don't find something, ... + for unit, unit_factor in CONVERSIONS.items(): + if unit != '': + # allow 1% error in factor + if are_near_relative(this_factor, unit_factor, eps=0.01): + return unit + return default + + +def convert_unit(value, to_unit): + """Returns userunits given a string representation of units in another system""" + value, from_unit = parse_unit(value, default_value=0.0) + if from_unit in CONVERSIONS and to_unit in CONVERSIONS: + return value * CONVERSIONS[from_unit] / CONVERSIONS.get(to_unit, CONVERSIONS['px']) + return 0.0 + + +def render_unit(value, unit): + """Checks and then renders a number with its unit""" + try: + if isinstance(value, str): + (value, unit) = parse_unit(value, default_unit=unit) + return "{:.6g}{:s}".format(value, unit) + except TypeError: + return '' diff --git a/share/extensions/inkex/utils.py b/share/extensions/inkex/utils.py new file mode 100644 index 0000000..dea5308 --- /dev/null +++ b/share/extensions/inkex/utils.py @@ -0,0 +1,290 @@ +# coding=utf-8 +# +# Copyright (C) 2010 Nick Drobchenko, nick@cnc-club.ru +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Basic common utility functions for calculated things +""" +from __future__ import absolute_import, print_function, unicode_literals + +import re +import os +import sys +import shutil + +from itertools import tee +from collections import defaultdict +from argparse import ArgumentTypeError + +# When python2 support is gone, enable tempfile's version +# from tempfile import TemporaryDirectory + +# All the names that get added to the inkex API itself. +__all__ = ('AbortExtension', 'DependencyError', 'Boolean', 'errormsg', 'addNS', 'NSS') + +ABORT_STATUS = -5 + +(X, Y) = range(2) +PY3 = sys.version_info[0] == 3 + +if PY3: + unicode = str # pylint: disable=redefined-builtin,invalid-name + +# a dictionary of all of the xmlns prefixes in a standard inkscape doc +NSS = { + 'sodipodi': 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd', + 'cc': 'http://creativecommons.org/ns#', + 'ccOLD': 'http://web.resource.org/cc/', + 'svg': 'http://www.w3.org/2000/svg', + 'dc': 'http://purl.org/dc/elements/1.1/', + 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'inkscape': 'http://www.inkscape.org/namespaces/inkscape', + 'xlink': 'http://www.w3.org/1999/xlink', + 'xml': 'http://www.w3.org/XML/1998/namespace' +} +SSN = dict((b, a) for (a, b) in NSS.items()) + +class KeyDict(dict): + """ + A normal dictionary, except asking for anything not in the dictionary + always returns the key itself. This is used for translation dictionaries. + """ + def __getitem__(self, key): + try: + return super(KeyDict, self).__getitem__(key) + except KeyError: + return key + +class TemporaryDirectory(object): # pylint: disable=too-few-public-methods + """Tiny replacement for python3's version.""" + def __init__(self, suffix="", prefix="tmp"): + self.suffix = suffix + self.prefix = prefix + self.path = None + def __enter__(self): + from tempfile import mkdtemp + self.path = mkdtemp(self.suffix, self.prefix, None) + return self.path + def __exit__(self, exc, value, traceback): + if os.path.isdir(self.path): + shutil.rmtree(self.path) + +def Boolean(value): + """ArgParser function to turn a boolean string into a python boolean""" + if value.upper() == 'TRUE': + return True + elif value.upper() == 'FALSE': + return False + return None + +def to_bytes(content): + """Ensures the content is bytes""" + if isinstance(content, bytes): + return content + return str(content).encode("utf8") + +def debug(what): + """Print debug message if debugging is switched on""" + errormsg(what) + return what + +def do_nothing(*args, **kwargs): # pylint: disable=unused-argument + """A blank function to do nothing""" + pass + +def errormsg(msg): + """Intended for end-user-visible error messages. + + (Currently just writes to stderr with an appended newline, but could do + something better in future: e.g. could add markup to distinguish error + messages from status messages or debugging output.) + + Note that this should always be combined with translation: + + import inkex + ... + inkex.errormsg(_("This extension requires two selected paths.")) + """ + try: + sys.stderr.write(msg) + except TypeError: + sys.stderr.write(unicode(msg)) + except UnicodeEncodeError: + # Python 2: + # Fallback for cases where sys.stderr.encoding is not Unicode. + # Python 3: + # This will not work as write() does not accept byte strings, but AFAIK + # we should never reach this point as the default error handler is + # 'backslashreplace'. + + # This will be None by default if stderr is piped, so use ASCII as a + # last resort. + encoding = sys.stderr.encoding or 'ascii' + sys.stderr.write(msg.encode(encoding, 'backslashreplace')) + + # Write '\n' separately to avoid dealing with different string types. + sys.stderr.write('\n') + + +class AbortExtension(Exception): + """Raised to print a message to the user without backtrace""" + + def __init__(self, message=""): + self.message = message + + def write(self): + """write the error message out to the user""" + errormsg(self.message) + + +class DependencyError(NotImplementedError): + """Raised when we need an external python module that isn't available""" + +class FragmentError(Exception): + """Raised when trying to do rooty things on an xml fragment""" + +class InitSubClassPy3(type): + """Provide a poly-fill for python3 __init_subclass__()""" + def __init__(cls, name, bases, dct): + if '__metaclass__' not in cls.__dict__: + if hasattr(cls, '__init_subclass__'): + cls.__init_subclass__() + super(InitSubClassPy3, cls).__init__(name, bases, dct) + +def to(kind): # pylint: disable=invalid-name + """ + Decorator which will turn a generator into a list, tuple or other object type. + """ + + def _inner(call): + def _outer(*args, **kw): + return kind(call(*args, **kw)) + + return _outer + + return _inner + + +def strargs(string, kind=float): + """Returns a list of floats from a string with commas or space separators, + also splits at -(minus) signs by adding a space in front of the - sign + """ + return [kind(val) for val in string.replace(',', ' ').replace('-', ' -').replace('e ', 'e').split()] + + +def addNS(tag, ns=None): # pylint: disable=invalid-name + """Add a known namespace to a name for use with lxml""" + if tag.startswith('{') and ns: + _, tag = removeNS(tag) + if not tag.startswith('{'): + tag = tag.replace('__', ':') + if ':' in tag: + (ns, tag) = tag.rsplit(':', 1) + if ns in NSS: + ns = NSS[ns] + if ns is not None: + return "{%s}%s" % (ns, tag) + return tag + + +def removeNS(name): # pylint: disable=invalid-name + """The reverse of addNS, finds any namespace and returns tuple (ns, tag)""" + if name[0] == '{': + (url, tag) = name[1:].split('}', 1) + return SSN.get(url, 'svg'), tag + if ':' in name: + return name.rsplit(':', 1) + return 'svg', name + +def splitNS(name): # pylint: disable=invalid-name + """Like removeNS, but returns a url instead of a prefix""" + (prefix, tag) = removeNS(name) + return (NSS[prefix], tag) + +class classproperty(object): # pylint: disable=invalid-name, too-few-public-methods + """Combine classmethod and property decorators""" + + def __init__(self, func): + self.func = func + + def __get__(self, obj, owner): + return self.func(owner) + + +def filename_arg(name): + """Existing file to read or option used in script arguments""" + filename = os.path.abspath(os.path.expanduser(name)) + if not os.path.isfile(filename): + raise ArgumentTypeError("File not found: {}".format(name)) + return filename + +def pairwise(iterable, start=True): + "Iterate over a list with overlapping pairs (see itertools recipes)" + first, then = tee(iterable) + starter = [(None, next(then, None))] + if not start: + starter = [] + return starter + list(zip(first, then)) + +class CloningVat(object): + """ + When modifying defs, sometimes we want to know if every backlink would have + needed changing, or it was just some of them. + + This tracks the def elements, their promises and creates clones if needed. + """ + def __init__(self, svg): + self.svg = svg + self.tracks = defaultdict(set) + self.set_ids = defaultdict(list) + + def track(self, elem, parent, set_id=None, **kwargs): + """Track the element and connected parent""" + elem_id = elem.get('id') + parent_id = parent.get('id') + self.tracks[elem_id].add(parent_id) + self.set_ids[elem_id].append((set_id, kwargs)) + + def process(self, process, types=(), make_clones=True, **kwargs): + """ + Process each tracked item if the backlinks match the parents + + Optionally make clones, process the clone and set the new id. + """ + for elem_id in list(self.tracks): + parents = self.tracks[elem_id] + elem = self.svg.getElementById(elem_id) + backlinks = set([blk.get('id') for blk in elem.backlinks(*types)]) + if backlinks == parents: + # No need to clone, we're processing on-behalf of all parents + process(elem, **kwargs) + elif make_clones: + clone = elem.copy() + elem.getparent().append(clone) + clone.set_random_id() + for update, upkw in self.set_ids.get(elem_id, ()): + update(elem.get('id'), clone.get('id'), **upkw) + process(clone, **kwargs) + +def fullmatch(regex, string, flags=0): + """Emulate python-3.4 re.fullmatch().""" + if hasattr(regex, 'fullmatch'): + return regex.fullmatch(string, flags) + if hasattr(regex, 'pattern'): + regex = regex.pattern + return re.match("(?:" + regex + r")\Z", string, flags=flags) diff --git a/share/extensions/inkscape.extension.rng b/share/extensions/inkscape.extension.rng new file mode 100644 index 0000000..6ff3078 --- /dev/null +++ b/share/extensions/inkscape.extension.rng @@ -0,0 +1,544 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + file + executable + extension + + + + + + + + + + + + + + + + + + + + python + perl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + path + extensions + inx + absolute + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + g + path + rect + text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + header + url + + + + + + + default + preserve + + + + + + + + + + + + + + + + + + + + + + + + + + + + expand + + + + + + + + + + + + + + + + + + + + + + + + + + int + + + + + + + + + + + + + + full + + + + + + + + + + float + + + + + + + + + + + + + + + + + + + full + + + + + + + bool + + + + + + color + + + + + colorbutton + + + + + + + + + + + + string + + + + + + + + + + multiline + + + + + + + + + + + path + + + + + file + files + folder + folders + file_new + folder_new + + + + + + + + + + + + + optiongroup + + + + + combo + radio + + + + + + + + + + + + + + + + + + + + + + + + notebook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 1 + + + + + + + yes + no + + + + diff --git a/share/extensions/inkscape_follow_link.inx b/share/extensions/inkscape_follow_link.inx new file mode 100644 index 0000000..0865c00 --- /dev/null +++ b/share/extensions/inkscape_follow_link.inx @@ -0,0 +1,12 @@ + + + Follow Link + org.inkscape.follow_link + + all + + + + diff --git a/share/extensions/inkscape_follow_link.py b/share/extensions/inkscape_follow_link.py new file mode 100755 index 0000000..426651f --- /dev/null +++ b/share/extensions/inkscape_follow_link.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# coding=utf-8 + +import threading +import webbrowser + +import inkex +from inkex import Anchor + +class ThreadWebsite(threading.Thread): + """Visit the website without locking inkscape""" + def __init__(self, url): + threading.Thread.__init__(self) + self.url = url + + def run(self): + webbrowser.open(self.url) + +class FollowLink(inkex.EffectExtension): + """Get the first selected item and follow it's href/url""" + def effect(self): + for node in self.svg.selection.filter(Anchor): + vwswli = ThreadWebsite(node.get('xlink:href')) + vwswli.start() + break + +if __name__ == '__main__': + FollowLink().run() diff --git a/share/extensions/inkscape_help_askaquestion.inx b/share/extensions/inkscape_help_askaquestion.inx new file mode 100644 index 0000000..f24632c --- /dev/null +++ b/share/extensions/inkscape_help_askaquestion.inx @@ -0,0 +1,14 @@ + + + Ask Us a Question + org.inkscape.help.askaquestion + + https://inkscape.org/en/ask/ + + all + + + + diff --git a/share/extensions/inkscape_help_commandline.inx b/share/extensions/inkscape_help_commandline.inx new file mode 100644 index 0000000..c85c113 --- /dev/null +++ b/share/extensions/inkscape_help_commandline.inx @@ -0,0 +1,14 @@ + + + Command Line Options + org.inkscape.help.commandline + + https://inkscape.org/doc/inkscape-man.html + + all + + + + diff --git a/share/extensions/inkscape_help_faq.inx b/share/extensions/inkscape_help_faq.inx new file mode 100644 index 0000000..e90ec88 --- /dev/null +++ b/share/extensions/inkscape_help_faq.inx @@ -0,0 +1,13 @@ + + + FAQ + org.inkscape.help.faq + https://inkscape.org/learn/faq/ + + all + + + + diff --git a/share/extensions/inkscape_help_keys.inx b/share/extensions/inkscape_help_keys.inx new file mode 100644 index 0000000..f7d60bf --- /dev/null +++ b/share/extensions/inkscape_help_keys.inx @@ -0,0 +1,14 @@ + + + Keys and Mouse Reference + org.inkscape.help.keys + + https://inkscape.org/doc/keys.html + + all + + + + diff --git a/share/extensions/inkscape_help_manual.inx b/share/extensions/inkscape_help_manual.inx new file mode 100644 index 0000000..1b12cf3 --- /dev/null +++ b/share/extensions/inkscape_help_manual.inx @@ -0,0 +1,14 @@ + + + Inkscape Manual + org.inkscape.help.manual + + http://tavmjong.free.fr/INKSCAPE/MANUAL/html/index.php + + all + + + + diff --git a/share/extensions/inkscape_help_relnotes.inx b/share/extensions/inkscape_help_relnotes.inx new file mode 100644 index 0000000..0a8622b --- /dev/null +++ b/share/extensions/inkscape_help_relnotes.inx @@ -0,0 +1,14 @@ + + + New in This Version + org.inkscape.help.relnotes + + https://inkscape.org/release/1.0/ + + all + + + + diff --git a/share/extensions/inkscape_help_reportabug.inx b/share/extensions/inkscape_help_reportabug.inx new file mode 100644 index 0000000..f79e531 --- /dev/null +++ b/share/extensions/inkscape_help_reportabug.inx @@ -0,0 +1,13 @@ + + + Report a Bug + org.inkscape.help.reportabug + http://inkscape.org/contribute/report-bugs/ + + all + + + + diff --git a/share/extensions/inkscape_help_svgspec.inx b/share/extensions/inkscape_help_svgspec.inx new file mode 100644 index 0000000..6c44b7a --- /dev/null +++ b/share/extensions/inkscape_help_svgspec.inx @@ -0,0 +1,13 @@ + + + SVG 1.1 Specification + org.inkscape.help.svgspec + http://www.w3.org/TR/SVG11/ + + all + + + + diff --git a/share/extensions/inkweb.js b/share/extensions/inkweb.js new file mode 100644 index 0000000..acdf3e2 --- /dev/null +++ b/share/extensions/inkweb.js @@ -0,0 +1,216 @@ +/* +** InkWeb - Inkscape's Javscript features for the open vector web +** +** Copyright (C) 2009 Aurelio A. Heckert, aurium (a) gmail dot com +** +** ********* Bugs and New Fetures ************************************* +** If you found any bug on this script or if you want to propose a +** new feature, please report it in the inkscape bug tracker +** https://bugs.launchpad.net/inkscape/+filebug +** and assign that to Aurium. +** ******************************************************************** +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published +** by the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program. If not, see . +*/ + +var InkWeb = { + + version: 0.04, + + NS: { + svg: "http://www.w3.org/2000/svg", + sodipodi: "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd", + inkscape: "http://www.inkscape.org/namespaces/inkscape", + cc: "http://creativecommons.org/ns#", + dc: "http://purl.org/dc/elements/1.1/", + rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace" + } + +}; + +InkWeb.el = function (tag, attributes) { + // A helper to create SVG elements + var element = document.createElementNS( this.NS.svg, tag ); + for ( var att in attributes ) { + switch ( att ) { + case "parent": + attributes.parent.appendChild( element ); + break; + case "text": + element.appendChild( document.createTextNode( attributes.text ) ); + break; + default: + element.setAttribute( att, attributes[att] ); + } + } + return element; +} + +InkWeb.reGetStyleAttVal = function (att) { + return new RegExp( "(^|.*;)[ ]*"+ att +":([^;]*)(;.*|$)" ) +} + +InkWeb.getStyle = function (el, att) { + // This method is needed because el.style is only working + // to HTML style in the Firefox 3.0 + if ( typeof(el) == "string" ) + el = document.getElementById(el); + var style = el.getAttribute("style"); + var match = this.reGetStyleAttVal(att).exec(style); + if ( match ) { + return match[2]; + } else { + return false; + } +} + +InkWeb.setStyle = function (el, att, val) { + if ( typeof(el) == "string" ) + el = document.getElementById(el); + var style = el.getAttribute("style"); + re = this.reGetStyleAttVal(att); + if ( re.test(style) ) { + style = style.replace( re, "$1"+ att +":"+ val +"$3" ); + } else { + style += ";"+ att +":"+ val; + } + el.setAttribute( "style", style ); + return val +} + +InkWeb.transmitAtt = function (conf) { + conf.att = conf.att.split( /\s+/ ); + if ( typeof(conf.from) == "string" ) + conf.from = document.getElementById( conf.from ); + if ( ! conf.to.join ) + conf.to = [ conf.to ]; + for ( var toEl,elN=0; toEl=conf.to[elN]; elN++ ) { + if ( typeof(toEl) == "string" ) + toEl = document.getElementById( toEl ); + for ( var i=0; i + + Interpolate + org.ekips.filter.interp + 0.00 + 5 + 2 + true + false + false + + path + + + + + + diff --git a/share/extensions/interp.py b/share/extensions/interp.py new file mode 100755 index 0000000..bac878c --- /dev/null +++ b/share/extensions/interp.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import copy +from collections import namedtuple +from itertools import combinations + +import inkex +from inkex.styles import Style +from inkex.utils import pairwise +from inkex.paths import CubicSuperPath +from inkex.tween import interppoints +from inkex.bezier import csplength, cspbezsplitatlength, cspbezsplit, bezlenapprx + +class Interp(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("-e", "--exponent", type=float, default=0.0,\ + help="values other than zero give non linear interpolation") + pars.add_argument("-s", "--steps", type=int, default=5,\ + help="number of interpolation steps") + pars.add_argument("-m", "--method", type=int, default=2,\ + help="method of interpolation") + pars.add_argument("-d", "--dup", type=inkex.Boolean, default=True,\ + help="duplicate endpaths") + pars.add_argument("--style", type=inkex.Boolean, default=True,\ + help="try interpolation of some style properties") + pars.add_argument("--zsort", type=inkex.Boolean, default=False,\ + help="use z-order instead of selection order") + + def effect(self): + exponent = self.options.exponent + if exponent >= 0: + exponent += 1.0 + else: + exponent = 1.0 / (1.0 - exponent) + steps = [1.0 / (self.options.steps + 1.0)] + for i in range(self.options.steps - 1): + steps.append(steps[0] + steps[-1]) + steps = [step ** exponent for step in steps] + + if self.options.zsort: + # work around selection order swapping with Live Preview + objects = self.svg.get_z_selected() + else: + # use selection order (default) + objects = self.svg.selected + + objects = [node for node in objects.values() if isinstance(node, inkex.PathElement)] + + # prevents modification of original objects + objects = copy.deepcopy(objects) + + for node in objects: + node.apply_transform() + + objectpairs = pairwise(objects, start=False) + + for (elem1, elem2) in objectpairs: + start = elem1.path.to_superpath() + end = elem2.path.to_superpath() + sst = copy.deepcopy(elem1.style) + est = copy.deepcopy(elem2.style) + basestyle = copy.deepcopy(sst) + + if 'stroke-width' in basestyle: + basestyle['stroke-width'] = sst.interpolate_prop(est, 0, 'stroke-width') + + # prepare for experimental style tweening + if self.options.style: + styledefaults = Style( + {'opacity': 1.0, + 'stroke-opacity': 1.0, + 'fill-opacity': 1.0, + 'stroke-width': 1.0, + 'stroke': None, + 'fill': None}) + for key in styledefaults: + sst.setdefault(key, styledefaults[key]) + est.setdefault(key, styledefaults[key]) + + isnotplain = lambda x: not (x == 'none' or x[:1] == '#') + isgradient = lambda x: x.startswith('url(#') + + if isgradient(sst['stroke']) and isgradient(est['stroke']): + strokestyle = 'gradient' + elif isnotplain(sst['stroke']) or isnotplain(est['stroke']) or (sst['stroke'] == 'none' and est['stroke'] == 'none'): + strokestyle = 'notplain' + else: + strokestyle = 'color' + + if isgradient(sst['fill']) and isgradient(est['fill']): + fillstyle = 'gradient' + elif isnotplain(sst['fill']) or isnotplain(est['fill']) or (sst['fill'] == 'none' and est['fill'] == 'none'): + fillstyle = 'notplain' + else: + fillstyle = 'color' + + if strokestyle is 'color': + if sst['stroke'] == 'none': + sst['stroke-width'] = '0.0' + sst['stroke-opacity'] = '0.0' + sst['stroke'] = est['stroke'] + elif est['stroke'] == 'none': + est['stroke-width'] = '0.0' + est['stroke-opacity'] = '0.0' + est['stroke'] = sst['stroke'] + + if fillstyle is 'color': + if sst['fill'] == 'none': + sst['fill-opacity'] = '0.0' + sst['fill'] = est['fill'] + elif est['fill'] == 'none': + est['fill-opacity'] = '0.0' + est['fill'] = sst['fill'] + + if self.options.method == 2: + # subdivide both paths into segments of relatively equal lengths + slengths, stotal = csplength(start) + elengths, etotal = csplength(end) + lengths = {} + t = 0 + for sp in slengths: + for l in sp: + t += l / stotal + lengths.setdefault(t, 0) + lengths[t] += 1 + t = 0 + for sp in elengths: + for l in sp: + t += l / etotal + lengths.setdefault(t, 0) + lengths[t] += -1 + sadd = [k for (k, v) in lengths.items() if v < 0] + sadd.sort() + eadd = [k for (k, v) in lengths.items() if v > 0] + eadd.sort() + + t = 0 + s = [[]] + for sp in slengths: + if not start[0]: + s.append(start.pop(0)) + s[-1].append(start[0].pop(0)) + for l in sp: + pt = t + t += l / stotal + if sadd and t > sadd[0]: + while sadd and sadd[0] < t: + nt = (sadd[0] - pt) / (t - pt) + bezes = cspbezsplitatlength(s[-1][-1][:], start[0][0][:], nt) + s[-1][-1:] = bezes[:2] + start[0][0] = bezes[2] + pt = sadd.pop(0) + s[-1].append(start[0].pop(0)) + t = 0 + e = [[]] + for sp in elengths: + if not end[0]: + e.append(end.pop(0)) + e[-1].append(end[0].pop(0)) + for l in sp: + pt = t + t += l / etotal + if eadd and t > eadd[0]: + while eadd and eadd[0] < t: + nt = (eadd[0] - pt) / (t - pt) + bezes = cspbezsplitatlength(e[-1][-1][:], end[0][0][:], nt) + e[-1][-1:] = bezes[:2] + end[0][0] = bezes[2] + pt = eadd.pop(0) + e[-1].append(end[0].pop(0)) + start = s[:] + end = e[:] + else: + # which path has fewer segments? + lengthdiff = len(start) - len(end) + # swap shortest first + if lengthdiff > 0: + start, end = end, start + # subdivide the shorter path + for x in range(abs(lengthdiff)): + maxlen = 0 + subpath = 0 + segment = 0 + for y in range(len(start)): + for z in range(1, len(start[y])): + leng = bezlenapprx(start[y][z - 1], start[y][z]) + if leng > maxlen: + maxlen = leng + subpath = y + segment = z + sp1, sp2 = start[subpath][segment - 1:segment + 1] + start[subpath][segment - 1:segment + 1] = cspbezsplit(sp1, sp2) + # if swapped, swap them back + if lengthdiff > 0: + start, end = end, start + + # break paths so that corresponding subpaths have an equal number of segments + s = [[]] + e = [[]] + while start and end: + if start[0] and end[0]: + s[-1].append(start[0].pop(0)) + e[-1].append(end[0].pop(0)) + elif end[0]: + s.append(start.pop(0)) + e[-1].append(end[0][0]) + e.append([end[0].pop(0)]) + elif start[0]: + e.append(end.pop(0)) + s[-1].append(start[0][0]) + s.append([start[0].pop(0)]) + else: + s.append(start.pop(0)) + e.append(end.pop(0)) + + if self.options.dup: + steps = [0] + steps + [1] + # create an interpolated path for each interval + group = self.svg.get_current_layer().add(inkex.Group()) + for time in steps: + interp = [] + # process subpaths + for ssp, esp in zip(s, e): + if not (ssp or esp): + break + interp.append([]) + # process superpoints + for sp, ep in zip(ssp, esp): + if not (sp or ep): + break + interp[-1].append([]) + # process points + for p1, p2 in zip(sp, ep): + if not (sp or ep): + break + interp[-1][-1].append(interppoints(p1, p2, time)) + + # remove final subpath if empty. + if not interp[-1]: + del interp[-1] + + # basic style interpolation + if self.options.style: + basestyle.update(sst.interpolate(est, time)) + for prop in ['stroke', 'fill']: + if isgradient(sst[prop]) and isgradient(est[prop]): + gradid1 = sst[prop][4:-1] + gradid2 = est[prop][4:-1] + grad1 = self.svg.getElementById(gradid1) + grad2 = self.svg.getElementById(gradid2) + newgrad = grad1.interpolate(grad2, time) + stops, orientation = newgrad.stops_and_orientation() + self.svg.defs.add(orientation) + basestyle[prop] = 'url(#{})'.format(orientation.get_id()) + if len(stops): + self.svg.defs.add(stops, orientation) + orientation.set('xlink:href', '#{}'.format(stops.get_id())) + + new = group.add(inkex.PathElement()) + new.style = basestyle + new.path = CubicSuperPath(interp) + + +if __name__ == '__main__': + Interp().run() diff --git a/share/extensions/interp_att_g.inx b/share/extensions/interp_att_g.inx new file mode 100644 index 0000000..d0109d8 --- /dev/null +++ b/share/extensions/interp_att_g.inx @@ -0,0 +1,59 @@ + + + Interpolate Attribute in a group + org.inkscape.filter.interp_att_g + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + all + + + + + + diff --git a/share/extensions/interp_att_g.py b/share/extensions/interp_att_g.py new file mode 100755 index 0000000..d7762a9 --- /dev/null +++ b/share/extensions/interp_att_g.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2009 Aurelio A. Heckert, aurium (a) gmail dot com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Interpolation of attributes in selected objects or group's children. +""" + +import inkex + +class InterpAttG(inkex.EffectExtension): + """ + This effect applies a value for any interpolatable attribute for all + elements inside the selected group or for all elements in a multiple selection. + """ + def __init__(self): + super(InterpAttG, self).__init__() + self.arg_parser.add_argument( + "-a", "--att", type=str, dest="att", default="fill", + help="Attribute to be interpolated.") + self.arg_parser.add_argument( + "-o", "--att-other", type=str, dest="att_other", + help="Other attribute (for a limited UI).") + self.arg_parser.add_argument( + "-t", "--att-other-type", type=str, dest="att_other_type", + help="The other attribute type.") + self.arg_parser.add_argument( + "-w", "--att-other-where", type=str, dest="att_other_where", + help="That is a tag attribute or a style attribute?") + self.arg_parser.add_argument( + "-s", "--start-val", type=str, dest="start_val", default="#F00", + help="Initial interpolation value.") + self.arg_parser.add_argument( + "-e", "--end-val", type=str, dest="end_val", default="#00F", + help="End interpolation value.") + self.arg_parser.add_argument( + "-u", "--unit", type=str, dest="unit", default="color", + help="Values unit.") + self.arg_parser.add_argument( + "--zsort", type=inkex.Boolean, dest="zsort", default=True, + help="use z-order instead of selection order") + self.arg_parser.add_argument( + "--tab", type=str, dest="tab", + help="The selected UI-tab when OK was pressed") + + def get_color_steps(self, total): + """Get the color value, returning the start color and a single increment step""" + start_value = inkex.Color(self.options.start_val) + end_value = inkex.Color(self.options.end_val) + + color_inc = [ + (end_value[v] - start_value[v]) / float(total - 1) + for v in range(3)] + + return start_value, color_inc + + def get_number_steps(self, total): + """Get the number value, returning the start float and a single increment step""" + start_value = self.options.start_val.replace(",", ".") + end_value = self.options.end_val.replace(",", ".") + unit = self.options.unit + + if unit != 'none': + start_value = self.svg.unittouu(start_value + unit) + end_value = self.svg.unittouu(end_value + unit) + + try: + start_value = float(start_value) + end_value = float(end_value) + except ValueError: + inkex.errormsg( + _("Bad values for a number field: {}, {}.".format(start_value, end_value))) + return 0, 0 + + val_inc = (end_value - start_value) / float(total - 1) + return start_value, val_inc + + def get_elements(self): + """Returns a list of elements to work on""" + if not self.svg.selection: + return [] + + if len(self.svg.selection) > 1: + # multiple selection + if self.options.zsort: + return self.svg.selection.paint_order() + return self.svg.selected + + # must be a group + node = self.svg.selection.filter(inkex.Group).first() + return list(node) or [] + + def effect(self): + if self.options.att == 'other': + if self.options.att_other is not None: + inte_att = self.options.att_other + else: + inkex.errormsg(_("You selected 'Other'. Please enter an attribute to interpolate.")) + return + + inte_att_type = self.options.att_other_type + where = self.options.att_other_where + else: + inte_att = self.options.att + inte_att_type = 'float' + if inte_att in ('width', 'height'): + where = 'tag' + elif inte_att in ('scale', 'trans-x', 'trans-y'): + where = 'transform' + elif inte_att == 'opacity': + where = 'style' + elif inte_att in ('fill', 'stroke'): + inte_att_type = 'color' + where = 'style' + + collection = self.get_elements() + + if not collection: + inkex.errormsg(_('There is no selection to interpolate')) + return False + + if inte_att_type == 'color': + cur, inc = self.get_color_steps(len(collection)) + else: + cur, inc = self.get_number_steps(len(collection)) + + for node in collection: + if inte_att_type == 'color': + val = inkex.Color([int(cur[i]) for i in range(3)]) + elif inte_att_type == 'float': + val = cur + elif inte_att_type == 'int': + val = int(round(cur)) + else: + raise KeyError("Unknown attr type: {}".format(inte_att_type)) + + if where == 'style': + node.style[inte_att] = str(val) + elif where == 'transform': + if inte_att == 'trans-x': + node.transform.add_translate(val, 0) + elif inte_att == 'trans-y': + node.transform.add_translate(0, val) + elif inte_att == 'scale': + node.transform.add_scale(val) + elif where == 'tag': + node.set(inte_att, str(val)) + else: + raise KeyError("Unknown update {}".format(where)) + + if inte_att_type == 'color': + cur = [cur[i] + inc[i] for i in range(3)] + else: + cur += inc + + return True + +if __name__ == '__main__': + InterpAttG().run() diff --git a/share/extensions/jessyInk.js b/share/extensions/jessyInk.js new file mode 100644 index 0000000..433c1d1 --- /dev/null +++ b/share/extensions/jessyInk.js @@ -0,0 +1,2727 @@ +// Copyright 2008, 2009 Hannes Hochreiner +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +// Set onload event handler. +window.onload = jessyInkInit; + +// Creating a namespace dictionary. The standard Inkscape namespaces are taken from inkex.py. +var NSS = new Object(); +NSS['sodipodi']='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'; +NSS['cc']='http://web.resource.org/cc/'; +NSS['svg']='http://www.w3.org/2000/svg'; +NSS['dc']='http://purl.org/dc/elements/1.1/'; +NSS['rdf']='http://www.w3.org/1999/02/22-rdf-syntax-ns#'; +NSS['inkscape']='http://www.inkscape.org/namespaces/inkscape'; +NSS['xlink']='http://www.w3.org/1999/xlink'; +NSS['xml']='http://www.w3.org/XML/1998/namespace'; +NSS['jessyink']='https://launchpad.net/jessyink'; + +// Keycodes. +var LEFT_KEY = 37; // cursor left keycode +var UP_KEY = 38; // cursor up keycode +var RIGHT_KEY = 39; // cursor right keycode +var DOWN_KEY = 40; // cursor down keycode +var PAGE_UP_KEY = 33; // page up keycode +var PAGE_DOWN_KEY = 34; // page down keycode +var HOME_KEY = 36; // home keycode +var END_KEY = 35; // end keycode +var ENTER_KEY = 13; // next slide +var SPACE_KEY = 32; +var ESCAPE_KEY = 27; + +// Presentation modes. +var SLIDE_MODE = 1; +var INDEX_MODE = 2; +var DRAWING_MODE = 3; + +// Mouse handler actions. +var MOUSE_UP = 1; +var MOUSE_DOWN = 2; +var MOUSE_MOVE = 3; +var MOUSE_WHEEL = 4; + +// Parameters. +var ROOT_NODE = document.getElementsByTagNameNS(NSS["svg"], "svg")[0]; +var HEIGHT = 0; +var WIDTH = 0; +var INDEX_COLUMNS_DEFAULT = 4; +var INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT; +var INDEX_OFFSET = 0; +var STATE_START = -1; +var STATE_END = -2; +var BACKGROUND_COLOR = null; +var slides = new Array(); + +// Initialisation. +var currentMode = SLIDE_MODE; +var masterSlide = null; +var activeSlide = 0; +var activeEffect = 0; +var timeStep = 30; // 40 ms equal 25 frames per second. +var lastFrameTime = null; +var processingEffect = false; +var transCounter = 0; +var effectArray = 0; +var defaultTransitionInDict = new Object(); +defaultTransitionInDict["name"] = "appear"; +var defaultTransitionOutDict = new Object(); +defaultTransitionOutDict["name"] = "appear"; +var jessyInkInitialised = false; + +// Initialise char and key code dictionaries. +var charCodeDictionary = getDefaultCharCodeDictionary(); +var keyCodeDictionary = getDefaultKeyCodeDictionary(); + +// Initialise mouse handler dictionary. +var mouseHandlerDictionary = getDefaultMouseHandlerDictionary(); + +var progress_bar_visible = false; +var timer_elapsed = 0; +var timer_start = timer_elapsed; +var timer_duration = 15; // 15 minutes + +var history_counter = 0; +var history_original_elements = new Array(); +var history_presentation_elements = new Array(); + +var mouse_original_path = null; +var mouse_presentation_path = null; +var mouse_last_x = -1; +var mouse_last_y = -1; +var mouse_min_dist_sqr = 3 * 3; +var path_colour = "red"; +var path_width_default = 3; +var path_width = path_width_default; +var path_paint_width = path_width; + +var number_of_added_slides = 0; + +/** Initialisation function. + * The whole presentation is set-up in this function. + */ +function jessyInkInit() +{ + // Make sure we only execute this code once. Double execution can occur if the onload event handler is set + // in the main svg tag as well (as was recommended in earlier versions). Executing this function twice does + // not lead to any problems, but it takes more time. + if (jessyInkInitialised) + return; + + // Making the presentation scalable. + var VIEWBOX = ROOT_NODE.getAttribute("viewBox"); + + if (VIEWBOX) + { + WIDTH = ROOT_NODE.viewBox.animVal.width; + HEIGHT = ROOT_NODE.viewBox.animVal.height; + } + else + { + HEIGHT = parseFloat(ROOT_NODE.getAttribute("height")); + WIDTH = parseFloat(ROOT_NODE.getAttribute("width")); + ROOT_NODE.setAttribute("viewBox", "0 0 " + WIDTH + " " + HEIGHT); + } + + ROOT_NODE.setAttribute("width", "100%"); + ROOT_NODE.setAttribute("height", "100%"); + + // Setting the background color. + var namedViews = document.getElementsByTagNameNS(NSS["sodipodi"], "namedview"); + + for (var counter = 0; counter < namedViews.length; counter++) + { + if (namedViews[counter].hasAttribute("id") && namedViews[counter].hasAttribute("pagecolor")) + { + if (namedViews[counter].getAttribute("id") == "base") + { + BACKGROUND_COLOR = namedViews[counter].getAttribute("pagecolor"); + var newAttribute = "background-color:" + BACKGROUND_COLOR + ";"; + + if (ROOT_NODE.hasAttribute("style")) + newAttribute += ROOT_NODE.getAttribute("style"); + + ROOT_NODE.setAttribute("style", newAttribute); + } + } + } + + // Defining clip-path. + var defsNodes = document.getElementsByTagNameNS(NSS["svg"], "defs"); + + if (defsNodes.length > 0) + { + var existingClipPath = document.getElementById("jessyInkSlideClipPath"); + + if (!existingClipPath) + { + var rectNode = document.createElementNS(NSS["svg"], "rect"); + var clipPath = document.createElementNS(NSS["svg"], "clipPath"); + + rectNode.setAttribute("x", 0); + rectNode.setAttribute("y", 0); + rectNode.setAttribute("width", WIDTH); + rectNode.setAttribute("height", HEIGHT); + + clipPath.setAttribute("id", "jessyInkSlideClipPath"); + clipPath.setAttribute("clipPathUnits", "userSpaceOnUse"); + + clipPath.appendChild(rectNode); + defsNodes[0].appendChild(clipPath); + } + } + + // Making a list of the slide and finding the master slide. + var nodes = document.getElementsByTagNameNS(NSS["svg"], "g"); + var tempSlides = new Array(); + var existingJessyInkPresentationLayer = null; + + for (var counter = 0; counter < nodes.length; counter++) + { + if (nodes[counter].getAttributeNS(NSS["inkscape"], "groupmode") && (nodes[counter].getAttributeNS(NSS["inkscape"], "groupmode") == "layer")) + { + if (nodes[counter].getAttributeNS(NSS["inkscape"], "label") && nodes[counter].getAttributeNS(NSS["jessyink"], "masterSlide") == "masterSlide") + masterSlide = nodes[counter]; + else if (nodes[counter].getAttributeNS(NSS["inkscape"], "label") && nodes[counter].getAttributeNS(NSS["jessyink"], "presentationLayer") == "presentationLayer") + existingJessyInkPresentationLayer = nodes[counter]; + else + tempSlides.push(nodes[counter].getAttribute("id")); + } + else if (nodes[counter].getAttributeNS(NSS['jessyink'], 'element')) + { + handleElement(nodes[counter]); + } + } + + // Hide master slide set default transitions. + if (masterSlide) + { + masterSlide.style.display = "none"; + + if (masterSlide.hasAttributeNS(NSS["jessyink"], "transitionIn")) + defaultTransitionInDict = propStrToDict(masterSlide.getAttributeNS(NSS["jessyink"], "transitionIn")); + + if (masterSlide.hasAttributeNS(NSS["jessyink"], "transitionOut")) + defaultTransitionOutDict = propStrToDict(masterSlide.getAttributeNS(NSS["jessyink"], "transitionOut")); + } + + if (existingJessyInkPresentationLayer != null) + { + existingJessyInkPresentationLayer.parentNode.removeChild(existingJessyInkPresentationLayer); + } + + // Set start slide. + var hashObj = new LocationHash(window.location.hash); + + activeSlide = hashObj.slideNumber; + activeEffect = hashObj.effectNumber; + + if (activeSlide < 0) + activeSlide = 0; + else if (activeSlide >= tempSlides.length) + activeSlide = tempSlides.length - 1; + + var originalNode = document.getElementById(tempSlides[counter]); + + var JessyInkPresentationLayer = document.createElementNS(NSS["svg"], "g"); + JessyInkPresentationLayer.setAttributeNS(NSS["inkscape"], "groupmode", "layer"); + JessyInkPresentationLayer.setAttributeNS(NSS["inkscape"], "label", "JessyInk Presentation Layer"); + JessyInkPresentationLayer.setAttributeNS(NSS["jessyink"], "presentationLayer", "presentationLayer"); + JessyInkPresentationLayer.setAttribute("id", "jessyink_presentation_layer"); + JessyInkPresentationLayer.style.display = "inherit"; + ROOT_NODE.appendChild(JessyInkPresentationLayer); + + // Gathering all the information about the transitions and effects of the slides, set the background + // from the master slide and substitute the auto-texts. + for (var counter = 0; counter < tempSlides.length; counter++) + { + var originalNode = document.getElementById(tempSlides[counter]); + originalNode.style.display = "none"; + var node = suffixNodeIds(originalNode.cloneNode(true), "_" + counter); + JessyInkPresentationLayer.appendChild(node); + slides[counter] = new Object(); + slides[counter]["original_element"] = originalNode; + slides[counter]["element"] = node; + + // Set build in transition. + slides[counter]["transitionIn"] = new Object(); + + var dict; + + if (node.hasAttributeNS(NSS["jessyink"], "transitionIn")) + dict = propStrToDict(node.getAttributeNS(NSS["jessyink"], "transitionIn")); + else + dict = defaultTransitionInDict; + + slides[counter]["transitionIn"]["name"] = dict["name"]; + slides[counter]["transitionIn"]["options"] = new Object(); + + for (key in dict) + if (key != "name") + slides[counter]["transitionIn"]["options"][key] = dict[key]; + + // Set build out transition. + slides[counter]["transitionOut"] = new Object(); + + if (node.hasAttributeNS(NSS["jessyink"], "transitionOut")) + dict = propStrToDict(node.getAttributeNS(NSS["jessyink"], "transitionOut")); + else + dict = defaultTransitionOutDict; + + slides[counter]["transitionOut"]["name"] = dict["name"]; + slides[counter]["transitionOut"]["options"] = new Object(); + + for (key in dict) + if (key != "name") + slides[counter]["transitionOut"]["options"][key] = dict[key]; + + // Copy master slide content. + if (masterSlide) + { + var clonedNode = suffixNodeIds(masterSlide.cloneNode(true), "_" + counter); + clonedNode.removeAttributeNS(NSS["inkscape"], "groupmode"); + clonedNode.removeAttributeNS(NSS["inkscape"], "label"); + clonedNode.style.display = "inherit"; + + node.insertBefore(clonedNode, node.firstChild); + } + + // Setting clip path. + node.setAttribute("clip-path", "url(#jessyInkSlideClipPath)"); + + // Substitute auto texts. + substituteAutoTexts(node, node.getAttributeNS(NSS["inkscape"], "label"), counter + 1, tempSlides.length); + + node.removeAttributeNS(NSS["inkscape"], "groupmode"); + node.removeAttributeNS(NSS["inkscape"], "label"); + + // Set effects. + var tempEffects = new Array(); + var groups = new Object(); + + for (var IOCounter = 0; IOCounter <= 1; IOCounter++) + { + var propName = ""; + var dir = 0; + + if (IOCounter == 0) + { + propName = "effectIn"; + dir = 1; + } + else if (IOCounter == 1) + { + propName = "effectOut"; + dir = -1; + } + + var effects = getElementsByPropertyNS(node, NSS["jessyink"], propName); + + for (var effectCounter = 0; effectCounter < effects.length; effectCounter++) + { + var element = document.getElementById(effects[effectCounter]); + var dict = propStrToDict(element.getAttributeNS(NSS["jessyink"], propName)); + + // Put every element that has an effect associated with it, into its own group. + // Unless of course, we already put it into its own group. + if (!(groups[element.id])) + { + var newGroup = document.createElementNS(NSS["svg"], "g"); + + element.parentNode.insertBefore(newGroup, element); + newGroup.appendChild(element.parentNode.removeChild(element)); + groups[element.id] = newGroup; + } + + var effectDict = new Object(); + + effectDict["effect"] = dict["name"]; + effectDict["dir"] = dir; + effectDict["element"] = groups[element.id]; + + for (var option in dict) + { + if ((option != "name") && (option != "order")) + { + if (!effectDict["options"]) + effectDict["options"] = new Object(); + + effectDict["options"][option] = dict[option]; + } + } + + if (!tempEffects[dict["order"]]) + tempEffects[dict["order"]] = new Array(); + + tempEffects[dict["order"]][tempEffects[dict["order"]].length] = effectDict; + } + } + + // Make invisible, but keep in rendering tree to ensure that bounding box can be calculated. + node.setAttribute("opacity",0); + node.style.display = "inherit"; + + // Create a transform group. + var transformGroup = document.createElementNS(NSS["svg"], "g"); + + // Add content to transform group. + while (node.firstChild) + transformGroup.appendChild(node.firstChild); + + // Transfer the transform attribute from the node to the transform group. + if (node.getAttribute("transform")) + { + transformGroup.setAttribute("transform", node.getAttribute("transform")); + node.removeAttribute("transform"); + } + + // Create a view group. + var viewGroup = document.createElementNS(NSS["svg"], "g"); + + viewGroup.appendChild(transformGroup); + slides[counter]["viewGroup"] = node.appendChild(viewGroup); + + // Insert background. + if (BACKGROUND_COLOR != null) + { + var rectNode = document.createElementNS(NSS["svg"], "rect"); + + rectNode.setAttribute("x", 0); + rectNode.setAttribute("y", 0); + rectNode.setAttribute("width", WIDTH); + rectNode.setAttribute("height", HEIGHT); + rectNode.setAttribute("id", "jessyInkBackground" + counter); + rectNode.setAttribute("fill", BACKGROUND_COLOR); + + slides[counter]["viewGroup"].insertBefore(rectNode, slides[counter]["viewGroup"].firstChild); + } + + // Set views. + var tempViews = new Array(); + var views = getElementsByPropertyNS(node, NSS["jessyink"], "view"); + var matrixOld = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + + // Set initial view even if there are no other views. + slides[counter]["viewGroup"].setAttribute("transform", matrixOld.toAttribute()); + slides[counter].initialView = matrixOld.toAttribute(); + + for (var viewCounter = 0; viewCounter < views.length; viewCounter++) + { + var element = document.getElementById(views[viewCounter]); + var dict = propStrToDict(element.getAttributeNS(NSS["jessyink"], "view")); + + if (dict["order"] == 0) + { + matrixOld = pointMatrixToTransformation(rectToMatrix(element)).mult((new matrixSVG()).fromSVGMatrix(slides[counter].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(element.parentNode.getScreenCTM())).inv()); + slides[counter].initialView = matrixOld.toAttribute(); + } + else + { + var effectDict = new Object(); + + effectDict["effect"] = dict["name"]; + effectDict["dir"] = 1; + effectDict["element"] = slides[counter]["viewGroup"]; + effectDict["order"] = dict["order"]; + + for (var option in dict) + { + if ((option != "name") && (option != "order")) + { + if (!effectDict["options"]) + effectDict["options"] = new Object(); + + effectDict["options"][option] = dict[option]; + } + } + + effectDict["options"]["matrixNew"] = pointMatrixToTransformation(rectToMatrix(element)).mult((new matrixSVG()).fromSVGMatrix(slides[counter].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(element.parentNode.getScreenCTM())).inv()); + + tempViews[dict["order"]] = effectDict; + } + + // Remove element. + element.parentNode.removeChild(element); + } + + // Consolidate view array and append it to the effect array. + if (tempViews.length > 0) + { + for (var viewCounter = 0; viewCounter < tempViews.length; viewCounter++) + { + if (tempViews[viewCounter]) + { + tempViews[viewCounter]["options"]["matrixOld"] = matrixOld; + matrixOld = tempViews[viewCounter]["options"]["matrixNew"]; + + if (!tempEffects[tempViews[viewCounter]["order"]]) + tempEffects[tempViews[viewCounter]["order"]] = new Array(); + + tempEffects[tempViews[viewCounter]["order"]][tempEffects[tempViews[viewCounter]["order"]].length] = tempViews[viewCounter]; + } + } + } + + // Set consolidated effect array. + if (tempEffects.length > 0) + { + slides[counter]["effects"] = new Array(); + + for (var effectCounter = 0; effectCounter < tempEffects.length; effectCounter++) + { + if (tempEffects[effectCounter]) + slides[counter]["effects"][slides[counter]["effects"].length] = tempEffects[effectCounter]; + } + } + + node.setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + counter + ")) { indexSetActiveSlide(" + counter + "); };"); + + // Set visibility for initial state. + if (counter == activeSlide) + { + node.style.display = "inherit"; + node.setAttribute("opacity",1); + } + else + { + node.style.display = "none"; + node.setAttribute("opacity",0); + } + } + + // Set key handler. + var jessyInkObjects = document.getElementsByTagNameNS(NSS["svg"], "g"); + + for (var counter = 0; counter < jessyInkObjects.length; counter++) + { + var elem = jessyInkObjects[counter]; + + if (elem.getAttributeNS(NSS["jessyink"], "customKeyBindings")) + { + if (elem.getCustomKeyBindings != undefined) + keyCodeDictionary = elem.getCustomKeyBindings(); + + if (elem.getCustomCharBindings != undefined) + charCodeDictionary = elem.getCustomCharBindings(); + } + } + + // Set mouse handler. + var jessyInkMouseHandler = document.getElementsByTagNameNS(NSS["jessyink"], "mousehandler"); + + for (var counter = 0; counter < jessyInkMouseHandler.length; counter++) + { + var elem = jessyInkMouseHandler[counter]; + + if (elem.getMouseHandler != undefined) + { + var tempDict = elem.getMouseHandler(); + + for (mode in tempDict) + { + if (!mouseHandlerDictionary[mode]) + mouseHandlerDictionary[mode] = new Object(); + + for (handler in tempDict[mode]) + mouseHandlerDictionary[mode][handler] = tempDict[mode][handler]; + } + } + } + + // Check effect number. + if ((activeEffect < 0) || (!slides[activeSlide].effects)) + { + activeEffect = 0; + } + else if (activeEffect > slides[activeSlide].effects.length) + { + activeEffect = slides[activeSlide].effects.length; + } + + createProgressBar(JessyInkPresentationLayer); + hideProgressBar(); + setProgressBarValue(activeSlide); + setTimeIndicatorValue(0); + setInterval("updateTimer()", 1000); + setSlideToState(activeSlide, activeEffect); + jessyInkInitialised = true; +} + +/** Function to substitute the auto-texts. + * + * @param node the node + * @param slideName name of the slide the node is on + * @param slideNumber number of the slide the node is on + * @param numberOfSlides number of slides in the presentation + */ +function substituteAutoTexts(node, slideName, slideNumber, numberOfSlides) +{ + var texts = node.getElementsByTagNameNS(NSS["svg"], "tspan"); + + for (var textCounter = 0; textCounter < texts.length; textCounter++) + { + if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "slideNumber") + texts[textCounter].firstChild.nodeValue = slideNumber; + else if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "numberOfSlides") + texts[textCounter].firstChild.nodeValue = numberOfSlides; + else if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "slideTitle") + texts[textCounter].firstChild.nodeValue = slideName; + } +} + +/** Convenience function to get an element depending on whether it has a property with a particular name. + * This function emulates some dearly missed XPath functionality. + * + * @param node the node + * @param namespace namespace of the attribute + * @param name attribute name + */ +function getElementsByPropertyNS(node, namespace, name) +{ + var elems = new Array(); + + if (node.getAttributeNS(namespace, name)) + elems.push(node.getAttribute("id")); + + for (var counter = 0; counter < node.childNodes.length; counter++) + { + if (node.childNodes[counter].nodeType == 1) + elems = elems.concat(getElementsByPropertyNS(node.childNodes[counter], namespace, name)); + } + + return elems; +} + +/** Function to dispatch the next effect, if there is none left, change the slide. + * + * @param dir direction of the change (1 = forwards, -1 = backwards) + */ +function dispatchEffects(dir) +{ + if (slides[activeSlide]["effects"] && (((dir == 1) && (activeEffect < slides[activeSlide]["effects"].length)) || ((dir == -1) && (activeEffect > 0)))) + { + processingEffect = true; + + if (dir == 1) + { + effectArray = slides[activeSlide]["effects"][activeEffect]; + activeEffect += dir; + } + else if (dir == -1) + { + activeEffect += dir; + effectArray = slides[activeSlide]["effects"][activeEffect]; + } + + transCounter = 0; + startTime = (new Date()).getTime(); + lastFrameTime = null; + effect(dir); + } + else if (((dir == 1) && (activeSlide < (slides.length - 1))) || (((dir == -1) && (activeSlide > 0)))) + { + changeSlide(dir); + } +} + +/** Function to skip effects and directly either put the slide into start or end state or change slides. + * + * @param dir direction of the change (1 = forwards, -1 = backwards) + */ +function skipEffects(dir) +{ + if (slides[activeSlide]["effects"] && (((dir == 1) && (activeEffect < slides[activeSlide]["effects"].length)) || ((dir == -1) && (activeEffect > 0)))) + { + processingEffect = true; + + if (slides[activeSlide]["effects"] && (dir == 1)) + activeEffect = slides[activeSlide]["effects"].length; + else + activeEffect = 0; + + if (dir == 1) + setSlideToState(activeSlide, STATE_END); + else + setSlideToState(activeSlide, STATE_START); + + processingEffect = false; + } + else if (((dir == 1) && (activeSlide < (slides.length - 1))) || (((dir == -1) && (activeSlide > 0)))) + { + changeSlide(dir); + } +} + +/** Function to change between slides. + * + * @param dir direction (1 = forwards, -1 = backwards) + */ +function changeSlide(dir) +{ + processingEffect = true; + effectArray = new Array(); + + effectArray[0] = new Object(); + if (dir == 1) + { + effectArray[0]["effect"] = slides[activeSlide]["transitionOut"]["name"]; + effectArray[0]["options"] = slides[activeSlide]["transitionOut"]["options"]; + effectArray[0]["dir"] = -1; + } + else if (dir == -1) + { + effectArray[0]["effect"] = slides[activeSlide]["transitionIn"]["name"]; + effectArray[0]["options"] = slides[activeSlide]["transitionIn"]["options"]; + effectArray[0]["dir"] = 1; + } + effectArray[0]["element"] = slides[activeSlide]["element"]; + + activeSlide += dir; + setProgressBarValue(activeSlide); + + effectArray[1] = new Object(); + + if (dir == 1) + { + effectArray[1]["effect"] = slides[activeSlide]["transitionIn"]["name"]; + effectArray[1]["options"] = slides[activeSlide]["transitionIn"]["options"]; + effectArray[1]["dir"] = 1; + } + else if (dir == -1) + { + effectArray[1]["effect"] = slides[activeSlide]["transitionOut"]["name"]; + effectArray[1]["options"] = slides[activeSlide]["transitionOut"]["options"]; + effectArray[1]["dir"] = -1; + } + + effectArray[1]["element"] = slides[activeSlide]["element"]; + + if (slides[activeSlide]["effects"] && (dir == -1)) + activeEffect = slides[activeSlide]["effects"].length; + else + activeEffect = 0; + + if (dir == -1) + setSlideToState(activeSlide, STATE_END); + else + setSlideToState(activeSlide, STATE_START); + + transCounter = 0; + startTime = (new Date()).getTime(); + lastFrameTime = null; + effect(dir); +} + +/** Function to toggle between index and slide mode. +*/ +function toggleSlideIndex() +{ + var suspendHandle = ROOT_NODE.suspendRedraw(500); + + if (currentMode == SLIDE_MODE) + { + hideProgressBar(); + INDEX_OFFSET = -1; + indexSetPageSlide(activeSlide); + currentMode = INDEX_MODE; + } + else if (currentMode == INDEX_MODE) + { + for (var counter = 0; counter < slides.length; counter++) + { + slides[counter]["element"].setAttribute("transform","scale(1)"); + + if (counter == activeSlide) + { + slides[counter]["element"].style.display = "inherit"; + slides[counter]["element"].setAttribute("opacity",1); + activeEffect = 0; + } + else + { + slides[counter]["element"].setAttribute("opacity",0); + slides[counter]["element"].style.display = "none"; + } + } + currentMode = SLIDE_MODE; + setSlideToState(activeSlide, STATE_START); + setProgressBarValue(activeSlide); + + if (progress_bar_visible) + { + showProgressBar(); + } + } + + ROOT_NODE.unsuspendRedraw(suspendHandle); + ROOT_NODE.forceRedraw(); +} + +/** Function to run an effect. + * + * @param dir direction in which to play the effect (1 = forwards, -1 = backwards) + */ +function effect(dir) +{ + var done = true; + + var suspendHandle = ROOT_NODE.suspendRedraw(200); + + for (var counter = 0; counter < effectArray.length; counter++) + { + if (effectArray[counter]["effect"] == "fade") + done &= fade(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]); + else if (effectArray[counter]["effect"] == "appear") + done &= appear(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]); + else if (effectArray[counter]["effect"] == "pop") + done &= pop(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]); + else if (effectArray[counter]["effect"] == "view") + done &= view(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]); + } + + ROOT_NODE.unsuspendRedraw(suspendHandle); + ROOT_NODE.forceRedraw(); + + if (!done) + { + var currentTime = (new Date()).getTime(); + var timeDiff = 1; + + transCounter = currentTime - startTime; + + if (lastFrameTime != null) + { + timeDiff = timeStep - (currentTime - lastFrameTime); + + if (timeDiff <= 0) + timeDiff = 1; + } + + lastFrameTime = currentTime; + + window.setTimeout("effect(" + dir + ")", timeDiff); + } + else + { + window.location.hash = (activeSlide + 1) + '_' + activeEffect; + processingEffect = false; + } +} + +/** Function to display the index sheet. + * + * @param offsetNumber offset number + */ +function displayIndex(offsetNumber) +{ + var offsetX = 0; + var offsetY = 0; + + if (offsetNumber < 0) + offsetNumber = 0; + else if (offsetNumber >= slides.length) + offsetNumber = slides.length - 1; + + for (var counter = 0; counter < slides.length; counter++) + { + if ((counter < offsetNumber) || (counter > offsetNumber + INDEX_COLUMNS * INDEX_COLUMNS - 1)) + { + slides[counter]["element"].setAttribute("opacity",0); + slides[counter]["element"].style.display = "none"; + } + else + { + offsetX = ((counter - offsetNumber) % INDEX_COLUMNS) * WIDTH; + offsetY = Math.floor((counter - offsetNumber) / INDEX_COLUMNS) * HEIGHT; + + slides[counter]["element"].setAttribute("transform","scale("+1/INDEX_COLUMNS+") translate("+offsetX+","+offsetY+")"); + slides[counter]["element"].style.display = "inherit"; + slides[counter]["element"].setAttribute("opacity",0.5); + } + + setSlideToState(counter, STATE_END); + } + + //do we need to save the current offset? + if (INDEX_OFFSET != offsetNumber) + INDEX_OFFSET = offsetNumber; +} + +/** Function to set the active slide in the slide view. + * + * @param nbr index of the active slide + */ +function slideSetActiveSlide(nbr) +{ + if (nbr >= slides.length) + nbr = slides.length - 1; + else if (nbr < 0) + nbr = 0; + + slides[activeSlide]["element"].setAttribute("opacity",0); + slides[activeSlide]["element"].style.display = "none"; + + activeSlide = parseInt(nbr); + + setSlideToState(activeSlide, STATE_START); + slides[activeSlide]["element"].style.display = "inherit"; + slides[activeSlide]["element"].setAttribute("opacity",1); + + activeEffect = 0; + setProgressBarValue(nbr); +} + +/** Function to set the active slide in the index view. + * + * @param nbr index of the active slide + */ +function indexSetActiveSlide(nbr) +{ + if (nbr >= slides.length) + nbr = slides.length - 1; + else if (nbr < 0) + nbr = 0; + + slides[activeSlide]["element"].setAttribute("opacity",0.5); + + activeSlide = parseInt(nbr); + window.location.hash = (activeSlide + 1) + '_0'; + + slides[activeSlide]["element"].setAttribute("opacity",1); +} + +/** Function to set the page and active slide in index view. + * + * @param nbr index of the active slide + * + * NOTE: To force a redraw, + * set INDEX_OFFSET to -1 before calling indexSetPageSlide(). + * + * This is necessary for zooming (otherwise the index might not + * get redrawn) and when switching to index mode. + * + * INDEX_OFFSET = -1 + * indexSetPageSlide(activeSlide); + */ +function indexSetPageSlide(nbr) +{ + if (nbr >= slides.length) + nbr = slides.length - 1; + else if (nbr < 0) + nbr = 0; + + //calculate the offset + var offset = nbr - nbr % (INDEX_COLUMNS * INDEX_COLUMNS); + + if (offset < 0) + offset = 0; + + //if different from kept offset, then record and change the page + if (offset != INDEX_OFFSET) + { + INDEX_OFFSET = offset; + displayIndex(INDEX_OFFSET); + } + + //set the active slide + indexSetActiveSlide(nbr); +} + +/** Event handler for key press. + * + * @param e the event + */ +function keydown(e) +{ + if (!e) + e = window.event; + + code = e.keyCode || e.charCode; + + if (!processingEffect && keyCodeDictionary[currentMode] && keyCodeDictionary[currentMode][code]) + return keyCodeDictionary[currentMode][code](); + else + document.onkeypress = keypress; +} +// Set event handler for key down. +document.onkeydown = keydown; + +/** Event handler for key press. + * + * @param e the event + */ +function keypress(e) +{ + document.onkeypress = null; + + if (!e) + e = window.event; + + str = String.fromCharCode(e.keyCode || e.charCode); + + if (!processingEffect && charCodeDictionary[currentMode] && charCodeDictionary[currentMode][str]) + return charCodeDictionary[currentMode][str](); +} + +/** Function to supply the default char code dictionary. + * + * @returns default char code dictionary + */ +function getDefaultCharCodeDictionary() +{ + var charCodeDict = new Object(); + + charCodeDict[SLIDE_MODE] = new Object(); + charCodeDict[INDEX_MODE] = new Object(); + charCodeDict[DRAWING_MODE] = new Object(); + + charCodeDict[SLIDE_MODE]["i"] = function () { return toggleSlideIndex(); }; + charCodeDict[SLIDE_MODE]["d"] = function () { return slideSwitchToDrawingMode(); }; + charCodeDict[SLIDE_MODE]["D"] = function () { return slideQueryDuration(); }; + charCodeDict[SLIDE_MODE]["n"] = function () { return slideAddSlide(activeSlide); }; + charCodeDict[SLIDE_MODE]["p"] = function () { return slideToggleProgressBarVisibility(); }; + charCodeDict[SLIDE_MODE]["t"] = function () { return slideResetTimer(); }; + charCodeDict[SLIDE_MODE]["e"] = function () { return slideUpdateExportLayer(); }; + + charCodeDict[DRAWING_MODE]["d"] = function () { return drawingSwitchToSlideMode(); }; + charCodeDict[DRAWING_MODE]["0"] = function () { return drawingResetPathWidth(); }; + charCodeDict[DRAWING_MODE]["1"] = function () { return drawingSetPathWidth(1.0); }; + charCodeDict[DRAWING_MODE]["3"] = function () { return drawingSetPathWidth(3.0); }; + charCodeDict[DRAWING_MODE]["5"] = function () { return drawingSetPathWidth(5.0); }; + charCodeDict[DRAWING_MODE]["7"] = function () { return drawingSetPathWidth(7.0); }; + charCodeDict[DRAWING_MODE]["9"] = function () { return drawingSetPathWidth(9.0); }; + charCodeDict[DRAWING_MODE]["b"] = function () { return drawingSetPathColour("blue"); }; + charCodeDict[DRAWING_MODE]["c"] = function () { return drawingSetPathColour("cyan"); }; + charCodeDict[DRAWING_MODE]["g"] = function () { return drawingSetPathColour("green"); }; + charCodeDict[DRAWING_MODE]["k"] = function () { return drawingSetPathColour("black"); }; + charCodeDict[DRAWING_MODE]["m"] = function () { return drawingSetPathColour("magenta"); }; + charCodeDict[DRAWING_MODE]["o"] = function () { return drawingSetPathColour("orange"); }; + charCodeDict[DRAWING_MODE]["r"] = function () { return drawingSetPathColour("red"); }; + charCodeDict[DRAWING_MODE]["w"] = function () { return drawingSetPathColour("white"); }; + charCodeDict[DRAWING_MODE]["y"] = function () { return drawingSetPathColour("yellow"); }; + charCodeDict[DRAWING_MODE]["z"] = function () { return drawingUndo(); }; + + charCodeDict[INDEX_MODE]["i"] = function () { return toggleSlideIndex(); }; + charCodeDict[INDEX_MODE]["-"] = function () { return indexDecreaseNumberOfColumns(); }; + charCodeDict[INDEX_MODE]["="] = function () { return indexIncreaseNumberOfColumns(); }; + charCodeDict[INDEX_MODE]["+"] = function () { return indexIncreaseNumberOfColumns(); }; + charCodeDict[INDEX_MODE]["0"] = function () { return indexResetNumberOfColumns(); }; + + return charCodeDict; +} + +/** Function to supply the default key code dictionary. + * + * @returns default key code dictionary + */ +function getDefaultKeyCodeDictionary() +{ + var keyCodeDict = new Object(); + + keyCodeDict[SLIDE_MODE] = new Object(); + keyCodeDict[INDEX_MODE] = new Object(); + keyCodeDict[DRAWING_MODE] = new Object(); + + keyCodeDict[SLIDE_MODE][LEFT_KEY] = function() { return dispatchEffects(-1); }; + keyCodeDict[SLIDE_MODE][RIGHT_KEY] = function() { return dispatchEffects(1); }; + keyCodeDict[SLIDE_MODE][UP_KEY] = function() { return skipEffects(-1); }; + keyCodeDict[SLIDE_MODE][DOWN_KEY] = function() { return skipEffects(1); }; + keyCodeDict[SLIDE_MODE][PAGE_UP_KEY] = function() { return dispatchEffects(-1); }; + keyCodeDict[SLIDE_MODE][PAGE_DOWN_KEY] = function() { return dispatchEffects(1); }; + keyCodeDict[SLIDE_MODE][HOME_KEY] = function() { return slideSetActiveSlide(0); }; + keyCodeDict[SLIDE_MODE][END_KEY] = function() { return slideSetActiveSlide(slides.length - 1); }; + keyCodeDict[SLIDE_MODE][SPACE_KEY] = function() { return dispatchEffects(1); }; + + keyCodeDict[INDEX_MODE][LEFT_KEY] = function() { return indexSetPageSlide(activeSlide - 1); }; + keyCodeDict[INDEX_MODE][RIGHT_KEY] = function() { return indexSetPageSlide(activeSlide + 1); }; + keyCodeDict[INDEX_MODE][UP_KEY] = function() { return indexSetPageSlide(activeSlide - INDEX_COLUMNS); }; + keyCodeDict[INDEX_MODE][DOWN_KEY] = function() { return indexSetPageSlide(activeSlide + INDEX_COLUMNS); }; + keyCodeDict[INDEX_MODE][PAGE_UP_KEY] = function() { return indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS); }; + keyCodeDict[INDEX_MODE][PAGE_DOWN_KEY] = function() { return indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS); }; + keyCodeDict[INDEX_MODE][HOME_KEY] = function() { return indexSetPageSlide(0); }; + keyCodeDict[INDEX_MODE][END_KEY] = function() { return indexSetPageSlide(slides.length - 1); }; + keyCodeDict[INDEX_MODE][ENTER_KEY] = function() { return toggleSlideIndex(); }; + + keyCodeDict[DRAWING_MODE][ESCAPE_KEY] = function () { return drawingSwitchToSlideMode(); }; + + return keyCodeDict; +} + +/** Function to handle all mouse events. + * + * @param evnt event + * @param action type of event (e.g. mouse up, mouse wheel) + */ +function mouseHandlerDispatch(evnt, action) +{ + if (!evnt) + evnt = window.event; + + var retVal = true; + + if (!processingEffect && mouseHandlerDictionary[currentMode] && mouseHandlerDictionary[currentMode][action]) + { + var subRetVal = mouseHandlerDictionary[currentMode][action](evnt); + + if (subRetVal != null && subRetVal != undefined) + retVal = subRetVal; + } + + if (evnt.preventDefault && !retVal) + evnt.preventDefault(); + + evnt.returnValue = retVal; + + return retVal; +} + +// Set mouse event handler. +document.onmousedown = function(e) { return mouseHandlerDispatch(e, MOUSE_DOWN); }; +document.onmouseup = function(e) { return mouseHandlerDispatch(e, MOUSE_UP); }; +document.onmousemove = function(e) { return mouseHandlerDispatch(e, MOUSE_MOVE); }; + +// Moz +if (window.addEventListener) +{ + window.addEventListener('DOMMouseScroll', function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); }, false); +} + +// Opera Safari OK - may not work in IE +window.onmousewheel = function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); }; + +/** Function to supply the default mouse handler dictionary. + * + * @returns default mouse handler dictionary + */ +function getDefaultMouseHandlerDictionary() +{ + var mouseHandlerDict = new Object(); + + mouseHandlerDict[SLIDE_MODE] = new Object(); + mouseHandlerDict[INDEX_MODE] = new Object(); + mouseHandlerDict[DRAWING_MODE] = new Object(); + + mouseHandlerDict[SLIDE_MODE][MOUSE_DOWN] = function(evnt) { return dispatchEffects(1); }; + mouseHandlerDict[SLIDE_MODE][MOUSE_WHEEL] = function(evnt) { return slideMousewheel(evnt); }; + + mouseHandlerDict[INDEX_MODE][MOUSE_DOWN] = function(evnt) { return toggleSlideIndex(); }; + + mouseHandlerDict[DRAWING_MODE][MOUSE_DOWN] = function(evnt) { return drawingMousedown(evnt); }; + mouseHandlerDict[DRAWING_MODE][MOUSE_UP] = function(evnt) { return drawingMouseup(evnt); }; + mouseHandlerDict[DRAWING_MODE][MOUSE_MOVE] = function(evnt) { return drawingMousemove(evnt); }; + + return mouseHandlerDict; +} + +/** Function to switch from slide mode to drawing mode. +*/ +function slideSwitchToDrawingMode() +{ + currentMode = DRAWING_MODE; + + var tempDict; + + if (ROOT_NODE.hasAttribute("style")) + tempDict = propStrToDict(ROOT_NODE.getAttribute("style")); + else + tempDict = new Object(); + + tempDict["cursor"] = "crosshair"; + ROOT_NODE.setAttribute("style", dictToPropStr(tempDict)); +} + +/** Function to switch from drawing mode to slide mode. +*/ +function drawingSwitchToSlideMode() +{ + currentMode = SLIDE_MODE; + + var tempDict; + + if (ROOT_NODE.hasAttribute("style")) + tempDict = propStrToDict(ROOT_NODE.getAttribute("style")); + else + tempDict = new Object(); + + tempDict["cursor"] = "auto"; + ROOT_NODE.setAttribute("style", dictToPropStr(tempDict)); +} + +/** Function to decrease the number of columns in index mode. +*/ +function indexDecreaseNumberOfColumns() +{ + if (INDEX_COLUMNS >= 3) + { + INDEX_COLUMNS -= 1; + INDEX_OFFSET = -1 + indexSetPageSlide(activeSlide); + } +} + +/** Function to increase the number of columns in index mode. +*/ +function indexIncreaseNumberOfColumns() +{ + if (INDEX_COLUMNS < 7) + { + INDEX_COLUMNS += 1; + INDEX_OFFSET = -1 + indexSetPageSlide(activeSlide); + } +} + +/** Function to reset the number of columns in index mode. +*/ +function indexResetNumberOfColumns() +{ + if (INDEX_COLUMNS != INDEX_COLUMNS_DEFAULT) + { + INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT; + INDEX_OFFSET = -1 + indexSetPageSlide(activeSlide); + } +} + +/** Function to reset path width in drawing mode. +*/ +function drawingResetPathWidth() +{ + path_width = path_width_default; + set_path_paint_width(); +} + +/** Function to set path width in drawing mode. + * + * @param width new path width + */ +function drawingSetPathWidth(width) +{ + path_width = width; + set_path_paint_width(); +} + +/** Function to set path colour in drawing mode. + * + * @param colour new path colour + */ +function drawingSetPathColour(colour) +{ + path_colour = colour; +} + +/** Function to query the duration of the presentation from the user in slide mode. +*/ +function slideQueryDuration() +{ + var new_duration = prompt("Length of presentation in minutes?", timer_duration); + + if ((new_duration != null) && (new_duration != '')) + { + timer_duration = new_duration; + } + + updateTimer(); +} + +/** Function to add new slide in slide mode. + * + * @param afterSlide after which slide to insert the new one + */ +function slideAddSlide(afterSlide) +{ + addSlide(afterSlide); + slideSetActiveSlide(afterSlide + 1); + updateTimer(); +} + +/** Function to toggle the visibility of the progress bar in slide mode. +*/ +function slideToggleProgressBarVisibility() +{ + if (progress_bar_visible) + { + progress_bar_visible = false; + hideProgressBar(); + } + else + { + progress_bar_visible = true; + showProgressBar(); + } +} + +/** Function to reset the timer in slide mode. +*/ +function slideResetTimer() +{ + timer_start = timer_elapsed; + updateTimer(); +} + +/** Convenience function to pad a string with zero in front up to a certain length. + */ +function padString(str, len) +{ + var outStr = str; + + while (outStr.length < len) + { + outStr = '0' + outStr; + } + + return outStr; +} + +/** Function to update the export layer. + */ +function slideUpdateExportLayer() +{ + // Suspend redraw since we are going to mess with the slides. + var suspendHandle = ROOT_NODE.suspendRedraw(2000); + + var tmpActiveSlide = activeSlide; + var tmpActiveEffect = activeEffect; + var exportedLayers = new Array(); + + for (var counterSlides = 0; counterSlides < slides.length; counterSlides++) + { + var exportNode; + + setSlideToState(counterSlides, STATE_START); + + var maxEffect = 0; + + if (slides[counterSlides].effects) + { + maxEffect = slides[counterSlides].effects.length; + } + + exportNode = slides[counterSlides].element.cloneNode(true); + exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer"); + exportNode.setAttributeNS(NSS["inkscape"], "label", "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString("0", maxEffect.toString().length)); + + exportedLayers.push(exportNode); + + if (slides[counterSlides]["effects"]) + { + for (var counter = 0; counter < slides[counterSlides]["effects"].length; counter++) + { + for (var subCounter = 0; subCounter < slides[counterSlides]["effects"][counter].length; subCounter++) + { + var effect = slides[counterSlides]["effects"][counter][subCounter]; + if (effect["effect"] == "fade") + fade(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "appear") + appear(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "pop") + pop(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "view") + view(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); + } + + var layerName = "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString((counter + 1).toString(), maxEffect.toString().length); + exportNode = slides[counterSlides].element.cloneNode(true); + exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer"); + exportNode.setAttributeNS(NSS["inkscape"], "label", layerName); + exportNode.setAttribute("id", layerName); + + exportedLayers.push(exportNode); + } + } + } + + activeSlide = tmpActiveSlide; + activeEffect = tmpActiveEffect; + setSlideToState(activeSlide, activeEffect); + + // Copy image. + var newDoc = document.documentElement.cloneNode(true); + + // Delete viewbox form new imag and set width and height. + newDoc.removeAttribute('viewbox'); + newDoc.setAttribute('width', WIDTH); + newDoc.setAttribute('height', HEIGHT); + + // Delete all layers and script elements. + var nodesToBeRemoved = new Array(); + + for (var childCounter = 0; childCounter < newDoc.childNodes.length; childCounter++) + { + var child = newDoc.childNodes[childCounter]; + + if (child.nodeType == 1) + { + if ((child.nodeName.toUpperCase() == 'G') || (child.nodeName.toUpperCase() == 'SCRIPT')) + { + nodesToBeRemoved.push(child); + } + } + } + + for (var ndCounter = 0; ndCounter < nodesToBeRemoved.length; ndCounter++) + { + var nd = nodesToBeRemoved[ndCounter]; + + // Before removing the node, check whether it contains any definitions. + var defs = nd.getElementsByTagNameNS(NSS["svg"], "defs"); + + for (var defsCounter = 0; defsCounter < defs.length; defsCounter++) + { + if (defs[defsCounter].id) + { + newDoc.appendChild(defs[defsCounter].cloneNode(true)); + } + } + + // Remove node. + nd.parentNode.removeChild(nd); + } + + // Set current layer. + if (exportedLayers[0]) + { + var namedView; + + for (var nodeCounter = 0; nodeCounter < newDoc.childNodes.length; nodeCounter++) + { + if ((newDoc.childNodes[nodeCounter].nodeType == 1) && (newDoc.childNodes[nodeCounter].getAttribute('id') == 'base')) + { + namedView = newDoc.childNodes[nodeCounter]; + } + } + + if (namedView) + { + namedView.setAttributeNS(NSS['inkscape'], 'current-layer', exportedLayers[0].getAttributeNS(NSS['inkscape'], 'label')); + } + } + + // Add exported layers. + while (exportedLayers.length > 0) + { + var nd = exportedLayers.pop(); + + nd.setAttribute("opacity",1); + nd.style.display = "inherit"; + + newDoc.appendChild(nd); + } + + // Serialise the new document. + window.location = 'data:application/svg+xml;base64;charset=utf-8,' + window.btoa(unescape(encodeURIComponent((new XMLSerializer()).serializeToString(newDoc)))); + + // Unsuspend redraw. + ROOT_NODE.unsuspendRedraw(suspendHandle); + ROOT_NODE.forceRedraw(); +} + +/** Function to undo last drawing operation. +*/ +function drawingUndo() +{ + mouse_presentation_path = null; + mouse_original_path = null; + + if (history_presentation_elements.length > 0) + { + var p = history_presentation_elements.pop(); + var parent = p.parentNode.removeChild(p); + + p = history_original_elements.pop(); + parent = p.parentNode.removeChild(p); + } +} + +/** Event handler for mouse down in drawing mode. + * + * @param e the event + */ +function drawingMousedown(e) +{ + var value = 0; + + if (e.button) + value = e.button; + else if (e.which) + value = e.which; + + if (value == 1) + { + history_counter++; + + var p = calcCoord(e); + + mouse_last_x = e.clientX; + mouse_last_y = e.clientY; + mouse_original_path = document.createElementNS(NSS["svg"], "path"); + mouse_original_path.setAttribute("stroke", path_colour); + mouse_original_path.setAttribute("stroke-width", path_paint_width); + mouse_original_path.setAttribute("fill", "none"); + mouse_original_path.setAttribute("id", "path " + Date()); + mouse_original_path.setAttribute("d", "M" + p.x + "," + p.y); + slides[activeSlide]["original_element"].appendChild(mouse_original_path); + history_original_elements.push(mouse_original_path); + + mouse_presentation_path = document.createElementNS(NSS["svg"], "path"); + mouse_presentation_path.setAttribute("stroke", path_colour); + mouse_presentation_path.setAttribute("stroke-width", path_paint_width); + mouse_presentation_path.setAttribute("fill", "none"); + mouse_presentation_path.setAttribute("id", "path " + Date() + " presentation copy"); + mouse_presentation_path.setAttribute("d", "M" + p.x + "," + p.y); + + if (slides[activeSlide]["viewGroup"]) + slides[activeSlide]["viewGroup"].appendChild(mouse_presentation_path); + else + slides[activeSlide]["element"].appendChild(mouse_presentation_path); + + history_presentation_elements.push(mouse_presentation_path); + + return false; + } + + return true; +} + +/** Event handler for mouse up in drawing mode. + * + * @param e the event + */ +function drawingMouseup(e) +{ + if(!e) + e = window.event; + + if (mouse_presentation_path != null) + { + var p = calcCoord(e); + var d = mouse_presentation_path.getAttribute("d"); + d += " L" + p.x + "," + p.y; + mouse_presentation_path.setAttribute("d", d); + mouse_presentation_path = null; + mouse_original_path.setAttribute("d", d); + mouse_original_path = null; + + return false; + } + + return true; +} + +/** Event handler for mouse move in drawing mode. + * + * @param e the event + */ +function drawingMousemove(e) +{ + if(!e) + e = window.event; + + var dist = (mouse_last_x - e.clientX) * (mouse_last_x - e.clientX) + (mouse_last_y - e.clientY) * (mouse_last_y - e.clientY); + + if (mouse_presentation_path == null) + { + return true; + } + + if (dist >= mouse_min_dist_sqr) + { + var p = calcCoord(e); + var d = mouse_presentation_path.getAttribute("d"); + d += " L" + p.x + "," + p.y; + mouse_presentation_path.setAttribute("d", d); + mouse_original_path.setAttribute("d", d); + mouse_last_x = e.clientX; + mouse_last_y = e.clientY; + } + + return false; +} + +/** Event handler for mouse wheel events in slide mode. + * based on http://adomas.org/javascript-mouse-wheel/ + * + * @param e the event + */ +function slideMousewheel(e) +{ + var delta = 0; + + if (!e) + e = window.event; + + if (e.wheelDelta) + { // IE Opera + delta = e.wheelDelta/120; + } + else if (e.detail) + { // MOZ + delta = -e.detail/3; + } + + if (delta > 0) + skipEffects(-1); + else if (delta < 0) + skipEffects(1); + + if (e.preventDefault) + e.preventDefault(); + + e.returnValue = false; +} + +/** Event handler for mouse wheel events in index mode. + * based on http://adomas.org/javascript-mouse-wheel/ + * + * @param e the event + */ +function indexMousewheel(e) +{ + var delta = 0; + + if (!e) + e = window.event; + + if (e.wheelDelta) + { // IE Opera + delta = e.wheelDelta/120; + } + else if (e.detail) + { // MOZ + delta = -e.detail/3; + } + + if (delta > 0) + indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS); + else if (delta < 0) + indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS); + + if (e.preventDefault) + e.preventDefault(); + + e.returnValue = false; +} + +/** Function to set the path paint width. +*/ +function set_path_paint_width() +{ + var svgPoint1 = document.documentElement.createSVGPoint(); + var svgPoint2 = document.documentElement.createSVGPoint(); + + svgPoint1.x = 0.0; + svgPoint1.y = 0.0; + svgPoint2.x = 1.0; + svgPoint2.y = 0.0; + + var matrix = slides[activeSlide]["element"].getTransformToElement(ROOT_NODE); + + if (slides[activeSlide]["viewGroup"]) + matrix = slides[activeSlide]["viewGroup"].getTransformToElement(ROOT_NODE); + + svgPoint1 = svgPoint1.matrixTransform(matrix); + svgPoint2 = svgPoint2.matrixTransform(matrix); + + path_paint_width = path_width / Math.sqrt((svgPoint2.x - svgPoint1.x) * (svgPoint2.x - svgPoint1.x) + (svgPoint2.y - svgPoint1.y) * (svgPoint2.y - svgPoint1.y)); +} + +/** The view effect. + * + * @param dir direction the effect should be played (1 = forwards, -1 = backwards) + * @param element the element the effect should be applied to + * @param time the time that has elapsed since the beginning of the effect + * @param options a dictionary with additional options (e.g. length of the effect); for the view effect the options need to contain the old and the new matrix. + */ +function view(dir, element, time, options) +{ + var length = 250; + var fraction; + + if (!options["matrixInitial"]) + { + var tempString = slides[activeSlide]["viewGroup"].getAttribute("transform"); + + if (tempString) + options["matrixInitial"] = (new matrixSVG()).fromAttribute(tempString); + else + options["matrixInitial"] = (new matrixSVG()).fromSVGElements(1, 0, 0, 1, 0, 0); + } + + if ((time == STATE_END) || (time == STATE_START)) + fraction = 1; + else + { + if (options && options["length"]) + length = options["length"]; + + fraction = time / length; + } + + if (dir == 1) + { + if (fraction <= 0) + { + element.setAttribute("transform", options["matrixInitial"].toAttribute()); + } + else if (fraction >= 1) + { + element.setAttribute("transform", options["matrixNew"].toAttribute()); + + set_path_paint_width(); + + options["matrixInitial"] = null; + return true; + } + else + { + element.setAttribute("transform", options["matrixInitial"].mix(options["matrixNew"], fraction).toAttribute()); + } + } + else if (dir == -1) + { + if (fraction <= 0) + { + element.setAttribute("transform", options["matrixInitial"].toAttribute()); + } + else if (fraction >= 1) + { + element.setAttribute("transform", options["matrixOld"].toAttribute()); + set_path_paint_width(); + + options["matrixInitial"] = null; + return true; + } + else + { + element.setAttribute("transform", options["matrixInitial"].mix(options["matrixOld"], fraction).toAttribute()); + } + } + + return false; +} + +/** The fade effect. + * + * @param dir direction the effect should be played (1 = forwards, -1 = backwards) + * @param element the element the effect should be applied to + * @param time the time that has elapsed since the beginning of the effect + * @param options a dictionary with additional options (e.g. length of the effect) + */ +function fade(dir, element, time, options) +{ + var length = 250; + var fraction; + + if ((time == STATE_END) || (time == STATE_START)) + fraction = 1; + else + { + if (options && options["length"]) + length = options["length"]; + + fraction = time / length; + } + + if (dir == 1) + { + if (fraction <= 0) + { + element.style.display = "none"; + element.setAttribute("opacity", 0); + } + else if (fraction >= 1) + { + element.style.display = "inherit"; + element.setAttribute("opacity", 1); + return true; + } + else + { + element.style.display = "inherit"; + element.setAttribute("opacity", fraction); + } + } + else if (dir == -1) + { + if (fraction <= 0) + { + element.style.display = "inherit"; + element.setAttribute("opacity", 1); + } + else if (fraction >= 1) + { + element.setAttribute("opacity", 0); + element.style.display = "none"; + return true; + } + else + { + element.style.display = "inherit"; + element.setAttribute("opacity", 1 - fraction); + } + } + return false; +} + +/** The appear effect. + * + * @param dir direction the effect should be played (1 = forwards, -1 = backwards) + * @param element the element the effect should be applied to + * @param time the time that has elapsed since the beginning of the effect + * @param options a dictionary with additional options (e.g. length of the effect) + */ +function appear(dir, element, time, options) +{ + if (dir == 1) + { + element.style.display = "inherit"; + element.setAttribute("opacity",1); + } + else if (dir == -1) + { + element.style.display = "none"; + element.setAttribute("opacity",0); + } + return true; +} + +/** The pop effect. + * + * @param dir direction the effect should be played (1 = forwards, -1 = backwards) + * @param element the element the effect should be applied to + * @param time the time that has elapsed since the beginning of the effect + * @param options a dictionary with additional options (e.g. length of the effect) + */ +function pop(dir, element, time, options) +{ + var length = 500; + var fraction; + + if ((time == STATE_END) || (time == STATE_START)) + fraction = 1; + else + { + if (options && options["length"]) + length = options["length"]; + + fraction = time / length; + } + + if (dir == 1) + { + if (fraction <= 0) + { + element.setAttribute("opacity", 0); + element.setAttribute("transform", "scale(0)"); + element.style.display = "none"; + } + else if (fraction >= 1) + { + element.setAttribute("opacity", 1); + element.removeAttribute("transform"); + element.style.display = "inherit"; + return true; + } + else + { + element.style.display = "inherit"; + var opacityFraction = fraction * 3; + if (opacityFraction > 1) + opacityFraction = 1; + element.setAttribute("opacity", opacityFraction); + var offsetX = WIDTH * (1.0 - fraction) / 2.0; + var offsetY = HEIGHT * (1.0 - fraction) / 2.0; + element.setAttribute("transform", "translate(" + offsetX + "," + offsetY + ") scale(" + fraction + ")"); + } + } + else if (dir == -1) + { + if (fraction <= 0) + { + element.setAttribute("opacity", 1); + element.setAttribute("transform", "scale(1)"); + element.style.display = "inherit"; + } + else if (fraction >= 1) + { + element.setAttribute("opacity", 0); + element.removeAttribute("transform"); + element.style.display = "none"; + return true; + } + else + { + element.setAttribute("opacity", 1 - fraction); + element.setAttribute("transform", "scale(" + 1 - fraction + ")"); + element.style.display = "inherit"; + } + } + return false; +} + +/** Function to set a slide either to the start or the end state. + * + * @param slide the slide to use + * @param state the state into which the slide should be set + */ +function setSlideToState(slide, state) +{ + slides[slide]["viewGroup"].setAttribute("transform", slides[slide].initialView); + + if (slides[slide]["effects"]) + { + if (state == STATE_END) + { + for (var counter = 0; counter < slides[slide]["effects"].length; counter++) + { + for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++) + { + var effect = slides[slide]["effects"][counter][subCounter]; + if (effect["effect"] == "fade") + fade(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "appear") + appear(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "pop") + pop(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "view") + view(effect["dir"], effect["element"], STATE_END, effect["options"]); + } + } + } + else if (state == STATE_START) + { + for (var counter = slides[slide]["effects"].length - 1; counter >= 0; counter--) + { + for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++) + { + var effect = slides[slide]["effects"][counter][subCounter]; + if (effect["effect"] == "fade") + fade(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]); + else if (effect["effect"] == "appear") + appear(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]); + else if (effect["effect"] == "pop") + pop(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]); + else if (effect["effect"] == "view") + view(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]); + } + } + } + else + { + setSlideToState(slide, STATE_START); + + for (var counter = 0; counter < slides[slide]["effects"].length && counter < state; counter++) + { + for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++) + { + var effect = slides[slide]["effects"][counter][subCounter]; + if (effect["effect"] == "fade") + fade(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "appear") + appear(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "pop") + pop(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "view") + view(effect["dir"], effect["element"], STATE_END, effect["options"]); + } + } + } + } + + window.location.hash = (activeSlide + 1) + '_' + activeEffect; +} + +/** Convenience function to translate a attribute string into a dictionary. + * + * @param str the attribute string + * @return a dictionary + * @see dictToPropStr + */ +function propStrToDict(str) +{ + var list = str.split(";"); + var obj = new Object(); + + for (var counter = 0; counter < list.length; counter++) + { + var subStr = list[counter]; + var subList = subStr.split(":"); + if (subList.length == 2) + { + obj[subList[0]] = subList[1]; + } + } + + return obj; +} + +/** Convenience function to translate a dictionary into a string that can be used as an attribute. + * + * @param dict the dictionary to convert + * @return a string that can be used as an attribute + * @see propStrToDict + */ +function dictToPropStr(dict) +{ + var str = ""; + + for (var key in dict) + { + str += key + ":" + dict[key] + ";"; + } + + return str; +} + +/** Sub-function to add a suffix to the ids of the node and all its children. + * + * @param node the node to change + * @param suffix the suffix to add + * @param replace dictionary of replaced ids + * @see suffixNodeIds + */ +function suffixNoneIds_sub(node, suffix, replace) +{ + if (node.nodeType == 1) + { + if (node.getAttribute("id")) + { + var id = node.getAttribute("id") + replace["#" + id] = id + suffix; + node.setAttribute("id", id + suffix); + } + + if ((node.nodeName == "use") && (node.getAttributeNS(NSS["xlink"], "href")) && (replace[node.getAttribute(NSS["xlink"], "href")])) + node.setAttribute(NSS["xlink"], "href", node.getAttribute(NSS["xlink"], "href") + suffix); + + if (node.childNodes) + { + for (var counter = 0; counter < node.childNodes.length; counter++) + suffixNoneIds_sub(node.childNodes[counter], suffix, replace); + } + } +} + +/** Function to add a suffix to the ids of the node and all its children. + * + * @param node the node to change + * @param suffix the suffix to add + * @return the changed node + * @see suffixNodeIds_sub + */ +function suffixNodeIds(node, suffix) +{ + var replace = new Object(); + + suffixNoneIds_sub(node, suffix, replace); + + return node; +} + +/** Function to build a progress bar. + * + * @param parent node to attach the progress bar to + */ +function createProgressBar(parent_node) +{ + var g = document.createElementNS(NSS["svg"], "g"); + g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)"); + g.setAttribute("id", "layer_progress_bar"); + g.setAttribute("style", "display: none;"); + + var rect_progress_bar = document.createElementNS(NSS["svg"], "rect"); + rect_progress_bar.setAttribute("style", "marker: none; fill: rgb(128, 128, 128); stroke: none;"); + rect_progress_bar.setAttribute("id", "rect_progress_bar"); + rect_progress_bar.setAttribute("x", 0); + rect_progress_bar.setAttribute("y", 0.99 * HEIGHT); + rect_progress_bar.setAttribute("width", 0); + rect_progress_bar.setAttribute("height", 0.01 * HEIGHT); + g.appendChild(rect_progress_bar); + + var circle_timer_indicator = document.createElementNS(NSS["svg"], "circle"); + circle_timer_indicator.setAttribute("style", "marker: none; fill: rgb(255, 0, 0); stroke: none;"); + circle_timer_indicator.setAttribute("id", "circle_timer_indicator"); + circle_timer_indicator.setAttribute("cx", 0.005 * HEIGHT); + circle_timer_indicator.setAttribute("cy", 0.995 * HEIGHT); + circle_timer_indicator.setAttribute("r", 0.005 * HEIGHT); + g.appendChild(circle_timer_indicator); + + parent_node.appendChild(g); +} + +/** Function to hide the progress bar. + * + */ +function hideProgressBar() +{ + var progress_bar = document.getElementById("layer_progress_bar"); + + if (!progress_bar) + { + return; + } + + progress_bar.setAttribute("style", "display: none;"); +} + +/** Function to show the progress bar. + * + */ +function showProgressBar() +{ + var progress_bar = document.getElementById("layer_progress_bar"); + + if (!progress_bar) + { + return; + } + + progress_bar.setAttribute("style", "display: inherit;"); +} + +/** Set progress bar value. + * + * @param value the current slide number + * + */ +function setProgressBarValue(value) +{ + var rect_progress_bar = document.getElementById("rect_progress_bar"); + + if (!rect_progress_bar) + { + return; + } + + if (value < 1) + { + // First slide, assumed to be the title of the presentation + var x = 0; + var w = 0.01 * HEIGHT; + } + else if (value >= slides.length - 1) + { + // Last slide, assumed to be the end of the presentation + var x = WIDTH - 0.01 * HEIGHT; + var w = 0.01 * HEIGHT; + } + else + { + value -= 1; + value /= (slides.length - 2); + + var x = WIDTH * value; + var w = WIDTH / (slides.length - 2); + } + + rect_progress_bar.setAttribute("x", x); + rect_progress_bar.setAttribute("width", w); +} + +/** Set time indicator. + * + * @param value the percentage of time elapse so far between 0.0 and 1.0 + * + */ +function setTimeIndicatorValue(value) +{ + var circle_timer_indicator = document.getElementById("circle_timer_indicator"); + + if (!circle_timer_indicator) + { + return; + } + + if (value < 0.0) + { + value = 0.0; + } + + if (value > 1.0) + { + value = 1.0; + } + + var cx = (WIDTH - 0.01 * HEIGHT) * value + 0.005 * HEIGHT; + circle_timer_indicator.setAttribute("cx", cx); +} + +/** Update timer. + * + */ +function updateTimer() +{ + timer_elapsed += 1; + setTimeIndicatorValue((timer_elapsed - timer_start) / (60 * timer_duration)); +} + +/** Convert screen coordinates to document coordinates. + * + * @param e event with screen coordinates + * + * @return coordinates in SVG file coordinate system + */ +function calcCoord(e) +{ + var svgPoint = document.documentElement.createSVGPoint(); + svgPoint.x = e.clientX + window.pageXOffset; + svgPoint.y = e.clientY + window.pageYOffset; + + var matrix = slides[activeSlide]["element"].getScreenCTM(); + + if (slides[activeSlide]["viewGroup"]) + matrix = slides[activeSlide]["viewGroup"].getScreenCTM(); + + svgPoint = svgPoint.matrixTransform(matrix.inverse()); + return svgPoint; +} + +/** Add slide. + * + * @param after_slide after which slide the new slide should be inserted into the presentation + */ +function addSlide(after_slide) +{ + number_of_added_slides++; + + var g = document.createElementNS(NSS["svg"], "g"); + g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)"); + g.setAttribute("id", "Whiteboard " + Date() + " presentation copy"); + g.setAttribute("style", "display: none;"); + + var new_slide = new Object(); + new_slide["element"] = g; + + // Set build in transition. + new_slide["transitionIn"] = new Object(); + var dict = defaultTransitionInDict; + new_slide["transitionIn"]["name"] = dict["name"]; + new_slide["transitionIn"]["options"] = new Object(); + + for (key in dict) + if (key != "name") + new_slide["transitionIn"]["options"][key] = dict[key]; + + // Set build out transition. + new_slide["transitionOut"] = new Object(); + dict = defaultTransitionOutDict; + new_slide["transitionOut"]["name"] = dict["name"]; + new_slide["transitionOut"]["options"] = new Object(); + + for (key in dict) + if (key != "name") + new_slide["transitionOut"]["options"][key] = dict[key]; + + // Copy master slide content. + if (masterSlide) + { + var clonedNode = suffixNodeIds(masterSlide.cloneNode(true), "_" + Date() + " presentation_copy"); + clonedNode.removeAttributeNS(NSS["inkscape"], "groupmode"); + clonedNode.removeAttributeNS(NSS["inkscape"], "label"); + clonedNode.style.display = "inherit"; + + g.appendChild(clonedNode); + } + + // Substitute auto texts. + substituteAutoTexts(g, "Whiteboard " + number_of_added_slides, "W" + number_of_added_slides, slides.length); + + g.setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + (after_slide + 1) + ")) { indexSetActiveSlide(" + (after_slide + 1) + "); };"); + + // Create a transform group. + var transformGroup = document.createElementNS(NSS["svg"], "g"); + + // Add content to transform group. + while (g.firstChild) + transformGroup.appendChild(g.firstChild); + + // Transfer the transform attribute from the node to the transform group. + if (g.getAttribute("transform")) + { + transformGroup.setAttribute("transform", g.getAttribute("transform")); + g.removeAttribute("transform"); + } + + // Create a view group. + var viewGroup = document.createElementNS(NSS["svg"], "g"); + + viewGroup.appendChild(transformGroup); + new_slide["viewGroup"] = g.appendChild(viewGroup); + + // Insert background. + if (BACKGROUND_COLOR != null) + { + var rectNode = document.createElementNS(NSS["svg"], "rect"); + + rectNode.setAttribute("x", 0); + rectNode.setAttribute("y", 0); + rectNode.setAttribute("width", WIDTH); + rectNode.setAttribute("height", HEIGHT); + rectNode.setAttribute("id", "jessyInkBackground" + Date()); + rectNode.setAttribute("fill", BACKGROUND_COLOR); + + new_slide["viewGroup"].insertBefore(rectNode, new_slide["viewGroup"].firstChild); + } + + // Set initial view even if there are no other views. + var matrixOld = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + + new_slide["viewGroup"].setAttribute("transform", matrixOld.toAttribute()); + new_slide.initialView = matrixOld.toAttribute(); + + // Insert slide + var node = slides[after_slide]["element"]; + var next_node = node.nextSibling; + var parent_node = node.parentNode; + + if (next_node) + { + parent_node.insertBefore(g, next_node); + } + else + { + parent_node.appendChild(g); + } + + g = document.createElementNS(NSS["svg"], "g"); + g.setAttributeNS(NSS["inkscape"], "groupmode", "layer"); + g.setAttributeNS(NSS["inkscape"], "label", "Whiteboard " + number_of_added_slides); + g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)"); + g.setAttribute("id", "Whiteboard " + Date()); + g.setAttribute("style", "display: none;"); + + new_slide["original_element"] = g; + + node = slides[after_slide]["original_element"]; + next_node = node.nextSibling; + parent_node = node.parentNode; + + if (next_node) + { + parent_node.insertBefore(g, next_node); + } + else + { + parent_node.appendChild(g); + } + + before_new_slide = slides.slice(0, after_slide + 1); + after_new_slide = slides.slice(after_slide + 1); + slides = before_new_slide.concat(new_slide, after_new_slide); + + //resetting the counter attributes on the slides that follow the new slide... + for (var counter = after_slide+2; counter < slides.length; counter++) + { + slides[counter]["element"].setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + counter + ")) { indexSetActiveSlide(" + counter + "); };"); + } +} + +/** Convenience function to obtain a transformation matrix from a point matrix. + * + * @param mPoints Point matrix. + * @return A transformation matrix. + */ +function pointMatrixToTransformation(mPoints) +{ + mPointsOld = (new matrixSVG()).fromElements(0, WIDTH, WIDTH, 0, 0, HEIGHT, 1, 1, 1); + + return mPointsOld.mult(mPoints.inv()); +} + +/** Convenience function to obtain a matrix with three corners of a rectangle. + * + * @param rect an svg rectangle + * @return a matrixSVG containing three corners of the rectangle + */ +function rectToMatrix(rect) +{ + rectWidth = rect.getBBox().width; + rectHeight = rect.getBBox().height; + rectX = rect.getBBox().x; + rectY = rect.getBBox().y; + rectXcorr = 0; + rectYcorr = 0; + + scaleX = WIDTH / rectWidth; + scaleY = HEIGHT / rectHeight; + + if (scaleX > scaleY) + { + scaleX = scaleY; + rectXcorr -= (WIDTH / scaleX - rectWidth) / 2; + rectWidth = WIDTH / scaleX; + } + else + { + scaleY = scaleX; + rectYcorr -= (HEIGHT / scaleY - rectHeight) / 2; + rectHeight = HEIGHT / scaleY; + } + + if (rect.transform.baseVal.numberOfItems < 1) + { + mRectTrans = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + } + else + { + mRectTrans = (new matrixSVG()).fromSVGMatrix(rect.transform.baseVal.consolidate().matrix); + } + + newBasePoints = (new matrixSVG()).fromElements(rectX, rectX, rectX, rectY, rectY, rectY, 1, 1, 1); + newVectors = (new matrixSVG()).fromElements(rectXcorr, rectXcorr + rectWidth, rectXcorr + rectWidth, rectYcorr, rectYcorr, rectYcorr + rectHeight, 0, 0, 0); + + return mRectTrans.mult(newBasePoints.add(newVectors)); +} + +/** Function to handle JessyInk elements. + * + * @param node Element node. + */ +function handleElement(node) +{ + if (node.getAttributeNS(NSS['jessyink'], 'element') == 'core.video') + { + var url; + var width; + var height; + var x; + var y; + var transform; + + var tspans = node.getElementsByTagNameNS("http://www.w3.org/2000/svg", "tspan"); + + for (var tspanCounter = 0; tspanCounter < tspans.length; tspanCounter++) + { + if (tspans[tspanCounter].getAttributeNS("https://launchpad.net/jessyink", "video") == "url") + { + url = tspans[tspanCounter].firstChild.nodeValue; + } + } + + var rects = node.getElementsByTagNameNS("http://www.w3.org/2000/svg", "rect"); + + for (var rectCounter = 0; rectCounter < rects.length; rectCounter++) + { + if (rects[rectCounter].getAttributeNS("https://launchpad.net/jessyink", "video") == "rect") + { + x = rects[rectCounter].getAttribute("x"); + y = rects[rectCounter].getAttribute("y"); + width = rects[rectCounter].getAttribute("width"); + height = rects[rectCounter].getAttribute("height"); + transform = rects[rectCounter].getAttribute("transform"); + } + } + + for (var childCounter = 0; childCounter < node.childNodes.length; childCounter++) + { + if (node.childNodes[childCounter].nodeType == 1) + { + if (node.childNodes[childCounter].style) + { + node.childNodes[childCounter].style.display = 'none'; + } + else + { + node.childNodes[childCounter].setAttribute("style", "display: none;"); + } + } + } + + var foreignNode = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject"); + foreignNode.setAttribute("x", x); + foreignNode.setAttribute("y", y); + foreignNode.setAttribute("width", width); + foreignNode.setAttribute("height", height); + foreignNode.setAttribute("transform", transform); + + var videoNode = document.createElementNS("http://www.w3.org/1999/xhtml", "video"); + videoNode.setAttribute("src", url); + + foreignNode.appendChild(videoNode); + node.appendChild(foreignNode); + } +} + +/** Class processing the location hash. + * + * @param str location hash + */ +function LocationHash(str) +{ + this.slideNumber = 0; + this.effectNumber = 0; + + str = str.substr(1, str.length - 1); + + var parts = str.split('_'); + + // Try to extract slide number. + if (parts.length >= 1) + { + try + { + var slideNumber = parseInt(parts[0]); + + if (!isNaN(slideNumber)) + { + this.slideNumber = slideNumber - 1; + } + } + catch (e) + { + } + } + + // Try to extract effect number. + if (parts.length >= 2) + { + try + { + var effectNumber = parseInt(parts[1]); + + if (!isNaN(effectNumber)) + { + this.effectNumber = effectNumber; + } + } + catch (e) + { + } + } +} + +/** Class representing an svg matrix. +*/ +function matrixSVG() +{ + this.e11 = 0; // a + this.e12 = 0; // c + this.e13 = 0; // e + this.e21 = 0; // b + this.e22 = 0; // d + this.e23 = 0; // f + this.e31 = 0; + this.e32 = 0; + this.e33 = 0; +} + +/** Constructor function. + * + * @param a element a (i.e. 1, 1) as described in the svg standard. + * @param b element b (i.e. 2, 1) as described in the svg standard. + * @param c element c (i.e. 1, 2) as described in the svg standard. + * @param d element d (i.e. 2, 2) as described in the svg standard. + * @param e element e (i.e. 1, 3) as described in the svg standard. + * @param f element f (i.e. 2, 3) as described in the svg standard. + */ +matrixSVG.prototype.fromSVGElements = function(a, b, c, d, e, f) +{ + this.e11 = a; + this.e12 = c; + this.e13 = e; + this.e21 = b; + this.e22 = d; + this.e23 = f; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + + return this; +} + +/** Constructor function. + * + * @param matrix an svg matrix as described in the svg standard. + */ +matrixSVG.prototype.fromSVGMatrix = function(m) +{ + this.e11 = m.a; + this.e12 = m.c; + this.e13 = m.e; + this.e21 = m.b; + this.e22 = m.d; + this.e23 = m.f; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + + return this; +} + +/** Constructor function. + * + * @param e11 element 1, 1 of the matrix. + * @param e12 element 1, 2 of the matrix. + * @param e13 element 1, 3 of the matrix. + * @param e21 element 2, 1 of the matrix. + * @param e22 element 2, 2 of the matrix. + * @param e23 element 2, 3 of the matrix. + * @param e31 element 3, 1 of the matrix. + * @param e32 element 3, 2 of the matrix. + * @param e33 element 3, 3 of the matrix. + */ +matrixSVG.prototype.fromElements = function(e11, e12, e13, e21, e22, e23, e31, e32, e33) +{ + this.e11 = e11; + this.e12 = e12; + this.e13 = e13; + this.e21 = e21; + this.e22 = e22; + this.e23 = e23; + this.e31 = e31; + this.e32 = e32; + this.e33 = e33; + + return this; +} + +/** Constructor function. + * + * @param attrString string value of the "transform" attribute (currently only "matrix" is accepted) + */ +matrixSVG.prototype.fromAttribute = function(attrString) +{ + str = attrString.substr(7, attrString.length - 8); + + str = str.trim(); + + strArray = str.split(","); + + // Opera does not use commas to separate the values of the matrix, only spaces. + if (strArray.length != 6) + strArray = str.split(" "); + + this.e11 = parseFloat(strArray[0]); + this.e21 = parseFloat(strArray[1]); + this.e31 = 0; + this.e12 = parseFloat(strArray[2]); + this.e22 = parseFloat(strArray[3]); + this.e32 = 0; + this.e13 = parseFloat(strArray[4]); + this.e23 = parseFloat(strArray[5]); + this.e33 = 1; + + return this; +} + +/** Output function + * + * @return a string that can be used as the "transform" attribute. + */ +matrixSVG.prototype.toAttribute = function() +{ + return "matrix(" + this.e11 + ", " + this.e21 + ", " + this.e12 + ", " + this.e22 + ", " + this.e13 + ", " + this.e23 + ")"; +} + +/** Matrix nversion. + * + * @return the inverse of the matrix + */ +matrixSVG.prototype.inv = function() +{ + out = new matrixSVG(); + + det = this.e11 * (this.e33 * this.e22 - this.e32 * this.e23) - this.e21 * (this.e33 * this.e12 - this.e32 * this.e13) + this.e31 * (this.e23 * this.e12 - this.e22 * this.e13); + + out.e11 = (this.e33 * this.e22 - this.e32 * this.e23) / det; + out.e12 = -(this.e33 * this.e12 - this.e32 * this.e13) / det; + out.e13 = (this.e23 * this.e12 - this.e22 * this.e13) / det; + out.e21 = -(this.e33 * this.e21 - this.e31 * this.e23) / det; + out.e22 = (this.e33 * this.e11 - this.e31 * this.e13) / det; + out.e23 = -(this.e23 * this.e11 - this.e21 * this.e13) / det; + out.e31 = (this.e32 * this.e21 - this.e31 * this.e22) / det; + out.e32 = -(this.e32 * this.e11 - this.e31 * this.e12) / det; + out.e33 = (this.e22 * this.e11 - this.e21 * this.e12) / det; + + return out; +} + +/** Matrix multiplication. + * + * @param op another svg matrix + * @return this * op + */ +matrixSVG.prototype.mult = function(op) +{ + out = new matrixSVG(); + + out.e11 = this.e11 * op.e11 + this.e12 * op.e21 + this.e13 * op.e31; + out.e12 = this.e11 * op.e12 + this.e12 * op.e22 + this.e13 * op.e32; + out.e13 = this.e11 * op.e13 + this.e12 * op.e23 + this.e13 * op.e33; + out.e21 = this.e21 * op.e11 + this.e22 * op.e21 + this.e23 * op.e31; + out.e22 = this.e21 * op.e12 + this.e22 * op.e22 + this.e23 * op.e32; + out.e23 = this.e21 * op.e13 + this.e22 * op.e23 + this.e23 * op.e33; + out.e31 = this.e31 * op.e11 + this.e32 * op.e21 + this.e33 * op.e31; + out.e32 = this.e31 * op.e12 + this.e32 * op.e22 + this.e33 * op.e32; + out.e33 = this.e31 * op.e13 + this.e32 * op.e23 + this.e33 * op.e33; + + return out; +} + +/** Matrix addition. + * + * @param op another svg matrix + * @return this + op + */ +matrixSVG.prototype.add = function(op) +{ + out = new matrixSVG(); + + out.e11 = this.e11 + op.e11; + out.e12 = this.e12 + op.e12; + out.e13 = this.e13 + op.e13; + out.e21 = this.e21 + op.e21; + out.e22 = this.e22 + op.e22; + out.e23 = this.e23 + op.e23; + out.e31 = this.e31 + op.e31; + out.e32 = this.e32 + op.e32; + out.e33 = this.e33 + op.e33; + + return out; +} + +/** Matrix mixing. + * + * @param op another svg matrix + * @parma contribOp contribution of the other matrix (0 <= contribOp <= 1) + * @return (1 - contribOp) * this + contribOp * op + */ +matrixSVG.prototype.mix = function(op, contribOp) +{ + contribThis = 1.0 - contribOp; + out = new matrixSVG(); + + out.e11 = contribThis * this.e11 + contribOp * op.e11; + out.e12 = contribThis * this.e12 + contribOp * op.e12; + out.e13 = contribThis * this.e13 + contribOp * op.e13; + out.e21 = contribThis * this.e21 + contribOp * op.e21; + out.e22 = contribThis * this.e22 + contribOp * op.e22; + out.e23 = contribThis * this.e23 + contribOp * op.e23; + out.e31 = contribThis * this.e31 + contribOp * op.e31; + out.e32 = contribThis * this.e32 + contribOp * op.e32; + out.e33 = contribThis * this.e33 + contribOp * op.e33; + + return out; +} + +/** Trimming function for strings. +*/ +String.prototype.trim = function() +{ + return this.replace(/^\s+|\s+$/g, ''); +} + +/** SVGElement.getTransformToElement polyfill */ +SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(elem) { + return elem.getScreenCTM().inverse().multiply(this.getScreenCTM()); +}; diff --git a/share/extensions/jessyInk_core_mouseHandler_noclick.js b/share/extensions/jessyInk_core_mouseHandler_noclick.js new file mode 100644 index 0000000..4e66c51 --- /dev/null +++ b/share/extensions/jessyInk_core_mouseHandler_noclick.js @@ -0,0 +1,53 @@ +// Copyright 2008, 2009 Hannes Hochreiner +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +// Add event listener for initialisation. +document.addEventListener("DOMContentLoaded", jessyInk_core_mouseHandler_noclick_init, false); + +/** Initialisation function. + * + * This function looks for the objects of the appropriate sub-type and hands them to another function that will add the required methods. + */ +function jessyInk_core_mouseHandler_noclick_init() +{ + var elems = document.getElementsByTagNameNS("https://launchpad.net/jessyink", "mousehandler"); + + for (var counter = 0; counter < elems.length; counter++) + { + if (elems[counter].getAttributeNS("https://launchpad.net/jessyink", "subtype") == "jessyInk_core_mouseHandler_noclick") + jessyInk_core_mouseHandler_noclick(elems[counter]); + } +} + +/** Function to initialise an object. + * + * @param obj Object to be initialised. + */ +function jessyInk_core_mouseHandler_noclick(obj) +{ + /** Function supplying a custom mouse handler. + * + * @returns A dictionary containing the new mouse handler functions. + */ + obj.getMouseHandler = function () + { + var handlerDictio = new Object(); + + handlerDictio[SLIDE_MODE] = new Object(); + handlerDictio[SLIDE_MODE][MOUSE_DOWN] = null; + + return handlerDictio; + } +} + diff --git a/share/extensions/jessyInk_core_mouseHandler_zoomControl.js b/share/extensions/jessyInk_core_mouseHandler_zoomControl.js new file mode 100644 index 0000000..cd200c8 --- /dev/null +++ b/share/extensions/jessyInk_core_mouseHandler_zoomControl.js @@ -0,0 +1,434 @@ +// Copyright 2008, 2009 Hannes Hochreiner +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +// Add event listener for initialisation. +document.addEventListener("DOMContentLoaded", jessyInk_core_mouseHandler_zoomControl_init, false); + +/** Initialisation function. + * + * This function looks for the objects of the appropriate sub-type and hands them to another function that will add the required methods. + */ +function jessyInk_core_mouseHandler_zoomControl_init() +{ + var elems = document.getElementsByTagNameNS("https://launchpad.net/jessyink", "mousehandler"); + + for (var counter = 0; counter < elems.length; counter++) + { + if (elems[counter].getAttributeNS("https://launchpad.net/jessyink", "subtype") == "jessyInk_core_mouseHandler_zoomControl") + jessyInk_core_mouseHandler_zoomControl(elems[counter]); + } +} + +/** Function to initialise an object. + * + * @param obj Object to be initialised. + */ +function jessyInk_core_mouseHandler_zoomControl(obj) +{ + // Last dragging position. + obj.dragging_last; + // Flag to indicate whether dragging is active currently. + obj.dragging_active = false; + // Flag to indicate whether dragging is working currently. + obj.dragging_working = false; + // Flag to indicate whether the user clicked. + obj.click = false; + + /** Function supplying a custom mouse handler. + * + * @returns A dictionary containing the new mouse handler functions. + */ + obj.getMouseHandler = function () + { + var handlerDictio = new Object(); + + handlerDictio[SLIDE_MODE] = new Object(); + handlerDictio[SLIDE_MODE][MOUSE_DOWN] = obj.mousedown; + handlerDictio[SLIDE_MODE][MOUSE_MOVE] = obj.mousemove; + handlerDictio[SLIDE_MODE][MOUSE_UP] = obj.mouseup; + handlerDictio[SLIDE_MODE][MOUSE_WHEEL] = obj.mousewheel; + + return handlerDictio; + } + + /** Event handler for mouse clicks. + * + * @param e Event object. + */ + obj.mouseclick = function (e) + { + var elem = obj.getAdHocViewBbox(slides[activeSlide]["viewGroup"], obj.getCoords(e)); + + processingEffect = true; + + effectArray = new Array(); + + effectArray[0] = new Object(); + effectArray[0]["effect"] = "view"; + effectArray[0]["dir"] = 1; + effectArray[0]["element"] = slides[activeSlide]["viewGroup"]; + effectArray[0]["options"] = new Object(); + effectArray[0]["options"]["length"] = 200; + + if (elem == null) + effectArray[0]["options"]["matrixNew"] = (new matrixSVG()).fromSVGElements(1, 0, 0, 1, 0, 0); + else + effectArray[0]["options"]["matrixNew"] = obj.pointMatrixToTransformation(obj.rectToMatrix(elem)).mult((new matrixSVG()).fromSVGMatrix(slides[activeSlide].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(elem.parentNode.getScreenCTM())).inv()); + + transCounter = 0; + startTime = (new Date()).getTime(); + lastFrameTime = null; + effect(1); + + return false; + } + + /** Function to search for the element the user clicked on. + * + * This function searches for the element with the highest z-order, which encloses the point the user clicked on + * and which view box fits entierly into the currently visible part of the slide. + * + * @param elem Element to start the search from. + * @param pnt Point where the user clicked. + * @returns The element the user clicked on or null, if no element could be found. + */ + obj.getAdHocViewBbox = function (elem, pnt) + { + var children = elem.childNodes; + + for (var counter = 0; counter < children.length; counter++) + { + if (children[counter].getBBox) + { + var childPointList = obj.projectRect(children[counter].getBBox(), children[counter].getScreenCTM()); + + var viewBbox = document.documentElement.createSVGRect(); + + viewBbox.x = 0.0; + viewBbox.y = 0.0; + viewBbox.width = WIDTH; + viewBbox.height = HEIGHT; + + var screenPointList = obj.projectRect(viewBbox, slides[activeSlide]["element"].getScreenCTM()); + + if (obj.pointsWithinRect([pnt], childPointList) && obj.pointsWithinRect(childPointList, screenPointList)) + return children[counter]; + + child = obj.getAdHocViewBbox(children[counter], pnt); + + if (child != null) + return child; + } + } + + return null; + } + + /** Function to project a rectangle using the projection matrix supplied. + * + * @param rect The rectangle to project. + * @param projectionMatrix The projection matrix. + * @returns A list of the four corners of the projected rectangle starting from the upper left corner and going counter-clockwise. + */ + obj.projectRect = function (rect, projectionMatrix) + { + var pntUL = document.documentElement.createSVGPoint(); + pntUL.x = rect.x; + pntUL.y = rect.y; + pntUL = pntUL.matrixTransform(projectionMatrix); + + var pntLL = document.documentElement.createSVGPoint(); + pntLL.x = rect.x; + pntLL.y = rect.y + rect.height; + pntLL = pntLL.matrixTransform(projectionMatrix); + + var pntUR = document.documentElement.createSVGPoint(); + pntUR.x = rect.x + rect.width; + pntUR.y = rect.y; + pntUR = pntUR.matrixTransform(projectionMatrix); + + var pntLR = document.documentElement.createSVGPoint(); + pntLR.x = rect.x + rect.width; + pntLR.y = rect.y + rect.height; + pntLR = pntLR.matrixTransform(projectionMatrix); + + return [pntUL, pntLL, pntUR, pntLR]; + } + + /** Function to determine whether all the points supplied in a list are within a rectangle. + * + * @param pnts List of points to check. + * @param pointList List of points representing the four corners of the rectangle. + * @return True, if all points are within the rectangle; false, otherwise. + */ + obj.pointsWithinRect = function (pnts, pointList) + { + var pntUL = pointList[0]; + var pntLL = pointList[1]; + var pntUR = pointList[2]; + + var matrixOrig = (new matrixSVG()).fromElements(pntUL.x, pntLL.x, pntUR.x, pntUL.y, pntLL.y, pntUR.y, 1, 1, 1); + var matrixProj = (new matrixSVG()).fromElements(0, 0, 1, 0, 1, 0, 1, 1, 1); + + var matrixProjection = matrixProj.mult(matrixOrig.inv()); + + for (var blockCounter = 0; blockCounter < Math.ceil(pnts.length / 3.0); blockCounter++) + { + var subPnts = new Array(); + + for (var pntCounter = 0; pntCounter < 3.0; pntCounter++) + { + if (blockCounter * 3.0 + pntCounter < pnts.length) + subPnts[pntCounter] = pnts[blockCounter * 3.0 + pntCounter]; + else + { + var tmpPnt = document.documentElement.createSVGPoint(); + + tmpPnt.x = 0.0; + tmpPnt.y = 0.0; + + subPnts[pntCounter] = tmpPnt; + } + } + + var matrixPnt = (new matrixSVG).fromElements(subPnts[0].x, subPnts[1].x, subPnts[2].x, subPnts[0].y, subPnts[1].y, subPnts[2].y, 1, 1, 1); + var matrixTrans = matrixProjection.mult(matrixPnt); + + for (var pntCounter = 0; pntCounter < 3.0; pntCounter++) + { + if (blockCounter * 3.0 + pntCounter < pnts.length) + { + if ((pntCounter == 0) && !((matrixTrans.e11 > 0.01) && (matrixTrans.e11 < 0.99) && (matrixTrans.e21 > 0.01) && (matrixTrans.e21 < 0.99))) + return false; + else if ((pntCounter == 1) && !((matrixTrans.e12 > 0.01) && (matrixTrans.e12 < 0.99) && (matrixTrans.e22 > 0.01) && (matrixTrans.e22 < 0.99))) + return false; + else if ((pntCounter == 2) && !((matrixTrans.e13 > 0.01) && (matrixTrans.e13 < 0.99) && (matrixTrans.e23 > 0.01) && (matrixTrans.e23 < 0.99))) + return false; + } + } + } + + return true; + } + + /** Event handler for mouse movements. + * + * @param e Event object. + */ + obj.mousemove = function (e) + { + obj.click = false; + + if (!obj.dragging_active || obj.dragging_working) + return false; + + obj.dragging_working = true; + + var p = obj.getCoords(e); + + if (slides[activeSlide].viewGroup.transform.baseVal.numberOfItems < 1) + { + var matrix = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + } + else + { + var matrix = (new matrixSVG()).fromSVGMatrix(slides[activeSlide].viewGroup.transform.baseVal.consolidate().matrix); + } + + matrix.e13 += p.x - obj.dragging_last.x; + matrix.e23 += p.y - obj.dragging_last.y; + + slides[activeSlide]["viewGroup"].setAttribute("transform", matrix.toAttribute()); + + obj.dragging_last = p; + obj.dragging_working = false; + + return false; + } + + /** Event handler for mouse down. + * + * @param e Event object. + */ + obj.mousedown = function (e) + { + if (obj.dragging_active) + return false; + + var value = 0; + + if (e.button) + value = e.button; + else if (e.which) + value = e.which; + + if (value == 1) + { + obj.dragging_last = obj.getCoords(e); + obj.dragging_active = true; + obj.click = true; + } + + return false; + } + + /** Event handler for mouse up. + * + * @param e Event object. + */ + obj.mouseup = function (e) + { + obj.dragging_active = false; + + if (obj.click) + return obj.mouseclick(e); + else + return false; + } + + /** Function to get the coordinates of a point corrected for the offset of the viewport. + * + * @param e Point. + * @returns Coordinates of the point corrected for the offset of the viewport. + */ + obj.getCoords = function (e) + { + var svgPoint = document.documentElement.createSVGPoint(); + svgPoint.x = e.clientX + window.pageXOffset; + svgPoint.y = e.clientY + window.pageYOffset; + + return svgPoint; + } + + /** Event handler for scrolling. + * + * @param e Event object. + */ + obj.mousewheel = function(e) + { + var p = obj.projectCoords(obj.getCoords(e)); + + if (slides[activeSlide].viewGroup.transform.baseVal.numberOfItems < 1) + { + var matrix = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + } + else + { + var matrix = (new matrixSVG()).fromSVGMatrix(slides[activeSlide].viewGroup.transform.baseVal.consolidate().matrix); + } + + if (e.wheelDelta) + { // IE Opera + delta = e.wheelDelta/120; + } + else if (e.detail) + { // MOZ + delta = -e.detail/3; + } + + var widthOld = p.x * matrix.e11 + p.y * matrix.e12; + var heightOld = p.x * matrix.e21 + p.y * matrix.e22; + + matrix.e11 *= (1.0 - delta / 20.0); + matrix.e12 *= (1.0 - delta / 20.0); + matrix.e21 *= (1.0 - delta / 20.0); + matrix.e22 *= (1.0 - delta / 20.0); + + var widthNew = p.x * matrix.e11 + p.y * matrix.e12; + var heightNew = p.x * matrix.e21 + p.y * matrix.e22; + + matrix.e13 += (widthOld - widthNew); + matrix.e23 += (heightOld - heightNew); + + slides[activeSlide]["viewGroup"].setAttribute("transform", matrix.toAttribute()); + + return false; + } + + /** Function to project a point to screen coordinates. + * + * @param Point. + * @returns The point projected to screen coordinates. + */ + obj.projectCoords = function(pnt) + { + var matrix = slides[activeSlide]["element"].getScreenCTM(); + + if (slides[activeSlide]["viewGroup"]) + matrix = slides[activeSlide]["viewGroup"].getScreenCTM(); + + pnt = pnt.matrixTransform(matrix.inverse()); + return pnt; + } + + /** Function to convert a rectangle into a point matrix. + * + * The function figures out a rectangle that encloses the rectangle given and has the same width/height ratio as the viewport of the presentation. + * + * @param rect Rectangle. + * @return The upper left, upper right and lower right corner of the rectangle in a point matrix. + */ + obj.rectToMatrix = function(rect) + { + rectWidth = rect.getBBox().width; + rectHeight = rect.getBBox().height; + rectX = rect.getBBox().x; + rectY = rect.getBBox().y; + rectXcorr = 0; + rectYcorr = 0; + + scaleX = WIDTH / rectWidth; + scaleY = HEIGHT / rectHeight; + + if (scaleX > scaleY) + { + scaleX = scaleY; + rectXcorr -= (WIDTH / scaleX - rectWidth) / 2; + rectWidth = WIDTH / scaleX; + } + else + { + scaleY = scaleX; + rectYcorr -= (HEIGHT / scaleY - rectHeight) / 2; + rectHeight = HEIGHT / scaleY; + } + + if (rect.transform.baseVal.numberOfItems < 1) + { + mRectTrans = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + } + else + { + mRectTrans = (new matrixSVG()).fromSVGMatrix(rect.transform.baseVal.consolidate().matrix); + } + + newBasePoints = (new matrixSVG()).fromElements(rectX, rectX, rectX, rectY, rectY, rectY, 1, 1, 1); + newVectors = (new matrixSVG()).fromElements(rectXcorr, rectXcorr + rectWidth, rectXcorr + rectWidth, rectYcorr, rectYcorr, rectYcorr + rectHeight, 0, 0, 0); + + return mRectTrans.mult(newBasePoints.add(newVectors)); + } + + /** Function to return a transformation matrix from a point matrix. + * + * @param mPoints The point matrix. + * @returns The transformation matrix. + */ + obj.pointMatrixToTransformation = function(mPoints) + { + mPointsOld = (new matrixSVG()).fromElements(0, WIDTH, WIDTH, 0, 0, HEIGHT, 1, 1, 1); + + return mPointsOld.mult(mPoints.inv()); + } +} + diff --git a/share/extensions/jessyink_autotexts.inx b/share/extensions/jessyink_autotexts.inx new file mode 100644 index 0000000..de87a33 --- /dev/null +++ b/share/extensions/jessyink_autotexts.inx @@ -0,0 +1,28 @@ + + + Auto-texts + org.inkscape.jessyink.auto_texts + + + + + + + + + + + + + + + all + + + + + + + diff --git a/share/extensions/jessyink_autotexts.py b/share/extensions/jessyink_autotexts.py new file mode 100755 index 0000000..fd73ca6 --- /dev/null +++ b/share/extensions/jessyink_autotexts.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# +# Copyright 2008, 2009 Hannes Hochreiner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +"""Automatic text for jessyInk""" + + +import inkex +from jessyink_install import JessyInkMixin, _ + +class AutoTexts(JessyInkMixin, inkex.EffectExtension): + """Add AutoText to jessyInk""" + def add_arguments(self, pars): + pars.add_argument('--tab', dest='what') + pars.add_argument('--autoText', default='none') + + def effect(self): + self.is_installed() + + if not self.svg.selected: + inkex.errormsg(_("To assign an effect, please select an object.\n\n")) + + for node in self.svg.selected.get(inkex.Tspan).values(): + if self.options.autoText == "slideTitle": + node.set("jessyink:autoText", "slideTitle") + elif self.options.autoText == "slideNumber": + node.set("jessyink:autoText", "slideNumber") + elif self.options.autoText == "numberOfSlides": + node.set("jessyink:autoText", "numberOfSlides") + elif node.get("jessyink:autoText"): + node.set("jessyink:autoText", None) + +if __name__ == '__main__': + AutoTexts().run() diff --git a/share/extensions/jessyink_effects.inx b/share/extensions/jessyink_effects.inx new file mode 100644 index 0000000..13e8cb2 --- /dev/null +++ b/share/extensions/jessyink_effects.inx @@ -0,0 +1,40 @@ + + + Effects + org.inkscape.jessyink.jessyink_effects + + + + 1 + 0.8 + + + + + + + + 1 + 0.8 + + + + + + + + + + + + + all + + + + + + + diff --git a/share/extensions/jessyink_effects.py b/share/extensions/jessyink_effects.py new file mode 100755 index 0000000..d7c7dba --- /dev/null +++ b/share/extensions/jessyink_effects.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# +# Copyright 2008, 2009 Hannes Hochreiner +# 2020 Martin Owens +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +"""Jessyink effect extension.""" + +import inkex + +from jessyink_install import JessyInkMixin, _ + +class JessyinkEffects(JessyInkMixin, inkex.EffectExtension): + """Add ad effect to jessy ink selected items""" + def add_arguments(self, pars): + pars.add_argument('--tab') + pars.add_argument('--effectInOrder', type=int, default=1) + pars.add_argument('--effectInDuration', type=float, default=0.8) + pars.add_argument('--effectIn', default='none') + pars.add_argument('--effectOutOrder', type=int, default=2) + pars.add_argument('--effectOutDuration', type=float, default=0.8) + pars.add_argument('--effectOut', default='none') + + def effect(self): + self.is_installed() + if not self.svg.selected: + raise inkex.AbortExtension( + _("No object selected. Please select the object you want to " + "assign an effect to and then press apply.\n")) + + for elem in self.svg.selected.values(): + self._process(elem, 'effectIn') + self._process(elem, 'effectOut') + + def _process(self, elem, name): + effect = getattr(self.options, name) + order = getattr(self.options, name + 'Order') + duration = int(getattr(self.options, name + 'Duration') * 1000) + + if effect in ("appear", "fade", "pop"): + elem.set("jessyink:" + name, inkex.Style(name=effect, order=order, length=duration)) + # Remove possible view argument. + elem.pop('jessyink:view', None) + else: + elem.pop('jessyink:' + name, None) + +if __name__ == '__main__': + JessyinkEffects().run() diff --git a/share/extensions/jessyink_export.inx b/share/extensions/jessyink_export.inx new file mode 100644 index 0000000..216aded --- /dev/null +++ b/share/extensions/jessyink_export.inx @@ -0,0 +1,29 @@ + + + JessyInk zipped pdf or png output + jessyink.export + org.inkscape.output.svg.inkscape + + + + + + + 92 + + + + + + + .zip + application/x-zip + JessyInk zipped pdf or png output (*.zip) + Creates a zip file containing pdfs or pngs of all slides of a JessyInk presentation. + true + + + diff --git a/share/extensions/jessyink_export.py b/share/extensions/jessyink_export.py new file mode 100755 index 0000000..66a27b5 --- /dev/null +++ b/share/extensions/jessyink_export.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright 2008, 2009 Hannes Hochreiner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +""" +JessyInk Export to zipfile multiple layers. +""" + +import zipfile + +import inkex +from inkex.base import TempDirMixin +from inkex.command import take_snapshot + +from jessyink_install import JessyInkMixin + +class Export(JessyInkMixin, TempDirMixin, inkex.OutputExtension): + """ + JessyInkExport Output Extension saves to a zipfile each of the layers. + """ + dir_prefix = 'jessyInk-' + + def add_arguments(self, pars): + pars.add_argument('--tab', type=str, dest='what') + pars.add_argument('--type', type=str, dest='type', default='png') + pars.add_argument('--resolution', type=int, default=96) + + def save(self, stream): + self.is_installed() + + with zipfile.ZipFile(stream, "w", compression=zipfile.ZIP_STORED) as output: + + # Find layers. + layers = self.svg.xpath("//svg:g[@inkscape:groupmode='layer']") + + if len(layers) < 1: + inkex.errormsg("No layers found.") + return + + for node in layers: + # Make all layers invisible + node.style['display'] = "none" + + for node in layers: + # Show only one layer at a time. + node.style.update("display:inherit;opacity:1") + + name = node.get('inkscape:label') + newname = "{}.{}".format(name, self.options.type) + filename = take_snapshot(self.document, dirname=self.tempdir, + name=name, ext=self.options.type, + dpi=self.options.resolution) + output.write(filename, newname) + + node.style['display'] = "none" + + +if __name__ == '__main__': + Export().run() diff --git a/share/extensions/jessyink_install.inx b/share/extensions/jessyink_install.inx new file mode 100644 index 0000000..d44cd88 --- /dev/null +++ b/share/extensions/jessyink_install.inx @@ -0,0 +1,21 @@ + + + Install/update + jessyink.install + jessyInk.js + + + + + + + all + + + + + + + diff --git a/share/extensions/jessyink_install.py b/share/extensions/jessyink_install.py new file mode 100755 index 0000000..a7654e3 --- /dev/null +++ b/share/extensions/jessyink_install.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# +# Copyright 2008, 2009 Hannes Hochreiner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +"""Install jessyInk scripts""" + +import inkex +from inkex import Script +from inkex.utils import NSS + +from inkex.localization import inkex_gettext as _ + +NSS[u"jessyink"] = u"https://launchpad.net/jessyink" + +class JessyInkMixin(object): + """Common jessyInk items""" + def is_installed(self): + """Check jessyInk is installed correctly""" + scripts = self.svg.getElement("//svg:script[@jessyink:version='1.5.5']") + if scripts is None: + raise inkex.AbortExtension(_( + "The JessyInk script is not installed in this SVG file or has a " + "different version than the JessyInk extensions. Please select " + "\"install/update...\" from the \"JessyInk\" sub-menu of the \"Extensions\" " + "menu to install or update the JessyInk script.\n\n")) + + def attr_remove(self, prop, is_removed=True): + """Remove a property if it exists in the svg""" + if is_removed: + for node in self.svg.xpath("//*[@jessyink:{}]".format(prop)): + node.set("jessyink:{}".format(prop), None) + for node in self.svg.xpath("//*[@jessyInk_{}]".format(prop)): + node.set("jessyInk_{}".format(prop), None) + + def attr_update(self, name): + """Update a single attr""" + for node in self.svg.xpath("//*[@jessyInk_{}]".format(name)): + node.set("jessyink:{}".format(name), node.get("jessyInk_{}".format(name))) + node.set("jessyInk_{}".format(name), None) + for node in self.svg.xpath("//*[@jessyink:{}]".format(name)): + node.set("jessyink:{}".format(name), node.get("jessyink:{}".format(name)).replace("=", ":")) + + @staticmethod + def prop_str_to_list(string): + """Script string to list of instructions""" + return [prop.strip() for prop in string.split(";") if prop] + + @staticmethod + def list_to_prop_str(lst): + """List of instructions to script string""" + return "; ".join(lst) + ';' + + +class Install(JessyInkMixin, inkex.EffectExtension): + """Install jessyInk extension into an SVG""" + def add_arguments(self, pars): + pars.add_argument('--tab', type=str, dest='what') + + def effect(self): + # Find and delete old script node + for node in self.svg.xpath("//svg:script[@id='JessyInk']"): + node.getparent().remove(node) + + # Create new script node + script_elem = Script() + with open(self.get_resource("jessyInk.js")) as fhl: + script_elem.text = fhl.read() + script_elem.set("id", "JessyInk") + script_elem.set("jessyink:version", '1.5.5') + self.svg.append(script_elem) + + # Remove "jessyInkInit()" in the "onload" attribute, if present. + prop_list = [prop.strip() for prop in self.svg.get("onload", '').split(';')] + if "jessyInkInit()" in prop_list: + prop_list.remove("jessyInkInit()") + self.svg.set("onload", "; ".join(prop_list) or None) + + # Update jessyInk attributes to new formats + for attr in ('effectIn', 'effectOut', 'masterSlide', + 'transitionIn', 'transitionOut', 'autoText'): + self.attr_update(attr) + + +# Create effect instance +if __name__ == '__main__': + Install().run() diff --git a/share/extensions/jessyink_key_bindings.inx b/share/extensions/jessyink_key_bindings.inx new file mode 100644 index 0000000..5686da5 --- /dev/null +++ b/share/extensions/jessyink_key_bindings.inx @@ -0,0 +1,68 @@ + + + Key bindings + org.inkscape.jessyink.key_bindings + + + LEFT, PAGE_UP + RIGHT, PAGE_DOWN, SPACE + UP + DOWN + HOME + END + i + d + D + n + p + t + e + + + ESCAPE, d + 0 + 1 + 3 + 5 + 7 + 9 + b + c + g + k + m + o + r + w + y + z + + + LEFT + RIGHT + UP + DOWN + PAGE_UP + PAGE_DOWN + HOME + END + ENTER, i + - + +, = + 0 + + + + + + + g + + + + + + + diff --git a/share/extensions/jessyink_key_bindings.py b/share/extensions/jessyink_key_bindings.py new file mode 100755 index 0000000..77d1543 --- /dev/null +++ b/share/extensions/jessyink_key_bindings.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python +# +# Copyright 2008, 2009 Hannes Hochreiner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +"""Effect to add key bindings to jessyInk slide show""" + +import inkex +from inkex import Group, Script +from jessyink_install import JessyInkMixin + +KEY_CODES = ('LEFT', 'RIGHT', 'DOWN', 'UP', 'HOME', 'END', + 'ENTER', 'SPACE', 'PAGE_UP', 'PAGE_DOWN', 'ESCAPE') + +class KeyBindings(JessyInkMixin, inkex.EffectExtension): + """Add key bindings to slide show""" + modes = ('slide', 'index', 'drawing') + + def set_options(self, namespace, opt_str, value): + """Sort through all the options and combine them""" + slot, action = opt_str.split('_', 1) + keycodes = getattr(namespace, slot + "KeyCodes", {}) + charcodes = getattr(namespace, slot + "CharCodes", {}) + if value: + for val in value.split(","): + val = val.strip() + if val in KEY_CODES: + keycodes[val + "_KEY"] = self.actions[slot][action] + elif len(val) == 1: + charcodes[val] = self.actions[slot][action] + setattr(namespace, slot + "KeyCodes", keycodes) + setattr(namespace, slot + "CharCodes", charcodes) + + actions = { + 'slide': { + "export": "slideUpdateExportLayer();", + "addSlide": "slideAddSlide(activeSlide);", + "resetTimer": "slideResetTimer();", + "setDuration": "slideQueryDuration();", + "backWithEffects": "dispatchEffects(-1);", + "nextWithEffects": "dispatchEffects(1);", + "backWithoutEffects": "skipEffects(-1);", + "nextWithoutEffects": "skipEffects(1);", + "switchToIndexMode": "toggleSlideIndex();", + "switchToDrawingMode": "slideSwitchToDrawingMode();", + "toggleProgressBar": "slideToggleProgressBarVisibility();", + "firstSlide": "slideSetActiveSlide(0);", + "lastSlide": "slideSetActiveSlide(slides.length - 1);", + }, + 'drawing': { + "undo": "drawingUndo();", + "switchToSlideMode": "drawingSwitchToSlideMode();", + "pathWidthDefault": "drawingResetPathWidth();", + "pathWidth1": "drawingSetPathWidth(1.0);", + "pathWidth3": "drawingSetPathWidth(3.0);", + "pathWidth5": "drawingSetPathWidth(5.0);", + "pathWidth7": "drawingSetPathWidth(7.0);", + "pathWidth9": "drawingSetPathWidth(9.0);", + "pathColourBlue": "drawingSetPathColour(\"blue\");", + "pathColourCyan": "drawingSetPathColour(\"cyan\");", + "pathColourGreen": "drawingSetPathColour(\"green\");", + "pathColourBlack": "drawingSetPathColour(\"black\");", + "pathColourMagenta": "drawingSetPathColour(\"magenta\");", + "pathColourOrange": "drawingSetPathColour(\"orange\");", + "pathColourRed": "drawingSetPathColour(\"red\");", + "pathColourWhite": "drawingSetPathColour(\"white\");", + "pathColourYellow": "drawingSetPathColour(\"yellow\");", + }, + 'index': { + "selectSlideToLeft": "indexSetPageSlide(activeSlide - 1);", + "selectSlideToRight": "indexSetPageSlide(activeSlide + 1);", + "selectSlideAbove": "indexSetPageSlide(activeSlide - INDEX_COLUMNS);", + "selectSlideBelow": "indexSetPageSlide(activeSlide + INDEX_COLUMNS);", + "previousPage": "indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS);", + "nextPage": "indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS);", + "firstSlide": "indexSetPageSlide(0);", + "lastSlide": "indexSetPageSlide(slides.length - 1);", + "switchToSlideMode": "toggleSlideIndex();", + "decreaseNumberOfColumns": "indexDecreaseNumberOfColumns();", + "increaseNumberOfColumns": "indexIncreaseNumberOfColumns();", + "setNumberOfColumnsToDefault": "indexResetNumberOfColumns();", + } + } + + def add_arguments(self, pars): + pars.add_argument('--tab') + for slot, actions in self.actions.items(): + for action in actions: + pars.add_argument('--{slot}_{action}'.format(slot=slot, action=action)) + + def effect(self): + self.is_installed() + + for name in list(self.options.__dict__): + if '_' in name: + self.set_options(self.options, name, self.options.__dict__.pop(name)) + + # Remove old master slide property + for node in self.svg.xpath("//svg:g[@jessyink:customKeyBindings='customKeyBindings']"): + node.delete() + + # Set custom key bindings. + node_text = """function getCustomKeyBindingsSub() +{ + var keyDict = new Object(); + keyDict[SLIDE_MODE] = new Object(); + keyDict[INDEX_MODE] = new Object(); + keyDict[DRAWING_MODE] = new Object(); +""" + + for key, value in self.options.slideKeyCodes.items(): + node_text += " keyDict[SLIDE_MODE][{key}] = function() {{ {value} }};\n".format(**locals()) + + for key, value in self.options.drawingKeyCodes.items(): + node_text += " keyDict[DRAWING_MODE][{key}] = function() {{ {value} }};\n".format(**locals()) + + for key, value in self.options.indexKeyCodes.items(): + node_text += " keyDict[INDEX_MODE][{key}] = function() {{ {value} }};\n".format(**locals()) + + # Set custom char bindings. + node_text += """ return keyDict; +} + +function getCustomCharBindingsSub() +{ + var charDict = new Object(); + charDict[SLIDE_MODE] = new Object(); + charDict[INDEX_MODE] = new Object(); + charDict[DRAWING_MODE] = new Object(); +""" + + for key, value in self.options.slideCharCodes.items(): + node_text += ' charDict[SLIDE_MODE]["{key}"] = function() {{ {value} }};\n'.format(**locals()) + + for key, value in self.options.drawingCharCodes.items(): + node_text += ' charDict[DRAWING_MODE]["{key}"] = function() {{ {value} }};\n'.format(**locals()) + + for key, value in self.options.indexCharCodes.items(): + node_text += ' charDict[INDEX_MODE]["{key}"] = function() {{ {value} }};\n'.format(**locals()) + + node_text += " return charDict;" + "\n" + node_text += "}" + "\n" + + # Create new script node + group = self.svg.add(Group()) + script = group.add(Script()) + script.text = node_text + group.set("jessyink:customKeyBindings", "customKeyBindings") + group.set("onload", "this.getCustomCharBindings = function() { "\ + "return getCustomCharBindingsSub(); }; "\ + "this.getCustomKeyBindings = function() { return getCustomKeyBindingsSub(); };") + + +if __name__ == '__main__': + KeyBindings().run() diff --git a/share/extensions/jessyink_master_slide.inx b/share/extensions/jessyink_master_slide.inx new file mode 100644 index 0000000..8ca3074 --- /dev/null +++ b/share/extensions/jessyink_master_slide.inx @@ -0,0 +1,24 @@ + + + Master slide + org.inkscape.jessyink.master_slide + + + + + + + + + + + g + + + + + + + diff --git a/share/extensions/jessyink_master_slide.py b/share/extensions/jessyink_master_slide.py new file mode 100755 index 0000000..99d689d --- /dev/null +++ b/share/extensions/jessyink_master_slide.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# +# Copyright 2008, 2009 Hannes Hochreiner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +""" +Jessyink Set Master Slide +""" + +import inkex + +from jessyink_install import JessyInkMixin, _ + +class MasterSlide(JessyInkMixin, inkex.EffectExtension): + """Effect Extension for master slide""" + def add_arguments(self, pars): + self.arg_parser.add_argument('--tab') + self.arg_parser.add_argument('--layerName', default='') + + def effect(self): + self.is_installed() + # Remove old master slide property + for node in self.svg.xpath("//*[@jessyink:masterSlide='masterSlide']"): + node.set("jessyink:masterSlide", None) + + # Set new master slide. + if self.options.layerName != "": + nodes = self.svg.xpath(("//*[@inkscape:groupmode='layer' " + "and @inkscape:label='{self.options.layerName}']").format(**locals())) + if not nodes: + inkex.errormsg(_("Layer not found. Removed current master slide selection.\n")) + elif len(nodes) > 1: + inkex.errormsg(_("More than one layer with this name found. " + "Removed current master slide selection.\n")) + else: + nodes[0].set("jessyink:masterSlide", "masterSlide") + +if __name__ == '__main__': + MasterSlide().run() diff --git a/share/extensions/jessyink_mouse_handler.inx b/share/extensions/jessyink_mouse_handler.inx new file mode 100644 index 0000000..c1b0af4 --- /dev/null +++ b/share/extensions/jessyink_mouse_handler.inx @@ -0,0 +1,27 @@ + + + Mouse handler + org.inkscape.jessyink.add_mouse_handler + + + + + + + + + + + + + + g + + + + + + + diff --git a/share/extensions/jessyink_mouse_handler.py b/share/extensions/jessyink_mouse_handler.py new file mode 100755 index 0000000..7d2648c --- /dev/null +++ b/share/extensions/jessyink_mouse_handler.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# +# Copyright 2008, 2009 Hannes Hochreiner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +"""Add mouse handler for jessyInk""" + +import inkex +from inkex.elements import BaseElement, Script +from jessyink_install import JessyInkMixin + +class MouseHandler(BaseElement): + """jessyInk mouse handler""" + tag_name = 'jessyink:mousehandler' + +class AddMouseHandler(JessyInkMixin, inkex.EffectExtension): + """Add mouse handler""" + def add_arguments(self, pars): + pars.add_argument('--tab') + pars.add_argument('--mouseSetting', default='default') + + def effect(self): + self.is_installed() + # Remove old mouse handler + for node in self.svg.xpath("//jessyink:mousehandler"): + node.delete() + + # Create new script node. + script = Script() + group = MouseHandler() + + if self.options.mouseSetting == "noclick": + name = "noclick" + elif self.options.mouseSetting == "draggingZoom": + name = "zoomControl" + elif self.options.mouseSetting == "default": + # Default is to remove script and continue + return + + with open(self.get_resource("jessyInk_core_mouseHandler_" + name + ".js")) as fhl: + script.text = fhl.read() + group.set("jessyink:subtype", "jessyInk_core_mouseHandler_" + name) + + group.append(script) + self.svg.append(group) + +if __name__ == '__main__': + AddMouseHandler().run() diff --git a/share/extensions/jessyink_summary.inx b/share/extensions/jessyink_summary.inx new file mode 100644 index 0000000..3a6b688 --- /dev/null +++ b/share/extensions/jessyink_summary.inx @@ -0,0 +1,20 @@ + + + Summary + jessyink.summary + + + + + + + all + + + + + + + diff --git a/share/extensions/jessyink_summary.py b/share/extensions/jessyink_summary.py new file mode 100755 index 0000000..e8e8a6e --- /dev/null +++ b/share/extensions/jessyink_summary.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# +# Copyright 2008, 2009 Hannes Hochreiner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +"""Effect to print out jessyInk summary""" + +from collections import defaultdict +import inkex + +from jessyink_install import JessyInkMixin, _ + +class Summary(JessyInkMixin, inkex.EffectExtension): + """Print of jessyInk summary""" + def add_arguments(self, pars): + pars.add_argument('--tab') + + def effect(self): + self.is_installed() + + # Find the script node, if present + for node in self.svg.xpath("//svg:script[@id='JessyInk']"): + version = node.get("jessyink:version") + if version: + self.msg(_("JessyInk script version {} installed.".format(version))) + else: + self.msg(_("JessyInk script installed.")) + + slides = [] + master_slide = None + + for node in self.svg.descendants().get(inkex.Layer).values(): + if node.get("jessyink:master_slide"): + master_slide = node + else: + slides.append(node) + + if master_slide is not None: + self.msg(_("\nMaster slide:")) + self.describe_node(master_slide, "\t",\ + ["", len(slides), ""]) + + for i, slide in enumerate(slides): + self.msg(_("\nSlide {0!s}:").format(i+1)) + self.describe_node(slide, "\t", [i + 1, len(slides), slide.label]) + + def describe_node(self, node, prefix, dat): + """Standard print out formatter""" + + self.msg(_("{prefix}Layer name: {node.label}".format(**locals()))) + self.describe_transition(node, prefix, "In") + self.describe_transition(node, prefix, "Out") + self.describe_autotext(node, prefix, dat) + self.describe_effects(node, prefix) + + def describe_transition(self, node, prefix, transition): + """Display information about transitions.""" + trans = inkex.Style(node.get("jessyink:transition" + transition)) + if trans: + name = trans["name"] + if name != "appear" and "length" in trans: + length = int(trans["length"] / 1000.0) + self.msg(_("{prefix}Transition {transition}: {name} ({length!s} s)".format(**locals()))) + else: + self.msg(_("{prefix}Transition {transition}: {name}".format(**locals()))) + + def describe_autotext(self, node, prefix, dat): + """Display information about auto-texts.""" + auto_texts = {"slide_num" : dat[0], "num" : dat[1], "title" : dat[2]} + for x, child in enumerate(node.xpath(".//*[@jessyink:autoText]")): + if not x: + self.msg(_("\n{}Auto-texts:".format(prefix))) + + pid = child.getparent().get("id") + val = auto_texts[child.get('jessyink:autoText')] + self.msg(_( + '{prefix}\t"{child.text}" (object id "{pid}") will be replaced by "{val}".'.format(**locals()))) + + def describe_effects(self, node, prefix): + """Display information about effects.""" + effects = sorted(self.collect_effects(node), key=lambda val: val[0]) + for x, (enum, effect) in enumerate(effects): + ret = "" + + order = effect[0]["order"] + if not x: + ret += _("\n{prefix}Initial effect (order number {order}):".format(**locals())) + else: + ret += _("\n{prefix}Effect {enum!s} (order number {order}):".format(**locals())) + + for item in effect: + eid = item["id"] + if item["type"] == "view": + ret += _("{prefix}\tView will be set according to object \"{eid}\"".format(**locals())) + else: + ret += _("{prefix}\tObject \"{eid}\"".format(**locals())) + + if item["direction"] == "in": + ret += _(" will appear") + elif item["direction"] == "out": + ret += _(" will disappear") + + if item["name"] != "appear": + ret += _(" using effect \"{0}\"").format(item["name"]) + + if "length" in item: + ret += _(" in {0!s} s").format(int(item["length"]) / 1000.0) + + self.msg(ret + ".\n") + + @staticmethod + def collect_effects(node): + """Collect information about effects.""" + effects = defaultdict(list) + for child in node.xpath(".//*[@jessyink:effectIn]"): + effect_data = inkex.Style(child.get('jessyink:effectIn')) + effect_data["direction"] = "in" + effect_data["id"] = child.get("id") + effect_data["type"] = "effect" + effects[effect_data["order"]].append(effect_data) + + for child in node.xpath(".//*[@jessyink:effectOut]"): + effect_data = inkex.Style(child.get('jessyink:effectOut')) + effect_data["direction"] = "out" + effect_data["id"] = child.get("id") + effect_data["type"] = "effect" + effects[effect_data["order"]].append(effect_data) + + for child in node.xpath(".//*[@jessyink:view]"): + effect_data = inkex.Style(child.get('jessyink:view')) + effect_data["id"] = child.get("id") + effect_data["type"] = "view" + effects[effect_data["order"]].append(effect_data) + return effects + +if __name__ == '__main__': + Summary().run() diff --git a/share/extensions/jessyink_transitions.inx b/share/extensions/jessyink_transitions.inx new file mode 100644 index 0000000..90701f5 --- /dev/null +++ b/share/extensions/jessyink_transitions.inx @@ -0,0 +1,39 @@ + + + Transitions + jessyink.transitions + + + + + 0.8 + + + + + + + + 0.8 + + + + + + + + + + + + + g + + + + + + + diff --git a/share/extensions/jessyink_transitions.py b/share/extensions/jessyink_transitions.py new file mode 100755 index 0000000..87cf258 --- /dev/null +++ b/share/extensions/jessyink_transitions.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# +# Copyright 2008, 2009 Hannes Hochreiner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +"""Effect to add transitions""" + +import inkex +from inkex.styles import Style + +from jessyink_install import JessyInkMixin, _ + +class Transitions(JessyInkMixin, inkex.EffectExtension): + """Add transition to later""" + def add_arguments(self, pars): + pars.add_argument('--tab', dest='what') + pars.add_argument('--layerName', default='') + pars.add_argument('--effectIn', default='default') + pars.add_argument('--effectOut', default='default') + pars.add_argument('--effectInDuration', type=float, default=0.8) + pars.add_argument('--effectOutDuration', type=float, default=0.8) + + def effect(self): + self.is_installed() + + if not self.options.layerName: + raise inkex.AbortExtension(_("Please enter a layer name.")) + + node = self.svg.getElement("//*[@inkscape:groupmode='layer' " + "and @inkscape:label='{}']".format(self.options.layerName)) + if node is None: + raise inkex.AbortExtension(_("Layer '{}' not found.".format(self.options.layerName))) + + if self.options.effectIn == "default": + node.set("jessyink:transitionIn", None) + else: + length = int(self.options.effectInDuration * 1000) + node.set("jessyink:transitionIn", Style(name=self.options.effectIn, length=length)) + + if self.options.effectOut == "default": + node.set("jessyink:transitionOut", None) + else: + length = int(self.options.effectOutDuration * 1000) + node.set("jessyink:transitionOut", Style(name=self.options.effectOut, length=length)) + +if __name__ == '__main__': + Transitions().run() diff --git a/share/extensions/jessyink_uninstall.inx b/share/extensions/jessyink_uninstall.inx new file mode 100644 index 0000000..a5d1711 --- /dev/null +++ b/share/extensions/jessyink_uninstall.inx @@ -0,0 +1,29 @@ + + + Uninstall/remove + jessyink.uninstall + + + + true + true + true + true + true + true + + + + + + + all + + + + + + + diff --git a/share/extensions/jessyink_uninstall.py b/share/extensions/jessyink_uninstall.py new file mode 100755 index 0000000..da9f913 --- /dev/null +++ b/share/extensions/jessyink_uninstall.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# +# Copyright 2008, 2009 Hannes Hochreiner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +"""Uninstall jessyInk""" + +import inkex +from jessyink_install import JessyInkMixin + +class Uninstall(JessyInkMixin, inkex.EffectExtension): + """Uninstall jessyInk from this svg""" + def add_arguments(self, pars): + pars.add_argument('--tab') + pars.add_argument('--remove_script', type=inkex.Boolean, default=True) + pars.add_argument('--remove_effects', type=inkex.Boolean, default=True) + pars.add_argument('--remove_masterSlide', type=inkex.Boolean, default=True) + pars.add_argument('--remove_transitions', type=inkex.Boolean, default=True) + pars.add_argument('--remove_autoTexts', type=inkex.Boolean, default=True) + pars.add_argument('--remove_views', type=inkex.Boolean, default=True) + + def effect(self): + # Remove script, if so desired. + if self.options.remove_script: + # Find and delete script node. + for node in self.svg.xpath("//svg:script[@id='JessyInk']"): + node.delete() + + # Remove "jessyInkInit()" in the "onload" attribute, if present. + prop_list = [] + if self.svg.get("onload"): + prop_list = self.prop_str_to_list(self.svg.get("onload")) + + for prop in prop_list: + if prop == "jessyInkInit()": + prop_list.remove("jessyInkInit()") + + if prop_list: + self.svg.set("onload", self.list_to_prop_str(prop_list)) + else: + if self.document.getroot().get("onload"): + del self.document.getroot().attrib["onload"] + + self.attr_remove("effectIn", self.options.remove_effects) + self.attr_remove("effectOut", self.options.remove_effects) + self.attr_remove("masterSlide", self.options.remove_masterSlide) + self.attr_remove("transitionIn", self.options.remove_transitions) + self.attr_remove("transitionOut", self.options.remove_transitions) + self.attr_remove("autoText", self.options.remove_autoTexts) + self.attr_remove("view", self.options.remove_views) + + +# Create effect instance. +if __name__ == '__main__': + Uninstall().run() diff --git a/share/extensions/jessyink_video.inx b/share/extensions/jessyink_video.inx new file mode 100644 index 0000000..62a7e4c --- /dev/null +++ b/share/extensions/jessyink_video.inx @@ -0,0 +1,20 @@ + + + Video + jessyink.core.video + + + + + + + all + + + + + + + diff --git a/share/extensions/jessyink_video.py b/share/extensions/jessyink_video.py new file mode 100755 index 0000000..0b783d7 --- /dev/null +++ b/share/extensions/jessyink_video.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# +# Copyright 2008, 2009 Hannes Hochreiner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +"""Add a video to the slideshow""" + +import re +from copy import deepcopy + +import inkex +from jessyink_install import JessyInkMixin, _ + +class Video(JessyInkMixin, inkex.EffectExtension): + """Add jessyink video""" + def add_arguments(self, pars): + self.arg_parser.add_argument('--tab', dest='what') + + def effect(self): + self.is_installed() + # Check version. + base_view = self.svg.xpath("//sodipodi:namedview[@id='base']") + if base_view is None: + raise inkex.AbortExtension(_( + "Could not obtain the selected layer for inclusion of the video element.")) + + layer = self.svg.get_current_layer() + if layer is None: + raise inkex.AbortExtension(_( + "Could not obtain the selected layer for inclusion of the video element.\n\n")) + + template = inkex.load_svg(self.get_resource('jessyink_video.svg')) + root = template.getroot() + + elem = layer.add(root.getElement("//svg:g[@jessyink:element='core.video']").copy()) + node_dict = find_internal_links(elem, root, {}) + delete_ids(elem) + + for node in node_dict.values(): + elem.insert(0, node) + + for node in node_dict.values(): + node.set_id(self.svg.get_unique_id("jessyink.core.video"), backlinks=True) + +def find_internal_links(node, svg, node_dict): + """Get all clone links and css links""" + for entry in re.findall(br"url\(#.*\)", node.tostring()): + entry = entry.decode() + link_id = entry[5:len(entry) - 1] + + if link_id not in node_dict: + node_dict[link_id] = deepcopy(svg.getElementById(link_id)) + find_internal_links(node_dict[link_id], svg, node_dict) + + for entry in node.iter(): + if entry.get('xlink:href'): + link_id = entry.get('xlink:href') + if link_id not in node_dict: + node_dict[link_id] = deepcopy(svg.getElementById(link_id)) + find_internal_links(node_dict[link_id], svg, node_dict) + + return node_dict + +def delete_ids(node): + """Delete ids in the given node's children""" + for entry in node.iter(): + if 'id' in entry.attrib: + del entry.attrib['id'] + +if __name__ == '__main__': + Video().run() diff --git a/share/extensions/jessyink_video.svg b/share/extensions/jessyink_video.svg new file mode 100644 index 0000000..1d9e755 --- /dev/null +++ b/share/extensions/jessyink_video.svg @@ -0,0 +1,596 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + <replace this text with the url of the movie> + + + + + + + JessyInk video element + + + diff --git a/share/extensions/jessyink_view.inx b/share/extensions/jessyink_view.inx new file mode 100644 index 0000000..e71c9ad --- /dev/null +++ b/share/extensions/jessyink_view.inx @@ -0,0 +1,26 @@ + + + View + jessyink.view + + + 1 + 0.8 + false + + + + + + + + rect + + + + + + + diff --git a/share/extensions/jessyink_view.py b/share/extensions/jessyink_view.py new file mode 100755 index 0000000..d772c33 --- /dev/null +++ b/share/extensions/jessyink_view.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# +# Copyright 2008, 2009 Hannes Hochreiner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +"""Extension to sssign jessyInk views to objects""" + +import inkex + +from jessyink_install import JessyInkMixin, _ + +class View(JessyInkMixin, inkex.EffectExtension): + """Assign jessyInk views to objects""" + def add_arguments(self, pars): + pars.add_argument('--tab', dest='what') + pars.add_argument('--viewOrder', type=int, default=1) + pars.add_argument('--viewDuration', type=float, default=0.8) + pars.add_argument('--removeView', type=inkex.Boolean) + + def effect(self): + self.is_installed() + + rect = self.svg.selected.first() + + if rect is None: + raise inkex.AbortExtension(_("No object selected. Please select the object you want " + "to assign a view to and then press apply.\n")) + + if not self.options.removeView: + view_order = str(self.options.viewOrder) + # Remove the view that currently has the requested order number. + for node in rect.xpath("ancestor::svg:g[@inkscape:groupmode='layer']" + "/descendant::*[@jessyink:view]"): + prop_dict = inkex.Style(node.get("jessyink:view")) + + if prop_dict["order"] == view_order: + node.set("jessyink:view", None) + + # Set the new view. + rect.set("jessyink:view", inkex.Style( + name="view", + order=view_order, + length=int(self.options.viewDuration * 1000), + )) + + # Remove possible effect arguments. + self.attr_remove('effectIn') + self.attr_remove('effectOut') + else: + self.attr_remove('view') + +if __name__ == '__main__': + View().run() diff --git a/share/extensions/jitternodes.inx b/share/extensions/jitternodes.inx new file mode 100644 index 0000000..bd298ed --- /dev/null +++ b/share/extensions/jitternodes.inx @@ -0,0 +1,31 @@ + + + Jitter nodes + org.inkscape.filter.jitter_nodes + + + 10.0 + 10.0 + true + false + + + + + + + + + + + + + path + + + + + + diff --git a/share/extensions/jitternodes.py b/share/extensions/jitternodes.py new file mode 100755 index 0000000..9c326f5 --- /dev/null +++ b/share/extensions/jitternodes.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2012 Juan Pablo Carbajal ajuanpi-dev@gmail.com +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import math +import random +import inkex + + +class JitterNodes(inkex.EffectExtension): + """Jiggle nodes around""" + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("--radiusx", type=float, default=10.0, help="Randum radius X") + pars.add_argument("--radiusy", type=float, default=10.0, help="Randum radius Y") + pars.add_argument("--ctrl", type=inkex.Boolean, default=True, help="Randomize ctrl points") + pars.add_argument("--end", type=inkex.Boolean, default=True, help="Randomize nodes") + pars.add_argument("--dist", type=self.arg_method('dist'), + default=self.dist_uniform, help="Distribution of displacement") + + def effect(self): + for node in self.svg.selection.filter(inkex.PathElement).values(): + path = node.path.to_superpath() + for subpath in path: + closed = subpath[0] == subpath[-1] + for index, csp in enumerate(subpath): + if closed and index == len(subpath) - 1: + subpath[index] = subpath[0] + break + if self.options.end: + delta = self.randomize([0, 0]) + csp[0][0] += delta[0] + csp[0][1] += delta[1] + csp[1][0] += delta[0] + csp[1][1] += delta[1] + csp[2][0] += delta[0] + csp[2][1] += delta[1] + if self.options.ctrl: + csp[0] = self.randomize(csp[0]) + csp[2] = self.randomize(csp[2]) + node.path = path + + def randomize(self, pos): + """Randomise the given position [x, y] as set in the options""" + delta = self.options.dist(self.options.radiusx, self.options.radiusy) + return [pos[0] + delta[0], pos[1] + delta[1]] + + @staticmethod + def dist_gaussian(x, y): + """Gaussian distribution""" + return random.gauss(0.0, x), random.gauss(0.0, y) + + @staticmethod + def dist_pareto(x, y): + """Pareto distribution""" + # sign is used to fake a double sided pareto distribution. + # for parameter value between 1 and 2 the distribution has infinite variance + # I truncate the distribution to a high value and then normalize it. + # The idea is to get spiky distributions, any distribution with long-tails is + # good (ideal would be Levy distribution). + sign = random.uniform(-1.0, 1.0) + return x * math.copysign(min(random.paretovariate(1.0), 20.0) / 20.0, sign),\ + y * math.copysign(min(random.paretovariate(1.0), 20.0) / 20.0, sign) + + @staticmethod + def dist_lognorm(x, y): + """Log Norm distribution""" + sign = random.uniform(-1.0, 1.0) + return x * math.copysign(random.lognormvariate(0.0, 1.0) / 3.5, sign),\ + y * math.copysign(random.lognormvariate(0.0, 1.0) / 3.5, sign) + + @staticmethod + def dist_uniform(x, y): + """Uniform distribution""" + return random.uniform(-x, x), random.uniform(-y, y) + +if __name__ == '__main__': + JitterNodes().run() diff --git a/share/extensions/launch_webbrowser.py b/share/extensions/launch_webbrowser.py new file mode 100755 index 0000000..fa79345 --- /dev/null +++ b/share/extensions/launch_webbrowser.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2006-2019 AUTHORS +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Open the default operating system web browser at the url specified. +""" +import webbrowser +import threading +from argparse import ArgumentParser + +BROWSER = None # Default from 'webbrowser' overright in tests + +class ThreadWebsite(threading.Thread): + """Visit website without locking Inkscape""" + def __init__(self, args=None): + threading.Thread.__init__(self) + parser = ArgumentParser() + parser.add_argument("-u", "--url", + default="https://www.inkscape.org/", + help="The URL to open in web browser") + self.options = parser.parse_args(args) + + def run(self): + webbrowser.get(BROWSER).open(self.options.url) + +if __name__ == '__main__': + ThreadWebsite().start() diff --git a/share/extensions/layer2png.inx b/share/extensions/layer2png.inx new file mode 100644 index 0000000..1242854 --- /dev/null +++ b/share/extensions/layer2png.inx @@ -0,0 +1,36 @@ + + + Export Layer Slices + org.inkscape.output.export_slices + + + false + 128, 64, 48, 32, 24, 16 + slices + true + + 300 + + + + + + + + + + + + + + all + + + + + + + diff --git a/share/extensions/layer2png.py b/share/extensions/layer2png.py new file mode 100755 index 0000000..9b28d31 --- /dev/null +++ b/share/extensions/layer2png.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2007-2019 Matt Harrison, matthewharrison [at] gmail.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +""" +A script that slices images. It might be useful for web design. + +You pass it the name of a layer containing rectangles that cover +the areas that you want exported (the default name for this layer +is "slices"). It then sets the opacity to 0 for all the rectangles +defined in that layer and exports as png whatever they covered. +The output filenames are based on the "Id" field of "Object Properties" +right click contextual menu of the rectangles. + +One side effect is that after exporting, it sets the slice rectangles +to different colors with a 25% opacity. (If you want to hide them, +just click on the eye next to the layer). + + * red - overwrote a file + * green - wrote a new file + * grey - skipped (not overwriting) + +For good pixel exports set the Document Properties, default units to "px" +and the width/height to the real size. (I use 1024x768) + +Here's the process I've used for slicing web layout with +Inkscape: Create your webpage layout (set page units to "px", +width/height appropriately and snap to 1 pixel intervals. This should +allow pixel perfect alignment). Then create a new layer, naming it +slices. Draw rectangles over the areas you want to slice (set +x,y,width,height to whole pixel values). Name these rectangles using +the Object Properties found in the right click contextual menu (the +saved images name will be based on that value, so name them something +like "header" instead of the default/non-useful "rect4312"). +""" +import os +import tempfile + +import inkex +from inkex.command import inkscape + +class ExportSlices(inkex.EffectExtension): + """Exports all rectangles in the current layer""" + GREEN = "#00ff00" # new export + GREY = "#555555" # not exported + RED = "#ff0000" # overwrite + + + def __init__(self): + super(ExportSlices, self).__init__() + self.color_map = {} # map node id to color based on overwrite + + + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("--directory", default=os.path.expanduser("~"),\ + help="Existing destination directory") + pars.add_argument("--layer", default="slices", help="Layer with slices (rects) in it") + pars.add_argument("--iconmode", type=inkex.Boolean, help="Icon export mode") + pars.add_argument("--sizes", default="128, 64, 48, 32, 24, 16",\ + help="sizes to export comma separated") + pars.add_argument("--overwrite", type=inkex.Boolean, help="Overwrite existing exports?") + pars.add_argument("--dpi", default="300", help="Dots per inch (300 default)") + + def effect(self): + if not os.path.isdir(self.options.directory): + os.makedirs(self.options.directory) + + nodes = self.get_layer_nodes(self.options.layer) + if nodes is None: + raise inkex.AbortExtension("Slice: '{}' does not exist.".format(self.options.layer)) + + # set opacity to zero in slices + for node in nodes: + self.clear_color(node) + + # save file once now + # if we have multiple slices we will make multiple calls + # to inkscape + (_, tmp_svg) = tempfile.mkstemp('.svg') + with open(tmp_svg, 'wb') as fout: + fout.write(self.svg.tostring()) + + # in case there are overlapping rects, clear them all out before + # saving any + for node in nodes: + if self.options.iconmode: + for size in self.options.sizes.split(","): + size = size.strip() + if size.isdigit(): + png_size = int(size) + self.export_node(node, png_size, png_size) + else: + self.export_node(node) + + # change slice colors to grey/green/red and set opacity to 25% in real document + for node in nodes: + self.change_color(node) + return self.document + + def get_layer_nodes(self, layer_name): + """ + given the name of a layer one that contains the rectangles defining slices, + return the nodes of the rectangles. + """ + # get layer we intend to slice + slice_node = None + slice_layer = self.svg.findall('svg:g') + for node in slice_layer: + label_value = node.label + if label_value == layer_name: + slice_node = node + + if slice_node is not None: + return slice_node.findall('svg:rect') + return slice_node + + + def clear_color(self, node): + """ + set opacity to zero, and stroke to none + + Node looks like this: + + + 3 - Convert Glyph Layers to SVG Font + org.inkscape.typography.layers_to_svg_font + + all + + + + + + diff --git a/share/extensions/layers2svgfont.py b/share/extensions/layers2svgfont.py new file mode 100755 index 0000000..b5d7503 --- /dev/null +++ b/share/extensions/layers2svgfont.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2011 Felipe Correa da Silva Sanches +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +"""Convert known layer structures to svg font glyphs""" + +import inkex +from inkex import SVGfont, FontFace, Glyph + +class LayersToSvgFont(inkex.EffectExtension): + """Convert layers to an svg font""" + def guideline_value(self, label, index): + for guide in self.svg.namedview.get_guides(): + if guide.label == label: + return guide.point[index] + return 0 + + def flip_cordinate_system(self, path, emsize, baseline): + path = path.copy() + path.transform.add_scale(1, -1) + path.transform.add_translate(0, int(emsize) - int(baseline)) + path.apply_transform() + return str(path.path) + + def effect(self): + emsize = int(float(self.svg.get("width"))) + baseline = self.guideline_value("baseline", 1) + ascender = self.guideline_value("ascender", 1) - baseline + caps = self.guideline_value("caps", 1) - baseline + xheight = self.guideline_value("xheight", 1) - baseline + descender = baseline - self.guideline_value("descender", 1) + + font = self.svg.defs.get_or_create('svg:font', SVGfont) + font.set("horiz-adv-x", str(emsize)) + font.set("horiz-origin-y", str(baseline)) + + fontface = font.get_or_create('font-face', FontFace) + fontface.set("font-family", "SVGFont") + fontface.set("units-per-em", str(emsize)) + fontface.set("cap-height", str(caps)) + fontface.set("x-height", str(xheight)) + fontface.set("ascent", str(ascender)) + fontface.set("descent", str(descender)) + + for group in self.svg.findall('svg:g'): + label = group.label + if "GlyphLayer-" in label: + unicode_char = label.split("GlyphLayer-")[1] + glyph = font.get_or_create("svg:glyph[@unicode='{}']".format(unicode_char), Glyph) + glyph.set("unicode", unicode_char) + + ############################ + # Option 1: + # Using clone (svg:use) as childnode of svg:glyph + + # use = glyph.get_or_create('svg:use', UseElement) + # use.set('xlink:href', "#"+group.get("id")) + # TODO: This code creates nodes but they do not render on svg fonts dialog. why? + + ############################ + # Option 2: + # Using svg:paths as childnodes of svg:glyph + + # for p in group.findall('svg:path'): + # d = p.get("d") + # d = self.flip_cordinate_system(d, emsize, baseline) + # path = glyph.add(PathElement()) + # path.set("d", d) + + ############################ + # Option 3: + # Using curve description in d attribute of svg:glyph + + path_d = "" + for path in group.findall('svg:path'): + path_d += " " + self.flip_cordinate_system(path, emsize, baseline) + glyph.set("d", path_d) + +if __name__ == '__main__': + LayersToSvgFont().run() diff --git a/share/extensions/layout_nup.inx b/share/extensions/layout_nup.inx new file mode 100644 index 0000000..96dc343 --- /dev/null +++ b/share/extensions/layout_nup.inx @@ -0,0 +1,75 @@ + + + N-up layout + org.greygreen.inkscape.effects.nup + + + + + + + + + + 816 + 1056 + + 0 + 0 + 0 + 0 + + + 2 + 2 + 100 + 200 + true + + 12 + 12 + 12 + 12 + + 0 + 0 + 0 + 0 + + + true + true + true + false + false + false + + + + + + + + all + + + + + + + + + + + diff --git a/share/extensions/layout_nup.py b/share/extensions/layout_nup.py new file mode 100755 index 0000000..5799f55 --- /dev/null +++ b/share/extensions/layout_nup.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2007 Terry Brown, terry_n_brown@yahoo.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +from __future__ import absolute_import, unicode_literals + +import inkex +from inkex import Use, Rectangle +from inkex.base import SvgOutputMixin + +class Nup(inkex.OutputExtension, SvgOutputMixin): + """N-up Layout generator""" + def add_arguments(self, pars): + pars.add_argument('--unit', default='px') + pars.add_argument('--rows', type=int, default=2) + pars.add_argument('--cols', type=int, default=2) + pars.add_argument('--paddingTop', type=float) + pars.add_argument('--paddingBottom', type=float) + pars.add_argument('--paddingLeft', type=float) + pars.add_argument('--paddingRight', type=float) + pars.add_argument('--marginTop', type=float) + pars.add_argument('--marginBottom', type=float) + pars.add_argument('--marginLeft', type=float) + pars.add_argument('--marginRight', type=float) + pars.add_argument('--pgMarginTop', type=float) + pars.add_argument('--pgMarginBottom', type=float) + pars.add_argument('--pgMarginLeft', type=float) + pars.add_argument('--pgMarginRight', type=float) + pars.add_argument('--pgSizeX', type=float) + pars.add_argument('--pgSizeY', type=float) + pars.add_argument('--sizeX', type=float) + pars.add_argument('--sizeY', type=float) + pars.add_argument('--calculateSize', type=inkex.Boolean, default=True) + pars.add_argument('--showHolder', type=inkex.Boolean, default=True) + pars.add_argument('--showCrosses', type=inkex.Boolean, default=True) + pars.add_argument('--showInner', type=inkex.Boolean, default=True) + pars.add_argument('--showOuter', type=inkex.Boolean, default=False) + pars.add_argument('--showInnerBox', type=inkex.Boolean, default=False) + pars.add_argument('--showOuterBox', type=inkex.Boolean, default=False) + pars.add_argument('--tab') + + def save(self, stream): + show_list = [] + for i in ['showHolder', 'showCrosses', 'showInner', 'showOuter', + 'showInnerBox', 'showOuterBox', ]: + if getattr(self.options, i): + show_list.append(i.lower().replace('show', '')) + opt = self.options + ret = self.generate_nup( + unit=opt.unit, + pgSize=(opt.pgSizeX, opt.pgSizeY), + pgMargin=(opt.pgMarginTop, opt.pgMarginRight, opt.pgMarginBottom, opt.pgMarginLeft), + num=(opt.rows, opt.cols), + calculateSize=opt.calculateSize, + size=(opt.sizeX, opt.sizeY), + margin=(opt.marginTop, opt.marginRight, opt.marginBottom, opt.marginLeft), + padding=(opt.paddingTop, opt.paddingRight, opt.paddingBottom, opt.paddingLeft), + show=show_list, + ) + if ret: + stream.write(ret) + + def expandTuple(self, unit, x, length=4): + try: + iter(x) + except: + return None + + if len(x) != length: + x *= 2 + if len(x) != length: + raise Exception("expandTuple: requires 2 or 4 item tuple") + try: + return tuple(map(lambda ev: (self.svg.unittouu(str(eval(str(ev))) + unit) / self.svg.unittouu('1px')), x)) + except: + return None + + def generate_nup(self, + unit="px", + pgSize=("8.5*96", "11*96"), + pgMargin=(0, 0), + pgPadding=(0, 0), + num=(2, 2), + calculateSize=True, + size=None, + margin=(0, 0), + padding=(20, 20), + show=['default'], + ): + """Generate the SVG. Inputs are run through 'eval(str(x))' so you can use + '8.5*72' instead of 612. Margin / padding dimension tuples can be + (top & bottom, left & right) or (top, right, bottom, left). + + Keyword arguments: + pgSize -- page size, width x height + pgMargin -- extra space around each page + pgPadding -- added to pgMargin + n -- rows x cols + size -- override calculated size, width x height + margin -- white space around each piece + padding -- inner padding for each piece + show -- list of keywords indicating what to show + - 'crosses' - cutting guides + - 'inner' - inner boundary + - 'outer' - outer boundary + """ + + if 'default' in show: + show = set(show).union(['inner', 'innerbox', 'holder', 'crosses']) + + pgMargin = self.expandTuple(unit, pgMargin) + pgPadding = self.expandTuple(unit, pgPadding) + margin = self.expandTuple(unit, margin) + padding = self.expandTuple(unit, padding) + + pgSize = self.expandTuple(unit, pgSize, length=2) + # num = tuple(map(lambda ev: eval(str(ev)), num)) + + if not pgMargin or not pgPadding: + return inkex.errormsg("No padding or margin available.") + + page_edge = list(map(sum, zip(pgMargin, pgPadding))) + + top, right, bottom, left = 0, 1, 2, 3 + width, height = 0, 1 + rows, cols = 0, 1 + size = self.expandTuple(unit, size, length=2) + if size is None or calculateSize or len(size) < 2 or size[0] == 0 or size[1] == 0: + size = ((pgSize[width] + - page_edge[left] - page_edge[right] + - num[cols] * (margin[left] + margin[right])) / num[cols], + (pgSize[height] + - page_edge[top] - page_edge[bottom] + - num[rows] * (margin[top] + margin[bottom])) / num[rows] + ) + else: + size = self.expandTuple(unit, size, length=2) + + # sep is separation between same points on pieces + sep = (size[width] + margin[right] + margin[left], + size[height] + margin[top] + margin[bottom]) + + style = 'stroke:#000000;stroke-opacity:1;fill:none;fill-opacity:1;' + + padbox = Rectangle( + x=str(page_edge[left] + margin[left] + padding[left]), + y=str(page_edge[top] + margin[top] + padding[top]), + width=str(size[width] - padding[left] - padding[right]), + height=str(size[height] - padding[top] - padding[bottom]), + style=style, + ) + margbox = Rectangle( + x=str(page_edge[left] + margin[left]), + y=str(page_edge[top] + margin[top]), + width=str(size[width]), + height=str(size[height]), + style=style, + ) + + doc = self.get_template(width=pgSize[width], height=pgSize[height]) + svg = doc.getroot() + + def make_clones(under, to): + for row in range(0, num[rows]): + for col in range(0, num[cols]): + if row == 0 and col == 0: + continue + use = under.add(Use()) + use.set('xlink:href', '#' + to) + use.transform.add_translate(col * sep[width], row * sep[height]) + + # guidelayer ##################################################### + if {'inner', 'outer'}.intersection(show): + layer = svg.add(inkex.Layer.new('Guide Layer')) + if 'inner' in show: + ibox = layer.add(padbox.copy()) + ibox.style['stroke'] = '#8080ff' + ibox.set('id', 'innerguide') + make_clones(layer, 'innerguide') + if 'outer' in show: + obox = layer.add(margbox.copy()) + obox.style['stroke'] = '#8080ff' + obox.set('id', 'outerguide') + make_clones(layer, 'outerguide') + + # crosslayer ##################################################### + if {'crosses'}.intersection(show): + layer = svg.add(inkex.Layer.new('Cut Layer')) + + if 'crosses' in show: + crosslen = 12 + group = layer.add(inkex.Group(id='cross')) + x, y = 0, 0 + path = 'M%f %f' % (x + page_edge[left] + margin[left], + y + page_edge[top] + margin[top] - crosslen) + path += ' L%f %f' % (x + page_edge[left] + margin[left], + y + page_edge[top] + margin[top] + crosslen) + path += ' M%f %f' % (x + page_edge[left] + margin[left] - crosslen, + y + page_edge[top] + margin[top]) + path += ' L%f %f' % (x + page_edge[left] + margin[left] + crosslen, + y + page_edge[top] + margin[top]) + group.add(inkex.PathElement(style=style + 'stroke-width:0.05', + d=path, id='crossmarker')) + for row in 0, 1: + for col in 0, 1: + if row or col: + cln = group.add(Use()) + cln.set('xlink:href', '#crossmarker') + cln.transform.add_translate(col * size[width], row * size[height]) + make_clones(layer, 'cross') + + # clonelayer ##################################################### + layer = svg.add(inkex.Layer.new('Clone Layer')) + make_clones(layer, 'main') + + # mainlayer ###################################################### + layer = svg.add(inkex.Layer.new('Main Layer')) + group = layer.add(inkex.Group(id='main')) + + if 'innerbox' in show: + group.add(padbox) + if 'outerbox' in show: + group.add(margbox) + if 'holder' in show: + x, y = (page_edge[left] + margin[left] + padding[left], + page_edge[top] + margin[top] + padding[top]) + w, h = (size[width] - padding[left] - padding[right], + size[height] - padding[top] - padding[bottom]) + path = 'M{:f} {:f}'.format(x + w / 2., y) + path += ' L{:f} {:f}'.format(x + w, y + h / 2.) + path += ' L{:f} {:f}'.format(x + w / 2., y + h) + path += ' L{:f} {:f}'.format(x, y + h / 2.) + path += ' Z' + group.add(inkex.PathElement(style=style, d=path)) + + return svg.tostring() + + +if __name__ == '__main__': + Nup().run() diff --git a/share/extensions/lindenmayer.inx b/share/extensions/lindenmayer.inx new file mode 100644 index 0000000..32fe8bc --- /dev/null +++ b/share/extensions/lindenmayer.inx @@ -0,0 +1,51 @@ + + + L-system + org.ekips.filter.turtle.lindenmayer + + + ++F + F=FF-[-F+F+F]+[+F-F-F] + 3 + 25.0 + 0.0 + 16.0 + 16.0 + 0.0 + + + + + + + all + + + + + + diff --git a/share/extensions/lindenmayer.py b/share/extensions/lindenmayer.py new file mode 100755 index 0000000..7ff76e1 --- /dev/null +++ b/share/extensions/lindenmayer.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import random +import inkex +from inkex import turtle as pturtle + +class Lindenmayer(inkex.GenerateExtension): + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("--order", type=int, default=3, help="number of iteration") + pars.add_argument("--langle", type=float, default=16.0, help="angle for turning left") + pars.add_argument("--rangle", type=float, default=16.0, help="angle for turning right") + pars.add_argument("--step", type=float, default=25.0, help="step size") + pars.add_argument("--randomizestep", type=float, default=0.0, help="randomize step") + pars.add_argument("--randomizeangle", type=float, default=0.0, help="randomize angle") + pars.add_argument("--axiom", default="++F", help="initial state of system") + pars.add_argument("--rules", default="F=FF-[-F+F+F]+[+F-F-F]", help="replacement rules") + self.stack = [] + self.turtle = pturtle.pTurtle() + + def iterate(self): + self.rules = dict([map((lambda s: s.strip()), i.split("=")) for i in self.options.rules.upper().split(";") if i.count("=") == 1]) + string = self.__recurse(self.options.axiom.upper(), 0) + self.__compose_path(string) + return self.turtle.getPath() + + def __compose_path(self, string): + self.turtle.pu() + point = self.svg.namedview.center + self.turtle.setpos(point) + self.turtle.pd() + for c in string: + if c in 'ABCDEF': + self.turtle.pd() + self.turtle.fd(self.options.step * (random.normalvariate(1.0, 0.01 * self.options.randomizestep))) + elif c in 'GHIJKL': + self.turtle.pu() + self.turtle.fd(self.options.step * (random.normalvariate(1.0, 0.01 * self.options.randomizestep))) + elif c == '+': + self.turtle.lt(self.options.langle * (random.normalvariate(1.0, 0.01 * self.options.randomizeangle))) + elif c == '-': + self.turtle.rt(self.options.rangle * (random.normalvariate(1.0, 0.01 * self.options.randomizeangle))) + elif c == '|': + self.turtle.lt(180) + elif c == '[': + self.stack.append([self.turtle.getpos(), self.turtle.getheading()]) + elif c == ']': + self.turtle.pu() + pos, heading = self.stack.pop() + self.turtle.setpos(pos) + self.turtle.setheading(heading) + + def __recurse(self, rule, level): + level_string = '' + for c in rule: + if level < self.options.order: + try: + level_string = level_string + self.__recurse(self.rules[c], level + 1) + except KeyError: + level_string = level_string + c + else: + level_string = level_string + c + return level_string + + def generate(self): + self.options.step = self.svg.unittouu(str(self.options.step) + 'px') + sty = {'stroke-linejoin': 'miter', 'stroke-width': str(self.svg.unittouu('1px')), + 'stroke-opacity': '1.0', 'fill-opacity': '1.0', + 'stroke': '#000000', 'stroke-linecap': 'butt', + 'fill': 'none'} + return inkex.PathElement(style=str(inkex.Style(sty)), d=self.iterate()) + + +if __name__ == '__main__': + Lindenmayer().run() diff --git a/share/extensions/lorem_ipsum.inx b/share/extensions/lorem_ipsum.inx new file mode 100644 index 0000000..2419ca7 --- /dev/null +++ b/share/extensions/lorem_ipsum.inx @@ -0,0 +1,24 @@ + + + Lorem ipsum + com.kaioa.lorem_ipsum + + + 5 + 16 + 4 + + + + + + + all + + + + + + diff --git a/share/extensions/lorem_ipsum.py b/share/extensions/lorem_ipsum.py new file mode 100755 index 0000000..b64b7d4 --- /dev/null +++ b/share/extensions/lorem_ipsum.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2006 Jos Hirth, kaioa.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Example filltext sentences generated over at http://lipsum.com/ +""" + +import random + +import inkex +from inkex import Layer, FlowRoot, FlowRegion, FlowPara, Rectangle + +CORPA = [ + 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. ', + 'Duis sem velit, ultrices et, fermentum auctor, rhoncus ut, ligula. ', + 'Phasellus at purus sed purus cursus iaculis. ', + 'Suspendisse fermentum. ', + 'Pellentesque et arcu. ', + 'Maecenas viverra. ', + 'In consectetuer, lorem eu lobortis egestas, velit odio imperdiet' + ' eros, sit amet sagittis nunc mi ac neque. ', + 'Sed non ipsum. ', + 'Nullam venenatis gravida orci. ', + 'Curabitur nunc ante, ullamcorper vel, auctor a, aliquam at, tortor. ', + 'Etiam sodales orci nec ligula. ', + 'Sed at turpis vitae velit euismod aliquet. ', + 'Fusce venenatis ligula in pede. ', + 'Pellentesque viverra dolor non nunc. ', + 'Donec interdum vestibulum libero. ', + 'Morbi volutpat. ', + 'Phasellus hendrerit. ', + 'Quisque dictum quam vel neque. ', + 'Quisque aliquam, nulla ac scelerisque convallis, nisi ligula sagittis' + ' risus, at nonummy arcu urna pulvinar nibh. ', + 'Nam pharetra. ', + 'Nam rhoncus, lectus vel hendrerit congue, nisl lorem feugiat ante, in' + ' fermentum erat nulla tristique arcu. ', + 'Mauris et dolor. ', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere' + ' cubilia Curae; Donec gravida, ante vel ornare lacinia, orci enim porta' + ' est, eget sollicitudin lectus lectus eget lacus. ', + 'Praesent a lacus vitae turpis consequat semper. ', + 'In commodo, dolor quis fermentum ullamcorper, urna massa volutpat' + ' massa, vitae mattis purus arcu nec nulla. ', + 'In hac habitasse platea dictumst. ', + 'Praesent scelerisque. ', + 'Nullam sapien mauris, venenatis at, fermentum at, tempus eu, urna. ', + 'Vestibulum non arcu a ante feugiat vestibulum. ', + 'Nam laoreet dui sed magna. ', + 'Proin diam augue, semper vitae, varius et, viverra id, felis. ', + 'Pellentesque sit amet dui vel justo gravida auctor. ', + 'Aenean scelerisque metus eget sem. ', + 'Maecenas rhoncus rhoncus ipsum. ', + 'Donec nonummy lacinia leo. ', + 'Aenean turpis ipsum, rhoncus vitae, posuere vitae, euismod sed, ligula. ', + 'Pellentesque habitant morbi tristique senectus et netus et malesuada' + ' fames ac turpis egestas. ', + 'Mauris tempus diam. ', + 'Maecenas justo. ', + 'Sed a lorem ut est tincidunt consectetuer. ', + 'Ut eu metus id lectus vestibulum ultrices. ', + 'Suspendisse lectus. ', + 'Vivamus posuere, ante eu tempor dictum, felis nibh facilisis sem, eu' + ' auctor metus nulla non lorem. ', + 'Suspendisse potenti. ', + 'Integer fringilla. ', + 'Morbi urna. ', + 'Morbi pulvinar nulla sit amet nisl. ', + 'Mauris urna sem, suscipit vitae, dignissim id, ultrices sed, nunc. ', + 'Morbi a mauris. ', + 'Pellentesque suscipit accumsan massa. ', + 'Quisque arcu ante, cursus in, ornare quis, viverra ut, justo. ', + 'Quisque facilisis, urna sit amet pulvinar mollis, purus arcu adipiscing' + ' velit, non condimentum diam purus eu massa. ', + 'Suspendisse potenti. ', + 'Phasellus nisi metus, tempus sit amet, ultrices ac, porta nec, felis. ', + 'Aliquam metus. ', + 'Nam a nunc. ', + 'Vivamus feugiat. ', + 'Nunc metus. ', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere' + ' cubilia Curae; Vivamus eu orci. ', + 'Sed elementum, felis quis porttitor sollicitudin, augue nulla sodales' + ' sapien, sit amet posuere quam purus at lacus. ', + 'Curabitur tincidunt tellus nec purus. ', + 'Nam consectetuer mollis dolor. ', + 'Sed quis elit. ', + 'Aenean luctus vulputate turpis. ', + 'Proin lectus orci, venenatis pharetra, egestas id, tincidunt vel, eros. ', + 'Nulla facilisi. ', + 'Aliquam vel nibh. ', + 'Vivamus nisi elit, nonummy id, facilisis non, blandit ac, dolor. ', + 'Etiam cursus purus interdum libero. ', + 'Nam id neque. ', + 'Etiam pede nunc, vestibulum vel, rutrum et, tincidunt eu, enim. ', + 'Aenean id purus. ', + 'Aenean ultrices turpis. ', + 'Mauris et pede. ', + 'Suspendisse potenti. ', + 'Aliquam velit dui, commodo quis, porttitor eget, convallis et, nisi. ', + 'Maecenas convallis dui. ', + 'In leo ante, venenatis eu, volutpat ut, imperdiet auctor, enim. ', + 'Mauris ac massa vestibulum nisl facilisis viverra. ', + 'Phasellus magna sem, vulputate eget, ornare sed, dignissim sit amet, pede. ', + 'Aenean justo ipsum, luctus ut, volutpat laoreet, vehicula in, libero. ', + 'Praesent semper, neque vel condimentum hendrerit, lectus elit pretium' + 'ligula, nec consequat nisl velit at dui. ', + 'Proin dolor sapien, adipiscing id, sagittis eu, molestie viverra, mauris. ', + 'Aenean ligula. ', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere' + ' cubilia Curae; Suspendisse potenti. ', + 'Etiam pharetra lacus sed velit imperdiet bibendum. ', + 'Nunc in turpis ac lacus eleifend sagittis. ', + 'Nam massa turpis, nonummy et, consectetuer id, placerat ac, ante. ', + 'In tempus urna. ', + 'Quisque vehicula porttitor odio. ', + 'Aliquam sed erat. ', + 'Vestibulum viverra varius enim. ', + 'Donec ut purus. ', + 'Pellentesque convallis dolor vel libero. ', + 'Integer tempus malesuada pede. ', + 'Integer porta. ', + 'Donec diam eros, tristique sit amet, pretium vel, pellentesque ut, neque. ', + 'Nulla blandit justo a metus. ', + 'Curabitur accumsan felis in erat. ', + 'Curabitur lorem risus, sagittis vitae, accumsan a, iaculis id, metus. ', + 'Nulla sagittis condimentum ligula. ', + 'Aliquam imperdiet lobortis metus. ', + 'Suspendisse molestie sem. ', + 'Ut venenatis. ', + 'Pellentesque condimentum felis a sem. ', + 'Fusce nonummy commodo dui. ', + 'Nullam libero nunc, tristique eget, laoreet eu, sagittis id, ante. ', + 'Etiam fermentum. ', + 'Phasellus auctor enim eget sem. ', + 'Morbi turpis arcu, egestas congue, condimentum quis, tristique cursus, leo. ', + 'Sed fringilla. ', + 'Nam malesuada sapien eu nibh. ', + 'Pellentesque ac turpis. ', + 'Nulla sed lacus. ', + 'Mauris sed nulla quis nisi interdum tempor. ', + 'Quisque pretium rutrum ligula. ', + 'Mauris tempor ultrices justo. ', + 'In hac habitasse platea dictumst. ', + 'Donec sit amet enim. ', + 'Suspendisse venenatis. ', + 'Nam nisl quam, posuere non, volutpat sed, semper vitae, magna. ', + 'Donec ut urna. ', + 'Integer risus velit, facilisis eget, viverra et, venenatis id, leo. ', + 'Cras facilisis felis sit amet lorem. ', + 'Nam molestie nisl at metus. ', + 'Suspendisse viverra placerat tortor. ', + 'Phasellus lacinia iaculis mi. ', + 'Sed dolor. ', + 'Quisque malesuada nulla sed pede volutpat pulvinar. ', + 'Cras gravida. ', + 'Mauris tincidunt aliquam ante. ', + 'Fusce consectetuer tellus ut nisl. ', + 'Curabitur risus urna, placerat et, luctus pulvinar, auctor vel, orci. ', + 'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. ', + 'Praesent aliquet, neque pretium congue mattis, ipsum augue dignissim ante, ac' + ' pretium nisl lectus at magna. ', + 'Vivamus quis mi. ', + 'Nam sed nisl nec elit suscipit ullamcorper. ', + 'Donec tempus quam quis neque. ', + 'Donec rutrum venenatis dui. ', + 'Praesent a eros. ', + 'Aliquam justo lectus, iaculis a, auctor sed, congue in, nisl. ', + 'Etiam non neque ac mi vestibulum placerat. ', + 'Donec at diam a tellus dignissim vestibulum. ', + 'Integer accumsan. ', + 'Cras ac enim vel dui vestibulum suscipit. ', + 'Pellentesque tempor. ', + 'Praesent lacus. ' +] + + +class LoremIpsum(inkex.EffectExtension): + """Generate text with psuedo latin content""" + def add_arguments(self, pars): + pars.add_argument("--num", type=int, default=5, help="Number of paragraphs to generate") + pars.add_argument("-c", "--sentencecount", type=int, default=16, + help="Number of Sentences") + pars.add_argument("-f", "--fluctuation", type=int, default=4, help="+/-") + pars.add_argument("--tab", help="The selected UI-tab when OK was pressed") + + def make_paragraph(self, text_index=0): + """Make a paragraph""" + _min = max(1, self.options.sentencecount - self.options.fluctuation) + _max = max(2, self.options.sentencecount + self.options.fluctuation) + scount = int(random.random() * _max + _min) + for sentence in range(scount): + if text_index + sentence == 0: + yield CORPA[0] + else: + index = int(random.random() * (len(CORPA) - 1)) + yield CORPA[index] + + def add_text(self, node): + """Create many flowed text paragraph and append to node""" + for text_index in range(self.options.num): + para = node.add(FlowPara()) + para.text = ''.join(self.make_paragraph(text_index)) + node.append(FlowPara()) + + def effect(self): + # Existing text flow to insert new text into + for node in self.svg.selection.filter(FlowRoot): + self.add_text(node) + return + + # New text layer with lorum ipsum content + root = FlowRoot() + root.set('xml:space', 'preserve') + region = root.add(FlowRegion()) + + shape = self.svg.selection.first() + if shape is not None: + parent = shape.getparent() + region.add(shape.copy()) + else: + parent = self.svg.add(Layer.new('lorum ipsum')) + region.add(Rectangle(x='0', y='0',\ + width=str(int(self.svg.width)),\ + height=str(int(self.svg.height)))) + + parent.add(root) + self.add_text(root) + + +if __name__ == '__main__': + LoremIpsum().run() diff --git a/share/extensions/markers_strokepaint.inx b/share/extensions/markers_strokepaint.inx new file mode 100644 index 0000000..ec895a9 --- /dev/null +++ b/share/extensions/markers_strokepaint.inx @@ -0,0 +1,39 @@ + + + Color Markers + org.inkscape.filter.markers_stroke_paint + + + + + + + + false + true + + + + + true + -1 + + + true + 255 + + + + + false + + + all + + + + + + diff --git a/share/extensions/markers_strokepaint.py b/share/extensions/markers_strokepaint.py new file mode 100755 index 0000000..b6b5f1c --- /dev/null +++ b/share/extensions/markers_strokepaint.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# coding=utf-8 +# coding=utf-8 +# +# Copyright (C) 2006 Aaron Spike, aaron@ekips.org +# Copyright (C) 2010 Nicolas Dufour, nicoduf@yahoo.fr (color options) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import inkex +from inkex.localization import inkex_gettext as _ + +MARKERS = ['marker', 'marker-start', 'marker-mid', 'marker-end'] + +class MarkersStrokePaint(inkex.EffectExtension): + """Add marker stroke to outline markers on selected objects.""" + def add_arguments(self, pars): + pars.add_argument("--modify", type=inkex.Boolean, default=False, + help="Do not create a copy, modify the markers") + pars.add_argument("--type", dest="fill_type", default="stroke", + help="Replace the markers' fill with the object stroke or fill color") + pars.add_argument("--alpha", type=inkex.Boolean, dest="assign_alpha", default=True, + help="Assign the object fill and stroke alpha to the markers") + pars.add_argument("--invert", type=inkex.Boolean, default=False, + help="Invert fill and stroke colors") + pars.add_argument("--assign_fill", type=inkex.Boolean, default=True, + help="Assign a fill color to the markers") + pars.add_argument("--fill_color", type=inkex.Color, default=inkex.Color(1364325887), + help="Choose a custom fill color") + pars.add_argument("--assign_stroke", type=inkex.Boolean, default=True, + help="Assign a stroke color to the markers") + pars.add_argument("--stroke_color", type=inkex.Color, default=inkex.Color(1364325887), + help="Choose a custom fill color") + pars.add_argument("--tab", type=self.arg_method('method'), default=self.method_custom, + help="The selected UI-tab when OK was pressed") + pars.add_argument("--colortab", help="The selected custom color tab when OK was pressed") + + def method_custom(self, _): + """Choose custom colors""" + fill = self.options.fill_color if self.options.assign_fill else None + stroke = self.options.stroke_color if self.options.assign_stroke else None + return fill, stroke + + def method_object(self, style): + """Use object colors""" + fill = style.get_color('fill') + stroke = style.get_color('stroke') + + if self.options.fill_type == "solid": + fill = stroke + elif self.options.fill_type == "filled": + stroke = None + elif self.options.invert: + fill, stroke = stroke, fill + + if not self.options.assign_alpha: + # Remove alpha values + fill = fill.to_rgb() + stroke = stroke.to_rgb() + + return fill, stroke + + def effect(self): + for node in self.svg.selected.values(): + fill, stroke = self.options.tab(node.style) + + for attr in MARKERS: + if not node.style.get(attr, '').startswith('url(#'): + continue + + marker_id = node.style[attr][5:-1] + marker_node = self.svg.getElement('/svg:svg//svg:marker[@id="%s"]' % marker_id) + + if marker_node is None: + inkex.errormsg(_("unable to locate marker: %s") % marker_id) + continue + + if not self.options.modify: + marker_node = marker_node.copy() + self.svg.defs.append(marker_node) + marker_id = self.svg.get_unique_id(marker_id) + + node.style[attr] = "url(#%s)" % marker_id + marker_node.set('id', marker_id) + marker_node.set('inkscape:stockid', marker_id) + + for child in marker_node: + if stroke is not None: + child.style.set_color(stroke, 'stroke') + if fill is not None: + child.style.set_color(fill, 'fill') + +if __name__ == '__main__': + MarkersStrokePaint().run() diff --git a/share/extensions/measure.inx b/share/extensions/measure.inx new file mode 100644 index 0000000..498236f --- /dev/null +++ b/share/extensions/measure.inx @@ -0,0 +1,73 @@ + + + Measure Path + org.inkscape.visualise.measure_length + + + + + + + + + + + + + + + + + + + custom + 50 + + + + + + + + + + + + + 0 + + + + + + 12 + -6 + 2 + 1 + + + + + + + + + + + + + path + + + + + + diff --git a/share/extensions/measure.py b/share/extensions/measure.py new file mode 100755 index 0000000..2f18f9c --- /dev/null +++ b/share/extensions/measure.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2015 ~suv +# Copyright (C) 2010 Alvin Penner +# Copyright (C) 2006 Georg Wiora +# Copyright (C) 2006 Nathan Hurst +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This extension module can measure arbitrary path and object length +It adds text to the selected path containing the length in a given unit. +Area and Center of Mass calculated using Green's Theorem: +http://mathworld.wolfram.com/GreensTheorem.html +""" + +import inkex + +from inkex import TextElement, TextPath, Tspan +from inkex.bezier import csparea, cspcofm, csplength + +class MeasureLength(inkex.EffectExtension): + """Measure the length of selected paths""" + def add_arguments(self, pars): + pars.add_argument("--type", dest="mtype", default="length",\ + help="Type of measurement") + pars.add_argument("--method", type=self.arg_method(), default=self.method_textonpath,\ + help="Text Orientation method") + pars.add_argument("--presetFormat", default="TaP_start", help="Preset text layout") + pars.add_argument("--startOffset", default="custom", help="Text Offset along Path") + pars.add_argument("--startOffsetCustom", type=int, default=50,\ + help="Text Offset along Path") + pars.add_argument("--anchor", default="start", help="Text Anchor") + pars.add_argument("--position", default="start", help="Text Position") + pars.add_argument("--angle", type=float, default=0, help="Angle") + pars.add_argument("-f", "--fontsize", type=int, default=20,\ + help="Size of length label text in px") + pars.add_argument("-o", "--offset", type=float, default=-6,\ + help="The distance above the curve") + pars.add_argument("-u", "--unit", default="mm",\ + help="The unit of the measurement") + pars.add_argument("-p", "--precision", type=int, default=2,\ + help="Number of significant digits after decimal point") + pars.add_argument("-s", "--scale", type=float, default=1.1,\ + help="Scale Factor (Drawing:Real Length)") + + def effect(self): + # get number of digits + prec = int(self.options.precision) + scale = self.svg.unittouu('1px') # convert to document units + self.options.offset *= scale + factor = 1.0 + + if self.svg.get('viewBox'): + factor = self.svg.scale / self.svg.unittouu('1px') + self.options.fontsize /= factor + + factor *= scale / self.svg.unittouu('1' + self.options.unit) + + # loop over all selected paths + for node in self.svg.selection.filter(inkex.PathElement).values(): + csp = node.path.transform(node.composed_transform()).to_superpath() + if self.options.mtype == "length": + slengths, stotal = csplength(csp) + self.group = node.getparent().add(TextElement()) + elif self.options.mtype == "area": + stotal = abs(csparea(csp) * factor * self.options.scale) + self.group = node.getparent().add(TextElement()) + else: + try: + xc, yc = cspcofm(csp) + except ValueError as err: + raise inkex.AbortExtension(str(err)) + self.group = node.getparent().add(inkex.PathElement()) + self.group.set('id', 'MassCenter_' + node.get('id')) + self.add_cross(self.group, xc, yc, scale) + continue + # Format the length as string + val = round(stotal * factor * self.options.scale, prec) + self.options.method(node, str(val)) + + def method_textonpath(self, node, lenstr): + startOffset = self.options.startOffset + if startOffset == "custom": + startOffset = str(self.options.startOffsetCustom) + '%' + if self.options.mtype == "length": + self.add_textonpath(self.group, 0, 0, lenstr + ' ' + self.options.unit, node, self.options.anchor, startOffset, self.options.offset) + else: + self.add_textonpath(self.group, 0, 0, lenstr + ' ' + self.options.unit + '^2', node, self.options.anchor, startOffset, self.options.offset) + + def method_fixedtext(self, node, lenstr): + _id = node.get('id') + csp = node.path.transform(node.composed_transform()).to_superpath() + if self.options.position == "mass": + tx, ty = cspcofm(csp) + anchor = 'middle' + elif self.options.position == "center": + bbox = node.bounding_box() + tx, ty = bbox.center + anchor = 'middle' + else: # default + tx = csp[0][0][1][0] + ty = csp[0][0][1][1] + anchor = 'start' + if self.options.mtype == "length": + self.add_fixedtext(self.group, tx, ty, lenstr + ' ' + self.options.unit, anchor, -int(self.options.angle), self.options.offset + self.options.fontsize / 2) + else: + self.add_fixedtext(self.group, tx, ty, lenstr + ' ' + self.options.unit + '^2', anchor, -int(self.options.angle), -self.options.offset + self.options.fontsize / 2) + + def method_presets(self, node, lenstr): + """A preset option for alignments""" + preset_dict = { + 'default_cofm': [None, None, None, None, None], + 'default_length': [self.method_textonpath, "50%", "start", None, None], + 'TaP_start': [self.method_textonpath, "0%", "start", None, None], + 'TaP_middle': [self.method_textonpath, "50%", "middle", None, None], + 'TaP_end': [self.method_textonpath, "100%", "end", None, None], + 'default_area': [self.method_fixedtext, None, None, "start", 0.0], + 'FT_start': [self.method_fixedtext, None, None, "start", 0.0], + 'FT_bbox': [self.method_fixedtext, None, None, "center", 0.0], + 'FT_mass': [self.method_fixedtext, None, None, "mass", 0.0], + } + + if self.options.presetFormat == "default": + current_preset = 'default_' + self.options.mtype + else: + current_preset = self.options.presetFormat + + self.options.startOffset = preset_dict[current_preset][1] + self.options.anchor = preset_dict[current_preset][2] + self.options.position = preset_dict[current_preset][3] + self.options.angle = preset_dict[current_preset][4] + method = preset_dict[current_preset][0] + if method is not None: + return method(node, lenstr) + + def add_cross(self, node, x, y, scale): + l = 3 * scale # 3 pixels in document units + node.set('d', 'm %s,%s %s,0 %s,0 m %s,%s 0,%s 0,%s' % (str(x - l), str(y), str(l), str(l), str(-l), str(-l), str(l), str(l))) + node.set('style', 'stroke:#000000;fill:none;stroke-width:%s' % str(0.5 * scale)) + + def add_textonpath(self, node, x, y, text, _node, anchor, startOffset, dy=0): + new = node.add(TextPath()) + s = {'text-align': 'center', 'vertical-align': 'bottom', + 'text-anchor': anchor, 'font-size': str(self.options.fontsize), + 'fill-opacity': '1.0', 'stroke': 'none', + 'font-weight': 'normal', 'font-style': 'normal', 'fill': '#000000'} + new.style = s + new.href = _node + new.set('startOffset', startOffset) + new.set('dy', str(dy)) # dubious merit + # new.append(tp) + if text[-2:] == "^2": + new.append(Tspan.superscript("2")) + new.text = str(text)[:-2] + else: + new.text = str(text) + # node.set('transform','rotate(180,'+str(-x)+','+str(-y)+')') + node.set('x', str(x)) + node.set('y', str(y)) + + def add_fixedtext(self, node, x, y, text, anchor, angle, dy=0): + new = node.add(Tspan()) + new.set('sodipodi:role', 'line') + s = {'text-align': 'center', 'vertical-align': 'bottom', + 'text-anchor': anchor, 'font-size': str(self.options.fontsize), + 'fill-opacity': '1.0', 'stroke': 'none', + 'font-weight': 'normal', 'font-style': 'normal', 'fill': '#000000'} + new.style = s + new.set('dy', str(dy)) + if text[-2:] == "^2": + new.append(Tspan.superscript("2")) + new.text = str(text)[:-2] + else: + new.text = str(text) + node.set('x', str(x)) + node.set('y', str(y)) + node.set('transform', 'rotate(%s, %s, %s)' % (angle, x, y)) + +if __name__ == '__main__': + MeasureLength().run() diff --git a/share/extensions/media_zip.inx b/share/extensions/media_zip.inx new file mode 100644 index 0000000..ac8be03 --- /dev/null +++ b/share/extensions/media_zip.inx @@ -0,0 +1,19 @@ + + + Compressed Inkscape SVG with media export + org.inkscape.output.compressed_media + org.inkscape.output.svg.inkscape + images + false + + .zip + application/x-zip + Compressed Inkscape SVG with media (*.zip) + Inkscape's native file format compressed with Zip and including all media files + false + + + diff --git a/share/extensions/media_zip.py b/share/extensions/media_zip.py new file mode 100755 index 0000000..9881bff --- /dev/null +++ b/share/extensions/media_zip.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005 Pim Snel, pim@lingewoud.com +# Copyright (C) 2008 Aaron Spike, aaron@ekips.org +# Copyright (C) 2011 Nicolas Dufour, nicoduf@yahoo.fr +# +# * Fix for a bug related to special characters in the path (LP #456248). +# * Fix for Windows support (LP #391307 ). +# * Font list and image directory features. +# +# this is the first Python script ever created +# its based on embedimage.py +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# TODOs +# - fix bug: not saving existing .zip after a Collect for Output is run +# this bug occurs because after running an effect extension the inkscape:output_extension is reset to svg.inkscape +# the file name is still xxx.zip. after saving again the file xxx.zip is written with a plain .svg which +# looks like a corrupt zip +# - maybe add better extension +# - consider switching to lzma in order to allow cross platform compression with no encoding problem... +# +""" +An extension which collects all images to the documents directory and +creates a zip archive containing all images and the document +""" + +import os +import shutil +import tempfile +import zipfile + +import inkex +from inkex import TextElement, Tspan, FlowRoot, FlowPara, FlowSpan + +try: # PY2 + from urllib import url2pathname + from urlparse import urlparse +except ImportError: # PY3 + from urllib.parse import urlparse + from urllib.request import url2pathname + +ENCODING = "cp437" if os.name == 'nt' else "latin-1" + +class CompressedMedia(inkex.OutputExtension): + """Output a compressed file""" + def add_arguments(self, pars): + pars.add_argument("--image_dir", help="Image directory") + pars.add_argument("--font_list", type=inkex.Boolean, help="Add font list") + + def collect_images(self, docname, z): + """ + Collects all images in the document + and copy them to the temporary directory. + """ + imgdir = self.options.image_dir + + for node in self.svg.xpath('//svg:image'): + xlink = node.get('xlink:href') + if xlink[:4] != 'data': + absref = node.get('sodipodi:absref') + url = urlparse(xlink) + href = url2pathname(url.path) + + if href is not None and os.path.isfile(href): + absref = os.path.realpath(href) + + image_path = os.path.join(imgdir, os.path.basename(absref)) + + if os.path.isfile(absref): + shutil.copy(absref, self.tmp_dir) + z.write(absref, image_path.encode(ENCODING)) + elif os.path.isfile(os.path.join(self.tmp_dir, absref)): + # TODO: please explain why this clause is necessary + shutil.copy(os.path.join(self.tmp_dir, absref), self.tmp_dir) + z.write(os.path.join(self.tmp_dir, absref), image_path.encode(ENCODING)) + else: + inkex.errormsg('Could not locate file: %s' % absref) + + node.set('xlink:href', image_path) + + def collect_svg(self, docstripped, z): + """ + Copy SVG document to the temporary directory + and add it to the temporary compressed file + """ + dst_file = os.path.join(self.tmp_dir, docstripped) + with open(dst_file, 'wb') as stream: + self.document.write(stream) + z.write(dst_file, docstripped + '.svg') + + def is_text(self, node): + """ + Returns true if the tag in question is an element that + can hold text. + """ + return isinstance(node, (TextElement, Tspan, FlowRoot, FlowPara, FlowSpan)) + + def get_fonts(self, node): + """ + Given a node, returns a list containing all the fonts that + the node is using. + """ + fonts = [] + s = '' + if 'style' in node.attrib: + s = dict(inkex.Style.parse_str(node.attrib['style'])) + if not s: + return fonts + + if 'font-family' in s: + if 'font-weight' in s: + fonts.append(s['font-family'] + ' ' + s['font-weight']) + else: + fonts.append(s['font-family']) + elif '-inkscape-font-specification' in s: + fonts.append(s['-inkscape-font-specification']) + return fonts + + def list_fonts(self, z): + """ + Walks through nodes, building a list of all fonts found, then + reports to the user with that list. + Based on Craig Marshall's replace_font.py + """ + nodes = [] + items = self.document.getroot().getiterator() + nodes.extend(filter(self.is_text, items)) + fonts_found = [] + for node in nodes: + for f in self.get_fonts(node): + if not f in fonts_found: + fonts_found.append(f) + findings = sorted(fonts_found) + # Write list to the temporary compressed file + filename = 'fontlist.txt' + dst_file = os.path.join(self.tmp_dir, filename) + with open(dst_file, 'w') as stream: + if len(findings) == 0: + stream.write("Didn't find any fonts in this document/selection.") + else: + if len(findings) == 1: + stream.write("Found the following font only: %s" % findings[0]) + else: + stream.write("Found the following fonts:\n%s" % '\n'.join(findings)) + z.write(dst_file, filename) + + def save(self, stream): + docname = self.svg.get('sodipodi:docname') + + if docname is None: + docname = self.options.input_file + + # TODO: replace whatever extension + docstripped = os.path.basename(docname.replace('.zip', '')) + docstripped = docstripped.replace('.svg', '') + docstripped = docstripped.replace('.svgz', '') + + # Create os temp dir + self.tmp_dir = tempfile.mkdtemp() + + # Create destination zip in same directory as the document + with zipfile.ZipFile(stream, 'w') as z: + self.collect_images(docname, z) + self.collect_svg(docstripped, z) + if self.options.font_list: + self.list_fonts(z) + +if __name__ == '__main__': + CompressedMedia().run() diff --git a/share/extensions/merge_styles.inx b/share/extensions/merge_styles.inx new file mode 100644 index 0000000..a8b76c2 --- /dev/null +++ b/share/extensions/merge_styles.inx @@ -0,0 +1,19 @@ + + + Merge Styles into CSS + org.inkscape.stylesheet.merge_styles + + + + class1 + + + all + + + + + + diff --git a/share/extensions/merge_styles.py b/share/extensions/merge_styles.py new file mode 100755 index 0000000..40c48fc --- /dev/null +++ b/share/extensions/merge_styles.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2014-2019 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Merges styles into class based styles and removes. +""" + +import inkex + +class MergeStyles(inkex.EffectExtension): + """Merge any styles which are the same for CSS""" + def add_arguments(self, pars): + self.arg_parser.add_argument("-n", "--name", type=str, dest="name",\ + help="Name of selected element's common class") + + def effect(self): + """Apply the style effect""" + newclass = self.options.name + if not newclass: + newclass = self.svg.get_unique_id('css') + + elements = self.svg.selected.values() + common = None + + for elem in elements: + style = set(elem.style.items()) + if common is not None: + common &= style + else: + common = style + + if not common: + return inkex.errormsg("There are no common styles between these elements.") + + self.svg.stylesheet.add('.' + newclass, inkex.Style(sorted(common))) + + for elem in elements: + elem.style -= dict(common).keys() + elem.classes.append(newclass) + return True + +if __name__ == '__main__': + MergeStyles().run() diff --git a/share/extensions/motion.inx b/share/extensions/motion.inx new file mode 100644 index 0000000..9ca1db2 --- /dev/null +++ b/share/extensions/motion.inx @@ -0,0 +1,16 @@ + + + Motion + org.ekips.filter.motion + 100 + 45 + + path + + + + + + diff --git a/share/extensions/motion.py b/share/extensions/motion.py new file mode 100755 index 0000000..72d9a45 --- /dev/null +++ b/share/extensions/motion.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import math + +import inkex +from inkex.paths import Move, Line, Curve, ZoneClose, Arc, Path, Vert, Horz, TepidQuadratic, Quadratic, Smooth +from inkex.transforms import Vector2d +from inkex.bezier import beziertatslope, beziersplitatt + + +class Motion(inkex.EffectExtension): + """Generate a motion path""" + + def add_arguments(self, pars): + pars.add_argument("-a", "--angle", type=float, default=45.0, \ + help="direction of the motion vector") + pars.add_argument("-m", "--magnitude", type=float, default=100.0, \ + help="magnitude of the motion vector") + + @staticmethod + def makeface(last, segment, facegroup, delx, dely): + """translate path segment along vector""" + elem = facegroup.add(inkex.PathElement()) + + npt = segment.translate([delx, dely]) + + # reverse direction of path segment + if isinstance(segment, Curve): + rev = Curve(npt.x3, npt.y3, npt.x2, npt.y2, + last[0] + delx, last[1] + dely + ) + elif isinstance(segment, Line): + rev = Line(last[0] + delx, last[1] + dely) + else: + raise RuntimeError("Unexpected segment type {}".format(type(segment))) + + elem.path = inkex.Path([ + Move(last[0], last[1]), + segment, + npt.to_line(Vector2d()), + rev, + ZoneClose(), + ]) + + def effect(self): + delx = math.cos(math.radians(self.options.angle)) * self.options.magnitude + dely = math.sin(math.radians(self.options.angle)) * self.options.magnitude + for node in self.svg.selection.filter(inkex.PathElement).values(): + group = node.getparent().add(inkex.Group()) + facegroup = group.add(inkex.Group()) + group.append(node) + + if node.transform: + group.transform = node.transform + node.transform = None + + facegroup.style = node.style + + for cmd_proxy in node.path.to_absolute().proxy_iterator(): + self.process_segment(cmd_proxy, facegroup, delx, dely) + + @staticmethod + def process_segment(cmd_proxy, facegroup, delx, dely): + """Process each segments""" + + segments = [] + if isinstance(cmd_proxy.command, (Curve, Smooth, TepidQuadratic, Quadratic, Arc)): + prev = cmd_proxy.previous_end_point + for curve in cmd_proxy.to_curves(): + bez = [prev] + curve.to_bez() + prev = curve.end_point(cmd_proxy.first_point, prev) + tees = [t for t in beziertatslope(bez, (dely, delx)) if 0 < t < 1] + tees.sort() + if len(tees) == 1: + one, two = beziersplitatt(bez, tees[0]) + segments.append(Curve(*(one[1] + one[2] + one[3]))) + segments.append(Curve(*(two[1] + two[2] + two[3]))) + elif len(tees) == 2: + one, two = beziersplitatt(bez, tees[0]) + two, three = beziersplitatt(two, tees[1]) + segments.append(Curve(*(one[1] + one[2] + one[3]))) + segments.append(Curve(*(two[1] + two[2] + two[3]))) + segments.append(Curve(*(three[1] + three[2] + three[3]))) + else: + segments.append(curve) + elif isinstance(cmd_proxy.command, (Line, Curve)): + segments.append(cmd_proxy.command) + elif isinstance(cmd_proxy.command, ZoneClose): + segments.append(Line(cmd_proxy.first_point.x, cmd_proxy.first_point.y)) + elif isinstance(cmd_proxy.command, (Vert, Horz)): + segments.append(cmd_proxy.command.to_line(cmd_proxy.end_point)) + + for seg in Path([Move(*cmd_proxy.previous_end_point)] + segments).proxy_iterator(): + if isinstance(seg.command, Move): continue + Motion.makeface(seg.previous_end_point, seg.command, facegroup, delx, dely) + + +if __name__ == '__main__': + Motion().run() diff --git a/share/extensions/new_glyph_layer.inx b/share/extensions/new_glyph_layer.inx new file mode 100644 index 0000000..cec7639 --- /dev/null +++ b/share/extensions/new_glyph_layer.inx @@ -0,0 +1,15 @@ + + + 2 - Add Glyph Layer + org.inkscape.typography.new_glyph_layer + + + all + + + + + + diff --git a/share/extensions/new_glyph_layer.py b/share/extensions/new_glyph_layer.py new file mode 100755 index 0000000..cff9b4d --- /dev/null +++ b/share/extensions/new_glyph_layer.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2011 Felipe Correa da Silva Sanches +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import locale +import sys + +import inkex + +class NewGlyphLayer(inkex.EffectExtension): + def add_arguments(self, pars): + self.arg_parser.add_argument("--text", default='', help="Unicode chars") + + self.encoding = sys.stdin.encoding + if self.encoding == 'cp0' or self.encoding is None: + self.encoding = locale.getpreferredencoding() + + def effect(self): + # Get all the options + unicode_chars = self.options.text + if isinstance(unicode_chars, bytes): + unicode_chars = unicode_chars.decode(self.encoding) + + # TODO: remove duplicate chars + + for char in unicode_chars: + # Create a new layer. + layer = self.svg.add(inkex.Layer.new(u'GlyphLayer-' + char)) + layer.set('style', 'display:none') # initially not visible + + # TODO: make it optional ("Use current selection as template glyph") + # Move selection to the newly created layer + for node in self.svg.selected.values(): + layer.append(node) + +if __name__ == '__main__': + NewGlyphLayer().run() diff --git a/share/extensions/next_glyph_layer.inx b/share/extensions/next_glyph_layer.inx new file mode 100644 index 0000000..81353d1 --- /dev/null +++ b/share/extensions/next_glyph_layer.inx @@ -0,0 +1,14 @@ + + + View Next Glyph + org.inkscape.typography.next_layer + + all + + + + + + diff --git a/share/extensions/next_glyph_layer.py b/share/extensions/next_glyph_layer.py new file mode 100755 index 0000000..ded32b1 --- /dev/null +++ b/share/extensions/next_glyph_layer.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2011 Felipe Correa da Silva Sanches +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import inkex + +class NextLayer(inkex.EffectExtension): + """Show the next glyph layer""" + def effect(self): + count = 0 + glyphs = [] + for group in self.svg.findall('svg:g'): + if "GlyphLayer-" in group.label: + glyphs.append(group) + if group.style.get('display', '') == "inline": + count += 1 + current = len(glyphs) - 1 + + if count != 1 or len(glyphs) < 2: + return + + self.process_glyphs(glyphs, current) + + @staticmethod + def process_glyphs(glyphs, current): + """Process the glyphs""" + glyphs[current].set("style", "display:none") + glyphs[(current+1)%len(glyphs)].set("style", "display:inline") + +if __name__ == '__main__': + NextLayer().run() diff --git a/share/extensions/nicechart.inx b/share/extensions/nicechart.inx new file mode 100644 index 0000000..e37cd42 --- /dev/null +++ b/share/extensions/nicechart.inx @@ -0,0 +1,102 @@ + + + + + + NiceCharts + org.inkscape.filter.nice_chart + + + + + + + ; + 0 + 1 + utf-8 + false + + + + + apples:3,bananas:5,oranges:10,pears:4 + + + + + sans-serif + 10 + #000000 + + + false + 100 + 10 + 100 + 5 + 1 + 5 + 50 + false + + + + + + + + + + + + false + false + + + false + + + + + + + + + + + all + + + + + + diff --git a/share/extensions/nicechart.py b/share/extensions/nicechart.py new file mode 100755 index 0000000..d60bdbf --- /dev/null +++ b/share/extensions/nicechart.py @@ -0,0 +1,523 @@ +#!/usr/bin/env python +# coding=utf-8 +# nicechart.py +# +# Copyright 2011-2016 +# +# Christoph Sterz +# Florian Weber +# Maren Hachmann +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# pylint: disable=attribute-defined-outside-init + +# TODO / Ideas: +# allow negative values for bar charts +# show values for stacked bar charts +# don't create a new layer for each chart, but a normal group +# correct bar height for stacked bars (it's only half as high as it should be, double) +# adjust position of heading +# use aliasing workaround for stacked bars (e.g. let the rectangles overlap) + +# The extension creates one chart for a single value column in one go, +# e.g. chart all temperatures for all months of the year 1978 into one chart. +# (for this, select column 0 for labels and column 1 for values). +# "1978" etc. can be used as heading (Need not be numeric. If not used delete the heading line.) +# Month names can be used as labels +# Values can be shown, in addition to labels (doesn't work with stacked bar charts) +# Values can contain commas as decimal separator, as long as delimiter isn't comma +# Negative values are not yet supported. + +# See tests/data/nicechart_01.csv for example data + +import re +import csv +import math + +from argparse import ArgumentTypeError + +import inkex +from inkex.utils import filename_arg +from inkex import Filter, TextElement, Circle, Rectangle +from inkex.paths import Move, line + +# www.sapdesignguild.org/goodies/diagram_guidelines/color_palettes.html#mss +COLOUR_TABLE = { + "red": ["#460101", "#980101", "#d40000", "#f44800", "#fb8b00", "#eec73e", "#d9bb7a", "#fdd99b"], + "blue": ["#000442", "#0F1781", "#252FB7", "#3A45E1", "#656DDE", "#8A91EC"], + "gray": ["#222222", "#444444", "#666666", "#888888", "#aaaaaa", "#cccccc", "#eeeeee"], + "contrast": ["#0000FF", "#FF0000", "#00FF00", "#CF9100", "#FF00FF", "#00FFFF"], + "sap": ["#f8d753", "#5c9746", "#3e75a7", "#7a653e", "#e1662a", "#74796f", "#c4384f", + "#fff8a3", "#a9cc8f", "#b2c8d9", "#bea37a", "#f3aa79", "#b5b5a9", "#e6a5a5"] +} + +class NiceChart(inkex.GenerateExtension): + """ + Inkscape extension that can draw pie charts and bar charts + (stacked, single, horizontally or vertically) + with optional drop shadow, from a csv file or from pasted text + """ + container_layer = True + + @property + def container_label(self): + """Layer title/label""" + return 'Chart-Layer: {}'.format(self.options.what) + + def add_arguments(self, pars): + pars.add_argument('--tab') + pars.add_argument('--encoding', default='utf-8') + pars.add_argument('-w', '--what', default='22,11,67', help='Chart Values') + pars.add_argument("-t", "--type", type=self.arg_method('render'), + default=self.render_bar, help="Chart Type") + pars.add_argument("-b", "--blur", type=inkex.Boolean, default=True, help="Blur Type") + pars.add_argument("-f", "--filename", type=filename_arg, help="Name of File") + pars.add_argument("-i", "--input_type", default='file', help="Chart Type") + pars.add_argument("-d", "--delimiter", default=';', help="delimiter") + pars.add_argument("-c", "--colors", default='default', help="color-scheme") + pars.add_argument("--colors_override", help="color-scheme-override") + pars.add_argument("--reverse_colors", type=inkex.Boolean, default=False, + help="reverse color-scheme") + pars.add_argument("-k", "--col_key", type=int, default=0, + help="column that contains the keys") + pars.add_argument("-v", "--col_val", type=int, default=1, + help="column that contains the values") + pars.add_argument("--headings", type=inkex.Boolean, default=True, + help="first line of the CSV file consists of headings for the columns") + pars.add_argument("-r", "--rotate", type=inkex.Boolean, default=False, + help="Draw barchart horizontally") + pars.add_argument("-W", "--bar-width", type=int, default=10, help="width of bars") + pars.add_argument("-p", "--pie-radius", type=int, default=100, help="radius of pie-charts") + pars.add_argument("-H", "--bar-height", type=int, default=100, help="height of bars") + pars.add_argument("-O", "--bar-offset", type=int, default=5, help="distance between bars") + pars.add_argument("--stroke-width", type=float, default=1.0) + pars.add_argument("-o", "--text-offset", type=int, default=5, + help="distance between bar and descriptions") + pars.add_argument("--heading-offset", type=int, default=50, + help="distance between chart and chart title") + pars.add_argument("--segment-overlap", type=inkex.Boolean, default=False, + help="Remove aliasing effects by letting pie chart segments overlap") + pars.add_argument("-F", "--font", default='sans-serif', help="font of description") + pars.add_argument("-S", "--font-size", type=int, default=10, + help="font size of description") + pars.add_argument("-C", "--font-color", default='black', help="font color of description") + + pars.add_argument("-V", "--show_values", type=inkex.Boolean, default=False, + help="Show values in chart") + + def get_data(self): + """Process the data""" + col_key = self.options.col_key + col_val = self.options.col_val + + def process_value(val): + """Confirm the values from files or direct""" + val = float(val) + if val < 0: + raise inkex.AbortExtension("Negative values are currently not supported!") + return val + + if self.options.input_type == "file": + if self.options.filename is None: + raise inkex.AbortExtension("Filename not specified!") + + # Future: use encoding when opening the file here (if ever needed) + with open(self.options.filename, "r") as fhl: + reader = csv.reader(fhl, delimiter=self.options.delimiter) + title = col_val + + if self.options.headings: + header = next(reader) + title = header[col_val] + + values = [(line[col_key], process_value(line[col_val])) for line in reader] + return (title,) + tuple(zip(*values)) + + elif self.options.input_type == "direct_input": + (keys, values) = zip(*[l.split(':', 1) for l in self.options.what.split(',')]) + return ('Direct Input', keys, [process_value(val) for val in values]) + + raise inkex.AbortExtension("Unknown input type") + + def get_blur(self): + """Add blur to the svg and return if needed""" + if self.options.blur: + defs = self.svg.defs + # Create new Filter + filt = defs.add(Filter(height='3', width='3', x='-0.5', y='-0.5')) + # Append Gaussian Blur to that Filter + filt.add_primitive('feGaussianBlur', stdDeviation='1.1') + return 'filter:url(#%s);' % filt.get_id() + return '' + + def get_color(self): + """Get the next available color""" + if not hasattr(self, '_colors'): + # Generate list of available colours + if self.options.colors_override: + colors = self.options.colors_override.strip() + else: + colors = self.options.colors + + if colors[0].isalpha(): + colors = COLOUR_TABLE.get(colors.lower(), COLOUR_TABLE['red']) + + else: + colors = re.findall("(#[0-9a-fA-F]{6})", colors) + # to be sure we create a fallback: + if not colors: + colors = COLOUR_TABLE['red'] + + if self.options.reverse_colors: + colors.reverse() + # Cache the list of colours for later use + self._colors = colors + self._color_index = 0 + + color = self._colors[self._color_index] + # Increase index to the next available color + self._color_index = (self._color_index + 1) % len(self._colors) + return color + + def generate(self): + """Generates a nice looking chart into SVG document.""" + + # Process the data from a file or text box + (self.title, keys, values) = self.get_data() + if not values: + raise inkex.AbortExtension("No data to render into a chart.") + + # Get the page attributes: + self.width = self.svg.unittouu(self.svg.get('width')) + self.height = self.svg.unittouu(self.svg.attrib['height']) + self.fontoff = float(self.options.font_size) / 3 + + # Check if a drop shadow should be drawn: + self.blur = self.get_blur() + + # Draw the right type of chart + for elem in self.options.type(keys, values): + yield elem + + def draw_header(self, heading_x): + """Draw an optional header text""" + if self.options.headings and self.title: + headingtext = self.draw_text(self.title, 4, anchor='end') + headingtext.set("y", str(self.height / 2 + self.options.heading_offset)) + headingtext.set("x", str(heading_x)) + return headingtext + return None + + def render_bar(self, keys, values): + """Draw bar chart""" + bar_height = self.options.bar_height + bar_width = self.options.bar_width + bar_offset = self.options.bar_offset + + # Normalize the bars to the largest value + value_max = max(list(values) + [0.0]) + + # Draw Single bars with their shadows + for cnt, value in enumerate(values): + # Draw each bar a set amount offset + offset = cnt * (bar_width + bar_offset) + bar_value = (value / value_max) * bar_height + + # Calculate the location of the bar + x = self.width / 2 + offset + y = self.height / 2 - int(bar_value) + width = bar_width + height = int(bar_value) + + if self.options.rotate: + # Rotate the bar and align to the left + x, y, width, height = y, x, height, width + x += width + + for elem in self.draw_rectangle(x, y, width, height): + yield elem + + # If keys are given, create text elements + if keys: + text = self.draw_text(keys[cnt], anchor='end') + if not self.options.rotate: # =vertical + text.set("transform", "rotate(-90)") + # y after rotation: + text.set("x", "-" + str(self.height / 2 + self.options.text_offset)) + # x after rotation: + text.set("y", str(self.width / 2 + offset + bar_width / 2 + self.fontoff)) + else: # =horizontal + text.set("y", str(self.width / 2 + offset + bar_width / 2 + self.fontoff)) + text.set("x", str(self.height / 2 - self.options.text_offset)) + + yield text + + if self.options.show_values: + vtext = self.draw_text(int(value)) + if not self.options.rotate: # =vertical + vtext.set("transform", "rotate(-90)") + # y after rotation: + vtext.set("x", "-" + str(self.height / 2 + value - self.options.text_offset)) + # x after rotation: + vtext.set("y", str(self.width / 2 + offset + bar_width / 2 + self.fontoff)) + else: # =horizontal + vtext.set("y", str(self.width / 2 + offset + bar_width / 2 + self.fontoff)) + vtext.set("x", str(self.height / 2 + value + self.options.text_offset)) + yield vtext + + yield self.draw_header(self.width / 2) + + def draw_rectangle(self, x, y, width, height): + """Draw a rectangle bar with optional shadow""" + if self.blur: + shadow = Rectangle(x=str(x+1), y=str(y+1), width=str(width), height=str(height)) + shadow.set("style", self.blur) + yield shadow + + rect = Rectangle(x=str(x), y=str(y), width=str(width), height=str(height)) + rect.set("style", "fill:" + self.get_color()) + yield rect + + def draw_text(self, text, add_size=0, anchor='start', **kwargs): + """Draw a textual label""" + vtext = TextElement(**kwargs) + vtext.style = { + 'fill': self.options.font_color, + 'font-family': self.options.font, + 'font-size': str(self.options.font_size + add_size) + 'px', + 'font-style': 'normal', + 'font-variant': 'normal', + 'font-weight': 'normal', + 'font-stretch': 'normal', + '-inkscape-font-specification': 'Bitstream Charter', + 'text-align': anchor, + 'text-anchor': anchor, + } + vtext.text = str(text) + return vtext + + def render_pie_abs(self, keys, values): + """Draw a pie chart, with absolute values""" + # pie_abs = True + for elem in self.render_pie(keys, values, True): + yield elem + + def render_pie(self, keys, values, pie_abs=False): + """Draw pie chart""" + pie_radius = self.options.pie_radius + + # Iterate all values to draw the different slices + color = 0 + x = float(self.width) / 2 + y = float(self.height) / 2 + + # Create the shadow first (if it should be created): + if self.blur: + shadow = Circle(cx=str(x), cy=str(y)) + shadow.set('r', str(pie_radius)) + shadow.set("style", self.blur + "fill:#000000") + yield shadow + + # Add a grey background circle with a light stroke + background = Circle(cx=str(x), cy=str(y)) + background.set("r", str(pie_radius)) + background.set("style", "stroke:#ececec;fill:#f9f9f9") + yield background + + # create value sum in order to divide the slices + try: + valuesum = sum(values) + except ValueError: + valuesum = 0 + + if pie_abs: + valuesum = 100 + + # Set an offsetangle + offset = 0 + + # Draw single slices + for cnt, value in enumerate(values): + # Calculate the PI-angles for start and end + angle = (2 * 3.141592) / valuesum * float(value) + start = offset + end = offset + angle + + # proper overlapping + if self.options.segment_overlap: + if cnt != len(values) - 1: + end += 0.09 # add a 5° overlap + if cnt == 0: + start -= 0.09 # let the first element overlap into the other direction + + # then add the slice + pieslice = inkex.PathElement() + pieslice.set('sodipodi:type', 'arc') + pieslice.set('sodipodi:cx', x) + pieslice.set('sodipodi:cy', y) + pieslice.set('sodipodi:rx', pie_radius) + pieslice.set('sodipodi:ry', pie_radius) + pieslice.set('sodipodi:start', start) + pieslice.set('sodipodi:end', end) + pieslice.set("style", "fill:" + self.get_color() + ";stroke:none;fill-opacity:1") + ang = angle / 2 + offset + + # If text is given, draw short paths and add the text + if keys: + elem = inkex.PathElement() + elem.path = [ + Move( + (self.width / 2) + pie_radius * math.cos(ang), + (self.height / 2) + pie_radius * math.sin(ang), + ), line( + (self.options.text_offset - 2) * math.cos(ang), + (self.options.text_offset - 2) * math.sin(ang), + ), + ] + + elem.style = { + 'fill': 'none', + 'stroke': self.options.font_color, + 'stroke-width': self.options.stroke_width, + 'stroke-linecap': 'butt', + } + yield elem + + label = keys[cnt] + if self.options.show_values: + label += ' ({}{})'.format(str(value), ('', '%')[pie_abs]) + + # check if it is right or left of the Pie + anchor = 'start' if math.cos(ang) > 0 else 'end' + text = self.draw_text(label, anchor=anchor) + + off = pie_radius + self.options.text_offset + text.set("x", (self.width / 2) + off * math.cos(ang)) + text.set("y", (self.height / 2) + off * math.sin(ang) + self.fontoff) + yield text + + # increase the rotation-offset and the colorcycle-position + offset = offset + angle + color = (color + 1) % 8 + + # append the objects to the extension-layer + yield pieslice + + yield self.draw_header(self.width / 2 - pie_radius) + + def render_stbar(self, keys, values): + """Draw stacked bar chart""" + + # Iterate over all values to draw the different slices + color = 0 + + # create value sum in order to divide the bars + try: + valuesum = sum(values) + except ValueError: + valuesum = 0.0 + + # Init offset + offset = 0 + width = self.options.bar_width + height = self.options.bar_height + x = self.width / 2 + y = self.height / 2 + + if self.blur: + if self.options.rotate: + width, height = height, width + shy = y + else: + shy = str(y - self.options.bar_height) + + # Create rectangle element + shadow = Rectangle( + x=str(x), y=str(shy), + width=str(width), height=str(height), + ) + + # Set shadow blur (connect to filter object in xml path) + shadow.set("style", self.blur) + yield shadow + + # Draw Single bars + for cnt, value in enumerate(values): + + # Calculate the individual heights normalized on 100units + normedvalue = (self.options.bar_height / valuesum) * float(value) + + # Create rectangle element + rect = Rectangle() + + # Set chart position to center of document. + if not self.options.rotate: + rect.set('x', str(self.width / 2)) + rect.set('y', str(self.height / 2 - offset - normedvalue)) + rect.set("width", str(self.options.bar_width)) + rect.set("height", str(normedvalue)) + else: + rect.set('x', str(self.width / 2 + offset)) + rect.set('y', str(self.height / 2)) + rect.set("height", str(self.options.bar_width)) + rect.set("width", str(normedvalue)) + + rect.set("style", "fill:" + self.get_color()) + + # If text is given, draw short paths and add the text + # TODO: apply overlap workaround for visible gaps in between + if keys: + if not self.options.rotate: + x1 = (self.width + self.options.bar_width) / 2 + y1 = y - offset - (normedvalue / 2) + x2 = self.options.bar_width / 2 + self.options.text_offset + y2 = 0 + txt = self.width / 2 + self.options.bar_width + self.options.text_offset + 1 + tyt = y - offset + self.fontoff - (normedvalue / 2) + else: + x1 = x + offset + normedvalue / 2 + y1 = y + self.options.bar_width / 2 + x2 = 0 + y2 = self.options.bar_width / 2 + (self.options.font_size \ + * cnt) + self.options.text_offset + txt = x + offset + normedvalue / 2 - self.fontoff + tyt = (y) + self.options.bar_width + (self.options.font_size \ + * (cnt + 1)) + self.options.text_offset + + elem = inkex.PathElement() + elem.path = [Move(x1, y1), line(x2, y2)] + elem.style = { + 'fill': 'none', + 'stroke': self.options.font_color, + 'stroke-width': self.options.stroke_width, + 'stroke-linecap': 'butt', + } + yield elem + yield self.draw_text(keys[cnt], x=str(txt), y=str(tyt)) + + # Increase Offset and Color + offset = offset + normedvalue + color = (color + 1) % 8 + + # Draw rectangle + yield rect + + yield self.draw_header(self.width / 2 + offset + normedvalue) + + +if __name__ == '__main__': + NiceChart().run() diff --git a/share/extensions/output_scour.inx b/share/extensions/output_scour.inx new file mode 100644 index 0000000..3940051 --- /dev/null +++ b/share/extensions/output_scour.inx @@ -0,0 +1,129 @@ + + + Optimized SVG Output + org.inkscape.output.scour_inkscape + + + + 5 + + true + true + + true + true + + false + false + + true + + + + false + false + false + true + false + + + true + + + + + + 1 + false + + + true + + false + + + true + + + + + + output_scour.svg + + + + + + + + + + + + + + + + 0.31 + true + + + + + .svg + image/svg+xml + Optimized SVG (*.svg) + Scalable Vector Graphics + + + + diff --git a/share/extensions/output_scour.py b/share/extensions/output_scour.py new file mode 100755 index 0000000..998383e --- /dev/null +++ b/share/extensions/output_scour.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +""" +Run the scour module on the svg output. +""" + +from distutils.version import StrictVersion + +import inkex + +try: + import scour + from scour.scour import scourString +except ImportError: + raise inkex.DependencyError("""Failed to import module 'scour'. +Please make sure it is installed (e.g. using 'pip install scour' + or 'sudo apt-get install python-scour') and try again. +""") + + +class ScourInkscape(inkex.OutputExtension): + """Scour Inkscape Extension""" + + # Scour options + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("--simplify-colors", type=inkex.Boolean, dest="simple_colors") + pars.add_argument("--style-to-xml", type=inkex.Boolean) + pars.add_argument("--group-collapsing", type=inkex.Boolean, dest="group_collapse") + pars.add_argument("--create-groups", type=inkex.Boolean, dest="group_create") + pars.add_argument("--enable-id-stripping", type=inkex.Boolean, dest="strip_ids") + pars.add_argument("--shorten-ids", type=inkex.Boolean) + pars.add_argument("--shorten-ids-prefix") + pars.add_argument("--embed-rasters", type=inkex.Boolean) + pars.add_argument("--keep-unreferenced-defs", type=inkex.Boolean, dest="keep_defs") + pars.add_argument("--keep-editor-data", type=inkex.Boolean) + pars.add_argument("--remove-metadata", type=inkex.Boolean) + pars.add_argument("--strip-xml-prolog", type=inkex.Boolean) + pars.add_argument("--set-precision", type=int, dest="digits") + pars.add_argument("--indent", dest="indent_type") + pars.add_argument("--nindent", type=int, dest="indent_depth") + pars.add_argument("--line-breaks", type=inkex.Boolean, dest="newlines") + pars.add_argument("--strip-xml-space", type=inkex.Boolean, dest="strip_xml_space_attribute") + pars.add_argument("--protect-ids-noninkscape", type=inkex.Boolean) + pars.add_argument("--protect-ids-list") + pars.add_argument("--protect-ids-prefix") + pars.add_argument("--enable-viewboxing", type=inkex.Boolean) + pars.add_argument("--enable-comment-stripping", type=inkex.Boolean, dest="strip_comments") + pars.add_argument("--renderer-workaround", type=inkex.Boolean) + + # options for internal use of the extension + pars.add_argument("--scour-version") + pars.add_argument("--scour-version-warn-old", type=inkex.Boolean) + + def save(self, stream): + # version check if enabled in options + if self.options.scour_version_warn_old: + scour_version = scour.__version__ + scour_version_min = self.options.scour_version + if StrictVersion(scour_version) < StrictVersion(scour_version_min): + raise inkex.AbortExtension(""" +The extension 'Optimized SVG Output' is designed for Scour {scour_version_min} or later but you're + using the older version Scour {scour_version}. + +Note: You can permanently disable this message on the 'About' tab of the extension window.""".format(**locals())) + del self.options.scour_version + del self.options.scour_version_warn_old + + # do the scouring + stream.write(scourString(self.svg.tostring(), self.options).encode('utf8')) + +if __name__ == '__main__': + ScourInkscape().run() diff --git a/share/extensions/output_scour.svg b/share/extensions/output_scour.svg new file mode 100644 index 0000000..8f1b941 --- /dev/null +++ b/share/extensions/output_scour.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/share/extensions/param_curves.inx b/share/extensions/param_curves.inx new file mode 100644 index 0000000..d7a51c5 --- /dev/null +++ b/share/extensions/param_curves.inx @@ -0,0 +1,47 @@ + + + Parametric Curves + org.inkscape.effect.param_curves + + + 0.0 + 1.0 + true + -1.0 + 1.0 + -1.0 + 1.0 + 30 + false + + + + + + + + + + cos(3*t) + sin(5*t) + true + false + + rect + + + + + + diff --git a/share/extensions/param_curves.py b/share/extensions/param_curves.py new file mode 100755 index 0000000..14a2da0 --- /dev/null +++ b/share/extensions/param_curves.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2009 Michel Chatelain. +# 2007 Tavmjong Bah, tavmjong@free.fr +# 2006 Georg Wiora, xorx@quarkbox.de +# 2006 Johan Engelen, johan@shouraizou.nl +# 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Changes: +# * This program is derived by Michel Chatelain from funcplot.py. +# His changes are in the Public Domain. +# * Michel Chatelain, 17-18 janvier 2009, a partir de funcplot.py +# * 20 janvier 2009 : adaptation a la version 0.46 a partir de la nouvelle version de funcplot.py +# +""" +Parametric Curves has no real description, even in the inx file, which is really odd. +""" + +from math import pi, cos, sin, tan + +import inkex + +def maths_eval(user_function): + """Try and make the eval safer""" + func = 'lambda t: ' + (user_function.strip('"') or 't') + return eval( # pylint: disable=eval-used + func, + { + 'pi': pi, 'sin': sin, 'cos': cos, 'tan': tan, + }, {}) + +def drawfunction(t_start, t_end, xleft, xright, ybottom, ytop, samples, width, height, left, bottom, + fx="cos(3*t)", fy="sin(5*t)", times2pi=False, isoscale=True, drawaxis=True): + if times2pi: + t_start *= 2 * pi + t_end *= 2 * pi + + # coords and scales based on the source rect + scalex = width / (xright - xleft) + xoff = left + coordx = lambda x: (x - xleft) * scalex + xoff # convert x-value to coordinate + scaley = height / (ytop - ybottom) + yoff = bottom + coordy = lambda y: (ybottom - y) * scaley + yoff # convert y-value to coordinate + + # Check for isotropic scaling and use smaller of the two scales, correct ranges + if isoscale: + if scaley < scalex: + # compute zero location + xzero = coordx(0) + # set scale + scalex = scaley + # correct x-offset + xleft = (left - xzero) / scalex + xright = (left + width - xzero) / scalex + else: + # compute zero location + yzero = coordy(0) + # set scale + scaley = scalex + # correct x-offset + ybottom = (yzero - bottom) / scaley + ytop = (bottom + height - yzero) / scaley + + # functions specified by the user + f1 = maths_eval(fx) + f2 = maths_eval(fy) + + # step is increment of t + step = (t_end - t_start) / (samples - 1) + third = step / 3.0 + ds = step * 0.001 # Step used in calculating derivatives + + a = [] # path array + # add axis + if drawaxis: + # check for visibility of x-axis + if ybottom <= 0 <= ytop: + # xaxis + a.append(['M', [left, coordy(0)]]) + a.append(['l', [width, 0]]) + # check for visibility of y-axis + if xleft <= 0 <= xright: + # xaxis + a.append(['M', [coordx(0), bottom]]) + a.append(['l', [0, -height]]) + + # initialize functions and derivatives for 0; + # they are carried over from one iteration to the next, to avoid extra function calculations. + #print("RET: {}".format(f1(1))) + x0 = f1(t_start) + y0 = f2(t_start) + + # numerical derivatives, using 0.001*step as the small differential + t1 = t_start + ds # Second point AFTER first point (Good for first point) + x1 = f1(t1) + y1 = f2(t1) + dx0 = (x1 - x0) / ds + dy0 = (y1 - y0) / ds + + # Start curve + a.append(['M', [coordx(x0), coordy(y0)]]) # initial moveto + for i in range(int(samples - 1)): + t1 = (i + 1) * step + t_start + t2 = t1 - ds # Second point BEFORE first point (Good for last point) + x1 = f1(t1) + x2 = f1(t2) + y1 = f2(t1) + y2 = f2(t2) + + # numerical derivatives + dx1 = (x1 - x2) / ds + dy1 = (y1 - y2) / ds + + # create curve + a.append(['C', + [coordx(x0 + (dx0 * third)), coordy(y0 + (dy0 * third)), + coordx(x1 - (dx1 * third)), coordy(y1 - (dy1 * third)), + coordx(x1), coordy(y1)] + ]) + t0 = t1 # Next segment's start is this segments end + x0 = x1 + y0 = y1 + dx0 = dx1 # Assume the functions are smooth everywhere, so carry over the derivatives too + dy0 = dy1 + return a + + +class ParamCurves(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("--t_start", type=float, default=0.0, help="Start t-value") + pars.add_argument("--t_end", type=float, default=1.0, help="End t-value") + pars.add_argument("--times2pi", type=inkex.Boolean, default=True, + help="Multiply t-range by 2*pi") + pars.add_argument("--xleft", type=float, default=-1.0, help="x-value of left") + pars.add_argument("--xright", type=float, default=1.0, help="x-value of right") + pars.add_argument("--ybottom", type=float, default=-1.0, help="y-value of bottom") + pars.add_argument("--ytop", type=float, default=1.0, help="y-value of top") + pars.add_argument("-s", "--samples", type=int, default=8, help="Samples") + pars.add_argument("--fofx", default="cos(3*t)", help="fx(t) for plotting") + pars.add_argument("--fofy", default="sin(5*t)", help="fy(t) for plotting") + pars.add_argument("--remove", type=inkex.Boolean, default=True, help="Remove rectangle") + pars.add_argument("--isoscale", type=inkex.Boolean, default=True, help="Isotropic scaling") + pars.add_argument("--drawaxis", type=inkex.Boolean, default=True) + pars.add_argument("--tab", default="sampling") + + def effect(self): + for node in self.svg.selected.values(): + if isinstance(node, inkex.Rectangle): + # create new path with basic dimensions of selected rectangle + newpath = inkex.PathElement() + x = float(node.get('x')) + y = float(node.get('y')) + width = float(node.get('width')) + height = float(node.get('height')) + + # copy attributes of rect + newpath.style = node.style + newpath.transform = node.transform + + # top and bottom were exchanged + newpath.path = \ + drawfunction(self.options.t_start, + self.options.t_end, + self.options.xleft, + self.options.xright, + self.options.ybottom, + self.options.ytop, + self.options.samples, + width, height, x, y + height, + self.options.fofx, + self.options.fofy, + self.options.times2pi, + self.options.isoscale, + self.options.drawaxis) + newpath.set('title', self.options.fofx + " " + self.options.fofy) + + # newpath.set('desc', '!func;' + self.options.fofx + ';' + self.options.fofy + ';' + # + `self.options.t_start` + ';' + # + `self.options.t_end` + ';' + # + `self.options.samples`) + + # add path into SVG structure + node.getparent().append(newpath) + # option whether to remove the rectangle or not. + if self.options.remove: + node.getparent().remove(node) + + +if __name__ == '__main__': + ParamCurves().run() diff --git a/share/extensions/path_envelope.inx b/share/extensions/path_envelope.inx new file mode 100644 index 0000000..d27a5cb --- /dev/null +++ b/share/extensions/path_envelope.inx @@ -0,0 +1,14 @@ + + + Envelope + org.inkscape.path.envelope + + path + + + + + + diff --git a/share/extensions/path_envelope.py b/share/extensions/path_envelope.py new file mode 100755 index 0000000..7f92da7 --- /dev/null +++ b/share/extensions/path_envelope.py @@ -0,0 +1,99 @@ +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import inkex +from inkex.transforms import DirectedLineSegment +from inkex.localization import inkex_gettext as _ + +class Envelope(inkex.EffectExtension): + """Distort a path/group of paths to a second path""" + def effect(self): + if len(self.svg.selection) != 2: + raise inkex.AbortExtension(_("You must select two objects only.")) + + obj, envelope = self.svg.selection.values() + + if isinstance(obj, (inkex.PathElement, inkex.Group)): + if isinstance(envelope, inkex.PathElement): + # Get bounding box plus any extra composed transform of parents. + bbox = obj.bounding_box(obj.getparent().composed_transform()) + + # distill trafo into four node points + path = envelope.path.transform(envelope.composed_transform()).to_superpath() + if len(path[0]) < 4: + raise inkex.AbortExtension(_("Selected path is too short. Must be four or more nodes.")) + + trafo = [[(csp[1][0], csp[1][1]) for csp in subs] for subs in path][0][:4] + + #vectors pointing away from the trafo origin + tbox = [ + DirectedLineSegment(trafo[0], trafo[1]), + DirectedLineSegment(trafo[1], trafo[2]), + DirectedLineSegment(trafo[3], trafo[2]), + DirectedLineSegment(trafo[0], trafo[3]), + ] + else: + if isinstance(envelope, inkex.Group): + raise inkex.AbortExtension(_("The second selected object is a group, not a" + " path.\nTry using Object->Ungroup.")) + raise inkex.AbortExtension(_("The second selected object is not a path.\nTry using" + " the procedure Path->Object to Path.")) + else: + raise inkex.AbortExtension(_("The first selected object is neither a path nor a group.\nTry using" + " the procedure Path->Object to Path.")) + + self.process_object(obj, tbox, bbox) + + def process_object(self, obj, tbox, bbox): + if isinstance(obj, inkex.PathElement): + self.process_path(obj, tbox, bbox) + elif isinstance(obj, inkex.Group): + self.process_group(obj, tbox, bbox) + + def process_group(self, group, tbox, bbox): + """Go through all groups to process all paths inside them""" + for node in group: + self.process_object(node, tbox, bbox) + + def process_path(self, element, tbox, bbox): + # Get out path's absolute and root coordinates, so obj and envelope + # are always in the same coordinate system. + points = element.path.to_absolute().transform(element.composed_transform()).to_superpath() + + for subs in points: + for csp in subs: + csp[0] = self.transform_point(tbox, bbox, *csp[0]) + csp[1] = self.transform_point(tbox, bbox, *csp[1]) + csp[2] = self.transform_point(tbox, bbox, *csp[2]) + + # Put the modified path back, undo the root transformation + element.path = points.to_path().transform(-element.composed_transform()) + + @staticmethod + def transform_point(tbox, bbox, x, y): + """Transform algorithm thanks to Jose Hevia (freon)""" + vector = DirectedLineSegment((bbox.left, bbox.top), (x, y)) + xratio = vector.dx / bbox.width + yratio = vector.dy / bbox.height + horz = DirectedLineSegment(tbox[0].point_at_ratio(xratio), tbox[2].point_at_ratio(xratio)) + vert = DirectedLineSegment(tbox[3].point_at_ratio(yratio), tbox[1].point_at_ratio(yratio)) + return vert.intersect(horz) + +if __name__ == '__main__': + Envelope().run() diff --git a/share/extensions/path_mesh_m2p.inx b/share/extensions/path_mesh_m2p.inx new file mode 100644 index 0000000..9ce099d --- /dev/null +++ b/share/extensions/path_mesh_m2p.inx @@ -0,0 +1,37 @@ + + + Mesh-Gradient to Path + org.inkscape.meshes.mesh_to_path + + + + + + + + + + + + + + + + + + + + + all + + + + + + + + + + diff --git a/share/extensions/path_mesh_m2p.py b/share/extensions/path_mesh_m2p.py new file mode 100755 index 0000000..cb4f8e2 --- /dev/null +++ b/share/extensions/path_mesh_m2p.py @@ -0,0 +1,350 @@ +#!/usr/bin/env python +# +# Copyright (C) 2016 su_v, +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +""" +Convert mesh gradient to path +""" + +import inkex +from inkex.elements import MeshGradient + +# globals +EPSILON = 1e-3 +MG_PROPS = [ + 'fill', + 'stroke' +] + +def isclose(a, b, rel_tol=1e-09, abs_tol=0.0): + """Test approximate equality. + + ref: + PEP 485 -- A Function for testing approximate equality + https://www.python.org/dev/peps/pep-0485/#proposed-implementation + """ + # pylint: disable=invalid-name + return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) + + +def reverse_path(csp): + """Reverse path in CSP notation.""" + rcsp = [] + for subpath in reversed(csp): + rsub = [list(reversed(cp)) for cp in reversed(subpath)] + rcsp.append(rsub) + return rcsp + + +def join_path(csp1, sp1, csp2, sp2): + """Join sub-paths *sp1* and *sp2*.""" + pt1 = csp1[sp1][-1][1] + pt2 = csp2[sp2][0][1] + if (isclose(pt1[0], pt2[0], EPSILON) and + isclose(pt1[1], pt2[1], EPSILON)): + csp1[sp1][-1][2] = csp2[sp2][0][2] + csp1[sp1].extend(csp2[sp2][1:]) + else: + # inkex.debug('not close') + csp1.append(csp2[sp2]) + return csp1 + + +def is_url(val): + """Check whether attribute value is linked resource.""" + return val.startswith('url(#') + + +def mesh_corners(meshgradient): + """Return list of mesh patch corners, patch paths.""" + rows = len(meshgradient) + cols = len(meshgradient[0]) + # first corner of mesh gradient + corner_x = float(meshgradient.get('x', '0.0')) + corner_y = float(meshgradient.get('y', '0.0')) + # init corner and meshpatch lists + corners = [[None for _ in range(cols+1)] for _ in range(rows+1)] + corners[0][0] = [corner_x, corner_y] + meshpatch_csps = [] + for meshrow in range(rows): + for meshpatch in range(cols): + # get start point for current meshpatch edges + if meshrow == 0: + first_corner = corners[meshrow][meshpatch] + if meshrow > 0: + first_corner = corners[meshrow][meshpatch+1] + # parse path of meshpatch edges + path = 'M {},{}'.format(*first_corner) + for edge in meshgradient[meshrow][meshpatch]: + path = ' '.join([path, edge.get('path')]) + csp = inkex.Path(path).to_superpath() + # update corner list with current meshpatch + if meshrow == 0: + corners[meshrow][meshpatch+1] = csp[0][1][1] + corners[meshrow+1][meshpatch+1] = csp[0][2][1] + if meshpatch == 0: + corners[meshrow+1][meshpatch] = csp[0][3][1] + if meshrow > 0: + corners[meshrow][meshpatch+1] = csp[0][0][1] + corners[meshrow+1][meshpatch+1] = csp[0][1][1] + if meshpatch == 0: + corners[meshrow+1][meshpatch] = csp[0][2][1] + # append to list of meshpatch csp + meshpatch_csps.append(csp) + return corners, meshpatch_csps + + +def mesh_hvlines(meshgradient): + """Return lists of vertical and horizontal patch edges.""" + rows = len(meshgradient) + cols = len(meshgradient[0]) + # init lists for horizontal, vertical lines + hlines = [[None for _ in range(cols)] for _ in range(rows+1)] + vlines = [[None for _ in range(rows)] for _ in range(cols+1)] + for meshrow in range(rows): + for meshpatch in range(cols): + # horizontal edges + if meshrow == 0: + edge = meshgradient[meshrow][meshpatch][0] + hlines[meshrow][meshpatch] = edge.get('path') + edge = meshgradient[meshrow][meshpatch][2] + hlines[meshrow+1][meshpatch] = edge.get('path') + if meshrow > 0: + edge = meshgradient[meshrow][meshpatch][1] + hlines[meshrow+1][meshpatch] = edge.get('path') + # vertical edges + if meshrow == 0: + edge = meshgradient[meshrow][meshpatch][1] + vlines[meshpatch+1][meshrow] = edge.get('path') + if meshpatch == 0: + edge = meshgradient[meshrow][meshpatch][3] + vlines[meshpatch][meshrow] = edge.get('path') + if meshrow > 0: + edge = meshgradient[meshrow][meshpatch][0] + vlines[meshpatch+1][meshrow] = edge.get('path') + if meshpatch == 0: + edge = meshgradient[meshrow][meshpatch][2] + vlines[meshpatch][meshrow] = edge.get('path') + return hlines, vlines + + +def mesh_to_outline(corners, hlines, vlines): + """Construct mesh outline as CSP path.""" + outline_csps = [] + path = 'M {},{}'.format(*corners[0][0]) + for edge_path in hlines[0]: + path = ' '.join([path, edge_path]) + for edge_path in vlines[-1]: + path = ' '.join([path, edge_path]) + for edge_path in reversed(hlines[-1]): + path = ' '.join([path, edge_path]) + for edge_path in reversed(vlines[0]): + path = ' '.join([path, edge_path]) + outline_csps.append(inkex.Path(path).to_superpath()) + return outline_csps + + +def mesh_to_grid(corners, hlines, vlines): + """Construct mesh grid with CSP paths.""" + rows = len(corners) - 1 + cols = len(corners[0]) - 1 + gridline_csps = [] + # horizontal + path = 'M {},{}'.format(*corners[0][0]) + for edge_path in hlines[0]: + path = ' '.join([path, edge_path]) + gridline_csps.append(inkex.Path(path).to_superpath()) + for i in range(1, rows+1): + path = 'M {},{}'.format(*corners[i][-1]) + for edge_path in reversed(hlines[i]): + path = ' '.join([path, edge_path]) + gridline_csps.append(inkex.Path(path).to_superpath()) + # vertical + path = 'M {},{}'.format(*corners[-1][0]) + for edge_path in reversed(vlines[0]): + path = ' '.join([path, edge_path]) + gridline_csps.append(inkex.Path(path).to_superpath()) + for j in range(1, cols+1): + path = 'M {},{}'.format(*corners[0][j]) + for edge_path in vlines[j]: + path = ' '.join([path, edge_path]) + gridline_csps.append(inkex.Path(path).to_superpath()) + return gridline_csps + + +def mesh_to_faces(corners, hlines, vlines): + """Construct mesh faces with CSP paths.""" + rows = len(corners) - 1 + cols = len(corners[0]) - 1 + face_csps = [] + for row in range(rows): + for col in range(cols): + # init new face + face = [] + # init edge paths + edge_t = hlines[row][col] + edge_b = hlines[row+1][col] + edge_l = vlines[col][row] + edge_r = vlines[col+1][row] + # top edge, first + if row == 0: + path = 'M {},{}'.format(*corners[row][col]) + path = ' '.join([path, edge_t]) + face.append(inkex.Path(path).to_superpath()[0]) + else: + path = 'M {},{}'.format(*corners[row][col+1]) + path = ' '.join([path, edge_t]) + face.append(reverse_path(inkex.Path(path).to_superpath())[0]) + # right edge + path = 'M {},{}'.format(*corners[row][col+1]) + path = ' '.join([path, edge_r]) + join_path(face, -1, inkex.Path(path).to_superpath(), 0) + # bottom edge + path = 'M {},{}'.format(*corners[row+1][col+1]) + path = ' '.join([path, edge_b]) + join_path(face, -1, inkex.Path(path).to_superpath(), 0) + # left edge + if col == 0: + path = 'M {},{}'.format(*corners[row+1][col]) + path = ' '.join([path, edge_l]) + join_path(face, -1, inkex.Path(path).to_superpath(), 0) + else: + path = 'M {},{}'.format(*corners[row][col]) + path = ' '.join([path, edge_l]) + join_path(face, -1, reverse_path(inkex.Path(path).to_superpath()), 0) + # append face to output list + face_csps.append(face) + return face_csps + + +class MeshToPath(inkex.EffectExtension): + """Effect extension to convert mesh geometry to path data.""" + def add_arguments(self, pars): + pars.add_argument("--tab", help="The selected UI-tab") + pars.add_argument("--mode", default="outline", help="Edge mode") + + def process_props(self, mdict, res_type='meshgradient'): + """Process style properties of style dict *mdict*.""" + result = [] + for key, val in mdict.items(): + if key in MG_PROPS: + if is_url(val): + paint_server = self.svg.getElementById(val) + if res_type == 'meshgradient' and isinstance(paint_server, MeshGradient): + result.append(paint_server) + return result + + def process_style(self, node, res_type='meshgradient'): + """Process style of *node*.""" + result = [] + # Presentation attributes + adict = dict(node.attrib) + result.extend(self.process_props(adict, res_type)) + # Inline CSS style properties + result.extend(self.process_props(node.style, res_type)) + # TODO: check for child paint servers + return result + + def find_meshgradients(self, node): + """Parse node style, return list with linked meshgradients.""" + return self.process_style(node, res_type='meshgradient') + + # ----- Process meshgradient definitions + + def mesh_to_csp(self, meshgradient): + """Parse mesh geometry and build csp-based path data.""" + + # init variables + transform = None + mode = self.options.mode + + # gradient units + mesh_units = meshgradient.get('gradientUnits', 'objectBoundingBox') + if mesh_units == 'objectBoundingBox': + # TODO: position and scale based on "objectBoundingBox" units + return + + # Inkscape SVG 0.92 and SVG 2.0 draft mesh transformations + transform = meshgradient.gradientTransform * meshgradient.transform + + # parse meshpatches, calculate absolute corner coords + corners, meshpatch_csps = mesh_corners(meshgradient) + + if mode == 'meshpatches': + return meshpatch_csps, transform + else: + hlines, vlines = mesh_hvlines(meshgradient) + if mode == 'outline': + return mesh_to_outline(corners, hlines, vlines), transform + elif mode == 'gridlines': + return mesh_to_grid(corners, hlines, vlines), transform + elif mode == 'faces': + return mesh_to_faces(corners, hlines, vlines), transform + + # ----- Convert meshgradient definitions + + def csp_to_path(self, node, csp_list, transform=None): + """Create new paths based on csp data, return group with paths.""" + # set up stroke width, group + stroke_width = self.svg.unittouu('1px') + stroke_color = '#000000' + style = { + 'fill': 'none', + 'stroke': stroke_color, + 'stroke-width': str(stroke_width), + } + + group = inkex.Group() + # apply gradientTransform and node's preserved transform to group + group.transform = transform * node.transform + + # convert each csp to path, append to group + for csp in csp_list: + elem = group.add(inkex.PathElement()) + elem.style = style + elem.path = inkex.CubicSuperPath(csp) + if self.options.mode == 'outline': + elem.path.close() + elif self.options.mode == 'faces': + if len(csp) == 1 and len(csp[0]) == 5: + elem.path.close() + return group + + def effect(self): + """Main routine to convert mesh geometry to path data.""" + # loop through selection + for node in self.svg.selected.values(): + meshgradients = self.find_meshgradients(node) + # if style references meshgradient + if meshgradients: + for meshgradient in meshgradients: + csp_list = None + result = None + # parse mesh geometry + if meshgradient is not None: + csp_list, mat = self.mesh_to_csp(meshgradient) + # generate new paths with path data based on mesh geometry + if csp_list is not None: + result = self.csp_to_path(node, csp_list, mat) + # add result (group) to document + if result is not None: + index = node.getparent().index(node) + node.getparent().insert(index+1, result) + + +if __name__ == '__main__': + MeshToPath().run() diff --git a/share/extensions/path_mesh_p2m.inx b/share/extensions/path_mesh_p2m.inx new file mode 100644 index 0000000..f8be151 --- /dev/null +++ b/share/extensions/path_mesh_p2m.inx @@ -0,0 +1,32 @@ + + + Path to Mesh-Gradient + org.inkscape.meshes.path_to_mesh + + + + + + + + + + + + + + + + all + + + + + + + + + + diff --git a/share/extensions/path_mesh_p2m.py b/share/extensions/path_mesh_p2m.py new file mode 100755 index 0000000..d5a4877 --- /dev/null +++ b/share/extensions/path_mesh_p2m.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# +# Copyright (C) 2016 su_v, +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +""" +Convert path to mesh gradient +""" + +import inkex +from inkex.paths import Line, Curve +from inkex.elements import MeshGradient + +class PathToMesh(inkex.EffectExtension): + """Convert path data to mesh geometry.""" + def add_arguments(self, pars): + pars.add_argument("--tab", help="The selected UI-tab") + + def add_mesh(self, meshgradient): + """Add meshgradient definition to current document.""" + self.svg.defs.append(meshgradient) + meshgradient.set_random_id('meshgradient') + return meshgradient.get_id() + + def effect(self): + """Main routine to convert path data to mesh geometry.""" + # loop through selection + for node in self.svg.selection.filter(inkex.PathElement).values(): + csp = None + meshgradient = None + mesh_id = None + # parse path data + csp = node.path.to_superpath() + # convert csp to meshgradient definition + if csp is not None: + # TODO: check length of path data / csp + meshgradient = self.to_mesh(node, csp) + # add meshgradient to document + if meshgradient is not None: + mesh_id = self.add_mesh(meshgradient) + # apply meshgradient to node + if mesh_id is not None: + node.style['fill'] = 'url(#{})'.format(mesh_id) + + def to_mesh(self, node, csp, subpath=0): + """Convert csp to meshgradient geometry.""" + # mesh data + corners, edges = self.to_meshdata(csp[subpath]) + # alternating stop colors + colors = [node.style.get('fill'), '#ffffff'] + # define meshgradient with first corner as initial point + meshgradient = MeshGradient.new_mesh(pos=corners[0], rows=1, cols=1) + # define stops (stop-color, path) for first meshpatch + meshgradient[0][0].stops(edges[0:4], colors) + return meshgradient + + @staticmethod + def to_meshdata(subpath): + """Convert csp subpath to corners, edge path data.""" + if len(subpath) >= 5: + corners = [] + edges = [] + for i, corner in enumerate(subpath[:4]): + corners.append(corner[1]) + edge = [list(subpath[i]), list(subpath[i+1])] + edge[0][0] = list(edge[0][1]) + edge[1][2] = list(edge[1][1]) + if inkex.CubicSuperPath.is_line(edge[0], edge[1]): + edges.append(Line(*edge[1][1])) + else: + edges.append(Curve(*(edge[0][2] + edge[1][0] + edge[1][1]))) + + return corners, edges + +if __name__ == '__main__': + PathToMesh().run() diff --git a/share/extensions/path_number_nodes.inx b/share/extensions/path_number_nodes.inx new file mode 100644 index 0000000..c08a8ee --- /dev/null +++ b/share/extensions/path_number_nodes.inx @@ -0,0 +1,30 @@ + + + Number Nodes + org.inkscape.filter.number_nodes + + + 20px + 10px + 1 + 1 + + + + + + + + path + + + + + + diff --git a/share/extensions/path_number_nodes.py b/share/extensions/path_number_nodes.py new file mode 100755 index 0000000..878a1a0 --- /dev/null +++ b/share/extensions/path_number_nodes.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +import math + +import inkex +from inkex import TextElement, Circle + +class NumberNodes(inkex.EffectExtension): + """Replace the selection's nodes with numbered dots according to the options""" + def add_arguments(self, pars): + pars.add_argument("--dotsize", default="10px", help="Size of the dots on the path nodes") + pars.add_argument("--fontsize", default="20px", help="Size of node labels") + pars.add_argument("--start", type=int, default=1, help="First number in the sequence") + pars.add_argument("--step", type=int, default=1, help="Numbering step between two nodes") + pars.add_argument("--tab", help="The selected UI-tab when OK was pressed") + + def effect(self): + if not self.svg.selected: + raise inkex.AbortExtension("Please select an object.") + for node in self.svg.selection.filter(inkex.PathElement).values(): + self.add_dot(node) + + def add_dot(self, node): + """Add a dot label for this path element""" + group = node.getparent().add(inkex.Group()) + dot_group = group.add(inkex.Group()) + num_group = group.add(inkex.Group()) + group.transform = node.transform + + style = inkex.Style({'stroke': 'none', 'fill': '#000'}) + + for step, (x, y) in enumerate(node.path.end_points): + circle = dot_group.add(Circle(cx=str(x), cy=str(y),\ + r=str(self.svg.unittouu(self.options.dotsize) / 2))) + circle.style = style + num_group.append(self.add_text( + x + (self.svg.unittouu(self.options.dotsize) / 2), + y - (self.svg.unittouu(self.options.dotsize) / 2), + self.options.start + (self.options.step * step))) + + node.delete() + + def add_text(self, x, y, text): + """Add a text label at the given location""" + elem = TextElement(x=str(x), y=str(y)) + elem.text = str(text) + elem.style = { + 'font-size': self.svg.unittouu(self.options.fontsize), + 'fill-opacity': '1.0', + 'stroke': 'none', + 'font-weight': 'normal', + 'font-style': 'normal', + 'fill': '#999'} + return elem + +if __name__ == '__main__': + NumberNodes().run() diff --git a/share/extensions/path_to_absolute.inx b/share/extensions/path_to_absolute.inx new file mode 100644 index 0000000..b324b6d --- /dev/null +++ b/share/extensions/path_to_absolute.inx @@ -0,0 +1,14 @@ + + + To Absolute + org.inkscape.path.to_absolute + + all + + + + + + diff --git a/share/extensions/path_to_absolute.py b/share/extensions/path_to_absolute.py new file mode 100755 index 0000000..4db9d98 --- /dev/null +++ b/share/extensions/path_to_absolute.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2020 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +""" +Path To Absolute +""" +import inkex + +class ToAbsolute(inkex.EffectExtension): + """Convert any selected object to absolute/object-to-path/bezier only paths""" + def effect(self): + """Performs the effect.""" + for node in self.svg.selected.values(): + if not isinstance(node, inkex.PathElement): + node = node.replace_with(node.to_path_element()) + node.path = node.path.to_absolute().to_superpath().to_path() + +if __name__ == '__main__': + ToAbsolute().run() diff --git a/share/extensions/pathalongpath.inx b/share/extensions/pathalongpath.inx new file mode 100644 index 0000000..eafae28 --- /dev/null +++ b/share/extensions/pathalongpath.inx @@ -0,0 +1,37 @@ + + + Pattern along Path + org.inkscape.generate.path_along_path + pathmodifier.py + + + + + + + + + + + + + 0.0 + 0.0 + 0.0 + false + true + + + + + + + all + + + + + + diff --git a/share/extensions/pathalongpath.py b/share/extensions/pathalongpath.py new file mode 100755 index 0000000..5ac4286 --- /dev/null +++ b/share/extensions/pathalongpath.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python +# +# Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# barraud@math.univ-lille1.fr +# +""" +This script deforms an object (the pattern) along other paths (skeletons)... +The first selected object is the pattern +the last selected ones are the skeletons. + +Imagine a straight horizontal line L in the middle of the bounding box of the pattern. +Consider the normal bundle of L: the collection of all the vertical lines meeting L. +Consider this as the initial state of the plane; in particular, think of the pattern +as painted on these lines. + +Now move and bend L to make it fit a skeleton, and see what happens to the normals: +they move and rotate, deforming the pattern. +""" +import copy + +import inkex +from inkex.bezier import pointdistance, beziersplitatt, tpoint +from inkex.paths import CubicSuperPath + +import pathmodifier + +def flipxy(path): + for pathcomp in path: + for ctl in pathcomp: + for pt in ctl: + tmp = pt[0] + pt[0] = -pt[1] + pt[1] = -tmp + + +def offset(pathcomp, dx, dy): + for ctl in pathcomp: + for pt in ctl: + pt[0] += dx + pt[1] += dy + + +def stretch(pathcomp, xscale, yscale, org): + for ctl in pathcomp: + for pt in ctl: + pt[0] = org[0] + (pt[0] - org[0]) * xscale + pt[1] = org[1] + (pt[1] - org[1]) * yscale + + +def linearize(p, tolerance=0.001): + """ + This function receives a component of a 'cubicsuperpath' and returns two things: + The path subdivided in many straight segments, and an array containing the length of each segment. + + We could work with bezier path as well, but bezier arc lengths are (re)computed for each point + in the deformed object. For complex paths, this might take a while. + """ + zero = 0.000001 + i = 0 + d = 0 + lengths = [] + while i < len(p) - 1: + box = pointdistance(p[i][1], p[i][2]) + box += pointdistance(p[i][2], p[i + 1][0]) + box += pointdistance(p[i + 1][0], p[i + 1][1]) + chord = pointdistance(p[i][1], p[i + 1][1]) + if (box - chord) > tolerance: + b1, b2 = beziersplitatt([p[i][1], p[i][2], p[i + 1][0], p[i + 1][1]], 0.5) + p[i][2][0], p[i][2][1] = b1[1] + p[i + 1][0][0], p[i + 1][0][1] = b2[2] + p.insert(i + 1, [[b1[2][0], b1[2][1]], [b1[3][0], b1[3][1]], [b2[1][0], b2[1][1]]]) + else: + d = (box + chord) / 2 + lengths.append(d) + i += 1 + new = [p[i][1] for i in range(0, len(p) - 1) if lengths[i] > zero] + new.append(p[-1][1]) + lengths = [l for l in lengths if l > zero] + return new, lengths + + +class PathAlongPath(pathmodifier.PathModifier): + """Deform a path along a second path""" + def add_arguments(self, pars): + pars.add_argument("-n", "--noffset", type=float, default=0.0, help="normal offset") + pars.add_argument("-t", "--toffset", type=float, default=0.0, help="tangential offset") + pars.add_argument("-k", "--kind", type=str, default='') + pars.add_argument("-c", "--copymode", default="Single", + help="repeat the path to fit deformer's length") + pars.add_argument("-p", "--space", type=float, default=0.0) + pars.add_argument("-v", "--vertical", type=inkex.Boolean, default=False, + help="reference path is vertical") + pars.add_argument("-d", "--duplicate", type=inkex.Boolean, default=False, + help="duplicate pattern before deformation") + pars.add_argument("--tab", help="The selected UI-tab when OK was pressed") + + def prepare_selection(self): + """ + first selected->pattern, all but first selected-> skeletons + """ + skeletons = self.svg.selection.paint_order() + + elem = skeletons.pop() + if self.options.duplicate: + elem = elem.duplicate() + pattern = elem.to_path_element() + elem.replace_with(pattern) + + self.expand_clones(skeletons, True, False) + self.objects_to_paths(skeletons) + return pattern, skeletons.id_dict() + + def lengthtotime(self, l): + """ + Receives an arc length l, and returns the index of the segment in self.skelcomp + containing the corresponding point, to gether with the position of the point on this segment. + + If the deformer is closed, do computations modulo the toal length. + """ + if self.skelcompIsClosed: + l = l % sum(self.lengths) + if l <= 0: + return 0, l / self.lengths[0] + i = 0 + while (i < len(self.lengths)) and (self.lengths[i] <= l): + l -= self.lengths[i] + i += 1 + t = l / self.lengths[min(i, len(self.lengths) - 1)] + return i, t + + def apply_diffeomorphism(self, bpt, vects=()): + """ + The kernel of this stuff: + bpt is a base point and for v in vectors, v'=v-p is a tangent vector at bpt. + """ + s = bpt[0] - self.skelcomp[0][0] + i, t = self.lengthtotime(s) + if i == len(self.skelcomp) - 1: + x, y = tpoint(self.skelcomp[i - 1], self.skelcomp[i], 1 + t) + dx = (self.skelcomp[i][0] - self.skelcomp[i - 1][0]) / self.lengths[-1] + dy = (self.skelcomp[i][1] - self.skelcomp[i - 1][1]) / self.lengths[-1] + else: + x, y = tpoint(self.skelcomp[i], self.skelcomp[i + 1], t) + dx = (self.skelcomp[i + 1][0] - self.skelcomp[i][0]) / self.lengths[i] + dy = (self.skelcomp[i + 1][1] - self.skelcomp[i][1]) / self.lengths[i] + + vx = 0 + vy = bpt[1] - self.skelcomp[0][1] + if self.options.wave: + bpt[0] = x + vx * dx + bpt[1] = y + vy + vx * dy + else: + bpt[0] = x + vx * dx - vy * dy + bpt[1] = y + vx * dy + vy * dx + + for v in vects: + vx = v[0] - self.skelcomp[0][0] - s + vy = v[1] - self.skelcomp[0][1] + if self.options.wave: + v[0] = x + vx * dx + v[1] = y + vy + vx * dy + else: + v[0] = x + vx * dx - vy * dy + v[1] = y + vx * dy + vy * dx + + def effect(self): + if len(self.options.ids) < 2: + raise inkex.AbortExtension("This extension requires two selected paths.") + + self.options.wave = (self.options.kind == "Ribbon") + if self.options.copymode == "Single": + self.options.repeat = False + self.options.stretch = False + elif self.options.copymode == "Repeated": + self.options.repeat = True + self.options.stretch = False + elif self.options.copymode == "Single, stretched": + self.options.repeat = False + self.options.stretch = True + elif self.options.copymode == "Repeated, stretched": + self.options.repeat = True + self.options.stretch = True + + pattern, skels = self.prepare_selection() + bbox = pattern.bounding_box() + + if self.options.vertical: + # flipxy(bbox)... + bbox = inkex.BoundingBox(-bbox.y, -bbox.x) + + width = bbox.width + delta_x = width + self.options.space + if delta_x < 0.01: + raise inkex.AbortExtension("The total length of the pattern is too small\n"\ + "Please choose a larger object or set 'Space between copies' > 0") + + if isinstance(pattern, inkex.PathElement): + pattern.path = self._sekl_call(skels, pattern.path.to_superpath(), delta_x, bbox) + + def _sekl_call(self, skeletons, p0, dx, bbox): + if self.options.vertical: + flipxy(p0) + newp = [] + for skelnode in skeletons.values(): + self.curSekeleton = skelnode.path.to_superpath() + if self.options.vertical: + flipxy(self.curSekeleton) + for comp in self.curSekeleton: + path = copy.deepcopy(p0) + self.skelcomp, self.lengths = linearize(comp) + # !!!!>----> TODO: really test if path is closed! end point==start point is not enough! + self.skelcompIsClosed = (self.skelcomp[0] == self.skelcomp[-1]) + + length = sum(self.lengths) + xoffset = self.skelcomp[0][0] - bbox.x.minimum + self.options.toffset + yoffset = self.skelcomp[0][1] - bbox.y.center - self.options.noffset + + if self.options.repeat: + NbCopies = max(1, int(round((length + self.options.space) / dx))) + width = dx * NbCopies + if not self.skelcompIsClosed: + width -= self.options.space + bbox.x.maximum = bbox.x.minimum + width + new = [] + for sub in path: + for _ in range(NbCopies): + new.append(copy.deepcopy(sub)) + offset(sub, dx, 0) + path = new + + for sub in path: + offset(sub, xoffset, yoffset) + + if self.options.stretch: + if not bbox.width: + raise inkex.AbortExtension("The 'stretch' option requires that the pattern must have non-zero width :\nPlease edit the pattern width.") + for sub in path: + stretch(sub, length / bbox.width, 1, self.skelcomp[0]) + + for sub in path: + for ctlpt in sub: + self.apply_diffeomorphism(ctlpt[1], (ctlpt[0], ctlpt[2])) + + if self.options.vertical: + flipxy(path) + newp += path + return CubicSuperPath(newp) + + +if __name__ == '__main__': + PathAlongPath().run() diff --git a/share/extensions/pathmodifier.py b/share/extensions/pathmodifier.py new file mode 100755 index 0000000..fce1c21 --- /dev/null +++ b/share/extensions/pathmodifier.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# barraud@math.univ-lille1.fr +# +""" +This code defines a basic class (PathModifier) of effects whose purpose is +to somehow deform given objects: one common tasks for all such effect is to +convert shapes, groups, clones to paths. The class has several functions to +make this (more or less!) easy. +As an example, a second class (Diffeo) is derived from it, +to implement deformations of the form X=f(x,y), Y=g(x,y)... +""" + +import inkex +from inkex import PathElement, Group, Use + +# This deprecated API is used by some external extensions. +from inkex.deprecated import zSort # pylint: disable=unused-import + +class PathModifier(inkex.EffectExtension): + """Select list manipulation""" + def expand_groups(self, elements, transferTransform=True): + for node_id, node in list(elements.items()): + if isinstance(node, inkex.Group): + mat = node.transform + for child in node: + if transferTransform: + child.transform *= mat + elements.update(self.expand_groups({child.get('id'): child})) + if transferTransform and node.get("transform"): + del node.attrib["transform"] + # Group is now replaced, so remove it. + elements.pop(node_id) + return elements + + def expand_clones(self, elements, transferTransform=True, replace=True): + for node_id, node in list(elements.items()): + if isinstance(node, Group): + self.expand_groups(elements, transferTransform) + self.expand_clones(elements, transferTransform, replace) + # Hum... not very efficient if there are many clones of groups... + + elif isinstance(node, Use): + newnode = node.unlink() + elements.pop(node_id) + newid = newnode.get('id') + elements.update(self.expand_clones({newid: newnode}, transferTransform, replace)) + return elements + + def objects_to_paths(self, elements, replace=True): + """Replace all non-paths with path objects""" + for node in list(elements.values()): + elem = node.to_path_element() + if replace: + node.replace_with(elem) + elem.set('id', node.get('id')) + elements[elem.get('id')] = elem + + def effect(self): + raise NotImplementedError("overwrite this method in subclasses") + self.objects_to_paths(self.svg.selected, True) + self.bbox = self.svg.selection.bounding_box() + for node in self.svg.selection.filter(PathElement): + path = node.path.to_superpath() + # do what ever you want with "path"! + node.path = path + + +class Diffeo(PathModifier): + def applyDiffeo(self, bpt, vects=()): + # bpt is a base point and for v in vectors, v'=v-p is a tangent vector at bpt. + # Defaults to identity! + for v in vects: + v[0] -= bpt[0] + v[1] -= bpt[1] + + # -- your transformations go here: + # x,y=bpt + # bpt[0]=f(x,y) + # bpt[1]=g(x,y) + # for v in vects: + # vx,vy=v + # v[0]=df/dx(x,y)*vx+df/dy(x,y)*vy + # v[1]=dg/dx(x,y)*vx+dg/dy(x,y)*vy + # + # -- !caution! y-axis is pointing downward! + + for v in vects: + v[0] += bpt[0] + v[1] += bpt[1] + + def effect(self): + self.expand_clones(self.svg.selected, True) + self.expand_groups(self.svg.selected, True) + self.objects_to_paths(self.svg.selected, True) + self.bbox = self.svg.selection.bounding_box() + for node in self.svg.selection.filter(PathElement).values(): + path = node.path.to_superpath() + for sub in path: + for ctlpt in sub: + self.applyDiffeo(ctlpt[1], (ctlpt[0], ctlpt[2])) + node.path = path diff --git a/share/extensions/pathscatter.inx b/share/extensions/pathscatter.inx new file mode 100644 index 0000000..0e06d3a --- /dev/null +++ b/share/extensions/pathscatter.inx @@ -0,0 +1,39 @@ + + + Scatter + org.inkscape.path.path_scatter + pathmodifier.py + + + false + false + 0.0 + 0.0 + 0.0 + false + + + + + + true + false + + + + + + + + + + + all + + + + + + diff --git a/share/extensions/pathscatter.py b/share/extensions/pathscatter.py new file mode 100755 index 0000000..235c4aa --- /dev/null +++ b/share/extensions/pathscatter.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# barraud@math.univ-lille1.fr +""" +This script deforms an object (the pattern) along other paths (skeletons)... +The first selected object is the pattern +the last selected ones are the skeletons. + +Imagine a straight horizontal line L in the middle of the bounding box of the pattern. +Consider the normal bundle of L: the collection of all the vertical lines meeting L. +Consider this as the initial state of the plane; in particular, think of the pattern +as painted on these lines. + +Now move and bend L to make it fit a skeleton, and see what happens to the normals: +they move and rotate, deforming the pattern. +""" +import copy +import random + +import inkex +from inkex import bezier, Transform, BoundingBox, Group, Use + +import pathmodifier + +def flipxy(path): + for pathcomp in path: + for ctl in pathcomp: + for pt in ctl: + tmp = pt[0] + pt[0] = -pt[1] + pt[1] = -tmp + + +def offset(pathcomp, dx, dy): + for ctl in pathcomp: + for pt in ctl: + pt[0] += dx + pt[1] += dy + + +def stretch(pathcomp, xscale, yscale, org): + for ctl in pathcomp: + for pt in ctl: + pt[0] = org[0] + (pt[0] - org[0]) * xscale + pt[1] = org[1] + (pt[1] - org[1]) * yscale + + +def linearize(p, tolerance=0.001): + """ + This function receives a component of a 'cubicsuperpath' and returns two things: + The path subdivided in many straight segments, and an array containing the length of each segment. + + We could work with bezier path as well, but bezier arc lengths are (re)computed for each point + in the deformed object. For complex paths, this might take a while. + """ + zero = 0.000001 + i = 0 + d = 0 + lengths = [] + while i < len(p) - 1: + box = bezier.pointdistance(p[i][1], p[i][2]) + box += bezier.pointdistance(p[i][2], p[i + 1][0]) + box += bezier.pointdistance(p[i + 1][0], p[i + 1][1]) + chord = bezier.pointdistance(p[i][1], p[i + 1][1]) + if (box - chord) > tolerance: + b1, b2 = bezier.beziersplitatt([p[i][1], p[i][2], p[i + 1][0], p[i + 1][1]], 0.5) + p[i][2][0], p[i][2][1] = b1[1] + p[i + 1][0][0], p[i + 1][0][1] = b2[2] + p.insert(i + 1, [[b1[2][0], b1[2][1]], [b1[3][0], b1[3][1]], [b2[1][0], b2[1][1]]]) + else: + d = (box + chord) / 2 + lengths.append(d) + i += 1 + new = [p[i][1] for i in range(0, len(p) - 1) if lengths[i] > zero] + new.append(p[-1][1]) + lengths = [l for l in lengths if l > zero] + return new, lengths + + +class PathScatter(pathmodifier.Diffeo): + def __init__(self): + super(PathScatter, self).__init__() + self.arg_parser.add_argument("-n", "--noffset", type=float, dest="noffset", default=0.0, help="normal offset") + self.arg_parser.add_argument("-t", "--toffset", type=float, dest="toffset", default=0.0, help="tangential offset") + self.arg_parser.add_argument("-g", "--grouppick", type=inkex.Boolean, dest="grouppick", default=False, + help="if pattern is a group then randomly pick group members") + self.arg_parser.add_argument("-m", "--pickmode", type=str, dest="pickmode", default="rand", + help="group pick mode (rand=random seq=sequentially)") + self.arg_parser.add_argument("-f", "--follow", type=inkex.Boolean, dest="follow", default=True, + help="choose between wave or snake effect") + self.arg_parser.add_argument("-s", "--stretch", type=inkex.Boolean, dest="stretch", default=True, + help="repeat the path to fit deformer's length") + self.arg_parser.add_argument("-p", "--space", type=float, dest="space", default=0.0) + self.arg_parser.add_argument("-v", "--vertical", type=inkex.Boolean, dest="vertical", default=False, + help="reference path is vertical") + self.arg_parser.add_argument("-d", "--duplicate", type=inkex.Boolean, dest="duplicate", default=False, + help="duplicate pattern before deformation") + self.arg_parser.add_argument("-c", "--copymode", type=str, dest="copymode", default="clone", + help="duplicate pattern before deformation") + self.arg_parser.add_argument("--tab", type=str, dest="tab", + help="The selected UI-tab when OK was pressed") + + def prepareSelectionList(self): + # first selected->pattern, all but first selected-> skeletons + pattern_node = self.svg.selected.pop() + + self.gNode = Group() + pattern_node.getparent().append(self.gNode) + + if self.options.copymode == "copy": + self.patternNode = pattern_node.duplicate() + elif self.options.copymode == "clone": + # TODO: allow 4th option: duplicate the first copy and clone the next ones. + self.patternNode = self.gNode.add(Use()) + self.patternNode.href = pattern_node + else: + self.patternNode = pattern_node + + self.skeletons = self.svg.selected + self.expand_clones(self.skeletons, True, False) + self.objects_to_paths(self.skeletons, False) + + def lengthtotime(self, l): + """ + Receives an arc length l, and returns the index of the segment in self.skelcomp + containing the corresponding point, to gether with the position of the point on this segment. + + If the deformer is closed, do computations modulo the total length. + """ + if self.skelcompIsClosed: + l = l % sum(self.lengths) + if l <= 0: + return 0, l / self.lengths[0] + i = 0 + while (i < len(self.lengths)) and (self.lengths[i] <= l): + l -= self.lengths[i] + i += 1 + t = l / self.lengths[min(i, len(self.lengths) - 1)] + return i, t + + def localTransformAt(self, s, follow=True): + """ + receives a length, and returns the corresponding point and tangent of self.skelcomp + if follow is set to false, returns only the translation + """ + i, t = self.lengthtotime(s) + if i == len(self.skelcomp) - 1: + x, y = bezier.between_point(self.skelcomp[i - 1], self.skelcomp[i], 1 + t) + dx = (self.skelcomp[i][0] - self.skelcomp[i - 1][0]) / self.lengths[-1] + dy = (self.skelcomp[i][1] - self.skelcomp[i - 1][1]) / self.lengths[-1] + else: + x, y = bezier.between_point(self.skelcomp[i], self.skelcomp[i + 1], t) + dx = (self.skelcomp[i + 1][0] - self.skelcomp[i][0]) / self.lengths[i] + dy = (self.skelcomp[i + 1][1] - self.skelcomp[i][1]) / self.lengths[i] + if follow: + mat = [[dx, -dy, x], [dy, dx, y]] + else: + mat = [[1, 0, x], [0, 1, y]] + return mat + + def effect(self): + + if len(self.options.ids) < 2: + inkex.errormsg(_("This extension requires two selected paths.")) + return + self.prepareSelectionList() + + # center at (0,0) + bbox = self.patternNode.bounding_box() + mat = [[1, 0, -bbox.center.x], [0, 1, -bbox.center.y]] + if self.options.vertical: + bbox = BoundingBox(-bbox.y, bbox.x) + mat = (Transform([[0, -1, 0], [1, 0, 0]]) * Transform(mat)).matrix + mat[1][2] += self.options.noffset + self.patternNode.transform *= mat + + width = bbox.width + dx = width + self.options.space + + # check if group and expand it + patternList = [] + if self.options.grouppick and isinstance(self.patternNode, Group): + mat = self.patternNode.transform + for child in self.patternNode: + child.transform *= mat + patternList.append(child) + else: + patternList.append(self.patternNode) + # inkex.debug(patternList) + + counter = 0 + for skelnode in self.skeletons.values(): + self.curSekeleton = skelnode.path.to_superpath() + for comp in self.curSekeleton: + self.skelcomp, self.lengths = linearize(comp) + # !!!!>----> TODO: really test if path is closed! end point==start point is not enough! + self.skelcompIsClosed = (self.skelcomp[0] == self.skelcomp[-1]) + + length = sum(self.lengths) + if self.options.stretch: + dx = width + self.options.space + n = int((length - self.options.toffset + self.options.space) / dx) + if n > 0: + dx = (length - self.options.toffset) / n + + xoffset = self.skelcomp[0][0] - bbox.x.minimum + self.options.toffset + yoffset = self.skelcomp[0][1] - bbox.y.center - self.options.noffset + + s = self.options.toffset + while s <= length: + mat = self.localTransformAt(s, self.options.follow) + if self.options.pickmode == "rand": + clone = copy.deepcopy(patternList[random.randint(0, len(patternList) - 1)]) + + if self.options.pickmode == "seq": + clone = copy.deepcopy(patternList[counter]) + counter = (counter + 1) % len(patternList) + + # !!!--> should it be given an id? + # seems to work without this!?! + myid = patternList[random.randint(0, len(patternList) - 1)].tag.split('}')[-1] + clone.set("id", self.svg.get_unique_id(myid)) + self.gNode.append(clone) + + clone.transform *= mat + s += dx + + self.patternNode.getparent().remove(self.patternNode) + + +if __name__ == '__main__': + PathScatter().run() diff --git a/share/extensions/pdflatex.inx b/share/extensions/pdflatex.inx new file mode 100644 index 0000000..30900ee --- /dev/null +++ b/share/extensions/pdflatex.inx @@ -0,0 +1,19 @@ + + + LaTeX (pdflatex) + org.inkscape.generate.pdf_latex + pdflatex + \(\displaystyle\frac{\pi^2}{6}=\lim_{n \to \infty}\sum_{k=1}^n \frac{1}{k^2}\) + + + all + + + + + + + + diff --git a/share/extensions/pdflatex.py b/share/extensions/pdflatex.py new file mode 100755 index 0000000..9f02d5f --- /dev/null +++ b/share/extensions/pdflatex.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2019 Marc Jeanmougin +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +# +""" +Generate Latex via a PDF using pdflatex +""" + +import os + +import inkex +from inkex.base import TempDirMixin +from inkex.command import call, inkscape +from inkex import load_svg, ShapeElement, Defs + +class PdfLatex(TempDirMixin, inkex.GenerateExtension): + """ + Use pdflatex to generate LaTeX, this whole hack is required because + we don't want to open a LaTeX document as a document, but as a + generated fragment (like import, but done manually). + """ + def add_arguments(self, pars): + pars.add_argument('--formule', type=str, default='') + pars.add_argument('--packages', type=str, default='') + + def generate(self): + tex_file = os.path.join(self.tempdir, 'input.tex') + pdf_file = os.path.join(self.tempdir, 'input.pdf') # Auto-generate by pdflatex + svg_file = os.path.join(self.tempdir, 'output.svg') + + with open(tex_file, 'w') as fhl: + self.write_latex(fhl) + + call('pdflatex', tex_file,\ + output_directory=self.tempdir,\ + halt_on_error=True, oldie=True) + + inkscape(pdf_file, export_filename=svg_file, pdf_page=1, + pdf_poppler=True, export_type="svg") + + with open(svg_file, 'r') as fhl: + svg = load_svg(fhl).getroot() + svg.set_random_ids(backlinks=True) + for child in svg: + if isinstance(child, ShapeElement): + yield child + elif isinstance(child, Defs): + for def_child in child: + #def_child.set_random_id() + self.svg.defs.append(def_child) + + def write_latex(self, stream): + """Takes a forumle and wraps it in latex""" + stream.write(r"""%% processed with pdflatex.py +\documentclass{minimal} +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{amsfonts} +""") + for package in self.options.packages.split(','): + if package: + stream.write('\\usepackage{{{}}}\n'.format(package)) + stream.write("\n\\begin{document}\n") + stream.write(self.options.formule) + stream.write("\n\\end{document}\n") + +if __name__ == '__main__': + PdfLatex().run() diff --git a/share/extensions/perfectboundcover.inx b/share/extensions/perfectboundcover.inx new file mode 100644 index 0000000..69ed244 --- /dev/null +++ b/share/extensions/perfectboundcover.inx @@ -0,0 +1,41 @@ + + + Perfect-Bound Cover Template + org.inkscape.effects.perfect_bound_cover + + 6 + 9 + 64 + true + + + + + + + + + 0 + + + + + + + + + 0 + .25 + + + all + + + + + + + + diff --git a/share/extensions/perfectboundcover.py b/share/extensions/perfectboundcover.py new file mode 100755 index 0000000..99314a2 --- /dev/null +++ b/share/extensions/perfectboundcover.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2007 John Bintz, jcoswell@cosellproductions.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Greate perfect bound cover +""" + +import inkex +from inkex import Guide + + +def caliper_to_ppi(caliper): + return 2 / caliper + + +def bond_weight_to_ppi(bond_weight): + return caliper_to_ppi(bond_weight * .0002) + + +def points_to_ppi(points): + return caliper_to_ppi(points / 1000.0) + + +class PerfectBoundCover(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("--width", type=float, default=6.0, help="cover width (in)") + pars.add_argument("--height", type=float, default=9.0, help="cover height (in)") + pars.add_argument("--pages", type=int, default=64, help="number of pages") + pars.add_argument("--paperthicknessmeasurement", default=100.0, + help="paper thickness measurement") + pars.add_argument("--paperthickness", type=float, default=0.0, help="paper thickness") + pars.add_argument("--coverthicknessmeasurement", default=100.0, + help="cover thickness measurement") + pars.add_argument("--coverthickness", type=float, default=0.0, help="cover thickness") + pars.add_argument("--bleed", type=float, default=0.25, help="cover bleed (in)") + pars.add_argument("--removeguides", type=inkex.Boolean, default=False, help="remove guide") + + def effect(self): + switch = { + "ppi": lambda x: x, + "caliper": caliper_to_ppi, + "bond_weight": bond_weight_to_ppi, + "points": points_to_ppi, + "width": lambda x: x + } + + if self.options.paperthickness > 0: + if self.options.paperthicknessmeasurement == "width": + paper_spine = self.options.paperthickness + else: + paper_spine = self.options.pages / switch[self.options.paperthicknessmeasurement](self.options.paperthickness) + else: + paper_spine = 0 + + if self.options.coverthickness > 0: + if self.options.coverthicknessmeasurement == "width": + cover_spine = self.options.coverthickness + else: + cover_spine = 4.0 / switch[self.options.coverthicknessmeasurement](self.options.coverthickness) + else: + cover_spine = 0 + + spine_width = paper_spine + cover_spine + + document_width = (self.options.bleed + self.options.width * 2) + spine_width + document_height = self.options.bleed * 2 + self.options.height + + root = self.document.getroot() + + root.set("width", "%sin" % document_width) + root.set("height", "%sin" % document_height) + + guides = [] + + guides.append(["horizontal", self.options.bleed]) + guides.append(["horizontal", document_height - self.options.bleed]) + guides.append(["vertical", self.options.bleed]) + guides.append(["vertical", document_width - self.options.bleed]) + guides.append(["vertical", (document_width / 2) - (spine_width / 2)]) + guides.append(["vertical", (document_width / 2) + (spine_width / 2)]) + + namedview = self.svg.namedview + if namedview is not None: + if self.options.removeguides: + for node in namedview.get_guides(): + node.delete() + for guide in guides: + newguide = namedview.add(Guide()) + newguide.set("orientation", guide[0]) + newguide.set("position", "%f" % (guide[1] * 96)) + +if __name__ == '__main__': + PerfectBoundCover().run() diff --git a/share/extensions/perspective.inx b/share/extensions/perspective.inx new file mode 100644 index 0000000..35775a2 --- /dev/null +++ b/share/extensions/perspective.inx @@ -0,0 +1,14 @@ + + + Perspective + org.ekips.filter.perspective + + path + + + + + + diff --git a/share/extensions/perspective.py b/share/extensions/perspective.py new file mode 100755 index 0000000..88c3632 --- /dev/null +++ b/share/extensions/perspective.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" +Perspective approach & math by Dmitry Platonov, shadowjack@mail.ru, 2006 +""" + +import inkex +from inkex.localization import inkex_gettext as _ + +X, Y = range(2) + +try: + import numpy as np + import numpy.linalg as lin + FLOAT = np.float64 +except ImportError: + np = None + + +class Perspective(inkex.EffectExtension): + """Apply a perspective to a path/group of paths""" + def effect(self): + if np is None: + raise inkex.AbortExtension( + _("Failed to import the numpy or numpy.linalg modules." + " These modules are required by this extension. Please install them." + " On a Debian-like system this can be done with the command, " + "sudo apt-get install python-numpy.")) + if len(self.svg.selection) != 2: + raise inkex.AbortExtension(_("This extension requires two selected objects.")) + + obj, envelope = self.svg.selection.values() + + if isinstance(obj, (inkex.PathElement, inkex.Group)): + if isinstance(envelope, inkex.PathElement): + path = envelope.path.transform(envelope.composed_transform()).to_superpath() + + if len(path) < 1 or len(path[0]) < 4: + raise inkex.AbortExtension( + _("This extension requires that the second path be four nodes long.")) + + dip = np.zeros((4, 2), dtype=FLOAT) + for i in range(4): + dip[i][0] = path[0][i][1][0] + dip[i][1] = path[0][i][1][1] + + # Get bounding box plus any extra composed transform of parents. + bbox = obj.bounding_box(obj.getparent().composed_transform()) + + sip = np.array([ + [bbox.left, bbox.bottom], + [bbox.left, bbox.top], + [bbox.right, bbox.top], + [bbox.right, bbox.bottom]], dtype=FLOAT) + else: + if isinstance(envelope, inkex.Group): + raise inkex.AbortExtension(_("The second selected object is a group, not a" + " path.\nTry using Object->Ungroup.")) + raise inkex.AbortExtension(_("The second selected object is not a path.\nTry using" + " the procedure Path->Object to Path.")) + else: + raise inkex.AbortExtension(_("The first selected object is neither a path nor a group.\nTry using" + " the procedure Path->Object to Path.")) + + solmatrix = np.zeros((8, 8), dtype=FLOAT) + free_term = np.zeros(8, dtype=FLOAT) + for i in (0, 1, 2, 3): + solmatrix[i][0] = sip[i][0] + solmatrix[i][1] = sip[i][1] + solmatrix[i][2] = 1 + solmatrix[i][6] = -dip[i][0] * sip[i][0] + solmatrix[i][7] = -dip[i][0] * sip[i][1] + solmatrix[i + 4][3] = sip[i][0] + solmatrix[i + 4][4] = sip[i][1] + solmatrix[i + 4][5] = 1 + solmatrix[i + 4][6] = -dip[i][1] * sip[i][0] + solmatrix[i + 4][7] = -dip[i][1] * sip[i][1] + free_term[i] = dip[i][0] + free_term[i + 4] = dip[i][1] + + res = lin.solve(solmatrix, free_term) + projmatrix = np.array([ + [res[0], res[1], res[2]], + [res[3], res[4], res[5]], + [res[6], res[7], 1.0]], dtype=FLOAT) + + self.process_object(obj, projmatrix) + + def process_object(self, obj, matrix): + if isinstance(obj, inkex.PathElement): + self.process_path(obj, matrix) + elif isinstance(obj, inkex.Group): + self.process_group(obj, matrix) + + def process_group(self, group, matrix): + """Go through all groups to process all paths inside them""" + for node in group: + self.process_object(node, matrix) + + def process_path(self, element, matrix): + """Apply the transformation to the selected path""" + point = element.path.to_absolute().transform(element.composed_transform()).to_superpath() + for subs in point: + for csp in subs: + csp[0] = self.project_point(csp[0], matrix) + csp[1] = self.project_point(csp[1], matrix) + csp[2] = self.project_point(csp[2], matrix) + element.path = inkex.Path(point).transform(-element.composed_transform()) + + @staticmethod + def project_point(point, matrix): + """Apply the matrix to the given point""" + return [(point[X] * matrix[0][0] + point[Y] * matrix[0][1] + matrix[0][2]) / + (point[X] * matrix[2][0] + point[Y] * matrix[2][1] + matrix[2][2]), + (point[X] * matrix[1][0] + point[Y] * matrix[1][1] + matrix[1][2]) / + (point[X] * matrix[2][0] + point[Y] * matrix[2][1] + matrix[2][2])] + + +if __name__ == '__main__': + Perspective().run() diff --git a/share/extensions/pixelsnap.inx b/share/extensions/pixelsnap.inx new file mode 100644 index 0000000..bac2c3a --- /dev/null +++ b/share/extensions/pixelsnap.inx @@ -0,0 +1,20 @@ + + + PixelSnap + org.inkscape.filter.pixel_snap + + + true + true + + 0.5 + + all + + + + + + diff --git a/share/extensions/pixelsnap.py b/share/extensions/pixelsnap.py new file mode 100755 index 0000000..670098d --- /dev/null +++ b/share/extensions/pixelsnap.py @@ -0,0 +1,451 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (c) 2009 Bryan Hoyt (MIT License) +# 2011 Nicolas Dufour +# 2013 Johan B. C. Engelen +# 2019 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +TODO: mark elements that have previously been snapped, along with the settings + used, so that the same settings can be used for that element next time when + it's selected as part of a group (and add an option to the extension dialog + "Use previous/default settings" which is selected by default) + +TODO: make elem_offset return [x_offset, y_offset] so we can handle non-symetric scaling + => will probably need to take into account non-symetric scaling on stroke-widths, + too (horizontal vs vertical strokes) + +TODO: Transforming points isn't quite perfect, to say the least. In particular, + when translating a point on a bezier curve, we translate the handles by the same amount. + BUT, some handles that are attached to a particular point are conceptually + handles of the prev/next node. + Best way to fix it would be to keep a list of the fractional_offsets[] of + each point, without transforming anything. Then go through each point and + transform the appropriate handle according to the relevant fraction_offset + in the list. + + i.e. calculate first, then modify. + + In fact, that might be a simpler algorithm anyway -- it avoids having + to keep track of all the first_xy/next_xy guff. + +Note: This doesn't work very well on paths which have both straight segments + and curved segments. + The biggest three problems are: + a) we don't take handles into account (segments where the nodes are + aligned are always treated as straight segments, even where the + handles make it curve) + b) when we snap a straight segment right before/after a curve, it + doesn't make any attempt to keep the transition from the straight + segment to the curve smooth. + c) no attempt is made to keep equal widths equal. (or nearly-equal + widths nearly-equal). For example, font strokes. + +Note: Paths that have curves & arcs on some sides of the bounding box won't + be snapped correctly on that side of the bounding box, and nor will they + be translated/resized correctly before the path is modified. Doesn't affect + most applications of this extension, but it highlights the fact that we + take a geometrically simplistic approach to inspecting & modifying the path. +""" + +from __future__ import division +from __future__ import print_function + +import sys + +import inkex +from inkex import PathElement, Group, Image, Rectangle, ShapeElement, Transform + +Precision = 5 # number of digits of precision for comparing float numbers + +class TransformError(Exception): + pass + +def transform_point(transform, pt, inverse=False): + """apply_to_point with inbuilt inverse""" + if inverse: + transform = -transform + return transform.apply_to_point(pt) + +def transform_dimensions(transform, width=None, height=None, inverse=False): + """ Dimensions don't get translated. I'm not sure how much diff rotate/skew + makes in this context, but we currently ignore anything besides scale. + """ + if inverse: + transform = -transform + + if width is not None: + width *= transform.a + if height is not None: + height *= transform.d + + if width is not None and height is not None: + return width, height + if width is not None: + return width + if height is not None: + return height + + +class PixelSnap(inkex.EffectExtension): + def add_arguments(self, pars): + """Add inx options""" + pars.add_argument("-a", "--snap_ancestors", type=inkex.Boolean, default=True,\ + help="Snap unselected ancestors' translations "\ + "(groups, layers, document height) first") + pars.add_argument("-t", "--ancestor_offset", type=inkex.Boolean, default=True,\ + help="Calculate offset relative to unselected ancestors' "\ + "transforms (includes document height offset)") + pars.add_argument("-g", "--max_gradient", type=float, default=0.5,\ + help="Maximum slope to consider straight (%)") + + def vertical(self, pt1, pt2): + hlen = abs(pt1[0] - pt2[0]) + vlen = abs(pt1[1] - pt2[1]) + if vlen == 0 and hlen == 0: + return True + elif vlen == 0: + return False + return (hlen / vlen) < self.options.max_gradient / 100 + + def horizontal(self, pt1, pt2): + hlen = round(abs(pt1[0] - pt2[0]), Precision) + vlen = round(abs(pt1[1] - pt2[1]), Precision) + if hlen == 0 and vlen == 0: + return True + elif hlen == 0: + return False + return (vlen / hlen) < self.options.max_gradient / 100 + + def stroke_width_offset(self, elem, parent_transform=None): + """ Returns the amount the bounding-box is offset due to the stroke-width. + Transform is taken into account. + """ + stroke_width = self.stroke_width(elem) + if stroke_width == 0: + return 0 # if there's no stroke, no need to worry about the transform + + transform = (elem.transform * Transform(parent_transform)) + + if abs(abs(transform.a) - abs(transform.d)) > (10 ** -Precision): + raise TransformError("Selection contains non-symetric scaling") # *** wouldn't be hard to get around this by calculating vertical_offset & horizontal_offset separately, maybe 1 functions, or maybe returning a tuple + + stroke_width = transform_dimensions(transform, width=stroke_width) + + return stroke_width / 2 + + def stroke_width(self, elem, setval=None): + """Get/set stroke-width in pixels, untransformed""" + style = dict(inkex.Style.parse_str(elem.attrib.get('style', ''))) + stroke = style.get('stroke', None) + if stroke == 'none': + stroke = None + + stroke_width = 0 + if stroke and setval is None: + stroke_width = self.svg.unittouu(style.get('stroke-width', '').strip()) + + if setval: + style['stroke-width'] = str(setval) + elem.attrib['style'] = str(inkex.Style(style)) + else: + return stroke_width + + def transform_path_node(self, transform, path, i): + """ Modifies a segment so that every point is transformed, including handles + """ + segtype = path[i][0].lower() + + if segtype == 'z': + return + elif segtype == 'h': + path[i][1][0] = transform_point(transform, [path[i][1][0], 0])[0] + elif segtype == 'v': + path[i][1][0] = transform_point(transform, [0, path[i][1][0]])[1] + else: + first_coordinate = 0 + if segtype == 'a': + first_coordinate = 5 # for elliptical arcs, skip the radius x/y, rotation, large-arc, and sweep + for j in range(first_coordinate, len(path[i][1]), 2): + x, y = path[i][1][j], path[i][1][j + 1] + x, y = transform_point(transform, (x, y)) + path[i][1][j] = x + path[i][1][j + 1] = y + + def pathxy(self, path, i, setval=None): + """ Return the endpoint of the given path segment. + Inspects the segment type to know which elements are the endpoints. + """ + segtype = path[i][0].lower() + x = y = 0 + + if segtype == 'z': + i = 0 + + if segtype == 'h': + if setval: + path[i][1][0] = setval[0] + else: + x = path[i][1][0] + + elif segtype == 'v': + if setval: + path[i][1][0] = setval[1] + else: + y = path[i][1][0] + else: + if setval and segtype != 'z': + path[i][1][-2] = setval[0] + path[i][1][-1] = setval[1] + else: + x = path[i][1][-2] + y = path[i][1][-1] + + if setval is None: + return [x, y] + + def snap_path_scale(self, elem, parent_transform=None): + + path = elem.original_path.to_arrays() + transform = elem.transform * Transform(parent_transform) + bbox = elem.bounding_box() + + # In case somebody tries to snap a 0-high element, + # or a curve/arc with all nodes in a line, and of course + # because we should always check for divide-by-zero! + if not bbox.width or not bbox.height: + return + + width, height = bbox.width, bbox.height + min_xy, max_xy = bbox.minimum, bbox.maximum + rescale = round(width) / width, round(height) / height + + min_xy = transform_point(transform, min_xy, inverse=True) + max_xy = transform_point(transform, max_xy, inverse=True) + + for i in range(len(path)): + translate = Transform(translate=min_xy) + self.transform_path_node(-translate, path, i) # center transform + self.transform_path_node(Transform(scale=rescale), path, i) + self.transform_path_node(translate, path, i) # uncenter transform + + elem.original_path = path + + def snap_path_pos(self, elem, parent_transform=None): + path = elem.original_path.to_arrays() + transform = elem.transform * Transform(parent_transform) + bbox = elem.bounding_box() + min_xy, max_xy = bbox.minimum, bbox.maximum + + fractional_offset = min_xy[0] - round(min_xy[0]), min_xy[1] - round(min_xy[1]) - self.document_offset + fractional_offset = transform_dimensions(transform, fractional_offset[0], fractional_offset[1], inverse=True) + + for i in range(len(path)): + self.transform_path_node(-Transform(translate=fractional_offset), path, i) + + path = str(inkex.Path(path)) + if elem.get('inkscape:original-d'): + elem.set('inkscape:original-d', path) + else: + elem.set('d', path) + + def snap_transform(self, elem): + # Only snaps the x/y translation of the transform, nothing else. + # Doesn't take any parent_transform into account -- assumes + # that the parent's transform has already been snapped. + transform = elem.transform + # if we've got any skew/rotation, get outta here + if transform.c or transform.b: + raise TransformError("TR: Selection contains transformations with skew/rotation") + + trm = list(transform.to_hexad()) + trm[4] = round(transform.e) + trm[5] = round(transform.f) + elem.transform *= Transform(trm) + + def snap_stroke(self, elem, parent_transform=None): + transform = elem.transform * Transform(parent_transform) + + stroke_width = self.stroke_width(elem) + if (stroke_width == 0): return # no point raising a TransformError if there's no stroke to snap + + if abs(abs(transform.a) - abs(transform.d)) > (10**-Precision): + raise TransformError("Selection contains non-symetric scaling, can't snap stroke width") + + if stroke_width: + stroke_width = transform_dimensions(transform, width=stroke_width) + stroke_width = round(stroke_width) + stroke_width = transform_dimensions(transform, width=stroke_width, inverse=True) + self.stroke_width(elem, stroke_width) + + def snap_path(self, elem, parent_transform=None): + path = elem.original_path.to_arrays() + + transform = (elem.transform * Transform(parent_transform)) + + if transform.c or transform.b: # if we've got any skew/rotation, get outta here + raise TransformError("Path: Selection contains transformations with skew/rotation") + + offset = self.stroke_width_offset(elem, parent_transform) % 1 + + prev_xy = self.pathxy(path, -1) + first_xy = self.pathxy(path, 0) + for i in range(len(path)): + segtype = path[i][0].lower() + xy = self.pathxy(path, i) + if segtype == 'z': + xy = first_xy + if (i == len(path) - 1) or \ + ((i == len(path) - 2) and path[-1][0].lower() == 'z'): + next_xy = first_xy + else: + next_xy = self.pathxy(path, i + 1) + + if not (xy and prev_xy and next_xy): + prev_xy = xy + continue + + xy_untransformed = tuple(xy) + xy = list(transform_point(transform, xy)) + prev_xy = transform_point(transform, prev_xy) + next_xy = transform_point(transform, next_xy) + + on_vertical = on_horizontal = False + + if self.horizontal(xy, prev_xy): + # on 2-point paths, first.next==first.prev==last and last.next==last.prev==first + if len(path) > 2 or i == 0: + # make the almost-equal values equal, so they round in the same direction + xy[1] = prev_xy[1] + on_horizontal = True + if self.horizontal(xy, next_xy): + on_horizontal = True + + if self.vertical(xy, prev_xy): # as above + if len(path) > 2 or i == 0: + xy[0] = prev_xy[0] + on_vertical = True + if self.vertical(xy, next_xy): + on_vertical = True + + prev_xy = tuple(xy_untransformed) + + fractional_offset = [0, 0] + if on_vertical: + fractional_offset[0] = xy[0] - (round(xy[0] - offset) + offset) + if on_horizontal: + fractional_offset[1] = xy[1] - (round(xy[1] - offset) + offset)\ + - self.document_offset + + fractional_offset = transform_dimensions( + transform, fractional_offset[0], fractional_offset[1], inverse=True) + self.transform_path_node(-Transform(translate=fractional_offset), path, i) + + elem.original_path = path + + def snap_rect(self, elem, parent_transform=None): + transform = (elem.transform * Transform(parent_transform)) + + if transform.c or transform.b: # if we've got any skew/rotation, get outta here + raise TransformError("Rect: Selection contains transformations with skew/rotation") + + offset = self.stroke_width_offset(elem, parent_transform) % 1 + + width = self.svg.unittouu(elem.attrib['width']) + height = self.svg.unittouu(elem.attrib['height']) + x = self.svg.unittouu(elem.attrib['x']) + y = self.svg.unittouu(elem.attrib['y']) + + width, height = transform_dimensions(transform, width, height) + x, y = transform_point(transform, [x, y]) + + # Snap to the nearest pixel + height = round(height) + width = round(width) + x = round(x - offset) + offset # If there's a stroke of non-even width, it's shifted by half a pixel + y = round(y - offset) + offset + + width, height = transform_dimensions(transform, width, height, inverse=True) + x, y = transform_point(transform, [x, y], inverse=True) + + y += self.document_offset / transform.d + + # Position the elem at the newly calculate values + elem.attrib['width'] = str(width) + elem.attrib['height'] = str(height) + elem.attrib['x'] = str(x) + elem.attrib['y'] = str(y) + + def snap_image(self, elem, parent_transform=None): + self.snap_rect(elem, parent_transform) + + def pixel_snap(self, elem, parent_transform=None): + if not isinstance(elem, (Group, Image, Rectangle, PathElement)): + return + + if isinstance(elem, Group): + self.snap_transform(elem) + transform = elem.transform * Transform(parent_transform) + for child in elem: + try: + self.pixel_snap(child, transform) + except TransformError as err: + raise inkex.AbortExtension(str(err)) + return + + # If we've been given a parent_transform, we can assume that the + # parents have already been snapped, or don't need to be + if self.options.snap_ancestors and parent_transform is None: + # Loop through ancestors from outermost to innermost, excluding this element. + for child in elem.ancestors().values(): + self.snap_transform(child) + + # If we haven't been given a parent_transform, then we need to calculate it + if self.options.ancestor_offset and parent_transform is None: + if isinstance(elem.getparent(), ShapeElement): + parent_transform = elem.getparent().composed_transform() + + self.snap_transform(elem) + try: + self.snap_stroke(elem, parent_transform) + except TransformError as err: + raise inkex.AbortExtension(str(err)) + + if isinstance(elem, PathElement): + self.snap_path_scale(elem, parent_transform) + self.snap_path_pos(elem, parent_transform) + self.snap_path(elem, parent_transform) # would be quite useful to make this an option, as scale/pos alone doesn't mess with the path itself, and works well for sans-serif text + elif isinstance(elem, Rectangle): + self.snap_rect(elem, parent_transform) + elif isinstance(elem, Image): + self.snap_image(elem, parent_transform) + + def effect(self): + svg = self.document.getroot() + + self.document_offset = self.svg.unittouu(svg.attrib['height']) % 1 # although SVG units are absolute, the elements are positioned relative to the top of the page, rather than zero + + for elem in self.svg.selected.values(): + try: + self.pixel_snap(elem) + except TransformError as err: + raise inkex.AbortExtension(str(err)) + + +if __name__ == '__main__': + PixelSnap().run() diff --git a/share/extensions/plotter.inx b/share/extensions/plotter.inx new file mode 100644 index 0000000..366a133 --- /dev/null +++ b/share/extensions/plotter.inx @@ -0,0 +1,104 @@ + + + Plot + org.ekips.filter.plot + hpgl_decoder.py + hpgl_encoder.py + + + + + + + + /dev/usb/lp2 + COM1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1016.0 + 1016.0 + 1 + 0 + 0 + + + + + + + false + false + false + + + + + 1.00 + 0.25 + true + 1.2 + true + + + + + + path + + + + + + diff --git a/share/extensions/plotter.py b/share/extensions/plotter.py new file mode 100755 index 0000000..afcbccd --- /dev/null +++ b/share/extensions/plotter.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2013 +# 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import inkex +from inkex.ports import Serial +from inkex.localization import inkex_gettext as _ + +import hpgl_encoder + +class Plot(inkex.EffectExtension): + """Generate a plot in HPGL output""" + def add_arguments(self, pars): + pars.add_argument('--tab') + pars.add_argument('--parallelPort', default='/dev/usb/lp2', help='Parallel port') + pars.add_argument('--serialPort', default='COM1', help='Serial port') + pars.add_argument('--serialBaudRate', default='9600', help='Serial Baud rate') + pars.add_argument('--serialByteSize', default='eight', help='Serial byte size') + pars.add_argument('--serialStopBits', default='one', help='Serial stop bits') + pars.add_argument('--serialParity', default='none', help='Serial parity') + pars.add_argument('--serialFlowControl', default='0', help='Flow control') + pars.add_argument('--resolutionX', type=float, default=1016.0, help='Resolution X (dpi)') + pars.add_argument('--resolutionY', type=float, default=1016.0, help='Resolution Y (dpi)') + pars.add_argument('--pen', type=int, default=1, help='Pen number') + pars.add_argument('--force', type=int, default=24, help='Pen force (g)') + pars.add_argument('--speed', type=int, default=20, help='Pen speed (cm/s)') + pars.add_argument('--orientation', default='90', help='Rotation (Clockwise)') + pars.add_argument('--mirrorX', type=inkex.Boolean, default=False, help='Mirror X axis') + pars.add_argument('--mirrorY', type=inkex.Boolean, default=False, help='Mirror Y axis') + pars.add_argument('--center', type=inkex.Boolean, default=False, help='Center zero point') + pars.add_argument('--overcut', type=float, default=1.0, help='Overcut (mm)') + pars.add_argument('--precut', type=inkex.Boolean, default=True, help='Use precut') + pars.add_argument('--flat', type=float, default=1.2, help='Curve flatness') + pars.add_argument('--autoAlign', type=inkex.Boolean, default=True, help='Auto align') + pars.add_argument('--toolOffset', type=float, default=0.25,\ + help='Tool (Knife) offset correction (mm)') + pars.add_argument('--portType', type=self.arg_method('to'),\ + default=self.to_serial, dest="to_port", help='Port type') + pars.add_argument('--commandLanguage', type=self.arg_method('convert'),\ + default=self.convert_hpgl, dest="to_language", help='Command Language Filter') + + def effect(self): + # get hpgl data + encoder = hpgl_encoder.hpglEncoder(self) + try: + self.options.to_port(self.options.to_language(encoder.getHpgl())) + except hpgl_encoder.NoPathError: + raise inkex.AbortExtension(_("No paths where found. Please convert objects to paths.")) + + def convert_hpgl(self, hpgl): + """Convert raw HPGL to HPGL""" + init = 'IN' + if self.options.force > 0: + init += ';FS%d' % self.options.force + if self.options.speed > 0: + init += ';VS%d' % self.options.speed + return init + hpgl + ';PU0,0;SP0;IN; ' + + def convert_dmpl(self, hpgl): + """Convert HPGL to DMPL""" + # ;: = Initialise plotter + # H = Home position + # A = Absolute pen positioning + # Ln = Line type + # Pn = Pen select + # Vn = velocity + # ECn = Coordinate addressing, 1: 0.001 inch, 5: 0.005 inch, M: 0.1 mm + # D = Pen down + # U = Pen up + # Z = Reset plotter + # n,n, = Coordinate pair + hpgl = hpgl.replace(';', ',') + hpgl = hpgl.replace('SP', 'P') + hpgl = hpgl.replace('PU', 'U') + hpgl = hpgl.replace('PD', 'D') + init = ';:HAL0' + if self.options.speed > 0: + init += 'V%d' % self.options.speed + init += 'EC1' + return init + hpgl[1:] + ',P0,U0,0,Z ' + + def convert_knk(self, hpgl): + """Convert HPGL to KNK Plotter Language""" + init = 'ZG' + if self.options.force > 0: + init += ';FS%d' % self.options.force + if self.options.speed > 0: + init += ';VS%d' % self.options.speed + return init + hpgl + ';SP0;PU0,0;@ ' + + def to_parallel(self, hpgl): + """Output to hgpl to a parallel port""" + port = open(self.options.parallelPort, "wb") + port.write(hpgl.encode('utf8')) + port.close() + + def to_serial(self, hpgl): + """Output to hgpl to a serial port""" + with Serial(self.options.serialPort, + baud=self.options.serialBaudRate, + stop=self.options.serialStopBits, + size=self.options.serialByteSize, + flow=self.options.serialFlowControl, + parity=self.options.serialParity, + ) as comx: + comx.write(hpgl.encode('utf8')) + +if __name__ == '__main__': + Plot().run() diff --git a/share/extensions/polyhedron_3d.inx b/share/extensions/polyhedron_3d.inx new file mode 100644 index 0000000..a9208f4 --- /dev/null +++ b/share/extensions/polyhedron_3d.inx @@ -0,0 +1,97 @@ + + + 3D Polyhedron + org.inkscape.render.poly_3d + + + + + + + + + + + + + + + + + + + + + + great_rhombicuboct.obj + + + + false + + + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + 100 + 255 + 0 + 0 + 100 + 100 + 2 + true + 1 + 1 + -2 + + + + + + false + + + + + + + + all + + + + + + diff --git a/share/extensions/polyhedron_3d.py b/share/extensions/polyhedron_3d.py new file mode 100755 index 0000000..ef91f87 --- /dev/null +++ b/share/extensions/polyhedron_3d.py @@ -0,0 +1,389 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2007 John Beard john.j.beard@gmail.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This extension draws 3d objects from a Wavefront .obj 3D file stored in a local folder +Many settings for appearance, lighting, rotation, etc are available. + + ^y + | + __--``| |_--``| __-- + __--`` | __--``| |_--`` + | z | | |_--``| + | <----|--------|-----_0-----|---------------- + | | |_--`` | | + | __--`` <-``| |_--`` + |__--`` x |__--``| + IMAGE PLANE SCENE| + | + + Vertices are given as "v" followed by three numbers (x,y,z). + All files need a vertex list + v x.xxx y.yyy z.zzz + + Faces are given by a list of vertices + (vertex 1 is the first in the list above, 2 the second, etc): + f 1 2 3 + + Edges are given by a list of vertices. These will be broken down + into adjacent pairs automatically. + l 1 2 3 + + Faces are rendered according to the painter's algorithm and perhaps + back-face culling, if selected. The parameter to sort the faces by + is user-selectable between max, min and average z-value of the vertices +""" + +import os +from math import acos, cos, floor, pi, sin, sqrt + +import inkex +from inkex.utils import pairwise +from inkex import Group, Circle +from inkex.paths import Move, Line + +try: + import numpy +except: + numpy = None + +def draw_circle(r, cx, cy, width, fill, name, parent): + """Draw an SVG circle""" + circle = parent.add(Circle(cx=str(cx), cy=str(cy), r=str(r))) + circle.style = {'stroke': '#000000', 'stroke-width': str(width), 'fill': fill} + circle.label = name + + +def draw_line(x1, y1, x2, y2, width, name, parent): + elem = parent.add(inkex.PathElement()) + elem.style = {'stroke': '#000000', 'stroke-width': str(width), 'fill': 'none', + 'stroke-linecap': 'round'} + elem.set('inkscape:label', name) + elem.path = [Move(x1, y1), Line(x2, y2)] + +def draw_poly(pts, face, st, name, parent): + """Draw polygone""" + style = {'stroke': '#000000', 'stroke-width': str(st.th), 'stroke-linejoin': st.linejoin, + 'stroke-opacity': st.s_opac, 'fill': st.fill, 'fill-opacity': st.f_opac} + path = inkex.Path() + for facet in face: + if not path: # for first point + path.append(Move(pts[facet - 1][0], -pts[facet - 1][1])) + else: + path.append(Line(pts[facet - 1][0], -pts[facet - 1][1])) + path.close() + + poly = parent.add(inkex.PathElement()) + poly.label = name + poly.style = style + poly.path = path + + +def draw_edges(edge_list, pts, st, parent): + for edge in edge_list: # for every edge + pt_1 = pts[edge[0] - 1][0:2] # the point at the start + pt_2 = pts[edge[1] - 1][0:2] # the point at the end + name = 'Edge' + str(edge[0]) + '-' + str(edge[1]) + draw_line(pt_1[0], -pt_1[1], pt_2[0], -pt_2[1], st.th, name, parent) + + +def draw_faces(faces_data, pts, obj, shading, fill_col, st, parent): + for face in faces_data: # for every polygon that has been sorted + if shading: + st.fill = get_darkened_colour(fill_col, face[1] / pi) # darken proportionally to angle to lighting vector + else: + st.fill = get_darkened_colour(fill_col, 1) # do not darken colour + + face_no = face[3] # the number of the face to draw + draw_poly(pts, obj.fce[face_no], st, 'Face:' + str(face_no), parent) + + +def get_darkened_colour(rgb, factor): + """return a hex triplet of colour, reduced in lightness 0.0-1.0""" + return '#' + "%02X" % floor(factor * rgb[0]) \ + + "%02X" % floor(factor * rgb[1]) \ + + "%02X" % floor(factor * rgb[2]) # make the colour string + + +def make_rotation_log(options): + """makes a string recording the axes and angles of each rotation, so an object can be repeated""" + return options.r1_ax + str('%.2f' % options.r1_ang) + ':' + \ + options.r2_ax + str('%.2f' % options.r2_ang) + ':' + \ + options.r3_ax + str('%.2f' % options.r3_ang) + ':' + \ + options.r1_ax + str('%.2f' % options.r4_ang) + ':' + \ + options.r2_ax + str('%.2f' % options.r5_ang) + ':' + \ + options.r3_ax + str('%.2f' % options.r6_ang) + +def normalise(vector): + """return the unit vector pointing in the same direction as the argument""" + length = sqrt(numpy.dot(vector, vector)) + return numpy.array(vector) / length + +def get_normal(pts, face): + """normal vector for the plane passing though the first three elements of face of pts""" + return numpy.cross( + (numpy.array(pts[face[0] - 1]) - numpy.array(pts[face[1] - 1])), + (numpy.array(pts[face[0] - 1]) - numpy.array(pts[face[2] - 1])), + ).flatten() + +def get_unit_normal(pts, face, cw_wound): + """ + Returns the unit normal for the plane passing through the + first three points of face, taking account of winding + """ + # if it is clockwise wound, reverse the vector direction + winding = -1 if cw_wound else 1 + return winding * normalise(get_normal(pts, face)) + +def rotate(matrix, rads, axis): + """choose the correct rotation matrix to use""" + if axis == 'x': + trans_mat = numpy.array([ + [1, 0, 0], [0, cos(rads), -sin(rads)], [0, sin(rads), cos(rads)]]) + elif axis == 'y': + trans_mat = numpy.array([ + [cos(rads), 0, sin(rads)], [0, 1, 0], [-sin(rads), 0, cos(rads)]]) + elif axis == 'z': + trans_mat = numpy.array([ + [cos(rads), -sin(rads), 0], [sin(rads), cos(rads), 0], [0, 0, 1]]) + return numpy.matmul(trans_mat, matrix) + +class Style(object): # container for style information + def __init__(self, options): + self.th = options.th + self.fill = '#ff0000' + self.col = '#000000' + self.r = 2 + self.f_opac = str(options.f_opac / 100.0) + self.s_opac = str(options.s_opac / 100.0) + self.linecap = 'round' + self.linejoin = 'round' + + +class WavefrontObj(object): + """Wavefront based 3d object defined by the vertices and the faces (eg a polyhedron)""" + name = property(lambda self: self.meta.get('name', None)) + + def __init__(self, filename): + self.meta = { + 'name': os.path.basename(filename).rsplit('.', 1)[0] + } + self.vtx = [] + self.edg = [] + self.fce = [] + self._parse_file(filename) + + def _parse_file(self, filename): + if not os.path.isfile(filename): + raise IOError("Can't find wavefront object file {}".format(filename)) + with open(filename, 'r') as fhl: + for line in fhl: + self._parse_line(line.strip()) + + def _parse_line(self, line): + if line.startswith('#'): + if ':' in line: + name, value = line.split(':', 1) + self.meta[name.lower()] = value + elif line: + (kind, line) = line.split(None, 1) + kind_name = 'add_' + kind + if hasattr(self, kind_name): + getattr(self, kind_name)(line) + + @staticmethod + def _parse_numbers(line, typ=str): + # Ignore any slash options and always pick the first one + return [typ(v.split('/')[0]) for v in line.split()] + + def add_v(self, line): + """Add vertex from parsed line""" + vertex = self._parse_numbers(line, float) + if len(vertex) == 3: + self.vtx.append(vertex) + + def add_l(self, line): + """Add line from parsed line""" + vtxlist = self._parse_numbers(line, int) + # we need at least 2 vertices to make an edge + if len(vtxlist) > 1: + # we can have more than one vertex per line - get adjacent pairs + self.edg.append(pairwise(vtxlist)) + + def add_f(self, line): + """Add face from parsed line""" + vtxlist = self._parse_numbers(line, int) + # we need at least 3 vertices to make an edge + if len(vtxlist) > 2: + self.fce.append(vtxlist) + + def get_transformed_pts(self, trans_mat): + """translate vertex points according to the matrix""" + transformed_pts = [] + for vtx in self.vtx: + transformed_pts.append((numpy.matmul(trans_mat, numpy.array(vtx).T)).T.tolist()) + return transformed_pts + + def get_edge_list(self): + """make an edge vertex list from an existing face vertex list""" + edge_list = [] + for face in self.fce: + for j, edge in enumerate(face): + # Ascending order of certices (for duplicate detection) + edge_list.append(sorted([edge, face[(j + 1) % len(face)]])) + return [list(x) for x in sorted(set(tuple(x) for x in edge_list))] + +class Poly3D(inkex.GenerateExtension): + """Generate a polyhedron from a wavefront 3d model file""" + def add_arguments(self, pars): + pars.add_argument("--tab", default="object") + + # MODEL FILE SETTINGS + pars.add_argument("--obj", default='cube') + pars.add_argument("--spec_file", default='great_rhombicuboct.obj') + pars.add_argument("--cw_wound", type=inkex.Boolean, default=True) + pars.add_argument("--type", default='face') + # VEIW SETTINGS + pars.add_argument("--r1_ax", default="x") + pars.add_argument("--r2_ax", default="x") + pars.add_argument("--r3_ax", default="x") + pars.add_argument("--r4_ax", default="x") + pars.add_argument("--r5_ax", default="x") + pars.add_argument("--r6_ax", default="x") + pars.add_argument("--r1_ang", type=float, default=0.0) + pars.add_argument("--r2_ang", type=float, default=0.0) + pars.add_argument("--r3_ang", type=float, default=0.0) + pars.add_argument("--r4_ang", type=float, default=0.0) + pars.add_argument("--r5_ang", type=float, default=0.0) + pars.add_argument("--r6_ang", type=float, default=0.0) + pars.add_argument("--scl", type=float, default=100.0) + # STYLE SETTINGS + pars.add_argument("--show", type=self.arg_method('gen')) + pars.add_argument("--shade", type=inkex.Boolean, default=True) + pars.add_argument("--f_r", type=int, default=255) + pars.add_argument("--f_g", type=int, default=0) + pars.add_argument("--f_b", type=int, default=0) + pars.add_argument("--f_opac", type=int, default=100) + pars.add_argument("--s_opac", type=int, default=100) + pars.add_argument("--th", type=float, default=2) + pars.add_argument("--lv_x", type=float, default=1) + pars.add_argument("--lv_y", type=float, default=1) + pars.add_argument("--lv_z", type=float, default=-2) + pars.add_argument("--back", type=inkex.Boolean, default=False) + pars.add_argument("--z_sort", type=self.arg_method('z_sort'), default=self.z_sort_min) + + def get_filename(self): + """Get the filename for the spec file""" + if self.options.obj == 'from_file': + return self.options.spec_file + moddir = self.ext_path() + return os.path.join(moddir, 'Poly3DObjects', self.options.obj + '.obj') + + def generate(self): + if numpy is None: + raise inkex.AbortExtension("numpy is required.") + so = self.options + + obj = WavefrontObj(self.get_filename()) + + scale = self.svg.unittouu('1px') # convert to document units + st = Style(so) # initialise style + + # we will put all the rotations in the object name, so it can be repeated in + poly = Group.new(obj.name + ':' + make_rotation_log(so)) + (pos_x, pos_y) = self.svg.namedview.center + poly.transform.add_translate(pos_x, pos_y) + poly.transform.add_scale(scale) + + # TRANSFORMATION OF THE OBJECT (ROTATION, SCALE, ETC) + trans_mat = numpy.identity(3, float) # init. trans matrix as identity matrix + for i in range(1, 7): # for each rotation + axis = getattr(so, 'r{}_ax'.format(i)) + angle = getattr(so, 'r{}_ang'.format(i)) * pi / 180 + trans_mat = rotate(trans_mat, angle, axis) + # scale by linear factor (do this only after the transforms to reduce round-off) + trans_mat = trans_mat * so.scl + + # the points as projected in the z-axis onto the viewplane + transformed_pts = obj.get_transformed_pts(trans_mat) + so.show(obj, st, poly, transformed_pts) + return poly + + def gen_vtx(self, obj, st, poly, transformed_pts): + """Generate Vertex""" + for i, pts in enumerate(transformed_pts): + draw_circle(st.r, pts[0], pts[1], st.th, '#000000', 'Point' + str(i), poly) + + def gen_edg(self, obj, st, poly, transformed_pts): + """Generate edges""" + # we already have an edge list + edge_list = obj.edg + if obj.fce: + # we must generate the edge list from the faces + edge_list = obj.get_edge_list() + + draw_edges(edge_list, transformed_pts, st, poly) + + def gen_fce(self, obj, st, poly, transformed_pts): + """Generate face""" + so = self.options + # colour tuple for the face fill + fill_col = (so.f_r, so.f_g, so.f_b) + # unit light vector + lighting = normalise((so.lv_x, -so.lv_y, so.lv_z)) + # we have a face list + if obj.fce: + z_list = [] + + for i, face in enumerate(obj.fce): + # get the normal vector to the face + norm = get_unit_normal(transformed_pts, face, so.cw_wound) + # get the angle between the normal and the lighting vector + angle = acos(numpy.dot(norm, lighting)) + z_sort_param = so.z_sort(transformed_pts, face) + + # include all polygons or just the front-facing ones as needed + if so.back or norm[2] > 0: + # record the maximum z-value of the face and angle to + # light, along with the face ID and normal + z_list.append((z_sort_param, angle, norm, i)) + + z_list.sort(key=lambda x: x[0]) # sort by ascending sort parameter of the face + draw_faces(z_list, transformed_pts, obj, so.shade, fill_col, st, poly) + + else: # we cannot generate a list of faces from the edges without a lot of computation + raise inkex.AbortExtension("Face data not found.") + + @staticmethod + def z_sort_max(pts, face): + """returns the largest z_value of any point in the face""" + return max([pts[facet - 1][2] for facet in face]) + + @staticmethod + def z_sort_min(pts, face): + """returns the smallest z_value of any point in the face""" + return min([pts[facet - 1][2] for facet in face]) + + @staticmethod + def z_sort_cent(pts, face): + """returns the centroid z_value of any point in the face""" + return sum([pts[facet - 1][2] for facet in face]) / len(face) + +if __name__ == '__main__': + Poly3D().run() diff --git a/share/extensions/prepare_file_save_as.inx b/share/extensions/prepare_file_save_as.inx new file mode 100644 index 0000000..6c8bf86 --- /dev/null +++ b/share/extensions/prepare_file_save_as.inx @@ -0,0 +1,14 @@ + + + Pre-Process File Save As... + org.inkscape.file.pre_process + + path + + + + + + diff --git a/share/extensions/prepare_file_save_as.py b/share/extensions/prepare_file_save_as.py new file mode 100755 index 0000000..0e89d79 --- /dev/null +++ b/share/extensions/prepare_file_save_as.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2014 Ryan Lerch (multiple difference) +# 2016 Maren Hachmann (refactoring, extend to multibool) +# 2017 Alvin Penner (apply to 'File Save As...') +# +# This code is based on 'inkscape-extension-multiple-difference' by Ryan Lerch +# see : https://github.com/ryanlerch/inkscape-extension-multiple-difference +# also: https://github.com/Moini/inkscape-extensions-multi-bool +# It will call up a new instance of Inkscape and process the image there, +# so that the original file is left intact. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This extension will pre-process a vector image by applying the operations: +'EditSelectAllInAllLayers' and 'ObjectToPath' +before calling the dialog File->Save As.... +""" + +import inkex +from inkex.base import TempDirMixin +from inkex.command import inkscape_command +from inkex import load_svg + +class PreProcess(TempDirMixin, inkex.EffectExtension): + def effect(self): + self.document = load_svg(inkscape_command( + self.svg, + verbs=['EditSelectAllInAllLayers', 'EditUnlinkClone', 'ObjectToPath'], + )) + +if __name__ == '__main__': + PreProcess().run() diff --git a/share/extensions/previous_glyph_layer.inx b/share/extensions/previous_glyph_layer.inx new file mode 100644 index 0000000..955bbe1 --- /dev/null +++ b/share/extensions/previous_glyph_layer.inx @@ -0,0 +1,14 @@ + + + View Previous Glyph + org.inkscape.typography.previous_layer + + all + + + + + + diff --git a/share/extensions/previous_glyph_layer.py b/share/extensions/previous_glyph_layer.py new file mode 100755 index 0000000..bdfc28e --- /dev/null +++ b/share/extensions/previous_glyph_layer.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2011 Felipe Correa da Silva Sanches +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +from next_glyph_layer import NextLayer + +class PreviousLayer(NextLayer): + """Like next glyph layer, but for the previous""" + @staticmethod + def process_glyphs(glyphs, current): + glyphs[current].set("style", "display:none") + glyphs[current-1].set("style", "display:inline") + +if __name__ == '__main__': + PreviousLayer().run() diff --git a/share/extensions/print_win32_vector.inx b/share/extensions/print_win32_vector.inx new file mode 100644 index 0000000..e86163c --- /dev/null +++ b/share/extensions/print_win32_vector.inx @@ -0,0 +1,15 @@ + + + Win32 Vector Print + com.vaxxine.print.print_win32_vector + org.inkscape.output.svg.inkscape + + all + + + + + + diff --git a/share/extensions/print_win32_vector.py b/share/extensions/print_win32_vector.py new file mode 100755 index 0000000..18db29a --- /dev/null +++ b/share/extensions/print_win32_vector.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2012 Alvin Penner, penner@vaxxine.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This extension will generate vector graphics printout, specifically for Windows GDI32. + +This is a modified version of the file dxf_outlines.py by Aaron Spike, aaron@ekips.org +It will write only to the default printer. +The printing preferences dialog will be called. +In order to ensure a pure vector output, use a linewidth < 1 printer pixel + +- see http://www.lessanvaezi.com/changing-printer-settings-using-the-windows-api/ +- get GdiPrintSample.zip at http://archive.msdn.microsoft.com/WindowsPrintSample + +""" + +import sys +import ctypes + +import inkex +from inkex import PathElement, Rectangle, Group, Use, Transform +from inkex.paths import Path + +if sys.platform.startswith('win'): + myspool = ctypes.WinDLL("winspool.drv") + mygdi = ctypes.WinDLL("gdi32.dll") +else: + myspool = None + mygdi = None + +LOGBRUSH = ctypes.c_long * 3 +DM_IN_PROMPT = 4 # call printer property sheet +DM_OUT_BUFFER = 2 # write to DEVMODE structure + +class PrintWin32Vector(inkex.EffectExtension): + def __init__(self): + super(PrintWin32Vector, self).__init__() + self.visibleLayers = True # print only visible layers + + def process_shape(self, node, mat): + """Process shape""" + rgb = (0, 0, 0) # stroke color + fillcolor = None # fill color + stroke = 1 # pen width in printer pixels + # Very NB : If the pen width is greater than 1 then the output will Not be a vector output ! + style = node.style + if style: + if 'stroke' in style: + if style['stroke'] and style['stroke'] != 'none' and style['stroke'][0:3] != 'url': + rgb = inkex.Color(style['stroke']).to_rgb() + if 'stroke-width' in style: + stroke = self.svg.unittouu(style['stroke-width'])/self.svg.unittouu('1px') + stroke = int(stroke*self.scale) + if 'fill' in style: + if style['fill'] and style['fill'] != 'none' and style['fill'][0:3] != 'url': + fill = inkex.Color(style['fill']).to_rgb() + fillcolor = fill[0] + 256*fill[1] + 256*256*fill[2] + color = rgb[0] + 256*rgb[1] + 256*256*rgb[2] + if isinstance(node, PathElement): + p = node.path.to_superpath() + if not p: + return + elif isinstance(node, Rectangle): + x = float(node.get('x')) + y = float(node.get('y')) + width = float(node.get('width')) + height = float(node.get('height')) + p = [[[x, y],[x, y],[x, y]]] + p.append([[x + width, y],[x + width, y],[x + width, y]]) + p.append([[x + width, y + height],[x + width, y + height],[x + width, y + height]]) + p.append([[x, y + height],[x, y + height],[x, y + height]]) + p.append([[x, y],[x, y],[x, y]]) + p = [p] + else: + return + mat += node.transform + p = Path(p).transform(Transform(mat)).to_arrays() + hPen = mygdi.CreatePen(0, stroke, color) + mygdi.SelectObject(self.hDC, hPen) + self.emit_path(p) + if fillcolor is not None: + brush = LOGBRUSH(0, fillcolor, 0) + hBrush = mygdi.CreateBrushIndirect(ctypes.addressof(brush)) + mygdi.SelectObject(self.hDC, hBrush) + mygdi.BeginPath(self.hDC) + self.emit_path(p) + mygdi.EndPath(self.hDC) + mygdi.FillPath(self.hDC) + return + + def emit_path(self, p): + for sub in p: + mygdi.MoveToEx(self.hDC, int(sub[0][1][0]), int(sub[0][1][1]), None) + POINTS = ctypes.c_long*(6*(len(sub)-1)) + points = POINTS() + for i in range(len(sub)-1): + points[6*i] = int(sub[i][2][0]) + points[6*i + 1] = int(sub[i][2][1]) + points[6*i + 2] = int(sub[i + 1][0][0]) + points[6*i + 3] = int(sub[i + 1][0][1]) + points[6*i + 4] = int(sub[i + 1][1][0]) + points[6*i + 5] = int(sub[i + 1][1][1]) + mygdi.PolyBezierTo(self.hDC, ctypes.addressof(points), 3*(len(sub)-1)) + return + + def process_clone(self, node): + trans = node.get('transform') + x = node.get('x') + y = node.get('y') + mat = Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) + if trans: + mat *= Transform(trans) + if x: + mat *= Transform([[1.0, 0.0, float(x)], [0.0, 1.0, 0.0]]) + if y: + mat *= Transform([[1.0, 0.0, 0.0], [0.0, 1.0, float(y)]]) + # push transform + if trans or x or y: + self.groupmat.append(Transform(self.groupmat[-1]) * mat) + # get referenced node + refnode = node.href + if refnode is not None: + if isinstance(refnode, inkex.Group): + self.process_group(refnode) + elif refnode.tag == 'svg:use': + self.process_clone(refnode) + else: + self.process_shape(refnode, self.groupmat[-1]) + # pop transform + if trans or x or y: + self.groupmat.pop() + + def process_group(self, group): + if isinstance(group, inkex.Layer): + style = group.style + if 'display' in style: + if style['display'] == 'none' and self.visibleLayers: + return + trans = group.get('transform') + if trans: + self.groupmat.append(Transform(self.groupmat[-1]) * Transform(trans)) + for node in group: + if isinstance(node, Group): + self.process_group(node) + elif isinstance(node, Use): + self.process_clone(node) + else: + self.process_shape(node, self.groupmat[-1]) + if trans: + self.groupmat.pop() + + def effect(self): + pcchBuffer = ctypes.c_long() + myspool.GetDefaultPrinterA(None, ctypes.byref(pcchBuffer)) # get length of printer name + pname = ctypes.create_string_buffer(pcchBuffer.value) + myspool.GetDefaultPrinterA(pname, ctypes.byref(pcchBuffer)) # get printer name + hPrinter = ctypes.c_long() + if myspool.OpenPrinterA(pname.value, ctypes.byref(hPrinter), None) == 0: + return inkex.errormsg(_("Failed to open default printer")) + + # get printer properties dialog + + pcchBuffer = myspool.DocumentPropertiesA(0, hPrinter, pname, None, None, 0) + pDevMode = ctypes.create_string_buffer(pcchBuffer + 100) # allocate extra just in case + pcchBuffer = myspool.DocumentPropertiesA(0, hPrinter, pname, ctypes.byref(pDevMode), None, DM_IN_PROMPT + DM_OUT_BUFFER) + myspool.ClosePrinter(hPrinter) + if pcchBuffer != 1: # user clicked Cancel + exit() + + # initiallize print document + + docname = self.svg.xpath('@sodipodi:docname') + if not docname: + docname = ['New document 1'] + lpszDocName = ctypes.create_string_buffer('Inkscape ' + docname[0].split('\\')[-1]) + DOCINFO = ctypes.c_long * 5 + docInfo = DOCINFO(20, ctypes.addressof(lpszDocName), 0, 0, 0) + self.hDC = mygdi.CreateDCA(None, pname, None, ctypes.byref(pDevMode)) + if mygdi.StartDocA(self.hDC, ctypes.byref(docInfo)) < 0: + exit() # user clicked Cancel + + self.scale = (ord(pDevMode[58]) + 256.0*ord(pDevMode[59]))/96 # use PrintQuality from DEVMODE + self.scale /= self.svg.unittouu('1px') + h = self.svg.unittouu(self.svg.xpath('@height')[0]) + doc = self.document.getroot() + # process viewBox height attribute to correct page scaling + viewBox = doc.get('viewBox') + if viewBox: + viewBox2 = viewBox.split(',') + if len(viewBox2) < 4: + viewBox2 = viewBox.split(' ') + self.scale *= h / self.svg.unittouu(self.addDocumentUnit(viewBox2[3])) + self.groupmat = [[[self.scale, 0.0, 0.0], [0.0, self.scale, 0.0]]] + self.process_group(doc) + mygdi.EndDoc(self.hDC) + +if __name__ == '__main__': + PrintWin32Vector().run() diff --git a/share/extensions/printing_marks.inx b/share/extensions/printing_marks.inx new file mode 100644 index 0000000..ab44d0a --- /dev/null +++ b/share/extensions/printing_marks.inx @@ -0,0 +1,49 @@ + + + Printing Marks + org.inkscape.generate.printing_marks + + + + true + false + true + false + true + false + + + + + + + + + + + + + + 5 + + 5 + 5 + 5 + 5 + + + + + all + + + + + + + + + + diff --git a/share/extensions/printing_marks.py b/share/extensions/printing_marks.py new file mode 100755 index 0000000..53e2832 --- /dev/null +++ b/share/extensions/printing_marks.py @@ -0,0 +1,401 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Authors: +# Nicolas Dufour - Association Inkscape-fr +# Aurelio A. Heckert +# +# Copyright (C) 2008 Authors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This extension allows you to draw crop, registration and other +printing marks in Inkscape. +""" + +import math +import inkex +from inkex import Circle, Rectangle, TextElement + + +class PrintingMarks(inkex.EffectExtension): + # Default parameters + stroke_width = 0.25 + + def add_arguments(self, pars): + pars.add_argument("--where", help="Apply crop marks to...") + pars.add_argument("--crop_marks", type=inkex.Boolean, default=True, help="Draw crop Marks") + pars.add_argument("--bleed_marks", type=inkex.Boolean, help="Draw Bleed Marks") + pars.add_argument("--registration_marks", type=inkex.Boolean,\ + dest="reg_marks", default=False, help="Draw Registration Marks?") + pars.add_argument("--star_target", type=inkex.Boolean, help="Draw Star Target?") + pars.add_argument("--colour_bars", type=inkex.Boolean, help="Draw Colour Bars?") + pars.add_argument("--page_info", type=inkex.Boolean, help="Draw Page Information?") + pars.add_argument("--unit", default="px", help="Draw measurement") + pars.add_argument("--crop_offset", type=float, default=0.0, help="Offset") + pars.add_argument("--bleed_top", type=float, default=0.0, help="Bleed Top Size") + pars.add_argument("--bleed_bottom", type=float, default=0.0, help="Bleed Bottom Size") + pars.add_argument("--bleed_left", type=float, default=0.0, help="Bleed Left Size") + pars.add_argument("--bleed_right", type=float, default=0.0, help="Bleed Right Size") + pars.add_argument("--tab", help="The selected UI-tab when OK was pressed") + + def draw_crop_line(self, x1, y1, x2, y2, name, parent): + style = {'stroke': '#000000', 'stroke-width': str(self.stroke_width), + 'fill': 'none'} + line_attribs = {'style': str(inkex.Style(style)), + 'id': name, + 'd': 'M ' + str(x1) + ',' + str(y1) + ' L ' + str(x2) + ',' + str(y2)} + parent.add(inkex.PathElement(**line_attribs)) + + def draw_bleed_line(self, x1, y1, x2, y2, name, parent): + style = {'stroke': '#000000', 'stroke-width': str(self.stroke_width), + 'fill': 'none', + 'stroke-miterlimit': '4', 'stroke-dasharray': '4, 2, 1, 2', + 'stroke-dashoffset': '0'} + line_attribs = {'style': str(inkex.Style(style)), + 'id': name, + 'd': 'M ' + str(x1) + ',' + str(y1) + ' L ' + str(x2) + ',' + str(y2)} + parent.add(inkex.PathElement(**line_attribs)) + + def draw_reg_circles(self, cx, cy, r, name, colours, parent): + for i in range(len(colours)): + style = {'stroke': colours[i], 'stroke-width': str(r / len(colours)), + 'fill': 'none'} + circle_attribs = {'style': str(inkex.Style(style)), + 'inkscape:label': name, + 'cx': str(cx), 'cy': str(cy), + 'r': str((r / len(colours)) * (i + 0.5))} + parent.add(Circle(**circle_attribs)) + + def draw_reg_marks(self, cx, cy, rotate, name, parent): + colours = ['#000000', '#00ffff', '#ff00ff', '#ffff00', '#000000'] + g = parent.add(inkex.Group(id=name)) + for i in range(len(colours)): + style = {'fill': colours[i], 'fill-opacity': '1', 'stroke': 'none'} + r = (self.mark_size / 2) + step = r + stroke = r / len(colours) + regoffset = stroke * i + regmark_attribs = {'style': str(inkex.Style(style)), + 'd': 'm' + + ' ' + str(-regoffset) + ',' + str(r) + + ' ' + str(-stroke) + ',0' + + ' ' + str(step) + ',' + str(-r) + + ' ' + str(-step) + ',' + str(-r) + + ' ' + str(stroke) + ',0' + + ' ' + str(step) + ',' + str(r) + + ' ' + str(-step) + ',' + str(r) + + ' z', + 'transform': 'translate(' + str(cx) + ',' + str(cy) + + ') rotate(' + str(rotate) + ')'} + g.add(inkex.PathElement(**regmark_attribs)) + + def draw_star_target(self, cx, cy, name, parent): + r = (self.mark_size / 2) + style = {'fill': '#000 device-cmyk(1,1,1,1)', 'fill-opacity': '1', 'stroke': 'none'} + d = ' M 0,0' + i = 0 + while i < (2 * math.pi): + i += math.pi / 16 + d += ' L 0,0 ' + \ + ' L ' + str(math.sin(i) * r) + ',' + str(math.cos(i) * r) + \ + ' L ' + str(math.sin(i + 0.09) * r) + ',' + str(math.cos(i + 0.09) * r) + regmark_attribs = {'style': str(inkex.Style(style)), + 'inkscape:label': name, + 'transform': 'translate(' + str(cx) + ',' + str(cy) + ')', + 'd': d} + parent.add(inkex.PathElement(**regmark_attribs)) + + def draw_coluor_bars(self, cx, cy, rotate, name, parent): + group = parent.add(inkex.Group(id=name)) + group.transform = inkex.Transform(translate=(cx, cy), rotate=rotate) + bbox = parent.bounding_box() + loc = 0 + if bbox: + loc = min(self.mark_size / 3, max(bbox.width, bbox.height) / 45) + for bar in [{'c': '*', 'stroke': '#000', 'x': 0, 'y': -(loc + 1)}, + {'c': 'r', 'stroke': '#0FF', 'x': 0, 'y': 0}, + {'c': 'g', 'stroke': '#F0F', 'x': (loc * 11) + 1, 'y': -(loc + 1)}, + {'c': 'b', 'stroke': '#FF0', 'x': (loc * 11) + 1, 'y': 0} + ]: + i = 0 + while i <= 1: + color = inkex.Color('white') + if bar['c'] == 'r' or bar['c'] == '*': + color.red = 255 * i + if bar['c'] == 'g' or bar['c'] == '*': + color.green = 255 * i + if bar['c'] == 'b' or bar['c'] == '*': + color.blue = 255 * i + r_att = {'fill': str(color), + 'stroke': bar['stroke'], + 'stroke-width': '0.5', + 'x': str((loc * i * 10) + bar['x']), 'y': str(bar['y']), + 'width': str(loc), 'height': str(loc)} + group.add(Rectangle(*r_att)) + i += 0.1 + + def effect(self): + self.mark_size = self.svg.unittouu('1cm') + self.min_mark_margin = self.svg.unittouu('3mm') + + if self.options.where == 'selection': + bbox = self.svg.selection.bounding_box() + else: + bbox = self.svg.get_page_bbox() + + # Get SVG document dimensions + # self.width must be replaced by bbox.right. same to others. + svg = self.document.getroot() + + # Convert parameters to user unit + offset = self.svg.unittouu(str(self.options.crop_offset) + + self.options.unit) + bt = self.svg.unittouu(str(self.options.bleed_top) + self.options.unit) + bb = self.svg.unittouu(str(self.options.bleed_bottom) + self.options.unit) + bl = self.svg.unittouu(str(self.options.bleed_left) + self.options.unit) + br = self.svg.unittouu(str(self.options.bleed_right) + self.options.unit) + # Bleed margin + if bt < offset: + bmt = 0 + else: + bmt = bt - offset + if bb < offset: + bmb = 0 + else: + bmb = bb - offset + if bl < offset: + bml = 0 + else: + bml = bl - offset + if br < offset: + bmr = 0 + else: + bmr = br - offset + + # Define the new document limits + offset_left = bbox.left - offset + offset_right = bbox.right + offset + offset_top = bbox.top - offset + offset_bottom = bbox.bottom + offset + + # Get middle positions + middle_vertical = bbox.top + (bbox.height / 2) + middle_horizontal = bbox.left + (bbox.width / 2) + + # Test if printing-marks layer existis + layer = self.svg.xpath('//*[@id="printing-marks" and @inkscape:groupmode="layer"]') + if layer: + svg.remove(layer[0]) # remove if it existis + # Create a new layer + layer = svg.add(inkex.Layer.new("Printing Marks")) + layer.set('id', 'printing-marks') + layer.set('sodipodi:insensitive', 'true') + + # Crop Mark + if self.options.crop_marks: + # Create a group for Crop Mark + g_crops = layer.add(inkex.Group(id='CropMarks')) + g_crops.label = 'CropMarks' + + # Top left Mark + self.draw_crop_line(bbox.left, offset_top, + bbox.left, offset_top - self.mark_size, + 'cropTL1', g_crops) + self.draw_crop_line(offset_left, bbox.top, + offset_left - self.mark_size, bbox.top, + 'cropTL2', g_crops) + + # Top right Mark + self.draw_crop_line(bbox.right, offset_top, + bbox.right, offset_top - self.mark_size, + 'cropTR1', g_crops) + self.draw_crop_line(offset_right, bbox.top, + offset_right + self.mark_size, bbox.top, + 'cropTR2', g_crops) + + # Bottom left Mark + self.draw_crop_line(bbox.left, offset_bottom, + bbox.left, offset_bottom + self.mark_size, + 'cropBL1', g_crops) + self.draw_crop_line(offset_left, bbox.bottom, + offset_left - self.mark_size, bbox.bottom, + 'cropBL2', g_crops) + + # Bottom right Mark + self.draw_crop_line(bbox.right, offset_bottom, + bbox.right, offset_bottom + self.mark_size, + 'cropBR1', g_crops) + self.draw_crop_line(offset_right, bbox.bottom, + offset_right + self.mark_size, bbox.bottom, + 'cropBR2', g_crops) + + # Bleed Mark + if self.options.bleed_marks: + # Create a group for Bleed Mark + g_attribs = {'inkscape:label': 'BleedMarks', + 'id': 'BleedMarks'} + g_bleed = layer.add(inkex.Group(**g_attribs)) + + # Top left Mark + self.draw_bleed_line(bbox.left - bl, offset_top - bmt, + bbox.left - bl, offset_top - bmt - self.mark_size, + 'bleedTL1', g_bleed) + self.draw_bleed_line(offset_left - bml, bbox.top - bt, + offset_left - bml - self.mark_size, bbox.top - bt, + 'bleedTL2', g_bleed) + + # Top right Mark + self.draw_bleed_line(bbox.right + br, offset_top - bmt, + bbox.right + br, offset_top - bmt - self.mark_size, + 'bleedTR1', g_bleed) + self.draw_bleed_line(offset_right + bmr, bbox.top - bt, + offset_right + bmr + self.mark_size, bbox.top - bt, + 'bleedTR2', g_bleed) + + # Bottom left Mark + self.draw_bleed_line(bbox.left - bl, offset_bottom + bmb, + bbox.left - bl, offset_bottom + bmb + self.mark_size, + 'bleedBL1', g_bleed) + self.draw_bleed_line(offset_left - bml, bbox.bottom + bb, + offset_left - bml - self.mark_size, bbox.bottom + bb, + 'bleedBL2', g_bleed) + + # Bottom right Mark + self.draw_bleed_line(bbox.right + br, offset_bottom + bmb, + bbox.right + br, offset_bottom + bmb + self.mark_size, + 'bleedBR1', g_bleed) + self.draw_bleed_line(offset_right + bmr, bbox.bottom + bb, + offset_right + bmr + self.mark_size, bbox.bottom + bb, + 'bleedBR2', g_bleed) + + # Registration Mark + if self.options.reg_marks: + # Create a group for Registration Mark + g_attribs = {'inkscape:label': 'RegistrationMarks', + 'id': 'RegistrationMarks'} + g_center = layer.add(inkex.Group(**g_attribs)) + + # Left Mark + cx = max(bml + offset, self.min_mark_margin) + self.draw_reg_marks(bbox.left - cx - (self.mark_size / 2), + middle_vertical - self.mark_size * 1.5, + '0', 'regMarkL', g_center) + + # Right Mark + cx = max(bmr + offset, self.min_mark_margin) + self.draw_reg_marks(bbox.right + cx + (self.mark_size / 2), + middle_vertical - self.mark_size * 1.5, + '180', 'regMarkR', g_center) + + # Top Mark + cy = max(bmt + offset, self.min_mark_margin) + self.draw_reg_marks(middle_horizontal, + bbox.top - cy - (self.mark_size / 2), + '90', 'regMarkT', g_center) + + # Bottom Mark + cy = max(bmb + offset, self.min_mark_margin) + self.draw_reg_marks(middle_horizontal, + bbox.bottom + cy + (self.mark_size / 2), + '-90', 'regMarkB', g_center) + + # Star Target + if self.options.star_target: + # Create a group for Star Target + g_attribs = {'inkscape:label': 'StarTarget', + 'id': 'StarTarget'} + g_center = layer.add(inkex.Group(**g_attribs)) + + if bbox.height < bbox.width: + # Left Star + cx = max(bml + offset, self.min_mark_margin) + self.draw_star_target(bbox.left - cx - (self.mark_size / 2), + middle_vertical, + 'starTargetL', g_center) + # Right Star + cx = max(bmr + offset, self.min_mark_margin) + self.draw_star_target(bbox.right + cx + (self.mark_size / 2), + middle_vertical, + 'starTargetR', g_center) + else: + # Top Star + cy = max(bmt + offset, self.min_mark_margin) + self.draw_star_target(middle_horizontal - self.mark_size * 1.5, + bbox.top - cy - (self.mark_size / 2), + 'starTargetT', g_center) + # Bottom Star + cy = max(bmb + offset, self.min_mark_margin) + self.draw_star_target(middle_horizontal - self.mark_size * 1.5, + bbox.bottom + cy + (self.mark_size / 2), + 'starTargetB', g_center) + + # Colour Bars + if self.options.colour_bars: + # Create a group for Colour Bars + g_attribs = {'inkscape:label': 'ColourBars', + 'id': 'PrintingColourBars'} + g_center = layer.add(inkex.Group(**g_attribs)) + + if bbox.height > bbox.width: + # Left Bars + cx = max(bml + offset, self.min_mark_margin) + self.draw_coluor_bars(bbox.left - cx - (self.mark_size / 2), + middle_vertical + self.mark_size, + 90, + 'PrintingColourBarsL', g_center) + # Right Bars + cx = max(bmr + offset, self.min_mark_margin) + self.draw_coluor_bars(bbox.right + cx + (self.mark_size / 2), + middle_vertical + self.mark_size, + 90, + 'PrintingColourBarsR', g_center) + else: + # Top Bars + cy = max(bmt + offset, self.min_mark_margin) + self.draw_coluor_bars(middle_horizontal + self.mark_size, + bbox.top - cy - (self.mark_size / 2), + 0, + 'PrintingColourBarsT', g_center) + # Bottom Bars + cy = max(bmb + offset, self.min_mark_margin) + self.draw_coluor_bars(middle_horizontal + self.mark_size, + bbox.bottom + cy + (self.mark_size / 2), + 0, + 'PrintingColourBarsB', g_center) + + # Page Information + if self.options.page_info: + # Create a group for Page Information + g_attribs = {'inkscape:label': 'PageInformation', + 'id': 'PageInformation'} + g_pag_info = layer.add(inkex.Group(**g_attribs)) + y_margin = max(bmb + offset, self.min_mark_margin) + txt_attribs = { + 'style': 'font-size:12px;font-style:normal;font-weight:normal;fill:#000000;font-family:Bitstream Vera Sans,sans-serif;text-anchor:middle;text-align:center', + 'x': str(middle_horizontal), + 'y': str(bbox.bottom + y_margin + self.mark_size + 20) + } + txt = g_pag_info.add(TextElement(**txt_attribs)) + txt.text = 'Page size: ' + \ + str(round(self.svg.uutounit(bbox.width, self.options.unit), 2)) + \ + 'x' + \ + str(round(self.svg.uutounit(bbox.height, self.options.unit), 2)) + \ + ' ' + self.options.unit + + +if __name__ == '__main__': + PrintingMarks().run() diff --git a/share/extensions/ps_input.inx b/share/extensions/ps_input.inx new file mode 100644 index 0000000..4419f58 --- /dev/null +++ b/share/extensions/ps_input.inx @@ -0,0 +1,18 @@ + + + PostScript Input + org.inkscape.input.postscript_input + org.inkscape.input.pdf + ps2pdf + + + diff --git a/share/extensions/ps_input.py b/share/extensions/ps_input.py new file mode 100755 index 0000000..7e1728a --- /dev/null +++ b/share/extensions/ps_input.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2008 Stephen Silver +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +# +""" +Simple wrapper around ps2pdf +""" + +import inkex +from inkex.command import call + +class PostscriptInput(inkex.CallExtension): + """Load Postscript/EPS Files by calling ps2pdf program""" + input_ext = 'ps' + output_ext = 'pdf' + multi_inx = True + + def add_arguments(self, pars): + pars.add_argument('--crop', type=inkex.Boolean, default=False) + + def call(self, input_file, output_file): + crop = '-dEPSCrop' if self.options.crop else None + call('ps2pdf', crop, input_file, output_file) + +if __name__ == '__main__': + PostscriptInput().run() diff --git a/share/extensions/render_alphabetsoup.inx b/share/extensions/render_alphabetsoup.inx new file mode 100644 index 0000000..22d3127 --- /dev/null +++ b/share/extensions/render_alphabetsoup.inx @@ -0,0 +1,18 @@ + + + Alphabet Soup + org.inkscape.filter.alphabet_soup + render_alphabetsoup_config.py + Inkscape + 8.0 + false + + all + + + + + + diff --git a/share/extensions/render_alphabetsoup.py b/share/extensions/render_alphabetsoup.py new file mode 100755 index 0000000..4807d4d --- /dev/null +++ b/share/extensions/render_alphabetsoup.py @@ -0,0 +1,503 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2001-2002 Matt Chisholm matt@theory.org +# Copyright (C) 2008 Joel Holdsworth joel@airwebreathe.org.uk +# for AP +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import cmath +import copy +import math +import os +import random +import re +import sys + +import inkex +from inkex import Vector2d, load_svg + +import render_alphabetsoup_config + +syntax = render_alphabetsoup_config.syntax +alphabet = render_alphabetsoup_config.alphabet +units = render_alphabetsoup_config.units +font = render_alphabetsoup_config.font + + +def load_path(filename): + """Loads a super-path from a given SVG file""" + base = os.path.normpath( + os.path.join(os.getcwd(), os.path.dirname(__file__)) + ) + # __file__ is better then sys.argv[0] because this file may be a module + # for another one. + fullpath = os.path.join(base, filename) + tree = load_svg(fullpath) + root = tree.getroot() + elem = root.findone('svg:path') + if elem is None: + return None, 0, 0 + width = float(root.get("width")) + height = float(root.get("height")) + return elem.path.to_arrays(), width, height # Currently we only support a single path + + +def combinePaths(pathA, pathB): + if pathA is None and pathB is None: + return None + elif pathA is None: + return pathB + elif pathB is None: + return pathA + else: + return pathA + pathB + + +def reverseComponent(c): + nc = [] + last = c.pop() + nc.append(['M', last[1][-2:]]) + while c: + this = c.pop() + cmd = last[0] + if cmd == 'C': + nc.append([last[0], last[1][2:4] + last[1][:2] + this[1][-2:]]) + else: + nc.append([last[0], this[1][-2:]]) + last = this + return nc + + +def reversePath(sp): + rp = [] + component = [] + for p in sp: + cmd, params = p + if cmd == 'Z': + rp.extend(reverseComponent(component)) + rp.append(['Z', []]) + component = [] + else: + component.append(p) + return rp + + +def _lr_cb(p, width): + p.scale(-1, 1) + p.translate(width, 0) + + +def _tp_cb(p, height): + p.scale(1, -1) + p.translate(0, height) + +def flip(sp, cb, param): + # print('flip before +' + str(sp)) + p = inkex.Path(sp) + cb(p, param) + del sp[:] + + prev = Vector2d() + prev_prev = Vector2d() + first = Vector2d() + + for i, seg in enumerate(p): + if i == 0: + first = seg.end_point(first, prev) + cps = [] + for cp in seg.control_points(first, prev, prev_prev): + prev_prev = prev + prev = cp + cps.extend(cp) + sp.append([seg.letter, cps]) + # print('flip after +' + str(sp)) + +def flipLeftRight(sp, width): + return flip(sp, _lr_cb, width) + +def flipTopBottom(sp, height): + return flip(sp, _tp_cb, height) + +def solveQuadratic(a, b, c): + det = b * b - 4.0 * a * c + if det >= 0: # real roots + sdet = math.sqrt(det) + else: # complex roots + sdet = cmath.sqrt(det) + return (-b + sdet) / (2 * a), (-b - sdet) / (2 * a) + + +def cbrt(x): + if x >= 0: + return x ** (1.0 / 3.0) + else: + return -((-x) ** (1.0 / 3.0)) + + +def findRealRoots(a, b, c, d): + if a != 0: + a, b, c, d = 1, b / float(a), c / float(a), d / float(a) # Divide through by a + t = b / 3.0 + p, q = c - 3 * t ** 2, d - c * t + 2 * t ** 3 + u, v = solveQuadratic(1, q, -(p / 3.0) ** 3) + if isinstance(u, complex): # Complex Cubic Root + r = math.sqrt(u.real ** 2 + u.imag ** 2) + w = math.atan2(u.imag, u.real) + y1 = 2 * cbrt(r) * math.cos(w / 3.0) + else: # Complex Real Root + y1 = cbrt(u) + cbrt(v) + + y2, y3 = solveQuadratic(1, y1, p + y1 ** 2) + + if isinstance(y2, complex): # Are y2 and y3 complex? + return [y1 - t] + return [y1 - t, y2 - t, y3 - t] + elif b != 0: + det = c * c - 4.0 * b * d + if det >= 0: + return [(-c + math.sqrt(det)) / (2.0 * b), (-c - math.sqrt(det)) / (2.0 * b)] + elif c != 0: + return [-d / c] + return [] + + +def mxfm(image, width, height, stack): # returns possibly transformed image + tbimage = image + if stack[0] == "-": # top-bottom flip + flipTopBottom(tbimage, height) + tbimage = reversePath(tbimage) + stack.pop(0) + + lrimage = tbimage + if stack[0] == "|": # left-right flip + flipLeftRight(tbimage, width) + lrimage = reversePath(lrimage) + stack.pop(0) + return lrimage + + +def comparerule(rule, nodes): # compare node list to nodes in rule + for i in range(0, len(nodes)): # range( a, b ) = (a, a+1, a+2 ... b-2, b-1) + if nodes[i] == rule[i][0]: + pass + else: + return 0 + return 1 + + +def findrule(state, nodes): # find the rule which generated this subtree + ruleset = syntax[state][1] + nodelen = len(nodes) + for rule in ruleset: + rulelen = len(rule) + if (rulelen == nodelen) and (comparerule(rule, nodes)): + return rule + return + + +def generate(state): # generate a random tree (in stack form) + stack = [state] + if len(syntax[state]) == 1: # if this is a stop symbol + return stack + else: + stack.append("[") + path = random.randint(0, (len(syntax[state][1]) - 1)) # choose randomly from next states + for symbol in syntax[state][1][path]: # recurse down each non-terminal + if symbol != 0: # 0 denotes end of list ### + substack = generate(symbol[0]) # get subtree + for elt in substack: + stack.append(elt) + if symbol[3]: + stack.append("-") # top-bottom flip + if symbol[4]: + stack.append("|") # left-right flip + # else: + # inkex.debug("found end of list in generate( state =", state, ")") # this should be deprecated/never happen + stack.append("]") + return stack + + +def draw(stack): # draw a character based on a tree stack + state = stack.pop(0) + # print state, + + image, width, height = load_path(font + syntax[state][0]) # load the image + if stack[0] != "[": # terminal stack element + if len(syntax[state]) == 1: # this state is a terminal node + return image, width, height + else: + substack = generate(state) # generate random substack + return draw(substack) # draw random substack + else: + # inkex.debug("[") + stack.pop(0) + images = [] # list of daughter images + nodes = [] # list of daughter names + while stack[0] != "]": # for all nodes in stack + newstate = stack[0] # the new state + newimage, width, height = draw(stack) # draw the daughter state + if newimage: + tfimage = mxfm(newimage, width, height, stack) # maybe transform daughter state + images.append([tfimage, width, height]) # list of daughter images + nodes.append(newstate) # list of daughter nodes + else: + # inkex.debug(("recurse on",newstate,"failed")) # this should never happen + return None, 0, 0 + rule = findrule(state, nodes) # find the rule for this subtree + + for i in range(0, len(images)): + currimg, width, height = images[i] + + if currimg: + # box = inkex.Path(currimg).bounding_box() + dx = rule[i][1] * units + dy = rule[i][2] * units + # newbox = ((box[0]+dx),(box[1]+dy),(box[2]+dx),(box[3]+dy)) + currimg = (inkex.Path(currimg).translate(dx, dy)).to_arrays() + image = combinePaths(image, currimg) + + stack.pop(0) + return image, width, height + + +def draw_crop_scale(stack, zoom): # draw, crop and scale letter image + image, width, height = draw(stack) + bbox = inkex.Path(image).bounding_box() + image = (inkex.Path(image).translate(-bbox.x.minimum, 0)).to_arrays() + image = (inkex.Path(image).scale (zoom / units, zoom / units)).to_arrays() + return image, bbox.width, bbox.height + + +def randomize_input_string(tokens, zoom): # generate a glyph starting from each token in the input string + imagelist = [] + + stack = None + for i in range(0, len(tokens)): + char = tokens[i] + # if ( re.match("[a-zA-Z0-9?]", char)): + if char in alphabet: + if (i > 0) and (char == tokens[i - 1]): # if this letter matches previous letter + imagelist.append(imagelist[len(stack) - 1]) # make them the same image + else: # generate image for letter + stack = alphabet[char][random.randint(0, (len(alphabet[char]) - 1))].split(".") + # stack = string.split( alphabet[char][random.randint(0,(len(alphabet[char])-2))] , "." ) + imagelist.append(draw_crop_scale(stack, zoom)) + elif char == " ": # add a " " space to the image list + imagelist.append(" ") + else: # this character is not in config.alphabet, skip it + sys.stderr.write('bad character "{}"\n'.format(char)) + return imagelist + + +def generate_random_string(tokens, zoom): # generate a totally random glyph for each glyph in the input string + imagelist = [] + for char in tokens: + if char == " ": # add a " " space to the image list + imagelist.append(" ") + else: + if re.match("[a-z]", char): # generate lowercase letter + stack = generate("lc") + elif re.match("[A-Z]", char): # generate uppercase letter + stack = generate("UC") + else: # this character is not in config.alphabet, skip it + sys.stderr.write('bad character"{}"\n'.format(char)) + stack = generate("start") + imagelist.append(draw_crop_scale(stack, zoom)) + + return imagelist + + +def optikern(image, width, zoom): # optical kerning algorithm + left = [] + right = [] + + resolution = 8 + for i in range(0, 18 * resolution): + y = 1.0 / resolution * (i + 0.5) * zoom + xmin = None + xmax = None + + for cmd, params in image: + if cmd == 'M': + # A move cannot contribute to the bounding box + last = params[:] + lastctrl = params[:] + elif cmd == 'L': + if (last[1] <= y <= params[1]) or (params[1] <= y <= last[1]): + if params[0] == last[0]: + x = params[0] + else: + a = (params[1] - last[1]) / (params[0] - last[0]) + b = last[1] - a * last[0] + if a != 0: + x = (y - b) / a + else: + x = None + + if x: + if xmin is None or x < xmin: + xmin = x + if xmax is None or x > xmax: + xmax = x + + last = params[:] + lastctrl = params[:] + elif cmd == 'C': + if last: + bx0, by0 = last[:] + bx1, by1, bx2, by2, bx3, by3 = params[:] + + d = by0 - y + c = -3 * by0 + 3 * by1 + b = 3 * by0 - 6 * by1 + 3 * by2 + a = -by0 + 3 * by1 - 3 * by2 + by3 + + ts = findRealRoots(a, b, c, d) + + for t in ts: + if 0 <= t <= 1: + x = (-bx0 + 3 * bx1 - 3 * bx2 + bx3) * (t ** 3) + \ + (3 * bx0 - 6 * bx1 + 3 * bx2) * (t ** 2) + \ + (-3 * bx0 + 3 * bx1) * t + \ + bx0 + if xmin is None or x < xmin: + xmin = x + if xmax is None or x > xmax: + xmax = x + + last = params[-2:] + lastctrl = params[2:4] + + elif cmd == 'Q': + # Quadratic beziers are ignored + last = params[-2:] + lastctrl = params[2:4] + + elif cmd == 'A': + # Arcs are ignored + last = params[-2:] + lastctrl = params[2:4] + + if xmin is not None and xmax is not None: + left.append(xmin) # distance from left edge of region to left edge of bbox + right.append(width - xmax) # distance from right edge of region to right edge of bbox + else: + left.append(width) + right.append(width) + + return left, right + + +def layoutstring(imagelist, zoom): # layout string of letter-images using optical kerning + kernlist = [] + length = zoom + for entry in imagelist: + if entry == " ": # leaving room for " " space characters + length = length + (zoom * render_alphabetsoup_config.space) + else: + image, width, height = entry + length = length + width + zoom # add letter length to overall length + kernlist.append(optikern(image, width, zoom)) # append kerning data for this image + + workspace = None + + position = zoom + for i in range(0, len(kernlist)): + while imagelist[i] == " ": + position = position + (zoom * render_alphabetsoup_config.space) + imagelist.pop(i) + image, width, height = imagelist[i] + + # set the kerning + if i == 0: + kern = 0 # for first image, kerning is zero + else: + kerncompare = [] # kerning comparison array + for j in range(0, len(kernlist[i][0])): + kerncompare.append(kernlist[i][0][j] + kernlist[i - 1][1][j]) + kern = min(kerncompare) + + position = position - kern # move position back by kern amount + thisimage = copy.deepcopy(image) + thisimage = (inkex.Path(thisimage).translate(position, 0)).to_arrays() + workspace = combinePaths(workspace, thisimage) + position = position + width + zoom # advance position by letter width + + return workspace + + +def tokenize(text): + """Tokenize the string, looking for LaTeX style, multi-character tokens in the string, like \\yogh.""" + tokens = [] + i = 0 + while i < len(text): + c = text[i] + i += 1 + if c == '\\': # found the beginning of an escape + t = '' + while i < len(text): # gobble up content of the escape + c = text[i] + if c == '\\': # found another escape, stop this one + break + i += 1 + if c == ' ': # a space terminates this escape + break + t += c # stick this character onto the token + if t: + tokens.append(t) + else: + tokens.append(c) + return tokens + + +class AlphabetSoup(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("-t", "--text", default="Inkscape", help="The text for alphabet soup") + pars.add_argument("-z", "--zoom", type=float, default=8.0, help="The zoom on the output") + pars.add_argument("-r", "--randomize", type=inkex.Boolean, default=False,\ + help="Generate random (unreadable) text") + + def effect(self): + zoom = self.svg.unittouu(str(self.options.zoom) + 'px') + + if self.options.randomize: + imagelist = generate_random_string(self.options.text, zoom) + else: + tokens = tokenize(self.options.text) + imagelist = randomize_input_string(tokens, zoom) + + image = layoutstring(imagelist, zoom) + + if image: + s = {'stroke': 'none', 'fill': '#000000'} + + new = inkex.PathElement( + style=str(inkex.Style(s)), + d=str(inkex.Path(image))) + + layer = self.svg.get_current_layer() + layer.append(new) + + # compensate preserved transforms of parent layer + if layer.getparent() is not None: + mat = (self.svg.get_current_layer().transform * inkex.Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])).matrix + new.transform *= -inkex.Transform(mat) + + +if __name__ == '__main__': + AlphabetSoup().run() diff --git a/share/extensions/render_alphabetsoup_config.py b/share/extensions/render_alphabetsoup_config.py new file mode 100755 index 0000000..91b486a --- /dev/null +++ b/share/extensions/render_alphabetsoup_config.py @@ -0,0 +1,902 @@ +# coding=utf-8 +# pylint: disable=invalid-name,line-too-long +# Syntax format: (raise your hand if you know lisp :-) +# +# 'state0': ("file.svg", ( ( ('state1', dx, dy, T-B, L|R),), +# ( ('state2', ...), ('state3', ...),), +# ( ('state4', ...),), +# ) ), +# ) +# +# Translation of the above in CNF: +# state0 -> state1 +# state0 -> state2 state3 +# state0 -> state4 +# +# Semantics at state0: +# Paste subtree image from state1 onto "file.svg". +# Subtree image is translated by (dx, dy) (measured in units, not pixels!). +# Subtree image is flipped top to bottom if v==1. +# Subtree image is flipped left to right if h==1. +# +# Notes: +# Origin (0,0) is at *upper* left corner. +# For optional reflections, add both reflecting and non-reflecting rules +# For 180 degree rotations, set v = 1, h = 1. +# It helps to have an empty "epsilon" image. +# +"""Data for render alphabet soup""" + +syntax = { + "start": ( + "epsilon.svg", + ((("lc", 0, 0, 0, 0),), (("UC", 0, 0, 0, 0),)), # start state + ), + # lowercase + "lc": ( + "epsilon.svg", + ( + (("barsym", 0, 0, 0, 0),), # (2096714) (26) + (("lc2", 0, 0, 0, 0),), # (830) (19) + ), + ), + # uppercase + "UC": ( + "epsilon.svg", + ((("UCb", 0, 0, 0, 0),), (("UCu", 0, -5, 0, 0),)), # (2160) (30) + ), + "UCb": ( + "epsilon.svg", + ( + (("Bar", 0, 0, 0, 0),), # (21) Psi T I KK Phi + (("Bar", 0, 0, 0, 1),), # + (("D", 0, 0, 0, 0),), # (39) D O Q C G + (("D", 0, 0, 0, 1),), # + (("E", 0, 0, 0, 0),), # (373) E B PL 3 3r 8 S Theta Eth/Dyet + (("E", 0, 0, 0, 1),), # + (("F", 0, 0, 0, 0),), # (84) F P R + (("F", 0, 0, 0, 1),), # + (("H", 0, 0, 0, 0),), # (8) H Hblock + (("H", 0, 0, 0, 1),), # + (("L", 0, 0, 0, 0),), # (12) L J U + (("L", 0, 0, 0, 1),), # + (("V", 0, 0, 0, 1),), # (6) A V M Delta Forall W + (("X", 0, 0, 0, 0),), # (172) X N M W Sigma NN + (("X", 0, 0, 0, 0),), # (172) X N M W Sigma NN + ), + ), + "UCu": ("epsilon.svg", ((("UCb", 0, 0, 1, 1),),)), + # for statistical balancing + "lc2": ( + "epsilon.svg", + ( + (("osym", 0, 0, 0, 0),), # (40) o, c, e, ou + (("vsym", 0, 0, 0, 0),), # (40) v, w, ^, y + (("dsym", 0, 0, 0, 1),), # (96) x, z, 7, 2, yogh + (("lc3", 0, 0, 0, 0),), # (928) (5) + ), + ), + "lc3": ( + "epsilon.svg", + ( + (("3sym", 0, 0, 0, 0),), # (40) epsilon + (("ssym", 0, 0, 0, 0),), # (8) s + (("asym", 0, 0, 0, 0),), # (880) a 6 9 + ), + ), + # symmetry rules + "barsym": ( + "epsilon.svg", + ( + (("bar", 0, 0, 0, 0),), + (("bar", 0, 0, 0, 1),), + (("bar", 0, 0, 1, 0),), + (("bar", 0, 0, 1, 1),), + ), + ), + "6sym": ( + "epsilon.svg", + ( + (("6", 0, 0, 0, 0),), + (("6", 0, 0, 0, 1),), + (("6", 0, 0, 1, 0),), + (("6", 0, 0, 1, 1),), + ), + ), + "3sym": ( + "epsilon.svg", + ( + (("3", 0, 0, 0, 0),), + (("3", 0, 0, 0, 1),), + (("3", 0, 0, 1, 0),), + (("3", 0, 0, 1, 1),), + ), + ), + "vsym": ("epsilon.svg", ((("v", 0, 0, 0, 0),), (("v", 0, 0, 1, 1),))), + "osym": ("epsilon.svg", ((("o", 0, 0, 0, 0),), (("o", 0, 0, 0, 1),))), + "ssym": ("epsilon.svg", ((("s", 0, 0, 0, 0),), (("s", 0, 0, 0, 1),))), + "dsym": ( + "epsilon.svg", + ( + (("diag", 0, 0, 0, 0), ("diag", 0, 0, 1, 1)), + (("diag", 0, 0, 0, 1), ("diag", 0, 0, 1, 0)), + (("dstk", 0, 0, 0, 0),), + ), + ), + "dstk": ( + "epsilon.svg", + ( + (("stik", 0, 4, 0, 0), ("z", 0, 0, 1, 1)), + (("stik", 0, 4, 0, 0), ("x", 0, 0, 1, 1)), + (("stik", 0, 4, 0, 1), ("z", 0, 0, 1, 0)), + (("stik", 0, 4, 0, 1), ("x", 0, 0, 1, 0)), + ), + ), + "asym": ( + "epsilon.svg", + ( + (("abase", 0, 0, 0, 0),), + (("abase", 0, 0, 0, 1),), + (("abase", 0, 0, 1, 0),), + (("abase", 0, 0, 1, 1),), + ), + ), + # epsilon rules + "diag": ( + "epsilon.svg", + ( + (("x", 0, 0, 0, 0),), + (("yogh", 0, 0, 1, 1),), + (("z", 0, 0, 0, 0),), + (("7", 0, 0, 0, 0),), + (("2", 0, 0, 0, 0),), + ), + ), + "bar": ( + "bar.svg", + ( + (("vert", 0, 0, 0, 0), ("vert", 0, 0, 1, 0)), # f l i t j glot. + (("k", 0, 0, 0, 0), ("vert", 0, 0, 0, 0), ("vert", 0, 0, 1, 0)), # k + (("b", 0, 0, 0, 0), ("vert", 0, 0, 1, 0)), # h heng + (("n", 0, 0, 0, 0), ("vert", 0, 0, 1, 0)), # n m r eng u uu mu + (("b1", 0, 0, 0, 0), ("b0", 0, 0, 1, 0)), # thorn eject. + (("b1", 0, 0, 0, 0), ("n0", 0, 0, 1, 0)), # b p q d + (("n1", 0, 0, 0, 0), ("n0", 0, 0, 1, 0)), # open-a + ), + ), + "vert": ( + "epsilon.svg", + ( + (("xtnd", 0, 0, 0, 0),), + (("srf", 0, 0, 1, 0),), + # (('xtnd', 0,0,0,1),), + # (('srf', 0,0,1,1),), + ), + ), + "srf": ( + "epsilon.svg", + ( + (("lserif", 0, 0, 0, 0),), + (("lserif", 0, 0, 0, 1),), + (("serif", 0, 0, 0, 0),), + (("tserif", 0, 0, 0, 0),), + (("tserif", 0, 0, 0, 1),), + ), + ), + "xtnd": ( + "epsilon.svg", + ( + (("cross", 0, 0, 0, 0),), # this needs to be L-R flippable + (("cross", 0, 0, 0, 1),), + (("l", 0, 0, 0, 0),), + (("?", 0, 0, 0, 0),), + (("?", 0, 0, 0, 1),), + (("idot", 0, 0, 0, 0),), + ), + ), + "loop": ( + "epsilon.svg", + ((("o0", 5, 0, 0, 1),), (("30", 5, 0, 0, 1),)), # loop-around elts + ), + "elike": ( + "epsilon.svg", + ( + (("e", 0, 0, 0, 0), ("crv", 0, 0, 1, 0)), + (("a", 0, 0, 0, 0), ("crv", 0, 0, 1, 0)), + (("crv", 0, 0, 0, 0), ("crv", 0, 0, 1, 0)), + ), + ), + "loop2": ("epsilon.svg", ((("elike", 0, 0, 0, 0),), (("loop", 0, 0, 0, 0),))), + "hlike": ( + "epsilon.svg", + ( + (("h", 0, 0, 0, 0),), # h-like extensions + (("m", 0, 0, 0, 0),), + (("crv", 0, 0, 0, 0),), + ), + ), + "crv": ( + "epsilon.svg", + ((("r", 0, 0, 0, 0),), (("cserif", 0, 0, 1, 0),)), # curvy things + ), + # image rules + "abase": ( + "abase.svg", + ( + (("n0", 0, 0, 1, 0), ("loop2", 0, 0, 0, 0)), + (("n0", 0, 0, 1, 0), ("loop2", 0, 0, 1, 0)), + (("b0", 0, 0, 1, 0), ("loop2", 0, 0, 0, 0)), + (("b0", 0, 0, 1, 0), ("loop2", 0, 0, 1, 0)), + ), + ), + "v": ( + "v.svg", + ( + (("vserl", 0, 0, 0, 0), ("vserr", 0, 0, 0, 0)), + (("vserl", 0, 0, 0, 0), ("vserr", 0, 0, 0, 0), ("y0", 0, 0, 0, 0)), + (("vserl", 0, 0, 0, 0), ("w", 6, 0, 0, 0)), + (("vserl", 0, 0, 0, 0), ("w", 6, 0, 0, 0), ("y0", 0, 0, 0, 0)), + ), + ), + "w": ( + "v.svg", + ((("vserr", 0, 0, 0, 0),), (("vserr", 0, 0, 0, 0), ("y0", 0, 0, 0, 0))), + ), + "y0": ( + "epsilon.svg", + ((("y", 0, 0, 0, 1),), (("y", 0, 0, 0, 0),), (("gamma", 0, 0, 0, 0),)), + ), + "l": ( + "l.svg", + ((("j", 0, 0, 0, 0),), (("j", 0, 0, 0, 1),), (("srf", 0, -4, 1, 0),)), + ), + "o": ("o.svg", ((("loop2", 0, 0, 0, 0),), (("loop2", 0, 0, 1, 0),))), + "cross": ("cross.svg", ((("t", 0, 0, 0, 0),), (("f0", 0, 0, 0, 0),))), + "f": ( + "f.svg", + ((("j", 0, 0, 0, 0),), (("j", 0, 0, 0, 1),), (("srf", 0, -4, 1, 0),)), + ), + "f0": ("f.svg", ((("j", 0, 0, 0, 0),), (("srf", 0, -4, 1, 0),))), + "idot": ( + "idot.svg", + ( + (("serif", 0, 0, 1, 0),), + (("lserif", 0, 0, 1, 0),), + (("lserif", 0, 0, 1, 1),), + ), + ), + "stik": ( + "f.svg", + ( + (("srf", 0, -4, 1, 0),), + # (('srf', 0,-4,1,1),), + ), + ), + "3": ("3.svg", ((("loop2", 0, 0, 0, 0),),)), + # uppercase + # Bar rules + "X": ( + "epsilon.svg", + ( + (("Xtb", 0, 0, 0, 0), ("Xtb", 0, -5, 1, 1)), + (("Xlr", 0, 0, 0, 0), ("Xlr", 0, -5, 1, 1)), + (("Xtb", 0, 0, 0, 0), ("Xtb2", 0, -5, 1, 1)), + (("Xlr", 0, 0, 0, 0), ("Xlr2", 0, -5, 1, 1)), + (("Xtb2", 0, 0, 0, 0), ("Xtb", 0, -5, 1, 1)), + (("Xlr2", 0, 0, 0, 0), ("Xlr", 0, -5, 1, 1)), + ), + ), + "Xtb": ( + "epsilon.svg", + ( + (("Xnw", 0, 0, 0, 0), ("Xne", 0, 0, 0, 0)), + (("Xne", 0, 0, 0, 0), ("Xh", 0, 0, 0, 0), ("Lterm2", 0, 0, 0, 0)), + (("Xnw", 0, 0, 0, 0), ("Xh", 0, 0, 0, 1), ("Lterm2", 0, 0, 0, 1)), + ( + ("Xne", 0, 0, 0, 0), + ("Xh", 0, 0, 0, 0), + ("Xnw", 0, 0, 0, 0), + ("Xh", 0, 0, 0, 1), + ), + ), + ), + "Xlr": ( + "epsilon.svg", + ( + (("Xne", 0, -5, 1, 1), ("Xnw", 0, 0, 0, 0)), + ( + ("Xne", 0, -5, 1, 1), + ("Xvt", 0, 0, 0, 0), + ("Xvb", 0, 0, 0, 0), + ("ITSerif", 0.5, 0, 0, 0), + ), + ( + ("Xnw", 0, 0, 0, 0), + ("Xvt", 0, 0, 0, 0), + ("Xvt", 0, -5, 1, 0), + ("IBSerif", 0, 0, 0, 0), + ), + ( + ("Xne", 0, -5, 1, 1), + ("Xnw", 0, 0, 0, 0), + ("Xvt", 0, 0, 0, 0), + ("Xvb", 0, 0, 0, 0), + ), + ), + ), + "Xtb2": ("epsilon.svg", ((("Xne", 0, 0, 0, 0),), (("Xnw", 0, 0, 0, 0),))), + "Xlr2": ("epsilon.svg", ((("Xnw", 0, 0, 0, 0),), (("Xne", 0, -5, 1, 1),))), + "Xne": ("Xne.svg",), + "Xnw": ("Xnw.svg",), + "Xh": ("Xh.svg",), + "Xvt": ("Xvt.svg",), + "Xvb": ("Xvb.svg",), + "Bar": ( + "barcap.svg", + ( + (("Bartop", 0, 0, 0, 0), ("Barbot", 0, 0, 0, 0), ("Barmid", 0, 0, 0, 0)), + (("Bartop2", 0, 0, 0, 0), ("Barbot2", 0, 0, 0, 0)), + ), + ), + "Bartop": ("epsilon.svg", ((("ITSerif", 0.5, 0, 0, 0),), (("Tt", 0, 0, 0, 0),))), + "Barbot": ("epsilon.svg", ((("IBSerif", 0, 0, 0, 0),), (("Tb", 0, 0, 0, 0),))), + "Barbot2": ("epsilon.svg", ((("Barbot", 0, 0, 0, 0),), (("Psi", 0, 0, 0, 0),))), + "Bartop2": ("epsilon.svg", ((("Bartop", 0, 0, 0, 0),), (("Psi", 0, -5, 1, 0),))), + "Barmid": ( + "epsilon.svg", + ( + ( + ("Hm", 0, 0, 0, 0), + ("Eserif", 0, 0, 0, 0), + ("Hm", -7.5, 0, 0, 1), + ("Eserif", -7.5, 0, 0, 1), + ), + (("P", -2.5, 3, 0, 0), ("P", -5, 3, 0, 1)), + (("P", -5, 3, 0, 1),), # points left + ), + ), + "Psi": ( + "epsilon.svg", + ((("IBSerif", 0, 0, 0, 0), ("R", -2.5, 0, 0, 0), ("R", -5, 0, 0, 1)),), + ), + # D / E / F / H / L rules + "D": ( + "epsilon.svg", + ( + (("Dterm", 0, 0, 0, 0), ("Dterm", 0, 0, 0, 1)), + (("Dterm", 0, 0, 0, 0), ("Dterm2", 0, 0, 0, 1)), + ), + ), + "E": ( + "epsilon.svg", + ( + (("Eterm", 0, 0, 0, 0), ("Eterm", 0, 0, 0, 1)), + (("Eterm", 0, 0, 0, 0), ("Eterm2", 0, 0, 0, 1)), + (("Eterm2", 0, 0, 0, 1), ("Eterm2", 0, -5, 1, 0)), # for S + ), + ), + "F": ( + "epsilon.svg", + ( + (("Fterm", 0, 0, 0, 0), ("Fterm", 0, 0, 0, 1)), + (("Fterm", 0, 0, 0, 0), ("Fterm2", 0, 0, 0, 1)), + ), + ), + "H": ( + "epsilon.svg", + ( + (("Hterm", 0, 0, 0, 0), ("Hterm", 0, 0, 0, 1)), + (("Hterm", 0, 0, 0, 0), ("Hterm2", 0, 0, 0, 1)), + ), + ), + "L": ( + "epsilon.svg", + ( + (("Lterm", 0, 0, 0, 0), ("Lterm", 0, 0, 0, 1)), + (("Lterm", 0, 0, 0, 0), ("Lterm2", 0, 0, 0, 1)), + ), + ), + "Dterm": ( + "epsilon.svg", + ( + (("Barterm", 0, 0, 0, 0), ("Et", 0, 0, 0, 0), ("Eb", 0, 0, 0, 0)), + (("O", 0, 0, 0, 0),), + ), + ), + "Dterm2": ( + "epsilon.svg", + ((("C", 0, 0, 0, 1),), (("Ltserif", 0, 0, 0, 1), ("Lbserif", 0, 0, 0, 1))), + ), + "Eterm": ( + "epsilon.svg", + ( + ( + ("Barterm", 0, 0, 0, 0), + ("Et", 0, 0, 0, 0), + ("Hm", 0, 0, 0, 0), + ("Eb", 0, 0, 0, 0), + ), + (("B", 0, 0, 0, 1),), + (("O", 0, 0, 0, 0), ("Ocross", 0, 0, 0, 0)), + (("Dterm", 0, 0, 0, 0), ("Eserif", 0, 0, 0, 1)), + (("Dterm2", 0, 0, 0, 0), ("Eserif", 0, 0, 0, 1)), + ), + ), + "Eterm2": ("epsilon.svg", ((("P", 0, 0, 0, 1), ("Lterm2", 0, -5, 1, 0)),)), + "Fterm": ( + "epsilon.svg", + ( + ( + ("Barterm", 0, 0, 0, 0), + ("Et", 0, 0, 0, 0), + ("Hm", 0, 0, 0, 0), + ("IBSerif", 0, 0, 0, 0), + ), + (("Lterm", 0, 0, 0, 0), ("Eserif", 0, 0, 0, 1)), + (("P", 0, 0, 0, 1), ("R", 0, 0, 0, 1)), + (("Ltserif", 0, 0, 0, 1), ("R", 0, 0, 0, 1)), + (("Ltserif", 0, 0, 0, 1), ("Rblock", 0, 0, 0, 1)), + (("Uterm", 0, 0, 0, 0), ("Ocross", 0, 0, 0, 0)), + ), + ), + "Fterm2": ( + "epsilon.svg", + ((("P", 0, 0, 0, 1),), (("Lterm2", 0, 0, 0, 0), ("Eserif", 0, 0, 0, 1))), + ), + "Hterm": ( + "epsilon.svg", + ( + ( + ("Barterm", 0, 0, 0, 0), + ("Hm", 0, 0, 0, 0), + ("ITSerif", 0.5, 0, 0, 0), + ("IBSerif", 0, 0, 0, 0), + ), + (("R", 0, 0, 0, 1), ("R", 0, -5, 1, 1)), + ), + ), + "Hterm2": ("epsilon.svg", ((("R", 0, 0, 0, 1),), (("Rblock", 0, 0, 0, 1),))), + "Lterm": ( + "epsilon.svg", + ( + (("Barterm", 0, 0, 0, 0), ("Et", 0, 0, 0, 0), ("IBSerif", 0, 0, 0, 0)), + (("Uterm", 0, 0, 0, 0),), + ), + ), + "Lterm2": ("epsilon.svg", ((("Ltserif", 0, 0, 0, 1),), (("Cserif", 0, -5, 1, 1),))), + "B": ("epsilon.svg", ((("P", 0, 0, 0, 0), ("P", 0, 6, 0, 0)),)), + "C": ("epsilon.svg", ((("Cserif", 0, 0, 0, 0), ("Cserif", 0, -5, 1, 0)),)), + "Cserif": ( + "epsilon.svg", + ( # (('Ctail', 0,0,0,0),), # I just hate the way these look... + (("Cblob", 0, 0, 0, 0),), + (("Chook", 0, -5, 1, 0),), + (("G", 0, 0, 0, 0),), + ), + ), + "O": ( + "epsilon.svg", + ((("Oterm", 0, 0, 0, 0),), (("Q", 0, 0, 0, 1),), (("Qu", 0, 0, 0, 1),)), + ), + "Qu": ("epsilon.svg", ((("Q", 0, -5, 1, 0),),)), + "Barterm": ("barcap.svg",), + "Ctail": ("Ctail.svg",), + "Chook": ("Chook.svg",), + "Cblob": ("Cblob.svg",), + "G": ("G.svg",), + "Ltserif": ("Lt.svg",), + "Lbserif": ("Lb.svg",), + "Et": ("Et.svg",), + "Eb": ("Eb.svg",), + "Hm": ("hcap.svg",), + "P": ("P.svg",), + "Tb": ("Tb.svg",), + "Tt": ("Tt.svg",), + "Ocross": ("Ocross.svg",), + "Oterm": ("ocap.svg",), + "Q": ("Q.svg",), + "R": ("rcap.svg", ((("IBSerif", -0.5, 0, 0, 1),),)), + "Rblock": ("Rblock.svg", ((("IBSerif", -0.5, 0, 0, 1),),)), + "Uterm": ("U.svg", ((("IBSerif", -0.5, 0, 0, 0),),)), + "IBSerif": ("IBSerif.svg",), + "ITSerif": ("ITSerif.svg",), + "Eserif": ("Eserif.svg",), + # V rules + "V": ( + "vcap.svg", + ((("V2", 0, 0, 0, 0),), (("V2", 0, 0, 0, 0), ("Across", 0, 0, 0, 0))), + ), + "V2": ( + "epsilon.svg", + ((("M", 0, 0, 0, 0),), (("Delta", 0, 0, 0, 0),), (("Vser", 0, 0, 0, 0),)), + ), + "M": ("mcap.svg", ((("IBSerif", -1.5, 0, 0, 0), ("IBSerif", 1.5, 0, 0, 1)),)), + "Delta": ("Delta.svg",), + "Vser": ("Vser.svg",), + "Across": ("acap.svg",), + # single daughter rules + "b": ( + "b.svg", + ( + (("hlike", 0, 0, 0, 0), ("f", 0, 0, 0, 0)), + # (('hlike', 0,0,0,0), ('f', 0,0,0,1),), + ), + ), + "b1": ( + "b.svg", + ( + (("loop", 0, 0, 0, 0), ("f", 0, 0, 0, 0)), + # (('loop', 0,0,0,0), ('f', 0,0,0,1),), + ), + ), + "b0": ( + "b.svg", + ( + (("f", 0, 0, 0, 0),), + # (('f', 0,0,0,1),), + ), + ), + "h": ("h.svg", ((("vert", 5, 0, 1, 0),),)), + "m": ( + "m.svg", + ((("h", 5, 0, 0, 0), ("vert", 5, 0, 1, 0)),), + ), # change later to allow 3 humped m + "n": ("n.svg", ((("hlike", 0, 0, 0, 0),),)), + "n1": ("n.svg", ((("loop", 0, 0, 0, 0),),)), + "s": ("s.svg", ((("crv", 0, 0, 0, 0), ("crv", 5, 0, 1, 1)),)), + "j": ("j.svg", ((("crv", 0, -5, 0, 0),),)), + "?": ("question.svg", ((("crv", -2.5, -5, 0, 0),),)), + "yogh": ("yogh.svg", ((("crv", -2.5, 4, 1, 0),),)), + # terminal rules + "2": ("2.svg",), + "30": ("3.svg",), + "7": ("7.svg",), + "a": ("a.svg",), + "cserif": ("cserif.svg",), + "e": ("e.svg",), + "k": ("k.svg",), + "n0": ("n.svg",), + "o0": ("o.svg",), + "r": ("r.svg",), + "serif": ("serif.svg",), + "tserif": ("tserif.svg",), + "lserif": ("lserif.svg",), + "t": ("t.svg",), + "x": ("x.svg",), + "z": ("z.svg",), + "vserl": ("vserl.svg",), + "vserr": ("vserr.svg",), + "y": ("y.svg",), + "gamma": ("gamma.svg",), +} + +alphabet = { + # Uppercase fix Y make 2) + "1": ( + "start.[.UC.[.UCb.[.Bar.[.Bartop2.[.Bartop.[.ITSerif.].].Barbot2.[.Barbot.[.IBSerif.].].].].].]", + ), + "33": ( + "start.[.UC.[.UCb.[.E.[.Eterm.[.Dterm2.[.C.|.].Eserif.|.].Eterm.[.O.Ocross.].|.].].].]", + ), + "3": ( + "start.[.UC.[.UCb.[.E.[.Eterm.[.B.[.P.P.].|.].Eterm.[.Dterm2.[.C.|.].Eserif.|.].|.].|.].].]", + "start.[.UC.[.UCb.[.E.[.Eterm.[.Dterm2.[.C.|.].Eserif.|.].Eterm.[.O.Ocross.].|.].].].]", + ), + "4": ( + "start.[.UC.[.UCu.[.UCb.[.H.[.Hterm.[.Barterm.Hm.ITSerif.IBSerif.].Hterm2.[.Rblock.[.IBSerif.|.].|.].|.].].-.|.].].]", + ), + "5": ( + "start.[.UC.[.UCu.[.UCb.[.E.[.Eterm.[.Dterm2.[.Ltserif.|.Lbserif.|.].Eserif.|.].Eterm2.[.P.|.Lterm2.[.Ltserif.|.].-.].|.].|.].-.|.].].]", + ), + "6": ( + "start.[.UC.[.UCu.[.UCb.[.E.[.Eterm.[.Dterm.[.O.].Eserif.|.].Eterm2.[.P.|.Lterm2.-.].|.].|.].-.|.].].]", + "start.[.UC.[.UCu.[.UCb.[.E.[.Eterm.[.O.Ocross.].Eterm2.[.P.|.Lterm2.-.].|.].|.].-.|.].].]", + ), + "7": ( + "start.[.UC.[.UCb.[.X.[.Xtb.[.Xne.Xh.Lterm2.[.Ltserif.|.].].Xtb2.[.Xne.].-.|.].].].]", + ), + "8": ( + "start.[.UC.[.UCb.[.E.[.Eterm.[.B.[.P.P.].|.].Eterm.[.B.[.P.P.].|.].|.].].].]", + ), + "9": ( + "start.[.UC.[.UCb.[.E.[.Eterm.[.Dterm.[.O.].Eserif.|.].Eterm2.[.P.|.Lterm2.-.].|.].|.].].]", + "start.[.UC.[.UCb.[.E.[.Eterm.[.O.Ocross.].Eterm2.[.P.|.Lterm2.-.].|.].|.].].]", + ), + "0": ("start.[.UC.[.UCb.[.D.[.Dterm.[.O.].Dterm.[.O.].|.].].].]",), + "A": ( + "start.[.UC.[.UCb.[.F.[.Fterm.[.Barterm.Et.Hm.IBSerif.].Fterm.[.Barterm.Et.Hm.IBSerif.].|.].].].]", # no flip needed + "start.[.UC.[.UCb.[.F.[.Fterm.[.Barterm.Et.Hm.IBSerif.].Fterm.[.Lterm.[.Uterm.[.IBSerif.].].Eserif.|.].|.].].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Barterm.Et.Hm.IBSerif.].Fterm.[.Lterm.[.Uterm.[.IBSerif.].].Eserif.|.].|.].|.].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Barterm.Et.Hm.IBSerif.].Fterm.[.Uterm.[.IBSerif.].Ocross.].|.].].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Barterm.Et.Hm.IBSerif.].Fterm.[.Uterm.[.IBSerif.].Ocross.].|.].|.].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Lterm.[.Uterm.[.IBSerif.].].Eserif.|.].Fterm.[.Uterm.[.IBSerif.].Ocross.].|.].|.].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Uterm.[.IBSerif.].Ocross.].Fterm.[.Lterm.[.Uterm.[.IBSerif.].].Eserif.|.].|.].|.].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Uterm.[.IBSerif.].Ocross.].Fterm.[.Uterm.[.IBSerif.].Ocross.].|.].].].]", + "start.[.UC.[.UCu.[.UCb.[.V.[.V2.[.Vser.].Across.].|.].-.|.].].]", + ), + "B": ( + "start.[.UC.[.UCb.[.E.[.Eterm.[.B.[.P.P.].|.].Eterm.|.].|.].].]", + "start.[.UC.[.UCb.[.E.[.Eterm.[.Dterm.Eserif.|.].Eterm.[.B.[.P.P.].|.].|.].].].]", + ), + "Be": ( + "start.[.UC.[.UCu.[.UCb.[.E.[.Eterm.[.Barterm.Et.Hm.Eb.].Eterm2.[.P.|.Lterm2.-.].|.].|.].-.|.].].]", # cyrillic + "start.[.UC.[.UCu.[.UCb.[.E.[.Eterm.[.Dterm.[.Barterm.Et.Eb.].Eserif.|.].Eterm2.[.P.|.Lterm2.-.].|.].|.].-.|.].].]", + ), + "C": ("start.[.UC.[.UCb.[.D.[.Dterm.[.O.[.Oterm.].].Dterm2.[.C.|.].|.].].].]",), + "D": ( + "start.[.UC.[.UCb.[.D.[.Dterm.Dterm.[.O.[.Oterm.].].|.].].].]", + "start.[.UC.[.UCb.[.D.[.Dterm.[.O.[.Oterm.].].Dterm2.[.Ltserif.|.Lbserif.|.].|.].|.].].].", + "start.[.UC.[.UCb.[.D.[.Dterm.[.Barterm.Et.Eb.].Dterm.[.Barterm.Et.Eb.].|.].].].]", + ), + "Delta": ("start.[.UC.[.UCu.[.UCb.[.V.[.V2.[.Delta.].].|.].-.|.].].]",), # Delta + "De": ( + "start.[.UC.[.UCu.[.UCb.[.D.[.Dterm.[.Barterm.Et.Eb.].Dterm.[.Barterm.Et.Eb.].|.].].-.|.].].]", + ), # Cyrillic + "E": ( + "start.[.UC.[.UCb.[.E.[.Eterm.[.Dterm2.Eserif.|.].Eterm.[.Dterm.[.Barterm.Et.Eb.].Eserif.|.].|.].|.].].]", + "start.[.UC.[.UCb.[.E.[.Eterm.[.Dterm2.Eserif.|.].Eterm.[.Dterm.[.O.].Eserif.|.].|.].|.].].]", + "start.[.UC.[.UCb.[.E.[.Eterm.[.Dterm2.Eserif.|.].Eterm.[.B.[.P.P.].|.].|.].|.].].]", + "start.[.UC.[.UCb.[.E.[.Eterm.[.Dterm2.Eserif.|.].Eterm.[.Barterm.Et.Hm.Eb.].|.].|.].].]", + "start.[.UC.[.UCb.[.E.[.Eterm.[.O.Ocross.].Eterm.[.Dterm2.Eserif.|.].|.].].].]", + ), + "Eth": ( + "start.[.UC.[.UCb.[.E.[.Eterm.[.Dterm.[.O.].Eserif.|.].Eterm.[.Barterm.Et.Hm.Eb.].|.].|.].].]", + ), + "F": ("start.[.UC.[.UCb.[.F.[.Fterm.Fterm2.[.Lterm2.Eserif.|.].|.].].].]",), + "G": ( + "start.[.UC.[.UCb.[.D.[.Dterm.[.O.].Dterm2.[.C.[.Cserif.[.G.].Cserif.-.].|.].|.].].].]", + ), + "Gamma": ( + "start.[.UC.[.UCb.[.L.[.Lterm.[.Barterm.Et.IBSerif.].Lterm2.|.].].].]", + ), # Gamma + "H": ( + "start.[.UC.[.UCb.[.H.[.Hterm.[.Barterm.Hm.ITSerif.IBSerif.].Hterm.[.Barterm.Hm.ITSerif.IBSerif.].|.].].].]", + "start.[.UC.[.UCb.[.H.[.Hterm.[.Barterm.Hm.ITSerif.IBSerif.].Hterm2.[.R.[.IBSerif.|.].|.].|.].].].]", + "start.[.UC.[.UCb.[.H.[.Hterm.[.Barterm.Hm.ITSerif.IBSerif.].Hterm2.[.Rblock.[.IBSerif.|.].|.].|.].].].]", + ), + "Che": ( + "start.[.UC.[.UCu.[.UCb.[.H.[.Hterm.[.Barterm.Hm.ITSerif.IBSerif.].Hterm2.[.Rblock.[.IBSerif.|.].|.].|.].].-.|.].].]", # Cyrillic + "start.[.UC.[.UCu.[.UCb.[.H.[.Hterm.[.Barterm.Hm.ITSerif.IBSerif.].Hterm2.[.R.[.IBSerif.|.].|.].|.].].-.|.].].]", + ), + "Heng": ( + "start.[.UC.[.UCb.[.F.[.Fterm.[.Barterm.Et.Hm.IBSerif.].Fterm.[.Ltserif.|.R.[.IBSerif.|.].|.].|.].].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Barterm.Et.Hm.IBSerif.].Fterm.[.Ltserif.|.Rblock.[.IBSerif.|.].|.].|.].].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Lterm.[.Barterm.Et.IBSerif.].Eserif.|.].Fterm.[.Ltserif.|.R.[.IBSerif.|.].|.].|.].].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Lterm.[.Barterm.Et.IBSerif.].Eserif.|.].Fterm.[.Ltserif.|.Rblock.[.IBSerif.|.].|.].|.].].].]", + ), + "I": ( + "start.[.UC.[.UCb.[.Bar.[.Bartop2.[.Bartop.[.ITSerif.].].Barbot2.[.Barbot.[.IBSerif.].].].].].]", + "start.[.UC.[.UCb.[.Bar.[.Bartop2.[.Bartop.[.Tt.].].Barbot2.[.Barbot.[.Tb.].].].].].]", + ), + "J": ( + "start.[.UC.[.UCu.[.UCb.[.L.[.Lterm.[.Uterm.[.IBSerif.].].Lterm2.|.].].-.|.].].]", + ), + "K": ( + "start.[.UC.[.UCu.[.UCb.[.X.[.Xlr.[.Xne.-.|.Xnw.].Xlr.[.Xne.-.|.Xvt.Xvb.ITSerif.].-.|.].].-.|.].].]", + "start.[.UC.[.UCb.[.H.[.Hterm.[.Barterm.Hm.ITSerif.IBSerif.].Hterm.[.R.[.IBSerif.|.].|.R.[.IBSerif.|.].-.|.].|.].].].]", + ), + "Zhe": ( + "start.[.UC.[.UCb.[.Bar.[.Bartop2.[.Psi.[.IBSerif.R.[.IBSerif.|.].R.[.IBSerif.|.].|.].-.].Barbot2.[.Psi.[.IBSerif.R.[.IBSerif.|.].R.[.IBSerif.|.].|.].].].].].]", + ), # Cyrillic + "L": ( + "start.[.UC.[.UCu.[.UCb.[.L.[.Lterm.[.Barterm.Et.IBSerif.].Lterm2.|.].|.].-.|.].].]", + ), + "Lambda": ("start.[.UC.[.UCu.[.UCb.[.V.[.V2.[.Vser.].].|.].-.|.].].]",), # Lambda + "M": ( + "start.[.UC.[.UCu.[.UCb.[.X.[.Xlr.[.Xne.-.|.Xvt.Xvb.ITSerif.].Xlr.[.Xnw.Xvt.Xvt.-.IBSerif.].-.|.].].-.|.].].]", + "start.[.UC.[.UCb.[.V.[.V2.[.M.[.IBSerif.IBSerif.|.].].].|.].].]", + ), + "N": ( + "start.[.UC.[.UCu.[.UCb.[.X.[.Xlr.[.Xnw.Xvt.Xvt.-.IBSerif.].Xlr.[.Xnw.Xvt.Xvt.-.IBSerif.].-.|.].].-.|.].].]", + "start.[.UC.[.UCb.[.L.[.Lterm.[.Uterm.[.IBSerif.].].Lterm.[.Barterm.Et.IBSerif.].|.].|.].].]", + ), + "NN": ( + "start.[.UC.[.UCu.[.UCb.[.X.[.Xlr.[.Xne.-.|.Xvt.Xvb.ITSerif.].Xlr.[.Xne.-.|.Xvt.Xvb.ITSerif.].-.|.].].-.|.].].]", + ), # Cyrillic I + "O": ("start.[.UC.[.UCb.[.D.[.Dterm.[.O.].Dterm.[.O.].|.].].].]",), + "P": ( + "start.[.UC.[.UCb.[.F.[.Fterm.[.Barterm.Et.Hm.IBSerif.].Fterm2.[.P.|.].|.].].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Ltserif.|.R.[.IBSerif.|.].|.].Fterm2.[.P.|.].|.].].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Ltserif.|.Rblock.[.IBSerif.|.].|.].Fterm2.[.P.|.].|.].].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Lterm.[.Barterm.Et.IBSerif.].Eserif.|.].Fterm2.[.P.|.].|.].].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Lterm.[.Uterm.[.IBSerif.].].Eserif.|.].Fterm2.[.P.|.].|.].].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Uterm.[.IBSerif.].Ocross.].Fterm2.[.P.|.].|.].].].]", + ), + "PL": ( + "start.[.UC.[.UCb.[.E.[.Eterm.[.Dterm.[.Barterm.Et.Eb.].Eserif.|.].Eterm2.[.P.|.Lterm2.-.].|.].].].]", + "start.[.UC.[.UCb.[.E.[.Eterm.[.Barterm.Et.Hm.Eb.].Eterm2.[.P.|.Lterm2.[.Cserif.-.|.].-.].|.].].].]", + ), + "Phi": ( + "start.[.UC.[.UCb.[.Bar.[.Bartop.[.ITSerif.].Barbot.[.IBSerif.].Barmid.[.P.P.|.].].].].]", + ), + "Pi": ( + "start.[.UC.[.UCb.[.L.[.Lterm.[.Barterm.Et.IBSerif.].Lterm.[.Barterm.Et.IBSerif.].|.].].].]", + ), + "Psi": ( + "start.[.UC.[.UCb.[.Bar.[.Bartop2.[.Psi.[.IBSerif.R.[.IBSerif.|.].R.[.IBSerif.|.].|.].-.].Barbot2.[.Barbot.[.IBSerif.].].].].].]", + ), + "Soft": ( + "start.[.UC.[.UCu.[.UCb.[.F.[.Fterm.[.Barterm.Et.Hm.IBSerif.].Fterm2.[.P.|.].|.].|.].-.|.].].]", # Cyrillic Yeru/Soft/Hard + "start.[.UC.[.UCu.[.UCb.[.F.[.Fterm.[.Lterm.[.Barterm.Et.IBSerif.].Eserif.|.].Fterm2.[.P.|.].|.].|.].-.|.].].]", + "start.[.UC.[.UCu.[.UCb.[.F.[.Fterm.[.Lterm.[.Uterm.[.IBSerif.].].Eserif.|.].Fterm2.[.P.|.].|.].|.].-.|.].].]", + "start.[.UC.[.UCu.[.UCb.[.F.[.Fterm.[.Ltserif.|.Rblock.[.IBSerif.|.].|.].Fterm2.[.P.|.].|.].|.].-.|.].].]", + "start.[.UC.[.UCu.[.UCb.[.F.[.Fterm.[.Uterm.[.IBSerif.].Ocross.].Fterm2.[.P.|.].|.].|.].-.|.].].]", + ), + "Q": ( + "start.[.UC.[.UCb.[.D.[.Dterm.[.O.[.Oterm.].].Dterm.[.O.[.Q.|.].].|.].].].]", + ), + "R": ( + "start.[.UC.[.UCb.[.F.[.Fterm.[.Lterm.[.Barterm.Et.IBSerif.].Eserif.|.].Fterm.[.P.|.R.[.IBSerif.|.].|.].|.].].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Lterm.[.Uterm.[.IBSerif.].].Eserif.|.].Fterm.[.P.|.R.[.IBSerif.|.].|.].|.].].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.P.|.R.[.IBSerif.|.].|.].Fterm.[.Barterm.Et.Hm.IBSerif.].|.].|.].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Uterm.[.IBSerif.].Ocross.].Fterm.[.P.|.R.[.IBSerif.|.].|.].|.].].].]", + ), + "Ya": ( + "start.[.UC.[.UCb.[.F.[.Fterm.[.Lterm.[.Barterm.Et.IBSerif.].Eserif.|.].Fterm.[.P.|.R.[.IBSerif.|.].|.].|.].|.].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Lterm.[.Uterm.[.IBSerif.].].Eserif.|.].Fterm.[.P.|.R.[.IBSerif.|.].|.].|.].|.].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.P.|.R.[.IBSerif.|.].|.].Fterm.[.Barterm.Et.Hm.IBSerif.].|.].].].]", + "start.[.UC.[.UCb.[.F.[.Fterm.[.Uterm.[.IBSerif.].Ocross.].Fterm.[.P.|.R.[.IBSerif.|.].|.].|.].|.].].]", + ), + "S": ( + "start.[.UC.[.UCb.[.E.[.Eterm2.[.P.|.Lterm2.-.].|.Eterm2.[.P.|.Lterm2.-.].-.].|.].].]", + ), + "Sigma": ( + "start.[.UC.[.UCb.[.X.[.Xtb.[.Xnw.Xh.|.Lterm2.|.].Xtb.[.Xne.Xh.Lterm2.].-.|.].].].]", + ), + "T": ( + "start.[.UC.[.UCb.[.Bar.[.Bartop2.[.Bartop.[.Tt.].].Barbot2.[.Barbot.[.IBSerif.].].].].].]", + "start.[.UC.[.UCb.[.Bar.[.Bartop.[.ITSerif.].Barbot.[.IBSerif.].Barmid.[.Hm.Eserif.Hm.|.Eserif.|.].].].].]", + ), + "Theta": ( + "start.[.UC.[.UCb.[.E.[.Eterm.[.O.Ocross.].Eterm.[.O.Ocross.].|.].].].]", + ), + "Thorn": ( + "start.[.UC.[.UCu.[.UCb.[.Bar.[.Bartop.[.ITSerif.].Barbot.[.IBSerif.].Barmid.[.P.|.].].].-.|.].].]", + ), + "U": ( + "start.[.UC.[.UCu.[.UCb.[.L.[.Lterm.[.Barterm.Et.IBSerif.].Lterm.[.Barterm.Et.IBSerif.].|.].|.].-.|.].].]", + "start.[.UC.[.UCu.[.UCb.[.L.[.Lterm.[.Barterm.Et.IBSerif.].Lterm.[.Uterm.[.IBSerif.].].|.].].-.|.].].]", + "start.[.UC.[.UCu.[.UCb.[.L.[.Lterm.[.Uterm.[.IBSerif.].].Lterm.[.Uterm.[.IBSerif.].].|.].].-.|.].].]", + "start.[.UC.[.UCu.[.UCb.[.L.[.Lterm.[.Barterm.Et.IBSerif.].Lterm.[.Barterm.Et.IBSerif.].|.].].-.|.].].]", + ), + "Tse": ( + "start.[.UC.[.UCu.[.UCb.[.L.[.Lterm.[.Barterm.Et.IBSerif.].Lterm.[.Barterm.Et.IBSerif.].|.].].-.|.].].]", + ), # Cyrillic + "V": ("start.[.UC.[.UCb.[.V.[.V2.[.Vser.].].|.].].]",), + "W": ( + "start.[.UC.[.UCb.[.X.[.Xlr.[.Xne.-.|.Xvt.Xvb.ITSerif.].Xlr.[.Xnw.Xvt.Xvt.-.IBSerif.].-.|.].].].]", + "start.[.UC.[.UCu.[.UCb.[.V.[.V2.[.M.[.IBSerif.IBSerif.|.].].].|.].-.|.].].]", + ), + "X": ( + "start.[.UC.[.UCu.[.UCb.[.X.[.Xlr.[.Xne.-.|.Xnw.].Xlr.[.Xne.-.|.Xnw.].-.|.].].-.|.].].]", + "start.[.UC.[.UCb.[.H.[.Hterm.[.R.|.R.-.|.].Hterm.[.R.|.R.-.|.].|.].].].]", + ), + "Xi": ( + "start.[.UC.[.UCb.[.E.[.Eterm.[.Dterm2.[.Ltserif.|.Lbserif.|.].Eserif.|.].Eterm.[.Dterm2.[.Ltserif.|.Lbserif.|.].Eserif.|.].|.].|.].].]", + ), + "Y": ("start.[.UC.[.UCb.[.X.[.Xlr.[.Xne.-.|.Xnw.].Xlr2.[.Xne.-.|.].-.|.].].].]",), + "Z": ( + "start.[.UC.[.UCb.[.X.[.Xtb.[.Xne.Xh.Lterm2.].Xtb.[.Xne.Xh.Lterm2.].-.|.].].].]", + ), + # Lowercase + "a": ( + "start.[.lc.[.lc2.[.lc3.[.asym.[.abase.[.n0.-.loop2.[.elike.[.a.crv.-.].].-.].|.].].].].]", + "start.[.lc.[.barsym.[.bar.[.n1.[.loop.].n0.-.].-.|.].].]", + "start.[.lc.[.lc2.[.lc3.[.asym.[.abase.[.n0.-.loop2.[.loop.].].|.].].].].]", + ), + "carat": ("start.[.lc.[.lc2.[.vsym.[.v.[.vserl.vserr.].-.|.].].].]",), + "b": ( + "start.[.lc.[.barsym.[.bar.[.b1.[.loop.f.].n0.-.].].].]", + "start.[.lc.[.lc2.[.lc3.[.asym.[.abase.[.b0.[.f.].-.loop2.[.loop.].].-.].].].].]", + "start.[.lc.[.barsym.[.bar.[.b1.[.loop.f.|.].n0.-.].].].]", + "start.[.lc.[.lc2.[.lc3.[.asym.[.abase.[.b0.[.f.|.].-.loop2.[.loop.].].-.].].].].]", + ), + "c": ( + "start.[.lc.[.lc2.[.osym.[.o.[.loop2.[.elike.[.crv.-.|.crv.|.].|.].].].].].]", + ), + "d": ( + "start.[.lc.[.barsym.[.bar.[.b1.[.loop.f.].n0.-.].].|.].]", + "start.[.lc.[.lc2.[.lc3.[.asym.[.abase.[.b0.[.f.].-.loop2.[.loop.].].-.].|.].].].]", + "start.[.lc.[.barsym.[.bar.[.b1.[.loop.f.|.].n0.-.].].|.].]", + "start.[.lc.[.lc2.[.lc3.[.asym.[.abase.[.b0.[.f.|.].-.loop2.[.loop.].].-.].|.].].].]", + ), + "e": ( + "start.[.lc.[.lc2.[.osym.[.o.[.loop2.[.elike.[.e.crv.-.].].].].].].]", + "start.[.lc.[.lc2.[.lc3.[.3sym.[.3.[.loop2.[.elike.[.crv.crv.-.].].].].].].].]", + ), + "epsi": ( + "start.[.lc.[.lc2.[.lc3.[.3sym.[.3.[.loop2.[.elike.[.crv.crv.-.].].].].].].].]", + ), + "f": ( + "start.[.lc.[.barsym.[.bar.[.vert.[.xtnd.[.cross.[.f0.[.j.].].].].vert.-.].].].]", + ), + "g": ( + "start.[.lc.[.barsym.[.bar.[.b1.[.loop.f.[.j.[.crv.].].].n0.-.].-.|.].].]", + "start.[.lc.[.lc2.[.lc3.[.asym.[.abase.[.b0.[.f.[.j.].].-.loop2.[.loop.].].-.].-.|.].].].]", + ), + "gamma": ("start.[.lc.[.lc2.[.vsym.[.v.[.vserl.vserr.y0.[.gamma.].].].].].]",), + "h": ( + "start.[.lc.[.barsym.[.bar.[.b.[.hlike.[.h.].f.].vert.-.].].].]", + "start.[.lc.[.barsym.[.bar.[.b.[.hlike.[.h.].f.|.].vert.-.].].].]", + ), + "heng": ( + "start.[.lc.[.barsym.[.bar.[.b.[.hlike.[.h.[.vert.[.xtnd.[.l.[.j.[.crv.].].].|.].-.].].f.[.j.[.crv.].].].vert.[.srf.-.|.].-.].].].]", + ), + "i": ( + "start.[.lc.[.barsym.[.bar.[.vert.vert.[.xtnd.[.idot.].-.].].-.|.].].]", + "start.[.lc.[.barsym.[.bar.[.vert.vert.[.xtnd.[.idot.].-.].|.].-.|.].].]", + ), + "j": ( + "start.[.lc.[.barsym.[.bar.[.vert.[.xtnd.[.idot.].-.].vert.[.xtnd.[.l.[.j.].].].|.].-.].].]", + ), + "k": ("start.[.lc.[.barsym.[.bar.[.k.vert.vert.-.].].].]",), + "l": ( + "start.[.lc.[.barsym.[.bar.[.vert.vert.[.xtnd.[.l.].-.].|.].-.|.].].]", + "start.[.lc.[.barsym.[.bar.[.vert.vert.[.xtnd.[.l.|.].-.].|.].-.|.].].]", + ), + "lambda": ("start.[.lc.[.lc2.[.vsym.[.v.[.vserl.vserr.y0.].-.|.].].].]",), + "m": ( + "start.[.lc.[.barsym.[.bar.[.n.[.hlike.[.m.[.h.[.vert.-.].vert.-.].].].vert.-.].].].]", + ), + "mu": ( + "start.[.lc.[.barsym.[.bar.[.b.[.hlike.[.h.[.vert.-.].].f.].vert.-.].-.|.].].]", + ), + "muu": ( + "start.[.lc.[.barsym.[.bar.[.b.[.hlike.[.m.[.h.[.vert.-.].vert.-.].].f.].vert.-.|.].-.|.].].]", + ), + "n": ("start.[.lc.[.barsym.[.bar.[.n.[.hlike.[.h.[.vert.-.].].].vert.-.].].].]",), + "ng": ( + "start.[.lc.[.barsym.[.bar.[.n.[.hlike.[.h.[.vert.[.xtnd.[.l.[.j.].].|.].-.].].].vert.-.].].].]", + ), + "o": ("start.[.lc.[.lc2.[.osym.[.o.[.loop2.[.loop.[.o0.|.].].].].].].]",), + "p": ( + "start.[.lc.[.barsym.[.bar.[.b1.[.loop.f.].n0.-.].].-.].]", + "start.[.lc.[.lc2.[.lc3.[.asym.[.abase.[.b0.[.f.].-.loop2.[.loop.].].-.].-.].].].]", + "start.[.lc.[.barsym.[.bar.[.b1.[.loop.f.|.].n0.-.].].-.].]", + "start.[.lc.[.lc2.[.lc3.[.asym.[.abase.[.b0.[.f.|.].-.loop2.[.loop.].].-.].-.].].].]", + ), + "q": ( + "start.[.lc.[.barsym.[.bar.[.b1.[.loop.f.].n0.-.].].-.|.].]", + "start.[.lc.[.lc2.[.lc3.[.asym.[.abase.[.b0.[.f.].-.loop2.[.loop.].].-.].-.|.].].].]", + "start.[.lc.[.barsym.[.bar.[.b1.[.loop.f.|.].n0.-.].].-.|.].]", + "start.[.lc.[.lc2.[.lc3.[.asym.[.abase.[.b0.[.f.|.].-.loop2.[.loop.].].-.].-.|.].].].]", + ), + "r": ("start.[.lc.[.barsym.[.bar.[.n.[.hlike.[.crv.].].vert.-.].].].]",), + "s": ("start.[.lc.[.lc2.[.lc3.[.ssym.[.s.[.crv.crv.-.|.].].].].].]",), + "t": ("start.[.lc.[.barsym.[.bar.[.vert.[.xtnd.[.cross.[.f0.].].].vert.-.].].].]",), + "u": ( + "start.[.lc.[.barsym.[.bar.[.n.[.hlike.[.h.[.vert.-.].].].vert.-.].-.|.].].]", + ), + "uu": ( + "start.[.lc.[.barsym.[.bar.[.n.[.hlike.[.m.[.h.[.vert.-.].vert.-.].].].vert.-.].-.|.].].]", + ), + "v": ("start.[.lc.[.lc2.[.vsym.[.v.[.vserl.vserr.].].].].]",), + "w": ("start.[.lc.[.lc2.[.vsym.[.v.[.vserl.w.[.vserr.].].].].].]",), + "x": ("start.[.lc.[.lc2.[.dsym.[.diag.[.x.].-.diag.[.x.].|.].-.].].]",), + "y": ("start.[.lc.[.lc2.[.vsym.[.v.[.vserl.vserr.y0.].].].].]",), + "yogh": ( + "start.[.lc.[.lc2.[.dsym.[.diag.[.z.].|.diag.[.yogh.[.crv.-.].-.|.].-.].].].]", + ), + "z": ("start.[.lc.[.lc2.[.dsym.[.diag.[.z.].diag.[.z.].-.|.].|.].].]",), + "glot": ("start.[.lc.[.barsym.[.bar.[.vert.[.xtnd.[.?.|.].].vert.-.].].].]",), + "1l": ( + "start.[.lc.[.barsym.[.bar.[.vert.[.srf.[.lserif.].].vert.[.srf.[.serif.].-.].|.].].-.].]", + ), + "2l": ("start.[.lc.[.lc2.[.dsym.[.diag.[.2.].diag.[.z.].-.|.].|.].].]",), + "3l": ( + "start.[.lc.[.lc2.[.dsym.[.diag.[.z.].|.diag.[.yogh.[.crv.-.].-.|.].-.].].].]", + ), + "6l": ( + "start.[.lc.[.lc2.[.lc3.[.asym.[.abase.[.b0.[.f.[.j.].].-.loop2.[.loop.].-.].-.].].].].]", + ), + "7l": ("start.[.lc.[.lc2.[.dsym.[.diag.[.z.].diag.[.7.].-.|.].|.].].]",), + "8l": ("start.[.lc.[.lc2.[.lc3.[.3sym.[.3.[.loop2.[.loop.[.30.|.].].].].].].].]",), + "9l": ( + "start.[.lc.[.lc2.[.lc3.[.asym.[.abase.[.b0.[.f.[.j.].].-.loop2.[.loop.].-.].|.].].].].]", + ), + "0l": ("start.[.lc.[.lc2.[.osym.[.o.[.loop2.[.loop.[.o0.|.].].].].].].]",), +} + +space = 4 # number of unit boxes to make a " " space in string +units = 36 # pixels per unit box in font +font = "alphabet_soup/" # location of font images diff --git a/share/extensions/render_barcode.inx b/share/extensions/render_barcode.inx new file mode 100644 index 0000000..effde05 --- /dev/null +++ b/share/extensions/render_barcode.inx @@ -0,0 +1,32 @@ + + + Classic + org.inkscape.render.barcode + + + + + + + + + + + + + + + + 30 + + all + + + + + + + + diff --git a/share/extensions/render_barcode.py b/share/extensions/render_barcode.py new file mode 100755 index 0000000..e836ab9 --- /dev/null +++ b/share/extensions/render_barcode.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2007 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Inkscape's general barcode extension. Run from within inkscape or use the +Barcode module provided for outside or scripting. +""" + +import inkex +from barcode import get_barcode + +class Barcode(inkex.GenerateExtension): + """ + Raw barcode Effect class, see Barcode base class. + """ + def add_arguments(self, pars): + pars.add_argument("-l", "--height", type=int, default=30, help="Barcode Height") + pars.add_argument("-t", "--type", default='', help="Barcode Type") + pars.add_argument("-d", "--text", default='', help="Text to print on barcode") + + def generate(self): + (pos_x, pos_y) = self.svg.namedview.center + + return get_barcode( + self.options.type, + text=self.options.text, + height=self.options.height, + document=self.document, + x=pos_x, y=pos_y, + scale=self.svg.unittouu('1px'), + ).generate() + +if __name__ == '__main__': + Barcode().run() diff --git a/share/extensions/render_barcode_datamatrix.inx b/share/extensions/render_barcode_datamatrix.inx new file mode 100644 index 0000000..6b638e0 --- /dev/null +++ b/share/extensions/render_barcode_datamatrix.inx @@ -0,0 +1,50 @@ + + + Datamatrix + org.inkscape.render.data_matrix + Inkscape + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4 + + all + + + + + + + + diff --git a/share/extensions/render_barcode_datamatrix.py b/share/extensions/render_barcode_datamatrix.py new file mode 100755 index 0000000..4904c77 --- /dev/null +++ b/share/extensions/render_barcode_datamatrix.py @@ -0,0 +1,513 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright (C) 2009 John Beard john.j.beard@gmail.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This extension renders a DataMatrix 2D barcode, as specified in +BS ISO/IEC 16022:2006. Only ECC200 codes are considered, as these are the only +ones recommended for an "open" system. + +The size of the DataMatrix is variable between 10x10 to 144x144 + +The absolute size of the DataMatrix modules (the little squares) is also +variable. + +If more data is given than can be contained in one DataMatrix, +more than one DataMatrices will be produced. + +Text is encoded as ASCII (the standard provides for other options, but these are +not implemented). Consecutive digits are encoded in a compressed form, halving +the space required to store them. + +The basis processing flow is; + * Convert input string to codewords (modified ASCII and compressed digits) + * Split codewords into blocks of the right size for Reed-Solomon coding + * Interleave the blocks if required + * Apply Reed-Solomon coding + * De-interleave the blocks if required + * Place the codewords into the matrix bit by bit + * Render the modules in the matrix as squares +""" + +import inkex +from inkex import Rectangle + +INVALID_BIT = 2 + +# return parameters for the selected datamatrix size +# drow number of rows in each data region +# dcol number of cols in each data region +# reg_row number of rows of data regions +# reg_col number of cols of data regions +# nd number of data codewords per reed-solomon block +# nc number of ECC codewords per reed-solomon block +# inter number of interleaved Reed-Solomon blocks +SYMBOLS = { + # 'id': (nrow, ncol, drow, dcol, reg_row, reg_col, nd, nc, inter) + 'sq10': (10, 10, 8, 8, 1, 1, 3, 5, 1), + 'sq12': (12, 12, 10, 10, 1, 1, 5, 7, 1), + 'sq14': (14, 14, 12, 12, 1, 1, 8, 10, 1), + 'sq16': (16, 16, 14, 14, 1, 1, 12, 12, 1), + 'sq18': (18, 18, 16, 16, 1, 1, 18, 14, 1), + 'sq20': (20, 20, 18, 18, 1, 1, 22, 18, 1), + 'sq22': (22, 22, 20, 20, 1, 1, 30, 20, 1), + 'sq24': (24, 24, 22, 22, 1, 1, 36, 24, 1), + 'sq26': (26, 26, 24, 24, 1, 1, 44, 28, 1), + 'sq32': (32, 32, 14, 14, 2, 2, 62, 36, 1), + 'sq36': (36, 36, 16, 16, 2, 2, 86, 42, 1), + 'sq40': (40, 40, 18, 18, 2, 2, 114, 48, 1), + 'sq44': (44, 44, 20, 20, 2, 2, 144, 56, 1), + 'sq48': (48, 48, 22, 22, 2, 2, 174, 68, 1), + 'sq52': (52, 52, 24, 24, 2, 2, 102, 42, 2), + 'sq64': (64, 64, 14, 14, 4, 4, 140, 56, 2), + 'sq72': (72, 72, 16, 16, 4, 4, 92, 36, 4), + 'sq80': (80, 80, 18, 18, 4, 4, 114, 48, 4), + 'sq88': (88, 88, 20, 20, 4, 4, 144, 56, 4), + 'sq96': (96, 96, 22, 22, 4, 4, 174, 68, 4), + 'sq104': (104, 104, 24, 24, 4, 4, 136, 56, 6), + 'sq120': (120, 120, 18, 18, 6, 6, 175, 68, 6), + 'sq132': (132, 132, 20, 20, 6, 6, 163, 62, 8), + # there are two separate sections of the data matrix with different interleaving + # and reed-solomon parameters. this will be handled separately. + 'sq144': (144, 144, 22, 22, 6, 6, 0, 0, 0), + 'rect8x18': (8, 18, 6, 16, 1, 1, 5, 7, 1), + 'rect8x32': (8, 32, 6, 14, 1, 2, 10, 11, 1), + 'rect12x26': (12, 26, 10, 24, 1, 1, 16, 14, 1), + 'rect12x36': (12, 36, 10, 16, 1, 2, 22, 18, 1), + 'rect16x36': (16, 36, 14, 16, 1, 2, 32, 24, 1), + 'rect16x48': (16, 48, 14, 22, 1, 2, 49, 28, 1), +} + + + +# CODEWORD STREAM GENERATION ========================================= +# take the text input and return the codewords, +# including the Reed-Solomon error-correcting codes. +# ===================================================================== + +def get_codewords(text, nd, nc, inter, size144): + # convert the data to the codewords + data = list(encode_to_ascii(text)) + + if not size144: # render a "normal" datamatrix + data_blocks = partition_data(data, nd * inter) # partition into data blocks of length nd*inter -> inter Reed-Solomon block + + data_blocks = interleave(data_blocks, inter) # interleave consecutive inter blocks if required + + data_blocks = reed_solomon(data_blocks, nd, nc) # generate and append the Reed-Solomon codewords + + data_blocks = combine_interleaved(data_blocks, inter, nd, nc, False) # concatenate Reed-Solomon blocks bound for the same datamatrix + + else: # we have a 144x144 datamatrix + data_blocks = partition_data(data, 1558) # partition the data into datamtrix-sized chunks (1558 =156*8 + 155*2 ) + + for i in range(len(data_blocks)): # for each datamtrix + + inter = 8 + nd = 156 + nc = 62 + block1 = data_blocks[i][0:156 * 8] + block1 = interleave([block1], inter) # interleave into 8 blocks + block1 = reed_solomon(block1, nd, nc) # generate and append the Reed-Solomon codewords + + inter = 2 + nd = 155 + nc = 62 + block2 = data_blocks[i][156 * 8:] + block2 = interleave([block2], inter) # interleave into 2 blocks + block2 = reed_solomon(block2, nd, nc) # generate and append the Reed-Solomon codewords + + blocks = block1 + blocks.extend(block2) + + blocks = combine_interleaved(blocks, 10, nd, nc, True) + + data_blocks[i] = blocks[0] + + return data_blocks + + +# Takes a codeword stream and splits up into "inter" blocks. +# eg interleave( [1,2,3,4,5,6], 2 ) -> [1,3,5], [2,4,6] +def interleave(blocks, inter): + if inter == 1: # if we don't have to interleave, just return the blocks + return blocks + else: + result = [] + for block in blocks: # for each codeword block in the stream + block_length = int(len(block) / inter) # length of each interleaved block + inter_blocks = [[0] * block_length for i in range(inter)] # the interleaved blocks + + for i in range(block_length): # for each element in the interleaved blocks + for j in range(inter): # for each interleaved block + inter_blocks[j][i] = block[i * inter + j] + + result.extend(inter_blocks) # add the interleaved blocks to the output + + return result + + +# Combine interleaved blocks into the groups for the same datamatrix +# +# e.g combine_interleaved( [[d1, d3, d5, e1, e3, e5], [d2, d4, d6, e2, e4, e6]], 2, 3, 3 ) +# --> [[d1, d2, d3, d4, d5, d6, e1, e2, e3, e4, e5, e6]] +def combine_interleaved(blocks, inter, nd, nc, size144): + if inter == 1: # the blocks aren't interleaved + return blocks + else: + result = [] + for i in range(len(blocks) // inter): # for each group of "inter" blocks -> one full datamatrix + data_codewords = [] # interleaved data blocks + + if size144: + nd_range = 1558 # 1558 = 156*8 + 155*2 + nc_range = 620 # 620 = 62*8 + 62*2 + else: + nd_range = nd * inter + nc_range = nc * inter + + for j in range(nd_range): # for each codeword in the final list + data_codewords.append(blocks[i * inter + j % inter][j // inter]) + + for j in range(nc_range): # for each block, add the ecc codewords + data_codewords.append(blocks[i * inter + j % inter][nd + j // inter]) + + result.append(data_codewords) + return result + + +def encode_to_ascii(text): + """Encode this text into chunks, ascii or digits""" + i = 0 + while i < len(text): + # check for double digits, if the next char is also a digit + if text[i].isdigit() and (i < len(text) - 1) and text[i + 1].isdigit(): + yield int(text[i] + text[i + 1]) + 130 + i += 2 # move on 2 characters + else: # encode as a normal ascii, + yield ord(text[i]) + 1 # codeword is ASCII value + 1 (ISO 16022:2006 5.2.3) + i += 1 # next character + + +# partition data into blocks of the appropriate size to suit the +# Reed-Solomon block being used. +# e.g. partition_data([1,2,3,4,5], 3) -> [[1,2,3],[4,5,PAD]] +def partition_data(data, rs_data): + PAD_VAL = 129 # PAD codeword (ISO 16022:2006 5.2.3) + data_blocks = [] + i = 0 + while i < len(data): + if len(data) >= i + rs_data: # we have a whole block in our data + data_blocks.append(data[i:i + rs_data]) + i = i + rs_data + else: # pad out with the pad codeword + data_block = data[i:len(data)] # add any remaining data + pad_pos = len(data) + padded = False + while len(data_block) < rs_data: # and then pad with randomised pad codewords + if not padded: + data_block.append(PAD_VAL) # add a normal pad codeword + padded = True + else: + data_block.append(randomise_pad_253(PAD_VAL, pad_pos)) + pad_pos += 1 + data_blocks.append(data_block) + break + + return data_blocks + + +# Pad character randomisation, to prevent regular patterns appearing +# in the data matrix +def randomise_pad_253(pad_value, pad_position): + pseudo_random_number = ((149 * pad_position) % 253) + 1 + randomised = pad_value + pseudo_random_number + if randomised <= 254: + return randomised + else: + return randomised - 254 + + +# REED-SOLOMON ENCODING ROUTINES ===================================== + +# "prod(x,y,log,alog,gf)" returns the product "x" times "y" +def prod(x, y, log, alog, gf): + if x == 0 or y == 0: + return 0 + else: + result = alog[(log[x] + log[y]) % (gf - 1)] + return result + + +# generate the log & antilog lists: +def gen_log_alog(gf, pp): + log = [0] * gf + alog = [0] * gf + + log[0] = 1 - gf + alog[0] = 1 + + for i in range(1, gf): + alog[i] = alog[i - 1] * 2 + + if alog[i] >= gf: + alog[i] = alog[i] ^ pp + + log[alog[i]] = i + + return log, alog + + +# generate the generator polynomial coefficients: +def gen_poly_coeffs(nc, log, alog, gf): + c = [0] * (nc + 1) + c[0] = 1 + + for i in range(1, nc + 1): + c[i] = c[i - 1] + + j = i - 1 + while j >= 1: + c[j] = c[j - 1] ^ prod(c[j], alog[i], log, alog, gf) + j -= 1 + + c[0] = prod(c[0], alog[i], log, alog, gf) + + return c + + +# "ReedSolomon(wd,nd,nc)" takes "nd" data codeword values in wd[] +# and adds on "nc" check codewords, all within GF(gf) where "gf" is a +# power of 2 and "pp" is the value of its prime modulus polynomial */ +def reed_solomon(data, nd, nc): + # parameters of the polynomial arithmetic + gf = 256 # operating on 8-bit codewords -> Galois field = 2^8 = 256 + pp = 301 # prime modulus polynomial for ECC-200 is 0b100101101 = 301 (ISO 16022:2006 5.7.1) + + log, alog = gen_log_alog(gf, pp) + c = gen_poly_coeffs(nc, log, alog, gf) + + for block in data: # for each block of data codewords + + block.extend([0] * (nc + 1)) # extend to make space for the error codewords + + # generate "nc" checkwords in the list block + for i in range(0, nd): + k = block[nd] ^ block[i] + + for j in range(0, nc): + block[nd + j] = block[nd + j + 1] ^ prod(k, c[nc - j - 1], log, alog, gf) + + block.pop() + + return data + + +# MODULE PLACEMENT ROUTINES=========================================== +# These routines take a steam of codewords, and place them into the +# DataMatrix in accordance with Annex F of BS ISO/IEC 16022:2006 + +def bit(byte, bit_ch): + """bit() returns the bit'th bit of the byte""" + # the MSB is bit 1, LSB is bit 8 + return (byte >> (8 - bit_ch)) % 2 + + +def module(array, nrow, ncol, row, col, bit_ch): + """place a given bit with appropriate wrapping within array""" + if row < 0: + row = row + nrow + col = col + 4 - ((nrow + 4) % 8) + + if col < 0: + col = col + ncol + row = row + 4 - ((ncol + 4) % 8) + + array[row][col] = bit_ch + + +def place_square(case, array, nrow, ncol, row, col, char): + """Populate corner cases (0-3) and utah case (-1)""" + for i in range(8): + x, y = [ + [(row - 1, 0), (row - 1, 1), (row - 1, 2), (0, col - 2), + (0, col - 1), (1, col - 1), (2, col - 1), (3, col - 1)], + [(row - 3, 0), (row - 2, 0), (row - 1, 0), (0, col - 4), + (0, col - 3), (0, col - 2), (0, col - 1), (1, col - 1)], + [(row - 3, 0), (row - 2, 0), (row - 1, 0), (0, col - 2), + (0, col - 1), (1, col - 1), (2, col - 1), (3, col - 1)], + [(row - 1, 0), (row - 1, col - 1), (0, col - 3), (0, col - 2), + (0, col - 1), (1, col - 3), (1, col - 2), (1, col - 1)], + + # "utah" places the 8 bits of a utah-shaped symbol character in ECC200 + [(row - 2, col -2), (row - 2, col -1), (row - 1, col - 2), (row - 1, col - 1), + (row - 1, col), (row, col - 2), (row, col - 1), (row, col)], + ][case][i] + module(array, nrow, ncol, x, y, bit(char, i + 1)) + return 1 + +def place_bits(data, nrow, ncol): + """fill an nrow x ncol array with the bits from the codewords in data.""" + # initialise and fill with -1's (invalid value) + array = [[INVALID_BIT] * ncol for i in range(nrow)] + # Starting in the correct location for character #1, bit 8,... + char = 0 + row = 4 + col = 0 + while True: + + # first check for one of the special corner cases, then... + if (row == nrow) and (col == 0): + char += place_square(0, array, nrow, ncol, nrow, ncol, data[char]) + elif (row == nrow - 2) and (col == 0) and (ncol % 4): + char += place_square(1, array, nrow, ncol, nrow, ncol, data[char]) + elif (row == nrow - 2) and (col == 0) and (ncol % 8 == 4): + char += place_square(2, array, nrow, ncol, nrow, ncol, data[char]) + elif (row == nrow + 4) and (col == 2) and ((ncol % 8) == 0): + char += place_square(3, array, nrow, ncol, nrow, ncol, data[char]) + + # sweep upward diagonally, inserting successive characters,... + while (row >= 0) and (col < ncol): + if (row < nrow) and (col >= 0) and (array[row][col] == INVALID_BIT): + char += place_square(-1, array, nrow, ncol, row, col, data[char]) + row -= 2 + col += 2 + + row += 1 + col += 3 + + # & then sweep downward diagonally, inserting successive characters,... + while (row < nrow) and (col >= 0): + if (row >= 0) and (col < ncol) and (array[row][col] == INVALID_BIT): + char += place_square(-1, array, nrow, ncol, row, col, data[char]) + row += 2 + col -= 2 + + row += 3 + col += 1 + + # ... until the entire array is scanned + if not ((row < nrow) or (col < ncol)): + break + + # Lastly, if the lower righthand corner is untouched, fill in fixed pattern */ + if array[nrow - 1][ncol - 1] == INVALID_BIT: + array[nrow - 1][ncol - 2] = 0 + array[nrow - 1][ncol - 1] = 1 + array[nrow - 2][ncol - 1] = 0 + array[nrow - 2][ncol - 2] = 1 + + return array # return the array of 1's and 0's + + +def add_finder_pattern(array, data_nrow, data_ncol, reg_row, reg_col): + # get the total size of the datamatrix + nrow = (data_nrow + 2) * reg_row + ncol = (data_ncol + 2) * reg_col + + datamatrix = [[0] * ncol for i in range(nrow)] # initialise and fill with 0's + + for i in range(reg_col): # for each column of data regions + for j in range(nrow): + datamatrix[j][i * (data_ncol + 2)] = 1 # vertical black bar on left + datamatrix[j][i * (data_ncol + 2) + data_ncol + 1] = j % 2 # alternating blocks + + for i in range(reg_row): # for each row of data regions + for j in range(ncol): + datamatrix[i * (data_nrow + 2) + data_nrow + 1][j] = 1 # horizontal black bar at bottom + datamatrix[i * (data_nrow + 2)][j] = (j + 1) % 2 # alternating blocks + + for i in range(data_nrow * reg_row): + for j in range(data_ncol * reg_col): + # offset by 1, plus two for every addition block + dest_col = j + 1 + 2 * (j // data_ncol) + dest_row = i + 1 + 2 * (i // data_nrow) + + datamatrix[dest_row][dest_col] = array[i][j] # transfer from the plain bit array + + return datamatrix + + + + +class DataMatrix(inkex.GenerateExtension): + container_label = 'DataMatrix' + + def add_arguments(self, pars): + pars.add_argument("--text", default='Inkscape') + pars.add_argument("--symbol", type=self.arg_symbols, required=True) + pars.add_argument("--size", type=int, default=4) + + @staticmethod + def arg_symbols(value): + """Turn a symbol key into matrix metrics""" + try: + return SYMBOLS[value] + except KeyError: + raise inkex.AbortExtension("Invalid symbol size.") + + def generate(self): + size = str(self.options.size) + style = inkex.Style({'stroke': 'none', 'stroke-width': '1', 'fill': '#000000'}) + attribs = {'style': str(style), 'height': size, 'width': size} + + if not self.options.text: + raise inkex.AbortExtension("Please enter an input string.") + + # create a 2d list corresponding to the 1's and 0s of the DataMatrix + encoded = self.encode(self.options.text, *self.options.symbol) + for x, y in self.render_data_matrix(encoded): + attribs.update({'x': str(x), 'y': str(y)}) + yield Rectangle(**attribs) + + def encode(self, text, nrow, ncol, data_nrow, data_ncol, reg_row, reg_col, nd, nc, inter): + """ + Take an input string and convert it to a sequence (or sequences) + of codewords as specified in ISO/IEC 16022:2006 (section 5.2.3) + """ + # generate the codewords including padding and ECC + codewords = get_codewords(text, nd, nc, inter, nrow == 144) + + # break up into separate arrays if more than one DataMatrix is needed + module_arrays = [] + for codeword_stream in codewords: # for each datamatrix + # place the codewords' bits across the array as modules + bit_array = place_bits(codeword_stream, data_nrow * reg_row, data_ncol * reg_col) + # add finder patterns around the modules + module_arrays.append(add_finder_pattern(bit_array, data_nrow, data_ncol, reg_row, reg_col)) + + return module_arrays + + def render_data_matrix(self, module_arrays): + """turn a 2D array of 1's and 0's into a set of black squares""" + ncol = self.options.symbol[1] + size = self.options.size + spacing = ncol * size * 1.5 + for i, line in enumerate(module_arrays): + height = len(line) + width = len(line[0]) + + for y in range(height): # loop over all the modules in the datamatrix + for x in range(width): + if line[y][x] == 1: # A binary 1 is a filled square + yield (x * size + i * spacing, y * size) + elif line[y][x] == INVALID_BIT: # we have an invalid bit value + inkex.errormsg('Invalid bit value, {}!'.format(line[y][x])) + +if __name__ == '__main__': + DataMatrix().run() diff --git a/share/extensions/render_barcode_qrcode.inx b/share/extensions/render_barcode_qrcode.inx new file mode 100644 index 0000000..83ffd57 --- /dev/null +++ b/share/extensions/render_barcode_qrcode.inx @@ -0,0 +1,61 @@ + + + QR Code + org.inkscape.qr_code + + www.inkscape.org + + + + + + + + + + + + + + + + + + + + + + + + + + + false + 4 + + + + + + + + + + + 0.2 + + m 0,1 l 0.5,-1 l 0.5,1 + + + + all + + + + + + + + diff --git a/share/extensions/render_barcode_qrcode.py b/share/extensions/render_barcode_qrcode.py new file mode 100755 index 0000000..955465f --- /dev/null +++ b/share/extensions/render_barcode_qrcode.py @@ -0,0 +1,1081 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2009 Kazuhiko Arase (http://www.d-project.com/) +# 2010 Bulia Byak +# 2018 Kirill Okhotnikov +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Provide the QR Code rendering. +""" + +from __future__ import print_function + +from itertools import product + +import sys +import inkex +from inkex import Group, Rectangle, Use, PathElement + + +class QRCode(object): + PAD0 = 0xEC + PAD1 = 0x11 + + def __init__(self, correction): + self.typeNumber = 1 + self.errorCorrectLevel = correction + self.qrDataList = [] + self.modules = [] + self.moduleCount = 0 + + def getTypeNumber(self): + return self.typeNumber + + def setTypeNumber(self, typeNumber): + self.typeNumber = typeNumber + + def clearData(self): + self.qrDataList = [] + + def addData(self, data): + self.qrDataList.append(QR8BitByte(data)) + + def getDataCount(self): + return len(self.qrDataList) + + def getData(self, index): + return self.qrDataList[index] + + def isDark(self, row, col): + return (self.modules[row][col] if self.modules[row][col] is not None + else False) + + def getModuleCount(self): + return self.moduleCount + + def make(self): + self._make(False, self._getBestMaskPattern()) + + def _getBestMaskPattern(self): + minLostPoint = 0 + pattern = 0 + for i in range(8): + self._make(True, i) + lostPoint = QRUtil.getLostPoint(self) + if i == 0 or minLostPoint > lostPoint: + minLostPoint = lostPoint + pattern = i + return pattern + + def _make(self, test, maskPattern): + + self.moduleCount = self.typeNumber * 4 + 17 + self.modules = [[None] * self.moduleCount + for i in range(self.moduleCount)] + + self._setupPositionProbePattern(0, 0) + self._setupPositionProbePattern(self.moduleCount - 7, 0) + self._setupPositionProbePattern(0, self.moduleCount - 7) + + self._setupPositionAdjustPattern() + self._setupTimingPattern() + + self._setupTypeInfo(test, maskPattern) + + if self.typeNumber >= 7: + self._setupTypeNumber(test) + + data = QRCode._createData( + self.typeNumber, + self.errorCorrectLevel, + self.qrDataList) + + self._mapData(data, maskPattern) + + def _mapData(self, data, maskPattern): + + rows = list(range(self.moduleCount)) + cols = [col - 1 if col <= 6 else col + for col in range(self.moduleCount - 1, 0, -2)] + maskFunc = QRUtil.getMaskFunction(maskPattern) + + byteIndex = 0 + bitIndex = 7 + + for col in cols: + rows.reverse() + for row in rows: + for c in range(2): + if self.modules[row][col - c] is None: + + dark = False + if byteIndex < len(data): + dark = ((data[byteIndex] >> bitIndex) & 1) == 1 + if maskFunc(row, col - c): + dark = not dark + self.modules[row][col - c] = dark + + bitIndex -= 1 + if bitIndex == -1: + byteIndex += 1 + bitIndex = 7 + + def _setupPositionAdjustPattern(self): + pos = QRUtil.getPatternPosition(self.typeNumber) + for row in pos: + for col in pos: + if self.modules[row][col] is not None: + continue + for r in range(-2, 3): + for c in range(-2, 3): + self.modules[row + r][col + c] = ( + r == -2 or r == 2 or c == -2 or c == 2 + or (r == 0 and c == 0)) + + def _setupPositionProbePattern(self, row, col): + for r in range(-1, 8): + for c in range(-1, 8): + if (row + r <= -1 or self.moduleCount <= row + r + or col + c <= -1 or self.moduleCount <= col + c): + continue + self.modules[row + r][col + c] = ( + (0 <= r <= 6 and (c == 0 or c == 6)) + or (0 <= c <= 6 and (r == 0 or r == 6)) + or (2 <= r <= 4 and 2 <= c <= 4)) + + def _setupTimingPattern(self): + for r in range(8, self.moduleCount - 8): + if self.modules[r][6] is not None: + continue + self.modules[r][6] = r % 2 == 0 + for c in range(8, self.moduleCount - 8): + if self.modules[6][c] is not None: + continue + self.modules[6][c] = c % 2 == 0 + + def _setupTypeNumber(self, test): + bits = QRUtil.getBCHTypeNumber(self.typeNumber) + for i in range(18): + self.modules[i // 3][i % 3 + self.moduleCount - 8 - 3] = ( + not test and ((bits >> i) & 1) == 1) + for i in range(18): + self.modules[i % 3 + self.moduleCount - 8 - 3][i // 3] = ( + not test and ((bits >> i) & 1) == 1) + + def _setupTypeInfo(self, test, maskPattern): + + data = (self.errorCorrectLevel << 3) | maskPattern + bits = QRUtil.getBCHTypeInfo(data) + + # vertical + for i in range(15): + mod = not test and ((bits >> i) & 1) == 1 + if i < 6: + self.modules[i][8] = mod + elif i < 8: + self.modules[i + 1][8] = mod + else: + self.modules[self.moduleCount - 15 + i][8] = mod + + # horizontal + for i in range(15): + mod = not test and ((bits >> i) & 1) == 1 + if i < 8: + self.modules[8][self.moduleCount - i - 1] = mod + elif i < 9: + self.modules[8][15 - i - 1 + 1] = mod + else: + self.modules[8][15 - i - 1] = mod + + # fixed + self.modules[self.moduleCount - 8][8] = not test + + @staticmethod + def _createData(typeNumber, errorCorrectLevel, dataArray): + + rsBlocks = RSBlock.getRSBlocks(typeNumber, errorCorrectLevel) + + buffer = BitBuffer() + + for data in dataArray: + buffer.put(data.getMode(), 4) + buffer.put(data.getLength(), data.getLengthInBits(typeNumber)) + data.write(buffer) + + totalDataCount = sum(rsBlock.getDataCount() + for rsBlock in rsBlocks) + + if buffer.getLengthInBits() > totalDataCount * 8: + raise Exception('code length overflow. (%s>%s)' % + (buffer.getLengthInBits(), totalDataCount * 8)) + + # end code + if buffer.getLengthInBits() + 4 <= totalDataCount * 8: + buffer.put(0, 4) + + # padding + while buffer.getLengthInBits() % 8 != 0: + buffer.put(False) + + # padding + while True: + if buffer.getLengthInBits() >= totalDataCount * 8: + break + buffer.put(QRCode.PAD0, 8) + if buffer.getLengthInBits() >= totalDataCount * 8: + break + buffer.put(QRCode.PAD1, 8) + + return QRCode._createBytes(buffer, rsBlocks) + + @staticmethod + def _createBytes(buffer, rsBlocks): + + offset = 0 + + maxDcCount = 0 + maxEcCount = 0 + + dcdata = [None] * len(rsBlocks) + ecdata = [None] * len(rsBlocks) + + for r in range(len(rsBlocks)): + + dcCount = rsBlocks[r].getDataCount() + ecCount = rsBlocks[r].getTotalCount() - dcCount + + maxDcCount = max(maxDcCount, dcCount) + maxEcCount = max(maxEcCount, ecCount) + + dcdata[r] = [0] * dcCount + for i in range(len(dcdata[r])): + dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset] + offset += dcCount + + rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount) + rawPoly = Polynomial(dcdata[r], rsPoly.getLength() - 1) + + modPoly = rawPoly.mod(rsPoly) + ecdata[r] = [0] * (rsPoly.getLength() - 1) + for i in range(len(ecdata[r])): + modIndex = i + modPoly.getLength() - len(ecdata[r]) + ecdata[r][i] = modPoly.get(modIndex) if modIndex >= 0 else 0 + + totalCodeCount = sum(rsBlock.getTotalCount() + for rsBlock in rsBlocks) + + data = [0] * totalCodeCount + + index = 0 + + for i in range(maxDcCount): + for r in range(len(rsBlocks)): + if i < len(dcdata[r]): + data[index] = dcdata[r][i] + index += 1 + + for i in range(maxEcCount): + for r in range(len(rsBlocks)): + if i < len(ecdata[r]): + data[index] = ecdata[r][i] + index += 1 + + return data + + @staticmethod + def getMinimumQRCode(data, errorCorrectLevel): + mode = Mode.MODE_8BIT_BYTE # fixed to 8bit byte + qr = QRCode(correction=errorCorrectLevel) + qr.addData(data) + length = qr.getData(0).getLength() + for typeNumber in range(1, 11): + if length <= QRUtil.getMaxLength( + typeNumber, mode, errorCorrectLevel): + qr.setTypeNumber(typeNumber) + break + qr.make() + return qr + + +class Mode(object): + MODE_NUMBER = 1 << 0 + MODE_ALPHA_NUM = 1 << 1 + MODE_8BIT_BYTE = 1 << 2 + MODE_KANJI = 1 << 3 + + +class MaskPattern(object): + PATTERN000 = 0 + PATTERN001 = 1 + PATTERN010 = 2 + PATTERN011 = 3 + PATTERN100 = 4 + PATTERN101 = 5 + PATTERN110 = 6 + PATTERN111 = 7 + + +class QRUtil(object): + @staticmethod + def getPatternPosition(typeNumber): + return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1] + + PATTERN_POSITION_TABLE = [ + [], + [6, 18], + [6, 22], + [6, 26], + [6, 30], + [6, 34], + [6, 22, 38], + [6, 24, 42], + [6, 26, 46], + [6, 28, 50], + [6, 30, 54], + [6, 32, 58], + [6, 34, 62], + [6, 26, 46, 66], + [6, 26, 48, 70], + [6, 26, 50, 74], + [6, 30, 54, 78], + [6, 30, 56, 82], + [6, 30, 58, 86], + [6, 34, 62, 90], + [6, 28, 50, 72, 94], + [6, 26, 50, 74, 98], + [6, 30, 54, 78, 102], + [6, 28, 54, 80, 106], + [6, 32, 58, 84, 110], + [6, 30, 58, 86, 114], + [6, 34, 62, 90, 118], + [6, 26, 50, 74, 98, 122], + [6, 30, 54, 78, 102, 126], + [6, 26, 52, 78, 104, 130], + [6, 30, 56, 82, 108, 134], + [6, 34, 60, 86, 112, 138], + [6, 30, 58, 86, 114, 142], + [6, 34, 62, 90, 118, 146], + [6, 30, 54, 78, 102, 126, 150], + [6, 24, 50, 76, 102, 128, 154], + [6, 28, 54, 80, 106, 132, 158], + [6, 32, 58, 84, 110, 136, 162], + [6, 26, 54, 82, 110, 138, 166], + [6, 30, 58, 86, 114, 142, 170] + ] + + MAX_LENGTH = [ + [[41, 25, 17, 10], [34, 20, 14, 8], [27, 16, 11, 7], [17, 10, 7, 4]], + [[77, 47, 32, 20], [63, 38, 26, 16], [48, 29, 20, 12], [34, 20, 14, 8]], + [[127, 77, 53, 32], [101, 61, 42, 26], [77, 47, 32, 20], [58, 35, 24, 15]], + [[187, 114, 78, 48], [149, 90, 62, 38], [111, 67, 46, 28], [82, 50, 34, 21]], + [[255, 154, 106, 65], [202, 122, 84, 52], [144, 87, 60, 37], [106, 64, 44, 27]], + [[322, 195, 134, 82], [255, 154, 106, 65], [178, 108, 74, 45], [139, 84, 58, 36]], + [[370, 224, 154, 95], [293, 178, 122, 75], [207, 125, 86, 53], [154, 93, 64, 39]], + [[461, 279, 192, 118], [365, 221, 152, 93], [259, 157, 108, 66], [202, 122, 84, 52]], + [[552, 335, 230, 141], [432, 262, 180, 111], [312, 189, 130, 80], [235, 143, 98, 60]], + [[652, 395, 271, 167], [513, 311, 213, 131], [364, 221, 151, 93], [288, 174, 119, 74]] + ] + + @staticmethod + def getMaxLength(typeNumber, mode, errorCorrectLevel): + e = {1: 0, 0: 1, 3: 2, 2: 3}[errorCorrectLevel] + m = { + Mode.MODE_NUMBER: 0, + Mode.MODE_ALPHA_NUM: 1, + Mode.MODE_8BIT_BYTE: 2, + Mode.MODE_KANJI: 3 + }[mode] + return QRUtil.MAX_LENGTH[typeNumber - 1][e][m] + + @staticmethod + def getErrorCorrectPolynomial(errorCorrectLength): + a = Polynomial([1]) + for i in range(errorCorrectLength): + a = a.multiply(Polynomial([1, QRMath.gexp(i)])) + return a + + @staticmethod + def getMaskFunction(maskPattern): + return { + MaskPattern.PATTERN000: + lambda i, j: (i + j) % 2 == 0, + MaskPattern.PATTERN001: + lambda i, j: i % 2 == 0, + MaskPattern.PATTERN010: + lambda i, j: j % 3 == 0, + MaskPattern.PATTERN011: + lambda i, j: (i + j) % 3 == 0, + MaskPattern.PATTERN100: + lambda i, j: (i // 2 + j // 3) % 2 == 0, + MaskPattern.PATTERN101: + lambda i, j: (i * j) % 2 + (i * j) % 3 == 0, + MaskPattern.PATTERN110: + lambda i, j: ((i * j) % 2 + (i * j) % 3) % 2 == 0, + MaskPattern.PATTERN111: + lambda i, j: ((i * j) % 3 + (i + j) % 2) % 2 == 0 + }[maskPattern] + + @staticmethod + def getLostPoint(qrcode): + + moduleCount = qrcode.getModuleCount() + lostPoint = 0 + + # LEVEL1 + for row in range(moduleCount): + for col in range(moduleCount): + sameCount = 0 + dark = qrcode.isDark(row, col) + for r in range(-1, 2): + if row + r < 0 or moduleCount <= row + r: + continue + for c in range(-1, 2): + if col + c < 0 or moduleCount <= col + c: + continue + if r == 0 and c == 0: + continue + if dark == qrcode.isDark(row + r, col + c): + sameCount += 1 + if sameCount > 5: + lostPoint += (3 + sameCount - 5) + + # LEVEL2 + for row in range(moduleCount - 1): + for col in range(moduleCount - 1): + count = 0 + if qrcode.isDark(row, col): + count += 1 + if qrcode.isDark(row + 1, col): + count += 1 + if qrcode.isDark(row, col + 1): + count += 1 + if qrcode.isDark(row + 1, col + 1): + count += 1 + if count == 0 or count == 4: + lostPoint += 3 + + # LEVEL3 + for row in range(moduleCount): + for col in range(moduleCount - 6): + if (qrcode.isDark(row, col) + and not qrcode.isDark(row, col + 1) + and qrcode.isDark(row, col + 2) + and qrcode.isDark(row, col + 3) + and qrcode.isDark(row, col + 4) + and not qrcode.isDark(row, col + 5) + and qrcode.isDark(row, col + 6)): + lostPoint += 40 + + for col in range(moduleCount): + for row in range(moduleCount - 6): + if (qrcode.isDark(row, col) + and not qrcode.isDark(row + 1, col) + and qrcode.isDark(row + 2, col) + and qrcode.isDark(row + 3, col) + and qrcode.isDark(row + 4, col) + and not qrcode.isDark(row + 5, col) + and qrcode.isDark(row + 6, col)): + lostPoint += 40 + + # LEVEL4 + darkCount = 0 + for col in range(moduleCount): + for row in range(moduleCount): + if qrcode.isDark(row, col): + darkCount += 1 + + ratio = abs(100 * darkCount // moduleCount // moduleCount - 50) // 5 + lostPoint += ratio * 10 + + return lostPoint + + G15 = ((1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | + (1 << 2) | (1 << 1) | (1 << 0)) + G18 = ((1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | + (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0)) + G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1) + + @staticmethod + def getBCHTypeInfo(data): + d = data << 10 + while QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0: + d ^= (QRUtil.G15 << (QRUtil.getBCHDigit(d) - + QRUtil.getBCHDigit(QRUtil.G15))) + return ((data << 10) | d) ^ QRUtil.G15_MASK + + @staticmethod + def getBCHTypeNumber(data): + d = data << 12 + while QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0: + d ^= (QRUtil.G18 << (QRUtil.getBCHDigit(d) - + QRUtil.getBCHDigit(QRUtil.G18))) + return (data << 12) | d + + @staticmethod + def getBCHDigit(data): + digit = 0 + while data != 0: + digit += 1 + data >>= 1 + return digit + + @staticmethod + def stringToBytes(s): + return [ord(c) & 0xff for c in s] + + +class QR8BitByte(object): + + def __init__(self, data): + self.mode = Mode.MODE_8BIT_BYTE + self.data = data + + def getMode(self): + return self.mode + + def getData(self): + return self.data + + ''' + def write(self, buffer): raise Exception('not implemented.') + def getLength(self): raise Exception('not implemented.') + ''' + + def write(self, buffer): + data = QRUtil.stringToBytes(self.getData()) + for d in data: + buffer.put(d, 8) + + def getLength(self): + return len(QRUtil.stringToBytes(self.getData())) + + def getLengthInBits(self, type): + if 1 <= type < 10: # 1 - 9 + return { + Mode.MODE_NUMBER: 10, + Mode.MODE_ALPHA_NUM: 9, + Mode.MODE_8BIT_BYTE: 8, + Mode.MODE_KANJI: 8 + }[self.mode] + + elif type < 27: # 10 - 26 + return { + Mode.MODE_NUMBER: 12, + Mode.MODE_ALPHA_NUM: 11, + Mode.MODE_8BIT_BYTE: 16, + Mode.MODE_KANJI: 10 + }[self.mode] + + elif type < 41: # 27 - 40 + return { + Mode.MODE_NUMBER: 14, + Mode.MODE_ALPHA_NUM: 13, + Mode.MODE_8BIT_BYTE: 16, + Mode.MODE_KANJI: 12 + }[self.mode] + + else: + raise Exception('type:%s' % type) + + +class QRMath(object): + EXP_TABLE = None + LOG_TABLE = None + + @staticmethod + def _init(): + + QRMath.EXP_TABLE = [0] * 256 + for i in range(256): + QRMath.EXP_TABLE[i] = (1 << i if i < 8 else + QRMath.EXP_TABLE[i - 4] ^ QRMath.EXP_TABLE[i - 5] ^ + QRMath.EXP_TABLE[i - 6] ^ QRMath.EXP_TABLE[i - 8]) + + QRMath.LOG_TABLE = [0] * 256 + for i in range(255): + QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i + + @staticmethod + def glog(n): + if n < 1: + raise Exception('log(%s)' % n) + return QRMath.LOG_TABLE[n] + + @staticmethod + def gexp(n): + while n < 0: + n += 255 + while n >= 256: + n -= 255 + return QRMath.EXP_TABLE[n] + + +# initialize statics +QRMath._init() + + +class Polynomial(object): + def __init__(self, num, shift=0): + offset = 0 + length = len(num) + while offset < length and num[offset] == 0: + offset += 1 + self.num = num[offset:] + [0] * shift + + def get(self, index): + return self.num[index] + + def getLength(self): + return len(self.num) + + def __repr__(self): + return ','.join([str(self.get(i)) + for i in range(self.getLength())]) + + def toLogString(self): + return ','.join([str(QRMath.glog(self.get(i))) + for i in range(self.getLength())]) + + def multiply(self, e): + num = [0] * (self.getLength() + e.getLength() - 1) + for i in range(self.getLength()): + for j in range(e.getLength()): + num[i + j] ^= QRMath.gexp(QRMath.glog(self.get(i)) + + QRMath.glog(e.get(j))) + return Polynomial(num) + + def mod(self, e): + if self.getLength() - e.getLength() < 0: + return self + ratio = QRMath.glog(self.get(0)) - QRMath.glog(e.get(0)) + num = self.num[:] + for i in range(e.getLength()): + num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio) + return Polynomial(num).mod(e) + + +class RSBlock(object): + RS_BLOCK_TABLE = [ + + # L + # M + # Q + # H + + # 1 + [1, 26, 19], + [1, 26, 16], + [1, 26, 13], + [1, 26, 9], + + # 2 + [1, 44, 34], + [1, 44, 28], + [1, 44, 22], + [1, 44, 16], + + # 3 + [1, 70, 55], + [1, 70, 44], + [2, 35, 17], + [2, 35, 13], + + # 4 + [1, 100, 80], + [2, 50, 32], + [2, 50, 24], + [4, 25, 9], + + # 5 + [1, 134, 108], + [2, 67, 43], + [2, 33, 15, 2, 34, 16], + [2, 33, 11, 2, 34, 12], + + # 6 + [2, 86, 68], + [4, 43, 27], + [4, 43, 19], + [4, 43, 15], + + # 7 + [2, 98, 78], + [4, 49, 31], + [2, 32, 14, 4, 33, 15], + [4, 39, 13, 1, 40, 14], + + # 8 + [2, 121, 97], + [2, 60, 38, 2, 61, 39], + [4, 40, 18, 2, 41, 19], + [4, 40, 14, 2, 41, 15], + + # 9 + [2, 146, 116], + [3, 58, 36, 2, 59, 37], + [4, 36, 16, 4, 37, 17], + [4, 36, 12, 4, 37, 13], + + # 10 + [2, 86, 68, 2, 87, 69], + [4, 69, 43, 1, 70, 44], + [6, 43, 19, 2, 44, 20], + [6, 43, 15, 2, 44, 16] + ] + + def __init__(self, totalCount, dataCount): + self.totalCount = totalCount + self.dataCount = dataCount + + def getDataCount(self): + return self.dataCount + + def getTotalCount(self): + return self.totalCount + + def __repr__(self): + return '(total=%s,data=%s)' % (self.totalCount, self.dataCount) + + @staticmethod + def getRSBlocks(typeNumber, errorCorrectLevel): + rsBlock = RSBlock.getRsBlockTable(typeNumber, errorCorrectLevel) + length = len(rsBlock) // 3 + list = [] + for i in range(length): + count = rsBlock[i * 3 + 0] + totalCount = rsBlock[i * 3 + 1] + dataCount = rsBlock[i * 3 + 2] + list += [RSBlock(totalCount, dataCount)] * count + return list + + @staticmethod + def getRsBlockTable(typeNumber, errorCorrectLevel): + return { + 1: RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0], + 0: RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1], + 3: RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2], + 2: RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3] + }[errorCorrectLevel] + + +class BitBuffer(object): + def __init__(self, inclements=32): + self.inclements = inclements + self.buffer = [0] * self.inclements + self.length = 0 + + def getBuffer(self): + return self.buffer + + def getLengthInBits(self): + return self.length + + def get(self, index): + return ((self.buffer[index // 8] >> (7 - index % 8)) & 1) == 1 + + def putBit(self, bit): + if self.length == len(self.buffer) * 8: + self.buffer += [0] * self.inclements + if bit: + self.buffer[self.length // 8] |= (0x80 >> (self.length % 8)) + self.length += 1 + + def put(self, num, length): + for i in range(length): + self.putBit(((num >> (length - i - 1)) & 1) == 1) + + def __repr__(self): + return ''.join('1' if self.get(i) else '0' + for i in range(self.getLengthInBits())) + + +class GridDrawer(object): + """Mechanism to draw grids of boxes""" + def __init__(self, invert_code, smooth_factor): + self.invert_code = invert_code + self.smoothFactor = smooth_factor + self.grid = None + + def set_grid(self, grid): + if len({len(g) for g in grid}) != 1: + raise Exception("The array is not rectangular") + else: + self.grid = grid + + def row_count(self): + return len(self.grid) if self.grid is not None else 0 + + def col_count(self): + return len(self.grid[0]) if self.row_count() > 0 else 0 + + def isDark(self, col, row): + inside = col >= 0 and 0 <= row < self.row_count() and col < self.col_count() + return False if not inside else self.grid[row][col] != self.invert_code + + @staticmethod + def moveByDirection(xyd): + dm = {0: (1, 0), 1: (0, -1), 2: (-1, 0), 3: (0, 1)} + return xyd[0] + dm[xyd[2]][0], xyd[1] + dm[xyd[2]][1] + + @staticmethod + def makeDirectionsTable(): + result = [] + for cfg in product(range(2), repeat=4): + result.append([]) + for d in range(4): + if cfg[3 - d] == 0 and cfg[3 - (d - 1) % 4] != 0: + result[-1].append(d) + return result + + def createVertexesForAdvDrawer(self): + dirTable = self.makeDirectionsTable() + result = [] + # Create vertex + for row in range(self.row_count() + 1): + for col in range(self.col_count() + 1): + indx = (2 ** 0 if self.isDark(col - 0, row - 1) else 0) + \ + (2 ** 1 if self.isDark(col - 1, row - 1) else 0) + \ + (2 ** 2 if self.isDark(col - 1, row - 0) else 0) + \ + (2 ** 3 if self.isDark(col - 0, row - 0) else 0) + + for d in dirTable[indx]: + result.append((col, row, d, len(dirTable[indx]) > 1)) + + return result + + def getSmoothPosition(self, v, extraSmoothFactor=1.0): + vn = self.moveByDirection(v) + sc = extraSmoothFactor * self.smoothFactor / 2.0 + sc1 = 1.0 - sc + return (v[0] * sc1 + vn[0] * sc, v[1] * sc1 + vn[1] * sc), (v[0] * sc + vn[0] * sc1, v[1] * sc + vn[1] * sc1) + + +class QrCode(inkex.GenerateExtension): + """Generate QR Code Extension""" + def add_arguments(self, pars): + pars.add_argument("--text", default='www.inkscape.org') + pars.add_argument("--typenumber", type=int, default=0) + pars.add_argument("--correctionlevel", type=int, default=0) + pars.add_argument("--encoding", default="latin_1") + pars.add_argument("--modulesize", type=float, default=10.0) + pars.add_argument("--invert", type=inkex.Boolean, default="false") + pars.add_argument("--drawtype", default="greedy") + pars.add_argument("--smoothval", type=float, default=0.2) + pars.add_argument("--symbolid", default='') + pars.add_argument("--groupid", default='') + + def generate(self): + + scale = self.svg.unittouu('1px') # convert to document units + opt = self.options + + if not opt.text: + raise inkex.AbortExtension('Please enter an input text') + elif opt.drawtype == "symbol" and opt.symbolid == "": + raise inkex.AbortExtension('Please enter symbol id') + + if sys.version_info >= (3, 0, 0): + # for Python 3 ugly hack to represent bytes as str for Python2 compatibility + text_bytes = bytes(opt.text, opt.encoding).decode("latin_1") + text_str = str(opt.text) + else: + text_bytes = opt.text + text_str = opt.text.decode('utf-8') + + grp = Group() + grp.set('inkscape:label', 'QR Code: ' + text_str) + if opt.groupid: + grp.set('id', opt.groupid) + pos_x, pos_y = self.svg.namedview.center + grp.transform.add_translate(pos_x, pos_y) + if scale: + grp.transform.add_scale(scale) + + # GENERATE THE QRCODE + if opt.typenumber == 0: + # Automatic QR code size` + code = QRCode.getMinimumQRCode(text_bytes, opt.correctionlevel) + else: + # Manual QR code size + code = QRCode(correction=opt.correctionlevel) + code.setTypeNumber(int(opt.typenumber)) + code.addData(text_bytes) + code.make() + + self.boxsize = opt.modulesize + self.invert_code = opt.invert + self.margin = 4 + self.draw = GridDrawer(opt.invert, opt.smoothval) + self.draw.set_grid(code.modules) + self.render_svg(grp, opt.drawtype) + return grp + + def render_adv(self, greedy): + + verts = self.draw.createVertexesForAdvDrawer() + qrPathStr = "" + while len(verts) > 0: + vertsIndexStart = len(verts) - 1 + vertsIndexCur = vertsIndexStart + ringIndexes = [] + while True: + ringIndexes.append(vertsIndexCur) + nextPos = self.draw.moveByDirection(verts[vertsIndexCur]) + nextIndexes = [i for i, x in enumerate(verts) if x[0] == nextPos[0] and x[1] == nextPos[1]] + if len(nextIndexes) == 0 or len(nextIndexes) > 2: + raise Exception("Vertex " + str(next_c) + " has no connections") + elif len(nextIndexes) == 1: + vertsIndexNext = nextIndexes[0] + else: + if {verts[nextIndexes[0]][2], verts[nextIndexes[1]][2]} != {(verts[vertsIndexCur][2] - 1) % 4, (verts[vertsIndexCur][2] + 1) % 4}: + raise Exception("Bad next vertex directions " + str(verts[nextIndexes[0]]) + str(verts[nextIndexes[1]])) + + # Greedy - CCW turn, proud and neutral CW turn + vertsIndexNext = nextIndexes[0] if (greedy == "g") == (verts[nextIndexes[0]][2] == (verts[vertsIndexCur][2] + 1) % 4) else nextIndexes[1] + + if vertsIndexNext == vertsIndexStart: + break + + vertsIndexCur = vertsIndexNext + + posStart, _ = self.draw.getSmoothPosition(verts[ringIndexes[0]]) + qrPathStr += "M %f,%f " % self.get_svg_pos(posStart[0], posStart[1]) + for ri in range(len(ringIndexes)): + vc = verts[ringIndexes[ri]] + vn = verts[ringIndexes[(ri + 1) % len(ringIndexes)]] + if vn[2] != vc[2]: + if (greedy != "n") or not vn[3]: + # Add bezier + # Opt length http://spencermortensen.com/articles/bezier-circle/ + # c = 0.552284749 + ex = 1 - 0.552284749 + _, bs = self.draw.getSmoothPosition(vc) + _, bp1 = self.draw.getSmoothPosition(vc, ex) + bp2, _ = self.draw.getSmoothPosition(vn, ex) + bf, _ = self.draw.getSmoothPosition(vn) + qrPathStr += "L %f,%f " % self.get_svg_pos(bs[0], bs[1]) + qrPathStr += "C %f,%f %f,%f %f,%f " \ + % (self.get_svg_pos(bp1[0], bp1[1]) + self.get_svg_pos(bp2[0], bp2[1]) + + self.get_svg_pos(bf[0], bf[1])) + else: + # Add straight + qrPathStr += "L %f,%f " % self.get_svg_pos(vn[0], vn[1]) + + qrPathStr += "z " + + # Delete already processed vertex + for i in sorted(ringIndexes, reverse=True): + del verts[i] + + path = PathElement() + path.set('d', qrPathStr) + return path + + def render_obsolete(self): + for row in range(self.draw.row_count()): + for col in range(self.draw.col_count()): + if self.draw.isDark(col, row): + x, y = self.get_svg_pos(col, row) + return Rectangle.new(x, y, self.boxsize, self.boxsize) + + def render_path(self, pointStr): + singlePath = self.get_icon_path_str(pointStr) + pathStr = "" + for row in range(self.draw.row_count()): + for col in range(self.draw.col_count()): + if self.draw.isDark(col, row): + x, y = self.get_svg_pos(col, row) + pathStr += "M %f,%f " % (x, y) + singlePath + " z " + + path = PathElement() + path.set('d', pathStr) + return path + + def render_symbol(self): + symbol = self.svg.getElementById(self.options.symbolid) + if symbol is None: + raise inkex.AbortExtension("Can't find symbol " + self.options.symbolid) + bbox = symbol.path.bounding_box() + transform = inkex.Transform(scale=( + float(self.boxsize) / bbox.width, + float(self.boxsize) / bbox.height, + )) + for row in range(self.draw.row_count()): + for col in range(self.draw.col_count()): + if self.draw.isDark(col, row): + x, y = self.get_svg_pos(col, row) + # Inkscape doesn't support width/height on use tags + return Use.new(symbol, x, y, transform=transform) + + render_pathcustom = lambda self: self.render_path(self.options.symbolid) + render_neutral = lambda self: self.render_adv("n") + render_greedy = lambda self: self.render_adv("g") + render_proud = lambda self: self.render_adv("p") + render_simple = lambda self: self.render_path("h 1 v 1 h -1") + + def render_circle(self): + s = 'm 0.5,0.5 ' \ + 'c 0.2761423745,0 0.5,0.2238576255 0.5,0.5 ' \ + 'c 0,0.2761423745 -0.2238576255,0.5 -0.5,0.5 ' \ + 'c -0.2761423745,0 -0.5,-0.2238576255 -0.5,-0.5 ' \ + 'c 0,-0.2761423745 0.2238576255,-0.5 0.5,-0.5' + return self.render_path(s) + + def render_svg(self, grp, drawtype): + """Render to svg""" + drawer = getattr(self, "render_" + drawtype, self.render_obsolete) + if drawer is None: + raise Exception("Unknown draw type: " + drawtype) + + canvas_width = (self.draw.col_count() + 2 * self.margin) * self.boxsize + canvas_height = (self.draw.row_count() + 2 * self.margin) * self.boxsize + + # white background providing margin: + rect = grp.add(Rectangle.new(0, 0, canvas_width, canvas_height)) + rect.style['stroke'] = 'none' + rect.style['fill'] = "black" if self.invert_code else "white" + + qrg = grp.add(Group()) + qrg.style['stroke'] = 'none' + qrg.style['fill'] = "white" if self.invert_code else "black" + qrg.add(drawer()) + + def get_svg_pos(self, col, row): + return (col + self.margin) * self.boxsize, (row + self.margin) * self.boxsize + + def get_icon_path_str(self, pointStr): + result = "" + digBuffer = "" + for c in pointStr: + if c.isdigit() or c == "-" or c == '.': + digBuffer += c + else: + if len(digBuffer) > 0: + result += str(float(digBuffer) * self.boxsize) + digBuffer = "" + result += c + + if len(digBuffer) > 0: + result += str(float(digBuffer) * self.boxsize) + + return result + + + +if __name__ == '__main__': + QrCode().run() diff --git a/share/extensions/render_gear_rack.inx b/share/extensions/render_gear_rack.inx new file mode 100644 index 0000000..13a744f --- /dev/null +++ b/share/extensions/render_gear_rack.inx @@ -0,0 +1,19 @@ + + + Rack Gear + org.inkscape.render.rack_gear + 100. + 10.0 + 20.0 + + all + + + + + + + + diff --git a/share/extensions/render_gear_rack.py b/share/extensions/render_gear_rack.py new file mode 100755 index 0000000..201df6f --- /dev/null +++ b/share/extensions/render_gear_rack.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# +# Copyright (C) 2013 Brett Graham (hahahaha @ hahaha.org) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Generate a gear rack as SVG. +""" + +from math import acos, cos, radians, sin, sqrt, tan + +import inkex + +def involute_intersect_angle(Rb, R): + Rb, R = float(Rb), float(R) + return (sqrt(R ** 2 - Rb ** 2) / Rb) - (acos(Rb / R)) + + +def point_on_circle(radius, angle): + x = radius * cos(angle) + y = radius * sin(angle) + return x, y + + +def points_to_svgd(p): + """ + p: list of 2 tuples (x, y coordinates) + """ + f = p[0] + p = p[1:] + svgd = 'M{:.3f},{:.3f}'.format(f[0], f[1]) + for x in p: + svgd += 'L{:.3f},{:.3f}'.format(x[0], x[1]) + return svgd + + +class RackGear(inkex.GenerateExtension): + container_label = 'Rendered Gear Rack' + def add_arguments(self, pars): + pars.add_argument("--length", type=float, default=100.0, help="Rack Length") + pars.add_argument("--spacing", type=float, default=10.0, help="Tooth Spacing") + pars.add_argument("--angle", type=float, default=20.0, help="Contact Angle") + + def generate(self): + length = self.svg.unittouu(str(self.options.length) + 'px') + spacing = self.svg.unittouu(str(self.options.spacing) + 'px') + angle = radians(self.options.angle) + + # generate points: list of (x, y) pairs + points = [] + x = 0 + tas = tan(angle) * spacing + while x < length: + # move along path, generating the next 'tooth' + points.append((x, 0)) + points.append((x + tas, spacing)) + points.append((x + spacing, spacing)) + points.append((x + spacing + tas, 0)) + x += spacing * 2. + + path = points_to_svgd(points) + + # Create SVG Path for gear + style = {'stroke': '#000000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))} + yield inkex.PathElement(style=str(inkex.Style(style)), d=str(path)) + +if __name__ == '__main__': + RackGear().run() diff --git a/share/extensions/render_gears.inx b/share/extensions/render_gears.inx new file mode 100644 index 0000000..94623d9 --- /dev/null +++ b/share/extensions/render_gears.inx @@ -0,0 +1,26 @@ + + + Gear + org.ekips.filter.gears + 24 + 20.0 + 20.0 + 20.0 + + + + + + + + all + + + + + + + + diff --git a/share/extensions/render_gears.py b/share/extensions/render_gears.py new file mode 100755 index 0000000..00690b5 --- /dev/null +++ b/share/extensions/render_gears.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +# +# Copyright (C) 2007 Aaron Spike (aaron @ ekips.org) +# Copyright (C) 2007 Tavmjong Bah (tavmjong @ free.fr) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Generate gears in SVG +""" + +from math import acos, cos, pi, radians, sin, sqrt + +import inkex +from inkex import PathElement + + +def involute_intersect_angle(Rb, R): + Rb, R = float(Rb), float(R) + return (sqrt(R ** 2 - Rb ** 2) / Rb) - (acos(Rb / R)) + + +def point_on_circle(radius, angle): + x = radius * cos(angle) + y = radius * sin(angle) + return x, y + + +def points_to_svgd(p): + f = p[0] + p = p[1:] + svgd = 'M{:.5f},{:.5f}'.format(f[0], f[1]) + for x in p: + svgd += ' L{:.5f},{:.5f}'.format(x[0], x[1]) + svgd += 'z' + return svgd + + +class Gears(inkex.GenerateExtension): + container_label = 'Rendered Gears' + + def add_arguments(self, pars): + pars.add_argument("--teeth", type=int, default=24, help="Number of teeth") + pars.add_argument("--pitch", type=float, default=20.0, help="Circular Pitch") + pars.add_argument("--angle", type=float, default=20.0, help="Pressure Angle") + pars.add_argument("--centerdiameter", type=float, default=10.0, help="Diameter of hole") + pars.add_argument("--unit", default="px", help="unit for pitch and center diameter") + + def generate(self): + teeth = self.options.teeth + pitch = self.svg.unittouu(str(self.options.pitch) + self.options.unit) + angle = self.options.angle # Angle of tangent to tooth at circular pitch wrt radial line. + centerdiameter = self.svg.unittouu(str(self.options.centerdiameter) + self.options.unit) + + # print >>sys.stderr, "Teeth: %s\n" % teeth + + two_pi = 2.0 * pi + + # Pitch (circular pitch): Length of the arc from one tooth to the next) + # Pitch diameter: Diameter of pitch circle. + pitch_diameter = float(teeth) * pitch / pi + pitch_radius = pitch_diameter / 2.0 + + # Base Circle + base_diameter = pitch_diameter * cos(radians(angle)) + base_radius = base_diameter / 2.0 + + # Diametrial pitch: Number of teeth per unit length. + pitch_diametrial = float(teeth) / pitch_diameter + + # Addendum: Radial distance from pitch circle to outside circle. + addendum = 1.0 / pitch_diametrial + + # Outer Circle + outer_radius = pitch_radius + addendum + outer_diameter = outer_radius * 2.0 + + # Tooth thickness: Tooth width along pitch circle. + tooth = (pi * pitch_diameter) / (2.0 * float(teeth)) + + # Undercut? + undercut = (2.0 / (sin(radians(angle)) ** 2)) + needs_undercut = teeth < undercut + + # Clearance: Radial distance between top of tooth on one gear to bottom of gap on another. + clearance = 0.0 + + # Dedendum: Radial distance from pitch circle to root diameter. + dedendum = addendum + clearance + + # Root diameter: Diameter of bottom of tooth spaces. + root_radius = pitch_radius - dedendum + root_diameter = root_radius * 2.0 + + half_thick_angle = two_pi / (4.0 * float(teeth)) + pitch_to_base_angle = involute_intersect_angle(base_radius, pitch_radius) + pitch_to_outer_angle = involute_intersect_angle(base_radius, outer_radius) - pitch_to_base_angle + + centers = [(x * two_pi / float(teeth)) for x in range(teeth)] + + points = [] + + for c in centers: + + # Angles + pitch1 = c - half_thick_angle + base1 = pitch1 - pitch_to_base_angle + outer1 = pitch1 + pitch_to_outer_angle + + pitch2 = c + half_thick_angle + base2 = pitch2 + pitch_to_base_angle + outer2 = pitch2 - pitch_to_outer_angle + + # Points + b1 = point_on_circle(base_radius, base1) + p1 = point_on_circle(pitch_radius, pitch1) + o1 = point_on_circle(outer_radius, outer1) + + b2 = point_on_circle(base_radius, base2) + p2 = point_on_circle(pitch_radius, pitch2) + o2 = point_on_circle(outer_radius, outer2) + + if root_radius > base_radius: + pitch_to_root_angle = pitch_to_base_angle - involute_intersect_angle(base_radius, root_radius) + root1 = pitch1 - pitch_to_root_angle + root2 = pitch2 + pitch_to_root_angle + r1 = point_on_circle(root_radius, root1) + r2 = point_on_circle(root_radius, root2) + p_tmp = [r1, p1, o1, o2, p2, r2] + else: + r1 = point_on_circle(root_radius, base1) + r2 = point_on_circle(root_radius, base2) + p_tmp = [r1, b1, p1, o1, o2, p2, b2, r2] + + points.extend(p_tmp) + + path = points_to_svgd(points) + + # Create SVG Path for gear + style = {'stroke': '#000000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))} + gear = PathElement() + gear.style = style + gear.path = path + yield gear + + if centerdiameter > 0.0: + arc = PathElement.arc((0, 0), centerdiameter / 2) + arc.style = style + yield arc + + +if __name__ == '__main__': + Gears().run() diff --git a/share/extensions/replace_font.inx b/share/extensions/replace_font.inx new file mode 100644 index 0000000..01fd456 --- /dev/null +++ b/share/extensions/replace_font.inx @@ -0,0 +1,36 @@ + + + Replace font + org.inkscape.replace_font + + org.inkscape.output.svg.inkscape + + + + + + + + + + + + + + + + + + + + + all + + + + + + + diff --git a/share/extensions/replace_font.py b/share/extensions/replace_font.py new file mode 100755 index 0000000..474dc34 --- /dev/null +++ b/share/extensions/replace_font.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2010 Craig Marshall, craig9 [at] gmail.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +""" +This script finds all fonts in the current drawing that match the +specified find font, and replaces them with the specified replacement +font. + +It can also replace all fonts indiscriminately, and list all fonts +currently being used. +""" +import inkex + +text_tags = ['{http://www.w3.org/2000/svg}tspan', + '{http://www.w3.org/2000/svg}text', + '{http://www.w3.org/2000/svg}flowRoot', + '{http://www.w3.org/2000/svg}flowPara', + '{http://www.w3.org/2000/svg}flowSpan'] +font_attributes = ['font-family', '-inkscape-font-specification'] + +def set_font(node, new_font, style=None): + """ + Sets the font attribute in the style attribute of node, using the + font name stored in new_font. If the style dict is open already, + it can be passed in, otherwise it will be optned anyway. + + Returns a dirty boolean flag + """ + dirty = False + if not style: + style = get_style(node) + if style: + for att in font_attributes: + if att in style: + style[att] = new_font + set_style(node, style) + dirty = True + return dirty + +def find_replace_font(node, find, replace): + """ + Searches the relevant font attributes/styles of node for find, and + replaces them with replace. + + Returns a dirty boolean flag + """ + dirty = False + style = get_style(node) + if style: + for att in font_attributes: + if att in style and style[att].strip().lower() == find: + set_font(node, replace, style) + dirty = True + return dirty + +def is_styled_text(node): + """ + Returns true if the tag in question is a "styled" element that + can hold text. + """ + return node.tag in text_tags and 'style' in node.attrib + +def is_text(node): + """ + Returns true if the tag in question is an element that + can hold text. + """ + return node.tag in text_tags + + +def get_style(node): + """ + Sugar coated way to get style dict from a node + """ + if 'style' in node.attrib: + return dict(inkex.Style.parse_str(node.attrib['style'])) + +def set_style(node, style): + """ + Sugar coated way to set the style dict, for node + """ + node.attrib['style'] = str(inkex.Style(style)) + +def get_fonts(node): + """ + Given a node, returns a list containing all the fonts that + the node is using. + """ + fonts = [] + s = get_style(node) + if not s: + return fonts + for a in font_attributes: + if a in s: + fonts.append(s[a]) + return fonts + +def report_replacements(num): + """ + Sends a message to the end user showing success of failure + of the font replacement + """ + if num == 0: + inkex.errormsg(_('Couldn\'t find anything using that font, please ensure the spelling and spacing is correct.')) + +def report_findings(findings): + """ + Tells the user which fonts were found, if any + """ + if len(findings) == 0: + inkex.errormsg(_("Didn't find any fonts in this document/selection.")) + else: + if len(findings) == 1: + inkex.errormsg(_(u"Found the following font only: %s") % findings[0]) + else: + inkex.errormsg(_(u"Found the following fonts:\n%s") % '\n'.join(findings)) + +class ReplaceFont(inkex.EffectExtension): + """ + Replaces all instances of one font with another + """ + def add_arguments(self, pars): + pars.add_argument("--fr_find") + pars.add_argument("--fr_replace") + pars.add_argument("--r_replace") + pars.add_argument("--action") + pars.add_argument("--scope") + + def find_child_text_items(self, node): + """ + Recursive method for appending all text-type elements + to self.selected_items + """ + if is_text(node): + yield node + + for child in node: + for textchild in self.find_child_text_items(child): + yield textchild + + def relevant_items(self, scope): + """ + Depending on the scope, returns all text elements, or all + selected text elements including nested children + """ + items = [] + to_return = [] + + selected = self.svg + if scope == "selection_only": + selected = self.svg.selected.values() + + for item in selected: + items.extend(self.find_child_text_items(item)) + + if not items: + return inkex.errormsg(_("There was nothing selected")) + + return items + + def find_replace(self, nodes, find, replace): + """ + Walks through nodes, replacing fonts as it goes according + to find and replace + """ + replacements = 0 + for node in nodes: + if find_replace_font(node, find, replace): + replacements += 1 + report_replacements(replacements) + + def replace_all(self, nodes, replace): + """ + Walks through nodes, setting fonts indiscriminately. + """ + replacements = 0 + for node in nodes: + if set_font(node, replace): + replacements += 1 + report_replacements(replacements) + + def list_all(self, nodes): + """ + Walks through nodes, building a list of all fonts found, then + reports to the user with that list + """ + fonts_found = [] + for node in nodes: + for f in get_fonts(node): + if not f in fonts_found: + fonts_found.append(f) + report_findings(sorted(fonts_found)) + + def effect(self): + if not self.options.action: + return inkex.errormsg("Nothing to do, no action specified.") + action = self.options.action.strip("\"") # TODO Is this a bug? (Extra " characters) + scope = self.options.scope + + relevant_items = self.relevant_items(scope) + + if action == "find_replace": + find = self.options.fr_find + if find is None or find == "": + return inkex.errormsg(_("Please enter a search string in the find box.")) + find = find.strip().lower() + replace = self.options.fr_replace + if replace is None or replace == "": + return inkex.errormsg(_("Please enter a replacement font in the replace with box.")) + self.find_replace(relevant_items, find, replace) + elif action == "replace_all": + replace = self.options.r_replace + if replace is None or replace == "": + return inkex.errormsg(_("Please enter a replacement font in the replace all box.")) + self.replace_all(relevant_items, replace) + elif action == "list_only": + self.list_all(relevant_items) + +if __name__ == "__main__": + ReplaceFont().run() diff --git a/share/extensions/restack.inx b/share/extensions/restack.inx new file mode 100644 index 0000000..70935cf --- /dev/null +++ b/share/extensions/restack.inx @@ -0,0 +1,57 @@ + + + Restack + org.inkscape.filter.restack + + + + + + + + + + + + + + + + 0.00 + + + + + + + + + + + + + + + + + + + + + + + + + + path + + + + + + diff --git a/share/extensions/restack.py b/share/extensions/restack.py new file mode 100755 index 0000000..0d7553c --- /dev/null +++ b/share/extensions/restack.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# +# Copyright (C) 2007-2011 Rob Antonishen; rob.antonishen@gmail.com +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +import random + +import inkex +from inkex.utils import KeyDict +from inkex import SvgDocumentElement + +# Old settings, supported because users click 'ok' without looking. +XAN = KeyDict({'l': 'left', 'r': 'right', 'm': 'center_x'}) +YAN = KeyDict({'t': 'top', 'b': 'bottom', 'm': 'center_y'}) +CUSTOM_DIRECTION = {270: 'tb', 90: 'bt', 0: 'lr', 360: 'lr', 180: 'rl'} + +class Restack(inkex.EffectExtension): + """Change the z-order of objects based on their position on the canvas""" + restack_help = staticmethod(lambda: None) + + def add_arguments(self, pars): + pars.add_argument("--tab", type=self.arg_method('restack'), default=self.restack_positional) + pars.add_argument("--direction", default="tb", help="direction to restack") + pars.add_argument("--angle", type=float, default=0.0, help="arbitrary angle") + pars.add_argument("--xanchor", default="m", help="horizontal point to compare") + pars.add_argument("--yanchor", default="m", help="vertical point to compare") + pars.add_argument("--zsort", default="rev", help="Restack mode based on Z-Order") + pars.add_argument("--nb_direction", default='', help='Direction tab') + + def effect(self): + if not self.svg.selected: + raise inkex.AbortExtension("There is no selection to restack.") + + # process selection to get list of objects to be arranged + parentnode = None + for node in self.svg.selection.filter(SvgDocumentElement): + parentnode = node + self.svg.set_selection(*list(node)) + + if parentnode is None: + parentnode = self.svg.get_current_layer() + + self.options.tab(parentnode) + + def restack_positional(self, parentnode): + """Restack based on canvas position""" + # move them to the top of the object stack in this order. + for node in sorted(self.svg.selected.values(), key=self._sort): + parentnode.append(node) + return True + + def _sort(self, node): + x, y = self.options.xanchor, self.options.yanchor + selbox = self.svg.selection.bounding_box() + direction = self.options.direction + if 'custom' in self.options.nb_direction: + direction = self.options.angle + return node.bounding_box().get_anchor(x, y, direction, selbox) + + def restack_z_order(self, parentnode): + """Restack based on z-order""" + objects = list(self.svg.selected.values()) + if self.options.zsort == "rev": + objects.reverse() + elif self.options.zsort == "rand": + random.shuffle(objects) + if parentnode is not None: + for item in objects: + parentnode.append(item) + return True + +if __name__ == '__main__': + Restack().run() diff --git a/share/extensions/rtree.inx b/share/extensions/rtree.inx new file mode 100644 index 0000000..7c3efbd --- /dev/null +++ b/share/extensions/rtree.inx @@ -0,0 +1,17 @@ + + + Random Tree + org.ekips.filter.turtle_rtree + 100.0 + 40.0 + false + + all + + + + + + diff --git a/share/extensions/rtree.py b/share/extensions/rtree.py new file mode 100755 index 0000000..298ca72 --- /dev/null +++ b/share/extensions/rtree.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# Copyright (C) 2015 su_v, suv-sf@users.sf.net +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import inkex +from inkex import turtle as pturtle + +class TurtleRtree(inkex.GenerateExtension): + """Create RTree Turtle path""" + def add_arguments(self, pars): + pars.add_argument("--size", type=float, default=100.0, + help="initial branch size") + pars.add_argument("--minimum", type=float, default=4.0, + help="minimum branch size") + pars.add_argument("--pentoggle", type=inkex.Boolean, default=False, + help="Lift pen for backward steps") + + def generate(self): + self.options.size = self.svg.unittouu(str(self.options.size) + 'px') + self.options.minimum = self.svg.unittouu(str(self.options.minimum) + 'px') + point = self.svg.namedview.center + + style = inkex.Style({ + 'stroke-linejoin': 'miter', 'stroke-width': str(self.svg.unittouu('1px')), + 'stroke-opacity': '1.0', 'fill-opacity': '1.0', + 'stroke': '#000000', 'stroke-linecap': 'butt', + 'fill': 'none' + }) + tur = pturtle.pTurtle() + tur.pu() + tur.setpos(point) + tur.pd() + tur.rtree(self.options.size, self.options.minimum, self.options.pentoggle) + return inkex.PathElement(d=tur.getPath(), style=str(style)) + +if __name__ == '__main__': + TurtleRtree().run() diff --git a/share/extensions/rubberstretch.inx b/share/extensions/rubberstretch.inx new file mode 100644 index 0000000..63a67f7 --- /dev/null +++ b/share/extensions/rubberstretch.inx @@ -0,0 +1,16 @@ + + + Rubber Stretch + org.inkscape.path.rubber_stretch + 25 + 25 + + path + + + + + + diff --git a/share/extensions/rubberstretch.py b/share/extensions/rubberstretch.py new file mode 100755 index 0000000..6c2be96 --- /dev/null +++ b/share/extensions/rubberstretch.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Distorts selected paths, stretching it vertically while squeezing horizontally. + +Amount is controlled by ratio parameter. + +Curve gives further effect by bunching the surface towards the ends. +""" + +from inkex.utils import X, Y +from pathmodifier import Diffeo + + +class RubberStretch(Diffeo): + """Distort selected paths""" + ratio = property(lambda self: -(self.options.ratio / 100)) + curve = property(lambda self: min(self.options.curve / 100, 0.99)) + + def add_arguments(self, pars): + pars.add_argument("-r", "--ratio", type=float, default=0.5) + pars.add_argument("-c", "--curve", type=float, default=0.5) + + def applyDiffeo(self, bpt, vects=()): + for vect in vects: + vect[0] -= bpt[0] + vect[1] -= bpt[1] + vect[1] *= -1 + bpt[1] *= -1 + bx0 = self.bbox.center.x + by0 = -self.bbox.center.y + + x, y = (bpt[0] - bx0), (bpt[1] - by0) + sx1 = (1 + self.curve * (x / self.bbox.width + 1) * \ + (x / self.bbox.width - 1)) * 2 ** self.ratio + sy1 = (1 + self.curve * (y / self.bbox.height + 1) * \ + (y / self.bbox.height - 1)) * 2 ** self.ratio + bpt[0] = bx0 + x * sy1 + bpt[1] = by0 + y / sx1 + for vect in vects: + dx_dx = sy1 + dx_dy = x * 2 * self.curve * y / self.bbox.height / self.bbox.height * 2 ** self.ratio + dy_dx = -y * 2 * self.curve * x / self.bbox.width / \ + self.bbox.width * 2 ** self.ratio / sx1 / sx1 + dy_dy = 1 / sx1 + vect[0] = dx_dx * vect[X] + dx_dy * vect[Y] + vect[1] = dy_dx * vect[X] + dy_dy * vect[Y] + + # --spherify + # s=((x*x+y*y)/(w*w+h*h))**(-a/2) + # bpt[0]=x0+s*x + # bpt[1]=y0+s*y + # for v in vects: + # dx,dy=v + # v[0]=(1-a/2/(x*x+y*y)*2*x*x)*s*dx+( -a/2/(x*x+y*y)*2*y*x)*s*dy + # v[1]=( -a/2/(x*x+y*y)*2*x*y)*s*dx+(1-a/2/(x*x+y*y)*2*y*y)*s*dy + + for vect in vects: + vect[0] += bpt[0] + vect[1] += bpt[1] + vect[1] *= -1 + bpt[1] *= -1 + + +if __name__ == "__main__": + RubberStretch().run() diff --git a/share/extensions/scribus_export_pdf.inx b/share/extensions/scribus_export_pdf.inx new file mode 100644 index 0000000..c0186e0 --- /dev/null +++ b/share/extensions/scribus_export_pdf.inx @@ -0,0 +1,41 @@ + + + Export to PDF via Scribus + com.activdesign.export.scribus + scribus_export_pdf.py + scribus + + + + + + + + + 0 + false + false + + + + + + + + + + + .pdf + application/pdf + Scribus CMYK PDF (*.PDF) + Exports the document to PDF using scribus + + + + diff --git a/share/extensions/scribus_export_pdf.py b/share/extensions/scribus_export_pdf.py new file mode 100644 index 0000000..3243791 --- /dev/null +++ b/share/extensions/scribus_export_pdf.py @@ -0,0 +1,153 @@ +#!/bin/env python3 +# coding=utf-8 +# +# Copyright (C) 2019 Marc Jeanmougin, Cédric Gémy, a-l-e +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +# + + +import os +import re +import sys +import inkex +from inkex import AbortExtension +from inkex.base import TempDirMixin +from inkex.command import take_snapshot, call + + +SCRIBUS_EXE = "scribus" +VERSION_REGEX = re.compile(r"(\d+)\.(\d+)\.(\d+)") + + +# several things could be taken into consideration here : +# - the fact that openDoc works on svg files is a workaround +# - the commented parts should be the correct way to do things +# and even include a possibility to add margins +# BUT currently fails to place the SVG document +# (object placed top-left instead of SVG placed top-left) +class Scribus(TempDirMixin, inkex.OutputExtension): + def add_arguments(self, arg_parser): + arg_parser.add_argument("--pdf-version", type=int, dest="pdfVersion", default="13", + help="PDF version (see Scribus documentation)") + arg_parser.add_argument("--bleed", type=float, dest="bleed", default="0", + help="Bleed value") + arg_parser.add_argument("--bleed-marks", type=inkex.Boolean, dest="bleedMarks", + default=False, help="Bleed marks") + arg_parser.add_argument("--color-marks", type=inkex.Boolean, dest="colorMarks", + default=False, help="Color Marks") + arg_parser.add_argument("--intent", type=int, dest="intent", default="0", + help="0: Perceptual, 1: Relative Colorimetric, 2: Saturation, 3: Absolute Colorimetric") + arg_parser.add_argument("--title", type=str, dest="title", default="", help="PDF title") + #arg_parser.add_argument("--fonts", type=int, dest="fonts", default="1", + # help="Embed fonts : 0 for embedding, 1 to convert to path, 2 to prevent embedding") + + def generate_script(self, stream, width, height, icc): + margin = self.options.bleed + pdfVersion = self.options.pdfVersion + embedFonts = 1 #self.options.fonts + bleedMarks = self.options.bleedMarks + colorMarks = self.options.colorMarks + if ((bleedMarks or colorMarks) and margin < 7): + raise AbortExtension("You need 7mm bleed to show cutting marks or color marks") + if (bleedMarks or colorMarks): + margin = margin - 7 #because scribus is weird. At the time of 1.5.5, it adds 7 when those are set. + stream.write(""" +import scribus +import sys +icc = "{icc}" +margin = {margin} +class exportPDF(): + def __init__(self, svg=sys.argv[1], o=sys.argv[2]): + #scribus.newDocument(({width},{height}), (margin,margin,margin,margin), + # PORTRAIT, 1, UNIT_MILLIMETERS, PAGE_1, 0, 1) + #scribus.placeSVG(svg, 0, 0) + scribus.openDoc(svg) + pdf = scribus.PDFfile() + scribus.setUnit(UNIT_MILLIMETERS) + pdf.bleedl = margin + pdf.bleedr = margin + pdf.bleedt = margin + pdf.bleedb = margin + pdf.useDocBleeds = False + pdf.cropMarks = {bleedMarks} + pdf.bleedMarks = {bleedMarks} + pdf.colorMarks = {colorMarks} + pdf.version = {pdfVersion} + pdf.allowAnnots = True + pdf.allowChange = True + pdf.allowCopy = True + pdf.allowPrinting = True + pdf.noembicc = False #embed icc ! + pdf.solidpr = icc + pdf.imagepr = icc + pdf.printprofc = icc + pdf.intenti = {self.options.intent} + pdf.intents = {self.options.intent} + pdf.info = "{self.options.title}" + pdf.profiles = True + pdf.profilei = True + pdf.outdst = 1 # output destination : 0=screen, 1=printer + pdf.file = o + pdf.compress = True + pdf.compressmtd = 0 # 0 = automatic, 1 = jpeg ; 2 = zip, 3 = none + pdf.quality = 0 #max + pdf.fontEmbedding = {embedFonts} + pdf.thumbnails = True + + pdf.save() +exportPDF()""".format(**locals())) + + def save(self, stream): + scribus_version = call(SCRIBUS_EXE, '-g', '--version').decode('utf-8') + version_match = VERSION_REGEX.search(scribus_version) + if version_match is None: + raise AbortExtension("Could not detect Scribus version ({})".format(scribus_version)) + major = int(version_match.group(1)) + minor = int(version_match.group(2)) + point = int(version_match.group(3)) + if (major < 1) or (major == 1 and minor < 5): + raise AbortExtension("Found Scribus {}. This extension requires Scribus 1.5.x".format(version_match.group(0))) + + input_file = self.options.input_file + py_file = os.path.join(self.tempdir, 'scribus.py') + svg_file = os.path.join(self.tempdir, 'in.svg') + profiles = self.svg.defs.findall("svg:color-profile") + if len(profiles) == 0: + raise AbortExtension("You did not link a color profile in this document.") + elif len(profiles) > 1: + raise AbortExtension("More than one color profiles are linked in this document. No output generated.") + iccPath = profiles[0].get("xlink:href") + + + with open(input_file) as f: + with open(svg_file, "w") as f1: + for line in f: + f1.write(line) + f.close() + + pdf_file = os.path.join(self.tempdir, 'out.pdf') + width = self.svg.unittouu(self.svg.get('width')) + height = self.svg.unittouu(self.svg.get('height')) + + with open(py_file, 'w') as fhl: + self.generate_script(fhl, width, height, iccPath) + call(SCRIBUS_EXE, '-g', '-py', py_file, svg_file, pdf_file) + with open(pdf_file, 'rb') as fhl: + stream.write(fhl.read()) + +if __name__ == '__main__': + Scribus().run() + diff --git a/share/extensions/seamless_pattern.svg b/share/extensions/seamless_pattern.svg new file mode 100644 index 0000000..df47259 --- /dev/null +++ b/share/extensions/seamless_pattern.svg @@ -0,0 +1,560 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + Seamless pattern + Use the layers "Pattern Foreground" and "Pattern Background" on the pattern page to create your design. The separation into two layers will make it easier for you to create and edit overlapping content like a foreground drawing with a background fill. The layer named "Pattern" is for using the seamless pattern, copying it to other documents, adding opacity etc. Select the group on the page, and use Object->Pattern->Objects to Pattern to convert your creation into a fill pattern. The layer "Preview Background" provides an easy way to preview your creation if it contains transparency.Changing this layer's visibility will not alter the pattern. If an object is moved outside the pattern/page limits, it will be difficult to select it. To move it back onto the page, select the object using the rubberband selection (click and drag a selection box) with the selection tool.Then move it back onto the page, using the arrow keys or the "Align and Distribute" dialog (Shift+Ctrl+A). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Seamless Pattern + Seamless Pattern + Seamless Pattern + + diff --git a/share/extensions/setup.cfg b/share/extensions/setup.cfg new file mode 100644 index 0000000..b7e4789 --- /dev/null +++ b/share/extensions/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test=pytest diff --git a/share/extensions/setup.py b/share/extensions/setup.py new file mode 100755 index 0000000..26ad3c2 --- /dev/null +++ b/share/extensions/setup.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2012 Martin Owens +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3.0 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +""" +This is a test framework setup.py only, not used for packaging. +""" + +from setuptools import setup + +setup( + name='inkscape-core-extensions', + version='0.0', + description='Inkscape core extensions for testing', + long_description='N/A', + author='Inkscape Authors', + url='https://gitlab.com/inkscape/extensions', + author_email='developers@inkscape.org', + test_suite='tests', + platforms='linux', + license='GPLv2', + classifiers=[ + 'Development Status :: 0 - Test Only', + 'Intended Audience :: Developers', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + ], + install_requires=['scour', 'numpy', 'pyserial'], + setup_requires=["pytest-runner"], + tests_require=["pytest", "pytest-cov"] +) diff --git a/share/extensions/setup_typography_canvas.inx b/share/extensions/setup_typography_canvas.inx new file mode 100644 index 0000000..5109962 --- /dev/null +++ b/share/extensions/setup_typography_canvas.inx @@ -0,0 +1,19 @@ + + + 1 - Setup Typography Canvas + org.inkscape.typography.setup_typography_canvas + 1000 + 750 + 700 + 500 + 250 + + all + + + + + + diff --git a/share/extensions/setup_typography_canvas.py b/share/extensions/setup_typography_canvas.py new file mode 100755 index 0000000..61d4539 --- /dev/null +++ b/share/extensions/setup_typography_canvas.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2011 Felipe Correa da Silva Sanches +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import inkex +from inkex import Guide + + +class SetupTypographyCanvas(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("-e", "--emsize", type=int, default=1000) + pars.add_argument("-c", "--caps", type=int, default=700, help="Caps Height") + pars.add_argument("-x", "--xheight", type=int, default=500) + pars.add_argument("-a", "--ascender", type=int, default=750) + pars.add_argument("-d", "--descender", type=int, default=250) + + def create_horizontal_guideline(self, name, position): + return self.svg.add(Guide().move_to(0, position, (0, 1)).update(inkscape__label=name)) + + def create_vertical_guideline(self, name, position): + return self.svg.add(Guide().move_to(position, 0, (1, 0)).update(inkscape__label=name)) + + def effect(self): + # Get all the options + emsize = self.options.emsize + ascender = self.options.ascender + caps = self.options.caps + xheight = self.options.xheight + descender = self.options.descender + + # Get access to main SVG document element + self.svg.set("width", str(emsize)) + self.svg.set("height", str(emsize)) + self.svg.set("viewBox", "0 0 " + str(emsize) + " " + str(emsize)) + + baseline = descender + # Create guidelines + self.create_horizontal_guideline("baseline", baseline) + self.create_horizontal_guideline("ascender", baseline + ascender) + self.create_horizontal_guideline("caps", baseline + caps) + self.create_horizontal_guideline("xheight", baseline + xheight) + self.create_horizontal_guideline("descender", baseline - descender) + + namedview = self.svg.namedview + namedview.set('inkscape:document-units', 'px') + namedview.set('inkscape:cx', str(emsize / 2.0)) + namedview.set('inkscape:cy', str(emsize / 2.0)) + +if __name__ == '__main__': + SetupTypographyCanvas().run() diff --git a/share/extensions/spirograph.inx b/share/extensions/spirograph.inx new file mode 100644 index 0000000..bf5e764 --- /dev/null +++ b/share/extensions/spirograph.inx @@ -0,0 +1,23 @@ + + + Spirograph + org.ekips.filter.spirograph + 100.0 + 60.0 + 50.0 + + + + + 0.0 + 16 + + all + + + + + + diff --git a/share/extensions/spirograph.py b/share/extensions/spirograph.py new file mode 100755 index 0000000..5d7dcf4 --- /dev/null +++ b/share/extensions/spirograph.py @@ -0,0 +1,102 @@ +#! /usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2007 Joel Holdsworth joel@airwebreathe.org.uk +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import math +import inkex + +class Spirograph(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("--primaryr", type=float, default=60.0, + help="The radius of the outer gear") + pars.add_argument("--secondaryr", type=float, default=100.0, + help="The radius of the inner gear") + pars.add_argument("--penr", type=float, default=50.0, + help="The distance of the pen from the inner gear") + pars.add_argument("--gearplacement", default="inside", + help="Selects whether the gear is inside or outside the ring") + pars.add_argument("--rotation", type=float, default=0.0, + help="The number of degrees to rotate the image by") + pars.add_argument("--quality", type=int, default=16, + help="The quality of the calculated output") + + def effect(self): + self.options.primaryr = self.svg.unittouu(str(self.options.primaryr) + 'px') + self.options.secondaryr = self.svg.unittouu(str(self.options.secondaryr) + 'px') + self.options.penr = self.svg.unittouu(str(self.options.penr) + 'px') + + if self.options.secondaryr == 0: + return + if self.options.quality == 0: + return + + if self.options.gearplacement.strip(' ').lower().startswith('outside'): + a = self.options.primaryr + self.options.secondaryr + flip = -1 + else: + a = self.options.primaryr - self.options.secondaryr + flip = 1 + + ratio = a / self.options.secondaryr + if ratio == 0: + return + scale = 2 * math.pi / (ratio * self.options.quality) + + rotation = - math.pi * self.options.rotation / 180 + + new = inkex.PathElement() + new.style = inkex.Style(stroke='#000000', fill='none', stroke_width='1.0') + + path_string = '' + maxPointCount = 1000 + + for i in range(maxPointCount): + + theta = i * scale + + view_center = self.svg.namedview.center + x = a * math.cos(theta + rotation) + \ + self.options.penr * math.cos(ratio * theta + rotation) * flip + \ + view_center[0] + y = a * math.sin(theta + rotation) - \ + self.options.penr * math.sin(ratio * theta + rotation) + \ + view_center[1] + + dx = (-a * math.sin(theta + rotation) - ratio * self.options.penr * math.sin(ratio * theta + rotation) * flip) * scale / 3 + dy = (a * math.cos(theta + rotation) - ratio * self.options.penr * math.cos(ratio * theta + rotation)) * scale / 3 + + if i <= 0: + path_string += 'M {},{} C {},{} '.format(str(x), str(y), str(x + dx), str(y + dy)) + else: + path_string += '{},{} {},{}'.format(str(x - dx), str(y - dy), str(x), str(y)) + + if math.fmod(i / ratio, self.options.quality) == 0 and i % self.options.quality == 0: + path_string += 'Z' + break + else: + if i == maxPointCount - 1: + pass # we reached the allowed maximum of points, stop here + else: + path_string += ' C {},{} '.format(str(x + dx), str(y + dy)) + + new.path = path_string + self.svg.get_current_layer().append(new) + +if __name__ == '__main__': + Spirograph().run() diff --git a/share/extensions/straightseg.inx b/share/extensions/straightseg.inx new file mode 100644 index 0000000..1f0cdb8 --- /dev/null +++ b/share/extensions/straightseg.inx @@ -0,0 +1,16 @@ + + + Straighten Segments + org.inkscape.filter.segment_straightener + 50.0 + 1 + + path + + + + + + diff --git a/share/extensions/straightseg.py b/share/extensions/straightseg.py new file mode 100755 index 0000000..172cebb --- /dev/null +++ b/share/extensions/straightseg.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +"""Straintens path segments (but doesn't turn them into lines)""" +import inkex + +from inkex.bezier import percent_point + +class SegmentStraightener(inkex.EffectExtension): + """Make segments straiter""" + def add_arguments(self, pars): + pars.add_argument("-p", "--percent", type=float, default=10.0,\ + help="move curve handles PERCENT percent closer to a straight line") + pars.add_argument("-b", "--behavior", type=int, default=1,\ + help="straightening behavior for cubic segments") + + def effect(self): + for node in self.svg.selection.get(inkex.PathElement).values(): + path = node.path.to_arrays() + last = [] + sub_start = [] + for cmd, params in path: + if cmd == 'C': + if self.options.behavior <= 1: + #shorten handles towards end points + params[:2] = percent_point(params[:2], last[:], self.options.percent) + params[2:4] = percent_point(params[2:4], params[-2:], self.options.percent) + else: + #shorten handles towards thirds of the segment + dest1 = percent_point(last[:], params[-2:], 33.3) + dest2 = percent_point(params[-2:], last[:], 33.3) + params[:2] = percent_point(params[:2], dest1[:], self.options.percent) + params[2:4] = percent_point(params[2:4], dest2[:], self.options.percent) + elif cmd == 'Q': + dest = percent_point(last[:], params[-2:], 50) + params[:2] = percent_point(params[:2], dest, self.options.percent) + if cmd == 'M': + sub_start = params[-2:] + if cmd == 'Z': + last = sub_start[:] + else: + last = params[-2:] + node.path = path + +if __name__ == '__main__': + SegmentStraightener().run() diff --git a/share/extensions/svg2fxg.inx b/share/extensions/svg2fxg.inx new file mode 100644 index 0000000..f9ed756 --- /dev/null +++ b/share/extensions/svg2fxg.inx @@ -0,0 +1,14 @@ + + + FXG Output + org.inkscape.output.fxg + + .fxg + text/xml+fxg + Flash XML Graphics (*.fxg) + Adobe's XML Graphics file format + + + svg2fxg.xsl + + diff --git a/share/extensions/svg2fxg.xsl b/share/extensions/svg2fxg.xsl new file mode 100644 index 0000000..1e25264 --- /dev/null +++ b/share/extensions/svg2fxg.xsl @@ -0,0 +1,3008 @@ + + + + + + + + + + + + + + + + + + + 2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Collapsed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pad + reflect + repeat + + + + + + + linearRGB + rgb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pad + reflect + repeat + + + + + + + linearRGB + rgb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + 0 + 1 + + + + + + + + + + + + nonZero + + + evenOdd + + + + + + + + nonZero + + + evenOdd + + + + nonZero + + + evenOdd + + + + + + + + nonZero + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FXG does not support dasharrays + + + + + FXG does not support dashoffsets + + + + + + + bevel + round + miter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + miter + + + + + + + + round + square + round + none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + preserve + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ + preserve + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + normal + bold + normal + + + + + + + + + + + + + + + + + + + + + + + + + + + Arial + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + superscript + subscript + -1000 + 1000 + + + + + % + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rl + tb + + + + rotate270 + + + + + + + + + + + + + + + + + + + + + underline + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ltr + rtl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ascent + + + + + + + + + + + + + + + + preserve + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ascent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + 0 + + + + + + + + + + 0 + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + 0 + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Embed('') + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FXG does not support polygons + + + + + FXG does not support polylines + + + diff --git a/share/extensions/svg2xaml.inx b/share/extensions/svg2xaml.inx new file mode 100644 index 0000000..dea7865 --- /dev/null +++ b/share/extensions/svg2xaml.inx @@ -0,0 +1,15 @@ + + + XAML Output + org.inkscape.output.xaml + false + + .xaml + text/xml+xaml + Microsoft XAML (*.xaml) + Microsoft's GUI definition format + + + svg2xaml.xsl + + diff --git a/share/extensions/svg2xaml.xsl b/share/extensions/svg2xaml.xsl new file mode 100644 index 0000000..7036f0f --- /dev/null +++ b/share/extensions/svg2xaml.xsl @@ -0,0 +1,2987 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Collapsed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + Absolute + RelativeToBoundingBox + + + + + + Pad + Reflect + Repeat + + + + + + + + + + + 0 + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Absolute + RelativeToBoundingBox + + + + + + Pad + Reflect + Repeat + + + + + + + + + + + 0 + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + 0 + 1 + + + + + + + + + + + NonZero + + + EvenOdd + + + + + + + NonZero + + + EvenOdd + + + + NonZero + + + EvenOdd + + + + + + + + NonZero + + + + + + + + + + + + + + + + + + + + + + + + #000000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bevel + Round + Miter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Round + Square + Flat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #000000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fill + + + + + + + + + + preserve + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 12 + + + + + + + + + + + + + + + + + + + + + + + + + + Thin + ExtraLight + Light + Normal + Medium + SemiBold + Bold + ExtraBold + Black + ExtraBlack + normal + + + + + + + + + + + + + + + + + + + + + + + + + + + Arial + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Normal + Superscript + Subscript + + % + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rl + tb + + + + rotate270 + + + + + + + + + + + + + + + + + + + + + Underline + Strikethrough + Overline + None + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LeftToRight + RightToLeft + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Left + Center + Right + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/svg_fonts/EMSAllure.svg b/share/extensions/svg_fonts/EMSAllure.svg new file mode 100644 index 0000000..79115cf --- /dev/null +++ b/share/extensions/svg_fonts/EMSAllure.svg @@ -0,0 +1,235 @@ + + + + + + +Font name: EMS Allure +License: SIL Open Font License http://scripts.sil.org/OFL +Created by: Sheldon B. Michaels +SVG font conversion by: Windell H. Oskay +A derivative of: Allura +Designer: Rob Leuschke, TypeSETit +Link: http://www.typesetit.com +Google font page: https://fonts.google.com/specimen/Allura + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/EMSElfin.svg b/share/extensions/svg_fonts/EMSElfin.svg new file mode 100644 index 0000000..989b235 --- /dev/null +++ b/share/extensions/svg_fonts/EMSElfin.svg @@ -0,0 +1,236 @@ + + + + + + +Font name: EMS Elfin +License: SIL Open Font License http://scripts.sil.org/OFL +Created by: Sheldon B. Michaels +SVG font conversion by: Windell H. Oskay +A derivative of: Mountains of Christmas +Designer: Crystal Kluge, Tart Workshop +Link: http://www.tartworkshop.com +Google font page: https://fonts.google.com/specimen/Mountains+of+Christmas +Note: SIL OFL per metadata; Google cites Apache License, version 2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/EMSFelix.svg b/share/extensions/svg_fonts/EMSFelix.svg new file mode 100644 index 0000000..7ab9b16 --- /dev/null +++ b/share/extensions/svg_fonts/EMSFelix.svg @@ -0,0 +1,235 @@ + + + + + + +Font name: EMS Felix +License: SIL Open Font License http://scripts.sil.org/OFL +Created by: Sheldon B. Michaels +SVG font conversion by: Windell H. Oskay +A derivative of: Felipa +Designer: Fontstage +Link: https://twitter.com/fontstage +Google font page: https://fonts.google.com/specimen/Felipa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/EMSNixish.svg b/share/extensions/svg_fonts/EMSNixish.svg new file mode 100644 index 0000000..6710f2b --- /dev/null +++ b/share/extensions/svg_fonts/EMSNixish.svg @@ -0,0 +1,235 @@ + + + + + + +Font name: EMS Nixish +License: SIL Open Font License http://scripts.sil.org/OFL +Created by: Sheldon B. Michaels +SVG font conversion by: Windell H. Oskay +A derivative of: Nixie One +Designer: Jovanny Lemonad +Link: http://jovanny.ru +Google font page: https://fonts.google.com/specimen/Nixie+One + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/EMSNixishItalic.svg b/share/extensions/svg_fonts/EMSNixishItalic.svg new file mode 100644 index 0000000..df9c3ae --- /dev/null +++ b/share/extensions/svg_fonts/EMSNixishItalic.svg @@ -0,0 +1,235 @@ + + + + + + +Font name: EMS Nixish Italic +License: SIL Open Font License http://scripts.sil.org/OFL +Created by: Sheldon B. Michaels +SVG font conversion by: Windell H. Oskay +A derivative of: Nixie One +Designer: Jovanny Lemonad +Link: http://jovanny.ru +Google font page: https://fonts.google.com/specimen/Nixie+One + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/EMSOsmotron.svg b/share/extensions/svg_fonts/EMSOsmotron.svg new file mode 100644 index 0000000..03f053c --- /dev/null +++ b/share/extensions/svg_fonts/EMSOsmotron.svg @@ -0,0 +1,235 @@ + + + + + + +Font name: EMS Osmotron +License: SIL Open Font License http://scripts.sil.org/OFL +Created by: Sheldon B. Michaels +SVG font conversion by: Windell H. Oskay +A derivative of: Orbitron (Regular) +Designer: Matt McInerney, the League of Moveable Type +Link: https://www.theleagueofmoveabletype.com +Google font page: https://fonts.google.com/specimen/Orbitron + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/EMSReadability.svg b/share/extensions/svg_fonts/EMSReadability.svg new file mode 100644 index 0000000..88a59fe --- /dev/null +++ b/share/extensions/svg_fonts/EMSReadability.svg @@ -0,0 +1,235 @@ + + + + + + +Font name: EMS Readability +License: SIL Open Font License http://scripts.sil.org/OFL +Created by: Sheldon B. Michaels +SVG font conversion by: Windell H. Oskay +A derivative of: Source Sans Pro-Light +Designer: Paul D. Hunt, Adobe +Link: http://www.adobe.com +Google font page: https://fonts.google.com/specimen/Source+Sans+Pro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/EMSReadabilityItalic.svg b/share/extensions/svg_fonts/EMSReadabilityItalic.svg new file mode 100644 index 0000000..b4d8efe --- /dev/null +++ b/share/extensions/svg_fonts/EMSReadabilityItalic.svg @@ -0,0 +1,235 @@ + + + + + + +Font name: EMS Readability Italic +License: SIL Open Font License http://scripts.sil.org/OFL +Created by: Sheldon B. Michaels +SVG font conversion by: Windell H. Oskay +A derivative of: Source Sans Pro-Light +Designer: Paul D. Hunt, Adobe +Link: http://www.adobe.com +Google font page: https://fonts.google.com/specimen/Source+Sans+Pro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/EMSTech.svg b/share/extensions/svg_fonts/EMSTech.svg new file mode 100644 index 0000000..0b2f108 --- /dev/null +++ b/share/extensions/svg_fonts/EMSTech.svg @@ -0,0 +1,236 @@ + + + + + + + +Font name: EMS Tech +License: SIL Open Font License http://scripts.sil.org/OFL +Created by: Sheldon B. Michaels +SVG font conversion by: Windell H. Oskay +A derivative of: Architects Daughter +Designer: Kimberly Geswein, Kimberly Geswein Fonts +Link: http://www.kimberlygeswein.com/ +Google font page: https://fonts.google.com/specimen/Architects+Daughter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/HersheyGothEnglish.svg b/share/extensions/svg_fonts/HersheyGothEnglish.svg new file mode 100644 index 0000000..d7e95b5 --- /dev/null +++ b/share/extensions/svg_fonts/HersheyGothEnglish.svg @@ -0,0 +1,261 @@ + + + + + + +Font name: Hershey Gothic English + +Originally prepared in 2011 and converted to SVG fonts +in 2019 by Windell H. Oskay, www.evilmadscientist.com + +Contents adapted from emergent.unpythonic.net/software/hershey + by way of "Hershey Fonts in SVG" by Marty McGuire + http://www.thingiverse.com/thing:6168 + +------------------------------------------------------------------- +The Hershey Fonts are a set of vector fonts with a liberal license. + +USE RESTRICTION: + This distribution of the Hershey Fonts may be used by anyone for + any purpose, commercial or otherwise, providing that: + 1. The following acknowledgements must be distributed with + the font data: + - The Hershey Fonts were originally created by Dr. + A. V. Hershey while working at the U. S. + National Bureau of Standards. + - The format of the Font data in this distribution + was originally created by + James Hurt + Cognition, Inc. + 900 Technology Park Drive + Billerica, MA 01821 + (mit-eddie!ci-dandelion!hurt) + 2. The font data in this distribution may be converted into + any other format *EXCEPT* the format distributed by + the U.S. NTIS where each point is described + in eight bytes as "xxx yyy:", where xxx and yyy are + the coordinate values as ASCII numbers. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/HersheySans1.svg b/share/extensions/svg_fonts/HersheySans1.svg new file mode 100644 index 0000000..9321d2e --- /dev/null +++ b/share/extensions/svg_fonts/HersheySans1.svg @@ -0,0 +1,260 @@ + + + + + + +Font name: Hershey Sans 1-stroke + +Originally prepared in 2011 and converted to SVG fonts +in 2019 by Windell H. Oskay, www.evilmadscientist.com + +Contents adapted from emergent.unpythonic.net/software/hershey + by way of "Hershey Fonts in SVG" by Marty McGuire + http://www.thingiverse.com/thing:6168 + +------------------------------------------------------------------- +The Hershey Fonts are a set of vector fonts with a liberal license. + +USE RESTRICTION: + This distribution of the Hershey Fonts may be used by anyone for + any purpose, commercial or otherwise, providing that: + 1. The following acknowledgements must be distributed with + the font data: + - The Hershey Fonts were originally created by Dr. + A. V. Hershey while working at the U. S. + National Bureau of Standards. + - The format of the Font data in this distribution + was originally created by + James Hurt + Cognition, Inc. + 900 Technology Park Drive + Billerica, MA 01821 + (mit-eddie!ci-dandelion!hurt) + 2. The font data in this distribution may be converted into + any other format *EXCEPT* the format distributed by + the U.S. NTIS where each point is described + in eight bytes as "xxx yyy:", where xxx and yyy are + the coordinate values as ASCII numbers. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/HersheySansMed.svg b/share/extensions/svg_fonts/HersheySansMed.svg new file mode 100644 index 0000000..d586e09 --- /dev/null +++ b/share/extensions/svg_fonts/HersheySansMed.svg @@ -0,0 +1,260 @@ + + + + + + +Font name: Hershey Sans medium + +Originally prepared in 2011 and converted to SVG fonts +in 2019 by Windell H. Oskay, www.evilmadscientist.com + +Contents adapted from emergent.unpythonic.net/software/hershey + by way of "Hershey Fonts in SVG" by Marty McGuire + http://www.thingiverse.com/thing:6168 + +------------------------------------------------------------------- +The Hershey Fonts are a set of vector fonts with a liberal license. + +USE RESTRICTION: + This distribution of the Hershey Fonts may be used by anyone for + any purpose, commercial or otherwise, providing that: + 1. The following acknowledgements must be distributed with + the font data: + - The Hershey Fonts were originally created by Dr. + A. V. Hershey while working at the U. S. + National Bureau of Standards. + - The format of the Font data in this distribution + was originally created by + James Hurt + Cognition, Inc. + 900 Technology Park Drive + Billerica, MA 01821 + (mit-eddie!ci-dandelion!hurt) + 2. The font data in this distribution may be converted into + any other format *EXCEPT* the format distributed by + the U.S. NTIS where each point is described + in eight bytes as "xxx yyy:", where xxx and yyy are + the coordinate values as ASCII numbers. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/HersheyScript1.svg b/share/extensions/svg_fonts/HersheyScript1.svg new file mode 100644 index 0000000..202db1e --- /dev/null +++ b/share/extensions/svg_fonts/HersheyScript1.svg @@ -0,0 +1,261 @@ + + + + + + +Font name: Hershey Script 1-stroke + +Originally prepared in 2011 and converted to SVG fonts +in 2019 by Windell H. Oskay, www.evilmadscientist.com + +Contents adapted from emergent.unpythonic.net/software/hershey + by way of "Hershey Fonts in SVG" by Marty McGuire + http://www.thingiverse.com/thing:6168 + +------------------------------------------------------------------- +The Hershey Fonts are a set of vector fonts with a liberal license. + +USE RESTRICTION: + This distribution of the Hershey Fonts may be used by anyone for + any purpose, commercial or otherwise, providing that: + 1. The following acknowledgements must be distributed with + the font data: + - The Hershey Fonts were originally created by Dr. + A. V. Hershey while working at the U. S. + National Bureau of Standards. + - The format of the Font data in this distribution + was originally created by + James Hurt + Cognition, Inc. + 900 Technology Park Drive + Billerica, MA 01821 + (mit-eddie!ci-dandelion!hurt) + 2. The font data in this distribution may be converted into + any other format *EXCEPT* the format distributed by + the U.S. NTIS where each point is described + in eight bytes as "xxx yyy:", where xxx and yyy are + the coordinate values as ASCII numbers. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/HersheyScriptMed.svg b/share/extensions/svg_fonts/HersheyScriptMed.svg new file mode 100644 index 0000000..3c73469 --- /dev/null +++ b/share/extensions/svg_fonts/HersheyScriptMed.svg @@ -0,0 +1,261 @@ + + + + + + +Font name: Hershey Script medium + +Originally prepared in 2011 and converted to SVG fonts +in 2019 by Windell H. Oskay, www.evilmadscientist.com + +Contents adapted from emergent.unpythonic.net/software/hershey + by way of "Hershey Fonts in SVG" by Marty McGuire + http://www.thingiverse.com/thing:6168 + +------------------------------------------------------------------- +The Hershey Fonts are a set of vector fonts with a liberal license. + +USE RESTRICTION: + This distribution of the Hershey Fonts may be used by anyone for + any purpose, commercial or otherwise, providing that: + 1. The following acknowledgements must be distributed with + the font data: + - The Hershey Fonts were originally created by Dr. + A. V. Hershey while working at the U. S. + National Bureau of Standards. + - The format of the Font data in this distribution + was originally created by + James Hurt + Cognition, Inc. + 900 Technology Park Drive + Billerica, MA 01821 + (mit-eddie!ci-dandelion!hurt) + 2. The font data in this distribution may be converted into + any other format *EXCEPT* the format distributed by + the U.S. NTIS where each point is described + in eight bytes as "xxx yyy:", where xxx and yyy are + the coordinate values as ASCII numbers. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/HersheySerifBold.svg b/share/extensions/svg_fonts/HersheySerifBold.svg new file mode 100644 index 0000000..193977b --- /dev/null +++ b/share/extensions/svg_fonts/HersheySerifBold.svg @@ -0,0 +1,261 @@ + + + + + + +Font name: Hershey Serif bold + +Originally prepared in 2011 and converted to SVG fonts +in 2019 by Windell H. Oskay, www.evilmadscientist.com + +Contents adapted from emergent.unpythonic.net/software/hershey + by way of "Hershey Fonts in SVG" by Marty McGuire + http://www.thingiverse.com/thing:6168 + +------------------------------------------------------------------- +The Hershey Fonts are a set of vector fonts with a liberal license. + +USE RESTRICTION: + This distribution of the Hershey Fonts may be used by anyone for + any purpose, commercial or otherwise, providing that: + 1. The following acknowledgements must be distributed with + the font data: + - The Hershey Fonts were originally created by Dr. + A. V. Hershey while working at the U. S. + National Bureau of Standards. + - The format of the Font data in this distribution + was originally created by + James Hurt + Cognition, Inc. + 900 Technology Park Drive + Billerica, MA 01821 + (mit-eddie!ci-dandelion!hurt) + 2. The font data in this distribution may be converted into + any other format *EXCEPT* the format distributed by + the U.S. NTIS where each point is described + in eight bytes as "xxx yyy:", where xxx and yyy are + the coordinate values as ASCII numbers. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/HersheySerifBoldItalic.svg b/share/extensions/svg_fonts/HersheySerifBoldItalic.svg new file mode 100644 index 0000000..9dcf9e7 --- /dev/null +++ b/share/extensions/svg_fonts/HersheySerifBoldItalic.svg @@ -0,0 +1,262 @@ + + + + + + +Font name: Hershey Serif bold italic + +Originally prepared in 2011 and converted to SVG fonts +in 2019 by Windell H. Oskay, www.evilmadscientist.com + +Contents adapted from emergent.unpythonic.net/software/hershey + by way of "Hershey Fonts in SVG" by Marty McGuire + http://www.thingiverse.com/thing:6168 + +------------------------------------------------------------------- +The Hershey Fonts are a set of vector fonts with a liberal license. + +USE RESTRICTION: + This distribution of the Hershey Fonts may be used by anyone for + any purpose, commercial or otherwise, providing that: + 1. The following acknowledgements must be distributed with + the font data: + - The Hershey Fonts were originally created by Dr. + A. V. Hershey while working at the U. S. + National Bureau of Standards. + - The format of the Font data in this distribution + was originally created by + James Hurt + Cognition, Inc. + 900 Technology Park Drive + Billerica, MA 01821 + (mit-eddie!ci-dandelion!hurt) + 2. The font data in this distribution may be converted into + any other format *EXCEPT* the format distributed by + the U.S. NTIS where each point is described + in eight bytes as "xxx yyy:", where xxx and yyy are + the coordinate values as ASCII numbers. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/HersheySerifMed.svg b/share/extensions/svg_fonts/HersheySerifMed.svg new file mode 100644 index 0000000..22ecabd --- /dev/null +++ b/share/extensions/svg_fonts/HersheySerifMed.svg @@ -0,0 +1,260 @@ + + + + + + +Font name: Hershey Serif medium + +Originally prepared in 2011 and converted to SVG fonts +in 2019 by Windell H. Oskay, www.evilmadscientist.com + +Contents adapted from emergent.unpythonic.net/software/hershey + by way of "Hershey Fonts in SVG" by Marty McGuire + http://www.thingiverse.com/thing:6168 + +------------------------------------------------------------------- +The Hershey Fonts are a set of vector fonts with a liberal license. + +USE RESTRICTION: + This distribution of the Hershey Fonts may be used by anyone for + any purpose, commercial or otherwise, providing that: + 1. The following acknowledgements must be distributed with + the font data: + - The Hershey Fonts were originally created by Dr. + A. V. Hershey while working at the U. S. + National Bureau of Standards. + - The format of the Font data in this distribution + was originally created by + James Hurt + Cognition, Inc. + 900 Technology Park Drive + Billerica, MA 01821 + (mit-eddie!ci-dandelion!hurt) + 2. The font data in this distribution may be converted into + any other format *EXCEPT* the format distributed by + the U.S. NTIS where each point is described + in eight bytes as "xxx yyy:", where xxx and yyy are + the coordinate values as ASCII numbers. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/HersheySerifMedItalic.svg b/share/extensions/svg_fonts/HersheySerifMedItalic.svg new file mode 100644 index 0000000..5b7a37f --- /dev/null +++ b/share/extensions/svg_fonts/HersheySerifMedItalic.svg @@ -0,0 +1,260 @@ + + + + + + +Font name: Hershey Serif medium italic + +Originally prepared in 2011 and converted to SVG fonts +in 2019 by Windell H. Oskay, www.evilmadscientist.com + +Contents adapted from emergent.unpythonic.net/software/hershey + by way of "Hershey Fonts in SVG" by Marty McGuire + http://www.thingiverse.com/thing:6168 + +------------------------------------------------------------------- +The Hershey Fonts are a set of vector fonts with a liberal license. + +USE RESTRICTION: + This distribution of the Hershey Fonts may be used by anyone for + any purpose, commercial or otherwise, providing that: + 1. The following acknowledgements must be distributed with + the font data: + - The Hershey Fonts were originally created by Dr. + A. V. Hershey while working at the U. S. + National Bureau of Standards. + - The format of the Font data in this distribution + was originally created by + James Hurt + Cognition, Inc. + 900 Technology Park Drive + Billerica, MA 01821 + (mit-eddie!ci-dandelion!hurt) + 2. The font data in this distribution may be converted into + any other format *EXCEPT* the format distributed by + the U.S. NTIS where each point is described + in eight bytes as "xxx yyy:", where xxx and yyy are + the coordinate values as ASCII numbers. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/svg_fonts/OFL.txt b/share/extensions/svg_fonts/OFL.txt new file mode 100644 index 0000000..f1a20ac --- /dev/null +++ b/share/extensions/svg_fonts/OFL.txt @@ -0,0 +1,97 @@ +Copyright (c) , (), +with Reserved Font Name . +Copyright (c) , (), +with Reserved Font Name . +Copyright (c) , (). + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/share/extensions/svgcalendar.inx b/share/extensions/svgcalendar.inx new file mode 100644 index 0000000..49e051a --- /dev/null +++ b/share/extensions/svgcalendar.inx @@ -0,0 +1,144 @@ + + + Calendar + org.inkscape.render.calendar + + + 2020 + 0 + true + false + + + + + + + + + + + + true + + 3 + 6cm + 1cm + + + #808080 + #686868 + #909090 + #000000 + #787878 + #B0B0B0 + #787878 + + + arial + arial + arial + arial + + + + January February March April May June July August September October November December + Sun Mon Tue Wed Thu Fri Sat + + Wk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + + + + + + diff --git a/share/extensions/svgcalendar.py b/share/extensions/svgcalendar.py new file mode 100755 index 0000000..4ede4b8 --- /dev/null +++ b/share/extensions/svgcalendar.py @@ -0,0 +1,405 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2008 Aurelio A. Heckert +# Week number option added by Olav Vitters and Nicolas Dufour (2012) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +A calendar generator plugin for Inkscape, but also can be used as a standalone +command line application. +# +More on ISO week number calculation on: +http://en.wikipedia.org/wiki/ISO_week_date +(The first week of a year is the week that contains the first Thursdayof the year.) +""" + +__version__ = "0.3" + +import datetime +import calendar +import re +import sys + +import inkex +from inkex import TextElement + +if sys.version_info[0] > 2: + def unicode(s, encoding): + if isinstance(s, bytes): + return s.decode(encoding) + return s + + +class Calendar(inkex.EffectExtension): + """Generate Calendar in SVG""" + def add_arguments(self, pars): + pars.add_argument("--tab", type=str, dest="tab") + pars.add_argument("--month", type=int, default=0,\ + help="Month to be generated. If 0, then the entry year will be generated.") + pars.add_argument("--year", type=int, default=0,\ + help="Year to be generated. If 0, then the current year will be generated.") + pars.add_argument("--fill-empty-day-boxes", type=inkex.Boolean,\ + dest="fill_edb", default=True, help="Fill empty day boxes with next month days.") + pars.add_argument("--show-week-number", type=inkex.Boolean,\ + dest="show_weeknr", default=False, help="Include a week number column.") + pars.add_argument("--start-day", default="sun", help='Week start day. ("sun" or "mon")') + pars.add_argument("--weekend", default="sat+sun",\ + help='Define the weekend days. ("sat+sun" or "sat" or "sun")') + pars.add_argument( + "--auto-organize", type=inkex.Boolean, dest="auto_organize", default=True, + help='Automatically set the size and positions.') + pars.add_argument( + "--months-per-line", type=int, dest="months_per_line", default=3, + help='Number of months side by side.') + pars.add_argument( + "--month-width", type=str, dest="month_width", default="6cm", + help='The width of the month days box.') + pars.add_argument( + "--month-margin", type=str, dest="month_margin", default="1cm", + help='The space between the month boxes.') + pars.add_argument( + "--color-year", type=str, dest="color_year", default="#888", + help='Color for the year header.') + pars.add_argument( + "--color-month", type=str, dest="color_month", default="#666", + help='Color for the month name header.') + pars.add_argument( + "--color-day-name", type=str, dest="color_day_name", default="#999", + help='Color for the week day names header.') + pars.add_argument( + "--color-day", type=str, dest="color_day", default="#000", + help='Color for the common day box.') + pars.add_argument( + "--color-weekend", type=str, dest="color_weekend", default="#777", + help='Color for the weekend days.') + pars.add_argument( + "--color-nmd", type=str, dest="color_nmd", default="#BBB", + help='Color for the next month day, in empty day boxes.') + pars.add_argument( + "--color-weeknr", type=str, dest="color_weeknr", default="#808080", + help='Color for the week numbers.') + pars.add_argument( + "--font-year", type=str, dest="font_year", default="arial", + help='Font for the year string.') + pars.add_argument( + "--font-month", type=str, dest="font_month", default="arial", + help='Font for the month strings.') + pars.add_argument( + "--font-day-name", type=str, dest="font_day_name", default="arial", + help='Font for the days of the week strings.') + pars.add_argument( + "--font-day", type=str, dest="font_day", default="arial", + help='Font for the day strings.') + pars.add_argument( + "--month-names", type=str, dest="month_names", + default='January February March ' + 'April May June July ' + 'August September October ' + 'November December', + help='The month names for localization.') + pars.add_argument( + "--day-names", type=str, dest="day_names", default='Sun Mon Tue Wed Thu Fri Sat', + help='The week day names for localization.') + pars.add_argument( + "--weeknr-name", type=str, dest="weeknr_name", default='Wk', + help='The week number column name for localization.') + pars.add_argument( + "--encoding", type=str, dest="input_encode", default='utf-8', + help='The input encoding of the names.') + + def validate_options(self): + # inkex.errormsg( self.options.input_encode ) + # Convert string names lists in real lists + m = re.match(r'\s*(.*[^\s])\s*', self.options.month_names) + self.options.month_names = re.split(r'\s+', m.group(1)) + m = re.match(r'\s*(.*[^\s])\s*', self.options.day_names) + self.options.day_names = re.split(r'\s+', m.group(1)) + # Validate names lists + if len(self.options.month_names) != 12: + inkex.errormsg('The month name list "' + + str(self.options.month_names) + + '" is invalid. Using default.') + self.options.month_names = ['January', 'February', 'March', + 'April', 'May', 'June', + 'July', 'August', 'September', + 'October', 'November', 'December'] + if len(self.options.day_names) != 7: + inkex.errormsg('The day name list "' + + str(self.options.day_names) + + '" is invalid. Using default.') + self.options.day_names = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', + 'Fri', 'Sat'] + # Convert year 0 to current year + if self.options.year == 0: + self.options.year = datetime.datetime.today().year + # Year 1 starts it's week at monday, obligatorily + if self.options.year == 1: + self.options.start_day = 'mon' + # Set the calendar start day + if self.options.start_day == 'sun': + calendar.setfirstweekday(6) + else: + calendar.setfirstweekday(0) + # Convert string numbers with unit to user space float numbers + self.options.month_width = self.svg.unittouu(self.options.month_width) + self.options.month_margin = self.svg.unittouu(self.options.month_margin) + + # initial values + month_x_pos = 0 + month_y_pos = 0 + weeknr = 0 + + def calculate_size_and_positions(self): + # month_margin month_width months_per_line auto_organize + self.doc_w = self.svg.unittouu(self.document.getroot().get('width')) + self.doc_h = self.svg.unittouu(self.document.getroot().get('height')) + if self.options.show_weeknr: + self.cols_before = 1 + else: + self.cols_before = 0 + if self.options.auto_organize: + if self.doc_h > self.doc_w: + self.months_per_line = 3 + else: + self.months_per_line = 4 + else: + self.months_per_line = self.options.months_per_line + # self.month_w = self.doc_w / self.months_per_line + if self.options.auto_organize: + self.month_w = (self.doc_w * 0.8) / self.months_per_line + self.month_margin = self.month_w / 10 + else: + self.month_w = self.options.month_width + self.month_margin = self.options.month_margin + self.day_w = self.month_w / (7 + self.cols_before) + self.day_h = self.month_w / 9 + self.month_h = self.day_w * 7 + if self.options.month == 0: + self.year_margin = ((self.doc_w + self.day_w - + (self.month_w * self.months_per_line) - + (self.month_margin * + (self.months_per_line - 1))) / 2) # - self.month_margin + else: + self.year_margin = (self.doc_w - self.month_w) / 2 + self.style_day = { + 'font-size': str(self.day_w / 2), + 'font-family': self.options.font_day, + 'text-anchor': 'middle', + 'text-align': 'center', + 'fill': self.options.color_day + } + self.style_weekend = self.style_day.copy() + self.style_weekend['fill'] = self.options.color_weekend + self.style_nmd = self.style_day.copy() + self.style_nmd['fill'] = self.options.color_nmd + self.style_month = self.style_day.copy() + self.style_month['fill'] = self.options.color_month + self.style_month['font-family'] = self.options.font_month + self.style_month['font-size'] = str(self.day_w / 1.5) + self.style_month['font-weight'] = 'bold' + self.style_day_name = self.style_day.copy() + self.style_day_name['fill'] = self.options.color_day_name + self.style_day_name['font-family'] = self.options.font_day_name + self.style_day_name['font-size'] = str(self.day_w / 3) + self.style_year = self.style_day.copy() + self.style_year['fill'] = self.options.color_year + self.style_year['font-family'] = self.options.font_year + self.style_year['font-size'] = str(self.day_w * 2) + self.style_year['font-weight'] = 'bold' + self.style_weeknr = self.style_day.copy() + self.style_weeknr['fill'] = self.options.color_weeknr + self.style_weeknr['font-size'] = str(self.day_w / 3) + + def is_weekend(self, pos): + # weekend values: "sat+sun" or "sat" or "sun" + if self.options.start_day == 'sun': + if self.options.weekend == 'sat+sun' and pos == 0: + return True + if self.options.weekend == 'sat+sun' and pos == 6: + return True + if self.options.weekend == 'sat' and pos == 6: + return True + if self.options.weekend == 'sun' and pos == 0: + return True + else: + if self.options.weekend == 'sat+sun' and pos == 5: + return True + if self.options.weekend == 'sat+sun' and pos == 6: + return True + if self.options.weekend == 'sat' and pos == 5: + return True + if self.options.weekend == 'sun' and pos == 6: + return True + return False + + def in_line_month(self, cal): + cal2 = [] + for week in cal: + for day in week: + if day != 0: + cal2.append(day) + return cal2 + + def write_month_header(self, g, m): + txt_atts = {'style': str(inkex.Style(self.style_month)), + 'x': str((self.month_w - self.day_w) / 2), + 'y': str(self.day_h / 5)} + try: + g.add(TextElement(**txt_atts)).text = unicode( + self.options.month_names[m - 1], + self.options.input_encode) + except: + raise ValueError('You must select a correct system encoding.') + + gw = g.add(inkex.Group()) + week_x = 0 + if self.options.start_day == 'sun': + day_names = self.options.day_names[:] + else: + day_names = self.options.day_names[1:] + day_names.append(self.options.day_names[0]) + + if self.options.show_weeknr: + day_names.insert(0, self.options.weeknr_name) + + for wday in day_names: + txt_atts = {'style': str(inkex.Style(self.style_day_name)), + 'x': str(self.day_w * week_x), + 'y': str(self.day_h)} + try: + gw.add(TextElement(**txt_atts)).text = unicode( + wday, self.options.input_encode) + except: + raise ValueError('You must select a correct system encoding.') + + week_x += 1 + + def create_month(self, m): + txt_atts = { + 'transform': 'translate(' + + str(self.year_margin + + (self.month_w + self.month_margin) * + self.month_x_pos) + + ',' + + str((self.day_h * 4) + + (self.month_h * self.month_y_pos)) + + ')', + 'id': 'month_' + + str(m) + + '_' + + str(self.options.year)} + g = self.year_g.add(inkex.Group(**txt_atts)) + self.write_month_header(g, m) + gdays = g.add(inkex.Group()) + cal = calendar.monthcalendar(self.options.year, m) + if m == 1: + if self.options.year > 1: + before_month = \ + self.in_line_month(calendar.monthcalendar(self.options.year - 1, 12)) + else: + before_month = \ + self.in_line_month(calendar.monthcalendar(self.options.year, m - 1)) + if m == 12: + next_month = \ + self.in_line_month(calendar.monthcalendar(self.options.year + 1, 1)) + else: + next_month = \ + self.in_line_month(calendar.monthcalendar(self.options.year, m + 1)) + if len(cal) < 6: + # add a line after the last week + cal.append([0, 0, 0, 0, 0, 0, 0]) + if len(cal) < 6: + # add a line before the first week (Feb 2009) + cal.reverse() + cal.append([0, 0, 0, 0, 0, 0, 0]) + cal.reverse() + # How mutch before month days will be showed: + bmd = cal[0].count(0) + cal[1].count(0) + before = True + week_y = 0 + for week in cal: + if (self.weeknr != 0 and + ((self.options.start_day == 'mon' and week[0] != 0) or + (self.options.start_day == 'sun' and week[1] != 0))) or \ + (self.weeknr == 0 and + ((self.options.start_day == 'mon' and week[3] > 0) or + (self.options.start_day == 'sun' and week[4] > 0))): + self.weeknr += 1 + week_x = 0 + if self.options.show_weeknr: + # Remove leap week (starting previous year) and empty weeks + if self.weeknr != 0 and not (week[0] == 0 and week[6] == 0): + style = self.style_weeknr + txt_atts = {'style': str(inkex.Style(style)), + 'x': str(self.day_w * week_x), + 'y': str(self.day_h * (week_y + 2))} + gdays.add(TextElement(**txt_atts)).text = str(self.weeknr) + week_x += 1 + else: + week_x += 1 + for day in week: + style = self.style_day + if self.is_weekend(week_x - self.cols_before): + style = self.style_weekend + if day == 0: + style = self.style_nmd + txt_atts = {'style': str(inkex.Style(style)), + 'x': str(self.day_w * week_x), + 'y': str(self.day_h * (week_y + 2))} + text = None + if day == 0 and not self.options.fill_edb: + pass # draw nothing + elif day == 0: + if before: + text = str(before_month[-bmd]) + bmd -= 1 + else: + text = str(next_month[bmd]) + bmd += 1 + else: + text = str(day) + before = False + if text: + gdays.add(TextElement(**txt_atts)).text = text + week_x += 1 + week_y += 1 + self.month_x_pos += 1 + if self.month_x_pos >= self.months_per_line: + self.month_x_pos = 0 + self.month_y_pos += 1 + + def effect(self): + self.validate_options() + self.calculate_size_and_positions() + parent = self.document.getroot() + txt_atts = {'id': 'year_' + str(self.options.year)} + self.year_g = parent.add(inkex.Group(**txt_atts)) + txt_atts = {'style': str(inkex.Style(self.style_year)), + 'x': str(self.doc_w / 2), + 'y': str(self.day_w * 1.5)} + self.year_g.add(TextElement(**txt_atts)).text = str(self.options.year) + try: + if self.options.month == 0: + for m in range(1, 13): + self.create_month(m) + else: + self.create_month(self.options.month) + except ValueError as err: + return inkex.errormsg(str(err)) + +if __name__ == '__main__': + Calendar().run() diff --git a/share/extensions/svgfont2layers.inx b/share/extensions/svgfont2layers.inx new file mode 100644 index 0000000..13538b7 --- /dev/null +++ b/share/extensions/svgfont2layers.inx @@ -0,0 +1,15 @@ + + + Convert SVG Font to Glyph Layers + org.inkscape.typography.svg_font_to_layers + 30 + + all + + + + + + diff --git a/share/extensions/svgfont2layers.py b/share/extensions/svgfont2layers.py new file mode 100755 index 0000000..49c827f --- /dev/null +++ b/share/extensions/svgfont2layers.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2011 Felipe Correa da Silva Sanches +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +"""Extension for converting svg fonts to layers""" + +import inkex + +class SvgFontToLayers(inkex.EffectExtension): + """Convert an svg font to layers""" + def add_arguments(self, pars): + pars.add_argument("--count", type=int, default=30,\ + help="Stop making layers after this number of glyphs.") + + def flip_cordinate_system(self, elem, emsize, baseline): + """Scale and translate the element's path, returns the path object""" + path = elem.path + path.scale(1, -1, inplace=True) + path.translate(0, int(emsize) - int(baseline), inplace=True) + return path + + def effect(self): + # TODO: detect files with multiple svg fonts declared. + # Current code only reads the first svgfont instance + font = self.svg.defs.findone('svg:font') + if font is None: + return inkex.errormsg("There are no svg fonts") + #setwidth = font.get("horiz-adv-x") + baseline = font.get("horiz-origin-y") + if baseline is None: + baseline = 0 + + fontface = font.findone('svg:font-face') + + # TODO: where should we save the font family name? + # fontfamily = fontface.get("font-family") + emsize = fontface.get("units-per-em") + + # TODO: should we guarantee that equals ? + caps = int(fontface.get("cap-height", 0)) + xheight = int(fontface.get("x-height", 0)) + ascender = int(fontface.get("ascent", 0)) + descender = int(fontface.get("descent", 0)) + + self.svg.set("width", emsize) + self.svg.namedview.new_guide(baseline, True, "baseline") + self.svg.namedview.new_guide(baseline + ascender, True, "ascender") + self.svg.namedview.new_guide(baseline + caps, True, "caps") + self.svg.namedview.new_guide(baseline + xheight, True, "xheight") + self.svg.namedview.new_guide(baseline - descender, True, "decender") + + # TODO: missing-glyph + count = 0 + for glyph in font.findall('svg:glyph'): + unicode_char = glyph.get("unicode") + if unicode_char is None: + continue + + layer = self.svg.add(inkex.Layer.new("GlyphLayer-" + unicode_char)) + # glyph layers (except the first one) are innitially hidden + if count != 0: + layer.style['display'] = 'none' + + ############################ + # Option 1: + # Using clone (svg:use) as childnode of svg:glyph + + # use = self.get_or_create(glyph, inkex.Use()) + # use.href = group + # TODO: This code creates nodes but they do not render on svg fonts dialog. why? + + ############################ + # Option 2: + # Using svg:paths as childnodes of svg:glyph + for elem in glyph.findall('svg:path'): + new_path = layer.add(inkex.PathElement()) + new_path.path = self.flip_cordinate_system(elem, emsize, baseline) + + ############################ + # Option 3: + # Using curve description in d attribute of svg:glyph + path = layer.add(inkex.PathElement()) + path.path = self.flip_cordinate_system(glyph, emsize, baseline) + + count += 1 + if count >= self.options.count: + break + +if __name__ == '__main__': + SvgFontToLayers().run() diff --git a/share/extensions/synfig_fileformat.py b/share/extensions/synfig_fileformat.py new file mode 100755 index 0000000..f28cadd --- /dev/null +++ b/share/extensions/synfig_fileformat.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python +# coding=utf-8 +""" +synfig_fileformat.py +Synfig file format utilities + +Copyright (C) 2011 Nikita Kitaev + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +# ##### Constants ########################################## +kux = 60.0 # Number of SVG units (pixels) per Synfig "unit" +gamma = 2.2 +tangent_scale = 3.0 # Synfig tangents are scaled by a factor of 3 + +# ##### Layer parameters, types, and default values ######## +layers = {} + +# Layer_Composite is the parent of most layers +default_composite = { + "z_depth": ["real", 0.0], + "amount": ["real", 1.0], + "blend_method": ["integer", 0], +} + +layers["PasteCanvas"] = default_composite.copy() +layers["PasteCanvas"].update({ + "origin": ["vector", [0.0, 0.0]], + "canvas": ["canvas", None], + "zoom": ["real", 0.0], + "time_offset": ["time", "0s"], + "children_lock": ["bool", False], + "focus": ["vector", [0.0, 0.0]] +}) + +# Layers in mod_geometry + +layers["circle"] = default_composite.copy() +layers["circle"].update({ + "color": ["color", [0, 0, 0, 1]], + "radius": ["real", 1.0], + "feather": ["real", 0.0], + "origin": ["vector", [0.0, 0.0]], + "invert": ["bool", False], + "falloff": ["integer", 2] +}) + +layers["rectangle"] = default_composite.copy() +layers["rectangle"].update({ + "color": ["color", [0, 0, 0, 1]], + "point1": ["vector", [0, 0]], + "point2": ["vector", [1, 1]], + "expand": ["real", 0.0], + "invert": ["bool", False] +}) + +default_shape = default_composite.copy() +default_shape.update({ + "color": ["color", [0, 0, 0, 1]], + "origin": ["vector", [0.0, 0.0]], + "invert": ["bool", False], + "antialias": ["bool", True], + "feather": ["real", 0.0], + "blurtype": ["integer", 1], + "winding_style": ["integer", 0] +}) + +layers["region"] = default_shape.copy() +layers["region"].update({ + "bline": ["bline", None] +}) + +layers["outline"] = default_shape.copy() +layers["outline"].update({ + "bline": ["bline", None], + "round_tip[0]": ["bool", True], + "round_tip[1]": ["bool", True], + "sharp_cusps": ["bool", True], + "width": ["real", 1.0], + "loopyness": ["real", 1.0], + "expand": ["real", 0.0], + "homogeneous_width": ["bool", True] +}) + +# Layers in mod_gradient + +layers["linear_gradient"] = default_composite.copy() +layers["linear_gradient"].update({ + "p1": ["vector", [0, 0]], + "p2": ["vector", [1, 1]], + "gradient": ["gradient", {0.0: [0, 0, 0, 1], 1.0: [1, 1, 1, 1]}], + "loop": ["bool", False], + "zigzag": ["bool", False] +}) + +layers["radial_gradient"] = default_composite.copy() +layers["radial_gradient"].update({ + "gradient": ["gradient", {0.0: [0, 0, 0, 1], 1.0: [1, 1, 1, 1]}], + "center": ["vector", [0, 0]], + "radius": ["real", 1.0], + "loop": ["bool", False], + "zigzag": ["bool", False] +}) + +# Layers in lyr_std + +layers["import"] = default_composite.copy() +layers["import"].update({ + "tl": ["vector", [-1, 1]], + "br": ["vector", [1, -1]], + "c": ["integer", 1], + "gamma_adjust": ["real", 1.0], + "filename": ["string", ""], # foo + "time_offset": ["time", "0s"] +}) + +# transforms are not blending +layers["warp"] = { + "src_tl": ["vector", [-1, 1]], + "src_br": ["vector", [1, -1]], + "dest_tl": ["vector", [-1, 1]], + "dest_tr": ["vector", [1, 1]], + "dest_br": ["vector", [1, -1]], + "dest_bl": ["vector", [-1, -1]], + "clip": ["bool", False], + "horizon": ["real", 4.0] +} + +layers["rotate"] = { + "origin": ["vector", [0.0, 0.0]], + "amount": ["angle", 0] # +} + +layers["translate"] = { + "origin": ["vector", [0.0, 0.0]] +} + +# Layers in mod_filter +layers["blur"] = default_composite.copy() +layers["blur"].update({ + "size": ["vector", [1, 1]], + "type": ["integer", 3] # 1 is fast gaussian, 3 is regular +}) + +# ##### Layer versions ##################################### +layer_versions = { + "outline": "0.2", + "rectangle": "0.2", + "linear_gradient": "0.0", + "blur": "0.2", + None: "0.1" # default +} + +# ##### Blend Methods ###################################### +blend_method_names = { + 0: "composite", + 1: "straight", + 13: "onto", + 21: "straight onto", + 12: "behind", + 16: "screen", + 20: "overlay", + 17: "hand light", + 6: "multiply", + 7: "divide", + 4: "add", + 5: "subtract", + 18: "difference", + 2: "brighten", + 3: "darken", + 8: "color", + 9: "hue", + 10: "saturation", + 11: "luminance", + 14: "alpha brighten", # deprecated + 15: "alpha darken", # deprecated + 19: "alpha over" # deprecated +} + +blend_methods = dict((v, k) for (k, v) in blend_method_names.items()) + + +# ##### Functions ########################################## +def paramType(layer, param, value=None): + if layer in layers.keys(): + layer_params = layers[layer] + if param in layer_params.keys(): + return layer_params[param][0] + else: + raise Exception("Invalid parameter type for layer") + else: + # Unknown layer, try to determine parameter type based on value + if value is None: + raise Exception("No information for given layer") + if type(value) == int: + return "integer" + elif type(value) == float: + return "real" + elif type(value) == bool: + return "bool" + elif type(value) == dict: + if "points" in value.keys(): + return "bline" + elif 0.0 in value.keys(): + return "gradient" + else: + raise Exception("Could not automatically determine parameter type") + elif type(value) == list: + if len(value) == 2: + return "vector" + elif len(value) == 3 or len(value) == 4: + return "color" + else: + # The first two could also be canvases + return "canvas" + elif type(value) == str: + return "string" + + +def defaultLayerVersion(layer): + if layer in layer_versions.keys(): + return layer_versions[layer] + else: + return layer_versions[None] + + +def defaultLayerParams(layer): + if layer in layers.keys(): + return layers[layer].copy() + else: + return {} diff --git a/share/extensions/synfig_output.inx b/share/extensions/synfig_output.inx new file mode 100644 index 0000000..72fd384 --- /dev/null +++ b/share/extensions/synfig_output.inx @@ -0,0 +1,18 @@ + + + Synfig Output + org.inkscape.output.synfig_export + synfig_fileformat.py + synfig_prepare.py + + .sif + image/sif + Synfig Animation (*.sif) + Synfig Animation written using the sif-file exporter extension + true + + + diff --git a/share/extensions/synfig_output.py b/share/extensions/synfig_output.py new file mode 100755 index 0000000..fdc6c87 --- /dev/null +++ b/share/extensions/synfig_output.py @@ -0,0 +1,1336 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2011 Nikita Kitaev +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +# +""" +An Inkscape extension for exporting Synfig files (.sif) +""" +import math +import uuid +from copy import deepcopy + +from lxml import etree + +import inkex +from inkex import Group, Layer, Anchor, Switch, PathElement, \ + Metadata, NamedView, Gradient, SvgDocumentElement, \ + Path, Transform + +import synfig_fileformat as sif +from synfig_prepare import MalformedSVGError, SynfigPrep, get_dimension + + +# ##### Utility Classes #################################### +class UnsupportedException(Exception): + """When part of an element is not supported, this exception is raised to invalidate the whole element""" + pass + + +class SynfigDocument(object): + """A synfig document, with commands for adding layers and layer parameters""" + + def __init__(self, width=1024, height=768, name="Synfig Animation 1"): + self.root_canvas = etree.fromstring( + """ + + {} + + """.format(width, height, name) + ) + + self._update_viewbox() + + self.gradients = {} + self.filters = {} + + # ## Properties + + def get_root_canvas(self): + return self.root_canvas + + def get_root_tree(self): + return self.root_canvas.getroottree() + + def _update_viewbox(self): + """Update the viewbox to match document width and height""" + attr_viewbox = "{:f} {:f} {:f} {:f}".format(-self.width / 2.0 / sif.kux, + self.height / 2.0 / sif.kux, + self.width / 2.0 / sif.kux, + -self.height / 2.0 / sif.kux) + self.root_canvas.set("view-box", attr_viewbox) + + def get_width(self): + return float(self.root_canvas.get("width", "0")) + + def set_width(self, value): + self.root_canvas.set("width", str(value)) + self._update_viewbox() + + def get_height(self): + return float(self.root_canvas.get("height", "0")) + + def set_height(self, value): + self.root_canvas.set("height", str(value)) + self._update_viewbox() + + def get_name(self): + return self.root_canvas.get("name", "") + + def set_name(self, value): + self.root_canvas.set("name", value) + self._update_viewbox() + + width = property(get_width, set_width) + height = property(get_height, set_height) + name = property(get_name, set_name) + + # ## Public utility functions + + def new_guid(self): + """Generate a new GUID""" + return uuid.uuid4().hex + + # ## Coordinate system conversions + + def distance_svg2sif(self, distance): + """Convert distance from SVG to Synfig units""" + return distance / sif.kux + + def distance_sif2svg(self, distance): + """Convert distance from Synfig to SVG units""" + return distance * sif.kux + + def coor_svg2sif(self, vector): + """Convert SVG coordinate [x, y] to Synfig units""" + x = vector[0] + y = self.height - vector[1] + + x -= self.width / 2.0 + y -= self.height / 2.0 + x /= sif.kux + y /= sif.kux + + return [x, y] + + def coor_sif2svg(self, vector): + """Convert Synfig coordinate [x, y] to SVG units""" + x = vector[0] * sif.kux + self.width / 2.0 + y = vector[1] * sif.kux + self.height / 2.0 + + y = self.height - y + + assert self.coor_svg2sif([x, y]) == vector, "sif to svg coordinate conversion error" + + return [x, y] + + def list_coor_svg2sif(self, l): + """Scan a list for coordinate pairs and convert them to Synfig units""" + # If list has two numerical elements, + # treat it as a coordinate pair + if type(l) == list and len(l) == 2: + if type(l[0]) == int or type(l[0]) == float: + if type(l[1]) == int or type(l[1]) == float: + l_sif = self.coor_svg2sif(l) + l[0] = l_sif[0] + l[1] = l_sif[1] + return + + # Otherwise recursively iterate over the list + for x in l: + if type(x) == list: + self.list_coor_svg2sif(x) + + def list_coor_sif2svg(self, l): + """Scan a list for coordinate pairs and convert them to SVG units""" + # If list has two numerical elements, + # treat it as a coordinate pair + if type(l) == list and len(l) == 2: + if type(l[0]) == int or type(l[0]) == float: + if type(l[1]) == int or type(l[1]) == float: + l_sif = self.coor_sif2svg(l) + l[0] = l_sif[0] + l[1] = l_sif[1] + return + + # Otherwise recursively iterate over the list + for x in l: + if type(x) == list: + self.list_coor_sif2svg(x) + + def bline_coor_svg2sif(self, b): + """Convert a BLine from SVG to Synfig coordinate units""" + self.list_coor_svg2sif(b["points"]) + + def bline_coor_sif2svg(self, b): + """Convert a BLine from Synfig to SVG coordinate units""" + self.list_coor_sif2svg(b["points"]) + + # ## XML Builders -- private + # ## used to create XML elements in the Synfig document + + def build_layer(self, layer_type, desc, canvas=None, active=True, version="auto"): + """Build an empty layer""" + if canvas is None: + layer = self.root_canvas.makeelement("layer") + else: + layer = etree.SubElement(canvas, "layer") + + layer.set("type", layer_type) + layer.set("desc", desc) + if active: + layer.set("active", "true") + else: + layer.set("active", "false") + + if version == "auto": + version = sif.defaultLayerVersion(layer_type) + + if type(version) == float: + version = str(version) + + layer.set("version", version) + + return layer + + def _calc_radius(self, p1x, p1y, p2x, p2y): + """Calculate radius of a tangent given two points""" + # Synfig tangents are scaled by a factor of 3 + return sif.tangent_scale * math.sqrt((p2x - p1x) ** 2 + (p2y - p1y) ** 2) + + def _calc_angle(self, p1x, p1y, p2x, p2y): + """Calculate angle (in radians) of a tangent given two points""" + dx = p2x - p1x + dy = p2y - p1y + if dx > 0 and dy > 0: + ag = math.pi + math.atan(dy / dx) + elif dx > 0 > dy: + ag = math.pi + math.atan(dy / dx) + elif dx < 0 and dy < 0: + ag = math.atan(dy / dx) + elif dx < 0 < dy: + ag = 2 * math.pi + math.atan(dy / dx) + elif dx == 0 and dy > 0: + ag = -1 * math.pi / 2 + elif dx == 0 and dy < 0: + ag = math.pi / 2 + elif dx == 0 and dy == 0: + ag = 0 + elif dx < 0 and dy == 0: + ag = 0 + elif dx > 0 and dy == 0: + ag = math.pi + + return (ag * 180) / math.pi + + def build_param(self, layer, name, value, param_type="auto", guid=None): + """Add a parameter node to a layer""" + if layer is None: + param = self.root_canvas.makeelement("param") + else: + param = etree.SubElement(layer, "param") + param.set("name", name) + + # Automatically detect param_type + if param_type == "auto": + if layer is not None: + layer_type = layer.get("type") + param_type = sif.paramType(layer_type, name) + else: + param_type = sif.paramType(None, name, value) + + if param_type == "real": + el = etree.SubElement(param, "real") + el.set("value", str(float(value))) + elif param_type == "integer": + el = etree.SubElement(param, "integer") + el.set("value", str(int(value))) + elif param_type == "vector": + el = etree.SubElement(param, "vector") + x = etree.SubElement(el, "x") + x.text = str(float(value[0])) + y = etree.SubElement(el, "y") + y.text = str(float(value[1])) + elif param_type == "color": + el = etree.SubElement(param, "color") + r = etree.SubElement(el, "r") + r.text = str(float(value[0])) + g = etree.SubElement(el, "g") + g.text = str(float(value[1])) + b = etree.SubElement(el, "b") + b.text = str(float(value[2])) + a = etree.SubElement(el, "a") + a.text = str(float(value[3])) if len(value) > 3 else "1.0" + elif param_type == "gradient": + el = etree.SubElement(param, "gradient") + # Value is a dictionary of color stops + # see get_gradient() + for pos in value.keys(): + color = etree.SubElement(el, "color") + color.set("pos", str(float(pos))) + + c = value[pos] + + r = etree.SubElement(color, "r") + r.text = str(float(c[0])) + g = etree.SubElement(color, "g") + g.text = str(float(c[1])) + b = etree.SubElement(color, "b") + b.text = str(float(c[2])) + a = etree.SubElement(color, "a") + a.text = str(float(c[3])) if len(c) > 3 else "1.0" + elif param_type == "bool": + el = etree.SubElement(param, "bool") + if value: + el.set("value", "true") + else: + el.set("value", "false") + elif param_type == "time": + el = etree.SubElement(param, "time") + if type(value) == int: + el.set("value", "{:d}s".format(value)) + elif type(value) == float: + el.set("value", "{:f}s".format(value)) + elif type(value) == str: + el.set("value", value) + elif param_type == "bline": + el = etree.SubElement(param, "bline") + el.set("type", "bline_point") + + # value is a bline (dictionary type), see path_to_bline_list + if value["loop"]: + el.set("loop", "true") + else: + el.set("loop", "false") + + for vertex in value["points"]: + x = float(vertex[1][0]) + y = float(vertex[1][1]) + + tg1x = float(vertex[0][0]) + tg1y = float(vertex[0][1]) + + tg2x = float(vertex[2][0]) + tg2y = float(vertex[2][1]) + + tg1_radius = self._calc_radius(x, y, tg1x, tg1y) + tg1_angle = self._calc_angle(x, y, tg1x, tg1y) + + tg2_radius = self._calc_radius(x, y, tg2x, tg2y) + tg2_angle = self._calc_angle(x, y, tg2x, tg2y) - 180.0 + + if vertex[3]: + split = "true" + else: + split = "false" + + entry = etree.SubElement(el, "entry") + composite = etree.SubElement(entry, "composite") + composite.set("type", "bline_point") + + point = etree.SubElement(composite, "point") + vector = etree.SubElement(point, "vector") + etree.SubElement(vector, "x").text = str(x) + etree.SubElement(vector, "y").text = str(y) + + width = etree.SubElement(composite, "width") + etree.SubElement(width, "real").set("value", "1.0") + + origin = etree.SubElement(composite, "origin") + etree.SubElement(origin, "real").set("value", "0.5") + + split_el = etree.SubElement(composite, "split") + etree.SubElement(split_el, "bool").set("value", split) + + t1 = etree.SubElement(composite, "t1") + t2 = etree.SubElement(composite, "t2") + + t1_rc = etree.SubElement(t1, "radial_composite") + t1_rc.set("type", "vector") + + t2_rc = etree.SubElement(t2, "radial_composite") + t2_rc.set("type", "vector") + + t1_r = etree.SubElement(t1_rc, "radius") + t2_r = etree.SubElement(t2_rc, "radius") + t1_radius = etree.SubElement(t1_r, "real") + t2_radius = etree.SubElement(t2_r, "real") + t1_radius.set("value", str(tg1_radius)) + t2_radius.set("value", str(tg2_radius)) + + t1_t = etree.SubElement(t1_rc, "theta") + t2_t = etree.SubElement(t2_rc, "theta") + t1_angle = etree.SubElement(t1_t, "angle") + t2_angle = etree.SubElement(t2_t, "angle") + t1_angle.set("value", str(tg1_angle)) + t2_angle.set("value", str(tg2_angle)) + elif param_type == "canvas": + el = etree.SubElement(param, "canvas") + el.set("xres", "10.0") + el.set("yres", "10.0") + + # "value" is a list of layers + if value is not None: + for layer in value: + el.append(layer) + else: + raise AssertionError("Unsupported param type {}".format(param_type)) + + if guid: + el.set("guid", guid) + else: + el.set("guid", self.new_guid()) + + return param + + # ## Public layer API + # ## Should be used by outside functions to create layers and set layer parameters + + def create_layer(self, layer_type, desc, params={}, guids={}, canvas=None, active=True, version="auto"): + """Create a new layer + + Keyword arguments: + layer_type -- layer type string used internally by Synfig + desc -- layer description + params -- a dictionary of parameter names and their values + guids -- a dictionary of parameter types and their guids (optional) + active -- set to False to create a hidden layer + """ + layer = self.build_layer(layer_type, desc, canvas, active, version) + default_layer_params = sif.defaultLayerParams(layer_type) + + for param_name in default_layer_params.keys(): + param_type = default_layer_params[param_name][0] + if param_name in params.keys(): + param_value = params[param_name] + else: + param_value = default_layer_params[param_name][1] + + if param_name in guids.keys(): + param_guid = guids[param_name] + else: + param_guid = None + + if param_value is not None: + self.build_param(layer, param_name, param_value, param_type, guid=param_guid) + + return layer + + def set_param(self, layer, name, value, param_type="auto", guid=None, modify_linked=False): + """Set a layer parameter + + Keyword arguments: + layer -- the layer to set the parameter for + name -- parameter name + value -- parameter value + param_type -- parameter type (default "auto") + guid -- guid of the parameter value + """ + if modify_linked: + raise AssertionError("Modifying linked parameters is not supported") + + layer_type = layer.get("type") + assert layer_type, "Layer does not have a type" + + if param_type == "auto": + param_type = sif.paramType(layer_type, name) + + # Remove existing parameters with this name + existing = [] + for param in layer.iterchildren(): + if param.get("name") == name: + existing.append(param) + + if len(existing) == 0: + self.build_param(layer, name, value, param_type, guid) + elif len(existing) > 1: + raise AssertionError("Found multiple parameters with the same name") + else: + new_param = self.build_param(None, name, value, param_type, guid) + layer.replace(existing[0], new_param) + + def set_params(self, layer, params={}, guids={}, modify_linked=False): + """Set layer parameters + + Keyword arguments: + layer -- the layer to set the parameter for + params -- a dictionary of parameter names and their values + guids -- a dictionary of parameter types and their guids (optional) + """ + for param_name in params.keys(): + if param_name in guids.keys(): + self.set_param(layer, param_name, params[param_name], guid=guids[param_name], modify_linked=modify_linked) + else: + self.set_param(layer, param_name, params[param_name], modify_linked=modify_linked) + + def get_param(self, layer, name, param_type="auto"): + """Get the value of a layer parameter + + Keyword arguments: + layer -- the layer to get the parameter from + name -- param name + param_type -- parameter type (default "auto") + + NOT FULLY IMPLEMENTED + """ + layer_type = layer.get("type") + assert layer_type, "Layer does not have a type" + + if param_type == "auto": + param_type = sif.paramType(layer_type, name) + + for param in layer.iterchildren(): + if param.get("name") == name: + if param_type == "real": + return float(param[0].get("value", "0")) + elif param_type == "integer": + return int(param[0].get("integer", "0")) + else: + raise Exception("Getting this type of parameter not yet implemented") + + # ## Global defs, and related + + # SVG Filters + def add_filter(self, filter_id, f): + """Register a filter""" + self.filters[filter_id] = f + + # SVG Gradients + def add_linear_gradient(self, gradient_id, p1, p2, mtx=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], stops=[], link="", spread_method="pad"): + """Register a linear gradient definition""" + gradient = { + "type": "linear", + "p1": p1, + "p2": p2, + "mtx": mtx, + "spreadMethod": spread_method + } + if stops: + gradient["stops"] = stops + gradient["stops_guid"] = self.new_guid() + elif link != "": + gradient["link"] = link + else: + raise MalformedSVGError("Gradient has neither stops nor link") + self.gradients[gradient_id] = gradient + + def add_radial_gradient(self, gradient_id, center, radius, focus, mtx=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], stops=[], link="", spread_method="pad"): + """Register a radial gradient definition""" + gradient = { + "type": "radial", + "center": center, + "radius": radius, + "focus": focus, + "mtx": mtx, + "spreadMethod": spread_method + } + if stops: + gradient["stops"] = stops + gradient["stops_guid"] = self.new_guid() + elif link != "": + gradient["link"] = link + else: + raise MalformedSVGError("Gradient has neither stops nor link") + self.gradients[gradient_id] = gradient + + def get_gradient(self, gradient_id): + """ + Return a gradient with a given id + + Linear gradient format: + { + "type" : "linear", + "p1" : [x, y], + "p2" : [x, y], + "mtx" : mtx, + "stops" : color stops, + "stops_guid": color stops guid, + "spreadMethod": "pad", "reflect", or "repeat" + } + + Radial gradient format: + { + "type" : "radial", + "center" : [x, y], + "radius" : r, + "focus" : [x, y], + "mtx" : mtx, + "stops" : color stops, + "stops_guid": color stops guid, + "spreadMethod": "pad", "reflect", or "repeat" + } + + Color stops format + { + 0.0 : color ([r,g,b,a] or [r,g,b]) at start, + [a number] : color at that position, + 1.0 : color at end + } + """ + + if gradient_id not in self.gradients.keys(): + return None + + gradient = self.gradients[gradient_id] + + # If the gradient has no link, we are done + if "link" not in gradient.keys() or gradient["link"] == "": + return gradient + + # If the gradient does have a link, find the color stops recursively + if gradient["link"] not in self.gradients.keys(): + raise MalformedSVGError("Linked gradient ID not found") + + linked_gradient = self.get_gradient(gradient["link"]) + gradient["stops"] = linked_gradient["stops"] + gradient["stops_guid"] = linked_gradient["stops_guid"] + del gradient["link"] + + # Update the gradient in our listing + # (so recursive lookup only happens once) + self.gradients[gradient_id] = gradient + + return gradient + + def gradient_to_params(self, gradient): + """Transform gradient to a list of parameters to pass to a Synfig layer""" + # Create a copy of the gradient + g = gradient.copy() + + # Set synfig-only attribs + if g["spreadMethod"] == "repeat": + g["loop"] = True + elif g["spreadMethod"] == "reflect": + g["loop"] = True + # Reflect the gradient + # Original: 0.0 [A . B . C] 1.0 + # New: 0.0 [A . B . C . B . A] 1.0 + # (with gradient size doubled) + new_stops = {} + + # reflect the stops + for pos in g["stops"]: + val = g["stops"][pos] + if pos == 1.0: + new_stops[pos / 2.0] = val + else: + new_stops[pos / 2.0] = val + new_stops[1 - pos / 2.0] = val + g["stops"] = new_stops + + # double the gradient size + if g["type"] == "linear": + g["p2"] = [g["p1"][0] + 2.0 * (g["p2"][0] - g["p1"][0]), + g["p1"][1] + 2.0 * (g["p2"][1] - g["p1"][1])] + if g["type"] == "radial": + g["radius"] *= 2.0 + + # Rename "stops" to "gradient" + g["gradient"] = g["stops"] + + # Convert coordinates + if g["type"] == "linear": + g["p1"] = self.coor_svg2sif(g["p1"]) + g["p2"] = self.coor_svg2sif(g["p2"]) + + if g["type"] == "radial": + g["center"] = self.coor_svg2sif(g["center"]) + g["radius"] = self.distance_svg2sif(g["radius"]) + + # Delete extra attribs + removed_attribs = ["type", + "stops", + "stops_guid", + "mtx", + "focus", + "spreadMethod"] + for x in removed_attribs: + if x in g.keys(): + del g[x] + return g + + # ## Public operations API + # Operations act on a series of layers, and (optionally) on a series of named parameters + # The "is_end" attribute should be set to true when the layers are at the end of a canvas + # (i.e. when adding transform layers on top of them does not require encapsulation) + + def op_blur(self, layers, x, y, name="Blur", is_end=False): + """Gaussian blur the given layers by the given x and y amounts + + Keyword arguments: + layers -- list of layers + x -- x-amount of blur + y -- x-amount of blur + is_end -- set to True if layers are at the end of a canvas + + Returns: list of layers + """ + blur = self.create_layer("blur", name, params={ + "blend_method": sif.blend_methods["straight"], + "size": [x, y] + }) + + if is_end: + return layers + [blur] + else: + return self.op_encapsulate(layers + [blur]) + + def op_color(self, layers, overlay, is_end=False): + """Apply a color overlay to the given layers + + Should be used to apply a gradient or pattern to a shape + + Keyword arguments: + layers -- list of layers + overlay -- color layer to apply + is_end -- set to True if layers are at the end of a canvas + + Returns: list of layers + """ + if not layers: + return layers + if overlay is None: + return layers + + overlay_enc = self.op_encapsulate([overlay]) + self.set_param(overlay_enc[0], "blend_method", sif.blend_methods["straight onto"]) + ret = layers + overlay_enc + + if is_end: + return ret + else: + return self.op_encapsulate(ret) + + def op_encapsulate(self, layers, name="Inline Canvas", is_end=False): + """Encapsulate the given layers + + Keyword arguments: + layers -- list of layers + name -- Name of the PasteCanvas layer that is created + is_end -- set to True if layers are at the end of a canvas + + Returns: list of one layer + """ + + if not layers: + return layers + + layer = self.create_layer("PasteCanvas", name, params={"canvas": layers}) + return [layer] + + def op_fade(self, layers, opacity, is_end=False): + """Increase the opacity of the given layers by a certain amount + + Keyword arguments: + layers -- list of layers + opacity -- the opacity to apply (float between 0.0 to 1.0) + name -- name of the Transform layer that is added + is_end -- set to True if layers are at the end of a canvas + + Returns: list of layers + """ + # If there is blending involved, first encapsulate the layers + for layer in layers: + if self.get_param(layer, "blend_method") != sif.blend_methods["composite"]: + return self.op_fade(self.op_encapsulate(layers), opacity, is_end) + + # Otherwise, set their amount + for layer in layers: + amount = self.get_param(layer, "amount") + self.set_param(layer, "amount", amount * opacity) + + return layers + + def op_filter(self, layers, filter_id, is_end=False): + """Apply a filter to the given layers + + Keyword arguments: + layers -- list of layers + filter_id -- id of the filter + is_end -- set to True if layers are at the end of a canvas + + Returns: list of layers + """ + if filter_id not in self.filters.keys(): + raise MalformedSVGError("Filter {} not found".format(filter_id)) + + try: + ret = self.filters[filter_id](self, layers, is_end) + assert type(ret) == list + return ret + except UnsupportedException: + # If the filter is not supported, ignore it. + return layers + + def op_set_blend(self, layers, blend_method, is_end=False): + """Set the blend method of the given group of layers + + If more than one layer is supplied, they will be encapsulated. + + Keyword arguments: + layers -- list of layers + blend_method -- blend method to give the layers + is_end -- set to True if layers are at the end of a canvas + + Returns: list of layers + """ + if not layers: + return layers + if blend_method == "composite": + return layers + + layer = layers[0] + if len(layers) > 1 or self.get_param(layers[0], "amount") != 1.0: + layer = self.op_encapsulate(layers)[0] + + layer = deepcopy(layer) + + self.set_param(layer, "blend_method", sif.blend_methods[blend_method]) + + return [layer] + + def op_transform(self, layers, mtx, name="Transform", is_end=False): + """Apply a matrix transformation to the given layers + + Keyword arguments: + layers -- list of layers + mtx -- transformation matrix + name -- name of the Transform layer that is added + is_end -- set to True if layers are at the end of a canvas + + Returns: list of layers + """ + if not layers: + return layers + if mtx is None or mtx == [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]: + return layers + + src_tl = [100, 100] + src_br = [200, 200] + + dest_tl = [100, 100] + dest_tr = [200, 100] + dest_br = [200, 200] + dest_bl = [100, 200] + + dest_tl = Transform(mtx).apply_to_point(dest_tl) + dest_tr = Transform(mtx).apply_to_point(dest_tr) + dest_br = Transform(mtx).apply_to_point(dest_br) + dest_bl = Transform(mtx).apply_to_point(dest_bl) + + warp = self.create_layer("warp", name, params={ + "src_tl": self.coor_svg2sif(src_tl), + "src_br": self.coor_svg2sif(src_br), + "dest_tl": self.coor_svg2sif(dest_tl), + "dest_tr": self.coor_svg2sif(dest_tr), + "dest_br": self.coor_svg2sif(dest_br), + "dest_bl": self.coor_svg2sif(dest_bl) + }) + + if is_end: + return layers + [warp] + else: + return self.op_encapsulate(layers + [warp]) + + +# ##### Utility Functions ################################## + +# ## Path related + +def path_to_bline_list(path_d, nodetypes=None, mtx=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]): + """ + Convert a path to a BLine List + + bline_list format: + + Vertex: + [[tg1x, tg1y], [x,y], [tg2x, tg2y], split = T/F] + Vertex list: + [ vertex, vertex, vertex, ...] + Bline: + { + "points" : vertex_list, + "loop" : True / False + } + """ + + # Exit on empty paths + if not path_d: + return [] + + # Parse the path + path = Path(path_d).to_arrays() + + # Append (more than) enough c's to the nodetypes + if nodetypes is None: + nt = "" + else: + nt = nodetypes + + for _ in range(len(path)): + nt += "c" + + # Create bline list + # borrows code from cubicsuperpath.py + + # bline_list := [bline, bline, ...] + # bline := { + # "points":[vertex, vertex, ...], + # "loop":True/False, + # } + + bline_list = [] + + subpathstart = [] + last = [] + lastctrl = [] + lastsplit = True + + for s in path: + cmd, params = s + if cmd != "M" and bline_list == []: + raise MalformedSVGError("Bad path data: path doesn't start with moveto, {}, {}".format(s, path)) + elif cmd == "M": + # Add previous point to subpath + if last: + bline_list[-1]["points"].append([lastctrl[:], last[:], last[:], lastsplit]) + # Start a new subpath + bline_list.append({"nodetypes": "", "loop": False, "points": []}) + # Save coordinates of this point + subpathstart = params[:] + last = params[:] + lastctrl = params[:] + lastsplit = False if nt[0] == "z" else True + nt = nt[1:] + elif cmd in "LHV": + bline_list[-1]["points"].append([lastctrl[:], last[:], last[:], lastsplit]) + if cmd == 'H': + last = [params[0], last[1]] + lastctrl = [params[0], last[1]] + elif cmd == 'V': + last = [last[0], params[0]] + lastctrl = [last[0], params[0]] + else: + last = params[:] + lastctrl = params[:] + lastsplit = False if nt[0] == "z" else True + nt = nt[1:] + elif cmd == 'C': + bline_list[-1]["points"].append([lastctrl[:], last[:], params[:2], lastsplit]) + last = params[-2:] + lastctrl = params[2:4] + lastsplit = False if nt[0] == "z" else True + nt = nt[1:] + elif cmd == 'Q': + q0 = last[:] + q1 = params[0:2] + q2 = params[2:4] + x0 = q0[0] + x1 = 1. / 3 * q0[0] + 2. / 3 * q1[0] + x2 = 2. / 3 * q1[0] + 1. / 3 * q2[0] + x3 = q2[0] + y0 = q0[1] + y1 = 1. / 3 * q0[1] + 2. / 3 * q1[1] + y2 = 2. / 3 * q1[1] + 1. / 3 * q2[1] + y3 = q2[1] + bline_list[-1]["points"].append([lastctrl[:], [x0, y0], [x1, y1], lastsplit]) + last = [x3, y3] + lastctrl = [x2, y2] + lastsplit = False if nt[0] == "z" else True + nt = nt[1:] + elif cmd == 'A': + from inkex.paths import arc_to_path + arcp = arc_to_path(last[:], params[:]) + arcp[0][0] = lastctrl[:] + last = arcp[-1][1] + lastctrl = arcp[-1][0] + lastsplit = False if nt[0] == "z" else True + nt = nt[1:] + for el in arcp[:-1]: + el.append(True) + bline_list[-1]["points"].append(el) + elif cmd == "Z": + if len(bline_list[-1]["points"]) == 0: + # If the path "loops" after only one point + # e.g. "M 0 0 Z" + bline_list[-1]["points"].append([lastctrl[:], last[:], last[:], False]) + elif last == subpathstart: + # If we are back to the original position + # merge our tangent into the first point + bline_list[-1]["points"][0][0] = lastctrl[:] + else: + # Otherwise draw a line to the starting point + bline_list[-1]["points"].append([lastctrl[:], last[:], last[:], lastsplit]) + + # Clear the variables (no more points need to be added) + last = [] + lastctrl = [] + lastsplit = True + + # Loop the subpath + bline_list[-1]["loop"] = True + # Append final superpoint, if needed + if last: + bline_list[-1]["points"].append([lastctrl[:], last[:], last[:], lastsplit]) + + # Apply the transformation + if mtx != [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]: + for bline in bline_list: + for vertex in bline["points"]: + for point in vertex: + if not isinstance(point, bool): + pnt = Transform(mtx).apply_to_point(point) + point[0], point[1] = pnt[0], pnt[1] + + return bline_list + + +# ## Style related + +def extract_color(style, color_attrib, *opacity_attribs): + if color_attrib in style.keys(): + if style[color_attrib] == "none": + return [1, 1, 1, 0] + c = inkex.Color(style[color_attrib]).to_rgb() + else: + c = (0, 0, 0) + + # Convert color scales and adjust gamma + color = [pow(c[0] / 255.0, sif.gamma), pow(c[1] / 255.0, sif.gamma), pow(c[2] / 255.0, sif.gamma), 1.0] + + for opacity in opacity_attribs: + if opacity in style.keys(): + color[3] *= float(style[opacity]) + return color + + +def extract_opacity(style, *opacity_attribs): + ret = 1.0 + for opacity in opacity_attribs: + if opacity in style.keys(): + ret *= float(style[opacity]) + return ret + + +def extract_width(style, width_attrib, mtx): + if width_attrib in style.keys(): + width = get_dimension(style[width_attrib]) + else: + width = 1 + + area_scale_factor = mtx[0][0] * mtx[1][1] - mtx[0][1] * mtx[1][0] + linear_scale_factor = math.sqrt(abs(area_scale_factor)) + + return width * linear_scale_factor / sif.kux + + +# ##### Main Class ######################################### +class SynfigExport(SynfigPrep): + def __init__(self): + SynfigPrep.__init__(self) + + def effect(self): + # Prepare the document for exporting + SynfigPrep.effect(self) + svg = self.document.getroot() + width = get_dimension(svg.get("width", 1024)) + height = get_dimension(svg.get("height", 768)) + + title = svg.getElement('svg:title') + if title: + name = title.text + else: + name = svg.get('sodipodi:docname', "Synfig Animation 1") + + doc = SynfigDocument(width, height, name) + + layers = [] + for node in svg.iterchildren(): + layers += self.convert_node(node, doc) + + root_canvas = doc.get_root_canvas() + for layer in layers: + root_canvas.append(layer) + + self.synfig_document = doc.get_root_tree() + + def save(self, stream): + self.synfig_document.write(stream) + + def convert_node(self, node, d): + """Convert an SVG node to a list of Synfig layers""" + # Parse tags that don't draw any layers + if isinstance(node, SvgDocumentElement): + self.parse_defs(node, d) + return [] + elif not isinstance(node, (Group, Anchor, Switch, PathElement, Metadata, NamedView)): + # An unsupported element + return [] + + layers = [] + if isinstance(node, Group): + for subnode in node: + layers += self.convert_node(subnode, d) + if isinstance(node, Layer): + name = node.label or "Inline Canvas" + layers = d.op_encapsulate(layers, name=name) + + elif isinstance(node, (Anchor, Switch)): + # Treat anchor and switch as a group + for subnode in node: + layers += self.convert_node(subnode, d) + elif isinstance(node, PathElement): + layers = self.convert_path(node, d) + + style = node.style + if "filter" in style.keys() and style["filter"].startswith("url"): + filter_id = style["filter"][5:].split(")")[0] + layers = d.op_filter(layers, filter_id) + + opacity = extract_opacity(style, "opacity") + if opacity != 1.0: + layers = d.op_fade(layers, opacity) + + return layers + + def parse_defs(self, node, d): + for child in node.iterchildren(): + if isinstance(child, Gradient): + self.parse_gradient(child, d) + elif child.TAG == "filter": + self.parse_filter(child, d) + + def parse_gradient(self, node, d): + if node.TAG == "linearGradient": + gradient_id = node.get("id", str(id(node))) + x1 = float(node.get("x1", "0.0")) + x2 = float(node.get("x2", "0.0")) + y1 = float(node.get("y1", "0.0")) + y2 = float(node.get("y2", "0.0")) + + mtx = node.gradientTransform.matrix + + link = node.get('xlink:href', "#")[1:] + spread_method = node.get("spreadMethod", "pad") + if link == "": + stops = self.parse_stops(node, d) + d.add_linear_gradient(gradient_id, [x1, y1], [x2, y2], mtx, stops=stops, spread_method=spread_method) + else: + d.add_linear_gradient(gradient_id, [x1, y1], [x2, y2], mtx, link=link, spread_method=spread_method) + elif node.TAG == "radialGradient": + gradient_id = node.get("id", str(id(node))) + cx = float(node.get("cx", "0.0")) + cy = float(node.get("cy", "0.0")) + r = float(node.get("r", "0.0")) + fx = float(node.get("fx", "0.0")) + fy = float(node.get("fy", "0.0")) + + mtx = node.gradientTransform.matrix + + link = node.get('xlink:href', "#")[1:] + spread_method = node.get("spreadMethod", "pad") + if link == "": + stops = self.parse_stops(node, d) + d.add_radial_gradient(gradient_id, [cx, cy], r, [fx, fy], mtx, stops=stops, spread_method=spread_method) + else: + d.add_radial_gradient(gradient_id, [cx, cy], r, [fx, fy], mtx, link=link, spread_method=spread_method) + + def parse_stops(self, node, d): + stops = {} + for stop in node.iterchildren(): + if stop.TAG == "stop": + offset = float(stop.get("offset")) + style = stop.style + stops[offset] = extract_color(style, "stop-color", "stop-opacity") + else: + raise MalformedSVGError("Child of gradient is not a stop") + + return stops + + def parse_filter(self, node, d): + filter_id = node.get("id", str(id(node))) + + # A filter is just like an operator (the op_* functions), + # except that it's created here + def the_filter(d, layers, is_end=False): + refs = {None: layers, # default + "SourceGraphic": layers} + encapsulate_result = not is_end + + for child in node.iterchildren(): + if child.get("in") not in refs: + # "SourceAlpha", "BackgroundImage", + # "BackgroundAlpha", "FillPaint", "StrokePaint" + # are not supported + raise UnsupportedException + l_in = refs[child.get("in")] + l_out = [] + if child.TAG == "feGaussianBlur": + std_dev = child.get("stdDeviation", "0") + std_dev = std_dev.replace(",", " ").split() + x = float(std_dev[0]) + if len(std_dev) > 1: + y = float(std_dev[1]) + else: + y = x + + if x == 0 and y == 0: + l_out = l_in + else: + x = d.distance_svg2sif(x) + y = d.distance_svg2sif(y) + l_out = d.op_blur(l_in, x, y, is_end=True) + elif child.TAG == "feBlend": + # Note: Blend methods are not an exact match + # because SVG uses alpha channel in places where + # Synfig does not + mode = child.get("mode", "normal") + if mode == "normal": + blend_method = "composite" + elif mode == "multiply": + blend_method = "multiply" + elif mode == "screen": + blend_method = "screen" + elif mode == "darken": + blend_method = "darken" + elif mode == "lighten": + blend_method = "brighten" + else: + raise MalformedSVGError("Invalid blend method") + + if child.get("in2") == "BackgroundImage": + encapsulate_result = False + l_out = d.op_set_blend(l_in, blend_method) + d.op_set_blend(l_in, "behind") + elif child.get("in2") not in refs: + raise UnsupportedException + else: + l_in2 = refs[child.get("in2")] + l_out = l_in2 + d.op_set_blend(l_in, blend_method) + + else: + # This filter element is currently unsupported + raise UnsupportedException + + # Output the layers + if child.get("result"): + refs[child.get("result")] = l_out + + # Set the default for the next filter element + refs[None] = l_out + + # Return the output from the last element + if len(refs[None]) > 1 and encapsulate_result: + return d.op_encapsulate(refs[None]) + else: + return refs[None] + + d.add_filter(filter_id, the_filter) + + def convert_path(self, node, d): + """Convert an SVG path node to a list of Synfig layers""" + layers = [] + + node_id = node.get("id", str(id(node))) + style = node.style + + mtx = node.transform.matrix + blines = path_to_bline_list(node.get("d"), node.get('sodipodi:nodetypes'), mtx) + for bline in blines: + d.bline_coor_svg2sif(bline) + bline_guid = d.new_guid() + + if style.setdefault("fill", "#000000") != "none": + if style["fill"].startswith("url"): + # Set the color to black, so we can later overlay + # the shape with a gradient or pattern + color = [0, 0, 0, 1] + else: + color = extract_color(style, "fill", "fill-opacity") + + layer = d.create_layer("region", node_id, { + "bline": bline, + "color": color, + "winding_style": 1 if style.setdefault("fill-rule", "nonzero") == "evenodd" else 0, + }, guids={ + "bline": bline_guid + }) + + if style["fill"].startswith("url"): + color_layer = self.convert_url(style["fill"][5:].split(")")[0], mtx, d)[0] + layer = d.op_color([layer], overlay=color_layer)[0] + layer = d.op_fade([layer], extract_opacity(style, "fill-opacity"))[0] + + layers.append(layer) + + if style.setdefault("stroke", "none") != "none": + if style["stroke"].startswith("url"): + # Set the color to black, so we can later overlay + # the shape with a gradient or pattern + color = [0, 0, 0, 1] + else: + color = extract_color(style, "stroke", "stroke-opacity") + + layer = d.create_layer("outline", node_id, { + "bline": bline, + "color": color, + "width": extract_width(style, "stroke-width", mtx), + "sharp_cusps": True if style.setdefault("stroke-linejoin", "miter") == "miter" else False, + "round_tip[0]": False if style.setdefault("stroke-linecap", "butt") == "butt" else True, + "round_tip[1]": False if style.setdefault("stroke-linecap", "butt") == "butt" else True + }, guids={ + "bline": bline_guid + }) + + if style["stroke"].startswith("url"): + color_layer = self.convert_url(style["stroke"][5:].split(")")[0], mtx, d)[0] + layer = d.op_color([layer], overlay=color_layer)[0] + layer = d.op_fade([layer], extract_opacity(style, "stroke-opacity"))[0] + + layers.append(layer) + + return layers + + def convert_url(self, url_id, mtx, d): + """Return a list Synfig layers that represent the gradient with the given id""" + gradient = d.get_gradient(url_id) + if gradient is None: + # Patterns and other URLs not supported + return [None] + + if gradient["type"] == "linear": + layer = d.create_layer("linear_gradient", url_id, + d.gradient_to_params(gradient), + guids={"gradient": gradient["stops_guid"]}) + + if gradient["type"] == "radial": + layer = d.create_layer("radial_gradient", url_id, + d.gradient_to_params(gradient), + guids={"gradient": gradient["stops_guid"]}) + + trm = Transform(mtx) * Transform(gradient["mtx"]) + return d.op_transform([layer], trm.matrix) + + +if __name__ == '__main__': + SynfigExport().run() diff --git a/share/extensions/synfig_prepare.py b/share/extensions/synfig_prepare.py new file mode 100755 index 0000000..144577c --- /dev/null +++ b/share/extensions/synfig_prepare.py @@ -0,0 +1,476 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2011 Nikita Kitaev +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +# +""" +Simplifies SVG files in preparation for sif export. +""" + +import os +import tempfile +from subprocess import PIPE, Popen + +import inkex +from inkex import load_svg, Group, PathElement, ShapeElement,\ + Anchor, Switch, SvgDocumentElement, Transform + +###### Utility Classes #################################### + +class MalformedSVGError(Exception): + """Raised when the SVG document is invalid or contains unsupported features""" + + def __init__(self, value): + self.value = value + + def __str__(self): + return """SVG document is invalid or contains unsupported features + +Error message: %s + +The SVG to Synfig converter is designed to handle SVG files that were created using Inkscape. Unsupported features are most likely to occur in SVG files written by other programs. +""" % repr(self.value) + + +class InkscapeActionGroup(object): + """A class for calling Inkscape to perform operations on a document""" + + def __init__(self, svg_document=None): + self.command = "" + self.init_args = "" + self.has_selection = False + self.has_action = False + self.set_svg_document(svg_document) + + def set_svg_document(self, svg_document): + """Set the SVG document that Inkscape will operate on""" + self.svg_document = svg_document + self.svg = svg_document.getroot() + + def set_init_args(self, cmd): + """Set the initial arguments to Inkscape subprocess + + Can be used to pass additional arguments to Inkscape, or an initializer + command (e.g. unlock all objects before proceeding). + """ + self.init_args = cmd + + def clear(self): + """Clear all actions""" + self.command = "" + self.has_action = False + self.has_selection = False + + def verb(self, verb): + """Run an Inkscape verb + + For a list of verbs, run `inkscape --verb-list` + """ + if self.has_selection: + self.command += "--verb=%s " % verb + + if not self.has_action: + self.has_action = True + + def select_id(self, object_id): + """Select object with given id""" + self.command += "--select=%s " % object_id + if not self.has_selection: + self.has_selection = True + + def select_node(self, node): + """Select the object represented by the SVG node + + Selection will fail if node has no id attribute + """ + node_id = node.get("id", None) + if node_id is None: + raise MalformedSVGError("Node has no id") + self.select_id(node_id) + + def select_nodes(self, nodes): + """Select objects represented by SVG nodes + + Selection will fail if any node has no id attribute + """ + for node in nodes: + self.select_node(node) + + def select_xpath(self, xpath): + """Select objects matching a given XPath expression + + Selection will fail if any matching node has no id attribute + """ + self.select_nodes(self.svg.xpath(xpath)) + + def deselect(self): + """Deselect all objects""" + if self.has_selection: + self.verb("EditDeselect") + self.has_selection = False + + def run_file(self, filename): + """Run the actions on a specific file""" + if not self.has_action: + return + + cmd = self.init_args + " " + self.command + "--verb=FileSave --verb=FileQuit" + p = Popen('inkscape "{}" {}'.format(filename, cmd), shell=True, stdout=PIPE, stderr=PIPE) + rc = p.wait() + f = p.stdout + err = p.stderr + + f.close() + err.close() + + def run_document(self): + """Run the actions on the svg xml tree""" + if not self.has_action: + return self.svg_document + + # First save the document + svgfile = tempfile.mktemp(".svg") + self.svg_document.write(svgfile) + + # Run the action on the document + self.run_file(svgfile) + + # Open the resulting file + with open(svgfile, 'r') as stream: + self.svg_document = load_svg(stream) + + # Clean up. + try: + os.remove(svgfile) + except Exception: + pass + + # Return the new document + return self.svg_document + + +class SynfigExportActionGroup(InkscapeActionGroup): + """An action group with stock commands designed for Synfig exporting""" + + def __init__(self, svg_document=None): + InkscapeActionGroup.__init__(self, svg_document) + self.set_init_args("--verb=UnlockAllInAllLayers") + self.objects_to_paths() + self.unlink_clones() + + def objects_to_paths(self): + """Convert unsupported objects to paths""" + # Flow roots contain rectangles inside them, so they need to be + # converted to paths separately from other shapes + self.select_xpath("//svg:flowRoot") + self.verb("ObjectToPath") + self.deselect() + + non_paths = [ + "svg:rect", + "svg:circle", + "svg:ellipse", + "svg:line", + "svg:polyline", + "svg:polygon", + "svg:text" + ] + + # Build an xpath command to select these nodes + xpath_cmd = " | ".join(["//" + np for np in non_paths]) + + # Select all of these elements + # Note: already selected elements are not deselected + self.select_xpath(xpath_cmd) + + # Convert them to paths + self.verb("ObjectToPath") + self.deselect() + + def unlink_clones(self): + """Unlink clones (remove elements)""" + self.select_xpath("//svg:use") + self.verb("EditUnlinkClone") + self.deselect() + + +###### Utility Functions ################################## + +### Path related + +def fuse_subpaths(path_node): + """Fuse subpaths of a path. Should only be used on unstroked paths""" + path = path_node.path.to_arrays() + + if len(path) == 0: + return + + i = 0 + initial_point = [path[i][1][-2], path[i][1][-1]] + prev_end = initial_point[:] + return_stack = [] + while i < len(path): + # Remove any terminators: they are redundant + if path[i][0] == "Z": + path.remove(["Z", []]) + continue + + if path[i][0] == 'V': + prev_end[0] = path[i][1][0] + i += 1 + continue + elif path[i][0] == 'H': + prev_end[1] = path[i][1][0] + i += 1 + continue + elif path[1][0] != 'M' or i == 0: + prev_end = path[i][1][-2:] + i += 1 + continue + + # This element begins a new path - it should be a moveto + assert (path[i][0] == 'M') + + # Swap it for a lineto + path[i][0] = 'L' + # If the old subpath has not been closed yet, close it + if prev_end != initial_point: + path.insert(i, ['L', initial_point]) + i += 1 + + # Set the initial point of this subpath + initial_point = path[i][1][-2:] + + # Append this point to the return stack + return_stack.append(initial_point) + # end while + + # Now pop the entire return stack + while return_stack: + el = ['L', return_stack.pop()] + path.insert(i, el) + i += 1 + + path_d = str(inkex.Path(path)) + path_node.set("d", path_d) + + +def split_fill_and_stroke(path_node): + """Split a path into two paths, one filled and one stroked + + Returns a the list [fill, stroke], where each is the XML element of the + fill or stroke, or None. + """ + style = dict(inkex.Style.parse_str(path_node.get("style", ""))) + + # If there is only stroke or only fill, don't split anything + if "fill" in style and style["fill"] == "none": + if "stroke" not in style or style["stroke"] == "none": + return [None, None] # Path has neither stroke nor fill + else: + return [None, path_node] + if "stroke" not in style.keys() or style["stroke"] == "none": + return [path_node, None] + + + group = Group() + fill = group.add(PathElement()) + stroke = group.add(PathElement()) + + d = path_node.pop('d') + if d is None: + raise AssertionError("Cannot split stroke and fill of non-path element") + + nodetypes = path_node.pop('sodipodi:nodetypes', None) + path_id = path_node.pop('id', str(id(path_node))) + transform = path_node.pop('transform', None) + path_node.pop('style') + + # Pass along all remaining attributes to the group + for attrib_name, attrib_value in path_node.attrib.items(): + group.set(attrib_name, attrib_value) + + group.set("id", path_id) + + # Next split apart the style attribute + style_group = {} + style_fill = {"stroke": "none", "fill": "#000000"} + style_stroke = {"fill": "none", "stroke": "none"} + + for key in style.keys(): + if key.startswith("fill"): + style_fill[key] = style[key] + elif key.startswith("stroke"): + style_stroke[key] = style[key] + elif key.startswith("marker"): + style_stroke[key] = style[key] + elif key.startswith("filter"): + style_group[key] = style[key] + else: + style_fill[key] = style[key] + style_stroke[key] = style[key] + + if len(style_group) != 0: + group.set("style", str(inkex.Style(style_group))) + + fill.set("style", str(inkex.Style(style_fill))) + stroke.set("style", str(inkex.Style(style_stroke))) + + # Finalize the two paths + fill.set("d", d) + stroke.set("d", d) + if nodetypes is not None: + fill.set('sodipodi:nodetypes', nodetypes) + stroke.set('sodipodi:nodetypes', nodetypes) + fill.set("id", path_id + "-fill") + stroke.set("id", path_id + "-stroke") + if transform is not None: + fill.set("transform", transform) + stroke.set("transform", transform) + + # Replace the original node with the group + path_node.getparent().replace(path_node, group) + + return [fill, stroke] + + +### Object related + +def propagate_attribs(node, parent_style={}, parent_transform=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]): + """Propagate style and transform to remove inheritance""" + + # Don't enter non-graphical portions of the document + if not isinstance(node, (ShapeElement, SvgDocumentElement)): + return + + # Compose the transformations + if isinstance(node, SvgDocumentElement) and node.get("viewBox"): + vx, vy, vw, vh = [get_dimension(x) for x in node.get_viewbox()] + dw = get_dimension(node.get("width", vw)) + dh = get_dimension(node.get("height", vh)) + this_transform = Transform(translate=(-vx, -vy), scale=(dw / vw, dh / vh)) + del node.attrib["viewBox"] + else: + this_transform = Transform(parent_transform) + + this_transform *= node.transform + + # Compose the style attribs + this_style = dict(inkex.Style.parse_str(node.get("style", ""))) + remaining_style = {} # Style attributes that are not propagated + + non_propagated = ["filter"] # Filters should remain on the topmost ancestor + for key in non_propagated: + if key in this_style.keys(): + remaining_style[key] = this_style[key] + del this_style[key] + + # Create a copy of the parent style, and merge this style into it + parent_style_copy = parent_style.copy() + parent_style_copy.update(this_style) + this_style = parent_style_copy + + # Merge in any attributes outside of the style + style_attribs = ["fill", "stroke"] + for attrib in style_attribs: + if node.get(attrib): + this_style[attrib] = node.get(attrib) + del node.attrib[attrib] + + if isinstance(node, (SvgDocumentElement, Group, Anchor, Switch)): + # Leave only non-propagating style attributes + if remaining_style: + node.style = remaining_style + else: + if "style" in node.keys(): + del node.attrib["style"] + + # Remove the transform attribute + if "transform" in node.keys(): + del node.attrib["transform"] + + # Continue propagating on subelements + for child in node.iterchildren(): + propagate_attribs(child, this_style, this_transform) + else: + # This element is not a container + + # Merge remaining_style into this_style + this_style.update(remaining_style) + + # Set the element's style and transform attribs + node.style = this_style + node.transform = this_transform + + +### Style related + +def get_dimension(s="1024"): + """Convert an SVG length string from arbitrary units to pixels""" + if s == "": + return 0 + if isinstance(s, float): + return s + try: + last = int(s[-1]) + except: + last = None + + if type(last) == int: + return float(s) + elif s[-1] == "%": + return 1024 + elif s[-2:] == "px": + return float(s[:-2]) + elif s[-2:] == "pt": + return float(s[:-2]) * 1.333 + elif s[-2:] == "em": + return float(s[:-2]) * 16 + elif s[-2:] == "mm": + return float(s[:-2]) * 3.779 + elif s[-2:] == "pc": + return float(s[:-2]) * 16 + elif s[-2:] == "cm": + return float(s[:-2]) * 37.79 + elif s[-2:] == "in": + return float(s[:-2]) * 96 + else: + return 1024 + + +###### Main Class ######################################### +class SynfigPrep(inkex.EffectExtension): + def effect(self): + """Transform document in preparation for exporting it into the Synfig format""" + + a = SynfigExportActionGroup(self.document) + self.document = a.run_document() + + # Remove inheritance of attributes + propagate_attribs(self.document.getroot()) + + # Fuse multiple subpaths in fills + for node in self.document.getroot().xpath('//svg:path'): + if node.get("d", "").lower().count("m") > 1: + # There are multiple subpaths + fill = split_fill_and_stroke(node)[0] + if fill is not None: + fuse_subpaths(fill) + + +if __name__ == '__main__': + SynfigPrep().run() diff --git a/share/extensions/tar_layers.inx b/share/extensions/tar_layers.inx new file mode 100644 index 0000000..2e726bf --- /dev/null +++ b/share/extensions/tar_layers.inx @@ -0,0 +1,17 @@ + + + Collection of SVG files One per root layer + org.inkscape.output.tar_layers + org.inkscape.output.svg.inkscape + + .tar + application/tar + Layers as Separate SVG (*.tar) + Each layer split into it's own svg file and collected as a tape archive (tar file) + false + + + diff --git a/share/extensions/tar_layers.py b/share/extensions/tar_layers.py new file mode 100755 index 0000000..09a1dfb --- /dev/null +++ b/share/extensions/tar_layers.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2014 Martin Owens, email@doctormo.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +An extension to export multiple svg files from a single svg file containing layers. + +Each defs is duplicated for each svg outputted. +""" + +from __future__ import unicode_literals +import os +import sys +import copy +import tarfile +import io +import calendar +import time +import inkex + +class TarLayers(inkex.OutputExtension): + """Entry point to our layers export""" + def make_template(self): + """Returns the current document as a new empty document with the same defs""" + newdoc = copy.deepcopy(self.document) + for (name, layer) in self.layers(newdoc): + layer.getparent().remove(layer) + return newdoc + + def layers(self, document): + for node in document.getroot().iterchildren(): + if isinstance(node, inkex.Layer) and node.label: + yield (node.label, node) + + def io_document(self, name, doc): + string = io.BytesIO() + doc.write(string) + info = tarfile.TarInfo(name=name+'.svg') + info.mtime = calendar.timegm(time.gmtime()) + info.size = string.tell() + string.seek(0) + return dict(tarinfo=info, fileobj=string) + + def save(self, stream): + """Save the tar file output""" + tar = tarfile.open(fileobj=stream, mode='w|') + + # Switch stdout to binary on Windows. + if sys.platform == "win32": + import msvcrt + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + + template = self.make_template() + + previous = None + for (name, _layer) in self.layers(self.document): + layer = copy.deepcopy(_layer) + if previous != None: + template.getroot().replace(previous, layer) + else: + template.getroot().append(layer) + previous = layer + + tar.addfile(**self.io_document(name, template)) + + +if __name__ == '__main__': #pragma: no cover + TarLayers().run() diff --git a/share/extensions/template.py b/share/extensions/template.py new file mode 100755 index 0000000..849b719 --- /dev/null +++ b/share/extensions/template.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Generic template functionality controlled by the INX file. +""" + +import inkex + +class InxDefinedTemplate(inkex.TemplateExtension): + """Most functionality is in TemplateExtension""" + multi_inx = True + themes = { + 'white': ('#ffffff', '#666666'), + 'gray': ('#808080', '#444444'), + 'black': ('#000000', '#999999'), + } + + def add_arguments(self, pars): + pars.add_argument("--background", type=self.arg_theme, default="normal") + pars.add_argument("--noborder", type=inkex.Boolean) + + def arg_theme(self, value): + """Set the page theme based on the value given""" + if value in self.themes: + return self.themes[value] + return None + + def set_namedview(self, width, height, unit): + super(InxDefinedTemplate, self).set_namedview(width, height, unit) + namedview = self.svg.namedview + if self.options.background: + namedview.set('pagecolor', self.options.background[0]) + namedview.set('bordercolor', self.options.background[1]) + namedview.set('inkscape:pageopacity', "1.0") + namedview.set('inkscape:pageshadow', "0") + + if self.options.noborder: + namedview.set('bordercolor', namedview.get('pagecolor')) + namedview.set('borderopacity', "0") + +if __name__ == '__main__': + InxDefinedTemplate().run() diff --git a/share/extensions/template_business_card.inx b/share/extensions/template_business_card.inx new file mode 100644 index 0000000..2f9f044 --- /dev/null +++ b/share/extensions/template_business_card.inx @@ -0,0 +1,30 @@ + + + Business Card + org.inkscape.template.business_card + + + + + + + + + + + + + all + + + Business Card... + Tavmjong Bah + Business card of chosen size. + 2014-10-09 + business card + + + diff --git a/share/extensions/template_desktop.inx b/share/extensions/template_desktop.inx new file mode 100644 index 0000000..5d2e71a --- /dev/null +++ b/share/extensions/template_desktop.inx @@ -0,0 +1,42 @@ + + + Desktop + org.inkscape.template.desktop + + + + + + + + + + + + + + + + + + + + + 1920 + 1080 + + + all + + + Desktop... + Tavmjong Bah + Empty desktop of chosen size. + 2014-10-09 + empty desktop + + + diff --git a/share/extensions/template_dvd_cover.inx b/share/extensions/template_dvd_cover.inx new file mode 100644 index 0000000..ff12eaf --- /dev/null +++ b/share/extensions/template_dvd_cover.inx @@ -0,0 +1,29 @@ + + + DVD Cover + org.inkscape.template.dvd_cover + + + + + + + + + 3 + + + all + + + DVD Cover... + Tavmjong Bah + DVD cover of chosen size. + 2014-10-10 + dvd cover + + + diff --git a/share/extensions/template_dvd_cover.py b/share/extensions/template_dvd_cover.py new file mode 100755 index 0000000..278a020 --- /dev/null +++ b/share/extensions/template_dvd_cover.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2010 Tavmjong Bah +# 2019 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Generic template functionality controlled by the INX file. +""" + +import inkex + +class DvdCover(inkex.TemplateExtension): + """Create an empty DVD Cover (in mm)""" + multi_inx = True + def add_arguments(self, pars): + pars.add_argument("-s", "--spine", type=float, default=14.0, + help="Dvd spine width (mm)") + pars.add_argument("-b", "--bleed", type=float, default=3.0, + help="Bleed (extra area around image") + + def get_size(self): + # Dimensions in mm + width = 259.0 # Before adding spine width or bleed + height = 183.0 # Before adding bleed + bleed = self.options.bleed + spine = self.options.spine + + return (width + spine + bleed * 2.0, 'mm', + height + bleed * 2.0, 'mm') + + def set_namedview(self, width, height, unit): + super(DvdCover, self).set_namedview(width, height, unit) + (width, _, height, unit) = self.get_size() + bleed, spine = self.options.bleed, self.options.spine + self.svg.namedview.new_guide(bleed, True, "bottom") + self.svg.namedview.new_guide(height - bleed, True, "top") + self.svg.namedview.new_guide(bleed, False, "left edge") + self.svg.namedview.new_guide((width - spine) / 2.0, False, "left spline") + self.svg.namedview.new_guide((width + spine) / 2.0, False, "right spline") + self.svg.namedview.new_guide(width - bleed, False, "top") + +if __name__ == '__main__': + DvdCover().run() diff --git a/share/extensions/template_envelope.inx b/share/extensions/template_envelope.inx new file mode 100644 index 0000000..f10a408 --- /dev/null +++ b/share/extensions/template_envelope.inx @@ -0,0 +1,25 @@ + + + Envelope + org.inkscape.template.envelope + + + + + + + + all + + + Envelope... + Windell Oskay + Blank envelope of chosen size. + 2019-07-11 + empty envelope dl no10 + + + diff --git a/share/extensions/template_generic.inx b/share/extensions/template_generic.inx new file mode 100644 index 0000000..3ce33ce --- /dev/null +++ b/share/extensions/template_generic.inx @@ -0,0 +1,41 @@ + + + Generic Canvas + org.inkscape.template.generic_canvas + + 800 + 600 + + + + + + + + + + + + + + + + + + false + + + all + + + Generic canvas... + Tavmjong Bah + Generic canvas of chosen size. + 2014-10-09 + empty generic canvas + + + diff --git a/share/extensions/template_icon.inx b/share/extensions/template_icon.inx new file mode 100644 index 0000000..1ce6db5 --- /dev/null +++ b/share/extensions/template_icon.inx @@ -0,0 +1,23 @@ + + + Icon + org.inkscape.template.icon + + 16 + true + + + all + + + Icon... + Tavmjong Bah + Empty icon of chosen size. + 2014-10-09 + empty icon + + + diff --git a/share/extensions/template_page.inx b/share/extensions/template_page.inx new file mode 100644 index 0000000..413a282 --- /dev/null +++ b/share/extensions/template_page.inx @@ -0,0 +1,43 @@ + + + Blank Page + org.inkscape.template.page + + + + + + + + + + + + + + + + + + + + + + + false + + + all + + + Blank Page... + Jan Darowski, updated by Tavmjong Bah + Empty page of chosen size. + 2019-07-11 + empty page sheet tabloid ledger letter legal print paper a4 a3 a5 letter black white opaque + + + diff --git a/share/extensions/template_seamless_pattern.inx b/share/extensions/template_seamless_pattern.inx new file mode 100644 index 0000000..f70fd29 --- /dev/null +++ b/share/extensions/template_seamless_pattern.inx @@ -0,0 +1,23 @@ + + + Seamless Pattern + org.inkscape.template.seamless_pattern + + 100 + 100 + + + all + + + Seamless Pattern... + Jabiertxof + Create seamless patterns. + 2014-10-16 + live seamless patterns + + + diff --git a/share/extensions/template_seamless_pattern.py b/share/extensions/template_seamless_pattern.py new file mode 100755 index 0000000..f10af39 --- /dev/null +++ b/share/extensions/template_seamless_pattern.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# coding=utf-8 + +# Written by Jabiertxof +# V.06 + +import os + +import inkex +from inkex import load_svg + +class SeamlessPattern(inkex.TemplateExtension): + """Generate a seamless pattern template""" + multi_inx = True + + @classmethod + def get_template(cls): + name = "seamless_pattern.svg" + path = os.path.dirname(os.path.realpath(__file__)) + return load_svg(os.path.join(path, name)) + + def set_namedview(self, width, height, unit): + width = self.options.width + height = self.options.height + factor = width / height + clip_rect = self.svg.getElementById("clipPathRect") + clip_rect.set("width", str(width)) + clip_rect.set("height", str(height)) + + scale = inkex.Transform(scale=(width / 100, height / 100)) + self.svg.getElementById("designTop").transform = scale + self.svg.getElementById("designBottom").transform = scale + + scale = (1, factor) if factor <= 1 else (1 / factor, 1) + for child in self.svg.getElementById("designTop"): + child.transform = inkex.Transform(scale=scale) + + text_preview = self.svg.getElementById('textPreview') + if text_preview is not None: + x = width / 100.0 / factor + y = height / 1000.0 + if factor <= 1: + x *= factor + y *= factor + text_preview.transform = inkex.Transform(translate=(int(width) * 2, 0), scale=(x, y)) + + info_group = self.svg.getElementById('infoGroup') + if info_group is not None: + scale = 100 if factor <= 1 else 1000 + info_group.transform = inkex.Transform(scale=(width / scale, height / scale * factor)) + + sides = [(x, y) for y in (-height, 0, height) for x in (-width, 0, width)] + for i, (x, y) in enumerate(sides): + top = self.svg.getElementById('top{i}'.format(i=i+1)) + bottom = self.svg.getElementById('bottom{i}'.format(i=i+1)) + if top is not None and bottom is not None: + bottom.transform = top.transform = inkex.Transform(translate=(x, y)) + + clones = [(x, y) for x in (0, width, width * 2) for y in (0, height, height * 2)] + for i, (x, y) in enumerate(clones): + preview = self.svg.getElementById("clonePreview{i}".format(i=i)) + if preview is not None: + preview.transform = inkex.Transform(translate=(x, y)) + + pattern_generator = self.svg.getElementById('fullPatternClone') + if pattern_generator is not None: + pattern_generator.transform = inkex.Transform(translate=(width * 2, -height)) + pattern_generator.set("inkscape:tile-cx", width / 2) + pattern_generator.set("inkscape:tile-cy", height / 2) + pattern_generator.set("inkscape:tile-w", width) + pattern_generator.set("inkscape:tile-h", height) + pattern_generator.set("inkscape:tile-x0", width) + pattern_generator.set("inkscape:tile-y0", height) + pattern_generator.set("width", width) + pattern_generator.set("height", height) + + namedview = self.svg.namedview + namedview.set('inkscape:document-units', 'px') + namedview.set('inkscape:cx', (width * 5.5) / 2) + namedview.set('inkscape:cy', "0") + namedview.set('inkscape:zoom', 1 / (width / 100)) + +if __name__ == '__main__': + SeamlessPattern().run() diff --git a/share/extensions/template_video.inx b/share/extensions/template_video.inx new file mode 100644 index 0000000..7d8c63d --- /dev/null +++ b/share/extensions/template_video.inx @@ -0,0 +1,35 @@ + + + Video Screen + org.inkscape.template.video + + + + + + + + + + + + + + 1920 + 1080 + + + all + + + Video... + Tavmjong Bah + Video screen of chosen size. + 2014-10-09 + empty video + + + diff --git a/share/extensions/tests/README.md b/share/extensions/tests/README.md new file mode 100644 index 0000000..e5990d2 --- /dev/null +++ b/share/extensions/tests/README.md @@ -0,0 +1,33 @@ +# Inkscape Extension Tests + +This folder contains tests for the Inkscape extensions and libraries in this +repo. + +Pytest and Pytest-Coverage are required to run tests. Usually the best way to install it is: + +More info here: https://docs.pytest.org/en/latest/getting-started.html + +```shell +$ # Python 2 +$ pip install pytest pytest-cov + +$ # Python 3 +$ pip3 install pytest pytest-cov +``` + +To run all tests: + +```shell +# In the top-level directory of the extensions repo: +$ python2 -m pytest +$ python3 -m pytest +``` + +To run the tests in a specific file (in this case, +`tests/test_color_blackandwhite.py`): + +```shell +# In the top-level directory of the extensions repo: +$ python2 -m pytest tests/test_color_blackandwhite.py +$ python3 -m pytest tests/test_color_blackandwhite.py +``` diff --git a/share/extensions/tests/__init__.py b/share/extensions/tests/__init__.py new file mode 100644 index 0000000..bf893c0 --- /dev/null +++ b/share/extensions/tests/__init__.py @@ -0,0 +1 @@ +# coding=utf-8 \ No newline at end of file diff --git a/share/extensions/tests/__pycache__/__init__.cpython-39.pyc b/share/extensions/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..4cb9e98 Binary files /dev/null and b/share/extensions/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/share/extensions/tests/__pycache__/test_addnodes.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_addnodes.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..311e07a Binary files /dev/null and b/share/extensions/tests/__pycache__/test_addnodes.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_HSL_adjust.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_HSL_adjust.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..f52e185 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_HSL_adjust.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_blackandwhite.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_blackandwhite.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..a0b8532 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_blackandwhite.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_brighter.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_brighter.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..74d5c97 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_brighter.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_custom.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_custom.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..a47d7d8 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_custom.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_darker.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_darker.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..e3ec9f9 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_darker.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_desaturate.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_desaturate.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..7e4bfde Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_desaturate.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_grayscale.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_grayscale.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..b90c525 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_grayscale.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_lesshue.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_lesshue.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..169a5ec Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_lesshue.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_lesslight.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_lesslight.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..c22c25b Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_lesslight.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_lesssaturation.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_lesssaturation.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..b3e93e1 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_lesssaturation.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_list.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_list.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..35a6f36 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_list.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_morehue.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_morehue.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..689e4c9 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_morehue.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_morelight.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_morelight.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..3a78d55 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_morelight.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_moresaturation.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_moresaturation.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..3259e2e Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_moresaturation.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_negative.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_negative.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..e6bc7c9 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_negative.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_randomize.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_randomize.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..1cf8353 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_randomize.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_removeblue.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_removeblue.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..521e30d Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_removeblue.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_removegreen.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_removegreen.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..4892907 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_removegreen.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_removered.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_removered.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..e7b52d3 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_removered.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_replace.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_replace.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..b70b378 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_replace.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_color_rgbbarrel.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_color_rgbbarrel.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..6564183 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_color_rgbbarrel.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_convert2dashes.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_convert2dashes.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..ab845df Binary files /dev/null and b/share/extensions/tests/__pycache__/test_convert2dashes.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_deprecated_simple.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_deprecated_simple.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..bb34f65 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_deprecated_simple.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_dhw_input.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_dhw_input.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..30ce06a Binary files /dev/null and b/share/extensions/tests/__pycache__/test_dhw_input.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_dimension.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_dimension.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..1cf7522 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_dimension.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_docinfo.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_docinfo.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..1fad839 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_docinfo.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_dpiswitcher.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_dpiswitcher.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..6252034 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_dpiswitcher.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_draw_from_triangle.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_draw_from_triangle.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..c50def6 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_draw_from_triangle.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_dxf12_outlines.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_dxf12_outlines.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..593aa61 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_dxf12_outlines.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_dxf_input.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_dxf_input.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..0386427 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_dxf_input.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_dxf_outlines.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_dxf_outlines.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..9a9a6a6 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_dxf_outlines.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_edge3d.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_edge3d.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..2086578 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_edge3d.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_embedimage.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_embedimage.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..2becb3d Binary files /dev/null and b/share/extensions/tests/__pycache__/test_embedimage.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_export_gimp_palette.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_export_gimp_palette.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..076a83e Binary files /dev/null and b/share/extensions/tests/__pycache__/test_export_gimp_palette.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_extractimage.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_extractimage.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..29ba874 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_extractimage.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_extrude.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_extrude.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..4858662 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_extrude.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_fig_input.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_fig_input.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..0dcf254 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_fig_input.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_flatten.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_flatten.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..3c821d9 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_flatten.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_foldablebox.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_foldablebox.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..4557929 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_foldablebox.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_fractalize.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_fractalize.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..846e2db Binary files /dev/null and b/share/extensions/tests/__pycache__/test_fractalize.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_frame.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_frame.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..a59c401 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_frame.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_funcplot.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_funcplot.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..df75629 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_funcplot.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_gcodetools.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_gcodetools.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..694faa9 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_gcodetools.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_generate_voronoi.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_generate_voronoi.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..a544d21 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_generate_voronoi.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_gimp_xcf.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_gimp_xcf.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..797dd5d Binary files /dev/null and b/share/extensions/tests/__pycache__/test_gimp_xcf.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_grid_cartesian.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_grid_cartesian.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..dcc10a4 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_grid_cartesian.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_grid_isometric.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_grid_isometric.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..0499a80 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_grid_isometric.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_grid_polar.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_grid_polar.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..e73860c Binary files /dev/null and b/share/extensions/tests/__pycache__/test_grid_polar.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_guides_creator.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_guides_creator.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..4728f63 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_guides_creator.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_guillotine.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_guillotine.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..1db7459 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_guillotine.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_handles.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_handles.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..97d96b7 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_handles.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_hershey.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_hershey.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..e299852 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_hershey.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_hpgl_decoder.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_hpgl_decoder.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..13cdda2 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_hpgl_decoder.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_hpgl_input.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_hpgl_input.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..70ce0f9 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_hpgl_input.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_hpgl_output.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_hpgl_output.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..4a59bee Binary files /dev/null and b/share/extensions/tests/__pycache__/test_hpgl_output.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_image_attributes.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_image_attributes.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..67beb0b Binary files /dev/null and b/share/extensions/tests/__pycache__/test_image_attributes.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_ink2canvas_svg.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_ink2canvas_svg.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..3036d6d Binary files /dev/null and b/share/extensions/tests/__pycache__/test_ink2canvas_svg.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..1ca2343 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_base.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_base.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..256b884 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_base.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_bezier.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_bezier.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..a61d8bd Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_bezier.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_bounding_box.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_bounding_box.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..bf7e8ef Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_bounding_box.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_colors.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_colors.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..0895ec6 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_colors.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_command.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_command.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..4e35a97 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_command.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_cubic_paths.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_cubic_paths.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..eed99ee Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_cubic_paths.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_deprecated.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_deprecated.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..1c2d485 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_deprecated.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_elements.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_elements.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..75d954b Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_elements.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_elements_base.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_elements_base.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..bb9ce2d Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_elements_base.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_elements_selections.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_elements_selections.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..201635e Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_elements_selections.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_extensions.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_extensions.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..af0de94 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_extensions.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_inx.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_inx.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..8529812 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_inx.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_paths.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_paths.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..37c2806 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_paths.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_styles.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_styles.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..be9409f Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_styles.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_svg.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_svg.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..e2c172b Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_svg.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_tester.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_tester.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..b5a21e2 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_tester.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_transforms.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_transforms.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..32e2233 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_transforms.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_tween.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_tween.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..467a5d2 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_tween.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_units.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_units.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..f439b42 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_units.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkex_utils.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkex_utils.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..3fa971f Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkex_utils.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkscape_follow_link.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkscape_follow_link.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..d87ced6 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkscape_follow_link.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_inkwebeffect.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_inkwebeffect.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..4246cb3 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_inkwebeffect.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_interp.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_interp.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..42c37ec Binary files /dev/null and b/share/extensions/tests/__pycache__/test_interp.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_interp_att_g.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_interp_att_g.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..9f5ef55 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_interp_att_g.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_jessyink_autotexts.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_jessyink_autotexts.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..00165ea Binary files /dev/null and b/share/extensions/tests/__pycache__/test_jessyink_autotexts.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_jessyink_effects.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_jessyink_effects.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..924420e Binary files /dev/null and b/share/extensions/tests/__pycache__/test_jessyink_effects.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_jessyink_export.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_jessyink_export.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..8a55950 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_jessyink_export.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_jessyink_install.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_jessyink_install.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..efb6de1 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_jessyink_install.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_jessyink_keybindings.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_jessyink_keybindings.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..1517c37 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_jessyink_keybindings.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_jessyink_masterslide.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_jessyink_masterslide.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..ef3be48 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_jessyink_masterslide.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_jessyink_mousehandler.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_jessyink_mousehandler.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..1124175 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_jessyink_mousehandler.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_jessyink_summary.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_jessyink_summary.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..f992af1 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_jessyink_summary.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_jessyink_transitions.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_jessyink_transitions.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..1a8f9ab Binary files /dev/null and b/share/extensions/tests/__pycache__/test_jessyink_transitions.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_jessyink_uninstall.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_jessyink_uninstall.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..40be5f8 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_jessyink_uninstall.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_jessyink_video.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_jessyink_video.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..bf87a07 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_jessyink_video.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_jessyink_view.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_jessyink_view.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..bb09d2c Binary files /dev/null and b/share/extensions/tests/__pycache__/test_jessyink_view.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_jitternodes.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_jitternodes.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..7ae7257 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_jitternodes.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_launch_webbrowser.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_launch_webbrowser.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..7175342 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_launch_webbrowser.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_layer2png.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_layer2png.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..a86c952 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_layer2png.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_layers2svgfont.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_layers2svgfont.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..244ec95 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_layers2svgfont.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_layout_nup.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_layout_nup.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..03a69ae Binary files /dev/null and b/share/extensions/tests/__pycache__/test_layout_nup.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_lindenmayer.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_lindenmayer.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..89b0b05 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_lindenmayer.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_lorem_ipsum.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_lorem_ipsum.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..432db4f Binary files /dev/null and b/share/extensions/tests/__pycache__/test_lorem_ipsum.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_markers_strokepaint.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_markers_strokepaint.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..03372a4 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_markers_strokepaint.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_measure.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_measure.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..30ea84e Binary files /dev/null and b/share/extensions/tests/__pycache__/test_measure.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_media_zip.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_media_zip.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..ae0e8c7 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_media_zip.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_merge_styles.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_merge_styles.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..6ba2618 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_merge_styles.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_motion.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_motion.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..0aa71da Binary files /dev/null and b/share/extensions/tests/__pycache__/test_motion.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_new_glyph_layer.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_new_glyph_layer.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..aa8c9a7 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_new_glyph_layer.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_next_glyph_layer.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_next_glyph_layer.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..e76286e Binary files /dev/null and b/share/extensions/tests/__pycache__/test_next_glyph_layer.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_nicechart.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_nicechart.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..c162372 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_nicechart.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_output_scour.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_output_scour.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..cec4bc1 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_output_scour.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_param_curves.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_param_curves.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..5e8efe3 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_param_curves.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_path_envelope.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_path_envelope.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..c9750a4 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_path_envelope.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_path_mesh.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_path_mesh.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..16c65bf Binary files /dev/null and b/share/extensions/tests/__pycache__/test_path_mesh.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_path_number_nodes.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_path_number_nodes.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..cf63be1 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_path_number_nodes.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_path_to_absolute.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_path_to_absolute.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..95b2076 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_path_to_absolute.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_pathalongpath.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_pathalongpath.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..a846eaf Binary files /dev/null and b/share/extensions/tests/__pycache__/test_pathalongpath.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_pathscatter.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_pathscatter.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..47d65e8 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_pathscatter.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_pdflatex.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_pdflatex.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..487cf4b Binary files /dev/null and b/share/extensions/tests/__pycache__/test_pdflatex.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_perfectboundcover.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_perfectboundcover.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..2b50bbb Binary files /dev/null and b/share/extensions/tests/__pycache__/test_perfectboundcover.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_perspective.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_perspective.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..20841f2 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_perspective.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_pixelsnap.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_pixelsnap.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..22c05a0 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_pixelsnap.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_plotter.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_plotter.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..d4138a3 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_plotter.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_polyhedron_3d.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_polyhedron_3d.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..ef4a0b7 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_polyhedron_3d.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_prepare_file_save_as.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_prepare_file_save_as.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..921a813 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_prepare_file_save_as.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_previous_glyph_layer.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_previous_glyph_layer.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..7d627f8 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_previous_glyph_layer.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_print_win32_vector.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_print_win32_vector.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..ebae3ed Binary files /dev/null and b/share/extensions/tests/__pycache__/test_print_win32_vector.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_printing_marks.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_printing_marks.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..434f1cc Binary files /dev/null and b/share/extensions/tests/__pycache__/test_printing_marks.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_ps_input.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_ps_input.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..2c088d6 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_ps_input.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_render_alphabetsoup.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_render_alphabetsoup.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..c5463fb Binary files /dev/null and b/share/extensions/tests/__pycache__/test_render_alphabetsoup.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_render_barcode.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_render_barcode.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..5eaf753 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_render_barcode.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_render_barcode_datamatrix.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_render_barcode_datamatrix.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..15e97b0 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_render_barcode_datamatrix.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_render_barcode_qrcode.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_render_barcode_qrcode.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..58efbb7 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_render_barcode_qrcode.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_render_gear_rack.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_render_gear_rack.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..5f87262 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_render_gear_rack.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_render_gears.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_render_gears.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..9075e4d Binary files /dev/null and b/share/extensions/tests/__pycache__/test_render_gears.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_replace_font.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_replace_font.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..7e66c7e Binary files /dev/null and b/share/extensions/tests/__pycache__/test_replace_font.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_restack.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_restack.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..4208212 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_restack.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_rtree.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_rtree.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..281d8de Binary files /dev/null and b/share/extensions/tests/__pycache__/test_rtree.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_rubberstretch.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_rubberstretch.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..2514947 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_rubberstretch.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_scribus_pdf.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_scribus_pdf.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..5781885 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_scribus_pdf.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_setup_typography_canvas.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_setup_typography_canvas.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..1ada73c Binary files /dev/null and b/share/extensions/tests/__pycache__/test_setup_typography_canvas.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_spirograph.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_spirograph.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..119769b Binary files /dev/null and b/share/extensions/tests/__pycache__/test_spirograph.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_straightseg.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_straightseg.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..ad929d3 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_straightseg.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_svgcalendar.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_svgcalendar.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..ee49b94 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_svgcalendar.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_svgfont2layers.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_svgfont2layers.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..64cb8f7 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_svgfont2layers.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_synfig_fileformat.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_synfig_fileformat.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..6efb161 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_synfig_fileformat.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_synfig_output.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_synfig_output.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..422f87b Binary files /dev/null and b/share/extensions/tests/__pycache__/test_synfig_output.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_synfig_prepare.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_synfig_prepare.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..a756782 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_synfig_prepare.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_tar_layers.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_tar_layers.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..7679f60 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_tar_layers.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_template.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_template.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..2b49258 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_template.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_template_dvd_cover.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_template_dvd_cover.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..89d4a35 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_template_dvd_cover.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_template_seamless_pattern.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_template_seamless_pattern.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..dc181af Binary files /dev/null and b/share/extensions/tests/__pycache__/test_template_seamless_pattern.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_text_braille.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_text_braille.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..24cd157 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_text_braille.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_text_extract.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_text_extract.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..3ab933a Binary files /dev/null and b/share/extensions/tests/__pycache__/test_text_extract.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_text_flipcase.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_text_flipcase.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..c41d963 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_text_flipcase.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_text_lowercase.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_text_lowercase.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..392da24 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_text_lowercase.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_text_merge.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_text_merge.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..1522b9c Binary files /dev/null and b/share/extensions/tests/__pycache__/test_text_merge.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_text_randomcase.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_text_randomcase.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..8963f34 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_text_randomcase.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_text_sentencecase.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_text_sentencecase.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..851eb07 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_text_sentencecase.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_text_split.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_text_split.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..9457aaf Binary files /dev/null and b/share/extensions/tests/__pycache__/test_text_split.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_text_titlecase.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_text_titlecase.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..858ea89 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_text_titlecase.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_text_uppercase.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_text_uppercase.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..256bbef Binary files /dev/null and b/share/extensions/tests/__pycache__/test_text_uppercase.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_triangle.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_triangle.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..74eb1a6 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_triangle.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_ungroup_deep.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_ungroup_deep.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..3012c0f Binary files /dev/null and b/share/extensions/tests/__pycache__/test_ungroup_deep.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_voronoi.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_voronoi.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..fb5ba28 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_voronoi.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_voronoi2svg.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_voronoi2svg.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..7d94ff3 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_voronoi2svg.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_web_interactive_mockup.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_web_interactive_mockup.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..9166ed2 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_web_interactive_mockup.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_web_set_att.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_web_set_att.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..e15fb9e Binary files /dev/null and b/share/extensions/tests/__pycache__/test_web_set_att.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_web_transmit_att.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_web_transmit_att.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..712ded5 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_web_transmit_att.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_webslicer_create_group.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_webslicer_create_group.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..efebe56 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_webslicer_create_group.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_webslicer_create_rect.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_webslicer_create_rect.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..390e788 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_webslicer_create_rect.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_webslicer_export.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_webslicer_export.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..5df6a39 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_webslicer_export.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_whirl.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_whirl.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..e4ea896 Binary files /dev/null and b/share/extensions/tests/__pycache__/test_whirl.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/__pycache__/test_wireframe_sphere.cpython-39-PYTEST.pyc b/share/extensions/tests/__pycache__/test_wireframe_sphere.cpython-39-PYTEST.pyc new file mode 100644 index 0000000..053f3ef Binary files /dev/null and b/share/extensions/tests/__pycache__/test_wireframe_sphere.cpython-39-PYTEST.pyc differ diff --git a/share/extensions/tests/add_pylint.py b/share/extensions/tests/add_pylint.py new file mode 100755 index 0000000..fdd62b1 --- /dev/null +++ b/share/extensions/tests/add_pylint.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# coding=utf-8 +# +# Copyright (C) 2019 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Add pylint scores to html coverage report html. + +This would be done deeper in coverage (as a plugin) but that functionality isn't available. +""" + +import os +import re +import sys +from pylint import lint +from pylint.reporters.text import TextReporter + +DIR = os.path.dirname(__file__) +REX = re.compile(r'.+?\">([^<]+\.py).+?\<\/tr\>') + +ARGS = ["--rcfile=" + os.path.join(DIR, '..', '.pylintrc')] +stdout = sys.stdout + +class WritableObject(object): + """dummy output stream for pylint""" + def __init__(self): + self.content = [] + + def write(self, line): + "dummy write" + self.content.append(line) + + def read(self): + "dummy read" + return self.content + +def run_pylint(fname): + "run pylint on the given file" + pylint_output = WritableObject() + # Pipe lint errors to devnull + temp, sys.stderr = sys.stderr, open('/dev/null', 'w') + try: + lint.Run([fname]+ARGS, reporter=TextReporter(pylint_output), exit=False) + except Exception: # pylint: disable=broad-except + return None + sys.stderr = temp + for output in pylint_output.read(): + rates = re.findall(r'rated at (\-?[\d\.]+)', output) + for rate in rates: + return float(rate) + if ' rated ' in output: + print(f"FAIL: {output}") + return None + + +def add_lint(fname): + """ + Parse index.html and append in the needed pylint score for this file. + """ + # Read in index file and strip out html whitespace (for easier rex'ing) + with open(fname, 'r') as fhl: + html = re.sub(r'\>\s+\<', '><', fhl.read()) + + # Keep a tab on how much we've inserted into the html + adjust = 0 + scores = [] + for match in REX.finditer(html): + score = add_lint_one(match.groups()[0]) + scores.append(score) + (start, end) = match.span() + start += adjust + end += adjust + old_content = html[start:end] + new_content = old_content[:-5] + f'{score}' + html = html[:start] + new_content + html[end:] + adjust += len(new_content) - len(old_content) + + total = sum(scores) / len(scores) + html = html.replace('coverage', 'coveragepylint') + html = html.replace('', f'{total:.2f}') + + with open(fname, 'w') as fhl: + fhl.write(html) + +def add_lint_one(py_file): + score = run_pylint(py_file) + if score is None: + score = -11.0 + return score + +if __name__ == '__main__': + if len(sys.argv) == 2 and sys.argv[-1].endswith('.html'): + for filename in sys.argv[1:]: + if os.path.isfile(filename): + add_lint(filename) + else: + for my_py_file in sys.argv[1:]: + score = add_lint_one(my_py_file) + print(f"{score},{my_py_file}") diff --git a/share/extensions/tests/data/README.md b/share/extensions/tests/data/README.md new file mode 100644 index 0000000..21dd50d --- /dev/null +++ b/share/extensions/tests/data/README.md @@ -0,0 +1,10 @@ +Files here should only be used in tests and won't be installed with Inkscape extensions. + +Folders: + + io - Non SVG files which extensions can read or save to for comparisons. + svg - SVG files which tests read in and test extensions and modules with. + cmd - Caches of calls to external programs, used when those programs are not available. + refs - Comparison files, all the expected outputs of calls. + batches - Data files where each row contains test data for the module. + diff --git a/share/extensions/tests/data/batches/barcodes.dat b/share/extensions/tests/data/batches/barcodes.dat new file mode 100644 index 0000000..6c77002 --- /dev/null +++ b/share/extensions/tests/data/batches/barcodes.dat @@ -0,0 +1,512 @@ +ean8:9493712:2020001011010001100010110111101020201000100110011011011001100110202 +ean8:2811738:2020010011011011100110010011001020201000100100001010010001011100202 +ean8:2248057:2020010011001001101000110110111020201110010100111010001001010000202 +ean8:0995042:2020001101000101100010110110001020201110010101110011011001110100202 +ean8:0262682:2020001101001001101011110010011020201010000100100011011001010000202 +ean8:2006346:2020010011000110100011010101111020201000010101110010100001000100202 +ean8:9892307:2020001011011011100010110010011020201000010111001010001001010000202 +ean8:7033691:2020111011000110101111010111101020201010000111010011001101000100202 +ean8:6587381:2020101111011000101101110111011020201000010100100011001101010000202 +ean8:4491441:2020100011010001100010110011001020201011100101110011001101000100202 +ean8:0099044:2020001101000110100010110001011020201110010101110010111001001000202 +ean8:7565347:2020111011011000101011110110001020201000010101110010001001000100202 +ean8:0764195:2020001101011101101011110100011020201100110111010010011101011100202 +ean8:4882328:2020100011011011101101110010011020201000010110110010010001110100202 +ean8:4635078:2020100011010111101111010110001020201110010100010010010001000100202 +ean8:8992114:2020110111000101100010110010011020201100110110011010111001101100202 +ean8:6658162:2020101111010111101100010110111020201100110101000011011001001000202 +ean8:7880049:2020111011011011101101110001101020201110010101110011101001010000202 +ean8:7782648:2020111011011101101101110010011020201010000101110010010001110010202 +ean8:8249002:2020110111001001101000110001011020201110010111001011011001000100202 +ean8:8735885:2020110111011101101111010110001020201001000100100010011101001000202 +ean8:9818050:2020001011011011100110010110111020201110010100111011100101110100202 +ean8:3410390:2020111101010001100110010001101020201000010111010011100101010000202 +ean8:9699144:2020001011010111100010110001011020201100110101110010111001101100202 +ean8:7628790:2020111011010111100100110110111020201000100111010011100101110100202 +ean8:2020310:2020010011000110100100110001101020201000010110011011100101001000202 +ean8:0429248:2020001101010001100100110001011020201101100101110010010001000100202 +ean8:3135528:2020111101001100101111010110001020201001110110110010010001001110202 +ean8:3182301:2020111101001100101101110010011020201000010111001011001101101100202 +ean8:0320063:2020001101011110100100110001101020201110010101000010000101010000202 +ean8:0865925:2020001101011011101011110110001020201110100110110010011101001110202 +ean8:3413434:2020111101010001100110010111101020201011100100001010111001011100202 +ean8:6730313:2020101111011101101111010001101020201000010110011010000101000100202 +ean8:4091215:2020100011000110100010110011001020201101100110011010011101001000202 +ean8:6504268:2020101111011000100011010100011020201101100101000010010001000100202 +ean8:0127593:2020001101001100100100110111011020201001110111010010000101000010202 +ean8:9214184:2020001011001001100110010100011020201100110100100010111001100110202 +ean8:4457000:2020100011010001101100010111011020201110010111001011100101101100202 +ean8:2575579:2020010011011000101110110110001020201001110100010011101001011100202 +ean8:3501739:2020111101011000100011010011001020201000100100001011101001011100202 +ean8:0733658:2020001101011101101111010111101020201010000100111010010001011100202 +ean8:6162818:2020101111001100101011110010011020201001000110011010010001101100202 +ean8:2397509:2020010011011110100010110111011020201001110111001011101001001110202 +ean8:3813610:2020111101011011100110010111101020201010000110011011100101001000202 +ean8:9488712:2020001011010001101101110110111020201000100110011011011001110100202 +ean8:1754326:2020011001011101101100010100011020201000010110110010100001101100202 +ean8:5221351:2020110001001001100100110011001020201000010100111011001101110100202 +ean8:6047658:2020101111000110101000110111011020201010000100111010010001010000202 +ean8:0475473:2020001101010001101110110110001020201011100100010010000101101100202 +ean8:0775368:2020001101011101101110110110001020201000010101000010010001001000202 +ean8:5825189:2020110001011011100100110110001020201100110100100011101001001000202 +ean8:9934666:2020001011000101101111010100011020201010000101000010100001110100202 +ean8:4849196:2020100011011011101000110001011020201100110111010010100001110100202 +ean8:6672405:2020101111010111101110110010011020201011100111001010011101010000202 +ean8:6368942:2020101111011110101011110110111020201110100101110011011001010000202 +ean8:0356360:2020001101011110101100010101111020201000010101000011100101100110202 +ean8:1506024:2020011001011000100011010101111020201110010110110010111001101100202 +ean8:0218852:2020001101001001100110010110111020201001000100111011011001101100202 +ean8:4357801:2020100011011110101100010111011020201001000111001011001101010000202 +ean8:8268643:2020110111001001101011110110111020201010000101110010000101000100202 +ean8:0117677:2020001101001100100110010111011020201010000100010010001001000010202 +ean8:8066330:2020110111000110101011110101111020201000010100001011100101110010202 +ean8:3074324:2020111101000110101110110100011020201000010110110010111001000010202 +ean8:7241642:2020111011001001101000110011001020201010000101110011011001010000202 +ean8:3375910:2020111101011110101110110110001020201110100110011011100101011100202 +ean8:5797167:2020110001011101100010110111011020201100110101000010001001011100202 +ean8:4413827:2020100011010001100110010111101020201001000110110010001001100110202 +ean8:0008083:2020001101000110100011010110111020201110010100100010000101001110202 +ean8:3240693:2020111101001001101000110001101020201010000111010010000101100110202 +ean8:3358989:2020111101011110101100010110111020201110100100100011101001000010202 +ean8:2525297:2020010011011000100100110110001020201101100111010010001001101100202 +ean8:9429514:2020001011010001100100110001011020201001110110011010111001010000202 +ean8:7496160:2020111011010001100010110101111020201100110101000011100101000010202 +ean8:2549811:2020010011011000101000110001011020201001000110011011001101110010202 +ean8:0366059:2020001101011110101011110101111020201110010100111011101001100110202 +ean8:7565529:2020111011011000101011110110001020201001110110110011101001000100202 +ean8:0544331:2020001101011000101000110100011020201000010100001011001101011100202 +ean8:5278325:2020110001001001101110110110111020201000010110110010011101001000202 +ean8:9659719:2020001011010111101100010001011020201000100110011011101001011100202 +ean8:6060276:2020101111000110101011110001101020201101100100010010100001000010202 +ean8:9983234:2020001011000101101101110111101020201101100100001010111001010000202 +ean8:9912723:2020001011000101100110010010011020201000100110110010000101000100202 +ean8:3934993:2020111101000101101111010100011020201110100111010010000101011100202 +ean8:9878705:2020001011011011101110110110111020201000100111001010011101110010202 +ean8:6099896:2020101111000110100010110001011020201001000111010010100001001110202 +ean8:9344302:2020001011011110101000110100011020201000010111001011011001110100202 +ean8:5779196:2020110001011101101110110001011020201100110111010010100001001000202 +ean8:2670364:2020010011010111101110110001101020201000010101000010111001110010202 +ean8:4049157:2020100011000110101000110001011020201100110100111010001001001000202 +ean8:6303180:2020101111011110100011010111101020201100110100100011100101001110202 +ean8:4252404:2020100011001001101100010010011020201011100111001010111001001110202 +ean8:6442834:2020101111010001101000110010011020201001000100001010111001001110202 +ean8:7330702:2020111011011110101111010001101020201000100111001011011001110010202 +ean8:6650523:2020101111010111101100010001101020201001110110110010000101001110202 +ean8:3261954:2020111101001001101011110011001020201110100100111010111001010000202 +ean8:1149083:2020011001001100101000110001011020201110010100100010000101001000202 +ean8:1227062:2020011001001001100100110111011020201110010101000011011001110010202 +ean8:7770854:2020111011011101101110110001101020201001000100111010111001110010202 +ean8:4841093:2020100011011011101000110011001020201110010111010010000101110100202 +ean8:9642244:2020001011010111101000110010011020201101100101110010111001100110202 +ean13:082432472648:20201101110010011010001101111010010011010001102020100010011011001010000101110010010001110010202 +ean13:867963203104:20201011110010001000101100001010100001001001102020111001010000101100110111001010111001001110202 +ean13:509274672682:20200011010010111001101101110110100011000010102020100010011011001010000100100011011001110010202 +ean13:137969745319:20201111010111011001011101011110010111001000102020101110010011101000010110011011101001101100202 +ean13:167788639568:20201011110111011001000101101110001001000010102020100001011101001001110101000010010001101100202 +ean13:763909437639:20201011110100001000101101001110001011001110102020100001010001001010000100001011101001110010202 +ean13:552090940230:20201100010011011010011100010110001101001011102020101110011100101101100100001011100101110100202 +ean13:064430020090:20201011110100011010001101111010001101000110102020110110011100101110010111010011100101001000202 +ean13:932131660272:20201111010011011011001101111010110011010111102020101000011100101101100100010011011001001000202 +ean13:697948544222:20200010110010001001011100111010110111011000102020101110010111001101100110110011011001110010202 +ean13:156973387097:20201100010101111001011101110110100001010000102020100100010001001110010111010010001001100110202 +ean13:742353933447:20201000110011011011110101110010111101001011102020100001010000101011100101110010001001001000202 +ean13:165804313397:20201011110110001000100100011010011101010000102020110011010000101000010111010010001001101100202 +ean13:142542648187:20201000110010011011100101000110011011000010102020101110010010001100110100100010001001101100202 +ean13:293746185362:20200010110111101001000100111010101111011001102020100100010011101000010101000011011001011100202 +ean13:079018947306:20201110110001011000110100110010110111000101102020101110010001001000010111001010100001110010202 +ean13:114714603442:20200110010100011001000100110010011101000010102020111001010000101011100101110011011001000100202 +ean13:886864480655:20201101110000101011011100001010011101010001102020100100011100101010000100111010011101011100202 +ean13:087399049352:20201101110111011011110100010110001011000110102020101110011101001000010100111011011001000010202 +ean13:680731212600:20201101110100111001000101000010011001001001102020110011011011001010000111001011100101001000202 +ean13:839853108496:20201111010010111011011101110010100001001100102020111001010010001011100111010010100001001000202 +ean13:759662798861:20201100010010111010111100001010010011001000102020111010010010001001000101000011001101011100202 +ean13:522030126885:20200100110011011010011101111010001101011001102020110110010100001001000100100010011101011100202 +ean13:872252192093:20201110110011011001001101110010011011001100102020111010011011001110010111010010000101011100202 +ean13:978323527315:20201110110001001010000100100110100001011000102020110110010001001000010110011010011101110100202 +ean13:594820728295:20200010110011101000100100100110001101001000102020110110010010001101100111010010011101000100202 +ean13:817309047999:20200110010010001011110101001110010111000110102020101110010001001110100111010011101001011100202 +ean13:529552650589:20200100110010111011100101100010010011000010102020100111011100101001110100100011101001000010202 +ean13:301953031072:20200011010011001001011101110010100001000110102020100001011001101110010100010011011001101100202 +ean13:279759074172:20201110110001011001000101110010001011010011102020100010010111001100110100010011011001011100202 +ean13:966269952443:20201011110000101001101101011110010111000101102020100111011011001011100101110010000101000100202 +ean13:393886491651:20200010110111101000100100010010000101010001102020111010011001101010000100111011001101110100202 +ean13:592818880852:20200010110011011000100100110010110111000100102020100100011100101001000100111011011001110010202 +ean13:629415132010:20200100110010111001110101100110110001001100102020100001011011001110010110011011100101001000202 +ean13:649798154930:20201000110010111001000100101110110111001100102020100111010111001110100100001011100101110100202 +ean13:106475417609:20200011010101111001110101110110111001001110102020110011010001001010000111001011101001110010202 +ean13:853904645292:20201100010100001000101101001110011101010111102020101110010011101101100111010011011001100110202 +ean13:431977667645:20201111010110011000101101110110010001000010102020101000010001001010000101110010011101000010202 +ean13:814819509497:20200110010011101011011101100110010111011000102020111001011101001011100111010010001001000100202 +ean13:308817835364:20200011010110111000100101100110010001011011102020100001010011101000010101000010111001011100202 +ean13:618801393027:20200110010001001000100101001110011001011110102020111010010000101110010110110010001001110010202 +ean13:164480667538:20201011110100011001110101101110100111000010102020101000010001001001110100001010010001011100202 +ean13:081023710137:20201101110011001000110100100110111101011101102020110011011100101100110100001010001001000100202 +ean13:493359422890:20200010110100001011110101100010010111001110102020110110011011001001000111010011100101110010202 +ean13:275532814865:20201110110110001011100101000010010011000100102020110011010111001001000101000010011101001000202 +ean13:111345042302:20200110010011001010000101000110111001010011102020101110011011001000010111001011011001001000202 +ean13:513537464719:20200110010100001011100101111010111011001110102020101000010111001000100110011011101001001110202 +ean13:766449694282:20201011110000101010001100111010001011000010102020111010010111001101100100100011011001110100202 +ean13:961792988802:20201011110110011001000100010110011011000101102020100100010010001001000111001011011001001110202 +ean13:866540005468:20201011110000101011000100111010100111000110102020111001010011101011100101000010010001101100202 +ean13:888983089059:20201101110001001000101100010010100001000110102020100100011101001110010100111011101001100110202 +ean13:722513175727:20200100110011011011000101100110111101011001102020100010010011101000100110110010001001110100202 +ean13:843297735811:20201000110100001001001100101110010001011101102020100001010011101001000110011011001101101100202 +ean13:627233353075:20200100110010001001101101000010111101011110102020100111010000101110010100010010011101110010202 +ean13:203591705672:20200011010111101011100100101110011001001000102020111001010011101010000100010011011001001110202 +ean13:686779501514:20201101110000101001000100100010001011011000102020111001011001101001110110011010111001001110202 +ean13:379483955591:20201110110001011001110100010010100001000101102020100111010011101001110111010011001101101100202 +ean13:576363706332:20201110110000101010000101011110111101001000102020111001010100001000010100001011011001000010202 +ean13:295226681860:20200010110110001001101100110110101111000010102020100100011001101001000101000011100101110100202 +ean13:901443422086:20200011010110011001110101000110100001010001102020110110011011001110010100100010100001000100202 +ean13:093300442985:20200010110111101011110100011010001101010001102020101110011011001110100100100010011101000010202 +ean13:517520551545:20200110010010001011100100100110001101011100102020100111011001101001110101110010011101000010202 +ean13:370982564882:20201110110001101001011100010010011011011000102020101000010111001001000100100011011001110010202 +ean13:640884610730:20201000110100111000100100010010100011010111102020110011011100101000100100001011100101001110202 +ean13:601211133573:20200011010110011001101101100110011001001100102020100001010000101001110100010010000101110100202 +ean13:772390839786:20201110110011011011110100101110001101000100102020100001011101001000100100100010100001110100202 +ean13:037823325554:20201111010111011011011100100110111101011110102020110110010011101001110100111010111001000010202 +ean13:636746415900:20201111010000101001000100111010101111010001102020110011010011101110100111001011100101000100202 +ean13:378365729312:20201110110110111010000100001010111001011101102020110110011101001000010110011011011001110010202 +ean13:588008084415:20201101110001001010011100011010110111010011102020100100010111001011100110011010011101000010202 +ean13:493847586062:20200010110100001011011101000110010001011100102020100100010100001110010101000011011001110010202 +ean13:661725378018:20201011110110011001000100110110110001011110102020100010010010001110010110011010010001110010202 +ean13:785941917380:20201101110111001000101100111010011001001011102020110011010001001000010100100011100101011100202 +ean13:476858945743:20201110110000101011011101100010001001001011102020101110010011101000100101110010000101010000202 +ean13:621022451023:20200100110110011010011100110110010011010001102020100111011001101110010110110010000101001000202 +ean13:738611126276:20201111010001001010111101100110011001011001102020110110010100001101100100010010100001110010202 +ean13:610023637901:20200110010100111010011100110110111101010111102020100001010001001110100111001011001101001000202 +ean13:310432899227:20200110010001101001110101000010011011011011102020111010011101001101100110110010001001110010202 +ean13:079481718507:20201110110001011010001101101110011001011101102020110011010010001001110111001010001001000010202 +ean13:794525704564:20200010110011101011000100110110110001001000102020111001010111001001110101000010111001010000202 +ean13:367079205365:20201011110111011010011100100010010111001001102020111001010011101000010101000010011101100110202 +ean13:791478205064:20200010110110011010001100100010110111001101102020111001010011101110010101000010111001000100202 +ean13:622805103976:20200100110011011000100101001110110001001100102020111001010000101110100100010010100001100110202 +ean13:659692878482:20201100010010111000010100101110010011011011102020100010010010001011100100100011011001011100202 +ean13:312673979722:20200110010010011000010100100010100001000101102020100010011101001000100110110011011001110010202 +ean13:634789369262:20201111010011101001000100010010001011011110102020101000011101001101100101000011011001000100202 +ean13:508031061191:20200011010001001010011101111010011001010011102020101000011001101100110111010011001101000100202 +ean13:617546677352:20200110010010001011100100111010101111010111102020100010010001001000010100111011011001000010202 +ean13:386750842077:20201101110101111001000101110010100111011011102020101110011011001110010100010010001001100110202 +ean13:217930060540:20200110010111011001011101000010001101010011102020101000011100101001110101110011100101100110202 +ean13:529453929533:20200100110010111001110101100010111101001011102020110110011101001001110100001010000101000010202 +ean13:403667633768:20200011010100001010111101011110010001000010102020100001010000101000100101000010010001110100202 +ean13:788401327704:20201101110001001010001101001110011001010000102020110110010001001000100111001010111001000100202 +ean13:316882500762:20200110010101111000100100010010011011011000102020111001011100101000100101000011011001101100202 +ean13:413254666172:20200110010100001001001101100010011101000010102020101000010100001100110100010011011001100110202 +ean13:666719764771:20201011110000101001000101100110001011011101102020101000010111001000100100010011001101100110202 +ean13:502326102133:20200011010011011010000100100110101111011001102020111001011011001100110100001010000101010000202 +ean13:690549661049:20200010110100111011100100111010001011010111102020101000011001101110010101110011101001001110202 +ean13:055572466568:20201100010110001011000101110110010011010001102020101000010100001001110101000010010001110100202 +ean13:636596356068:20201111010000101011100100101110101111011110102020100111010100001110010101000010010001000010202 +ean2:02:010110100111010010011 +ean2:19:010110110011010010111 +ean2:34:010110100001010100011 +upce:844677:202000100100111010011101010111101110110111011020202 +upce:610879:202000010101100110001101011011101110110010111020202 +upce:982526:202001011100010010011011011000100100110101111020202 +upce:846420:202000100100111010101111010001100110110001101020202 +upce:322140:202010000100110110010011001100100111010001101020202 +upce:625042:202000010100100110110001010011101000110011011020202 +upce:996072:202001011100101110101111000110101110110011011020202 +upce:539996:202011100101000010001011000101100010110000101020202 +upce:340168:202010000100111010001101001100100001010110111020202 +upce:841913:202000100101000110100111000101101100110011001020202 +upce:124122:202011001100110110100011001100100110110010011020202 +upce:401968:202001110100011010110011000101101011110001001020202 +upce:225085:202001101100100110111001010011101101110110001020202 +upce:368038:202010000101011110001001010011101111010110111020202 +upce:817155:202000100100110010111011011001101100010111001020202 +upce:554120:202011100101110010100011011001100100110001101020202 +upce:113080:202011001101100110111101000110101101110100111020202 +upce:755380:202001000101100010110001011110100010010100111020202 +upce:201858:202001101100011010110011000100101100010110111020202 +upce:226567:202001101100100110000101011100101011110111011020202 +upce:507089:202011100101001110111011000110101101110010111020202 +upce:442680:202001110101000110011011010111101101110100111020202 +upce:557738:202011100101100010111011001000101000010110111020202 +upce:928720:202001011100100110001001011101100100110100111020202 +upce:395240:202010000100101110110001001101101000110001101020202 +upce:931548:202001011101111010110011011000101000110001001020202 +upce:729228:202001000100100110001011001001100110110001001020202 +upce:116746:202011001100110010000101011101100111010101111020202 +upce:293469:202001101100101110111101010001101011110010111020202 +upce:555569:202011100101100010111001011000100001010001011020202 +upce:786474:202001000100010010000101010001101110110100011020202 +upce:761950:202001000101011110110011000101101100010100111020202 +upce:585584:202011100101101110111001011000100010010100011020202 +upce:929816:202001011100110110001011011011100110010000101020202 +upce:311259:202010000100110010011001001101101110010001011020202 +upce:694531:202000010100101110011101011000101111010011001020202 +upce:375808:202010000100100010110001011011101101110011101020202 +upce:240462:202001101100111010001101010001100001010010011020202 +upce:309270:202010000100011010010111001001100100010001101020202 +upce:531272:202011100101111010110011001001101110110011011020202 +upce:910577:202001011100110010001101011100100100010111011020202 +upce:816561:202000100100110010000101011000100001010011001020202 +upce:805838:202000100100011010110001011011101000010001001020202 +upce:533215:202011100101000010111101001101100110010110001020202 +upce:346122:202010000101000110000101001100100100110011011020202 +upce:625930:202000010100100110110001001011101000010001101020202 +upce:952717:202001011101110010010011001000100110010111011020202 +upce:402896:202001110101001110010011011011100010110000101020202 +upce:295983:202001101100101110110001000101101101110100001020202 +upce:024592:202010011100100110011101011000100010110011011020202 +upce:289724:202001101101101110010111011101100100110011101020202 +upce:252704:202001101101110010011011011101100011010100011020202 +upce:273290:202001101101110110111101001101100010110100111020202 +upce:385633:202010000100010010110001010111101111010100001020202 +upce:549951:202011100100111010001011001011101100010011001020202 +upce:568347:202011100101011110110111010000100111010111011020202 +upce:214740:202001101100110010011101001000101000110001101020202 +upce:969934:202001011101011110010111000101101111010011101020202 +upce:832367:202000100101111010010011010000100001010111011020202 +upce:359222:202010000101100010001011001101100110110010011020202 +upce:508230:202011100101001110001001001001101111010001101020202 +upce:308002:202010000101001110110111010011100011010010011020202 +upce:318751:202010000100110010001001011101101110010011001020202 +upce:174459:202011001100100010011101010001101100010001011020202 +upce:403448:202001110101001110111101010001101000110001001020202 +upce:429459:202001110100100110010111010001101110010001011020202 +upce:271338:202001101100100010110011011110101111010110111020202 +upce:515949:202011100100110010110001001011101000110010111020202 +upce:066186:202010011101011110000101001100100010010101111020202 +upce:609466:202000010100011010001011001110101011110000101020202 +upce:857406:202000100101110010010001010001101011110100011020202 +upce:237382:202001101101111010111011010000101101110011011020202 +upce:260304:202001101100001010001101010000100011010100011020202 +upce:899152:202000100100101110010111001100101100010010011020202 +upce:773424:202001000100100010111101001110100100110100011020202 +upce:367789:202010000101011110111011011101100010010010111020202 +upce:344078:202010000100111010011101000110101110110110111020202 +upce:627031:202000010100100110010001000110101000010011001020202 +upce:146732:202011001100111010101111001000101111010010011020202 +upce:608666:202000010100011010001001000010101011110101111020202 +upce:855228:202000100101110010111001001001100100110110111020202 +upce:439192:202001110101000010010111001100100010110010011020202 +upce:917451:202001011100110010010001010001101110010011001020202 +upce:072613:202010011101110110100111010111100110010011011020202 +upce:813590:202000100100110010100001011000100010110100111020202 +upce:176138:202011001100100010101111001100101000010110111020202 +upce:690945:202000010100101110001101001011101000110110001020202 +upce:849613:202000100100111010001011010111101100110111101020202 +upce:269053:202001101101011110001011000110101110010100001020202 +upce:701050:202001000100011010011001010011101110010001101020202 +upce:074527:202010011101110110011101011000100110110111011020202 +upce:218296:202001101100110010001001001101100010110101111020202 +upce:874938:202000100100100010100011000101101111010001001020202 +upce:023936:202010011100110110111101000101101111010000101020202 +upce:896764:202000100100101110000101011101101011110100011020202 +upce:979612:202001011100100010001011010111101100110010011020202 +upce:137449:202011001101000010010001010001101000110001011020202 +upce:687160:202000010100010010010001001100101011110001101020202 +upce:839620:202000100101111010010111000010100100110001101020202 +upce:921800:202001011100100110110011000100100011010001101020202 +ean5:72823:010110010001010011011010110111010010011010111101 +ean5:19075:010110110011010001011010001101010010001010110001 +ean5:16659:010110011001010000101010101111010111001010001011 +ean5:54239:010110111001010100011010011011010111101010001011 +ean5:70848:010110111011010001101010001001010011101010110111 +ean5:75795:010110010001010110001010111011010001011010111001 +ean5:15726:010110011001010110001010010001010011011010101111 +ean5:58560:010110110001010110111010110001010000101010100111 +ean5:24670:010110011011010100011010101111010111011010100111 +ean5:24946:010110011011010100011010001011010100011010000101 +ean5:69776:010110000101010001011010010001010111011010101111 +ean5:74692:010110010001010100011010101111010010111010010011 +ean5:10925:010110110011010001101010001011010010011010111001 +ean5:63372:010110000101010111101010111101010111011010011011 +ean5:62942:010110101111010010011010010111010011101010010011 +ean5:35265:010110111101010110001010011011010101111010111001 +ean5:38088:010110111101010001001010001101010001001010110111 +ean5:50203:010110111001010100111010010011010001101010111101 +ean5:57111:010110111001010111011010011001010011001010110011 +ean5:64593:010110101111010100011010111001010001011010100001 +ean5:52424:010110110001010010011010011101010011011010100011 +ean5:77259:010110010001010111011010010011010111001010001011 +ean5:07028:010110001101010111011010100111010011011010110111 +ean5:50627:010110111001010001101010101111010011011010111011 +ean5:60414:010110000101010001101010011101010011001010100011 +ean5:86940:010110001001010101111010010111010100011010001101 +ean5:36095:010110111101010101111010100111010001011010111001 +ean5:01124:010110100111010011001010011001010011011010100011 +ean5:57346:010110111001010111011010100001010100011010101111 +ean5:79607:010110010001010001011010000101010001101010111011 +ean5:31184:010110111101010011001010110011010001001010100011 +ean5:29583:010110011011010001011010110001010110111010100001 +ean5:75568:010110111011010110001010111001010101111010001001 +ean5:53258:010110110001010100001010010011010111001010110111 +ean5:78345:010110010001010110111010111101010100011010111001 +ean5:56838:010110110001010000101010001001010111101010110111 +ean5:61031:010110101111010110011010001101010100001010011001 +ean5:98054:010110001011010110111010001101010111001010011101 +ean5:28204:010110010011010110111010010011010100111010011101 +ean5:18738:010110011001010001001010111011010100001010110111 +ean5:17221:010110110011010111011010010011010010011010110011 +ean5:02270:010110001101010011011010010011010010001010001101 +ean5:02378:010110001101010011011010100001010111011010110111 +ean5:15957:010110110011010110001010010111010110001010111011 +ean5:13621:010110011001010111101010000101010010011010110011 +ean5:98891:010110001011010001001010110111010010111010011001 +ean5:50554:010110110001010100111010110001010111001010100011 +ean5:22677:010110010011010010011010101111010010001010010001 +ean5:49420:010110011101010001011010100011010010011010100111 +ean5:57712:010110110001010010001010010001010011001010010011 +ean5:13839:010110011001010100001010110111010111101010010111 +ean5:36422:010110111101010101111010011101010010011010011011 +ean5:10137:010110011001010100111010110011010111101010111011 +ean5:22734:010110010011010011011010010001010111101010100011 +ean5:25562:010110010011010110001010110001010000101010011011 +ean5:49015:010110100011010010111010001101010110011010110001 +ean5:34620:010110100001010100011010000101010010011010001101 +ean5:74909:010110010001010100011010010111010001101010001011 +ean5:57115:010110110001010111011010110011010110011010110001 +ean5:84963:010110001001010011101010001011010101111010111101 +ean5:95948:010110001011010110001010010111010100011010001001 +ean5:36006:010110100001010101111010100111010001101010101111 +ean5:05457:010110100111010110001010100011010110001010010001 +ean5:54806:010110111001010100011010110111010001101010000101 +ean5:32152:010110100001010010011010110011010110001010010011 +ean5:64913:010110101111010100011010010111010011001010100001 +ean5:53560:010110111001010111101010111001010101111010001101 +ean5:57373:010110110001010111011010100001010111011010100001 +ean5:54685:010110110001010100011010101111010001001010111001 +ean5:75408:010110010001010110001010100011010100111010110111 +ean5:56632:010110111001010000101010101111010111101010010011 +ean5:22639:010110010011010010011010101111010100001010010111 +ean5:92955:010110010111010010011010001011010111001010110001 +ean5:75465:010110111011010111001010100011010000101010110001 +ean5:56506:010110111001010101111010110001010100111010101111 +ean5:22627:010110011011010010011010000101010010011010111011 +ean5:98251:010110010111010110111010010011010110001010110011 +ean5:90220:010110010111010001101010011011010010011010001101 +ean5:93426:010110010111010111101010100011010011011010101111 +ean5:74169:010110010001010100011010110011010101111010001011 +ean5:36259:010110100001010101111010011011010110001010001011 +ean5:71855:010110111011010110011010001001010110001010110001 +ean5:04584:010110001101010100011010111001010001001010100011 +ean5:01152:010110100111010011001010011001010110001010011011 +ean5:72757:010110111011010010011010111011010111001010010001 +ean5:28673:010110010011010001001010101111010111011010100001 +ean5:75084:010110010001010111001010001101010110111010100011 +ean5:10562:010110011001010100111010110001010101111010011011 +ean5:63726:010110000101010111101010111011010011011010101111 +ean5:04292:010110001101010100011010011011010001011010011011 +ean5:77513:010110111011010010001010110001010110011010111101 +ean5:17826:010110011001010111011010110111010011011010000101 +ean5:44790:010110011101010011101010111011010001011010001101 +ean5:39572:010110111101010010111010111001010111011010010011 +ean5:55321:010110111001010111001010111101010010011010011001 +ean5:89717:010110110111010001011010111011010110011010010001 +ean5:02126:010110001101010011011010011001010011011010101111 +ean5:30106:010110100001010100111010011001010001101010101111 +ean5:57489:010110110001010111011010011101010110111010010111 +ean5:26505:010110011011010000101010110001010001101010110001 +upca:40710631975:20201000110001101011101100110010001101010111102020100001011001101110100100010010011101100110202 +upca:80176224795:20201101110001101001100101110110101111001001102020110110010111001000100111010010011101100110202 +upca:92748137722:20200010110010011011101101000110110111001100102020100001010001001000100110110011011001010000202 +upca:32450677259:20201111010010011010001101100010001101010111102020100010010001001101100100111011101001110010202 +upca:40851674702:20201000110001101011011101100010011001010111102020100010010111001000100111001011011001001000202 +upca:59115174167:20201100010001011001100100110010110001001100102020100010010111001100110101000010001001100110202 +upca:84003578078:20201101110100011000110100011010111101011000102020100010010010001110010100010010010001001000202 +upca:95062540829:20200010110110001000110101011110010011011000102020101110011100101001000110110011101001010000202 +upca:64901078016:20201011110100011000101100011010011001000110102020100010010010001110010110011010100001110010202 +upca:48432140125:20201000110110111010001101111010010011001100102020101110011100101100110110110010011101010000202 +upca:17430588576:20200110010111011010001101111010001101011000102020100100010010001001110100010010100001001000202 +upca:63153964133:20201011110111101001100101100010111101000101102020101000010111001100110100001010000101010000202 +upca:74093932950:20201110110100011000110100010110111101000101102020100001011011001110100100111011100101001110202 +upca:57541363089:20201100010111011011000101000110011001011110102020101000010000101110010100100011101001000100202 +upca:07377087198:20200011010111011011110101110110111011000110102020100100010001001100110111010010010001110100202 +upca:05635524594:20200011010110001010111101111010110001011000102020110110010111001001110111010010111001001000202 +upca:70318362910:20201110110001101011110100110010110111011110102020101000011011001110100110011011100101011100202 +upca:36925218506:20201111010101111000101100100110110001001001102020110011010010001001110111001010100001001110202 +upca:45343236277:20201000110110001011110101000110111101001001102020100001010100001101100100010010001001110010202 +upca:03926388241:20200011010111101000101100100110101111011110102020100100010010001101100101110011001101101100202 +upca:64192083538:20201011110100011001100100010110010011000110102020100100010000101001110100001010010001100110202 +upca:25525653142:20200100110110001011000100100110110001010111102020100111010000101100110101110011011001110010202 +upca:38104462945:20201111010110111001100100011010100011010001102020101000011011001110100101110010011101001000202 +upca:09225889378:20200011010001011001001100100110110001011011102020100100011101001000010100010010010001000100202 +upca:93748811785:20200010110111101011101101000110110111011011102020110011011001101000100100100010011101001110202 +upca:30467816622:20201111010001101010001101011110111011011011102020110011010100001010000110110011011001110100202 +upca:74931591622:20201110110100011000101101111010011001011000102020111010011001101010000110110011011001000010202 +upca:92475784788:20200010110010011010001101110110110001011101102020100100010111001000100100100010010001110100202 +upca:40454544331:20201000110001101010001101100010100011011000102020101110010111001000010100001011001101000010202 +upca:35237883084:20201111010110001001001101111010111011011011102020100100010000101110010100100010111001100110202 +upca:75646706855:20201110110110001010111101000110101111011101102020111001010100001001000100111010011101000100202 +upca:82889483314:20201101110010011011011101101110001011010001102020100100010000101000010110011010111001101100202 +upca:73893633416:20201110110111101011011100010110111101010111102020100001010000101011100110011010100001001110202 +upca:10454576813:20200110010001101010001101100010100011011000102020100010010100001001000110011010000101101100202 +upca:33334522835:20201111010111101011110101111010100011011000102020110110011011001001000100001010011101110100202 +upca:45793591164:20201000110110001011101100010110111101011000102020111010011001101100110101000010111001110010202 +upca:18057726450:20200110010110111000110101100010111011011101102020110110010100001011100100111011100101000100202 +upca:96083152912:20200010110101111000110101101110111101001100102020100111011011001110100110011011011001001000202 +upca:16563181689:20200110010101111011000101011110111101001100102020100100011001101010000100100011101001101100202 +upca:51276473461:20201100010011001001001101110110101111010001102020100010010000101011100101000011001101011100202 +upca:08128913293:20200011010110111001100100100110110111000101102020110011010000101101100111010010000101011100202 +upca:34146752391:20201111010100011001100101000110101111011101102020100111011011001000010111010011001101000100202 +upca:97515692692:20200010110111011011000100110010110001010111102020111010011011001010000111010011011001000100202 +upca:97669919853:20200010110111011010111101011110001011000101102020110011011101001001000100111010000101010000202 +upca:18678734797:20200110010110111010111101110110110111011101102020100001010111001000100111010010001001110100202 +upca:74076303825:20201110110100011000110101110110101111011110102020111001010000101001000110110010011101000010202 +upca:08662387020:20200011010110111010111101011110010011011110102020100100010001001110010110110011100101010000202 +upca:26133671442:20200100110101111001100101111010111101010111102020100010011001101011100101110011011001000010202 +upca:87138605931:20201101110111011001100101111010110111010111102020111001010011101110100100001011001101001110202 +upca:67189289819:20201011110111011001100101101110001011001001102020100100011101001001000110011011101001110010202 +upca:09759720707:20200011010001011011101101100010001011011101102020110110011100101000100111001010001001000010202 +upca:56485529906:20201100010101111010001101101110110001011000102020110110011101001110100111001010100001110100202 +upca:58154525395:20201100010110111001100101100010100011011000102020110110010011101000010111010010011101001000202 +upca:67888609275:20201011110111011011011101101110110111010111102020111001011101001101100100010010011101010000202 +upca:15168153237:20200110010110001001100101011110110111001100102020100111010000101101100100001010001001110010202 +upca:74424730673:20201110110100011010001100100110100011011101102020100001011100101010000100010010000101110100202 +upca:45168971187:20201000110110001001100101011110110111000101102020100010011001101100110100100010001001000100202 +upca:12149723784:20200110010010011001100101000110001011011101102020110110010000101000100100100010111001011100202 +upca:33804286457:20201111010111101011011100011010100011001001102020100100010100001011100100111010001001101100202 +upca:27558248562:20200100110111011011000101100010110111001001102020101110010010001001110101000011011001011100202 +upca:92390267160:20200010110010011011110100010110001101001001102020101000010001001100110101000011100101000100202 +upca:63742039163:20201011110111101011101101000110010011000110102020100001011101001100110101000010000101101100202 +upca:67117098326:20201011110111011001100100110010111011000110102020111010010010001000010110110010100001010000202 +upca:32901624258:20201111010010011000101100011010011001010111102020110110010111001101100100111010010001001000202 +upca:88848685777:20201101110110111011011101000110110111010111102020100100010011101000100100010010001001101100202 +upca:57070085601:20201100010111011000110101110110001101000110102020100100010011101010000111001011001101100110202 +upca:83043128438:20201101110111101000110101000110111101001100102020110110010010001011100100001010010001010000202 +upca:52432147628:20201100010010011010001101111010010011001100102020101110010001001010000110110010010001001000202 +upca:15067795584:20200110010110001000110101011110111011011101102020111010010011101001110100100010111001100110202 +upca:27777156430:20200100110111011011101101110110111011001100102020100111010100001011100100001011100101100110202 +upca:82471218344:20201101110010011010001101110110011001001001102020110011010010001000010101110010111001011100202 +upca:94175044091:20200010110100011001100101110110110001000110102020101110010111001110010111010011001101010000202 +upca:34936749963:20201111010100011000101101111010101111011101102020101110011101001110100101000010000101110100202 +upca:46502492658:20201000110101111011000100011010010011010001102020111010011011001010000100111010010001100110202 +upca:47153985860:20201000110111011001100101100010111101000101102020100100010011101001000101000011100101010000202 +upca:79601763982:20201110110001011010111100011010011001011101102020101000010000101110100100100011011001110010202 +upca:91685238225:20200010110011001010111101101110110001001001102020100001010010001101100110110010011101110100202 +upca:46916417994:20201000110101111000101100110010101111010001102020110011010001001110100111010010111001011100202 +upca:30878845484:20201111010001101011011101110110110111011011102020101110010011101011100100100010111001110100202 +upca:39321907655:20201111010001011011110100100110011001000101102020111001010001001010000100111010011101011100202 +upca:48232524842:20201000110110111001001101111010010011011000102020110110010111001001000101110011011001010000202 +upca:94750199914:20200010110100011011101101100010001101001100102020111010011101001110100110011010111001010000202 +upca:28310653784:20200100110110111011110100110010001101010111102020100111010000101000100100100010111001100110202 +upca:00814405096:20200011010001101011011100110010100011010001102020111001010011101110010111010010100001000100202 +upca:60974743279:20201011110001101000101101110110100011011101102020101110010000101101100100010011101001011100202 +upca:45141255670:20201000110110001001100101000110011001001001102020100111010011101010000100010011100101010000202 +upca:62241294735:20201011110010011001001101000110011001001001102020111010010111001000100100001010011101001110202 +upca:67241197048:20201011110111011001001101000110011001001100102020111010010001001110010101110010010001110100202 +upca:57709892169:20201100010111011011101100011010001011011011102020111010011011001100110101000011101001000100202 +upca:49234133045:20201000110001011001001101111010100011001100102020100001010000101110010101110010011101010000202 +upca:44106865868:20201000110100011001100100011010101111011011102020101000010011101001000101000010010001001000202 +upca:07033900539:20200011010111011000110101111010111101000101102020111001011100101001110100001011101001000100202 +upca:63922260360:20201011110111101000101100100110010011001001102020101000011100101000010101000011100101110100202 +upca:63544942112:20201011110111101011000101000110100011000101102020101110011011001100110110011011011001001110202 +upca:52602007800:20201100010010011010111100011010010011000110102020111001010001001001000111001011100101001000202 +upca:94048257810:20200010110100011000110101000110110111001001102020100111010001001001000110011011100101101100202 +upca:82699181094:20201101110010011010111100010110001011001100102020100100011001101110010111010010111001000010202 +upca:94628599972:20200010110100011010111100100110110111011000102020111010011101001110100100010011011001011100202 +upca:09938844603:20200011010001011000101101111010110111011011102020101110010111001010000111001010000101010000202 +upca:37128560103:20201111010111011001100100100110110111011000102020101000011100101100110111001010000101110010202 +code128:Something:11010010000110111010001000111101011110111010101100100001001111010010011000010100001101001100001010010011010000100011101101100011101011 +code128:OrOther:1101001000010001110110100100111101000111011010011110100100110000101011001000010010011110101001111001100011101011 +code128:Inkscape:110100100001100010001011000010100110000100101011110010010000101100100101100001010011110010110010000100111001101100011101011 +code128:Simple:11010010000110111010001000011010011110111010101001111001100101000010110010000111010111101100011101011 +code25i:123456:10101101001010110011011010010100110100110010101101 +code39:123456:10010110110101101001010110101100101011011011001010101010011010110110100110101010110011010101001011011010 +code39ext:123456:10010110110101001001010010101010110011010010010100101101010110010100100101001010110101100101001001010010101011011001010010010100101100101010110100100101001010011010101101001011011010 +rm4scc:123456:25051525350515352515052535150535251515252505251535050535353 +rm4scc:a1b2c3e4:251525152505152535153505250515352505253515150525350535350515053525053505353 diff --git a/share/extensions/tests/data/cmd/fig2dev/c15e8c5e5e50d45b2579ac22522b007e.msg b/share/extensions/tests/data/cmd/fig2dev/c15e8c5e5e50d45b2579ac22522b007e.msg new file mode 100644 index 0000000..bebbc39 --- /dev/null +++ b/share/extensions/tests/data/cmd/fig2dev/c15e8c5e5e50d45b2579ac22522b007e.msg @@ -0,0 +1,29 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: fig2dev +Arguments: -L io/test.fig output.svg svg + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="output.svg" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: output.svg + +PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IS0tIENyZWF0b3I6IGZpZzJk +ZXYgVmVyc2lvbiAzLjIuNmEgLS0+CjwhLS0gQ3JlYXRpb25EYXRlOiAyMDE5LTA0LTI1IDEwOjUx +OjIyIC0tPgo8IS0tIE1hZ25pZmljYXRpb246IDEuMDUgLS0+CjxzdmcJeG1sbnM9Imh0dHA6Ly93 +d3cudzMub3JnLzIwMDAvc3ZnIgoJeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkv +eGxpbmsiCgl3aWR0aD0iMzA1cHQiIGhlaWdodD0iMjE3cHQiCgl2aWV3Qm94PSIxODc4IDE3NDMg +NDgzOSAzNDQ0Ij4KPGcgZmlsbD0ibm9uZSI+CjwhLS0gQ2lyY2xlIC0tPgo8Y2lyY2xlIGN4PSIy +NzAwIiBjeT0iMzI4NSIgcj0iNTg3IgoJc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9Ijhw +eCIvPgo8IS0tIExpbmUgLS0+CjxyZWN0IHg9IjE4OTAiIHk9IjI1NjUiIHdpZHRoPSIzMDYwIiBo +ZWlnaHQ9IjI2MTAiCglzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iOHB4Ii8+CjwhLS0g +TGluZSAtLT4KPHJlY3QgeD0iNTIyMCIgeT0iMjYxMCIgd2lkdGg9IjE0ODUiIGhlaWdodD0iMTcx +MCIgcng9IjEwNSIKCXN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSI4cHgiLz4KPCEtLSBU +ZXh0IC0tPgo8dGV4dCB4bWw6c3BhY2U9InByZXNlcnZlIiB4PSIyNTIwIiB5PSIxODkwIiBmaWxs +PSIjMDAwMDAwIiBmb250LWZhbWlseT0iVGltZXMiIGZvbnQtc3R5bGU9Im5vcm1hbCIgZm9udC13 +ZWlnaHQ9Im5vcm1hbCIgZm9udC1zaXplPSIxNDQiIHRleHQtYW5jaG9yPSJzdGFydCI+SGVsbG8g +SW5rc2NhcGU8L3RleHQ+CjwvZz4KPC9zdmc+Cg== + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/gimp/6c0e5d2fbe380e26e310ebfc206d81ea.msg b/share/extensions/tests/data/cmd/gimp/6c0e5d2fbe380e26e310ebfc206d81ea.msg new file mode 100644 index 0000000..93fb936 --- /dev/null +++ b/share/extensions/tests/data/cmd/gimp/6c0e5d2fbe380e26e310ebfc206d81ea.msg @@ -0,0 +1,2204 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: gimp +Arguments: - --batch-interpreter=plug-in-script-fu-eval -b -i + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 + +V2VsY29tZSB0byBUaW55U2NoZW1lLCBWZXJzaW9uIDEuNDAKQ29weXJpZ2h0IChjKSBEaW1pdHJp +b3MgU291ZmxpcwoKdHM+IApHaXZlczogMAp0cz4gCkV2YWw6IChkZWZpbmUgKHBuZy10by1sYXll +ciBpbWcgcG5nX2ZpbGVuYW1lIGxheWVyX25hbWUpIChsZXQqICgocG5nIChjYXIgKGZpbGUtcG5n +LWxvYWQgUlVOLU5PTklOVEVSQUNUSVZFIHBuZ19maWxlbmFtZSBwbmdfZmlsZW5hbWUpKSkgKHBu +Z19sYXllciAoY2FyIChnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIgcG5nKSkpICh4Y2ZfbGF5 +ZXIgKGNhciAoZ2ltcC1sYXllci1uZXctZnJvbS1kcmF3YWJsZSBwbmdfbGF5ZXIgaW1nKSkpKSAo +Z2ltcC1pbWFnZS1hZGQtbGF5ZXIgaW1nIHhjZl9sYXllciAtMSkgKGdpbXAtZHJhd2FibGUtc2V0 +LW5hbWUgeGNmX2xheWVyIGxheWVyX25hbWUpKSkKRXZhbDogKGxhbWJkYSAoaW1nIHBuZ19maWxl +bmFtZSBsYXllcl9uYW1lKSAobGV0KiAoKHBuZyAoY2FyIChmaWxlLXBuZy1sb2FkIFJVTi1OT05J +TlRFUkFDVElWRSBwbmdfZmlsZW5hbWUgcG5nX2ZpbGVuYW1lKSkpIChwbmdfbGF5ZXIgKGNhciAo +Z2ltcC1pbWFnZS1nZXQtYWN0aXZlLWxheWVyIHBuZykpKSAoeGNmX2xheWVyIChjYXIgKGdpbXAt +bGF5ZXItbmV3LWZyb20tZHJhd2FibGUgcG5nX2xheWVyIGltZykpKSkgKGdpbXAtaW1hZ2UtYWRk +LWxheWVyIGltZyB4Y2ZfbGF5ZXIgLTEpIChnaW1wLWRyYXdhYmxlLXNldC1uYW1lIHhjZl9sYXll +ciBsYXllcl9uYW1lKSkpCkFwcGx5IHRvOiAoKChpbWcgcG5nX2ZpbGVuYW1lIGxheWVyX25hbWUp +IChsZXQqICgocG5nIChjYXIgKGZpbGUtcG5nLWxvYWQgUlVOLU5PTklOVEVSQUNUSVZFIHBuZ19m +aWxlbmFtZSBwbmdfZmlsZW5hbWUpKSkgKHBuZ19sYXllciAoY2FyIChnaW1wLWltYWdlLWdldC1h +Y3RpdmUtbGF5ZXIgcG5nKSkpICh4Y2ZfbGF5ZXIgKGNhciAoZ2ltcC1sYXllci1uZXctZnJvbS1k +cmF3YWJsZSBwbmdfbGF5ZXIgaW1nKSkpKSAoZ2ltcC1pbWFnZS1hZGQtbGF5ZXIgaW1nIHhjZl9s +YXllciAtMSkgKGdpbXAtZHJhd2FibGUtc2V0LW5hbWUgeGNmX2xheWVyIGxheWVyX25hbWUpKSkp +CkV2YWw6IChpZiAobWFjcm8/IGZvcm0pIChtYWNyby1leHBhbmQtYWxsIChtYWNyby1leHBhbmQg +Zm9ybSkpIGZvcm0pCkV2YWw6IChtYWNybz8gZm9ybSkKRXZhbDogbWFjcm8/CkV2YWw6IGZvcm0K +QXBwbHkgdG86ICgoKGltZyBwbmdfZmlsZW5hbWUgbGF5ZXJfbmFtZSkgKGxldCogKChwbmcgKGNh +ciAoZmlsZS1wbmctbG9hZCBSVU4tTk9OSU5URVJBQ1RJVkUgcG5nX2ZpbGVuYW1lIHBuZ19maWxl +bmFtZSkpKSAocG5nX2xheWVyIChjYXIgKGdpbXAtaW1hZ2UtZ2V0LWFjdGl2ZS1sYXllciBwbmcp +KSkgKHhjZl9sYXllciAoY2FyIChnaW1wLWxheWVyLW5ldy1mcm9tLWRyYXdhYmxlIHBuZ19sYXll +ciBpbWcpKSkpIChnaW1wLWltYWdlLWFkZC1sYXllciBpbWcgeGNmX2xheWVyIC0xKSAoZ2ltcC1k +cmF3YWJsZS1zZXQtbmFtZSB4Y2ZfbGF5ZXIgbGF5ZXJfbmFtZSkpKSkKRXZhbDogZm9ybQpHaXZl +czogcG5nLXRvLWxheWVyCnRzPiAKRXZhbDogKGxldCogKChpbWcgKGNhciAoZ2ltcC1pbWFnZS1u +ZXcgMjAwIDIwMCBSR0IpKSkpIChnaW1wLWltYWdlLXNldC1yZXNvbHV0aW9uIGltZyA5Ni4wIDk2 +LjApIChnaW1wLWltYWdlLXVuZG8tZGlzYWJsZSBpbWcpIChmb3ItZWFjaCAobGFtYmRhIChuYW1l +cykgKHBuZy10by1sYXllciBpbWcgKGNhciBuYW1lcykgKGNkciBuYW1lcykpKSAobWFwIGNvbnMg +JygiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTMucG5nIiAiL3RtcC9naW1wLW91dC1xaW50 +Zmc4eS9TbGlkZTIucG5nIiAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIikgJygi +U2xpZGUzIiAiU2xpZGUyIiAiU2xpZGUxIikpKSAoZ2ltcC1pbWFnZS1yZXNpemUtdG8tbGF5ZXJz +IGltZykgKGdpbXAtaW1hZ2UtdW5kby1lbmFibGUgaW1nKSAoZ2ltcC1maWxlLXNhdmUgUlVOLU5P +TklOVEVSQUNUSVZFIGltZyAoY2FyIChnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIgaW1nKSkg +Ii90bXAvZ2ltcC1vdXQtcWludGZnOHkvdGVzdC5zdmcueGNmIiAiL3RtcC9naW1wLW91dC1xaW50 +Zmc4eS90ZXN0LnN2Zy54Y2YiKSkKRXZhbDogKGNhciAoZ2ltcC1pbWFnZS1uZXcgMjAwIDIwMCBS +R0IpKQpFdmFsOiBjYXIKRXZhbDogKGdpbXAtaW1hZ2UtbmV3IDIwMCAyMDAgUkdCKQpFdmFsOiBn +aW1wLWltYWdlLW5ldwpFdmFsOiAyMDAKRXZhbDogMjAwCkV2YWw6IFJHQgpBcHBseSB0bzogKDIw +MCAyMDAgMCkKRXZhbDogKGFwcGx5IGdpbXAtcHJvYy1kYi1jYWxsIChjb25zICJnaW1wLWltYWdl +LW5ldyIgeCkpCkV2YWw6IGFwcGx5CkV2YWw6IGdpbXAtcHJvYy1kYi1jYWxsCkV2YWw6IChjb25z +ICJnaW1wLWltYWdlLW5ldyIgeCkKRXZhbDogY29ucwpFdmFsOiAiZ2ltcC1pbWFnZS1uZXciCkV2 +YWw6IHgKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1uZXciICgyMDAgMjAwIDApKQpBcHBseSB0bzog +KCM8Rk9SRUlHTiBQUk9DRURVUkUgOTQ4OTYzODc0NjA4NDg+ICgiZ2ltcC1pbWFnZS1uZXciIDIw +MCAyMDAgMCkpCkFwcGx5IHRvOiAoImdpbXAtaW1hZ2UtbmV3IiAyMDAgMjAwIDApCkFwcGx5IHRv +OiAoKDEpKQpFdmFsOiAoZ2ltcC1pbWFnZS1zZXQtcmVzb2x1dGlvbiBpbWcgOTYuMCA5Ni4wKQpF +dmFsOiBnaW1wLWltYWdlLXNldC1yZXNvbHV0aW9uCkV2YWw6IGltZwpFdmFsOiA5Ni4wCkV2YWw6 +IDk2LjAKQXBwbHkgdG86ICgxIDk2LjAgOTYuMCkKRXZhbDogKGFwcGx5IGdpbXAtcHJvYy1kYi1j +YWxsIChjb25zICJnaW1wLWltYWdlLXNldC1yZXNvbHV0aW9uIiB4KSkKRXZhbDogYXBwbHkKRXZh +bDogZ2ltcC1wcm9jLWRiLWNhbGwKRXZhbDogKGNvbnMgImdpbXAtaW1hZ2Utc2V0LXJlc29sdXRp +b24iIHgpCkV2YWw6IGNvbnMKRXZhbDogImdpbXAtaW1hZ2Utc2V0LXJlc29sdXRpb24iCkV2YWw6 +IHgKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1zZXQtcmVzb2x1dGlvbiIgKDEgOTYuMCA5Ni4wKSkK +QXBwbHkgdG86ICgjPEZPUkVJR04gUFJPQ0VEVVJFIDk0ODk2Mzg3NDYwODQ4PiAoImdpbXAtaW1h +Z2Utc2V0LXJlc29sdXRpb24iIDEgOTYuMCA5Ni4wKSkKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1z +ZXQtcmVzb2x1dGlvbiIgMSA5Ni4wIDk2LjApCkV2YWw6IChnaW1wLWltYWdlLXVuZG8tZGlzYWJs +ZSBpbWcpCkV2YWw6IGdpbXAtaW1hZ2UtdW5kby1kaXNhYmxlCkV2YWw6IGltZwpBcHBseSB0bzog +KDEpCkV2YWw6IChhcHBseSBnaW1wLXByb2MtZGItY2FsbCAoY29ucyAiZ2ltcC1pbWFnZS11bmRv +LWRpc2FibGUiIHgpKQpFdmFsOiBhcHBseQpFdmFsOiBnaW1wLXByb2MtZGItY2FsbApFdmFsOiAo +Y29ucyAiZ2ltcC1pbWFnZS11bmRvLWRpc2FibGUiIHgpCkV2YWw6IGNvbnMKRXZhbDogImdpbXAt +aW1hZ2UtdW5kby1kaXNhYmxlIgpFdmFsOiB4CkFwcGx5IHRvOiAoImdpbXAtaW1hZ2UtdW5kby1k +aXNhYmxlIiAoMSkpCkFwcGx5IHRvOiAoIzxGT1JFSUdOIFBST0NFRFVSRSA5NDg5NjM4NzQ2MDg0 +OD4gKCJnaW1wLWltYWdlLXVuZG8tZGlzYWJsZSIgMSkpCkFwcGx5IHRvOiAoImdpbXAtaW1hZ2Ut +dW5kby1kaXNhYmxlIiAxKQpFdmFsOiAoZm9yLWVhY2ggKGxhbWJkYSAobmFtZXMpIChwbmctdG8t +bGF5ZXIgaW1nIChjYXIgbmFtZXMpIChjZHIgbmFtZXMpKSkgKG1hcCBjb25zICcoIi90bXAvZ2lt +cC1vdXQtcWludGZnOHkvU2xpZGUzLnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUy +LnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIpICcoIlNsaWRlMyIgIlNs +aWRlMiIgIlNsaWRlMSIpKSkKRXZhbDogZm9yLWVhY2gKRXZhbDogKGxhbWJkYSAobmFtZXMpIChw +bmctdG8tbGF5ZXIgaW1nIChjYXIgbmFtZXMpIChjZHIgbmFtZXMpKSkKQXBwbHkgdG86ICgoKG5h +bWVzKSAocG5nLXRvLWxheWVyIGltZyAoY2FyIG5hbWVzKSAoY2RyIG5hbWVzKSkpKQpFdmFsOiAo +aWYgKG1hY3JvPyBmb3JtKSAobWFjcm8tZXhwYW5kLWFsbCAobWFjcm8tZXhwYW5kIGZvcm0pKSBm +b3JtKQpFdmFsOiAobWFjcm8/IGZvcm0pCkV2YWw6IG1hY3JvPwpFdmFsOiBmb3JtCkFwcGx5IHRv +OiAoKChuYW1lcykgKHBuZy10by1sYXllciBpbWcgKGNhciBuYW1lcykgKGNkciBuYW1lcykpKSkK +RXZhbDogZm9ybQpFdmFsOiAobWFwIGNvbnMgJygiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlk +ZTMucG5nIiAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAiL3RtcC9naW1wLW91 +dC1xaW50Zmc4eS9TbGlkZTEucG5nIikgJygiU2xpZGUzIiAiU2xpZGUyIiAiU2xpZGUxIikpCkV2 +YWw6IG1hcApFdmFsOiBjb25zCkV2YWw6ICcoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUz +LnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgIi90bXAvZ2ltcC1vdXQt +cWludGZnOHkvU2xpZGUxLnBuZyIpCkV2YWw6ICcoIlNsaWRlMyIgIlNsaWRlMiIgIlNsaWRlMSIp +CkFwcGx5IHRvOiAoIzxjb25zIFBST0NFRFVSRSA3Nj4gKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5 +L1NsaWRlMy5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICIvdG1wL2dp +bXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMyIgIlNsaWRlMiIgIlNsaWRlMSIp +KQpFdmFsOiAoaWYgKG51bGw/IGxpc3RzKSAoYXBwbHkgcHJvYykgKGlmIChudWxsPyAoY2FyIGxp +c3RzKSkgJygpIChsZXQqICgodW56IChhcHBseSB1bnppcDEtd2l0aC1jZHIgbGlzdHMpKSAoY2Fy +cyAoY2FyIHVueikpIChjZHJzIChjZHIgdW56KSkpIChjb25zIChhcHBseSBwcm9jIGNhcnMpIChh +cHBseSBtYXAgKGNvbnMgcHJvYyBjZHJzKSkpKSkpCkV2YWw6IChudWxsPyBsaXN0cykKRXZhbDog +bnVsbD8KRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1Ns +aWRlMy5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICIvdG1wL2dpbXAt +b3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMyIgIlNsaWRlMiIgIlNsaWRlMSIpKSkK +RXZhbDogKGlmIChudWxsPyAoY2FyIGxpc3RzKSkgJygpIChsZXQqICgodW56IChhcHBseSB1bnpp +cDEtd2l0aC1jZHIgbGlzdHMpKSAoY2FycyAoY2FyIHVueikpIChjZHJzIChjZHIgdW56KSkpIChj +b25zIChhcHBseSBwcm9jIGNhcnMpIChhcHBseSBtYXAgKGNvbnMgcHJvYyBjZHJzKSkpKSkKRXZh +bDogKG51bGw/IChjYXIgbGlzdHMpKQpFdmFsOiBudWxsPwpFdmFsOiAoY2FyIGxpc3RzKQpFdmFs +OiBjYXIKRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1Ns +aWRlMy5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICIvdG1wL2dpbXAt +b3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMyIgIlNsaWRlMiIgIlNsaWRlMSIpKSkK +QXBwbHkgdG86ICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBuZyIgIi90bXAvZ2lt +cC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUx +LnBuZyIpKQpFdmFsOiAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAxLXdpdGgtY2RyIGxpc3RzKSkg +KGNhcnMgKGNhciB1bnopKSAoY2RycyAoY2RyIHVueikpKSAoY29ucyAoYXBwbHkgcHJvYyBjYXJz +KSAoYXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpKSkKRXZhbDogKGFwcGx5IHVuemlwMS13aXRo +LWNkciBsaXN0cykKRXZhbDogYXBwbHkKRXZhbDogdW56aXAxLXdpdGgtY2RyCkV2YWw6IGxpc3Rz +CkFwcGx5IHRvOiAoIzxDTE9TVVJFPiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMy5w +bmciICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICIvdG1wL2dpbXAtb3V0LXFp +bnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMyIgIlNsaWRlMiIgIlNsaWRlMSIpKSkKQXBwbHkg +dG86ICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBuZyIgIi90bXAvZ2ltcC1vdXQt +cWludGZnOHkvU2xpZGUyLnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIp +ICgiU2xpZGUzIiAiU2xpZGUyIiAiU2xpZGUxIikpCkV2YWw6ICh1bnppcDEtd2l0aC1jZHItaXRl +cmF0aXZlIGxpc3RzICcoKSAnKCkpCkV2YWw6IHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUKRXZh +bDogbGlzdHMKRXZhbDogJygpCkV2YWw6ICcoKQpBcHBseSB0bzogKCgoIi90bXAvZ2ltcC1vdXQt +cWludGZnOHkvU2xpZGUzLnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIg +Ii90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIpICgiU2xpZGUzIiAiU2xpZGUyIiAi +U2xpZGUxIikpICgpICgpKQpFdmFsOiAoaWYgKG51bGw/IGxpc3RzKSAoY29ucyBjYXJzIGNkcnMp +IChsZXQgKChjYXIxIChjYWFyIGxpc3RzKSkgKGNkcjEgKGNkYXIgbGlzdHMpKSkgKHVuemlwMS13 +aXRoLWNkci1pdGVyYXRpdmUgKGNkciBsaXN0cykgKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEpKSAo +YXBwZW5kIGNkcnMgKGxpc3QgY2RyMSkpKSkpCkV2YWw6IChudWxsPyBsaXN0cykKRXZhbDogbnVs +bD8KRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRl +My5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICIvdG1wL2dpbXAtb3V0 +LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMyIgIlNsaWRlMiIgIlNsaWRlMSIpKSkKRXZh +bDogKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAodW56aXAx +LXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkp +IChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKQpFdmFsOiAoY2FhciBsaXN0cykKRXZhbDogY2Fh +cgpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUz +LnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgIi90bXAvZ2ltcC1vdXQt +cWludGZnOHkvU2xpZGUxLnBuZyIpICgiU2xpZGUzIiAiU2xpZGUyIiAiU2xpZGUxIikpKQpFdmFs +OiAoY2FyIChjYXIgeCkpCkV2YWw6IGNhcgpFdmFsOiAoY2FyIHgpCkV2YWw6IGNhcgpFdmFsOiB4 +CkFwcGx5IHRvOiAoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTMucG5nIiAiL3RtcC9n +aW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlk +ZTEucG5nIikgKCJTbGlkZTMiICJTbGlkZTIiICJTbGlkZTEiKSkpCkFwcGx5IHRvOiAoKCIvdG1w +L2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMy5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1Ns +aWRlMi5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSkKRXZhbDogKGNk +YXIgbGlzdHMpCkV2YWw6IGNkYXIKRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAt +b3V0LXFpbnRmZzh5L1NsaWRlMy5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5w +bmciICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMyIgIlNsaWRl +MiIgIlNsaWRlMSIpKSkKRXZhbDogKGNkciAoY2FyIHgpKQpFdmFsOiBjZHIKRXZhbDogKGNhciB4 +KQpFdmFsOiBjYXIKRXZhbDogeApBcHBseSB0bzogKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkv +U2xpZGUzLnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgIi90bXAvZ2lt +cC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIpICgiU2xpZGUzIiAiU2xpZGUyIiAiU2xpZGUxIikp +KQpBcHBseSB0bzogKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTMucG5nIiAiL3RtcC9n +aW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlk +ZTEucG5nIikpCkV2YWw6ICh1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlIChjZHIgbGlzdHMpIChh +cHBlbmQgY2FycyAobGlzdCBjYXIxKSkgKGFwcGVuZCBjZHJzIChsaXN0IGNkcjEpKSkKRXZhbDog +dW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZQpFdmFsOiAoY2RyIGxpc3RzKQpFdmFsOiBjZHIKRXZh +bDogbGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMy5wbmci +ICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRm +Zzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMyIgIlNsaWRlMiIgIlNsaWRlMSIpKSkKRXZhbDogKGFw +cGVuZCBjYXJzIChsaXN0IGNhcjEpKQpFdmFsOiBhcHBlbmQKRXZhbDogY2FycwpFdmFsOiAobGlz +dCBjYXIxKQpFdmFsOiBsaXN0CkV2YWw6IGNhcjEKQXBwbHkgdG86ICgiL3RtcC9naW1wLW91dC1x +aW50Zmc4eS9TbGlkZTMucG5nIikKRXZhbDogeApBcHBseSB0bzogKCgpICgiL3RtcC9naW1wLW91 +dC1xaW50Zmc4eS9TbGlkZTMucG5nIikpCkV2YWw6IChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkK +RXZhbDogYXBwZW5kCkV2YWw6IGNkcnMKRXZhbDogKGxpc3QgY2RyMSkKRXZhbDogbGlzdApFdmFs +OiBjZHIxCkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICIv +dG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSkKRXZhbDogeApBcHBseSB0bzogKCgp +ICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgIi90bXAvZ2ltcC1vdXQtcWlu +dGZnOHkvU2xpZGUxLnBuZyIpKSkKQXBwbHkgdG86ICgoKCJTbGlkZTMiICJTbGlkZTIiICJTbGlk +ZTEiKSkgKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMy5wbmciKSAoKCIvdG1wL2dpbXAt +b3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5w +bmciKSkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChjb25zIGNhcnMgY2RycykgKGxldCAoKGNh +cjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAodW56aXAxLXdpdGgtY2RyLWl0 +ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2Ry +cyAobGlzdCBjZHIxKSkpKSkKRXZhbDogKG51bGw/IGxpc3RzKQpFdmFsOiBudWxsPwpFdmFsOiBs +aXN0cwpBcHBseSB0bzogKCgoIlNsaWRlMyIgIlNsaWRlMiIgIlNsaWRlMSIpKSkKRXZhbDogKGxl +dCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAodW56aXAxLXdpdGgt +Y2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChhcHBl +bmQgY2RycyAobGlzdCBjZHIxKSkpKQpFdmFsOiAoY2FhciBsaXN0cykKRXZhbDogY2FhcgpFdmFs +OiBsaXN0cwpBcHBseSB0bzogKCgoIlNsaWRlMyIgIlNsaWRlMiIgIlNsaWRlMSIpKSkKRXZhbDog +KGNhciAoY2FyIHgpKQpFdmFsOiBjYXIKRXZhbDogKGNhciB4KQpFdmFsOiBjYXIKRXZhbDogeApB +cHBseSB0bzogKCgoIlNsaWRlMyIgIlNsaWRlMiIgIlNsaWRlMSIpKSkKQXBwbHkgdG86ICgoIlNs +aWRlMyIgIlNsaWRlMiIgIlNsaWRlMSIpKQpFdmFsOiAoY2RhciBsaXN0cykKRXZhbDogY2RhcgpF +dmFsOiBsaXN0cwpBcHBseSB0bzogKCgoIlNsaWRlMyIgIlNsaWRlMiIgIlNsaWRlMSIpKSkKRXZh +bDogKGNkciAoY2FyIHgpKQpFdmFsOiBjZHIKRXZhbDogKGNhciB4KQpFdmFsOiBjYXIKRXZhbDog +eApBcHBseSB0bzogKCgoIlNsaWRlMyIgIlNsaWRlMiIgIlNsaWRlMSIpKSkKQXBwbHkgdG86ICgo +IlNsaWRlMyIgIlNsaWRlMiIgIlNsaWRlMSIpKQpFdmFsOiAodW56aXAxLXdpdGgtY2RyLWl0ZXJh +dGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAo +bGlzdCBjZHIxKSkpCkV2YWw6IHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUKRXZhbDogKGNkciBs +aXN0cykKRXZhbDogY2RyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCgiU2xpZGUzIiAiU2xpZGUy +IiAiU2xpZGUxIikpKQpFdmFsOiAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpCkV2YWw6IGFwcGVu +ZApFdmFsOiBjYXJzCkV2YWw6IChsaXN0IGNhcjEpCkV2YWw6IGxpc3QKRXZhbDogY2FyMQpBcHBs +eSB0bzogKCJTbGlkZTMiKQpFdmFsOiB4CkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRm +Zzh5L1NsaWRlMy5wbmciKSAoIlNsaWRlMyIpKQpFdmFsOiAoYXBwZW5kIGNkcnMgKGxpc3QgY2Ry +MSkpCkV2YWw6IGFwcGVuZApFdmFsOiBjZHJzCkV2YWw6IChsaXN0IGNkcjEpCkV2YWw6IGxpc3QK +RXZhbDogY2RyMQpBcHBseSB0bzogKCgiU2xpZGUyIiAiU2xpZGUxIikpCkV2YWw6IHgKQXBwbHkg +dG86ICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICIvdG1wL2dpbXAtb3V0 +LXFpbnRmZzh5L1NsaWRlMS5wbmciKSkgKCgiU2xpZGUyIiAiU2xpZGUxIikpKQpBcHBseSB0bzog +KCgpICgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTMucG5nIiAiU2xpZGUzIikgKCgiL3Rt +cC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9T +bGlkZTEucG5nIikgKCJTbGlkZTIiICJTbGlkZTEiKSkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMp +IChjb25zIGNhcnMgY2RycykgKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBs +aXN0cykpKSAodW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNh +cnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKSkKRXZhbDogKG51bGw/ +IGxpc3RzKQpFdmFsOiBudWxsPwpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgpKQpFdmFsOiAoY29u +cyBjYXJzIGNkcnMpCkV2YWw6IGNvbnMKRXZhbDogY2FycwpFdmFsOiBjZHJzCkFwcGx5IHRvOiAo +KCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMy5wbmciICJTbGlkZTMiKSAoKCIvdG1wL2dp +bXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRl +MS5wbmciKSAoIlNsaWRlMiIgIlNsaWRlMSIpKSkKRXZhbDogKGNhciB1bnopCkV2YWw6IGNhcgpF +dmFsOiB1bnoKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMy5wbmci +ICJTbGlkZTMiKSAoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgIi90bXAvZ2lt +cC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIpICgiU2xpZGUyIiAiU2xpZGUxIikpKQpFdmFsOiAo +Y2RyIHVueikKRXZhbDogY2RyCkV2YWw6IHVuegpBcHBseSB0bzogKCgoIi90bXAvZ2ltcC1vdXQt +cWludGZnOHkvU2xpZGUzLnBuZyIgIlNsaWRlMyIpICgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9T +bGlkZTIucG5nIiAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIikgKCJTbGlkZTIi +ICJTbGlkZTEiKSkpCkV2YWw6IChjb25zIChhcHBseSBwcm9jIGNhcnMpIChhcHBseSBtYXAgKGNv +bnMgcHJvYyBjZHJzKSkpCkV2YWw6IGNvbnMKRXZhbDogKGFwcGx5IHByb2MgY2FycykKRXZhbDog +YXBwbHkKRXZhbDogcHJvYwpFdmFsOiBjYXJzCkFwcGx5IHRvOiAoIzxjb25zIFBST0NFRFVSRSA3 +Nj4gKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMy5wbmciICJTbGlkZTMiKSkKQXBwbHkg +dG86ICgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTMucG5nIiAiU2xpZGUzIikKRXZhbDog +KGFwcGx5IG1hcCAoY29ucyBwcm9jIGNkcnMpKQpFdmFsOiBhcHBseQpFdmFsOiBtYXAKRXZhbDog +KGNvbnMgcHJvYyBjZHJzKQpFdmFsOiBjb25zCkV2YWw6IHByb2MKRXZhbDogY2RycwpBcHBseSB0 +bzogKCM8Y29ucyBQUk9DRURVUkUgNzY+ICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUy +LnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIpICgiU2xpZGUyIiAiU2xp +ZGUxIikpKQpBcHBseSB0bzogKCM8Q0xPU1VSRT4gKCM8Y29ucyBQUk9DRURVUkUgNzY+ICgiL3Rt +cC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9T +bGlkZTEucG5nIikgKCJTbGlkZTIiICJTbGlkZTEiKSkpCkFwcGx5IHRvOiAoIzxjb25zIFBST0NF +RFVSRSA3Nj4gKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICIvdG1wL2dpbXAt +b3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMiIgIlNsaWRlMSIpKQpFdmFsOiAoaWYg +KG51bGw/IGxpc3RzKSAoYXBwbHkgcHJvYykgKGlmIChudWxsPyAoY2FyIGxpc3RzKSkgJygpIChs +ZXQqICgodW56IChhcHBseSB1bnppcDEtd2l0aC1jZHIgbGlzdHMpKSAoY2FycyAoY2FyIHVueikp +IChjZHJzIChjZHIgdW56KSkpIChjb25zIChhcHBseSBwcm9jIGNhcnMpIChhcHBseSBtYXAgKGNv +bnMgcHJvYyBjZHJzKSkpKSkpCkV2YWw6IChudWxsPyBsaXN0cykKRXZhbDogbnVsbD8KRXZhbDog +bGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICIv +dG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMiIgIlNsaWRlMSIpKSkK +RXZhbDogKGlmIChudWxsPyAoY2FyIGxpc3RzKSkgJygpIChsZXQqICgodW56IChhcHBseSB1bnpp +cDEtd2l0aC1jZHIgbGlzdHMpKSAoY2FycyAoY2FyIHVueikpIChjZHJzIChjZHIgdW56KSkpIChj +b25zIChhcHBseSBwcm9jIGNhcnMpIChhcHBseSBtYXAgKGNvbnMgcHJvYyBjZHJzKSkpKSkKRXZh +bDogKG51bGw/IChjYXIgbGlzdHMpKQpFdmFsOiBudWxsPwpFdmFsOiAoY2FyIGxpc3RzKQpFdmFs +OiBjYXIKRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1Ns +aWRlMi5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMiIg +IlNsaWRlMSIpKSkKQXBwbHkgdG86ICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBu +ZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIpKQpFdmFsOiAobGV0KiAoKHVu +eiAoYXBwbHkgdW56aXAxLXdpdGgtY2RyIGxpc3RzKSkgKGNhcnMgKGNhciB1bnopKSAoY2RycyAo +Y2RyIHVueikpKSAoY29ucyAoYXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkgbWFwIChjb25zIHByb2Mg +Y2RycykpKSkKRXZhbDogKGFwcGx5IHVuemlwMS13aXRoLWNkciBsaXN0cykKRXZhbDogYXBwbHkK +RXZhbDogdW56aXAxLXdpdGgtY2RyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoIzxDTE9TVVJFPiAo +KCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRm +Zzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMiIgIlNsaWRlMSIpKSkKQXBwbHkgdG86ICgoIi90bXAv +Z2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xp +ZGUxLnBuZyIpICgiU2xpZGUyIiAiU2xpZGUxIikpCkV2YWw6ICh1bnppcDEtd2l0aC1jZHItaXRl +cmF0aXZlIGxpc3RzICcoKSAnKCkpCkV2YWw6IHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUKRXZh +bDogbGlzdHMKRXZhbDogJygpCkV2YWw6ICcoKQpBcHBseSB0bzogKCgoIi90bXAvZ2ltcC1vdXQt +cWludGZnOHkvU2xpZGUyLnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIp +ICgiU2xpZGUyIiAiU2xpZGUxIikpICgpICgpKQpFdmFsOiAoaWYgKG51bGw/IGxpc3RzKSAoY29u +cyBjYXJzIGNkcnMpIChsZXQgKChjYXIxIChjYWFyIGxpc3RzKSkgKGNkcjEgKGNkYXIgbGlzdHMp +KSkgKHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUgKGNkciBsaXN0cykgKGFwcGVuZCBjYXJzIChs +aXN0IGNhcjEpKSAoYXBwZW5kIGNkcnMgKGxpc3QgY2RyMSkpKSkpCkV2YWw6IChudWxsPyBsaXN0 +cykKRXZhbDogbnVsbD8KRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LXFp +bnRmZzh5L1NsaWRlMi5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAo +IlNsaWRlMiIgIlNsaWRlMSIpKSkKRXZhbDogKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2Ry +MSAoY2RhciBsaXN0cykpKSAodW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAo +YXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKQpFdmFs +OiAoY2FhciBsaXN0cykKRXZhbDogY2FhcgpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoIi90bXAv +Z2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xp +ZGUxLnBuZyIpICgiU2xpZGUyIiAiU2xpZGUxIikpKQpFdmFsOiAoY2FyIChjYXIgeCkpCkV2YWw6 +IGNhcgpFdmFsOiAoY2FyIHgpCkV2YWw6IGNhcgpFdmFsOiB4CkFwcGx5IHRvOiAoKCgiL3RtcC9n +aW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlk +ZTEucG5nIikgKCJTbGlkZTIiICJTbGlkZTEiKSkpCkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0 +LXFpbnRmZzh5L1NsaWRlMi5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmci +KSkKRXZhbDogKGNkYXIgbGlzdHMpCkV2YWw6IGNkYXIKRXZhbDogbGlzdHMKQXBwbHkgdG86ICgo +KCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICIvdG1wL2dpbXAtb3V0LXFpbnRm +Zzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMiIgIlNsaWRlMSIpKSkKRXZhbDogKGNkciAoY2FyIHgp +KQpFdmFsOiBjZHIKRXZhbDogKGNhciB4KQpFdmFsOiBjYXIKRXZhbDogeApBcHBseSB0bzogKCgo +Ii90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZn +OHkvU2xpZGUxLnBuZyIpICgiU2xpZGUyIiAiU2xpZGUxIikpKQpBcHBseSB0bzogKCgiL3RtcC9n +aW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlk +ZTEucG5nIikpCkV2YWw6ICh1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlIChjZHIgbGlzdHMpIChh +cHBlbmQgY2FycyAobGlzdCBjYXIxKSkgKGFwcGVuZCBjZHJzIChsaXN0IGNkcjEpKSkKRXZhbDog +dW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZQpFdmFsOiAoY2RyIGxpc3RzKQpFdmFsOiBjZHIKRXZh +bDogbGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmci +ICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMiIgIlNsaWRlMSIp +KSkKRXZhbDogKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEpKQpFdmFsOiBhcHBlbmQKRXZhbDogY2Fy +cwpFdmFsOiAobGlzdCBjYXIxKQpFdmFsOiBsaXN0CkV2YWw6IGNhcjEKQXBwbHkgdG86ICgiL3Rt +cC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIikKRXZhbDogeApBcHBseSB0bzogKCgpICgi +L3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIikpCkV2YWw6IChhcHBlbmQgY2RycyAo +bGlzdCBjZHIxKSkKRXZhbDogYXBwZW5kCkV2YWw6IGNkcnMKRXZhbDogKGxpc3QgY2RyMSkKRXZh +bDogbGlzdApFdmFsOiBjZHIxCkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1Ns +aWRlMS5wbmciKSkKRXZhbDogeApBcHBseSB0bzogKCgpICgoIi90bXAvZ2ltcC1vdXQtcWludGZn +OHkvU2xpZGUxLnBuZyIpKSkKQXBwbHkgdG86ICgoKCJTbGlkZTIiICJTbGlkZTEiKSkgKCIvdG1w +L2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciKSAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5 +L1NsaWRlMS5wbmciKSkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChjb25zIGNhcnMgY2Rycykg +KGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAodW56aXAxLXdp +dGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChh +cHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKSkKRXZhbDogKG51bGw/IGxpc3RzKQpFdmFsOiBudWxs +PwpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoIlNsaWRlMiIgIlNsaWRlMSIpKSkKRXZhbDogKGxl +dCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAodW56aXAxLXdpdGgt +Y2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChhcHBl +bmQgY2RycyAobGlzdCBjZHIxKSkpKQpFdmFsOiAoY2FhciBsaXN0cykKRXZhbDogY2FhcgpFdmFs +OiBsaXN0cwpBcHBseSB0bzogKCgoIlNsaWRlMiIgIlNsaWRlMSIpKSkKRXZhbDogKGNhciAoY2Fy +IHgpKQpFdmFsOiBjYXIKRXZhbDogKGNhciB4KQpFdmFsOiBjYXIKRXZhbDogeApBcHBseSB0bzog +KCgoIlNsaWRlMiIgIlNsaWRlMSIpKSkKQXBwbHkgdG86ICgoIlNsaWRlMiIgIlNsaWRlMSIpKQpF +dmFsOiAoY2RhciBsaXN0cykKRXZhbDogY2RhcgpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoIlNs +aWRlMiIgIlNsaWRlMSIpKSkKRXZhbDogKGNkciAoY2FyIHgpKQpFdmFsOiBjZHIKRXZhbDogKGNh +ciB4KQpFdmFsOiBjYXIKRXZhbDogeApBcHBseSB0bzogKCgoIlNsaWRlMiIgIlNsaWRlMSIpKSkK +QXBwbHkgdG86ICgoIlNsaWRlMiIgIlNsaWRlMSIpKQpFdmFsOiAodW56aXAxLXdpdGgtY2RyLWl0 +ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2Ry +cyAobGlzdCBjZHIxKSkpCkV2YWw6IHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUKRXZhbDogKGNk +ciBsaXN0cykKRXZhbDogY2RyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCgiU2xpZGUyIiAiU2xp +ZGUxIikpKQpFdmFsOiAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpCkV2YWw6IGFwcGVuZApFdmFs +OiBjYXJzCkV2YWw6IChsaXN0IGNhcjEpCkV2YWw6IGxpc3QKRXZhbDogY2FyMQpBcHBseSB0bzog +KCJTbGlkZTIiKQpFdmFsOiB4CkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1Ns +aWRlMi5wbmciKSAoIlNsaWRlMiIpKQpFdmFsOiAoYXBwZW5kIGNkcnMgKGxpc3QgY2RyMSkpCkV2 +YWw6IGFwcGVuZApFdmFsOiBjZHJzCkV2YWw6IChsaXN0IGNkcjEpCkV2YWw6IGxpc3QKRXZhbDog +Y2RyMQpBcHBseSB0bzogKCgiU2xpZGUxIikpCkV2YWw6IHgKQXBwbHkgdG86ICgoKCIvdG1wL2dp +bXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSkgKCgiU2xpZGUxIikpKQpBcHBseSB0bzogKCgp +ICgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAiU2xpZGUyIikgKCgiL3RtcC9n +aW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIikgKCJTbGlkZTEiKSkpCkV2YWw6IChpZiAobnVs +bD8gbGlzdHMpIChjb25zIGNhcnMgY2RycykgKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2Ry +MSAoY2RhciBsaXN0cykpKSAodW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAo +YXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKSkKRXZh +bDogKG51bGw/IGxpc3RzKQpFdmFsOiBudWxsPwpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgpKQpF +dmFsOiAoY29ucyBjYXJzIGNkcnMpCkV2YWw6IGNvbnMKRXZhbDogY2FycwpFdmFsOiBjZHJzCkFw +cGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICJTbGlkZTIiKSAo +KCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMSIpKSkKRXZhbDog +KGNhciB1bnopCkV2YWw6IGNhcgpFdmFsOiB1bnoKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0 +LXFpbnRmZzh5L1NsaWRlMi5wbmciICJTbGlkZTIiKSAoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkv +U2xpZGUxLnBuZyIpICgiU2xpZGUxIikpKQpFdmFsOiAoY2RyIHVueikKRXZhbDogY2RyCkV2YWw6 +IHVuegpBcHBseSB0bzogKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgIlNs +aWRlMiIpICgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIikgKCJTbGlkZTEiKSkp +CkV2YWw6IChjb25zIChhcHBseSBwcm9jIGNhcnMpIChhcHBseSBtYXAgKGNvbnMgcHJvYyBjZHJz +KSkpCkV2YWw6IGNvbnMKRXZhbDogKGFwcGx5IHByb2MgY2FycykKRXZhbDogYXBwbHkKRXZhbDog +cHJvYwpFdmFsOiBjYXJzCkFwcGx5IHRvOiAoIzxjb25zIFBST0NFRFVSRSA3Nj4gKCIvdG1wL2dp +bXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciICJTbGlkZTIiKSkKQXBwbHkgdG86ICgiL3RtcC9n +aW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAiU2xpZGUyIikKRXZhbDogKGFwcGx5IG1hcCAo +Y29ucyBwcm9jIGNkcnMpKQpFdmFsOiBhcHBseQpFdmFsOiBtYXAKRXZhbDogKGNvbnMgcHJvYyBj +ZHJzKQpFdmFsOiBjb25zCkV2YWw6IHByb2MKRXZhbDogY2RycwpBcHBseSB0bzogKCM8Y29ucyBQ +Uk9DRURVUkUgNzY+ICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIpICgiU2xp +ZGUxIikpKQpBcHBseSB0bzogKCM8Q0xPU1VSRT4gKCM8Y29ucyBQUk9DRURVUkUgNzY+ICgiL3Rt +cC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIikgKCJTbGlkZTEiKSkpCkFwcGx5IHRvOiAo +Izxjb25zIFBST0NFRFVSRSA3Nj4gKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmci +KSAoIlNsaWRlMSIpKQpFdmFsOiAoaWYgKG51bGw/IGxpc3RzKSAoYXBwbHkgcHJvYykgKGlmIChu +dWxsPyAoY2FyIGxpc3RzKSkgJygpIChsZXQqICgodW56IChhcHBseSB1bnppcDEtd2l0aC1jZHIg +bGlzdHMpKSAoY2FycyAoY2FyIHVueikpIChjZHJzIChjZHIgdW56KSkpIChjb25zIChhcHBseSBw +cm9jIGNhcnMpIChhcHBseSBtYXAgKGNvbnMgcHJvYyBjZHJzKSkpKSkpCkV2YWw6IChudWxsPyBs +aXN0cykKRXZhbDogbnVsbD8KRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0 +LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMSIpKSkKRXZhbDogKGlmIChudWxsPyAoY2Fy +IGxpc3RzKSkgJygpIChsZXQqICgodW56IChhcHBseSB1bnppcDEtd2l0aC1jZHIgbGlzdHMpKSAo +Y2FycyAoY2FyIHVueikpIChjZHJzIChjZHIgdW56KSkpIChjb25zIChhcHBseSBwcm9jIGNhcnMp +IChhcHBseSBtYXAgKGNvbnMgcHJvYyBjZHJzKSkpKSkKRXZhbDogKG51bGw/IChjYXIgbGlzdHMp +KQpFdmFsOiBudWxsPwpFdmFsOiAoY2FyIGxpc3RzKQpFdmFsOiBjYXIKRXZhbDogbGlzdHMKQXBw +bHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMSIp +KSkKQXBwbHkgdG86ICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIpKQpFdmFs +OiAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAxLXdpdGgtY2RyIGxpc3RzKSkgKGNhcnMgKGNhciB1 +bnopKSAoY2RycyAoY2RyIHVueikpKSAoY29ucyAoYXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkgbWFw +IChjb25zIHByb2MgY2RycykpKSkKRXZhbDogKGFwcGx5IHVuemlwMS13aXRoLWNkciBsaXN0cykK +RXZhbDogYXBwbHkKRXZhbDogdW56aXAxLXdpdGgtY2RyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAo +IzxDTE9TVVJFPiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRl +MSIpKSkKQXBwbHkgdG86ICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIpICgi +U2xpZGUxIikpCkV2YWw6ICh1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlIGxpc3RzICcoKSAnKCkp +CkV2YWw6IHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUKRXZhbDogbGlzdHMKRXZhbDogJygpCkV2 +YWw6ICcoKQpBcHBseSB0bzogKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIp +ICgiU2xpZGUxIikpICgpICgpKQpFdmFsOiAoaWYgKG51bGw/IGxpc3RzKSAoY29ucyBjYXJzIGNk +cnMpIChsZXQgKChjYXIxIChjYWFyIGxpc3RzKSkgKGNkcjEgKGNkYXIgbGlzdHMpKSkgKHVuemlw +MS13aXRoLWNkci1pdGVyYXRpdmUgKGNkciBsaXN0cykgKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEp +KSAoYXBwZW5kIGNkcnMgKGxpc3QgY2RyMSkpKSkpCkV2YWw6IChudWxsPyBsaXN0cykKRXZhbDog +bnVsbD8KRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1Ns +aWRlMS5wbmciKSAoIlNsaWRlMSIpKSkKRXZhbDogKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAo +Y2RyMSAoY2RhciBsaXN0cykpKSAodW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3Rz +KSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKQpF +dmFsOiAoY2FhciBsaXN0cykKRXZhbDogY2FhcgpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoIi90 +bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIpICgiU2xpZGUxIikpKQpFdmFsOiAoY2Fy +IChjYXIgeCkpCkV2YWw6IGNhcgpFdmFsOiAoY2FyIHgpCkV2YWw6IGNhcgpFdmFsOiB4CkFwcGx5 +IHRvOiAoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIikgKCJTbGlkZTEiKSkp +CkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSkKRXZhbDog +KGNkYXIgbGlzdHMpCkV2YWw6IGNkYXIKRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dp +bXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMSIpKSkKRXZhbDogKGNkciAoY2Fy +IHgpKQpFdmFsOiBjZHIKRXZhbDogKGNhciB4KQpFdmFsOiBjYXIKRXZhbDogeApBcHBseSB0bzog +KCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIpICgiU2xpZGUxIikpKQpBcHBs +eSB0bzogKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIikpCkV2YWw6ICh1bnpp +cDEtd2l0aC1jZHItaXRlcmF0aXZlIChjZHIgbGlzdHMpIChhcHBlbmQgY2FycyAobGlzdCBjYXIx +KSkgKGFwcGVuZCBjZHJzIChsaXN0IGNkcjEpKSkKRXZhbDogdW56aXAxLXdpdGgtY2RyLWl0ZXJh +dGl2ZQpFdmFsOiAoY2RyIGxpc3RzKQpFdmFsOiBjZHIKRXZhbDogbGlzdHMKQXBwbHkgdG86ICgo +KCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMSIpKSkKRXZhbDog +KGFwcGVuZCBjYXJzIChsaXN0IGNhcjEpKQpFdmFsOiBhcHBlbmQKRXZhbDogY2FycwpFdmFsOiAo +bGlzdCBjYXIxKQpFdmFsOiBsaXN0CkV2YWw6IGNhcjEKQXBwbHkgdG86ICgiL3RtcC9naW1wLW91 +dC1xaW50Zmc4eS9TbGlkZTEucG5nIikKRXZhbDogeApBcHBseSB0bzogKCgpICgiL3RtcC9naW1w +LW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIikpCkV2YWw6IChhcHBlbmQgY2RycyAobGlzdCBjZHIx +KSkKRXZhbDogYXBwZW5kCkV2YWw6IGNkcnMKRXZhbDogKGxpc3QgY2RyMSkKRXZhbDogbGlzdApF +dmFsOiBjZHIxCkFwcGx5IHRvOiAoKCkpCkV2YWw6IHgKQXBwbHkgdG86ICgoKSAoKCkpKQpBcHBs +eSB0bzogKCgoIlNsaWRlMSIpKSAoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIp +ICgoKSkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChjb25zIGNhcnMgY2RycykgKGxldCAoKGNh +cjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAodW56aXAxLXdpdGgtY2RyLWl0 +ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2Ry +cyAobGlzdCBjZHIxKSkpKSkKRXZhbDogKG51bGw/IGxpc3RzKQpFdmFsOiBudWxsPwpFdmFsOiBs +aXN0cwpBcHBseSB0bzogKCgoIlNsaWRlMSIpKSkKRXZhbDogKGxldCAoKGNhcjEgKGNhYXIgbGlz +dHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAodW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2Ry +IGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIx +KSkpKQpFdmFsOiAoY2FhciBsaXN0cykKRXZhbDogY2FhcgpFdmFsOiBsaXN0cwpBcHBseSB0bzog +KCgoIlNsaWRlMSIpKSkKRXZhbDogKGNhciAoY2FyIHgpKQpFdmFsOiBjYXIKRXZhbDogKGNhciB4 +KQpFdmFsOiBjYXIKRXZhbDogeApBcHBseSB0bzogKCgoIlNsaWRlMSIpKSkKQXBwbHkgdG86ICgo +IlNsaWRlMSIpKQpFdmFsOiAoY2RhciBsaXN0cykKRXZhbDogY2RhcgpFdmFsOiBsaXN0cwpBcHBs +eSB0bzogKCgoIlNsaWRlMSIpKSkKRXZhbDogKGNkciAoY2FyIHgpKQpFdmFsOiBjZHIKRXZhbDog +KGNhciB4KQpFdmFsOiBjYXIKRXZhbDogeApBcHBseSB0bzogKCgoIlNsaWRlMSIpKSkKQXBwbHkg +dG86ICgoIlNsaWRlMSIpKQpFdmFsOiAodW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxp +c3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkp +CkV2YWw6IHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUKRXZhbDogKGNkciBsaXN0cykKRXZhbDog +Y2RyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCgiU2xpZGUxIikpKQpFdmFsOiAoYXBwZW5kIGNh +cnMgKGxpc3QgY2FyMSkpCkV2YWw6IGFwcGVuZApFdmFsOiBjYXJzCkV2YWw6IChsaXN0IGNhcjEp +CkV2YWw6IGxpc3QKRXZhbDogY2FyMQpBcHBseSB0bzogKCJTbGlkZTEiKQpFdmFsOiB4CkFwcGx5 +IHRvOiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciKSAoIlNsaWRlMSIpKQpF +dmFsOiAoYXBwZW5kIGNkcnMgKGxpc3QgY2RyMSkpCkV2YWw6IGFwcGVuZApFdmFsOiBjZHJzCkV2 +YWw6IChsaXN0IGNkcjEpCkV2YWw6IGxpc3QKRXZhbDogY2RyMQpBcHBseSB0bzogKCgpKQpFdmFs +OiB4CkFwcGx5IHRvOiAoKCgpKSAoKCkpKQpBcHBseSB0bzogKCgpICgiL3RtcC9naW1wLW91dC1x +aW50Zmc4eS9TbGlkZTEucG5nIiAiU2xpZGUxIikgKCgpICgpKSkKRXZhbDogKGlmIChudWxsPyBs +aXN0cykgKGNvbnMgY2FycyBjZHJzKSAobGV0ICgoY2FyMSAoY2FhciBsaXN0cykpIChjZHIxIChj +ZGFyIGxpc3RzKSkpICh1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlIChjZHIgbGlzdHMpIChhcHBl +bmQgY2FycyAobGlzdCBjYXIxKSkgKGFwcGVuZCBjZHJzIChsaXN0IGNkcjEpKSkpKQpFdmFsOiAo +bnVsbD8gbGlzdHMpCkV2YWw6IG51bGw/CkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCkpCkV2YWw6 +IChjb25zIGNhcnMgY2RycykKRXZhbDogY29ucwpFdmFsOiBjYXJzCkV2YWw6IGNkcnMKQXBwbHkg +dG86ICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIgIlNsaWRlMSIpICgoKSAo +KSkpCkV2YWw6IChjYXIgdW56KQpFdmFsOiBjYXIKRXZhbDogdW56CkFwcGx5IHRvOiAoKCgiL3Rt +cC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAiU2xpZGUxIikgKCkgKCkpKQpFdmFsOiAo +Y2RyIHVueikKRXZhbDogY2RyCkV2YWw6IHVuegpBcHBseSB0bzogKCgoIi90bXAvZ2ltcC1vdXQt +cWludGZnOHkvU2xpZGUxLnBuZyIgIlNsaWRlMSIpICgpICgpKSkKRXZhbDogKGNvbnMgKGFwcGx5 +IHByb2MgY2FycykgKGFwcGx5IG1hcCAoY29ucyBwcm9jIGNkcnMpKSkKRXZhbDogY29ucwpFdmFs +OiAoYXBwbHkgcHJvYyBjYXJzKQpFdmFsOiBhcHBseQpFdmFsOiBwcm9jCkV2YWw6IGNhcnMKQXBw +bHkgdG86ICgjPGNvbnMgUFJPQ0VEVVJFIDc2PiAoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xp +ZGUxLnBuZyIgIlNsaWRlMSIpKQpBcHBseSB0bzogKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1Ns +aWRlMS5wbmciICJTbGlkZTEiKQpFdmFsOiAoYXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpCkV2 +YWw6IGFwcGx5CkV2YWw6IG1hcApFdmFsOiAoY29ucyBwcm9jIGNkcnMpCkV2YWw6IGNvbnMKRXZh +bDogcHJvYwpFdmFsOiBjZHJzCkFwcGx5IHRvOiAoIzxjb25zIFBST0NFRFVSRSA3Nj4gKCgpICgp +KSkKQXBwbHkgdG86ICgjPENMT1NVUkU+ICgjPGNvbnMgUFJPQ0VEVVJFIDc2PiAoKSAoKSkpCkFw +cGx5IHRvOiAoIzxjb25zIFBST0NFRFVSRSA3Nj4gKCkgKCkpCkV2YWw6IChpZiAobnVsbD8gbGlz +dHMpIChhcHBseSBwcm9jKSAoaWYgKG51bGw/IChjYXIgbGlzdHMpKSAnKCkgKGxldCogKCh1bnog +KGFwcGx5IHVuemlwMS13aXRoLWNkciBsaXN0cykpIChjYXJzIChjYXIgdW56KSkgKGNkcnMgKGNk +ciB1bnopKSkgKGNvbnMgKGFwcGx5IHByb2MgY2FycykgKGFwcGx5IG1hcCAoY29ucyBwcm9jIGNk +cnMpKSkpKSkKRXZhbDogKG51bGw/IGxpc3RzKQpFdmFsOiBudWxsPwpFdmFsOiBsaXN0cwpBcHBs +eSB0bzogKCgoKSAoKSkpCkV2YWw6IChpZiAobnVsbD8gKGNhciBsaXN0cykpICcoKSAobGV0KiAo +KHVueiAoYXBwbHkgdW56aXAxLXdpdGgtY2RyIGxpc3RzKSkgKGNhcnMgKGNhciB1bnopKSAoY2Ry +cyAoY2RyIHVueikpKSAoY29ucyAoYXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkgbWFwIChjb25zIHBy +b2MgY2RycykpKSkpCkV2YWw6IChudWxsPyAoY2FyIGxpc3RzKSkKRXZhbDogbnVsbD8KRXZhbDog +KGNhciBsaXN0cykKRXZhbDogY2FyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCgpICgpKSkKQXBw +bHkgdG86ICgoKSkKRXZhbDogJygpCkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5 +L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpICgpKQpBcHBseSB0bzogKCgiL3RtcC9naW1wLW91dC1x +aW50Zmc4eS9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5 +L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkKQXBwbHkgdG86ICgoIi90bXAvZ2ltcC1vdXQtcWlu +dGZnOHkvU2xpZGUzLnBuZyIgLiAiU2xpZGUzIikgKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9T +bGlkZTIucG5nIiAuICJTbGlkZTIiKSAoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBu +ZyIgLiAiU2xpZGUxIikpKQpBcHBseSB0bzogKCM8Q0xPU1VSRT4gKCgiL3RtcC9naW1wLW91dC1x +aW50Zmc4eS9TbGlkZTMucG5nIiAuICJTbGlkZTMiKSAoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkv +U2xpZGUyLnBuZyIgLiAiU2xpZGUyIikgKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5w +bmciIC4gIlNsaWRlMSIpKSkKRXZhbDogKGlmIChudWxsPyBsaXN0cykgKGFwcGx5IHByb2MpIChp +ZiAobnVsbD8gKGNhciBsaXN0cykpICN0IChsZXQqICgodW56IChhcHBseSB1bnppcDEtd2l0aC1j +ZHIgbGlzdHMpKSAoY2FycyAoY2FyIHVueikpIChjZHJzIChjZHIgdW56KSkpIChhcHBseSBwcm9j +IGNhcnMpIChhcHBseSBtYXAgKGNvbnMgcHJvYyBjZHJzKSkpKSkKRXZhbDogKG51bGw/IGxpc3Rz +KQpFdmFsOiBudWxsPwpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoKCIvdG1wL2dpbXAtb3V0LXFp +bnRmZzh5L1NsaWRlMy5wbmciIC4gIlNsaWRlMyIpICgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9T +bGlkZTIucG5nIiAuICJTbGlkZTIiKSAoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBu +ZyIgLiAiU2xpZGUxIikpKSkKRXZhbDogKGlmIChudWxsPyAoY2FyIGxpc3RzKSkgI3QgKGxldCog +KCh1bnogKGFwcGx5IHVuemlwMS13aXRoLWNkciBsaXN0cykpIChjYXJzIChjYXIgdW56KSkgKGNk +cnMgKGNkciB1bnopKSkgKGFwcGx5IHByb2MgY2FycykgKGFwcGx5IG1hcCAoY29ucyBwcm9jIGNk +cnMpKSkpCkV2YWw6IChudWxsPyAoY2FyIGxpc3RzKSkKRXZhbDogbnVsbD8KRXZhbDogKGNhciBs +aXN0cykKRXZhbDogY2FyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCgoIi90bXAvZ2ltcC1vdXQt +cWludGZnOHkvU2xpZGUzLnBuZyIgLiAiU2xpZGUzIikgKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5 +L1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpICgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEu +cG5nIiAuICJTbGlkZTEiKSkpKQpBcHBseSB0bzogKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkv +U2xpZGUzLnBuZyIgLiAiU2xpZGUzIikgKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5w +bmciIC4gIlNsaWRlMiIpICgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJT +bGlkZTEiKSkpCkV2YWw6IChsZXQqICgodW56IChhcHBseSB1bnppcDEtd2l0aC1jZHIgbGlzdHMp +KSAoY2FycyAoY2FyIHVueikpIChjZHJzIChjZHIgdW56KSkpIChhcHBseSBwcm9jIGNhcnMpIChh +cHBseSBtYXAgKGNvbnMgcHJvYyBjZHJzKSkpCkV2YWw6IChhcHBseSB1bnppcDEtd2l0aC1jZHIg +bGlzdHMpCkV2YWw6IGFwcGx5CkV2YWw6IHVuemlwMS13aXRoLWNkcgpFdmFsOiBsaXN0cwpBcHBs +eSB0bzogKCM8Q0xPU1VSRT4gKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBuZyIg +LiAiU2xpZGUzIikgKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4gIlNsaWRl +MiIpICgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkpKQpB +cHBseSB0bzogKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBuZyIgLiAiU2xpZGUz +IikgKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpICgiL3Rt +cC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkpCkV2YWw6ICh1bnpp +cDEtd2l0aC1jZHItaXRlcmF0aXZlIGxpc3RzICcoKSAnKCkpCkV2YWw6IHVuemlwMS13aXRoLWNk +ci1pdGVyYXRpdmUKRXZhbDogbGlzdHMKRXZhbDogJygpCkV2YWw6ICcoKQpBcHBseSB0bzogKCgo +KCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMy5wbmciIC4gIlNsaWRlMyIpICgiL3RtcC9n +aW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSAoIi90bXAvZ2ltcC1vdXQt +cWludGZnOHkvU2xpZGUxLnBuZyIgLiAiU2xpZGUxIikpKSAoKSAoKSkKRXZhbDogKGlmIChudWxs +PyBsaXN0cykgKGNvbnMgY2FycyBjZHJzKSAobGV0ICgoY2FyMSAoY2FhciBsaXN0cykpIChjZHIx +IChjZGFyIGxpc3RzKSkpICh1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlIChjZHIgbGlzdHMpIChh +cHBlbmQgY2FycyAobGlzdCBjYXIxKSkgKGFwcGVuZCBjZHJzIChsaXN0IGNkcjEpKSkpKQpFdmFs +OiAobnVsbD8gbGlzdHMpCkV2YWw6IG51bGw/CkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCgoIi90 +bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBuZyIgLiAiU2xpZGUzIikgKCIvdG1wL2dpbXAt +b3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpICgiL3RtcC9naW1wLW91dC1xaW50 +Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkpKQpFdmFsOiAobGV0ICgoY2FyMSAoY2FhciBs +aXN0cykpIChjZHIxIChjZGFyIGxpc3RzKSkpICh1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlIChj +ZHIgbGlzdHMpIChhcHBlbmQgY2FycyAobGlzdCBjYXIxKSkgKGFwcGVuZCBjZHJzIChsaXN0IGNk +cjEpKSkpCkV2YWw6IChjYWFyIGxpc3RzKQpFdmFsOiBjYWFyCkV2YWw6IGxpc3RzCkFwcGx5IHRv +OiAoKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBuZyIgLiAiU2xpZGUzIikgKCIv +dG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpICgiL3RtcC9naW1w +LW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkpKQpFdmFsOiAoY2FyIChjYXIg +eCkpCkV2YWw6IGNhcgpFdmFsOiAoY2FyIHgpCkV2YWw6IGNhcgpFdmFsOiB4CkFwcGx5IHRvOiAo +KCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBuZyIgLiAiU2xpZGUzIikgKCIvdG1w +L2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpICgiL3RtcC9naW1wLW91 +dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkpKQpBcHBseSB0bzogKCgoIi90bXAv +Z2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBuZyIgLiAiU2xpZGUzIikgKCIvdG1wL2dpbXAtb3V0 +LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpICgiL3RtcC9naW1wLW91dC1xaW50Zmc4 +eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkpCkV2YWw6IChjZGFyIGxpc3RzKQpFdmFsOiBjZGFy +CkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUz +LnBuZyIgLiAiU2xpZGUzIikgKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4g +IlNsaWRlMiIpICgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEi +KSkpKQpFdmFsOiAoY2RyIChjYXIgeCkpCkV2YWw6IGNkcgpFdmFsOiAoY2FyIHgpCkV2YWw6IGNh +cgpFdmFsOiB4CkFwcGx5IHRvOiAoKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBu +ZyIgLiAiU2xpZGUzIikgKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4gIlNs +aWRlMiIpICgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkp +KQpBcHBseSB0bzogKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBuZyIgLiAiU2xp +ZGUzIikgKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpICgi +L3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkpCkV2YWw6ICh1 +bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlIChjZHIgbGlzdHMpIChhcHBlbmQgY2FycyAobGlzdCBj +YXIxKSkgKGFwcGVuZCBjZHJzIChsaXN0IGNkcjEpKSkKRXZhbDogdW56aXAxLXdpdGgtY2RyLWl0 +ZXJhdGl2ZQpFdmFsOiAoY2RyIGxpc3RzKQpFdmFsOiBjZHIKRXZhbDogbGlzdHMKQXBwbHkgdG86 +ICgoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTMucG5nIiAuICJTbGlkZTMiKSAoIi90 +bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgLiAiU2xpZGUyIikgKCIvdG1wL2dpbXAt +b3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkpCkV2YWw6IChhcHBlbmQgY2Fy +cyAobGlzdCBjYXIxKSkKRXZhbDogYXBwZW5kCkV2YWw6IGNhcnMKRXZhbDogKGxpc3QgY2FyMSkK +RXZhbDogbGlzdApFdmFsOiBjYXIxCkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5 +L1NsaWRlMy5wbmciIC4gIlNsaWRlMyIpKQpFdmFsOiB4CkFwcGx5IHRvOiAoKCkgKCgiL3RtcC9n +aW1wLW91dC1xaW50Zmc4eS9TbGlkZTMucG5nIiAuICJTbGlkZTMiKSkpCkV2YWw6IChhcHBlbmQg +Y2RycyAobGlzdCBjZHIxKSkKRXZhbDogYXBwZW5kCkV2YWw6IGNkcnMKRXZhbDogKGxpc3QgY2Ry +MSkKRXZhbDogbGlzdApFdmFsOiBjZHIxCkFwcGx5IHRvOiAoKCgiL3RtcC9naW1wLW91dC1xaW50 +Zmc4eS9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSAoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xp +ZGUxLnBuZyIgLiAiU2xpZGUxIikpKQpFdmFsOiB4CkFwcGx5IHRvOiAoKCkgKCgoIi90bXAvZ2lt +cC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgLiAiU2xpZGUyIikgKCIvdG1wL2dpbXAtb3V0LXFp +bnRmZzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkpCkFwcGx5IHRvOiAoKCkgKCgiL3RtcC9n +aW1wLW91dC1xaW50Zmc4eS9TbGlkZTMucG5nIiAuICJTbGlkZTMiKSkgKCgoIi90bXAvZ2ltcC1v +dXQtcWludGZnOHkvU2xpZGUyLnBuZyIgLiAiU2xpZGUyIikgKCIvdG1wL2dpbXAtb3V0LXFpbnRm +Zzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChj +b25zIGNhcnMgY2RycykgKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0 +cykpKSAodW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMg +KGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKSkKRXZhbDogKG51bGw/IGxp +c3RzKQpFdmFsOiBudWxsPwpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgpKQpFdmFsOiAoY29ucyBj +YXJzIGNkcnMpCkV2YWw6IGNvbnMKRXZhbDogY2FycwpFdmFsOiBjZHJzCkFwcGx5IHRvOiAoKCgi +L3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTMucG5nIiAuICJTbGlkZTMiKSkgKCgoIi90bXAv +Z2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgLiAiU2xpZGUyIikgKCIvdG1wL2dpbXAtb3V0 +LXFpbnRmZzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkpCkV2YWw6IChjYXIgdW56KQpFdmFs +OiBjYXIKRXZhbDogdW56CkFwcGx5IHRvOiAoKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xp +ZGUzLnBuZyIgLiAiU2xpZGUzIikpICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBu +ZyIgLiAiU2xpZGUyIikgKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciIC4gIlNs +aWRlMSIpKSkpCkV2YWw6IChjZHIgdW56KQpFdmFsOiBjZHIKRXZhbDogdW56CkFwcGx5IHRvOiAo +KCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBuZyIgLiAiU2xpZGUzIikpICgoIi90 +bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgLiAiU2xpZGUyIikgKCIvdG1wL2dpbXAt +b3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkpCkV2YWw6IChhcHBseSBwcm9j +IGNhcnMpCkV2YWw6IGFwcGx5CkV2YWw6IHByb2MKRXZhbDogY2FycwpBcHBseSB0bzogKCM8Q0xP +U1VSRT4gKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTMucG5nIiAuICJTbGlkZTMiKSkp +CkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMy5wbmciIC4gIlNsaWRl +MyIpKQpFdmFsOiAocG5nLXRvLWxheWVyIGltZyAoY2FyIG5hbWVzKSAoY2RyIG5hbWVzKSkKRXZh +bDogcG5nLXRvLWxheWVyCkV2YWw6IGltZwpFdmFsOiAoY2FyIG5hbWVzKQpFdmFsOiBjYXIKRXZh +bDogbmFtZXMKQXBwbHkgdG86ICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBuZyIg +LiAiU2xpZGUzIikpCkV2YWw6IChjZHIgbmFtZXMpCkV2YWw6IGNkcgpFdmFsOiBuYW1lcwpBcHBs +eSB0bzogKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTMucG5nIiAuICJTbGlkZTMiKSkK +QXBwbHkgdG86ICgxICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMy5wbmciICJTbGlkZTMi +KQpFdmFsOiAobGV0KiAoKHBuZyAoY2FyIChmaWxlLXBuZy1sb2FkIFJVTi1OT05JTlRFUkFDVElW +RSBwbmdfZmlsZW5hbWUgcG5nX2ZpbGVuYW1lKSkpIChwbmdfbGF5ZXIgKGNhciAoZ2ltcC1pbWFn +ZS1nZXQtYWN0aXZlLWxheWVyIHBuZykpKSAoeGNmX2xheWVyIChjYXIgKGdpbXAtbGF5ZXItbmV3 +LWZyb20tZHJhd2FibGUgcG5nX2xheWVyIGltZykpKSkgKGdpbXAtaW1hZ2UtYWRkLWxheWVyIGlt +ZyB4Y2ZfbGF5ZXIgLTEpIChnaW1wLWRyYXdhYmxlLXNldC1uYW1lIHhjZl9sYXllciBsYXllcl9u +YW1lKSkKRXZhbDogKGNhciAoZmlsZS1wbmctbG9hZCBSVU4tTk9OSU5URVJBQ1RJVkUgcG5nX2Zp +bGVuYW1lIHBuZ19maWxlbmFtZSkpCkV2YWw6IGNhcgpFdmFsOiAoZmlsZS1wbmctbG9hZCBSVU4t +Tk9OSU5URVJBQ1RJVkUgcG5nX2ZpbGVuYW1lIHBuZ19maWxlbmFtZSkKRXZhbDogZmlsZS1wbmct +bG9hZApFdmFsOiBSVU4tTk9OSU5URVJBQ1RJVkUKRXZhbDogcG5nX2ZpbGVuYW1lCkV2YWw6IHBu +Z19maWxlbmFtZQpBcHBseSB0bzogKDEgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBu +ZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBuZyIpCkV2YWw6IChhcHBseSBnaW1w +LXByb2MtZGItY2FsbCAoY29ucyAiZmlsZS1wbmctbG9hZCIgeCkpCkV2YWw6IGFwcGx5CkV2YWw6 +IGdpbXAtcHJvYy1kYi1jYWxsCkV2YWw6IChjb25zICJmaWxlLXBuZy1sb2FkIiB4KQpFdmFsOiBj +b25zCkV2YWw6ICJmaWxlLXBuZy1sb2FkIgpFdmFsOiB4CkFwcGx5IHRvOiAoImZpbGUtcG5nLWxv +YWQiICgxICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMy5wbmciICIvdG1wL2dpbXAtb3V0 +LXFpbnRmZzh5L1NsaWRlMy5wbmciKSkKQXBwbHkgdG86ICgjPEZPUkVJR04gUFJPQ0VEVVJFIDk0 +ODk2Mzg3NDYwODQ4PiAoImZpbGUtcG5nLWxvYWQiIDEgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkv +U2xpZGUzLnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUzLnBuZyIpKQpBcHBseSB0 +bzogKCJmaWxlLXBuZy1sb2FkIiAxICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMy5wbmci +ICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMy5wbmciKQpBcHBseSB0bzogKCgyKSkKRXZh +bDogKGNhciAoZ2ltcC1pbWFnZS1nZXQtYWN0aXZlLWxheWVyIHBuZykpCkV2YWw6IGNhcgpFdmFs +OiAoZ2ltcC1pbWFnZS1nZXQtYWN0aXZlLWxheWVyIHBuZykKRXZhbDogZ2ltcC1pbWFnZS1nZXQt +YWN0aXZlLWxheWVyCkV2YWw6IHBuZwpBcHBseSB0bzogKDIpCkV2YWw6IChhcHBseSBnaW1wLXBy +b2MtZGItY2FsbCAoY29ucyAiZ2ltcC1pbWFnZS1nZXQtYWN0aXZlLWxheWVyIiB4KSkKRXZhbDog +YXBwbHkKRXZhbDogZ2ltcC1wcm9jLWRiLWNhbGwKRXZhbDogKGNvbnMgImdpbXAtaW1hZ2UtZ2V0 +LWFjdGl2ZS1sYXllciIgeCkKRXZhbDogY29ucwpFdmFsOiAiZ2ltcC1pbWFnZS1nZXQtYWN0aXZl +LWxheWVyIgpFdmFsOiB4CkFwcGx5IHRvOiAoImdpbXAtaW1hZ2UtZ2V0LWFjdGl2ZS1sYXllciIg +KDIpKQpBcHBseSB0bzogKCM8Rk9SRUlHTiBQUk9DRURVUkUgOTQ4OTYzODc0NjA4NDg+ICgiZ2lt +cC1pbWFnZS1nZXQtYWN0aXZlLWxheWVyIiAyKSkKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1nZXQt +YWN0aXZlLWxheWVyIiAyKQpBcHBseSB0bzogKCgzKSkKRXZhbDogKGNhciAoZ2ltcC1sYXllci1u +ZXctZnJvbS1kcmF3YWJsZSBwbmdfbGF5ZXIgaW1nKSkKRXZhbDogY2FyCkV2YWw6IChnaW1wLWxh +eWVyLW5ldy1mcm9tLWRyYXdhYmxlIHBuZ19sYXllciBpbWcpCkV2YWw6IGdpbXAtbGF5ZXItbmV3 +LWZyb20tZHJhd2FibGUKRXZhbDogcG5nX2xheWVyCkV2YWw6IGltZwpBcHBseSB0bzogKDMgMSkK +RXZhbDogKGFwcGx5IGdpbXAtcHJvYy1kYi1jYWxsIChjb25zICJnaW1wLWxheWVyLW5ldy1mcm9t +LWRyYXdhYmxlIiB4KSkKRXZhbDogYXBwbHkKRXZhbDogZ2ltcC1wcm9jLWRiLWNhbGwKRXZhbDog +KGNvbnMgImdpbXAtbGF5ZXItbmV3LWZyb20tZHJhd2FibGUiIHgpCkV2YWw6IGNvbnMKRXZhbDog +ImdpbXAtbGF5ZXItbmV3LWZyb20tZHJhd2FibGUiCkV2YWw6IHgKQXBwbHkgdG86ICgiZ2ltcC1s +YXllci1uZXctZnJvbS1kcmF3YWJsZSIgKDMgMSkpCkFwcGx5IHRvOiAoIzxGT1JFSUdOIFBST0NF +RFVSRSA5NDg5NjM4NzQ2MDg0OD4gKCJnaW1wLWxheWVyLW5ldy1mcm9tLWRyYXdhYmxlIiAzIDEp +KQpBcHBseSB0bzogKCJnaW1wLWxheWVyLW5ldy1mcm9tLWRyYXdhYmxlIiAzIDEpCkFwcGx5IHRv +OiAoKDQpKQpFdmFsOiAoZ2ltcC1pbWFnZS1hZGQtbGF5ZXIgaW1nIHhjZl9sYXllciAtMSkKRXZh +bDogZ2ltcC1pbWFnZS1hZGQtbGF5ZXIKRXZhbDogaW1nCkV2YWw6IHhjZl9sYXllcgpFdmFsOiAt +MQpBcHBseSB0bzogKDEgNCAtMSkKRXZhbDogKGFwcGx5IGdpbXAtcHJvYy1kYi1jYWxsIChjb25z +ICJnaW1wLWltYWdlLWFkZC1sYXllciIgeCkpCkV2YWw6IGFwcGx5CkV2YWw6IGdpbXAtcHJvYy1k +Yi1jYWxsCkV2YWw6IChjb25zICJnaW1wLWltYWdlLWFkZC1sYXllciIgeCkKRXZhbDogY29ucwpF +dmFsOiAiZ2ltcC1pbWFnZS1hZGQtbGF5ZXIiCkV2YWw6IHgKQXBwbHkgdG86ICgiZ2ltcC1pbWFn +ZS1hZGQtbGF5ZXIiICgxIDQgLTEpKQpBcHBseSB0bzogKCM8Rk9SRUlHTiBQUk9DRURVUkUgOTQ4 +OTYzODc0NjA4NDg+ICgiZ2ltcC1pbWFnZS1hZGQtbGF5ZXIiIDEgNCAtMSkpCkFwcGx5IHRvOiAo +ImdpbXAtaW1hZ2UtYWRkLWxheWVyIiAxIDQgLTEpCkV2YWw6IChnaW1wLWRyYXdhYmxlLXNldC1u +YW1lIHhjZl9sYXllciBsYXllcl9uYW1lKQpFdmFsOiBnaW1wLWRyYXdhYmxlLXNldC1uYW1lCkV2 +YWw6IHhjZl9sYXllcgpFdmFsOiBsYXllcl9uYW1lCkFwcGx5IHRvOiAoNCAiU2xpZGUzIikKRXZh +bDogKGFwcGx5IGdpbXAtcHJvYy1kYi1jYWxsIChjb25zICJnaW1wLWRyYXdhYmxlLXNldC1uYW1l +IiB4KSkKRXZhbDogYXBwbHkKRXZhbDogZ2ltcC1wcm9jLWRiLWNhbGwKRXZhbDogKGNvbnMgImdp +bXAtZHJhd2FibGUtc2V0LW5hbWUiIHgpCkV2YWw6IGNvbnMKRXZhbDogImdpbXAtZHJhd2FibGUt +c2V0LW5hbWUiCkV2YWw6IHgKQXBwbHkgdG86ICgiZ2ltcC1kcmF3YWJsZS1zZXQtbmFtZSIgKDQg +IlNsaWRlMyIpKQpBcHBseSB0bzogKCM8Rk9SRUlHTiBQUk9DRURVUkUgOTQ4OTYzODc0NjA4NDg+ +ICgiZ2ltcC1kcmF3YWJsZS1zZXQtbmFtZSIgNCAiU2xpZGUzIikpCkFwcGx5IHRvOiAoImdpbXAt +ZHJhd2FibGUtc2V0LW5hbWUiIDQgIlNsaWRlMyIpCkV2YWw6IChhcHBseSBtYXAgKGNvbnMgcHJv +YyBjZHJzKSkKRXZhbDogYXBwbHkKRXZhbDogbWFwCkV2YWw6IChjb25zIHByb2MgY2RycykKRXZh +bDogY29ucwpFdmFsOiBwcm9jCkV2YWw6IGNkcnMKQXBwbHkgdG86ICgjPENMT1NVUkU+ICgoKCIv +dG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpICgiL3RtcC9naW1w +LW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkpKQpBcHBseSB0bzogKCM8Q0xP +U1VSRT4gKCM8Q0xPU1VSRT4gKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAu +ICJTbGlkZTIiKSAoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIgLiAiU2xpZGUx +IikpKSkKQXBwbHkgdG86ICgjPENMT1NVUkU+ICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xp +ZGUyLnBuZyIgLiAiU2xpZGUyIikgKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmci +IC4gIlNsaWRlMSIpKSkKRXZhbDogKGlmIChudWxsPyBsaXN0cykgKGFwcGx5IHByb2MpIChpZiAo +bnVsbD8gKGNhciBsaXN0cykpICcoKSAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAxLXdpdGgtY2Ry +IGxpc3RzKSkgKGNhcnMgKGNhciB1bnopKSAoY2RycyAoY2RyIHVueikpKSAoY29ucyAoYXBwbHkg +cHJvYyBjYXJzKSAoYXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpKSkpKQpFdmFsOiAobnVsbD8g +bGlzdHMpCkV2YWw6IG51bGw/CkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCgoIi90bXAvZ2ltcC1v +dXQtcWludGZnOHkvU2xpZGUyLnBuZyIgLiAiU2xpZGUyIikgKCIvdG1wL2dpbXAtb3V0LXFpbnRm +Zzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkpCkV2YWw6IChpZiAobnVsbD8gKGNhciBsaXN0 +cykpICcoKSAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAxLXdpdGgtY2RyIGxpc3RzKSkgKGNhcnMg +KGNhciB1bnopKSAoY2RycyAoY2RyIHVueikpKSAoY29ucyAoYXBwbHkgcHJvYyBjYXJzKSAoYXBw +bHkgbWFwIChjb25zIHByb2MgY2RycykpKSkpCkV2YWw6IChudWxsPyAoY2FyIGxpc3RzKSkKRXZh +bDogbnVsbD8KRXZhbDogKGNhciBsaXN0cykKRXZhbDogY2FyCkV2YWw6IGxpc3RzCkFwcGx5IHRv +OiAoKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgLiAiU2xpZGUyIikgKCIv +dG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkpCkFwcGx5IHRv +OiAoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSAoIi90 +bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIgLiAiU2xpZGUxIikpKQpFdmFsOiAobGV0 +KiAoKHVueiAoYXBwbHkgdW56aXAxLXdpdGgtY2RyIGxpc3RzKSkgKGNhcnMgKGNhciB1bnopKSAo +Y2RycyAoY2RyIHVueikpKSAoY29ucyAoYXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkgbWFwIChjb25z +IHByb2MgY2RycykpKSkKRXZhbDogKGFwcGx5IHVuemlwMS13aXRoLWNkciBsaXN0cykKRXZhbDog +YXBwbHkKRXZhbDogdW56aXAxLXdpdGgtY2RyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoIzxDTE9T +VVJFPiAoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSAo +Ii90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIgLiAiU2xpZGUxIikpKSkKQXBwbHkg +dG86ICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpICgi +L3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkpCkV2YWw6ICh1 +bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlIGxpc3RzICcoKSAnKCkpCkV2YWw6IHVuemlwMS13aXRo +LWNkci1pdGVyYXRpdmUKRXZhbDogbGlzdHMKRXZhbDogJygpCkV2YWw6ICcoKQpBcHBseSB0bzog +KCgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpICgiL3Rt +cC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkpICgpICgpKQpFdmFs +OiAoaWYgKG51bGw/IGxpc3RzKSAoY29ucyBjYXJzIGNkcnMpIChsZXQgKChjYXIxIChjYWFyIGxp +c3RzKSkgKGNkcjEgKGNkYXIgbGlzdHMpKSkgKHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUgKGNk +ciBsaXN0cykgKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEpKSAoYXBwZW5kIGNkcnMgKGxpc3QgY2Ry +MSkpKSkpCkV2YWw6IChudWxsPyBsaXN0cykKRXZhbDogbnVsbD8KRXZhbDogbGlzdHMKQXBwbHkg +dG86ICgoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSAo +Ii90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIgLiAiU2xpZGUxIikpKSkKRXZhbDog +KGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAodW56aXAxLXdp +dGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChh +cHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKQpFdmFsOiAoY2FhciBsaXN0cykKRXZhbDogY2FhcgpF +dmFsOiBsaXN0cwpBcHBseSB0bzogKCgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5w +bmciIC4gIlNsaWRlMiIpICgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJT +bGlkZTEiKSkpKQpFdmFsOiAoY2FyIChjYXIgeCkpCkV2YWw6IGNhcgpFdmFsOiAoY2FyIHgpCkV2 +YWw6IGNhcgpFdmFsOiB4CkFwcGx5IHRvOiAoKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xp +ZGUyLnBuZyIgLiAiU2xpZGUyIikgKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmci +IC4gIlNsaWRlMSIpKSkpCkFwcGx5IHRvOiAoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlk +ZTIucG5nIiAuICJTbGlkZTIiKSAoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIg +LiAiU2xpZGUxIikpKQpFdmFsOiAoY2RhciBsaXN0cykKRXZhbDogY2RhcgpFdmFsOiBsaXN0cwpB +cHBseSB0bzogKCgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4gIlNsaWRl +MiIpICgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkpKQpF +dmFsOiAoY2RyIChjYXIgeCkpCkV2YWw6IGNkcgpFdmFsOiAoY2FyIHgpCkV2YWw6IGNhcgpFdmFs +OiB4CkFwcGx5IHRvOiAoKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgLiAi +U2xpZGUyIikgKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIp +KSkpCkFwcGx5IHRvOiAoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAuICJT +bGlkZTIiKSAoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIgLiAiU2xpZGUxIikp +KQpFdmFsOiAodW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNh +cnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpCkV2YWw6IHVuemlwMS13 +aXRoLWNkci1pdGVyYXRpdmUKRXZhbDogKGNkciBsaXN0cykKRXZhbDogY2RyCkV2YWw6IGxpc3Rz +CkFwcGx5IHRvOiAoKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIgLiAiU2xp +ZGUyIikgKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkp +CkV2YWw6IChhcHBlbmQgY2FycyAobGlzdCBjYXIxKSkKRXZhbDogYXBwZW5kCkV2YWw6IGNhcnMK +RXZhbDogKGxpc3QgY2FyMSkKRXZhbDogbGlzdApFdmFsOiBjYXIxCkFwcGx5IHRvOiAoKCIvdG1w +L2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpKQpFdmFsOiB4CkFwcGx5 +IHRvOiAoKCkgKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAuICJTbGlkZTIi +KSkpCkV2YWw6IChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkKRXZhbDogYXBwZW5kCkV2YWw6IGNk +cnMKRXZhbDogKGxpc3QgY2RyMSkKRXZhbDogbGlzdApFdmFsOiBjZHIxCkFwcGx5IHRvOiAoKCgi +L3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkpCkV2YWw6IHgK +QXBwbHkgdG86ICgoKSAoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJT +bGlkZTEiKSkpKQpBcHBseSB0bzogKCgpICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUy +LnBuZyIgLiAiU2xpZGUyIikpICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmci +IC4gIlNsaWRlMSIpKSkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChjb25zIGNhcnMgY2Rycykg +KGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAodW56aXAxLXdp +dGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChh +cHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKSkKRXZhbDogKG51bGw/IGxpc3RzKQpFdmFsOiBudWxs +PwpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgpKQpFdmFsOiAoY29ucyBjYXJzIGNkcnMpCkV2YWw6 +IGNvbnMKRXZhbDogY2FycwpFdmFsOiBjZHJzCkFwcGx5IHRvOiAoKCgiL3RtcC9naW1wLW91dC1x +aW50Zmc4eS9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSkgKCgoIi90bXAvZ2ltcC1vdXQtcWludGZn +OHkvU2xpZGUxLnBuZyIgLiAiU2xpZGUxIikpKSkKRXZhbDogKGNhciB1bnopCkV2YWw6IGNhcgpF +dmFsOiB1bnoKQXBwbHkgdG86ICgoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5n +IiAuICJTbGlkZTIiKSkgKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJT +bGlkZTEiKSkpKQpFdmFsOiAoY2RyIHVueikKRXZhbDogY2RyCkV2YWw6IHVuegpBcHBseSB0bzog +KCgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpKSAoKCIv +dG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkpCkV2YWw6IChj +b25zIChhcHBseSBwcm9jIGNhcnMpIChhcHBseSBtYXAgKGNvbnMgcHJvYyBjZHJzKSkpCkV2YWw6 +IGNvbnMKRXZhbDogKGFwcGx5IHByb2MgY2FycykKRXZhbDogYXBwbHkKRXZhbDogcHJvYwpFdmFs +OiBjYXJzCkFwcGx5IHRvOiAoIzxDTE9TVVJFPiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1Ns +aWRlMi5wbmciIC4gIlNsaWRlMiIpKSkKQXBwbHkgdG86ICgoIi90bXAvZ2ltcC1vdXQtcWludGZn +OHkvU2xpZGUyLnBuZyIgLiAiU2xpZGUyIikpCkV2YWw6IChwbmctdG8tbGF5ZXIgaW1nIChjYXIg +bmFtZXMpIChjZHIgbmFtZXMpKQpFdmFsOiBwbmctdG8tbGF5ZXIKRXZhbDogaW1nCkV2YWw6IChj +YXIgbmFtZXMpCkV2YWw6IGNhcgpFdmFsOiBuYW1lcwpBcHBseSB0bzogKCgiL3RtcC9naW1wLW91 +dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSkKRXZhbDogKGNkciBuYW1lcykKRXZh +bDogY2RyCkV2YWw6IG5hbWVzCkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1Ns +aWRlMi5wbmciIC4gIlNsaWRlMiIpKQpBcHBseSB0bzogKDEgIi90bXAvZ2ltcC1vdXQtcWludGZn +OHkvU2xpZGUyLnBuZyIgIlNsaWRlMiIpCkV2YWw6IChsZXQqICgocG5nIChjYXIgKGZpbGUtcG5n +LWxvYWQgUlVOLU5PTklOVEVSQUNUSVZFIHBuZ19maWxlbmFtZSBwbmdfZmlsZW5hbWUpKSkgKHBu +Z19sYXllciAoY2FyIChnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIgcG5nKSkpICh4Y2ZfbGF5 +ZXIgKGNhciAoZ2ltcC1sYXllci1uZXctZnJvbS1kcmF3YWJsZSBwbmdfbGF5ZXIgaW1nKSkpKSAo +Z2ltcC1pbWFnZS1hZGQtbGF5ZXIgaW1nIHhjZl9sYXllciAtMSkgKGdpbXAtZHJhd2FibGUtc2V0 +LW5hbWUgeGNmX2xheWVyIGxheWVyX25hbWUpKQpFdmFsOiAoY2FyIChmaWxlLXBuZy1sb2FkIFJV +Ti1OT05JTlRFUkFDVElWRSBwbmdfZmlsZW5hbWUgcG5nX2ZpbGVuYW1lKSkKRXZhbDogY2FyCkV2 +YWw6IChmaWxlLXBuZy1sb2FkIFJVTi1OT05JTlRFUkFDVElWRSBwbmdfZmlsZW5hbWUgcG5nX2Zp +bGVuYW1lKQpFdmFsOiBmaWxlLXBuZy1sb2FkCkV2YWw6IFJVTi1OT05JTlRFUkFDVElWRQpFdmFs +OiBwbmdfZmlsZW5hbWUKRXZhbDogcG5nX2ZpbGVuYW1lCkFwcGx5IHRvOiAoMSAiL3RtcC9naW1w +LW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIu +cG5nIikKRXZhbDogKGFwcGx5IGdpbXAtcHJvYy1kYi1jYWxsIChjb25zICJmaWxlLXBuZy1sb2Fk +IiB4KSkKRXZhbDogYXBwbHkKRXZhbDogZ2ltcC1wcm9jLWRiLWNhbGwKRXZhbDogKGNvbnMgImZp +bGUtcG5nLWxvYWQiIHgpCkV2YWw6IGNvbnMKRXZhbDogImZpbGUtcG5nLWxvYWQiCkV2YWw6IHgK +QXBwbHkgdG86ICgiZmlsZS1wbmctbG9hZCIgKDEgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xp +ZGUyLnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBuZyIpKQpBcHBseSB0bzog +KCM8Rk9SRUlHTiBQUk9DRURVUkUgOTQ4OTYzODc0NjA4NDg+ICgiZmlsZS1wbmctbG9hZCIgMSAi +L3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTIucG5nIiAiL3RtcC9naW1wLW91dC1xaW50Zmc4 +eS9TbGlkZTIucG5nIikpCkFwcGx5IHRvOiAoImZpbGUtcG5nLWxvYWQiIDEgIi90bXAvZ2ltcC1v +dXQtcWludGZnOHkvU2xpZGUyLnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUyLnBu +ZyIpCkFwcGx5IHRvOiAoKDMpKQpFdmFsOiAoY2FyIChnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5 +ZXIgcG5nKSkKRXZhbDogY2FyCkV2YWw6IChnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIgcG5n +KQpFdmFsOiBnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIKRXZhbDogcG5nCkFwcGx5IHRvOiAo +MykKRXZhbDogKGFwcGx5IGdpbXAtcHJvYy1kYi1jYWxsIChjb25zICJnaW1wLWltYWdlLWdldC1h +Y3RpdmUtbGF5ZXIiIHgpKQpFdmFsOiBhcHBseQpFdmFsOiBnaW1wLXByb2MtZGItY2FsbApFdmFs +OiAoY29ucyAiZ2ltcC1pbWFnZS1nZXQtYWN0aXZlLWxheWVyIiB4KQpFdmFsOiBjb25zCkV2YWw6 +ICJnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIiCkV2YWw6IHgKQXBwbHkgdG86ICgiZ2ltcC1p +bWFnZS1nZXQtYWN0aXZlLWxheWVyIiAoMykpCkFwcGx5IHRvOiAoIzxGT1JFSUdOIFBST0NFRFVS +RSA5NDg5NjM4NzQ2MDg0OD4gKCJnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIiIDMpKQpBcHBs +eSB0bzogKCJnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIiIDMpCkFwcGx5IHRvOiAoKDYpKQpF +dmFsOiAoY2FyIChnaW1wLWxheWVyLW5ldy1mcm9tLWRyYXdhYmxlIHBuZ19sYXllciBpbWcpKQpF +dmFsOiBjYXIKRXZhbDogKGdpbXAtbGF5ZXItbmV3LWZyb20tZHJhd2FibGUgcG5nX2xheWVyIGlt +ZykKRXZhbDogZ2ltcC1sYXllci1uZXctZnJvbS1kcmF3YWJsZQpFdmFsOiBwbmdfbGF5ZXIKRXZh +bDogaW1nCkFwcGx5IHRvOiAoNiAxKQpFdmFsOiAoYXBwbHkgZ2ltcC1wcm9jLWRiLWNhbGwgKGNv +bnMgImdpbXAtbGF5ZXItbmV3LWZyb20tZHJhd2FibGUiIHgpKQpFdmFsOiBhcHBseQpFdmFsOiBn +aW1wLXByb2MtZGItY2FsbApFdmFsOiAoY29ucyAiZ2ltcC1sYXllci1uZXctZnJvbS1kcmF3YWJs +ZSIgeCkKRXZhbDogY29ucwpFdmFsOiAiZ2ltcC1sYXllci1uZXctZnJvbS1kcmF3YWJsZSIKRXZh +bDogeApBcHBseSB0bzogKCJnaW1wLWxheWVyLW5ldy1mcm9tLWRyYXdhYmxlIiAoNiAxKSkKQXBw +bHkgdG86ICgjPEZPUkVJR04gUFJPQ0VEVVJFIDk0ODk2Mzg3NDYwODQ4PiAoImdpbXAtbGF5ZXIt +bmV3LWZyb20tZHJhd2FibGUiIDYgMSkpCkFwcGx5IHRvOiAoImdpbXAtbGF5ZXItbmV3LWZyb20t +ZHJhd2FibGUiIDYgMSkKQXBwbHkgdG86ICgoNykpCkV2YWw6IChnaW1wLWltYWdlLWFkZC1sYXll +ciBpbWcgeGNmX2xheWVyIC0xKQpFdmFsOiBnaW1wLWltYWdlLWFkZC1sYXllcgpFdmFsOiBpbWcK +RXZhbDogeGNmX2xheWVyCkV2YWw6IC0xCkFwcGx5IHRvOiAoMSA3IC0xKQpFdmFsOiAoYXBwbHkg +Z2ltcC1wcm9jLWRiLWNhbGwgKGNvbnMgImdpbXAtaW1hZ2UtYWRkLWxheWVyIiB4KSkKRXZhbDog +YXBwbHkKRXZhbDogZ2ltcC1wcm9jLWRiLWNhbGwKRXZhbDogKGNvbnMgImdpbXAtaW1hZ2UtYWRk +LWxheWVyIiB4KQpFdmFsOiBjb25zCkV2YWw6ICJnaW1wLWltYWdlLWFkZC1sYXllciIKRXZhbDog +eApBcHBseSB0bzogKCJnaW1wLWltYWdlLWFkZC1sYXllciIgKDEgNyAtMSkpCkFwcGx5IHRvOiAo +IzxGT1JFSUdOIFBST0NFRFVSRSA5NDg5NjM4NzQ2MDg0OD4gKCJnaW1wLWltYWdlLWFkZC1sYXll +ciIgMSA3IC0xKSkKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1hZGQtbGF5ZXIiIDEgNyAtMSkKRXZh +bDogKGdpbXAtZHJhd2FibGUtc2V0LW5hbWUgeGNmX2xheWVyIGxheWVyX25hbWUpCkV2YWw6IGdp +bXAtZHJhd2FibGUtc2V0LW5hbWUKRXZhbDogeGNmX2xheWVyCkV2YWw6IGxheWVyX25hbWUKQXBw +bHkgdG86ICg3ICJTbGlkZTIiKQpFdmFsOiAoYXBwbHkgZ2ltcC1wcm9jLWRiLWNhbGwgKGNvbnMg +ImdpbXAtZHJhd2FibGUtc2V0LW5hbWUiIHgpKQpFdmFsOiBhcHBseQpFdmFsOiBnaW1wLXByb2Mt +ZGItY2FsbApFdmFsOiAoY29ucyAiZ2ltcC1kcmF3YWJsZS1zZXQtbmFtZSIgeCkKRXZhbDogY29u +cwpFdmFsOiAiZ2ltcC1kcmF3YWJsZS1zZXQtbmFtZSIKRXZhbDogeApBcHBseSB0bzogKCJnaW1w +LWRyYXdhYmxlLXNldC1uYW1lIiAoNyAiU2xpZGUyIikpCkFwcGx5IHRvOiAoIzxGT1JFSUdOIFBS +T0NFRFVSRSA5NDg5NjM4NzQ2MDg0OD4gKCJnaW1wLWRyYXdhYmxlLXNldC1uYW1lIiA3ICJTbGlk +ZTIiKSkKQXBwbHkgdG86ICgiZ2ltcC1kcmF3YWJsZS1zZXQtbmFtZSIgNyAiU2xpZGUyIikKRXZh +bDogKGFwcGx5IG1hcCAoY29ucyBwcm9jIGNkcnMpKQpFdmFsOiBhcHBseQpFdmFsOiBtYXAKRXZh +bDogKGNvbnMgcHJvYyBjZHJzKQpFdmFsOiBjb25zCkV2YWw6IHByb2MKRXZhbDogY2RycwpBcHBs +eSB0bzogKCM8Q0xPU1VSRT4gKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIg +LiAiU2xpZGUxIikpKSkKQXBwbHkgdG86ICgjPENMT1NVUkU+ICgjPENMT1NVUkU+ICgoIi90bXAv +Z2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIgLiAiU2xpZGUxIikpKSkKQXBwbHkgdG86ICgj +PENMT1NVUkU+ICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIgLiAiU2xpZGUx +IikpKQpFdmFsOiAoaWYgKG51bGw/IGxpc3RzKSAoYXBwbHkgcHJvYykgKGlmIChudWxsPyAoY2Fy +IGxpc3RzKSkgJygpIChsZXQqICgodW56IChhcHBseSB1bnppcDEtd2l0aC1jZHIgbGlzdHMpKSAo +Y2FycyAoY2FyIHVueikpIChjZHJzIChjZHIgdW56KSkpIChjb25zIChhcHBseSBwcm9jIGNhcnMp +IChhcHBseSBtYXAgKGNvbnMgcHJvYyBjZHJzKSkpKSkpCkV2YWw6IChudWxsPyBsaXN0cykKRXZh +bDogbnVsbD8KRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4 +eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkpKQpFdmFsOiAoaWYgKG51bGw/IChjYXIgbGlzdHMp +KSAnKCkgKGxldCogKCh1bnogKGFwcGx5IHVuemlwMS13aXRoLWNkciBsaXN0cykpIChjYXJzIChj +YXIgdW56KSkgKGNkcnMgKGNkciB1bnopKSkgKGNvbnMgKGFwcGx5IHByb2MgY2FycykgKGFwcGx5 +IG1hcCAoY29ucyBwcm9jIGNkcnMpKSkpKQpFdmFsOiAobnVsbD8gKGNhciBsaXN0cykpCkV2YWw6 +IG51bGw/CkV2YWw6IChjYXIgbGlzdHMpCkV2YWw6IGNhcgpFdmFsOiBsaXN0cwpBcHBseSB0bzog +KCgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkpCkFw +cGx5IHRvOiAoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEi +KSkpCkV2YWw6IChsZXQqICgodW56IChhcHBseSB1bnppcDEtd2l0aC1jZHIgbGlzdHMpKSAoY2Fy +cyAoY2FyIHVueikpIChjZHJzIChjZHIgdW56KSkpIChjb25zIChhcHBseSBwcm9jIGNhcnMpIChh +cHBseSBtYXAgKGNvbnMgcHJvYyBjZHJzKSkpKQpFdmFsOiAoYXBwbHkgdW56aXAxLXdpdGgtY2Ry +IGxpc3RzKQpFdmFsOiBhcHBseQpFdmFsOiB1bnppcDEtd2l0aC1jZHIKRXZhbDogbGlzdHMKQXBw +bHkgdG86ICgjPENMT1NVUkU+ICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmci +IC4gIlNsaWRlMSIpKSkpCkFwcGx5IHRvOiAoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlk +ZTEucG5nIiAuICJTbGlkZTEiKSkpCkV2YWw6ICh1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlIGxp +c3RzICcoKSAnKCkpCkV2YWw6IHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUKRXZhbDogbGlzdHMK +RXZhbDogJygpCkV2YWw6ICcoKQpBcHBseSB0bzogKCgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5 +L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkgKCkgKCkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMp +IChjb25zIGNhcnMgY2RycykgKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBs +aXN0cykpKSAodW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNh +cnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKSkKRXZhbDogKG51bGw/ +IGxpc3RzKQpFdmFsOiBudWxsPwpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoKCIvdG1wL2dpbXAt +b3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkpCkV2YWw6IChsZXQgKChjYXIx +IChjYWFyIGxpc3RzKSkgKGNkcjEgKGNkYXIgbGlzdHMpKSkgKHVuemlwMS13aXRoLWNkci1pdGVy +YXRpdmUgKGNkciBsaXN0cykgKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEpKSAoYXBwZW5kIGNkcnMg +KGxpc3QgY2RyMSkpKSkKRXZhbDogKGNhYXIgbGlzdHMpCkV2YWw6IGNhYXIKRXZhbDogbGlzdHMK +QXBwbHkgdG86ICgoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlk +ZTEiKSkpKQpFdmFsOiAoY2FyIChjYXIgeCkpCkV2YWw6IGNhcgpFdmFsOiAoY2FyIHgpCkV2YWw6 +IGNhcgpFdmFsOiB4CkFwcGx5IHRvOiAoKCgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUx +LnBuZyIgLiAiU2xpZGUxIikpKSkKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5 +L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkKRXZhbDogKGNkYXIgbGlzdHMpCkV2YWw6IGNkYXIK +RXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEu +cG5nIiAuICJTbGlkZTEiKSkpKQpFdmFsOiAoY2RyIChjYXIgeCkpCkV2YWw6IGNkcgpFdmFsOiAo +Y2FyIHgpCkV2YWw6IGNhcgpFdmFsOiB4CkFwcGx5IHRvOiAoKCgoIi90bXAvZ2ltcC1vdXQtcWlu +dGZnOHkvU2xpZGUxLnBuZyIgLiAiU2xpZGUxIikpKSkKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAt +b3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkKRXZhbDogKHVuemlwMS13aXRo +LWNkci1pdGVyYXRpdmUgKGNkciBsaXN0cykgKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEpKSAoYXBw +ZW5kIGNkcnMgKGxpc3QgY2RyMSkpKQpFdmFsOiB1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlCkV2 +YWw6IChjZHIgbGlzdHMpCkV2YWw6IGNkcgpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoKCIvdG1w +L2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSkpCkV2YWw6IChhcHBl +bmQgY2FycyAobGlzdCBjYXIxKSkKRXZhbDogYXBwZW5kCkV2YWw6IGNhcnMKRXZhbDogKGxpc3Qg +Y2FyMSkKRXZhbDogbGlzdApFdmFsOiBjYXIxCkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0LXFp +bnRmZzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKQpFdmFsOiB4CkFwcGx5IHRvOiAoKCkgKCgi +L3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkpCkV2YWw6IChh +cHBlbmQgY2RycyAobGlzdCBjZHIxKSkKRXZhbDogYXBwZW5kCkV2YWw6IGNkcnMKRXZhbDogKGxp +c3QgY2RyMSkKRXZhbDogbGlzdApFdmFsOiBjZHIxCkFwcGx5IHRvOiAoKCkpCkV2YWw6IHgKQXBw +bHkgdG86ICgoKSAoKCkpKQpBcHBseSB0bzogKCgpICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkv +U2xpZGUxLnBuZyIgLiAiU2xpZGUxIikpICgoKSkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChj +b25zIGNhcnMgY2RycykgKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0 +cykpKSAodW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMg +KGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKSkKRXZhbDogKG51bGw/IGxp +c3RzKQpFdmFsOiBudWxsPwpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgpKQpFdmFsOiAoY29ucyBj +YXJzIGNkcnMpCkV2YWw6IGNvbnMKRXZhbDogY2FycwpFdmFsOiBjZHJzCkFwcGx5IHRvOiAoKCgi +L3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkgKCgpKSkKRXZh +bDogKGNhciB1bnopCkV2YWw6IGNhcgpFdmFsOiB1bnoKQXBwbHkgdG86ICgoKCgiL3RtcC9naW1w +LW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIiAuICJTbGlkZTEiKSkgKCkpKQpFdmFsOiAoY2RyIHVu +eikKRXZhbDogY2RyCkV2YWw6IHVuegpBcHBseSB0bzogKCgoKCIvdG1wL2dpbXAtb3V0LXFpbnRm +Zzh5L1NsaWRlMS5wbmciIC4gIlNsaWRlMSIpKSAoKSkpCkV2YWw6IChjb25zIChhcHBseSBwcm9j +IGNhcnMpIChhcHBseSBtYXAgKGNvbnMgcHJvYyBjZHJzKSkpCkV2YWw6IGNvbnMKRXZhbDogKGFw +cGx5IHByb2MgY2FycykKRXZhbDogYXBwbHkKRXZhbDogcHJvYwpFdmFsOiBjYXJzCkFwcGx5IHRv +OiAoIzxDTE9TVVJFPiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciIC4gIlNs +aWRlMSIpKSkKQXBwbHkgdG86ICgoIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIg +LiAiU2xpZGUxIikpCkV2YWw6IChwbmctdG8tbGF5ZXIgaW1nIChjYXIgbmFtZXMpIChjZHIgbmFt +ZXMpKQpFdmFsOiBwbmctdG8tbGF5ZXIKRXZhbDogaW1nCkV2YWw6IChjYXIgbmFtZXMpCkV2YWw6 +IGNhcgpFdmFsOiBuYW1lcwpBcHBseSB0bzogKCgiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlk +ZTEucG5nIiAuICJTbGlkZTEiKSkKRXZhbDogKGNkciBuYW1lcykKRXZhbDogY2RyCkV2YWw6IG5h +bWVzCkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L1NsaWRlMS5wbmciIC4gIlNs +aWRlMSIpKQpBcHBseSB0bzogKDEgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIg +IlNsaWRlMSIpCkV2YWw6IChsZXQqICgocG5nIChjYXIgKGZpbGUtcG5nLWxvYWQgUlVOLU5PTklO +VEVSQUNUSVZFIHBuZ19maWxlbmFtZSBwbmdfZmlsZW5hbWUpKSkgKHBuZ19sYXllciAoY2FyIChn +aW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIgcG5nKSkpICh4Y2ZfbGF5ZXIgKGNhciAoZ2ltcC1s +YXllci1uZXctZnJvbS1kcmF3YWJsZSBwbmdfbGF5ZXIgaW1nKSkpKSAoZ2ltcC1pbWFnZS1hZGQt +bGF5ZXIgaW1nIHhjZl9sYXllciAtMSkgKGdpbXAtZHJhd2FibGUtc2V0LW5hbWUgeGNmX2xheWVy +IGxheWVyX25hbWUpKQpFdmFsOiAoY2FyIChmaWxlLXBuZy1sb2FkIFJVTi1OT05JTlRFUkFDVElW +RSBwbmdfZmlsZW5hbWUgcG5nX2ZpbGVuYW1lKSkKRXZhbDogY2FyCkV2YWw6IChmaWxlLXBuZy1s +b2FkIFJVTi1OT05JTlRFUkFDVElWRSBwbmdfZmlsZW5hbWUgcG5nX2ZpbGVuYW1lKQpFdmFsOiBm +aWxlLXBuZy1sb2FkCkV2YWw6IFJVTi1OT05JTlRFUkFDVElWRQpFdmFsOiBwbmdfZmlsZW5hbWUK +RXZhbDogcG5nX2ZpbGVuYW1lCkFwcGx5IHRvOiAoMSAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9T +bGlkZTEucG5nIiAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIikKRXZhbDogKGFw +cGx5IGdpbXAtcHJvYy1kYi1jYWxsIChjb25zICJmaWxlLXBuZy1sb2FkIiB4KSkKRXZhbDogYXBw +bHkKRXZhbDogZ2ltcC1wcm9jLWRiLWNhbGwKRXZhbDogKGNvbnMgImZpbGUtcG5nLWxvYWQiIHgp +CkV2YWw6IGNvbnMKRXZhbDogImZpbGUtcG5nLWxvYWQiCkV2YWw6IHgKQXBwbHkgdG86ICgiZmls +ZS1wbmctbG9hZCIgKDEgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIgIi90bXAv +Z2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIpKQpBcHBseSB0bzogKCM8Rk9SRUlHTiBQUk9D +RURVUkUgOTQ4OTYzODc0NjA4NDg+ICgiZmlsZS1wbmctbG9hZCIgMSAiL3RtcC9naW1wLW91dC1x +aW50Zmc4eS9TbGlkZTEucG5nIiAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS9TbGlkZTEucG5nIikp +CkFwcGx5IHRvOiAoImZpbGUtcG5nLWxvYWQiIDEgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xp +ZGUxLnBuZyIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvU2xpZGUxLnBuZyIpCkFwcGx5IHRvOiAo +KDQpKQpFdmFsOiAoY2FyIChnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIgcG5nKSkKRXZhbDog +Y2FyCkV2YWw6IChnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIgcG5nKQpFdmFsOiBnaW1wLWlt +YWdlLWdldC1hY3RpdmUtbGF5ZXIKRXZhbDogcG5nCkFwcGx5IHRvOiAoNCkKRXZhbDogKGFwcGx5 +IGdpbXAtcHJvYy1kYi1jYWxsIChjb25zICJnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIiIHgp +KQpFdmFsOiBhcHBseQpFdmFsOiBnaW1wLXByb2MtZGItY2FsbApFdmFsOiAoY29ucyAiZ2ltcC1p +bWFnZS1nZXQtYWN0aXZlLWxheWVyIiB4KQpFdmFsOiBjb25zCkV2YWw6ICJnaW1wLWltYWdlLWdl +dC1hY3RpdmUtbGF5ZXIiCkV2YWw6IHgKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1nZXQtYWN0aXZl +LWxheWVyIiAoNCkpCkFwcGx5IHRvOiAoIzxGT1JFSUdOIFBST0NFRFVSRSA5NDg5NjM4NzQ2MDg0 +OD4gKCJnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIiIDQpKQpBcHBseSB0bzogKCJnaW1wLWlt +YWdlLWdldC1hY3RpdmUtbGF5ZXIiIDQpCkFwcGx5IHRvOiAoKDkpKQpFdmFsOiAoY2FyIChnaW1w +LWxheWVyLW5ldy1mcm9tLWRyYXdhYmxlIHBuZ19sYXllciBpbWcpKQpFdmFsOiBjYXIKRXZhbDog +KGdpbXAtbGF5ZXItbmV3LWZyb20tZHJhd2FibGUgcG5nX2xheWVyIGltZykKRXZhbDogZ2ltcC1s +YXllci1uZXctZnJvbS1kcmF3YWJsZQpFdmFsOiBwbmdfbGF5ZXIKRXZhbDogaW1nCkFwcGx5IHRv +OiAoOSAxKQpFdmFsOiAoYXBwbHkgZ2ltcC1wcm9jLWRiLWNhbGwgKGNvbnMgImdpbXAtbGF5ZXIt +bmV3LWZyb20tZHJhd2FibGUiIHgpKQpFdmFsOiBhcHBseQpFdmFsOiBnaW1wLXByb2MtZGItY2Fs +bApFdmFsOiAoY29ucyAiZ2ltcC1sYXllci1uZXctZnJvbS1kcmF3YWJsZSIgeCkKRXZhbDogY29u +cwpFdmFsOiAiZ2ltcC1sYXllci1uZXctZnJvbS1kcmF3YWJsZSIKRXZhbDogeApBcHBseSB0bzog +KCJnaW1wLWxheWVyLW5ldy1mcm9tLWRyYXdhYmxlIiAoOSAxKSkKQXBwbHkgdG86ICgjPEZPUkVJ +R04gUFJPQ0VEVVJFIDk0ODk2Mzg3NDYwODQ4PiAoImdpbXAtbGF5ZXItbmV3LWZyb20tZHJhd2Fi +bGUiIDkgMSkpCkFwcGx5IHRvOiAoImdpbXAtbGF5ZXItbmV3LWZyb20tZHJhd2FibGUiIDkgMSkK +QXBwbHkgdG86ICgoMTApKQpFdmFsOiAoZ2ltcC1pbWFnZS1hZGQtbGF5ZXIgaW1nIHhjZl9sYXll +ciAtMSkKRXZhbDogZ2ltcC1pbWFnZS1hZGQtbGF5ZXIKRXZhbDogaW1nCkV2YWw6IHhjZl9sYXll +cgpFdmFsOiAtMQpBcHBseSB0bzogKDEgMTAgLTEpCkV2YWw6IChhcHBseSBnaW1wLXByb2MtZGIt +Y2FsbCAoY29ucyAiZ2ltcC1pbWFnZS1hZGQtbGF5ZXIiIHgpKQpFdmFsOiBhcHBseQpFdmFsOiBn +aW1wLXByb2MtZGItY2FsbApFdmFsOiAoY29ucyAiZ2ltcC1pbWFnZS1hZGQtbGF5ZXIiIHgpCkV2 +YWw6IGNvbnMKRXZhbDogImdpbXAtaW1hZ2UtYWRkLWxheWVyIgpFdmFsOiB4CkFwcGx5IHRvOiAo +ImdpbXAtaW1hZ2UtYWRkLWxheWVyIiAoMSAxMCAtMSkpCkFwcGx5IHRvOiAoIzxGT1JFSUdOIFBS +T0NFRFVSRSA5NDg5NjM4NzQ2MDg0OD4gKCJnaW1wLWltYWdlLWFkZC1sYXllciIgMSAxMCAtMSkp +CkFwcGx5IHRvOiAoImdpbXAtaW1hZ2UtYWRkLWxheWVyIiAxIDEwIC0xKQpFdmFsOiAoZ2ltcC1k +cmF3YWJsZS1zZXQtbmFtZSB4Y2ZfbGF5ZXIgbGF5ZXJfbmFtZSkKRXZhbDogZ2ltcC1kcmF3YWJs +ZS1zZXQtbmFtZQpFdmFsOiB4Y2ZfbGF5ZXIKRXZhbDogbGF5ZXJfbmFtZQpBcHBseSB0bzogKDEw +ICJTbGlkZTEiKQpFdmFsOiAoYXBwbHkgZ2ltcC1wcm9jLWRiLWNhbGwgKGNvbnMgImdpbXAtZHJh +d2FibGUtc2V0LW5hbWUiIHgpKQpFdmFsOiBhcHBseQpFdmFsOiBnaW1wLXByb2MtZGItY2FsbApF +dmFsOiAoY29ucyAiZ2ltcC1kcmF3YWJsZS1zZXQtbmFtZSIgeCkKRXZhbDogY29ucwpFdmFsOiAi +Z2ltcC1kcmF3YWJsZS1zZXQtbmFtZSIKRXZhbDogeApBcHBseSB0bzogKCJnaW1wLWRyYXdhYmxl +LXNldC1uYW1lIiAoMTAgIlNsaWRlMSIpKQpBcHBseSB0bzogKCM8Rk9SRUlHTiBQUk9DRURVUkUg +OTQ4OTYzODc0NjA4NDg+ICgiZ2ltcC1kcmF3YWJsZS1zZXQtbmFtZSIgMTAgIlNsaWRlMSIpKQpB +cHBseSB0bzogKCJnaW1wLWRyYXdhYmxlLXNldC1uYW1lIiAxMCAiU2xpZGUxIikKRXZhbDogKGFw +cGx5IG1hcCAoY29ucyBwcm9jIGNkcnMpKQpFdmFsOiBhcHBseQpFdmFsOiBtYXAKRXZhbDogKGNv +bnMgcHJvYyBjZHJzKQpFdmFsOiBjb25zCkV2YWw6IHByb2MKRXZhbDogY2RycwpBcHBseSB0bzog +KCM8Q0xPU1VSRT4gKCgpKSkKQXBwbHkgdG86ICgjPENMT1NVUkU+ICgjPENMT1NVUkU+ICgpKSkK +QXBwbHkgdG86ICgjPENMT1NVUkU+ICgpKQpFdmFsOiAoaWYgKG51bGw/IGxpc3RzKSAoYXBwbHkg +cHJvYykgKGlmIChudWxsPyAoY2FyIGxpc3RzKSkgJygpIChsZXQqICgodW56IChhcHBseSB1bnpp +cDEtd2l0aC1jZHIgbGlzdHMpKSAoY2FycyAoY2FyIHVueikpIChjZHJzIChjZHIgdW56KSkpIChj +b25zIChhcHBseSBwcm9jIGNhcnMpIChhcHBseSBtYXAgKGNvbnMgcHJvYyBjZHJzKSkpKSkpCkV2 +YWw6IChudWxsPyBsaXN0cykKRXZhbDogbnVsbD8KRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCkp +KQpFdmFsOiAoaWYgKG51bGw/IChjYXIgbGlzdHMpKSAnKCkgKGxldCogKCh1bnogKGFwcGx5IHVu +emlwMS13aXRoLWNkciBsaXN0cykpIChjYXJzIChjYXIgdW56KSkgKGNkcnMgKGNkciB1bnopKSkg +KGNvbnMgKGFwcGx5IHByb2MgY2FycykgKGFwcGx5IG1hcCAoY29ucyBwcm9jIGNkcnMpKSkpKQpF +dmFsOiAobnVsbD8gKGNhciBsaXN0cykpCkV2YWw6IG51bGw/CkV2YWw6IChjYXIgbGlzdHMpCkV2 +YWw6IGNhcgpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoKSkpCkFwcGx5IHRvOiAoKCkpCkV2YWw6 +ICcoKQpBcHBseSB0bzogKCgjdCkgKCkpCkFwcGx5IHRvOiAoKCN0KSAoKCN0KSkpCkV2YWw6IChn +aW1wLWltYWdlLXJlc2l6ZS10by1sYXllcnMgaW1nKQpFdmFsOiBnaW1wLWltYWdlLXJlc2l6ZS10 +by1sYXllcnMKRXZhbDogaW1nCkFwcGx5IHRvOiAoMSkKRXZhbDogKGFwcGx5IGdpbXAtcHJvYy1k +Yi1jYWxsIChjb25zICJnaW1wLWltYWdlLXJlc2l6ZS10by1sYXllcnMiIHgpKQpFdmFsOiBhcHBs +eQpFdmFsOiBnaW1wLXByb2MtZGItY2FsbApFdmFsOiAoY29ucyAiZ2ltcC1pbWFnZS1yZXNpemUt +dG8tbGF5ZXJzIiB4KQpFdmFsOiBjb25zCkV2YWw6ICJnaW1wLWltYWdlLXJlc2l6ZS10by1sYXll +cnMiCkV2YWw6IHgKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1yZXNpemUtdG8tbGF5ZXJzIiAoMSkp +CkFwcGx5IHRvOiAoIzxGT1JFSUdOIFBST0NFRFVSRSA5NDg5NjM4NzQ2MDg0OD4gKCJnaW1wLWlt +YWdlLXJlc2l6ZS10by1sYXllcnMiIDEpKQpBcHBseSB0bzogKCJnaW1wLWltYWdlLXJlc2l6ZS10 +by1sYXllcnMiIDEpCkV2YWw6IChnaW1wLWltYWdlLXVuZG8tZW5hYmxlIGltZykKRXZhbDogZ2lt +cC1pbWFnZS11bmRvLWVuYWJsZQpFdmFsOiBpbWcKQXBwbHkgdG86ICgxKQpFdmFsOiAoYXBwbHkg +Z2ltcC1wcm9jLWRiLWNhbGwgKGNvbnMgImdpbXAtaW1hZ2UtdW5kby1lbmFibGUiIHgpKQpFdmFs +OiBhcHBseQpFdmFsOiBnaW1wLXByb2MtZGItY2FsbApFdmFsOiAoY29ucyAiZ2ltcC1pbWFnZS11 +bmRvLWVuYWJsZSIgeCkKRXZhbDogY29ucwpFdmFsOiAiZ2ltcC1pbWFnZS11bmRvLWVuYWJsZSIK +RXZhbDogeApBcHBseSB0bzogKCJnaW1wLWltYWdlLXVuZG8tZW5hYmxlIiAoMSkpCkFwcGx5IHRv +OiAoIzxGT1JFSUdOIFBST0NFRFVSRSA5NDg5NjM4NzQ2MDg0OD4gKCJnaW1wLWltYWdlLXVuZG8t +ZW5hYmxlIiAxKSkKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS11bmRvLWVuYWJsZSIgMSkKRXZhbDog +KGdpbXAtZmlsZS1zYXZlIFJVTi1OT05JTlRFUkFDVElWRSBpbWcgKGNhciAoZ2ltcC1pbWFnZS1n +ZXQtYWN0aXZlLWxheWVyIGltZykpICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L3Rlc3Quc3ZnLnhj +ZiIgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvdGVzdC5zdmcueGNmIikKRXZhbDogZ2ltcC1maWxl +LXNhdmUKRXZhbDogUlVOLU5PTklOVEVSQUNUSVZFCkV2YWw6IGltZwpFdmFsOiAoY2FyIChnaW1w +LWltYWdlLWdldC1hY3RpdmUtbGF5ZXIgaW1nKSkKRXZhbDogY2FyCkV2YWw6IChnaW1wLWltYWdl +LWdldC1hY3RpdmUtbGF5ZXIgaW1nKQpFdmFsOiBnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIK +RXZhbDogaW1nCkFwcGx5IHRvOiAoMSkKRXZhbDogKGFwcGx5IGdpbXAtcHJvYy1kYi1jYWxsIChj +b25zICJnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIiIHgpKQpFdmFsOiBhcHBseQpFdmFsOiBn +aW1wLXByb2MtZGItY2FsbApFdmFsOiAoY29ucyAiZ2ltcC1pbWFnZS1nZXQtYWN0aXZlLWxheWVy +IiB4KQpFdmFsOiBjb25zCkV2YWw6ICJnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIiCkV2YWw6 +IHgKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1nZXQtYWN0aXZlLWxheWVyIiAoMSkpCkFwcGx5IHRv +OiAoIzxGT1JFSUdOIFBST0NFRFVSRSA5NDg5NjM4NzQ2MDg0OD4gKCJnaW1wLWltYWdlLWdldC1h +Y3RpdmUtbGF5ZXIiIDEpKQpBcHBseSB0bzogKCJnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIi +IDEpCkFwcGx5IHRvOiAoKDEwKSkKRXZhbDogIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvdGVzdC5z +dmcueGNmIgpFdmFsOiAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS90ZXN0LnN2Zy54Y2YiCkFwcGx5 +IHRvOiAoMSAxIDEwICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L3Rlc3Quc3ZnLnhjZiIgIi90bXAv +Z2ltcC1vdXQtcWludGZnOHkvdGVzdC5zdmcueGNmIikKRXZhbDogKGFwcGx5IGdpbXAtcHJvYy1k +Yi1jYWxsIChjb25zICJnaW1wLWZpbGUtc2F2ZSIgeCkpCkV2YWw6IGFwcGx5CkV2YWw6IGdpbXAt +cHJvYy1kYi1jYWxsCkV2YWw6IChjb25zICJnaW1wLWZpbGUtc2F2ZSIgeCkKRXZhbDogY29ucwpF +dmFsOiAiZ2ltcC1maWxlLXNhdmUiCkV2YWw6IHgKQXBwbHkgdG86ICgiZ2ltcC1maWxlLXNhdmUi +ICgxIDEgMTAgIi90bXAvZ2ltcC1vdXQtcWludGZnOHkvdGVzdC5zdmcueGNmIiAiL3RtcC9naW1w +LW91dC1xaW50Zmc4eS90ZXN0LnN2Zy54Y2YiKSkKQXBwbHkgdG86ICgjPEZPUkVJR04gUFJPQ0VE +VVJFIDk0ODk2Mzg3NDYwODQ4PiAoImdpbXAtZmlsZS1zYXZlIiAxIDEgMTAgIi90bXAvZ2ltcC1v +dXQtcWludGZnOHkvdGVzdC5zdmcueGNmIiAiL3RtcC9naW1wLW91dC1xaW50Zmc4eS90ZXN0LnN2 +Zy54Y2YiKSkKQXBwbHkgdG86ICgiZ2ltcC1maWxlLXNhdmUiIDEgMSAxMCAiL3RtcC9naW1wLW91 +dC1xaW50Zmc4eS90ZXN0LnN2Zy54Y2YiICIvdG1wL2dpbXAtb3V0LXFpbnRmZzh5L3Rlc3Quc3Zn +LnhjZiIpCkdpdmVzOiAoI3QpCnRzPiAKRXZhbDogKGdpbXAtcXVpdCAwKQpFdmFsOiBnaW1wLXF1 +aXQKRXZhbDogMApBcHBseSB0bzogKDApCkV2YWw6IChhcHBseSBnaW1wLXByb2MtZGItY2FsbCAo +Y29ucyAiZ2ltcC1xdWl0IiB4KSkKRXZhbDogYXBwbHkKRXZhbDogZ2ltcC1wcm9jLWRiLWNhbGwK +RXZhbDogKGNvbnMgImdpbXAtcXVpdCIgeCkKRXZhbDogY29ucwpFdmFsOiAiZ2ltcC1xdWl0IgpF +dmFsOiB4CkFwcGx5IHRvOiAoImdpbXAtcXVpdCIgKDApKQpBcHBseSB0bzogKCM8Rk9SRUlHTiBQ +Uk9DRURVUkUgOTQ4OTYzODc0NjA4NDg+ICgiZ2ltcC1xdWl0IiAwKSkKQXBwbHkgdG86ICgiZ2lt +cC1xdWl0IiAwKQ== + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="test.svg.xcf" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: test.svg.xcf + +Z2ltcCB4Y2YgZmlsZQAAAAPoAAAD6AAAAAAAAAARAAAAAQEAAAATAAAACELAAABCwAAAAAAAFAAA +AAQAAAAEAAAAFgAAAAQAAAABAAAAFQAAARwAAAAQZ2ltcC1pbWFnZS1ncmlkAAAAAAEAAAEAKHN0 +eWxlIHNvbGlkKQooZmdjb2xvciAoY29sb3ItcmdiYSAwLjAwMDAwMCAwLjAwMDAwMCAwLjAwMDAw +MCAxLjAwMDAwMCkpCihiZ2NvbG9yIChjb2xvci1yZ2JhIDEuMDAwMDAwIDEuMDAwMDAwIDEuMDAw +MDAwIDEuMDAwMDAwKSkKKHhzcGFjaW5nIDEwLjAwMDAwMCkKKHlzcGFjaW5nIDEwLjAwMDAwMCkK +KHNwYWNpbmctdW5pdCBpbmNoZXMpCih4b2Zmc2V0IDAuMDAwMDAwKQooeW9mZnNldCAwLjAwMDAw +MCkKKG9mZnNldC11bml0IGluY2hlcykKAAAAAAAAAAAAAAABiwAATJ4AAO1iAAAAAAAAAAAAAAPo +AAAD6AAAAAEAAAAHU2xpZGUxAAAAAAIAAAAAAAAABgAAAAQAAAD/AAAACAAAAAQAAAABAAAACQAA +AAQAAAAAAAAAHAAAAAQAAAAAAAAACgAAAAQAAAAAAAAACwAAAAQAAAAAAAAADAAAAAQAAAAAAAAA +DQAAAAQAAAAAAAAADwAAAAgAAAAAAAAAAAAAAAcAAAAEAAAAAAAAABQAAAAEAAAABAAAAAAAAAAA +AAACQgAAAAAAAAPoAAAD6AAAAAQAAAJmAABMbgAATHoAAEyGAABMkgAAAAAAAAPoAAAD6AAABnIA +AAaCAAAGkgAABqIAAAayAAAGwgAABtIAAAbiAAAG8gAABwIAAAcSAAAHIgAABzIAAAdCAAAHUgAA +B2IAAAdyAAAHggAACNUAAAtAAAANVAAAD18AABICAAAVzwAAF3sAABo8AAAaTAAAGlwAABpsAAAa +fAAAGowAABqcAAAarAAAGrwAABrMAAAa3AAAGuwAAButAAAcNAAAHf8AAB40AAAecQAAHoEAAB6R +AAAeoQAAHrEAAB7BAAAe0QAAHuEAAB7xAAAgvQAAINUAACKlAAAksQAAJMkAACThAAAm6QAAJvkA +ACv8AAAsKQAALJEAADFSAAAxYgAAMXIAADGCAAAxkgAAMwIAADMaAAA0hgAANjYAADZWAAA2dgAA +OCYAADg2AAA8jQAAPMgAAD0nAABBPgAAQU4AAEFeAABBbgAAQX4AAEGOAABBngAAQa4AAEG+AABB +zgAAQd4AAEHuAABB/gAAQg4AAEIeAABCLgAAQj4AAEJOAABCXgAAQm4AAEJ+AABCjgAAQp4AAEKu +AABCvgAAQs4AAELeAABC7gAAQv4AAEMOAABDHgAAQy4AAEM+AABDTgAAQ14AAENuAABDfgAAQ44A +AEOeAABDrgAAQ74AAEPOAABD3gAAQ+4AAEP+AABEDgAARB4AAEQuAABEPgAARE4AAEReAABEbgAA +RH4AAESOAABEngAARK4AAES+AABEzgAARN4AAETuAABE/gAARQ4AAEUeAABFLgAART4AAEVOAABF +XgAARW4AAEV+AABFjgAARZ4AAEWuAABFvgAARc4AAEXeAABF7gAARf4AAEYOAABGHgAARi4AAEY+ +AABGTgAARl4AAEZuAABGfgAARo4AAEaeAABGrgAARr4AAEbOAABG3gAARu4AAEb+AABHDgAARx4A +AEcuAABHPgAAR04AAEdeAABHbgAAR34AAEeOAABHngAAR64AAEe+AABHzgAAR94AAEfuAABH/gAA +SA4AAEgeAABILgAASD4AAEhOAABIXgAASG4AAEh+AABIjgAASJ4AAEiuAABIvgAASM4AAEjeAABI +7gAASP4AAEkOAABJHgAASS4AAEk+AABJTgAASV4AAEluAABJfgAASY4AAEmeAABJrgAASb4AAEnO +AABJ3gAASe4AAEn+AABKDgAASh4AAEouAABKPgAASk4AAEpeAABKbgAASn4AAEqOAABKngAASq4A +AEq+AABKzgAASt4AAEruAABK/gAASw4AAEseAABLLgAASz4AAEtOAABLXgAAS24AAEt+AABLjgAA +S54AAEuuAABLvgAAS84AAEveAABL7gAAS/4AAEwOAABMHgAATC4AAEw+AABMTgAATF4AAAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +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 +KACpxeG3twd82aSg49kRAACfzN+mrv18AABV+q2Vr8wAFADw5WwAROTkQwBs5AAAqf49AAEA/hEA +AQDvDd9zAACf/0oAAGr4HQC6kQABAP4GABQA8aKvAIehoYYAr6EAAKnIAAMA9RBKYWbJnwAAn9kA +AQD3BfRkAKfSSQQAFgDxYO8DyV1eyQPvXwAAqasAAgD0U/LOq6reswAAn7oAAgD22IMAHrj88q0x +ABQA8R3/RvwbG/xG/xwAAKmoAAIA/ed8AAEA+bKzAACfygACAP3pbwABAPoSUb7xFAAUAPfZyNYA +ANfI2AABAP2pqAABAO0H/k4AABLvswAAn/ocAAA3/zkAAwD8Iv86ABQA95f/kwAAlP+WAAEA/amo +AAIA5sbRT1XN4LMAAJ/lz1Nb4bgAALJ5Skis5QwAFAD3P6o9AAA9qj8AAQD9cXAAAgDnHaTXwFdo +eAAAn7JUw9eVDwAAabTUz50lAC8A/Z+yADwA/Z+yADwA/XWDAH8NEQB/EAAAfxAAAH8QAAA0APR7 +4y8iDgDA1zQABAAzAPQUtfD/ZgASqfz2+wA7APwPMCAAfw8/AH8QAAB/EAAAfxAAAO4xXAAR35wA +Ed+cAABH/EIiFAAtAO7xfAGu1AoAADj6WgACmun/mQAtAP4FAH8PfgB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/CgAAfwoAAH8KAAB/CgAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfwIkABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4Aj +ABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMA +G4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAb +gCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuA +IwAbgH8CJAAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb +/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/ +IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8j +ABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMA +G/9/EAAAfxAAAH8CAAB/DgCAfwIAAH8OAP9/EAAAfxAAAH8CAAAHgDcAB4A3AAeANwAHgDcAB4A3 +AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcA +B4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAH +gDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeA +NwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AH8CAAAH/zcAB/83AAf/NwAH/zcAB/83 +AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcA +B/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH +/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/ +NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83ACMAG/8jABv/IwAb/yMAG/8jABv/IwAb +/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jAA//LwAP/y8AD/8vAA// +LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8v +AA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8A +D/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP +/y8AD/8LAH8QAAB/EAAAIwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMA +G/8jABv/IwAb/yMAG/8jABv/IwAb/yMAD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP +/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA// +LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8v +AA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/wsAfwQA/38MAAB/EAAA +fxAAAH8EAP9/DAAAfwQA/38MAAB/EAAAfxAAAH8EAP9/DAAAO/8DADv/AwA7/wMAO/8DADv/AwA7 +/wMAO/8DADv/AwA7/wMAO/8DADv/AwA7/wMAO/8DADv/AwA7/wMAO/8vAA//LwAP/y8AD/8vAA// +LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8v +AA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8A +D/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP +/y8AD/8DAH8QAAB/EAAAO/8DADv/AwA7/wMAO/8DADv/AwA7/wMAO/8DADv/AwA7/wMAO/8DADv/ +AwA7/wMAO/8DADv/AwA7/wMAO/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8v +AA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8A +D/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP +/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8DAH8QAAB/EAAAfxAAAH8Q +AAB/BDsA+gIsXYChADYA+y99uvH/Av80APwrjN3/Bv8xAPwFVsD/Cf8vAPwCW9z/C/8uAP1E0v8N +/ywA/BCZ/v8O/ysA/UXn/xD/KQD8AXj8/xH/KAD9B6n/E/8nAP0KuP8U/yYA/QvB/xX/JQD9A7D/ +Fv8lAP6Q/xf/JAD+Wv8Y/yMA/Rzu/xj/IwD+vf8Z/yIA/lP/Gv8hAP0F2v8a/yEA/mP/G/8hAP7N +/xv/IAD+PP8c/yAA/pj/HP8fAP0B4/8c/x8A/jD/Hf8fAP5p/x3/HwD+lf8d/x8A/sH/Hf8fAP7c +/x3/HwD+6/8d/x8A/vj/Hf8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB// +HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/zkA/YCCgAKANAD9gIKAB4AxAP2HgYAKgC4A/ouA +DoAsAP6DgBCAKgAUgCgA/oKAFIAmAP6DgBaAJAD9gIGAF4AjAP6CgBmAIQAdgCAAHoAfAP6CgB2A +HgAggB0AIYAcAP6BgCCAGwD+gYAcgPqBlq7A0AAZAP6BgBqA+5e+3fj/Av8ZAP6CgBiA/JXG7v8G +/xgA/oOAFoD8gqvg/wn/FwAXgPyBre7/C/8XABaA/aLp/w3/FgAVgPyIzP7/Dv8VAP6HgBOA/aLz +/xD/FQAUgP28/v8R/xQA/oKAEYD9g9T/E/8UABKA/YXc/xT/EwASgP2F4P8V/xMAEYD9gdj/Fv8S +ABKA/sj/F/8SABGA/q3/GP8RABGA/Y73/xj/EQD+gYAPgP7e/xn/EQAQgP6p/xr/EAD+hoAOgP2C +7f8a/xAAEID+sf8b/xAAEID+5v8b/xAAD4D+nv8c/w8A/oWADoD+zP8c/w8AEID+8f8c/w8AD4D+ +mP8d/w8AD4D+tP8d/w8AD4D+yv8d/w8AD4D+4P8d/w8AD4D+7v8d/w8AD4D+9f8d/w8AD4D+/P8d +/w8AD4Af/w8AD4Af/w8AD4Af/w8AD4Af/w8AD4Af/w8AD4Af/w8AD4Af/w8AD4Af/w8AD4Af/w8A +D4Af/w8AD4Af/w8AD4Af/w8AD4Af/w8AD4Af/w8AD4Af/w8AD4Af/w8AD4Af/38QAAA5APkWO1x+ +oMEAMwD6BCtmotX/BP8xAPsRVaLy/wj/LgD7C2S6+v8L/ywA/CWI7f8O/yoA/EK8/v8Q/ygA/TWz +/xP/JgD8J67+/xT/JAD8BHf3/xb/IwD9L8z/GP8hAPwCgvv/Gf8gAP0SuP8b/x8A/S/d/xz/HgD9 +UPX/Hf8dAP1Y+v8e/xwA/WX9/x//GwD9bf7/IP8aAP1T/f8h/xkA/Tf2/yL/GAD9Ien/I/8XAP0I +0v8k/xcA/o//Jf8WAP1E/v8l/xUA/RHj/yb/FQD+mf8n/xQA/Sv8/yf/FAD+tf8o/xMA/kL/Kf8T +AP7G/yn/EgD+Mv8q/xIA/p//Kv8RAP0U9/8q/xEA/nX/K/8RAP7I/yv/EAD9Ff3/K/8QAP5e/yz/ +EAD+qf8s/xAA/uv/LP8PAP4Z/y3/DwD+Rv8t/w8A/nL/Lf8PAP6f/y3/DwD+vv8t/w8A/sz/Lf8P +AP7c/y3/DwD+6v8t/w8A/vj/Lf8PAC//DwAv/w8AL/8PAC//DwAv/w8AL/8PAC//DwAv/w8AL/8P +AC//DwAv/w8AL/8PAC//DwAv/w8AL/8PAC//DwAv/38EAAD6w9zk7vz/fwv6/38EAID64e7y9/7/ +fwv6/38QAAD6zN3k7vz/fw/6/38EAAAy//X87uTdw6GAXCsCAAEAO//78Ll8Lf9/C3//OYD+gYAB +gAEAfwPAgDL/9f738u7h0MCulYGAAYA7//v43L6W/38Lf/9/EAAAMv/y/O7k3MzBn31cOhYAAP88 +//3ms/9/D3//fwSAAPzciyoAOwAC//y+VQQAOAAE//zaWQIANgAG//3QQgA1AAj//aoTADMACf/9 +5kMAMgAK//z8dQEAMAAM//2oBgAvAA3//bcJAC4ADv/9vwoALQAP//2uAgAsABD//owALAAR//5X +ACsAEf/97x4AKgAS//65ACoAE//+TgApABP//dgFACgAFP/+YQAoABT//s8AKAAV//45ACcAFf/+ +lQAnABX//eIBACYAFv/+LQAmABb//mYAJgAW//6SACYAFv/+vgAmABb//toAJgAW//7pACYAFv/+ ++AAmABf/JwAX/ycAF/8nABf/JwAX/ycAF/8nABf/JwAX/ycAF/8nABf/JwAX/ycAF/8nABf/JwAX +/ycAF/8nABf/JwAX/ycAPwD8gYCAADsABYA5AAaA/YGLADUACoA0AAyAMgANgP6CAC8AEIAuABKA +LAASgP6CACoAE4D9gYAAKAAVgP6HACcAF4AnABiAJgAZgCUAGYD+gQAjABuAIwAbgP6BACEA/O7F +lYAYgP6CACAAAv/836qCgBeAIAAE//ztrIGAFYD+kgAeAAb//eihgBWAHwAI//3ViYATgP6BAB0A +Cf/986GAE4D+iAAcAAr//f66gBOAHQAM//3Ug4ASgBwADf/924SAEYAcAA7//d+FgBGAGwAP//3X +gYAQgBsAEP/+xoAQgP6CABkAEf/+q4AQgBoAEf/994+AEIAZABL//tyAD4D+gQAYABP//qeAD4AZ +ABP//eyCgA6A/oYAFwAU//6wgA+AGAAU//7ngA+AGAAV//6cgA6AGAAV//7KgA+AFwAV//7xgA+A +FwAW//6WgA2A/oEAFgAW//6zgA6AFwAW//7JgA6AFwAW//7fgA6AFwAW//7tgA6AFwAW//70gA6A +FwAW//78gA6AFwAX/w+AFwAX/w+AFwAX/w+AFwAX/w+AFwAX/w+AFwAX/w+AFwAX/w+AFwAX/w+A +FwAX/w+AFwAX/w+AFwAX/w+AFwAX/w+AFwAX/w+AFwAX/w+AFwAX/w+AFwAX/w+AFwAX/w+AFwB/ +EAAAPwD8dzwIADsAAf/79rNmFgA4AAT/+/q6YwsANQAH//zxmSwAMwAJ//z+ukAAMQAM//27NQAv +AA3//P6tJgAtAA///PeABgArABH//dE1ACoAEv/8+38CACgAFP/9thEAJwAV//3hNAAmABb//fVO +ACUAF//9/F4AJAAY//38YwAjABn//f5qACIAGv/9/VEAIQAb//31NQAgABz//ecgAB8AHf/90AcA +HgAe//6LAB4AHv/9/UEAHQAf//3hDwAcACD//pQAHAAg//37KAAbACH//rEAGwAi//5AABoAIv/+ +xAAaACP//jMAGQAj//6cABkAI//99hIAGAAk//5xABgAJP/+wgAYACT//fwTABcAJf/+WgAXACX/ +/qYAFwAl//7pABcAJv/+GAAWACb//kQAFgAm//5xABYAJv/+nQAWACb//rwAFgAm//7LABYAJv/+ +2gAWACb//ukAFgAm//74ABYAJ/8XACf/FwAn/xcAJ/8XACf/FwAn/xcAJ/8XACf/FwAn/xcAJ/8X +ACf/FwAn/xcAJ/8XACf/FwAn/xcAJ/8XACf/FwB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8KAAB/CgAAfwoAAH8KAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAAjABuAIwAbgCMAG4Aj +ABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMA +G4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAb +gCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuAIwAbgH8FAAAjABv/ +IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8j +ABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMA +G/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb +/38FAAB/EAAAfxAAAH8LAIB/BQAAfwsA/38FAAB/EAAAfxAAAAeANwAHgDcAB4A3AAeANwAHgDcA +B4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAH +gDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeA +NwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgH8FOAAH/zcAB/83AAf/NwAH +/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/ +NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83 +AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/9/BTgAIwAP/y8A +D/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP +/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA// +LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8j +ABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/38DAAB/EAAAfxAAACMAD/8vAA//LwAP/y8A +D/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP +/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA// +LwAP/y8AD/8vAA//LwAP/y8AG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8jABv/IwAb/yMAG/8j +ABv/IwAb/yMAG/8jABv/IwAb/yMAG/9/AwAAfwkAAH8EAP9/AwAAfxAAAH8QAAB/CQAAfwQA/38D +AAB/CQAAfwQA/38DAAB/EAAAfxAAAH8JAAB/BAD/fwMAACsAD/8vAA//LwAP/y8AD/8vAA//LwAP +/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA// +LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8v +AA//LwAP/wMAO/8DADv/AwA7/wMAO/8DADv/AwA7/wMAO/8DADv/AwA7/wMAO/8DADv/AwA7/wMA +O/8DADv/AwA7/wMAO/9/AwQAfxAAAH8QAAArAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP +/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA// +LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8D +ADv/AwA7/wMAO/8DADv/AwA7/wMAO/8DADv/AwA7/wMAO/8DADv/AwA7/wMAO/8DADv/AwA7/wMA +O/8DADv/fwMEAH8QAAB/EAAAfxAAAH8QAAAfAB//HwAf/x8AH/8fAB//HwAf/x8A/vj/Hf8fAP7r +/x3/HwD+3P8d/x8A/sH/Hf8fAP6U/x3/HwD+af8d/x8A/i//Hf8gAP7g/xz/IAD+l/8c/yAA/jr/ +HP8hAP7M/xv/IQD+Yv8b/yEA/QTV/xr/IgD+UP8a/yMA/rv/Gf8jAP0b7f8Y/yQA/lj/GP8lAP6N +/xf/JQD9Aq7/Fv8mAP0Kv/8V/ycA/Qew/xT/KAD9Bqf/E/8pAPwBdPv/Ef8rAP1B5P8Q/ywA/A+Q +/f8O/y4A/UDO/w3/LwD8AVfZ/wv/MQD7BFK8/v8I/zQA/CmJ2v8G/zcA+yx7t+//Av86APoCKlt+ +oAB/Bv8ADwAPgB//DwAPgB//DwAPgB//DwAPgB//DwAPgB//DwAPgP78/x3/DwAPgP71/x3/DwAP +gP7u/x3/DwAPgP7g/x3/DwAPgP7K/x3/DwAPgP60/x3/DwAPgP6X/x3/DwAQgP7w/xz/DwD+hYAO +gP7L/xz/EAAPgP6d/xz/EAAQgP7m/xv/EAD+gYAOgP6x/xv/EAAQgP2C6v8a/xEAEID+qP8a/xEA +EYD+3f8Z/xEA/oeAD4D9jfb/GP8SABGA/qz/GP8SAP6CgBCA/sb/F/8TABGA/YHX/xb/EwD+gYAQ +gP2F3/8V/xQAEoD9g9j/FP8UAP6DgBGA/YPT/xP/FQAUgP26/f8R/xUAFYD9oPL/EP8WAP6CgBOA +/IfI/v8O/xcAFoD9oOf/Df8XAP6SgBaA/avs/wv/GAAYgPuCqd7+/wj/GQAagPyUxO3/Bv8aAP6B +gBqA+5a92/f/Av8bAB6A+oGVrb/QABsAIoAdAP6BgB+AHgAggB8AH4AgAB6AIQAdgCMAG4AkAP2A +gYAXgCYA/oOAFoAoABaAKgAUgCwAEoAuABCAMQD9i4GACoA0AP2AgoAHgDkA/YCCgAKAfwMAAH8Q +AAAPAC//DwAv/w8AL/8PAC//DwAv/w8A/vj/Lf8PAP7q/y3/DwD+3P8t/w8A/sz/Lf8PAP69/y3/ +DwD+n/8t/w8A/nL/Lf8PAP5G/y3/DwD+Gf8t/xAA/ur/LP8QAP6o/yz/EAD+Xf8s/xAA/RL8/yv/ +EQD+xf8r/xEA/nT/K/8RAP0R9f8q/xIA/p7/Kv8SAP4x/yr/EwD+xf8p/xMA/kH/Kf8UAP6y/yj/ +FAD9Kfv/J/8VAP6X/yf/FQD9EOL/Jv8WAP09/P8l/xcA/oz/Jf8XAP0H0f8k/xgA/SDo/yP/GQD9 +NvX/Iv8aAP1R/f8h/xsA/Wr+/yD/HAD9XPv/H/8dAP1X+v8e/x4A/U71/x3/HwD9KNf/HP8gAP0O +rv8b/yEA/AJ++v8Z/yMA/S7K/xj/JAD8BG/0/xb/JgD8Jar+/xT/KAD9LKr/E/8qAPw+uP7/EP8s +APwkiOz/Dv8uAPsKYrn6/wv/MQD7C02i8f8I/zQA+QQrZqLV/v8D/zkA+RQ5WnydvwB/Av8AfwjA +//rB2+Pu/P85/38HAAB/CMD/+uDt8ff+/zn/fwQAgH8DAAB/EAAAfwzA//rM2uPu/P85/38DAAB/ +CLz/++62eSr/Mf/1/O7j2sGfflspAQB/BwIAfwi8//v327yV/zH/9v738e3gz7+tlIB/A/2A/oGA +AYB/AwIAfxAAAH8Mvv/95rP/Mf/0/O7i2sy+nXtaOBQAfwMBABf/JwAX/ycAF/8nABf/JwAX/ycA +Fv/++AAmABb//ugAJgAW//7ZACYAFv/+vwAmABb//pIAJgAW//5mACYAFv/+LAAmABX//uAAJwAV +//6TACcAFf/+NwAnABT//s4AKAAU//5gACgAE//91wQAKAAT//5LACkAEv/+tgAqABH//e4dACoA +Ef/+VAArABD//ooALAAP//2rAgAsAA7//b0JAC0ADf/9tgkALgAM//2mBgAvAAr//ftyADEACf/9 +4z8AMgAH//z+oRIAMwAG//3NPwA1AAT//NdUAQA2AAH/+/67UQMAOAD82YgoAH8HvAAX/w+AFwAX +/w+AFwAX/w+AFwAX/w+AFwAX/w+AFwAW//78gA6AFwAW//70gA6AFwAW//7sgA6AFwAW//7fgA6A +FwAW//7JgA6AFwAW//6zgA6AFwAW//6WgA6AFwAV//7wgA6A/oEAFgAV//7JgA6A/oUAFgAV//6b +gA6AGAAU//7ngA+AGAAU//6wgA+AGAAT//3rgoAOgP6GABcAE//+pYAPgBkAEv/+24APgP6BABgA +Ef/9946AEIAZABH//qqAEIAaABD//sWAEYAaAA///dWBgBCAGwAO//3ehIAQgP6CABoADf/924SA +EYAcAAz//dODgBGA/oMAGwAK//39uYATgB0ACf/98Z+AFIAdAAf//P7QiYAUgB4ABv/95p+AFYAf +AAT//euqgBeAHwAB//v+3aiBgBeAIAD87MSUgBiA/oIAIAAbgP6BACEAGoD+gQAiABqAJAAZgCUA +GIAmABaA/oIAJgAVgP6HACcAFIAqABKA/oIAKgAQgP2BqgArAA+A/oMALQANgP6CAC8ADIAyAAqA +NAAIgDYABYA5APyBgJIAfwN8AH8QAAAn/xcAJ/8XACf/FwAn/xcAJ/8XACb//vgAFgAm//7oABYA +Jv/+2QAWACb//soAFgAm//67ABYAJv/+nAAWACb//nAAFgAm//5DABYAJv/+FwAWACX//ukAFwAl +//6lABcAJf/+WgAXACT//fwTABcAJP/+wQAYACT//m8AGAAj//31EgAYACP//poAGQAj//4yABkA +Iv/+wgAaACL//j8AGgAh//6vABsAIP/9+icAGwAg//6TABwAH//93w4AHAAe//39QAAdAB7//okA +HgAd//3OBgAeABz//eYeAB8AG//99DMAIAAa//38TwAhABn//f5nACIAGP/9/GIAIwAX//37XAAk +ABb//fRMACUAFf/92y0AJgAU//20EQAnABL//Pp6AQAoABH//dAzACoAD//89ncDACsADf/8/qkj +AC0ADP/9uzMALwAJ//z9tzwAMQAH//zwmSoAMwAE//v5uGIKADUAAf/79rNeEAA4APx3PAcAfwN8 +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfwoAAH8KAAB/CgAAfwoAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8KAAB/CgAAfwoAAH8KAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/CgAAfwoAAH8KAAB/CgAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfwoAAH8KAAB/CgAAfwoAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8KAAB/CgAAfwoAAH8KAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/CgAAfwoAAH8KAAB/CgAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfwoAAH8KAAB/CgAAfwoAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8KAAB/CgAAfwoAAH8KAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/CgAAfwoAAH8KAAB/CgAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fwoAAH8KAAB/CgAAfwoAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/ +CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8K +AAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoA +AH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAA +fwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/ +BkAAfwZAAH8GQAB/BkAAAAAB9AAAAfQAAAAAAAAA+gAAAPoAAAAAAAAAfQAAAH0AAAAAAAAAPgAA +AD4AAAAAAAAD6AAAA+gAAAABAAAAB1NsaWRlMgAAAAAGAAAABAAAAP8AAAAIAAAABAAAAAEAAAAJ +AAAABAAAAAAAAAAcAAAABAAAAAAAAAAKAAAABAAAAAAAAAALAAAABAAAAAAAAAAMAAAABAAAAAAA +AAANAAAABAAAAAAAAAAPAAAACAAAAAAAAAAAAAAABwAAAAQAAAAAAAAAFAAAAAQAAAADAAAAAAAA +AAAAAE1NAAAAAAAAA+gAAAPoAAAABAAATXEAAO0yAADtPgAA7UoAAO1WAAAAAAAAA+gAAAPoAABR +fQAAUY0AAFGdAABRrQAAUb0AAFHNAABR3QAAUe0AAFH9AABSDQAAUh0AAFItAABSPQAAUk0AAFJd +AABSbQAAUn0AAFKNAABSnQAAUq0AAFK9AABSzQAAUt0AAFLtAABS/QAAUw0AAFMdAABTLQAAUz0A +AFNNAABTXQAAU20AAFN9AABTjQAAU50AAFOtAABTvQAAU80AAFPdAABT7QAAU/0AAFQNAABUHQAA +VC0AAFQ9AABUTQAAVF0AAFRtAABUfQAAVI0AAFSdAABUrQAAVL0AAFTNAABU3QAAVO0AAFT9AABV +DQAAVR0AAFUtAABVPQAAVU0AAFVdAABVbQAAVX0AAFWNAABVnQAAVa0AAFW9AABVzQAAVd0AAFXt +AABV/QAAVg0AAFYdAABWLQAAVj0AAFZNAABWXQAAVm0AAFZ9AABWjQAAVp0AAFatAABWvQAAVs0A +AFbdAABW7QAAVv0AAFcNAABXHQAAVy0AAFc9AABXTQAAV10AAFdtAABXfQAAV40AAFm+AABbPgAA +XHoAAF6CAABgrgAAYcsAAGTGAABk1gAAaNIAAGtAAABvpQAAcV8AAHFvAABxfwAAcY8AAHGfAABz +6wAAdWAAAHbAAAB4/wAAeysAAHw2AAB/agAAf3oAAIPVAACGmAAAiyQAAI0DAACNEwAAjSMAAI0z +AACNQwAAjVMAAI1jAACNcwAAjYMAAI2TAACNowAAjbMAAI3DAACN0wAAjeMAAI3zAACOAwAAjhMA +AI4jAACOMwAAjkMAAJAqAACRQwAAkZ0AAJR9AACVmgAAlaoAAJdHAACXbQAAl30AAJmUAACbNQAA +nBYAAJwmAACcNgAAnEYAAJxWAACdhgAAoHoAAKOxAACkGQAAplYAAKdjAACn/QAAqpEAAKuXAACt +tAAArcQAALBIAACwWAAAsGgAALB4AACwiAAAsJgAALDuAACxIAAAsTAAALFAAACxUAAAsWAAALFw +AACxpgAAsbYAALHGAACx1gAAseYAALH2AACyBgAAshYAALImAAC4SAAAvGAAALxwAADBrQAAxdcA +AMaOAADGngAAxq4AAMa+AADGzgAAxt4AAMbuAADG/gAAxw4AAMceAADIVQAAztIAANYXAADW2gAA +3U8AAOSFAADk/gAA5Q4AAOUeAADlLgAA5T4AAOVOAADlXgAA5W4AAOV+AADljgAA5Z4AAOgGAADo +FgAA6CYAAOrKAADregAA67IAAOvCAADr0gAA6+IAAOvyAADsAgAA7BIAAOwiAADsMgAA7EIAAOxS +AADsYgAA7HIAAOyCAADskgAA7KIAAOyyAADswgAA7NIAAOziAADs8gAA7QIAAO0SAADtIgAAAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/CgAAfwoAAH8KAAB/CgAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfwoAAH8KAAB/CgAAfwoAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8KAAB/CgAAfwoAAH8KAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/CgAAfwoAAH8KAAB/CgAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfwoAAH8KAAB/CgAAfwoAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8KAAB/CgAAfwoAAH8KAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/BX4A/YOAADoA/ouAAYA6AP6BgAKAOAD+g4AEgDcA/oGABYA1 +AP6ZgAeANAD+hYAIgDMA/oOACYAyAP6CgAqAMQD+goALgDAADoAvAP6DgA2ALgAQgC0A/pKAD4At +ABGALAASgCsAE4ArABOAKgD+gYASgCkAFYApABWAKAD+g4AUgCgAFoAnAP6EgBWAJwAXgCYA/oaA +FoAmABiAJgAYgCUA/oOAF4AlABmAJQAZgCQA/oSAGIAkABqAJAAagCQAGoAjABuAIwD+goAZgCMA +G4AjABuAIwAbgCMAG4AjABuAIwAbgH8FfgD9IZEAOgD7C4j7/wA5AP1N4/8B/zgA/SG+/wP/NwD9 +T+7/BP81APwFiP7/Bf80AP0Zy/8H/zMA/SHa/wj/MgD9LeX/Cf8xAP0/8f8K/zAA/S7v/wv/LwD9 +I+f/DP8uAP0W2v8N/y0A/QfL/w7/LQD+kv8P/ywA/Uz+/w//KwD9Hu3/EP8qAP0Bvv8R/yoA/lv/ +Ev8pAP0O5f8S/ykA/pL/E/8oAP0l/P8T/ygA/p3/FP8nAP0d+f8U/ycA/pT/Ff8mAP0V9v8V/yYA +/m7/Fv8mAP7L/xb/JQD+Kf8X/yUA/of/F/8lAP7a/xf/JAD+G/8Y/yQA/l7/GP8kAP6f/xj/JAD+ +4f8Y/yMA/hT/Gf8jAP47/xn/IwD+Yv8Z/yMA/on/Gf8jAP6v/xn/IwD+x/8Z/yMA/tP/Gf8jAP7g +/xn/fxAAAH8QAAB/BAwA/oaAEID+hgAmAP2AgYAXgP6EAB8A/oOAH4AaACWA/YGAABQAKoD+hAAS +ACyA/oMAEAAvgA8AMIAOADGA/oQACwAygP6BAAoANYAJADWA/oUABwA2gP6DAAYAN4D+ggAFADiA +/oIABAA6gAQAO4ADADyAAgA9gAEAPYABAD6A/gCAPYD+g4B/Bn+AfwQMAOsTOmKIr8fS3e75+e7d +0seuiGI6EwAmAPsYVZne/xL/++aqZh8AHwD8KYfa/xr//NmFKAAZAPwUbsv/IP/8yW0UABQA/B6R +9f8k//z4mR0AEgD+9/8o//z6oicAEAAs//z7kQ4ADgAu//zmXgEADAAw//28HwALADH//fBVAAoA +Mv/8/pEGAAgANP/9yBcABwA1//3eJQAGADb//egxAAUAN//98T0ABAA4//3xMgADADn//eomAAIA +Ov/93RgAAQA7//vIBgAA/zv//I0AAP87//z+UAD/PP/97iH/Pf/+u/9/Bj//fxAAAH8QAAB/CcAA +/oEAPQD9gIgAPAABgD0AAYD+gwA7AAKAPAACgP6EADoAA4A7AAOA/oYAOQADgP6BADkABIA6AASA +/oMAOAAFgDkABYA5AAWA/oQANwAFgP6BADcABoA4AAaAOAAHgDcAB4A3AAaA/oEANgAHgDcAB4A3 +AAeANwAHgDcAB4A3AH8JwAD+VwA9AP3nDwA8AP3/jgA8APz/+yMAOwAB//6aADsAAf/9+h8AOgAC +//6RADoAAv/99hMAOQAD//5rADkAA//+yAA5AAT//icAOAAE//6CADgABP/+1gA4AAX//hsANwAF +//5bADcABf/+nAA3AAX//t4ANwAG//4SADYABv/+OAA2AAb//l8ANgAG//6GADYABv/+rQA2AAb/ +/sQANgAG//7RADYABv/+3gA2AH8GvwD+/wA7AAL/OwAD/zkABf84AAb/NgAI/zUACf80AAr/MwAL +/zIADP8wAA7/LwAP/y4AEP8uABD/LQAR/ywAEv8rABP/KgAU/yoAFP8pABX/KAAW/ygAFf8oABX/ +KAAV/ykAFP8qABP/KgAT/ysAEv8rABP/KwAS/ywAEf8tABH/LAAR/y0AEf8tABH/LQAQ/y4AEP8u +ABD/CgB/EAAAfxAAAH8GvwD+JgA7APwKifkAOgD7P9j//wA4AP0PoP8C/zgA/Tnc/wP/NgD8BIb8 +/wT/NQD9F7//Bv80AP065P8H/zMA/Vn4/wj/MgD9b/3/Cf8wAP0BlP8L/y8A/QKb/wz/LgD9AqH/ +Df8uAP6S/w7/LQD+ev8P/ywA/Vr+/w//KwD9MPX/EP8qAP0R3P8R/yoA/q3/Ev8pAP5c/xL//v4A +JwD9Gu7/Ef/980sAJwD+pv8R//3nMwAnAP04/v8Q//3gIgAnAP0Bx/8Q//3dHwAoAP5I/xD//eol +ACkA/rz/D//98zEAKQD+M/8P//3+VQAqAP6Y/w///owAKgD9Buz/Dv/91ggAKgD+T/8O//39NgAr +AP6e/w7//qAALAD+3v8N//36HQArAP4g/w7//qIALAD+Wv8O//48ACwA/oL/Df/93wEALAD+qv8N +//6YAC0A/s//Df/+WwAtAP7h/w3//jEACQB/AjwAA/8yAAz/LAAS/ycAF/8iABz/HgAg/xsAI/8X +ACf/FAAq/xEALf8PAC//DAAy/woANP8IADb/BQA5/wMAO/8BAD3//gD/Ov8CADT/CgAu/xAAKf8V +ACX/GQAh/x0AHv8gABr/JAAY/yYAFf8pABL/LAAQ/y4ADv8wAAz/MgAK/zQACP82AAb/OAAF/zkA +A/87AAL/PAAB/38EvgB/EAAAfxAAAH8CPAD7DiI2SgAxAPYCHDxdfp+/4Pr/Av8sAPgIM2KRudn4 +/wr/JwD5ETxvosz4/xD/IgD6AzNwrun/Fv8eAPoEM2+q7v8a/xsA+yp3wPf/Hv8XAPsDPIjW/yL/ +FAD7Dlak7f8l/xEA+wJEovX/KP8PAPwogOD/K/8MAPwQZsT/Lv8KAPwjkfb/MP8IAPw8qvz/Mv8F +APwCTcX/Nf8DAPwFZtz/N/8BAPwCXNz/Nf/4+ubTwABI1P8v//b948KhgF8+HgMAAQD+tP8s//ng +sYNUJwYACQAo//n8z5xtPxAADwAk//r7zJFVEgAUACD/+vm9fkAJABgAHf/77apeEAAcABr/+9qM +PwMAHwAX//zmiiwAIwAU//v3qUoDACUAEv/8yWgRACgAD//8/bNEACsADf/895gmAC0AC//87YEW +AC8ACf/88oIPADEAB//8+5IVADMABv/9vS0ANQAE//3mVAA3AAL//P6aEAA4AAH//elNADoA/P+2 +FgA7AP2IAgB/BL0AfwIAACP/GwAs/xIAMv8MADf/BwA8/wIAfwLP/wEALv8iABz/KgAU/zAADv81 +AAn/OQAF/z0AAf9/CEAAfxAAAH8QAAB/AgAA211wg5aru8HMzNfd3e7u9P//9O7u3d3XzMzBu6uW +g3BdSTYiDgAaACP/9vrgv55+XTwbAQARACv/+PjZuJBiMwgACwAx//n73bOATSIABgA3//rornAy +AwABADv/+/O7gET/fwJ//9utmIRxXks8MzMmIiEREQsAAAsRESEiJjMzPEtecoWZrcDU5/r/Gv8i +APYDHj9ggKLD4/3/Ev8qAPkHJ1WEsuD/Df8wAPkRP26c0Pz/B/81APoMRIC7+P8D/zkA+QpBf776 +/wA8AP0LTQB/CD8AfwNAAP7/AD0AA/87AAf/NwAK/zQADf8xAA//LwAS/ywAFP8qABb/KAAZ/yUA +G/8jAB3/IQAe/yAAIP8eACL/HAAj/xsAJf8ZACb/GQAn/xsAJP8cACP/HgAh/yAAH/8hAB//IQAe +/yIAHf8jABv/JQAa/yYAGf8mABn/JwAY/ycAF/8oABf/KQAW/ykAFf8qABX/KgAU/ysAFP8rABP/ +LAAT/ywAEv8sABP/LAAS/y0AEf8tABH/LgAR/y0AEf8tABH/LgAQ/y4AEP8uABD/AwB/EAAAfxAA +AH8DQAD+CAA9APv2v3YpADoAA//73pFEBQA2AAb/++2jVg4AMwAJ//v4s1UEADAADP/85pEwAC4A +D//81XcVACsAEf/8+KIqACkAE//8/bNEACcAFv/8zV4EACQAGP/83GUFACIAGv/821oCACAAHP/9 +0kYAHwAe//2xJQAdAB///PmGCQAbACH//d1FABoAIv/8/p0OABgA/Zno/yH//eA+ABgA+wRAjdv/ +H//8/IMDABoA/CyL5v8e//28FQAbAPsDS6r4/xz//eM3AB0A/BFpyv8b//33VgAfAPwzovz/Gf/9 +/oAAIAD8J5n3/xn//ZEBACAA/BaC7f8Y//2XAQAhAPwPg/P/F//9nAIAIgD8FpT7/xb//o8AJAD9 +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 +AiBFaoahvNbz/xb/GAD4DDhhhqzS9/8e/xMA+RNEdqfY/f8k/w4A+gQ2c6/h/yr/CgD6CD59ufP/ +Lv8HAPsoc734/zL/AwD7CEiT3f82//oACliv9f85//2k8/9/C73/fwYAAAHu8uPazLyuoZSHclxD +KxQALwAO//b548enhGE/HAEAJgAW//j31Kp8TyIBACAAHP/58saYYSgBABsAIf/67bR8QgwAFwAl +//r6wn04AwATACn/++6tZxsAEAAs//v+xnMgAA0AMP/8zHghAAoAMv/7/b1bCQAHADX//O+WNAAF +ADX//OmQMgAFADL/+/irTQUABwAw//zFaBEACgAt//zfgiUADQAq//v2qkQBAA8AJ//7/LdaCQAS +ACX//NN0GgAVACL//OmPMQAYAB//+/eqTAQAGgAd//zMbhQAHQAa//ziiCUAIAAX//vxnD4BACIA +FP/7/LZZCQAlABL//NV/HwAoAA///OiOMAArAAz/+/iyVQUALQAJ//v+w2UQADAAB//83YAjADMA +BP/78KFEAQA1AAH/+/y1WAkAOAD80HIYAH8CPAB/AgAACoD6gYCAg4sALgAWgP2EgAAlAByA/YKO +AB8AIID8gYCAABoAJ4D+mQAVACqA/YGAABEALoD+ggAOADKA/pIACgA1gP6OAAcAN4D9gZIABAA6 +gP6CAAIAPID8gYAAgH8A/4AB9/Lx7ebe19DKw7muoZWKgC+ADv/3/PHj08Kwn46AJ4AW//n76tW+ +p5GAIYAc//r548ywlIAcgCH/+vbavqGGgBeAJf/6/eG+nIGAE4Ap//v31rONgBCALP/7/uO5kIAN +gDD//Oa8kIAKgDL/+/7erYSAB4A1//z3y5qABYA1//z0yJmABYAy//v81aaCgAeAMP/84rSIgAqA +Lf/878GSgA2AKv/8+9WigBCAJ//7/tuthIASgCX//Om6jYAVgCL//PTHmIAYgB//+/vVpoKAGoAd +//zmt4qAHYAa//zxxJKAIIAX//z4zp+AI4AU//v+26yEgCWAEv/86r+PgCiAD//89MeYgCuADP/7 +/NmqgoAqgPyBhgD/CP/7/uGyiIAsgAMAB//87sCRgCuA/YKAAAQABP/8+NCigC2ACAAB//v+2qyE +gCqA/YGAAAoA/Oi5jIAsgA4ALID9gZkADwApgP2BgAASACeA/oMAFQAlgBkAIoD+iwAaAB+A/oUA +HQAcgP6CACAAGYD9gZkAIgB/EAAAfwIAAAHu8ePd0MK1p5mJfG1UPCULAC4AD//29NnCp4ViQB0C +ACUAF//4+Ne0jGAzCQAfAB3/+fzYqn1QGgAaACP/+uGnbjYFABUAJ//69sKFQQYAEQAr//v0tXAr +AA4AL//75J1MBwAKADL/+/GlUgkABwA1//v0qlUHAAQAOP/87ZEvAAIAO//71XcUAP88//33n/9/ +Bz3//fis/zr/+8xvFQD/N//84IMmAAIANf/78p4/AgAEADL/+/3EZgwABwAw//zTdRoACgAt//zp +kDIADQAq//v4q00FAA8AKP/8zG8UABIAJf/834IlABUAIv/78Z0+AQAXAB//+/3EZgsAGgAd//zS +dBkAHQAa//zojzEAIAAX//v4s1UFACIAfxAAAH8FAAD+gAA9AAKAPAAFgDkABoD+qgA2AAiANgAJ +gP6BADMAC4D+hAAxAA2AMQAPgC8AD4D+gQAtABKALAASgP6BACoAFYApABeAJwAXgP6DACUAGID9 +gYAAIwAagP6DACIAHID+qgAgAB2AIQAcgCIAF4D9gYAAJAAWgCgAEoD9gZkAKQARgC0ADoAwAAqA +/YGqADEACYA1AAWA/oQANwADgDsA/YCZAH8DvQB/EAAAfwUAAP4sAD0A/P63RAA7AAL//NFmCAA4 +AAT//OBgAwA2AAb//NVQAQA0AAj//chBADMACv/9tB0AMQAL//zwZAEALwAN//25HgAuAA7//PFn +AQAsABD//bwgACsAEf/882kBACkAE//9viIAKAAU//z0bAIAJgAW//3BIwAlABf//PVvAgAjABn/ +/cMlACIAGv/89nIDACAAG//982gAIAAY//v9xGYMACEAFv/81XccACQAE//865I0ACcAEP/7+K1P +BQApAA7//MdqEgAsAAv//OSRLgAvAAj/+/aqTQMAMQAF//v8uVwKADQAA//81HYbADcA+//umTwA +OgD9TgUAfwO9AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfwoAAH8KAAB/CgAAfwoA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAACMAG4AjABuAIwAbgCMAG4AjABuAIwAbgCMAG4AjABuA +IwAbgCMA/oGAGYAjABuAIwAbgCQAGoAkABqAJAAagCQAGoAlABmAJQAZgCUAGYAmABiAJgD+gYAW +gCYAGIAnABeAJwAXgCgAFoAoAP6DgBSAKQAVgCkA/omAE4AqABSAKwATgCsA/oSAEYAsABKALQAR +gC0AEYAuAP6GgA6ALwAPgDAA/oKADIAxAA2AMgAMgDMA/oOACYA0AP6FgAiANQAJgDcA/oGABYA4 +AP6EgASAOgD+gYACgDsAA4A9AAGAfwRAACMA/u3/Gf8jAP75/xn/IwD++f8Z/yMA/u3/Gf8jAP7g +/xn/IwD+0/8Z/yMA/sf/Gf8jAP6v/xn/IwD+iP8Z/yMA/mH/Gf8jAP46/xn/IwD+Ev8Z/yQA/uD/ +GP8kAP6f/xj/JAD+Xv8Y/yQA/hr/GP8lAP7Y/xf/JQD+hv8X/yUA/ib/F/8mAP7K/xb/JgD+bf8W +/yYA/RT2/xX/JwD+kv8V/ycA/Rz5/xT/KAD+m/8U/ygA/SP7/xP/KQD+j/8T/ykA/Q3k/xL/KgD+ +WP8S/ysA/rz/Ef8rAP0d7P8Q/ywA/Uz+/w//LQD+j/8P/y0A/QbI/w7/LgD9Fdj/Df8vAP0i5v8M +/zAA/S3v/wv/MQD9PvD/Cv8yAP0s5P8J/zMA/SHZ/wj/NAD9F8j/B/81APwEgP3/Bf83AP1N7f8E +/zgA/R+7/wP/OgD9TeL/Af87APsOjvv/ADwA/SCRAH8EPwB/EAAAfxAAAH8H/oD9gQCAPIABADyA +/JkAAIA6gP6FAAEAOoD+gwACADmA/oIAAwA5gAUAOIAGADeABwA1gP6GAAcANYAJADKA/oEACgAx +gP6EAAsAMIAOAC+ADwAsgP6DABAA/oWAKYAVAP2GgYAkgBoAIID+gwAfAP2AgYAXgP6EACYAEID8 +gYCHAH8DHwB/B3///rn/PP/97iD/O//8/k8A/zv//IoAAP86//vFBQAA/zn//dwXAAEAOf/95iEA +AgA4//3xMQADADf//fA8AAQANv/95zAABQA1//3dJAAGADT//cUVAAcAMv/8/pEGAAgAMf/98FMA +CgAw//25HQALAC7//eVeAA0ALP/8+owMAA4A/vf/KP/8+aIlABAA/BeI9f8k//z4mRwAFAD8E2vI +/yD//MdqEgAZAPwmg9b/Gv/81YElAB8A+xZVmd3/Ev/75qpmHQAmAOsSOGCHrcXQ3e35+e3d0MSt +hl84EQB/Ax8AfxAAAH8QAAAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcAB4A3AAeANwAHgDcA +BoD+ggA2AAeANwAGgDgABoA4AAaAOAAFgP6FADcABYA5AAWAOQAEgP6DADgABIA6AAOA/oEAOQAD +gP6GADkAA4A7AAKA/oQAOgACgDwAAYD+gwA7AAGAPQABgD0A/oAAfwj+AAb//usANgAG//74ADYA +Bv/++AA2AAb//usANgAG//7eADYABv/+0QA2AAb//sQANgAG//6tADYABv/+hQA2AAb//l4ANgAG +//43ADYABv/+EgA2AAX//t4ANwAF//6bADcABf/+WgA3AAT//f4ZADcABP/+1QA4AAT//oEAOAAE +//4nADgAA//+xgA5AAP//mkAOQAC//31EwA5AAL//o8AOgAB//34GwA6AAH//pcAOwD8//ohADsA +/f+LADwA/eYOADwA/lQAfwj+ACMAEP8uABD/LgAQ/y4AEP8uABD/LgAQ/y4AEP8uABH/LQAR/y0A +Ef8uABH/LQAR/y0AEv8sABP/LAAS/ywAE/8sABP/KwAU/ysAFP8qABX/KgAV/ykAFv8pABX/KgAU +/yoAFP8rABP/LAAS/y0AEf8uABD/LgAQ/y8AD/8wAA7/MgAM/zMAC/80AAr/NQAJ/zYACP84AAb/ +OQAF/zsAA/88AAL/PgD+/wB/BX8AfxAAAH8QAAAjAP7s/w3//hMALQD++f8N//4GAC0A/vn/Df/+ +BgAtAP7t/w3//hQALQD+4f8N//4xAC0A/s7/Df/+XAAtAP6p/w3//pkALQD+gv8N//3hAQAsAP5a +/w7//j0ALAD+IP8O//6kAC0A/t3/Df/9+x8ALAD+nf8O//6jACwA/k3/Dv/9/jgAKwD9Bez/Dv/9 +1wkAKwD+l/8P//6QACsA/jH/EP/+WAArAP66/w///fQ0ACoA/kX/EP/96ycAKgD+xP8Q//3fIQAp +AP02/v8Q//3iJQApAP6i/xH//ew8ACgA/Rns/xH//fVSACgA/lj/Ev/+/gApAP6q/xL/KgD9ENv/ +Ef8rAP0u9P8Q/ywA/Vj+/w//LQD+dv8P/y4A/o3/Dv8uAP0CnP8N/y8A/QGX/wz/MAD9AZD/C/8y +AP1v/f8J/zMA/VX3/wj/NAD9NuL/B/81AP0VvP8G/zYA/AOC/P8E/zgA/Tfb/wP/OQD8DZv+/wH/ +OwD7Pdf//wA7APwIhPcAPQD+HwB/BX8AfwWAAAH/PQAC/zwAA/87AAX/OQAG/zgACP82AAr/NAAM +/zIADv8wABD/LgAS/ywAFf8pABj/JgAa/yQAHv8gACH/HQAl/xkAKf8VAC7/EAA0/wsAO/8FADz/ +AwA7/wUAOf8IADb/CgA0/wwAMv8PAC//EQAt/xQAKv8XACf/GwAj/x4AIP8iABz/JwAX/ywAEv8z +AAv/OwAD/38BAAB/EAAAfxAAAH8FgAD9iAMAPAD8/7sYADsAAf/96lAAOgAD//2fEgA4AAT//ehZ +ADcABv/9xzwANQAH//z+qiMAMwAJ//z5jhIAMQAL//zvhBgALwAN//z4mykALQAP//z+u00AKwAS +//zNbBMAKAAU//v5rE0EACUAF//86I0uACMAGv/73Y9CBAAfAB3/+/SzXhAAHAAg//r6v4BDCwAY +ACT/+vvMkVUTABQAKP/5/dGecEISAA8A/aP9/yv/+eO1hlcrCAAKAP0rtf8w//fuzKqIZkQiBAAE +AP1K1P81//v76NXCAAIA/ANj2/83/wUA/AJNxP81/wgA/DOi/P8y/woA/CKR9f8w/wwA+w9eu/7/ +Lf8PAPwngN7/K/8RAPsCPJn0/yj/FAD7Ck2Z5/8l/xcA+wIzgMz/Iv8bAPsmdL31/x7/HgD6BDNv +qu3/Gv8iAPoDMG6s5/8W/ycA+RE8b5nE9/8Q/ywA+AYwXoy11ff/Cv8zAPcZOlt7nL3e+P8C/zsA ++wwfNEgAfwD/AH8JfgAB/zkABf81AAn/MAAO/yoAFP8jACr/AQB/Ayz/AgA3/wcAMv8MACv/EwAj +/38BHAB/EAAAfxAAAH8JfgD9C00AOAD5C0SBwPv/ADQA+g1EgLv4/wP/MAD5E0JwntH9/wf/KgD5 +CSxYh7bk/w3/IwD3ETNVd5m73f3/Ev/br5yHc2BNQTMzKiIiEhEMAAAMERIiIiozM0FNYHOHnK/C +1ej7/38C1//787uARP82//rnq24wAgABADH/+fvVqoBNGQAGACv/+PbVtIxeMAYACwAj//f43b2c +e1o5GAASANtbboGUqLu8zMzU3d3s7vP///Pu7N3d1MzMvLunlIFuW0g0HwwAfwEbACoAEP8uABD/ +LgAQ/y4AEP8uABD/LgAQ/y4AEP8tABH/LQAR/y0AEf8sABH/LQAR/ywAEv8rABP/KwAS/ysAE/8q +ABP/KgAU/ykAFP8pABX/KAAV/ygAFv8mABf/JgAX/yYAGP8kABn/JAAZ/yMAGv8iABv/IQAd/x8A +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 +8KE0AAIAOP/8w1UEAAIANf/885YqAAUAMv/7/cNmDQAHADD//MpvGAAKACz/+/zAdCUADQAp//vw +qVsSABAAJf/69bt7OwUAEwAh//rvuXw8BgAXABz/+fLBjVgkAQAbABb/+PLKoXhRJgIAIAAO//f4 +2ryef2FCHAAnAPDu4NvMxruxoo98aVNALBgAfwUwABiAJgAUgP6DACgAF4AnABmA/oIAIwAcgP6C +ACAAIIAeACKA/oIAGgAmgBgAKYAVACuA/oIAEQAvgA8A/omAL4D+ggALAPv+4beLgDCACQAC//v9 +27GJgDCABgAF//v927CIgC+A/oIAAgAI//v+4beKgDCA/gD/Cv/7/NmviIAugA7/+/zZroeAK4AR +//v82K6HgCiAFP/7/Nmuh4AlgBf/+/vZroeAIoAa//v72a6HgB+AHf/7+9arhYAcgCD/+/rVq4WA +GYAj//v72a6GgBaAJv/7+tSphIATgCn/+/nTqYSAEIAs//v506iEgA2AL//7+dKohIAKgDL/+/jS +p4SAB4A1//v62a6FgASAOP/8+NCagAKAOP/84aqCgAKANf/8+cuVgAWAMv/7/uGzhoAHgDD//OW3 +jIAKgCz/+/7gupKADYAp//v41K2JgBCAJf/6+t29nYKAE4Ah//r33L6eg4AXgBz/+vngxqySgByA +Fv/4+eXQvKiTgYAggA7/9/zt3s+/sKGOgCeA8Pfw7ebj3djRx760qaCWjIB/AW6A/YkAgDqAAwA3 +gP2BqgAEADSA/YGAAAcAMYD9gYAACgAtgP2BgwAOACyAEgAlgPyBgoAAFQAggPyBgIgAGgAcgP2C +jgAfABOA+oGAgoOqACUACoD6gYCAg4gAfwEvAH8QAAAU//v+xGYQACUAE//9uiUAKAAU//vjjzoB +ACUAF//74o45AQAiABr/++GNNwEAHwAd//vgjDYBABwAIP/834o1ABoAI//74ZE8AQAWACb//N2I +MgAUACn//NyHMQARACz/++CRPAEADQAv//zahC8ACwAy//zfkTwACAA1//zXgiwABQA4//zWgSsA +AgA7//vdiCoA/z3//tT/fwe9//3ymf86//vVag0A/zf/++2ZPAEAAQA1//vzokcDAAQAMv/776dX +CgAHAC//+9uOQQQACgAr//vzt3UpAA4AJ//687d4OAQAEQAi//n+2aVxOQQAFQAd//n93at3Qg8A +GgAX//j40qqCWDEJAB8AD//29ta4m31ePyEDACUA7+7l3c/Lu7aqnIZyX0w4JQ8AfwEvAH8QAAB/ +BAAA/YCDADwAAoD9gYAAOQAGgP6DADYACoA0AAyA/oMAMAAPgP6DAC0AEYD9gYAAKgAVgP6DACcA +GID+gwAkABqA/YGEACEAH4AfACCAHgAegCAAHID+hQAgAByAIgAagCQAGYAlABeAJwAVgP6DACcA +FYApABKA/oEAKgARgP6FACsAEIAuAA+ALwAOgDAAC4D+hAAxAAqANAAHgP2BgAA0AAaA/qoANgAD +gP2BgAA4AAGA/oEAOwD+ggB/BD4AfxAAAH8EAAD9fikAPAAB//zTfSgAOQAE//zRfCcANgAH//zV +gCgAMwAK//zVgCcAMAAN//zVgCcALQAQ//zNdyIAKgAT//zLdiEAJwAW//zVgCUAJAAY//v+yXMf +ACEAG//7/sdyHgAeAB3//P6JAgAdABz//ehGAB8AG//9uBcAIAAZ//z7gAIAIQAY//3aNAAjABf/ +/aoQACQAFf/98lwAJgAU//3MJQAnABL//P6LBgAoABH//ehHACoAEP/9uBcAKwAO//z5dAEALAAN +//3aNAAuAAv//PR6CAAvAAn//P6kGwAxAAj//cs6ADMABv/81lsCADQABP/81V4DADYAAv/80lUC +ADgA+/66TQEAOgD+KwB/BD4AfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/CgAAfwoA +AH8KAAB/CgAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfwoAAH8KAAB/CgAAfwoAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/AtgA+huGqoUiADgA/Sfr/wH//PyoMgA2AP6w/wX//btFADQA/un/B//8zlkEADEA +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 +ADwAAoA1AAGA+oGAgIEAgAGA/oYALQABgP6BgAeA/gCAAoD+hwAlAAGAAYEMgPyBAACABIAfAPuZ +gIGBgBGAAgAGgBgA/IuCgYAYgAIABoD+kgAQAPyAgoGAHYADAAiACgD9gIKAI4D+ggACAAiA/qoA +AgD+h4AqgAQACYD+gYAtgP6DAAMAOYAFADmABQA4gAYAOIAGAP7GgCSA+YebsMXWh4AJgAcA/f/B +gB2A+Yidssfc8f8C//7HgAqABwAB//68gBaA+IiZrsTZ7v7/Bv/9+YuACYAIAAL//rqAD4D5iJ2z +yN3z/w3//r2ACoAIAAL//f60gAiA+Yyis8jd9P8S//30hoAJgAkAA//9/rGAAYD5jKK3zOH5/xj/ +/rSACYAKAAT/+/3W5/v/Hf/97YKACYAKACf//quACYALACb//uaACYD+gwAKACb//qKACYAMACX/ +/t+ACYD+hAALACT//f6bgAmADQAk//7VgAqADQAj//39k4AJgA4AI//+y4AKgA4AIv/9+46ACYAP +ACL//sKACoAPACH//feIgAmAEAB/EAAAfwfAAP4hAD0A/eEfADwA/P/aGAA0APQaRG+ZxFUA///S +EwAtAPkaRG+ZxPP/Av/82AQA/wH//c8RACUA+AQiTXeizPX/CP/8UQAA/wL//cYMAB4A+AUiTXei +zPf/Df/7yQEAAP8D//28CAAXAPgLM12HsNv8/xP//kAAAQAF//25BwAQAPgOM1WAqtX9/xj//rUA +AgAG//2sBAAJAPkSPWaQuuP/Hv/9/S8AAgAH//2qAwACAPkRPGaIs93/JP/+owADAAj/+qJvmcPt +/yn//fkhAAMAOP/+kgAEADf//fIWAAQAN//+gAAFADb//eoOAAUANv/+bgAGADX//d4GAAYANf/+ +WgAHADT//dECAAcANP/+SAAIADP//sEACQAy//3+OAAJADL//q0ACgAx//38KQAKADH//pwACwAw +//33HQALADD//okADAAv//3vEgAMAC///ngADQAu//3lCgANAC7//mQADgAt//3YBAAOAC3//lIA +DwB/EAAAfxAAAH8QAAB/EAAAfwbzAP4aAD0A/aFpADwA/Mn+YwA7APvv//xYADkA/hj/Af/9+k0A +OAD+P/8C//35SQA3AP5m/wP//fU/ADYA/o3/BP/98TYANQD+tP8F//3wMwA0AP7b/wb//eorADIA +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 +/kr/H/8dAP4n/x//HQD9Bvz/Hv8eAP7g/x7/HgD+vf8e/x4A/pr/Hv8eAP53/x3/APIh//5yABsA +IP/95AkAGwAg//5gABwAH//91wQAHAAf//5OAB0AHv/9yAEAHQAe//5AAB4AHf/+swAfABz//f0w +AB8AHP/+oQAgABv//fggACAAG//+jgAhABr//fIWACEAGv/+fQAiABn//ekNACIAGf/+awAjABn/ +/j8AIwAZ//7CACMAGv/+RAAiABr//scAIgAb//5MACEAG//90gIAIAAc//5UACAAHP/92AMAHwAd +//5cAB8AHf/92wUAHgAe//5kAB4AHv/94QcAHQAf//5sAB0AH//95goAHAAg//51ABwAIP/97A4A +GwAh//59ABsAIf/98BIAGgAi//6EABoAIv/99BYAGQAj//6NABkAI//99xwAGAAk//6UABgAJP/9 ++SAAFwAl//6dABcAJf/9/CgAFgAm//6lABYABP/82b3s/x3//f0sABUAA//12BgAABE7bpnD9/8Y +//6tABUAAv/92BkABgD4BCpVf7Ld/v8S//3+NgAUAAH//dkaAA0A+RA7ZpDD9P8O//61ABQA/P/a +GgAUAPkZTH+q1P3/Cf/+PgATAP3aGwAaAPkNM12Qu+v/BP/+wgATAP4fACEA9xlEbqHU/P9CADsA +/Ak0OAB/A1MAIf/+uYAJgBEAIP/98oSACYARACD//rCACYASAB///euCgAmAEgAf//6ngAmAEwAe +//7kgAmA/oMAEgAe//6ggAmAFAAd//7ZgAqAFAAc//3+mIAJgBUAHP/+0IAKgBUAG//9/JCACID+ +gQAVABv//seACYD+kgAVABr//fmLgAmAFwAa//6+gAqAFwAZ//30hoAJgBgAGf/+tYAJgBkAGf/+ +n4AJgBkAGf/+4YAKgBgAGv/+ooAJgBgAGv/+44AKgBcAG//+poAJgBcAG//96YGACYAWABz//qqA +CYAWABz//eyBgAmAFQAd//6ugAmAFQAd//3tgoAJgBQAHv/+soAJgBQAHv/98IOACID+ggASAB// +/raACYATAB///fOFgAmAEgAg//66gAmAEgAg//32h4AJgBEAIf/+voAJgBEAIf/9+ImACYAQACL/ +/sKACYAQACL//fqLgAiA/oEADgAj//7GgAmADwAj//37joAIgP6BAA0AJP/+yoAKgA0AJP/9/JCA +CID+gQAMACX//s6ACoAMACX//f6UgAiA/oEACwAm//7SgAmA/pIACgAE//zs3vb/Hf/9/paACID+ +gQAKAAP/9eyMgICInbfM4fv/GP/+1oAKgAoAAv/97IyABoD4gpWqv9nu/v8S//3+m4AIgP6BAAkA +Af/97I2ADYD5iJ2zyOH6/w7//tqACYD+iQAIAPz/7Y2AFID5jKa/1er+/wn//p+ACYAJAP3tjYAa +gPmGma7I3fX/BP/+4YAKgAgA/o+AIYD3jKK30Or+/6GACYAIACiA/ISanIAJgP6GAAYAN4AHADiA +BgA4gAYACID+gYAtgP6EAAQACIACAPyqgIGAKYAFAAaA/oIACAD9gIKAJID+gwADAAWA/oIADwD9 +gIGAH4AEAASA/oIAFQD9gIKAG4ADAASAHQABgP6BgBSAAwACgP6CACIA/JmCgYAPgP6CAAEAAYD+ +ggApAA6AAgABgDAA/KqAgYAFgPuCAACCADYA/YiCgAGAAQB/EAAALP/9ygEADwAs//5CABAAK//+ +twARACr//f4yABEAKv/+pQASACn//fojABIAKf/+kwATACj//fMYABMAKP/+gAAUACf//esOABQA +J//+bwAVACb//d8HABUAJv/+XAAWACX//dICABYAJf/+SgAXACT//sEAGAAk//6FABgAJP/98BIA +FwAl//6FABcAJf/98xYAFgAm//6MABYAJv/99xwAFQAn//6VABUAJ//9+iIAFAAo//6dABQAKP/9 +/CgAEwAp//6lABMAKf/9/S8AEgAq//6tABIAKv/9/jYAEQAr//61ABEALP/+PgAQACz//r4AEAAt +//5CAA8ALf/9ygEADgAu//5LAA4ALv/9zQEADQAv//5TAA0AL//91AIADAAw//5bAAwAMP/92gQA +CwAx//5jAAsAMf/94QcACgAy//5rAAoAMv/95goACQAz//5zAAkAM//96w0ACAA0//58AAgANP/9 +8BIABwA1//6DAAcANf/98xUABgA2//6LAAYANv/99hoABQA3//6UAAUAB//66nefzPf/Kf/9+R8A +BAAG//3uMgABAPgDIk2AqtX+/yT//psABAAF//3vMwAIAPkOM16Ru+z/H//9/CcAAwAE//3tMQAO +APgBIlF+q9r9/xr//qQAAwAD//3vNQAVAPkGK16Is+j/Ff/9/S4AAgAC//3vNAAcAPkcSnem0/r/ +EP/+rQACAAH//fA3ACIA+QUrVYCz3f8L//3+NQABAPz/8TcAKQD5EDxmkcT1/wb//rQAAQD99D4A +LwD4AyJNgKrV/v8B//s9AAA7ADYA+g89apiFAAAAfxAAAH8DPwD+gAA7AAKAOgAEgDgABoA2AAiA +NAD+goAIgDMAC4A0AAqANgD+mYAGgDgA/ZmBgAOAOgD9gIGAAYA8APyZgIAAPQD+gAB/Cb8AfxAA +AH8DPwD+FgA7APwglvgAOQD6IpH4//8ANwD8LKL7/wL/NgD8NK/+/wT/NAD8O7P+/wb/MwD9Mtz/ +CP80APwEYt3/Bv82APwFXtv/BP84APwFZeD/Av86APoGZ+H//wA7APwFXt0APQD+BgB/Cb8ALAD+ +Jf8Q/ywA/mP/EP8qAP07sv8R/ygA/Tu9/xP/JQD8AUzK/xX/IwD8AkzN/xf/IQD8BV3Y/xn/HwD8 +Bl3b/xv/HQD8C27k/x3/GwD8DW7n/x//GQD8E3/u/yH/FwD8FX/w/yP/FQD8HpD2/yX/EwD8I5z5 +/yf/EQD8KqH7/yn/DwD8LqH8/yv/DQD8ObT+/y3/CwD9O7L/MP8KAP6H/zL/CwD9Q8T/MP8NAP07 +vv8u/w8A/UfH/yz/EQD9Scj/Kv8TAP1Eyf8o/xUA/UTK/yb/FwD9RMr/JP8ZAP1Ey/8i/xsA/UTM +/yD/HAD8AUzN/x7/HgD8AUzN/xz/IAD8AUzO/xr/IgD8AUzP/xj/JAD8AkzQ/xb/JgD8AlXX/xT/ +KAD8AlXY/xL/KgD8A13h/xD/LAD+f/8Q/ywA/lz/EP8sAP45/xD/LAD+Fv8Q/y0A/vH/D/8tAP7P +/w//LQD+q/8P/y0A/oj/D/8tAP5m/w//LQD+Qv8P/y0A/h//D/8tAP0C+P8O/y4A/tn/Dv8uAP61 +/w7/LgD+kv8O/y4A/m//Dv8uAP5M/w7/LgD+Kf8O/y4A/Qf9/w3/LwD+4f8N/y8A/r7/Df8vAP6b +/w3/LwD+ef8N/y8A/lb/Df8vAP4y/wz//uUALgD+Dv8L//3pKgAvAP7r/wn//egpADAA/sn/CP/9 +6iwAAAAXABSA/pL/EP8VABaA/rH/EP8TAP6CgBSA/Z3Z/xH/EQAWgP2d3v8T/w8AFoD9puX/Ff8M +AP2AgYATgPyBpub/F/8KAP2AgYATgPyCruz/Gf8IABaA/IOu7f8b/wYA/pKAFID8hbfy/x3/BAD9 +gIGAE4D8hrfz/x//AgD9iYGAE4D8ib/3/yH//gCAFYD8ir/4/yP/FYD8j8j7/yX/E4D8kc78/yf/ +EYD8ldD9/yn/D4D8l9D+/yv/DYD8nNr+/y3/C4D9ndn/MP8KgP7D/zL/C4D9oeL/MP8NgP2d3/8u +/w+A/aPj/yz/EYD9pOT/Kv8TgP2i5P8o/xWA/aLl/yb//gCAFYD9ouX/JP8CABaA/aLl/yL/BAAW +gP2i5v8g/wYAFoD9pub/Hv8IAP6SgBSA/abm/xz/CgD9gIGAE4D9puf/Gv8MAP2LgYATgP2m5/8Y +/w4A/YuBgBKA/IGm6P8W/xAA/YuBgBKA/IGq6/8U/xIA/YuBgBKA/IGq7P8S/xQAFYD8ga7w/xD/ +FgD9gIGAEoD+v/8Q/xgA/YCBgBCA/q7/EP8aAP2JgYAOgP6c/xD/HAD+h4ANgP6L/xD/HgD9iYGA +C4D++P8P/yAADID+5/8P/yIA/oaACID+1f8P/yMACYD+xP8P/yMA/oGAB4D+s/8P/yMACYD+of8P +/yMACYD+j/8P/yMACYD9gfz/Dv8kAAmA/uz/Dv8kAAmA/tr/Dv8kAAmA/sn/Dv8kAAmA/rf/Dv8k +AAmA/qb/Dv8kAAmA/pT/Dv8kAP6EgAeA/YP+/w3/JAAKgP7w/w3/JQAJgP7f/w3/JQAJgP7N/w3/ +JQAJgP68/w3/JQD+gYAHgP6r/w3/JQAJgP6Z/wz//vIAJAD+g4AHgP6H/wv//fSVACQACoD+9f8J +//z0lIAAJQAJgP7k/wj//fWWgACAfxAAABcA/Cyi+/8j/xUA/DSv/v8l/xMA/Duz/v8n/xEA/Tyz +/yr/DwD9RMf/LP8MAPwCTcv/Lv8KAPwCVdb/MP8IAPwGXtn/Mv8GAPwHZuL/NP8EAPwMb+X/Nv8C +APwNd+3/OP/7ABSA7/86//2I9f9/Av3//V7d/zz/+wAGZuP/Ov8CAPwGZuT/OP8EAPwGZuT/Nv8G +APwGZuX/NP8IAPwHZuX/Mv8KAPwKb+b/MP8MAPwLb+f/Lv8OAPwLb+f/LP8QAPwLb+j/Kv8SAPwL +b+j/KP8UAPwOfO3/Jv8WAPwMd+7/JP8YAPwMd+7/Iv8aAPwNd+7/IP8cAPwRgvH/Hv8eAPwNd+// +HP8gAPwSgPD/Gv8iAP0T0/8Z/yMA/qD/Gf8jAP59/xn/IwD+Wv8Z/yMA/jb/Gf8jAP4U/xn/JAD+ +7/8Y/yQA/s3/GP8kAP6q/xj/JAD+h/8Y/yQA/mT/GP8kAP5A/xj/JAD+Hf8Y/yQA/QL3/xf/JQD+ +1v8X/yUA/rP/F/8lAP6Q/xf/JQD+bf8X/yUA/kr/F/8lAP4n/xf/JQD9Bvz/Fv8mAP7g/xb/Kv/9 +9hsAEQAq//6FABIAKf/97hEAEgAp//5yABMAKP/95AkAEwAo//5gABQAJ//91wQAFAAn//5OABUA +Jv/9yAEAFQAm//5AABYAJf/+sgAXACT//f0vABcAJP/+oAAYACP//fgfABgAI//+jgAZACL//fIW +ABkAIv/+fQAaACH//ekNABoAIf/+awAbACH//kAAGwAh//7DABsAIv/+RAAaACL//scAGgAj//5M +ABkAI//90gIAGAAk//5UABgAJP/92AQAFwAl//5dABcAJf/93wYAFgAm//5lABYAJv/94QcAFQAn +//5tABUAJ//95woAFAAo//51ABQAKP/97A4AEwAp//59ABMAKf/98BIAEgAq//6FABIAKv/99BcA +EQAr//6OABEAK//99xwAEAAs//6VABAALP/9+SAADwAt//6eAA8ALf/9/CgADgAu//6lAA4ADP/8 +2bzr/x3//f0tAA0AC//11xgAABE7bpnD9v8Y//6tAA0ACv/92BkABgD4BCJMf7Ld/v8S//3+NgAM +AAn//dkaAA0A+Q87ZpDD9P8O//62AAwACP/92hoAFAD5GUx3odT9/wn//j4ACwAH//3aGwAaAPkN +M12Qu+r/BP/+wgALAAb//dsbACEA9xlEbqHM+f9DAAoABf/93B0AJwD8CTM4AAoABP/94SEANwAD +//3fHwA4AAL//eAgADkAAf/94iIAOgD8/+MjADsA/ecpADwA/iYAfwD+ACr//fuNgAmABwAq//7C +gAqABwAp//33iIAJgAgAKf/+uYAJgAkAKP/98oSACYAJACj//rCACYAKACf//euCgAiA/oIACQAn +//6ngAmACwAm//7kgAmA/oMACgAm//6ggAmADAAl//7ZgAmA/oUACwAk//3+l4AJgA0AJP/+0IAK +gA0AI//9/I+ACID+gQANACP//seACYD+kgANACL//fmLgAmADwAi//6+gAqADwAh//30hoAIgP6B +AA8AIf/+tYAJgBEAIf/+oIAJgBEAIf/+4YAKgBAAIv/+ooAJgBAAIv/+44AKgA8AI//+poAJgA8A +I//96YGACYAOACT//qqACYAOACT//eyCgAmADQAl//6ugAmADQAl//3vg4AJgAwAJv/+soAJgAwA +Jv/98IOACID+ggAKACf//raACYALACf//fOFgAmACgAo//66gAmACgAo//32h4AJgAkAKf/+voAJ +gAkAKf/9+ImACID+gQAHACr//sKACYAIACr//fqLgAiA/oEABgAr//7HgAmABwAr//37joAIgP6B +AAUALP/+yoAKgAUALP/9/JCACYAFAC3//s+ACoAEAC3//f6UgAmABAAu//7SgAmA/pIAAgAM//zs +3vX/Hf/9/paACYADAAv/9euMgICInbfM4fv/GP/+1oAKgAIACv/97IyABoD4gpGmv9nu/v8S//3+ +m4AJgAIACf/97I2ADYD5h52zyOH6/w7//tuACYD8iQAA/wf//e2NgBSA+Yymu9Dq/v8J//6fgAmA +AQAH//3tjYAagPmGma7I3fX/BP/+4YAKgP4A/wX//e2NgCGA94yit9Dm/P+hgAmA/gD/BP/97o6A +J4D8hJmcgAmA/ob/A//98JCAN4AD//3vj4A4gAL//fCQgDmAAf/98ZGAOoD8//GRgAuA/oIAAQD8 +qoCBgCeA/fOUgAuA/oIACAD9gIKAIoD+k4AMgBAAAYD+gYAogP6CABUA/YCCgCOA/oIAHAD8hICB +gByAIwD8gIKBgAuAfxAAADb//mQABgA1//3YBAAGADX//lIABwA0//3KAQAHADT//kIACAAz//62 +AAkAMv/9/TEACQAy//6kAAoAMf/9+iMACgAx//6SAAsAMP/98xcACwAw//6AAAwAL//96w4ADAAv +//5vAA0ALv/93wcADQAu//5cAA4ALf/90QIADgAt//5JAA8ALP/+wQAQACz//oUAEAAs//3wEgAP +AC3//oYADwAt//3zFgAOAC7//o0ADgAu//33HAANAC///pUADQAv//36IgAMADD//p0ADAAw//38 +KAALADH//qUACwAx//39LwAKADL//q4ACgAz//42AAkAM//+tgAJADT//j4ACAA0//6/AAgANf/+ +QwAHADX//coBAAYANv/+SwAGADb//c4BAAUAN//+UwAFADf//dQCAAQAOP/+XAAEADj//dsEAAMA +Of/+ZAADADn//eEHAAIAOv/+bAACADr//eYKAAEAO//+dAABADv/++sNAAD/O//8fAAA/zv//PAS +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 +EDxmkcT0/wr//fQ+AC8A+gMiTYCq/wX//fI6ADUABf/99UAANgAE//31QQA3AAP//fVBADgAAv/9 +9UIAOQAB//32QwA6APz/+EsAOwD990cAPAD+TQB/DX4AfxAAAASAOgAFgDoA+oiAgYCAAH8PeQB/ +EAAAA//+tAA5AP3V/f8B//4+ADkA+g88aZiEAH8PeQB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoA +AH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAA +fwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/ +CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8K +AAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoA +AH8GQAB/BkAAfwZAAH8GQAAAAAH0AAAB9AAAAAAAAAD6AAAA+gAAAAAAAAB9AAAAfQAAAAAAAAA+ +AAAAPgAAAAAAAAPoAAAD6AAAAAEAAAAHU2xpZGUzAAAAAAYAAAAEAAAA/wAAAAgAAAAEAAAAAQAA +AAkAAAAEAAAAAAAAABwAAAAEAAAAAAAAAAoAAAAEAAAAAAAAAAsAAAAEAAAAAAAAAAwAAAAEAAAA +AAAAAA0AAAAEAAAAAAAAAA8AAAAIAAAAAAAAAAAAAAAHAAAABAAAAAAAAAAUAAAABAAAAAIAAAAA +AAAAAAAA7hEAAAAAAAAD6AAAA+gAAAAEAADuNQABEWYAARFyAAERfgABEYoAAAAAAAAD6AAAA+gA +APJBAADyUQAA8mEAAPJxAADygQAA8pEAAPKhAADysQAA8sEAAPLRAADy4QAA8vEAAPMBAADzEQAA +8yEAAPMxAADzQQAA81EAAPNhAADzcQAA84EAAPORAADzoQAA87EAAPPBAADz0QAA8+EAAPPxAAD0 +AQAA9BEAAPQhAAD0MQAA9EEAAPRRAAD0YQAA9HEAAPSBAAD0kQAA9KEAAPSxAAD0wQAA9NEAAPTh +AAD08QAA9QEAAPURAAD1IQAA9TEAAPVBAAD1UQAA9WEAAPVxAAD1gQAA9ZEAAPWhAAD1sQAA9cEA +APXRAAD14QAA9fEAAPYBAAD2EQAA9iEAAPYxAAD2QQAA9lEAAPZhAAD2cQAA9oEAAPaRAAD2oQAA +9rEAAPbBAAD20QAA9uEAAPbxAAD3AQAA9xEAAPchAAD3MQAA90EAAPdRAAD3YQAA93EAAPeBAAD3 +kQAA96EAAPexAAD3wQAA99EAAPfhAAD5QgAA+VwAAPl2AAD5kAAA+pgAAPqoAAD6uAAA+sgAAPrY +AAD66AAA+vgAAPsIAAD7GAAA+ygAAPs4AAD7SAAA/hAAAP4gAAD+MAAA/kAAAQBIAAEAWAABAGgA +AQB4AAEAiAABAJgAAQCoAAEAuAABAMgAAQDYAAEA6AABAPgAAQPAAAED0AABA+AAAQPwAAEF+AAB +BggAAQYYAAEGKAABBjgAAQZIAAEGWAABBmgAAQZ4AAEGiAABBpgAAQaoAAEInAABCLYAAQjQAAEI +6gABClYAAQpmAAEKdgABCoYAAQqWAAEKpgABCrYAAQrGAAEK1gABCuYAAQr2AAELBgABCxYAAQsm +AAELNgABC0YAAQtWAAELZgABC3YAAQuGAAELlgABC6YAAQu2AAELxgABC9YAAQvmAAEL9gABDAYA +AQwWAAEMJgABDDYAAQxGAAEMVgABDGYAAQx2AAEMhgABDJYAAQymAAEMtgABDMYAAQzWAAEM5gAB +DPYAAQ0GAAENFgABDSYAAQ02AAENRgABDVYAAQ1mAAENdgABDYYAAQ2WAAENpgABDbYAAQ3GAAEN +1gABDeYAAQ32AAEOBgABDhYAAQ4mAAEONgABDkYAAQ5WAAEOZgABDnYAAQ6GAAEOlgABDqYAAQ62 +AAEOxgABDtYAAQ7mAAEO9gABDwYAAQ8WAAEPJgABDzYAAQ9GAAEPVgABD2YAAQ92AAEPhgABD5YA +AQ+mAAEPtgABD8YAAQ/WAAEP5gABD/YAARAGAAEQFgABECYAARA2AAEQRgABEFYAARBmAAEQdgAB +EIYAARCWAAEQpgABELYAARDGAAEQ1gABEOYAARD2AAERBgABERYAAREmAAERNgABEUYAARFWAAAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8KAAB/CgAAfwoAAH8KAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/CgAAfwoAAH8KAAB/CgAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfwoAAH8KAAB/CgAAfwoAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8KAAB/CgAAfwoAAH8KAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/CgAAfwoAAH8KAAB/CgAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8ISwA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0 +/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/ +CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/38QAAB/EAAAfwhLAP4HCjIKCgD+WYAygAoA/lmA +MoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAy +gAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKA +CgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAK +AP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKAfwhAAH8HwP9/EAAAfxAAAH8IQAA/Cn8HgIB/CEAA +fwfA/38QAAB/EAAAfwhAAD8KfweAgH8IQAB/B8D/fxAAAH8QAAB/CEAAPwp/B4CAfwhAAAP/OwAD +/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/ +OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87 +AAP/OwB/EAAAfxAAAH8IQAADCjsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsA +A4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwAD +gDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAfwoAAH8KAAB/CgAAfwoAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAAKADT/CgA0/woANP8KADT/CgA0/woANP8K +ADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woA +NP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0 +/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/ +CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8K +ADT/fxAAAH8QAAAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5Z +gDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmA +MoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAy +gAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKA +CgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAK +AP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA +/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+ +WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5Z +gDKAfxAA/38QAAB/EAAAfxAAgH8QAP9/EAAAfxAAAH8QAIB/EAD/fxAAAH8QAAB/EACAA/87AAP/ +OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87 +AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsA +A/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD +/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/ +OwAD/zsAA/87AAP/OwAD/zsAA/87AH8QAAB/EAAAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7 +AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsA +A4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwAD +gDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOA +OwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7 +AH8KAAB/CgAAfwoAAH8KAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAACgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0 +/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/ +CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8K +ADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woA +NP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/38QAAB/EAAACgD+WYAygAoA/lmAMoAK +AP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA +/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+ +WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5Z +gDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmA +MoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAy +gAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKA +CgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAK +AP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygH8QAP9/EAAAfxAAAH8QAIB/EAD/fxAA +AH8QAAB/EACAfxAA/38QAAB/EAAAfxAAgAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsA +A/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD +/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/ +OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87 +AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwB/EAAA +fxAAAAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwAD +gDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOA +OwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7 +AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsA +A4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwB/CgAAfwoAAH8KAAB/CgAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAAoANP8KADT/CgA0/woANP8KADT/ +CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8K +ADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woA +NP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/CgA0/woANP8KADT/fwUAAH8QAAB/EAAACgD+ +WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5Z +gDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmA +MoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAy +gAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKA +CgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAKAP5ZgDKACgD+WYAygAoA/lmAMoAK +AP5ZgDKACgD+WYAygAoA/iY3Mjd/BQAAfwsA/38FAAB/EAAAfxAAAH8KwIA/N38FAAB/CwD/fwUA +AH8QAAB/EAAAfwrAgD83fwUAAH8LAP9/BQAAfxAAAH8QAAB/CsCAPzd/BQAAA/87AAP/OwAD/zsA +A/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD +/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/ +OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/OwAD/zsAA/87AAP/fwU8AH8QAAB/ +EAAAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOA +OwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7 +AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsAA4A7AAOAOwADgDsA +A4A7AAM3fwU8AH8KAAB/CgAAfwoAAH8KAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/CgAAfwoAAH8KAAB/CgAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfwoAAH8KAAB/CgAAfwoAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8KAAB/CgAAfwoAAH8KAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/CgAAfwoAAH8KAAB/CgAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfwoAAH8KAAB/CgAAfwoAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8K +AAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoA +AH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAA +fwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/ +CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/BkAAfwZAAH8GQAB/BkAAAAAB9AAA +AfQAAAAAAAAA+gAAAPoAAAAAAAAAfQAAAH0AAAAAAAAAPgAAAD4AAAAA + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/gimp/9de0e6f5cf782a299a1c062b5412dd63.msg b/share/extensions/tests/data/cmd/gimp/9de0e6f5cf782a299a1c062b5412dd63.msg new file mode 100644 index 0000000..e6bcb22 --- /dev/null +++ b/share/extensions/tests/data/cmd/gimp/9de0e6f5cf782a299a1c062b5412dd63.msg @@ -0,0 +1,2918 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: gimp +Arguments: - --batch-interpreter=plug-in-script-fu-eval -b -i + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 + +V2VsY29tZSB0byBUaW55U2NoZW1lLCBWZXJzaW9uIDEuNDAKQ29weXJpZ2h0IChjKSBEaW1pdHJp +b3MgU291ZmxpcwoKdHM+IApHaXZlczogMAp0cz4gCkV2YWw6IChkZWZpbmUgKHBuZy10by1sYXll +ciBpbWcgcG5nX2ZpbGVuYW1lIGxheWVyX25hbWUpIChsZXQqICgocG5nIChjYXIgKGZpbGUtcG5n +LWxvYWQgUlVOLU5PTklOVEVSQUNUSVZFIHBuZ19maWxlbmFtZSBwbmdfZmlsZW5hbWUpKSkgKHBu +Z19sYXllciAoY2FyIChnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIgcG5nKSkpICh4Y2ZfbGF5 +ZXIgKGNhciAoZ2ltcC1sYXllci1uZXctZnJvbS1kcmF3YWJsZSBwbmdfbGF5ZXIgaW1nKSkpKSAo +Z2ltcC1pbWFnZS1hZGQtbGF5ZXIgaW1nIHhjZl9sYXllciAtMSkgKGdpbXAtZHJhd2FibGUtc2V0 +LW5hbWUgeGNmX2xheWVyIGxheWVyX25hbWUpKSkKRXZhbDogKGxhbWJkYSAoaW1nIHBuZ19maWxl +bmFtZSBsYXllcl9uYW1lKSAobGV0KiAoKHBuZyAoY2FyIChmaWxlLXBuZy1sb2FkIFJVTi1OT05J +TlRFUkFDVElWRSBwbmdfZmlsZW5hbWUgcG5nX2ZpbGVuYW1lKSkpIChwbmdfbGF5ZXIgKGNhciAo +Z2ltcC1pbWFnZS1nZXQtYWN0aXZlLWxheWVyIHBuZykpKSAoeGNmX2xheWVyIChjYXIgKGdpbXAt +bGF5ZXItbmV3LWZyb20tZHJhd2FibGUgcG5nX2xheWVyIGltZykpKSkgKGdpbXAtaW1hZ2UtYWRk +LWxheWVyIGltZyB4Y2ZfbGF5ZXIgLTEpIChnaW1wLWRyYXdhYmxlLXNldC1uYW1lIHhjZl9sYXll +ciBsYXllcl9uYW1lKSkpCkFwcGx5IHRvOiAoKChpbWcgcG5nX2ZpbGVuYW1lIGxheWVyX25hbWUp +IChsZXQqICgocG5nIChjYXIgKGZpbGUtcG5nLWxvYWQgUlVOLU5PTklOVEVSQUNUSVZFIHBuZ19m +aWxlbmFtZSBwbmdfZmlsZW5hbWUpKSkgKHBuZ19sYXllciAoY2FyIChnaW1wLWltYWdlLWdldC1h +Y3RpdmUtbGF5ZXIgcG5nKSkpICh4Y2ZfbGF5ZXIgKGNhciAoZ2ltcC1sYXllci1uZXctZnJvbS1k +cmF3YWJsZSBwbmdfbGF5ZXIgaW1nKSkpKSAoZ2ltcC1pbWFnZS1hZGQtbGF5ZXIgaW1nIHhjZl9s +YXllciAtMSkgKGdpbXAtZHJhd2FibGUtc2V0LW5hbWUgeGNmX2xheWVyIGxheWVyX25hbWUpKSkp +CkV2YWw6IChpZiAobWFjcm8/IGZvcm0pIChtYWNyby1leHBhbmQtYWxsIChtYWNyby1leHBhbmQg +Zm9ybSkpIGZvcm0pCkV2YWw6IChtYWNybz8gZm9ybSkKRXZhbDogbWFjcm8/CkV2YWw6IGZvcm0K +QXBwbHkgdG86ICgoKGltZyBwbmdfZmlsZW5hbWUgbGF5ZXJfbmFtZSkgKGxldCogKChwbmcgKGNh +ciAoZmlsZS1wbmctbG9hZCBSVU4tTk9OSU5URVJBQ1RJVkUgcG5nX2ZpbGVuYW1lIHBuZ19maWxl +bmFtZSkpKSAocG5nX2xheWVyIChjYXIgKGdpbXAtaW1hZ2UtZ2V0LWFjdGl2ZS1sYXllciBwbmcp +KSkgKHhjZl9sYXllciAoY2FyIChnaW1wLWxheWVyLW5ldy1mcm9tLWRyYXdhYmxlIHBuZ19sYXll +ciBpbWcpKSkpIChnaW1wLWltYWdlLWFkZC1sYXllciBpbWcgeGNmX2xheWVyIC0xKSAoZ2ltcC1k +cmF3YWJsZS1zZXQtbmFtZSB4Y2ZfbGF5ZXIgbGF5ZXJfbmFtZSkpKSkKRXZhbDogZm9ybQpHaXZl +czogcG5nLXRvLWxheWVyCnRzPiAKRXZhbDogKGxldCogKChpbWcgKGNhciAoZ2ltcC1pbWFnZS1u +ZXcgMjAwIDIwMCBSR0IpKSkpIChnaW1wLWltYWdlLXNldC1yZXNvbHV0aW9uIGltZyA5Ni4wIDk2 +LjApIChnaW1wLWltYWdlLXVuZG8tZGlzYWJsZSBpbWcpIChmb3ItZWFjaCAobGFtYmRhIChuYW1l +cykgKHBuZy10by1sYXllciBpbWcgKGNhciBuYW1lcykgKGNkciBuYW1lcykpKSAobWFwIGNvbnMg +JygiL3RtcC9naW1wLW91dC0zc2gyNXhuai9TbGlkZTIucG5nIikgJygiU2xpZGUyIikpKSAoZ2lt +cC1pbWFnZS1yZXNpemUtdG8tbGF5ZXJzIGltZykgKGZvci1lYWNoIChsYW1iZGEgKGhHdWlkZSkg +KGdpbXAtaW1hZ2UtYWRkLWhndWlkZSBpbWcgaEd1aWRlKSkgJyg5NTEuMDIyNyA0OS4wMjQxMjgp +KSAoZm9yLWVhY2ggKGxhbWJkYSAodkd1aWRlKSAoZ2ltcC1pbWFnZS1hZGQtdmd1aWRlIGltZyB2 +R3VpZGUpKSAnKDQ2LjkzNjU3NyA5NTMuMDE2NTkpKSAoZ2ltcC1pbWFnZS1ncmlkLXNldC1zcGFj +aW5nIGltZyAxMDAgMTAwKSAoZ2ltcC1pbWFnZS1ncmlkLXNldC1vZmZzZXQgaW1nIDAgMCkgKGdp +bXAtaW1hZ2UtdW5kby1lbmFibGUgaW1nKSAoZ2ltcC1maWxlLXNhdmUgUlVOLU5PTklOVEVSQUNU +SVZFIGltZyAoY2FyIChnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIgaW1nKSkgIi90bXAvZ2lt +cC1vdXQtM3NoMjV4bmovcmVmX2d1aWRlcy5zdmcueGNmIiAiL3RtcC9naW1wLW91dC0zc2gyNXhu +ai9yZWZfZ3VpZGVzLnN2Zy54Y2YiKSkKRXZhbDogKGNhciAoZ2ltcC1pbWFnZS1uZXcgMjAwIDIw +MCBSR0IpKQpFdmFsOiBjYXIKRXZhbDogKGdpbXAtaW1hZ2UtbmV3IDIwMCAyMDAgUkdCKQpFdmFs +OiBnaW1wLWltYWdlLW5ldwpFdmFsOiAyMDAKRXZhbDogMjAwCkV2YWw6IFJHQgpBcHBseSB0bzog +KDIwMCAyMDAgMCkKRXZhbDogKGFwcGx5IGdpbXAtcHJvYy1kYi1jYWxsIChjb25zICJnaW1wLWlt +YWdlLW5ldyIgeCkpCkV2YWw6IGFwcGx5CkV2YWw6IGdpbXAtcHJvYy1kYi1jYWxsCkV2YWw6IChj +b25zICJnaW1wLWltYWdlLW5ldyIgeCkKRXZhbDogY29ucwpFdmFsOiAiZ2ltcC1pbWFnZS1uZXci +CkV2YWw6IHgKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1uZXciICgyMDAgMjAwIDApKQpBcHBseSB0 +bzogKCM8Rk9SRUlHTiBQUk9DRURVUkUgOTM5NjExMzE1NTI0OTY+ICgiZ2ltcC1pbWFnZS1uZXci +IDIwMCAyMDAgMCkpCkFwcGx5IHRvOiAoImdpbXAtaW1hZ2UtbmV3IiAyMDAgMjAwIDApCkFwcGx5 +IHRvOiAoKDEpKQpFdmFsOiAoZ2ltcC1pbWFnZS1zZXQtcmVzb2x1dGlvbiBpbWcgOTYuMCA5Ni4w +KQpFdmFsOiBnaW1wLWltYWdlLXNldC1yZXNvbHV0aW9uCkV2YWw6IGltZwpFdmFsOiA5Ni4wCkV2 +YWw6IDk2LjAKQXBwbHkgdG86ICgxIDk2LjAgOTYuMCkKRXZhbDogKGFwcGx5IGdpbXAtcHJvYy1k +Yi1jYWxsIChjb25zICJnaW1wLWltYWdlLXNldC1yZXNvbHV0aW9uIiB4KSkKRXZhbDogYXBwbHkK +RXZhbDogZ2ltcC1wcm9jLWRiLWNhbGwKRXZhbDogKGNvbnMgImdpbXAtaW1hZ2Utc2V0LXJlc29s +dXRpb24iIHgpCkV2YWw6IGNvbnMKRXZhbDogImdpbXAtaW1hZ2Utc2V0LXJlc29sdXRpb24iCkV2 +YWw6IHgKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1zZXQtcmVzb2x1dGlvbiIgKDEgOTYuMCA5Ni4w +KSkKQXBwbHkgdG86ICgjPEZPUkVJR04gUFJPQ0VEVVJFIDkzOTYxMTMxNTUyNDk2PiAoImdpbXAt +aW1hZ2Utc2V0LXJlc29sdXRpb24iIDEgOTYuMCA5Ni4wKSkKQXBwbHkgdG86ICgiZ2ltcC1pbWFn +ZS1zZXQtcmVzb2x1dGlvbiIgMSA5Ni4wIDk2LjApCkV2YWw6IChnaW1wLWltYWdlLXVuZG8tZGlz +YWJsZSBpbWcpCkV2YWw6IGdpbXAtaW1hZ2UtdW5kby1kaXNhYmxlCkV2YWw6IGltZwpBcHBseSB0 +bzogKDEpCkV2YWw6IChhcHBseSBnaW1wLXByb2MtZGItY2FsbCAoY29ucyAiZ2ltcC1pbWFnZS11 +bmRvLWRpc2FibGUiIHgpKQpFdmFsOiBhcHBseQpFdmFsOiBnaW1wLXByb2MtZGItY2FsbApFdmFs +OiAoY29ucyAiZ2ltcC1pbWFnZS11bmRvLWRpc2FibGUiIHgpCkV2YWw6IGNvbnMKRXZhbDogImdp +bXAtaW1hZ2UtdW5kby1kaXNhYmxlIgpFdmFsOiB4CkFwcGx5IHRvOiAoImdpbXAtaW1hZ2UtdW5k +by1kaXNhYmxlIiAoMSkpCkFwcGx5IHRvOiAoIzxGT1JFSUdOIFBST0NFRFVSRSA5Mzk2MTEzMTU1 +MjQ5Nj4gKCJnaW1wLWltYWdlLXVuZG8tZGlzYWJsZSIgMSkpCkFwcGx5IHRvOiAoImdpbXAtaW1h +Z2UtdW5kby1kaXNhYmxlIiAxKQpFdmFsOiAoZm9yLWVhY2ggKGxhbWJkYSAobmFtZXMpIChwbmct +dG8tbGF5ZXIgaW1nIChjYXIgbmFtZXMpIChjZHIgbmFtZXMpKSkgKG1hcCBjb25zICcoIi90bXAv +Z2ltcC1vdXQtM3NoMjV4bmovU2xpZGUyLnBuZyIpICcoIlNsaWRlMiIpKSkKRXZhbDogZm9yLWVh +Y2gKRXZhbDogKGxhbWJkYSAobmFtZXMpIChwbmctdG8tbGF5ZXIgaW1nIChjYXIgbmFtZXMpIChj +ZHIgbmFtZXMpKSkKQXBwbHkgdG86ICgoKG5hbWVzKSAocG5nLXRvLWxheWVyIGltZyAoY2FyIG5h +bWVzKSAoY2RyIG5hbWVzKSkpKQpFdmFsOiAoaWYgKG1hY3JvPyBmb3JtKSAobWFjcm8tZXhwYW5k +LWFsbCAobWFjcm8tZXhwYW5kIGZvcm0pKSBmb3JtKQpFdmFsOiAobWFjcm8/IGZvcm0pCkV2YWw6 +IG1hY3JvPwpFdmFsOiBmb3JtCkFwcGx5IHRvOiAoKChuYW1lcykgKHBuZy10by1sYXllciBpbWcg +KGNhciBuYW1lcykgKGNkciBuYW1lcykpKSkKRXZhbDogZm9ybQpFdmFsOiAobWFwIGNvbnMgJygi +L3RtcC9naW1wLW91dC0zc2gyNXhuai9TbGlkZTIucG5nIikgJygiU2xpZGUyIikpCkV2YWw6IG1h +cApFdmFsOiBjb25zCkV2YWw6ICcoIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovU2xpZGUyLnBuZyIp +CkV2YWw6ICcoIlNsaWRlMiIpCkFwcGx5IHRvOiAoIzxjb25zIFBST0NFRFVSRSA3Nj4gKCIvdG1w +L2dpbXAtb3V0LTNzaDI1eG5qL1NsaWRlMi5wbmciKSAoIlNsaWRlMiIpKQpFdmFsOiAoaWYgKG51 +bGw/IGxpc3RzKSAoYXBwbHkgcHJvYykgKGlmIChudWxsPyAoY2FyIGxpc3RzKSkgJygpIChsZXQq +ICgodW56IChhcHBseSB1bnppcDEtd2l0aC1jZHIgbGlzdHMpKSAoY2FycyAoY2FyIHVueikpIChj +ZHJzIChjZHIgdW56KSkpIChjb25zIChhcHBseSBwcm9jIGNhcnMpIChhcHBseSBtYXAgKGNvbnMg +cHJvYyBjZHJzKSkpKSkpCkV2YWw6IChudWxsPyBsaXN0cykKRXZhbDogbnVsbD8KRXZhbDogbGlz +dHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL1NsaWRlMi5wbmciKSAoIlNs +aWRlMiIpKSkKRXZhbDogKGlmIChudWxsPyAoY2FyIGxpc3RzKSkgJygpIChsZXQqICgodW56IChh +cHBseSB1bnppcDEtd2l0aC1jZHIgbGlzdHMpKSAoY2FycyAoY2FyIHVueikpIChjZHJzIChjZHIg +dW56KSkpIChjb25zIChhcHBseSBwcm9jIGNhcnMpIChhcHBseSBtYXAgKGNvbnMgcHJvYyBjZHJz +KSkpKSkKRXZhbDogKG51bGw/IChjYXIgbGlzdHMpKQpFdmFsOiBudWxsPwpFdmFsOiAoY2FyIGxp +c3RzKQpFdmFsOiBjYXIKRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LTNz +aDI1eG5qL1NsaWRlMi5wbmciKSAoIlNsaWRlMiIpKSkKQXBwbHkgdG86ICgoIi90bXAvZ2ltcC1v +dXQtM3NoMjV4bmovU2xpZGUyLnBuZyIpKQpFdmFsOiAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAx +LXdpdGgtY2RyIGxpc3RzKSkgKGNhcnMgKGNhciB1bnopKSAoY2RycyAoY2RyIHVueikpKSAoY29u +cyAoYXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpKSkKRXZhbDog +KGFwcGx5IHVuemlwMS13aXRoLWNkciBsaXN0cykKRXZhbDogYXBwbHkKRXZhbDogdW56aXAxLXdp +dGgtY2RyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoIzxDTE9TVVJFPiAoKCIvdG1wL2dpbXAtb3V0 +LTNzaDI1eG5qL1NsaWRlMi5wbmciKSAoIlNsaWRlMiIpKSkKQXBwbHkgdG86ICgoIi90bXAvZ2lt +cC1vdXQtM3NoMjV4bmovU2xpZGUyLnBuZyIpICgiU2xpZGUyIikpCkV2YWw6ICh1bnppcDEtd2l0 +aC1jZHItaXRlcmF0aXZlIGxpc3RzICcoKSAnKCkpCkV2YWw6IHVuemlwMS13aXRoLWNkci1pdGVy +YXRpdmUKRXZhbDogbGlzdHMKRXZhbDogJygpCkV2YWw6ICcoKQpBcHBseSB0bzogKCgoIi90bXAv +Z2ltcC1vdXQtM3NoMjV4bmovU2xpZGUyLnBuZyIpICgiU2xpZGUyIikpICgpICgpKQpFdmFsOiAo +aWYgKG51bGw/IGxpc3RzKSAoY29ucyBjYXJzIGNkcnMpIChsZXQgKChjYXIxIChjYWFyIGxpc3Rz +KSkgKGNkcjEgKGNkYXIgbGlzdHMpKSkgKHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUgKGNkciBs +aXN0cykgKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEpKSAoYXBwZW5kIGNkcnMgKGxpc3QgY2RyMSkp +KSkpCkV2YWw6IChudWxsPyBsaXN0cykKRXZhbDogbnVsbD8KRXZhbDogbGlzdHMKQXBwbHkgdG86 +ICgoKCIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL1NsaWRlMi5wbmciKSAoIlNsaWRlMiIpKSkKRXZh +bDogKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAodW56aXAx +LXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkp +IChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKQpFdmFsOiAoY2FhciBsaXN0cykKRXZhbDogY2Fh +cgpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovU2xpZGUy +LnBuZyIpICgiU2xpZGUyIikpKQpFdmFsOiAoY2FyIChjYXIgeCkpCkV2YWw6IGNhcgpFdmFsOiAo +Y2FyIHgpCkV2YWw6IGNhcgpFdmFsOiB4CkFwcGx5IHRvOiAoKCgiL3RtcC9naW1wLW91dC0zc2gy +NXhuai9TbGlkZTIucG5nIikgKCJTbGlkZTIiKSkpCkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0 +LTNzaDI1eG5qL1NsaWRlMi5wbmciKSkKRXZhbDogKGNkYXIgbGlzdHMpCkV2YWw6IGNkYXIKRXZh +bDogbGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL1NsaWRlMi5wbmci +KSAoIlNsaWRlMiIpKSkKRXZhbDogKGNkciAoY2FyIHgpKQpFdmFsOiBjZHIKRXZhbDogKGNhciB4 +KQpFdmFsOiBjYXIKRXZhbDogeApBcHBseSB0bzogKCgoIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmov +U2xpZGUyLnBuZyIpICgiU2xpZGUyIikpKQpBcHBseSB0bzogKCgiL3RtcC9naW1wLW91dC0zc2gy +NXhuai9TbGlkZTIucG5nIikpCkV2YWw6ICh1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlIChjZHIg +bGlzdHMpIChhcHBlbmQgY2FycyAobGlzdCBjYXIxKSkgKGFwcGVuZCBjZHJzIChsaXN0IGNkcjEp +KSkKRXZhbDogdW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZQpFdmFsOiAoY2RyIGxpc3RzKQpFdmFs +OiBjZHIKRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL1Ns +aWRlMi5wbmciKSAoIlNsaWRlMiIpKSkKRXZhbDogKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEpKQpF +dmFsOiBhcHBlbmQKRXZhbDogY2FycwpFdmFsOiAobGlzdCBjYXIxKQpFdmFsOiBsaXN0CkV2YWw6 +IGNhcjEKQXBwbHkgdG86ICgiL3RtcC9naW1wLW91dC0zc2gyNXhuai9TbGlkZTIucG5nIikKRXZh +bDogeApBcHBseSB0bzogKCgpICgiL3RtcC9naW1wLW91dC0zc2gyNXhuai9TbGlkZTIucG5nIikp +CkV2YWw6IChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkKRXZhbDogYXBwZW5kCkV2YWw6IGNkcnMK +RXZhbDogKGxpc3QgY2RyMSkKRXZhbDogbGlzdApFdmFsOiBjZHIxCkFwcGx5IHRvOiAoKCkpCkV2 +YWw6IHgKQXBwbHkgdG86ICgoKSAoKCkpKQpBcHBseSB0bzogKCgoIlNsaWRlMiIpKSAoIi90bXAv +Z2ltcC1vdXQtM3NoMjV4bmovU2xpZGUyLnBuZyIpICgoKSkpCkV2YWw6IChpZiAobnVsbD8gbGlz +dHMpIChjb25zIGNhcnMgY2RycykgKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2Rh +ciBsaXN0cykpKSAodW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5k +IGNhcnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKSkKRXZhbDogKG51 +bGw/IGxpc3RzKQpFdmFsOiBudWxsPwpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoIlNsaWRlMiIp +KSkKRXZhbDogKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAo +dW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3Qg +Y2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKQpFdmFsOiAoY2FhciBsaXN0cykKRXZh +bDogY2FhcgpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoIlNsaWRlMiIpKSkKRXZhbDogKGNhciAo +Y2FyIHgpKQpFdmFsOiBjYXIKRXZhbDogKGNhciB4KQpFdmFsOiBjYXIKRXZhbDogeApBcHBseSB0 +bzogKCgoIlNsaWRlMiIpKSkKQXBwbHkgdG86ICgoIlNsaWRlMiIpKQpFdmFsOiAoY2RhciBsaXN0 +cykKRXZhbDogY2RhcgpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoIlNsaWRlMiIpKSkKRXZhbDog +KGNkciAoY2FyIHgpKQpFdmFsOiBjZHIKRXZhbDogKGNhciB4KQpFdmFsOiBjYXIKRXZhbDogeApB +cHBseSB0bzogKCgoIlNsaWRlMiIpKSkKQXBwbHkgdG86ICgoIlNsaWRlMiIpKQpFdmFsOiAodW56 +aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2Fy +MSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpCkV2YWw6IHVuemlwMS13aXRoLWNkci1pdGVy +YXRpdmUKRXZhbDogKGNkciBsaXN0cykKRXZhbDogY2RyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAo +KCgiU2xpZGUyIikpKQpFdmFsOiAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpCkV2YWw6IGFwcGVu +ZApFdmFsOiBjYXJzCkV2YWw6IChsaXN0IGNhcjEpCkV2YWw6IGxpc3QKRXZhbDogY2FyMQpBcHBs +eSB0bzogKCJTbGlkZTIiKQpFdmFsOiB4CkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0LTNzaDI1 +eG5qL1NsaWRlMi5wbmciKSAoIlNsaWRlMiIpKQpFdmFsOiAoYXBwZW5kIGNkcnMgKGxpc3QgY2Ry +MSkpCkV2YWw6IGFwcGVuZApFdmFsOiBjZHJzCkV2YWw6IChsaXN0IGNkcjEpCkV2YWw6IGxpc3QK +RXZhbDogY2RyMQpBcHBseSB0bzogKCgpKQpFdmFsOiB4CkFwcGx5IHRvOiAoKCgpKSAoKCkpKQpB +cHBseSB0bzogKCgpICgiL3RtcC9naW1wLW91dC0zc2gyNXhuai9TbGlkZTIucG5nIiAiU2xpZGUy +IikgKCgpICgpKSkKRXZhbDogKGlmIChudWxsPyBsaXN0cykgKGNvbnMgY2FycyBjZHJzKSAobGV0 +ICgoY2FyMSAoY2FhciBsaXN0cykpIChjZHIxIChjZGFyIGxpc3RzKSkpICh1bnppcDEtd2l0aC1j +ZHItaXRlcmF0aXZlIChjZHIgbGlzdHMpIChhcHBlbmQgY2FycyAobGlzdCBjYXIxKSkgKGFwcGVu +ZCBjZHJzIChsaXN0IGNkcjEpKSkpKQpFdmFsOiAobnVsbD8gbGlzdHMpCkV2YWw6IG51bGw/CkV2 +YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCkpCkV2YWw6IChjb25zIGNhcnMgY2RycykKRXZhbDogY29u +cwpFdmFsOiBjYXJzCkV2YWw6IGNkcnMKQXBwbHkgdG86ICgoIi90bXAvZ2ltcC1vdXQtM3NoMjV4 +bmovU2xpZGUyLnBuZyIgIlNsaWRlMiIpICgoKSAoKSkpCkV2YWw6IChjYXIgdW56KQpFdmFsOiBj +YXIKRXZhbDogdW56CkFwcGx5IHRvOiAoKCgiL3RtcC9naW1wLW91dC0zc2gyNXhuai9TbGlkZTIu +cG5nIiAiU2xpZGUyIikgKCkgKCkpKQpFdmFsOiAoY2RyIHVueikKRXZhbDogY2RyCkV2YWw6IHVu +egpBcHBseSB0bzogKCgoIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovU2xpZGUyLnBuZyIgIlNsaWRl +MiIpICgpICgpKSkKRXZhbDogKGNvbnMgKGFwcGx5IHByb2MgY2FycykgKGFwcGx5IG1hcCAoY29u +cyBwcm9jIGNkcnMpKSkKRXZhbDogY29ucwpFdmFsOiAoYXBwbHkgcHJvYyBjYXJzKQpFdmFsOiBh +cHBseQpFdmFsOiBwcm9jCkV2YWw6IGNhcnMKQXBwbHkgdG86ICgjPGNvbnMgUFJPQ0VEVVJFIDc2 +PiAoIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovU2xpZGUyLnBuZyIgIlNsaWRlMiIpKQpBcHBseSB0 +bzogKCIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL1NsaWRlMi5wbmciICJTbGlkZTIiKQpFdmFsOiAo +YXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpCkV2YWw6IGFwcGx5CkV2YWw6IG1hcApFdmFsOiAo +Y29ucyBwcm9jIGNkcnMpCkV2YWw6IGNvbnMKRXZhbDogcHJvYwpFdmFsOiBjZHJzCkFwcGx5IHRv +OiAoIzxjb25zIFBST0NFRFVSRSA3Nj4gKCgpICgpKSkKQXBwbHkgdG86ICgjPENMT1NVUkU+ICgj +PGNvbnMgUFJPQ0VEVVJFIDc2PiAoKSAoKSkpCkFwcGx5IHRvOiAoIzxjb25zIFBST0NFRFVSRSA3 +Nj4gKCkgKCkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChhcHBseSBwcm9jKSAoaWYgKG51bGw/ +IChjYXIgbGlzdHMpKSAnKCkgKGxldCogKCh1bnogKGFwcGx5IHVuemlwMS13aXRoLWNkciBsaXN0 +cykpIChjYXJzIChjYXIgdW56KSkgKGNkcnMgKGNkciB1bnopKSkgKGNvbnMgKGFwcGx5IHByb2Mg +Y2FycykgKGFwcGx5IG1hcCAoY29ucyBwcm9jIGNkcnMpKSkpKSkKRXZhbDogKG51bGw/IGxpc3Rz +KQpFdmFsOiBudWxsPwpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoKSAoKSkpCkV2YWw6IChpZiAo +bnVsbD8gKGNhciBsaXN0cykpICcoKSAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAxLXdpdGgtY2Ry +IGxpc3RzKSkgKGNhcnMgKGNhciB1bnopKSAoY2RycyAoY2RyIHVueikpKSAoY29ucyAoYXBwbHkg +cHJvYyBjYXJzKSAoYXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpKSkpCkV2YWw6IChudWxsPyAo +Y2FyIGxpc3RzKSkKRXZhbDogbnVsbD8KRXZhbDogKGNhciBsaXN0cykKRXZhbDogY2FyCkV2YWw6 +IGxpc3RzCkFwcGx5IHRvOiAoKCgpICgpKSkKQXBwbHkgdG86ICgoKSkKRXZhbDogJygpCkFwcGx5 +IHRvOiAoKCIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpICgp +KQpBcHBseSB0bzogKCM8Q0xPU1VSRT4gKCgiL3RtcC9naW1wLW91dC0zc2gyNXhuai9TbGlkZTIu +cG5nIiAuICJTbGlkZTIiKSkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChhcHBseSBwcm9jKSAo +aWYgKG51bGw/IChjYXIgbGlzdHMpKSAjdCAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAxLXdpdGgt +Y2RyIGxpc3RzKSkgKGNhcnMgKGNhciB1bnopKSAoY2RycyAoY2RyIHVueikpKSAoYXBwbHkgcHJv +YyBjYXJzKSAoYXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpKSkpCkV2YWw6IChudWxsPyBsaXN0 +cykKRXZhbDogbnVsbD8KRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKCgiL3RtcC9naW1wLW91dC0z +c2gyNXhuai9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSkpKQpFdmFsOiAoaWYgKG51bGw/IChjYXIg +bGlzdHMpKSAjdCAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAxLXdpdGgtY2RyIGxpc3RzKSkgKGNh +cnMgKGNhciB1bnopKSAoY2RycyAoY2RyIHVueikpKSAoYXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkg +bWFwIChjb25zIHByb2MgY2RycykpKSkKRXZhbDogKG51bGw/IChjYXIgbGlzdHMpKQpFdmFsOiBu +dWxsPwpFdmFsOiAoY2FyIGxpc3RzKQpFdmFsOiBjYXIKRXZhbDogbGlzdHMKQXBwbHkgdG86ICgo +KCgiL3RtcC9naW1wLW91dC0zc2gyNXhuai9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSkpKQpBcHBs +eSB0bzogKCgoIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovU2xpZGUyLnBuZyIgLiAiU2xpZGUyIikp +KQpFdmFsOiAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAxLXdpdGgtY2RyIGxpc3RzKSkgKGNhcnMg +KGNhciB1bnopKSAoY2RycyAoY2RyIHVueikpKSAoYXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkgbWFw +IChjb25zIHByb2MgY2RycykpKQpFdmFsOiAoYXBwbHkgdW56aXAxLXdpdGgtY2RyIGxpc3RzKQpF +dmFsOiBhcHBseQpFdmFsOiB1bnppcDEtd2l0aC1jZHIKRXZhbDogbGlzdHMKQXBwbHkgdG86ICgj +PENMT1NVUkU+ICgoKCIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL1NsaWRlMi5wbmciIC4gIlNsaWRl +MiIpKSkpCkFwcGx5IHRvOiAoKCgiL3RtcC9naW1wLW91dC0zc2gyNXhuai9TbGlkZTIucG5nIiAu +ICJTbGlkZTIiKSkpCkV2YWw6ICh1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlIGxpc3RzICcoKSAn +KCkpCkV2YWw6IHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUKRXZhbDogbGlzdHMKRXZhbDogJygp +CkV2YWw6ICcoKQpBcHBseSB0bzogKCgoKCIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL1NsaWRlMi5w +bmciIC4gIlNsaWRlMiIpKSkgKCkgKCkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChjb25zIGNh +cnMgY2RycykgKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAo +dW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3Qg +Y2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKSkKRXZhbDogKG51bGw/IGxpc3RzKQpF +dmFsOiBudWxsPwpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoKCIvdG1wL2dpbXAtb3V0LTNzaDI1 +eG5qL1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpKSkpCkV2YWw6IChsZXQgKChjYXIxIChjYWFyIGxp +c3RzKSkgKGNkcjEgKGNkYXIgbGlzdHMpKSkgKHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUgKGNk +ciBsaXN0cykgKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEpKSAoYXBwZW5kIGNkcnMgKGxpc3QgY2Ry +MSkpKSkKRXZhbDogKGNhYXIgbGlzdHMpCkV2YWw6IGNhYXIKRXZhbDogbGlzdHMKQXBwbHkgdG86 +ICgoKCgiL3RtcC9naW1wLW91dC0zc2gyNXhuai9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSkpKQpF +dmFsOiAoY2FyIChjYXIgeCkpCkV2YWw6IGNhcgpFdmFsOiAoY2FyIHgpCkV2YWw6IGNhcgpFdmFs +OiB4CkFwcGx5IHRvOiAoKCgoIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovU2xpZGUyLnBuZyIgLiAi +U2xpZGUyIikpKSkKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL1NsaWRlMi5w +bmciIC4gIlNsaWRlMiIpKSkKRXZhbDogKGNkYXIgbGlzdHMpCkV2YWw6IGNkYXIKRXZhbDogbGlz +dHMKQXBwbHkgdG86ICgoKCgiL3RtcC9naW1wLW91dC0zc2gyNXhuai9TbGlkZTIucG5nIiAuICJT +bGlkZTIiKSkpKQpFdmFsOiAoY2RyIChjYXIgeCkpCkV2YWw6IGNkcgpFdmFsOiAoY2FyIHgpCkV2 +YWw6IGNhcgpFdmFsOiB4CkFwcGx5IHRvOiAoKCgoIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovU2xp +ZGUyLnBuZyIgLiAiU2xpZGUyIikpKSkKQXBwbHkgdG86ICgoKCIvdG1wL2dpbXAtb3V0LTNzaDI1 +eG5qL1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpKSkKRXZhbDogKHVuemlwMS13aXRoLWNkci1pdGVy +YXRpdmUgKGNkciBsaXN0cykgKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEpKSAoYXBwZW5kIGNkcnMg +KGxpc3QgY2RyMSkpKQpFdmFsOiB1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlCkV2YWw6IChjZHIg +bGlzdHMpCkV2YWw6IGNkcgpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoKCIvdG1wL2dpbXAtb3V0 +LTNzaDI1eG5qL1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpKSkpCkV2YWw6IChhcHBlbmQgY2FycyAo +bGlzdCBjYXIxKSkKRXZhbDogYXBwZW5kCkV2YWw6IGNhcnMKRXZhbDogKGxpc3QgY2FyMSkKRXZh +bDogbGlzdApFdmFsOiBjYXIxCkFwcGx5IHRvOiAoKCIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL1Ns +aWRlMi5wbmciIC4gIlNsaWRlMiIpKQpFdmFsOiB4CkFwcGx5IHRvOiAoKCkgKCgiL3RtcC9naW1w +LW91dC0zc2gyNXhuai9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSkpCkV2YWw6IChhcHBlbmQgY2Ry +cyAobGlzdCBjZHIxKSkKRXZhbDogYXBwZW5kCkV2YWw6IGNkcnMKRXZhbDogKGxpc3QgY2RyMSkK +RXZhbDogbGlzdApFdmFsOiBjZHIxCkFwcGx5IHRvOiAoKCkpCkV2YWw6IHgKQXBwbHkgdG86ICgo +KSAoKCkpKQpBcHBseSB0bzogKCgpICgoIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovU2xpZGUyLnBu +ZyIgLiAiU2xpZGUyIikpICgoKSkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChjb25zIGNhcnMg +Y2RycykgKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAodW56 +aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2Fy +MSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKSkKRXZhbDogKG51bGw/IGxpc3RzKQpFdmFs +OiBudWxsPwpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgpKQpFdmFsOiAoY29ucyBjYXJzIGNkcnMp +CkV2YWw6IGNvbnMKRXZhbDogY2FycwpFdmFsOiBjZHJzCkFwcGx5IHRvOiAoKCgiL3RtcC9naW1w +LW91dC0zc2gyNXhuai9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSkgKCgpKSkKRXZhbDogKGNhciB1 +bnopCkV2YWw6IGNhcgpFdmFsOiB1bnoKQXBwbHkgdG86ICgoKCgiL3RtcC9naW1wLW91dC0zc2gy +NXhuai9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSkgKCkpKQpFdmFsOiAoY2RyIHVueikKRXZhbDog +Y2RyCkV2YWw6IHVuegpBcHBseSB0bzogKCgoKCIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL1NsaWRl +Mi5wbmciIC4gIlNsaWRlMiIpKSAoKSkpCkV2YWw6IChhcHBseSBwcm9jIGNhcnMpCkV2YWw6IGFw +cGx5CkV2YWw6IHByb2MKRXZhbDogY2FycwpBcHBseSB0bzogKCM8Q0xPU1VSRT4gKCgiL3RtcC9n +aW1wLW91dC0zc2gyNXhuai9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSkpCkFwcGx5IHRvOiAoKCIv +dG1wL2dpbXAtb3V0LTNzaDI1eG5qL1NsaWRlMi5wbmciIC4gIlNsaWRlMiIpKQpFdmFsOiAocG5n +LXRvLWxheWVyIGltZyAoY2FyIG5hbWVzKSAoY2RyIG5hbWVzKSkKRXZhbDogcG5nLXRvLWxheWVy +CkV2YWw6IGltZwpFdmFsOiAoY2FyIG5hbWVzKQpFdmFsOiBjYXIKRXZhbDogbmFtZXMKQXBwbHkg +dG86ICgoIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovU2xpZGUyLnBuZyIgLiAiU2xpZGUyIikpCkV2 +YWw6IChjZHIgbmFtZXMpCkV2YWw6IGNkcgpFdmFsOiBuYW1lcwpBcHBseSB0bzogKCgiL3RtcC9n +aW1wLW91dC0zc2gyNXhuai9TbGlkZTIucG5nIiAuICJTbGlkZTIiKSkKQXBwbHkgdG86ICgxICIv +dG1wL2dpbXAtb3V0LTNzaDI1eG5qL1NsaWRlMi5wbmciICJTbGlkZTIiKQpFdmFsOiAobGV0KiAo +KHBuZyAoY2FyIChmaWxlLXBuZy1sb2FkIFJVTi1OT05JTlRFUkFDVElWRSBwbmdfZmlsZW5hbWUg +cG5nX2ZpbGVuYW1lKSkpIChwbmdfbGF5ZXIgKGNhciAoZ2ltcC1pbWFnZS1nZXQtYWN0aXZlLWxh +eWVyIHBuZykpKSAoeGNmX2xheWVyIChjYXIgKGdpbXAtbGF5ZXItbmV3LWZyb20tZHJhd2FibGUg +cG5nX2xheWVyIGltZykpKSkgKGdpbXAtaW1hZ2UtYWRkLWxheWVyIGltZyB4Y2ZfbGF5ZXIgLTEp +IChnaW1wLWRyYXdhYmxlLXNldC1uYW1lIHhjZl9sYXllciBsYXllcl9uYW1lKSkKRXZhbDogKGNh +ciAoZmlsZS1wbmctbG9hZCBSVU4tTk9OSU5URVJBQ1RJVkUgcG5nX2ZpbGVuYW1lIHBuZ19maWxl +bmFtZSkpCkV2YWw6IGNhcgpFdmFsOiAoZmlsZS1wbmctbG9hZCBSVU4tTk9OSU5URVJBQ1RJVkUg +cG5nX2ZpbGVuYW1lIHBuZ19maWxlbmFtZSkKRXZhbDogZmlsZS1wbmctbG9hZApFdmFsOiBSVU4t +Tk9OSU5URVJBQ1RJVkUKRXZhbDogcG5nX2ZpbGVuYW1lCkV2YWw6IHBuZ19maWxlbmFtZQpBcHBs +eSB0bzogKDEgIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovU2xpZGUyLnBuZyIgIi90bXAvZ2ltcC1v +dXQtM3NoMjV4bmovU2xpZGUyLnBuZyIpCkV2YWw6IChhcHBseSBnaW1wLXByb2MtZGItY2FsbCAo +Y29ucyAiZmlsZS1wbmctbG9hZCIgeCkpCkV2YWw6IGFwcGx5CkV2YWw6IGdpbXAtcHJvYy1kYi1j +YWxsCkV2YWw6IChjb25zICJmaWxlLXBuZy1sb2FkIiB4KQpFdmFsOiBjb25zCkV2YWw6ICJmaWxl +LXBuZy1sb2FkIgpFdmFsOiB4CkFwcGx5IHRvOiAoImZpbGUtcG5nLWxvYWQiICgxICIvdG1wL2dp +bXAtb3V0LTNzaDI1eG5qL1NsaWRlMi5wbmciICIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL1NsaWRl +Mi5wbmciKSkKQXBwbHkgdG86ICgjPEZPUkVJR04gUFJPQ0VEVVJFIDkzOTYxMTMxNTUyNDk2PiAo +ImZpbGUtcG5nLWxvYWQiIDEgIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovU2xpZGUyLnBuZyIgIi90 +bXAvZ2ltcC1vdXQtM3NoMjV4bmovU2xpZGUyLnBuZyIpKQpBcHBseSB0bzogKCJmaWxlLXBuZy1s +b2FkIiAxICIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL1NsaWRlMi5wbmciICIvdG1wL2dpbXAtb3V0 +LTNzaDI1eG5qL1NsaWRlMi5wbmciKQpBcHBseSB0bzogKCgyKSkKRXZhbDogKGNhciAoZ2ltcC1p +bWFnZS1nZXQtYWN0aXZlLWxheWVyIHBuZykpCkV2YWw6IGNhcgpFdmFsOiAoZ2ltcC1pbWFnZS1n +ZXQtYWN0aXZlLWxheWVyIHBuZykKRXZhbDogZ2ltcC1pbWFnZS1nZXQtYWN0aXZlLWxheWVyCkV2 +YWw6IHBuZwpBcHBseSB0bzogKDIpCkV2YWw6IChhcHBseSBnaW1wLXByb2MtZGItY2FsbCAoY29u +cyAiZ2ltcC1pbWFnZS1nZXQtYWN0aXZlLWxheWVyIiB4KSkKRXZhbDogYXBwbHkKRXZhbDogZ2lt +cC1wcm9jLWRiLWNhbGwKRXZhbDogKGNvbnMgImdpbXAtaW1hZ2UtZ2V0LWFjdGl2ZS1sYXllciIg +eCkKRXZhbDogY29ucwpFdmFsOiAiZ2ltcC1pbWFnZS1nZXQtYWN0aXZlLWxheWVyIgpFdmFsOiB4 +CkFwcGx5IHRvOiAoImdpbXAtaW1hZ2UtZ2V0LWFjdGl2ZS1sYXllciIgKDIpKQpBcHBseSB0bzog +KCM8Rk9SRUlHTiBQUk9DRURVUkUgOTM5NjExMzE1NTI0OTY+ICgiZ2ltcC1pbWFnZS1nZXQtYWN0 +aXZlLWxheWVyIiAyKSkKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1nZXQtYWN0aXZlLWxheWVyIiAy +KQpBcHBseSB0bzogKCgzKSkKRXZhbDogKGNhciAoZ2ltcC1sYXllci1uZXctZnJvbS1kcmF3YWJs +ZSBwbmdfbGF5ZXIgaW1nKSkKRXZhbDogY2FyCkV2YWw6IChnaW1wLWxheWVyLW5ldy1mcm9tLWRy +YXdhYmxlIHBuZ19sYXllciBpbWcpCkV2YWw6IGdpbXAtbGF5ZXItbmV3LWZyb20tZHJhd2FibGUK +RXZhbDogcG5nX2xheWVyCkV2YWw6IGltZwpBcHBseSB0bzogKDMgMSkKRXZhbDogKGFwcGx5IGdp +bXAtcHJvYy1kYi1jYWxsIChjb25zICJnaW1wLWxheWVyLW5ldy1mcm9tLWRyYXdhYmxlIiB4KSkK +RXZhbDogYXBwbHkKRXZhbDogZ2ltcC1wcm9jLWRiLWNhbGwKRXZhbDogKGNvbnMgImdpbXAtbGF5 +ZXItbmV3LWZyb20tZHJhd2FibGUiIHgpCkV2YWw6IGNvbnMKRXZhbDogImdpbXAtbGF5ZXItbmV3 +LWZyb20tZHJhd2FibGUiCkV2YWw6IHgKQXBwbHkgdG86ICgiZ2ltcC1sYXllci1uZXctZnJvbS1k +cmF3YWJsZSIgKDMgMSkpCkFwcGx5IHRvOiAoIzxGT1JFSUdOIFBST0NFRFVSRSA5Mzk2MTEzMTU1 +MjQ5Nj4gKCJnaW1wLWxheWVyLW5ldy1mcm9tLWRyYXdhYmxlIiAzIDEpKQpBcHBseSB0bzogKCJn +aW1wLWxheWVyLW5ldy1mcm9tLWRyYXdhYmxlIiAzIDEpCkFwcGx5IHRvOiAoKDQpKQpFdmFsOiAo +Z2ltcC1pbWFnZS1hZGQtbGF5ZXIgaW1nIHhjZl9sYXllciAtMSkKRXZhbDogZ2ltcC1pbWFnZS1h +ZGQtbGF5ZXIKRXZhbDogaW1nCkV2YWw6IHhjZl9sYXllcgpFdmFsOiAtMQpBcHBseSB0bzogKDEg +NCAtMSkKRXZhbDogKGFwcGx5IGdpbXAtcHJvYy1kYi1jYWxsIChjb25zICJnaW1wLWltYWdlLWFk +ZC1sYXllciIgeCkpCkV2YWw6IGFwcGx5CkV2YWw6IGdpbXAtcHJvYy1kYi1jYWxsCkV2YWw6IChj +b25zICJnaW1wLWltYWdlLWFkZC1sYXllciIgeCkKRXZhbDogY29ucwpFdmFsOiAiZ2ltcC1pbWFn +ZS1hZGQtbGF5ZXIiCkV2YWw6IHgKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1hZGQtbGF5ZXIiICgx +IDQgLTEpKQpBcHBseSB0bzogKCM8Rk9SRUlHTiBQUk9DRURVUkUgOTM5NjExMzE1NTI0OTY+ICgi +Z2ltcC1pbWFnZS1hZGQtbGF5ZXIiIDEgNCAtMSkpCkFwcGx5IHRvOiAoImdpbXAtaW1hZ2UtYWRk +LWxheWVyIiAxIDQgLTEpCkV2YWw6IChnaW1wLWRyYXdhYmxlLXNldC1uYW1lIHhjZl9sYXllciBs +YXllcl9uYW1lKQpFdmFsOiBnaW1wLWRyYXdhYmxlLXNldC1uYW1lCkV2YWw6IHhjZl9sYXllcgpF +dmFsOiBsYXllcl9uYW1lCkFwcGx5IHRvOiAoNCAiU2xpZGUyIikKRXZhbDogKGFwcGx5IGdpbXAt +cHJvYy1kYi1jYWxsIChjb25zICJnaW1wLWRyYXdhYmxlLXNldC1uYW1lIiB4KSkKRXZhbDogYXBw +bHkKRXZhbDogZ2ltcC1wcm9jLWRiLWNhbGwKRXZhbDogKGNvbnMgImdpbXAtZHJhd2FibGUtc2V0 +LW5hbWUiIHgpCkV2YWw6IGNvbnMKRXZhbDogImdpbXAtZHJhd2FibGUtc2V0LW5hbWUiCkV2YWw6 +IHgKQXBwbHkgdG86ICgiZ2ltcC1kcmF3YWJsZS1zZXQtbmFtZSIgKDQgIlNsaWRlMiIpKQpBcHBs +eSB0bzogKCM8Rk9SRUlHTiBQUk9DRURVUkUgOTM5NjExMzE1NTI0OTY+ICgiZ2ltcC1kcmF3YWJs +ZS1zZXQtbmFtZSIgNCAiU2xpZGUyIikpCkFwcGx5IHRvOiAoImdpbXAtZHJhd2FibGUtc2V0LW5h +bWUiIDQgIlNsaWRlMiIpCkV2YWw6IChhcHBseSBtYXAgKGNvbnMgcHJvYyBjZHJzKSkKRXZhbDog +YXBwbHkKRXZhbDogbWFwCkV2YWw6IChjb25zIHByb2MgY2RycykKRXZhbDogY29ucwpFdmFsOiBw +cm9jCkV2YWw6IGNkcnMKQXBwbHkgdG86ICgjPENMT1NVUkU+ICgoKSkpCkFwcGx5IHRvOiAoIzxD +TE9TVVJFPiAoIzxDTE9TVVJFPiAoKSkpCkFwcGx5IHRvOiAoIzxDTE9TVVJFPiAoKSkKRXZhbDog +KGlmIChudWxsPyBsaXN0cykgKGFwcGx5IHByb2MpIChpZiAobnVsbD8gKGNhciBsaXN0cykpICco +KSAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAxLXdpdGgtY2RyIGxpc3RzKSkgKGNhcnMgKGNhciB1 +bnopKSAoY2RycyAoY2RyIHVueikpKSAoY29ucyAoYXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkgbWFw +IChjb25zIHByb2MgY2RycykpKSkpKQpFdmFsOiAobnVsbD8gbGlzdHMpCkV2YWw6IG51bGw/CkV2 +YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCgpKSkKRXZhbDogKGlmIChudWxsPyAoY2FyIGxpc3RzKSkg +JygpIChsZXQqICgodW56IChhcHBseSB1bnppcDEtd2l0aC1jZHIgbGlzdHMpKSAoY2FycyAoY2Fy +IHVueikpIChjZHJzIChjZHIgdW56KSkpIChjb25zIChhcHBseSBwcm9jIGNhcnMpIChhcHBseSBt +YXAgKGNvbnMgcHJvYyBjZHJzKSkpKSkKRXZhbDogKG51bGw/IChjYXIgbGlzdHMpKQpFdmFsOiBu +dWxsPwpFdmFsOiAoY2FyIGxpc3RzKQpFdmFsOiBjYXIKRXZhbDogbGlzdHMKQXBwbHkgdG86ICgo +KCkpKQpBcHBseSB0bzogKCgpKQpFdmFsOiAnKCkKRXZhbDogKGdpbXAtaW1hZ2UtcmVzaXplLXRv +LWxheWVycyBpbWcpCkV2YWw6IGdpbXAtaW1hZ2UtcmVzaXplLXRvLWxheWVycwpFdmFsOiBpbWcK +QXBwbHkgdG86ICgxKQpFdmFsOiAoYXBwbHkgZ2ltcC1wcm9jLWRiLWNhbGwgKGNvbnMgImdpbXAt +aW1hZ2UtcmVzaXplLXRvLWxheWVycyIgeCkpCkV2YWw6IGFwcGx5CkV2YWw6IGdpbXAtcHJvYy1k +Yi1jYWxsCkV2YWw6IChjb25zICJnaW1wLWltYWdlLXJlc2l6ZS10by1sYXllcnMiIHgpCkV2YWw6 +IGNvbnMKRXZhbDogImdpbXAtaW1hZ2UtcmVzaXplLXRvLWxheWVycyIKRXZhbDogeApBcHBseSB0 +bzogKCJnaW1wLWltYWdlLXJlc2l6ZS10by1sYXllcnMiICgxKSkKQXBwbHkgdG86ICgjPEZPUkVJ +R04gUFJPQ0VEVVJFIDkzOTYxMTMxNTUyNDk2PiAoImdpbXAtaW1hZ2UtcmVzaXplLXRvLWxheWVy +cyIgMSkpCkFwcGx5IHRvOiAoImdpbXAtaW1hZ2UtcmVzaXplLXRvLWxheWVycyIgMSkKRXZhbDog +KGZvci1lYWNoIChsYW1iZGEgKGhHdWlkZSkgKGdpbXAtaW1hZ2UtYWRkLWhndWlkZSBpbWcgaEd1 +aWRlKSkgJyg5NTEuMDIyNyA0OS4wMjQxMjgpKQpFdmFsOiBmb3ItZWFjaApFdmFsOiAobGFtYmRh +IChoR3VpZGUpIChnaW1wLWltYWdlLWFkZC1oZ3VpZGUgaW1nIGhHdWlkZSkpCkFwcGx5IHRvOiAo +KChoR3VpZGUpIChnaW1wLWltYWdlLWFkZC1oZ3VpZGUgaW1nIGhHdWlkZSkpKQpFdmFsOiAoaWYg +KG1hY3JvPyBmb3JtKSAobWFjcm8tZXhwYW5kLWFsbCAobWFjcm8tZXhwYW5kIGZvcm0pKSBmb3Jt +KQpFdmFsOiAobWFjcm8/IGZvcm0pCkV2YWw6IG1hY3JvPwpFdmFsOiBmb3JtCkFwcGx5IHRvOiAo +KChoR3VpZGUpIChnaW1wLWltYWdlLWFkZC1oZ3VpZGUgaW1nIGhHdWlkZSkpKQpFdmFsOiBmb3Jt +CkV2YWw6ICcoOTUxLjAyMjcgNDkuMDI0MTI4KQpBcHBseSB0bzogKCM8Q0xPU1VSRT4gKDk1MS4w +MjI3IDQ5LjAyNDEyOCkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChhcHBseSBwcm9jKSAoaWYg +KG51bGw/IChjYXIgbGlzdHMpKSAjdCAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAxLXdpdGgtY2Ry +IGxpc3RzKSkgKGNhcnMgKGNhciB1bnopKSAoY2RycyAoY2RyIHVueikpKSAoYXBwbHkgcHJvYyBj +YXJzKSAoYXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpKSkpCkV2YWw6IChudWxsPyBsaXN0cykK +RXZhbDogbnVsbD8KRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKDk1MS4wMjI3IDQ5LjAyNDEyOCkp +KQpFdmFsOiAoaWYgKG51bGw/IChjYXIgbGlzdHMpKSAjdCAobGV0KiAoKHVueiAoYXBwbHkgdW56 +aXAxLXdpdGgtY2RyIGxpc3RzKSkgKGNhcnMgKGNhciB1bnopKSAoY2RycyAoY2RyIHVueikpKSAo +YXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpKSkKRXZhbDogKG51 +bGw/IChjYXIgbGlzdHMpKQpFdmFsOiBudWxsPwpFdmFsOiAoY2FyIGxpc3RzKQpFdmFsOiBjYXIK +RXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKDk1MS4wMjI3IDQ5LjAyNDEyOCkpKQpBcHBseSB0bzog +KCg5NTEuMDIyNyA0OS4wMjQxMjgpKQpFdmFsOiAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAxLXdp +dGgtY2RyIGxpc3RzKSkgKGNhcnMgKGNhciB1bnopKSAoY2RycyAoY2RyIHVueikpKSAoYXBwbHkg +cHJvYyBjYXJzKSAoYXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpKQpFdmFsOiAoYXBwbHkgdW56 +aXAxLXdpdGgtY2RyIGxpc3RzKQpFdmFsOiBhcHBseQpFdmFsOiB1bnppcDEtd2l0aC1jZHIKRXZh +bDogbGlzdHMKQXBwbHkgdG86ICgjPENMT1NVUkU+ICgoOTUxLjAyMjcgNDkuMDI0MTI4KSkpCkFw +cGx5IHRvOiAoKDk1MS4wMjI3IDQ5LjAyNDEyOCkpCkV2YWw6ICh1bnppcDEtd2l0aC1jZHItaXRl +cmF0aXZlIGxpc3RzICcoKSAnKCkpCkV2YWw6IHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUKRXZh +bDogbGlzdHMKRXZhbDogJygpCkV2YWw6ICcoKQpBcHBseSB0bzogKCgoOTUxLjAyMjcgNDkuMDI0 +MTI4KSkgKCkgKCkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChjb25zIGNhcnMgY2RycykgKGxl +dCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAodW56aXAxLXdpdGgt +Y2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChhcHBl +bmQgY2RycyAobGlzdCBjZHIxKSkpKSkKRXZhbDogKG51bGw/IGxpc3RzKQpFdmFsOiBudWxsPwpF +dmFsOiBsaXN0cwpBcHBseSB0bzogKCgoOTUxLjAyMjcgNDkuMDI0MTI4KSkpCkV2YWw6IChsZXQg +KChjYXIxIChjYWFyIGxpc3RzKSkgKGNkcjEgKGNkYXIgbGlzdHMpKSkgKHVuemlwMS13aXRoLWNk +ci1pdGVyYXRpdmUgKGNkciBsaXN0cykgKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEpKSAoYXBwZW5k +IGNkcnMgKGxpc3QgY2RyMSkpKSkKRXZhbDogKGNhYXIgbGlzdHMpCkV2YWw6IGNhYXIKRXZhbDog +bGlzdHMKQXBwbHkgdG86ICgoKDk1MS4wMjI3IDQ5LjAyNDEyOCkpKQpFdmFsOiAoY2FyIChjYXIg +eCkpCkV2YWw6IGNhcgpFdmFsOiAoY2FyIHgpCkV2YWw6IGNhcgpFdmFsOiB4CkFwcGx5IHRvOiAo +KCg5NTEuMDIyNyA0OS4wMjQxMjgpKSkKQXBwbHkgdG86ICgoOTUxLjAyMjcgNDkuMDI0MTI4KSkK +RXZhbDogKGNkYXIgbGlzdHMpCkV2YWw6IGNkYXIKRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKDk1 +MS4wMjI3IDQ5LjAyNDEyOCkpKQpFdmFsOiAoY2RyIChjYXIgeCkpCkV2YWw6IGNkcgpFdmFsOiAo +Y2FyIHgpCkV2YWw6IGNhcgpFdmFsOiB4CkFwcGx5IHRvOiAoKCg5NTEuMDIyNyA0OS4wMjQxMjgp +KSkKQXBwbHkgdG86ICgoOTUxLjAyMjcgNDkuMDI0MTI4KSkKRXZhbDogKHVuemlwMS13aXRoLWNk +ci1pdGVyYXRpdmUgKGNkciBsaXN0cykgKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEpKSAoYXBwZW5k +IGNkcnMgKGxpc3QgY2RyMSkpKQpFdmFsOiB1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlCkV2YWw6 +IChjZHIgbGlzdHMpCkV2YWw6IGNkcgpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoOTUxLjAyMjcg +NDkuMDI0MTI4KSkpCkV2YWw6IChhcHBlbmQgY2FycyAobGlzdCBjYXIxKSkKRXZhbDogYXBwZW5k +CkV2YWw6IGNhcnMKRXZhbDogKGxpc3QgY2FyMSkKRXZhbDogbGlzdApFdmFsOiBjYXIxCkFwcGx5 +IHRvOiAoOTUxLjAyMjcpCkV2YWw6IHgKQXBwbHkgdG86ICgoKSAoOTUxLjAyMjcpKQpFdmFsOiAo +YXBwZW5kIGNkcnMgKGxpc3QgY2RyMSkpCkV2YWw6IGFwcGVuZApFdmFsOiBjZHJzCkV2YWw6IChs +aXN0IGNkcjEpCkV2YWw6IGxpc3QKRXZhbDogY2RyMQpBcHBseSB0bzogKCg0OS4wMjQxMjgpKQpF +dmFsOiB4CkFwcGx5IHRvOiAoKCkgKCg0OS4wMjQxMjgpKSkKQXBwbHkgdG86ICgoKSAoOTUxLjAy +MjcpICgoNDkuMDI0MTI4KSkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChjb25zIGNhcnMgY2Ry +cykgKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAodW56aXAx +LXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkp +IChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKSkKRXZhbDogKG51bGw/IGxpc3RzKQpFdmFsOiBu +dWxsPwpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgpKQpFdmFsOiAoY29ucyBjYXJzIGNkcnMpCkV2 +YWw6IGNvbnMKRXZhbDogY2FycwpFdmFsOiBjZHJzCkFwcGx5IHRvOiAoKDk1MS4wMjI3KSAoKDQ5 +LjAyNDEyOCkpKQpFdmFsOiAoY2FyIHVueikKRXZhbDogY2FyCkV2YWw6IHVuegpBcHBseSB0bzog +KCgoOTUxLjAyMjcpICg0OS4wMjQxMjgpKSkKRXZhbDogKGNkciB1bnopCkV2YWw6IGNkcgpFdmFs +OiB1bnoKQXBwbHkgdG86ICgoKDk1MS4wMjI3KSAoNDkuMDI0MTI4KSkpCkV2YWw6IChhcHBseSBw +cm9jIGNhcnMpCkV2YWw6IGFwcGx5CkV2YWw6IHByb2MKRXZhbDogY2FycwpBcHBseSB0bzogKCM8 +Q0xPU1VSRT4gKDk1MS4wMjI3KSkKQXBwbHkgdG86ICg5NTEuMDIyNykKRXZhbDogKGdpbXAtaW1h +Z2UtYWRkLWhndWlkZSBpbWcgaEd1aWRlKQpFdmFsOiBnaW1wLWltYWdlLWFkZC1oZ3VpZGUKRXZh +bDogaW1nCkV2YWw6IGhHdWlkZQpBcHBseSB0bzogKDEgOTUxLjAyMjcpCkV2YWw6IChhcHBseSBn +aW1wLXByb2MtZGItY2FsbCAoY29ucyAiZ2ltcC1pbWFnZS1hZGQtaGd1aWRlIiB4KSkKRXZhbDog +YXBwbHkKRXZhbDogZ2ltcC1wcm9jLWRiLWNhbGwKRXZhbDogKGNvbnMgImdpbXAtaW1hZ2UtYWRk +LWhndWlkZSIgeCkKRXZhbDogY29ucwpFdmFsOiAiZ2ltcC1pbWFnZS1hZGQtaGd1aWRlIgpFdmFs +OiB4CkFwcGx5IHRvOiAoImdpbXAtaW1hZ2UtYWRkLWhndWlkZSIgKDEgOTUxLjAyMjcpKQpBcHBs +eSB0bzogKCM8Rk9SRUlHTiBQUk9DRURVUkUgOTM5NjExMzE1NTI0OTY+ICgiZ2ltcC1pbWFnZS1h +ZGQtaGd1aWRlIiAxIDk1MS4wMjI3KSkKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1hZGQtaGd1aWRl +IiAxIDk1MS4wMjI3KQpFdmFsOiAoYXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpCkV2YWw6IGFw +cGx5CkV2YWw6IG1hcApFdmFsOiAoY29ucyBwcm9jIGNkcnMpCkV2YWw6IGNvbnMKRXZhbDogcHJv +YwpFdmFsOiBjZHJzCkFwcGx5IHRvOiAoIzxDTE9TVVJFPiAoKDQ5LjAyNDEyOCkpKQpBcHBseSB0 +bzogKCM8Q0xPU1VSRT4gKCM8Q0xPU1VSRT4gKDQ5LjAyNDEyOCkpKQpBcHBseSB0bzogKCM8Q0xP +U1VSRT4gKDQ5LjAyNDEyOCkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChhcHBseSBwcm9jKSAo +aWYgKG51bGw/IChjYXIgbGlzdHMpKSAnKCkgKGxldCogKCh1bnogKGFwcGx5IHVuemlwMS13aXRo +LWNkciBsaXN0cykpIChjYXJzIChjYXIgdW56KSkgKGNkcnMgKGNkciB1bnopKSkgKGNvbnMgKGFw +cGx5IHByb2MgY2FycykgKGFwcGx5IG1hcCAoY29ucyBwcm9jIGNkcnMpKSkpKSkKRXZhbDogKG51 +bGw/IGxpc3RzKQpFdmFsOiBudWxsPwpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoNDkuMDI0MTI4 +KSkpCkV2YWw6IChpZiAobnVsbD8gKGNhciBsaXN0cykpICcoKSAobGV0KiAoKHVueiAoYXBwbHkg +dW56aXAxLXdpdGgtY2RyIGxpc3RzKSkgKGNhcnMgKGNhciB1bnopKSAoY2RycyAoY2RyIHVueikp +KSAoY29ucyAoYXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpKSkp +CkV2YWw6IChudWxsPyAoY2FyIGxpc3RzKSkKRXZhbDogbnVsbD8KRXZhbDogKGNhciBsaXN0cykK +RXZhbDogY2FyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCg0OS4wMjQxMjgpKSkKQXBwbHkgdG86 +ICgoNDkuMDI0MTI4KSkKRXZhbDogKGxldCogKCh1bnogKGFwcGx5IHVuemlwMS13aXRoLWNkciBs +aXN0cykpIChjYXJzIChjYXIgdW56KSkgKGNkcnMgKGNkciB1bnopKSkgKGNvbnMgKGFwcGx5IHBy +b2MgY2FycykgKGFwcGx5IG1hcCAoY29ucyBwcm9jIGNkcnMpKSkpCkV2YWw6IChhcHBseSB1bnpp +cDEtd2l0aC1jZHIgbGlzdHMpCkV2YWw6IGFwcGx5CkV2YWw6IHVuemlwMS13aXRoLWNkcgpFdmFs +OiBsaXN0cwpBcHBseSB0bzogKCM8Q0xPU1VSRT4gKCg0OS4wMjQxMjgpKSkKQXBwbHkgdG86ICgo +NDkuMDI0MTI4KSkKRXZhbDogKHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUgbGlzdHMgJygpICco +KSkKRXZhbDogdW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZQpFdmFsOiBsaXN0cwpFdmFsOiAnKCkK +RXZhbDogJygpCkFwcGx5IHRvOiAoKCg0OS4wMjQxMjgpKSAoKSAoKSkKRXZhbDogKGlmIChudWxs +PyBsaXN0cykgKGNvbnMgY2FycyBjZHJzKSAobGV0ICgoY2FyMSAoY2FhciBsaXN0cykpIChjZHIx +IChjZGFyIGxpc3RzKSkpICh1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlIChjZHIgbGlzdHMpIChh +cHBlbmQgY2FycyAobGlzdCBjYXIxKSkgKGFwcGVuZCBjZHJzIChsaXN0IGNkcjEpKSkpKQpFdmFs +OiAobnVsbD8gbGlzdHMpCkV2YWw6IG51bGw/CkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCg0OS4w +MjQxMjgpKSkKRXZhbDogKGxldCAoKGNhcjEgKGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0 +cykpKSAodW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMg +KGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpKQpFdmFsOiAoY2FhciBsaXN0 +cykKRXZhbDogY2FhcgpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoNDkuMDI0MTI4KSkpCkV2YWw6 +IChjYXIgKGNhciB4KSkKRXZhbDogY2FyCkV2YWw6IChjYXIgeCkKRXZhbDogY2FyCkV2YWw6IHgK +QXBwbHkgdG86ICgoKDQ5LjAyNDEyOCkpKQpBcHBseSB0bzogKCg0OS4wMjQxMjgpKQpFdmFsOiAo +Y2RhciBsaXN0cykKRXZhbDogY2RhcgpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoNDkuMDI0MTI4 +KSkpCkV2YWw6IChjZHIgKGNhciB4KSkKRXZhbDogY2RyCkV2YWw6IChjYXIgeCkKRXZhbDogY2Fy +CkV2YWw6IHgKQXBwbHkgdG86ICgoKDQ5LjAyNDEyOCkpKQpBcHBseSB0bzogKCg0OS4wMjQxMjgp +KQpFdmFsOiAodW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNh +cnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAobGlzdCBjZHIxKSkpCkV2YWw6IHVuemlwMS13 +aXRoLWNkci1pdGVyYXRpdmUKRXZhbDogKGNkciBsaXN0cykKRXZhbDogY2RyCkV2YWw6IGxpc3Rz +CkFwcGx5IHRvOiAoKCg0OS4wMjQxMjgpKSkKRXZhbDogKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEp +KQpFdmFsOiBhcHBlbmQKRXZhbDogY2FycwpFdmFsOiAobGlzdCBjYXIxKQpFdmFsOiBsaXN0CkV2 +YWw6IGNhcjEKQXBwbHkgdG86ICg0OS4wMjQxMjgpCkV2YWw6IHgKQXBwbHkgdG86ICgoKSAoNDku +MDI0MTI4KSkKRXZhbDogKGFwcGVuZCBjZHJzIChsaXN0IGNkcjEpKQpFdmFsOiBhcHBlbmQKRXZh +bDogY2RycwpFdmFsOiAobGlzdCBjZHIxKQpFdmFsOiBsaXN0CkV2YWw6IGNkcjEKQXBwbHkgdG86 +ICgoKSkKRXZhbDogeApBcHBseSB0bzogKCgpICgoKSkpCkFwcGx5IHRvOiAoKCkgKDQ5LjAyNDEy +OCkgKCgpKSkKRXZhbDogKGlmIChudWxsPyBsaXN0cykgKGNvbnMgY2FycyBjZHJzKSAobGV0ICgo +Y2FyMSAoY2FhciBsaXN0cykpIChjZHIxIChjZGFyIGxpc3RzKSkpICh1bnppcDEtd2l0aC1jZHIt +aXRlcmF0aXZlIChjZHIgbGlzdHMpIChhcHBlbmQgY2FycyAobGlzdCBjYXIxKSkgKGFwcGVuZCBj +ZHJzIChsaXN0IGNkcjEpKSkpKQpFdmFsOiAobnVsbD8gbGlzdHMpCkV2YWw6IG51bGw/CkV2YWw6 +IGxpc3RzCkFwcGx5IHRvOiAoKCkpCkV2YWw6IChjb25zIGNhcnMgY2RycykKRXZhbDogY29ucwpF +dmFsOiBjYXJzCkV2YWw6IGNkcnMKQXBwbHkgdG86ICgoNDkuMDI0MTI4KSAoKCkpKQpFdmFsOiAo +Y2FyIHVueikKRXZhbDogY2FyCkV2YWw6IHVuegpBcHBseSB0bzogKCgoNDkuMDI0MTI4KSAoKSkp +CkV2YWw6IChjZHIgdW56KQpFdmFsOiBjZHIKRXZhbDogdW56CkFwcGx5IHRvOiAoKCg0OS4wMjQx +MjgpICgpKSkKRXZhbDogKGNvbnMgKGFwcGx5IHByb2MgY2FycykgKGFwcGx5IG1hcCAoY29ucyBw +cm9jIGNkcnMpKSkKRXZhbDogY29ucwpFdmFsOiAoYXBwbHkgcHJvYyBjYXJzKQpFdmFsOiBhcHBs +eQpFdmFsOiBwcm9jCkV2YWw6IGNhcnMKQXBwbHkgdG86ICgjPENMT1NVUkU+ICg0OS4wMjQxMjgp +KQpBcHBseSB0bzogKDQ5LjAyNDEyOCkKRXZhbDogKGdpbXAtaW1hZ2UtYWRkLWhndWlkZSBpbWcg +aEd1aWRlKQpFdmFsOiBnaW1wLWltYWdlLWFkZC1oZ3VpZGUKRXZhbDogaW1nCkV2YWw6IGhHdWlk +ZQpBcHBseSB0bzogKDEgNDkuMDI0MTI4KQpFdmFsOiAoYXBwbHkgZ2ltcC1wcm9jLWRiLWNhbGwg +KGNvbnMgImdpbXAtaW1hZ2UtYWRkLWhndWlkZSIgeCkpCkV2YWw6IGFwcGx5CkV2YWw6IGdpbXAt +cHJvYy1kYi1jYWxsCkV2YWw6IChjb25zICJnaW1wLWltYWdlLWFkZC1oZ3VpZGUiIHgpCkV2YWw6 +IGNvbnMKRXZhbDogImdpbXAtaW1hZ2UtYWRkLWhndWlkZSIKRXZhbDogeApBcHBseSB0bzogKCJn +aW1wLWltYWdlLWFkZC1oZ3VpZGUiICgxIDQ5LjAyNDEyOCkpCkFwcGx5IHRvOiAoIzxGT1JFSUdO +IFBST0NFRFVSRSA5Mzk2MTEzMTU1MjQ5Nj4gKCJnaW1wLWltYWdlLWFkZC1oZ3VpZGUiIDEgNDku +MDI0MTI4KSkKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1hZGQtaGd1aWRlIiAxIDQ5LjAyNDEyOCkK +RXZhbDogKGFwcGx5IG1hcCAoY29ucyBwcm9jIGNkcnMpKQpFdmFsOiBhcHBseQpFdmFsOiBtYXAK +RXZhbDogKGNvbnMgcHJvYyBjZHJzKQpFdmFsOiBjb25zCkV2YWw6IHByb2MKRXZhbDogY2RycwpB +cHBseSB0bzogKCM8Q0xPU1VSRT4gKCgpKSkKQXBwbHkgdG86ICgjPENMT1NVUkU+ICgjPENMT1NV +UkU+ICgpKSkKQXBwbHkgdG86ICgjPENMT1NVUkU+ICgpKQpFdmFsOiAoaWYgKG51bGw/IGxpc3Rz +KSAoYXBwbHkgcHJvYykgKGlmIChudWxsPyAoY2FyIGxpc3RzKSkgJygpIChsZXQqICgodW56IChh +cHBseSB1bnppcDEtd2l0aC1jZHIgbGlzdHMpKSAoY2FycyAoY2FyIHVueikpIChjZHJzIChjZHIg +dW56KSkpIChjb25zIChhcHBseSBwcm9jIGNhcnMpIChhcHBseSBtYXAgKGNvbnMgcHJvYyBjZHJz +KSkpKSkpCkV2YWw6IChudWxsPyBsaXN0cykKRXZhbDogbnVsbD8KRXZhbDogbGlzdHMKQXBwbHkg +dG86ICgoKCkpKQpFdmFsOiAoaWYgKG51bGw/IChjYXIgbGlzdHMpKSAnKCkgKGxldCogKCh1bnog +KGFwcGx5IHVuemlwMS13aXRoLWNkciBsaXN0cykpIChjYXJzIChjYXIgdW56KSkgKGNkcnMgKGNk +ciB1bnopKSkgKGNvbnMgKGFwcGx5IHByb2MgY2FycykgKGFwcGx5IG1hcCAoY29ucyBwcm9jIGNk +cnMpKSkpKQpFdmFsOiAobnVsbD8gKGNhciBsaXN0cykpCkV2YWw6IG51bGw/CkV2YWw6IChjYXIg +bGlzdHMpCkV2YWw6IGNhcgpFdmFsOiBsaXN0cwpBcHBseSB0bzogKCgoKSkpCkFwcGx5IHRvOiAo +KCkpCkV2YWw6ICcoKQpBcHBseSB0bzogKCgyKSAoKSkKRXZhbDogKGZvci1lYWNoIChsYW1iZGEg +KHZHdWlkZSkgKGdpbXAtaW1hZ2UtYWRkLXZndWlkZSBpbWcgdkd1aWRlKSkgJyg0Ni45MzY1Nzcg +OTUzLjAxNjU5KSkKRXZhbDogZm9yLWVhY2gKRXZhbDogKGxhbWJkYSAodkd1aWRlKSAoZ2ltcC1p +bWFnZS1hZGQtdmd1aWRlIGltZyB2R3VpZGUpKQpBcHBseSB0bzogKCgodkd1aWRlKSAoZ2ltcC1p +bWFnZS1hZGQtdmd1aWRlIGltZyB2R3VpZGUpKSkKRXZhbDogKGlmIChtYWNybz8gZm9ybSkgKG1h +Y3JvLWV4cGFuZC1hbGwgKG1hY3JvLWV4cGFuZCBmb3JtKSkgZm9ybSkKRXZhbDogKG1hY3JvPyBm +b3JtKQpFdmFsOiBtYWNybz8KRXZhbDogZm9ybQpBcHBseSB0bzogKCgodkd1aWRlKSAoZ2ltcC1p +bWFnZS1hZGQtdmd1aWRlIGltZyB2R3VpZGUpKSkKRXZhbDogZm9ybQpFdmFsOiAnKDQ2LjkzNjU3 +NyA5NTMuMDE2NTkpCkFwcGx5IHRvOiAoIzxDTE9TVVJFPiAoNDYuOTM2NTc3IDk1My4wMTY1OSkp +CkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChhcHBseSBwcm9jKSAoaWYgKG51bGw/IChjYXIgbGlz +dHMpKSAjdCAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAxLXdpdGgtY2RyIGxpc3RzKSkgKGNhcnMg +KGNhciB1bnopKSAoY2RycyAoY2RyIHVueikpKSAoYXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkgbWFw +IChjb25zIHByb2MgY2RycykpKSkpCkV2YWw6IChudWxsPyBsaXN0cykKRXZhbDogbnVsbD8KRXZh +bDogbGlzdHMKQXBwbHkgdG86ICgoKDQ2LjkzNjU3NyA5NTMuMDE2NTkpKSkKRXZhbDogKGlmIChu +dWxsPyAoY2FyIGxpc3RzKSkgI3QgKGxldCogKCh1bnogKGFwcGx5IHVuemlwMS13aXRoLWNkciBs +aXN0cykpIChjYXJzIChjYXIgdW56KSkgKGNkcnMgKGNkciB1bnopKSkgKGFwcGx5IHByb2MgY2Fy +cykgKGFwcGx5IG1hcCAoY29ucyBwcm9jIGNkcnMpKSkpCkV2YWw6IChudWxsPyAoY2FyIGxpc3Rz +KSkKRXZhbDogbnVsbD8KRXZhbDogKGNhciBsaXN0cykKRXZhbDogY2FyCkV2YWw6IGxpc3RzCkFw +cGx5IHRvOiAoKCg0Ni45MzY1NzcgOTUzLjAxNjU5KSkpCkFwcGx5IHRvOiAoKDQ2LjkzNjU3NyA5 +NTMuMDE2NTkpKQpFdmFsOiAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAxLXdpdGgtY2RyIGxpc3Rz +KSkgKGNhcnMgKGNhciB1bnopKSAoY2RycyAoY2RyIHVueikpKSAoYXBwbHkgcHJvYyBjYXJzKSAo +YXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpKQpFdmFsOiAoYXBwbHkgdW56aXAxLXdpdGgtY2Ry +IGxpc3RzKQpFdmFsOiBhcHBseQpFdmFsOiB1bnppcDEtd2l0aC1jZHIKRXZhbDogbGlzdHMKQXBw +bHkgdG86ICgjPENMT1NVUkU+ICgoNDYuOTM2NTc3IDk1My4wMTY1OSkpKQpBcHBseSB0bzogKCg0 +Ni45MzY1NzcgOTUzLjAxNjU5KSkKRXZhbDogKHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUgbGlz +dHMgJygpICcoKSkKRXZhbDogdW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZQpFdmFsOiBsaXN0cwpF +dmFsOiAnKCkKRXZhbDogJygpCkFwcGx5IHRvOiAoKCg0Ni45MzY1NzcgOTUzLjAxNjU5KSkgKCkg +KCkpCkV2YWw6IChpZiAobnVsbD8gbGlzdHMpIChjb25zIGNhcnMgY2RycykgKGxldCAoKGNhcjEg +KGNhYXIgbGlzdHMpKSAoY2RyMSAoY2RhciBsaXN0cykpKSAodW56aXAxLXdpdGgtY2RyLWl0ZXJh +dGl2ZSAoY2RyIGxpc3RzKSAoYXBwZW5kIGNhcnMgKGxpc3QgY2FyMSkpIChhcHBlbmQgY2RycyAo +bGlzdCBjZHIxKSkpKSkKRXZhbDogKG51bGw/IGxpc3RzKQpFdmFsOiBudWxsPwpFdmFsOiBsaXN0 +cwpBcHBseSB0bzogKCgoNDYuOTM2NTc3IDk1My4wMTY1OSkpKQpFdmFsOiAobGV0ICgoY2FyMSAo +Y2FhciBsaXN0cykpIChjZHIxIChjZGFyIGxpc3RzKSkpICh1bnppcDEtd2l0aC1jZHItaXRlcmF0 +aXZlIChjZHIgbGlzdHMpIChhcHBlbmQgY2FycyAobGlzdCBjYXIxKSkgKGFwcGVuZCBjZHJzIChs +aXN0IGNkcjEpKSkpCkV2YWw6IChjYWFyIGxpc3RzKQpFdmFsOiBjYWFyCkV2YWw6IGxpc3RzCkFw +cGx5IHRvOiAoKCg0Ni45MzY1NzcgOTUzLjAxNjU5KSkpCkV2YWw6IChjYXIgKGNhciB4KSkKRXZh +bDogY2FyCkV2YWw6IChjYXIgeCkKRXZhbDogY2FyCkV2YWw6IHgKQXBwbHkgdG86ICgoKDQ2Ljkz +NjU3NyA5NTMuMDE2NTkpKSkKQXBwbHkgdG86ICgoNDYuOTM2NTc3IDk1My4wMTY1OSkpCkV2YWw6 +IChjZGFyIGxpc3RzKQpFdmFsOiBjZGFyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCg0Ni45MzY1 +NzcgOTUzLjAxNjU5KSkpCkV2YWw6IChjZHIgKGNhciB4KSkKRXZhbDogY2RyCkV2YWw6IChjYXIg +eCkKRXZhbDogY2FyCkV2YWw6IHgKQXBwbHkgdG86ICgoKDQ2LjkzNjU3NyA5NTMuMDE2NTkpKSkK +QXBwbHkgdG86ICgoNDYuOTM2NTc3IDk1My4wMTY1OSkpCkV2YWw6ICh1bnppcDEtd2l0aC1jZHIt +aXRlcmF0aXZlIChjZHIgbGlzdHMpIChhcHBlbmQgY2FycyAobGlzdCBjYXIxKSkgKGFwcGVuZCBj +ZHJzIChsaXN0IGNkcjEpKSkKRXZhbDogdW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZQpFdmFsOiAo +Y2RyIGxpc3RzKQpFdmFsOiBjZHIKRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKDQ2LjkzNjU3NyA5 +NTMuMDE2NTkpKSkKRXZhbDogKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEpKQpFdmFsOiBhcHBlbmQK +RXZhbDogY2FycwpFdmFsOiAobGlzdCBjYXIxKQpFdmFsOiBsaXN0CkV2YWw6IGNhcjEKQXBwbHkg +dG86ICg0Ni45MzY1NzcpCkV2YWw6IHgKQXBwbHkgdG86ICgoKSAoNDYuOTM2NTc3KSkKRXZhbDog +KGFwcGVuZCBjZHJzIChsaXN0IGNkcjEpKQpFdmFsOiBhcHBlbmQKRXZhbDogY2RycwpFdmFsOiAo +bGlzdCBjZHIxKQpFdmFsOiBsaXN0CkV2YWw6IGNkcjEKQXBwbHkgdG86ICgoOTUzLjAxNjU5KSkK +RXZhbDogeApBcHBseSB0bzogKCgpICgoOTUzLjAxNjU5KSkpCkFwcGx5IHRvOiAoKCkgKDQ2Ljkz +NjU3NykgKCg5NTMuMDE2NTkpKSkKRXZhbDogKGlmIChudWxsPyBsaXN0cykgKGNvbnMgY2FycyBj +ZHJzKSAobGV0ICgoY2FyMSAoY2FhciBsaXN0cykpIChjZHIxIChjZGFyIGxpc3RzKSkpICh1bnpp +cDEtd2l0aC1jZHItaXRlcmF0aXZlIChjZHIgbGlzdHMpIChhcHBlbmQgY2FycyAobGlzdCBjYXIx +KSkgKGFwcGVuZCBjZHJzIChsaXN0IGNkcjEpKSkpKQpFdmFsOiAobnVsbD8gbGlzdHMpCkV2YWw6 +IG51bGw/CkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCkpCkV2YWw6IChjb25zIGNhcnMgY2RycykK +RXZhbDogY29ucwpFdmFsOiBjYXJzCkV2YWw6IGNkcnMKQXBwbHkgdG86ICgoNDYuOTM2NTc3KSAo +KDk1My4wMTY1OSkpKQpFdmFsOiAoY2FyIHVueikKRXZhbDogY2FyCkV2YWw6IHVuegpBcHBseSB0 +bzogKCgoNDYuOTM2NTc3KSAoOTUzLjAxNjU5KSkpCkV2YWw6IChjZHIgdW56KQpFdmFsOiBjZHIK +RXZhbDogdW56CkFwcGx5IHRvOiAoKCg0Ni45MzY1NzcpICg5NTMuMDE2NTkpKSkKRXZhbDogKGFw +cGx5IHByb2MgY2FycykKRXZhbDogYXBwbHkKRXZhbDogcHJvYwpFdmFsOiBjYXJzCkFwcGx5IHRv +OiAoIzxDTE9TVVJFPiAoNDYuOTM2NTc3KSkKQXBwbHkgdG86ICg0Ni45MzY1NzcpCkV2YWw6IChn +aW1wLWltYWdlLWFkZC12Z3VpZGUgaW1nIHZHdWlkZSkKRXZhbDogZ2ltcC1pbWFnZS1hZGQtdmd1 +aWRlCkV2YWw6IGltZwpFdmFsOiB2R3VpZGUKQXBwbHkgdG86ICgxIDQ2LjkzNjU3NykKRXZhbDog +KGFwcGx5IGdpbXAtcHJvYy1kYi1jYWxsIChjb25zICJnaW1wLWltYWdlLWFkZC12Z3VpZGUiIHgp +KQpFdmFsOiBhcHBseQpFdmFsOiBnaW1wLXByb2MtZGItY2FsbApFdmFsOiAoY29ucyAiZ2ltcC1p +bWFnZS1hZGQtdmd1aWRlIiB4KQpFdmFsOiBjb25zCkV2YWw6ICJnaW1wLWltYWdlLWFkZC12Z3Vp +ZGUiCkV2YWw6IHgKQXBwbHkgdG86ICgiZ2ltcC1pbWFnZS1hZGQtdmd1aWRlIiAoMSA0Ni45MzY1 +NzcpKQpBcHBseSB0bzogKCM8Rk9SRUlHTiBQUk9DRURVUkUgOTM5NjExMzE1NTI0OTY+ICgiZ2lt +cC1pbWFnZS1hZGQtdmd1aWRlIiAxIDQ2LjkzNjU3NykpCkFwcGx5IHRvOiAoImdpbXAtaW1hZ2Ut +YWRkLXZndWlkZSIgMSA0Ni45MzY1NzcpCkV2YWw6IChhcHBseSBtYXAgKGNvbnMgcHJvYyBjZHJz +KSkKRXZhbDogYXBwbHkKRXZhbDogbWFwCkV2YWw6IChjb25zIHByb2MgY2RycykKRXZhbDogY29u +cwpFdmFsOiBwcm9jCkV2YWw6IGNkcnMKQXBwbHkgdG86ICgjPENMT1NVUkU+ICgoOTUzLjAxNjU5 +KSkpCkFwcGx5IHRvOiAoIzxDTE9TVVJFPiAoIzxDTE9TVVJFPiAoOTUzLjAxNjU5KSkpCkFwcGx5 +IHRvOiAoIzxDTE9TVVJFPiAoOTUzLjAxNjU5KSkKRXZhbDogKGlmIChudWxsPyBsaXN0cykgKGFw +cGx5IHByb2MpIChpZiAobnVsbD8gKGNhciBsaXN0cykpICcoKSAobGV0KiAoKHVueiAoYXBwbHkg +dW56aXAxLXdpdGgtY2RyIGxpc3RzKSkgKGNhcnMgKGNhciB1bnopKSAoY2RycyAoY2RyIHVueikp +KSAoY29ucyAoYXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpKSkp +KQpFdmFsOiAobnVsbD8gbGlzdHMpCkV2YWw6IG51bGw/CkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAo +KCg5NTMuMDE2NTkpKSkKRXZhbDogKGlmIChudWxsPyAoY2FyIGxpc3RzKSkgJygpIChsZXQqICgo +dW56IChhcHBseSB1bnppcDEtd2l0aC1jZHIgbGlzdHMpKSAoY2FycyAoY2FyIHVueikpIChjZHJz +IChjZHIgdW56KSkpIChjb25zIChhcHBseSBwcm9jIGNhcnMpIChhcHBseSBtYXAgKGNvbnMgcHJv +YyBjZHJzKSkpKSkKRXZhbDogKG51bGw/IChjYXIgbGlzdHMpKQpFdmFsOiBudWxsPwpFdmFsOiAo +Y2FyIGxpc3RzKQpFdmFsOiBjYXIKRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKDk1My4wMTY1OSkp +KQpBcHBseSB0bzogKCg5NTMuMDE2NTkpKQpFdmFsOiAobGV0KiAoKHVueiAoYXBwbHkgdW56aXAx +LXdpdGgtY2RyIGxpc3RzKSkgKGNhcnMgKGNhciB1bnopKSAoY2RycyAoY2RyIHVueikpKSAoY29u +cyAoYXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpKSkKRXZhbDog +KGFwcGx5IHVuemlwMS13aXRoLWNkciBsaXN0cykKRXZhbDogYXBwbHkKRXZhbDogdW56aXAxLXdp +dGgtY2RyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoIzxDTE9TVVJFPiAoKDk1My4wMTY1OSkpKQpB +cHBseSB0bzogKCg5NTMuMDE2NTkpKQpFdmFsOiAodW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZSBs +aXN0cyAnKCkgJygpKQpFdmFsOiB1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlCkV2YWw6IGxpc3Rz +CkV2YWw6ICcoKQpFdmFsOiAnKCkKQXBwbHkgdG86ICgoKDk1My4wMTY1OSkpICgpICgpKQpFdmFs +OiAoaWYgKG51bGw/IGxpc3RzKSAoY29ucyBjYXJzIGNkcnMpIChsZXQgKChjYXIxIChjYWFyIGxp +c3RzKSkgKGNkcjEgKGNkYXIgbGlzdHMpKSkgKHVuemlwMS13aXRoLWNkci1pdGVyYXRpdmUgKGNk +ciBsaXN0cykgKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEpKSAoYXBwZW5kIGNkcnMgKGxpc3QgY2Ry +MSkpKSkpCkV2YWw6IChudWxsPyBsaXN0cykKRXZhbDogbnVsbD8KRXZhbDogbGlzdHMKQXBwbHkg +dG86ICgoKDk1My4wMTY1OSkpKQpFdmFsOiAobGV0ICgoY2FyMSAoY2FhciBsaXN0cykpIChjZHIx +IChjZGFyIGxpc3RzKSkpICh1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlIChjZHIgbGlzdHMpIChh +cHBlbmQgY2FycyAobGlzdCBjYXIxKSkgKGFwcGVuZCBjZHJzIChsaXN0IGNkcjEpKSkpCkV2YWw6 +IChjYWFyIGxpc3RzKQpFdmFsOiBjYWFyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCg5NTMuMDE2 +NTkpKSkKRXZhbDogKGNhciAoY2FyIHgpKQpFdmFsOiBjYXIKRXZhbDogKGNhciB4KQpFdmFsOiBj +YXIKRXZhbDogeApBcHBseSB0bzogKCgoOTUzLjAxNjU5KSkpCkFwcGx5IHRvOiAoKDk1My4wMTY1 +OSkpCkV2YWw6IChjZGFyIGxpc3RzKQpFdmFsOiBjZGFyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAo +KCg5NTMuMDE2NTkpKSkKRXZhbDogKGNkciAoY2FyIHgpKQpFdmFsOiBjZHIKRXZhbDogKGNhciB4 +KQpFdmFsOiBjYXIKRXZhbDogeApBcHBseSB0bzogKCgoOTUzLjAxNjU5KSkpCkFwcGx5IHRvOiAo +KDk1My4wMTY1OSkpCkV2YWw6ICh1bnppcDEtd2l0aC1jZHItaXRlcmF0aXZlIChjZHIgbGlzdHMp +IChhcHBlbmQgY2FycyAobGlzdCBjYXIxKSkgKGFwcGVuZCBjZHJzIChsaXN0IGNkcjEpKSkKRXZh +bDogdW56aXAxLXdpdGgtY2RyLWl0ZXJhdGl2ZQpFdmFsOiAoY2RyIGxpc3RzKQpFdmFsOiBjZHIK +RXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKDk1My4wMTY1OSkpKQpFdmFsOiAoYXBwZW5kIGNhcnMg +KGxpc3QgY2FyMSkpCkV2YWw6IGFwcGVuZApFdmFsOiBjYXJzCkV2YWw6IChsaXN0IGNhcjEpCkV2 +YWw6IGxpc3QKRXZhbDogY2FyMQpBcHBseSB0bzogKDk1My4wMTY1OSkKRXZhbDogeApBcHBseSB0 +bzogKCgpICg5NTMuMDE2NTkpKQpFdmFsOiAoYXBwZW5kIGNkcnMgKGxpc3QgY2RyMSkpCkV2YWw6 +IGFwcGVuZApFdmFsOiBjZHJzCkV2YWw6IChsaXN0IGNkcjEpCkV2YWw6IGxpc3QKRXZhbDogY2Ry +MQpBcHBseSB0bzogKCgpKQpFdmFsOiB4CkFwcGx5IHRvOiAoKCkgKCgpKSkKQXBwbHkgdG86ICgo +KSAoOTUzLjAxNjU5KSAoKCkpKQpFdmFsOiAoaWYgKG51bGw/IGxpc3RzKSAoY29ucyBjYXJzIGNk +cnMpIChsZXQgKChjYXIxIChjYWFyIGxpc3RzKSkgKGNkcjEgKGNkYXIgbGlzdHMpKSkgKHVuemlw +MS13aXRoLWNkci1pdGVyYXRpdmUgKGNkciBsaXN0cykgKGFwcGVuZCBjYXJzIChsaXN0IGNhcjEp +KSAoYXBwZW5kIGNkcnMgKGxpc3QgY2RyMSkpKSkpCkV2YWw6IChudWxsPyBsaXN0cykKRXZhbDog +bnVsbD8KRXZhbDogbGlzdHMKQXBwbHkgdG86ICgoKSkKRXZhbDogKGNvbnMgY2FycyBjZHJzKQpF +dmFsOiBjb25zCkV2YWw6IGNhcnMKRXZhbDogY2RycwpBcHBseSB0bzogKCg5NTMuMDE2NTkpICgo +KSkpCkV2YWw6IChjYXIgdW56KQpFdmFsOiBjYXIKRXZhbDogdW56CkFwcGx5IHRvOiAoKCg5NTMu +MDE2NTkpICgpKSkKRXZhbDogKGNkciB1bnopCkV2YWw6IGNkcgpFdmFsOiB1bnoKQXBwbHkgdG86 +ICgoKDk1My4wMTY1OSkgKCkpKQpFdmFsOiAoY29ucyAoYXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkg +bWFwIChjb25zIHByb2MgY2RycykpKQpFdmFsOiBjb25zCkV2YWw6IChhcHBseSBwcm9jIGNhcnMp +CkV2YWw6IGFwcGx5CkV2YWw6IHByb2MKRXZhbDogY2FycwpBcHBseSB0bzogKCM8Q0xPU1VSRT4g +KDk1My4wMTY1OSkpCkFwcGx5IHRvOiAoOTUzLjAxNjU5KQpFdmFsOiAoZ2ltcC1pbWFnZS1hZGQt +dmd1aWRlIGltZyB2R3VpZGUpCkV2YWw6IGdpbXAtaW1hZ2UtYWRkLXZndWlkZQpFdmFsOiBpbWcK +RXZhbDogdkd1aWRlCkFwcGx5IHRvOiAoMSA5NTMuMDE2NTkpCkV2YWw6IChhcHBseSBnaW1wLXBy +b2MtZGItY2FsbCAoY29ucyAiZ2ltcC1pbWFnZS1hZGQtdmd1aWRlIiB4KSkKRXZhbDogYXBwbHkK +RXZhbDogZ2ltcC1wcm9jLWRiLWNhbGwKRXZhbDogKGNvbnMgImdpbXAtaW1hZ2UtYWRkLXZndWlk +ZSIgeCkKRXZhbDogY29ucwpFdmFsOiAiZ2ltcC1pbWFnZS1hZGQtdmd1aWRlIgpFdmFsOiB4CkFw +cGx5IHRvOiAoImdpbXAtaW1hZ2UtYWRkLXZndWlkZSIgKDEgOTUzLjAxNjU5KSkKQXBwbHkgdG86 +ICgjPEZPUkVJR04gUFJPQ0VEVVJFIDkzOTYxMTMxNTUyNDk2PiAoImdpbXAtaW1hZ2UtYWRkLXZn +dWlkZSIgMSA5NTMuMDE2NTkpKQpBcHBseSB0bzogKCJnaW1wLWltYWdlLWFkZC12Z3VpZGUiIDEg +OTUzLjAxNjU5KQpFdmFsOiAoYXBwbHkgbWFwIChjb25zIHByb2MgY2RycykpCkV2YWw6IGFwcGx5 +CkV2YWw6IG1hcApFdmFsOiAoY29ucyBwcm9jIGNkcnMpCkV2YWw6IGNvbnMKRXZhbDogcHJvYwpF +dmFsOiBjZHJzCkFwcGx5IHRvOiAoIzxDTE9TVVJFPiAoKCkpKQpBcHBseSB0bzogKCM8Q0xPU1VS +RT4gKCM8Q0xPU1VSRT4gKCkpKQpBcHBseSB0bzogKCM8Q0xPU1VSRT4gKCkpCkV2YWw6IChpZiAo +bnVsbD8gbGlzdHMpIChhcHBseSBwcm9jKSAoaWYgKG51bGw/IChjYXIgbGlzdHMpKSAnKCkgKGxl +dCogKCh1bnogKGFwcGx5IHVuemlwMS13aXRoLWNkciBsaXN0cykpIChjYXJzIChjYXIgdW56KSkg +KGNkcnMgKGNkciB1bnopKSkgKGNvbnMgKGFwcGx5IHByb2MgY2FycykgKGFwcGx5IG1hcCAoY29u +cyBwcm9jIGNkcnMpKSkpKSkKRXZhbDogKG51bGw/IGxpc3RzKQpFdmFsOiBudWxsPwpFdmFsOiBs +aXN0cwpBcHBseSB0bzogKCgoKSkpCkV2YWw6IChpZiAobnVsbD8gKGNhciBsaXN0cykpICcoKSAo +bGV0KiAoKHVueiAoYXBwbHkgdW56aXAxLXdpdGgtY2RyIGxpc3RzKSkgKGNhcnMgKGNhciB1bnop +KSAoY2RycyAoY2RyIHVueikpKSAoY29ucyAoYXBwbHkgcHJvYyBjYXJzKSAoYXBwbHkgbWFwIChj +b25zIHByb2MgY2RycykpKSkpCkV2YWw6IChudWxsPyAoY2FyIGxpc3RzKSkKRXZhbDogbnVsbD8K +RXZhbDogKGNhciBsaXN0cykKRXZhbDogY2FyCkV2YWw6IGxpc3RzCkFwcGx5IHRvOiAoKCgpKSkK +QXBwbHkgdG86ICgoKSkKRXZhbDogJygpCkFwcGx5IHRvOiAoKDQpICgpKQpFdmFsOiAoZ2ltcC1p +bWFnZS1ncmlkLXNldC1zcGFjaW5nIGltZyAxMDAgMTAwKQpFdmFsOiBnaW1wLWltYWdlLWdyaWQt +c2V0LXNwYWNpbmcKRXZhbDogaW1nCkV2YWw6IDEwMApFdmFsOiAxMDAKQXBwbHkgdG86ICgxIDEw +MCAxMDApCkV2YWw6IChhcHBseSBnaW1wLXByb2MtZGItY2FsbCAoY29ucyAiZ2ltcC1pbWFnZS1n +cmlkLXNldC1zcGFjaW5nIiB4KSkKRXZhbDogYXBwbHkKRXZhbDogZ2ltcC1wcm9jLWRiLWNhbGwK +RXZhbDogKGNvbnMgImdpbXAtaW1hZ2UtZ3JpZC1zZXQtc3BhY2luZyIgeCkKRXZhbDogY29ucwpF +dmFsOiAiZ2ltcC1pbWFnZS1ncmlkLXNldC1zcGFjaW5nIgpFdmFsOiB4CkFwcGx5IHRvOiAoImdp +bXAtaW1hZ2UtZ3JpZC1zZXQtc3BhY2luZyIgKDEgMTAwIDEwMCkpCkFwcGx5IHRvOiAoIzxGT1JF +SUdOIFBST0NFRFVSRSA5Mzk2MTEzMTU1MjQ5Nj4gKCJnaW1wLWltYWdlLWdyaWQtc2V0LXNwYWNp +bmciIDEgMTAwIDEwMCkpCkFwcGx5IHRvOiAoImdpbXAtaW1hZ2UtZ3JpZC1zZXQtc3BhY2luZyIg +MSAxMDAgMTAwKQpFdmFsOiAoZ2ltcC1pbWFnZS1ncmlkLXNldC1vZmZzZXQgaW1nIDAgMCkKRXZh +bDogZ2ltcC1pbWFnZS1ncmlkLXNldC1vZmZzZXQKRXZhbDogaW1nCkV2YWw6IDAKRXZhbDogMApB +cHBseSB0bzogKDEgMCAwKQpFdmFsOiAoYXBwbHkgZ2ltcC1wcm9jLWRiLWNhbGwgKGNvbnMgImdp +bXAtaW1hZ2UtZ3JpZC1zZXQtb2Zmc2V0IiB4KSkKRXZhbDogYXBwbHkKRXZhbDogZ2ltcC1wcm9j +LWRiLWNhbGwKRXZhbDogKGNvbnMgImdpbXAtaW1hZ2UtZ3JpZC1zZXQtb2Zmc2V0IiB4KQpFdmFs +OiBjb25zCkV2YWw6ICJnaW1wLWltYWdlLWdyaWQtc2V0LW9mZnNldCIKRXZhbDogeApBcHBseSB0 +bzogKCJnaW1wLWltYWdlLWdyaWQtc2V0LW9mZnNldCIgKDEgMCAwKSkKQXBwbHkgdG86ICgjPEZP +UkVJR04gUFJPQ0VEVVJFIDkzOTYxMTMxNTUyNDk2PiAoImdpbXAtaW1hZ2UtZ3JpZC1zZXQtb2Zm +c2V0IiAxIDAgMCkpCkFwcGx5IHRvOiAoImdpbXAtaW1hZ2UtZ3JpZC1zZXQtb2Zmc2V0IiAxIDAg +MCkKRXZhbDogKGdpbXAtaW1hZ2UtdW5kby1lbmFibGUgaW1nKQpFdmFsOiBnaW1wLWltYWdlLXVu +ZG8tZW5hYmxlCkV2YWw6IGltZwpBcHBseSB0bzogKDEpCkV2YWw6IChhcHBseSBnaW1wLXByb2Mt +ZGItY2FsbCAoY29ucyAiZ2ltcC1pbWFnZS11bmRvLWVuYWJsZSIgeCkpCkV2YWw6IGFwcGx5CkV2 +YWw6IGdpbXAtcHJvYy1kYi1jYWxsCkV2YWw6IChjb25zICJnaW1wLWltYWdlLXVuZG8tZW5hYmxl +IiB4KQpFdmFsOiBjb25zCkV2YWw6ICJnaW1wLWltYWdlLXVuZG8tZW5hYmxlIgpFdmFsOiB4CkFw +cGx5IHRvOiAoImdpbXAtaW1hZ2UtdW5kby1lbmFibGUiICgxKSkKQXBwbHkgdG86ICgjPEZPUkVJ +R04gUFJPQ0VEVVJFIDkzOTYxMTMxNTUyNDk2PiAoImdpbXAtaW1hZ2UtdW5kby1lbmFibGUiIDEp +KQpBcHBseSB0bzogKCJnaW1wLWltYWdlLXVuZG8tZW5hYmxlIiAxKQpFdmFsOiAoZ2ltcC1maWxl +LXNhdmUgUlVOLU5PTklOVEVSQUNUSVZFIGltZyAoY2FyIChnaW1wLWltYWdlLWdldC1hY3RpdmUt +bGF5ZXIgaW1nKSkgIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovcmVmX2d1aWRlcy5zdmcueGNmIiAi +L3RtcC9naW1wLW91dC0zc2gyNXhuai9yZWZfZ3VpZGVzLnN2Zy54Y2YiKQpFdmFsOiBnaW1wLWZp +bGUtc2F2ZQpFdmFsOiBSVU4tTk9OSU5URVJBQ1RJVkUKRXZhbDogaW1nCkV2YWw6IChjYXIgKGdp +bXAtaW1hZ2UtZ2V0LWFjdGl2ZS1sYXllciBpbWcpKQpFdmFsOiBjYXIKRXZhbDogKGdpbXAtaW1h +Z2UtZ2V0LWFjdGl2ZS1sYXllciBpbWcpCkV2YWw6IGdpbXAtaW1hZ2UtZ2V0LWFjdGl2ZS1sYXll +cgpFdmFsOiBpbWcKQXBwbHkgdG86ICgxKQpFdmFsOiAoYXBwbHkgZ2ltcC1wcm9jLWRiLWNhbGwg +KGNvbnMgImdpbXAtaW1hZ2UtZ2V0LWFjdGl2ZS1sYXllciIgeCkpCkV2YWw6IGFwcGx5CkV2YWw6 +IGdpbXAtcHJvYy1kYi1jYWxsCkV2YWw6IChjb25zICJnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5 +ZXIiIHgpCkV2YWw6IGNvbnMKRXZhbDogImdpbXAtaW1hZ2UtZ2V0LWFjdGl2ZS1sYXllciIKRXZh +bDogeApBcHBseSB0bzogKCJnaW1wLWltYWdlLWdldC1hY3RpdmUtbGF5ZXIiICgxKSkKQXBwbHkg +dG86ICgjPEZPUkVJR04gUFJPQ0VEVVJFIDkzOTYxMTMxNTUyNDk2PiAoImdpbXAtaW1hZ2UtZ2V0 +LWFjdGl2ZS1sYXllciIgMSkpCkFwcGx5IHRvOiAoImdpbXAtaW1hZ2UtZ2V0LWFjdGl2ZS1sYXll +ciIgMSkKQXBwbHkgdG86ICgoNCkpCkV2YWw6ICIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL3JlZl9n +dWlkZXMuc3ZnLnhjZiIKRXZhbDogIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovcmVmX2d1aWRlcy5z +dmcueGNmIgpBcHBseSB0bzogKDEgMSA0ICIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL3JlZl9ndWlk +ZXMuc3ZnLnhjZiIgIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovcmVmX2d1aWRlcy5zdmcueGNmIikK +RXZhbDogKGFwcGx5IGdpbXAtcHJvYy1kYi1jYWxsIChjb25zICJnaW1wLWZpbGUtc2F2ZSIgeCkp +CkV2YWw6IGFwcGx5CkV2YWw6IGdpbXAtcHJvYy1kYi1jYWxsCkV2YWw6IChjb25zICJnaW1wLWZp +bGUtc2F2ZSIgeCkKRXZhbDogY29ucwpFdmFsOiAiZ2ltcC1maWxlLXNhdmUiCkV2YWw6IHgKQXBw +bHkgdG86ICgiZ2ltcC1maWxlLXNhdmUiICgxIDEgNCAiL3RtcC9naW1wLW91dC0zc2gyNXhuai9y +ZWZfZ3VpZGVzLnN2Zy54Y2YiICIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL3JlZl9ndWlkZXMuc3Zn +LnhjZiIpKQpBcHBseSB0bzogKCM8Rk9SRUlHTiBQUk9DRURVUkUgOTM5NjExMzE1NTI0OTY+ICgi +Z2ltcC1maWxlLXNhdmUiIDEgMSA0ICIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL3JlZl9ndWlkZXMu +c3ZnLnhjZiIgIi90bXAvZ2ltcC1vdXQtM3NoMjV4bmovcmVmX2d1aWRlcy5zdmcueGNmIikpCkFw +cGx5IHRvOiAoImdpbXAtZmlsZS1zYXZlIiAxIDEgNCAiL3RtcC9naW1wLW91dC0zc2gyNXhuai9y +ZWZfZ3VpZGVzLnN2Zy54Y2YiICIvdG1wL2dpbXAtb3V0LTNzaDI1eG5qL3JlZl9ndWlkZXMuc3Zn +LnhjZiIpCkdpdmVzOiAoI3QpCnRzPiAKRXZhbDogKGdpbXAtcXVpdCAwKQpFdmFsOiBnaW1wLXF1 +aXQKRXZhbDogMApBcHBseSB0bzogKDApCkV2YWw6IChhcHBseSBnaW1wLXByb2MtZGItY2FsbCAo +Y29ucyAiZ2ltcC1xdWl0IiB4KSkKRXZhbDogYXBwbHkKRXZhbDogZ2ltcC1wcm9jLWRiLWNhbGwK +RXZhbDogKGNvbnMgImdpbXAtcXVpdCIgeCkKRXZhbDogY29ucwpFdmFsOiAiZ2ltcC1xdWl0IgpF +dmFsOiB4CkFwcGx5IHRvOiAoImdpbXAtcXVpdCIgKDApKQpBcHBseSB0bzogKCM8Rk9SRUlHTiBQ +Uk9DRURVUkUgOTM5NjExMzE1NTI0OTY+ICgiZ2ltcC1xdWl0IiAwKSkKQXBwbHkgdG86ICgiZ2lt +cC1xdWl0IiAwKQ== + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="ref_guides.svg.xcf" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: ref_guides.svg.xcf + +Z2ltcCB4Y2YgZmlsZQAAAAPoAAAD6AAAAAAAAAARAAAAAQEAAAASAAAAFAAAA7kCAAAALgIAAAAx +AQAAA7cBAAAAEwAAAAhCwAAAQsAAAAAAABQAAAAEAAAAAgAAABYAAAAEAAAAAQAAABUAAAEeAAAA +EGdpbXAtaW1hZ2UtZ3JpZAAAAAABAAABAihzdHlsZSBzb2xpZCkKKGZnY29sb3IgKGNvbG9yLXJn +YmEgMC4wMDAwMDAgMC4wMDAwMDAgMC4wMDAwMDAgMS4wMDAwMDApKQooYmdjb2xvciAoY29sb3It +cmdiYSAxLjAwMDAwMCAxLjAwMDAwMCAxLjAwMDAwMCAxLjAwMDAwMCkpCih4c3BhY2luZyAxMDAu +MDAwMDAwKQooeXNwYWNpbmcgMTAwLjAwMDAwMCkKKHNwYWNpbmctdW5pdCBpbmNoZXMpCih4b2Zm +c2V0IDAuMDAwMDAwKQooeW9mZnNldCAwLjAwMDAwMCkKKG9mZnNldC11bml0IGluY2hlcykKAAAA +AAAAAAAAAAABoQAAAAAAAAAAAAAD6AAAA+gAAAABAAAAB1NsaWRlMgAAAAACAAAAAAAAAAYAAAAE +AAAA/wAAAAgAAAAEAAAAAQAAAAkAAAAEAAAAAAAAABwAAAAEAAAAAAAAAAoAAAAEAAAAAAAAAAsA +AAAEAAAAAAAAAAwAAAAEAAAAAAAAAA0AAAAEAAAAAAAAAA8AAAAIAAAAAAAAAAAAAAAHAAAABAAA +AAAAAAAUAAAABAAAAAIAAAAAAAAAAAAAAlgAAAAAAAAD6AAAA+gAAAAEAAACfAAB7AUAAewRAAHs +HQAB7CkAAAAAAAAD6AAAA+gAAAaIAAAGmAAABqgAAAa4AAAGyAAABtgAAAboAAAG+AAABwgAAAcY +AAAHKAAABzgAAAdIAAAHWAAAB2gAAAd4AAAHiAAAB5gAAAeoAAAHuAAAB8gAAAssAAALWAAADsQA +ABH6AAASJgAAFWQAABn4AAAaLAAAHsgAAB7YAAAe6AAAHvgAAB8IAAAfGAAAHygAAB84AAAmuAAA +JsgAAC5IAAA1SAAANVgAADxYAABGmAAARqgAAFDoAABQ+AAAUQgAAFEYAABRKAAAUTgAAFFIAABR +WAAAUnEAAFKdAABTrgAAVLsAAFTnAABV7AAAV1UAAFeJAABY6QAAWPkAAFkJAABZGQAAWSkAAFtT +AABfbgAAX34AAF+OAABfngAAX64AAF++AABfzgAAX94AAF/uAABf/gAAYrIAAGXQAABl4AAAZfAA +AGYAAABuDAAAb14AAHX2AAB2BgAAdhYAAHYmAAB4YAAAeHAAAHiAAAB4kAAAeKAAAIWWAACQywAA +k/AAAJQAAACUEAAAlbEAAJk9AACZTQAAmV0AAJltAACa5wAAprUAAKnfAACtkQAAraEAAK2xAACy +uAAAuA4AALi5AAC4yQAAuNkAALooAAC95wAAvfcAAL4HAAC//wAAx7sAANLuAADfdAAA5GEAAORx +AADkgQAA5X0AAOgVAADoJQAA6DUAAOhFAADxJwAA8yMAAPoYAAD6KAAA+nYAAP9LAAEOYgABGBsA +AR43AAEeRwABHlcAASs8AAEzzgABOqcAATq3AAE6xwABPbwAAULyAAFDEgABQyIAAUMyAAFD/gAB +S2oAAUx0AAFNvAABTcwAAU3cAAFSxQABWZUAAVrgAAFa8AABWwAAAVtcAAFd2AABXegAAV34AAFe +CAABXhgAAV5uAAFefgABXo4AAV6eAAFergABXuMAAWALAAFgGwABYCsAAWA7AAFrNgABcE8AAXZf +AAF2bwABdn8AAXaPAAF2nwABdq8AAXa/AAF2zwABdt8AAYPDAAGLIQABkhAAAZIgAAGSMAABl2oA +AZ52AAGe9gABosQAAaL4AAGmQAABqWcAAambAAGtogABsEAAAbB0AAG6YgABwmwAAcPIAAHD2AAB +w+gAAcP4AAHECAABxBgAAco4AAHKWAABztgAAdN2AAHTlgAB2fcAAd53AAHehwAB5QcAAeUXAAHl +JwAB5TcAAeVHAAHlVwAB5WcAAeV3AAHm0wAB5vcAAehNAAHozAAB6OQAAelbAAHqCwAB6i8AAerV +AAHq5QAB6vUAAesFAAHrFQAB6yUAAes1AAHrRQAB61UAAetlAAHrdQAB64UAAeuVAAHrpQAB67UA +AevFAAHr1QAB6+UAAev1AAAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8KAAB/CgAAfwoAAH8KAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfwjs +AP2fpKUQpSoA/JKx0dwQ3CoA/aTR+xH7KgD9pd/7EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977Efsq +AP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA +/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9 +o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j +3vsR+38JLAD9F1lxEHErAP5YrRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0r +AP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA +/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+ +dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa1/CSwA/QkgKBAoKwD+Hz4RPisA/ik+ET4r +AP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA +/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+ +KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4p +PhE+fwjsAP0IXHcQdyoA/AfS0sQQxCoA/VzTgBGAKgD9f7+AEYAqAP2AwIARgCoA/YDAgBGAKgD9 +gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2A +wIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDA +gBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCA +EYAqAP2AwIARgH8IwAA/pT/cfwbA+38JAAA/cX8GwK1/CQAAPyh/BsA+fwjAAD93P8R/BsCAfwjA +AA2l/aSSAC4ADdz80LGSAC0ADvv90aQALQAO+/3fpQAtAA77/d6jAC0ADvv93qMALQAO+/3eowAt +AA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0A +Dvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO ++/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77 +/d6jAC0AfwkAAA1x/VkXAC4ADq3+VwAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50 +AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQA +LgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAu +AA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4AfwkAAA0o/SAJAC4ADj7+HwAuAA4+ +/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+ +KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4p +AC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikA +LgAOPv4pAC4AfwjAAA13/VwHAC4ADcT80tEHAC0ADoD901oALQAOgP2/fwAtAA6A/cCAAC0ADoD9 +wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3A +gAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCA +AC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAA +LQAOgP3AgAAtAA6A/cCAAC0AfwjzAPyApKOkCKQyAPylrsnQCNAyAPyjyef7CPsyAP2k0PsJ+zIA +/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9 +pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k +0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ ++wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7fwk0AP0TSVcIVzMA/UmErQitMwD+V60JrTMA/let +Ca0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60J +rTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmt +MwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa1/ +CTQA/QcaHwgfMwD9Gi8+CD4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+ +CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4J +PjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+ +MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPn8I8wD8AnPc/wj/MgD+dP8K/zIA/Nz/qIAIgDIA +/f7/gAmAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB +/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqA +MgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAfwjAAD+kP9B/BsD7fwkA +AD9XfwbArX8JAAA/H38GwD5/CMAAfwCA/38GwIB/CMAAFaQBo/6AACUAFdD8yK6kACUAFfv85cmk +ACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQA +JQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAl +ABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUA +Fvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAlAH8JAAAVV/1JEwAmABWt/YNI +ACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcA +JgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAm +ABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYA +Fq3+VwAmABat/lcAJgB/CQAAFR/9GgcAJgAVPv0vGgAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+ +/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+ +HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4f +ACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYAfwjAABX//NxyAgAl +ABf//nMAJQAVgPyp/9sAJQAWgP3//gAlABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaA +Af8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8m +ABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaA +Af8mAH8IuQD+n6QEpDcA/aejpASkNwD7paSwy9AC0DYA+/+kpM3QA9A2APqqpKfQ0fsC+zYA+qKk +qNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ +0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7 +Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7 +NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA ++qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7fwk6AP0YTFcCVzgA/QFQ +VwNXOAD8B1dbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKt +OAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8 +CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdb +rQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKt +OAD8CFdbrQKtfwk6AP0JGx8CHzkA/hwfAx84APwDHyA+Aj44APwDHyA+Aj44APwDHyA+Aj44APwD +HyA+Aj44APwDHyA+Aj44APwDHyA+Aj44APwDHyA+Aj44APwDHyA+Aj44APwDHyA+Aj44APwDHyA+ +Aj44APwDHyA+Aj44APwDHyA+Aj44APwDHyA+Aj44APwDHyA+Aj44APwDHyA+Aj44APwDHyA+Aj44 +APwDHyA+Aj44APwDHyA+Aj44APwDHyA+Aj44APwDHyA+Aj44APwDHyA+Aj44APwDHyA+Aj44APwD +HyA+Aj44APwDHyA+Aj44APwDHyA+Aj44APwDHyA+Aj5/CLkA/Big4f8C/zcA/R3q/wT/NwD+rP8F +/zYA/QHw/wX/NgD+Ff8B//71gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKA +NgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70 +gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B +//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+ +Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKA +NgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKAfwiAAH8AgKR/AIDQfwaA+38JAAB/ +AIBXfwaArX8JAAB/AIAffwaAPn8IgAB/AQD/fwaAgH8IgAAcpP2jmQAfAB6k/qoAHgAa0PrPyKuk +pQAeABzQ/MSkpAAeABr7+vLQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4A +Gvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7 ++vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r0 +0MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDM +pKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSk +AB4AGvv69NDMpKQAHgB/CQAAGlf8VkYPACAAHFf+QQAgABqt/J5XTwAgABqt/KBXTwAgABqt/KBX +TwAgABqt/KBXTwAgABqt/KBXTwAgABqt/KBXTwAgABqt/KBXTwAgABqt/KBXTwAgABqt/KBXTwAg +ABqt/KBXTwAgABqt/KBXTwAgABqt/KBXTwAgABqt/KBXTwAgABqt/KBXTwAgABqt/KBXTwAgABqt +/KBXTwAgABqt/KBXTwAgABqt/KBXTwAgABqt/KBXTwAgABqt/KBXTwAgABqt/KBXTwAgABqt/KBX +TwAgABqt/KBXTwAgABqt/KBXTwAgABqt/KBXTwAgABqt/KBXTwAgAH8JAAAbH/0ZBQAgABwf/hcA +IAAaPvw4HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAa +Pvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5 +HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwA +IAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAaPvw5HxwAIAAa +Pvw5HxwAIAB/CIAAGv/7/diLCgAfAB3//dIJAB4AHv/+fwAeAB7//sUAHgAagP6M/wH//ugAHgAa +gP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukA +HgAagP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH/ +/ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L +/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukAHgAa +gP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukA +HgAagP6L/wH//ukAHgB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8KAAB/CgAAfwoA +AH8KAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAKgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe ++xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977 +EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR ++yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7 +KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977Efsq +AP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA +/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9 +o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j +3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe ++xH7KgD9o977EfsrAP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50 +rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nSt +Ea0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0R +rSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGt +KwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0r +AP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA +/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+ +dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP50 +rRGtKwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ +ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4R +PisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+ +KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4r +AP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA +/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+ +KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4p +PhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPioA/YDA +gBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCA +EYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIAR +gCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGA +KgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAq +AP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA +/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9 +gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2A +wIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDA +gBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAfxAA+38Q +AK1/EAA+fxAAgA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAt +AA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0A +Dvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO ++/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77 +/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv9 +3qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3e +owAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6j +AC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMA +LQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAt +AA77/d6jAC0ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAu +AA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4A +Dq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAO +rf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t +/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+ +dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50 +AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQA +LgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4ADq3+dAAu +AA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4A +Dj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAO +Pv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+ +/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+ +KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4p +AC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikA +LgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAu +AA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOgP3AgAAt +AA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0A +DoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAO +gP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A +/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9 +wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3A +gAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCA +AC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAA +LQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAt +AA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAALQAOgP3AgAAtADIA/aTQ+wn7 +MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7Cfsy +AP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA +/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9 +pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k +0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ ++wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7 +CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ ++zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7 +MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MwD+V60JrTMA +/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+ +V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5X +rQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/let +Ca0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60J +rTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmt +MwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0z +AP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA +/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/h8+CT4zAP4fPgk+MwD+ +Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4f +Pgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+ +CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4J +PjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+ +MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4z +AP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA +/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+ +Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4yAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAy +AAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/ +CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAy +AAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/ +CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAy +AAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/ +CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAyAAH/CoAy +AAH/CoAyAAH/CoAyAAH/CoB/EAD7fxAArX8QAD5/EACAFvv90KQAJQAW+/3QpAAlABb7/dCkACUA +Fvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW ++/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7 +/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv9 +0KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3Q +pAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCk +ACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQA +JQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAl +ABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAlABb7/dCkACUA +Fvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5X +ACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcA +JgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAm +ABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYA +Fq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAW +rf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat +/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+ +VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5XACYAFq3+VwAmABat/lcAJgAWrf5X +ACYAFq3+VwAmABat/lcAJgAWrf5XACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8A +JgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAm +ABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYA +Fj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAW +Pv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+ +/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+ +HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4f +ACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABY+/h8A +JgAWPv4fACYAFj7+HwAmABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8m +ABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaA +Af8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8m +ABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaA +Af8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8m +ABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaA +Af8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8mABaAAf8m +ADYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2 +APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6 +oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKk +qNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ +0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7 +Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7 +NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA ++qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqi +pKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo +0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS ++wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC ++zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2 +APqipKjQ0vsC+zYA+qKkqNDS+wL7OAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKt +OAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8 +CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdb +rQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKt +OAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8 +CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdb +rQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKt +OAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8 +CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdb +rQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKt +OAD8CFdbrQKtOAD8CFdbrQKtOAD8CFdbrQKtOAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8 +Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8g +PgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+ +OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8 +Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8g +PgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+ +OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8 +Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8g +PgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+ +OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8 +Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+NgD+Fv8B//70gAKANgD+Fv8B//70gAKA +NgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70 +gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B +//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+ +Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKA +NgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70 +gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B +//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+ +Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKA +NgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70 +gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B +//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+ +Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKA +NgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70gAKANgD+Fv8B//70 +gAKAfxAA+38QAK1/EAA+fxAAgBr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQ +zKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00Myk +pAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQA +HgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4A +Gvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7 ++vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r0 +0MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDM +pKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSk +AB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAe +ABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa ++/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv6 +9NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQ +zKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQzKSkAB4AGq38oFdPACAAGq38oFdPACAA +Gq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38 +oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdP +ACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAA +Gq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38 +oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdP +ACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAA +Gq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38 +oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdP +ACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAA +Gq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGj78OR8cACAAGj78 +OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8c +ACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAA +Gj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78 +OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8c +ACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAA +Gj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78 +OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8c +ACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAA +Gj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78 +OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGoD+i/8B +//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+ +i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4A +GoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7p +AB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B +//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+ +i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4A +GoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7p +AB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B +//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+ +i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4A +GoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7p +AB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B +//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+i/8B//7pAB4AGoD+ +i/8B//7pAB4AGoD+i/8B//7pAB4AfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/CgAA +fwoAAH8KAAB/CgAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAACoA/aPe+xH7KgD9o977EfsqAP2j3vsR+yoA/aPe+xH7KgD9o977 +EfsqAP2l3/sR+yoA/aXR+xH7KgD7krDR3+AP4CsA/JKkpaMPo38NwAArAP50rRGtKwD+dK0RrSsA +/nStEa0rAP50rRGtKwD+dK0RrSsA/nStEa0rAP5YrRGtKwD8F1d1dw93fw4AACsA/ik+ET4rAP4p +PhE+KwD+KT4RPisA/ik+ET4rAP4pPhE+KwD+KT4RPisA/h8+ET4rAP0JHyoQKn8OAAAqAP2AwIAR +gCoA/YDAgBGAKgD9gMCAEYAqAP2AwIARgCoA/YDAgBGAKgD9f7+AEYAqAP1b04ARgCoA+wfS0727 +D7srAPwHWoWID4h/DcAAfwHA+z/gP6N/DcAAfwHArT93fw4AAH8BwD4/Kn8OAAB/AcCAP7s/iH8N +wAAO+/3eowAtAA77/d6jAC0ADvv93qMALQAO+/3eowAtAA77/d6jAC0ADvv936UALQAO+/3RpAAt +AAzg+9/RsJIALQAMo/ylpJIAfw3vAA6t/nQALgAOrf50AC4ADq3+dAAuAA6t/nQALgAOrf50AC4A +Dq3+dAAuAA6t/lcALgAMd/x1VxcAfw4vAA4+/ikALgAOPv4pAC4ADj7+KQAuAA4+/ikALgAOPv4p +AC4ADj7+KQAuAA4+/h8ALgANKv0fCQB/Di8ADoD9wIAALQAOgP3AgAAtAA6A/cCAAC0ADoD9wIAA +LQAOgP3AgAAtAA6A/b9/AC0ADoD901oALQAMu/u909EHAC0ADIj8hVoHAH8N7wAyAP2k0PsJ+zIA +/aTQ+wn7MgD9pND7CfsyAP2k0PsJ+zIA/aTQ+wn7MgD9pND7CfsyAPyjyeX7CPsyAPykrsnQCNAy +AP2Ao6QJpH8NwAAzAP5XrQmtMwD+V60JrTMA/letCa0zAP5XrQmtMwD+V60JrTMA/letCa0zAP1J +g60IrTMA/RNIVwhXfw4AADMA/h8+CT4zAP4fPgk+MwD+Hz4JPjMA/h8+CT4zAP4fPgk+MwD+Hz4J +PjMA/RovPgg+MwD9BxofCB9/DgAAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgAB/wqAMgD9/v+A +CYAyAPzc/6mACIAyAP5z/wr/MgD8AnLb/wj/fw3AAH8BwPs/0D+kfw3AAH8BwK0/V38OAAB/AcA+ +Px9/DgAAfwHAgH8AgP9/DcAAFvv90KQAJQAW+/3QpAAlABb7/dCkACUAFvv90KQAJQAW+/3QpAAl +ABb7/dCkACUAFfv85cmkACUAFdD8ya2lACUAFqT9o4AAfw3mABat/lcAJgAWrf5XACYAFq3+VwAm +ABat/lcAJgAWrf5XACYAFq3+VwAmABWt/YNIACYAFVf9SBMAfw4nABY+/h8AJgAWPv4fACYAFj7+ +HwAmABY+/h8AJgAWPv4fACYAFj7+HwAmABU+/S8aACYAFR/9GgcAfw4nABaAAf8mABaAAf8mABaA +Af8mABaAAf8mABaAAf8mABaA/f/+ACUAFYD8qf/bACUAF//+cQAlABX//NtyAgB/DeYANgD6oqSo +0NL7Avs2APqipKjQ0vsC+zYA+qKkqNDS+wL7NgD6oqSo0NL7Avs2APqipKjQ0vsC+zYA+qqkp9DR ++wL7NgD7/6SkzdAD0DcA+6WksMrQAtA3AP2ko6QEpDgA/Z+lpAOkfw2AADgA/AhXW60CrTgA/AhX +W60CrTgA/AhXW60CrTgA/AhXW60CrTgA/AhXW60CrTgA/AdXW60CrTgA/QFQVwNXOQD9GEtXAld/ +DgAAOAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+OAD8Ax8gPgI+ +OQD+HB8DHzkA/QkbHwIffw4AADYA/hb/Af/+9IACgDYA/hb/Af/+9IACgDYA/hb/Af/+9IACgDYA +/hb/Af/+9IACgDYA/hb/Af/+9IACgDYA/hX/Af/+9YACgDYA/QHv/wX/NwD+rP8F/zcA/Rzq/wT/ +OAD8GJ7g/wL/fw2AAH8BgPt/AIDQfwCApH8NgAB/AYCtfwCAV38OAAB/AYA+fwCAH38OAAB/AYCA +fwEA/38NgAAa+/r00MykpAAeABr7+vTQzKSkAB4AGvv69NDMpKQAHgAa+/r00MykpAAeABr7+vTQ +zKSkAB4AGvv68tDMpKQAHgAc0PzFpKQAHgAa0PrPx6ukpAAeAB2k/aOfAB4AHaT+ogB/DaAAGq38 +oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38oFdPACAAGq38nldPACAAHFf+QQAg +ABpX/FZGDwB/DiEAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAAGj78OR8cACAA +Gj78OB8cACAAHB/+FwAgABsf/RkFAH8OIQAagP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6L/wH/ +/ukAHgAagP6L/wH//ukAHgAagP6L/wH//ukAHgAagP6M/wH//ugAHgAe//7EAB4AHv/+fgAeAB3/ +/dEIAB4AGv/7/daKCwB/DaAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/CgAAfwoA +AH8KAAB/CgAAfxAAAH8QAAB/EAAAfxAAAH8MPwD+pAA7APyiqsAAOQD6qqa94PkAOAD5prLV9Pv7 +ADYA+6Wryer7Avs2APuis9v5+wP7NAD7/6K85/sF+zMA+5mpy/P7BvsyAPuZq9H5+wf7MQD7pa7W ++fsI+zAA/Kqw2/sK+y8A/Kqv2/sL+y4A/Jmr1fsM+y0A+/+r0fn7DPstAPylyvn7DfssAPykv/H7 +Dvt/DH4A/Q81ADoA+wcxeKgAOQD6HWCgra0ANwD8EEmMrQKtNwD8H2mprQOtNQD8BTGDrQWtNAD8 +Dk+brQatMwD8D1morQetMgD8E2KrrQitMQD9GW2tCq0wAP0Uba0LrS8A/RBirQytLgD8DFmrrQyt +LQD8BE+orQ2tLQD9NZqtDq1/DH4A/QYTADoA+wISKzwAOQD6CiM5Pj4ANwD8BhoyPgI+NwD8CyY8 +PgM+NQD8AhIvPgU+NAD8BR03PgY+MwD8BiE8Pgc+MgD8BiM9Pgg+MQD9CSc+Cj4wAP0IJz4LPi8A +/QUjPgw+LgD8BSE9Pgw+LQD8Ah08Pg0+LQD9Ezc+Dj5/DD8A/i0AOwD8LLXtADkA+gyP77aEADgA ++U3kzIuAgAA2APsRtOOegAKANgD7NOHEg4ADgDQA+wFx8KqABYAzAPsFp96PgAaAMgD7CrnThIAH +gDEA+xHJy4KACIAwAPwS2cCACoAvAPwJyb+AC4AuAPwFus2ADIAtAPsBptOCgAyALQD8dN+EgA2A +LAD8Ne+QgA6AfwrPAP2ip6UBpfujpaWjpQGl/aeZACsA56ijqLG2vMbN0dXb3Nzb1dHNxry2sail +qAAiAPamo6u1wdXg6PP7C/v1+fHo4tbEt6yloQAbAPiSo6690uPv+xb7+O/h0b2upZIAFgD5pam4 +zeP3+xz7+ffjzbiqowATAPutvtzy+yL7+vLfwKumABEA/d/3+yb7+vffwq2kAA8AK/v6+eC+qaIA +DQAt+/v217ahAAwAL/v76smrnwAKADD7+/nbtKYACQAy+/vov6b/AAcAM/v78c2ovwAGADT7+/fT +q5kABQA2+/zYraYABAA3+/zasaoAAwA4+/zar6IAAgA5+/zXrb8AAQA5+/n50qn/AAD7Ofv698yl +AAD7Ovv88b+mAH8LDADrChokL0RRWmBpcHBpYFpRQy8kGgoAJgD4DCA4YHqIm60Lrfesmoh6Yj4n +EQAfAPoUMFx8l60WrfqXe1wvFAAZAPoMKFJ/pq0crfqmf1InDAAUAPsPNHGcrSKt+5xyNw8AEgD9 +dKatJq37pnU7EgAQACut+6h4NAkADgAtrfyiZCMADQAvrfyMSBAACwAwrfyrbSAACgAyrfyFNAQA +CAAzrfyaTgwABwA0rfynWxIABgA1rfysZBUABQA3rf1sGQAEADit/WwVAAMAOa39ZBIAAgA5rfyr +WgwAAQA6rfqnTQUAAK06rf2bMwAAAH8LDADrAwkNERgdICImJycmIiAdGBENCQMAJgD4BQsVIisw +Nz4LPvc9NzArIxcNBgAfAPoHESEsNj4WPvo2LCARBwAZAPoDDh4tOz4cPvo7LRwOAwAUAPsGEyg4 +PiI++zgoFAYAEgD9Kjs+Jj77OykVBwAQACs++zwrEwQADgAtPvw6JAwADQAvPvwyGgYACwAwPvw9 +JwsACgAyPvwvEwIACAAzPvw3GwUABwA0Pvw7IAYABgA1Pvw9JAcABQA3Pv0mCAAEADg+/SYHAAMA +OT79IwcAAgA5Pvw9IAUAAQA6Pvo7GwIAAD46Pv03EgAAAH8KzwDxCzFEVWBsd3dsYFVEMQoAKwDn +I2SZw+f44NXNyMXCwsXIzdXg+OfDmGMjACIA9hRepObryrajj4ALgPWBkKO2zO3mpV0TABsA+AdT +t/POs5OAFoD4k7TO87ZSBwAWAPklmvDXr4aAHID5hq/X75kkABMA+6vwvY6AIoD6jr7wqSsAEQD9 +uIaAJoD6hrrvtCoADwArgPqEt/CMCwANAC2A+4nH6V8ADAAvgPuf47EQAAoAMID7gsDlOQAJADKA ++6fveQEABwAzgPuQ36QEAAYANID7hdW5CgAFADWA+4HJzhQABAA3gPzC1xIAAwA4gPzCzQsAAgA5 +gPzLugQAAQA5gPmC1aQBAACAOYD6heBxAACAOoD8j+45AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAA +AH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8JPwD+ogA4APyfo6OkAaQ1APydpaOkBaQyAP2fpqQJpDAA/qWkDKQtAP6qpA+kKwD9 +qqOkEKQpAP2kpaQSpCcA/f+jpBSkJgD+p6QWpCQA/p+kGKQjABukIQD9v6OkGqQgAP6npBykHwD+ +pqQdpB4A/qWkHqQcAP7/pCCkGwD+v6QdpPuosr7LABkA/qKkG6T8qrvK0ALQGQD+oqQZpPyot8bQ +BdAYAP6ZpBik/Km9ztAH0BcA/v+kF6T8qb3O0AnQFwD+paQVpPylu8/QC9AWABek/bHL0A3QFQD+ +o6QUpP2pxdAP0BQA/qekFKT9sc3QENATAP6/pBSk/rvQEtATAP6jpBKk/ajG0BPQfw18APsIHTRM +ADcA/A4uSlcCVzUA/AcmRVcFVzMA/AoxVFcHVzEA/AsxVFcJVy8A/AQuVlcLVy4A/RpNVw1XLAD9 +C0FXD1crAP0bUVcQVykA/QIuVxJXKAD9CUVXE1d/DXwA+wMKExsANwD8BRAbHwIfNQD8Ag0ZHwUf +MwD8BBIeHwcfMQD8BBIeHwkfLwD9AREfDB8uAP0JHB8NHywA/QQXHw8fKwD9Ch0fEB8pAP0BER8S +HygA/QMZHxMffwk/AP4WADgA+RhZmcjv/wA0APoNRIjM/v8D/zIA+whNqvr/B/8wAPwzkez/Cv8t +APwJbd7/Df8rAPwMb+X/D/8pAPwOd+3/Ef8nAPwBXuv/E/8mAP0xx/8V/yQA/BCR/P8W/yMA/Uzp +/xj/IQD8BID9/xn/IAD9HcT/G/8fAP1N7f8c/x4A/Xf+/x3/HAD9AYr/H/8bAP0Enf8g/xoA/Qu2 +/yH/GQD9C8X/Iv8YAP0Ftv8j/xcA/QGg/yT/FwD+jf8l/xYA/nn/Jv8VAP1L/v8m/xQA/R3s/yf/ +EwD9BMT/KP8TAP6L/yn/fwkAAPylpKOkDKT7o6SlogApABakAaP+nwAkABqk/KOloQAgAB6k/aOi +AB0AIaT9pf8AGgAkpP6fABgAJKQBo/6qABYAJ6T9o6oAFAAppP2jqgASACuk/qUAEQAtpP6hAA8A +L6QPADCk/pkADAAxpP6iAAsAMqT+owAKADOk/qUACQDrp660u8HGyMrNz8/NysjGwru0rqekIKT+ +gAAHABPQ+8zBtamkHaT+mQAGABfQ/Mm7q6QbpP6dAAUAGtD8xreopBmk/qIABAAc0PzPvqikGKT+ +qgADAB/Q/cCqpBek/oAAAgAg0PzPvaakFaT+pQACACLQ/My0o6QUpP6lAAEAJND9xamkFKT8owAA +0CTQ/c6ypBSk/aUA0CbQ/b2lpBOk/pnQJ9D9x6ekE6R/DQAA6wYUIS48REhLUVVVUUtIRDsuIRQG +ACoAE1f7TjojCwAmABdX/EotDgAjABpX/EUlBwAgABxX/FU0CgAeAB5X/FU3DQAcACBX/FYxBQAa +ACJX/U4gABkAJFf9QAsAFwAlV/1SHQAWACdX/TECABQAKFf9RAgAEwB/DQAA6wIHDBEVGBobHR4e +HRsaGBURDAcCACoAEx/7HBUMBAAmABcf/BoQBQAjABof/BgNAgAgABwf/B4TBAAeAB4f/B4UBQAc +ACEf/RICABoAIh/9HAsAGQAkH/0XBAAXACUf/R0KABYAJx/9EgEAFAAoH/0YAwATAH8JAADqPmWL +qrvH0t3u+fnu3dLGu6mLZT4WACkAFP/67siZWRgAJAAZ//vdmVUTACAAHP/7/LteCwAdAB//+/Gi +RAEAGgAi//zdawgAGAAk//zqgA8AFgAm//zxiBIAFAAo//zubwMAEgAq//3NNgARACv//P2iEwAP +AC3//ehJAA4ALv/8/pEFAAwAMP/9ySEACwAx//3sSwAKADL//f50AAkANP/9kQIABwA1//2jBQAG +ADb//bsNAAUAN//9xAsABAA4//27BgADADn//aUCAAIAOv/+kwACADv//nQAAQA7//v+TgAA/zv/ +/O0fAP88//3HBf89/wCHfxAAAH8QAAB/EAAAfxAAAH8KAAB/CgAAfwoAAH8KAAB/EAAAfxAAAH8Q +AAB/EAAAKwD8n7Tn+w/7KwD9q9n7EPsqAPyjyfn7EPspAPydter7EfspAP2p1fsS+ygA/KK/9vsS ++ygA/azi+xP7JwD8pMH5+xP7JwD9qt77FPsmAPyov/f7FPsmAP2o3fsV+yUA/Kq48vsV+yUA/aTN ++xb7JQD9ruP7FvskAPyhvPf7FvskAP2j0vsX+yQA/arj+xf7JAD9tfH7F/sjAP2jwvsY+yMA/aPU ++xj7IwD9quD7GPsjAP2x6fsY+yMA/bbx+xj7IgD8orz5+xj7IgD9pcb7GfsiAP2lzPsZ+yIA/aPQ ++xn7IgD9pdX7GfsiAP2k2PsZ+yIA/aPd+xn7IgD9o937GfsiAP2k2PsZ+yIA/aXV+xn7IgD9o9D7 +GfsiAP2kzPsZ+yIA/aXF+xn7IgD8orz5+xj7IwD9tfP7GPsjAP2x6PsY+yMA/ani+xj7IwD9o9T7 +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 +/alIrT2t/oytfw0+rf6MrTyt/alGrTyt/WgQrTut/IMfAK06rfuZMgAArTmt+qZMBQAArTit/KtZ +DQABADmt/WIPAAIAPD78MAsAPjw+/SYGPjw+/TwaPj0+/jI+fw0+Pv4yPjw+/TwYPjw+/SUGPjs+ +/C8LAD46Pvs3EgAAPjk++jsbAgAAPjg+/D0gBQABADk+/SMGAAIAPID8puUSgDyA/cWygDyA/YPk +gD2A/p6Afw0+gP6fgDyA/YPlgDyA/caugDuA/KnhD4A6gPuR7zMAgDmA+obhbgAAgDiA/ILWoQAB +ADmA/Mq4BAABAH8AgAD+pQA9AP21qgA8AP3WpgA8APz0vaQAOwD8++CrADsA+/v3waIAOgAB+/3f +qwA6AAH7/Pa+pQA5AAL7/d2oADkAAvv88biSADgAA/v9zaMAOAAD+/3irAA4AAP7/Pa8oQA3AAT7 +/dGkADcABPv94awANwAE+/3vtQA3AAX7/cGiADYABfv906QANgAF+/3gqgA2AAX7/eixADYABfv9 +8bYANgAF+/z5u5kANQAG+/3FpgA1AAb7/c2lADUABvv90KQANQAG+/3VowA1AAb7/dekADUABvv9 +3aQANQAG+/3dpAA1AAb7/dekADUABvv91aMANQAG+/3QpAA1AAb7/c2lADUABvv9xaYANQAF+/z5 +u5kANQAF+/3xtgA2AAX7/eivADYABfv94akANgAF+/3VowA2AAX7/cGnADYABPv97rUANwAE+/3j +qgA3AAT7/dCjADcAA/v89ryhADcAA/v95KwAOAAD+/3MpQA4AAL7/PO4kgA4AAL7/dupADkAAfv8 +9r2lADkAAfv93qsAOgD7+/fApgA6APz74asAOwD89L2mADsA/dWrADwA/bWiADwA/qUAfwG+AH8A +wAD+IQA9AP1jCQA8AP2gMwA8APyteBEAOwD8rag5ADsAAa39dREAOgABrf2kNAA6AAKt/XEKADkA +Aq39mycAOQADrf5QADkAA639fRMAOAADrf2kLgA4AASt/loAOAAErf17DgA3AASt/ZYjADcABa3+ +OQA3AAWt/l8ANwAFrf15CgA2AAWt/YcZADYABa39miMANgAFrf2sLwA2AAat/kMANgAGrf5QADYA +Bq3+VwA2AAat/mAANgAGrf5nADYABq3+cAA2AAat/nAANgAGrf5nADYABq3+YAA2AAat/lcANgAG +rf5QADYABq3+QwA2AAWt/awuADYABa39mSQANgAFrf2HGQA2AAWt/XkKADYABa3+XwA3AAWt/jkA +NwAErf2VIwA3AASt/XsOADcABK3+WQA4AAOt/aQuADgAA639fRMAOAADrf5QADkAAq39myYAOQAC +rf1wCgA5AAGt/aQ0ADoAAa39dA4AOgD8rac3ADsA/K12EAA7AP2eMgA8AP1hCQA8AP4gAH8B/gB/ +AMAA/gwAPQD9IwQAPAD9ORIAPAD8PisGADsA/D48FAA7AAE+/SkGADoAAT79OxMAOgACPv0oAwA5 +AAI+/TcOADkAAz7+HAA5AAM+/S0HADgAAz79OxEAOAAEPv4gADgABD79LAUANwAEPv01DAA3AAU+ +/hUANwAFPv4iADcABT79KwMANgAFPv0wCQA2AAU+/TcNADYABT79PRAANgAGPv4YADYABj7+HQA2 +AAY+/h8ANgAGPv4iADYABj7+JQA2AAY+/igANgAGPv4oADYABj7+JQA2AAY+/iIANgAGPv4fADYA +Bj7+HQA2AAY+/hgANgAFPv09EAA2AAU+/TcMADYABT79MAkANgAFPv0rAwA2AAU+/iIANwAFPv4U +ADcABD79NQwANwAEPv0sBQA3AAQ+/iAAOAADPv07EQA4AAM+/S0HADgAAz7+HAA5AAI+/TcOADkA +Aj79KAMAOQABPv07EgA6AAE+/SkFADoA/D47EwA7APw+KgYAOwD9OBIAPAD9IwQAPAD+CwB/Af4A +fwCAAP5SAD0A/eYMADwA/cyMADwA/IvwKgA7APyAt7MAOwD7gITtLAA6AAGA/bqpADoAAYD8h/El +ADkAAoD9vpgAOQACgPyP7wcAOAADgP3YUAA4AAOA/bGzADgAA4D8h/MTADcABID9z1oANwAEgP21 +oAA3AASA/ZTjADcABYD97CEANgAFgP3MYgA2AAWA/beWADYABYD9pMEANgAFgP2Q5gA2AAWA/IH4 +CgA1AAaA/eEuADUABoD91UQANQAGgP3PUQA1AAaA/cpeADUABoD9xmsANQAGgP3BeQA1AAaA/cF5 +ADUABoD9xmsANQAGgP3KXgA1AAaA/c9RADUABoD91UQANQAGgP3iLgA1AAWA/IH4CgA1AAWA/ZHl +ADYABYD9pMAANgAFgP24lQA2AAWA/cxhADYABYD97SAANgAEgP2V4wA3AASA/bWfADcABID90FkA +NwADgPyH8xMANwADgP2xswA4AAOA/dhPADgAAoD8j+8HADgAAoD9v5cAOQABgPyH8SUAOQABgP27 +pgA6APuAhe8rADoA/IC4sQA7APyM8CgAOwD9zogAPAD95gsAPAD+TwB/Ab4AfxAAAH8QAAB/EAAA +fxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfwiJAP6fAD0A/aSjADwAAaT+pQA6 +AP6fpAGk/qUAOQAFpDkABaT+owA3AAWk/aOkADYAB6T+owA1AAik/qIAMwD9qqOkBqT9o6cAMgD+ +o6QIpP2lnwAxAP6jpAqk/qEAMAANpP6lAC8ADqT+nQAuAA6k/aOiAC0AD6T9pZ8AKwD+n6QQpP6Z +ACoA/qKkEKT9pb8AKQATpP2lgAAoABWk/v8AJwAWpCgAFqT+pQAlAP2qo6QVpP6jACQA/qOkF6T+ +pQAjAP6jpBmkIwD+o6QZpP6lACEA/qOkGqT+pQAgAP6jpBuk/qUAHwAepP6mAB0A/p+kH6QZAH8Q +AAB/EAAAfwiJAP4YAD0A/btLADwA/On2QQA6APoQ///1PgA5AP44/wH//fE1ADgA/l//Av/97zIA +NwD+hv8D//3qKgA2AP6u/wT//egnADUA/tX/Bf/94iEAMwD9A/j/Bv/93BoAMgD+JP8I//3ZGAAx +AP5L/wn//dITADAA/nP/Cv/9zxEALwD+mv8L//3GDQAuAP7B/wz//cMLAC0A/uj/Df/9uggAKwD+ +EP8P//2wBQAqAP43/xD//awEACkA/l//Ef/9oQIAKAD+hv8S//2dAQAnAP6t/xP//o8AJwD+1P8U +//6NACUA/QP4/xX//n0AJAD+JP8X//53ACMA/kv/F//9/moAIgD+cv8Y//3+ZgAhAP6Z/xn//f1b +ACAA/sD/Gv/9+1IAHwD+6P8b//36TQAdAP4Q/x3//flJABgAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAABIA/qWkEqT9qcrQFNAR +AP6lpBKk/avL0BXQEQD+o6QRpP2uzdAW0BAA/qWkEaT9rM3QF9APAP6ApBGk/anL0BjQDwASpP2o +ytAY0P7ZAA0A/qqkEaT+xtAW0PvV5Pf7AA0A/qOkEKT+vdAV0P3V6PsC+w0A/p2kEKT+stAU0P3U +6fsE+w0AEaT9qczQE9D93/n7BfsMAP6fpA+k/aPF0BLQ/dLr+wf7DAD+o6QPpP6z0BLQ/dn3+wj7 +CwD+gKQPpP2my9AR0P7c+wr7CwD+o6QPpP690BDQ/c/h+wv7CwAQpP6q0BHQ/t/7DPsKAP6qpA+k +/r/QEND+3PsN+woA/qOkDqT9qM/QD9D+2fsO+woAEKT+vdAP0P3T9vsO+wkA/qGkDqT9p8/QD9D+ +7vsP+wkA/qOkDqT+t9AP0P7f+xD7CQAQpP7G0A7Q/dP3+xD7CQAPpP6q0A/Q/un7EfsIAP6mpA6k +/rvQDtD+1vsS+wgAEKT+ytAO0P7p+xL7CAAPpP6o0A7Q/tX7E/sIAA+k/rTQDtD+5PsT+wgAD6T+ +wNAO0P72+xP7BwD+pqQOpP7K0A3Q/tn7FPsHAP6lpA2k/qjQDtD+5PsU+wcAD6T+rtAO0P7y+xT7 +BwAPpP600A3Q/tT7FfsHAP6lpA2k/rzQDdD+2vsV+wcA/qWkDaT+wdAN0P7g+xX7BwAPpP7G0A3Q +/uX7FfsHAA+k/sjQDdD+7vsV+wcAD6T+y9AN0P7z+xX7BwAPpP7M0A3Q/vb7FfsHAA+k/s/QDdD+ ++fsV+wcAD6T+z9AN0P75+xX7BwAPpP7M0A3Q/vb7FfsHAA+k/svQDdD+8/sV+wcAD6T+yNAN0P7t ++xX7BwAPpP7G0A3Q/uX7FfsHAP6lpA2k/sHQDdD+4PsV+wcA/qWkDaT+u9AN0P7a+xX7BwAPpP60 +0A3Q/tP7FfsHAA+k/q7QDtD+8vsU+wcA/qOkDaT+p9AO0P7j+xT7BwD+oqQOpP7L0A3Q/tn7FPsI +AA+k/sDQDtD+9vsT+wgAD6T+tNAO0P7i+xP7CAD+o6QNpP6p0A7Q/tX7E/sIAP6jpA6k/srQDtD+ +6PsS+wgA/qKkDqT+u9AO0P7V+xL7CQAPpP6r0A/Q/uj7EfsJAP6lpA6k/sfQDtD90/f7EPsJAP6l +pA6k/rfQD9D+3vsQ+wkA/qqkDqT9qM/QDtD9z+37D/sKAP6jpA6k/r3QD9D90vf7DvsKAP6lpA6k +/anP0A/Q/tn7DvsKAP6fpA+k/r/QEND+2vsN+wsA/qOkDqT9qc/QEND+4PsM+wsA/qWkD6T+vdAQ +0P3P4PsL+wsA/v+kD6T9pczQEdD+2/sK+ycA/QtKVxRXJgD9D05XFVclAP0VUlcWVyQA/RBSVxdX +IwD9DE9XGFciAP0ISlcXV/1YaQAgAP0CRVcWV/thfaStACAA/jJXFVf9YoatAq0gAP4aVxRX/V6J +rQStHwD9ClFXElf8WHWprQWtHwD+QVcSV/1dj60HrR4A/h9XElf9aaatCK0dAP0FTlcRV/5xrQqt +HQD+MlcRV/54rQutHAD9DVZXEFf+d60MrRwA/jZXEFf+ca0NrRsA/QpVVw9X/mmtDq0bAP4yVw9X +/V6krQ6tGgD9B1RXDlf9WJKtD60aAP4mVw9X/nWtEK0aAP5FVw5X/V6orRCtGQD+DlcPV/6JrRGt +GQD+LlcOV/5jrRKtGQD+SlcOV/6JrRKtGAD+CVcOV/5hrROtGAD+IFcOV/59rROtGAD+NlcNV/1Y +o60TrRgA/k1XDVf+aa0UrRcA/gdXDlf+gK0UrRcA/hRXDlf+na0UrRcA/iFXDVf+Xq0VrRcA/i9X +DVf+a60VrRcA/jxXDVf+dq0VrRcA/kRXDVf+g60VrRcA/khXDVf+k60VrRcA/kxXDVf+m60VrRcA +/lFXDVf+oq0VrRcA/lVXDVf+qa0VrRcA/lVXDVf+qa0VrRcA/lFXDVf+oq0VrRcA/kxXDVf+m60V +rRcA/khXDVf+kq0VrRcA/kRXDVf+g60VrRcA/jxXDVf+dq0VrRcA/i5XDVf+a60VrRcA/iFXDVf+ +Xq0VrRcA/hRXDlf+na0UrRcA/gZXDlf+gK0UrRgA/kxXDVf+aa0UrRgA/jZXDVf9WKOtE60YAP4g +Vw5X/n2tE60YAP4JVw5X/mGtE60ZAP5KVw5X/oitEq0ZAP4uVw5X/mKtEq0ZAP4NVw9X/oetEa0a +AP5FVw5X/V6orRCtGgD+JVcPV/50rRCtGgD9B1RXDlf9WJGtD60bAP4yVw9X/V2krQ6tGwD9ClVX +D1f+aK0OrRwA/jVXEFf+bq0NrRwA/QxWVxBX/netDK0dAP4xVxFX/netC60dAP0ETlcRV/5vrQqt +JwD9BBsfFB8mAP0FHB8VHyUA/QgdHxYfJAD9Bh0fFx8jAP0EHB8YHyIA/QMbHxgf/iUAIAD9ARkf +Fh/7Iy07PgAgAP4SHxUf/SMwPgI+IAD+CR8UH/0hMT4EPh8A/QQdHxMf/So8PgU+HwD+Fx8SH/0h +Mz4HPh4A/gsfEh/9JTs+CD4dAP0CHB8RH/4oPgo+HQD+Eh8RH/4rPgs+HAD+BB8RH/4rPgw+HAD+ +Ex8QH/4oPg0+GwD9BB4fDx/+JT4OPhsA/hIfDx/9ITs+Dj4aAP0DHh8PH/40Pg8+GgD+DR8PH/4q +PhA+GgD+GR8OH/0hPD4QPhkA/gUfDx/+MT4RPhkA/hAfDh/+Iz4SPhkA/hsfDh/+MT4SPhgA/gMf +Dh/+Iz4TPhgA/gsfDh/+LT4TPhgA/hMfDh/+Oj4TPhgA/hsfDR/+JT4UPhcA/gIfDh/+Lj4UPhcA +/gcfDh/+OD4UPhcA/gwfDR/+Ij4VPhcA/hEfDR/+Jj4VPhcA/hUfDR/+Kj4VPhcA/hgfDR/+Lz4V +PhcA/hofDR/+ND4VPhcA/hsfDR/+Nz4VPhcA/h0fDR/+Oj4VPhcA/h4fDR/+PD4VPhcA/h4fDR/+ +PD4VPhcA/h0fDR/+Oj4VPhcA/hsfDR/+Nz4VPhcA/hofDR/+ND4VPhcA/hgfDR/+Lz4VPhcA/hUf +DR/+Kj4VPhcA/hEfDR/+Jj4VPhcA/gwfDR/+IT4VPhcA/gcfDh/+OD4UPhcA/gIfDh/+LT4UPhgA +/hsfDR/+JT4UPhgA/hMfDh/+Oj4TPhgA/gsfDh/+LD4TPhgA/gMfDh/+Iz4TPhkA/hofDh/+MD4S +PhkA/hAfDh/+Iz4SPhkA/gUfDx/+MD4RPhoA/hkfDh/9ITw+ED4aAP4NHw8f/ik+ED4aAP0CHh8P +H/40Pg8+GwD+Eh8PH/0hOz4OPhsA/QMeHw8f/iU+Dj4cAP4THxAf/ic+DT4cAP4EHxEf/io+DD4d +AP4RHxEf/io+Cz4dAP0CHB8RH/4oPgo+EgD9Sv7/Kf8RAP0R6f8q/xEA/pb/K/8QAP0z/P8r/w8A +/QLK/yz/DwD+aP8r//371AANAP0S7v8p//vlsYeAAA0A/oD/KP/94qaAAoANAP0N7f8m//3sooAE +gA0A/nb/Jf/8+76DgAWADAD9COb/JP/975uAB4AMAP5s/yT//dSGgAiACwD9At7/I//+xYAKgAsA +/kD/I//+uYALgAsA/p3/Iv/+uoAMgAoA/Qnw/yH//sWADYAKAP5Z/yH//tSADoAKAP63/yD//e2H +gA6ACQD9E/v/H//9+5iAD4AJAP5T/yD//r6AEIAJAP6U/x///e2EgBCACQD+1f8f//6igBGACAD9 +F/7/Hv/+34ASgAgA/lr/H//+ooASgAgA/pr/Hv/+5YATgAgA/sj/Hv/+sYATgAgA/u//Hf/9+4iA +E4AHAP4X/x7//tSAFIAHAP4+/x7//q2AFIAHAP5l/x7//o2AFIAHAP6M/x3//uuAFYAHAP6s/x3/ +/tCAFYAHAP66/x3//ryAFYAHAP7H/x3//qmAFYAHAP7T/x3//peAFYAHAP7g/x3//o+AFYAHAP7t +/x3//omAFYAHAP75/x3//oOAFYAHAP75/x3//oOAFYAHAP7t/x3//omAFYAHAP7g/x3//o+AFYAH +AP7T/x3//piAFYAHAP7H/x3//qmAFYAHAP66/x3//ryAFYAHAP6s/x3//tCAFYAHAP6M/x3//uyA +FYAHAP5l/x7//o2AFIAHAP49/x7//q6AFIAHAP4W/x7//tSAFIAIAP7u/x3//fuIgBOACAD+yP8e +//6ygBOACAD+mf8e//7lgBOACAD+Wf8f//6jgBKACAD9Fv7/Hv/+4oASgAkA/tX/H//+pIARgAkA +/pP/H//97YSAEIAJAP5S/yD//r+AEIAJAP0S+/8f//38mYAPgAoA/rX/IP/97oeADoAKAP5Y/yH/ +/tWADoAKAP0I7/8h//7KgA2ACwD+nP8i//67gAyACwD+Pv8j//67gAuACwD9Ad3/I//+x4AKgCnQ +/cuqpBKkKtD9zKykEaQr0P3NrqQQpCzQ/c2tpA+kAdDv1Nrg5e7x9vn59vHu5t/Z09Aa0P3Nq6QO +pP3j8vsO+/zy5dnQGND9yqikDaQU+/z24tXQFtD9x6WkDKQX+/3q19AV0P68pAykGfv96NPQFND+ +sqQLpBr7/Pfez9AS0P3NqaQKpBz7/e3T0BLQ/sSkCqQd+/322dAS0P6zpAmkH/v+29AR0P3LpqQI +pCD7/eDP0BDQ/rykCKQh+/7g0BDQ/c+ppAekIvv+3NAQ0P6+pAekI/v+2NAQ0P6ppAakI/v999TQ +D9D+vaQGpCT7/e3P0A7Q/c+npAWkJfv+39AP0P62pAWkJfv9+dPQDtD+x6QFpCb7/ujQD9D+qqQE +pCf7/tXQDtD+uqQEpCf7/ujQDtD+yaQEpCj7/tXQDtD+qKQDpCj7/uPQDtD+s6QDpCj7/vfQDtD+ +v6QDpCn7/tjQDdD+y6QDpCn7/uPQDtD+p6QCpCn7/vLQDtD+rqQCpCr7/tPQDdD+tKQCpCr7/tnQ +DdD+u6QCpCr7/t/QDdD+waQCpCr7/ubQDdD+xqQCpCr7/u7QDdD+x6QCpCr7/vHQDdD+y6QCpCr7 +/vTQDdD+zKQCpCr7/vnQDdD+z6QCpCr7/vnQDdD+z6QCpCr7/vTQDdD+zKQCpCr7/vHQDdD+y6QC +pCr7/u3QDdD+x6QCpCr7/uTQDdD+xqQCpCr7/t/QDdD+waQCpCr7/tnQDdD+u6QCpCr7/tLQDdD+ +tKQCpCn7/vHQDtD+raQCpCn7/uPQDtD+p6QCpCn7/tjQDdD+y6QDpCj7/fbP0A3Q/r+kA6Qo+/7h +0A7Q/rOkA6Qo+/7V0A7Q/qikA6Qn+/7o0A7Q/sikBKQn+/7V0A7Q/rqkBKQm+/7o0A/Q/qqkBKQl ++/3509AO0P7GpAWkJfv+3tAP0P62pAWkJPv+7dAP0P3Op6QFpCP7/fbS0A/Q/r2kBqQi+/352NAP +0P3PqKQGpCL7/tvQEND+vqQHpCH7/uDQEdD+qaQHpCD7/eDP0BDQ/rykCKQe+/3529AR0P3MpqQI +pClX/UwNABIAKlf9TxEAEQArV/1SFQAQACxX/VIRAA8AAVfvXmt2g5OZo6mpo5mTg3VqXlcaV/1Q +DQAOAP2Ana0OrfudgGlYVxdX/UsIAA0AFK38o31hVxZX/UQCAAwAF639jGRXFVf+MAAMABmt/Yde +VxRX/hsACwAarfyodFhXElf9UQsACgAcrf2RXlcSV/5AAAoAHa39pGhXElf+HgAJAB+t/m9XEVf9 +TwUACAAgrf53VxFX/jAACAAhrf53VxBX/VYMAAcAIq3+cFcQV/41AAcAI63+aFcPV/1VCwAGACOt +/aZeVw9X/jEABgAkrf2RWFcOV/1UBgAFACWt/nRXD1f+JQAFACWt/aleVw5X/kQABQAmrf6HVw9X +/g0ABAAnrf5jVw5X/iwABAAnrf6HVw5X/kkABAAorf5hVw5X/gkAAwAorf58Vw5X/h8AAwAorf2k +WFcNV/41AAMAKa3+aFcNV/5MAAMAKa3+flcOV/4GAAIAKa3+nFcOV/4TAAIAKq3+XlcNV/4gAAIA +Kq3+alcNV/4uAAIAKq3+dVcNV/47AAIAKq3+g1cNV/5DAAIAKq3+klcNV/5HAAIAKq3+mlcNV/5M +AAIAKq3+oVcNV/5QAAIAKq3+qVcNV/5VAAIAKq3+qVcNV/5VAAIAKq3+oVcNV/5QAAIAKq3+mlcN +V/5MAAIAKq3+kVcNV/5HAAIAKq3+glcNV/5DAAIAKq3+dVcNV/47AAIAKq3+alcNV/4tAAIAKq3+ +XVcNV/4gAAIAKa3+m1cOV/4TAAIAKa3+flcOV/4GAAIAKa3+aFcNV/5MAAMAKK39o1hXDVf+NQAD +ACit/ntXDlf+HwADACit/mBXDlf+CQADACet/oZXDlf+SQAEACet/mNXDlf+LAAEACat/oZXD1f+ +DQAEACWt/ahdVw5X/kQABQAlrf5zVw9X/iQABQAkrf2QWFcOV/1UBgAFACOt/aNdVw9X/jEABgAi +rf2sZ1cPV/1VCQAGACKt/m9XEFf+NAAHACGt/nZXEFf9VQsABwAgrf52VxFX/i8ACAAerf2sb1cR +V/1OBQAIACkf/RsEABIAKh/9HAYAEQArH/0dBwAQACwf/R0GAA8AAR/vIiYqLzQ3Ojw8Ojc0Lyom +IR8aH/0cBQAOAP0tOD4OPvw4LSUfGB/9GwMADQAUPvw6LCMfFh/9GAEADAAXPv0yJB8VH/4RAAwA +GT79MCEfFB/+CgALABo+/TwpHxMf/R0EAAoAHD79NCEfEh/+FwAKAB0+/TslHxIf/gsACQAfPv4o +HxEf/RwCAAgAID7+Kh8RH/4RAAgAIT7+Kh8RH/4EAAcAIj7+KB8QH/4TAAcAIz7+JR8PH/0eBAAG +ACM+/TshHw8f/hIABgAkPv40Hw8f/R4CAAUAJT7+KR8PH/4NAAUAJT79PCEfDh/+GAAFACY+/jAf +Dx/+BQAEACc+/iMfDh/+EAAEACc+/jAfDh/+GgAEACg+/iMfDh/+AwADACg+/iwfDh/+CwADACg+ +/jsfDh/+EwADACk+/iUfDR/+GwADACk+/i0fDh/+AgACACk+/jgfDh/+BwACACo+/iEfDR/+DAAC +ACo+/iYfDR/+EAACACo+/iofDR/+FQACACo+/i8fDR/+GAACACo+/jQfDR/+GQACACo+/jcfDR/+ +GwACACo+/jkfDR/+HQACACo+/jwfDR/+HgACACo+/jwfDR/+HgACACo+/jkfDR/+HQACACo+/jcf +DR/+GwACACo+/jQfDR/+GQACACo+/i4fDR/+GAACACo+/iofDR/+FQACACo+/iYfDR/+EAACACo+ +/iEfDR/+CwACACk+/jcfDh/+BwACACk+/i0fDh/+AgACACk+/iUfDR/+GwADACg+/jofDh/+EwAD +ACg+/iwfDh/+CwADACg+/iIfDh/+AwADACc+/jAfDh/+GgAEACc+/iMfDh/+EAAEACY+/jAfDx/+ +BQAEACU+/TwhHw4f/hgABQAlPv4pHw8f/g0ABQAkPv4zHw8f/R4CAAUAIz79OiEfDx/+EQAGACI+ +/T0lHw8f/R4DAAYAIj7+KB8QH/4SAAcAIT7+Kh8QH/0eBAAHACA+/iofER/+EQAIAB4+/T0oHxEf +/RwCAAgAPv/+/v9/AMH/7+vQvKmXkYiDg4iRl6q90ez/LP/9ro2ADoD7ja7U+/8o/xSA/Iiy5f8m +/xeA/Z7d/yT/GYD9pO3/Iv8agPyEv/z/IP8cgP2Z7f8f/x2A/YfV/x7/H4D+x/8d/yCA/rv/HP8h +gP67/xv/IoD+xv8a/yOA/tb/Gf8jgP2G7P8Y/ySA/Zn8/xf/JYD+v/8X/yWA/YPs/xb/JoD+pP8W +/yeA/uH/Ff8ngP6k/xX/KID+5f8U/yiA/rP/FP8ogP2H+/8T/ymA/tb/E/8pgP6w/xP/KYD+jv8T +/yqA/u3/Ev8qgP7R/xL/KoD+vv8S/yqA/qr/Ev8qgP6Y/xL/KoD+kP8S/yqA/or/Ev8qgP6D/xL/ +KoD+g/8S/yqA/or/Ev8qgP6Q/xL/KoD+mf8S/yqA/qv/Ev8qgP6+/xL/KoD+0f8S/yqA/u7/Ev8p +gP6P/xP/KYD+sP8T/ymA/tb/E/8ogP2I/P8T/yiA/rT/FP8ogP7m/xT/J4D+pf8V/yeA/uH/Ff8m +gP6l/xb/JYD9hO7/Fv8lgP7B/xf/JID9mvz/F/8jgP2I7/8Y/yKA/YHX/xn/IoD+x/8a/yGA/rz/ +G/8ggP68/xz/HoD9gcj/Hf/+owA9AP2kqgA8AAGkPQABpP6lADsAAqT+gAA6AAOkOwADpP6fADkA +A6T+owA5AASk/qoAOAAEpP6lADgABaT+qgA3AAakOAAGpP7/ADYABqT+owA2AAekNwAHpP6qADUA +B6T+pQA1AAek/qMANQAIpP6fADQACKT+pQA0AAik/qUANAAJpDUACaT+pgAzAAqkNAAKpDQACqQ0 +AAqkNAAKpP6qADIAC6QzAAukMwALpDMAC6QzAAukMwALpDMAC6QzAAukMwALpDMACqT+owAyAAqk +/qMAMgALpDMAC6QzAAqk/qMAMgALpDMAC6QzAAukMwALpDMAC6QzAAukMwAKpP6mADIACqQ0AAqk +NAAKpDQACqQ0AAmk/qYAMwAJpDUACaQ1AAik/qMANAAIpP6fADQACKQ2AAikNgAHpP6qADUAB6Q3 +AAak/qYANgAFpP2j/wA2AH8QAAB/EAAA/k4APQD95w8APAD9/5IAPAD8//02ADsAAf/9yAIAOgAC +//5lADoAAv/97BAAOQAD//59ADkAA//97AwAOAAE//50ADgABP/95wkANwAF//5qADcABf/93QEA +NgAG//49ADYABv/+mgA2AAb//fAJADUAB//+VQA1AAf//rIANQAH//36EAA0AAj//k8ANAAI//6Q +ADQACP/+0gA0AAj//f4XADMACf/+VwAzAAn//pgAMwAJ//7FADMACf/+7QAzAAr//hUAMgAK//47 +ADIACv/+YgAyAAr//okAMgAK//6qADIACv/+twAyAAr//sQAMgAK//7SADIACv/+3gAyAAr//usA +MgAK//74ADIACv/++AAyAAr//usAMgAK//7eADIACv/+0QAyAAr//sQAMgAK//63ADIACv/+qQAy +AAr//okAMgAK//5iADIACv/+OwAyAAr//hQAMgAJ//7tADMACf/+xQAzAAn//pcAMwAJ//5XADMA +CP/9/hcAMwAI//7SADQACP/+jwA0AAj//k4ANAAH//36EAA0AAf//rAANQAH//5UADUABv/98AkA +NQAG//6YADYABv/+PAA2AAX//dwBADYAfwoAAH8KAAB/CgAAfwoAAH8QAAB/EAAAfxAAAH8QAAAv +APyqrdj7C/swAPyqstr7CvsxAPulrdb5+wj7MgD7kqzQ9/sH+zMA+7+ozfH7Bvs1APymu+T7Bfs2 +APulstb5+wP7NwD7n6vH6vsC+zkA+aay1PT7+wA5APqiqb3g9wA7APykrL8APQD+pgB/DP8AMAD9 +E2qtC60xAP0ZbK0KrTIA/BNiq60IrTMA/BBXpq0HrTQA/AxOmq0GrTUA/AIvgK0FrTcA/B1nqa0D +rTgA/BBIjK0CrToA+h1eoK2tADoA+wkzeKcAPAD9EDUAfw0/ADAA/QYlPgs+MQD9CSY+Cj4yAPwG +Iz0+CD4zAPwGHzs+Bz40APwFGzc+Bj42AP0RLj4FPjcA/AolPD4DPjgA/AYaMj4CPjoA+gohOT4+ +ADoA+wQSKzsAPAD9BhMAfw0/AC8A/AnIxoALgDAA/BLXwoAKgDEA+xHIzIKACIAyAPsHstiGgAeA +MwD7BKTfkIAGgDUA/Gfwq4AFgDYA+zPhyYOAA4A3APsQseSfgAKAOQD5TeXQi4CAADkA+guM8LeF +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 +Pv00IR8SH/4WAAoAGj79PCkfEx/9HQQACgAZPv0wIR8UH/4KAAsAFz79MiQfFR/+EQAMABQ+/Dos +Ih8WH/0YAQAMAP0tNz4OPvw3LSUfGB/9GwMADQABH+8hJiovNDc6PDw6NzQvKiYhHxof/RwEAA4A +LB/9HQYADwArH/0dBwAQACof/RwGABEAKR/9GwQAEgAoH/0YAwATACcf/RIBABQAJR/9HQoAFgAk +H/0WBAAXACIf/RwLABkAIB/8HhEBABoAHh/8HhQEABwAHB/8HhMDAB4AGh/8GA0CACAAFx/8GhAE +ACMAEx/7HBUMBAAmAOsCBwwQFRgZGx0eHh0bGRgVEAwHAgB/CisAHYD9iNf/Hv8cgP2Z7v8f/xqA +/ITA/P8g/xmA/abu/yL/F4D9nt7/JP8UgPyIs+b/Jv/9sI+ADoD7j7DW/P8q/+/t0b2qmZGJg4OJ +kZmqvtHt/38BLP/+/f89//6E/zz//cEE/zv//OobAP86//v9RwAA/zr//nIAAQA6//6HAAIAOf/9 +owEAAgA4//20BAADADf//cMKAAQANv/9ugwABQA1//2iBQAGADT//ZECAAcAMv/9/XEACQAx//3r +SQAKADD//cEbAAsALv/8/pEFAAwALf/95kYADgAr//z9mQ8ADwAq//3MNQARACj//O5vAgASACb/ +/OuAEQAUACT//OmADgAWACL//NxpBwAYAB//+/CiRAEAGgAc//v8u14KAB0AGf/73ZlVEQAgABT/ ++uzGllYWACQA6jxjiqi5xdHd7fn57d3RxbmoiWM7FAB/BioABaT+pQA3AAWk/qoANwAEpP6jADgA +BKT+ogA4AAOk/qMAOQADpP6qADkAA6Q7AAKk/v8AOgABpP6lADsA/aSlADwAAaQ9AP6lAH8NPgB/ +EAAAfxAAAAX//mkANwAE//3nCQA3AAT//nIAOAAD//3rCwA4AAP//noAOQAC//3rDwA5AAL//mIA +OgAB//3FAQA6APz//DAAOwD9/5AAPAD95g4APAD+RwB/DT4AfwoAAH8KAAB/CgAAfwoAAH8QAAB/ +EAAAfxAAAH8QAAB/Df4A/aqjADoA+6qjpLEAOQD6pqO0ytUANwD4o6SqxNXq+wA1APb/o6a6z+P5 ++/sANAD5paSuytr2+wL7NAD6oqO30Ob7BPszAPqlp8LV8/sF+zIA+qSpyNz5+wb7fw4+AP0BGgA7 +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 +paQWpCMA/aqjpBikIQD9oqOkGqQfAP2do6QcpB0A/aGlpB6kGwD9oqOkIKQZAP6ipCOkFwD8oqSj +pCOkFQD+oqQnpBMA/qWkKKT+pgAQAP6mpCik/KjK8gAOAP6mpCik+qnK8/z8AAsA/f+mpCik/KvQ +9vwC/AoA/YCmpCik/KzQ9/wE/AgA/ZmjpCik/K/V+fwG/AYA/aqjpCik/LDZ+vwI/AQA/aKjpCik +/LPb+/wK/AIA/Z2jpCik/bTf/A38/ACmo6QopP244vwP/P6jpCik/bjl/BH8KKT9vur8E/wlpPyl +we38FfwjpPymxO78F/whpPymx/L8GPz+6KQepPyoyvP8GPz96LukHaT8qc32/Bj8/eK2pB2k/KvQ +9vwX/Pz74rWkHaT8rtb5/Bf8/PrbsqQdpPyw1fn8F/z8+tuxpB2k/LLb+/wX/Pz41a6kHaT8tNv7 +/Bf8/PjVraQdpP244vwY/Pz10KqkHaT9uOL8GPz89dCppB2k/b7p/Bj8/PTNqKQcpPylvur8GPz8 +8MempBKk/rKkB6T8psXu/Bj8/PDHpaQSpPyz2/ukB6T9s+X8Gfz92q+kEqT7ruj8/KQJpP245fwY +/Pz62K+kEqT9vemkC6T9uOL8GPz8+tuwpCGk/bji/Bj8/PnXrqQhpP244vwY/Pz51q6kIaT9ueb8 +GPz8+tmupCGk/bji/Bj8/PrZrqQhpP245fwY/Pz62a2kIaT9uuf8GPz8+dmtpCGk/bjl/Bj8/PnZ +raQhpP245fwY/Pz41a2kIaT9uOX8GPz8+NWtpCGk/bjl/Bj8/fjVrH8GPwD+BAA7APwIS5wAOQD6 +CUufr68ANwD8DlejrwKvNgD8D1elrwSvNAD8FWOprwavMgD8F2msrwivMAD8H26trwqvLgD8InWu +rwyvLAD9KHqvD68qAP0ogq8RrycA/AE0i68TryUA/AE6kq8VryMA/ANAla8XryEA/AVGm68Yr/6G +AB4A/AhLna8Yr/2GLQAdAPwKUqOvF6/8rnolAB0A/A5XpK8Xr/yueiIAHQD8FGSprxev/KxuGwAd +APwXY6qvF6/8q24ZAB0A/B5urK8Xr/ynYxMAHQD8IG6trxev/KdjEQAdAPwoeq6vF6/8olYLAB0A +/Sh6rxiv/KFXCgAcAPwBNYmvGK/8n1IHABwA/AE0i68Yr/yYRQQAEgD+GwAHAPwDQpSvGK/8l0YD +ABIA/B5urQAHAP0fgK8Zr/1rFQASAPsTh6+vAAkA/SmBrxiv/KtoFQASAP0xiQALAP0oeq8Yr/ys +bhcAIQD9KHqvGK/8qmYTACEA/Sh6rxiv/KplEwAhAP0rg68Yr/yraRMAIQD9KHqvGK/8q2kTACEA +/SiBrxiv/KtpEgAhAP0shK8Yr/yqaRIAIQD9KIGvGK/8qmkRACEA/SiBrxiv/KhjEQAhAP0ogq8Y +r/ynYxAAIQD9KIKvGK/9p2MQfwY/AP4BADsA/AMbNwA5APoDGzg+PgA3APwFHzo+Aj42APwFHzs+ +BD40APwIIzw+Bj4yAPwIJT0+CD4wAPwLJz0+Cj4uAP0MKT4NPiwA/Q4rPg8+KgD9Di4+ET4oAP0S +MT4TPiYA/RU0PhU+IwD8ARc1Phc+IQD8Ahk3Phg+/i8AHgD8Axs4Phg+/S8QAB0A/AMdOj4YPv0r +DQAdAPwFHzo+GD79KwwAHQD8ByM8Phc+/D0nCQAdAPwIIzw+Fz78PScJAB0A/AonPT4XPvw7IwcA +HQD8Cyc9Phc+/DsjBgAdAP0OKz4YPvw5HgQAHQD9Dis+GD78OR8EAB0A/RMxPhg+/DgdAgAdAP0S +MT4YPvw2GQEAEgD+CgAHAPwBFzU+GD78NRkBABIA/AsnPQAHAP0LLT4ZPv0mCAASAPsHMD4+AAkA +/Q8uPhg+/D0lBwASAP0SMQALAP0OKz4YPvw9JwgAIQD9Dis+GD78PCQHACEA/Q4rPhg+/DwkBwAh +AP0PLj4YPvw9JQcAIQD9Dis+GD78PSUHACEA/Q4uPhg+/D0lBgAhAP0QLz4YPvw8JQYAIQD9Di4+ +GD78PCUGACEA/Q4uPhg+/DwjBgAhAP0OLj4YPvw7IwYAIQD9Di4+GD79OyMGOQD9Df7/Av85AP41 +/wP/OQD+XP8D/zkA/oP/A/85AP6q/wP/OQD+0v8D/zgA/Rb2/wP/NgD8F4j1/wT/NAD8IJH3/wb/ +MgD8I5n7/wj/MAD8LqL8/wr/LgD8OrX+/wz/LAD9PLP/D/8pAPwBTMf/Ef8nAPwCTcv/E/8lAPwC +Vdb/Ff8jAPwGXtr/F/8hAPwLb+P/Gf8fAPwNb+b/G/8dAPwTgu//Hf8bAPwWgPD/H/8ZAPwekfb/ +If8XAPwhkfj/I/8VAPwsovv/Jf8TAPwwov3/J/8RAPw8s/7/Kf8PAP08vP8s/wwA/AFNyP8u/woA +/AJNzf8w/wgA/AVe2P8y/wYA/AZe2/80/wQA/Atv5P82/wIA/A1v6P84//sAFIDv/zr//Yj1/38H +ff8NpP7v/An8/sGkB6T+1PwB/P30sqQKpP2z9fwG/Ayk/ab7/An8/rOkB6T+4/wC/P3ysKQKpP20 +9vwF/Ayk/rP8Cfz9+6ekB6T+8PwD/P3wraQKpP239/wE/Ayk/sD8Cfz+8KQHpP2m+/wE/P3uraQK +pP24+PwD/Ayk/s38Cfz+46QHpP6z/Ab8/eyqpAqk/b36/AL8DKT+2/wJ/P7VpAek/sD8B/z97Kqk +CqT9wPr8AfwMpP7o/An8/sikB6T+zvwI/P3oqKQKpPvB+/z8pAuk/vX8Cfz+u6QHpP7b/An8/eWn +pAqk/MX7/KQKpP6r/Ar8/q2kB6T+6fwK/P3kpqQKpP3J/KQKpP64/An8/vikCKT+9vwL/P3fpaQK +pP7NpAqk/sf8Cfz+66QHpP6s/A38/dylpBak/tP8Cfz+3aQHpP64/A78/tmkFqT+4vwJ/P7QpAek +/sf8D/z+1qQVpP7u/An8/sKkB6T+1PwQ/P7RpBOk/ab6/An8/rSkB6T+4vwR/P7PpBKk/rL8Cvz+ +qKQHpP7u/BL8/sukEaT+wPwJ/P7xpAek/ab6/BP8/sikEKT+zfwJ/P7kpAek/rL8FPz9+8WkD6T+ +2vwJ/P7WpAek/r/8Ffz9+8GkDaT9uPH8Cfz+yaQHpP7N/Bb8/fq/pAqk/bji/Av8/rukB6T+2vwX +/P36vaQHpP2+6fwN/P6upAek/uj8GPz9+LqkA6T8pb7r/A78/vikCKT+9fwZ/Pj4uKSkpsTu/BD8 +/uykB6T+qvwb/Pv3tcTv/BL8/t6kB6T+uPwc/P73/BT8/tCkB6T+xvwz/P7DpAek/tP8Mvz9+rOk +B6T+4Pww/Pz627CkCKT+7vwu/Pz41a6kCaT9pfr8LPz899GrpAuk/rH8K/z89c+ppA2k/r78Kfz8 +9NCppA+k/sz8J/z88cqnpBGk/tn8Jfz878ampBKk/bPu/CP8/O3EpaQSpPyz2/v8Ivz87cSlpBKk +/bTf/CP8/ezBpBOk/bji/CT8/r6kE6T9uOX8JvwTpP2+6fwo/BCk/KW+6/wq/A6k/KbE7vws/Ayk +/KbE7/wu/Aqk/KbH8vww/Aik/KjK8/wy/Aak/KnN9vw0/ASk/KzT9/w2/AKk/KzT+Pw4/Pukr9X5 +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 +pP6mACoAEqQsABGk/qIAKwARpC0AEaQtABCkLgAPpP6lAC0ADqT+owAuAA6k/pIALgANpP6jAC8A +DaT+qgAvAAyk/qMAMAAMpDIAC6T+owAxAAukMwALpDMACaT+pQAzAAmk/qcAMwAJpDUACKT+qgA0 +AAek/qMANQAHpP6ZADUABqT+owA2AAak/r8ANgAFpP6lADcABaT+/wA3AASk/qYAOAADpP6jADkA +A6T+pgA5AASkOgAEpP6iADgABaQ5AAWk/qEANwAGpDgABqT+owA2AAekNwAHpP6iADUACKQ2AAik +/qMANAAJpDUACa/+ZgAzAAiv/acQADMACK/+WQA0AAev/aEKADQAB6/+TAA1AAav/ZkFADUABq/+ +PwA2AAWv/Y8BADYABa/+MQA3AASv/oQAOAADr/2uJwA4AAOv/nYAOQACr/2tHAA5AAKv/mkAOgAB +r/2pEwA6AAGv/lsAOwD8r6MMADsA/a9OADwA/ZoFADwA/kEAPQD+AgB/Cv4ACT7+JAAzAAg+/TsG +ADMACD7+HwA0AAc+/TkDADQABz7+GwA1AAY+/TYCADUABj7+FgA2AAU+/jMANwAFPv4SADcABD7+ +LwA4AAQ+/g4AOAADPv4qADkAAj79PQoAOQACPv4lADoAAT79PAcAOgABPv4gADsA/D46BAA7AP0+ +HAA8AP02AgA8AP4XAD0A/gEAfwr+AB7//fojAB0AHv/+kQAeAB3//fMXAB4AHf/+fwAfABz//ekN +AB8AHP/+awAgABv//dwGACAAG//+WAAhABr//dACACEAGv/+RQAiABn//r8AIwAY//3+NAAjABj/ +/qgAJAAX//36JQAkABf//pQAJQAW//30GAAlABb//oEAJgAV//3rDgAmABX//m4AJwAU//3fBwAn +ABT//lsAKAAT//3RAgAoABP//kgAKQAS//7BACoAEf/9/jkAKgAR//6rACsAEP/9/CkAKwAQ//6X +ACwAD//99hwALAAP//6EAC0ADv/97hEALQAO//5yAC4ADf/94AcALgAN//5eAC8ADP/90gMALwAM +//5LADAAC//+wgAxAAv//joAMQAK//6uADIACf/9/CoAMgAJ//6bADMACP/99x0AMwAI//6JADQA +B//97xIANAAH//51ADUABv/95AoANQAG//5hADYABf/91wQANgAF//5PADcABP/9yAEANwAE//48 +ADgAA//+sgA5AAP//j8AOQAD//6UADkAA//9+iEAOAAE//6dADgABP/9+yYANwAF//6mADcABf/9 +/S8ANgAG//6wADYAB//+NwA1AAf//rgANQAI//5AADQACP/+xAA0AH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfw0+AP2qpAA6APumo6SkADgA/aajpAKkNwD9maOkBKQ2AP6lpAakNAD+ +oaQIpDMA/qakCaQxAP2/o6QKpDAA/qKkDKQvAP6mpAuk/am9AC0A/qWkCqT7pbvP0AAsAP6lpAqk +/bHL0AHQfw9+AP0LMQA6APsELlZXADkA/RpNVwFXfw9+AP0EEgA6APsBER8fADkA/QkcHwEffw0+ +AP0PdgA6APsUgO7/ADgA/BeA8f8B/zcA/AVv8/8D/zYA/UHW/wX/NAD9G6r/B/8zAP1N7f8I/zEA +/ASA/f8J/zAA/R7F/wv/LwD9P+7/DP8uAP1P9v8N/y0A/WD7/w7/fwwLAPmqpqOlpKWkCKT5paOk +o6aqACQA+6ekpaWkFKT7paSlowAcAPyZpqOkH6T9o5kAFgD9pqOkJKT9o6YAEwArpP2jqgAQAC6k +/p8ADgAwpP6hAAwAMaT9o5IACgALpOunrrS7wcbIys3Pz83KyMbCu7Sup6QSpP6lAAkAB6T7qLK+ +y9AS0PvMwbWppA+k/aOnAAcABKT8qrvK0BrQ/Mm7q6QPpAcAAaT8qLfG0CDQ/Ma3qKQNpP6qAAQA +/Km9ztAk0PzPvqikDKT+pQADAP7O0CnQ/cCqpAuk/qMAAgAs0PzPvaakCaT9o6QAAQAu0PzMtKOk +CaT+pQAAAH8ODADrBhQhLjxESEtRVVVRS0hEOy4hFAYAJgD7CB00TFcSV/tOOiMLAB8A/A4uSlca +V/xKLQ4AGQD8ByZFVyBX/EUlBwAUAPwKMVRXJFf8VTQKABIA/lRXKFf8VTcNABAALFf8VjEFAA4A +Llf9TiAADQB/DgwA6wIHDBEVGBobHR4eHRsaGBURDAcCACYA+wMKExsfEh/7HBUMBAAfAPwFEBsf +Gh/8GhAFABkA/AINGR8gH/wYDQIAFAD8BBIeHyQf/B4TBAASAP4eHygf/B4UBQAQAC0f/RICAA4A +Lh/9HAsADQB/DAsA6QYoUHedusfS3e75+e7d0sa6nHZQKAYAJAD6Glye2fv/Ev/6+9mdWxkAHAD7 +BU2W1/8c//vXlUsFABYA/CuI6P8i//ztmTwAEwD+1v8o//zVdQ8AEAAs//zykRgADgAu//z0kRsA +DAAw//z2gAcACgAy//3bRwAJADT//acaAAcANf/97EwABgA2//z+kQYABAA4//3KIgADADn//e09 +AAIAOv/9+FQAAQA7//38ZgAAAH8QAAB/EAAAfxAAAH8QAAB/CgAAfwoAAH8KAAB/CgAAfxAAAH8Q +AAB/EAAAfxAAADAA+v+jqszf+wj7LwD6/6WuzOT7CfsvAPukrs/o+wr7LgD7o6vN6PsL+y0A+6Sp +y+P7DPssAPuip8ng+w37KwD7paLB2/sO+yoA+v+kt9X5+w77KgD7pK3R8fsP+ykA+6Omyuf7EPsp +APykvNr7EfsoAPukq9D2+xH7JwD7maPE4/sS+ycA+6O01fn7EvsmAPudpMrq+xP7JgD8pbPV+xT7 +JQD7n6PI6fsU+yUA/KSz1fsV+yUA/KTE5vsV+yQA+6ap0Pn7FfskAPykudr7FvsjAPuApMnr+xb7 +IwD8o63T+xf7IwD8pLnc+xf7IwD8pMTo+xf7IgD7qqTP9vsX+yIA/KSv1PsY+yIA/KW53fsY+yIA +/KTB5PsY+yIA/KPI6/sY+yEA+4Cjzvb7GPshAPylqNL7GfshAPymr9X7GfshAPyjsdf7GfshAPyk +s9n7GfshAPyjttv7GfshAPyluNz7GfshAPylud77GfshAPykuNz7GfshAPyjttv7GfshAPyks9n7 +GfshAPymsdf7GfshAPymr9X7GfshAPylqdL7GfshAPuAo872+xj7IgD8o8jr+xj7IgD8pMHk+xj7 +IgD8pLnc+xj7IgD8o6/V+xj7IgD7maTP9vsX+yMA/KTF6PsX+yMA/KW53PsX+yMA/KWs0/sX+yMA ++4Ckyev7FvskAPykudz7FvskAPukqNH5+xX7JQD8o8Tm+xX7JQD8pLLV+xX7JQD7n6PI6fsU+yYA +/KOz1PsU+yYA+6qkyer7E/snAPujs9X5+xL7JwD7maTF4/sS+ygA+6Sr0fT7EfsyAPwOTXetCK0x +APwSUYGtCa0wAPwUVoitCq0vAPwPUoetC60uAPwKTX+tDK0tAPwGSXmtDa0tAP08b60OrSwA/Cdh +qa0OrSsA/BJYmq0PrSoA/ARMhK0QrSoA/TBsrRGtKQD8D1ihrRGtKQD9QXytEq0oAPwgYaytEq0n +APwBSoytE60nAP0fY60UrScA/UiJrRStJgD9HGGtFa0mAP1Bg60VrSUA/ApZqa0VrSUA/SpurRat +JQD9SY+tFq0kAP0SXq0XrSQA/SpxrRetJAD9QIatF60jAPwCVaStF60jAP0WYq0YrSMA/StyrRit +IwD9OYCtGK0jAP1GkK0YrSMA/VOkrRitIgD9ClytGa0iAP0VY60ZrSIA/RpmrRmtIgD9H2qtGa0i +AP0jba0ZrSIA/SdwrRmtIgD9KnOtGa0iAP0ncK0ZrSIA/SNtrRmtIgD9H2qtGa0iAP0aZq0ZrSIA +/RVjrRmtIgD9ClutGa0jAP1TpK0YrSMA/UaQrRitIwD9OYCtGK0jAP0rca0YrSMA/RZirRitIwD8 +AlWkrRetJAD9QIatF60kAP0qcK0XrSQA/RFerRetJQD9SI6tFq0lAP0qbq0WrSUA/AlZqa0VrSYA +/UCDrRWtJgD9G2GtFa0nAP1IiK0UrScA/R5irRStJwD8AUqMrROtKAD8H2GsrRKtKQD9QXutEq0p +APwOWKCtEa0yAPwFGys+CD4xAPwHHS4+CT4wAPwHHzA+Cj4vAPwFHTA+Cz4uAPwDGy0+DD4tAPwC +Gis+DT4tAP0VKD4OPiwA/A4jPD4OPisA/AYfNz4PPioA/AIbLz4QPioA/REmPhE+KQD8BR85PhE+ +KQD9Fyw+Ej4oAPwLIz0+Ej4oAP0bMj4TPicA/QsjPhQ+JwD9GjE+FD4mAP0KIz4VPiYA/RcvPhU+ +JQD8BCA8PhU+JQD9Dyc+Fj4lAP0aMz4WPiQA/QYhPhc+JAD9Dyg+Fz4kAP0XMD4XPiMA/AEeOz4X +PiMA/QgjPhg+IwD9Dyk+GD4jAP0ULj4YPiMA/RkzPhg+IwD9Hjs+GD4iAP0EIT4ZPiIA/QgjPhk+ +IgD9CSQ+GT4iAP0LJj4ZPiIA/Q0nPhk+IgD9Dig+GT4iAP0PKT4ZPiIA/Q4oPhk+IgD9DSc+GT4i +AP0LJj4ZPiIA/QkkPhk+IgD9CCM+GT4iAP0DIT4ZPiMA/R47Phg+IwD9GTM+GD4jAP0ULj4YPiMA +/Q8oPhg+IwD9CCM+GD4jAPwBHjs+Fz4kAP0XMD4XPiQA/Q8oPhc+JAD9BiE+Fz4lAP0aMz4WPiUA +/Q8nPhY+JQD8AyA8PhU+JgD9Fy8+FT4mAP0KIz4VPicA/RowPhQ+JwD9CyM+FD4oAP0aMj4TPigA +/AsjPT4SPikA/RcsPhI+KQD8BR85PhE+MAD6AYD//rqACIAvAPoBm//8rIAJgC8A+4n/+aOACoAu +APts//ykgAuALQD7Wv3+r4AMgCwA+zf6/7eADYArAPsR3//HgA6AKgD6Aa7/5IOADoAqAPtw//eQ +gA+AKQD7JPf+qIAQgCkA/Lf/zoARgCgA+1T/9oqAEYAnAPsK4P+zgBKAJwD7gP/kgYASgCYA+w3t +/56AE4AmAPx3/+CAFIAlAPsI5v+igBSAJQD8a//kgBWAJQD8z/+qgBWAJAD7K//2g4AVgCQA/Ir/ +yoAWgCMA+wLj/5uAFoAjAPxA/+yAF4AjAPyD/8WAF4AjAPzF/6WAF4AiAPsM+v2HgBeAIgD8Sf/j +gBiAIgD8gv/DgBiAIgD8qv+tgBiAIgD80f+agBiAIQD7AvX/h4AYgCEA/B//8oAZgCEA/D//4YAZ +gCEA/E7/2YAZgCEA/Fr/0oAZgCEA/Gf/zIAZgCEA/HT/xoAZgCEA/Hz/wYAZgCEA/HP/xoAZgCEA +/Gf/zIAZgCEA/Fr/0oAZgCEA/E3/2YAZgCEA/D//4YAZgCEA/B//84AZgCEA+wL1/4eAGIAiAPzR +/5qAGIAiAPyq/62AGIAiAPyB/8SAGIAiAPxI/+OAGIAiAPsK+f2HgBeAIwD8xP+lgBeAIwD8gv/G +gBeAIwD8Pv/tgBeAIwD7AuP/nIAWgCQA/In/yoAWgCQA+yr/9oOAFYAlAPzO/6qAFYAlAPxq/+WA +FYAlAPsI5f+jgBSAJgD8df/jgBSAJgD7DOz/n4ATgCcA+33/5YGAEoAnAPsK3/+0gBKAKAD7Uf/2 +i4ARgDT7+uLLq6T/AAQANfv65M6upP8AAwA2+/vo0K6kAAMAN/v76c2qowACADj7++TMqaQAAQA5 ++/nhyaeiAAD7Ofv628OjnwD7Ofv6+dS3o//7Ovv78dCuo/s7+/zoyqb7PPv92bz7PPv99ND7Pfv+ +4ft/DH77/uL7PPv+9NE0rfx6Tg8ABgA1rfyAUhQABQA2rfyHVRMABAA3rfyKUg4AAwA4rfyBTgsA +AgA5rfx5SAUAAQA6rf1uPQABADqt+qtiJQAArTqt+5tYEwCtO638hksErTyt/WsvrTyt/aBZrT2t +/nutPa3+rK1/C/6t/qytPa3+eq08rf6gWDQ+/CscBQAGADU+/C4dBwAFADY+/DAfBwAEADc+/DEd +BQADADg+/C4cBAACADk+/CsaAgABADo+/ScWAAEAOj76PSMNAAA+Oj77Nx8HAD47PvwwGwE+PD79 +JhE+PD79OSA+PT7+LD49Pv49Pn8L/j7+PT49Pv4rPjw+/jkfNID6tv7/kQEABAA1gPqt/P+YAQAD +ADaA+6T5/4YAAwA3gPuh+/9yAAIAOID7rP7+XwABADmA+bj/+DQAAIA5gPrJ/90QAIA5gPqC4/+y +AYA6gPuP9v9sgDuA/Kb+9oA8gP3Q/4A8gP2L9oA9gP61gD2A/oGAfwv+gP6BgD2A/raAPID+i/d/ +AkAA/qUAPQD+pAA9AP2sowA8APzGoqoAOwD81bOjADsA++rKpKoAOgD7+9WzpQA6APr76MikqgA5 +AAH7/NWxpQA5AAH7/OTFpAA5AAH7+/nRqKIAOAAC+/zbuKQAOAAC+/vsyKSqADcAA/v80q2mADcA +A/v83LilADcAA/v858OkADcAA/v798+kogA2AAT7/NWvpAA2AAT7/N25owA2AAT7/OPAowA2AAT7 +/OvGowA2AAT7+/fOo/8ANQAF+/zRqacANQAF+/zVr6UANQAF+/zXsaMANQAF+/zYs6UANQAF+/za +taQANQAF+/zct6MANQAF+/zeuaUANQAF+/zct6MANQAF+/zataQANQAF+/zYs6UANQAF+/zXsaMA +NQAF+/zVr6MANQAF+/zSqKQANQAE+/v2zqP/ADUABPv868ajADYABPv848CjADYABPv83LmjADYA +BPv81a6jADYAA/v79s+kogA2AAP7/OXEowA3AAP7/Nu4pQA3AAP7/NKspgA3AAL7++rJpIAANwAC ++/zauKQAOAAB+/v30amiADgAAfv85sSlADkAAfv81bGkADkA+vvoyKOqADkA+/vVsqMAOgD76smk +ogA6APzVs6MAOwD8xaOiADsA/aqmADwAfwLAAP4QAD0A/kIAPQD9YR8APAD8jEsBADsA/K1jHgA7 +APuth0kBADoAAa39YRsAOgABrf2CQAA6AAGt/KlaCgA5AAKt/W0pADkAAq39jUkAOQADrf1dEAA4 +AAOt/W8pADgAA639hT8AOAADrfykVQIANwAErf1hFQA3AASt/XEqADcABK39fzgANwAErf2PRQA3 +AASt/aRTADcABa39WwkANgAFrf1iFAA2AAWt/WYaADYABa39aR4ANgAFrf1sIgA2AAWt/XAnADYA +Ba39cyoANgAFrf1wJwA2AAWt/WwiADYABa39aR4ANgAFrf1mGgA2AAWt/WIUADYABa39WwkANgAE +rf2jUwA3AASt/Y9FADcABK39fzgANwAErf1xKgA3AASt/WEVADcAA638o1UCADcAA639hD8AOAAD +rf1vKAA4AAOt/V0QADgAAq39jEgAOQACrf1sKAA5AAGt/KhZCgA5AAGt/YJAADoAAa39YBoAOgD8 +rYZHADsA/K1jHQA7APyLSwEAOwD9YB4APAD+QgA9AP4OAD0AfwLAAP4GAD0A/hgAPQD9IwsAPAD9 +MhsAPAD8PiMLADsA/D4wGgA7AAE+/SMKADoAAT79LhcAOgABPvw8IAQAOQACPv0nDgA5AAI+/TIa +ADkAAz79IQYAOAADPv0oDgA4AAM+/S8WADgAAz78Ox4BADcABD79IwgANwAEPv0oDwA3AAQ+/S0U +ADcABD79MxkANwAEPv07HQA3AAU+/SADADYABT79IwcANgAFPv0kCQA2AAU+/SULADYABT79JwwA +NgAFPv0oDgA2AAU+/SkPADYABT79KA4ANgAFPv0nDAA2AAU+/SULADYABT79JAkANgAFPv0jBwA2 +AAU+/SADADYABD79Oh0ANwAEPv0zGQA3AAQ+/S0UADcABD79KA8ANwAEPv0jBwA3AAM+/DoeAQA3 +AAM+/S8WADgAAz79KA4AOAADPv0hBgA4AAI+/TIaADkAAj79Jw4AOQABPvw8IAMAOQABPv0uFwA6 +AAE+/SIJADoA/D4wGQA7APw+IwoAOwD9MRsAPAD9IgsAPAD+FwA9AP4FAD0AfwJAAP4iAD0A/rQA +PQD9/1AAPAD8/+IMADsA/OX/fQA7APuf/+wMADoA+4Dh/3QAOgD6gKT/6AkAOQABgPzl/2kAOQAB +gPyr/8wAOQABgPuD9f8sADgAAoD8zP+GADgAAoD7nf/kAwA3AAOA/O7/PAA3AAOA/Mf/fwA3AAOA +/Kf/wQA3AAOA+4f9+QsANgAEgPzk/0YANgAEgPzE/4AANgAEgPyv/6cANgAEgPyb/84ANgAEgPuH +//QBADUABYD89P8dADUABYD84v8+ADUABYD82v9LADUABYD80/9YADUABYD8zf9lADUABYD8xv9y +ADUABYD8wf98ADUABYD8xv9yADUABYD8zf9lADUABYD80/9YADUABYD82v9LADUABYD84v89ADUA +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 +Ffz9+8KkDaT+qPwK/P62pAek/uL8FPz9+8SkD6T+8/wJ/P7CpAek/tX8E/z9+8SkEKT+6PwJ/P7P +pAek/sr8Evz9+8WkEaT+2/wJ/P7apAek/r38Efz9+8WkEqT+0PwJ/P7npAek/rL8EPz9+8akE6T+ +w/wJ/P7ypAek/ab7/A/8/sekFKT+tvwK/P6opAek/vH8Dvz+yaQVpP6r/Ar8/rOkB6T+5fwN/P7I +pBek/vb8Cfz+v6QHpP7Z/Az8/sqkC6T+0KQKpP7r/An8/sukB6T+zfwL/P7NpAuk/dD8pAqk/t78 +Cfz+16QHpP7B/Ar8/sukC6T80Pz8pAqk/tH8Cfz+46QHpP60/An8/s6kC6T+zPwB/Auk/sb8Cfz+ +76QHpP6o/Aj8/s+kC6T+zPwC/Auk/rn8Cfz9+6WkB6T+9PwG/P7PpAuk/sz8A/wLpP6u/Ar8/rCk +B6T+6PwF/P7PpAuk/sz8BPwMpP75/An8/rukB6T+3PwE/P7QpAuk/sv8BfwMpP7u/An8/sikB6T+ +0PwD/P7QpAuk/sr8BvwMpP7h/An8/tSkB6T+xPwC/P7TpAuk/sf8B/wMpP7V/An8/uCkB6T+t/wB +/P7UpAuk/sf8CPwMpP7J/An8/u2kB6T7rPz81KQLpP3G+/wI/Ayk/rz8Cfz++KQIpP331aQLpP3G ++/wJ/Ayk/rH8Cvz+raQHpP7GpAuk/cb7/Ar8DKT9pfr8Cfz+uKQUpP3F+/wL/A2k/vD8Cfz+xaQT +pP3C+/wM/A2k/uT8Cfz+0KQSpP3C+/wN/A2k/tj8Cfz+3aQRpP3B+/wO/A2k/sz8Cfz+6aQQpP3B ++/wP/A2k/sD8Cfz+9aQPpP3B+/wP/P72pAyk/rP8Cvz+qaQNpP3A+vwP/P32taQMpP6o/Ar8/rSk +DKT9wPr8D/z99bSkDqT+8/wJ/P7BpAuk/b35/A/8/fa2pA+k/uj8Cfz+zqQKpP2++vwP/P32tqQQ +pP7b/An8/tmkCaT9vvn8D/z99rWkEaT+z/wJ/P7mpAik/bz5/A/8/fe2pBKk/sP8Cfz+8aQHpP28 ++fwP/P33tqQTpP62/An8/fumpAWk/bz5/A/8/fe2pBSk/qr8Cvz+sqQEpP25+PwP/P33t6QWpP72 +/An8/r6kA6T9ufj8D/z997ekF6T+6vwJ/P7KpAKk/bj4/A/8/fe3pBik/t78Cfz+1qQBpP249/wP +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 +BVf+oq0RrSMA/lVXBVf+qa0RrSMA/lVXBVf+qa0RrSMA/lFXBVf+oq0RrSMA/kxXBVf+m60RrSMA +/khXBVf+lK0RrSMA/kRXBVf+jK0RrSMA/jxXBVf+fa0RrSMA/i5XBVf+cK0RrSMA/iFXBVf+Zq0R +rSMA/hRXBVf+Xa0RrSMA/gZXBlf+m60QrSQA/kxXBVf+f60QrSQA/jZXBVf+a60QrSQA/iBXBVf9 +XKytD60kAP4JVwZX/outD60lAP5KVwVX/mytD60lAP4uVwVX/VmnrQ6tJQD+DVcGV/5+rQ6tJgD+ +RVcFV/1erK0NrSYA/iVXBlf+g60NrSYA/QdUVwVX/mCtDa0nAP4yVwZX/oCtDK0nAP0KVVcFV/1b +o60LrTgA/QQXHwMfNwD9Ch0fBB81AP0BER8GHzQA/QMZHwcfMwD9BBsfCB8yAP0FHB8JHzEA/Qgd +Hwkf/icALwD9Bh0fCB/8ITM+AC4A/QQcHwgf+yU6Pj4ALQD9AxsfCB/+LD4CPi0A/QEZHwgf/i8+ +Az4tAP4SHwcf/SAyPgQ+LAD+CR8HH/0gND4FPisA/QQdHwcf/jI+Bj4rAP4XHwcf/i8+Bz4qAP4L +Hwcf/iw+CD4pAP0CHB8GH/4lPgk+KQD+Eh8GH/0hOz4JPigA/gQfBx/+Mz4KPigA/hMfBh/+KD4L +PicA/QQeHwUf/SE7Pgs+JwD+Eh8GH/4uPgw+JgD9Ax4fBR/+Ij4NPiYA/g0fBh/+Lz4NPiYA/hkf +BR/9Ij0+DT4lAP4FHwYf/i0+Dj4lAP4QHwUf/SA7Pg4+JQD+Gx8FH/4mPg8+JAD+Ax8GH/4yPg8+ +JAD+Cx8FH/4hPhA+JAD+Ex8FH/4mPhA+JAD+Gx8FH/4tPhA+IwD+Ah8GH/43PhA+IwD+Bx8FH/4h +PhE+IwD+DB8FH/4kPhE+IwD+ER8FH/4oPhE+IwD+FR8FH/4sPhE+IwD+GB8FH/4yPhE+IwD+Gh8F +H/41PhE+IwD+Gx8FH/43PhE+IwD+HR8FH/46PhE+IwD+Hh8FH/48PhE+IwD+Hh8FH/48PhE+IwD+ +HR8FH/46PhE+IwD+Gx8FH/43PhE+IwD+Gh8FH/41PhE+IwD+GB8FH/4yPhE+IwD+FR8FH/4sPhE+ +IwD+ER8FH/4oPhE+IwD+DB8FH/4kPhE+IwD+Bx8FH/4hPhE+IwD+Ah8GH/43PhA+JAD+Gx8FH/4t +PhA+JAD+Ex8FH/4mPhA+JAD+Cx8FH/0hPT4PPiQA/gMfBh/+MT4PPiUA/hofBR/+Jj4PPiUA/hAf +BR/9IDs+Dj4lAP4FHwYf/i0+Dj4mAP4ZHwUf/SI9Pg0+JgD+DR8GH/4vPg0+JgD9Ah4fBR/+Ij4N +PicA/hIfBh/+LT4MPicA/QMeHwUf/SA6Pgs+LAD+fP8Q/ysA/n3/Ef8qAP5m/xL/KQD9Ufz/Ev8o +AP0/9/8T/ycA/SDu/xT/JgD9Bcb/E//9/swAJQD+jv8T//zumoAAJAD9S/7/Ev/714iAgAAjAP0b +7f8R//39tYACgCQA/qr/Ef/9/KmAA4AjAP5F/xH//feegASAIgD9B9r/EP/995iABYAiAP5+/xD/ +/fuegAaAIQD9G/b/D//9/aeAB4AhAP6O/xD//rWACIAgAP0T8/8P//7VgAmAIAD+hf8P//3uh4AJ +gB8A/Q/w/w7//f2agAqAHwD+dv8P//7IgAuAHwD+0/8O//3zh4ALgB4A/jX/D//+rIAMgB4A/pL/ +Dv/+54ANgB0A/QTo/w7//qmADYAdAP5N/w7//euBgA2AHQD+lv8O//6vgA6AHQD+2P8N//34hYAO +gBwA/hr/Dv/+zoAPgBwA/l3/Dv/+n4APgBwA/p3/Df/+8YAQgBwA/tn/Df/+z4AQgBsA/Qb7/w3/ +/q6AEIAbAP4p/w7//o+AEIAbAP5P/w3//u6AEYAbAP53/w3//tmAEYAbAP6e/w3//sWAEYAbAP65 +/w3//rKAEYAbAP7F/w3//p+AEYAbAP7T/w3//pWAEYAbAP7g/w3//o+AEYAbAP7t/w3//omAEYAb +AP75/w3//oOAEYAbAP75/w3//oOAEYAbAP7t/w3//omAEYAbAP7g/w3//o+AEYAbAP7T/w3//paA +EYAbAP7G/w3//p+AEYAbAP65/w3//rKAEYAbAP6e/w3//saAEYAbAP53/w3//tmAEYAbAP5P/w3/ +/u+AEYAbAP4p/w7//o+AEIAbAP0G+/8N//6vgBCAHAD+2P8N//7QgBCAHAD+nf8N//3ygYAPgBwA +/lz/Dv/+oIAPgBwA/hn/Dv/+zoAPgB0A/tf/Df/9+IWADoAdAP6V/w7//rCADoAdAP5L/w7//euB +gA2AHQD9BOj/Dv/+qoANgB4A/pH/Dv/+6IANgB4A/jT/D//+roAMgB8A/tP/Dv/99IiAC4AM0O3T +19zi6u7x9vn59vHu6uLc19LQEND9xamkCaT9owDQB9D709rj8vsQ+/vy49nS0A3Q/c6ypAqkBdD8 +0dnp+xf7+/nq29HQDND9vaWkCKQD0PzU4/n7HPv8+ePU0AvQ/cenpAekAdD91OP7Ifv8+ejV0ArQ +/cuqpAak/dLk+yb7/ePS0AnQ/cyspAWk/vb7KPv999vQCdD9za6kBKQs+/3r0tAI0P3NraQDpC37 +/fbZ0AjQ/c2rpAKkL/v+4NAI0P3KqKQBpDD7/ufQCND7x6WkpPsw+/3r0dAH0Py8pKT7Mfv97dLQ +B9D9sqT7Mvv969HQBtD9zan7M/v+5dAH0P7E+zT7/uDQB9A2+/7Y0AbQNvv99tLQBdA3+/7r0AXQ +OPv+29AE0Dj7/fbS0APQOfv+5dAD0Dr7/tXQAtA6+/7m0ALQOvv9+dPQAdA7+/7j0AHQO/v799HQ +0Ps7+/zZ0ND7O/v86dDQ+zv7/PnS0Ps8+/3Z0Ps8+/3i0Ps8+/3x0Ps9+/7S+z37/tf7Pfv+2/s9 ++/7j+z37/ur7Pfv+7vs9+/7x+z37/vT7Pfv++fs9+/75+z37/vT7Pfv+8fs9+/7u+z37/ur7Pfv+ +4/s9+/7b+z37/tb7Pfv+0vs8+/3z0Ps8+/3k0Ps8+/3Y0Ps8+/3S0Ps7+/zp0ND7O/v82dDQ+zr7 ++/fR0ND7Ovv+4tAB0Dv7/tTQAdA6+/7k0ALQOvv+09AC0Dn7/uPQA9A4+/330tAD0AxX7V1mcX2M +lJmjqaujmpSMfXBmXVcQV/1ACwALAAhX+1xrf5ytEK37nH9rXFcNV/1SHQAKAAVX/Flqia0Xrfus +jG1ZVwxX/TECAAgAA1f8Xn+orRyt/Kh+XlcLV/1ECAAHAAFX/GB/rK0grfyshmFXClf9TA0ABgD9 +W4GtJq39gFtXCVf9TxEABQD+pK0orf2kb1cJV/1SFQAEACyt/Y9dVwhX/VIRAAMALa39pGlXCFf9 +UA0AAgAvrf55VwhX/UsIAAEAMK39hVhXB1f7RAIAAK0wrf2OWlcHV/wwAACtMa39klpXB1f9GwCt +Mq39jllXBlf9UQutM63+hFcHV/5ArTSt/nlXB1c2rf5oVwZXNq39o11XBVc3rf6PVwVXOK3+blcE +Vzit/aRbVwNXOa3+gFcDVzqt/mBXAlc6rf6CVwJXOq39rF5XAVc7rf5+VwFXO637p1lXV607rfxr +V1etO638ildXrTut/KxbV608rf1qV608rf19V608rf2bV609rf5drT2t/matPa3+b609rf58rT2t +/outPa3+k609rf6arT2t/qGtPa3+qa09rf6prT2t/qGtPa3+mq09rf6TrT2t/outPa3+fK09rf5v +rT2t/mWtPa3+XK08rf2bV608rf19V608rf1qV607rfysW1etO638iVdXrTut/GpXV606rfunWVdX +rTqt/n1XAVc6rf2sXlcBVzqt/oFXAlc6rf5fVwJXOa3+flcDVzit/aRbVwNXDB/tISQoLDI1Nzo8 +PTo3NTIsKCQhHxAf/RcEAAsACB/7ISYtOD4QPvs4LSYhHw0f/R0KAAoABR/8ICYxPhc++z0yJyAf +DB/9EgEACAADH/wiLTw+HD78PC0iHwsf/RgDAAcAAR/8Ii09PiA+/D0wIx8KH/0bBAAGAP0hLj4m +Pv0tIR8JH/0cBgAFAP47Pig+/TsoHwkf/R0HAAQALD79MyEfCB/9HQYAAwAtPv07JR8IH/0cBQAC +AC8+/isfCB/9GwMAAQAwPv4vHwgf+xgBAAA+MD79MyAfBx/8EQAAPjE+/TQgHwcf/QoAPjI+/TMg +HwYf/R0EPjM+/i8fBx/+Fz40Pv4rHwcfNj7+JR8GHzY+/TohHwUfNz7+Mx8FHzg+/icfBB84Pv07 +IR8DHzk+/i0fAx86Pv4iHwIfOj7+Lh8CHzo+/T0hHwEfOz7+LR8BHzs++zsgHx8+Oz78Jh8fPjs+ +/DEfHz47Pvw9IR8+PD79Jh8+PD79LR8+PD79Nx8+PT7+IT49Pv4kPj0+/ig+PT7+LD49Pv4xPj0+ +/jQ+PT7+Nz49Pv45Pj0+/jw+PT7+PD49Pv45Pj0+/jc+PT7+ND49Pv4xPj0+/iw+PT7+KD49Pv4k +Pj0+/iE+PD79Nx8+PD79LR8+PD79Jh8+Oz78PSEfPjs+/DEfHz47PvwmHx8+Oj77OyAfHz46Pv4t +HwEfOj79PSEfAR86Pv4uHwIfOj7+Ih8CHzk+/i0fAx84Pv07IR8DHwz/7e7ZxbKflpGIg4KIkJaf +ssbZ7/8c//z+egD/B//78c+vjoAQgPuOr9Dy/xr//nn/BP/8+NGigBeA+4GezPj/F//+/v8C//zr +r4SAHID8hLDr/xj//OivgYAggPyBpuX/FP/986yAJoD9rvP/Ev/+h4AogPyHyP7/EP8sgP2b7/8P +/y2A/YfU/w7/L4D9t/7/DP8wgP2n+/8L/zGA/Zz2/wr/MoD9mPf/Cf8zgP2c+v8I/zSA/aj+/wf/ +NYD+t/8H/zaA/tb/Bv82gP2I7/8F/zeA/Zv+/wT/OID+yf8E/ziA/Yfz/wP/OYD+rv8D/zqA/ub/ +Av86gP6r/wL/OoD9gez/Af87gP6w/wH/O4D7hfn//4A7gPzQ//+AO4D8of//gDuA/IHz/4A8gP3R +/4A8gP2x/4A8gP2P/4A9gP7vgD2A/tqAPYD+x4A9gP6zgD2A/qCAPYD+l4A9gP6QgD2A/oqAPYD+ +g4A9gP6DgD2A/oqAPYD+kIA9gP6XgD2A/qCAPYD+s4A9gP7HgD2A/tuAPYD+8IA8gP2P/4A8gP2x +/4A8gP3S/4A7gPyB8/+AO4D8ov//gDuA/NH//4A6gPuF+f//gDqA/rH/Af86gP2B7P8B/zqA/qz/ +Av86gP7p/wL/OYD+sP8D/ziA/Yfz/wP/fwCAAP6jAD0A/aSjADwAAaT+owA7AAKk/qIAOgADpP6q +ADkAA6T+owA5AASk/qUAOAAFpP6nADcABqQ4AAekNwAHpP6qADUAB6T+owA1AAik/qMANAD+s6QH +pDUA/cumpAak/qoAMwD90LykBqT+pQAzAPzQz6mkB6QzAAHQ/r6kBqT+pQAyAALQ/qmkBqQzAALQ +/r2kBqT+pwAxAALQ/c+npAWk/qMAMQAD0P62pAak/r8AMAAD0P7HpAekMQAE0P6qpAakMQAE0P66 +pAakMQAE0P7JpAak/p8ALwAF0P6opAWk/qMALwAF0P6zpAWk/qUALwAF0P6/pAakMAAF0P7LpAak +/pkALgAG0P6npAWk/qEALgAG0P6upAWk/qYALgAG0P60pAWk/qUALgAG0P67pAWk/qUALgAG0P7B +pAakLwAG0P7GpAakLwAG0P7HpAWk/qMALgAG0P7LpAakLwAG0P7MpAakLwAG0P7PpAWk/qMALgAG +0P7PpAWk/qMALgAG0P7MpAakLwAG0P7LpAakLwAG0P7HpAWk/qMALgAG0P7GpAakLwAG0P7BpAak +LwAG0P67pAakLwAG0P60pAakLwAG0P6tpAWk/qYALgAG0P6npAWk/qUALgAF0P7LpAak/pkALgAF +0P6/pAakMAAF0P6zpAWk/qUALwAF0P6opAWk/qMALwAE0P7IpAak/p8ALwAE0P66pAakMQAE0P6q +pAakMQAD0P7GpAak/qMAMAAD0P62pAak/r8AMAAC0P3Op6QFpP6lADEAAtD+vaQGpP6lADEAAdD9 +z6ikBqQzAH8DwAD+HgA9AP1PBQA8AP1XMAA8APxXVgwAOwABV/41ADsAAVf9VQsAOgACV/4xADoA +Alf9VAYAOQADV/4lADkAA1f+RAA5AARX/g0AOAAEV/4sADgABFf+SQA4AAVX/gkANwAFV/4fADcA +BVf+NQA3AAVX/kwANwAGV/4GADYABlf+EwA2AAZX/iAANgAGV/4uADYABlf+OwA2AAZX/kMANgAG +V/5HADYABlf+TAA2AAZX/lAANgAGV/5VADYABlf+VQA2AAZX/lAANgAGV/5MADYABlf+RwA2AAZX +/kMANgAGV/47ADYABlf+LQA2AAZX/iAANgAGV/4TADYABlf+BgA2AAVX/kwANwAFV/41ADcABVf+ +HwA3AAVX/gkANwAEV/5JADgABFf+LAA4AARX/g0AOAADV/5EADkAA1f+JAA5AAJX/VQGADkAAlf+ +MQA6AAFX/VUJADoAfwPAAP4LAD0A/RwCADwA/R8RADwAAR/+BAA7AAEf/hMAOwABH/0eBAA6AAIf +/hIAOgACH/0eAgA5AAMf/g0AOQADH/4YADkABB/+BQA4AAQf/hAAOAAEH/4aADgABR/+AwA3AAUf +/gsANwAFH/4TADcABR/+GwA3AAYf/gIANgAGH/4HADYABh/+DAA2AAYf/hAANgAGH/4VADYABh/+ +GAA2AAYf/hkANgAGH/4bADYABh/+HQA2AAYf/h4ANgAGH/4eADYABh/+HQA2AAYf/hsANgAGH/4Z +ADYABh/+GAA2AAYf/hUANgAGH/4QADYABh/+CwA2AAYf/gcANgAGH/4CADYABR/+GwA3AAUf/hMA +NwAFH/4LADcABR/+AwA3AAQf/hoAOAAEH/4QADgABB/+BQA4AAMf/hgAOQADH/4NADkAAh/9HgIA +OQACH/4RADoAAR/9HgMAOgB/AIAA/mQAPQD9+04APAD8//c9ADsAAf/97R4AOgAC//3JBgA5AAP/ +/osAOQAD//3+TwA4AAT//ewaADcABf/+pgA3AAb//kMANgAG//3YBgA1AAf//noANQAH//31GQA0 +AAj//owANAAI//30FQAzAAn//oIAMwAJ//3vDgAyAAr//nQAMgAK//7VADIAC//+MQAxAAv//o4A +MQAL//3pBAAwAAz//kkAMAAM//6SADAADP/+1AAwAAz//f4YAC8ADf/+WQAvAA3//psALwAN//7X +AC8ADf/9+gUALgAO//4mAC4ADv/+TQAuAA7//nQALgAO//6bAC4ADv/+twAuAA7//sQALgAO//7R +AC4ADv/+3gAuAA7//usALgAO//74AC4ADv/++AAuAA7//usALgAO//7eAC4ADv/+0QAuAA7//sQA +LgAO//63AC4ADv/+mgAuAA7//nMALgAO//5NAC4ADv/+JQAuAA3//foFAC4ADf/+1gAvAA3//psA +LwAN//5ZAC8ADP/9/hgALwAM//7UADAADP/+kgAwAAz//kgAMAAL//3oBAAwAAv//o0AMQAL//4w +ADEACv/+1AAyAH8KAAB/CgAAfwoAAH8KAAB/EAAAfxAAAH8QAAB/EAAAKQD8o7zZ+xH7KQD7qKbK +5fsQ+yoA+6St0fH7D/srAPukt9X5+w77KwD7n6PB2/sO+ywA+6WnyN/7DfstAPujqMvj+wz7LgD7 +pKrM6PsL+y8A+6Su0Oj7CvsvAPr/o63N4/sJ+zAA+v+jqsvg+wj7MgD6o6jH2/n7BvszAPqjp8LV +8fsF+zQAAaP8tdHm+wT7NQD5qqWtytn0+wL7NwD3o6a60eH5+/sANwD4paSrxNXq+wA5APqmorPJ +1QA6APuio6SxADwA/aKjAH8K/wAqAP0va60RrSoA/ARLg60QrSsA/BJYma0PrSwA/CZhqa0OrS0A +/TturQ6tLQD8BUl4rQ2tLgD8CUx+rQytLwD8DVCHrQutMAD8E1WHrQqtMQD8ElGArQmtMgD8Dk13 +rQitMwD7CUduqa0GrTQA+wU7YZqtBa02APwjV4KtBK03APsSS2ugrQKtOAD4AyxYe6ytrQA5APoN +P2GMrQA7APwfSmIAPAD9ARoAfws/ACoA/REmPhE+KgD8ARsvPhA+KwD8Bh83Pg8+LAD8DSM8Pg4+ +LQD9FSc+Dj4tAPwCGis+DT4uAPwDGy0+DD4vAPwEHDA+Cz4wAPwHHzA+Cj4xAPwGHC0+CT4yAPwF +Gyo+CD4zAPsDGSc8PgY+NAD7AhUjNz4FPjYA/AwfLj4EPjcA+wYbJjk+Aj44APgBEB8sPT4+ADkA ++gUXIzI+ADsA/AsaIwA9AP4JAH8LPwApAPy1/9CAEYApAPsj9/+pgBCAKgD7bf/3kYAPgCsA+63/ +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 +J/v17tjP0MqxpKSlogAMACn7+evV0NDBqKQBpAwAKvv5+eDR0MywpAGk/oAACQAs+/by1tDPu6Sk +o6oACAAt+/n54NDQxKakAaQIAC/79+nS0MiopKWqAAYAMPv38dTQyqukpKoABQAx+/f21dDMrKSl +qgAEADL79/fX0M2rpKWSAAMAM/v3+dbQyqikpIAAAgA0+/j31dDIp6SlAAIANfv49NTQw6SkpgAB +ADb79vPR0LikpKYAAPs2+/fo0M+vpKP/APs3+/jd0MuopKUA+zf7+PnV0MGkpKb7OPv589HQsKSk ++zn7+uDQyaSk+zn7+vnU0Lik+zr7++rPz6j7O/v819C7+zv7/OvQzfs8+/3X0Ps8+/3q0Ps9+/7U ++z37/uD7Pfv+9vt/CD77/vT7Pfv+4Ps9+wDV/GiGq60erfemf2NXV1U0CgATACWt+JBnV1dVMQQA +EQAnrfmUaVdXSxoAEAAprfmQYVdXPAgADgAqrfmreVlXTxgADQAsrfmdY1dWLgEACwAtrfmsd1dX +PwUACgAvrfqKW1dICQAJADCt+pteV0wNAAgAMa36o2JXTxAABwAyrfqnZldQDQAGADOt+qllV0sI +AAUANK36p2JXSAUABAA1rfqhXlc+AQADADat+5tbVykAAwA3rfuHV1YUAAIAOK37cldNBwABADit ++6tjVzkAAQA5rfmbWVcXAACtOa36dldKAgCtOa36qV5XKQCtOq37jFdTB607rfxmVy2tO638j1dS +rTyt/WRXrTyt/YtXrT2t/mCtPa3+ea09rf6hrX8IPq3+oK09rf55rT2tAGD8JTA9Ph4+9zstIx8f +HhMEABMAJT74MyUfHx4SAQARACc++TUlHx8bCQAQACk++TMjHx8VAwAOACo++T0rIB8cCQANACw+ ++jgjHx8RAAwALT75PSofHxcCAAoALz76MSEfGgMACQAwPvo3Ih8bBQAIADE++jojHxwGAAcAMj76 +OyQfHAUABgAzPvo8JB8bAwAFADQ++jsjHxoCAAQANT77OSIfFgAEADY++zcgHw8AAwA3PvswHx8H +AAIAOD77KR8cAgABADg++z0jHxQAAQA5Pvk3IB8IAAA+OT76Kh8aAQA+OT76PCIfDwA+Oj77Mh8e +Aj47PvwkHxA+Oz78Mx8dPjw+/SQfPjw+/TEfPj0+/iI+PT7+Kz49Pv45Pn8IPj7+OT49Pv4rPj0+ +ACL81aaCgB6A/Iav3/8E//z+oBYADwAlgP2a2P8E//zuagIADQAngP2W0/8E//26FgAMACmA/Zrl +/wP//elGAAsAKoD8grf4/wL//Px+AgAJACyA/Y3f/wP//acGAAgALYD8gbv+/wL//b4OAAcAL4D9 +ofP/Av/9yxUABgAwgP2P6/8C//3XFQAFADGA/Yji/wL//csMAAQAMoD9hdr/Av/9vQcAAwAzgP2D +3P8C//2lAgACADSA/YXi/wL//ncAAgA1gP2K6/8B//37PAABADaA/Y/1/wH/++MUAACANoD9pP7/ +Af/8tQEAgDeA/sP/Av/9YACAN4D9guH/Af/97BSAOID9j/r/Af/+mIA5gP68/wH//v2AOYD9g+v/ +Af87gP6f/wH/PID82f//gDuA/Jv//4A8gP3d/4A8gP2g/4A9gP7mgD2A/reAPYD+ioB/CD6A/ouA +PYD+t4A9gADmfwTAAP6kAD0A/qQAPQD9pKMAPAABpD0A/KekowA7APy5pKMAOwD7zKWkpAA6APvQ +sqSkADoA+9DDpKQAOgD60M+npKUAOQD619C3pKMAOQD65NDFpKQAOQD59tDPpaSqADgA+fvV0K+k +pQA4APn73dC7pKMAOAD5++nQx6SkADgA+Pv3z8+kpJkANwAB+/rS0KqkowA3AAH7+tfQsKSjADcA +Afv62tC3pKMANwAB+/ri0L6kpQA3AAH7+uXQwqSkADcAAfv66dDEpKQANwAB+/rr0MekpAA3AAH7 ++u7QyKSkADcAAfv68tDLpKQANwAB+/ry0MukpAA3AAH7+u7QyKSkADcAAfv669DHpKQANwAB+/rp +0MSkpAA3AAH7+ufQwqSkADcAAfv64tC+pKUANwAB+/ra0LekpQA3AAH7+tfQsaSmADcAAfv60tCp +pKEANwD4+/fPz6SkmQA3APn76dDHpKQAOAD5+93Qu6SkADgA+fvV0LCkpAA4APn20M+lpKYAOAD6 +5tDEpKQAOQD619C2pKQAOQAB0PyopKYAOQD70MKkpAA6APvQsqSlADoAfwXAAP4FAD0A/ioAPQD9 +UAMAPAD9Vx0APAD9Vz0APAD8WFYIADsA/GZXJQA7APyCV0IAOwD7o1dVBAA6APutYVcYADoA+61x +Vy4AOgD7rYhXRAA6APqtpldVAQA5AAGt/FxXDAA5AAGt/GRXGQA5AAGt/G5XJgA5AAGt/HpXNAA5 +AAGt/IRXOwA5AAGt/IlXQAA5AAGt/I9XRAA5AAGt/JVXSQA5AAGt/JxXTQA5AAGt/JxXTQA5AAGt +/JVXSQA5AAGt/I9XRAA5AAGt/IlXQAA5AAGt/IRXOwA5AAGt/HpXNAA5AAGt/G5XJgA5AAGt/GRX +GQA5AAGt/FxXCwA5APqtpldVAQA5APutiFdEADoA+61xVy0AOgD7rWFXFwA6APujV1UDADoA/IJX +QQA7APxmVyUAOwD8WFUHADsA/Vc8ADwA/VccADwAfwXAAP4CAD0A/g8APQD9HQEAPAD9HwoAPAD9 +HxYAPAABH/4DADsA/CQfDQA7APwuHxcAOwD7Oh8eAQA6APs+Ix8IADoA+z4oHxAAOgD7PjAfGAA6 +APs+Ox8eADoAAT78IR8EADkAAT78JB8JADkAAT78Jx8OADkAAT78Kx8SADkAAT78Lx8VADkAAT78 +MR8XADkAAT78Mx8YADkAAT78NR8aADkAAT78OB8bADkAAT78OB8bADkAAT78NR8aADkAAT78Mx8Y +ADkAAT78MR8XADkAAT78Lx8VADkAAT78Kx8SADkAAT78Jx8OADkAAT78JB8JADkAAT78IR8EADkA ++z47Hx4AOgD7PjAfGAA6APs+KB8QADoA+z4jHwgAOgD7Oh8eAQA6APwuHxcAOwD8JB8NADsA/B8e +AgA7AP0fFQA8AP0fCgA8AH8EwAD+OAA9AP7EAD0A/f9AADwA/f+8ADwAAf/+OgA7AAH//rIAOwAB +//39HAA6AAL//nYAOgAC//7UADoA/v3/Af/+MAA5AP7Z/wH//osAOQD+q/8B//7QADkA+Yj+///+ +FQA4AP2A5f8B//5VADgA/YDE/wH//pYAOAD9gKP/Af/+1gA4AP2Ahv8B//37BQA3AAGA/vH/Af/+ +JwA3AAGA/t3/Af/+TgA3AAGA/sr/Af/+dQA3AAGA/rb/Af/+mwA3AAGA/qj/Af/+rgA3AAGA/qL/ +Af/+uwA3AAGA/pv/Af/+yAA3AAGA/pX/Af/+1QA3AAGA/o7/Af/+4gA3AAGA/o7/Af/+4gA3AAGA +/pX/Af/+1QA3AAGA/pv/Af/+yAA3AAGA/qL/Af/+uwA3AAGA/qj/Af/+rgA3AAGA/rb/Af/+mwA3 +AAGA/sr/Af/+dAA3AAGA/t3/Af/+TQA3AAGA/vH/Af/+JgA3AP2Ahv8B//36BQA3AP2Ao/8B//7V +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 +rSEA/kRXAVf+ja0XrSEA/jxXAVf+gK0XrSEA/i5XAVf+dK0XrSEA/iFXAVf+aa0XrSEA/hRXAVf+ +YK0XrSEA/gZXAVf9WKmtFq0iAP5MVwFX/oytFq0iAP42VwFX/nStFq0iAP4gVwFX/mOtFq0iAP4J +VwFX/VimrRWtIwD+SlcBV/6ArRWtIwD+LlcBV/5krRWtfwC+AP0EEgA6APsEEh4fADgA/QERHwIf +OAD9CRwfAx82AP0EFx8EH/4hADQA/QodHwMf/CEuPQAyAP0BER8EH/soPD4+ADEA/QMZHwMf/SQ3 +PgI+MQD9BBsfAx/+Kj4EPjAA/QUcHwIf/SEzPgU+LwD9CB0fAh/9JDs+Bj4uAP0GHR8CH/0mPT4H +Pi0A/QQcHwIf/ig+CT4sAP0DGx8CH/4qPgo+KwD9ARkfAh/+KT4LPisA/hIfAh/+Jj4MPioA/gkf +Ah/9JD0+DD4pAP0EHR8BH/0hOz4NPikA/hcfAh/+Mz4OPigA/gsfAh/+Kz4PPicA/QIcHwEf/iQ+ +ED4nAP4SHwIf/jY+ED4mAP4EHwIf/ik+ET4mAP4THwEf/SE8PhE+JQD9BB4fAR/+Lj4SPiUA/hIf +AR/9IT0+Ej4kAP0DHh8BH/4tPhM+JAD+DR8BH/0hPD4TPiQA/hkfAR/+Kz4UPiMA/gUfAh/+OD4U +PiMA/hAfAR/+JD4VPiMA/hsfAR/+LT4VPiIA/gMfAh/+Oz4VPiIA/gsfAR/+Iz4WPiIA/hMfAR/+ +KT4WPiIA/hsfAR/+Mj4WPiEA/gIfAh/+PD4WPiEA/gcfAR/+Ij4XPiEA/gwfAR/+JT4XPiEA/hEf +AR/+KT4XPiEA/hUfAR/+Lj4XPiEA/hgfAR/+Mj4XPiEA/hofAR/+NT4XPiEA/hsfAR/+Nz4XPiEA +/h0fAR/+Oj4XPiEA/h4fAR/+PD4XPiEA/h4fAR/+PD4XPiEA/h0fAR/+Oj4XPiEA/hsfAR/+Nz4X +PiEA/hofAR/+NT4XPiEA/hgfAR/+Mj4XPiEA/hUfAR/+Lj4XPiEA/hEfAR/+KT4XPiEA/gwfAR/+ +JT4XPiEA/gcfAR/+Ij4XPiEA/gIfAh/+PD4WPiIA/hsfAR/+Mj4WPiIA/hMfAR/+KT4WPiIA/gsf +AR/+Iz4WPiIA/gMfAh/+Oz4VPiMA/hofAR/+LT4VPiMA/hAfAR/+JD4VPjgA/BeZ+f8C/zYA/AJe +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 +uwYAAwAxgP2J4/8C//3KDAAEADCA/ZDs/wL//dYUAAUAL4D9ofP/Av/9yxUABgAtgPyBwP7/Av/9 +vA0ABwAsgP2O4P8D//2kBQAIACqA/IK5+f8C//z7egEACQApgP2e6f8D//3oRAALACeA/ZfV/wT/ +/bYVAAwAJYD9m9n/BP/87WYBAA0A/Nang4AegPyGsOD/BP/8/p0VAA8AAf/79sqcgYAYgPuDpNL6 +/wX//cw7ABEABP/7+d27mYARgPqBmbvZ+f8G//zFTQEAEgAI/+v35NC9qZyWkIiCg4mRmJ6twdTn ++/8I//28QgAVAP2I6f8g//ziiCYAGAD7BU6n6v8a//vgmD0BAB0A+gEucLLv/xL/++emZCMAJgDr +GkFnjrTG0t7u+vjs3c/DrohiOhMAfwfiAPvNpaSkADoA/LmkpAA7APynpKMAOwD9pKUAPAD9pKUA +PAD+pAA9AP6lAH8OfgD9UAMAPAD+KQA9AP4FAH8PfgD9HAEAPAD+DwA9AP4CAH8PfgAB//39HAA6 +AAH//rEAOwAB//46ADsA/f+6ADwA/f8+ADwA/sIAPQD+NgB/Dn4AfwQoAPyko6WkE6QlAP2mo6QW +pCMA/qOkGaQhAPyAo6OkGaQgAP2qpaQbpB8A/Z+jpBykHwAfpB4AIKQdAP2ko6QepB0AIaQcAP6m +pCCkHAAipBsA/p+kIaQbAP6jpCGkGwD+paQhpBsAI6QbAA+k/a7J0BDQGwAPpP7J0BHQGwAPpBPQ +GwAPpBPQGwAPpBPQGwAPpBPQGwAPpBPQGwAPpBPQGwAPpBPQGwAPpBPQGwAPpBPQGwAPpBPQGwAP +pBPQGwAPpBPQGwAPpBPQGwAPpBPQGwAPpA/QA/sbAA+kD9AD+xsAD6QP0AP7GwAPpA/QA/sbAA+k +D9AD+xsAD6QP0AP7GwAPpA/QA/sbAA+kD9AD+xsAD6QP0AP7GwAPpA/QA/sbAA+kD9AD+xsAD6QP +0AP7GwAPpA/QA/sbAA+kD9AD+xsAD6QP0AP7GwAPpA/QA/t/CCwA/RNJVxBXKwD+SVcRVysAE1cr +ABNXKwATVysAE1crABNXKwATVysAE1crABNXKwATVysAE1crABNXKwATVysAE1crABNXKwAPVwOt +KwAPVwOtKwAPVwOtKwAPVwOtKwAPVwOtKwAPVwOtKwAPVwOtKwAPVwOtKwAPVwOtKwAPVwOtKwAP +VwOtKwAPVwOtKwAPVwOtKwAPVwOtKwAPVwOtKwAPVwOtfwgsAP0HGh8QHysA/hofER8rABMfKwAT +HysAEx8rABMfKwATHysAEx8rABMfKwATHysAEx8rABMfKwATHysAEx8rABMfKwATHysADx8DPisA +Dx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8D +PisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPn8EKAD5DlmQs9Dr/xD/JQD8PKfv/xX/IwD9 +JLH/GP8hAPwCevj/Gf8gAP0Dr/8b/x8A/QjA/xz/HwD+ov8d/x4A/nD/Hv8dAP0t+P8e/x0A/rf/ +H/8cAP0u/f8f/xwA/rD/IP8bAP0I9P8g/xsA/kj/If8bAP6N/yH/GwD+v/8h/xsA/uD/If8bAP7h +/yH/GwD+/v8h/xsAI/8bACP/GwAj/xsAI/8bACP/GwAj/xsAI/8bACP/GwAj/xsAI/8bACP/GwAj +/xsAI/8bAB//A4AbAB//A4AbAB//A4AbAB//A4AbAB//A4AbAB//A4AbAB//A4AbAB//A4AbAB// +A4AbAB//A4AbAB//A4AbAB//A4AbAB//A4AbAB//A4AbAB//A4AbAB//A4B/BAAAfwQApH8EANB/ +BAD7fwgAAH8EAFd/BACtfwgAAH8EAB9/BAA+fwQAAH8IAP9/BACAfwQAAA+k+6Oko6oAKgATpP2l +pwAoABekJwAYpCYAGKT9pZIAIwAapP6ZACIAG6T+qgAhABykIgAbpAGjIQAdpCEAHaT+pQAfAB6k +IAAepP6hAB4AH6QfAB+kHwAepP6jAB4ADdD9yK6kDqQfAA7Q/smkDqQfAA/QD6QfAA/QD6QfAA/Q +D6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6Qf +AA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/Q +D6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAA/QD6QfAH8IAAAN +V/1JEwAuAA5X/kgALgAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cv +AA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8A +D1cvAA9XLwAPVy8AD1cvAA9XLwB/CAAADR/9GgcALgAOH/4aAC4ADx8vAA8fLwAPHy8ADx8vAA8f +LwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8v +AA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8AfwQAAA3/+enjwIxL +CQAqABL//PWvMQAoABX//cItACYAFv/9+W0AJQAY//2hBwAjABn//cEFACIAGv/9nwMAIQAb//54 +ACEAG//9+CcAIAAc//6wACAAHf/+PgAfAB3//p8AHwAd//35EwAeAB7//lcAHgAe//6MAB4AHv/+ +tQAeAB7//tIAHgAe//7tAB4AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8f +AB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8A +H/8fAB//HwAf/x8AH/8fAB//HwAf/x8AfwU1AP3/oqQHpDMA/aWjpAikMgD+o6QKpDEA/qOkC6Qw +AP6ipAykMAD+paQMpC8A/qOkDaQvAA+kLwD+paQFpP2uydAE0C8AB6T+ydAF0C8AB6QH0C8AB6QH +0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8A +B6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH +0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8AB6QH0C8A +B6QH0C8AB6QH0C8AB6QH0C8AB6QH0H8HOAD9E0lXBFc3AP5JVwVXNwAHVzcAB1c3AAdXNwAHVzcA +B1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAH +VzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdX +NwAHV38HOAD9BxofBB83AP4aHwUfNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcf +NwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83 +AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHH38FNQD6AUKXyOn/BP8z +AP0ww/8I/zIA/Wf8/wn/MQD+bP8L/zAA/TT5/wv/MAD+vf8M/y8A/kX/Df8vAP6M/w3/LwD+y/8N +/y8A/uL/Df8vAP7+/w3/LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8A +D/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP +/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//fwUAAH8CAKR/AgDQfwcA+38HAAB/AgBX +fwcArX8HAAB/AgAffwcAPn8FAAB/BAD/fwcAgH8FAAAcpP6jACAAHqT+ogAeACCkHgAhpB0AIaT+ +owAbACKk/v8AGgAipP6jABoAIqT+owAaABnQ/ciupAakGwAa0P7JpAakGwAb0AekGwAb0AekGwAb +0AekGwAb0AekGwAb0AekGwAb0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7 +B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH +0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQ +B6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AH +pBsAE/sH0AekGwAT+wfQB6QbAH8HAAAZV/1JEwAiABpX/kgAIgAbVyMAG1cjABtXIwAbVyMAG1cj +ABtXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdX +IwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwAT +rQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdXIwATrQdX +IwB/BwAAGR/9GgcAIgAaH/4aACIAGx8jABsfIwAbHyMAGx8jABsfIwAbHyMAEz4HHyMAEz4HHyMA +Ez4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4H +HyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4HHyMA +Ez4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAEz4HHyMAfwUAABn/++LKjEUAIAAd +//28NAAeAB7//flqAB0AIP/+aAAcACD//fwvABsAIf/9xAEAGgAi//49ABoAIv/+lgAaACL//scA +GgAi//7oABoAIv/+/gAaACP/GwAj/xsAI/8bACP/GwAj/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP +/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP/xsA +E4AP/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP +/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP/xsAE4AP/xsAfwc7AAGk/KOkpAA3AP2fo6QDpDgABqQ3 +AAekNwADpPuuydDQADYAA6T+ydAB0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD +0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcA +A6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD +0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0DcAA6QD0H8IPAD7E0lXVwA6AP5JVwFXOwADVzsAA1c7 +AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsA +A1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXfwg8 +APsHGh8fADoA/hofAR87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMfOwAD +HzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMf +OwADHzsAAx87AAMfOwADHzsAAx9/BzsA+lGq3P//ADcA/RCn/wP/OAD+qP8E/zcA/lH/Bf83AP6r +/wX/NwD+3f8F/zcA/v7/Bf83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/ +NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83 +AAf/NwAH/zcAB/83AAf/NwAH/38HAAB/AQCkfwEA0H8HAPt/CAAAfwEAV38HAK1/CAAAfwEAH38H +AD5/BwAAfwIA/38HAIAfAP6lpAGk/qvQAtD+8vsU+x8A/aqjpAGk/sfQAdD+4PsU+yAAA6T+t9AB +0P3S+fsT+yAAA6T9qM/QAdD+4/sT+yAA/qqkAqT+vdAB0P7S+xP7IQADpP2pz9AB0P7k+xL7IQD+ +oaQCpP6/0AHQ/dL3+xH7IgADpP2pz9AB0P7e+xH7IgD+o6QCpP690AHQ/c/v+xD7IwD+o6QBpP2l +zNAB0P7W+xD7IwD+oaQCpP6z0ALQ/uH7D/skAP6lpAKk/sXQAtD+6/sO+yQA/oCkAqT9qM3QAdD9 +0vb7DfslAASk/rLQAtD91/n7DPsmAP6jpAKk/r3QAtD+2fsM+yYA/r+kAqT9pcfQAtD+3fsL+ycA +/qWkAqT9p8rQAtD+3vsK+ygABKT9qszQAtD92/n7CPspAASk/avN0ALQ/dn5+wf7KQD9/6WkAqT9 +r87QAtD91vb7BvsqAP6qpAOk/azM0ALQ/dLr+wX7KwD+qqQDpP2pydAD0P7e+wT7LAD+n6QDpP2n +x9AD0P3W8fsC+y0A/ZKlpAKk/KW6z9AD0Pvb9/v7AC0A/b+lpAOk/bHM0APQ/NPk+wAuAP3/paQD +pP2pxNAE0P7TADAA/qOkBKT9scvQA9AyAP6lpASk/Ka8z9AB0B2k/KOkowARAP2Zo6QEpPuqvc7Q +pCCk/qoAEQD+oqQFpP2nu6QhpBMA/f+jpCmkFAD8oqOjpAKkHdD9yK6kAqQWAP2io6QBpB7Q/smk +AqQYAPyqpaTQHtADpBoA/qrQHtADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb ++wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7 +A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD +0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQ +A6QbABv7A9ADpBsAG/sD0AOkGwAjAP4NVwJX/p2tFK0kAP5FVwFX/netFK0kAP4lVwFX/VyprROt +JAD9B1RXAVf+fq0TrSUA/jJXAVf9XaytEq0lAP0KVVcBV/6BrRKtJgD+NVcBV/1cp60RrSYA/QxW +VwFX/nStEa0nAP4xVwFX/ViXrRCtJwD9BE5XAVf+Za0QrSgA/h5XAlf+ea0PrSkA/kBXAVf9WI6t +Dq0pAP0KUVcBV/1coq0NrSoA/hpXAlf9ZqutDK0rAP4xVwJX/mutDK0rAP0CRFcCV/5xrQutLAD9 +B0pXAlf+dq0KrS0A/QxOVwJX/W+srQitLgD9D1JXAlf9a6utB60vAP0VUlcCV/1lo60GrTAA/Q9O +VwJX/VyOrQWtMQD9C0pXAlf9WHStBK0yAP0IRFcDV/1lmK0CrTMA/AEsVlcCV/pYb6etrQA0AP0a +UVcDV/xdgawANQD9C0BXBFf+XgA3AP0aTVcDVzkA/AUwVlcBVzsA+wsxVFcAPAD9CC4AfgAdV/1J +EwAeAB5X/kgAHgAfVx8AH1cfAButA1cfAButA1cfAButA1cfAButA1cfAButA1cfAButA1cfABut +A1cfAButA1cfAButA1cfAButA1cfAButA1cfAButA1cfAButA1cfAButA1cfAButA1cfAButA1cf +AButA1cfAButA1cfAButA1cfAButA1cfAButA1cfAButA1cfAButA1cfAButA1cfAButA1cfABut +A1cfAButA1cfAButA1cfACMA/gUfAh/+OD4UPiQA/hkfAR/+Kj4UPiQA/g0fAR/9ITw+Ez4kAP0C +Hh8BH/4tPhM+JQD+Eh8BH/0hPT4SPiUA/QMeHwEf/i4+Ej4mAP4THwEf/SE7PhE+JgD+BB8CH/4p +PhE+JwD+ER8CH/42PhA+JwD9AhwfAR/+JD4QPigA/gsfAh/+Kz4PPikA/hcfAh/+Mz4OPikA/QQd +HwEf/SE6Pg0+KgD+CR8CH/0kPT4MPisA/hEfAh/+Jj4MPisA/QEYHwIf/ig+Cz4sAP0DGh8CH/4q +Pgo+LQD9BBwfAh/9KD0+CD4uAP0FHR8CH/0mPT4HPi8A/QgdHwIf/SQ6PgY+MAD9BRwfAh/9ITM+ +BT4xAP0EGh8DH/4pPgQ+MgD9AxgfAx/9JDY+Aj40AP4QHwQf+yg7Pj4ANAD9CR0fAx/8IS49ADUA +/QQXHwQf/iEANwD9CRsfAx85AP0CER8CHzsA+wQSHh8APAD9AxEAfgAdH/0aBwAeAB4f/hoAHgAf +Hx8AHx8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+ +Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8f +ABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+Ax8fABs+ +Ax8fAB8A/mn/Bf/9/o2AFIAfAP0S+P8F//67gBSAIAD+rv8F//3xg4ATgCAA/lH/Bv/+sIATgCAA +/Qbr/wX//e6BgBKAIQD+hv8G//6sgBKAIQD9E/P/Bf/98IWAEYAiAP6P/wb//sCAEYAiAP0Z9/8F +//38k4AQgCMA/pb/Bv/+3IAQgCMA/RPs/wb//riAD4AkAP5p/wb//f2cgA6AJAD9Asj/Bv/98ImA +DYAlAP01/f8G//3agoAMgCYA/ov/B//+0IAMgCYA/QTF/wf//sSAC4AnAP0f7v8H//68gAqAKAD9 +TP7/B//9yIGACIApAP6G/wj//dCCgAeAKQD9AZ7/CP/924iABoAqAP0Drf8I//3wnIAFgCsA/QnC +/wj//f3AgASALAD9EMT/Cf/93JKAAoAtAP0HrP8J//r9yIWAgAAtAP0Env8K//zurIEALgD8AYX+ +/wr//u0AMAD9Tu3/Cv8yAP0fx/8n//zcqVAAEQD8BYv9/yj//aUPABEA/TTK/yj//qgAEgD8AV7r +/yf//lEAEwD8Fpb4/yX//qYAFQD8FoDx/yP//tsAFwD8FYXr/yH//v4AGQD+A/8i/xsAG4AH/xsA +G4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH +/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsA +G4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAPfv98tD7PPv9 +3tD7O/v8+dLQ+zv7/OLQ0Ps7+/zT0ND7Ovv+5NAB0Dr7/fnS0AHQOvv+3tAC0Dn7/fHP0AHQ/rz7 +OPv+1tAB0P3Mpvs3+/7f0ALQ/bOk+zb7/uvQAtD8w6Sk+zX7/fbS0AHQ+82qpKT7NPv9+dbQAtD+ +saQBpDX7/tnQAtD+vKQCpDT7/tzQAtD+xaQDpDP7/t/QAtD9yqekAqT+p/sx+/7c0ALQ/cyppAKk +/aUA+y/7/fnZ0ALQ/c2spAKk/KUAAPsu+/321tAC0P3Or6QCpPul/wAA+y37/e3S0ALQ/cutpAKk +/aO/AAEALfv+4NAD0P3KqqQDpP6ZAAIAK/v979bQA9D9xaekA6T+nwADACn7/Pnez9AD0P29paQC +pP2jqgAEACj7/eTS0APQ/c6ypAOk/aWqAAUA/eL5+yL7/Pnk09AE0P3DqKQDpP2l/wAGAPvQ0uDy ++x77/PLg0tAE0P3LtKQEpP6jAAgAA9D81uP2+xj7/Pfj1tAG0P28pqQEpP6nAAkABtD71d7q9/sQ ++/v36t7V0AbQ/M/AqqQEpP2jvwAKAP7O0AfQ68/U193j7O7x9vn59vHu7OXd19TP0AfQ/M++qaQF +pP6jAAwA/Ke2x9Ag0PzGtqekBaT9o6oADQACpPyru8nQGtD8yLqqpAak/aWmAA8ABaT7qLK+ytAS +0PvMwbWopAik/qEAEQAJpOunrrS7wcXIyszPz8zKyMbBu7SupqQIpPyjpKYAEwD+paQipPujpKWq +ABcAAaUfpP2lnwAdAPu/pqSlpBWk/KOkvwAlAP6npAGk/KOko6QEpPijpKOkpaSkAH8GoQA9rf2c +V608rf12V607rfyrXFetO638fVdXrTqt+6xeV1etOq3+gFcBVzqt/ahdVwFXOq3+clcBV/5VrTit +/ZhYVwFX/i+tOK3+ZFcBV/1OBa03rf53VwJX/R0ArTat/Y9YVwFX/D8AAK01rf2jXVcBV/tRCwAA +rTSt/atlVwJX/hsAAQA1rf5qVwJX/i8AAgA0rf5wVwJX/UMCAAIAM63+dVcCV/1LCAADADKt/nFX +Alf9TgsABAAwrf2ralcCV/1SEQAFAC+t/aJlVwJX/VIUAAYALq39kF1XAlf9TxAABwAtrf15WFcC +V/1LDAAIACut/ZdkVwNX/UMHAAkAKa38qHRYVwNX/TECAAoAJ638rIBdVwNX/VIcAAwA/X2rrSKt +/Kl9XlcEV/0/CgANAPtXXHecrR6t/Jx2XFcEV/1OIAAPAANX/GR/pK0YrfykfmNXBVf8VTAEABAA +BVf6WGJzi6itEK36qItzYlhXBVf8VTcNABIA/lRXB1frWGBocoCNk5miqamimZONgHJoYFhXB1f8 +VTQKABQA/AYlRFcgV/xEJAYAGQD8DS1JVxpX/EksDQAfAPsIHTRLVxJX+046IwoAJgDrBhMhLjtD +R0tRVVVRS0dDOy4gEwYAfwehAD0+/TgfPjw+/SofPjs+/D0hHz47PvwsHx8+Oj77PSEfHz46Pv4u +HwEfOj79PCEfAR86Pv4pHwEf/h4+OD7+Nh8CH/4RPjg+/iQfAR/9HAI+Nz7+Kx8CH/0KAD42Pv4z +HwIf/BYAAD41Pv06IR8BH/sdBAAAPjQ+/T0kHwIf/goAAQA1Pv4mHwIf/hEAAgA0Pv4oHwIf/RgB +AAIAMz7+Kh8CH/0bAwADADI+/igfAh/9HAQABAAwPv09Jh8CH/0dBgAFAC8+/TokHwIf/R0HAAYA +Lj79MyEfAh/9HAYABwAtPv4rHwMf/RsEAAgAKz79NiQfAx/9GAMACQApPv08KR8EH/0SAQAKACc+ +/D0uIR8DH/0dCgAMAP0tPT4iPvw8LSEfBB/9FgQADQD7HyEqOD4ePvw4KiEfBB/9HAsADwADH/wk +LTs+GD78Oy0jHwUf/B4RAQAQAAYf+yMpMTw+ED77PDEpIx8GH/weFAQAEgD+Hh8IH+0iJSktMjQ3 +Ojw8Ojc0Mi0pJSIfCB/8HhMDABQA/AINGB8gH/wYDQIAGQD8BRAaHxof/BoQBAAfAPsDChMbHxIf ++xwVDAQAJgDrAgcMEBUYGRsdHh4dGxkYFRAMBwIAfwehAD2A/Y7+gDyA/bz/gDuA/ILw/4A7gPyy +//+AOoD7ge3//4A6gP6t/wH/OoD9hO//Af86gP7C/wL/OYD9kvz/Av85gP7e/wP/OID+uv8E/zeA +/Zv8/wT/NoD9iO//Bf81gP2C3P8G/zWA/tH/B/80gP7G/wf//siAMoD+vf8H//3sHYAxgP7E/wf/ +/P5PAIAvgP2C0f8I//yCAACALoD9idz/CP/7mwEAAIAtgP2a7/8I//2yBAABAC2A/bf8/wj//ccK +AAIAK4D9k97/Cf/9whAAAwApgPyEwPz/Cf/9sgkABAAngPyBre//Cv/9mwMABQD9sYKAIoD8g7Ht +/wr//P6CAQAGAPv/8LuOgB6A/I688f8L//3sSwAIAAL/+/7er4eAGID7h7Df/v8M//3EHQAJAAX/ ++v3iwaCEgBCA+oSgweL9/w3//PyIBAAKAAn/6/zo1cKunZeRiYODiZGXna7C1ej8/xD//cgyAAwA +Lf/87m8DAA0AK//895MUAA8AKf/89JEbABEAJ//86oMUABMA/ESi9f8g//v4s1UGABcA+xFmtPL/ +Gv/78rNmEAAdAPoEOXu99P8S//rzvHo4BAAlAOsdQ2qRtcXR3e35+e3d0cW1kWlDHAB/BqEAAtD+ +qqQCpDcAAdD+xqQCpP6fADYAAdD+tqQCpDgA/NDOp6QBpP6mADcA/dC9pAGk/aOZADcA/c+opAKk +OQD+vqQCpP6qADgA/qmkAaT+owA5AAKk/aOnADkAA6Q7AAKk/qoAOgACpDwA/KSlgAA7AP2kowA8 +AP6jAD0A/pkAfww+AAJX/g0AOgABV/5EADsAAVf+JAA7APxXVAYAOwD9VzEAPAD9VQkAPAD+NAA9 +AP4LAH8OPgACH/4FADoAAR/+GAA7AAEf/g0AOwD8Hx4CADsA/R8RADwA/R4DADwA/hIAPQD+BAB/ +Dj4ABv/+ZQA2AAX//fcQADYABf/+qgA3AAX//k0ANwAE//3qBQA3AAT//oQAOAAD//30FQA4AAP/ +/osAOQAC//34GgA5AAL//pQAOgAB//3tFQA6AAH//mUAOwD8/8sCADsA/fwyADwA/ogAPQD+BQB/ +DD4AfwoAAH8KAAB/CgAAfwoAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8Q +AAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAAbAA+kD9AD+xsAD6QP0AP7GwAPpA/QA/sbAA+kD9AD ++xsAD6QP0AP7GwAPpA/QA/sbAA+kD9AD+xsAD6QP0AP7GwAPpA/QA/sbAA+kD9AD+xsAD6QP0AP7 +GwAPpA/QA/sbAA+kD9AD+xsAD6QP0AP7GwAPpA/QA/sbAA+kD9AD+xsAD6QP0AP7GwAPpA/QA/sb +AA+kD9AD+xsAD6QP0AP7GwAPpA/QA/sbAA+kD9AD+xsAD6QP0AP7GwAPpA/QA/sbAA+kD9AD+xsA +D6QP0AP7GwAPpA/QA/sbAA+kD9AD+xsAD6QP0AP7GwAPpA/QA/sbAA+kD9AD+xsAD6QP0AP7GwAP +pA/QA/sbAA+kD9AD+xsAD6QP0AP7GwAPpA/QA/sbAA+kD9AD+xsAD6QP0AP7GwAPpA/QA/sbAA+k +D9AD+xsAD6QP0AP7GwAPpA/QA/sbAA+kD9AD+xsAD6QP0AP7GwAPpA/QA/sbAA+kD9AD+xsAD6QP +0AP7GwAPpA/QA/sbAA+kD9AD+xsAD6QP0AP7GwAPpA/QA/sbAA+kD9AD+xsAD6QT0BsAD6QT0BsA +D6QT0BsAD6QT0BsAD6QT0BsAD6QT0BsAD6QT0BsAD6QT0BsAD6QT0BsAD6QT0BsAD6QT0BsAD6QT +0CsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsA +D1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cD +rSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsA +D1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cD +rSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsA +D1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAD1cDrSsAE1crABNXKwATVysAE1crABNXKwATVysAE1cr +ABNXKwATVysAE1crABNXKwATVysADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisA +Dx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8D +PisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisA +Dx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8D +PisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisA +Dx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisADx8DPisAEx8rABMfKwAT +HysAEx8rABMfKwATHysAEx8rABMfKwATHysAEx8rABMfKwATHxsAH/8DgBsAH/8DgBsAH/8DgBsA +H/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8D +gBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsA +H/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8D +gBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsA +H/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8DgBsAH/8D +gBsAH/8DgBsAI/8bACP/GwAj/xsAI/8bACP/GwAj/xsAI/8bACP/GwAj/xsAI/8bACP/GwAj/38N +APt/AwDQfw0ArX8DAFd/DQA+fwMAH38NAIB/AwD/D9APpB8AD9APpB8AD9APpB8AD9APpB8AD9AP +pB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8A +D9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9AP +pB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8A +D9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9AP +pB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8A +D9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9APpB8AD9AP +pB8AD9APpB8AD9APpB8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9X +LwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cv +AA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8A +D1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAP +Vy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA9XLwAPVy8AD1cvAA8fLwAPHy8ADx8vAA8f +LwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8v +AA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8A +Dx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAP +Hy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8fLwAPHy8ADx8vAA8f +LwAPHy8ADx8vAA8fLwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8f +AB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8A +H/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf +/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB// +HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8AH/8fAB//HwAf/x8ALwAHpAfQLwAHpAfQLwAH +pAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQ +LwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAH +pAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQ +LwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAH +pAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQ +LwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAHpAfQLwAH +pAfQLwAHpAfQLwAHpAfQLwAHpP7J0AXQLwAHpP2uydAE0DcAB1c3AAdXNwAHVzcAB1c3AAdXNwAH +VzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdX +NwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3 +AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcA +B1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwAHVzcAB1c3AAdXNwD+ +SVcFVzcA/RNIVwRXNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83 +AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcA +Bx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAH +HzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83AAcf +NwAHHzcABx83AAcfNwAHHzcABx83AAcfNwAHHzcABx83AP4aHwUfNwD9BxofBB8vAA//LwAP/y8A +D/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP +/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA// +LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8v +AA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8AD/8vAA//LwAP/y8A +D/8vAA//LwD+/v8N/y8A/un/Df8vAP7I/w3/fw4A+38CANB/DgCtfwIAV38OAD5/AgAffw4AgH8C +AP8T+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6Qb +ABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsA +E/sH0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT ++wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7 +B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH +0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQ +B6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AH +pBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsAE/sH0AekGwAT+wfQB6QbABP7B9AHpBsAG9AHpBsA +G9AHpBsAG9AHpBsAG9AHpBsAG9AHpBsAG9AHpBsAGtD+yaQGpBsAGdD9ya2kBaT+pQAaABOtB1cj +ABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOt +B1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cj +ABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOt +B1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cj +ABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOt +B1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABOtB1cjABtXIwAbVyMAG1cj +ABtXIwAbVyMAG1cjABpX/kgAIgAZV/1IEwAiABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8j +ABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+ +Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8j +ABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+ +Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8j +ABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABM+ +Bx8jABM+Bx8jABM+Bx8jABM+Bx8jABsfIwAbHyMAGx8jABsfIwAbHyMAGx8jABof/hoAIgAZH/0a +BwAiABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8b +ABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOA +D/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8b +ABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOA +D/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8b +ABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bABOAD/8bACP/ +GwAj/xsAI/8bACP/GwAj/xsAIv/+/gAaACL//uEAGgAi//7LABoANwADpAPQNwADpAPQNwADpAPQ +NwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwAD +pAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQ +NwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwAD +pAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQ +NwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwAD +pAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQNwADpAPQ +NwADpAPQNwADpAPQNwADpAPQNwADpAPQOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7 +AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsA +A1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwAD +VzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANX +OwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAA1c7AANXOwADVzsAAx87 +AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsA +Ax87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMfOwAD +HzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMf +OwADHzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsAAx87AAMfOwADHzsAAx87 +AAMfOwADHzsAAx87AAMfOwADHzsAAx83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcA +B/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH +/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/ +NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83 +AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/NwAH/zcAB/83AAf/fxAA+38Q +AK1/EAA+fxAAgBv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6Qb +ABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsA +G/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb ++wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7 +A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD +0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQ +A6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9AD +pBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOk +GwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6QbABv7A9ADpBsAG/sD0AOkGwAb+wPQA6Qb +ABv7A9ADpBsAG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8A +G60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60D +Vx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8A +G60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60D +Vx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8A +G60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60D +Vx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AG60DVx8AGz4DHx8A +Gz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4D +Hx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8A +Gz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4D +Hx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8A +Gz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4D +Hx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8A +Gz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AGz4DHx8AG4AH/xsAG4AH/xsAG4AH/xsAG4AH +/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsA +G4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH +/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsA +G4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH +/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsA +G4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH/xsAG4AH +/xsAG4AH/xsAG4AH/xsAG4AH/xsAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/CgAA +fwoAAH8KAAB/CgAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/ +EAAAfxAAAH8QAAB/EAAAfxAAABsAD6QT0BsAD6QT0BsAD6T+ydAR0BsAD6T9rsnQENAbACOkGwD+ +o6QhpBsA/qOkIaQbAP6mpCGkHAD+o6QgpBwAIqQdACGkHQD9oaOkHqQeACCkHgD+qqQepB8A/qqk +HaQgAP2fpaQbpCIA/aSjpBmkIwD9oqOkGKQlAP6lpBekJwD7qqOko6QSpH8LAAArABNXKwATVysA +/klXEVcrAP0TSFcQV38PAAArABMfKwATHysA/hofER8rAP0HGh8QH38PAAAbACP/GwAj/xsA/u3/ +If8bAP7T/yH/GwD+t/8h/xsA/o7/If8bAP5Z/yH/GwD9FPf/IP8cAP6n/yD/HAD+O/8g/x0A/q7/ +H/8dAP0m+P8e/x4A/nj/Hv8eAP0Dn/8d/x8A/QO//xz/IAD9CKH/G/8iAP1r+P8Z/yMA/SzA/xj/ +JQD8MK71/xX/JwD5CUuMwOPo/xD/fwsAAH8BANB/BACkfwsAAH8BAFd/DwAAfwEAH38PAAB/BQD/ +fwsAAA/QD6QfAA/QD6QfAA7Q/smkDqQfAA3Q/cmtpA6kHwAfpB8AHqT+pQAeAB6k/qMAHgAepP6f +AB4AHqQgAB2k/qIAHwAcpP6jACAAHKT+pgAgABykIgAbpCMAGaT9o6oAIgAYpP2lmQAjABik/oAA +JAAWpP6oACYAE6QBpSkADaQCo/ylpJ0AfwsrAA9XLwAPVy8ADlf+SAAuAA1X/UgTAH8PLwAPHy8A +Dx8vAA4f/hoALgANH/0aBwB/Dy8AH/8fAB7//v4AHgAe//7fAB4AHv/+3wAeAB7//r8AHgAe//6N +AB4AHv/+SAAeAB3//fQIAB4AHf/+rgAfABz//f0sAB8AHP/+tQAgABv//fcrACAAG//+bQAhABr/ +/p0AIgAZ//3ABgAiABj//awFACMAFv/893gCACQAFf/9sCMAJgAS//zvnj4AKAAN//nqzrKNVw0A +fwsrAC8AD6QvAP6lpA2kLwD9/6OkDKQwAP6jpAykMQD+paQLpDIADKQzAP6lpAmkNQAJpH8OAAB/ +EAAAfxAAAC8A/pf/Df8vAP5B/w3/LwD9AcP/DP8wAP0v/P8L/zEA/mb/C/8yAP1r+f8J/zMA/TO7 +/wj/NQD7Q4zK4f8E/38OAAB/AgCkfw4AAH8QAAB/EAAAfwIA/38OAAAjpBsAI6QbACKkHAAhpP6j +ABsAIaQdAB+k/qMAHQAdpAGjHwAcpP2l/wB/DiAAfxAAAH8QAAAi//6MABoAIv/+QwAaACH//rsA +GwAg//35MgAbACD//mgAHAAe//37ZwAdAB3//cMvAB4AGf/66MeVPgEAfw4gADcAA6QD0DcAA6QD +0DcA/qOkAaT+ydAB0DcAA6T7rsnQ0AA2AAekOAAGpDgA/qqkBKQ6APqlpKOkpAB/Df8AOwADVzsA +A1c7AP5JVwFXOwD7E0hXVwB/Dv8AOwADHzsAAx87AP4aHwEfOwD7BxofHwB/Dv8ANwAH/zcA/v7/ +Bf83AP7c/wX/NwD+qf8F/zcA/lH/Bf84AP6m/wT/OAD9D6b/A/86APpPqNz//wB/Df8AfwEA0H8B +AKR/DgAAfwEAV38PAAB/AQAffw8AAH8CAP9/DgAAH9ADpBsAH9ADpBsAHtD+yaQCpBsAHdD9ya2k +AaT+owAaACKk/qMAGgAipBwAIaT+qgAbACCkfw4fAB9XHwAfVx8AHlf+SAAeAB1X/UgTAH8PHwAf +Hx8AHx8fAB4f/hoAHgAdH/0aBwB/Dx8AI/8bACL//v4AGgAi//7bABoAIv/+pwAaACL//k4AGgAh +//6jABsAIP/9qA8AGwAd//zbplEAfw4eAH8QAAB/EAAAfxAAAH8QAAB/EAAAfxAAAH8QAAB/EAAA +fwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/ +CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8K +AAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoA +AH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAA +fwoAAH8KAAB/CgAAfwoAAH8KAAB/CgAAfwoAAH8GQAB/BkAAfwZAAH8GQAAAAAH0AAAB9AAAAAAA +AAD6AAAA+gAAAAAAAAB9AAAAfQAAAAAAAAA+AAAAPgAAAAA= + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/03dd4e9c9ee257c7ae161057ebe7c8a3.msg b/share/extensions/tests/data/cmd/inkscape/03dd4e9c9ee257c7ae161057ebe7c8a3.msg new file mode 100644 index 0000000..0e85321 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/03dd4e9c9ee257c7ae161057ebe7c8a3.msg @@ -0,0 +1,25 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=953:951:1000:1000 --export-filename=guides_8.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="guides_8.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: guides_8.png + +iVBORw0KGgoAAAANSUhEUgAAAC8AAAAxCAYAAABK+/BHAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAB9JREFUaIHtwQEBAAAAgiD/r25I +QAEAAAAAAAAAABcGJC0AAXI8dYQAAAAASUVORK5CYII= + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/0ac64d7c051206284e72bf3e0800186d.msg b/share/extensions/tests/data/cmd/inkscape/0ac64d7c051206284e72bf3e0800186d.msg new file mode 100644 index 0000000..8ec387e --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/0ac64d7c051206284e72bf3e0800186d.msg @@ -0,0 +1,1025 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=47:49:953:951 --export-filename=guides_4.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="guides_4.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: guides_4.png + +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 +EkVERPyloCgiIl6b6zrFggnvvwv+1dNqREREZNYUFEVExGtzCYoFFz6gkCgiIrIwKCiKiIin5rBF +RsGFD2yBf5mXgkRERGTWFBRFRMRT5uyCYsGAOxUSRUREFhYFRRER8dQstsiwXPjgJvi/81qQiIiI +zJqCooiIeCoARyltcVGO5ZZGEv+5EjWJiIjI7CgoioiIp+4E24BDZU6xDdiqkCgiIrJwKSiKiIjn +yjS0sQ3YvAm+UdGCREREZFYUFEVExHPG2YOi7cIWhUQREZGFT0FRREQ8d5YRRZvSdNOv+1GPiIiI +zE7Q7wJERKQmTex8ahvwkU3woG/ViIiIyKxoRFFERDznvDGiaAMf3QQP+FmPiIiIzI6CooiIeG4Q +DgAW8NHNsN3vekRERERERGQBeADe7ncNIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi +IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi +IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi +IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi +IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi +IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi +IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiJSmwy/C6gX +X4flNqwzYJ0LF7pwgQGtQGLsTxOQcsE2IAMMAmlK/94D7DFgrwF7grDnztIxERERERERzykozoN7 +wFwDlxtwqwu3GXAjkPL4NqeBR1142IGdH4G9Hl9fRERERGTOXDC2wXID1huwDrgIWA0088Zgyfi/ +Q2mAZGDsnxlgwID9Lrzqwl4X9myBowa4lf/b1B8FRY98CSKt8E4XPgzcBrRMfD4YDRBOhYikgkRS +ISINIYIREzNoYgQNAiETM2SC62IXXZyCg110cIouxVGbQtoin7YoDBfJpy1sy5lawjEX/h3Yvh8e +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 +N/B+4HJgFXBusonaXh/1PS8fC+DWjXBv4CqPDbUFPhTB1RFcHMB5Sedpc89Tv2j8t5N3faU5YVGU +mtBNcEsAVyWdQ4n6Ri9cmXQISZLUmhx6KjWhAL6YdAYlKgzhU0mHkCRJrcuiKDWhXrgfh5u0rQhu +uxaeSjqHJElqXRZFqUnV4JMRlJLOoYYrpuE3kw4hSZJam0VRalLXwfYAPpN0DjXcb26EbUmHkCRJ +rc2iKDWxLPwv4KtJ51DD/E0vfCHpEJIkqfVZFKUmtgFqWfh54PtJZ9HciuDuyf3MJEmS5pxFUWpy +G6BWgfcHcGPSWTQ3Avj7CXjvBignnUWSJLUH91GUWshm+JUIPh9AZ9JZNCuKwGefhz/+DIRJh5Ek +Se3Doii1mBvhwgC+GMA7k86i0/LNDviNq2BH0kEkSVL7sShKLWoLvDGqb8r+74EFSefRSRkJ6vtj +fvUauCfpMJIkqX1ZFKUWtxW6S3BFAO+K4OIAzgMywKqks7WzCA4D+4B9KXgugodK8J2fg1zS2SRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiS1hP8P8aK48RBoJeEAAAAASUVORK5CYII= + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/106011cd22941d59371660c2b4abf75c.msg b/share/extensions/tests/data/cmd/inkscape/106011cd22941d59371660c2b4abf75c.msg new file mode 100644 index 0000000..b7ea54f --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/106011cd22941d59371660c2b4abf75c.msg @@ -0,0 +1,25 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=0:0:47:49 --export-filename=f0oo.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="f0oo.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: f0oo.png + +iVBORw0KGgoAAAANSUhEUgAAAC8AAAAxCAYAAABK+/BHAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAB9JREFUaIHtwQEBAAAAgiD/r25I +QAEAAAAAAAAAABcGJC0AAXI8dYQAAAAASUVORK5CYII= + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/1b0252977bd9eafe0af2686834decd6e.msg b/share/extensions/tests/data/cmd/inkscape/1b0252977bd9eafe0af2686834decd6e.msg new file mode 100644 index 0000000..4240d93 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/1b0252977bd9eafe0af2686834decd6e.msg @@ -0,0 +1,28 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=47:951:953:1000 --export-filename=guides_7.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="guides_7.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: guides_7.png + +iVBORw0KGgoAAAANSUhEUgAAA4oAAAAxCAYAAACcaFfTAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMNJREFUeJztwTEBAAAAwqD1T20M +H6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAATga19wABjrwhtgAAAABJRU5ErkJggg== + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/1f50fa6d71e2543dd18dfc807fe56018.msg b/share/extensions/tests/data/cmd/inkscape/1f50fa6d71e2543dd18dfc807fe56018.msg new file mode 100644 index 0000000..d712d0c --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/1f50fa6d71e2543dd18dfc807fe56018.msg @@ -0,0 +1,25 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=953:951:1000:1000 --export-filename=f8oo.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="f8oo.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: f8oo.png + +iVBORw0KGgoAAAANSUhEUgAAAC8AAAAxCAYAAABK+/BHAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAB9JREFUaIHtwQEBAAAAgiD/r25I +QAEAAAAAAAAAABcGJC0AAXI8dYQAAAAASUVORK5CYII= + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/2911a62f9cfb6cf1e950992909bf7efb.msg b/share/extensions/tests/data/cmd/inkscape/2911a62f9cfb6cf1e950992909bf7efb.msg new file mode 100644 index 0000000..c63e43e --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/2911a62f9cfb6cf1e950992909bf7efb.msg @@ -0,0 +1,25 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=953:0:1000:49 --export-filename=f2oo.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="f2oo.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: f2oo.png + +iVBORw0KGgoAAAANSUhEUgAAAC8AAAAxCAYAAABK+/BHAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAB9JREFUaIHtwQEBAAAAgiD/r25I +QAEAAAAAAAAAABcGJC0AAXI8dYQAAAAASUVORK5CYII= + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/2aee6b61724952541d5114e64953ae32.msg b/share/extensions/tests/data/cmd/inkscape/2aee6b61724952541d5114e64953ae32.msg new file mode 100644 index 0000000..f5f42a9 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/2aee6b61724952541d5114e64953ae32.msg @@ -0,0 +1,28 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=0:49:47:951 --export-filename=guides_3.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="guides_3.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: guides_3.png + +iVBORw0KGgoAAAANSUhEUgAAAC8AAAOGCAYAAAB83MkXAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAALxJREFUeJztwQENAAAAwqD3T20P +BxQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCDAZoM +AAG1xnPlAAAAAElFTkSuQmCC + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/37d9cfceb38aade8eda7ad77e4ad0c29.msg b/share/extensions/tests/data/cmd/inkscape/37d9cfceb38aade8eda7ad77e4ad0c29.msg new file mode 100644 index 0000000..e7f73cd --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/37d9cfceb38aade8eda7ad77e4ad0c29.msg @@ -0,0 +1,25 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=953:0:1000:49 --export-filename=guides_2.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="guides_2.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: guides_2.png + +iVBORw0KGgoAAAANSUhEUgAAAC8AAAAxCAYAAABK+/BHAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAB9JREFUaIHtwQEBAAAAgiD/r25I +QAEAAAAAAAAAABcGJC0AAXI8dYQAAAAASUVORK5CYII= + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/581055ce0e3ef5df0c1ab22982a51513.msg b/share/extensions/tests/data/cmd/inkscape/581055ce0e3ef5df0c1ab22982a51513.msg new file mode 100644 index 0000000..952ed60 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/581055ce0e3ef5df0c1ab22982a51513.msg @@ -0,0 +1,94 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area-page --export-background-opacity=0 --export-dpi=96 --export-filename=Slide3.png --export-id-only --export-id=webslicer-layer --export-type=png Slide3.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="Slide3.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: Slide3.png + +iVBORw0KGgoAAAANSUhEUgAAA+gAAAPoCAYAAABNo9TkAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAETtJREFUeJzt18FNwwAQAMFzsEQp +6YeKaDAtpAy+8HQByLp9zFSw350BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAACAfzi2AwAA6Pqd+ZyZj+0O4B7HzM92A5dzOwAAgLSvmXluRwC3 ++d4O4PLYDgAAAAAMOgAAACQYdAAAAAgw6AAAABBg0AEAACDAoAMAAECAQQcAAIAAgw4AAAABBh0A +AAACDDoAAAAEGHQAAAAIMOgAAAAQYNABAAAgwKADAABAgEEHAACAAIMOAAAAAQYdAAAAAgw6AAAA +BBh0AAAACDDoAAAAEGDQAQAAIMCgAwAAQIBBBwAAgACDDgAAAAEGHQAAAAIMOgAAAAQYdAAAAAgw +6AAAABBg0AEAACDAoAMAAECAQQcAAIAAgw4AAAABBh0AAAACDDoAAAAEGHQAAAAIMOgAAAAQYNAB +AAAgwKADAABAgEEHAACAAIMOAAAAAQYdAAAAAgw6AAAABBh0AAAACDDoAAAAEGDQAQAAIMCgAwAA +QIBBBwAAgACDDgAAAAEGHQAAAAIMOgAAAAQYdAAAAAgw6AAAABBg0AEAACDAoAMAAECAQQcAAIAA +gw4AAAABBh0AAAACDDoAAAAEGHQAAAAIMOgAAAAQYNABAAAgwKADAABAgEEHAACAAIMOAAAAAQYd +AAAAAgw6AAAABBh0AAAACDDoAAAAEGDQAQAAIMCgAwAAQIBBBwAAgACDDgAAAAEGHQAAAAIMOgAA +AAQYdAAAAAgw6AAAABBg0AEAACDAoAMAAECAQQcAAIAAgw4AAAABBh0AAAACDDoAAAAEGHQAAAAI +MOgAAAAQYNABAAAgwKADAABAgEEHAACAAIMOAAAAAQYdAAAAAgw6AAAABBh0AAAACDDoAAAAEGDQ +AQAAIMCgAwAAQIBBBwAAgACDDgAAAAEGHQAAAAIMOgAAAAQYdAAAAAgw6AAAABBg0AEAACDAoAMA +AECAQQcAAIAAgw4AAAABBh0AAAACDDoAAAAEGHQAAAAIMOgAAAAQYNABAAAgwKADAABAgEEHAACA +AIMOAAAAAQYdAAAAAgw6AAAABBh0AAAACDDoAAAAEGDQAQAAIMCgAwAAQIBBBwAAgACDDgAAAAEG +HQAAAAIMOgAAAAQYdAAAAAgw6AAAABBg0AEAACDAoAMAAECAQQcAAIAAgw4AAAABBh0AAAACDDoA +AAAEGHQAAAAIMOgAAAAQYNABAAAgwKADAABAgEEHAACAAIMOAAAAAQYdAAAAAgw6AAAABBh0AAAA +CDDoAAAAEGDQAQAAIMCgAwAAQIBBBwAAgACDDgAAAAEGHQAAAAIMOgAAAAQYdAAAAAgw6AAAABBg +0AEAACDAoAMAAECAQQcAAIAAgw4AAAABBh0AAAACDDoAAAAEGHQAAAAIMOgAAAAQYNABAAAgwKAD +AABAgEEHAACAAIMOAAAAAQYdAAAAAgw6AAAABBh0AAAACDDoAAAAEGDQAQAAIMCgAwAAQIBBBwAA +gACDDgAAAAEGHQAAAAIMOgAAAAQYdAAAAAgw6AAAABBg0AEAACDAoAMAAECAQQcAAIAAgw4AAAAB +Bh0AAAACDDoAAAAEGHQAAAAIMOgAAAAQYNABAAAgwKADAABAgEEHAACAAIMOAAAAAQYdAAAAAgw6 +AAAABBh0AAAACDDoAAAAEGDQAQAAIMCgAwAAQIBBBwAAgACDDgAAAAEGHQAAAAIMOgAAAAQYdAAA +AAgw6AAAABBg0AEAACDAoAMAAECAQQcAAIAAgw4AAAAB53YAAABpr5l5b0cAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA +Hf4AWlAGPhJKDywAAAAASUVORK5CYII= + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/67e526c4c1e53207e5e46274e8cdfcc0.msg b/share/extensions/tests/data/cmd/inkscape/67e526c4c1e53207e5e46274e8cdfcc0.msg new file mode 100644 index 0000000..fe04dbb --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/67e526c4c1e53207e5e46274e8cdfcc0.msg @@ -0,0 +1,17 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-dpi=1 --export-filename=Slide3.png --export-type=png Slide3.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="Slide3.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: Slide3.png + +iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAAnAAAAJwEqCZFPAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAADFJREFUGJVjYBgwwIhL4j8DgywD +A4McVNFRFjyGKDEwMDhC2XgVPmFgYDhOqhMHEgAAHzAE3cTGGWYAAAAASUVORK5CYII= + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/7e16d346b278485f0e308b7c8eda301d.msg b/share/extensions/tests/data/cmd/inkscape/7e16d346b278485f0e308b7c8eda301d.msg new file mode 100644 index 0000000..3e6bff1 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/7e16d346b278485f0e308b7c8eda301d.msg @@ -0,0 +1,25 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=0:0:47:49 --export-filename=guides_0.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="guides_0.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: guides_0.png + +iVBORw0KGgoAAAANSUhEUgAAAC8AAAAxCAYAAABK+/BHAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAB9JREFUaIHtwQEBAAAAgiD/r25I +QAEAAAAAAAAAABcGJC0AAXI8dYQAAAAASUVORK5CYII= + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/818d4c30d07def36e80e32df12023124.msg b/share/extensions/tests/data/cmd/inkscape/818d4c30d07def36e80e32df12023124.msg new file mode 100644 index 0000000..4be9ba8 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/818d4c30d07def36e80e32df12023124.msg @@ -0,0 +1,261 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area-page --export-background-opacity=0 --export-dpi=96 --export-filename=Slide1.png --export-id-only --export-id=layer1 --export-type=png Slide1.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="Slide1.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: Slide1.png + +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 +2Ek5j6wOn1P2Y5fgPawnN6te3fY1139bvWDO9H6TZW4277X3q95Tfap6Ydtvl1s0Pv+531FHVH9V +fbz6bGP73nlemUdW/1SdVr1tB88DAAAwJcdU76o+WD1o8jhq8tyfVt+vfr+6Z/XM6qK2JekHNRL3 +V06mD2wkh69tNKU+qjqx+u6cso9Z0nez9h1Rba7uNJm+SnXJ5HHlybw7TZY5fDJ9UvWVRnL+kOpX +qm9XL51T7n2qrdWmyfQh1ecmj0dX96p+r20XUG45Wf5/q6dU920k+OdVV53GGwUAAODS/qU6ed68 +q1UXVw+cN/9F1TvnTP9MdX71mOrvqy83+jrPelL1tSnGujf4WPWMyf8Prz4yeTxsMu8Z1UfnLH9S +dVbb15j/dvW9OdPzE/SnVT9s54PAzSbovzln3v7VOY0LAAAAa4I+6MB6cPPG+exqbd8sff/GCN+z +Pl79biNxr1G7+/3lCHAdO6W6a3X85O8pk/l3rV41b96sDzYS7lmnV4dVBzQuoMx3p0kZ39pNLG+f +8/8FjZr6q+/2HQAArBISdGA9uFq1pbrdDp57+7zpTzTOfV+pPrnEce0NTmnUgB/cSMYfN5n/4kYt ++S0b3Q3m+tG86Usmf3f2nXRAY3vtzo7K3bSjBQEAViMJOrDWbO3St9/6ZmNAsT9sNFvfmcMatbqv +bCTzL2z0aZ61ZQdls2vvbmyTX2k0QX934zP8icm8LZN5i/GF6icXWQYAwKpnFHdgrTmrUWM+1ymT ++c9q+wuPG9p2z+0NjRG+z2k0g39oo8/0/5lX9mxTay6bH1UfqP64en9jYLYfT/5/yuS5Hy9yHW+s +bl/9/Lz5S3W7PQCAFSFBB9aakxq3Rzu9+nB1/0YC+ODGiO2fqV42We70xq25qp7YuJXaQxr9nD/Y +SCpfVB09WeYtjT7pn5uU/dSlfjPrxCnVldq+r/nbdjBvT72m+rvqddX/a1xoeX9j8DgAgHVDU05g +LTqycV/tKzZGDD9jMv/QRsJ+3eoHjWT9lOrCxsjg36w+NKecDY1bcn1tUk6NWtlbNJLLM+bMZ+eu +Xt2mel/1jcm8q1W3rf6n+vqcZW89+fv+OfOuXN2xMTr/JY3m8ber/r3RfH7ua49r3Hf9C9V/Nbbz +YY3+729s1ODPuntjm//vYt4cAAAAwELduLrpEpV9zeoOS1Q2AAAArCv/0miJsBSe1GhtAgAsEX3Q +AWDvceVGDbsB9gAAAGCJvLTRZ3/u47GT546s3jyZd261ufrHat/J849tDJ44t3n8LzfGb7hl4/72 +88t+ydK9FQAAAFjbdtbE/Z2NAfuuOZm+SWMAvbkj4b+qOq26QnWDxi30fmfO85q4AwAAwGW0owT9 +Zxo13vMHj/uj6lNzpg9u3GLw1dUnq9e3/d1eJOgAsMT2WekAAJbQpup61QHVVxr3ON+VQ6trNZr6 +fqHRDBjWuhtO/v5V29+27sqN42PWudXDGrci/GZ153nLAwBLTIIOrDXPa/tmt7NeVP3GnOnfrp7a +6Hs7m2i/abLM/FrAa1UvrO41md5UnVU9q3rBVKKGlXNQtaXR5/yS3Sx7l8bxcmh11erspQ0NAABY +y55Xfas6Zt7jmnOWeWYjEfmDRnJSdZtGc94vV0fMWfbI6quNJr23nsw7uNH895JqZvpvAZbMP1Zv +mDfvNo2a8Dvu5rW3qi6qHl79R/Xp6sA5zz+hOnM6YQIAAOvB8xoJ9c4c3agBPH4Hz12t0Yz3b+fM +e0n1g0Zt4XzPaCTp192jSNe/q1cPmDfvHtXPzpv3gMmyNS6C3Lq6UvXE6tmNFgv7TV77R9XTG02t +rzCvnGOr201e+6TGhZi77iCuG1e/Pyn7d6qbL+xtrWlPbbQQuWl1VONiU9Wp1WerW0ym96luVj1i +Mn1o9aXqH3YyXWP7XFIdNyl77oUuAABgL7S7BP2ERnPeQ3fy/InV96qNjcTwB9Xf72TZIxs1j0/Z +o0jXvxs2Pp/ZPs4HNPrvn1/tP2+ZG0ymT6re2xgT4M2Tx37Vn1SnV6+oXtZIMk9v++14YvXhRuL4 +r9V/Ni7G/MmcZR7UqAX+j8aFmJMnr9lbHFG9sbFfz73N2mGNz/6S6rzq4uqcxme3ofFZzq8xv3Xb +atRrHDMvrb6d26wBAACNBP3H1WvmPY6cPP9v7boZ7h82kourN/qeb61+dxfLf7uRDLJj36h+c/L/ +3aqvTx6zNdu/OZmedVLjAsod5pUz/4LKQY1E/PfmzDuxsb1+bs68J1QXVNeYTL+1ev68si53Gd7H +3uKKjVHdr922e6ADAKvExpUOAGAPbK7OmPeYHfzqgHY9sNXscwe2rbZwd8tffo8jXf9OaVsyftfJ +9I7mzfXB6t3z5n2/OqSRfD+60cT9rEZz9bk+1xjsb9bfN7b9PSbT32sMdHavtg2EetFC3tA6d071 +8cZYDBevbCgAwHxGcQfWoh9UT97Jc99t14NhXWPOcpvmzZtvw+S59y40wL3I26v/2/gsj2uMhr+h +MVr+psatup407zU7upf2Axo15F+ovtioKT+yS/dz/sq86YsbNfTXmkz/UaMZ9hsaTbn/uzEa/8cW +9K4AAFaAGnRgvfmfRvPoW+7k+ds2Bss6u/pO9fnGKNc7ctNG7bkEfedOadR837Uxmv7bJ/OOmcw7 +pEvXoO/o/vIvrJ7bGNDtQdWDG32i5ztkB/MObVxwqTE+wT0bA8k9cvLcqRnQDAAAYOp2N0jcwY1+ +42/v0n1s79OomX3cnHm/0egT/fPzlt23elv1zTRx353Tq/dXn5kz77OTeafNW/ak6tXz5u3X2Ab3 +mTPvsEaz97m3DDux0Vz96nPm3aqxTXd2keWoyfO33d2bAABYaZq4A+vNudVDq9dX72ncF/qcRoL2 ++MaAby+ds/zfNQYs+4/qxY0a+CtWj2mMQH7vxqB07NzbG5/tC+fMO6Vx8ePvLsPrL2wk889sNI+/ +XKMLwzk7WPY7jaT9WY2R4p9dvaWx3Wps9/+pPtFI+h/TuMjyyYW8IQCAlSBBB9aaTzQGgtuVdzSa +p/9uI3E8sNGv+VGN2tutc5bd0riN1EMb94S+Z6Pv8nurhzRGEmfXXl0d3hhNf9ZJjWbmJ81b9v1t +//nPekj1nOrPGi0gnttonj6/SftHqpc3+rUfUf1X298G782N1hC/1Oif/tFGP/gfLewtAQAAADtz +YqOGHABgXTJIHAAAAKwCmrgDsFZ8pjEIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +sAgbVjoAgFlba+tKxwDr1Qbf+Xunma7Uxq7flq5THTV5XKe6fHVotV91YHVwtWnF4mQxNlfnVudV +F1Q/qH5cndGGzmhrX6rOqL7QTGetXJjAZeHLGlg1JOiwdCToe4GZ9mljR7el21W3r46pfmqFo2J1 +ObP6SBt6T1t7b/XhZrpgpYMCtvFlDawaEnRYOhL0deo5HdpF3a2t/UJ1v+qglQ6JNeX86pTqDdVr +1bDDyvNlDawaEnRYOhL0deQvunw/7mHVg6s7V/uubECsExdXp7ahkzqwV/cH/XilA4K9kS9rYNWQ +oMPSkaCvA8/s+m3uV6tfqw5b6XBY186tXl29oJk+vdLBwN7ElzWwakjQYelI0NewmY6rnlIdl+3I +8tpava16djOdusKxwF7BSR5YNSTosHQk6GvQMzq2LR1f3XulQ4HqbW3sKR3fh1Y6EFjPfFkDq4YE +HZaOBH0NeWZHt7nnNAZ9s91YTbZWJ1d/3EyfW+lgYD1y0l91ZiQoq8qMY2QZSdBh6UjQ14CZ9mlD +v9fWZqr9Vzoc2IULqj9vNH2/aKWDgfVk40oHAACw13tGN63e39b+LMk5q9/+1QnVh3pGx650MLCe +SNABAFbKTBub6Rlt6cPVMSsdDizQT7el9/X0TmhGXgHT4EACAFgJMx1Wval6WrXPCkcDe2qfSbeM +/+o5HbrSwcBaJ0EHAFhuo0n7h6p7rHQoMCX36sI+3ky3WOlAYC2ToAMALKeZ7teW3lcdtdKhwJRd +s3pnM91npQOBtUqCDgCwXGZ6aPWa6sCVDgWWyIHVfzbTL690ILAWSdABAJbD0/u16pXVvisdCiyx +TdU/9fR+daUDgbXGgCTAuuE+z6xnW2vrSsfAIjy932prz28FzlMbN9RPHVk3u0pd97C6ziF11KF1 +1YNq/33qwH3r4P1qkzPomrR5a517YZ13cV14SX3zh3XG9+tLP6gvnF0fO7M++93asvxnkE1t7e+b +ab9metGyrx3WKKfiVWfGD7BVZcYxsowWm4BI0FnPHB9r2NN7QFt7TcvYcvGmV677HF13uFbd6moj +AWfvdc6F9YGv17u/Wq87rT511rKufksbemAndPKyrhXWKF/Wq44EfXWRoC8nCQjsnONjjXpGt2pL +76gOWOpV3ejIevTN6v43GDXksDNf/H6d/Nk68eP1me8syyrPa2N37vg+tCxrgzXMl/WqI0FfXSTo +y0kCAjvn+FiDntl12tz7qyst1Sr23Vj3v2E94di607WWai2sZ+/4cv3dh+rk0+qSLUu6qm9Vt2mm +Ly/pWmCN82W96kjQVxcJ+nKSgMDOOT7WmJn2rz5Y3WQpit+4oR5243r6Xeq6asuZgs+fXSe8o076 +3yXtr/6JDutWPbELl2wNsMYZxR0AYPqe0xIl53c7qj7++HrFL0rOmZ7rH1b/+oD66OPquOss2Wpu +2tk9a8lKh3VAgg4AME0z3a367WkXe8j+9ZIWF7YyAAAWTklEQVR711sfUTdZskbz7O1ueuU65Zfr +NQ+qw5dm5ITf7endZUlKhnVAgg4AMC3P6dDqxKbcpeDuR9Vnf6Mee0xt0FmBZfCgn6pPPaHuOv3a +9I1t7WX9WVecesmwDkjQAQCm5cL+rLr6tIrbUD359vXmR9RVrjCtUuGyueoV6i3/p/7gtlMfxOKa +XdCfTrdIWB8k6AAA0/CMblT96rSK229TvfqB9Zy71ia15qyQTRvquXevVz2wLrdpqkU/rpluMNUS +YR2QoAMATMOWnltNJYU5cN963cPqwTeaRmmweA+5Ub3uoXXAPlMrcp/qz6dWGqwTEnQAgMWa6bjq +56ZR1OX3rTf/Ut3jutMoDabnnterN/3SuIA0JfdppjtPrTRYByToAACLN5VbR23aUK98QN3xWtMo +Dabvztcet/jbOL1uF267BnNI0AEAFmOmm1e3mUZRf3WPuu/R0ygJls79bzD6pU/J7XpGx0ytNFjj +JOgAAIvzW9Mo5OE3qd++1TRKgqX3e7cZ/dKnYku/PqWSYM2ToAMA7KmZjqgeuthirn5wvXAqPdhh ++bz43nXN6dzN/OHNdNhUSoI1ToIOALCnNvToav/FFvPP96tDF10KLK9D9q+//4WpFHVAG3rkVEqC +NU6CDgCwp7b2gMUW8YAb1nHXmUYwsPx+9rp1n2mMm7C1+0+hFFjzJOgAAHvi2R1ZHbuYIvbdWM+5 +25TigRXy3LuPfXmRbtuzO3wK4cCaJkEHANgTF3WvFvlb6pdvWtfX85Y17ujD6xE/vehiNnVR95hC +OLCmSdABAPbMzy+2gCcsqv4dVo8nTucOBPeeSimwhknQAQD2zHGLefHtr1k3v+q0QoGV9TNXqdtc +fdHF3HUKocCaJkEHAFioP+2q1RGLKWIKTYJhVZnCPn2lZrrKFEKBNUuCDgCwUBd3k8W8fEP189ef +UiywSvzC0WPfXqQbL74IWLsk6AAAC7eoJOKmV6mrHzytUGB1uMbBdeMrLbKQDYu7+AVrnQQdAGDh +FpVETKGvLqxKt7nGIgvYqgadvZsEHQBg4a61mBcvupYRVqkp7NuLOrZgrZOgAwAs3KIaqEvQWa+m +sG9fcQphwJolQQcAWLhDFvPiq+l/zjo1hbEVJOjs1SToAAALt6g05Ir7TSsMWF2msG9L0NmrSdAB +ABZuUQn6QRJ01qkr7r/4IqYQBqxZEnQAgIVbVIq936ZphQGryxT2bZev2KtJ0AEAAGAVkKADAADA +KiBBBwAAgFVAgg4AAACrgAQdAAAAVgEJOgAAAKwCEnQAAABYBSToAAAAsApI0AEAAGAVkKADAADA +KiBBBwAAgFVAgg4AAACrgAQdAAAAVgEJOgAAAKwCEnQAAABYBSToAAAAsApI0AEAAGAVkKADAADA +KiBBBwAAgFVAgg4AAACrgAQdAAAAVgEJOgDAwl24qBdvnlYYsLpMYd9e1LEFa50EHQBg4c5dzIt/ +KAVhnfrBBYsvYgphwJolQQcAWLhFJejnSNBZp85ZfIJ+zhTCgDVLgg4AsHCLquX7xqLSe1i9vvnD +RRchQWevJkEHAFi4RSURnzprWmHA6jKFfVuCzl5Ngg4AsHBfXcyL/1eCzjr16cXv21+ZQhiwZknQ +AQAW7lOLefH7vjatMGB1mcK+/ekphAFrlgQdAGDhFpVEfOLb9RUNeVlnvnLOFGrQNyzu4hesdRJ0 +AICF2nfxScSbPz+NQGD1eMPpUyhkHzXo7N0k6AAAC/XUzqy+s5gi/uWTU4oFVolXLH6fPqun9u0p +hAJrlgQdAGDPvH0xL37f1+ojZ04rFFhZH/tWfeAbiy7mbVMIBdY0CToAwJ7Y0BsXW8SLPjSNQGDl +/c0HplLMoo8pWOsk6AAAe2Jrb6w2L6aIl3+iPrOohvKw8k777lSat2/ucr1lCuHAmiZBBwDYEzOd +XS2q3vCSLfXURTWUh5X3+28d+/Iivben9L0phANrmgQdAGDPnbzYAl57Wr3tjGmEAsvvzV+oN07j +jgQb+s8plAJrngQdAGBP7dc/VucttphHvbbOPn8K8cAy+v4F9bg3TKWo89rav0ylJFjjJOgAAHvq +j/t+ddJii/nGD+s33jSFeGAZPfYN9bVzp1LUKyddRmCvJ0EHAFiMjb1gGsW8+tP1vP+ZRkmw9P7i +ffXvn5lacS+eWkmwxknQAQAW4/g+Vr1vGkX9wX/XyadNoyRYOv/+mXry9O5Y/u5m+ujUSoM1ToIO +ALBYG/qTaRSzZWs94j/r1C9PozSYvrd/qX755LGvTsXGnjalkmBdkKADACzWCb2jmspwWeddXPd8 +Rb3u9GmUBtPz5i/Uvf+1zr9kakW+tuN759RKg3VAgg4AMA2b+qNqKqnLhZvrwf82+qXDavCqT9f9 +Xj3V5PziNvXkqZUG64QEHQBgGp7WZ6t/mFZxF22uh//H6Jd+yZZplQoLc8mW+r23jn3xos1TLfol +PS3tRGAeCToAwPT8cfW1aRW2tfrL99XP/su4FRssp6+fW3d7+ZLcXeDL1VOnXiqsAxJ0AIBpmekH +beyXqqnWNb7jy3Wjv63nf2CKg3PBTmzdWi//RN30xfXOr0y9+C3Vo5tpOndQh3VGgg4AME3H9+7q +r6dd7DkX1pP+X9315fWxb027dBg+embd5Z/rka+ts89fklX8ZTOduiQlwzogQQcAmLbDemr1yaUo ++tQv1zEvqYf+e33ue0uxBvZGp323HvRvdYuXLkmt+ayPlduqwa5sWOkAmG9Gw7VVZcYxsoy2ju6W +e2yDcxrrmONjDZrp2tX7qysv1Sr22Vj3ObqecGwdd+3aYCuzAFu31ilfqhd9qF5/em1e2l+hZ1a3 +bqavLulaYI1zGl91JOiriwR9OUlAYOccH2vUTLes3lEduNSruuER9aifqfvfsK5/2FKvjbXsc9+r +k0+rl3181Jwvgx+3sTt1fB9ZlrXBGubLetWRoK8uEvTlJAGBnXN8rGFP7/5t7d9bxq6FN75S3ffo +uv0169ZXr0P2X641sxp9/4J6/9fr3V+p151en/nOsq5+cxt6QCf0umVdK6xRvqxXHQn66iJBX04S +ENg5x8caN9MTqhe2Atth44a6wRF1s6vUdQ+row6t6xxSP3FQ7b9PXf5ydfB+tckesiZt3lrnXlg/ +vqjOv6TO/GGd8f360g/qi2ePQd9O++4iTyB7bmsb+vVO6CUrs3pYe5yKVx0J+uoiQV9Oi01AgJ2T +oK8CMz2iOrHaZ6VDgWWwuXpsM/3TSgcCa4lR3AEAlsNMr2hDj6guXulQYIldUj1Kcg4LJ0EHAFgu +J3RS9cDqvJUOBZbIj6v7N9MrVjoQWIsk6AAAy2mm11fHVKetdCgwZV9oY7dppv9a6UBgrZKgAwAs +t5lOq25VnbzSocCUvLH9umXH96mVDgTWMgk6AMBKmOncRnP3E9IvnbXr4uqpzfQL/XHfX+lgYK2T +oAMArJSZtjTTM6pbVB9a6XBggT4xadL+7NyJBaZCgg4AsNJm+mR12zb0pMYgW7Cand+GntxPdUzH +95GVDgbWE/dEBVYN90GHpeM+6GvIM7t+m/vTRvN3243VZGv1b23qKT2tL650MLAeOekDq4YEHZaO +BH0NmunG1fHVg1Y6FKje1saerMYclpYva2DVkKDD0pGgr2Ez3bF6SvWz2Y4sry3VW6s/bab3rHQw +sDdwkgdWDQk6LB0J+jow0/Wqx0weh69wNKxv51QnVc9vps+sdDCwN/FlDawaEnRYOhL0dWSmA6uH +Vg+p7lLtu7IBsU5cVL2jek316mY6b4Xjgb2SL2tg1ZCgw9KRoK9Tf9HlO6/j2tqDqvtWB690SKwp +51enVP9Wvb6ZfrDC8cBez5c1sGpI0GHpSND3AjPt08aObku3q25fHVPdMNuebc6sPtKG3tPW3tth +fagnduFKBwVs44QNrBoSdFg6EvS91ExHtLHrt6XrVEfV///3oEZt+wHV5Sf/b1qxOFmMzdW51Y+q +Cyb/n1t9qQ2d0da+VJ1RfaGZvrtyYQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AP9fe3BIAAAAACDo/2tnWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4Bf2JDlg/m2H/wAAAABJ +RU5ErkJggg== + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/82de0e8eb29f071d78cebe7a4116e246.msg b/share/extensions/tests/data/cmd/inkscape/82de0e8eb29f071d78cebe7a4116e246.msg new file mode 100644 index 0000000..3f75f15 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/82de0e8eb29f071d78cebe7a4116e246.msg @@ -0,0 +1,435 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-filename=output.svg --export-type=svg --pdf-page=1 --pdf-poppler input.pdf + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="output.svg" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: output.svg + +PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxz +dmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxu +czpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6 +Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0 +cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw +L3N2ZyIKICAgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiCiAgIHht +bG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9k +aS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVz +cGFjZXMvaW5rc2NhcGUiCiAgIHNvZGlwb2RpOmRvY25hbWU9ImlucHV0LnBkZiIKICAgaWQ9InN2 +ZzE0MSIKICAgdmVyc2lvbj0iMS4yIgogICB2aWV3Qm94PSIwIDAgNjEyIDc5MiIKICAgaGVpZ2h0 +PSI3OTJwdCIKICAgd2lkdGg9IjYxMnB0Ij4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGEx +NDUiPgogICAgPHJkZjpSREY+CiAgICAgIDxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj4K +ICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6 +dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBl +L1N0aWxsSW1hZ2UiIC8+CiAgICAgIDwvY2M6V29yaz4KICAgIDwvcmRmOlJERj4KICA8L21ldGFk +YXRhPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0ibmFtZWR2aWV3MTQzIgogICAgIGlu +a3NjYXBlOndpbmRvdy1oZWlnaHQ9IjQ4MCIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjY0 +MCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5 +PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAg +ICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVy +Y29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxkZWZzCiAgICAg +aWQ9ImRlZnM3MCI+CiAgICA8ZwogICAgICAgaWQ9Imc2OCI+CiAgICAgIDxzeW1ib2wKICAgICAg +ICAgaWQ9ImdseXBoMC0wIgogICAgICAgICBvdmVyZmxvdz0idmlzaWJsZSI+CiAgICAgICAgPHBh +dGgKICAgICAgICAgICBpZD0icGF0aDIiCiAgICAgICAgICAgZD0iIgogICAgICAgICAgIHN0eWxl +PSJzdHJva2U6bm9uZTsiIC8+CiAgICAgIDwvc3ltYm9sPgogICAgICA8c3ltYm9sCiAgICAgICAg +IGlkPSJnbHlwaDAtMSIKICAgICAgICAgb3ZlcmZsb3c9InZpc2libGUiPgogICAgICAgIDxwYXRo +CiAgICAgICAgICAgaWQ9InBhdGg1IgogICAgICAgICAgIGQ9Ik0gMi42NDA2MjUgLTMuNzE4NzUg +TCAzLjc2NTYyNSAtMy43MTg3NSBDIDMuNDM3NSAtMi4yNSAzLjM0Mzc1IC0xLjgxMjUgMy4zNDM3 +NSAtMS4xNDA2MjUgQyAzLjM0Mzc1IC0xIDMuMzQzNzUgLTAuNzM0Mzc1IDMuNDIxODc1IC0wLjM5 +MDYyNSBDIDMuNTMxMjUgMC4wNDY4NzUgMy42NDA2MjUgMC4xMDkzNzUgMy43ODEyNSAwLjEwOTM3 +NSBDIDMuOTg0Mzc1IDAuMTA5Mzc1IDQuMjAzMTI1IC0wLjA2MjUgNC4yMDMxMjUgLTAuMjY1NjI1 +IEMgNC4yMDMxMjUgLTAuMzI4MTI1IDQuMjAzMTI1IC0wLjM0Mzc1IDQuMTQwNjI1IC0wLjQ4NDM3 +NSBDIDMuODQzNzUgLTEuMjAzMTI1IDMuODQzNzUgLTEuODU5Mzc1IDMuODQzNzUgLTIuMTQwNjI1 +IEMgMy44NDM3NSAtMi42NTYyNSAzLjkyMTg3NSAtMy4yMDMxMjUgNC4wMzEyNSAtMy43MTg3NSBM +IDUuMTU2MjUgLTMuNzE4NzUgQyA1LjI5Njg3NSAtMy43MTg3NSA1LjY1NjI1IC0zLjcxODc1IDUu +NjU2MjUgLTQuMDYyNSBDIDUuNjU2MjUgLTQuMjk2ODc1IDUuNDM3NSAtNC4yOTY4NzUgNS4yNSAt +NC4yOTY4NzUgTCAxLjkwNjI1IC00LjI5Njg3NSBDIDEuNjg3NSAtNC4yOTY4NzUgMS4zMTI1IC00 +LjI5Njg3NSAwLjg3NSAtMy44MjgxMjUgQyAwLjUzMTI1IC0zLjQzNzUgMC4yNjU2MjUgLTIuOTg0 +Mzc1IDAuMjY1NjI1IC0yLjkzNzUgQyAwLjI2NTYyNSAtMi45MjE4NzUgMC4yNjU2MjUgLTIuODI4 +MTI1IDAuMzkwNjI1IC0yLjgyODEyNSBDIDAuNDY4NzUgLTIuODI4MTI1IDAuNDg0Mzc1IC0yLjg3 +NSAwLjU0Njg3NSAtMi45NTMxMjUgQyAxLjAzMTI1IC0zLjcxODc1IDEuNjA5Mzc1IC0zLjcxODc1 +IDEuODEyNSAtMy43MTg3NSBMIDIuMzc1IC0zLjcxODc1IEMgMi4wNjI1IC0yLjUxNTYyNSAxLjUz +MTI1IC0xLjMxMjUgMS4xMDkzNzUgLTAuNDA2MjUgQyAxLjAzMTI1IC0wLjI1IDEuMDMxMjUgLTAu +MjM0Mzc1IDEuMDMxMjUgLTAuMTU2MjUgQyAxLjAzMTI1IDAuMDMxMjUgMS4xODc1IDAuMTA5Mzc1 +IDEuMzEyNSAwLjEwOTM3NSBDIDEuNjA5Mzc1IDAuMTA5Mzc1IDEuNjg3NSAtMC4xNzE4NzUgMS44 +MTI1IC0wLjUzMTI1IEMgMS45NTMxMjUgLTEgMS45NTMxMjUgLTEuMDE1NjI1IDIuMDc4MTI1IC0x +LjUxNTYyNSBaIE0gMi42NDA2MjUgLTMuNzE4NzUgIgogICAgICAgICAgIHN0eWxlPSJzdHJva2U6 +bm9uZTsiIC8+CiAgICAgIDwvc3ltYm9sPgogICAgICA8c3ltYm9sCiAgICAgICAgIGlkPSJnbHlw +aDAtMiIKICAgICAgICAgb3ZlcmZsb3c9InZpc2libGUiPgogICAgICAgIDxwYXRoCiAgICAgICAg +ICAgaWQ9InBhdGg4IgogICAgICAgICAgIGQ9Ik0gMi44NTkzNzUgLTYuODEyNSBDIDIuODU5Mzc1 +IC02LjgxMjUgMi44NTkzNzUgLTYuOTIxODc1IDIuNzM0Mzc1IC02LjkyMTg3NSBDIDIuNSAtNi45 +MjE4NzUgMS43ODEyNSAtNi44NDM3NSAxLjUxNTYyNSAtNi44MTI1IEMgMS40Mzc1IC02LjgxMjUg +MS4zMjgxMjUgLTYuNzk2ODc1IDEuMzI4MTI1IC02LjYyNSBDIDEuMzI4MTI1IC02LjUgMS40MjE4 +NzUgLTYuNSAxLjU2MjUgLTYuNSBDIDIuMDQ2ODc1IC02LjUgMi4wNjI1IC02LjQzNzUgMi4wNjI1 +IC02LjMyODEyNSBMIDIuMDMxMjUgLTYuMTI1IEwgMC41OTM3NSAtMC4zOTA2MjUgQyAwLjU0Njg3 +NSAtMC4yNSAwLjU0Njg3NSAtMC4yMzQzNzUgMC41NDY4NzUgLTAuMTcxODc1IEMgMC41NDY4NzUg +MC4wNjI1IDAuNzUgMC4xMDkzNzUgMC44NDM3NSAwLjEwOTM3NSBDIDAuOTY4NzUgMC4xMDkzNzUg +MS4xMDkzNzUgMC4wMTU2MjUgMS4xNzE4NzUgLTAuMDkzNzUgQyAxLjIxODc1IC0wLjE4NzUgMS42 +NzE4NzUgLTIuMDMxMjUgMS43MzQzNzUgLTIuMjgxMjUgQyAyLjA3ODEyNSAtMi4yNSAyLjg5MDYy +NSAtMi4wOTM3NSAyLjg5MDYyNSAtMS40Mzc1IEMgMi44OTA2MjUgLTEuMzU5Mzc1IDIuODkwNjI1 +IC0xLjMyODEyNSAyLjg1OTM3NSAtMS4yMTg3NSBDIDIuODQzNzUgLTEuMTA5Mzc1IDIuODI4MTI1 +IC0wLjk4NDM3NSAyLjgyODEyNSAtMC44NzUgQyAyLjgyODEyNSAtMC4yOTY4NzUgMy4yMTg3NSAw +LjEwOTM3NSAzLjczNDM3NSAwLjEwOTM3NSBDIDQuMDMxMjUgMC4xMDkzNzUgNC4zMTI1IC0wLjA0 +Njg3NSA0LjUzMTI1IC0wLjQyMTg3NSBDIDQuNzgxMjUgLTAuODU5Mzc1IDQuODkwNjI1IC0xLjQw +NjI1IDQuODkwNjI1IC0xLjQyMTg3NSBDIDQuODkwNjI1IC0xLjUzMTI1IDQuNzk2ODc1IC0xLjUz +MTI1IDQuNzY1NjI1IC0xLjUzMTI1IEMgNC42NzE4NzUgLTEuNTMxMjUgNC42NTYyNSAtMS40ODQz +NzUgNC42MjUgLTEuMzQzNzUgQyA0LjQyMTg3NSAtMC42MjUgNC4yMDMxMjUgLTAuMTA5Mzc1IDMu +NzY1NjI1IC0wLjEwOTM3NSBDIDMuNTYyNSAtMC4xMDkzNzUgMy40Mzc1IC0wLjIxODc1IDMuNDM3 +NSAtMC41NzgxMjUgQyAzLjQzNzUgLTAuNzUgMy40ODQzNzUgLTAuOTg0Mzc1IDMuNTE1NjI1IC0x +LjE0MDYyNSBDIDMuNTYyNSAtMS4zMTI1IDMuNTYyNSAtMS4zNDM3NSAzLjU2MjUgLTEuNDUzMTI1 +IEMgMy41NjI1IC0yLjA5Mzc1IDIuOTM3NSAtMi4zNzUgMi4wNzgxMjUgLTIuNSBDIDIuMzkwNjI1 +IC0yLjY3MTg3NSAyLjcxODc1IC0yLjk4NDM3NSAyLjkzNzUgLTMuMjM0Mzc1IEMgMy40MjE4NzUg +LTMuNzY1NjI1IDMuODc1IC00LjE4NzUgNC4zNTkzNzUgLTQuMTg3NSBDIDQuNDIxODc1IC00LjE4 +NzUgNC40Mzc1IC00LjE4NzUgNC40NTMxMjUgLTQuMTcxODc1IEMgNC41NzgxMjUgLTQuMTU2MjUg +NC41NzgxMjUgLTQuMTU2MjUgNC42NzE4NzUgLTQuMDkzNzUgQyA0LjY4NzUgLTQuMDkzNzUgNC42 +ODc1IC00LjA3ODEyNSA0LjcwMzEyNSAtNC4wNjI1IEMgNC4yMzQzNzUgLTQuMDMxMjUgNC4xNDA2 +MjUgLTMuNjQwNjI1IDQuMTQwNjI1IC0zLjUxNTYyNSBDIDQuMTQwNjI1IC0zLjM1OTM3NSA0LjI1 +IC0zLjE3MTg3NSA0LjUxNTYyNSAtMy4xNzE4NzUgQyA0Ljc4MTI1IC0zLjE3MTg3NSA1LjA2MjUg +LTMuMzkwNjI1IDUuMDYyNSAtMy43ODEyNSBDIDUuMDYyNSAtNC4wNzgxMjUgNC44MjgxMjUgLTQu +NDA2MjUgNC4zOTA2MjUgLTQuNDA2MjUgQyA0LjEwOTM3NSAtNC40MDYyNSAzLjY1NjI1IC00LjMy +ODEyNSAyLjkzNzUgLTMuNTMxMjUgQyAyLjU5Mzc1IC0zLjE1NjI1IDIuMjAzMTI1IC0yLjc1IDEu +ODI4MTI1IC0yLjYwOTM3NSBaIE0gMi44NTkzNzUgLTYuODEyNSAiCiAgICAgICAgICAgc3R5bGU9 +InN0cm9rZTpub25lOyIgLz4KICAgICAgPC9zeW1ib2w+CiAgICAgIDxzeW1ib2wKICAgICAgICAg +aWQ9ImdseXBoMS0wIgogICAgICAgICBvdmVyZmxvdz0idmlzaWJsZSI+CiAgICAgICAgPHBhdGgK +ICAgICAgICAgICBpZD0icGF0aDExIgogICAgICAgICAgIGQ9IiIKICAgICAgICAgICBzdHlsZT0i +c3Ryb2tlOm5vbmU7IiAvPgogICAgICA8L3N5bWJvbD4KICAgICAgPHN5bWJvbAogICAgICAgICBp +ZD0iZ2x5cGgxLTEiCiAgICAgICAgIG92ZXJmbG93PSJ2aXNpYmxlIj4KICAgICAgICA8cGF0aAog +ICAgICAgICAgIGlkPSJwYXRoMTQiCiAgICAgICAgICAgZD0iTSAzLjUxNTYyNSAtMS4yNjU2MjUg +TCAzLjI4MTI1IC0xLjI2NTYyNSBDIDMuMjY1NjI1IC0xLjEwOTM3NSAzLjE4NzUgLTAuNzAzMTI1 +IDMuMDkzNzUgLTAuNjQwNjI1IEMgMy4wNDY4NzUgLTAuNTkzNzUgMi41MTU2MjUgLTAuNTkzNzUg +Mi40MDYyNSAtMC41OTM3NSBMIDEuMTI1IC0wLjU5Mzc1IEMgMS44NTkzNzUgLTEuMjM0Mzc1IDIu +MTA5Mzc1IC0xLjQzNzUgMi41MTU2MjUgLTEuNzY1NjI1IEMgMy4wMzEyNSAtMi4xNzE4NzUgMy41 +MTU2MjUgLTIuNjA5Mzc1IDMuNTE1NjI1IC0zLjI2NTYyNSBDIDMuNTE1NjI1IC00LjEwOTM3NSAy +Ljc4MTI1IC00LjYyNSAxLjg5MDYyNSAtNC42MjUgQyAxLjAzMTI1IC00LjYyNSAwLjQzNzUgLTQu +MDE1NjI1IDAuNDM3NSAtMy4zNzUgQyAwLjQzNzUgLTMuMDMxMjUgMC43MzQzNzUgLTIuOTg0Mzc1 +IDAuODEyNSAtMi45ODQzNzUgQyAwLjk2ODc1IC0yLjk4NDM3NSAxLjE3MTg3NSAtMy4xMDkzNzUg +MS4xNzE4NzUgLTMuMzU5Mzc1IEMgMS4xNzE4NzUgLTMuNDg0Mzc1IDEuMTI1IC0zLjczNDM3NSAw +Ljc2NTYyNSAtMy43MzQzNzUgQyAwLjk4NDM3NSAtNC4yMTg3NSAxLjQ1MzEyNSAtNC4zNzUgMS43 +ODEyNSAtNC4zNzUgQyAyLjQ4NDM3NSAtNC4zNzUgMi44NDM3NSAtMy44MjgxMjUgMi44NDM3NSAt +My4yNjU2MjUgQyAyLjg0Mzc1IC0yLjY1NjI1IDIuNDA2MjUgLTIuMTg3NSAyLjE4NzUgLTEuOTM3 +NSBMIDAuNTE1NjI1IC0wLjI2NTYyNSBDIDAuNDM3NSAtMC4yMDMxMjUgMC40Mzc1IC0wLjE4NzUg +MC40Mzc1IDAgTCAzLjMxMjUgMCBaIE0gMy41MTU2MjUgLTEuMjY1NjI1ICIKICAgICAgICAgICBz +dHlsZT0ic3Ryb2tlOm5vbmU7IiAvPgogICAgICA8L3N5bWJvbD4KICAgICAgPHN5bWJvbAogICAg +ICAgICBpZD0iZ2x5cGgxLTIiCiAgICAgICAgIG92ZXJmbG93PSJ2aXNpYmxlIj4KICAgICAgICA8 +cGF0aAogICAgICAgICAgIGlkPSJwYXRoMTciCiAgICAgICAgICAgZD0iTSA1LjM1OTM3NSAtMi4z +NDM3NSBDIDUuNDUzMTI1IC0yLjM0Mzc1IDUuNjA5Mzc1IC0yLjM0Mzc1IDUuNjA5Mzc1IC0yLjUx +NTYyNSBDIDUuNjA5Mzc1IC0yLjY4NzUgNS40NTMxMjUgLTIuNjg3NSA1LjM1OTM3NSAtMi42ODc1 +IEwgMC43NSAtMi42ODc1IEMgMC42NTYyNSAtMi42ODc1IDAuNDg0Mzc1IC0yLjY4NzUgMC40ODQz +NzUgLTIuNTE1NjI1IEMgMC40ODQzNzUgLTIuMzQzNzUgMC42NDA2MjUgLTIuMzQzNzUgMC43NSAt +Mi4zNDM3NSBaIE0gNS4zNTkzNzUgLTAuODEyNSBDIDUuNDUzMTI1IC0wLjgxMjUgNS42MDkzNzUg +LTAuODEyNSA1LjYwOTM3NSAtMC45Njg3NSBDIDUuNjA5Mzc1IC0xLjE1NjI1IDUuNDUzMTI1IC0x +LjE1NjI1IDUuMzU5Mzc1IC0xLjE1NjI1IEwgMC43NSAtMS4xNTYyNSBDIDAuNjQwNjI1IC0xLjE1 +NjI1IDAuNDg0Mzc1IC0xLjE1NjI1IDAuNDg0Mzc1IC0wLjk2ODc1IEMgMC40ODQzNzUgLTAuODEy +NSAwLjY1NjI1IC0wLjgxMjUgMC43NSAtMC44MTI1IFogTSA1LjM1OTM3NSAtMC44MTI1ICIKICAg +ICAgICAgICBzdHlsZT0ic3Ryb2tlOm5vbmU7IiAvPgogICAgICA8L3N5bWJvbD4KICAgICAgPHN5 +bWJvbAogICAgICAgICBpZD0iZ2x5cGgxLTMiCiAgICAgICAgIG92ZXJmbG93PSJ2aXNpYmxlIj4K +ICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJwYXRoMjAiCiAgICAgICAgICAgZD0iTSAyLjMy +ODEyNSAtNC40Mzc1IEMgMi4zMjgxMjUgLTQuNjI1IDIuMzI4MTI1IC00LjYyNSAyLjEyNSAtNC42 +MjUgQyAxLjY3MTg3NSAtNC4xODc1IDEuMDQ2ODc1IC00LjE4NzUgMC43NjU2MjUgLTQuMTg3NSBM +IDAuNzY1NjI1IC0zLjkzNzUgQyAwLjkyMTg3NSAtMy45Mzc1IDEuMzkwNjI1IC0zLjkzNzUgMS43 +NjU2MjUgLTQuMTI1IEwgMS43NjU2MjUgLTAuNTc4MTI1IEMgMS43NjU2MjUgLTAuMzQzNzUgMS43 +NjU2MjUgLTAuMjUgMS4wNzgxMjUgLTAuMjUgTCAwLjgxMjUgLTAuMjUgTCAwLjgxMjUgMCBDIDAu +OTM3NSAwIDEuNzk2ODc1IC0wLjAzMTI1IDIuMDQ2ODc1IC0wLjAzMTI1IEMgMi4yNjU2MjUgLTAu +MDMxMjUgMy4xNDA2MjUgMCAzLjI5Njg3NSAwIEwgMy4yOTY4NzUgLTAuMjUgTCAzLjAzMTI1IC0w +LjI1IEMgMi4zMjgxMjUgLTAuMjUgMi4zMjgxMjUgLTAuMzQzNzUgMi4zMjgxMjUgLTAuNTc4MTI1 +IFogTSAyLjMyODEyNSAtNC40Mzc1ICIKICAgICAgICAgICBzdHlsZT0ic3Ryb2tlOm5vbmU7IiAv +PgogICAgICA8L3N5bWJvbD4KICAgICAgPHN5bWJvbAogICAgICAgICBpZD0iZ2x5cGgyLTAiCiAg +ICAgICAgIG92ZXJmbG93PSJ2aXNpYmxlIj4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJw +YXRoMjMiCiAgICAgICAgICAgZD0iIgogICAgICAgICAgIHN0eWxlPSJzdHJva2U6bm9uZTsiIC8+ +CiAgICAgIDwvc3ltYm9sPgogICAgICA8c3ltYm9sCiAgICAgICAgIGlkPSJnbHlwaDItMSIKICAg +ICAgICAgb3ZlcmZsb3c9InZpc2libGUiPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InBh +dGgyNiIKICAgICAgICAgICBkPSJNIDEuMzEyNSAtMy4yNjU2MjUgTCAxLjMxMjUgLTMuNTE1NjI1 +IEMgMS4zMTI1IC02LjAzMTI1IDIuNTQ2ODc1IC02LjM5MDYyNSAzLjA2MjUgLTYuMzkwNjI1IEMg +My4yOTY4NzUgLTYuMzkwNjI1IDMuNzE4NzUgLTYuMzI4MTI1IDMuOTM3NSAtNS45ODQzNzUgQyAz +Ljc4MTI1IC01Ljk4NDM3NSAzLjM5MDYyNSAtNS45ODQzNzUgMy4zOTA2MjUgLTUuNTQ2ODc1IEMg +My4zOTA2MjUgLTUuMjM0Mzc1IDMuNjI1IC01LjA3ODEyNSAzLjg0Mzc1IC01LjA3ODEyNSBDIDQg +LTUuMDc4MTI1IDQuMzEyNSAtNS4xNzE4NzUgNC4zMTI1IC01LjU2MjUgQyA0LjMxMjUgLTYuMTU2 +MjUgMy44NzUgLTYuNjQwNjI1IDMuMDQ2ODc1IC02LjY0MDYyNSBDIDEuNzY1NjI1IC02LjY0MDYy +NSAwLjQyMTg3NSAtNS4zNTkzNzUgMC40MjE4NzUgLTMuMTU2MjUgQyAwLjQyMTg3NSAtMC40ODQz +NzUgMS41NzgxMjUgMC4yMTg3NSAyLjUgMC4yMTg3NSBDIDMuNjA5Mzc1IDAuMjE4NzUgNC41NjI1 +IC0wLjcxODc1IDQuNTYyNSAtMi4wMzEyNSBDIDQuNTYyNSAtMy4yOTY4NzUgMy42NzE4NzUgLTQu +MjUgMi41NjI1IC00LjI1IEMgMS44OTA2MjUgLTQuMjUgMS41MTU2MjUgLTMuNzUgMS4zMTI1IC0z +LjI2NTYyNSBaIE0gMi41IC0wLjA2MjUgQyAxLjg3NSAtMC4wNjI1IDEuNTc4MTI1IC0wLjY1NjI1 +IDEuNTE1NjI1IC0wLjgxMjUgQyAxLjMyODEyNSAtMS4yODEyNSAxLjMyODEyNSAtMi4wNzgxMjUg +MS4zMjgxMjUgLTIuMjUgQyAxLjMyODEyNSAtMy4wMzEyNSAxLjY1NjI1IC00LjAzMTI1IDIuNTQ2 +ODc1IC00LjAzMTI1IEMgMi43MTg3NSAtNC4wMzEyNSAzLjE3MTg3NSAtNC4wMzEyNSAzLjQ4NDM3 +NSAtMy40MDYyNSBDIDMuNjU2MjUgLTMuMDQ2ODc1IDMuNjU2MjUgLTIuNTMxMjUgMy42NTYyNSAt +Mi4wNDY4NzUgQyAzLjY1NjI1IC0xLjU2MjUgMy42NTYyNSAtMS4wNjI1IDMuNDg0Mzc1IC0wLjcw +MzEyNSBDIDMuMTg3NSAtMC4xMDkzNzUgMi43MzQzNzUgLTAuMDYyNSAyLjUgLTAuMDYyNSBaIE0g +Mi41IC0wLjA2MjUgIgogICAgICAgICAgIHN0eWxlPSJzdHJva2U6bm9uZTsiIC8+CiAgICAgIDwv +c3ltYm9sPgogICAgICA8c3ltYm9sCiAgICAgICAgIGlkPSJnbHlwaDItMiIKICAgICAgICAgb3Zl +cmZsb3c9InZpc2libGUiPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InBhdGgyOSIKICAg +ICAgICAgICBkPSJNIDYuODQzNzUgLTMuMjY1NjI1IEMgNyAtMy4yNjU2MjUgNy4xODc1IC0zLjI2 +NTYyNSA3LjE4NzUgLTMuNDUzMTI1IEMgNy4xODc1IC0zLjY1NjI1IDcgLTMuNjU2MjUgNi44NTkz +NzUgLTMuNjU2MjUgTCAwLjg5MDYyNSAtMy42NTYyNSBDIDAuNzUgLTMuNjU2MjUgMC41NjI1IC0z +LjY1NjI1IDAuNTYyNSAtMy40NTMxMjUgQyAwLjU2MjUgLTMuMjY1NjI1IDAuNzUgLTMuMjY1NjI1 +IDAuODkwNjI1IC0zLjI2NTYyNSBaIE0gNi44NTkzNzUgLTEuMzI4MTI1IEMgNyAtMS4zMjgxMjUg +Ny4xODc1IC0xLjMyODEyNSA3LjE4NzUgLTEuNTMxMjUgQyA3LjE4NzUgLTEuNzE4NzUgNyAtMS43 +MTg3NSA2Ljg0Mzc1IC0xLjcxODc1IEwgMC44OTA2MjUgLTEuNzE4NzUgQyAwLjc1IC0xLjcxODc1 +IDAuNTYyNSAtMS43MTg3NSAwLjU2MjUgLTEuNTMxMjUgQyAwLjU2MjUgLTEuMzI4MTI1IDAuNzUg +LTEuMzI4MTI1IDAuODkwNjI1IC0xLjMyODEyNSBaIE0gNi44NTkzNzUgLTEuMzI4MTI1ICIKICAg +ICAgICAgICBzdHlsZT0ic3Ryb2tlOm5vbmU7IiAvPgogICAgICA8L3N5bWJvbD4KICAgICAgPHN5 +bWJvbAogICAgICAgICBpZD0iZ2x5cGgyLTMiCiAgICAgICAgIG92ZXJmbG93PSJ2aXNpYmxlIj4K +ICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJwYXRoMzIiCiAgICAgICAgICAgZD0iTSAxLjc2 +NTYyNSAtNi45MjE4NzUgTCAwLjMyODEyNSAtNi44MTI1IEwgMC4zMjgxMjUgLTYuNSBDIDEuMDMx +MjUgLTYuNSAxLjEwOTM3NSAtNi40Mzc1IDEuMTA5Mzc1IC01LjkzNzUgTCAxLjEwOTM3NSAtMC43 +NSBDIDEuMTA5Mzc1IC0wLjMxMjUgMSAtMC4zMTI1IDAuMzI4MTI1IC0wLjMxMjUgTCAwLjMyODEy +NSAwIEMgMC42NTYyNSAtMC4wMTU2MjUgMS4xODc1IC0wLjAzMTI1IDEuNDM3NSAtMC4wMzEyNSBD +IDEuNjg3NSAtMC4wMzEyNSAyLjE3MTg3NSAtMC4wMTU2MjUgMi41NDY4NzUgMCBMIDIuNTQ2ODc1 +IC0wLjMxMjUgQyAxLjg3NSAtMC4zMTI1IDEuNzY1NjI1IC0wLjMxMjUgMS43NjU2MjUgLTAuNzUg +WiBNIDEuNzY1NjI1IC02LjkyMTg3NSAiCiAgICAgICAgICAgc3R5bGU9InN0cm9rZTpub25lOyIg +Lz4KICAgICAgPC9zeW1ib2w+CiAgICAgIDxzeW1ib2wKICAgICAgICAgaWQ9ImdseXBoMi00Igog +ICAgICAgICBvdmVyZmxvdz0idmlzaWJsZSI+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0i +cGF0aDM1IgogICAgICAgICAgIGQ9Ik0gMS43NjU2MjUgLTQuNDA2MjUgTCAwLjM3NSAtNC4yOTY4 +NzUgTCAwLjM3NSAtMy45ODQzNzUgQyAxLjAxNTYyNSAtMy45ODQzNzUgMS4xMDkzNzUgLTMuOTIx +ODc1IDEuMTA5Mzc1IC0zLjQzNzUgTCAxLjEwOTM3NSAtMC43NSBDIDEuMTA5Mzc1IC0wLjMxMjUg +MSAtMC4zMTI1IDAuMzI4MTI1IC0wLjMxMjUgTCAwLjMyODEyNSAwIEMgMC42NDA2MjUgLTAuMDE1 +NjI1IDEuMTg3NSAtMC4wMzEyNSAxLjQyMTg3NSAtMC4wMzEyNSBDIDEuNzgxMjUgLTAuMDMxMjUg +Mi4xMjUgLTAuMDE1NjI1IDIuNDY4NzUgMCBMIDIuNDY4NzUgLTAuMzEyNSBDIDEuNzk2ODc1IC0w +LjMxMjUgMS43NjU2MjUgLTAuMzU5Mzc1IDEuNzY1NjI1IC0wLjc1IFogTSAxLjc5Njg3NSAtNi4x +NDA2MjUgQyAxLjc5Njg3NSAtNi40NTMxMjUgMS41NjI1IC02LjY3MTg3NSAxLjI4MTI1IC02LjY3 +MTg3NSBDIDAuOTY4NzUgLTYuNjcxODc1IDAuNzUgLTYuNDA2MjUgMC43NSAtNi4xNDA2MjUgQyAw +Ljc1IC01Ljg3NSAwLjk2ODc1IC01LjYwOTM3NSAxLjI4MTI1IC01LjYwOTM3NSBDIDEuNTYyNSAt +NS42MDkzNzUgMS43OTY4NzUgLTUuODI4MTI1IDEuNzk2ODc1IC02LjE0MDYyNSBaIE0gMS43OTY4 +NzUgLTYuMTQwNjI1ICIKICAgICAgICAgICBzdHlsZT0ic3Ryb2tlOm5vbmU7IiAvPgogICAgICA8 +L3N5bWJvbD4KICAgICAgPHN5bWJvbAogICAgICAgICBpZD0iZ2x5cGgyLTUiCiAgICAgICAgIG92 +ZXJmbG93PSJ2aXNpYmxlIj4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJwYXRoMzgiCiAg +ICAgICAgICAgZD0iTSAxLjA5Mzc1IC0zLjQyMTg3NSBMIDEuMDkzNzUgLTAuNzUgQyAxLjA5Mzc1 +IC0wLjMxMjUgMC45ODQzNzUgLTAuMzEyNSAwLjMxMjUgLTAuMzEyNSBMIDAuMzEyNSAwIEMgMC42 +NzE4NzUgLTAuMDE1NjI1IDEuMTcxODc1IC0wLjAzMTI1IDEuNDUzMTI1IC0wLjAzMTI1IEMgMS43 +MDMxMjUgLTAuMDMxMjUgMi4yMTg3NSAtMC4wMTU2MjUgMi41NjI1IDAgTCAyLjU2MjUgLTAuMzEy +NSBDIDEuODkwNjI1IC0wLjMxMjUgMS43ODEyNSAtMC4zMTI1IDEuNzgxMjUgLTAuNzUgTCAxLjc4 +MTI1IC0yLjU5Mzc1IEMgMS43ODEyNSAtMy42MjUgMi41IC00LjE4NzUgMy4xMjUgLTQuMTg3NSBD +IDMuNzY1NjI1IC00LjE4NzUgMy44NzUgLTMuNjU2MjUgMy44NzUgLTMuMDc4MTI1IEwgMy44NzUg +LTAuNzUgQyAzLjg3NSAtMC4zMTI1IDMuNzY1NjI1IC0wLjMxMjUgMy4wOTM3NSAtMC4zMTI1IEwg +My4wOTM3NSAwIEMgMy40Mzc1IC0wLjAxNTYyNSAzLjk1MzEyNSAtMC4wMzEyNSA0LjIxODc1IC0w +LjAzMTI1IEMgNC40Njg3NSAtMC4wMzEyNSA1IC0wLjAxNTYyNSA1LjMyODEyNSAwIEwgNS4zMjgx +MjUgLTAuMzEyNSBDIDQuNjcxODc1IC0wLjMxMjUgNC41NjI1IC0wLjMxMjUgNC41NjI1IC0wLjc1 +IEwgNC41NjI1IC0yLjU5Mzc1IEMgNC41NjI1IC0zLjYyNSA1LjI2NTYyNSAtNC4xODc1IDUuOTA2 +MjUgLTQuMTg3NSBDIDYuNTMxMjUgLTQuMTg3NSA2LjY0MDYyNSAtMy42NTYyNSA2LjY0MDYyNSAt +My4wNzgxMjUgTCA2LjY0MDYyNSAtMC43NSBDIDYuNjQwNjI1IC0wLjMxMjUgNi41MzEyNSAtMC4z +MTI1IDUuODU5Mzc1IC0wLjMxMjUgTCA1Ljg1OTM3NSAwIEMgNi4yMDMxMjUgLTAuMDE1NjI1IDYu +NzE4NzUgLTAuMDMxMjUgNi45ODQzNzUgLTAuMDMxMjUgQyA3LjI1IC0wLjAzMTI1IDcuNzY1NjI1 +IC0wLjAxNTYyNSA4LjEwOTM3NSAwIEwgOC4xMDkzNzUgLTAuMzEyNSBDIDcuNTkzNzUgLTAuMzEy +NSA3LjM0Mzc1IC0wLjMxMjUgNy4zMjgxMjUgLTAuNjA5Mzc1IEwgNy4zMjgxMjUgLTIuNTE1NjI1 +IEMgNy4zMjgxMjUgLTMuMzc1IDcuMzI4MTI1IC0zLjY3MTg3NSA3LjAxNTYyNSAtNC4wMzEyNSBD +IDYuODc1IC00LjIwMzEyNSA2LjU0Njg3NSAtNC40MDYyNSA1Ljk2ODc1IC00LjQwNjI1IEMgNS4x +NDA2MjUgLTQuNDA2MjUgNC42ODc1IC0zLjgxMjUgNC41MzEyNSAtMy40MjE4NzUgQyA0LjM5MDYy +NSAtNC4yOTY4NzUgMy42NTYyNSAtNC40MDYyNSAzLjIwMzEyNSAtNC40MDYyNSBDIDIuNDY4NzUg +LTQuNDA2MjUgMiAtMy45ODQzNzUgMS43MTg3NSAtMy4zNTkzNzUgTCAxLjcxODc1IC00LjQwNjI1 +IEwgMC4zMTI1IC00LjI5Njg3NSBMIDAuMzEyNSAtMy45ODQzNzUgQyAxLjAxNTYyNSAtMy45ODQz +NzUgMS4wOTM3NSAtMy45MjE4NzUgMS4wOTM3NSAtMy40MjE4NzUgWiBNIDEuMDkzNzUgLTMuNDIx +ODc1ICIKICAgICAgICAgICBzdHlsZT0ic3Ryb2tlOm5vbmU7IiAvPgogICAgICA8L3N5bWJvbD4K +ICAgICAgPHN5bWJvbAogICAgICAgICBpZD0iZ2x5cGgyLTYiCiAgICAgICAgIG92ZXJmbG93PSJ2 +aXNpYmxlIj4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJwYXRoNDEiCiAgICAgICAgICAg +ZD0iTSAyLjkzNzUgLTYuMzc1IEMgMi45Mzc1IC02LjYyNSAyLjkzNzUgLTYuNjQwNjI1IDIuNzAz +MTI1IC02LjY0MDYyNSBDIDIuMDc4MTI1IC02IDEuMjAzMTI1IC02IDAuODkwNjI1IC02IEwgMC44 +OTA2MjUgLTUuNjg3NSBDIDEuMDkzNzUgLTUuNjg3NSAxLjY3MTg3NSAtNS42ODc1IDIuMTg3NSAt +NS45NTMxMjUgTCAyLjE4NzUgLTAuNzgxMjUgQyAyLjE4NzUgLTAuNDIxODc1IDIuMTU2MjUgLTAu +MzEyNSAxLjI2NTYyNSAtMC4zMTI1IEwgMC45NTMxMjUgLTAuMzEyNSBMIDAuOTUzMTI1IDAgQyAx +LjI5Njg3NSAtMC4wMzEyNSAyLjE1NjI1IC0wLjAzMTI1IDIuNTYyNSAtMC4wMzEyNSBDIDIuOTUz +MTI1IC0wLjAzMTI1IDMuODI4MTI1IC0wLjAzMTI1IDQuMTcxODc1IDAgTCA0LjE3MTg3NSAtMC4z +MTI1IEwgMy44NTkzNzUgLTAuMzEyNSBDIDIuOTUzMTI1IC0wLjMxMjUgMi45Mzc1IC0wLjQyMTg3 +NSAyLjkzNzUgLTAuNzgxMjUgWiBNIDIuOTM3NSAtNi4zNzUgIgogICAgICAgICAgIHN0eWxlPSJz +dHJva2U6bm9uZTsiIC8+CiAgICAgIDwvc3ltYm9sPgogICAgICA8c3ltYm9sCiAgICAgICAgIGlk +PSJnbHlwaDMtMCIKICAgICAgICAgb3ZlcmZsb3c9InZpc2libGUiPgogICAgICAgIDxwYXRoCiAg +ICAgICAgICAgaWQ9InBhdGg0NCIKICAgICAgICAgICBkPSIiCiAgICAgICAgICAgc3R5bGU9InN0 +cm9rZTpub25lOyIgLz4KICAgICAgPC9zeW1ib2w+CiAgICAgIDxzeW1ib2wKICAgICAgICAgaWQ9 +ImdseXBoMy0xIgogICAgICAgICBvdmVyZmxvdz0idmlzaWJsZSI+CiAgICAgICAgPHBhdGgKICAg +ICAgICAgICBpZD0icGF0aDQ3IgogICAgICAgICAgIGQ9Ik0gMC44NDM3NSAtMC40Mzc1IEMgMC44 +MjgxMjUgLTAuMzQzNzUgMC43ODEyNSAtMC4xNzE4NzUgMC43ODEyNSAtMC4xNTYyNSBDIDAuNzgx +MjUgMCAwLjkwNjI1IDAuMDYyNSAxLjAxNTYyNSAwLjA2MjUgQyAxLjE0MDYyNSAwLjA2MjUgMS4y +NSAtMC4wMTU2MjUgMS4yOTY4NzUgLTAuMDc4MTI1IEMgMS4zMjgxMjUgLTAuMTQwNjI1IDEuMzc1 +IC0wLjM3NSAxLjQyMTg3NSAtMC41MTU2MjUgQyAxLjQ1MzEyNSAtMC42NDA2MjUgMS41MzEyNSAt +MC45Njg3NSAxLjU2MjUgLTEuMTQwNjI1IEMgMS42MDkzNzUgLTEuMjk2ODc1IDEuNjU2MjUgLTEu +NDUzMTI1IDEuNjg3NSAtMS42MDkzNzUgQyAxLjc2NTYyNSAtMS44OTA2MjUgMS43ODEyNSAtMS45 +NTMxMjUgMS45ODQzNzUgLTIuMjM0Mzc1IEMgMi4xNzE4NzUgLTIuNTE1NjI1IDIuNSAtMi44NzUg +My4wMzEyNSAtMi44NzUgQyAzLjQyMTg3NSAtMi44NzUgMy40Mzc1IC0yLjUxNTYyNSAzLjQzNzUg +LTIuMzkwNjI1IEMgMy40Mzc1IC0xLjk2ODc1IDMuMTQwNjI1IC0xLjIwMzEyNSAzLjAzMTI1IC0w +LjkwNjI1IEMgMi45NTMxMjUgLTAuNzAzMTI1IDIuOTIxODc1IC0wLjY0MDYyNSAyLjkyMTg3NSAt +MC41MzEyNSBDIDIuOTIxODc1IC0wLjE1NjI1IDMuMjE4NzUgMC4wNjI1IDMuNTc4MTI1IDAuMDYy +NSBDIDQuMjgxMjUgMC4wNjI1IDQuNTc4MTI1IC0wLjg5MDYyNSA0LjU3ODEyNSAtMSBDIDQuNTc4 +MTI1IC0xLjA5Mzc1IDQuNSAtMS4wOTM3NSA0LjQ2ODc1IC0xLjA5Mzc1IEMgNC4zNzUgLTEuMDkz +NzUgNC4zNzUgLTEuMDQ2ODc1IDQuMzQzNzUgLTAuOTY4NzUgQyA0LjE4NzUgLTAuNDA2MjUgMy44 +NzUgLTAuMTI1IDMuNjA5Mzc1IC0wLjEyNSBDIDMuNDUzMTI1IC0wLjEyNSAzLjQyMTg3NSAtMC4y +MTg3NSAzLjQyMTg3NSAtMC4zNzUgQyAzLjQyMTg3NSAtMC41MzEyNSAzLjQ2ODc1IC0wLjYyNSAz +LjU5Mzc1IC0wLjkzNzUgQyAzLjY3MTg3NSAtMS4xNTYyNSAzLjk1MzEyNSAtMS44OTA2MjUgMy45 +NTMxMjUgLTIuMjgxMjUgQyAzLjk1MzEyNSAtMi45NTMxMjUgMy40MjE4NzUgLTMuMDc4MTI1IDMu +MDQ2ODc1IC0zLjA3ODEyNSBDIDIuNDY4NzUgLTMuMDc4MTI1IDIuMDc4MTI1IC0yLjcxODc1IDEu +ODc1IC0yLjQzNzUgQyAxLjgyODEyNSAtMi45MjE4NzUgMS40MjE4NzUgLTMuMDc4MTI1IDEuMTI1 +IC0zLjA3ODEyNSBDIDAuODI4MTI1IC0zLjA3ODEyNSAwLjY3MTg3NSAtMi44NTkzNzUgMC41Nzgx +MjUgLTIuNzAzMTI1IEMgMC40MjE4NzUgLTIuNDM3NSAwLjMyODEyNSAtMi4wNDY4NzUgMC4zMjgx +MjUgLTIgQyAwLjMyODEyNSAtMS45MjE4NzUgMC40MjE4NzUgLTEuOTIxODc1IDAuNDUzMTI1IC0x +LjkyMTg3NSBDIDAuNTQ2ODc1IC0xLjkyMTg3NSAwLjU0Njg3NSAtMS45Mzc1IDAuNTkzNzUgLTIu +MTI1IEMgMC43MDMxMjUgLTIuNTMxMjUgMC44NDM3NSAtMi44NzUgMS4xMDkzNzUgLTIuODc1IEMg +MS4yOTY4NzUgLTIuODc1IDEuMzQzNzUgLTIuNzE4NzUgMS4zNDM3NSAtMi41MzEyNSBDIDEuMzQz +NzUgLTIuNDA2MjUgMS4yODEyNSAtMi4xNDA2MjUgMS4yMTg3NSAtMS45NTMxMjUgQyAxLjE3MTg3 +NSAtMS43NjU2MjUgMS4xMDkzNzUgLTEuNDg0Mzc1IDEuMDc4MTI1IC0xLjMyODEyNSBaIE0gMC44 +NDM3NSAtMC40Mzc1ICIKICAgICAgICAgICBzdHlsZT0ic3Ryb2tlOm5vbmU7IiAvPgogICAgICA8 +L3N5bWJvbD4KICAgICAgPHN5bWJvbAogICAgICAgICBpZD0iZ2x5cGgzLTIiCiAgICAgICAgIG92 +ZXJmbG93PSJ2aXNpYmxlIj4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJwYXRoNTAiCiAg +ICAgICAgICAgZD0iTSAyLjE4NzUgLTQuNjI1IEMgMi4xODc1IC00LjY0MDYyNSAyLjIwMzEyNSAt +NC43MzQzNzUgMi4yMDMxMjUgLTQuNzM0Mzc1IEMgMi4yMDMxMjUgLTQuNzgxMjUgMi4xODc1IC00 +Ljg0Mzc1IDIuMDkzNzUgLTQuODQzNzUgQyAxLjk1MzEyNSAtNC44NDM3NSAxLjM3NSAtNC43ODEy +NSAxLjIwMzEyNSAtNC43NjU2MjUgQyAxLjE1NjI1IC00Ljc2NTYyNSAxLjA0Njg3NSAtNC43NSAx +LjA0Njg3NSAtNC42MDkzNzUgQyAxLjA0Njg3NSAtNC41MTU2MjUgMS4xNTYyNSAtNC41MTU2MjUg +MS4yMzQzNzUgLTQuNTE1NjI1IEMgMS41NjI1IC00LjUxNTYyNSAxLjU2MjUgLTQuNDUzMTI1IDEu +NTYyNSAtNC40MDYyNSBDIDEuNTYyNSAtNC4zNTkzNzUgMS41NDY4NzUgLTQuMzEyNSAxLjU0Njg3 +NSAtNC4yNSBMIDAuNTYyNSAtMC4zMTI1IEMgMC41MTU2MjUgLTAuMTg3NSAwLjUxNTYyNSAtMC4x +NzE4NzUgMC41MTU2MjUgLTAuMTU2MjUgQyAwLjUxNTYyNSAtMC4wNDY4NzUgMC42MDkzNzUgMC4w +NjI1IDAuNzY1NjI1IDAuMDYyNSBDIDAuOTUzMTI1IDAuMDYyNSAxLjAzMTI1IC0wLjA2MjUgMS4w +NzgxMjUgLTAuMjE4NzUgQyAxLjA5Mzc1IC0wLjI1IDEuMzkwNjI1IC0xLjQ4NDM3NSAxLjQyMTg3 +NSAtMS41NzgxMjUgQyAxLjkyMTg3NSAtMS41MzEyNSAyLjMxMjUgLTEuMzU5Mzc1IDIuMzEyNSAt +MSBDIDIuMzEyNSAtMC45Njg3NSAyLjMxMjUgLTAuOTM3NSAyLjI5Njg3NSAtMC44NTkzNzUgQyAy +LjI2NTYyNSAtMC43NjU2MjUgMi4yNjU2MjUgLTAuNzE4NzUgMi4yNjU2MjUgLTAuNjQwNjI1IEMg +Mi4yNjU2MjUgLTAuMTU2MjUgMi42NzE4NzUgMC4wNjI1IDMuMDE1NjI1IDAuMDYyNSBDIDMuNjg3 +NSAwLjA2MjUgMy44OTA2MjUgLTAuOTg0Mzc1IDMuODkwNjI1IC0xIEMgMy44OTA2MjUgLTEuMDkz +NzUgMy44MTI1IC0xLjA5Mzc1IDMuNzgxMjUgLTEuMDkzNzUgQyAzLjY4NzUgLTEuMDkzNzUgMy42 +NzE4NzUgLTEuMDQ2ODc1IDMuNjQwNjI1IC0wLjkyMTg3NSBDIDMuNTYyNSAtMC42MjUgMy4zNzUg +LTAuMTI1IDMuMDMxMjUgLTAuMTI1IEMgMi44NDM3NSAtMC4xMjUgMi43ODEyNSAtMC4yOTY4NzUg +Mi43ODEyNSAtMC40ODQzNzUgQyAyLjc4MTI1IC0wLjYwOTM3NSAyLjc4MTI1IC0wLjYyNSAyLjgy +ODEyNSAtMC43OTY4NzUgQyAyLjg0Mzc1IC0wLjgyODEyNSAyLjg1OTM3NSAtMC45Mzc1IDIuODU5 +Mzc1IC0xLjAxNTYyNSBDIDIuODU5Mzc1IC0xLjY0MDYyNSAyLjAzMTI1IC0xLjczNDM3NSAxLjcz +NDM3NSAtMS43NSBDIDEuOTM3NSAtMS44NzUgMi4xODc1IC0yLjEwOTM3NSAyLjMxMjUgLTIuMjE4 +NzUgQyAyLjY3MTg3NSAtMi41NDY4NzUgMy4wMTU2MjUgLTIuODc1IDMuNDA2MjUgLTIuODc1IEMg +My40ODQzNzUgLTIuODc1IDMuNTc4MTI1IC0yLjg1OTM3NSAzLjY0MDYyNSAtMi43ODEyNSBDIDMu +MzQzNzUgLTIuNzM0Mzc1IDMuMjgxMjUgLTIuNSAzLjI4MTI1IC0yLjM5MDYyNSBDIDMuMjgxMjUg +LTIuMjUgMy4zOTA2MjUgLTIuMTQwNjI1IDMuNTQ2ODc1IC0yLjE0MDYyNSBDIDMuNzUgLTIuMTQw +NjI1IDMuOTUzMTI1IC0yLjI5Njg3NSAzLjk1MzEyNSAtMi41NzgxMjUgQyAzLjk1MzEyNSAtMi44 +MTI1IDMuNzgxMjUgLTMuMDc4MTI1IDMuNDIxODc1IC0zLjA3ODEyNSBDIDMuMDE1NjI1IC0zLjA3 +ODEyNSAyLjY1NjI1IC0yLjc4MTI1IDIuMjk2ODc1IC0yLjQ1MzEyNSBDIDIgLTIuMTg3NSAxLjc4 +MTI1IC0xLjk2ODc1IDEuNDg0Mzc1IC0xLjg0Mzc1IFogTSAyLjE4NzUgLTQuNjI1ICIKICAgICAg +ICAgICBzdHlsZT0ic3Ryb2tlOm5vbmU7IiAvPgogICAgICA8L3N5bWJvbD4KICAgICAgPHN5bWJv +bAogICAgICAgICBpZD0iZ2x5cGg0LTAiCiAgICAgICAgIG92ZXJmbG93PSJ2aXNpYmxlIj4KICAg +ICAgICA8cGF0aAogICAgICAgICAgIGlkPSJwYXRoNTMiCiAgICAgICAgICAgZD0iIgogICAgICAg +ICAgIHN0eWxlPSJzdHJva2U6bm9uZTsiIC8+CiAgICAgIDwvc3ltYm9sPgogICAgICA8c3ltYm9s +CiAgICAgICAgIGlkPSJnbHlwaDQtMSIKICAgICAgICAgb3ZlcmZsb3c9InZpc2libGUiPgogICAg +ICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InBhdGg1NiIKICAgICAgICAgICBkPSJNIDYuNDA2MjUg +LTEuNTc4MTI1IEMgNS41MzEyNSAtMC45Mzc1IDUuNDIxODc1IC0wLjAxNTYyNSA1LjQyMTg3NSAt +MC4wMTU2MjUgQyA1LjQyMTg3NSAwLjA5Mzc1IDUuNDg0Mzc1IDAuMDkzNzUgNS41NzgxMjUgMC4w +OTM3NSBDIDUuNzAzMTI1IDAuMDkzNzUgNS43MzQzNzUgMC4wOTM3NSA1Ljc2NTYyNSAtMC4wMzEy +NSBDIDUuODEyNSAtMC4yMDMxMjUgNS45MjE4NzUgLTAuNjQwNjI1IDYuMjgxMjUgLTEuMDMxMjUg +QyA2LjY4NzUgLTEuNDUzMTI1IDcuMDMxMjUgLTEuNTYyNSA3LjMyODEyNSAtMS42NTYyNSBDIDcu +MzU5Mzc1IC0xLjY3MTg3NSA3LjM5MDYyNSAtMS43MDMxMjUgNy4zOTA2MjUgLTEuNzM0Mzc1IEMg +Ny4zOTA2MjUgLTEuODEyNSA3LjM1OTM3NSAtMS44MjgxMjUgNy4yNjU2MjUgLTEuODU5Mzc1IEMg +Ni4yNjU2MjUgLTIuMTcxODc1IDUuOTA2MjUgLTIuODkwNjI1IDUuNzUgLTMuNSBDIDUuNzM0Mzc1 +IC0zLjU3ODEyNSA1LjY3MTg3NSAtMy41NzgxMjUgNS41NzgxMjUgLTMuNTc4MTI1IEMgNS40ODQz +NzUgLTMuNTc4MTI1IDUuNDIxODc1IC0zLjU3ODEyNSA1LjQyMTg3NSAtMy40ODQzNzUgQyA1LjQy +MTg3NSAtMy40Njg3NSA1LjQ4NDM3NSAtMyA1LjgyODEyNSAtMi41MTU2MjUgQyA1Ljk4NDM3NSAt +Mi4yODEyNSA2LjE4NzUgLTIuMDc4MTI1IDYuNDA2MjUgLTEuOTIxODc1IEwgMC44MTI1IC0xLjky +MTg3NSBDIDAuNzAzMTI1IC0xLjkyMTg3NSAwLjUzMTI1IC0xLjkyMTg3NSAwLjUzMTI1IC0xLjc1 +IEMgMC41MzEyNSAtMS41NzgxMjUgMC43MDMxMjUgLTEuNTc4MTI1IDAuODEyNSAtMS41NzgxMjUg +WiBNIDYuNDA2MjUgLTEuNTc4MTI1ICIKICAgICAgICAgICBzdHlsZT0ic3Ryb2tlOm5vbmU7IiAv +PgogICAgICA8L3N5bWJvbD4KICAgICAgPHN5bWJvbAogICAgICAgICBpZD0iZ2x5cGg0LTIiCiAg +ICAgICAgIG92ZXJmbG93PSJ2aXNpYmxlIj4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJw +YXRoNTkiCiAgICAgICAgICAgZD0iTSA0LjAzMTI1IC0xLjkwNjI1IEMgMy42NTYyNSAtMi4zNDM3 +NSAzLjU0Njg3NSAtMi40NTMxMjUgMy4yODEyNSAtMi42NDA2MjUgQyAyLjg1OTM3NSAtMi45NTMx +MjUgMi40MDYyNSAtMy4wNzgxMjUgMi4wMzEyNSAtMy4wNzgxMjUgQyAxLjE1NjI1IC0zLjA3ODEy +NSAwLjUzMTI1IC0yLjMyODEyNSAwLjUzMTI1IC0xLjUgQyAwLjUzMTI1IC0wLjY4NzUgMS4xNDA2 +MjUgMC4wNjI1IDIgMC4wNjI1IEMgMi45Njg3NSAwLjA2MjUgMy42NTYyNSAtMC43MTg3NSAzLjkw +NjI1IC0xLjA5Mzc1IEMgNC4yNjU2MjUgLTAuNjU2MjUgNC4zOTA2MjUgLTAuNTQ2ODc1IDQuNjQw +NjI1IC0wLjM1OTM3NSBDIDUuMDc4MTI1IC0wLjA0Njg3NSA1LjUxNTYyNSAwLjA2MjUgNS45MDYy +NSAwLjA2MjUgQyA2Ljc2NTYyNSAwLjA2MjUgNy4zOTA2MjUgLTAuNjcxODc1IDcuMzkwNjI1IC0x +LjUgQyA3LjM5MDYyNSAtMi4zMjgxMjUgNi43OTY4NzUgLTMuMDc4MTI1IDUuOTIxODc1IC0zLjA3 +ODEyNSBDIDQuOTUzMTI1IC0zLjA3ODEyNSA0LjI4MTI1IC0yLjI4MTI1IDQuMDMxMjUgLTEuOTA2 +MjUgWiBNIDQuMjUgLTEuNjU2MjUgQyA0LjUzMTI1IC0yLjEwOTM3NSA1LjE0MDYyNSAtMi44MTI1 +IDUuOTg0Mzc1IC0yLjgxMjUgQyA2LjcwMzEyNSAtMi44MTI1IDcuMjAzMTI1IC0yLjE3MTg3NSA3 +LjIwMzEyNSAtMS41IEMgNy4yMDMxMjUgLTAuODQzNzUgNi42NTYyNSAtMC4zMTI1IDYuMDE1NjI1 +IC0wLjMxMjUgQyA1LjM1OTM3NSAtMC4zMTI1IDQuOTIxODc1IC0wLjg0Mzc1IDQuMjUgLTEuNjU2 +MjUgWiBNIDMuNjcxODc1IC0xLjM1OTM3NSBDIDMuNDA2MjUgLTAuOTA2MjUgMi43OTY4NzUgLTAu +MTg3NSAxLjkzNzUgLTAuMTg3NSBDIDEuMjE4NzUgLTAuMTg3NSAwLjczNDM3NSAtMC44MjgxMjUg +MC43MzQzNzUgLTEuNSBDIDAuNzM0Mzc1IC0yLjE3MTg3NSAxLjI4MTI1IC0yLjY4NzUgMS45MjE4 +NzUgLTIuNjg3NSBDIDIuNTYyNSAtMi42ODc1IDMuMDE1NjI1IC0yLjE1NjI1IDMuNjcxODc1IC0x +LjM1OTM3NSBaIE0gMy42NzE4NzUgLTEuMzU5Mzc1ICIKICAgICAgICAgICBzdHlsZT0ic3Ryb2tl +Om5vbmU7IiAvPgogICAgICA8L3N5bWJvbD4KICAgICAgPHN5bWJvbAogICAgICAgICBpZD0iZ2x5 +cGg1LTAiCiAgICAgICAgIG92ZXJmbG93PSJ2aXNpYmxlIj4KICAgICAgICA8cGF0aAogICAgICAg +ICAgIGlkPSJwYXRoNjIiCiAgICAgICAgICAgZD0iIgogICAgICAgICAgIHN0eWxlPSJzdHJva2U6 +bm9uZTsiIC8+CiAgICAgIDwvc3ltYm9sPgogICAgICA8c3ltYm9sCiAgICAgICAgIGlkPSJnbHlw +aDUtMSIKICAgICAgICAgb3ZlcmZsb3c9InZpc2libGUiPgogICAgICAgIDxwYXRoCiAgICAgICAg +ICAgaWQ9InBhdGg2NSIKICAgICAgICAgICBkPSJNIDEyLjYyNSAxMy45NTMxMjUgTCAxMy44Mjgx +MjUgMTAuNzY1NjI1IEwgMTMuNTc4MTI1IDEwLjc2NTYyNSBDIDEzLjE4NzUgMTEuNzk2ODc1IDEy +LjEyNSAxMi40ODQzNzUgMTAuOTg0Mzc1IDEyLjc4MTI1IEMgMTAuNzgxMjUgMTIuODI4MTI1IDku +Nzk2ODc1IDEzLjA5Mzc1IDcuODkwNjI1IDEzLjA5Mzc1IEwgMS44NzUgMTMuMDkzNzUgTCA2Ljk1 +MzEyNSA3LjE0MDYyNSBDIDcuMDE1NjI1IDcuMDYyNSA3LjAzMTI1IDcuMDMxMjUgNy4wMzEyNSA2 +Ljk4NDM3NSBDIDcuMDMxMjUgNi45NTMxMjUgNy4wMzEyNSA2LjkyMTg3NSA2Ljk2ODc1IDYuODI4 +MTI1IEwgMi4zMjgxMjUgMC40ODQzNzUgTCA3Ljc4MTI1IDAuNDg0Mzc1IEMgOS4xMjUgMC40ODQz +NzUgMTAuMDMxMjUgMC42MjUgMTAuMTI1IDAuNjQwNjI1IEMgMTAuNjU2MjUgMC43MTg3NSAxMS41 +MzEyNSAwLjg5MDYyNSAxMi4zMTI1IDEuMzkwNjI1IEMgMTIuNTYyNSAxLjU0Njg3NSAxMy4yMzQz +NzUgMiAxMy41NzgxMjUgMi43OTY4NzUgTCAxMy44MjgxMjUgMi43OTY4NzUgTCAxMi42MjUgMCBM +IDAuODQzNzUgMCBDIDAuNjA5Mzc1IDAgMC41OTM3NSAwLjAxNTYyNSAwLjU2MjUgMC4wNjI1IEMg +MC41NjI1IDAuMDkzNzUgMC41NjI1IDAuMjk2ODc1IDAuNTYyNSAwLjQwNjI1IEwgNS44MjgxMjUg +Ny42MDkzNzUgTCAwLjY3MTg3NSAxMy42NzE4NzUgQyAwLjU2MjUgMTMuNzgxMjUgMC41NjI1IDEz +Ljg0Mzc1IDAuNTYyNSAxMy44NDM3NSBDIDAuNTYyNSAxMy45NTMxMjUgMC42NTYyNSAxMy45NTMx +MjUgMC44NDM3NSAxMy45NTMxMjUgWiBNIDEyLjYyNSAxMy45NTMxMjUgIgogICAgICAgICAgIHN0 +eWxlPSJzdHJva2U6bm9uZTsiIC8+CiAgICAgIDwvc3ltYm9sPgogICAgPC9nPgogIDwvZGVmcz4K +ICA8ZwogICAgIGlkPSJzdXJmYWNlMSI+CiAgICA8ZwogICAgICAgaWQ9Imc3NCIKICAgICAgIHN0 +eWxlPSJmaWxsOnJnYigwJSwwJSwwJSk7ZmlsbC1vcGFjaXR5OjE7Ij4KICAgICAgPHVzZQogICAg +ICAgICBpZD0idXNlNzIiCiAgICAgICAgIHk9IjgxLjcxMiIKICAgICAgICAgeD0iOTMuMTIxIgog +ICAgICAgICB4bGluazpocmVmPSIjZ2x5cGgwLTEiIC8+CiAgICA8L2c+CiAgICA8ZwogICAgICAg +aWQ9Imc3OCIKICAgICAgIHN0eWxlPSJmaWxsOnJnYigwJSwwJSwwJSk7ZmlsbC1vcGFjaXR5OjE7 +Ij4KICAgICAgPHVzZQogICAgICAgICBpZD0idXNlNzYiCiAgICAgICAgIHk9Ijc4LjA5NyIKICAg +ICAgICAgeD0iOTkuMTU3IgogICAgICAgICB4bGluazpocmVmPSIjZ2x5cGgxLTEiIC8+CiAgICA8 +L2c+CiAgICA8cGF0aAogICAgICAgaWQ9InBhdGg4MCIKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4 +KDEsMCwwLC0xLDkzLjEyMSw4NS45NjIpIgogICAgICAgZD0iTSAwLjAwMDA5Mzc1IDAuMDAxMDYy +NSBMIDEwLjUwNzkwNiAwLjAwMTA2MjUgIgogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2Ut +d2lkdGg6MC4zOTg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ry +b2tlOnJnYigwJSwwJSwwJSk7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtbWl0ZXJsaW1pdDoxMDsi +IC8+CiAgICA8ZwogICAgICAgaWQ9Imc4NCIKICAgICAgIHN0eWxlPSJmaWxsOnJnYigwJSwwJSww +JSk7ZmlsbC1vcGFjaXR5OjE7Ij4KICAgICAgPHVzZQogICAgICAgICBpZD0idXNlODIiCiAgICAg +ICAgIHk9Ijk1LjI4NiIKICAgICAgICAgeD0iOTUuODgzIgogICAgICAgICB4bGluazpocmVmPSIj +Z2x5cGgyLTEiIC8+CiAgICA8L2c+CiAgICA8ZwogICAgICAgaWQ9Imc4OCIKICAgICAgIHN0eWxl +PSJmaWxsOnJnYigwJSwwJSwwJSk7ZmlsbC1vcGFjaXR5OjE7Ij4KICAgICAgPHVzZQogICAgICAg +ICBpZD0idXNlODYiCiAgICAgICAgIHk9Ijg4LjQ1MiIKICAgICAgICAgeD0iMTA3LjU4OSIKICAg +ICAgICAgeGxpbms6aHJlZj0iI2dseXBoMi0yIiAvPgogICAgPC9nPgogICAgPGcKICAgICAgIGlk +PSJnOTYiCiAgICAgICBzdHlsZT0iZmlsbDpyZ2IoMCUsMCUsMCUpO2ZpbGwtb3BhY2l0eToxOyI+ +CiAgICAgIDx1c2UKICAgICAgICAgaWQ9InVzZTkwIgogICAgICAgICB5PSI4OC40NTIiCiAgICAg +ICAgIHg9IjEyMS41OTQ0MjMiCiAgICAgICAgIHhsaW5rOmhyZWY9IiNnbHlwaDItMyIgLz4KICAg +ICAgPHVzZQogICAgICAgICBpZD0idXNlOTIiCiAgICAgICAgIHk9Ijg4LjQ1MiIKICAgICAgICAg +eD0iMTI0LjM2MjAzMyIKICAgICAgICAgeGxpbms6aHJlZj0iI2dseXBoMi00IiAvPgogICAgICA8 +dXNlCiAgICAgICAgIGlkPSJ1c2U5NCIKICAgICAgICAgeT0iODguNDUyIgogICAgICAgICB4PSIx +MjcuMTI5NjQ0IgogICAgICAgICB4bGluazpocmVmPSIjZ2x5cGgyLTUiIC8+CiAgICA8L2c+CiAg +ICA8ZwogICAgICAgaWQ9ImcxMDAiCiAgICAgICBzdHlsZT0iZmlsbDpyZ2IoMCUsMCUsMCUpO2Zp +bGwtb3BhY2l0eToxOyI+CiAgICAgIDx1c2UKICAgICAgICAgaWQ9InVzZTk4IgogICAgICAgICB5 +PSI5NC40MyIKICAgICAgICAgeD0iMTE4LjEwNSIKICAgICAgICAgeGxpbms6aHJlZj0iI2dseXBo +My0xIiAvPgogICAgPC9nPgogICAgPGcKICAgICAgIGlkPSJnMTA2IgogICAgICAgc3R5bGU9ImZp +bGw6cmdiKDAlLDAlLDAlKTtmaWxsLW9wYWNpdHk6MTsiPgogICAgICA8dXNlCiAgICAgICAgIGlk +PSJ1c2UxMDIiCiAgICAgICAgIHk9Ijk0LjQzIgogICAgICAgICB4PSIxMjMuMDMiCiAgICAgICAg +IHhsaW5rOmhyZWY9IiNnbHlwaDQtMSIgLz4KICAgICAgPHVzZQogICAgICAgICBpZD0idXNlMTA0 +IgogICAgICAgICB5PSI5NC40MyIKICAgICAgICAgeD0iMTMwLjk3MjQ2MSIKICAgICAgICAgeGxp +bms6aHJlZj0iI2dseXBoNC0yIiAvPgogICAgPC9nPgogICAgPGcKICAgICAgIGlkPSJnMTEwIgog +ICAgICAgc3R5bGU9ImZpbGw6cmdiKDAlLDAlLDAlKTtmaWxsLW9wYWNpdHk6MTsiPgogICAgICA8 +dXNlCiAgICAgICAgIGlkPSJ1c2UxMDgiCiAgICAgICAgIHk9Ijc1Ljk5OSIKICAgICAgICAgeD0i +MTQ1LjM1OSIKICAgICAgICAgeGxpbms6aHJlZj0iI2dseXBoMy0xIiAvPgogICAgPC9nPgogICAg +PGcKICAgICAgIGlkPSJnMTE0IgogICAgICAgc3R5bGU9ImZpbGw6cmdiKDAlLDAlLDAlKTtmaWxs +LW9wYWNpdHk6MTsiPgogICAgICA8dXNlCiAgICAgICAgIGlkPSJ1c2UxMTIiCiAgICAgICAgIHk9 +Ijc4Ljk4OCIKICAgICAgICAgeD0iMTQwLjYyNiIKICAgICAgICAgeGxpbms6aHJlZj0iI2dseXBo +NS0xIiAvPgogICAgPC9nPgogICAgPGcKICAgICAgIGlkPSJnMTE4IgogICAgICAgc3R5bGU9ImZp +bGw6cmdiKDAlLDAlLDAlKTtmaWxsLW9wYWNpdHk6MTsiPgogICAgICA8dXNlCiAgICAgICAgIGlk +PSJ1c2UxMTYiCiAgICAgICAgIHk9IjEwMC40MzUiCiAgICAgICAgIHg9IjE0MC41NzYiCiAgICAg +ICAgIHhsaW5rOmhyZWY9IiNnbHlwaDMtMiIgLz4KICAgIDwvZz4KICAgIDxnCiAgICAgICBpZD0i +ZzEyNCIKICAgICAgIHN0eWxlPSJmaWxsOnJnYigwJSwwJSwwJSk7ZmlsbC1vcGFjaXR5OjE7Ij4K +ICAgICAgPHVzZQogICAgICAgICBpZD0idXNlMTIwIgogICAgICAgICB5PSIxMDAuNDM1IgogICAg +ICAgICB4PSIxNDQuOTc5IgogICAgICAgICB4bGluazpocmVmPSIjZ2x5cGgxLTIiIC8+CiAgICAg +IDx1c2UKICAgICAgICAgaWQ9InVzZTEyMiIKICAgICAgICAgeT0iMTAwLjQzNSIKICAgICAgICAg +eD0iMTUxLjA5NTAyMyIKICAgICAgICAgeGxpbms6aHJlZj0iI2dseXBoMS0zIiAvPgogICAgPC9n +PgogICAgPGcKICAgICAgIGlkPSJnMTI4IgogICAgICAgc3R5bGU9ImZpbGw6cmdiKDAlLDAlLDAl +KTtmaWxsLW9wYWNpdHk6MTsiPgogICAgICA8dXNlCiAgICAgICAgIGlkPSJ1c2UxMjYiCiAgICAg +ICAgIHk9IjgxLjcxMiIKICAgICAgICAgeD0iMTYwLjQxNyIKICAgICAgICAgeGxpbms6aHJlZj0i +I2dseXBoMi02IiAvPgogICAgPC9nPgogICAgPHBhdGgKICAgICAgIGlkPSJwYXRoMTMwIgogICAg +ICAgdHJhbnNmb3JtPSJtYXRyaXgoMSwwLDAsLTEsMTU3LjkyMiw4NS45NjIpIgogICAgICAgZD0i +TSAtMC4wMDAxMjUgMC4wMDEwNjI1IEwgOS45Njg2MjUgMC4wMDEwNjI1ICIKICAgICAgIHN0eWxl +PSJmaWxsOm5vbmU7c3Ryb2tlLXdpZHRoOjAuMzk4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tl +LWxpbmVqb2luOm1pdGVyO3N0cm9rZTpyZ2IoMCUsMCUsMCUpO3N0cm9rZS1vcGFjaXR5OjE7c3Ry +b2tlLW1pdGVybGltaXQ6MTA7IiAvPgogICAgPGcKICAgICAgIGlkPSJnMTM0IgogICAgICAgc3R5 +bGU9ImZpbGw6cmdiKDAlLDAlLDAlKTtmaWxsLW9wYWNpdHk6MTsiPgogICAgICA8dXNlCiAgICAg +ICAgIGlkPSJ1c2UxMzIiCiAgICAgICAgIHk9Ijk1LjI4NiIKICAgICAgICAgeD0iMTU3LjkyMiIK +ICAgICAgICAgeGxpbms6aHJlZj0iI2dseXBoMC0yIiAvPgogICAgPC9nPgogICAgPGcKICAgICAg +IGlkPSJnMTM4IgogICAgICAgc3R5bGU9ImZpbGw6cmdiKDAlLDAlLDAlKTtmaWxsLW9wYWNpdHk6 +MTsiPgogICAgICA8dXNlCiAgICAgICAgIGlkPSJ1c2UxMzYiCiAgICAgICAgIHk9IjkyLjQwOCIK +ICAgICAgICAgeD0iMTYzLjQyMyIKICAgICAgICAgeGxpbms6aHJlZj0iI2dseXBoMS0xIiAvPgog +ICAgPC9nPgogIDwvZz4KPC9zdmc+Cg== + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/88e23ddddb7b44e469a8309d48b71594.msg b/share/extensions/tests/data/cmd/inkscape/88e23ddddb7b44e469a8309d48b71594.msg new file mode 100644 index 0000000..44d6dea --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/88e23ddddb7b44e469a8309d48b71594.msg @@ -0,0 +1,25 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=0:951:47:1000 --export-filename=f6oo.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="f6oo.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: f6oo.png + +iVBORw0KGgoAAAANSUhEUgAAAC8AAAAxCAYAAABK+/BHAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAB9JREFUaIHtwQEBAAAAgiD/r25I +QAEAAAAAAAAAABcGJC0AAXI8dYQAAAAASUVORK5CYII= + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/8d81a6ce40b41366bc5784c8b8e3f3e3.msg b/share/extensions/tests/data/cmd/inkscape/8d81a6ce40b41366bc5784c8b8e3f3e3.msg new file mode 100644 index 0000000..01c4c8b --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/8d81a6ce40b41366bc5784c8b8e3f3e3.msg @@ -0,0 +1,28 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=47:0:953:49 --export-filename=f1oo.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="f1oo.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: f1oo.png + +iVBORw0KGgoAAAANSUhEUgAAA4oAAAAxCAYAAACcaFfTAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMNJREFUeJztwTEBAAAAwqD1T20M +H6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAATga19wABjrwhtgAAAABJRU5ErkJggg== + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/9216316249e241e47bf118e187386687.msg b/share/extensions/tests/data/cmd/inkscape/9216316249e241e47bf118e187386687.msg new file mode 100644 index 0000000..922496e --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/9216316249e241e47bf118e187386687.msg @@ -0,0 +1,28 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=47:0:953:49 --export-filename=guides_1.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="guides_1.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: guides_1.png + +iVBORw0KGgoAAAANSUhEUgAAA4oAAAAxCAYAAACcaFfTAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMNJREFUeJztwTEBAAAAwqD1T20M +H6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAATga19wABjrwhtgAAAABJRU5ErkJggg== + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/96e4049adf5290ee08bfbcd580bd8dd1.msg b/share/extensions/tests/data/cmd/inkscape/96e4049adf5290ee08bfbcd580bd8dd1.msg new file mode 100644 index 0000000..8cb5321 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/96e4049adf5290ee08bfbcd580bd8dd1.msg @@ -0,0 +1,19 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-dpi=1 --export-filename=Slide2.png --export-type=png Slide2.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="Slide2.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: Slide2.png + +iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAAnAAAAJwEqCZFPAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAJ9JREFUGJXN0C9uAnEQxfHPDxYW +QVqDRfFHEE5AQoLA7BF6ARwnwKynqgYFCRaD4hakSQ0cgJ5gL7AIFkIIeJ6Zl5mX72SG91BOlBOB +H7FU9TorXUq6yy8+QTKZqCwGfr/6tvegCGWEPZUbcSqWqj0Gl+iiVWxpF76DcEuOvjUQRhv1ohU8 +KMD6z/w/c2x+6J0yh9nQ6um1V9J44/PVR85E2xqRgxB1CAAAAABJRU5ErkJggg== + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/994e3f169976a1af54f6590723d399ee.msg b/share/extensions/tests/data/cmd/inkscape/994e3f169976a1af54f6590723d399ee.msg new file mode 100644 index 0000000..7eff536 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/994e3f169976a1af54f6590723d399ee.msg @@ -0,0 +1,28 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=47:951:953:1000 --export-filename=f7oo.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="f7oo.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: f7oo.png + +iVBORw0KGgoAAAANSUhEUgAAA4oAAAAxCAYAAACcaFfTAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMNJREFUeJztwTEBAAAAwqD1T20M +H6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAATga19wABjrwhtgAAAABJRU5ErkJggg== + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/a274e3a1c2d893233fa49b8ad9850098.msg b/share/extensions/tests/data/cmd/inkscape/a274e3a1c2d893233fa49b8ad9850098.msg new file mode 100644 index 0000000..ae8c52e --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/a274e3a1c2d893233fa49b8ad9850098.msg @@ -0,0 +1,18 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-dpi=1 --export-filename=Slide1.png --export-type=png Slide1.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="Slide1.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: Slide1.png + +iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAAnAAAAJwEqCZFPAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAGFJREFUGJXlyrEJg1AYhdHzLIKg +VRBS2DuDpaO93nFSSkYxC7hBEvgtfIXOkAOX23zJ4VY+yuDnpCr/Cb5v7i8eE2StWeMqR9AFYzA+ +B/2y2pbVJqsvaZBK3AXJrJG1/toOYHQZ6KZhWDQAAAAASUVORK5CYII= + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/b493ad11b50db40a61c857109f083621.msg b/share/extensions/tests/data/cmd/inkscape/b493ad11b50db40a61c857109f083621.msg new file mode 100644 index 0000000..84695b2 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/b493ad11b50db40a61c857109f083621.msg @@ -0,0 +1,604 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --batch-process --verb=EditSelectAllInAllLayers;EditUnlinkClone;ObjectToPath;FileSave;FileQuit input.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="input.svg" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: input.svg + +PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxz +dmcKICAgeG1sbnM6bnMxPSJodHRwczovL2xhdW5jaHBhZC5uZXQvamVzc3lpbmsiCiAgIHhtbG5z +OmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6 +Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9y +Zy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMu +b3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHht +bG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9k +aS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVz +cGFjZXMvaW5rc2NhcGUiCiAgIHNvZGlwb2RpOmRvY25hbWU9ImlucHV0LnN2ZyIKICAgaW5rc2Nh +cGU6dmVyc2lvbj0iMS4xLWRldiAoMmNkMzE5ZWQ3MywgMjAyMC0wMy0zMSwgY3VzdG9tKSIKICAg +aWQ9InN2ZzgiCiAgIHZlcnNpb249IjEuMSIKICAgdmlld0JveD0iMCAwIDEwMDAgMTAwMCIKICAg +aGVpZ2h0PSIxMDAwIgogICB3aWR0aD0iMTAwMCI+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFk +YXRhNDEiPgogICAgPHJkZjpSREY+CiAgICAgIDxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0i +Ij4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8 +ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0 +eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgIDwvY2M6V29yaz4KICAgIDwvcmRmOlJERj4KICA8L21l +dGFkYXRhPgogIDxkZWZzCiAgICAgaWQ9ImRlZnMzMyI+CiAgICA8bWFya2VyCiAgICAgICBpbmtz +Y2FwZTppc3N0b2NrPSJ0cnVlIgogICAgICAgc3R5bGU9Im92ZXJmbG93OnZpc2libGUiCiAgICAg +ICBpZD0iQXJyb3cyTHN0YXJ0IgogICAgICAgcmVmWD0iMC4wIgogICAgICAgcmVmWT0iMC4wIgog +ICAgICAgb3JpZW50PSJhdXRvIgogICAgICAgaW5rc2NhcGU6c3RvY2tpZD0iQXJyb3cyTHN0YXJ0 +Ij4KICAgICAgPHBhdGgKICAgICAgICAgdHJhbnNmb3JtPSJzY2FsZSgxLjEpIHRyYW5zbGF0ZSgx +LDApIgogICAgICAgICBkPSJNIDguNzE4NTg3OCw0LjAzMzczNTIgTCAtMi4yMDcyODk1LDAuMDE2 +MDEzMjU2IEwgOC43MTg1ODg0LC00LjAwMTcwNzggQyA2Ljk3MzA5MDAsLTEuNjI5NjQ2OSA2Ljk4 +MzE0NzYsMS42MTU3NDQxIDguNzE4NTg3OCw0LjAzMzczNTIgeiAiCiAgICAgICAgIHN0eWxlPSJm +aWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2Utd2lkdGg6MC42MjU7c3Ryb2tlLWxpbmVqb2luOnJvdW5k +O3N0cm9rZTojMDAwMDAwO3N0cm9rZS1vcGFjaXR5OjE7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0 +eToxIgogICAgICAgICBpZD0icGF0aDg1OSIgLz4KICAgIDwvbWFya2VyPgogIDwvZGVmcz4KICA8 +c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0id2Vic2xpY2Vy +LWxheWVyIgogICAgIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjAiCiAgICAgaW5rc2NhcGU6 +d2luZG93LXk9IjIwIgogICAgIGlua3NjYXBlOndpbmRvdy14PSI2NyIKICAgICBpbmtzY2FwZTpj +eT0iNDgxLjE2OTg2IgogICAgIGlua3NjYXBlOmN4PSIyMDIuNzM0OSIKICAgICBpbmtzY2FwZTp6 +b29tPSIwLjY2NzUwODgiCiAgICAgaW5rc2NhcGU6c25hcC10ZXh0LWJhc2VsaW5lPSJ0cnVlIgog +ICAgIHNob3dncmlkPSJ0cnVlIgogICAgIGlkPSJiYXNlIgogICAgIGlua3NjYXBlOndpbmRvdy1o +ZWlnaHQ9Ijg3OSIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjExODgiCiAgICAgaW5rc2Nh +cGU6cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMCIKICAgICBndWlk +ZXRvbGVyYW5jZT0iMTAiCiAgICAgZ3JpZHRvbGVyYW5jZT0iMTAiCiAgICAgb2JqZWN0dG9sZXJh +bmNlPSIxMCIKICAgICBib3JkZXJvcGFjaXR5PSIxIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2 +IgogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiI+CiAgICA8aW5rc2NhcGU6Z3JpZAogICAgICAgZW1w +b3BhY2l0eT0iMC4yNTA5ODAzOSIKICAgICAgIGVtcGNvbG9yPSIjMDAwMGZmIgogICAgICAgb3Bh +Y2l0eT0iMC4yNTA5ODAzOSIKICAgICAgIGNvbG9yPSIjODA4MGZmIgogICAgICAgZW1wc3BhY2lu +Zz0iMTAiCiAgICAgICBzcGFjaW5neT0iMTAiCiAgICAgICBzcGFjaW5neD0iMTAiCiAgICAgICBp +ZD0iZ3JpZDI3IgogICAgICAgdHlwZT0ieHlncmlkIiAvPgogIDwvc29kaXBvZGk6bmFtZWR2aWV3 +PgogIDxnCiAgICAgc3R5bGU9ImRpc3BsYXk6aW5saW5lIgogICAgIGlua3NjYXBlOmxhYmVsPSJT +bGlkZTMiCiAgICAgaWQ9IndlYnNsaWNlci1sYXllciIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9 +ImxheWVyIj4KICAgIDxwYXRoCiAgICAgICBkPSJtIDY1MS4zMDg5LDM1My45MjY2MSBoIDI0OC42 +OTExMiB2IDIwMS40OTgxIEggNjUxLjMwODkgWiIKICAgICAgIHN0eWxlPSJvcGFjaXR5OjAuNTtm +aWxsOiNmZjAwMDA7c3Ryb2tlLXdpZHRoOjEuMTE5MjciCiAgICAgICBpZD0ic2xpY2VyZWN0MSI+ +CiAgICAgIDxkZXNjCiAgICAgICAgIGlkPSJkZXNjNTEiPmZvcm1hdDogcG5nCmRwaTogOTYKbGF5 +b3V0LWRpc3Bvc2l0aW9uOiBiZy1lbC1ub3JlcGVhdApsYXlvdXQtcG9zaXRpb24tYW5jaG9yOiB0 +bDwvZGVzYz4KICAgIDwvcGF0aD4KICA8L2c+CiAgPGcKICAgICBzdHlsZT0iZGlzcGxheTppbmxp +bmUiCiAgICAgaW5rc2NhcGU6bGFiZWw9IlNsaWRlMiIKICAgICBpZD0ibGF5ZXIyIgogICAgIGlu +a3NjYXBlOmdyb3VwbW9kZT0ibGF5ZXIiPgogICAgPHBhdGgKICAgICAgIGQ9Im0gMjAwLDQ1MCBh +IDUwLDUwIDAgMCAxIC01MCw1MCA1MCw1MCAwIDAgMSAtNTAsLTUwIDUwLDUwIDAgMCAxIDUwLC01 +MCA1MCw1MCAwIDAgMSA1MCw1MCIKICAgICAgIGlua3NjYXBlOmxhYmVsPSIjcGF0aDM3MzYiCiAg +ICAgICBzdHlsZT0iZGlzcGxheTppbmxpbmU7ZmlsbDojMDAwMDgwO3N0cm9rZTpub25lIgogICAg +ICAgaWQ9ImMxIiAvPgogICAgPHBhdGgKICAgICAgIGQ9Ik0gNTAwLDQ1MCBBIDEwMCw1MCAwIDAg +MSA0MDAsNTAwIDEwMCw1MCAwIDAgMSAzMDAsNDUwIDEwMCw1MCAwIDAgMSA0MDAsNDAwIDEwMCw1 +MCAwIDAgMSA1MDAsNDUwIgogICAgICAgaW5rc2NhcGU6bGFiZWw9IiNwYXRoMzczOCIKICAgICAg +IHN0eWxlPSJkaXNwbGF5OmlubGluZTtmaWxsOm5vbmU7c3Ryb2tlOiNmZjAwMDA7c3Ryb2tlLXdp +ZHRoOjE2IgogICAgICAgaWQ9ImMyIiAvPgogICAgPHBhdGgKICAgICAgIGQ9Im0gNzgzLjA4NjM1 +LDQ3Ny44MjM4MSBhIDEwMCw1MCAwIDAgMSAtMTExLjA5ODQ4LDIwLjE3NDQyIDEwMCw1MCAwIDAg +MSAtNzEuOTYzMDEsLTQ2Ljg4MzQzIDEwMCw1MCAwIDAgMSA2Ny43MTEyNywtNDguNDQwOTEgMTAw +LDUwIDAgMCAxIDExMi43ODY4LDE3LjY3NzkzIEwgNzAwLDQ1MCBaIgogICAgICAgaW5rc2NhcGU6 +bGFiZWw9IiNwYXRoMzc0MCIKICAgICAgIHN0eWxlPSJkaXNwbGF5OmlubGluZTtmaWxsOiNmZmZm +MDA7c3Ryb2tlOiMwMDgwMDA7c3Ryb2tlLXdpZHRoOjE2IgogICAgICAgaWQ9ImMzIiAvPgogICAg +PHBhdGgKICAgICAgIGlua3NjYXBlOmxhYmVsPSIjcGF0aDM3NDYiCiAgICAgICBpbmtzY2FwZTpj +b25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgaWQ9InAxIgogICAgICAgZD0iTSAxMDAsNjAw +IDIwMCw3MDAgMzAwLDYwMCA0MDAsNzAwIgogICAgICAgc3R5bGU9ImRpc3BsYXk6aW5saW5lO2Zp +bGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MTA7bWFya2VyLXN0YXJ0OnVybCgj +QXJyb3cyTHN0YXJ0KSIgLz4KICAgIDxwYXRoCiAgICAgICBpbmtzY2FwZTpsYWJlbD0iI3BhdGgz +NzQ4IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIGlkPSJw +MiIKICAgICAgIGQ9Im0gNTAwLDYwMCBjIDAsMCAwLDEwMCAxMDAsMTAwIDEwMCwwIDAsLTEwMCAx +MDAsLTEwMCAxMDAsMCAxMDAsMTAwIDEwMCwxMDAiCiAgICAgICBzdHlsZT0iZGlzcGxheTppbmxp +bmU7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxMCIgLz4KICAgIDxwYXRo +CiAgICAgICBkPSJtIDI0NC44MzMyLDg4OS4xNDAwNSAtNDUuMzM4ODcsLTguMTM0NDYgLTMyLjQw +NDI4LDMyLjczNzUzIC02LjI3NDE1LC00NS42MzM1MiAtNDEuMTQ4NzIsLTIwLjcwMTg0IDQxLjQ2 +MTI0LC0yMC4wNjg2MSA2Ljk3Mjk3LC00NS41MzE5NyAzMS44OTg2MSwzMy4yMzA0NCA0NS40NTgy +NCwtNy40Mzg0NyAtMjEuNzQ2ODEsNDAuNjA2MTUgeiIKICAgICAgIGlua3NjYXBlOnRyYW5zZm9y +bS1jZW50ZXIteT0iLTAuMTY0MzAxMzciCiAgICAgICBpbmtzY2FwZTp0cmFuc2Zvcm0tY2VudGVy +LXg9IjYuNDY3MzAxMSIKICAgICAgIHN0eWxlPSJkaXNwbGF5OmlubGluZTtmaWxsOiNmZmZmMDA7 +c3Ryb2tlOiMwMDgwMDA7c3Ryb2tlLXdpZHRoOjEwIgogICAgICAgaWQ9InMxIiAvPgogICAgPHBh +dGgKICAgICAgIGQ9Im0gNDQ0LjgzMzE5LDg5Mi4xMzYyNiAtNDUuMzM4ODgsLTguMTM0NDUgLTMy +LjQwNDI3LDMyLjczNzUzIC02LjI3NDE2LC00NS42MzM1MiAtNDEuMTQ4NzEsLTIwLjcwMTg0IDQx +LjQ2MTI0LC0yMC4wNjg2MSA2Ljk3Mjk2LC00NS41MzE5NyAzMS44OTg2MSwzMy4yMzA0MyA0NS40 +NTgyNSwtNy40Mzg0NiAtMjEuNzQ2ODIsNDAuNjA2MTUgeiIKICAgICAgIGlua3NjYXBlOnRyYW5z +Zm9ybS1jZW50ZXIteT0iLTAuMTY0MzAxMzciCiAgICAgICBpbmtzY2FwZTp0cmFuc2Zvcm0tY2Vu +dGVyLXg9IjYuNDY3MzAxMSIKICAgICAgIHN0eWxlPSJkaXNwbGF5OmlubGluZTtmaWxsOiNmZmZm +MDA7c3Ryb2tlOiMwMDgwMDA7c3Ryb2tlLXdpZHRoOjEwIgogICAgICAgaWQ9InUxIiAvPgogIDwv +Zz4KICA8ZwogICAgIHN0eWxlPSJkaXNwbGF5OmlubGluZSIKICAgICBpZD0ibGF5ZXIxIgogICAg +IGlua3NjYXBlOmdyb3VwbW9kZT0ibGF5ZXIiCiAgICAgaW5rc2NhcGU6bGFiZWw9IlNsaWRlMSI+ +CiAgICA8ZwogICAgICAgaW5rc2NhcGU6bGFiZWw9IiN0ZXh0MTIiCiAgICAgICBzdHlsZT0iZm9u +dC1zaXplOjE0LjY2NjdweDtsaW5lLWhlaWdodDoxLjI1O2ZvbnQtZmFtaWx5OnNhbnMtc2VyaWY7 +ZmlsbDojMDAwMDAwO3N0cm9rZTpub25lIgogICAgICAgaWQ9InQxIgogICAgICAgYXJpYS1sYWJl +bD0iSGVsbG8gV29ybGQiPgogICAgICA8cGF0aAogICAgICAgICBpZD0icGF0aDUyIgogICAgICAg +ICBkPSJtIDEwMS40Mzk0Niw4OS4zMDc5MTggaCAxLjQ0NjYxIHYgNC4zODI4MjMgaCA1LjI1NjUz +IHYgLTQuMzgyODIzIGggMS40NDY2MSBWIDEwMCBoIC0xLjQ0NjYxIHYgLTUuMDkxODA5IGggLTUu +MjU2NTMgViAxMDAgaCAtMS40NDY2MSB6IiAvPgogICAgICA8cGF0aAogICAgICAgICBpZD0icGF0 +aDU0IgogICAgICAgICBkPSJtIDExOS4yNzE1Myw5NS42NjAxNDYgdiAwLjY0NDUzMyBoIC02LjA1 +ODYxIHEgMC4wODU5LDEuMzYwNjggMC44MTY0MSwyLjA3NjgyOCAwLjczNzYzLDAuNzA4OTg2IDIu +MDQ4MTgsMC43MDg5ODYgMC43NTkxMiwwIDEuNDY4MSwtMC4xODYxOTkgMC43MTYxNSwtMC4xODYx +OTggMS40MTc5OCwtMC41NTg1OTUgdiAxLjI0NjA5NyBxIC0wLjcwODk5LDAuMzAwNzgyIC0xLjQ1 +Mzc4LDAuNDU4MzM0IC0wLjc0NDgsMC4xNTc1NSAtMS41MTEwOCwwLjE1NzU1IC0xLjkxOTI3LDAg +LTMuMDQzNjIsLTEuMTE3MTg3IC0xLjExNzE5LC0xLjExNzE5IC0xLjExNzE5LC0zLjAyMjE0MyAw +LC0xLjk2OTQwNSAxLjA1OTksLTMuMTIyNDAzIDEuMDY3MDYsLTEuMTYwMTU5IDIuODcxNzUsLTEu +MTYwMTU5IDEuNjE4NDksMCAyLjU1NjY0LDEuMDQ1NTc2IDAuOTQ1MzIsMS4wMzg0MTQgMC45NDUz +MiwyLjgyODc4MiB6IG0gLTEuMzE3NzEsLTAuMzg2NzE5IHEgLTAuMDE0MywtMS4wODEzODMgLTAu +NjA4NzMsLTEuNzI1OTE2IC0wLjU4NzI0LC0wLjY0NDUzMyAtMS41NjEyLC0wLjY0NDUzMyAtMS4x +MDI4NywwIC0xLjc2ODg4LDAuNjIzMDQ5IC0wLjY1ODg2LDAuNjIzMDQ4IC0wLjc1OTEyLDEuNzU0 +NTYxIHoiIC8+CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgIGQ9Im0g +MTIxLjQzNDI5LDg4Ljg1Njc0NSBoIDEuMzE3NzIgViAxMDAgaCAtMS4zMTc3MiB6IiAvPgogICAg +ICA8cGF0aAogICAgICAgICBpZD0icGF0aDU4IgogICAgICAgICBkPSJtIDEyNS41MDIwMSw4OC44 +NTY3NDUgaCAxLjMxNzcxIFYgMTAwIGggLTEuMzE3NzEgeiIgLz4KICAgICAgPHBhdGgKICAgICAg +ICAgaWQ9InBhdGg2MCIKICAgICAgICAgZD0ibSAxMzIuNjc3ODEsOTIuOTAyOTc4IHEgLTEuMDU5 +OSwwIC0xLjY3NTc5LDAuODMwNzMyIC0wLjYxNTg4LDAuODIzNTY5IC0wLjYxNTg4LDIuMjYzMDI2 +IDAsMS40Mzk0NTYgMC42MDg3MiwyLjI3MDE4NyAwLjYxNTg5LDAuODIzNTcgMS42ODI5NSwwLjgy +MzU3IDEuMDUyNzQsMCAxLjY2ODYyLC0wLjgzMDczMSAwLjYxNTg5LC0wLjgzMDczMSAwLjYxNTg5 +LC0yLjI2MzAyNiAwLC0xLjQyNTEzNCAtMC42MTU4OSwtMi4yNTU4NjUgLTAuNjE1ODgsLTAuODM3 +ODkzIC0xLjY2ODYyLC0wLjgzNzg5MyB6IG0gMCwtMS4xMTcxOSBxIDEuNzE4NzUsMCAyLjY5OTg4 +LDEuMTE3MTkgMC45ODExMiwxLjExNzE5MSAwLjk4MTEyLDMuMDkzNzU4IDAsMS45Njk0MDUgLTAu +OTgxMTIsMy4wOTM3NTcgLTAuOTgxMTMsMS4xMTcxODcgLTIuNjk5ODgsMS4xMTcxODcgLTEuNzI1 +OTIsMCAtMi43MDcwNCwtMS4xMTcxODcgLTAuOTczOTYsLTEuMTI0MzUyIC0wLjk3Mzk2LC0zLjA5 +Mzc1NyAwLC0xLjk3NjU2NyAwLjk3Mzk2LC0zLjA5Mzc1OCAwLjk4MTEyLC0xLjExNzE5IDIuNzA3 +MDQsLTEuMTE3MTkgeiIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGg2MiIKICAgICAg +ICAgZD0ibSAxNDIuMzA5OTksODkuMzA3OTE4IGggMS40NjA5NCBsIDIuMjQ4NzEsOS4wMzc3ODEg +Mi4yNDE1NCwtOS4wMzc3ODEgaCAxLjYyNTY1IGwgMi4yNDg3MSw5LjAzNzc4MSAyLjI0MTU0LC05 +LjAzNzc4MSBoIDEuNDY4MSBMIDE1My4xNTk2MywxMDAgaCAtMS44MTkwMiBMIDE0OS4wODQ3NSw5 +MC43MTg3MjkgMTQ2LjgwNzQsMTAwIGggLTEuODE5MDEgeiIgLz4KICAgICAgPHBhdGgKICAgICAg +ICAgaWQ9InBhdGg2NCIKICAgICAgICAgZD0ibSAxNTkuOTQ4NzEsOTIuOTAyOTc4IHEgLTEuMDU5 +OSwwIC0xLjY3NTc5LDAuODMwNzMyIC0wLjYxNTg5LDAuODIzNTY5IC0wLjYxNTg5LDIuMjYzMDI2 +IDAsMS40Mzk0NTYgMC42MDg3MywyLjI3MDE4NyAwLjYxNTg5LDAuODIzNTcgMS42ODI5NSwwLjgy +MzU3IDEuMDUyNzMsMCAxLjY2ODYyLC0wLjgzMDczMSAwLjYxNTg5LC0wLjgzMDczMSAwLjYxNTg5 +LC0yLjI2MzAyNiAwLC0xLjQyNTEzNCAtMC42MTU4OSwtMi4yNTU4NjUgLTAuNjE1ODksLTAuODM3 +ODkzIC0xLjY2ODYyLC0wLjgzNzg5MyB6IG0gMCwtMS4xMTcxOSBxIDEuNzE4NzUsMCAyLjY5OTg3 +LDEuMTE3MTkgMC45ODExMiwxLjExNzE5MSAwLjk4MTEyLDMuMDkzNzU4IDAsMS45Njk0MDUgLTAu +OTgxMTIsMy4wOTM3NTcgLTAuOTgxMTIsMS4xMTcxODcgLTIuNjk5ODcsMS4xMTcxODcgLTEuNzI1 +OTIsMCAtMi43MDcwNCwtMS4xMTcxODcgLTAuOTczOTYsLTEuMTI0MzUyIC0wLjk3Mzk2LC0zLjA5 +Mzc1NyAwLC0xLjk3NjU2NyAwLjk3Mzk2LC0zLjA5Mzc1OCAwLjk4MTEyLC0xLjExNzE5IDIuNzA3 +MDQsLTEuMTE3MTkgeiIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGg2NiIKICAgICAg +ICAgZD0ibSAxNzAuNDU0NTksOTMuMjEwOTIyIHEgLTAuMjIyLC0wLjEyODkwNyAtMC40ODY5OCwt +MC4xODYxOTggLTAuMjU3ODEsLTAuMDY0NDUgLTAuNTcyOTIsLTAuMDY0NDUgLTEuMTE3MTksMCAt +MS43MTg3NSwwLjczMDQ3MSAtMC41OTQ0LDAuNzIzMzA5IC0wLjU5NDQsMi4wODM5ODkgViAxMDAg +aCAtMS4zMjQ4OCB2IC04LjAyMDg1MiBoIDEuMzI0ODggdiAxLjI0NjA5NyBxIDAuNDE1MzYsLTAu +NzMwNDcxIDEuMDgxMzgsLTEuMDgxMzgzIDAuNjY2MDIsLTAuMzU4MDc0IDEuNjE4NDksLTAuMzU4 +MDc0IDAuMTM2MDcsMCAwLjMwMDc4LDAuMDIxNDggMC4xNjQ3MiwwLjAxNDMyIDAuMzY1MjQsMC4w +NTAxMyB6IiAvPgogICAgICA8cGF0aAogICAgICAgICBpZD0icGF0aDY4IgogICAgICAgICBkPSJt +IDE3MS44NTEwNyw4OC44NTY3NDUgaCAxLjMxNzcyIFYgMTAwIGggLTEuMzE3NzIgeiIgLz4KICAg +ICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGg3MCIKICAgICAgICAgZD0ibSAxODEuMTk2OCw5My4x +OTY1OTkgdiAtNC4zMzk4NTQgaCAxLjMxNzcxIFYgMTAwIGggLTEuMzE3NzEgdiAtMS4yMDMxMjgg +cSAtMC40MTUzNywwLjcxNjE0OCAtMS4wNTI3NCwxLjA2NzA2IC0wLjYzMDIxLDAuMzQzNzQ4IC0x +LjUxODIzLDAuMzQzNzQ4IC0xLjQ1Mzc4LDAgLTIuMzcwNDUsLTEuMTYwMTU2IC0wLjkwOTUsLTEu +MTYwMTU5IC0wLjkwOTUsLTMuMDUwNzg4IDAsLTEuODkwNjMgMC45MDk1LC0zLjA1MDc4OSAwLjkx +NjY3LC0xLjE2MDE1OSAyLjM3MDQ1LC0xLjE2MDE1OSAwLjg4ODAyLDAgMS41MTgyMywwLjM1MDkx +MyAwLjYzNzM3LDAuMzQzNzUgMS4wNTI3NCwxLjA1OTg5OCB6IG0gLTQuNDkwMjQsMi44MDAxMzcg +cSAwLDEuNDUzNzc5IDAuNTk0NCwyLjI4NDUxIDAuNjAxNTYsMC44MjM1NyAxLjY0NzE0LDAuODIz +NTcgMS4wNDU1NywwIDEuNjQ3MTQsLTAuODIzNTcgMC42MDE1NiwtMC44MzA3MzEgMC42MDE1Niwt +Mi4yODQ1MSAwLC0xLjQ1Mzc4IC0wLjYwMTU2LC0yLjI3NzM0OSAtMC42MDE1NywtMC44MzA3MzEg +LTEuNjQ3MTQsLTAuODMwNzMxIC0xLjA0NTU4LDAgLTEuNjQ3MTQsMC44MzA3MzEgLTAuNTk0NCww +LjgyMzU2OSAtMC41OTQ0LDIuMjc3MzQ5IHoiIC8+CiAgICA8L2c+CiAgICA8ZwogICAgICAgaW5r +c2NhcGU6bGFiZWw9IiNmbG93Um9vdDE0IgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo0MHB4O2xp +bmUtaGVpZ2h0OjEuMjU7Zm9udC1mYW1pbHk6c2Fucy1zZXJpZjtmaWxsOiMwMDAwMDA7c3Ryb2tl +Om5vbmUiCiAgICAgICBpZD0idDQiCiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLjI2NDU4MzMz +LDAsMCwwLjI2NDU4MzMzLDM3Mi4wMjk2MSwyOC45MDQ1MDUpIgogICAgICAgYXJpYS1sYWJlbD0i +ZmxvdyB0ZXh0IHdoaWNoIHdyYXBzIj4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGg3MyIK +ICAgICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo1NS40MzMxcHgiCiAgICAgICAgIGQ9Im0gMTIxLjU3 +NjA3LDIyNi41OTEyNSBoIDEzLjg1ODI4IHYgNDIuMTE2MTYgaCAtNS4wMDczOSB2IC0zNy45NzQ5 +MiBoIC04LjkwNTAyIHEgLTIuNjc5NjMsMCAtMy43MzUyNCwxLjA4MjY4IC0xLjAyODU0LDEuMDgy +NjggLTEuMDI4NTQsMy44OTc2NCB2IDIuNjc5NjIgaCA4LjIwMTI4IHYgMy44NzA1OCBoIC04LjIw +MTI4IHYgMjYuNDQ0NCBoIC01LjAwNzM5IHYgLTI2LjQ0NDQgaCAtNC43NjM3OCB2IC0zLjg3MDU4 +IGggNC43NjM3OCB2IC0yLjExMTIyIHEgMCwtNS4wNjE1MiAyLjM1NDgzLC03LjM2MjIxIDIuMzU0 +ODIsLTIuMzI3NzUgNy40NzA0NywtMi4zMjc3NSB6IiAvPgogICAgICA8cGF0aAogICAgICAgICBp +ZD0icGF0aDc1IgogICAgICAgICBzdHlsZT0iZm9udC1zaXplOjU1LjQzMzFweCIKICAgICAgICAg +ZD0ibSAxNTcuNjU2MzEsMjQxLjg4NDA3IHEgLTQuMDA1OTEsMCAtNi4zMzM2NiwzLjEzOTc3IC0y +LjMyNzc2LDMuMTEyNjkgLTIuMzI3NzYsOC41NTMxNSAwLDUuNDQwNDYgMi4zMDA2OSw4LjU4MDIy +IDIuMzI3NzYsMy4xMTI3IDYuMzYwNzMsMy4xMTI3IDMuOTc4ODQsMCA2LjMwNjYsLTMuMTM5Nzcg +Mi4zMjc3NiwtMy4xMzk3NiAyLjMyNzc2LC04LjU1MzE1IDAsLTUuMzg2MzIgLTIuMzI3NzYsLTgu +NTI2MDkgLTIuMzI3NzYsLTMuMTY2ODMgLTYuMzA2NiwtMy4xNjY4MyB6IG0gMCwtNC4yMjI0NCBx +IDYuNDk2MDcsMCAxMC4yMDQyNCw0LjIyMjQ0IDMuNzA4MTcsNC4yMjI0NCAzLjcwODE3LDExLjY5 +MjkyIDAsNy40NDM0MSAtMy43MDgxNywxMS42OTI5MiAtMy43MDgxNyw0LjIyMjQ0IC0xMC4yMDQy +NCw0LjIyMjQ0IC02LjUyMzEzLDAgLTEwLjIzMTMsLTQuMjIyNDQgLTMuNjgxMTEsLTQuMjQ5NTEg +LTMuNjgxMTEsLTExLjY5MjkyIDAsLTcuNDcwNDggMy42ODExMSwtMTEuNjkyOTIgMy43MDgxNywt +NC4yMjI0NCAxMC4yMzEzLC00LjIyMjQ0IHoiIC8+CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJw +YXRoNzciCiAgICAgICAgIHN0eWxlPSJmb250LXNpemU6NTUuNDMzMXB4IgogICAgICAgICBkPSJt +IDE3Ni45MDA5MSwyMzguMzkyNDMgaCA0Ljk4MDMyIGwgNi4yMjUzOSwyMy42NTY1MSA2LjE5ODMz +LC0yMy42NTY1MSBoIDUuODczNTMgbCA2LjIyNTQsMjMuNjU2NTEgNi4xOTgzMywtMjMuNjU2NTEg +aCA0Ljk4MDMxIGwgLTcuOTMwNjEsMzAuMzE0OTggaCAtNS44NzM1MyBsIC02LjUyMzEzLC0yNC44 +NDc0NSAtNi41NTAyLDI0Ljg0NzQ1IGggLTUuODczNTMgeiIgLz4KICAgICAgPHBhdGgKICAgICAg +ICAgaWQ9InBhdGg3OSIKICAgICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo1NS40MzMxcHgiCiAgICAg +ICAgIGQ9Im0gMjQ3LjczNTEsMjI5Ljc4NTE1IHYgOC42MDcyOCBoIDEwLjI1ODM3IHYgMy44NzA1 +OCBIIDI0Ny43MzUxIHYgMTYuNDU2NyBxIDAsMy43MDgxNyAxLjAwMTQ3LDQuNzYzNzggMS4wMjg1 +NSwxLjA1NTYxIDQuMTQxMjQsMS4wNTU2MSBoIDUuMTE1NjYgdiA0LjE2ODMxIGggLTUuMTE1NjYg +cSAtNS43NjUyNSwwIC03Ljk1NzY4LC0yLjEzODI5IC0yLjE5MjQyLC0yLjE2NTM1IC0yLjE5MjQy +LC03Ljg0OTQxIHYgLTE2LjQ1NjcgaCAtMy42NTQwNCB2IC0zLjg3MDU4IGggMy42NTQwNCB2IC04 +LjYwNzI4IHoiIC8+CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoODEiCiAgICAgICAgIHN0 +eWxlPSJmb250LXNpemU6NTUuNDMzMXB4IgogICAgICAgICBkPSJtIDI5MC41MDA4NywyNTIuMzA0 +ODQgdiAyLjQzNjAzIGggLTIyLjg5ODY0IHEgMC4zMjQ4MSw1LjE0MjcyIDMuMDg1NjQsNy44NDk0 +MSAyLjc4Nzg5LDIuNjc5NjMgNy43NDExNCwyLjY3OTYzIDIuODY5MSwwIDUuNTQ4NzMsLTAuNzAz +NzQgMi43MDY2OSwtMC43MDM3NCA1LjM1OTI1LC0yLjExMTIyIHYgNC43MDk2NSBxIC0yLjY3OTYz +LDEuMTM2ODEgLTUuNDk0NTksMS43MzIyOCAtMi44MTQ5NiwwLjU5NTQ3IC01LjcxMTEyLDAuNTk1 +NDcgLTcuMjUzOTQsMCAtMTEuNTAzNDYsLTQuMjIyNDQgLTQuMjIyNDQsLTQuMjIyNDQgLTQuMjIy +NDQsLTExLjQyMjI1IDAsLTcuNDQzNDEgNC4wMDU5MSwtMTEuODAxMTkgNC4wMzI5NywtNC4zODQ4 +NCAxMC44NTM4NCwtNC4zODQ4NCA2LjExNzEzLDAgOS42NjI5LDMuOTUxNzcgMy41NzI4NCwzLjky +NDcxIDMuNTcyODQsMTAuNjkxNDQgeiBtIC00Ljk4MDMyLC0xLjQ2MTYxIHEgLTAuMDU0MSwtNC4w +ODcxMSAtMi4zMDA2OSwtNi41MjMxMyAtMi4yMTk0OSwtMi40MzYwMyAtNS45MDA1OSwtMi40MzYw +MyAtNC4xNjgzMSwwIC02LjY4NTU0LDIuMzU0ODIgLTIuNDkwMTYsMi4zNTQ4MyAtMi44NjkwOSw2 +LjYzMTQxIHoiIC8+CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoODMiCiAgICAgICAgIHN0 +eWxlPSJmb250LXNpemU6NTUuNDMzMXB4IgogICAgICAgICBkPSJtIDMyMi45MDAwMSwyMzguMzky +NDMgLTEwLjk2MjEyLDE0Ljc1MTQ5IDExLjUzMDUyLDE1LjU2MzQ5IGggLTUuODczNTIgbCAtOC44 +MjM4MywtMTEuOTA5NDUgLTguODIzODIsMTEuOTA5NDUgaCAtNS44NzM1MyBsIDExLjc3NDEyLC0x +NS44NjEyMyAtMTAuNzcyNjQsLTE0LjQ1Mzc1IGggNS44NzM1MiBsIDguMDM4ODksMTAuNzk5NzIg +OC4wMzg4OCwtMTAuNzk5NzIgeiIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGg4NSIK +ICAgICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo1NS40MzMxcHgiCiAgICAgICAgIGQ9Im0gMzM1LjQz +MTk5LDIyOS43ODUxNSB2IDguNjA3MjggaCAxMC4yNTgzOCB2IDMuODcwNTggaCAtMTAuMjU4Mzgg +diAxNi40NTY3IHEgMCwzLjcwODE3IDEuMDAxNDgsNC43NjM3OCAxLjAyODU1LDEuMDU1NjEgNC4x +NDEyNCwxLjA1NTYxIGggNS4xMTU2NiB2IDQuMTY4MzEgaCAtNS4xMTU2NiBxIC01Ljc2NTI1LDAg +LTcuOTU3NjgsLTIuMTM4MjkgLTIuMTkyNDIsLTIuMTY1MzUgLTIuMTkyNDIsLTcuODQ5NDEgdiAt +MTYuNDU2NyBoIC0zLjY1NDA0IHYgLTMuODcwNTggaCAzLjY1NDA0IHYgLTguNjA3MjggeiIgLz4K +ICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGg4NyIKICAgICAgICAgc3R5bGU9ImZvbnQtc2l6 +ZTo1NS40MzMxcHgiCiAgICAgICAgIGQ9Im0gMTA4LjA0MjYsMzA3LjY4MzgyIGggNC45ODAzMiBs +IDYuMjI1NCwyMy42NTY1MSA2LjE5ODMzLC0yMy42NTY1MSBoIDUuODczNTIgbCA2LjIyNTQsMjMu +NjU2NTEgNi4xOTgzMywtMjMuNjU2NTEgaCA0Ljk4MDMyIGwgLTcuOTMwNjIsMzAuMzE0OTcgaCAt +NS44NzM1MiBsIC02LjUyMzE0LC0yNC44NDc0NSAtNi41NTAyLDI0Ljg0NzQ1IGggLTUuODczNTIg +eiIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGg4OSIKICAgICAgICAgc3R5bGU9ImZv +bnQtc2l6ZTo1NS40MzMxcHgiCiAgICAgICAgIGQ9Im0gMTgxLjUwMjI5LDMxOS43MDE1NCB2IDE4 +LjI5NzI1IGggLTQuOTgwMzIgdiAtMTguMTM0ODUgcSAwLC00LjMwMzY0IC0xLjY3ODE1LC02LjQ0 +MTkzIC0xLjY3ODE1LC0yLjEzODI5IC01LjAzNDQ1LC0yLjEzODI5IC00LjAzMjk4LDAgLTYuMzYw +NzQsMi41NzEzNiAtMi4zMjc3NSwyLjU3MTM2IC0yLjMyNzc1LDcuMDEwMzQgdiAxNy4xMzMzNyBo +IC01LjAwNzM5IHYgLTQyLjExNjE2IGggNS4wMDczOSB2IDE2LjUxMDgzIHEgMS43ODY0MiwtMi43 +MzM3NiA0LjE5NTM3LC00LjA4NzEgMi40MzYwMywtMS4zNTMzNSA1LjYwMjg2LC0xLjM1MzM1IDUu +MjIzOTIsMCA3LjkwMzU1LDMuMjQ4MDMgMi42Nzk2MywzLjIyMDk3IDIuNjc5NjMsOS41MDA1IHoi +IC8+CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoOTEiCiAgICAgICAgIHN0eWxlPSJmb250 +LXNpemU6NTUuNDMzMXB4IgogICAgICAgICBkPSJtIDE5MS40ODk5OSwzMDcuNjgzODIgaCA0Ljk4 +MDMyIHYgMzAuMzE0OTcgaCAtNC45ODAzMiB6IG0gMCwtMTEuODAxMTkgaCA0Ljk4MDMyIHYgNi4z +MDY2IGggLTQuOTgwMzIgeiIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGg5MyIKICAg +ICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo1NS40MzMxcHgiCiAgICAgICAgIGQ9Im0gMjI4LjY3OTk3 +LDMwOC44NDc3IHYgNC42NTU1MSBxIC0yLjExMTIyLC0xLjE2Mzg4IC00LjI0OTUxLC0xLjczMjI4 +IC0yLjExMTIyLC0wLjU5NTQ4IC00LjI3NjU4LC0wLjU5NTQ4IC00Ljg0NDk4LDAgLTcuNTI0NjEs +My4wODU2MyAtMi42Nzk2MywzLjA1ODU3IC0yLjY3OTYzLDguNjA3MjkgMCw1LjU0ODczIDIuNjc5 +NjMsOC42MzQzNiAyLjY3OTYzLDMuMDU4NTYgNy41MjQ2MSwzLjA1ODU2IDIuMTY1MzYsMCA0LjI3 +NjU4LC0wLjU2ODQgMi4xMzgyOSwtMC41OTU0OCA0LjI0OTUxLC0xLjc1OTM2IHYgNC42MDEzOSBx +IC0yLjA4NDE2LDAuOTc0NDEgLTQuMzMwNzEsMS40NjE2MSAtMi4yMTk0OSwwLjQ4NzIxIC00Ljcz +NjcyLDAuNDg3MjEgLTYuODQ3OTQsMCAtMTAuODgwOTEsLTQuMzAzNjUgLTQuMDMyOTcsLTQuMzAz +NjQgLTQuMDMyOTcsLTExLjYxMTcyIDAsLTcuNDE2MzQgNC4wNjAwNCwtMTEuNjY1ODUgNC4wODcx +MSwtNC4yNDk1MSAxMS4xNzg2NSwtNC4yNDk1MSAyLjMwMDY5LDAgNC40OTMxMSwwLjQ4NzIgMi4x +OTI0MiwwLjQ2MDE0IDQuMjQ5NTEsMS40MDc0OSB6IiAvPgogICAgICA8cGF0aAogICAgICAgICBp +ZD0icGF0aDk1IgogICAgICAgICBzdHlsZT0iZm9udC1zaXplOjU1LjQzMzFweCIKICAgICAgICAg +ZD0ibSAyNjIuNTk0ODUsMzE5LjcwMTU0IHYgMTguMjk3MjUgaCAtNC45ODAzMSB2IC0xOC4xMzQ4 +NSBxIDAsLTQuMzAzNjQgLTEuNjc4MTYsLTYuNDQxOTMgLTEuNjc4MTUsLTIuMTM4MjkgLTUuMDM0 +NDUsLTIuMTM4MjkgLTQuMDMyOTcsMCAtNi4zNjA3MywyLjU3MTM2IC0yLjMyNzc2LDIuNTcxMzYg +LTIuMzI3NzYsNy4wMTAzNCB2IDE3LjEzMzM3IGggLTUuMDA3MzggdiAtNDIuMTE2MTYgaCA1LjAw +NzM4IHYgMTYuNTEwODMgcSAxLjc4NjQyLC0yLjczMzc2IDQuMTk1MzgsLTQuMDg3MSAyLjQzNjAz +LC0xLjM1MzM1IDUuNjAyODYsLTEuMzUzMzUgNS4yMjM5MiwwIDcuOTAzNTUsMy4yNDgwMyAyLjY3 +OTYyLDMuMjIwOTcgMi42Nzk2Miw5LjUwMDUgeiIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9 +InBhdGg5NyIKICAgICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo1NS40MzMxcHgiCiAgICAgICAgIGQ9 +Im0gMTA4LjA0MjYsMzc2Ljk3NTIgaCA0Ljk4MDMyIGwgNi4yMjU0LDIzLjY1NjUxIDYuMTk4MzMs +LTIzLjY1NjUxIGggNS44NzM1MiBsIDYuMjI1NCwyMy42NTY1MSA2LjE5ODMzLC0yMy42NTY1MSBo +IDQuOTgwMzIgbCAtNy45MzA2MiwzMC4zMTQ5OCBoIC01Ljg3MzUyIGwgLTYuNTIzMTQsLTI0Ljg0 +NzQ2IC02LjU1MDIsMjQuODQ3NDYgaCAtNS44NzM1MiB6IiAvPgogICAgICA8cGF0aAogICAgICAg +ICBpZD0icGF0aDk5IgogICAgICAgICBzdHlsZT0iZm9udC1zaXplOjU1LjQzMzFweCIKICAgICAg +ICAgZD0ibSAxNzMuODY5NDEsMzgxLjYzMDcxIHEgLTAuODM5MDgsLTAuNDg3MiAtMS44NDA1NSwt +MC43MDM3NCAtMC45NzQ0MSwtMC4yNDM2IC0yLjE2NTM2LC0wLjI0MzYgLTQuMjIyNDQsMCAtNi40 +OTYwNywyLjc2MDgzIC0yLjI0NjU1LDIuNzMzNzYgLTIuMjQ2NTUsNy44NzY0OCB2IDE1Ljk2OTUg +aCAtNS4wMDczOSBWIDM3Ni45NzUyIGggNS4wMDczOSB2IDQuNzA5NjUgcSAxLjU2OTg4LC0yLjc2 +MDgzIDQuMDg3MTEsLTQuMDg3MTEgMi41MTcyMiwtMS4zNTMzNSA2LjExNzEyLC0xLjM1MzM1IDAu +NTE0MjgsMCAxLjEzNjgyLDAuMDgxMiAwLjYyMjU0LDAuMDU0MSAxLjM4MDQxLDAuMTg5NDcgeiIg +Lz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGgxMDEiCiAgICAgICAgIHN0eWxlPSJmb250 +LXNpemU6NTUuNDMzMXB4IgogICAgICAgICBkPSJtIDE5Mi45MjQ1MywzOTIuMDUxNDkgcSAtNi4w +MzU5MywwIC04LjM2MzY4LDEuMzgwNDEgLTIuMzI3NzYsMS4zODA0MSAtMi4zMjc3Niw0LjcwOTY1 +IDAsMi42NTI1NiAxLjczMjI4LDQuMjIyNDQgMS43NTkzNiwxLjU0MjgyIDQuNzYzNzksMS41NDI4 +MiA0LjE0MTI0LDAgNi42MzE0LC0yLjkyMzIzIDIuNTE3MjIsLTIuOTUwMyAyLjUxNzIyLC03Ljgy +MjM1IHYgLTEuMTA5NzQgeiBtIDkuOTMzNTcsLTIuMDU3MDkgdiAxNy4yOTU3OCBoIC00Ljk4MDMy +IHYgLTQuNjAxMzggcSAtMS43MDUyMSwyLjc2MDgyIC00LjI0OTUxLDQuMDg3MSAtMi41NDQyOSwx +LjI5OTIyIC02LjIyNTM5LDEuMjk5MjIgLTQuNjU1NTIsMCAtNy40MTYzNSwtMi41OTg0MyAtMi43 +MzM3NiwtMi42MjU0OSAtMi43MzM3NiwtNy4wMTAzNCAwLC01LjExNTY1IDMuNDEwNDQsLTcuNzE0 +MDggMy40Mzc1LC0yLjU5ODQyIDEwLjIzMTMsLTIuNTk4NDIgaCA2Ljk4MzI3IHYgLTAuNDg3MjEg +cSAwLC0zLjQzNzUgLTIuMjczNjIsLTUuMzA1MTIgLTIuMjQ2NTYsLTEuODk0NjkgLTYuMzMzNjYs +LTEuODk0NjkgLTIuNTk4NDMsMCAtNS4wNjE1MiwwLjYyMjU0IC0yLjQ2MzA5LDAuNjIyNTQgLTQu +NzM2NzIsMS44Njc2MiB2IC00LjYwMTM4IHEgMi43MzM3NiwtMS4wNTU2MSA1LjMwNTEyLC0xLjU2 +OTg4IDIuNTcxMzYsLTAuNTQxMzQgNS4wMDczOSwtMC41NDEzNCA2LjU3NzI2LDAgOS44MjUzLDMu +NDEwNDQgMy4yNDgwMywzLjQxMDQzIDMuMjQ4MDMsMTAuMzM5NTcgeiIgLz4KICAgICAgPHBhdGgK +ICAgICAgICAgaWQ9InBhdGgxMDMiCiAgICAgICAgIHN0eWxlPSJmb250LXNpemU6NTUuNDMzMXB4 +IgogICAgICAgICBkPSJtIDIxNy45NjE0Niw0MDIuNzQyOTMgdiAxNi4wNzc3NiBoIC01LjAwNzM4 +IFYgMzc2Ljk3NTIgaCA1LjAwNzM4IHYgNC42MDEzOCBxIDEuNTY5ODgsLTIuNzA2NyAzLjk1MTc3 +LC00LjAwNTkxIDIuNDA4OTYsLTEuMzI2MjggNS43MzgyLC0xLjMyNjI4IDUuNTIxNjUsMCA4Ljk1 +OTE2LDQuMzg0ODUgMy40NjQ1Niw0LjM4NDg0IDMuNDY0NTYsMTEuNTMwNTEgMCw3LjE0NTY4IC0z +LjQ2NDU2LDExLjUzMDUyIC0zLjQzNzUxLDQuMzg0ODUgLTguOTU5MTYsNC4zODQ4NSAtMy4zMjky +NCwwIC01LjczODIsLTEuMjk5MjIgLTIuMzgxODksLTEuMzI2MjggLTMuOTUxNzcsLTQuMDMyOTcg +eiBtIDE2Ljk0MzkxLC0xMC41ODMxOCBxIDAsLTUuNDk0NTkgLTIuMjczNjMsLTguNjA3MjggLTIu +MjQ2NTUsLTMuMTM5NzcgLTYuMTk4MzMsLTMuMTM5NzcgLTMuOTUxNzcsMCAtNi4yMjUzOSwzLjEz +OTc3IC0yLjI0NjU2LDMuMTEyNjkgLTIuMjQ2NTYsOC42MDcyOCAwLDUuNDk0NTkgMi4yNDY1Niw4 +LjYzNDM2IDIuMjczNjIsMy4xMTI3IDYuMjI1MzksMy4xMTI3IDMuOTUxNzgsMCA2LjE5ODMzLC0z +LjExMjcgMi4yNzM2MywtMy4xMzk3NyAyLjI3MzYzLC04LjYzNDM2IHoiIC8+CiAgICAgIDxwYXRo +CiAgICAgICAgIGlkPSJwYXRoMTA1IgogICAgICAgICBzdHlsZT0iZm9udC1zaXplOjU1LjQzMzFw +eCIKICAgICAgICAgZD0ibSAyNjcuNjU2MzgsMzc3Ljg2ODQxIHYgNC43MDk2NSBxIC0yLjExMTIy +LC0xLjA4MjY4IC00LjM4NDg1LC0xLjYyNDAyIC0yLjI3MzYyLC0wLjU0MTM0IC00LjcwOTY1LC0w +LjU0MTM0IC0zLjcwODE3LDAgLTUuNTc1NzksMS4xMzY4MSAtMS44NDA1NSwxLjEzNjgxIC0xLjg0 +MDU1LDMuNDEwNDQgMCwxLjczMjI4IDEuMzI2MjgsMi43MzM3NiAxLjMyNjI4LDAuOTc0NDEgNS4z +MzIxOSwxLjg2NzYyIGwgMS43MDUyMiwwLjM3ODkzIHEgNS4zMDUxMiwxLjEzNjgyIDcuNTI0NjEs +My4yMjA5NyAyLjI0NjU1LDIuMDU3MDkgMi4yNDY1NSw1Ljc2NTI2IDAsNC4yMjI0NCAtMy4zNTYz +LDYuNjg1NTQgLTMuMzI5MjMsMi40NjMwOSAtOS4xNzU2OSwyLjQ2MzA5IC0yLjQzNjAzLDAgLTUu +MDg4NTksLTAuNDg3MjEgLTIuNjI1NDksLTAuNDYwMTQgLTUuNTQ4NzIsLTEuNDA3NDggdiAtNS4x +NDI3MiBxIDIuNzYwODMsMS40MzQ1NSA1LjQ0MDQ2LDIuMTY1MzYgMi42Nzk2MiwwLjcwMzc0IDUu +MzA1MTIsMC43MDM3NCAzLjUxODcsMCA1LjQxMzM5LC0xLjE5MDk1IDEuODk0NjgsLTEuMjE4MDEg +MS44OTQ2OCwtMy40MTA0MyAwLC0yLjAzMDAyIC0xLjM4MDQxLC0zLjExMjcgLTEuMzUzMzUsLTEu +MDgyNjggLTUuOTgxOCwtMi4wODQxNiBsIC0xLjczMjI4LC0wLjQwNiBxIC00LjYyODQ1LC0wLjk3 +NDQxIC02LjY4NTU0LC0yLjk3NzM2IC0yLjA1NzA4LC0yLjAzMDAyIC0yLjA1NzA4LC01LjU0ODcz +IDAsLTQuMjc2NTcgMy4wMzE0OSwtNi42MDQzMyAzLjAzMTUsLTIuMzI3NzYgOC42MDcyOSwtMi4z +Mjc3NiAyLjc2MDgzLDAgNS4xOTY4NSwwLjQwNjAxIDIuNDM2MDMsMC40MDYgNC40OTMxMiwxLjIx +ODAxIHoiIC8+CiAgICA8L2c+CiAgICA8ZwogICAgICAgaW5rc2NhcGU6bGFiZWw9IiN0ZXh0Mzcy +NyIKICAgICAgIHN0eWxlPSJmb250LXNpemU6MTQuNjY2N3B4O2xpbmUtaGVpZ2h0OjEuMjU7Zm9u +dC1mYW1pbHk6c2Fucy1zZXJpZjtmaWxsOiMwMDAwMDA7c3Ryb2tlOm5vbmUiCiAgICAgICBpZD0i +dDIiCiAgICAgICBhcmlhLWxhYmVsPSJVUFBFUiI+CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJw +YXRoMTA4IgogICAgICAgICBzdHlsZT0iZm9udC1zaXplOjE0LjY2NjdweCIKICAgICAgICAgZD0i +bSAyMDEuMjc0NzQsODkuMzA3OTE4IGggMS40NTM3OCB2IDYuNDk1NDU4IHEgMCwxLjcxODc1NCAw +LjYyMzA1LDIuNDc3ODcgMC42MjMwNSwwLjc1MTk1NSAyLjAxOTU0LDAuNzUxOTU1IDEuMzg5MzIs +MCAyLjAxMjM3LC0wLjc1MTk1NSAwLjYyMzA1LC0wLjc1OTExNiAwLjYyMzA1LC0yLjQ3Nzg3IHYg +LTYuNDk1NDU4IGggMS40NTM3OCB2IDYuNjc0NDk1IHEgMCwyLjA5MTE1IC0xLjAzODQyLDMuMTU4 +MjEgLTEuMDMxMjUsMS4wNjcwNTcgLTMuMDUwNzgsMS4wNjcwNTcgLTIuMDI2NywwIC0zLjA2NTEy +LC0xLjA2NzA1NyAtMS4wMzEyNSwtMS4wNjcwNiAtMS4wMzEyNSwtMy4xNTgyMSB6IiAvPgogICAg +ICA8cGF0aAogICAgICAgICBpZD0icGF0aDExMCIKICAgICAgICAgc3R5bGU9ImZvbnQtc2l6ZTox +NC42NjY3cHgiCiAgICAgICAgIGQ9Im0gMjEzLjYyODI5LDkwLjQ5NjcyMyB2IDQuMDE3NTg3IGgg +MS44MTkwMSBxIDEuMDA5NzcsMCAxLjU2MTIsLTAuNTIyNzg3IDAuNTUxNDQsLTAuNTIyNzg4IDAu +NTUxNDQsLTEuNDg5NTg3IDAsLTAuOTU5NjM4IC0wLjU1MTQ0LC0xLjQ4MjQyNSAtMC41NTE0Mywt +MC41MjI3ODggLTEuNTYxMiwtMC41MjI3ODggeiBtIC0xLjQ0NjYyLC0xLjE4ODgwNSBoIDMuMjY1 +NjMgcSAxLjc5NzUzLDAgMi43MTQyLDAuODE2NDA4IDAuOTIzODMsMC44MDkyNDcgMC45MjM4Mywy +LjM3NzYxIDAsMS41ODI2ODYgLTAuOTIzODMsMi4zOTE5MzIgLTAuOTE2NjcsMC44MDkyNDcgLTIu +NzE0MiwwLjgwOTI0NyBoIC0xLjgxOTAxIFYgMTAwIGggLTEuNDQ2NjIgeiIgLz4KICAgICAgPHBh +dGgKICAgICAgICAgaWQ9InBhdGgxMTIiCiAgICAgICAgIHN0eWxlPSJmb250LXNpemU6MTQuNjY2 +N3B4IgogICAgICAgICBkPSJtIDIyMi40Nzk4Nyw5MC40OTY3MjMgdiA0LjAxNzU4NyBoIDEuODE5 +MDEgcSAxLjAwOTc3LDAgMS41NjEyMSwtMC41MjI3ODcgMC41NTE0MywtMC41MjI3ODggMC41NTE0 +MywtMS40ODk1ODcgMCwtMC45NTk2MzggLTAuNTUxNDMsLTEuNDgyNDI1IC0wLjU1MTQ0LC0wLjUy +Mjc4OCAtMS41NjEyMSwtMC41MjI3ODggeiBtIC0xLjQ0NjYyLC0xLjE4ODgwNSBoIDMuMjY1NjMg +cSAxLjc5NzUzLDAgMi43MTQyLDAuODE2NDA4IDAuOTIzODMsMC44MDkyNDcgMC45MjM4MywyLjM3 +NzYxIDAsMS41ODI2ODYgLTAuOTIzODMsMi4zOTE5MzIgLTAuOTE2NjcsMC44MDkyNDcgLTIuNzE0 +MiwwLjgwOTI0NyBoIC0xLjgxOTAxIFYgMTAwIGggLTEuNDQ2NjIgeiIgLz4KICAgICAgPHBhdGgK +ICAgICAgICAgaWQ9InBhdGgxMTQiCiAgICAgICAgIHN0eWxlPSJmb250LXNpemU6MTQuNjY2N3B4 +IgogICAgICAgICBkPSJtIDIyOS44ODQ4Myw4OS4zMDc5MTggaCA2Ljc2MDQ0IHYgMS4yMTc0NTEg +aCAtNS4zMTM4MiB2IDMuMTY1MzcyIGggNS4wOTE4MSB2IDEuMjE3NDUgaCAtNS4wOTE4MSB2IDMu +ODc0MzU4IGggNS40NDI3MiBWIDEwMCBoIC02Ljg4OTM0IHoiIC8+CiAgICAgIDxwYXRoCiAgICAg +ICAgIGlkPSJwYXRoMTE2IgogICAgICAgICBzdHlsZT0iZm9udC1zaXplOjE0LjY2NjdweCIKICAg +ICAgICAgZD0ibSAyNDQuMjM2NDMsOTQuOTg2OTY4IHEgMC40NjU0OSwwLjE1NzU1MiAwLjkwMjM0 +LDAuNjczMTc4IDAuNDQ0MDEsMC41MTU2MjYgMC44ODgwMywxLjQxNzk3MiBMIDI0Ny40OTQ5LDEw +MCBoIC0xLjU1NDA0IGwgLTEuMzY3ODQsLTIuNzQyODQ1IHEgLTAuNTI5OTUsLTEuMDc0MjIxIC0x +LjAzMTI2LC0xLjQyNTEzMyAtMC40OTQxNCwtMC4zNTA5MTMgLTEuMzUzNTEsLTAuMzUwOTEzIGgg +LTEuNTc1NTMgViAxMDAgSCAyMzkuMTY2MSBWIDg5LjMwNzkxOCBoIDMuMjY1NjQgcSAxLjgzMzMz +LDAgMi43MzU2OCwwLjc2NjI3OCAwLjkwMjM0LDAuNzY2Mjc4IDAuOTAyMzQsMi4zMTMxNTYgMCwx +LjAwOTc2OCAtMC40NzI2NSwxLjY3NTc4NSAtMC40NjU1LDAuNjY2MDE4IC0xLjM2MDY4LDAuOTIz +ODMxIHogbSAtMy42MjM3MSwtNC40OTAyNDUgdiAzLjc5NTU4MiBoIDEuODE5MDIgcSAxLjA0NTU3 +LDAgMS41NzU1MiwtMC40Nzk4MTkgMC41MzcxMSwtMC40ODY5ODEgMC41MzcxMSwtMS40MjUxMzQg +MCwtMC45MzgxNTMgLTAuNTM3MTEsLTEuNDEwODEgLTAuNTI5OTUsLTAuNDc5ODE5IC0xLjU3NTUy +LC0wLjQ3OTgxOSB6IiAvPgogICAgPC9nPgogICAgPGcKICAgICAgIGlua3NjYXBlOmxhYmVsPSIj +dGV4dDM3MzUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjEwLjU4MzNweDtsaW5lLWhlaWdodDox +LjI1O2ZvbnQtZmFtaWx5OnNhbnMtc2VyaWY7ZmlsbDojMDAwMDAwO3N0cm9rZTpub25lIgogICAg +ICAgaWQ9InQzIgogICAgICAgYXJpYS1sYWJlbD0iTXVsdGkgbGluZQp0ZXh0CkZPTyI+CiAgICAg +IDxwYXRoCiAgICAgICAgIGlkPSJwYXRoMTE5IgogICAgICAgICBzdHlsZT0iZm9udC1zaXplOjE0 +LjY2NjdweCIKICAgICAgICAgZD0ibSAzMDEuNDM5NDYsODkuMzA3OTE4IGggMi4xNTU2IGwgMi43 +Mjg1Miw3LjI3NjA1OSAyLjc0Mjg1LC03LjI3NjA1OSBoIDIuMTU1NiBWIDEwMCBoIC0xLjQxMDgx +IHYgLTkuMzg4NjkzIGwgLTIuNzU3MTcsNy4zMzMzNSBoIC0xLjQ1Mzc4IGwgLTIuNzU3MTYsLTcu +MzMzMzUgViAxMDAgaCAtMS40MDM2NSB6IiAvPgogICAgICA8cGF0aAogICAgICAgICBpZD0icGF0 +aDEyMSIKICAgICAgICAgc3R5bGU9ImZvbnQtc2l6ZToxNC42NjY3cHgiCiAgICAgICAgIGQ9Im0g +MzEzLjkwNzU4LDk2LjgzNDYyOCB2IC00Ljg1NTQ4IGggMS4zMTc3MiB2IDQuODA1MzUgcSAwLDEu +MTM4Njc0IDAuNDQ0MDEsMS43MTE1OTIgMC40NDQwMSwwLjU2NTc1NyAxLjMzMjAzLDAuNTY1NzU3 +IDEuMDY3MDYsMCAxLjY4Mjk1LC0wLjY4MDM0IDAuNjIzMDUsLTAuNjgwMzQgMC42MjMwNSwtMS44 +NTQ4MjIgdiAtNC41NDc1MzcgaCAxLjMxNzcxIFYgMTAwIGggLTEuMzE3NzEgdiAtMS4yMzE3NzQg +cSAtMC40Nzk4MiwwLjczMDQ3MSAtMS4xMTcxOSwxLjA4ODU0NSAtMC42MzAyMSwwLjM1MDkwOSAt +MS40NjgxMSwwLjM1MDkwOSAtMS4zODIxNiwwIC0yLjA5ODMxLC0wLjg1OTM3NCAtMC43MTYxNSwt +MC44NTkzNzcgLTAuNzE2MTUsLTIuNTEzNjc4IHogbSAzLjMxNTc3LC01LjA0ODg0IHoiIC8+CiAg +ICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoMTIzIgogICAgICAgICBzdHlsZT0iZm9udC1zaXpl +OjE0LjY2NjdweCIKICAgICAgICAgZD0ibSAzMjMuMzUzNTcsODguODU2NzQ1IGggMS4zMTc3MSBW +IDEwMCBoIC0xLjMxNzcxIHoiIC8+CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoMTI1Igog +ICAgICAgICBzdHlsZT0iZm9udC1zaXplOjE0LjY2NjdweCIKICAgICAgICAgZD0ibSAzMjguNzI0 +NjgsODkuNzAxNzk5IHYgMi4yNzczNDkgaCAyLjcxNDIgdiAxLjAyNDA5MSBoIC0yLjcxNDIgdiA0 +LjM1NDE3NyBxIDAsMC45ODExMjIgMC4yNjQ5NywxLjI2MDQxOSAwLjI3MjE0LDAuMjc5Mjk4IDEu +MDk1NzEsMC4yNzkyOTggaCAxLjM1MzUyIFYgMTAwIGggLTEuMzUzNTIgcSAtMS41MjU0LDAgLTIu +MTA1NDgsLTAuNTY1NzU3IC0wLjU4MDA4LC0wLjU3MjkxNyAtMC41ODAwOCwtMi4wNzY4MjcgdiAt +NC4zNTQxNzcgaCAtMC45NjY4IHYgLTEuMDI0MDkxIGggMC45NjY4IHYgLTIuMjc3MzQ5IHoiIC8+ +CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoMTI3IgogICAgICAgICBzdHlsZT0iZm9udC1z +aXplOjE0LjY2NjdweCIKICAgICAgICAgZD0ibSAzMzMuMTc5MTEsOTEuOTc5MTQ4IGggMS4zMTc3 +MSBWIDEwMCBoIC0xLjMxNzcxIHogbSAwLC0zLjEyMjQwMyBoIDEuMzE3NzEgdiAxLjY2ODYyNCBo +IC0xLjMxNzcxIHoiIC8+CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoMTI5IgogICAgICAg +ICBzdHlsZT0iZm9udC1zaXplOjE0LjY2NjdweCIKICAgICAgICAgZD0ibSAzNDEuOTE2MTEsODgu +ODU2NzQ1IGggMS4zMTc3MSBWIDEwMCBoIC0xLjMxNzcxIHoiIC8+CiAgICAgIDxwYXRoCiAgICAg +ICAgIGlkPSJwYXRoMTMxIgogICAgICAgICBzdHlsZT0iZm9udC1zaXplOjE0LjY2NjdweCIKICAg +ICAgICAgZD0ibSAzNDUuOTgzODMsOTEuOTc5MTQ4IGggMS4zMTc3MSBWIDEwMCBoIC0xLjMxNzcx +IHogbSAwLC0zLjEyMjQwMyBoIDEuMzE3NzEgdiAxLjY2ODYyNCBoIC0xLjMxNzcxIHoiIC8+CiAg +ICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoMTMzIgogICAgICAgICBzdHlsZT0iZm9udC1zaXpl +OjE0LjY2NjdweCIKICAgICAgICAgZD0iTSAzNTYuNzE4ODgsOTUuMTU4ODQzIFYgMTAwIGggLTEu +MzE3NzEgdiAtNC43OTgxODggcSAwLC0xLjEzODY3NSAtMC40NDQwMSwtMS43MDQ0MzEgLTAuNDQ0 +MDEsLTAuNTY1NzU3IC0xLjMzMjA0LC0wLjU2NTc1NyAtMS4wNjcwNiwwIC0xLjY4Mjk0LDAuNjgw +MzQgLTAuNjE1ODksMC42ODAzNDEgLTAuNjE1ODksMS44NTQ4MjIgViAxMDAgaCAtMS4zMjQ4NyB2 +IC04LjAyMDg1MiBoIDEuMzI0ODcgdiAxLjI0NjA5NyBxIDAuNDcyNjYsLTAuNzIzMzA5IDEuMTEw +MDMsLTEuMDgxMzgzIDAuNjQ0NTMsLTAuMzU4MDc0IDEuNDgyNDMsLTAuMzU4MDc0IDEuMzgyMTYs +MCAyLjA5MTE1LDAuODU5Mzc3IDAuNzA4OTgsMC44NTIyMTYgMC43MDg5OCwyLjUxMzY3OCB6IiAv +PgogICAgICA8cGF0aAogICAgICAgICBpZD0icGF0aDEzNSIKICAgICAgICAgc3R5bGU9ImZvbnQt +c2l6ZToxNC42NjY3cHgiCiAgICAgICAgIGQ9Im0gMzY2LjIyMjE2LDk1LjY2MDE0NiB2IDAuNjQ0 +NTMzIGggLTYuMDU4NjEgcSAwLjA4NTksMS4zNjA2OCAwLjgxNjQxLDIuMDc2ODI4IDAuNzM3NjMs +MC43MDg5ODYgMi4wNDgxOCwwLjcwODk4NiAwLjc1OTEyLDAgMS40NjgxLC0wLjE4NjE5OSAwLjcx +NjE1LC0wLjE4NjE5OCAxLjQxNzk4LC0wLjU1ODU5NSB2IDEuMjQ2MDk3IHEgLTAuNzA4OTksMC4z +MDA3ODIgLTEuNDUzNzgsMC40NTgzMzQgLTAuNzQ0OCwwLjE1NzU1IC0xLjUxMTA4LDAuMTU3NTUg +LTEuOTE5MjcsMCAtMy4wNDM2MiwtMS4xMTcxODcgLTEuMTE3MTksLTEuMTE3MTkgLTEuMTE3MTks +LTMuMDIyMTQzIDAsLTEuOTY5NDA1IDEuMDU5OSwtMy4xMjI0MDMgMS4wNjcwNiwtMS4xNjAxNTkg +Mi44NzE3NSwtMS4xNjAxNTkgMS42MTg0OSwwIDIuNTU2NjQsMS4wNDU1NzYgMC45NDUzMiwxLjAz +ODQxNCAwLjk0NTMyLDIuODI4NzgyIHogbSAtMS4zMTc3MSwtMC4zODY3MTkgcSAtMC4wMTQzLC0x +LjA4MTM4MyAtMC42MDg3MywtMS43MjU5MTYgLTAuNTg3MjQsLTAuNjQ0NTMzIC0xLjU2MTIsLTAu +NjQ0NTMzIC0xLjEwMjg3LDAgLTEuNzY4ODgsMC42MjMwNDkgLTAuNjU4ODYsMC42MjMwNDggLTAu +NzU5MTIsMS43NTQ1NjEgeiIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGgxMzciCiAg +ICAgICAgIHN0eWxlPSJmb250LXNpemU6MTQuNjY2N3B4IgogICAgICAgICBkPSJtIDMwMi42ODU1 +NSwxMDguMDM1MTcgdiAyLjI3NzM1IGggMi43MTQyIHYgMS4wMjQwOSBoIC0yLjcxNDIgdiA0LjM1 +NDE4IHEgMCwwLjk4MTEyIDAuMjY0OTgsMS4yNjA0MiAwLjI3MjEzLDAuMjc5MyAxLjA5NTcsMC4y +NzkzIGggMS4zNTM1MiB2IDEuMTAyODcgaCAtMS4zNTM1MiBxIC0xLjUyNTM5LDAgLTIuMTA1NDcs +LTAuNTY1NzYgLTAuNTgwMDgsLTAuNTcyOTIgLTAuNTgwMDgsLTIuMDc2ODMgdiAtNC4zNTQxOCBo +IC0wLjk2NjggdiAtMS4wMjQwOSBoIDAuOTY2OCB2IC0yLjI3NzM1IHoiIC8+CiAgICAgIDxwYXRo +CiAgICAgICAgIGlkPSJwYXRoMTM5IgogICAgICAgICBzdHlsZT0iZm9udC1zaXplOjE0LjY2Njdw +eCIKICAgICAgICAgZD0ibSAzMTQuMDAwNjgsMTEzLjk5MzUyIHYgMC42NDQ1MyBoIC02LjA1ODYg +cSAwLjA4NTksMS4zNjA2OCAwLjgxNjQsMi4wNzY4MyAwLjczNzY0LDAuNzA4OTkgMi4wNDgxOSww +LjcwODk5IDAuNzU5MTEsMCAxLjQ2ODEsLTAuMTg2MiAwLjcxNjE1LC0wLjE4NjIgMS40MTc5Nywt +MC41NTg2IHYgMS4yNDYxIHEgLTAuNzA4OTksMC4zMDA3OCAtMS40NTM3OCwwLjQ1ODM0IC0wLjc0 +NDc5LDAuMTU3NTUgLTEuNTExMDcsMC4xNTc1NSAtMS45MTkyOCwwIC0zLjA0MzYzLC0xLjExNzE5 +IC0xLjExNzE5LC0xLjExNzE5IC0xLjExNzE5LC0zLjAyMjE0IDAsLTEuOTY5NDEgMS4wNTk5LC0z +LjEyMjQxIDEuMDY3MDYsLTEuMTYwMTYgMi44NzE3NSwtMS4xNjAxNiAxLjYxODUsMCAyLjU1NjY1 +LDEuMDQ1NTggMC45NDUzMSwxLjAzODQxIDAuOTQ1MzEsMi44Mjg3OCB6IG0gLTEuMzE3NzEsLTAu +Mzg2NzIgcSAtMC4wMTQzLC0xLjA4MTM4IC0wLjYwODcyLC0xLjcyNTkxIC0wLjU4NzI0LC0wLjY0 +NDU0IC0xLjU2MTIsLTAuNjQ0NTQgLTEuMTAyODcsMCAtMS43Njg4OSwwLjYyMzA1IC0wLjY1ODg1 +LDAuNjIzMDUgLTAuNzU5MTIsMS43NTQ1NiB6IiAvPgogICAgICA8cGF0aAogICAgICAgICBpZD0i +cGF0aDE0MSIKICAgICAgICAgc3R5bGU9ImZvbnQtc2l6ZToxNC42NjY3cHgiCiAgICAgICAgIGQ9 +Im0gMzIyLjU3Mjk3LDExMC4zMTI1MiAtMi45MDA0LDMuOTAzMDEgMy4wNTA3OSw0LjExNzg1IGgg +LTEuNTU0MDQgbCAtMi4zMzQ2NCwtMy4xNTEwNSAtMi4zMzQ2NCwzLjE1MTA1IEggMzE0Ljk0NiBs +IDMuMTE1MjQsLTQuMTk2NjMgLTIuODUwMjcsLTMuODI0MjMgaCAxLjU1NDA0IGwgMi4xMjY5Niwy +Ljg1NzQzIDIuMTI2OTYsLTIuODU3NDMgeiIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBh +dGgxNDMiCiAgICAgICAgIHN0eWxlPSJmb250LXNpemU6MTQuNjY2N3B4IgogICAgICAgICBkPSJt +IDMyNS44ODg3MywxMDguMDM1MTcgdiAyLjI3NzM1IGggMi43MTQyIHYgMS4wMjQwOSBoIC0yLjcx +NDIgdiA0LjM1NDE4IHEgMCwwLjk4MTEyIDAuMjY0OTgsMS4yNjA0MiAwLjI3MjEzLDAuMjc5MyAx +LjA5NTcsMC4yNzkzIGggMS4zNTM1MiB2IDEuMTAyODcgaCAtMS4zNTM1MiBxIC0xLjUyNTM5LDAg +LTIuMTA1NDcsLTAuNTY1NzYgLTAuNTgwMDgsLTAuNTcyOTIgLTAuNTgwMDgsLTIuMDc2ODMgdiAt +NC4zNTQxOCBoIC0wLjk2NjggdiAtMS4wMjQwOSBoIDAuOTY2OCB2IC0yLjI3NzM1IHoiIC8+CiAg +ICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoMTQ1IgogICAgICAgICBzdHlsZT0iZm9udC1zaXpl +OjE0LjY2NjdweCIKICAgICAgICAgZD0ibSAzMDEuNDM5NDYsMTI1Ljk3NDY3IGggNi4xNDQ1NCB2 +IDEuMjE3NDUgaCAtNC42OTc5MyB2IDMuMTUxMDUgaCA0LjIzOTYgdiAxLjIxNzQ1IGggLTQuMjM5 +NiB2IDUuMTA2MTMgaCAtMS40NDY2MSB6IiAvPgogICAgICA8cGF0aAogICAgICAgICBpZD0icGF0 +aDE0NyIKICAgICAgICAgc3R5bGU9ImZvbnQtc2l6ZToxNC42NjY3cHgiCiAgICAgICAgIGQ9Im0g +MzE0LjIyOTg1LDEyNi45NTU3OSBxIC0xLjU3NTUyLDAgLTIuNTA2NTIsMS4xNzQ0OCAtMC45MjM4 +MywxLjE3NDQ4IC0wLjkyMzgzLDMuMjAxMTggMCwyLjAxOTU0IDAuOTIzODMsMy4xOTQwMiAwLjkz +MSwxLjE3NDQ4IDIuNTA2NTIsMS4xNzQ0OCAxLjU3NTUzLDAgMi40OTIxOSwtMS4xNzQ0OCAwLjky +MzgzLC0xLjE3NDQ4IDAuOTIzODMsLTMuMTk0MDIgMCwtMi4wMjY3IC0wLjkyMzgzLC0zLjIwMTE4 +IC0wLjkxNjY2LC0xLjE3NDQ4IC0yLjQ5MjE5LC0xLjE3NDQ4IHogbSAwLC0xLjE3NDQ4IHEgMi4y +NDg3LDAgMy41OTUwNiwxLjUxMTA3IDEuMzQ2MzYsMS41MDM5MSAxLjM0NjM2LDQuMDM5MDcgMCwy +LjUyOCAtMS4zNDYzNiw0LjAzOTA3IC0xLjM0NjM2LDEuNTAzOTEgLTMuNTk1MDYsMS41MDM5MSAt +Mi4yNTU4NiwwIC0zLjYwOTM4LC0xLjUwMzkxIC0xLjM0NjM2LC0xLjUwMzkxIC0xLjM0NjM2LC00 +LjAzOTA3IDAsLTIuNTM1MTYgMS4zNDYzNiwtNC4wMzkwNyAxLjM1MzUyLC0xLjUxMTA3IDMuNjA5 +MzgsLTEuNTExMDcgeiIgLz4KICAgICAgPHBhdGgKICAgICAgICAgaWQ9InBhdGgxNDkiCiAgICAg +ICAgIHN0eWxlPSJmb250LXNpemU6MTQuNjY2N3B4IgogICAgICAgICBkPSJtIDMyNS43NzQxNSwx +MjYuOTU1NzkgcSAtMS41NzU1MywwIC0yLjUwNjUyLDEuMTc0NDggLTAuOTIzODMsMS4xNzQ0OCAt +MC45MjM4MywzLjIwMTE4IDAsMi4wMTk1NCAwLjkyMzgzLDMuMTk0MDIgMC45MzA5OSwxLjE3NDQ4 +IDIuNTA2NTIsMS4xNzQ0OCAxLjU3NTUyLDAgMi40OTIxOSwtMS4xNzQ0OCAwLjkyMzgzLC0xLjE3 +NDQ4IDAuOTIzODMsLTMuMTk0MDIgMCwtMi4wMjY3IC0wLjkyMzgzLC0zLjIwMTE4IC0wLjkxNjY3 +LC0xLjE3NDQ4IC0yLjQ5MjE5LC0xLjE3NDQ4IHogbSAwLC0xLjE3NDQ4IHEgMi4yNDg3LDAgMy41 +OTUwNiwxLjUxMTA3IDEuMzQ2MzYsMS41MDM5MSAxLjM0NjM2LDQuMDM5MDcgMCwyLjUyOCAtMS4z +NDYzNiw0LjAzOTA3IC0xLjM0NjM2LDEuNTAzOTEgLTMuNTk1MDYsMS41MDM5MSAtMi4yNTU4Nyww +IC0zLjYwOTM4LC0xLjUwMzkxIC0xLjM0NjM2LC0xLjUwMzkxIC0xLjM0NjM2LC00LjAzOTA3IDAs +LTIuNTM1MTYgMS4zNDYzNiwtNC4wMzkwNyAxLjM1MzUxLC0xLjUxMTA3IDMuNjA5MzgsLTEuNTEx +MDcgeiIgLz4KICAgIDwvZz4KICAgIDxnCiAgICAgICBpbmtzY2FwZTpsYWJlbD0iI2czNzcyIgog +ICAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDQ1LjcxMDM4LC0xMjkuNjQ4MDcpIgogICAgICAg +aWQ9InQ1Ij4KICAgICAgPGcKICAgICAgICAgc3R5bGU9ImZvbnQtc2l6ZToxMC41ODMzcHg7bGlu +ZS1oZWlnaHQ6MS4yNTtmb250LWZhbWlseTpzYW5zLXNlcmlmO2ZpbGw6IzAwMDAwMDtzdHJva2U6 +bm9uZSIKICAgICAgICAgaWQ9InRleHQzNzYyIgogICAgICAgICBhcmlhLWxhYmVsPSJHcm91cGVk +Ij4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJwYXRoMTUyIgogICAgICAgICAgIHN0eWxl +PSJmb250LXNpemU6MTQuNjY2N3B4IgogICAgICAgICAgIGQ9Im0gNjMuMDE5NDUzLDIyOC4xMjI2 +OCB2IC0yLjg3MTc1IGggLTIuMzYzMjg2IHYgLTEuMTg4ODEgaCAzLjc5NTU4MSB2IDQuNTkwNTEg +cSAtMC44Mzc4OTIsMC41OTQ0IC0xLjg0NzY2LDAuOTAyMzQgLTEuMDA5NzY4LDAuMzAwNzggLTIu +MTU1NjA0LDAuMzAwNzggLTIuNTA2NTE2LDAgLTMuOTI0NDg4LC0xLjQ2MDk0IC0xLjQxMDgxMSwt +MS40NjgxIC0xLjQxMDgxMSwtNC4wODIwNCAwLC0yLjYyMTEgMS40MTA4MTEsLTQuMDgyMDQgMS40 +MTc5NzIsLTEuNDY4MSAzLjkyNDQ4OCwtMS40NjgxIDEuMDQ1NTc1LDAgMS45ODM3MjgsMC4yNTc4 +MSAwLjk0NTMxNSwwLjI1NzgyIDEuNzQwMjM5LDAuNzU5MTIgdiAxLjUzOTcyIHEgLTAuODAyMDg1 +LC0wLjY4MDM0IC0xLjcwNDQzMSwtMS4wMjQwOSAtMC45MDIzNDYsLTAuMzQzNzYgLTEuODk3Nzkx +LC0wLjM0Mzc2IC0xLjk2MjI0NCwwIC0yLjk1MDUyOCwxLjA5NTcxIC0wLjk4MTEyMiwxLjA5NTcx +IC0wLjk4MTEyMiwzLjI2NTYzIDAsMi4xNjI3NyAwLjk4MTEyMiwzLjI1ODQ3IDAuOTg4Mjg0LDEu +MDk1NzEgMi45NTA1MjgsMS4wOTU3MSAwLjc2NjI3OCwwIDEuMzY3ODQyLC0wLjEyODkxIDAuNjAx +NTY0LC0wLjEzNjA3IDEuMDgxMzgyLC0wLjQxNTM2IHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAg +ICAgICBpZD0icGF0aDE1NCIKICAgICAgICAgICBzdHlsZT0iZm9udC1zaXplOjE0LjY2NjdweCIK +ICAgICAgICAgICBkPSJtIDcxLjY5MiwyMjIuODU4OTkgcSAtMC4yMjIwMDYsLTAuMTI4OSAtMC40 +ODY5ODEsLTAuMTg2MiAtMC4yNTc4MTMsLTAuMDY0NCAtMC41NzI5MTgsLTAuMDY0NCAtMS4xMTcx +OSwwIC0xLjcxODc1NCwwLjczMDQ3IC0wLjU5NDQwMiwwLjcyMzMxIC0wLjU5NDQwMiwyLjA4Mzk5 +IHYgNC4yMjUyNyBoIC0xLjMyNDg3MyB2IC04LjAyMDg1IGggMS4zMjQ4NzMgdiAxLjI0NjEgcSAw +LjQxNTM2NiwtMC43MzA0NyAxLjA4MTM4MywtMS4wODEzOSAwLjY2NjAxNywtMC4zNTgwNyAxLjYx +ODQ5MywtMC4zNTgwNyAwLjEzNjA2OCwwIDAuMzAwNzgyLDAuMDIxNSAwLjE2NDcxNCwwLjAxNDMg +MC4zNjUyMzUsMC4wNTAxIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0icGF0aDE1 +NiIKICAgICAgICAgICBzdHlsZT0iZm9udC1zaXplOjE0LjY2NjdweCIKICAgICAgICAgICBkPSJt +IDc1Ljg4MTQ2MywyMjIuNTUxMDUgcSAtMS4wNTk4OTgsMCAtMS42NzU3ODUsMC44MzA3MyAtMC42 +MTU4ODcsMC44MjM1NyAtMC42MTU4ODcsMi4yNjMwMyAwLDEuNDM5NDUgMC42MDg3MjUsMi4yNzAx +OCAwLjYxNTg4NywwLjgyMzU3IDEuNjgyOTQ3LDAuODIzNTcgMS4wNTI3MzcsMCAxLjY2ODYyNCwt +MC44MzA3MyAwLjYxNTg4NiwtMC44MzA3MyAwLjYxNTg4NiwtMi4yNjMwMiAwLC0xLjQyNTE0IC0w +LjYxNTg4NiwtMi4yNTU4NyAtMC42MTU4ODcsLTAuODM3ODkgLTEuNjY4NjI0LC0wLjgzNzg5IHog +bSAwLC0xLjExNzE5IHEgMS43MTg3NTQsMCAyLjY5OTg3NiwxLjExNzE5IDAuOTgxMTIyLDEuMTE3 +MTkgMC45ODExMjIsMy4wOTM3NiAwLDEuOTY5NCAtMC45ODExMjIsMy4wOTM3NSAtMC45ODExMjIs +MS4xMTcxOSAtMi42OTk4NzYsMS4xMTcxOSAtMS43MjU5MTYsMCAtMi43MDcwMzgsLTEuMTE3MTkg +LTAuOTczOTYsLTEuMTI0MzUgLTAuOTczOTYsLTMuMDkzNzUgMCwtMS45NzY1NyAwLjk3Mzk2LC0z +LjA5Mzc2IDAuOTgxMTIyLC0xLjExNzE5IDIuNzA3MDM4LC0xLjExNzE5IHoiIC8+CiAgICAgICAg +PHBhdGgKICAgICAgICAgICBpZD0icGF0aDE1OCIKICAgICAgICAgICBzdHlsZT0iZm9udC1zaXpl +OjE0LjY2NjdweCIKICAgICAgICAgICBkPSJtIDgxLjYwMzQ4LDIyNi40ODI3IHYgLTQuODU1NDgg +aCAxLjMxNzcxMiB2IDQuODA1MzUgcSAwLDEuMTM4NjcgMC40NDQwMTEsMS43MTE1OSAwLjQ0NDAx +MiwwLjU2NTc2IDEuMzMyMDM0LDAuNTY1NzYgMS4wNjcwNiwwIDEuNjgyOTQ3LC0wLjY4MDM0IDAu +NjIzMDQ4LC0wLjY4MDM0IDAuNjIzMDQ4LC0xLjg1NDgyIHYgLTQuNTQ3NTQgaCAxLjMxNzcxMiB2 +IDguMDIwODUgaCAtMS4zMTc3MTIgdiAtMS4yMzE3NyBxIC0wLjQ3OTgxOCwwLjczMDQ3IC0xLjEx +NzE5LDEuMDg4NTQgLTAuNjMwMjEsMC4zNTA5MSAtMS40NjgxMDIsMC4zNTA5MSAtMS4zODIxNjUs +MCAtMi4wOTgzMTIsLTAuODU5MzcgUSA4MS42MDM0OCwyMjguMTM3IDgxLjYwMzQ4LDIyNi40ODI3 +IFogbSAzLjMxNTc2MywtNS4wNDg4NCB6IiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9 +InBhdGgxNjAiCiAgICAgICAgICAgc3R5bGU9ImZvbnQtc2l6ZToxNC42NjY3cHgiCiAgICAgICAg +ICAgZD0ibSA5Mi4zMjQyMDgsMjI4LjQ0NDk0IHYgNC4yNTM5MiBoIC0xLjMyNDg3MyB2IC0xMS4w +NzE2NCBoIDEuMzI0ODczIHYgMS4yMTc0NSBxIDAuNDE1MzY2LC0wLjcxNjE1IDEuMDQ1NTc2LC0x +LjA1OTkgMC42MzczNzEsLTAuMzUwOTEgMS41MTgyMzIsLTAuMzUwOTEgMS40NjA5NDEsMCAyLjM3 +MDQ0OCwxLjE2MDE2IDAuOTE2NjY5LDEuMTYwMTYgMC45MTY2NjksMy4wNTA3OSAwLDEuODkwNjMg +LTAuOTE2NjY5LDMuMDUwNzkgLTAuOTA5NTA3LDEuMTYwMTUgLTIuMzcwNDQ4LDEuMTYwMTUgLTAu +ODgwODYxLDAgLTEuNTE4MjMyLC0wLjM0Mzc1IC0wLjYzMDIxLC0wLjM1MDkxIC0xLjA0NTU3Niwt +MS4wNjcwNiB6IG0gNC40ODMwODQsLTIuODAwMTMgcSAwLC0xLjQ1Mzc4IC0wLjYwMTU2NCwtMi4y +NzczNSAtMC41OTQ0MDMsLTAuODMwNzMgLTEuNjM5OTc4LC0wLjgzMDczIC0xLjA0NTU3NSwwIC0x +LjY0NzEzOSwwLjgzMDczIC0wLjU5NDQwMywwLjgyMzU3IC0wLjU5NDQwMywyLjI3NzM1IDAsMS40 +NTM3OCAwLjU5NDQwMywyLjI4NDUxIDAuNjAxNTY0LDAuODIzNTcgMS42NDcxMzksMC44MjM1NyAx +LjA0NTU3NSwwIDEuNjM5OTc4LC0wLjgyMzU3IDAuNjAxNTY0LC0wLjgzMDczIDAuNjAxNTY0LC0y +LjI4NDUxIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0icGF0aDE2MiIKICAgICAg +ICAgICBzdHlsZT0iZm9udC1zaXplOjE0LjY2NjdweCIKICAgICAgICAgICBkPSJtIDEwNy4yMjAw +OCwyMjUuMzA4MjIgdiAwLjY0NDUzIGggLTYuMDU4NjEgcSAwLjA4NTksMS4zNjA2OCAwLjgxNjQx +LDIuMDc2ODMgMC43Mzc2MywwLjcwODk4IDIuMDQ4MTgsMC43MDg5OCAwLjc1OTExLDAgMS40Njgx +LC0wLjE4NjE5IDAuNzE2MTUsLTAuMTg2MiAxLjQxNzk3LC0wLjU1ODYgdiAxLjI0NjEgcSAtMC43 +MDg5OCwwLjMwMDc4IC0xLjQ1Mzc4LDAuNDU4MzMgLTAuNzQ0NzksMC4xNTc1NSAtMS41MTEwNyww +LjE1NzU1IC0xLjkxOTI3LDAgLTMuMDQzNjIsLTEuMTE3MTkgLTEuMTE3MTk1LC0xLjExNzE5IC0x +LjExNzE5NSwtMy4wMjIxNCAwLC0xLjk2OTQgMS4wNTk4OTUsLTMuMTIyNCAxLjA2NzA2LC0xLjE2 +MDE2IDIuODcxNzUsLTEuMTYwMTYgMS42MTg1LDAgMi41NTY2NSwxLjA0NTU4IDAuOTQ1MzIsMS4w +Mzg0MSAwLjk0NTMyLDIuODI4NzggeiBtIC0xLjMxNzcyLC0wLjM4NjcyIHEgLTAuMDE0MywtMS4w +ODEzOCAtMC42MDg3MiwtMS43MjU5MiAtMC41ODcyNCwtMC42NDQ1MyAtMS41NjEyLC0wLjY0NDUz +IC0xLjEwMjg3LDAgLTEuNzY4ODksMC42MjMwNSAtMC42NTg4NSwwLjYyMzA1IC0wLjc1OTExLDEu +NzU0NTYgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJwYXRoMTY0IgogICAgICAg +ICAgIHN0eWxlPSJmb250LXNpemU6MTQuNjY2N3B4IgogICAgICAgICAgIGQ9Im0gMTE0LjY2MDg1 +LDIyMi44NDQ2NyB2IC00LjMzOTg1IGggMS4zMTc3MSB2IDExLjE0MzI1IGggLTEuMzE3NzEgdiAt +MS4yMDMxMyBxIC0wLjQxNTM2LDAuNzE2MTUgLTEuMDUyNzQsMS4wNjcwNiAtMC42MzAyMSwwLjM0 +Mzc1IC0xLjUxODIzLDAuMzQzNzUgLTEuNDUzNzgsMCAtMi4zNzA0NSwtMS4xNjAxNSAtMC45MDk1 +LC0xLjE2MDE2IC0wLjkwOTUsLTMuMDUwNzkgMCwtMS44OTA2MyAwLjkwOTUsLTMuMDUwNzkgMC45 +MTY2NywtMS4xNjAxNiAyLjM3MDQ1LC0xLjE2MDE2IDAuODg4MDIsMCAxLjUxODIzLDAuMzUwOTEg +MC42MzczOCwwLjM0Mzc1IDEuMDUyNzQsMS4wNTk5IHogbSAtNC40OTAyNCwyLjgwMDE0IHEgMCwx +LjQ1Mzc4IDAuNTk0NCwyLjI4NDUxIDAuNjAxNTYsMC44MjM1NyAxLjY0NzE0LDAuODIzNTcgMS4w +NDU1NywwIDEuNjQ3MTQsLTAuODIzNTcgMC42MDE1NiwtMC44MzA3MyAwLjYwMTU2LC0yLjI4NDUx +IDAsLTEuNDUzNzggLTAuNjAxNTYsLTIuMjc3MzUgLTAuNjAxNTcsLTAuODMwNzMgLTEuNjQ3MTQs +LTAuODMwNzMgLTEuMDQ1NTgsMCAtMS42NDcxNCwwLjgzMDczIC0wLjU5NDQsMC44MjM1NyAtMC41 +OTQ0LDIuMjc3MzUgeiIgLz4KICAgICAgPC9nPgogICAgICA8ZwogICAgICAgICBzdHlsZT0iZm9u +dC1zaXplOjE0LjY2NjdweDtsaW5lLWhlaWdodDoxLjI1O2ZvbnQtZmFtaWx5OnNhbnMtc2VyaWY7 +ZmlsbDojMDAwMDAwO3N0cm9rZTpub25lIgogICAgICAgICBpZD0idGV4dDM3NjYiCiAgICAgICAg +IGFyaWEtbGFiZWw9InRleHQiPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InBhdGgxNjci +CiAgICAgICAgICAgc3R5bGU9ImZvbnQtc2l6ZToxNC42NjY3cHgiCiAgICAgICAgICAgZD0ibSA1 +Ni45NzUxNzIsMjQ5LjM0OTg3IHYgMi4yNzczNSBoIDIuNzE0MTk5IHYgMS4wMjQwOSBoIC0yLjcx +NDE5OSB2IDQuMzU0MTggcSAwLDAuOTgxMTIgMC4yNjQ5NzUsMS4yNjA0MiAwLjI3MjEzNiwwLjI3 +OTI5IDEuMDk1NzA2LDAuMjc5MjkgaCAxLjM1MzUxOCB2IDEuMTAyODcgaCAtMS4zNTM1MTggcSAt +MS41MjUzOTQsMCAtMi4xMDU0NzQsLTAuNTY1NzYgUSA1NS42NTAzLDI1OC41MDk0IDU1LjY1MDMs +MjU3LjAwNTQ5IHYgLTQuMzU0MTggaCAtMC45NjY3OTkgdiAtMS4wMjQwOSBIIDU1LjY1MDMgdiAt +Mi4yNzczNSB6IiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InBhdGgxNjkiCiAgICAg +ICAgICAgc3R5bGU9ImZvbnQtc2l6ZToxNC42NjY3cHgiCiAgICAgICAgICAgZD0ibSA2OC4yOTAz +MDMsMjU1LjMwODIyIHYgMC42NDQ1MyBoIC02LjA1ODYwOCBxIDAuMDg1OTQsMS4zNjA2OCAwLjgx +NjQwOCwyLjA3NjgzIDAuNzM3NjMyLDAuNzA4OTggMi4wNDgxODIsMC43MDg5OCAwLjc1OTExNiww +IDEuNDY4MTAyLC0wLjE4NjE5IDAuNzE2MTQ4LC0wLjE4NjIgMS40MTc5NzIsLTAuNTU4NiB2IDEu +MjQ2MSBxIC0wLjcwODk4NiwwLjMwMDc4IC0xLjQ1Mzc3OSwwLjQ1ODMzIC0wLjc0NDc5MywwLjE1 +NzU1IC0xLjUxMTA3MSwwLjE1NzU1IC0xLjkxOTI3NSwwIC0zLjA0MzYyNywtMS4xMTcxOSAtMS4x +MTcxOSwtMS4xMTcxOSAtMS4xMTcxOSwtMy4wMjIxNCAwLC0xLjk2OTQgMS4wNTk4OTgsLTMuMTIy +NCAxLjA2NzA2LC0xLjE2MDE2IDIuODcxNzUyLC0xLjE2MDE2IDEuNjE4NDkzLDAgMi41NTY2NDYs +MS4wNDU1OCAwLjk0NTMxNSwxLjAzODQxIDAuOTQ1MzE1LDIuODI4NzggeiBtIC0xLjMxNzcxMiwt +MC4zODY3MiBxIC0wLjAxNDMyLC0xLjA4MTM4IC0wLjYwODcyNSwtMS43MjU5MiAtMC41ODcyNDEs +LTAuNjQ0NTMgLTEuNTYxMjAxLC0wLjY0NDUzIC0xLjEwMjg2OCwwIC0xLjc2ODg4NSwwLjYyMzA1 +IC0wLjY1ODg1NSwwLjYyMzA1IC0wLjc1OTExNiwxLjc1NDU2IHoiIC8+CiAgICAgICAgPHBhdGgK +ICAgICAgICAgICBpZD0icGF0aDE3MSIKICAgICAgICAgICBzdHlsZT0iZm9udC1zaXplOjE0LjY2 +NjdweCIKICAgICAgICAgICBkPSJtIDc2Ljg2MjU4OCwyNTEuNjI3MjIgLTIuOTAwMzk3LDMuOTAz +IDMuMDUwNzg4LDQuMTE3ODUgaCAtMS41NTQwNCBsIC0yLjMzNDY0MSwtMy4xNTEwNSAtMi4zMzQ2 +NDEsMy4xNTEwNSBoIC0xLjU1NDA0IGwgMy4xMTUyNDIsLTQuMTk2NjIgLTIuODUwMjY3LC0zLjgy +NDIzIGggMS41NTQwNCBsIDIuMTI2OTU4LDIuODU3NDMgMi4xMjY5NTgsLTIuODU3NDMgeiIgLz4K +ICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJwYXRoMTczIgogICAgICAgICAgIHN0eWxlPSJm +b250LXNpemU6MTQuNjY2N3B4IgogICAgICAgICAgIGQ9Im0gODAuMTc4MzUxLDI0OS4zNDk4NyB2 +IDIuMjc3MzUgaCAyLjcxNDE5OSB2IDEuMDI0MDkgaCAtMi43MTQxOTkgdiA0LjM1NDE4IHEgMCww +Ljk4MTEyIDAuMjY0OTc0LDEuMjYwNDIgMC4yNzIxMzcsMC4yNzkyOSAxLjA5NTcwNiwwLjI3OTI5 +IGggMS4zNTM1MTkgdiAxLjEwMjg3IGggLTEuMzUzNTE5IHEgLTEuNTI1Mzk0LDAgLTIuMTA1NDcz +LC0wLjU2NTc2IC0wLjU4MDA4LC0wLjU3MjkxIC0wLjU4MDA4LC0yLjA3NjgyIHYgLTQuMzU0MTgg +aCAtMC45NjY3OTkgdiAtMS4wMjQwOSBoIDAuOTY2Nzk5IHYgLTIuMjc3MzUgeiIgLz4KICAgICAg +PC9nPgogICAgPC9nPgogICAgPHBhdGgKICAgICAgIGQ9Ik0gMTAwLDIwMCBIIDIwMCBWIDMwMCBI +IDEwMCBaIgogICAgICAgaW5rc2NhcGU6bGFiZWw9IiNyZWN0MzczMiIKICAgICAgIHN0eWxlPSJm +aWxsOiMwMDAwODA7c3Ryb2tlOm5vbmUiCiAgICAgICBpZD0icjEiIC8+CiAgICA8cGF0aAogICAg +ICAgZD0iTSAzMDAsMjAwIEggNTAwIFYgMzAwIEggMzAwIFoiCiAgICAgICBpbmtzY2FwZTpsYWJl +bD0iI3JlY3QzNzM0IgogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6I2ZmMDAwMDtzdHJv +a2Utd2lkdGg6MTYiCiAgICAgICBpZD0icjIiIC8+CiAgICA8cGF0aAogICAgICAgZD0iTSA2NDQu +OTQzMjMsMjAwIEggNzU1LjA1Njc3IEMgNzc5Ljk1NTMyLDIwMCA4MDAsMjE3LjM3MjA2IDgwMCwy +MzguOTUwOCB2IDIyLjA5ODQgQyA4MDAsMjgyLjYyNzk0IDc3OS45NTUzMiwzMDAgNzU1LjA1Njc3 +LDMwMCBIIDY0NC45NDMyMyBDIDYyMC4wNDQ2OCwzMDAgNjAwLDI4Mi42Mjc5NCA2MDAsMjYxLjA0 +OTIgViAyMzguOTUwOCBDIDYwMCwyMTcuMzcyMDYgNjIwLjA0NDY4LDIwMCA2NDQuOTQzMjMsMjAw +IFoiCiAgICAgICBpbmtzY2FwZTpsYWJlbD0iI3JlY3QzNzQ0IgogICAgICAgc3R5bGU9ImZpbGw6 +I2ZmZmYwMDtzdHJva2U6IzAwODAwMDtzdHJva2Utd2lkdGg6MTYiCiAgICAgICBpZD0icjMiIC8+ +CiAgPC9nPgogIDxzY3JpcHQKICAgICBpZD0iSmVzc3lJbmsiCiAgICAgbnMxOnZlcnNpb249IjEu +NS41Ij4vLyBkdW1teQo8L3NjcmlwdD4KPC9zdmc+Cg== + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/c1bf37c2aa47000fc9941d96b415218c.msg b/share/extensions/tests/data/cmd/inkscape/c1bf37c2aa47000fc9941d96b415218c.msg new file mode 100644 index 0000000..f3c3e2a --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/c1bf37c2aa47000fc9941d96b415218c.msg @@ -0,0 +1,28 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=953:49:1000:951 --export-filename=guides_5.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="guides_5.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: guides_5.png + +iVBORw0KGgoAAAANSUhEUgAAAC8AAAOGCAYAAAB83MkXAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAALxJREFUeJztwQENAAAAwqD3T20P +BxQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCDAZoM +AAG1xnPlAAAAAElFTkSuQmCC + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/c23825a5b83e906138ca8bff105b8b29.msg b/share/extensions/tests/data/cmd/inkscape/c23825a5b83e906138ca8bff105b8b29.msg new file mode 100644 index 0000000..051d4af --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/c23825a5b83e906138ca8bff105b8b29.msg @@ -0,0 +1,1038 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area-page --export-background-opacity=0 --export-dpi=96 --export-filename=Slide2.png --export-id-only --export-id=layer2 --export-type=png Slide2.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="Slide2.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: Slide2.png + +iVBORw0KGgoAAAANSUhEUgAAA+gAAAPoCAYAAABNo9TkAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAIABJREFUeJzs3XmcXHWd7//3qapT +W1fva9LZl04I4ELYVJZA2EFRwIwSCDLMyHDvXB86bvf+7syI+vCOjqPjZeYh8nP4RUkaMAiMssoW +VpVVEUP2vTvpfanuqurazvn90WFUZElyTvU51fV6Ph4+QmLqc95d1UnqXeec71cCAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADlz/A6AAAAAOC2 +tVKbKX1T0lmS5nidp4JZkrbb0n1p6SvXS2mvAwF+RkEHUDbWSlFTusKQ2r3OUslsqXtIuuszUtbr +LJh0oxRYJH3Olm4wpIVe56lktrRT0vd3St+7cbKYwAN3SLML0suG1Ox1FvyJ18LSKaukjNdBAL8K +eR0AAA7HTVKkoTX2wryzW1XVFk16naeSjfdM1Ox9oveLN/VmTqak+8Ni6e9ijZFvt5/cqEiN6XWc +ijaRzC888MLgdxYNZg1J3/E6T6WypH81pOZEW0ztJzbIrOItr1dsW8qO5LT/+QFlR/PH56UvSfqq +17kAv+JvKwBloUH6+LyzWoxlV8y52+sskAzLvnzsJ3svl3S711kgWdLftJ/cqDkfav5J0zG1W7zO +U8n6Xh89xrC1avvDB/5GFHRP2JLRKZ0jQ5p9aqOCkaDXkSqaYUjR+rBmnNCgPRt7ZUvniYIOvC0K +OoCyYEgLa2bFB7zOgUk1s+MDhrTY6xyYZEgLIjWmKOfea1lWu7n/9yMStxp45i6pSVJtMBxUMBKU +ETLSyy6b822vc1WqzGgusesXBz8f/cPVPfzZAN5BwOsAAHA4LMmwWTfDN2zJsHg9/ITXwn94TTwy +cei5Nw69AoZke5mn0gUCxpuff/5sAO+Agg4AAAAAgA9Q0AEAAAAA8AEKOgAAAAAAPkBBBwAAAADA +ByjoAAAAAAD4AAUdAAAAAAAfoKADAAAAAOADFHQAAAAAAHyAgg4AAAAAgA9Q0AEAAAAA8AEKOgAA +AAAAPkBBBwAAAADAByjoAAAAAAD4AAUdAAAAAAAfoKADAAAAAOADFHQAAAAAAHyAgg4AAAAAgA9Q +0AEAAAAA8AEKOgAAAAAAPkBBBwAAAADAByjoAAAAAAD4AAUdAAAAAAAfoKADAAAAAOADFHQAAAAA +AHyAgg4AAAAAgA9Q0AEAAAAA8AEKOgAAAAAAPkBBBwAAAADAByjoAAAAAAD4AAUdAAAAAAAfoKAD +AAAAAOADFHQAAAAAAHyAgg4AAAAAgA9Q0AEAAAAA8AEKOgAAAAAAPkBBBwAAAADAByjoAAAAAAD4 +AAUdAAAAAAAfoKADAAAAAOADFHQAAAAAAHyAgg4AAAAAgA9Q0AEAAAAA8AEKOgAAAAAAPkBBBwAA +AADAByjoAAAAAAD4AAUdAAAAAAAfoKADAAAAAOADFHQAAAAAAHyAgg4AAAAAgA9Q0AEAAAAA8AEK +OgAAAAAAPkBBBwAAAADAByjoAAAAAAD4AAUdAAAAAAAfoKADAAAAAOADFHQAAAAAAHyAgg4AAAAA +gA9Q0AEAAAAA8AEKOgAAAAAAPkBBBwAAAADAByjoAAAAAAD4AAUdAAAAAAAfoKADAAAAAOADFHQA +AAAAAHyAgg4AAAAAgA9Q0AEAAAAA8AEKOgAAAAAAPkBBBwAAAADAByjoAAAAAAD4AAUdAAAAAAAf +oKADAAAAAOADFHQAAAAAAHyAgg4AAAAAgA9Q0AEAAAAA8AEKOgAAAAAAPkBBBwAAAADAByjoAAAA +AAD4AAUdAAAAAAAfoKADAAAAAOADFHQAAAAAAHyAgg4AAAAAgA9Q0AEAAAAA8AEKOgAAAAAAPkBB +BwAAAADAByjoAAAAAAD4AAUdAAAAAAAfoKADAAAAAOADFHQAAAAAAHyAgg4AAAAAgA9Q0AEAAAAA +8AEKOgAAAAAAPkBBBwAAAADAByjoAAAAAAD4AAUdQFkISLYh2V7nwCRDsgO8Hn7Ca+E/vCYeiR56 +7u1Dr4AtGV7mqXSWZb/5+efPBvAOQl4HAIDDtCPZlb7C6xCYlNyfbrKl7V7nwCRb2pVN5hf2vT56 +TMuy2s1e56lkfa+PHpMdzUvSTq+zVKqPSwOd0kgxV6wrZosKKhjftGHvV7zOVemyyfwb/7nDyxyA +31HQAZSFQemnuzf2fVm2Lq+ZU9VnBGR5nakS2ZYCyX2plt1P9llD0t1e58EkQ7q5+4XBf5GtVf2/ +H/E6TkXLjubV9eKgDOn7XmepVIZkd0qP27Yu3//rQc1c3qBwgre8XrFtaWIkpwOvDL3xS494mQfw +Oy75AVA2bpIiDdLlhnSsJQW9zlOJAlLRljYNSXd/Rsp6nQeTbpQCC6XPGtINkhZ5nafC7bClm3dK +37tRfJDolfXSLFt6xZCavc6CP7Cl3xWkU66VJrzOAvgVBR0AAADTzlqpLSz9H1s6W9Jcr/NUMEvS +Vkn3W9JX10gprwMBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAuw+sAAAAAADAVbpNagtJSW1piSx2GNFdSvaSEpKpD/6s/ +9KMkpSQNH/px/NCPQ7a0z5C2GdLWorRljdQ39V8NpiMKOgAAAIBpZ63UFpLOkrTCkN4raYmkuhId +bljSNlt6VdKTBWnjtVJPiY6FaYyCDgAAAKDs3SpVR6VTLOkcQzpH0gnytu/ssqXHAtJjBemJa6RB +D7OgTFDQAQAAAJSltVI0Ip1rSVdL+qgk0+tMb6MoaaMtrbOlu9dMXioP/BkKOgAAAICy0iktt6U1 +klZLavQ6zxFKSvqZId12pfS4IdleB4J/UNABAAAA+N4GKZaT/krS5yTN9zqPS3ZJ+m5euvVaacLr +MPAeBR0AAACAb22QElnpOkP6kqSZXucpBVvql/T9iPSvq6RRr/PAOxR0HLXbpCpT6rAmLyuql1Rl +SYnAoW0pLCkV+MN2FMMBaTApbb1eSnuZGwAAAP63XqoxpBvsyWLe4HWeKZK0pZsj0j+vkoa8DoOp +R0HHYblTWpiXzjLjwZNideFlZjy0KFwVqo83R3oi1WYqFAtOhMKBfDgWKhoRoyhJdtYO5jKFYCFn +mYVMMZpN5qvSA9m2XKownE8XdmRGcpuK6eKLQenJT0g7vf4aAQAA4D1bMtZLVxvSdyQ1eZ3HI8O2 +9NWI9O+rJheYQ4WgoOMt3S41FaWLognzvHhT+OxEW9xsWlq9u2pmbCBaYw5WNUYGog2RI778xrJt +Y2IoV5sZyjbmkvnG8Z6JlqEd4/PHetPZVF92Y3Y493BBepBtKAAAACrPHdL7i9L3JZ3qdRafeMWQ +blgtveB1EEwNCjr+y01SpF66pKop8le1s+KnNR9Xt71+XlV3oj2+J9ESLWlhHu+baEx2peeP7B2f +OfDa6OLR7vQzmYHsfwxKD3xGypby2AAAAPDWWqnOlL4q6b9LCnqdx2csSZ1F6XOcxJr+KOhQp7Q4 +lAh+sXZW1armZXUHW46v3dK0pGZzKBLMeZGnkC2GB7Ymj+l7bXhp/6ZkW7I7tSE7Xvz21dIOL/IA +AACgdNZLl0i6VVKL11l8rlfStVdJD3kdBKVDQa9g66XjY7Xhf6xfXH3unNObX245tu7VWH046XWu +P5YZztUMvj7yvr3PDZzQvyX5i/xo7mtXSpu8zgUAAABnNkqhLunvDekfJAW8zlMmbEn/lpK+cL2U +9zoM3EdBr0C3ScdFa8PfaumoPnXuma0vtp1Q/4pXZ8sPVyFbDPf8ZujE/b8cOKl/c/K5zFD2S1dJ +r3udCwAAAEfuDml2UbpD0oe8zlKmXrCkT6yRdnsdBO6ioFeQDVKiEAl8vXVZ3acWnj/jVzPeV/9K +wAwUvM51JKy8FTr42+Hlux7r+UD/ptH/yGYKX1kzuY0bAAAAysDt0oct6UeqnK3TSmXQlq65WnrA +6yBwDwW9QtwufTgxO37zwpUzeuauaNkYqTbLutTmU4XY3qf7zti9sWdB/87xL10j/djrTAAAAHhn +ndKXbemfRA9xi21IX1ot/YvXQeAO/mBMc51SfbguvG7OB5reO39l26P18xP7vM7kpuHd43N3P9Zz +zr5fD7wyPpK75lppxOtMAAAA+FO2ZHRK35L0Ra+zBMMBRWpMhatNRapNhWtCMqNBBUIBBYKGguGA +AqHJmmQVbBVzlqyiLatgqZApKjtWUHYsr9xYXtlkXsWc5fFXJEm6aYf0uRsnV3xHGaOgT2O3SSfW +z626e+lF7fvnrGjZGAgFil5nKgXLsgP7nu47Y+t9XYuTe1JXXCn9yutMAAAAmLRRCnVLt0j6Sy+O +H4oGFW+OKNEaU3VbVGZVyNX5hUxRqYGsxnszGu+ZUD7l2R2k61PSX7J4XHmjoE9DtmSsMwOfnXlc +3VeOvWz2I03LaitiMbXBLaNLNt/bdcHB3w7/8ycL1jeNyVUuAQAA4JHbpKqAdJekC6fyuOEaU/Xz +qlQ7q0rhancL+bvJjuWV3J/WyN6Usskp78qPZaXLrpPGpvrAcAcFfZq5RTLraszbFpzVdsrSS2b+ +LNoQGfU601RKD2brdtzfden2J/qeax3Pf+osqawWwQMAAJguDpXzRyR9cCqOF4oEVDu3SrVzE4o3 +hKfikO8qPZTV6J6URvelVMhO2dXnz1nS+SykXJ4o6NPILVK8oTZ8b8dF7TOXXtr+s3Jbod0thVzR +3HF/90e3PHSga2go97HrpbTXmQAAACrJLZJZJf1MU3Dm3IyH1LSkRvULEwoE/VlvbMvW8O6U+l4f +VSE9JW/RHwtLF6+SfL2VMv6cP7+DccQ6pfpYY/jhpZfODiy8cOZDgYBR2QtE2Lax8+GDF2z62X4z +258970ppwOtIAAAAleDQgnBrJV1TyuOYVSE1dfi7mP8Zy9bIvrT6Xh9Vbqzkl7/fvkO6+kYWjisr +Qa8DwLkNUkNsRuzXx/3FvOGF58141DAM7r02DDUsrt4Rrgq1j+5P/e1l44U77pIyXscCAACY7jqk +70i6oVTzjZCh1uPqNfuDzYo3RWQEyqScS5JhKFoXVsOiaoXCQaUHs7Ktkr11P75RarxHeqhUB4D7 +yui7GW9lgxQzm6JPHvfJuel5K1qf8jqPH+3e2HP2pp/srRrrz57BvTgAAACls176X5L+T6nmV8+M +aebyRpnx6XGesZApqud3wxrekypZMTOkL7JPevmgoJexWySzoc58YNllcxo7Lm6/z+s8frbjoe6L +X7+na7hhKHsRC8cBAAC4b510sSHdpxJ0DLMqpFknN6qqJer2aF9I9U6o68XBUm3RZtvSR6+Wfl6K +4XBXwOsAODq2ZDQkzB93XDhrxqILZz7gdR6/W3D+zIcWXzCjvS9h/sjmgykAAABX3SHNNqQfqwTv +s6rb41p03oxpW84lqao1qsXnz1DN7HgpxhuGtLZTmluK4XDX9Lg2pAJ1hAJfWHD+zMuOu2L2XYFQ +oOh1Hr8zDMOuX5TYXhjPX/Lk7pRxj2X/2utMAAAA08FGKTQq3S9piauDA4Zaj6/XzOUN5bMInANG +0FDt7CoFw0Gl+iYkd29Nj0k69XzptvtZNM7XKOhlaL10Uvvyhh8e/4m5PwknTLYQO0yBYMCqmVO1 +e3x/+jPnH8w8fbfU5XUmAACAcneO9C1D+oSbM0OxoOataFXdnJKcUfa1eGNE1W1RJQ9kZBdcbemz +wpJ5j/S4m0Phrun/UdQ0s1aqa54df2359YufazqmdovXecrR4NZkx4s/2H5mZl/q+FXSkNd5AAAA +ylWndJE9efbctV5hVoU0/8xWhatDbo0sS/lUQbuf6lVuzNX70rkf3ee4B73MJGrC6xd/eNYeyvnR +a1xSs63j4vY9gbrwj7zOAgAAUK46pXp7cr9z18p5pNbUgpVtFV/OpckPKhac3aZofdjNsYYh3bJB +qnVzKNxDQS8j66RVcz7U9N55Z7Y86XWWcjdvRcsT7ac2ndApXeZ1FgAAgHJkSd+Q1OLWvHhzVAtW +tsmMcRfuG0LRoOaf1ap4c8TNsW056etuDoR7KOhl4lapumFB4qb5K9seY1E45wKhQHHhOW2P1M1P +/PsGKeF1HgAAgHLSKS03pE+7NS9aF9bc05sVNKknbxY0A5p7eoti7p5J/++d0sluDoQ7uHakTCRi +oa/NW9G6r35+Yq/XWaaL+vmJffPPbj3w29snblSm8AWv8wAAAJSDG6WALd0slxacNhMhzT2zZcrL +eSgSHAhXh7rDVaEBs9ocjNWZQ8FwMGfGg5lgOJCXpGLOMvPpYqyYK4YnRvKN2WS+MZcqNOXG8+3F +rNU4VVmDZkDzzmzRrsd7lR3LuzEyYEs33Sh98EZWdfcVFokrA7dJx80+qfGZU/6244cmq7a7KjuW +r3r+37b+9cGXhz54lfS613kAAAD8br30N5os6I6FokEtOHuK7jk3VIjVh7fGW6PbamdX7Y7Vhcec +jMuM5KrH9qfmj/dOdGSGc0tkl/7kZ268oF2P96gw4doFtZ++SvqhW8PgHAW9DPy0IfzgyZ9erBkn +Nb7gdZbpqPvFwVNevGV74ePDuQ97nQUAAMDPNkgNOWmHpHrHwwKGFqxsU7zB1Uu3/4wZDx2onRV/ +qXFJzeuhWDBbimPk04XowLbksrGuzPJ8ujCzFMd4Q2Ywq11P9Mq2XNmCbTAvLbpWGnFjGJxjBQaf +Wy8tm/H++n9ceunsnxtBg8tPSiDeEu0d2Tl22dld6Qfvlfq8zgMAAOBXH5G+bEgXuDFrxvsbVDur +dPucm1Wh/S3Lah+c/cHmRxNtsZ6AWbp1nIJmoFDdFjvY2FHzihkP7c6NFWqLOcv5hxhvwYyHFAgZ +Gu+ZcGNcPCCl75WedmMYnKOg+9wnasM3H/OxWQN1cxP7vM4yXQWChmUEFOrfnDz5roniT73OAwAA +4Ee3StUh6Q5JMaezambGNOP9DS6k+nOhSGCw9b31d886temJeFNksCQHeQex+vBow+Lq3wXNQHdm +ODfLLtqOn683izdFNDGSd+V+dEN6zyek798l5VyIBodYJtHH7pQWNndUr5zx/oaXvc4y3bW+t+Hl +xoXVF3RKi73OAgAA4EcR6QZJjlu1GQ9p5ilNLiR6E0OF2jlVGxdf3H5zw6LqXe4f4Mg0dtTsWHRR ++801s+NP2oZcP3vffnKjzCpXbntvzE+uKwAfoKD7WcL8n3NOa34lFAnyaVaJmbFgdu7pzS8H48HP +e50FAADAb9ZKUUmfdTrHljTrlEaFwu7WkEA4MNJ+StOPZp3a9LSftiQOmYHC7A80PzXnA823BiOB +ITdnB8MBtZ/kzkLytvT5DS5cGQHnKOg+tUEK182OXdF8bN2rXmepFE3Lal+tm131FzdJEa+zAAAA ++ElI+itJM5zOqZtbpaqWqAuJ/iDWEN60+MKZN9fNqep2dbCLambFDy48b+b/G6sPu7prUKI1qto5 +VW6MastJ17oxCM5Q0H0qJ32k+Zi6g7H6cNLrLJUi3hAZbT6mprdeutDrLAAAAH5xoxQwpL9zOidg +BtT2XnfXTUvMjP1ywTkzfloOV5yasWB2wbkz7qpqi/3azblt769XwJ095D9vs8uX5yjoPhVvilzX +cnztVq9zVJrm4+u3xhoj13mdAwAAwC8WSmdImu90TuvxdTJj7qxRbUuqn594ZO5pLY+6MnAKzTuj +5Rd186oec2WTNElmNKiWY2vdGLVgvXSaG4Nw9CjoPvRjqbFmVvy0piU1m73OUmmaltS8XtceX/Fj +yZ0begAAAMqcIV3tdEa4OqTGRdVuxJEk1c1LPDrzpMZfuTZwirWf3PRc/byqx9ya19hRIzPhfME4 +N15rOENB96GAdHHLcXXby+FSnenGjAWzzcfV7ghK53udBQAAwGuHFg673Omc5mNqXbt4OjEz9stZ +Jzf+0p1p3mk/uem5xIyYKx8yGMah59i5VSwW5y0Kug9Faszz6+f5d5GL6a5ufuJAtMY8z+scAAAA +XstKH5XkqPmF4iHVzXVlITPFGsKbyvGy9rcz9/SWR6L1YVeumm2YV+XGtmu1eekSN/Lg6FDQfaiq +OXJWzaz4bq9zVKqa9tjuWHNkpdc5AAAAvGZIa5zOaF5aIyPg/PR5MBIYmv2hlvscD/KZuWe0/Gco +Ehh0PChgqKmjxvEYi8vcPUVB95l10qLq1mgk3hx1dZ9EHL5Ea2wg0RKL3ebCYigAAADl6tCaPOc4 +mRGMBNWwIOE8jKFC+4mNd5mxYNb5MH8JRYK5Gcsb77YNOd6/vWFhQkGHe8wb0gVrpTqnWXB0KOj+ +s6JhSc0ur0NUuoaO6j0B6SyvcwAAAHglIK2Q5Oia6bq5cRlB52fPa2dXPVPdHu9xPMinambFD9bN +ij/jdI4RNFTr/HYCMyyd6XQIjg4F3WciieBJiRmxfq9zVLrqmbH+cDx4otc5AAAAvGK4cLKidq7z +s+fBSGCobXlD2S8K925mntT4bNCFS93r3bnfnxNVHqGg+0ykJnxsrDY84HWOShetMQcjteFjvc4B +AADgIUclLVxjKt4Qdhyi5di6B0JmoOB4kM8FQoFi8zG1DzudE2uMKFxtOpphS2c7zYGjQ0H3GTMe +WhRtCDtfJAKOxBojA2Y8tNjrHAAAAF64XWqVdIyTGfXznJ/JDSdCexoWVVfM7Z+NHTU7zERon9M5 +Lqyaf9yh7wFMMQq6j9wmVYUTobpofTjpdZZKF20Ij5qJYMMtUtzrLAAAAFPNklbK4c7ltbOcF/TG +jhrH92WXm8ZF1Y6/5trZjt/CGtbkGgSYYhR0HwlKi+NNkZ6AYdheZ6l0AcOw403R3mppkddZAAAA +PLDCyYNDsaDC1c725DbjoQOVdPb8DY0dNTvMeOiAkxmRGlOhWNBpFO5D9wAF3V9awzXmuNchMCla +bY7bUovXOQAAADzwHicPTrRGHQeonRV/yfGQMlXTHnvF6YyqFsevwfFOB+DIUdB9xJISoUgw53UO +TApGAjlbqvY6BwAAgAeWOHlwwmk5NFRo6Kje7GxI+WpaWvt7GXK0MJ7j10Ba6nQAjhwF3UcCUrUZ +CeS9zoFJZiyYp6ADAIBKc2hxsDonM+IOz6DH6sNbzXhowtGQMhaKBbPRuvB2JzNcuIqhYYPU7HQI +jgwF3UdsqToYC037LSTKRSASLBgUdAAAUGFsh2fPQ5GAwnFn959Xtca2OhowDVS1RLc5ebxZFVIw +7Kzu5aQORwNwxCjoPmJPnkGnoPtEOBbMS6rxOgcAAMBUclrQne7BLUk1s+N7HA8pc7VzqhwvkBdx +/lo4+l7AkaOgAwAAAPhjnhb0UCQ4EKsLjzkaMg3E6sPJYCQw5GSGCx+WUNCnGAXdRwxpLJ+1nF0P +BNfkMkVTEnvSAwCAimJI85w8PlrjrBSGq0PdjgZMI+GE2eXk8ZEax9VivtMBODIUdB8xpLFipkBB +9wkrWwzZUsV/egsAACqOo1v8TIf7n4erQgOOBkwj4arQoKPHOz+DznpMU4yC7iPW5Bl05zftwBX5 +TNE0KOgAAKDCON3FxowGHR3frDYdldLpJFJjOvqwwulrIQr6lKOg+4ghjRWyxbDXOTCpmLXCFHSg +sq2V2rzOAAAeSDh5cCDkrGLE6kxH911PJ9FGjWe9AAAgAElEQVRaZ8+FETKcRmDB5ClGQfcRQ+rL +JvN8SuUTE2P5akPq8zoHAO+Y0rp10sVe5wCAKebo/WjQYSk0o8GK3f/8zYIOn4uQ6bjuOfqwBkeO +gu4jRWl7pj/XYtm244+64Ixl20a6f6JlTNrhdRYAnnqPId3VKZ3ldRAAmEKOCrrhsBQGI8GcowHT +SDAScPRcGA6vZhCXuE85CrqPrJFS2VR+NDec41ISj00M5ury6cLg9VLa6ywAvLFWqpPUIilmS/d3 +Smd4nQkApoijs6ZOz6A7LaXTiRkNZp08Pmg6ey2crkeAI0dB95lCurA9NZht8jpHpcsMZRuzqeJ2 +r3MA8I4pLf2jn8Zt6b710kmeBQIAANMeBd1nJkZym3LJfKPXOSpdNplvyo3mNnmdA4Cnlrzp5zWS +HlknneBFGACYQuNOHlws2I4OXsxaLJp8SH6iGHHy+GLe2WvBgslTj4LuM7l08aVkd6bZ6xyVbvxA +pimXLr7kdQ4AnnpzQZcmL3t/eL20bKrDAMAUclTK7Lzl6OBFdjX6L04/rLALzl4LUdCnHAXdZ0xp +49D25AKvc1S6gW3J+SFpo9c5AHhq6Vv9oiE1S3pi3VsXeACYDhyVMqdn0HOZYszRgGmk4PC5KDj8 +sMSmoE85CrrPfELaOdY7kR3vm+Ayd4+M9WSax/sm0p+U9nidBYCn3qmAtxrSo7dJ86csDQBMHUeX +uFsOz9pOjObrHQ2YRjKjuQYnj7cdfljCJe5Tj4LuQ+n+7BPJrjRv+jySOpCZl+rPPuZ1DgDe2SAF +bWnhu/y22Yb06B3SzCkJBQBTxGkpy08UHR0/P8Z6TG/IjxUcPRdOXwtR0KccBd2Hcsn8L0b2jLd7 +naNSDe4ab88m8494nQOAd7LSfEN614V5DGmhJW1cK7VNRS4AmCJJJw/OJQuODp5LFdjR6JBcyllB +zyXzTiM4+l7AkaOg+5ApPTCwaXRhgQUyplw+U4wMbBpdaEkUdKCyHfb95bbUYUqP/FjijA+AacF2 +eJtfdsxZKcyO5Wc5GjCNZMfysx0+3mmEPU4H4MhQ0H1olTQ0ui/97MDW5DFeZ6k0Q1uTy0a6009e +Iw16nQWAd4y3WSDuHRwflB7rlLhvEsB0sNXJg3MOS2ExazVmRnLVjoZMA+mhbK2Vsxz9u5Idc3Y1 +gxx+L+DIUdB9KjWUvbXvtdEjfYMIh3peG16SHcz+h9c5AHjuaFZof58tPXCrVPFvKgGUN1va4uTx +WeeXVWt0f6ri12NK7ne+JpXTD0tEQZ9yFHSfikj39W8aacsM52q8zlIpJoaytQOvJ1sHpYe9zgLA +c0e7hdoHwtKDt0lVrqYBgClkOyxlxZylfMrZmdt070SHowHTQKrP2XOQTxVUzDlbUT9AQZ9yFHSf +WiXlkt2pDf2bRt7ndZZK0bNp9H2jXak7PyNlvc4CwHNHvce5IZ0WlO5dK0XdDAQAU2WN1Cdp2MmM +VN+EowyZ4dySfLpQsX+P5tOFaGYk56igj/c6ew0kDV0pDTgdgiNDQfcxa7z4z3uf7T+BxeJKL58p +RvY923dCIV38rtdZAHhrrVQnqdXJDFs615TuvEUyXYoFAFNtm5MHjzss6LIVGtiWXOZsSPnq35I8 +zrAVdDLD8Wvg8FYHHB0Kuo99Qto5uC35i57fDC33Ost01/PboROHto09sFra7nUWAN4KOjh7/iaX +JqQ7Nkohl+YBwJSxpVedPD7l/OytxroyFfseeLw7fYLTGU6vYpD0O6cDcOQo6D6XGc3fuO/Z/pOs +vMUbvBIp5Irmvmf6Tswn89/wOgsA7wWOfAX3t2VLlx+Qbr2Rf28BlJ8nnTw4nyk6XqAsny7MHNye +XOhoSBka3JpcnM8UZziZMZHMq5ApOo3ypNMBOHK8YfC5NdLmvi3J5w78drhiP0Estb7fDC/v3zr2 +9JXSJq+zAPAFt86gS5Jsac0i6Ye2ZLg5FwBKyZYen/zh6I12pR3nGNw2dqbjIWVmcOfYaU5nJPc7 +fu7tAAXdExT0MpAZzn1pz2MHT82O5VkV2GXZ0Vxix2M9p+ZHcl/2OgsA33C1oB/yl50Sa1wAKBuH +Forb7GTGyO6U4xz5VGH24LbkIseDysTg1uTi/HhhjtM5I3sdP/e/v1LqdToER46CXgbWSJt7X0ve +uv/pvor7BLHU9j7Tf/bQ5uQPrmQRDAB/UIqCLkmf7ZS+U6LZAOA6Q3rCyeOzY3llhnKOc/RvHr2w +UAG3e1p5K9S/efRCp3PSg1k39j9/3OkAHB0KepkYzRb+ccfG3vbhXWPzvM4yXQzvHp+7Z2NPWyZT ++LrXWQD4wwYpaEslO1NjS3+3TvqHUs0HAJdtdDpgZM+44xDFrNVw8KXBDzke5HPdLw6eXsxZ9U7n +jOxxfuWC7cJrj6NDQS8T10vp5O7x/7H78d6VVsFytOUCJKtgBXc+cuC8kT2pG9ZIzv8WAzAtZKX5 +hhQp5TEM6WudErfVAPA9c/Ie5IKTGaP7UrKLjm5ln5zTlT492ZV2tHCanyX3p2Ymu9OOP4SwirZG +9zl+a5svSE87HYKjQ0EvI6ule/b+cuCVfU/2neV1lnK3d2PvygMvDD2/WvqZ11kA+EqpLm//E7b0 +T+ul/zYVxwKAo7VKGrKlR5zMKGQtDe1yfhbdsBXsfmnw44VMsaQfonohny5Eu18e+rgc7nsuScM7 +x1TMWU7HPHitNOJ0CI4OBb3MpJK5a7bc3zVncPOoa9sAVZqB10eP2frQgfbiSO5ar7MA8J0pKeia +XNH93zulT0/R8QDgqASkdU5nDGwZlW05P4tu5az6fc/2fcTxIB+xbNvY92z/pVbOqnM6y7Zs9W9J +uhFrvRtDcHQo6GXmWmlkeH/6ss3/uf/89GDW8R/kSpMeyNa/fu/+85J7U5etkka9zgPAd6aqoEuS +YUs3d0qrp/CYAHBEctJ/yuHZ1Hy66Mp90ZKUGc4t2/1U70WuDPOBfc/0nzcxknPlxNvw7nE39j4f +DUsPuJEHR4eCXobWSC8deHXka9vv77q0kCuaXucpF4VsMbz1vq6Pdr828pXV0ste5wHgS1NZ0CUp +YEs/6pQum+LjAsBhuVaasKW7nc7p2zwq2/lJdElSunfipO4XBsp+0bju5wdOS/VkTnVlmGWrf7Mr +Z8/vXCVl3BiEo0NBL1NX5q3v7Xy879ltP++61LJsXsd3YVl2YPt93R/d9VTfk2vy1k1e5wHgT4bk +xe1DIVu683bpwx4cGwDeVUC6zemM/HhBQ9tdKZCSpOE9qXPKuaR3Pz9w2vDe1Eq35g1sG1M+5Wg9 +P0lSgMvbPUexK1OGZBup/Kd2Pnxw984HD1xs2bbhdSY/2/XQgQu2PHhgX+tY/jqvswDwp7VSnaRW +jw5vWtJP10uO978FALddKT0jaZfTOT2vjbpxCbakyYU8RvakztnzdN/5rgycIpZtG3ue7rtgZG9q +pVtv3guZovped37npi3t/KT0nAuR4AAFvYytkoqF4dyqzT/fn9/9RM+5Xufxq11P9KzcfF93YGI0 +d/lZDrcKATB9Baf+8vY3C0v66e3SmR7nAIA/YUi2pO86nWMXLB387bALif4g1ZM5deejB1eVw+ru ++XQhuvvRnlWpnswpbs49+JshWXnHK7crIH3n0GsND1HQy9wqKVMczF207d6u+t0be872Oo/f7H6i +Z+Xmu/c1ZvonzmW/cwDvxPC+oEtS3JJ+3imd7HUQAPhjeelWSQeczhndl1Kqd8KFRH8wMZw7ZvvD +B24Y2Zua5epgFyW70jN2PHLw024tCPeGsZ6MRven3RjVY0o/cmMQnKGgTwOrpKGxg5kPvnbHnvD2 +B7ov4Z70yXvOtz3UfdGmn+yNj/ZMnL5acvfjWgDTTsAfBV2SamzpkU5puddBAOAN10oThvSvbsza +/8KgG3t1/wkrb9V2vTDwqa5f9Z9ZyFshV4c7YOWt0P5f9a/Y/6v+66ycVe/m7GLO0sGXhlyZZUvf +ZnE4f+C+5WnkJikysy5816LzZ3QsuXTWPaFIMOd1Ji8UckVz28+7P7bj4QN7i8O5y/jLBsDhWD+5 +SrGfVlMfCEgrrpQ2eR0EACRpg5TISXskNTqdVTMzpjmntzgP9RaCkcBQ8zG1DzV21OwoyQEOU//W +ZMfg5tELii4X8zfsfbZfY92unD0fsKR5XG3qD0GvA8A9D0nFGyaKd+3dmz7ZSuUvqp5dtdeMh9y9 +hsjn0gPZ+td/um/Vjl/0PNeUzH/yEqkiP6QAcOQuk/5RUmneLR6duC1d8VHpgXulAa/DAMBdUu4y +KSrpLKezsmMFBcMBxRvdv3XcLtqx8Z6J94zsSS2UrVS8KTLo+kHewfDu8Tndvx64NLkvdbpdtGOl +OMbAlqSGto+5MsuWvrFGesKVYXCMM+jT1O0BXd98fP23jvnY7Edbj6+riLMvg1tGl2z+z67zD/x2 ++OtX5q3vscgFgMO1QQrmpHFNvvH0my5LOmONtNvrIADQKdXb0g5JDU5nGQFDC85uVawEJf2PhWLB +g7Wz4i83LqnZVKqTV/l0ITq4NXlssiu9PJ8pzijFMd6QGshq98ZeyXLlre5AWFq0SnK+DDxcQUGf +xjql5dWz4/csuqi9Z+HZrY8EQgF39rXwGatgBfc81Xf2tge6547uTV1xlfRrrzMBKC93SgsLk284 +/WqfIZ2xWtrrdRAA6JQ+bUu3uDErGAlq4cpWhatNN8a9M0OFaF14e1VLdFvN7PjueEPEUSlND2Vr +k/vT81N9Ex2ZkVyHYZf+6uTceEE7H+9RccK1t/XXXSX9f24Ng3MU9GlurVSXqAv/eNapTcsXntP2 +aP38xLR6cze8a2zezkcPntv1/NDz2dHcp66Skl5nAlB+OqWLbOkBr3O8i+22dObV0kGvgwCobDdK +gUXSLyW5sl1YOBHSgrPbFIpN7d23wUhgKJwwu8JVoYFIjTkYqTGHQ9HghBkLTgQjgZwkFbNWOJ8p +RgsTxWg2ma/PJvNNuVShMTeen1XMWo6vIjgShUxROx/vUT7l2q7Bv1wtncZVp/5CQa8Qt0sfTsyO +/2DBWa39885uezRSbZb1IhCZ0Vxi/7P9K/Y81TtraOf4l66WbvM6E4DytU76O0P6jtc5DsPWgHTm +lVKv10EAVLZ10gmG9IJcWtMqUmtqwco2Bc2K34zoLRXzlnZv7NXEsGvLKxUN6cTV0m/dGgh3sEhc +hbhb2nZhMn/L0LbxucO7x/86FA3a8eZIXyAYcHePixIr5Ipmz0tDJ//+J3s/sueJng19/dnL/lJ6 +yetcAMrbZdKnjPLY1qzJls79hLThLnaoAOChe6WDl00urHmyG/OKWUuZgZxqZ8VlBDmH+MesvKV9 +T/cpM+Te2seG9G+r2ffcl/jur0DrpWXRuvA3GxdXnzb3jJaXZixveMnvW7LlM8VIz2+HTuz61cCJ +fVtGn8oM5r58tbTV61wApod10pOGdKbXOY7A81np3Oskd5bwBYCjsFaqM6XNktrcmhmtNTXvzNYp +v9zdr/ITRe17qk+ZEVffqh+UtJRbQ/2Jgl7BbpeODSTMf2haXH3BvBUtrzQvq33V6WIZbpsYytb2 +vz76vn2/HDihf8vog5nR/NfWTP5DAACuWT/5ZsWVN5gtx9Wp7/cjbox6N0+npAuvl1zZBBcAjsY6 +6QJjcg0P165NN6tCmndmiyJTsXCcj+XGC9rzVK9y467dcy5JliFdtFr6hZtD4R4KOnSntFDx4Bfr +Zlf9RfMxNb0t76nf0thRs9mMBbNe5ClkipH+rcllva8NLx3YnGwe35/6SS5d/PZqaZcXeQBMbxuk +2pzkWqO+5OaTtPPRHm2+Z79bI9/JY3npw9dKJdk2CAAOxzrpm4b0ZTdnBqNBzTutueRbsPlVaiCr +fc/2q5h1fROmb1wl/b3bQ+EeCjr+ywYpnJUuqmqI/FXN7PiZzcfW7qibnzhQ0x7bnWiNDZTy2GM9 +meZkd3r+yO7UzIFNowtHu1MbJwZzt5rSQ6skX19+D6C8dUon29LzbswKhAx9/M7TZAQMvfzDHdr+ +0AE3xr6bXwxJl35G8uRDVQDYKIW6pI2GdJqbcw1Daj62Ts3LamVUUGsZ3DamnleHZbuzz/kfe7pd +WnmW5Oopebirgr7VcSQ2SA056YJItXlerDF8TqItFm/oqNlV0x7rj9WaA9GGyGC0ITwaMIwj+pvD +sm1jYihXmxnMNk0k843pnomWoZ3j88d60+nx3uzjEyO5hwPSw6ul4VJ9bQDwx9ZJawzpx27MqpkT +10X/98TJn9jSiz/Yrp2PTMmuaPe2S6t40wXAK+ulWZJ+I6nJ7dnVM2NqP6VJofD0XuHdylvqemFQ +yS7371yypX5Lev81Urfrw+EqCjoOy23SfENaEY0HTw7XhpeZidDicDzYEG2K9karzXEzGpgIRoKF +UCxYDEUDBUkqTFihQqYYLGaLocKEFZ0YyyfSAxOt+VRxMJ8q7MiM5jbl08UXQ9LGT0p7PP4SAVSo +TukbtvT/uDFr1qlNOu3Ly/7r57Yt/fp7W7T36T43xr+bu8LSJ1dJrl8PCQCHY510sSHdpxJ0DLMq +pPaTGpVojbo92hfGejLqfnFIhXRJPmflvvMyQkHHUdsgxbJSR0BqtqRaQ0pIShz6UbY0LmnclsYD +0qgl9UekbavYGgiAj3RKP7Wly92YdewVc3T86nl/8mu2ZetX392ifc/1u3GId/OjHdJ1N0pltYUm +gOljnfS3hvRvpZpfPTOmmcsbZMZDpTrElMpniur93bBG9qRKdgxb+vzV0ndLdgC4anp8Z8MTh4r2 +q17nAAAnbGmpW7MSM2N/9mtGwNCpn12qQraoAy8NuXWot/OpxZMfjv6PUh8IAN7K1dK/r5PmGtIX +SjF/7EBG2/sPquXYWjV21JTvvemWrYHtY+r7/Yisguv3mv+xb1LOy0u5fksDAODYjVJgkZSS5Mo1 +k+d+6/1q7Kh+y//PKth65p826eArJS/pkvS9q6TPTcWBAODNbMm4XbrVlq4t5XHCVSE1dtSofmFC +gWB51BrbsjW6L62+10eUGyv5siHrV0+us1LSTwDgrum90gIAAO9gqTRfLpVzafLSy7cTCBn60JeW +qfnYWrcO904+u066cSoOBABvZkj2uHS9LT1YyuPkUgUd/M2Qtt7frf7NSVlF//ZQ27I1tHNc2x7o +VtfzA1NRzh9ol66lnJefoNcBAADwysekD0ha7casaF1Yx66a846/JxAyNPvUZvW+NqLMUGl3kDSk +FZdJuXukZ0t6IAB4C/dL1seknxnSmZLe+S9Hh+yCrVTvhIZ2jCmfKSoUDvjmHvX0YFb9m5PqfmFQ +yf1pWfnS92VbejYtXXoF22+WJQo6AKBiXS5dIul8N2bVL0xowdlt7/r7gmZAcz7UrJ5XhzUxXNqS +Lmnl5VLqHumXpT4QALzZvVL+fOnOsPQ+SR2lPp5dtJUZyml417hG96Vl5S2FIkGFIlNbeSaSeQ3t +GFf3S4Ma2JJUZigne4rO7hvSo2HpI9dM3r6FMlQeN2sAAFAC66UfSLrejVkLz5uhk25YfNi/P5vM +6/F/eFXJfe7vd/smtqT/dtXk1woAU26DFMxKtxjSdV4cPxQNKt4cUaI1pkRrVOGEu2fXC5miUgNZ +jfdmNHZwolRbpR2OdSnpuuulvFcB4BwFHQBQsdZJTx66/NKx9127QEs/MuuIHjMxmtcT//tVJbtL +X9IN6a9XS7eW+kAA8FZsyVgv/ZMhfdnrLMFwQJFqU+EaU5HqkMLVpkLRoIIhQ4FQQIFwQMHQZE0q +FmxZOUtWwZJVsJWfKCo3llc2WVBuLK+JsbysnC92trxph/S5G9lms+xR0AEAFWu9dFDSu1+XfhjO ++PvjNHN5wxE/Lj2Y1RP/+1WN9064EeOdFA3pqtXSnaU+EAC8nU7pC7b0z6KHuMUypC+slv7V6yBw +B6u4AwAq0gapVi6Vc0mqaX/7FdzfSbwxorO+/l5VNbu2mPzbCdrSbeukj5T6QADwdlZL/2JI50nq +9TrLNDAg6RLK+fRCQQcAVKS8tMStWYGQ4ahgVzVHtOKrxytWH3Yr0tsxDemuTumiUh8IAN7Oaukx +SSfa7DLhxNNF6X1XSQ95HQTuoqADACqVawW9ekZMRtDZ1ZrVM2Ja8ZXjFak2XUr1tsK2dNftLt17 +D2+sl2atk85d9/+zd9/hcdVXwse/907vo96buy03OiY0m4SSRuhgY9L2DZvd9GwgmwAxpL27m2Q3 +5CVZkk2yuEGcYCeB0Ish9G7cu2R1WbLqaGY05b5/SCZALM2d0R3NSHM+z+OHPOHec49lLM25v9/v +HPjyevjhGvlMJ6aY66G5EpZrcDtybjoZGnBnAD74SWjJdDLCeNkxIFAIIYSYZJqRBXqF05A4vhoX +y29fxFO3vc3wYFq7ADvj8NA6uHg1/DWdDxITcy+Ux2FBHOqBBQrM0GAJUHT8lZAGN62RAkdMQcsh +CqxZDy8Dv8HAY0fTVBvwqevhsUwnItJHmjMIIYTISRvgDxpcYUSsBVdUsfj6OiNCAdC9r5+ta7YT +CcYMizmGvjh88AZ4Ld0PEmN7GsytUA3MeFchXs9IIe5OcHtLAObcCGkfBSBEOm0C3zDcAfwzMLmD +y7NfXIP/scFNV0NfppMR6SUFuhBCiJy0HrYDC42IdcaX5lK3vMSIUO/o2tPP1tu3Ew2lvUjvNcGK +6+DNdD8o190NFjdUKVCvwQJttBgH5gOpbsP47PUjK49CTAsbYKkGPweWZTqXLPE68Pnr4dVMJyIm +hxToQgghcs4aUGdBADCkdfqH/u0kCuZ4jAj1Hh3benj2+zuJRdK+e7kTWH497Er3g3LBJrBGYbY2 +ujVdGSnCZzBSkBvZrn9vBSwc3SYsxLQxOjN9NSMd34synU+G9Ghw+0H42Ro5wpJTpEAXQgiRczaM +nOM9aFS8y9edhdWdnrYubW8e468/3EU8/UV6ixnOu9bAr8t0tx68CsxmdGv6aCFez0h/g7Rv0VXg +E6vgT+l+jhCZ8mvwWOEzCnyT3Dmf3qXBXSr8dBX0ZDoZMfmkQBdCCJFz1sMlwENGxLL7rXzit2ca +EWpMzS918fyPdqPFtLQ+B2gywbnXQUO6HzSV/Bb8Vpj5vvPhC4A6MvdZ6uVVsEwZ6egsxLS2FlwK +/IMC3wAqMp1PmnRq8AsFfnI99Gc6GZE5UqALIYTIORvgqxr8xIhYRfU+LvjeEiNCjathawcv37kX +Lf3l2AETnHcdtKb9SVlmA+Sd4Hz4jNFfWUWD5atha6bzEGIy/RbsFvgM8HWy8O9lKjQ4qMKPu+E3 +X4JwpvMRmSdj1oQQQuQcDeYYFctbbsyItURqzy8hFonz6i/2p3vNdFYMHt8E518NR9P6pAzZADXA +vDjUqzBfG23UpkHeFFmOflCKc5GLPg0hRhrI/XwDnKLBDcBKoDCzmSWtD/izAmtXwZOyE0a8m6yg +CyGEyDnr4WngfCNiLf3UDOZdWmlEKF32PdTKG786kPbnaPC2DZZfDcfS/rA0OcEM8XpgMWB8R7/J +E1NhyUrYmelEhMgGd4KtEC6MjzSV+wRgyXROY4gBT2uwbgj+IKMRxVikQBdCCJFz1kMbBjUcOveW +hZSfkm9EKN32/rmFN3+b/l5uGrypwgXZ3Kjo+Azx41vT4zBjtBBfCrgynZ/RFPjtqpEtvkKI99kE +7gicGYcPKvBB4GQyW+8c0uAJFZ4Ansjm76Uie0iBLoQQIqesBy8j2wsN8ZGfn4anzKH7ei2moZgm +/uN3x32N7Phd44Tj6PCiFS68GgYn42FjGWeG+AJA/x/A1BaKw9wb4EimExFiKtgIJRos10Z2TC1h +5HhTut6odgP7NHgL2KrB1htGRlgKkRQ5gy6EECLXzDUqkGpWcBcnN9b6hZ/s4dQbZ2HzTmwX5sJr +a4gNx9m9pWlCcXRYNgx/3AQfuxqC6X7YJnCHYN7oyLIFCsxntGO6BqYcP6h5lxTnQui3EjqA+0Z/ +AbBx5Lz6PA3maiM/D6qBPMCtgVsBN+Bn5J8w8nKyV4NBZeR/DzKyEt6ojBTke1TYuxK6JvG3JqYx +KdCFEELkFGXkg5khPGWOpFbDtZhGyytdDLQOseK7SyY8O33JDXVomsaePzZPKI4OFwzDH++EjxvV +ZXgT+CIw613nw2cA9cMwTwXViGdMM70x+GGmkxBiqhstpJ8b/SVE1pECXQghRE7RDFxB91Qk18F9 +oCNEPKrR2xDgme/u4Pw1i7A4TBPKYekNM4gGYxx4tG1CcXS4sADuuxuuvhEiem/aMLIyNeP9M8SH +R2eIy1k73f7tkyNbaIUQQkxjUqALIYTINYYV6N6K5I4+D7b8rWlv975+nrljO+d/ZxFm+wSKdAVO +uXE20VCMhmfSe9xRg0+4YOPTcN1yiL77340xQ7xeg7LRNEXqWgNwZ6aTEEIIkX5SoAshhMg1xq2g +JzkDvb/lvUe4u/b089z/3cU5367HZEl9V7eiwBlfnEssEqfphbQfg7yyBYbXwVsKzGOkGJ+ngS/H +z4enjQK3y0gmIYTIDXLGSwghRM5YM/Jzb7ZR8ZJdQR9o/fsaq31bDy/+eDfx2MTKW8WksOxr8yk/ +rWBCcXRaqcC/MzLu6wzANxkPzVF7y+E3mU5CCCHE5JACXQghRM6YDbVAcm3Xx5HsGfT3r6Af1/xy +Ny/+ZDfaBIt01aRw9k0LKJvkuewifRT41vuPEwghhJi+pEAXQgiRM4xsEGf3WZLuwj7QMvYu5aYX +unjlrn1oE9wnrppHivTihf6JBRLZ4JWVsCXTSQghhJg8UqALIYTIJRnr4D4ciBLqG7/5+eGnO3jj +VwcmkhYAJqvKOd+qp2COd8KxROZocLMCcrRfCCFyiBToQgghcomBHdyTHLE2zur5u+1/uJU3f3Mw +lZTew+Iwcf53FpI/yzPhWGLyafDQanqW05kAACAASURBVNia6TyEEEJMLinQhRBC5BIDO7gn1yBu +rPPnJ7L3gRZ23NeYbEp/x+I0c96tC/FVuyYcS0yqOPCtTCchhBBi8kmBLoQQIpfMMyqQtzI9K+jH +7fhdI7u3NCV1z4nYvBaW37EYX5L5isxRYP1q2JbpPIQQQkw+KdCFEELkhPXgBUqNiudJcsRaMivo +x21be5g9f2pO+r73s/ssnLdmEe4SwxrYi/QZBm7PdBJCCCEyQwp0IYQQuWIuoBgRSDUruIuTK3aT +XUE/7q17DnHwsbaU7n03Z4GN5Xcsxllom3CsXGTzWCha4GPmhWWc9NmZnH3zAsy2tHyM+vkqOJSO +wEIIIbJfcvNhhBBCiKnLuPPnpQ4Uk/5aX4trDLSHUnuYBq/dfQCz3UTNucWpxRjlKraz/I7FPPXt +bQR7hicUa7py+K14q5x4K534qp14K5x4q13YfZb3XLfjvkai4bjRj+9X4ftGBxVCCDF1SIEuhBAi +VxhXoCd5njvQGSIeSb2Y0+IaL/10L4pJofoDRSnHAfCUOVj+3SU8dcs2Qr25W6Rb3ebRItyFt8qJ +v8qJr8aF3W9NeG+4P8LeP0/86MH7KfAfK6HL8MBCCCGmDCnQhRBC5ArjGsQl2cF9IIXz5++nxTVe ++q89mO0myk/Jn1Asb4WD5d9ZxJO3bmN4MDrh3LKZI8+Kt9qFb3RF3FflxFfrxuIwpRxzx32NRIIx +A7MEoDMEPzU6qBBCiKlFCnQhhBC5wrgV9CRnoPeleP78/eJRjef/fRfn3bKQ4kX+CcXy1bpYcfti +nrrtbYYDU7tIV0wKrkIb3qqRFXF3iR1flRN/nRuzPfVC/EQGO0IcfHziPQFO4DufhYF0BBZCCDF1 +SIEuhBBi2lsz0hR1llHxvJWTv4J+XGw4zrPf38F5ty6iqN43oVj+GW7OvWUhW2/fTjRk+Iqw4VST +gvNdhbi30ol/dIu6yTo5fW/fXn+YeFQzOuz+APza6KBCCCGmHinQhRBCTHszoAZIrqoeh6c8uRX0 +foNW0I+LhuM8+4MdLL99MfmzPBOKVTjPyzn/Ws+z39tBbALn5I2kmhU8ZQ68Va6RLenVTnyVLjyV +DhTVkEb8KeltCND0/FHD4yrw7RshYnhgIYQQU44U6EIIIaY91cDz53afBas7uR+fgwauoB8XGYqx +dc12VtyxGP8M94RilSz2c8636nn2Bzsn1MwuWRanGXeZHd9oIe4utY8U4lVOlMzV4WN6638PoRm8 +eK7AayvhD6uMDSuEEGKKkgJdCCFELsjY+fPIUJSgzm7pBXO8dO/r1x17OBBl63d3sOJ7S/BWTGyD +QOnSPM76+nye/49daDFjq1Cr24yr5G+FuK9qZHSZu9hu0GT69Ovc0Uv7th7D42rwTQUM3zMvhBBi +apICXQghRC4wrEBPthDWe/7c4bey/PZFbL1jB127+3THD/UOs/U7b7Pi+0twl9iTyu39Ks8o4Kyv +zeeFn+xOqUg/0egyb7ULR17i0WVZTYNt6w6nI/Kj18OT6QgshBBiapICXQghRC4wbgU9TefP3RUO +zHYT591Sz1O3vU3PwUHdzxjqDvP0rdtY8f0luIomVqRXnVXI6eE5vPKzvWNu537/6DJ3iR1/nRub +1zKhZ2erI88fpXuf4Q3WNQ2+ZXRQIYQQU5sU6EIIIXJB1q+ge0e3zlucZs6/bWRGef8R/c3lAkfD +bP3OdlZ8f8mEV6zrlpcQDcXY/2AL3moX3grHO13TPRVOzLbJ6ZieDeIxje0bG9IR+r7V8EY6Agsh +hJi6pEAXQggxrf0aPECZUfE8lUmuoLfqK7I97yr8bV4LK+5YwlPf3pZUB/iBtiBbb9/Oiu8uxuaZ +2Gr27EvKmX1J+YRiTAcHH2tjoM3wJn8RM9xqdFAhhBBTX+68AhdCCJGTbCMd3A1pRaaalZHGZkno +17mC7ntf8zm7z8L5ty9K+lx5X2OAp7+zneHBaFL3ib8XDcXYtelIOkL/4lo4mI7AQgghpjYp0IUQ +Qkx3xp0/L3WgmPTX+poGgzpXXz0n2DrvLLBx/ppFOPKT27Lee3iQZ767g0gwltR94r32/LlZdwf+ +JAyq8AOjgwohhJgepEAXQggx3Rk4Yi258+eBzhCx4cRzxVWzMmZzN3epg+XfXYLDn1yR3r2vn2e+ +u4NoSIr0VIT7I+z9U7PhcRX40UroMDywEEKIaUEKdCGEENNdxmagD+g9f142/sq8t9zBed9ZhNWd +XOuYrt19PP8fu4lHc2vMdmQoOuHf867fHyEyZPjLjc4Q/MTooEIIIaYPaRInhBBiuptnVCBvkgW6 +3vPnegp/f62LFbcv5qnb3mY4oP98edsbx3jhR7s46xsLUJPYnj8VDAeiBNpD9B4J0N88RN+RAP1N +Q6hmlUt+ekrKcQOdIfY/2mZgpu+447Ng+Lw2IYQQ04cU6EIIIaattVAMzDIqnqc8XSPW9MX1z3Bz +7i0L2Xr79qS2rje/3M2LP9nNWV+fj6JOvSJ9eDBKf1OAvqYh+o4M0dc8RKA9yGBH6ITXn33zgqR6 +Bbzf9o0NxCOJjyYkQ4ODNviVoUGFEEJMO1KgCyGEmHY2QkkMvqrAF4HkqupxJHsGfUDniLRkts4X +zvNyzr/W8+z3dhBLoohseqGLV2z7OP2Lc1GytEY/USHe1xgglESjtvzZHirPKEw5h76GAI3PdqZ8 +/1hUuOVqMLzjnBBCiOlFCnQhhBDTxr1QG4OvxuFzCiQ3nywBm9eS9GxxvTPM9a6gH1ey2D9SpP9w +Z1IrvYef7sBsN3HK5wzbVJCS4LFh+psC9DYN0d80sjW970jAkDPfS1bXTWio3ltrD6MZf2R/237Y +ZHhUIYQQ044U6EIIIaa8+2BmBG6KwWdI08+2ZBvERUMxgj36Fkw95cnFBig9KY9lX5nHCz/ejRbX +X1Huf7gVs01lySdnJP3MZGgxjcHOEH2NAfpbgiPnw5uHGGgZIho2dvv4cWUn51OyyJ/y/Ud39tH2 +5jEDMxqhwTfWQHp+00IIIaYVKdCFEEJMWRthcRz+JQorFTCl81m+yhTOn+uom+0+S9Ld2Y+rOquQ +M4bn8PKde5Na9d39x2ZMdhMLr6lJ6bnvpsU0Al1h+o+Mbk1vGqKvKcBAc/oK8RNRFFiyqjb1ABq8 +te6wYfm8y9bV8Hg6AgshhJh+pEAXQggx5ayDs4Bvx+ESJrShWT93kqvcere3Jxv3/WrPLyE2HOfV +/96v64XAcTvua8RkMzH/E5W6ro9F4gw0D42shjcG6G8ZOSc+2B4kHsv8GLfqc4rxz3CnfH/TS110 +7+03MCMAtDh8w+igQgghpi8p0IUQQkwZG+HsONwMfHSyn+1LukGcvg7uya7Mn8jMC8uIReK88T8H +k7pv2z2HUBSYd+nfivR4VGOwNUhfc2CkUVvTEP1NI9vUk9lKP5lUs8Ki61LfDaDFNXZsbDAuob/Z +dAO8lo7AQgghpicp0IUQQmQ1DZQN8BFGVszPzFQe7mRnoLfq7OA+wRX04+Z8pIJIMMb2DQ1J3ffW +PYfoOxIg1BOhv2WIwNFQUivx2WDWRWW4S1N/0XHoiXb6mvX9eSUhosCtRgcVQggxvUmBLoQQIiut +AXUOfGQj3AacmslcVJOCuyS5pvD9Ogu+ZDu4j6f+ymri4Tg7/3BE/00aHH6qw7AcJpvZbmLBldUp +3x8bjrNzUxJfL/1+uQr2pyOwEEKI6UsKdCGEEFllDaiz4QoNbo/D/EznA+AudaCakjjqrsFgm74t +7smuzCeyaFUt0eEYe//cYmjcbDXv0krsfmvK9+/5czND3WEDMwIgEIHvGR1UCCHE9CcFuhBCiKyw +CaxhuFaBWzSYnel83s2b5DnxQFdYVwfzVFbm9TjpUzOJheMceLTN8NjZxO6zMPdSfU3uTmR4MMre +PzUbmNE7fvxpaE9HYCGEENObFOhCCCEyai24FPiHYfgXBVKvttIo2RnoAzrPnye9Mq+XAqfcOJvY +cJzDT0/d7euJLLiqGosj9el6u35/hOHBqIEZAdAF/NjooEIIIXKDFOhCCCEy4tfgscJnlJGu7GWZ +zmc8nvLkVtD7dXZw9xh4/vz9FAVO/8IcouE4TS8cTdtzUqGaFDxlDrxVTryVTnzVLrwVDlrf6OHt +9fpmkbtL7My6qDzlHAJHQ+x7uDXl+8eiwHdXgeHz2oQQQuQGKdCFEEJMqnugwAxf1uCLgD/T+SRS +ON9HyaLk0hzQ3SDO2PPn76eoCsu+No94NE7LK91pfdaJqCYFZ6ENb9VoEV7pxF/twlvlxGRV33Nt +ZCi57eaLVtaimlPffbDj3kbikcTHEJLU0A13Gx1UCCFE7pACXQghxKRYC8UK/JMCX9HAl+l8Eimc +52XB5VWUn1aQ9L39rZlfQT9ONSl84BsL+OsPd9L2xrH0PMN8fEXcha/Kia/aia/ShafSgaLqK6L3 +bGkmPBDRda2v1kX1OcUp59vXGKDhmc6U7x+LArd8CQzvOCeEECJ3SIEuhBAirdZCtQpfB/4PkP6K +dCIUKD8ln/qrqimY4005zEBLdqygH6eaFT5w0wKe/d4OOnf0phzH4jThLnPgLnG8txCvcqJM4Ch9 +sGeYvQ/q7zq/9Ia6CT1v27rDaHFjh71r8PYBuNfQoEIIIXKOFOhCCCHSYi3UqSOr5TcCtkznMx5F +gbJT8ll0bS15M90TihUNxXSP7ZqMFfTjzDaVc79dz9Y7dtC1u2/ca60uM65SO77jK+JVTrzVLtzF +dkhDT7udv2skGorpurao3kfZSfkpP+vorj5aX0/LToKb14Dhe+aFEELkFinQhRBCGGotLFThJuA6 +YAKnhNNPNSlUn1PMgiurDFvNHmwNgo7FWavbjM1jMeSZepntJs67pZ6nbnubnoODWN3md5q0uUvs +aS/ET2SgLcihJ/RPJFu6um5Cz9u2Tl8TuiQ9uxoeSUdgIYQQuUUKdCGEEIbYAEs1+BqwClATXZ9J +qlmh+uxi6q+uxlNm7Cq23vPn3srJ2d7+fhanmRV3LCEei0/6C4IT2b6xgXhM33bzyjMLKZib+tGD +5pe66NpjeIN1TYVvGh1UCCFEbpICXQghxIRshLPjcLMGH2HS1l1TY7abmHFBKfMuq8RZkJ5d9/1Z +dv78RCxOE5D6/HCj9Bwc5Mjz+kbAKarCopW1KT9Li2ts39iQ8v3juH8lvJiOwEIIIXKPFOhCCCFS +MlqYr4nDBZnOJRGLw0TdilLmX1mFw29N67MG9HZwT3K2+nS0bd1hXccBAGZcUIKvKvWXGoee7KCv +Sd/LkyTE4nCb0UGFEELkLinQhRBC6KaBci98NA7fjsMZmc4nEZvXwuxLypnz0Qqs7vT/yItHNXob +Arqu9WRwBT0bdGzroX1bj65rTVaV+mtqUn5WLBJn56bGlO8fx69ugN3pCCyEECI3SYEuhBAioTWg +zoGPbIA1wMkZTichu8/CrIvLmfvxytHt3OkVj2o0/rWTnZuOMNiu9wx6Dq+ga/BWEs3a5ny0YkJH +EvY90MJQl+HjyYPA940OKoQQIrdJgS6EEGJMd4PFCdcp8K04zM10Pom4iuzM+XgFsy4sw2RNf5+6 +aCjGwSfa2fvHZt2j1QAUk4K7JHcL9CPPH6Xn4KCua60uM/Mvq0r5WcOBKLu3NKV8/zh+cj00pyOw +EEKI3CUFuhBCiL+zCaxhuFaBW4FZmc4nEXeJnfmXV1F3QSmqKf196iJDUfb9pZV9D7YQ7o8kfb+7 +2I6a1QPo0iceS65Z2/zLqyZ0PGH3H44wPBhN+f4x9CjwY6ODCiGEEFKgCyGEeMdacCnwD8PwDQUq +Mp1PIr4aF/MuraT2vGIUNf0Fb7g/wr4HW9j3l1YiQ6kXfZ4MjVjLBoeeaGegTd8xAGeBjTkfTf0/ +w6HuMPsfbk35/rFo8L3rQd8BeiGEECIJUqALIYTg1+CxwmcU+FegJNP5JOKf4ab+iiqqlhVNymC3 +UF+EAw+3sveBlgkV5sd5K3Jze3s0FGPn7/Q3a6u/pmZCRxV23NtINBxP+f4xNPbAXUYHFUIIIUAK +dCGEyGkboTAGX1Dgy4A/0/kkUjjPy4LLqyg/rWBSnhc4Gmbvn5o5+HgbsWHjCj1PeW6uoO99oIVg +z7Cua73lDmasSP1dUX9LkMNbO1K+fywa3PYlMLzjnBBCCAFSoAshRE7aCCUx+GocvqhA1leLhfO8 +LFpZS8miyXmHMNAWZPfmJhq2dhCP6hzUrZOv0knRAp+hMaeC8ECEPX/U31Nt0fV1KBPoJ7Bt7SG0 +mLF/dsB2G2wwOqgQQghxnBToQgiRQ+6F2tHC/HMK2DOdz7gUKD8ln4XX1JA/yzMpj+xrGmL35iYa +/9ppeHE32efls82uPxzRfTwgf7aHqjMLU35W974BWl7tTvn+sWjwr1dDzPDAQgghxCgp0IUQIgfc +BzMjcFMMPkOWf+9XFKhcVsTCa2vwVU3O4n5fQ4Ddf2qm8dlOtLixhbm/zk39lZN3Xj4bDXWHOfBI +m+7rl6yum9DXatu6w2D44jl/XQ1/MTyqEEII8S5Z/SFNCCHExKyHRcA3orBSAVOm8xmPalKoPqeY +BVdV4y2fnCZqvYcG2Xl/E00vHjW8oJvs8/LZbPuGBt1n+MtOzp/QUYaWV7rp3NGb8v1j0eCbhgcV +Qggh3kcKdCGEmIbuhZNiIx3ZryTL121Vi0rd8hLqr6rGWWiblGd27e5j1+YmWl87ZnjswnleFl1X +S8nirO+5Nyn6W4ZoeLZT17WKAktW1ab8LC2usW3D4ZTvH8eW1fBCOgILIYQQ7yYFuhBCTCMb4ew4 +3ByDj2Y6l0TMdhMzLihl/uVVOPKtk/LMrt19bL+3kY7tBq+wjp6Xr7+qhoI5k3Nefqp4657Dus/z +V59TjH+GO+VnHX66g/4jQynfP4YYcIvRQYUQQogTkQJdCCGmgQ3wQQ3uiMOyTOeSiMVpYtbF5cy/ +rAqrexJ+DGnQ+lo3O35/hGP7BwwNrShQdko+i66tJW9m6oXldNW9b4DW1/Q1a1NNCouuq0n5WbFI +PKkZ63op8NtVsMvwwEIIIcQJSIEuhBBT1BpQ58BH4nCrBqdlOp9EbF4Lsy8pZ87HKrC60v/jR9Og +7bVutt/XSM+hQUNjv3Ne/soqvBVZP6UuY5Jp1jbr4jLcpan3Htj/l1YCRw0fTx5S4Q6jgwohhBBj +kQJdCCGmmKfB3ArXafCvcZif6XwScRbamPeJSmZ+qAyTVU378+IxjcZnO9l9/xH6W4KGxlbNCtVn +F1N/dTWesslpZDdVJdOszWw3seDK6pSfNRyIsntzU8r3j+PO6yAtgYUQQogTkQJdCCGmiE1gDcO1 +rfBtDeZkOp9E3CV2Zn+0glkXlWGyTEJhHtVo/Gsnu35/hIE2gwvz443srq7GWTA5jeymMk2D7fc2 +6L5+3qWV2P2p9yHYs7mJ8EAk5fvH0GuFfzM6qBBCCDEeKdCFECLL3Qm2AvjkMNyqQKXx452N5a12 +Mv8TVdSeW4xiSn8D+WgoxsEn2tn7x2aGuo3d4vxOI7srqnDkTU4ju+mg4ekOehsCuq61eS3M/Xhl +ys8KHhtm319aUr5/LBr84Gowvs2/EEIIMQ4p0IUQIkttAncYPqvAzRqUZTqfRPy1LuZ+vJLa84pR +1MkrzPdsbiLYM2xo7ElvZDeNxKMaOzfpb9ZWf3U1Fqcp5eftuK+RaFjfjPUktAzBXUYHFUIIIRKR +Tx1CCJFl1oNXgc8Pw00K5Gc6n0QK53lZcHkV5acWTMrE9fBAhP0PtrDvL60MB6KGxrb7RlZzZ11S +jsWRetGYy/Y/1MpgR0jXte4SO7MuKk/5Wf2tQQ491Z7y/WPR4NYbwfB5bUIIIUQiUqALIUSW2ARF +Yfhn4Csa+DKdTyLvFOanFUzK88L9EfY/1MreB1qIDBlcmPutzP1YBbM/UoHZlv7z8tNVJBhj9+Yj +uq9ftLIW1Zz6W5231+mfsZ6EPZWwzuigQgghhB5SoAshRIathWoTfGN4ZDt7drcGV6D81ALqr6ym +YI5nUh4Z6h1m7wMt7P9Li+FbmV1FduZ8fPIa2U13ezY3EerT16zNV+Oi+pzilJ/VvW+A5pe7Ur5/ +LAp8czkY+wZICDFlbQJrFGbEYJ4KczWYo0CVBh7ANforb/SfAAGgZ/SfgwoMxuGIAvs02GeCPWY4 +dDUYezZLTBtSoAshRIashToVvgJ8TgN7pvMZj6JA2Sn5LLymhvxZk1OYD3aE2H1/E4efbiceNXaV +1FvhZP4Vk9fILhcEe4fZ+6D+Zm1LV9ehTOBLv229/hnrSXh5Jfx5leFhhRBTxb1QHoMVCqwAPjAM +MwCzwt++5ST41mNlpGB/59rj3+oUIA4MQ3QDHNLgOQWeisNTq6HN4N+KmKKkQBcp0UC5D2qiUGsC +jzby1tA7+ssExIB+oF+BQAwGVDi8cuQNYrY3oRYirTZAjQY/AK5h5O9L1lJNCjXnFTP/8mq8FZOz +uD/YHmT3lmYOPdlu+PZlX7WLeZ+YvEZ2uWTnfY1EQzFd1xYt8FF2SurtFdpeP0bndn0z1pOhwr/I +zyghcsvdYPHAxTG4RIHlMZgHaf9GYB4dlzpHg88owHrYAzylwUND8NiNYPjsSDE1SIEuEtoE7mE4 +R4FlGswF5m6AOWan2eFymTFZFFSzimpRRraIjr5ijEXixCMa8WicWERjOBBlw1A0uB72AvuAPRq8 +qMFfbxjZBiRETtgPTTPhQQVOAuZnOp/xeCqd1J5bPGnFOUDnjj7a3+wxvDh3Ftg47fOzKZznNTSu +gIG2IIee0NmsTYGln6xL+VmaBm+tP5zy/WPGhQdWwnOGBxZCZKWNUB+D1Qp8Kg4lWfDKdh4wT4F/ +csGxdfAHE6yT70u5Jwv+WxTZZhOYwnCOCis0WKGYlNOdBTaLs8iG3WvB6rFg91gwOUwDZpt6TDEp +YcWkDJvMalgxK2FGd/NoUc0Wi8ZtWkyzxmOaPRaK50dDMXd4IMLwQIRQf4RAZ5ih7nBEi2svK/CU +Ak/uh+fWjOwAEmJaWwPqTLhGgVvJ8kK9cL6PRdfWULLYPynPi0c1Dj3Zzu77jxA4auxs87KT8ll4 +bc2knaHPBUNdYR78p1eJRxJ/61YUuPi/TsFX7Up47Yn0HBrk0a+/kdK941Hh/JXwjOGBhRBZYxP4 +huFG4FNk+c/dd9mlwf/a4JdXQ1+mkxHpJwW6eMd6WASsBlY58q3l7lIHrmI7riJb3OKxtNp9lka7 +z9ph81u6XYX2bovDlPSn5kgwZgt2hQqCvZGCUN9wSbgvUjM8ECkfOhpWBztDDLQFCfUMtwAb4rDu +Bthh+G9UiCyzBtTZcIUGdzC6tS5bTXbn9nhMo/HZTnb+rlH36C69SpbksWRVLfmzpVA3whv/c4B9 +f2nVdW3lmYWcffOClJ/1+Dffontvf8r3j+Gu6+ELRgcVQmTeRiiMwRcU+BLvOh8+xQwAv43ADz8N +xs+XFFlDCvQctwkcw/AZ4B/MDtNSX5WTvDo3rhJ7tyPfts9dbD/sq3U1mm2mtHWajIZj1r6GQM1g +Z6gueCw8d+hoOL+vaYjehkHCg9E3FfgfK/z2agimKwchssEaKdTHFIvEOfREO7vvb2Ko29gV9fJT +RlbUJ6v53XQV7B3mL//4ir5O+wpc9B8nkzfTndKzju7s48lbtqV07zgicZh7Axi/f14IkREboSQG +n1fgq4z0SZoOAsCvY/DvnwT9nTnFlCEFeo7aBO4wfFZRuMlb4SwvmO3BXeoI2fOs+/w1rm35szyH +MpVbf/NQ2bFDg0uGjoYWDrQGXcf2D9DbMnRUgZ/b4D9le4+Y7tZIoT6mWCTOwcfb2bPZ4EJdGSnU +F11bm3LRKGDb2sPs3tKk69qyk/M579aFKT/r6dvepsP4RnG/uR4+a3RQIcTkuhssTvgnBb4HTNdv +6kEN/r0HfvglMPbNtcgoKdBzzGhh/i+Kwhc9ZY784oV+vBXOlrwZ7hcK5nj2qmZVXwveSRCPxk3d ++wbm9hwa/MBgW7D86J4++hoD3XGNn2rwE2ksJ6a7TWAahuuAWxhp0Ji1iupHzqgXL5y8M+qHn2pn +56Yjhq+olyzJY8n1tbKinoLwQIQH//EVIkP6fpRc8L0lFNX7UnpW995+Hv/mWyndO45oDOo/OdLI +VAgxBW2A5RrchYFnzE1WFdtoHyabx4LVa8ZiN400STYpmKwqqnmkrIpHNWLDceKxkUbJ0WCM8ECU +4z2Ywv0RYsOGtlo6oMAXVsGjRgYVmSMFeg7ZCB+Lwc/yal01xfV+XCX2poJZnucK5nqz/oNIz+HB +6u49/ecNtAZndOzspbcx0AJ8azWszXRuQqTbmr+tqH+XLC/UJ/2MepoL9aWr62RFPUk77m1kx6ZG +XdcWLfBxwfeXpPysZ7+3g9bXj6V8/xjuvR5WGh1UCJFe90CFCf4TuGqiscx2E84iG+4SB55SOxaX +sYOvosEYga4wgx1BBttDRALRCcfU4D4zfP060NcMRGQtKdBzwDqYpcDPrB7LxeWn5JNX524pqvc9 +llfnPpLp3JLVc3iw5ujOvgt7GwfL214/RrAv8pAKX1wFGduSL8RkWTPFCvVF19VOatf3w0+1s+N3 +jQSPGdgyQ7a+Jy0yFOWBG19heFDfB87ldyymZFFq/530NQR45Guvoxk7kS+uwcmrwfBD7kKI9NgI +H4vD/wL5qcawei3k1brwVbqweiZ3EnV4IEJ/0xC9jQHC/RMaf94NfOp6eNCg1EQGmDKdgEgfDZRZ +8GWTSdlcVO+fV3V2UbB8ad6jVecWP+TMs03Jc9yOPGtf3mzPm2azOugotFeZLeqCQFf4c5fFtf7N +8Eqm8xMinbaCthl2XQu/iMJBBRYCk7NUnaShrjANWzvo3NmHu8SOq9ie1ucpqkL+TA+zP1yOq9BG +z6FBokFjTuwMtAY5+Hgbxw4MvpkXMwAAIABJREFU4K104sizGhJ3ujJZVOJxjU6d58MH24LM+GBp +Ss+y+630HQnQ3zSU0v1jUBQo2wz3GRlUCGG8p8H8QbgN+AXgTPZ+s00lb6abspMLKF3sx1Vkx2RT +jU80YR4mXEV2CmZ7cJc7UE0KkUCUeCzpt49O4LrLIf9aeOr3kDVHV4V+soI+TW2APA1+68y3XVp5 +VqHmr3W9VXlawRMWt8XQTzGZFB6IuFpe7f5QX0NgSdOLRwkeG94cgc9+GgzvGiRENlojK+pjikXi +NDzdwY77Ggn2GL+ivvi6WvwzZEV9LNFQjAc//yqhXn1f+/NuWUjZKaktfPW3Bnn4S6+hJf9Bdlxx +OPMGeNnQoEIIw6yFahXuBc5K9l6L00zhXC95M92opuwsh7S4Rs/hAJ27+ogOpbQF/lUFrpVdplOP +rKBPQ2vhVODxwjmeZdUfKAqXnZy/peL0wudMVtOE9sxkG7PNFMmrc++xOEztVq91ZjwWXxzqCq+8 +Cl6+H5oznZ8Q6bZ1dEV9Mfy8AHYqsIQsX1Fv39aDq9iOuyS9K+qqaWRFfdbF5dh8FvoaAkRDxq2o +HxhdUfdVObHLivrfUc0qqND+Vo+u6/ubh5h1YVlKywY2j4XBthC9Dcb2DVWgajOsNzSoEMIQG+E8 +YCswJ5n7LC4zJQv9VJxRgKvIhqJmZ3EOoCgKjnwrhbM9WD0WQsk3l6sAVl8GL26BKXesNZdJgT7N +bIDVZqv656plRYVlS/Nb6s4rvsdX7ZrWxaqz0N7tLrHvVFCqrC5z1UB7aPVlce3QZtie6dyEmAxb +p2qhXjIJhbpZoXCOl1kXlWHzW+k7bGyhfvAxKdTHkjfDTcNTHUR0HDUI9Qzjr3XhrUx6h+o7zzrw +SCuaoY2RmXUZPLMFGgyNKoSYkA1wqQZbAN2jNhSzQsnCPKrOKsJZmN2F+d9RFOx+K/mzPJitJoa6 +w2hx3TuGHMDKK2DPZtiVxiyFgaRAn0bWw9ctDtPPa5eXmkoW+V+qu6D0fovTHMp0XpPB4jSH8ma4 +t8Vj2Ow+S81Aa/CySyPxvi2yPVHkkK1TrVB/ejILdXWkUL94pFDvTUOh3ndkCH+tC5vXYkjcqe74 +6CG9XdZ7mwLMurgcJYXPzVaXmaHuYXoODiZ/8zgUmL0ZfmNoUCFEyjbAp7SRnS02vfd4yh3UnluC +p9yR0veXbKEo4CywkVfrJhqOEeyN6Np0pIAZuOIKaN8Mr6c7TzFxU/g/U3GcBsp6+I7Na/nOjPOK +tYIF/icqTy94IdN5ZUrb692nd+zou7jhmQ4l3Be5cxV8RQFjDycKMQWs+dsZ9e8DszOdz3gK53lZ +tLI25W7eyYqGYhx8op3d9zfpPieth6JA5bIiFq2swVuR2mrwdBKPaTz0z68y2KHvXfGyr86j5tzi +lJ4VPDbMg59/xej5wgAXXQ+PGR1UCJGcDXDLaM8VXSwuM5WnFxjWpNRkVXvMDtNRi8vcZXWbu+0+ +a7fZbgqaLGrE4jQFTVY1gqposVDMGgnG7LHhuDUaijlCfcMFwwPRwshQtCASjBXHh+OG/KALdIRo +frU7mRFtmga3rIYfGPF8kT5SoE9xGigb4ZeOfNs/VJ9XHCtdmvfHkoX+HZnOK9M6tvcu6ny759KG +ZztNQ13hu1fB56VIF7lqzRQr1BevrKV4Egv1/Q+1sntLk+6xYHr8rVCvxVvhMCzuVHToyXZe+X/7 +dF3rKXPw4Z+dipJi06Y3f32QvQ+2pHTvOF5fBafJzxAhMmcd3KrAHXqv91Q4qTy9AJM19Y7sJova +b/NZDjkKbQ2+KtdhR561P+Vg7xLsGfb2NQXqhrrCdeG+yIx4JK57q/77xSNxml/tTnaSxbeuhx+m ++kyRfrLFfYqbDf/mLLR9se6C0uGK0/J/VzTftzfTOWUDd4m902w3tVrs5nmDHaEz3gjGrJvhyUzn +JUQmbB3d+n4R/LcFDmT7eLbDo1vf3SV2XJOw9b1ovo/ZHy7H4jTTc2jQsBXY/qYhDjzcOrL1vc6N +zZObW9/9tS6anu8iPJC4T+nwYBRXkZ28FDvk58/ycPDRNuJRQ2vp8rfhrS2wx8igQgh9NsDngJ/o +ulhVKFmUR/kp+Sl1Z1dUJezIt+0qmu97rOoDRQ/n1bn3ukscHRaHKZx0sDFYHKawu8TRkVfn3lMw +z/uS1WU+FI9q8WgwVoBGUgPYFZOCr8qFyWoi0BnS+xpxxWXQsgXeSOk3INJOCvQpbB18we6zfL9u +eUms/JT8TQVzvAcznVM2cRbYjpkdphbVrNT3twTP+3g4PrAFXsx0XkJkyoMQ3wLbpFD/e1Kop4+i +KFg9Zppf7NJ1fW/DILMuLk/pw7XZZiIajHF0tyELXe9QYOFiuHurrKILManWw2XAPUDCpXCzw0Tt ++SX4q5M/XmS2mbryZnseqzm7+E/5szy7HPk2fSMoJkhRFBx51j5/rXtfwWzPS5pGT2QwWhiPaUn9 +JpwFNjyldvpbg2iJX1AqCnz4Mti2BWRhLwtJgT5FrYeVVqfpl3XLSyhZmvfn4gX+3ZnOKRs58m29 +qEqPxWaa3988dOEnIvGGzbAt03kJkUknKNQXAakNoU6z9xTqpXbDzhKO5XihPvOiMkwWlZ7DAeIR +Awv1R9sY6gxTcVpBTh0y81W7aH6pi3Bf4lX0yFAMR56Vgtmp7frMm+nh4GNtRp9FL86DvVsg54+Q +CTFZNsJ5o93aE47IsLjMzDi/FLs/uRegZoepI2+W54nac4sfdJc62hWTYngTC70UkxJ3lzra8+d6 +XjNb1NbwQLQgme3vFqcZf5WTgbagnu9/qgKXXQbPygi27CMF+hS0Fs4wW9U/1y0vNRcvznu0/KR8 +6cg4DnexvTMejYfNVtOsvsbAJZfGtUe2QGum8xIi06RQH5vJqlK80M/sD5djdZrpOThIzIhCXYOe +w4PMvKgMiyN3fgQrCjj8Vo48f1TX9b2HRlfRzcm/xTBZVWLROJ07+pK+dzwKLP4i/OIeyNgHeCFy +xb1QG4en0TFKzeazULe8FKtL/+5w1awOFszzPlBzTvFD7hJHRza1d1cUBWehrbtgjveNeFzrCfdG +qrS4pmuOp8mq4qtyMdgZ0jOpxKzApZ+A+7aAsd8wxYTkzqeDaWID5GnweNWywoLiRXkvVS8reibT +OU0FnnJnc3gw4lTNak3fkcAHPw5r/wQ5MYJOiESmaqHuKXOkv1C3jKyoz7rE2EK96owCnEXpzT3b +eCudtL15jGB34q750VAMq9tM4TxvSs/Kn+nh0BPtRMOG1tIFA9C4Gd40MqgQ4r3uBosZHkRHU1Nn +kZ2680sw23SXNJqr2P5G7fkl93krnG0TSnQSuEscHXm17jdC/RFLZDBagY69V6pZxVftYqgrTGQo +YZHuUOAD18La34Mxs0fFhEmBPoVooLwNGwrnepeVLc1vqVtesllRFTkPp5O32nVo6NjwzOhgtCZ0 +bLh+M9x3e6aTEiKLTLlC/akOju7px1vuxFmgeyRuSo4X6jMvLEM1KfQeHpxQI7KiBX7y6lJrhDaV +OQttND7TqevantFVdJMl+S7MqllFQ6Fjm+HHSJeeD794WD7ICpE218CPFLgq0XV2v5Xa84t1f49Q +rWpP+ekF60uX5L1usqhT5u+walFj/hrXAavHciDQGZqhxbSEb3dVk4K30kmgXddKekUUHFvgcWMy +FhMlBfoUMhu+4si3fqXm7KJQ9bnF66xuSzDTOU0liqJozkLboWgotiTQFqp/PRjr2gKvZDovIbLN ++wt1YLGSpYV6oCPEoSfaJ69Qt6qULPaPFOqqMnJGPYVC3V/jmrSZ79nEU+ag/e1ehroSN0SOheOY +bSaK630pPSt/ppuGpzqIBA39HO53QPtmeNXIoEKIEevgIwrcSYKVYovbTN1y/Svndr91T93yko2u +QvukNH9LB7vPOuCvcb811BXKiwZjxYmuV00KvkonAy2Jz6QrsOwKeGuzNI3LClKgTxHrYJbJrN5f +t7zEVH5y/mZftas50zlNRRaHOWwyq0c1jYW9hweXXx7n3s0wZb9ZC5FOxwv1i+EXUqi/l9lmomRx +HjMvKsNsNSXdTM5ZYKNqWWEaM8xe3nIHh57s0HVtz8FBZl1UltIsY9WkoFpU2l4/lvS9CZx0Efzi +QUjc8U4Iods9UKHCY8C4HczNdhMzztd55lwhnjfL83DN2cWPmSxq1KBUM8ZkUWN5Mzy7IsFYKNQz +PJMELzJUs4qnzEFf01Cil8kK8KGrYP39MGBkziJ5UqBPEVfAhpLF/nkli/1vVZxW+Hym85nKnIW2 +7mDPcF4sFK8c7AjN2gwbM52TENlsqhbqvgonjkko1IsX+pl5YelooT5IPJJ4Rd1kU5n5obK05pat +nEV2ju7pJ9CRuA1IPBJHNSsp7zbIm+Gm8ZlOhgOGfi732KB3M7xgZFAhct2V8Gvg1HEvUhVqzy/R +1a1dU4gV1/vvL12cN+2m93jLnS2qSWkPdIbmkWAEncmq4iqy0dsQSDQo0qlB+Wa438hcRfKkQJ8C +1sOVNp/lW1XLCoN155XcZ7Ka5K39BDkLbU2h/shJ/c1DCz4Wim3bAnsynZMQ2W6qFeoHH5/8Qr1s +aT4HH0vcdyge0Zh/WVVac8pm3nIHh55o13Vtz6FBZl5YlkwTqHcoqoLZYaLlle6k703glMvhvzdD +4r36QoiE1sGHFPi/ia4rOykfX2XiEeGqSQmVn1KwoXCO96AhCWYhZ6G92+q2HB5sD85HY9w3Fhan +GdWsMNie8MXoosvhuc1w2LhMRbKS3zMmJtXdI2+zflR2cj4FszxPWtyWoUznNB3YPJZA3gz30+Un +56PBnZsg97o1CZGiGyGyGtYOwXwNPgkcynROY+nY1sNjN73J02u2c2x/+nfteaucuuabhwciRq/q +TikFc7yUn6rv3U40FGP35qaUn1W3vARvReIP9Ekq0ODLRgcVIhfdCTYF7kp0nbfcQcHsxGPBVZMS +qjiz8J68Ove0n+/tr3E1V55ZuFY1KQkr78K5Xr3fC+/apGP2vEgfKdCznBNuyqt11eTVuluKl+a9 +kel8ppPyk/Je89W4WvNqXFVh+Gqm8xFiqjleqFunWqF+IH2Fusmq4szXt1ofSLySMa0tub5O9+jh +/Q+1MtSd2mK1oirUX1Od0r3jxoWvb8rSHSRCTCX5cDMJRqpZnGbKz9DRt0MhWnpy/r3eCqe+LTrT +gLfC2V52asEGRVUS7rCtOL0AS+Kz+3PD8C/GZCdSIQV6Fvs1eBSFLxYv8FNU73tMVWSkmqEURSuq +9z1WstCPovBlWUUXIjVXw/CUKtS/kd5C3VWqb775QHtuD+Lw1bio1NkoLxaJs2cCq+jVZxfjr3Wl +fP8YfMPyIVaICbkHKoBvjneNBlSeUYA5UbNIhXjJIv8fcmHl/P38Na7mwnrfZhKcMjdZVSpOK0gY +T4Fvr4PcbJSSBaRAz2J2+CdftSvfVWZvzMVvNpMhr87d6Cq1H/FVuQoi8I+ZzkeIqezdhboCNwJZ +O20inYW6p9Sh6zodZwHTIh7V2PmHIxx6sp2eg4MJx++k08Jra1FUfcvoBx5rI3A0ta+Zoow8Kw2+ +tBFK0hFYiFygwteBcb9p+mtcuIoTv/jMm+l5pHCeL2fHhBXP9+3xz3A/mug6d4kdX3XCF5ZOZeTP +RmSANInLUr8FuwL3Vi0r8lScWvCAI98mo8DSRWMwEowtPnZwYMm1cNfvIXcPhgphgN9DbDO8fi3c +FR8p0k8CvJnO60Te00yu0olD5/b08fQeCdC5vTfhda5iOxWnJ17JMFI8pvHCj3Zx4OE2Wl7p5uBj +bez6wxEOPtZG545eBlqDhPsjKJqC1WvRvQU9VXafhcH20Eh34QS0OMRCMV2rPyfirXTS9uYxgt3D +Kd0/BqsG5s2Q8EOxEOK97oECFdYxznln1aJSc3YxJsv4a4qOPOuu6rOLHzc6x6nGW+5sGWgNlkRD +saLxrnMW2eg5NIgWH3fBfcm18Kvfg/S/mmRSoGepq+BGf6XzmtIleS2VZxY+mel8pjNnge3YYGtw +TqAzXBYaiLRshtcynZMQ00GuFuqhY8M0vdiV8DqL00zdislbfNXiGi/9154T5hYNxRhoDdK5s4+m +F7rY/0gr+x5opunFLrp29xPoChMLxrA4zSl1Ux9P3gw3Bx5pRdOxkN/bEKD67GJs3sQjlk7EVWin +4ZnOlO4dx9KrYO390G90YCGms6vgFuCC8a4pXZqHu2T81XPVqvbMWFG6UbWoMSPzm6o8ZY6DPQ2B +hVpMG/MLZzKrKGrCru7WOIQ2w9PGZynGIwV6lrocfll2Sn5p+cn5j7qK7Ecznc90F43Ew5H+yIKe +hkDpZvhlpvMRYjo5QaF+MtO4UI8Nxzn4eOL+RFpcY+7HK1NJM2laXOPF/9zDkef0/ziJRzRCPcP0 +NgRof7OHhq2d7PljMwcfa6PtzR56DgwS6otgMqlYPWaUFJfbrS4zQ13D9Bwa1PEbgeHBKFU6z66/ +n7vMQefOPgKdhh4vMGvg3AwPGhlUiOlsE/hisBEYs4i0esxUnVE4/mQMhXj56QUbnAW2xNuWcoTJ +okYtTnPzQPPQSYzz1XMU2OhtDBAf/5jTksvhFzJScnJJgZ6FNkK92Wm+o/zUgmD1B4oeUFRpDpdu +znzrsf7W4KnHDg7WfDwSv38LGL7EIkSuO16ofwx+bhr5O7YESDwzJwMCHSEOPtFO7+FBvJUu7H79 +E2dMNpOusWCRYIz5l1WhmtK7j1yLa7z0070c+asx73qjoRiBjhDd+wdoebmb/Y+0sntzE0eeO0r3 +vgEGWoNEBqOYXWYsdn0fM/y1bg480pZouyUA/UcCVJ1RmNSfybu5S+0cfqojpXvHseRq2PgHkONo +Quhw6ciYwo+Od03p0jwceeP/PXeVOl4pXZz3ppG5TQd2n3VgqCvsjASiFWNdoyigmlUGWsdtWGpX +4NhmeMH4LMVYpElcForDJ/21LpxFth2qWbbrTAbVrMacBbbd/honKqzMdD5CTGefhtD18DMr1I02 +k2vJdE4npEHzy928+F97krrN6jJjdSccYwMaDHakt1Hc8ZXzxmfT+84xHtXoaxqiYWsH29Yd5tkf +7ORPn36JP332JZ65Ywfb1h6m8dlO+hoCxGN/X4S7imzMurBU17M0Dbb/rjHlXIsW+Chdmpfy/WOw +xOA2o4MKMY2tHu9fmp1m/DXjNzJTzepg5WkFsv16DBWnFz6lmtVxtybl17oSjl3T4NOGJiYSkgI9 +y6wZ+TO5zl/jIq/OvS3T+eSSvFrXNn+tG+CGTbK7RIi0uxqGV8EvrTAjmwv1QHswweCav+fW2ck9 +kMYCXYtrvHzn3qS2tRsteGyYtjePsXtLEy/+5x4e/urr/P6a53j4S6/x/H/sZsd9jbS+2s1gR4j5 +V1Rjtun7WNL8cteEuu8vub5u/G2zKdBg1XpYYGxUIaaf9XAasHC8a4rmeRNOeMif43nU7DDJ1usx +WBymcMFs9/iN81SFwjkJT5wtuHekh4yYJFKgZ5mZcK4j31rpLLJ1+6tdWflhdbry1bqbnIW2Hoff +WhGFZZnOR4hcke2FejQcJ9ibXOdvt95Z6G3pmYV+vDhPQ0O0CdNiI6vtTS8cZcfvGnn2Bzt58B9f +4ZEvv6575BoabL8v9VX0vJnulLvBj8ME3G50UCGmoXFXz002E/kz3OMGMDtM7SUL/TsMzWoaKl6U +97bFYWob75r8mW5MCWbMRxP8mQljSYGeZRS4wF3qwFFgy9k5jpnkzLftdZXZ0RJ0FRVCGO8EhXpr +pnM6LtCeXCGtdxZ6oMP4Al3T4JX/ty8ri/PxhAciRIL6T3W1vX6Mo7v6Un7e4lW16Rgjd4WsNAkx +tqfBDFw93jX+GidKgt4ceTM9zxqZ13SWN8P9/Hj/XjEp+BIcJ1Bg1eifnZgEUqBnnxWuYjvuIntD +phPJRa5iW4Or2I4GKzKdixC56l2Fel22FOoD44+i+Tsu3Svoxm5xHynO93L4acOboGWlHRNYRfdV +u6g+u9jAbABQYvBdo4MKMV20wSXAuPMlfTUJVs9tpq7C+d7kmoPksIIFvl0mm9o93jV5CQp0oLgF +PmRcVmI8UqBnkU3gRlVOcxXZ4t5q15FM55OL/DXuBnexPY6qnLkWEn63EkKkz/FC/VgWrKgnvYJe +pnMFPcm449E0ePXn+9LRoTxrdWzvpWN76tOVFl5Xk3ClLgUfWQfnGB1UiOkgNlKgj8nqteDMH79z +u6/O9ZyqyIQjvVRF0fy17ufGu8ZRYMPqsSQKNe6fnTCOFOhZZBjOcRXaLBaPpdUiTS8ywuwwha1u +S5urwGZV4QOZzkcIAV+C8Cr4ZQBmK/A1IPGQcYMlu9LtKtG3gj7YGdI1WiwhDV77xX4OPZH8l2bh +dbWcffMCFl5bQ9WyQjxljnRs/U6b7RsbUr7XU+ag7vxxF/NSosIaw4MKMQ0oCXYo5tUm2GqtKuHC +ed5dhiaVAwrne3cqqjJuM5VEXfOR3aWTRs4SZBEFljmLbDj81oZM55LL7H5Lg7PIVjF4NHQW8Fim +8xFCjLgRhoD/vBvudsI/KnATCbZKGmUgyZVuZ74Ns00lGo6Pe108qjHUHcZVpK+gPyENXv/VAQ4+ +Pm4foBNatKqW+iurAag8s/A9eQ22Buk+OEB/8xB9RwIc2z9AqC+Sep5p0rWnn7bXj1F2Sn5K99df +U03DMx3Eo8YtyGmwYj1ccD08aVhQIaa4e6E8BnPHu8ZXOX6R6Ciw7jJbTdn3jSjLma2miD3fujvY +FV4y1jW+KiedO8bdkbTgt1D66Qy8JM81UqBnEQ3m2b0WbF7L1OrsM81YvZajNq8FEvwQEUJkxmih +/pM74a4C+KQ2slpZls5nBpI8g44CrhIHfUcCCS8dbAulXqBr8NovD3DgkeR3/7+7OH8/1azgrXbi +rXa+5/8f6grT1xigpzFAX0OA3sYAAy1DJ5xtPpnevreBspPzUxqd5iqyM/NDZex/2PATFD/Q4Ewl +6SF9QkxP8QQNeM0OE1bP+KWJv1ZGEKfKX+PaNl6BbvNaMDtMRMdu1qmYYTlwb1oSFO+QAj2LaDDX +5rFg81vGbeQg0svut3aNnsORAl2ILPYlCAO/vBvWO+H/KHAzaSrUwwMRhgejWN36f2y6S+36CvT2 +ICWL/cknpcEbv06tOF94Xc2Yxfl4nIU2nIW296xWx2Magy1B+poD9B0Z4tjBAfqbhhhM44z39+s5 +OEjzy13v2QWQjAVXVXP4qfaEOx6SdPp6+DDwFyODCjFVaXD+eP/eneBokMmi/n/27jw8rrO++//7 +nFk1M9Jol2XLtmTHezYSkpB9g9CythQC2LID3XKVX1seaOnThbaBUrrwtAXapyVPCzSxk1BTKC0U +KIQ4K4QkEBwvsR3vu3aNRrPPnPP7Y+REXjQjyUezaD6v6/KVeObMfb6WbXk+c9/39x5r6gnNvjNk +jWtcFjrct3103MpaU3bhC7b7iRyZ+t8tM7/MXQF9jimgV4j78v0ALvHWewi2+hXQyyjU5h/0N7gB +VtpgaPZDpLJNzKh/7n7457kM6rG+JN5Q4e7Ck033LPTxmc7OT9i++RD7/nsW4fx9S7n07qWzuueF +mK7XZtsX3/Da4+nxLGPHYkSOxV8N7qOHxp0Owa966ZHDLLqudVb75+uavFzy8wvZ843jjtZkwF/c +B9+5D+bmFy1SRQy4qdAbqlB74e+ZvrDnoLMV1RbTMGxf2HMwMZS6fKprQkUCug03zUlxchYF9Aqx +Aha7Au6Au84VVYO48nLXuVIuvyvmrXMFv5LIdQHHyl2TiBR3Jqg/CP9iwK86HdSjpxI0LZ9JQJ9e +J/fxWXRyf/HLB9j7Xydm/LpL717Cpe91LpwX4g25aV0TpnVN+NXH7JzN2KkEkYkl8iNHYkSOxIj1 +X/xs+9jROEef6mfpLbM7Om3NuxZz4HunyMSnfxb7NFx2CbwL+HcnBxWpNlvBm4Zlha4JFJlBD7b5 +DjlaVA2qa/EdLhjQizc4XX4/eO4F9QGYQwroFcKAbl/QjctnjpS7FgG3zzXsDbqD8USuBwV0kaqy +CWJMBHUTvg3c4sS4M20UN90Z9Jmesf7SlkOzCuerf6GLS9/fPePXOclwGYS7AoS7AnBj26uPZ+I5 +xk8lGD0aY+TAOJHjcUYPjpOKzuw94M6vHGHJjW2zOjrNV+9h1dsWsXOr46ecfnIr/Mfd4GjyF6km +SVhuFsgdbp+JN1A4loQWBbS8/SKFlwQODu8bm/J5T9CNy2uSS0+56Mfjhx5g31zUJ3kK6BUiB2HT +Y2C6jNJt2pMpmW4jaXpNLKgvdy0iMjubILYZnjMcCugzbRQ37Rn0U9MP/jseOszur838M8PV7+zi +ynsKTl6VlSfgoml5iKblIXpuf60xf2I4zciBaH6Z/LE4IweijJ1ITHk0XfRUgkOP97HszgWzqmPV +O7vY9+2TpMezs3r9FNZkYD2w2clBRaqJK79tcErFzuB2ec2RQLMv4mxVtSfQ7IuYXnPUSltTNj7x +1XuID029mNeT79GkgD6HFNArhAEh02Niuk0tb68AhstImW4TUwFdpNrtd2qgGc+gt/sxXAZ2kQ7n +2WSOZCSDP1z4DeqOhw6z699nPru7+h1dXPmByg3nhdQ1e6lrbmHhNS2vPnbBI+D2j5MczR/xu+vf +jrL0lnZcHnPG9/ME3Kz+xcW8tNnZlbQ23Hc/fEXLQqWGrS70ZLGA7q5zDThaTQ3z1LkGUgUCurdI +QLfzAf2bc1Gb5CmgVwgb6l1uE9NlpMtdi4DpMtOm28BWQBepaiYccKrL40xn0A2XQbDVN61u5uOn +EgUD+qzD+TurN5xPZcoj4IbyR8CNHo4RPR6nsWf6/QImW/mWhez75olXA79DlgXgl4H7nRxUpIoU +PBnH31A4oHuC7kFHq6mfGAeXAAAgAElEQVRh7jr3UCqSWTHV876GovFQpxzNsZl/vCxzwoR6021g +KKBXBNNtpEyPqYAuUuVycMCpseLDqUL78i4oON1l7gVC/M5HjswqnK96+6J5F84LCbT46LyqmTXv +WjzrcA7g9rtY867FDlaWZ8CfbIXp/YEQmWdsWFToeU+R88+9IbdOOHKIv77whx3FVjPY0OVoQXIe +BfTK4TdcBqbL0PK3CmC6jIzpMjAhUPxqEalUfjgKOPPBpz3zWfT6aR+1duHl8zu/coSdW2feF2nl +Wxfxug8un/HrJO+Sn+sk0OpzetiFabjX6UFFqkTBT808flfBF/vDXgV0h/jCnoJfy2K/F2jyas4p +oFcIGxJ2zsbK2YU/tpKSsHK2x8rZWPljm0SkSk10znas8+/MO7lP96i184P/y984zs5/m3npy9/U +yVW/shxmcR645Lk8JmvfvcTxcW34wy/qza3UpoZCT5ruwpHEW+fS+zGHuP2ugv+QGe7C/3hodenc +U0CvEDaMW1kbO2d7y12LgJW1fVbGwoBouWsRkYvm2DL3CwXpQqZ91No5ndz3fOM42x84OKN7ASx/ +0wKu+Y0VCucOWPbGBdP+gGW6DGjzwW86OqhIdSgY6lxFQqHLa2oLqENcPlfBhtTuIg02DQX0OaeA +XiEMiOayFpYCekWwcpbXytoK6CLzgOFgJ/eplqJPpX6aAS82adw9/3mcn80inC974wJe/xsrHQnn +ieE0P/2X/ez/7kkGX46QiTt67FhVMF0Gl77X+Vl04GNfhik7KIvMUwVDnVEkFLp8LgV0h7h8hT/s +MIqsZkABfc6pi3uFMCFqZSysrOX4pjeZOTtn+6yshaWALlL1bAdn0Ge6xD24wJ8PzEVayScjGTLx +HAcfPc3P/nUW4fzOBVzzoZUYDoTz5Giax+97icixs1eU1jV5aVgSJNwVoGl5iMYlQRqWBGZ1nFm1 +WHpLOy9//dh5X4uL1OSFjwJ/4uSgIhWu4B70ojPoRUKlTJ/HX3gG3eXREvdyU0CvHGNWxsbK2dNb +DylzysrafivfrXms3LWIyMUx4cDMeq9PbaZN4tx+F3VhL4lpHNn14pcOcPAHp2dc07I7F3DN/+dQ +OI9k2PYn54dzgMRImsRImr7tI68+ZroM6hfW0bA4SHhxgOblIRqWBAl1zI9/ygzT4NL3LeWZz7zs +6Lg2fORB+IdN0O/owCIiUvUU0CuEDYfTsQy5pNVc7loEcqlccyqWxYBD5a5FRC6O5eQe9P4kVs7G +dE0/DQcX+KcV0GcTznvu6HA0nD/2J9sZm8FssZWziRyLEzkW59ikx70hNw1dAZqX1xNeEiC8OEDj +snrcvuqbbV98fRuNy44xenDcyWFDBnyM/A+RWjAOTPkeN5e1cXun/kaWS1le023ObAmTXFAmmSu4 +WjeXKbzkS9s/554CeoV4BY4tj+fi2WQulEnkfJ66wstPZO5k4ll/NpkLZhK5WC+c6C13QSJyUTJw +0AMWDvRdsXM28cHUjGaI6zvrGNzj/GKcJTe1ca3DM+djR51Zyp0ezzK4Z+ysX7dhGgTbfDQsnhTc +u4LULw448muYMwZcvr6bJz+10+lhf/MB+Ow9cMLRgUUqU5QCAd3OWOCd+lt0LpXzeoJuBXQH5FJW +wX5XdrbomjMF9DmmgF4h7gNrM+xPRzOXxwaTLY2LgyfLXVOtig2mWpJjWYB9RtGdoyJS6T4IyS1w +EuhyYrzx04kZBfTgNDu5z8SSG9u4/n+txjAvPtmmxiaWtR+NOVDZ1GzLZrwvyXhfkpMvDL/6uCfg +ItRZR3hxML+3fXGAxp4QvobKOXV04dXNtKxqYGivox+0+F3wB6iru9SGgqEul7Up9Dc+k7J882Pj +TPnlUoVn0LOZwgHdVkCfcwroFcSAvalo5vLUaKaFxSigl0lyNN2ajmYA9pa7FhFxhg0HDMcCehKu +mP710+3kPl2Lb2jj+o+sxpjBMvuppMYyPFaCcF5IJp5j5MA4IwfGOfx436uPV1pTuis39vCDj293 +ethffxD+ZpO2U8n8VzDUWUVmbbPJnLPfSGtYsa+lndUS93JTQK8sLyfHMiQj6Q5gR7mLqVWpsUx7 +akwBXWSe2Q/c6sRAMz1qbbpnoU/H4htaueGjDofzI+UL54UUakrXtLye8OL83vamS+qpa5r7E0rb +1oXpuKyRvh2jTg7rMeHjwK84OahIBSoY6jLJXMEXJyPpFuCIkwXVqlQk01Lo+WK/FyigzzkF9Api +w4/iAylSkczSctdSy1Kjme7YQBLgmXLXIiLOMJxsFHdqpgHdmYmfxde3csNH1zgSztPjWR7/xI6K +DedTmdyUbrJSNaW7YmMP3/vfLzq9+emeh+Ez62GPo6OKVBADThT6a5Mey8KiAs9Hs62OF1WjkkW+ +lun8JFUhx4pdIBdHAb2CxOFJYzCVTkczC9Uorjwy8aw/PZ5ZEB9Kp234YbnrERFnTCxxd0R0hket ++Ro8eAJuMvHsrO/Z9YZWrv8d58L5tvteYsTZruRldcGmdC6DYOv5TekaFgfyZ9PPQvOKehZe1czJ +nwwXv3j6XFb+TPT1Tg4qUmEKrkpMRQuHwkw8W3DWV6YvEy8c0Iv9XqAVpnNOAb2C3AvxLZb9fHwg +dePY0diSllUNr5S7plozciTWHetPmrZl/2gTVNfUkogUst+pgWKnE/kZ1BmEvOAC/6yP6ep6Qys3 +/O6aGR3tNpVUNMO2P93B6KH5E86nYucu3JTOG3QTXhqkcWmQ8NIgTd1BGpYE8dS5pjXu5eu7OfnT +Yadn0d/7MPzlenjJ0VFFKkfBFSLpYgE9kWt3tJoalk3k2go9n4oW/jDZhH2OFiTnUUCvMAY8Nt6f +vHG8P9mjgF568YFU93hfEhO2lbsWEXGO4WBAz6YsEqPpGe17rp9lQO+8qpnrP7rakXCejmV54pM7 +ayKcF5KOZRnYHWFgd+Ssx+uavDQtDxFeEqShK0DL8nrqu+rO65TfuCzE4utbOfbDQSfLMm34BPCL +Tg4qUilysK/QhpNUkWXVVtpqTA6nwv5mX6TghVJQcjgVtjJWuNA1xT4ssbUdZ84poFeeR6OnEn+c +GE6tAr5X7mJqTWI4tWr8dBIDflDuWkTEOb0wtgWGAEeWScZOJ2YY0Ge+D33hNS3c9HtrMd3OhPPH +79vB8H719plKYiRN4oXhs2bbTbdBfec5TelW1HPZhh6O/3gIO+fcNLoNv/AgXLcJfuzYoCIVwg8H +0pBliuyRS1tkYlk8wamjyejxePeCZp/jRynUksjR+LJCz2diWXLpgh31M+Nw2NGi5DwK6BXmFXj6 +kpH08Xh/qmv0SKyrcWnweLlrqhUjh8aXJAZSjcnR9In92n8uMh/tx6GAHj2VpHVNwUmIswRnGNAX +Xt3MjR9b40w4P7Pn/EBtz5zPhpW9cFO6uiYvpmmQczCgA5jwSeDNjg4qUgHuhvSWfLPOVVNdE+tP +0tgTmnKM+GCqB1BAvwjjQ6megs/3Fe2xsv9eKLpJXS5OeQ4TlSndBxbw8OjRGJHD45eXu55aMno4 +dsXI4XGAB+/L/z6IyPziXCf3GR61Vj+Do9YWXNnEjf97rSPnfWfiWR7/5A6Fc4clRtLkMnPyz8Rd +m+G2uRhYpAIUPB1nvL9wOExFMsss23aq32ftsW0jHckUDuhFfg8MnXBUEgrolemBkUPjxAZSl1pZ +a3pda+SiZDOWOzGUWhs5GsOCh8tdj4g4z9Gj1mbYyX26M+gLrmzi5j9c51g433bfDoZf0bL2amLA +p8pdg8gcKdjfJ1Zk9tbKWPWjh8Z1FPEsDR8Y77Gy1tRLFMivYijEgsccLUouSAG9AvXC7kwi92L0 +VKJuaF90yqVA4pyRfWOroqcS/mw898Im2FnuekTEeZaDjeKiMzwLPdDqwywSuhdc0cTNf6BwLtz4 +kJa5yzxkF+nvk0nkijYoixyJaXXpLI0eiV1R6PnkWIZsIlfoEjurJsoloYBeoQz4l+FXoowcHL+x +3LXUgpGD4zcO7RvDhi+WuxYRmRvOzqDPLKAbBoTafVM+37YuzE2/vxaX16Fl7Z9QOK9mNvyZPevT +2kUq00Y4RZEO4JHj8UJPkxhKr82mcx4n66oF2XTOkxxJry50zdixwl97YPcH4bRzVclUFNArVAa+ +FDkRPxk9lVg4/MrY8nLXM58N7R1bET2V6Bw7mTjthQfKXY+IzI2MgwE9PZ4lPV74rNhzhaZY5t62 +NsytH78Ut//idzRl4jke/8ROhvYpnFe5a7bAO8tdhMgcKLhEevRQrOCLbcv2Db48ts7RimrAwO6x +S23LLnj0yOiRwl97tLy9ZBTQK9QHIQl8dmB3hIF90dvKXc98NnQgelN//jzcz9wNM5sWE5Gq8QHo +AxzrljbTWfQLBfTWNWFu/WMHw/kndzC0b+yix5LyM+CT9+l9mswzNny70POpaIbEcLrgGJHDsRvV +LG4GbNsYO1J4RW58KDWd888L/t6Jc/SNv4L54J8ix2JDsdOJrhE1xZgTIwej3eOnk0vGjscHbbi/ +3PWIyNwxwKaMjeJCnWd3cm9d3eBcOE/keOLPdjC0tyTh/DBwkPzXU+bOZcvhveUuQsRJXfA/5D8s +ndLo4cKfo2ZTudahl8fWOFnXfNa3K7Ium7IKHjE6erjo7HlfFzzqXFVSiAJ6Bbsbxm2bz/XvijCw +K3KXPi10mG0bA7sid/XvGMW2+btNUPS7k4hUvYNODRS9iBn01tUN3Ponl+Gpu/hwnk3meOrTuxjc +U5Jwvt2Ea3pheQrCBrzehnuAzwOP2jBQiiJqhQGf2Abuctch4pTbIQv8W6FrIkdj2LnCn/8NH4je +4mRd89nIwfGbCz1v5WwiRwu/BTbgoYnfOykBfdOvcHH4G+NI7JdHD4939784cvWCq5pfKHdN88Wp +nw5fM3o01jl6NHbUhs+Vux4RKQnHOrnHZjqDPnEWessqB8N5yuLJP99F/87Rix6rGBtecsEb18Mg +wK9AFPjJxI8Hz1z3CCy0YK0F6wy4Glhrw6UGTN0lT6ay4gRsAr5U7kJEnGLBZhN+e6rnsymL4YPj +tKyon3KMbCLXcXrHyOULLmt6aU6KnCdObx+5MpfMtRe6ZuRAlFzaKjbUZueqkmI0g17h7oU48NGT +Pxlm+ED0jlQ0Eyx3TfNBKpoJjhwcv/3EC8O44Dc1ey5SG2wHl7jPdAY92O6ndXUDtzkazneWMpzf +eSacF/J+OLkBHt0In+uFTb3w+jjUm/mQfrcNnwC+hYOrGea5P/28PtyQeWQTvECRI20H90SwrSKz +6K9E78rEs/6CF9WwbCLnGzkwfmeha2zLZqD46qtdG+BnzlUmxSigV4Fe+I/kWObbAy+P1Z14fuhN +5a5nPjjx3NBd/bsj/tRY5pvr4ZvlrkdESsN0MKDPdAbd5TG5/ROX4wk4GM53zH04B/Zk4c3TCedT +uRcy6/Nv8r66Ee7rhbf3wnIDmk242YB7yS+TfwZ9YHquJc3wq+UuQsRJ9qRVNxeSieeK7ou2s3bw +xPNDtzta2Dxy/MeDb7SyVqjQNSOHxoudfQ7wZeeqkulQQK8SHvjt/l2jycjh2BX9uyNqjHERBnZH +1o4eiV3evzuSsOHD5a5HRErHdHCJe3w4RTZVdFngWZw45zybsnjyUyUL5zstuHWuzr7dACPr4ekN +8P964cO9cJMXwiassfMN0v4c+C/g0Fzcv4r80f0QKHcRIk7xwf8DCn4T6385gl2kFWWsL3nN6NHY +IgdLmxdGj8S6Yv3JqwteZNkMvFx09nwE+Gen6pLpUUCvEu+DA1bW/r1jPxpgcNfoO+KDqaZy11SN +4oOppoFdo28//sMByNq/s0lv+kRqiguOAYXP8JkuG+J9M5tFv1jZlMVTJVrWDuy14a5N0F+Km51x +N+TWw56NsLUXPt4L7+yFZSloqOGmdJ0h+FC5ixBxyt0QAf6h0DWZ8SzDrxQNkMapnwy/JxPLnn+O +ZY3KxLP+Uy8O/xJQsLn04L4omVjhvm82fL4XdHZniakreJXZAl9rWVn/riU3tp+85Oc6v2S6zaLr +UiTPylquV75z8leOPj3QObw/+tVeuLvcNYlI6W2BfcAKJ8a6+Q/WsejagqfXOCaXzi9r73upZOH8 +9o1wqhQ3uxg11JRuCFimN8syXzwALa78sY1TLsM23Car3rIQd5G+Hb6wZ98lb174iMMlVqUD3zv1 +3uRoenWha7KJHPu+cxIrU3AVWMyE7ovZ3iSzoxn0KpOBXxncFz00uG9s4dFnBu4qdz3V5NgzA28e +2hftHN4f3Y/284nUsrI1iputXNriyU/vUji/gBk0pauKX08BLdqWJfPJPfkPne4vdI2dtTj1s5Gi +Y6UimZUnXhi6zqnaqtXx54euLxbOAU69OFwsnAP8X4Xz8tAxa1XmgzD6ELzv5HNDT/kaPNe6A+7R +rmtaflTuuird8eeGbhjcF73mxPNDKRveu1EzECI1y4D9RbY1TttMG8XNRi5j8cxf76Zve/E3qRfL +gH0m3PH+Kg+z90IG2DXx46tnHn8ImgxYl5uYaTdgHfA6qmR/twG/sxX+790wXO5aRJxgw98Y8BsU ++DsYORqjeVmIYEfhhu2jB8ff7Am6I+1rwnucrrMa9O+OrIkcGi/aTDp6OkHkWLzYZTET/taZymSm +NINehTbAc9mM9YHDj/dZgztH33R6+8iV5a6pkvXtGL1scHfkjYef7LdyGWvTRvhpuWsSkfIp51Fr +M5XLWDz16V2c/ElJ8theC257P5wsxc3K4UxTuo3wuY1wby/ctAjCFqwF3mfAp+38yR6Hy1zqVMJp ++Fi5ixBxysRKnU8Xu+7Yc0PTOavbGNg5+ksjh8aXOlJcFRk+GO0e2DX6LopsX86lLU69UPzfEwP+ +bD30OVWfzIz2oFexLfAhb737/y67c0Gu67rWr7SsbHCsO/F8MbR3bMWJF4bee/DR067UWOYjvfDZ +ctckIuX1MLzdyncGv2j1nXW89R+vcWKo81hZm6f/ahcnp/FmygGvuOZ5OJ+pLfmmdCsm720HrgRa +y1xazITlevMs88VW8KbhJWBVoesaFtax5Ob2ouOZLiO56A2tDzQsCszJ6ROVZux4vPP4s4P32JZd +tO/GkacHiJ4oOnv+sheuvNuphqoyYwroVW4L/EWgxff7y+5YkF5wVdNXFdJfM7R3bMXJF0fec+ix +057EUOrPe+Hj5a5JRMpvSz5o7XJiLMNl8J5/uwnT5ew/p6UO5zm4/R44UYqbVbsLNKW7mnywuPgD +7qfvs73wkRLeT2ROPQRvtOH7xa7rfF0TLSsbio5nuozkgquav9LUEzriSIEVavhgtPv0T0feN51w +PrhnjNPT2CplwZ2b4DFHCpRZUUCvcjYYW+ALgWbvr3ff2p5rv6LpvxZc1vRSuesqt9MvjVzRv2P0 +7Uee7HfFB1Nf2AAfMsCpbaciUsW+DH4PxHBom9fbvnAtoSJ7I2fCyto8/de7Ofn8kGNjFrA/B7cp +nF+c+8FTDyvP2dt+NdA5R7dMumDl+/PHBorMC1vgK8B7C11jmAbL7uigrqX4IQ22Qa5jXePX29aG +dztVYyXpfzmyemDn6C9hF+8pFhtMcWhbH1iF3wrb8NBG6HWsSJkVBfR5YCKk/6mvwfOnPbe2221r +wz9YdG3rM+Wuq1xOvDB03cCO0TcffrLfSI5l/qoX/kDhXEQm25IPNl1OjHXbn17GgiubnBgKK2vz +zGd2c+K5koTzIxPL2g+X4ma1aIqmdFcBF31msw3/byPce9FFilSIR2BhLr/UveDZlS6fi+V3duCt +90xnWLtxWeh/Fr2+5ceOFFkhjj8/dP1EQ7iiWS49nuXAD06TSxY9mXkwA5d9EGpia0AlU0CfRzbD +hz11rr9deku72bqq4bklN7Z9r5bOSbeyluvYMwNvHnwles3hJ/qtTDz74Y3wD+WuS0QqzxbYBtzm +xFivv/cSLvm5hRc9jsJ5bdgG7pOw5ExwN/JbLtYBa5jZ+7KMG9a8z8GmhyLl9hC8xc4fi1jw74I3 +5GbZHQuKno9+hq/Ru3fpTW3f8ATcc3/0xhzKJnK+w0/3vyM1kl47zes58IPTZGLZYpfaNvzCRof6 +s8jFUUCfZzbD+90e818XXdvibVnZcLLr+tavBlp8JTk4t5zig6mmYz8aeM/QvmjnieeHUhPd2reW +uy4RqUyb4V8M+BUnxlr9zi6u/MCyixrDytk889elC+cW3L4JDpXiZjI9UzSlex2FZxM398Km0lQo +Uhqb4TMG/G6x63xhD8vuXIDLM73dSqbXHO28qvnfG5cEq3JLz+iRWNepnw6/28pY4elcn8tYHNrW +R3KkeK83G/5qI/z+RRcpjlBAn4c255fPbW1eHlre9fqWVNu68Dfb1jU60hCpEvW/HFk9uHP0HSd/ +Olw3tC96xIL3boJ5tZRJRJy1Bf6AaRztMx1d17Vw0++vm/XrrZzNDz+zm+M/Lkk4P2rBbQrn1eMR +WGjD1TastV/b236mKV0OuLwX5uUeW6lN28B9Ah4Hbix2bbDNz9Kb2zCnGdIBu67Ft2PxG1q/6wm6 +5/acTIdk4ln/iReGb4udTlzLNLOblbE48mQ/scHUdC5/Nga33AuZiypUHKOAPk9thXAGvuhv9v7S +4uvbCHcHty+6puX7vnpPrNy1OSURSYdOvTD8ptEjscuP/3CAxEh6K/BrvTBW7tpEpLI9BO+xHVpl +E14S5Oc/d/WsXlvicH7YgNs2wLzualwL7odAHax1wRU29PXmlwSLzBsPwhITXgSai13rD3vovrVj +2svdAUy3Od60IvT9Sm+sfPqlkStG9kffZGXt4HRfk0nmOPpEP4nRaZ2SNgi8rheOz7pIcZwC+jy3 +GX7T5TL+unVNuK7j0nCicXn9tgWva3rBNIzqbZpm28aJnw5fM3Zw/PaBvVF//47RuJW1fncD/FO5 +SxOR6rAZrjLgJ06M5faZvPuRm2b8L6pt2fzo7/Zw9OkBJ8oo5qgBt2+Ag6W4mYjIxdoCbwAeBYqG +U0/QTfet7fim1zjuVS6/q7+xO/jDBZc1vkSFvDe2bNsY2RddMbhv7LZsIjejkyDS41kOP9FHerzo +nnOAhAl3rYenZ1epzBUF9BrwECyz4fO+es9bO69qpmlZ6GTbuvD3qvFsyJGD0e7+XZE3R47GFpz8 +yTDJSOabbvhtNToSkZnYAg1AxKnx3vnFN1DX7J329bZl8+xn93LkqX6nSijk2MTMucK5iFSVzfAO +A74GxY8Sc/lddN/UNq0j2M57rc8cauwOPd26pmGX2+sqy1LvbDrnGdg9dunYkfEbsymrYCf7C4kN +pjj69AC51LT6Q2eBX9Tqm8qkgF5DHoa3Z+HzTUuD3e3rwoQW1B1ruaT+6ZZVDfvKXVsxI4fGlwzt +GbstejLe07czQuRo7LgNf7QRHix3bSJSnbbAANDqxFh3/vkVtK2dVt+ekodzN9yuTt8iUq0ego02 +PMA0cothQNu6RtrWhjFmk3IMsr5G776mJcHtTSvq95umYc1ilGmzbNuIHI4tjhyJXR4fTF1mW/b0 +P+mdZGhflNPbR7CLnHM+wTbg1zbAF2dzL5l7Cug15kEIGvBRw+DD9Z11Le2XNlK/sO5U47LQM22r +GvZU0rFs2YzlHtk3tmrk4PiN0dPJzsE9EUaPxAZtm7+Lw2fvhXi5axSR6rUFngWuc2Ks635rFT13 +dBS9zrZsnv3cXo48WZJwftwNtymci0i12wx/aMCfT/f6+oV1LLquFbd32s3jzmO6zXFf2HMw0Oo7 +1Lg4cMjf7HNk1VV8KNU4djzeHR9M9aQimWVW1grNdiwrY3H8uSHGjs/oLfEf9MJfzvaeMvcU0GvU +RFD/VeBjDYsCi1pX1BPqrEv6m7z7GpcGtzdfUl+2pZBjx+OdwwfHr4gPJC+LnkwEhl8ZI3Ii0Q/8 +kwF/qyZwIuKELfAQsN6Jsda9ZwmXre8ueE2pw7kNt2+E/aW4mYjIXJsI6Z9imvnFE3Sz6JoWQh1+ +R+5vesyIJ+Dq9wTcg76Qe8gb9gx5/K6Ey+tKe+pcSZfPTAPkUpY3k8j5c+mcN5PM1aUjmZbUeLYl +Hcu2ZRO5tukek1ZM9HSCE88Pk41Pa785gA38US/8hRP3l7mjgF7jvgx+L3zQhl9117muCi8O0NQd +ItjhH/E3+/aF2n2HGpeGDrvrXNM6p2E2MvGsP3I0tnS8P9WTHE6tjA2mmsaOxRk5NE42ln3Bhi9m +4F8/CMm5qkFEas8W+ATwJ06MtfTmdq7/6Oopn7ctmx9/fi+HnyhJOD9twR2b4OVS3ExEpFQ2wyYj +vzS76J70M+oX1rHw6mY8gWm/pKJlEjn6Xhph9PCMDmbKAb/RC/88R2WJgxTQ5VUPw7ocbDRgQ12j +tyvY6SfY7ifU7re8Ic8pX6PnSF3Y0+cNe4eCrb4hT8A948CciWf9scFUS3I03Zoay7SnRjPd6Whm +QWwgaY73JRk/nSQ5mj5qwJYcbNEbTBGZKxNv9B5wYqzmS+q56zOvu+BzJQ7nfRbcru+dIjJfPQxv +t+DfgLrpvsb0mLSvC9OysmF2e9MrgWUz+EqU/p2jWNnpN5y3IQVs2JhvtidVoFr/iMocug/MlXBD +Dt5owB2GaVwXaPF5A20+/A0evA0efPVuXH5XzO13DZkuI2W6jLTpNpOG20gBNjamnbO9VtbyWznb +a+VsXy6Za8kmc8HkWJZMNENyLENsIEliKJ2yLPtZAx6z4dED8Ox9MKdNOUREtsCNOHS8jDfk5l2b +bzjvcduy+fHf7+Pw431O3KaYPuCOXthdipuJiJTLZrjZgG8wjXPSJ/MG3bSsbKBpeQjTVR0xyLZs +Ikfj9O8eJR2d9nL2MwZteOdG+OFc1CZzozr+ZEpZPQhBF9xkwfUGrLZhpQGr3HWugC/kwfQYmG4T +02Pg8pj5P1U25CUY23MAACAASURBVDIWVsbGyub/mx7PkEnkYjbsA/aasMeCH8bhGTV8E5FS+zIs +8MApp8Z71+Yb8IZeW0Jp2/Dc3+/l0DaFcxERpz0Ci3PwCPkPW2fE5XfRurKBlpX1FRvUbctm5FCM +gd2jZOKz6uH8nAveq6OIq09l/omUimeDsRkWu6EnByEj/yNM/mxhF/m9LmM2RGwYd8G4AQffD8fK +W7mIyGu25JtO1jsx1l2feR3Nl+SHKnE47zfhjvWwqxQ3ExGpFNvAfRw+bsAfAzNu2e7ymoSXBmla +GpzV+elzIT6UYvRwjMjRGLn0rBaU2sDfx+B374WynOkuF0cBXUREatZm2G7A5U6MdcPvrGHJTW1g +wwv3v8L+/3Fscr4QhXMRqXkPwRtt2AIUP+9yCr56D43dQRq6AvgaPA5WV1xyLMPYsTijR8Zns4x9 +skED7tkA33aqNim9+dHOUEREZBaM/DFkjgT06OlEycO5BXf2KpyLSI3bAI8+Aldl4W8MeN9sxkhF +M/TtGKVvxyhuv4tAm49QRx2hDv9Z25eckE3kiA2mGO9LED2VnMlRaYU8nIHf+SCcdmIwKR/NoIuI +SM3aAn8NfMyJsXru6MDlMUsSzm0YsPNHqe2c85uJiFSRh+FWC/4RWOvUmC6via/+tUbJ3noPbr8L +l3uiD5PXxOXOx6pc1sZKW/keTFmbTDJHOpohNZYlHc2QjGawZrd0fSqvAL/ZC99zclApHwV0ERGp +WZvhXgO+4MRYhsvAzk3/6JvZUjgXESnsfvAE4EMGfAoIlbueORK34TMj8Be/nT9KTeYJBXQREalZ +E/sWv1/uOqbLhgEjv6x9R7lrERGpdA9Cu5EP6v+LfDPj+WAc+JIL/ur9cLLcxYjzFNBFRKRmPQg9 +Jhwsdx3TNGLDGzfCT8tdiIhINXkAWkz4LQN+ixmenV5Bxmz4Jx/89d0wXO5iZO4ooIuISM3aCq40 +xAFvuWspYsSAN22An5S7EBGRarUFGgz4dRs+AKwrdz3TtBP4V+Cfe/NHg8o8p4AuIiI1bQvsA1aU +u44CRi140yZ4odyFiIjMFw/DuhxsNOAeYEG56znHsA3/7oLN6+HpchcjpaWALiIiNW1L/rzYny93 +HVNQOBcRmUPbwH0S7gJ+3oY7cLD7+wzY5I/MfMyG7/jg+3dDrgx1SAXQOegiIlLTDDgw973XZ2UU +uEvhXERk7twOWfIf1H4b4MuwwAN32HCHATcCywGPw7fNAPuBpw14zIBt66HP4XtIldIMuoiI1LQt ++e6+f1fuOs4xCtzVC8+XuxARkVp2P3j8+Yaiq4FVJqy0YTH549tCQD3QyGvHuY2T/x4enfj/ceCo +AfsM2JuBvUk4dG8+pIucRzPoIiJS00w4YJW7iLNFDHjzBoVzEZGymwjS+yZ+iMw5s9wFiIiIlFM2 +v8ywUkQMuGsDPFfuQkRERKT0FNBFRKSmjebPQa+ESfSIlZ85VzgXERGpUQroIiJS034bUsCJMpcx +ZsGbN8GPy1yHiIiIlJECuoiICBwo473HUDgXERERFNBFREQwyrcPfcyEn+uFZ8t0fxEREakgCugi +IlLz7Pw+9FKLmfCO9fCjMtxbREREKpACuoiI1LwyzKDHbHjbeniixPcVERGRCqaALiIiNc8q7R70 +uA1v2wiPl/CeIiIiUgXc5S5ARESk3Eo4gx434G29CuciIiJyAZpBFxGRmteb76Q+OMe3iRvwtg2w +bY7vIyIiIlVKAV1ERCRvLmfR4za8VeFcREREClFAFxERAey524ceN7TnXERERKZBAV1ERAQw5iag +J4B3aOZcREREpkMBXUREhDmZQU8Y8I5e+IHD44qIiMg8pYAuIiKCszPoNqRseM8GeNSpMUVERGT+ +U0AXEREBTOeaxKWBX9oI/+3QeCIiIlIjjHIXICIiUim25I9bq7+IIdLAL/XCtxwqSURERGqIZtBF +RERec/AiXps24d0K5yIiIjJbCugiIiKvme0+9LQJ714P33S0GhEREakpCugiIiKvmU1AT9vwHoVz +ERERuVgK6CIiIhNmcdRa2ob3bIT/mpOCREREpKYooIuIiEwwZxbQ0wbcrXAuIiIiTlFAFxERmTCD +o9YyNrx3A/znnBYkIiIiNUUBXUREZIILjpE/Kq2QjJ2fOf9GKWoSERGR2qGALiIiMuFuyBlwuMAl +OQM2KZyLiIjIXFBAFxERmaRAo7icAb0b4CslLUhERERqhgK6iIjIJMaFA3rOho0K5yIiIjKXFNBF +REQmucAMeo78svZHylGPiIiI1A53uQsQERGpMJM7uecMuGcDPFy2akRERKRmaAZdRERkEuu1GfQc +8IEN8FA56xEREZHaoYAuIiIyySgcBDLAB3phS7nrEREREREREalZD8Gby12DiIiIiIiIiIiIiIiI +iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI +iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI +iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI +iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI +iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI +iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI +iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiMhcMcpd +gFS+R2BxDlYasNKG1TZcYkALEJz40QjU25AzIAaMAlHy/z8A7DVgnwF73bD37vxjIiIiIiIiMokC +upzlPjCXw5UG3G7DHQbcDNQ7fJs+4AkbHrNg2z2wz+HxRURERERmzQZjMyw2YJUBK4E1wDKgidcm +qc78P+QnpkYm/hsDRgw4YMMeG/bZsHcjHDPALv2vRqqJArrwefC1wFtteD9wB9A8+Xm334W33oOv +3o2v3oOvwYPbZ2K6TQy3gctjYnpMsG1yWRsrbZHLWlhZm2wiRzqaIRXNkB7LkopmyGWsc0s4bsP/ +AFsOwJP3wXkXiIiIiIjMlfvBUw/XWfn3wrcD1wIBh28TA56zYZsBj8XguXsh4/A9pMopoNewh+Bq +GzbZ8H4D2s487qlzUdfqI9RRR6jTjzfgdvS+6fEs8cEUscEk46cSZOK5yU+fAL5mw5c2wnZHbywi +IiIiMuEBaHHB+4C3Azfx2mx4qcSAp4BveuErd8Nwie8vFUgBvcZsBVcG3mfD7wOXnnnc3+SlqTtI +w6IAnuDZgdxwGUm33zXoqXMNuoPuIV+9Z8gbco95vGba8JgZT50r6fK70li2kUtZ3kwi58+lc95c +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 +XBl3DsXqa4NwRdwhJEmSksYp7pLaKoDPxJ1BsQpD+GjcISRJkpLIgi6prQbh+zitbdmK4KZr4eG4 +c0jSPw43AAADDUlEQVSSJCWRBV1S29XgIxGU4s6htium4bfjDiFJkpRUFnRJbXcdbAvg43HnUNv9 +9tXwSNwhJEmSksqCLikWOfhfwBfjzqG2+ZtB+HTcISRJkpLMgi4pFhuhloNfAr4XdxadWhHcNnc/ +YkmSJB2DBV1SbDZCrQJvD+D6uLPo1Ajg72fgrRuhHHcWSZKkpPM+6JISYTP8WgSfCqAj7ixaEEXg +E0/CH38cwrjDSJIkLQYWdEmJcT28OIDPBPCGuLPopHw9A791JWyPO4gkSdJiYkGXlDhb4GURfBT4 +t0B33Hl0QiaC+v3tv3gN3B53GEmSpMXIgi4psbZCVwkuD+BNEVwSwHlAFlgTd7blLIKDwF5gbwqe +iODuEnzrFyEfdzZJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJknTq/X/JJrjxXJwO +bQAAAABJRU5ErkJggg== + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/d475a74656cd1f8ba68e095b0ec7c469.msg b/share/extensions/tests/data/cmd/inkscape/d475a74656cd1f8ba68e095b0ec7c469.msg new file mode 100644 index 0000000..2032307 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/d475a74656cd1f8ba68e095b0ec7c469.msg @@ -0,0 +1,1025 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=47:49:953:951 --export-filename=f4oo.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="f4oo.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: f4oo.png + +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 +EkVERPyloCgiIl6b6zrFggnvvwv+1dNqREREZNYUFEVExGtzCYoFFz6gkCgiIrIwKCiKiIin5rBF +RsGFD2yBf5mXgkRERGTWFBRFRMRT5uyCYsGAOxUSRUREFhYFRRER8dQstsiwXPjgJvi/81qQiIiI +zJqCooiIeCoARyltcVGO5ZZGEv+5EjWJiIjI7CgoioiIp+4E24BDZU6xDdiqkCgiIrJwKSiKiIjn +yjS0sQ3YvAm+UdGCREREZFYUFEVExHPG2YOi7cIWhUQREZGFT0FRREQ8d5YRRZvSdNOv+1GPiIiI +zE7Q7wJERKQmTex8ahvwkU3woG/ViIiIyKxoRFFERDznvDGiaAMf3QQP+FmPiIiIzI6CooiIeG4Q +DgAW8NHNsN3vekRERERERGQBeADe7ncNIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi +IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi +IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi +IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi +IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi +IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi +IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiJSmwy/C6gX +X4flNqwzYJ0LF7pwgQGtQGLsTxOQcsE2IAMMAmlK/94D7DFgrwF7grDnztIxERERERERzykozoN7 +wFwDlxtwqwu3GXAjkPL4NqeBR1142IGdH4G9Hl9fRERERGTOXDC2wXID1huwDrgIWA0088Zgyfi/ +Q2mAZGDsnxlgwID9Lrzqwl4X9myBowa4lf/b1B8FRY98CSKt8E4XPgzcBrRMfD4YDRBOhYikgkRS +ISINIYIREzNoYgQNAiETM2SC62IXXZyCg110cIouxVGbQtoin7YoDBfJpy1sy5lawjEX/h3Yvh8e +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 +N/B+4HJgFXBusonaXh/1PS8fC+DWjXBv4CqPDbUFPhTB1RFcHMB5Sedpc89Tv2j8t5N3faU5YVGU +mtBNcEsAVyWdQ4n6Ri9cmXQISZLUmhx6KjWhAL6YdAYlKgzhU0mHkCRJrcuiKDWhXrgfh5u0rQhu +uxaeSjqHJElqXRZFqUnV4JMRlJLOoYYrpuE3kw4hSZJam0VRalLXwfYAPpN0DjXcb26EbUmHkCRJ +rc2iKDWxLPwv4KtJ51DD/E0vfCHpEJIkqfVZFKUmtgFqWfh54PtJZ9HciuDuyf3MJEmS5pxFUWpy +G6BWgfcHcGPSWTQ3Avj7CXjvBignnUWSJLUH91GUWshm+JUIPh9AZ9JZNCuKwGefhz/+DIRJh5Ek +Se3Doii1mBvhwgC+GMA7k86i0/LNDviNq2BH0kEkSVL7sShKLWoLvDGqb8r+74EFSefRSRkJ6vtj +fvUauCfpMJIkqX1ZFKUWtxW6S3BFAO+K4OIAzgMywKqks7WzCA4D+4B9KXgugodK8J2fg1zS2SRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiS1hP8P8aK48RBoJeEAAAAASUVORK5CYII= + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/da3b4e4db182a123e61e1317b2e4578b.msg b/share/extensions/tests/data/cmd/inkscape/da3b4e4db182a123e61e1317b2e4578b.msg new file mode 100644 index 0000000..d898d17 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/da3b4e4db182a123e61e1317b2e4578b.msg @@ -0,0 +1,25 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=0:951:47:1000 --export-filename=guides_6.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="guides_6.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: guides_6.png + +iVBORw0KGgoAAAANSUhEUgAAAC8AAAAxCAYAAABK+/BHAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAB9JREFUaIHtwQEBAAAAgiD/r25I +QAEAAAAAAAAAABcGJC0AAXI8dYQAAAAASUVORK5CYII= + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/ee1fdaa387bcb73ee6f7a2359fa9bd62.msg b/share/extensions/tests/data/cmd/inkscape/ee1fdaa387bcb73ee6f7a2359fa9bd62.msg new file mode 100644 index 0000000..af25e58 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/ee1fdaa387bcb73ee6f7a2359fa9bd62.msg @@ -0,0 +1,28 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=953:49:1000:951 --export-filename=f5oo.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="f5oo.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: f5oo.png + +iVBORw0KGgoAAAANSUhEUgAAAC8AAAOGCAYAAAB83MkXAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAALxJREFUeJztwQENAAAAwqD3T20P +BxQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCDAZoM +AAG1xnPlAAAAAElFTkSuQmCC + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/f863b882b18ffd8659b3c20cdd549266.msg b/share/extensions/tests/data/cmd/inkscape/f863b882b18ffd8659b3c20cdd549266.msg new file mode 100644 index 0000000..4e28399 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/f863b882b18ffd8659b3c20cdd549266.msg @@ -0,0 +1,569 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area-page --export-background-opacity=0 --export-dpi=96 --export-filename=Slide2.png --export-id-only --export-id=layer2 --export-type=png Slide2.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="Slide2.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: Slide2.png + +iVBORw0KGgoAAAANSUhEUgAAA+gAAAPoCAYAAABNo9TkAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAIABJREFUeJzs3Xe4bGV99//3cABF +bNgriopdYzeKLcYYa3rsLU+MGpPYYouKDkWjiZonGlP8PYkR7GgSS1RUTCL22EGsoNgQUZAmcOCc ++f2xNoIdzp6918zer9d1rWv2OTr3+nBm77XnO/e97m8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAOeajB0AgPUxq0tWlzvfcdkfOy5fXbratdp95Wm7r/y5ao+Vx/P/76dXW1e+ +PmnlcevK35//f/9+dUL1vfMd3z3f43cndcp8/kuBpfeCLtHWrta2rtxwvbp0k/Zo1qUbrkWXXjn2 +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 +yUXqxPe3eVup7aiPVHfSJ31jmtUDGzbaGqOY+ujKuV8/qZNGOD9rZKWV2wOqP6huPUKEM6uHT4YP +ntgo9uv+zfqXhk0M19VNrlCPuVU95KYbtxf5ZvX9M+u1Rw7F+se+NVqM97Wl/9O+HT1aAlhiO40d +gB114l+nON8Rt224t5MNZjbsePyq1rc4396w0duvTeq2k/pHxfnGM6mTJvUPk7pNw3X34NZ3ZcRF +q9fNarqO52QtTXtss17TOhbnF9kyzJQf/gf1mT+ux95acb4Rbd02HNvHvRv8Tm3rM+3X45uqNeDC +MoO+lKb3aVhC6/XbMbPqd2r6H2MHYfVmw8/BS6s/WcfTntmwhP3Fk7KvwSY0q+tVT6oe1lBAr5eX +VE+Y2Ixpee3XM5t14Hqdbvdd6lG3rCffvq5yifU6K+vprG311i/UQZ+ud355aNW2QN6/Mpv+pbGD +wLJQ4C2d6Z7VJ6vLjJ1kyZ1UW25Z+35l7CDsuNmwCuifqkeu0ylPrP6uetmkvrNO52SBzeoK1Z82 +fEC0Xtflf6weq0hfOpOmvbDhg501d6mL1J/epp7wy3W5dV9Ez3r44Nfr4M8Mm8SddObYaX6uM6pn +dcP+tvvZlwV+kZ3HDsCFMqn+NcX5POxR215e/drYQdgxK8X5KxpmMNfaqdX/rV40qZPX4XwsiZUP +ap49qxdVT66eUF18jU/7mGqXWT1qko2YlsSkaf9YPWqtT7T7LvWUfYbC/FKWsG84X/3+UJQf/On6 +0oljp7nAdqv266h+0PABI/BzmEFfKvs9uGavGjvFxjJ5QD3n9WOn4MKbDUt9/2yNT3NO9Q/VAVpd +cUGszKg/u3p0a/8h+P+d1BPX+BzMw7T9q33X8hQ7Tep3b1AvvHvteam1PBPr7Yxz6m1frJd/vA47 +ZqmWzmyvPlQdVL2maaeNnAeWggJ9aUwvWX2u0p10vr5dF71+Pd2s6BKZ1TOq567xad7bcK/vEWt8 +HjaglXvU/6a65xqf6umTesEan4PVmPbI6v9by1Pcda960d3rZlday7OwnrbN6t1HD7Pl/z5u67Qd +cVSTDmrWq5v2jbHDwLKxxH15HJjifC1cqc58Tut0TyCrN6sH1ZpusHRc9bhJvXENz8EGN6kvVPea +1f2rv62uuEan+stZfW1Sr12j8VmNafdqWIWzJi5/sXrpver+N1qrM7DejvxOvfLT9Zoj6lunjp3m +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 +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkpREJXUBbWAD4H+ALYB7 +gPOA+5NWJEmSJElSm9kaeAro6TdWAD8Bdk5YlyRJkiRJbeVXvDScrzr+CByYrDpJkiRJktrEE6w9 +oPeN24DDgMFpypQkSZIkqbX9g7iA3jfuBz4CjExRrCRJkiRJrepEsgX0vvEUcAowsfElS6VVwYUt +JUmSJNVoPPAstYX0HmAxcDawaYPrlsqmAnwVOAdDuiRJkqQafZnaA3rf6AIuAXZrcO1SGfSF877n +gyFdkiRJLckPucXbELgPGJrT/q4HzgIuJYQVqZVVgK8AH1vl388FPozPAUlqF2OAzQhnFW4ATOod +k4GxvWMUMLp3W4CO3n/vs5DwvvEM4eDHYmA58BywrPe/c1cZjwOPERb+XVHQ3yZJarAfUP9R9FXH +X4F348rval2rHjlfdXwVv2SUpFazDrAv8FHg28CfCWvz5P05KuvoJgT2m4HvAZ8GXgdsUszdIEkq +0naEF/Yi3jDuJRxJHNGwv0Yq3kDh3NPdJak1TCRcavYbwO2Eo9Spw3jWsQj4C/AdwhlfuwCD8ryT +JEn5u4xi3xyeBKrAhAb9PVJRYsO5IV2SmtNWhCvd/IXmDOSxof1S4BhgdzzjUZJKZxaNeUNYTAg3 +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 +/4R1SZIkSZLUdmYAD7AymHcBJyatSJIkSZKanKfvqlYjCEfMJwDXElaclSRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ +kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJTWZQ +6gKkl5jDm5jFSDqZm7oUSZIkSWqkjtQFSC/RwyuBP1Bl29SlSJIkSVIjGdBVNlsDE4ErOY3pqYuR +JEmSpEYxoKtstun973p0cTmnMS1pNVKZzOFNnMouqcuQJElSMQzoKo8qg4HN+v3LhnTRyelskqok +qVR6eCXdXMGp7JS6FEmSJOXPgK7yGMQ0YOgq/7oxK/gjVSanKEkqmW2Adenm97aASJIktR4Dusqj +i63X8H+2BC7nDCY0shyphPqeI6EFpPqSM04kSZLU5AzoKpNt1vL/tuMFruBMxjWsGqlMVtcCAlfb +AiJJktQ6DOgqkzUdQe+zI8v4LVVGN6QaqUzW1gLyGaakKEmSJEn5MqCrTAYK6AB7AL/mS4wouhip +VNbWArKcP9gCIkmS1PwM6CqTrSK3249n+DnVlx1NlFqZLSCSJEktzoCucqiyLrB+hlscBPy4ty9X +agcxLSCXcRZjGlKNJEmScmdAV1ms7ejgmrwZ+DEXMyjvYqQSimkB2Z2lXMbnGVV4NZIkScqdAV3l +UIkKH6vzVu7ku1Sdy2p5sS0ge7GEn9kCIkmS1HwMNSqHnujwsTpHAF/JqRKpfLK3gLwO+IktIJIk +Sc3FgK6yqOUU9/4+SpUv5VKJVD61PD/ehC0gkiRJTcWArrKo9RT3/j7BHE7KYT9SudgCIkmS1Bb8 +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 +AABJRU5ErkJggg== + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/inkscape/fbabf7cf387f29c12ae11a51af0a9926.msg b/share/extensions/tests/data/cmd/inkscape/fbabf7cf387f29c12ae11a51af0a9926.msg new file mode 100644 index 0000000..7b971d9 --- /dev/null +++ b/share/extensions/tests/data/cmd/inkscape/fbabf7cf387f29c12ae11a51af0a9926.msg @@ -0,0 +1,28 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: inkscape +Arguments: --export-area=0:49:47:951 --export-filename=f3oo.png compare_file.svg + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: img + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="f3oo.png" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: f3oo.png + +iVBORw0KGgoAAAANSUhEUgAAAC8AAAOGCAYAAAB83MkXAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA +GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAALxJREFUeJztwQENAAAAwqD3T20P +BxQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCDAZoM +AAG1xnPlAAAAAElFTkSuQmCC + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/pdflatex/7b89a79118ea572d7e2fdafa2e82fc70.msg b/share/extensions/tests/data/cmd/pdflatex/7b89a79118ea572d7e2fdafa2e82fc70.msg new file mode 100644 index 0000000..cc385d1 --- /dev/null +++ b/share/extensions/tests/data/cmd/pdflatex/7b89a79118ea572d7e2fdafa2e82fc70.msg @@ -0,0 +1,1016 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: pdflatex +Arguments: -halt-on-error -output-directory=. input.tex + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 + +VGhpcyBpcyBwZGZUZVgsIFZlcnNpb24gMy4xNDE1OTI2NS0yLjYtMS40MC4xOCAoVGVYIExpdmUg +MjAxNy9EZWJpYW4pIChwcmVsb2FkZWQgZm9ybWF0PXBkZmxhdGV4KQogcmVzdHJpY3RlZCBcd3Jp +dGUxOCBlbmFibGVkLgplbnRlcmluZyBleHRlbmRlZCBtb2RlCigvdG1wL2lua3RtcHFvM3d3azll +L2lucHV0LnRleApMYVRlWDJlIDwyMDE3LTA0LTE1PgpCYWJlbCA8My4xOD4gYW5kIGh5cGhlbmF0 +aW9uIHBhdHRlcm5zIGZvciAzIGxhbmd1YWdlKHMpIGxvYWRlZC4KKC91c3Ivc2hhcmUvdGV4bGl2 +ZS90ZXhtZi1kaXN0L3RleC9sYXRleC9iYXNlL21pbmltYWwuY2xzCkRvY3VtZW50IENsYXNzOiBt +aW5pbWFsIDIwMDEvMDUvMjUgU3RhbmRhcmQgTGFUZVggbWluaW1hbCBjbGFzcwopICgvdXNyL3No +YXJlL3RleGxpdmUvdGV4bWYtZGlzdC90ZXgvbGF0ZXgvYW1zbWF0aC9hbXNtYXRoLnN0eQpGb3Ig +YWRkaXRpb25hbCBpbmZvcm1hdGlvbiBvbiBhbXNtYXRoLCB1c2UgdGhlIGA/JyBvcHRpb24uCigv +dXNyL3NoYXJlL3RleGxpdmUvdGV4bWYtZGlzdC90ZXgvbGF0ZXgvYW1zbWF0aC9hbXN0ZXh0LnN0 +eQooL3Vzci9zaGFyZS90ZXhsaXZlL3RleG1mLWRpc3QvdGV4L2xhdGV4L2Ftc21hdGgvYW1zZ2Vu +LnN0eSkpCigvdXNyL3NoYXJlL3RleGxpdmUvdGV4bWYtZGlzdC90ZXgvbGF0ZXgvYW1zbWF0aC9h +bXNic3kuc3R5KQooL3Vzci9zaGFyZS90ZXhsaXZlL3RleG1mLWRpc3QvdGV4L2xhdGV4L2Ftc21h +dGgvYW1zb3BuLnN0eSkpCigvdXNyL3NoYXJlL3RleGxpdmUvdGV4bWYtZGlzdC90ZXgvbGF0ZXgv +YW1zZm9udHMvYW1zc3ltYi5zdHkKKC91c3Ivc2hhcmUvdGV4bGl2ZS90ZXhtZi1kaXN0L3RleC9s +YXRleC9hbXNmb250cy9hbXNmb250cy5zdHkpKQpObyBmaWxlIGlucHV0LmF1eC4KKC91c3Ivc2hh +cmUvdGV4bGl2ZS90ZXhtZi1kaXN0L3RleC9sYXRleC9hbXNmb250cy91bXNhLmZkKQooL3Vzci9z +aGFyZS90ZXhsaXZlL3RleG1mLWRpc3QvdGV4L2xhdGV4L2Ftc2ZvbnRzL3Vtc2IuZmQpIFsxey92 +YXIvbGliL3RleG1mL2ZvCm50cy9tYXAvcGRmdGV4L3VwZG1hcC9wZGZ0ZXgubWFwfV0gKC90bXAv +aW5rdG1wcW8zd3drOWUvaW5wdXQuYXV4KSApPC91c3Ivc2hhcmUKL3RleGxpdmUvdGV4bWYtZGlz +dC9mb250cy90eXBlMS9wdWJsaWMvYW1zZm9udHMvY20vY21leDEwLnBmYj48L3Vzci9zaGFyZS90 +ZXhsaQp2ZS90ZXhtZi1kaXN0L2ZvbnRzL3R5cGUxL3B1YmxpYy9hbXNmb250cy9jbS9jbW1pMTAu +cGZiPjwvdXNyL3NoYXJlL3RleGxpdmUvdGV4Cm1mLWRpc3QvZm9udHMvdHlwZTEvcHVibGljL2Ft +c2ZvbnRzL2NtL2NtbWk3LnBmYj48L3Vzci9zaGFyZS90ZXhsaXZlL3RleG1mLWRpc3QKL2ZvbnRz +L3R5cGUxL3B1YmxpYy9hbXNmb250cy9jbS9jbXIxMC5wZmI+PC91c3Ivc2hhcmUvdGV4bGl2ZS90 +ZXhtZi1kaXN0L2ZvbnRzLwp0eXBlMS9wdWJsaWMvYW1zZm9udHMvY20vY21yNy5wZmI+PC91c3Iv +c2hhcmUvdGV4bGl2ZS90ZXhtZi1kaXN0L2ZvbnRzL3R5cGUxL3B1CmJsaWMvYW1zZm9udHMvY20v +Y21zeTcucGZiPgpPdXRwdXQgd3JpdHRlbiBvbiAvdG1wL2lua3RtcHFvM3d3azllL2lucHV0LnBk +ZiAoMSBwYWdlLCA0ODU2OCBieXRlcykuClRyYW5zY3JpcHQgd3JpdHRlbiBvbiAvdG1wL2lua3Rt +cHFvM3d3azllL2lucHV0LmxvZy4K + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="input.aux" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: input.aux + +XHJlbGF4IAo= + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="input.log" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: input.log + +VGhpcyBpcyBwZGZUZVgsIFZlcnNpb24gMy4xNDE1OTI2NS0yLjYtMS40MC4xOCAoVGVYIExpdmUg +MjAxNy9EZWJpYW4pIChwcmVsb2FkZWQgZm9ybWF0PXBkZmxhdGV4IDIwMTkuNC4yNikgIDMgQVBS +IDIwMjAgMjA6NDIKZW50ZXJpbmcgZXh0ZW5kZWQgbW9kZQogcmVzdHJpY3RlZCBcd3JpdGUxOCBl +bmFibGVkLgogJSYtbGluZSBwYXJzaW5nIGVuYWJsZWQuCioqL3RtcC9pbmt0bXBxbzN3d2s5ZS9p +bnB1dC50ZXgKKC90bXAvaW5rdG1wcW8zd3drOWUvaW5wdXQudGV4CkxhVGVYMmUgPDIwMTctMDQt +MTU+CkJhYmVsIDwzLjE4PiBhbmQgaHlwaGVuYXRpb24gcGF0dGVybnMgZm9yIDMgbGFuZ3VhZ2Uo +cykgbG9hZGVkLgooL3Vzci9zaGFyZS90ZXhsaXZlL3RleG1mLWRpc3QvdGV4L2xhdGV4L2Jhc2Uv +bWluaW1hbC5jbHMKRG9jdW1lbnQgQ2xhc3M6IG1pbmltYWwgMjAwMS8wNS8yNSBTdGFuZGFyZCBM +YVRlWCBtaW5pbWFsIGNsYXNzCikgKC91c3Ivc2hhcmUvdGV4bGl2ZS90ZXhtZi1kaXN0L3RleC9s +YXRleC9hbXNtYXRoL2Ftc21hdGguc3R5ClBhY2thZ2U6IGFtc21hdGggMjAxNy8wOS8wMiB2Mi4x +N2EgQU1TIG1hdGggZmVhdHVyZXMKXEBtYXRobWFyZ2luPVxza2lwNDEKCkZvciBhZGRpdGlvbmFs +IGluZm9ybWF0aW9uIG9uIGFtc21hdGgsIHVzZSB0aGUgYD8nIG9wdGlvbi4KKC91c3Ivc2hhcmUv +dGV4bGl2ZS90ZXhtZi1kaXN0L3RleC9sYXRleC9hbXNtYXRoL2Ftc3RleHQuc3R5ClBhY2thZ2U6 +IGFtc3RleHQgMjAwMC8wNi8yOSB2Mi4wMSBBTVMgdGV4dAoKKC91c3Ivc2hhcmUvdGV4bGl2ZS90 +ZXhtZi1kaXN0L3RleC9sYXRleC9hbXNtYXRoL2Ftc2dlbi5zdHkKRmlsZTogYW1zZ2VuLnN0eSAx +OTk5LzExLzMwIHYyLjAgZ2VuZXJpYyBmdW5jdGlvbnMKXEBlbXB0eXRva3M9XHRva3MxNApcZXhA +PVxkaW1lbjEwMgopKQooL3Vzci9zaGFyZS90ZXhsaXZlL3RleG1mLWRpc3QvdGV4L2xhdGV4L2Ft +c21hdGgvYW1zYnN5LnN0eQpQYWNrYWdlOiBhbXNic3kgMTk5OS8xMS8yOSB2MS4yZCBCb2xkIFN5 +bWJvbHMKXHBtYnJhaXNlQD1cZGltZW4xMDMKKQooL3Vzci9zaGFyZS90ZXhsaXZlL3RleG1mLWRp +c3QvdGV4L2xhdGV4L2Ftc21hdGgvYW1zb3BuLnN0eQpQYWNrYWdlOiBhbXNvcG4gMjAxNi8wMy8w +OCB2Mi4wMiBvcGVyYXRvciBuYW1lcwopClxpbmZAYmFkPVxjb3VudDc5CkxhVGVYIEluZm86IFJl +ZGVmaW5pbmcgXGZyYWMgb24gaW5wdXQgbGluZSAyMTMuClx1cHJvb3RAPVxjb3VudDgwClxsZWZ0 +cm9vdEA9XGNvdW50ODEKTGFUZVggSW5mbzogUmVkZWZpbmluZyBcb3ZlcmxpbmUgb24gaW5wdXQg +bGluZSAzNzUuClxjbGFzc251bUA9XGNvdW50ODIKXERPVFNDQVNFQD1cY291bnQ4MwpMYVRlWCBJ +bmZvOiBSZWRlZmluaW5nIFxsZG90cyBvbiBpbnB1dCBsaW5lIDQ3Mi4KTGFUZVggSW5mbzogUmVk +ZWZpbmluZyBcZG90cyBvbiBpbnB1dCBsaW5lIDQ3NS4KTGFUZVggSW5mbzogUmVkZWZpbmluZyBc +Y2RvdHMgb24gaW5wdXQgbGluZSA1OTYuClxNYXRoc3RydXRib3hAPVxib3gyNgpcc3RydXRib3hA +PVxib3gyNwpcYmlnQHNpemU9XGRpbWVuMTA0CkxhVGVYIEZvbnQgSW5mbzogICAgUmVkZWNsYXJp +bmcgZm9udCBlbmNvZGluZyBPTUwgb24gaW5wdXQgbGluZSA3MTIuCkxhVGVYIEZvbnQgSW5mbzog +ICAgUmVkZWNsYXJpbmcgZm9udCBlbmNvZGluZyBPTVMgb24gaW5wdXQgbGluZSA3MTMuClxtYWNj +QGRlcHRoPVxjb3VudDg0ClxjQE1heE1hdHJpeENvbHM9XGNvdW50ODUKXGRvdHNzcGFjZUA9XG11 +c2tpcDEwClxjQHBhcmVudGVxdWF0aW9uPVxjb3VudDg2Clxkc3BicmtAbHZsPVxjb3VudDg3Clx0 +YWdAaGVscD1cdG9rczE1Clxyb3dAPVxjb3VudDg4Clxjb2x1bW5APVxjb3VudDg5ClxtYXhmaWVs +ZHNAPVxjb3VudDkwClxhbmRoZWxwQD1cdG9rczE2ClxlcW5zaGlmdEA9XGRpbWVuMTA1ClxhbGln +bnNlcEA9XGRpbWVuMTA2Clx0YWdzaGlmdEA9XGRpbWVuMTA3Clx0YWd3aWR0aEA9XGRpbWVuMTA4 +Clx0b3R3aWR0aEA9XGRpbWVuMTA5ClxsaW5laHRAPVxkaW1lbjExMApcQGVudmJvZHk9XHRva3Mx +NwpcbXVsdGxpbmVnYXA9XHNraXA0MgpcbXVsdGxpbmV0YWdnYXA9XHNraXA0MwpcbWF0aGRpc3Bs +YXlAc3RhY2s9XHRva3MxOApMYVRlWCBJbmZvOiBSZWRlZmluaW5nIFxbIG9uIGlucHV0IGxpbmUg +MjgxNy4KTGFUZVggSW5mbzogUmVkZWZpbmluZyBcXSBvbiBpbnB1dCBsaW5lIDI4MTguCikKKC91 +c3Ivc2hhcmUvdGV4bGl2ZS90ZXhtZi1kaXN0L3RleC9sYXRleC9hbXNmb250cy9hbXNzeW1iLnN0 +eQpQYWNrYWdlOiBhbXNzeW1iIDIwMTMvMDEvMTQgdjMuMDEgQU1TIGZvbnQgc3ltYm9scwoKKC91 +c3Ivc2hhcmUvdGV4bGl2ZS90ZXhtZi1kaXN0L3RleC9sYXRleC9hbXNmb250cy9hbXNmb250cy5z +dHkKUGFja2FnZTogYW1zZm9udHMgMjAxMy8wMS8xNCB2My4wMSBCYXNpYyBBTVNGb250cyBzdXBw +b3J0ClxzeW1BTVNhPVxtYXRoZ3JvdXA0ClxzeW1BTVNiPVxtYXRoZ3JvdXA1CkxhVGVYIEZvbnQg +SW5mbzogICAgT3ZlcndyaXRpbmcgbWF0aCBhbHBoYWJldCBgXG1hdGhmcmFrJyBpbiB2ZXJzaW9u +IGBib2xkJwooRm9udCkgICAgICAgICAgICAgICAgICBVL2V1Zi9tL24gLS0+IFUvZXVmL2IvbiBv +biBpbnB1dCBsaW5lIDEwNi4KKSkKTm8gZmlsZSBpbnB1dC5hdXguClxvcGVub3V0MSA9IGBpbnB1 +dC5hdXgnLgoKTGFUZVggRm9udCBJbmZvOiAgICBDaGVja2luZyBkZWZhdWx0cyBmb3IgT01ML2Nt +bS9tL2l0IG9uIGlucHV0IGxpbmUgNy4KTGFUZVggRm9udCBJbmZvOiAgICAuLi4gb2theSBvbiBp +bnB1dCBsaW5lIDcuCkxhVGVYIEZvbnQgSW5mbzogICAgQ2hlY2tpbmcgZGVmYXVsdHMgZm9yIFQx +L2Ntci9tL24gb24gaW5wdXQgbGluZSA3LgpMYVRlWCBGb250IEluZm86ICAgIC4uLiBva2F5IG9u +IGlucHV0IGxpbmUgNy4KTGFUZVggRm9udCBJbmZvOiAgICBDaGVja2luZyBkZWZhdWx0cyBmb3Ig +T1QxL2Ntci9tL24gb24gaW5wdXQgbGluZSA3LgpMYVRlWCBGb250IEluZm86ICAgIC4uLiBva2F5 +IG9uIGlucHV0IGxpbmUgNy4KTGFUZVggRm9udCBJbmZvOiAgICBDaGVja2luZyBkZWZhdWx0cyBm +b3IgT01TL2Ntc3kvbS9uIG9uIGlucHV0IGxpbmUgNy4KTGFUZVggRm9udCBJbmZvOiAgICAuLi4g +b2theSBvbiBpbnB1dCBsaW5lIDcuCkxhVGVYIEZvbnQgSW5mbzogICAgQ2hlY2tpbmcgZGVmYXVs +dHMgZm9yIE9NWC9jbWV4L20vbiBvbiBpbnB1dCBsaW5lIDcuCkxhVGVYIEZvbnQgSW5mbzogICAg +Li4uIG9rYXkgb24gaW5wdXQgbGluZSA3LgpMYVRlWCBGb250IEluZm86ICAgIENoZWNraW5nIGRl +ZmF1bHRzIGZvciBVL2Ntci9tL24gb24gaW5wdXQgbGluZSA3LgpMYVRlWCBGb250IEluZm86ICAg +IC4uLiBva2F5IG9uIGlucHV0IGxpbmUgNy4KTGFUZVggRm9udCBJbmZvOiAgICBUcnkgbG9hZGlu +ZyBmb250IGluZm9ybWF0aW9uIGZvciBVK21zYSBvbiBpbnB1dCBsaW5lIDguCigvdXNyL3NoYXJl +L3RleGxpdmUvdGV4bWYtZGlzdC90ZXgvbGF0ZXgvYW1zZm9udHMvdW1zYS5mZApGaWxlOiB1bXNh +LmZkIDIwMTMvMDEvMTQgdjMuMDEgQU1TIHN5bWJvbHMgQQopCkxhVGVYIEZvbnQgSW5mbzogICAg +VHJ5IGxvYWRpbmcgZm9udCBpbmZvcm1hdGlvbiBmb3IgVSttc2Igb24gaW5wdXQgbGluZSA4LgoK +KC91c3Ivc2hhcmUvdGV4bGl2ZS90ZXhtZi1kaXN0L3RleC9sYXRleC9hbXNmb250cy91bXNiLmZk +CkZpbGU6IHVtc2IuZmQgMjAxMy8wMS8xNCB2My4wMSBBTVMgc3ltYm9scyBCCikgWzF7L3Zhci9s +aWIvdGV4bWYvZm9udHMvbWFwL3BkZnRleC91cGRtYXAvcGRmdGV4Lm1hcH1dICgvdG1wL2lua3Rt +cHFvM3d3azllL2kKbnB1dC5hdXgpICkgCkhlcmUgaXMgaG93IG11Y2ggb2YgVGVYJ3MgbWVtb3J5 +IHlvdSB1c2VkOgogOTIxIHN0cmluZ3Mgb3V0IG9mIDQ5NDkyMwogMTA2MTggc3RyaW5nIGNoYXJh +Y3RlcnMgb3V0IG9mIDYxODA3NDIKIDU3NDI4IHdvcmRzIG9mIG1lbW9yeSBvdXQgb2YgNTAwMDAw +MAogNDI5MiBtdWx0aWxldHRlciBjb250cm9sIHNlcXVlbmNlcyBvdXQgb2YgMTUwMDArNjAwMDAw +CiA1MzM5IHdvcmRzIG9mIGZvbnQgaW5mbyBmb3IgMjIgZm9udHMsIG91dCBvZiA4MDAwMDAwIGZv +ciA5MDAwCiAxNCBoeXBoZW5hdGlvbiBleGNlcHRpb25zIG91dCBvZiA4MTkxCiAyN2ksNG4sMjZw +LDI0MWIsOTZzIHN0YWNrIHBvc2l0aW9ucyBvdXQgb2YgNTAwMGksNTAwbiwxMDAwMHAsMjAwMDAw +Yiw4MDAwMHMKPC91c3Ivc2hhcmUvdGV4bGl2ZS90ZXhtZi1kaXN0L2ZvbnRzL3R5cGUxL3B1Ymxp +Yy9hbXNmb250cy9jbS9jbWV4MTAKLnBmYj48L3Vzci9zaGFyZS90ZXhsaXZlL3RleG1mLWRpc3Qv +Zm9udHMvdHlwZTEvcHVibGljL2Ftc2ZvbnRzL2NtL2NtbWkxMC5wZmI+PAovdXNyL3NoYXJlL3Rl +eGxpdmUvdGV4bWYtZGlzdC9mb250cy90eXBlMS9wdWJsaWMvYW1zZm9udHMvY20vY21taTcucGZi +PjwvdXNyL3NoCmFyZS90ZXhsaXZlL3RleG1mLWRpc3QvZm9udHMvdHlwZTEvcHVibGljL2Ftc2Zv +bnRzL2NtL2NtcjEwLnBmYj48L3Vzci9zaGFyZS90ZXgKbGl2ZS90ZXhtZi1kaXN0L2ZvbnRzL3R5 +cGUxL3B1YmxpYy9hbXNmb250cy9jbS9jbXI3LnBmYj48L3Vzci9zaGFyZS90ZXhsaXZlL3RleApt +Zi1kaXN0L2ZvbnRzL3R5cGUxL3B1YmxpYy9hbXNmb250cy9jbS9jbXN5Ny5wZmI+Ck91dHB1dCB3 +cml0dGVuIG9uIC90bXAvaW5rdG1wcW8zd3drOWUvaW5wdXQucGRmICgxIHBhZ2UsIDQ4NTY4IGJ5 +dGVzKS4KUERGIHN0YXRpc3RpY3M6CiAzMiBQREYgb2JqZWN0cyBvdXQgb2YgMTAwMCAobWF4LiA4 +Mzg4NjA3KQogMjIgY29tcHJlc3NlZCBvYmplY3RzIHdpdGhpbiAxIG9iamVjdCBzdHJlYW0KIDAg +bmFtZWQgZGVzdGluYXRpb25zIG91dCBvZiAxMDAwIChtYXguIDUwMDAwMCkKIDEgd29yZHMgb2Yg +ZXh0cmEgbWVtb3J5IGZvciBQREYgb3V0cHV0IG91dCBvZiAxMDAwMCAobWF4LiAxMDAwMDAwMCkK +Cg== + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="input.pdf" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: input.pdf + +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 +46pcyKoBMPgDsLsg12U0Dh0JCo0IPhOvQveLR8gzRS0XuVZpQpgHElJqp3u/eFnmOv5H3NrqgJ6Y +J0DCC20UQ07NFd8IXtze88nay5sGi47JQ5T80FrhZ8zl9OvfZuLSK+HecbCVK1t4iESWVZ4ZHVkR +13HLNAWiDlUvao6FdugrBiYUDRh0jG3OWFWcgxqE61afLDmS+jNxleyS1wU+4uyU8HnTFy/EC3uX +tW5kuldWQzOMsTWNk1uQ3zMhaC+YrdD+4MLGkzLEnjQukJNMqf80UiWRpdvgryKAaWpRNdCN4rD/ +hhZrxjL3Bdky8r0ZmWJ3in2yLK7pbvmv7GMdwc18pccI3pfU/OMv1f8HyCM0+gplbmRzdHJlYW0K +ZW5kb2JqCjI2IDAgb2JqCjw8Ci9MZW5ndGgxIDEzOTEKL0xlbmd0aDIgNjE4NgovTGVuZ3RoMyAw +Ci9MZW5ndGggNzEzNiAgICAgIAovRmlsdGVyIC9GbGF0ZURlY29kZQo+PgpzdHJlYW0KeNqNdgVU +E2zfNyUxOqSRSSi9jQaluzulxhgwYBuMkQMklBIRJBUkpRFRBBGkmYBIKCEhLaGkIkjzTr2f+37v +5/vO+b6zc7br33H9fteZ4GVTC3E1N7QrXBuNwopDJMCKQA0jczkgGCwlAQZLAgQFLRFYH/gfLUDQ +Go7xR6BRiv/LroGBQ7EEnSYUS3AzQqOA+gE+QIgUECKrCJFTBIOBkmCwwn8c0RhFoCY0EOEGNJIA +6qNRcH+AoAbaNwSD8PDEEqr85wgUggkDIQoKcmK/w4FqSDgGAYOigEZQrCccSagIg/oALdAwBBwb +8q8UQtc9sVhfRRAoKChIAor0l0BjPJSFxYBBCKwn0BzuD8cEwt2Av8YFGkOR8N+DSQAEgZaeCP8/ +agu0OzYIioEDCQofBAyO8icEBKDc4BggoTbQQs8QaOILR/1xNvzjIAb8azVAiATk73R/Rf9KhED9 +DobCYGikLxQVgkB5AN0RPnCgibahBDYYKwaEotx+OUJ9/NGEeGggFOEDdSU4/G4cCtRWMwNCCfP9 +NZ0/DIPwxfpL+CN8fk0I+pWGsGQtlJsGGomEo7D+gF/9aSIwcBhh6yGg39fqjUIHoXB/zu4IlJv7 +rxHcAnxBViiEXwBcT/MvD4IK8I/OA44FyoDBYDkFMBDuB4QHwzxBv5JbhvjCfxshv9SE/sNxvmhf +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 +S6H3gwZ5XbdEm2GiM8Pl1HWluWNjZ8EGLIT6C6N3XkR2OVfskgbpbdcyBNQVN43cuIDY1Huo/Spu +0S68bosMxNEhxrx1LIO1OH7P79OZWg87l5LYmqdoDnvc1Q1++sgFsSyHUgT5MNe58rzhIie/2ZN9 +XRbMDz2P6TIilbh3ut79UJnPOz9XxJVwMnuwVf95Og0c6xtP187J3e9+2aOTw+zArw/g77Up2OaZ +MYy+XYjFDroJi5+gjU7/H/Gl3pMKZW5kc3RyZWFtCmVuZG9iagozMSAwIG9iago8PAovUHJvZHVj +ZXIgKHBkZlRlWC0xLjQwLjE4KQovQ3JlYXRvciAoVGVYKQovQ3JlYXRpb25EYXRlIChEOjIwMjAw +NDAzMjA0MjIzLTA0JzAwJykKL01vZERhdGUgKEQ6MjAyMDA0MDMyMDQyMjMtMDQnMDAnKQovVHJh +cHBlZCAvRmFsc2UKL1BURVguRnVsbGJhbm5lciAoVGhpcyBpcyBwZGZUZVgsIFZlcnNpb24gMy4x +NDE1OTI2NS0yLjYtMS40MC4xOCAoVGVYIExpdmUgMjAxNy9EZWJpYW4pIGtwYXRoc2VhIHZlcnNp +b24gNi4yLjMpCj4+CmVuZG9iagoxMCAwIG9iago8PAovVHlwZSAvT2JqU3RtCi9OIDIyCi9GaXJz +dCAxNTcKL0xlbmd0aCAxMTIzICAgICAgCi9GaWx0ZXIgL0ZsYXRlRGVjb2RlCj4+CnN0cmVhbQp4 +2q1XbU/cRhD+7l8xH1tV2Pu+awlFggMCSYgoUEqE+OCCC1aPO3o2Cvn3fWZtcravB7o2EresZ2fm +mWdmdmQrEiQpx6JI5pakJiUMSUNaBpKWtIHQkfM49+QtNHMoQ0dJqMk8URoKypGCYtCClGcXOSn8 +KQfnpExQBAMnHOHQa0lQD9aRhVYQFIAmVCIlaQU3cKKV88n2dpKdf3ssKTsp7sokm8xnTTlratKI ++jTJTst6/rS4KWtQiILj8rYqdufPdCUgcODkc3WdwHwBO5Kt2rt30fMBvNH2NmUHkBs+wdYjpHYX +EGO7kwJRd1uNWLstiPEO3rKTxfzmrGzoCpHuHVB2Xj43dM1AV9IYk9prbKQOaU422FR3ayca/gu5 +WpF5EaDerq8YaZWnphMB0QkPfe0kWwmpU9RPuBQJubJC0Fs/5X0aBquPq/EqVd3aSrgruhBVlDqN +aFwQKRrHKtYIJm27h8ORZCVrtNYOFc+l6847WbTtvGvHFtaypPXPetyJQn2Ht1xvdt2GuiQwphLp +RWdcGTPYa8H7pUZr0UnV0kfQOtWcRpenljZdtdKM1VuD9+zNAxeXzHoJmdEeVK0R3C65bROFJDnL +pw6JsGSci6csCSrExHqkmFPurIG9FEKMl5VsbPBbbQeWLndWCy5srEhAhF5y4QNyByn6UpEzGnH6 +wJEHzb1pdM75RbyWgskhacufewHNIDT7aTkZvtGxNWzUQxshR4Fr5wJr2IDKUB6ryRlBzts2xKkj +Hfiy9NdlVkxsQQsbdAMuElpcR49KxEzHLgkcqfFcI1wkDEeO2aDvWj133ZtZPF/2yvpmUT0280U7 +bz4XDzj58uuHs70Pv0yO9y+lwMG0uKvJtBq7cXxtKUNbKneIzViD/PIU26lveIoZmEyKx8Oyurtv +CA8MwgdbTuDpqCmm1c3O7G5a8uFZUz5cIOQku+wsjJZwcF8seGL9lNVPDw9FU81nt1X9OC2+/dzG +cVDBXob+wHyL1u7h+5OPE9A6PlpHSyvQijfXxHu7ZOVy06flgu4Rk3zYJ7YlzQs1r9ZS+yt7rPp0 +lNiEzsX+0fuL80jH/zsb2ZGRXv4gMkG+QmY24KI24fLpcLLzeQdcTtdVxoiXyoj8/5D53nIuX0ul +/PupmGZVNs0esvmszOrqeUDNbELteOfy8OMlU1tTJbyPdGVS6ocw828xY07N1/mAk9uE0/nFb5Pz +fXA6+7Ku9Sxt5RZvYMryMO2NBzD8b71n1tMqFov51wWfZNXsz2pWNYMxodaMCXh++qOJjyyEy92i +LuN718oMHOSC3zHjC91BtagbjoNCSLJPRe/h9+q2ua/5vXVj7PGgGmIrOcZWtocthV+C+83BR2Nl +hK3H2BFuCc7xvoCbzcFHc2AEbsfg3BI94vkS226OPbyoI2j/KrSTS2S3OfLoOo2gV1pN6x606ZHW +q9D8YVLzl8kTf18k2cfqtqar2JPXQ81JgWs3v0tak963yD81t2jVCmVuZHN0cmVhbQplbmRvYmoK +MzIgMCBvYmoKPDwKL1R5cGUgL1hSZWYKL0luZGV4IFswIDMzXQovU2l6ZSAzMwovVyBbMSAyIDFd +Ci9Sb290IDMwIDAgUgovSW5mbyAzMSAwIFIKL0lEIFs8REJEMkQ1MEI4MjY2MEQ0OTNGREVDNDhC +NDE2MjFDRjk+IDxEQkQyRDUwQjgyNjYwRDQ5M0ZERUM0OEI0MTYyMUNGOT5dCi9MZW5ndGggOTYg +ICAgICAgIAovRmlsdGVyIC9GbGF0ZURlY29kZQo+PgpzdHJlYW0KeNoVybsZRAAUROEZb3a9CVWg +gm1EqBICuQoUQmILkOuHucH/neAAeBwkFBA51FwqKaWQWjIeq71GHHHFE18CCcnJfsTOs8b8XdaE +w9/64Xxbv9x6ayot9wU8R7w0kQtlCmVuZHN0cmVhbQplbmRvYmoKc3RhcnR4cmVmCjQ4MjIyCiUl +RU9GCg== + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Filename: inktmpqo3wwk9e + +TWlzc2luZyBGaWxl + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/ps2pdf/43caf043a269fc0e77f8633cd2ad4a88.msg b/share/extensions/tests/data/cmd/ps2pdf/43caf043a269fc0e77f8633cd2ad4a88.msg new file mode 100644 index 0000000..4cee228 --- /dev/null +++ b/share/extensions/tests/data/cmd/ps2pdf/43caf043a269fc0e77f8633cd2ad4a88.msg @@ -0,0 +1,59 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: ps2pdf +Arguments: test.ps output.pdf + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="output.pdf" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: output.pdf + +JVBERi0xLjQKJcfsj6IKNSAwIG9iago8PC9MZW5ndGggNiAwIFIvRmlsdGVyIC9GbGF0ZURlY29k +ZT4+CnN0cmVhbQp4nF2PTQ5CIQyE9z1FT1CHf3oMvQKJzwVvod4/ERCIMQToV8LM9MkQw+hr3uWk +yy3x8SaIAk5bW8LYr4NMFG9gWTP4JDVJnB9QSW178zpInZMUWJOVqLoxOgnKhSbbcdaFxkv0uclN +oabttPPXZWHZIVanUquAlHcHWwzTpRCWf+3lTLY+9tC/sxV6/M16pyt9AC7dOuplbmRzdHJlYW0K +ZW5kb2JqCjYgMCBvYmoKMTUwCmVuZG9iago0IDAgb2JqCjw8L1R5cGUvUGFnZS9NZWRpYUJveCBb +MCAwIDk0IDk4XQovUGFyZW50IDMgMCBSCi9SZXNvdXJjZXM8PC9Qcm9jU2V0Wy9QREZdCi9FeHRH +U3RhdGUgOCAwIFIKPj4KL0NvbnRlbnRzIDUgMCBSCj4+CmVuZG9iagozIDAgb2JqCjw8IC9UeXBl +IC9QYWdlcyAvS2lkcyBbCjQgMCBSCl0gL0NvdW50IDEKPj4KZW5kb2JqCjEgMCBvYmoKPDwvVHlw +ZSAvQ2F0YWxvZyAvUGFnZXMgMyAwIFIKL01ldGFkYXRhIDkgMCBSCj4+CmVuZG9iago3IDAgb2Jq +Cjw8L1R5cGUvRXh0R1N0YXRlCi9PUE0gMT4+ZW5kb2JqCjggMCBvYmoKPDwvUjcKNyAwIFI+Pgpl +bmRvYmoKOSAwIG9iago8PC9UeXBlL01ldGFkYXRhCi9TdWJ0eXBlL1hNTC9MZW5ndGggMTM2Nj4+ +c3RyZWFtCjw/eHBhY2tldCBiZWdpbj0n77u/JyBpZD0nVzVNME1wQ2VoaUh6cmVTek5UY3prYzlk +Jz8+Cjw/YWRvYmUteGFwLWZpbHRlcnMgZXNjPSJDUkxGIj8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0n +YWRvYmU6bnM6bWV0YS8nIHg6eG1wdGs9J1hNUCB0b29sa2l0IDIuOS4xLTEzLCBmcmFtZXdvcmsg +MS42Jz4KPHJkZjpSREYgeG1sbnM6cmRmPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJk +Zi1zeW50YXgtbnMjJyB4bWxuczppWD0naHR0cDovL25zLmFkb2JlLmNvbS9pWC8xLjAvJz4KPHJk +ZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9J3V1aWQ6YjUwM2RiMTktYWUyOS0xMWY1LTAwMDAtMmY0 +MTI5Y2Y4ZDIwJyB4bWxuczpwZGY9J2h0dHA6Ly9ucy5hZG9iZS5jb20vcGRmLzEuMy8nIHBkZjpQ +cm9kdWNlcj0nR1BMIEdob3N0c2NyaXB0IDkuMjYnLz4KPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJv +dXQ9J3V1aWQ6YjUwM2RiMTktYWUyOS0xMWY1LTAwMDAtMmY0MTI5Y2Y4ZDIwJyB4bWxuczp4bXA9 +J2h0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8nPjx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA0LTAz +VDIwOjQyOjI3LTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KPHhtcDpDcmVhdGVEYXRlPjIwMjAtMDQt +MDNUMjA6NDI6MjctMDQ6MDA8L3htcDpDcmVhdGVEYXRlPgo8eG1wOkNyZWF0b3JUb29sPmNhaXJv +IDEuMTUuMTAgKGh0dHA6Ly9jYWlyb2dyYXBoaWNzLm9yZyk8L3htcDpDcmVhdG9yVG9vbD48L3Jk +ZjpEZXNjcmlwdGlvbj4KPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9J3V1aWQ6YjUwM2RiMTkt +YWUyOS0xMWY1LTAwMDAtMmY0MTI5Y2Y4ZDIwJyB4bWxuczp4YXBNTT0naHR0cDovL25zLmFkb2Jl +LmNvbS94YXAvMS4wL21tLycgeGFwTU06RG9jdW1lbnRJRD0ndXVpZDpiNTAzZGIxOS1hZTI5LTEx +ZjUtMDAwMC0yZjQxMjljZjhkMjAnLz4KPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9J3V1aWQ6 +YjUwM2RiMTktYWUyOS0xMWY1LTAwMDAtMmY0MTI5Y2Y4ZDIwJyB4bWxuczpkYz0naHR0cDovL3B1 +cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8nIGRjOmZvcm1hdD0nYXBwbGljYXRpb24vcGRmJz48ZGM6 +dGl0bGU+PHJkZjpBbHQ+PHJkZjpsaSB4bWw6bGFuZz0neC1kZWZhdWx0Jz5VbnRpdGxlZDwvcmRm +OmxpPjwvcmRmOkFsdD48L2RjOnRpdGxlPjwvcmRmOkRlc2NyaXB0aW9uPgo8L3JkZjpSREY+Cjwv +eDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9 +J3cnPz4KZW5kc3RyZWFtCmVuZG9iagoyIDAgb2JqCjw8L1Byb2R1Y2VyKEdQTCBHaG9zdHNjcmlw +dCA5LjI2KQovQ3JlYXRpb25EYXRlKEQ6MjAyMDA0MDMyMDQyMjctMDQnMDAnKQovTW9kRGF0ZShE +OjIwMjAwNDAzMjA0MjI3LTA0JzAwJykKL0NyZWF0b3IoY2Fpcm8gMS4xNS4xMCBcKGh0dHA6Ly9j +YWlyb2dyYXBoaWNzLm9yZ1wpKT4+ZW5kb2JqCnhyZWYKMCAxMAowMDAwMDAwMDAwIDY1NTM1IGYg +CjAwMDAwMDA0NDIgMDAwMDAgbiAKMDAwMDAwMjAxOCAwMDAwMCBuIAowMDAwMDAwMzgzIDAwMDAw +IG4gCjAwMDAwMDAyNTQgMDAwMDAgbiAKMDAwMDAwMDAxNSAwMDAwMCBuIAowMDAwMDAwMjM1IDAw +MDAwIG4gCjAwMDAwMDA1MDYgMDAwMDAgbiAKMDAwMDAwMDU0NyAwMDAwMCBuIAowMDAwMDAwNTc2 +IDAwMDAwIG4gCnRyYWlsZXIKPDwgL1NpemUgMTAgL1Jvb3QgMSAwIFIgL0luZm8gMiAwIFIKL0lE +IFs8NzQ4MjJGMzlFRDU0RTBBNDQ2RTFBNTIzRDQxRjIwMUE+PDc0ODIyRjM5RUQ1NEUwQTQ0NkUx +QTUyM0Q0MUYyMDFBPl0KPj4Kc3RhcnR4cmVmCjIxOTQKJSVFT0YK + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/ps2pdf/9f4cd3145afec1e2a294713237712f4c.msg b/share/extensions/tests/data/cmd/ps2pdf/9f4cd3145afec1e2a294713237712f4c.msg new file mode 100644 index 0000000..1ecb16e --- /dev/null +++ b/share/extensions/tests/data/cmd/ps2pdf/9f4cd3145afec1e2a294713237712f4c.msg @@ -0,0 +1,60 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: ps2pdf +Arguments: test.eps output.pdf + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="output.pdf" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: output.pdf + +JVBERi0xLjQKJcfsj6IKNSAwIG9iago8PC9MZW5ndGggNiAwIFIvRmlsdGVyIC9GbGF0ZURlY29k +ZT4+CnN0cmVhbQp4nF2PSw4CMQiG95yCEyD0ORxDNx6g0XHRMRm9fyId28YY0paPwg/syCTIzfpb +NjhdMq5v2KGxBjsL4+uGV3wCU4rOh2D5C7vsmxNFsii+VpBEQdgdBRuoZLKEBhXU2V/Qg9R7yhE1 +O0qqE5OnqFigszvuOlACpbCYXBcyba+Nv10GljnEiFQwjzkvM8JTjHuXYrv2/rW5fbJR2Ib+3a3A +42/XO5zNPsecQcxlbmRzdHJlYW0KZW5kb2JqCjYgMCBvYmoKMTcxCmVuZG9iago0IDAgb2JqCjw8 +L1R5cGUvUGFnZS9NZWRpYUJveCBbMCAwIDYxMiA3OTJdCi9QYXJlbnQgMyAwIFIKL1Jlc291cmNl +czw8L1Byb2NTZXRbL1BERl0KL0V4dEdTdGF0ZSA4IDAgUgo+PgovQ29udGVudHMgNSAwIFIKPj4K +ZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFsKNCAwIFIKXSAvQ291bnQgMQo+ +PgplbmRvYmoKMSAwIG9iago8PC9UeXBlIC9DYXRhbG9nIC9QYWdlcyAzIDAgUgovTWV0YWRhdGEg +OSAwIFIKPj4KZW5kb2JqCjcgMCBvYmoKPDwvVHlwZS9FeHRHU3RhdGUKL09QTSAxPj5lbmRvYmoK +OCAwIG9iago8PC9SNwo3IDAgUj4+CmVuZG9iago5IDAgb2JqCjw8L1R5cGUvTWV0YWRhdGEKL1N1 +YnR5cGUvWE1ML0xlbmd0aCAxMzY2Pj5zdHJlYW0KPD94cGFja2V0IGJlZ2luPSfvu78nIGlkPSdX +NU0wTXBDZWhpSHpyZVN6TlRjemtjOWQnPz4KPD9hZG9iZS14YXAtZmlsdGVycyBlc2M9IkNSTEYi +Pz4KPHg6eG1wbWV0YSB4bWxuczp4PSdhZG9iZTpuczptZXRhLycgeDp4bXB0az0nWE1QIHRvb2xr +aXQgMi45LjEtMTMsIGZyYW1ld29yayAxLjYnPgo8cmRmOlJERiB4bWxuczpyZGY9J2h0dHA6Ly93 +d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMnIHhtbG5zOmlYPSdodHRwOi8vbnMu +YWRvYmUuY29tL2lYLzEuMC8nPgo8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0ndXVpZDpiNTAz +ZGIxOS1hZTI5LTExZjUtMDAwMC02OWZmOWZmNDIzN2QnIHhtbG5zOnBkZj0naHR0cDovL25zLmFk +b2JlLmNvbS9wZGYvMS4zLycgcGRmOlByb2R1Y2VyPSdHUEwgR2hvc3RzY3JpcHQgOS4yNicvPgo8 +cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0ndXVpZDpiNTAzZGIxOS1hZTI5LTExZjUtMDAwMC02 +OWZmOWZmNDIzN2QnIHhtbG5zOnhtcD0naHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyc+PHht +cDpNb2RpZnlEYXRlPjIwMjAtMDQtMDNUMjA6NDI6MjctMDQ6MDA8L3htcDpNb2RpZnlEYXRlPgo8 +eG1wOkNyZWF0ZURhdGU+MjAyMC0wNC0wM1QyMDo0MjoyNy0wNDowMDwveG1wOkNyZWF0ZURhdGU+ +Cjx4bXA6Q3JlYXRvclRvb2w+Y2Fpcm8gMS4xNS4xMCAoaHR0cDovL2NhaXJvZ3JhcGhpY3Mub3Jn +KTwveG1wOkNyZWF0b3JUb29sPjwvcmRmOkRlc2NyaXB0aW9uPgo8cmRmOkRlc2NyaXB0aW9uIHJk +ZjphYm91dD0ndXVpZDpiNTAzZGIxOS1hZTI5LTExZjUtMDAwMC02OWZmOWZmNDIzN2QnIHhtbG5z +OnhhcE1NPSdodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vJyB4YXBNTTpEb2N1bWVudElE +PSd1dWlkOmI1MDNkYjE5LWFlMjktMTFmNS0wMDAwLTY5ZmY5ZmY0MjM3ZCcvPgo8cmRmOkRlc2Ny +aXB0aW9uIHJkZjphYm91dD0ndXVpZDpiNTAzZGIxOS1hZTI5LTExZjUtMDAwMC02OWZmOWZmNDIz +N2QnIHhtbG5zOmRjPSdodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLycgZGM6Zm9ybWF0 +PSdhcHBsaWNhdGlvbi9wZGYnPjxkYzp0aXRsZT48cmRmOkFsdD48cmRmOmxpIHhtbDpsYW5nPSd4 +LWRlZmF1bHQnPlVudGl0bGVkPC9yZGY6bGk+PC9yZGY6QWx0PjwvZGM6dGl0bGU+PC9yZGY6RGVz +Y3JpcHRpb24+CjwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAKPD94cGFja2V0IGVuZD0ndyc/PgplbmRzdHJlYW0KZW5kb2JqCjIgMCBvYmoKPDwv +UHJvZHVjZXIoR1BMIEdob3N0c2NyaXB0IDkuMjYpCi9DcmVhdGlvbkRhdGUoRDoyMDIwMDQwMzIw +NDIyNy0wNCcwMCcpCi9Nb2REYXRlKEQ6MjAyMDA0MDMyMDQyMjctMDQnMDAnKQovQ3JlYXRvcihj +YWlybyAxLjE1LjEwIFwoaHR0cDovL2NhaXJvZ3JhcGhpY3Mub3JnXCkpPj5lbmRvYmoKeHJlZgow +IDEwCjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDQ2NSAwMDAwMCBuIAowMDAwMDAyMDQxIDAw +MDAwIG4gCjAwMDAwMDA0MDYgMDAwMDAgbiAKMDAwMDAwMDI3NSAwMDAwMCBuIAowMDAwMDAwMDE1 +IDAwMDAwIG4gCjAwMDAwMDAyNTYgMDAwMDAgbiAKMDAwMDAwMDUyOSAwMDAwMCBuIAowMDAwMDAw +NTcwIDAwMDAwIG4gCjAwMDAwMDA1OTkgMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSAxMCAvUm9v +dCAxIDAgUiAvSW5mbyAyIDAgUgovSUQgWzxBOUU4MEZFQ0M2RUE3QjZFRkNCRjdDMkVDMkNDRTNB +OT48QTlFODBGRUNDNkVBN0I2RUZDQkY3QzJFQzJDQ0UzQTk+XQo+PgpzdGFydHhyZWYKMjIxNwol +JUVPRgo= + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/scribus/a5ed3ec8aa6d61652cb04fa27f921943.msg b/share/extensions/tests/data/cmd/scribus/a5ed3ec8aa6d61652cb04fa27f921943.msg new file mode 100644 index 0000000..7e13c2e --- /dev/null +++ b/share/extensions/tests/data/cmd/scribus/a5ed3ec8aa6d61652cb04fa27f921943.msg @@ -0,0 +1,13 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: scribus +Arguments: --version -g + +----CALLDATA--//--CALLDATA-- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 + +U2NyaWJ1cyBWZXJzaW9uIDEuNS41Cg== + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/cmd/scribus/b8a765f070200c63d022a696ac129e35.msg b/share/extensions/tests/data/cmd/scribus/b8a765f070200c63d022a696ac129e35.msg new file mode 100644 index 0000000..2c60ed4 --- /dev/null +++ b/share/extensions/tests/data/cmd/scribus/b8a765f070200c63d022a696ac129e35.msg @@ -0,0 +1,261 @@ +Content-Type: multipart/mixed; boundary="--CALLDATA--//--CALLDATA--" +MIME-Version: 1.0 +Program: scribus +Arguments: -g -py in.svg out.pdf scribus.py + +----CALLDATA--//--CALLDATA-- +Content-Type: application/octet-stream; Name="out.pdf" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment +Filename: out.pdf + +JVBERi0xLjMKJcfsj6IKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFnZXMgMyAwIFIKL1Bh +Z2VMYXlvdXQgL1NpbmdsZVBhZ2UKL1ZpZXdlclByZWZlcmVuY2VzCjw8Ci9QYWdlRGlyZWN0aW9u +IC9MMlIKID4+Cj4+CmVuZG9iagoyIDAgb2JqCjw8Ci9DcmVhdG9yIChTY3JpYnVzIDEuNS41KQov +UHJvZHVjZXIgKFNjcmlidXMgUERGIExpYnJhcnkgMS41LjUpCi9UaXRsZSA8PgovQXV0aG9yIDw+ +Ci9TdWJqZWN0IDw+Ci9LZXl3b3JkcyA8PgovQ3JlYXRpb25EYXRlIChEOjIwMjAwODE0MTIyOTU3 +WikKL01vZERhdGUgKEQ6MjAyMDA4MTQxMjI5NTdaKQovVHJhcHBlZCAvRmFsc2UKPj4KZW5kb2Jq +CjQgMCBvYmoKPDwKL1dpZHRoIDEwMAovSGVpZ2h0IDEwMAovQ29sb3JTcGFjZSAvRGV2aWNlUkdC +Ci9CaXRzUGVyQ29tcG9uZW50IDgKL0xlbmd0aCAyNjM5Ci9GaWx0ZXIgL0ZsYXRlRGVjb2RlCj4+ +CnN0cmVhbQp42u1cCVQT1xpGfVoFK+L6XFpa2dSqYIIJ+tDiWl+hT21PVaCivudeVLSWJZFEXAFl +daOICyoKtg8QEBCVfZFFwYW1koogm4QAgRCWhP44HmwDDJNBoiT3O/fMmZn8/8y5X+7/3++fuUlb +GwICAgICAgICguzQ0NAAW4FAANumpibssPk1sE9ra2pqeLzy8nLYSvjW19eDmVAohC12BeIQiUT9 +jiu/q1dioqPv3rkD27jY2OvX/G6FhUXfu5eYmACfBgcHwXloEeHh/tevSfj+9tuvYaGhVy775ufl +xcfHYZYE7xsWFgq3C791C658OzLyTlQUl1slx2MSRlTHljT4fL4ixG9oSAiMKxgSJHyxGOyPkUgO +HA4Hog+2kOik9YXog8CPjIhQEK5yc3Lu30/Jyc6G/I9mSQQEBAQEhQLXfGM5ZR7pBu4911/NjT5p +QUZem6ke5jgNDMAMjPu6y/+csBan4ThCf0uUx5AjCnPEuzifuzvkuCrLUMmaouM4k3JiKk7TOjYT +zEayDa1CTlTwuR8mV/j97Y0vEDXYjrreVz35kXJzuVKPLSlLxcJXHVyALoXiCqIJBgn0nQhLf20/ +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 +jKBUchEsnNFxokUV3JenQbUr+PBotsGeG1NKOP+Q6G9j6YDpzrTwvMQueb78IEzLaemCk5S7GcM7 +81xVNGg0m1bwqqhNvlBSU2EZ5DCaTT9/b5xElwPi8eIIGPPNCNVwXGp0ipL6WPKvvQ6GTMaP3/6L +61mRK330sG5mPBn2/yTVgvwhwjKl2a76gU/w/kO7RdRqG+5pdunNvFBTMrD+dUjCvDDegfak7Jn8 +cfXwZd7ME+2aIfmR8jgHg+U+Fp8cWTDCfs54B+oMFxORGG8tceIfWXR3Gpbi9F2poLWmOxusvThL +32Xat77b5VWLpjxSHu9AD8t98wC5WlAbW5hxMsmf002Gx1BWVzX2AP1V0SCaO8Uq5AhIU5AZkND2 +hTkvO7f5WVWx/NE15sA8SMghOVIvD4CENnw/nepK2Rl8UEGeky84sz44O5qc7yy3FTsC2YrzQqG4 +ppy0b9DTGPTmBQEBAQEBAUHu8SdcfV4NCmVuZHN0cmVhbQplbmRvYmoKNSAwIG9iago8PAovVHlw +ZSAvWE9iamVjdAovU3VidHlwZSAvRm9ybQovRm9ybVR5cGUgMQovQkJveCBbIDAuMDAwMDAgLTQx +LjcxNTAzIDEwMDAuMDAwMDAgNDEuNzE1MDMgXQovUmVzb3VyY2VzIDw8IC9Qcm9jU2V0IFsvUERG +IC9UZXh0IC9JbWFnZUIgL0ltYWdlQyAvSW1hZ2VJXQo+PgovTGVuZ3RoIDMyNTgKL0ZpbHRlciAv +RmxhdGVEZWNvZGUgPj4Kc3RyZWFtCnjapVnNjhy9DbzPU8w5wDYkkRSlV8gt5yCnDYIg8R6+U14/ +VVR3U+OZnGLDdrtWP/xnSfrjUZ8Fv/H3oSq9P7UeXq3I8/vn8cejHK5WMeLoo4nxX9PJ/89SZ3/+ ++1GPwl/P/zz++jcs9PdHef4Zf/6FyeOoNjDpax6zWZ/Pn4T6MbuU9vz1sMNbKfUjhA/pVgHNo0vH +Dz9BFbK7jB5Y6YXrAxujlfEchzZq8FXrUWeFpI4VSvNAtJRen98PPzBLJbBeS2vPfrQpwwJZBrHD +imHnRL4fcvgocx/VDpXW1kqt1tqf2MWmaMjUiqhiHiQeXcM0dZo3GHSZ8QvSzTZrAnbMaaXGpBPS +0CR8tlZpEMOqbVsd7q4Nk2554Mo+vTxvkSFNhzNSrwv4fkD5pnUkBN8U9xgDO7nBiGadZi2HSbOB +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 +LaAU7R+vXkcTU2dJnCwkrdBqeuKUFxZKYtbP1mypYuNJK9nJXlIC54sLUz4NsQ0btmTdZlr6YOuZ +1hhVS7cc5UsB3QZNUfnGaA/n9XU+x2TbmCHmOVljQnYGlHtJ9sWW2ZB8EtY1Gm+O+I24LW5sM2LP +tJm113Ms3VRtxhvp63BejvBysBc3BvawW04+qcffhgkNqAKgjdNbtOAVsLmrm/zxnb2hqeMloobY +ng1bimozLUW19UxRjaqtqJtyU0C3a1PUxDdFJXvt5Ve8gjryW1EhXXBIe/Camvj5Of1a01JVeJG4 +yKZUTl01xFmjjS1W2EyLXbaeY+oma7N+0e461a4nePuBt5/8ZtHM5nyjm8EEQ5N6WXxxZENvstKW +7pa/wZzL9Pz33z3//Ijf+AzSV9wmU+r+6d9+H57//L+6y+/+ucAeXr6q+K2PTrlm1vuvS/0k+A0H +8nt+k6///Jeo7r6Vlf7K3fw9P3pXf+7g3thYxQ4I99ktfugsJnbD7R9cu9h8+voFwFBqu7wlwM/j +IDDMt18FtNlfqbh8wmvPfGH6nttW8R/72oO+fhhxLvDlU2gnKbefS/y6B6Pn/YZ/W1B/Y+3fgrV/ +VBPwf9ELf20KZW5kc3RyZWFtCmVuZG9iago3IDAgb2JqCjw8Ci9UeXBlIC9QYWdlCi9QYXJlbnQg +MyAwIFIKL01lZGlhQm94IFswIDAgMTAwMC4wMDAwMCAxMDAwLjAwMDAwXQovQmxlZWRCb3ggWzAu +MDAwMDAgMC4wMDAwMCAxMDAwLjAwMDAwIDEwMDAuMDAwMDBdCi9Dcm9wQm94IFswIDAgMTAwMC4w +MDAwMCAxMDAwLjAwMDAwXQovVHJpbUJveCBbMC4wMDAwMCAwLjAwMDAwIDEwMDAuMDAwMDAgMTAw +MC4wMDAwMF0KL0FydEJveCBbMC4wMDAwMCAwLjAwMDAwIDEwMDAuMDAwMDAgMTAwMC4wMDAwMF0K +L1JvdGF0ZSAwCi9Db250ZW50cyA2IDAgUgovVGh1bWIgNCAwIFIKPj4KZW5kb2JqCjggMCBvYmoK +PDwgL1Byb2NTZXQgWy9QREYgL1RleHQgL0ltYWdlQiAvSW1hZ2VDIC9JbWFnZUldCi9YT2JqZWN0 +IDw8Ci9SRTAgNSAwIFIKPj4KPj4KCmVuZG9iagozIDAgb2JqCjw8Ci9UeXBlIC9QYWdlcwovS2lk +cyBbNyAwIFIgXQovQ291bnQgMQovUmVzb3VyY2VzIDggMCBSCj4+CmVuZG9iagp4cmVmCjAgOQow +MDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMTUgMDAwMDAgbiAKMDAwMDAwMDEzNCAwMDAwMCBu +IAowMDAwMDEzNjUyIDAwMDAwIG4gCjAwMDAwMDAzNDMgMDAwMDAgbiAKMDAwMDAwMzEyMCAwMDAw +MCBuIAowMDAwMDA2NjAzIDAwMDAwIG4gCjAwMDAwMTMyNTAgMDAwMDAgbiAKMDAwMDAxMzU1OCAw +MDAwMCBuIAp0cmFpbGVyCjw8Ci9TaXplIDkKL1Jvb3QgMSAwIFIKL0luZm8gMiAwIFIKL0lEIFs8 +RDMzMDEzRDExMUQ3MDFFOTlCMjMzNDNGNzBFMDZBNUE+PEQzMzAxM0QxMTFENzAxRTk5QjIzMzQz +RjcwRTA2QTVBPl0KPj4Kc3RhcnR4cmVmCjEzNzI3CiUlRU9GCg== + +----CALLDATA--//--CALLDATA---- diff --git a/share/extensions/tests/data/io/PAGE_001.DHW b/share/extensions/tests/data/io/PAGE_001.DHW new file mode 100644 index 0000000..e291794 Binary files /dev/null and b/share/extensions/tests/data/io/PAGE_001.DHW differ diff --git a/share/extensions/tests/data/io/PGLT_161.DHW b/share/extensions/tests/data/io/PGLT_161.DHW new file mode 100644 index 0000000..63b1481 Binary files /dev/null and b/share/extensions/tests/data/io/PGLT_161.DHW differ diff --git a/share/extensions/tests/data/io/PGLT_162.DHW b/share/extensions/tests/data/io/PGLT_162.DHW new file mode 100644 index 0000000..6119ee8 Binary files /dev/null and b/share/extensions/tests/data/io/PGLT_162.DHW differ diff --git a/share/extensions/tests/data/io/PGLT_163.DHW b/share/extensions/tests/data/io/PGLT_163.DHW new file mode 100644 index 0000000..a7d52a6 Binary files /dev/null and b/share/extensions/tests/data/io/PGLT_163.DHW differ diff --git a/share/extensions/tests/data/io/nicechart_01.csv b/share/extensions/tests/data/io/nicechart_01.csv new file mode 100644 index 0000000..2cd8d62 --- /dev/null +++ b/share/extensions/tests/data/io/nicechart_01.csv @@ -0,0 +1,13 @@ +Month;1978;1979;1980;1981 +January;2;1,3;0.1;2.3 +February;6.5;2.4;1.2;6.1 +March;7.4;6.7;7.9;4.7 +April;7.7;6.4;8.2;8.9 +May;10.9;11.7;18.7;11.1 +June;12.6;14.2;14.7;14.7 +July;16.5;15.5;17.5;15.1 +August;15.9;15.4;14.6;16.6 +September;14;14.5;13.2;15.3 +October;11.9;13.9;11.5;9.2 +November;6.7;8.5;7;6.6 +December;6.4;2.2;6.3;3.5 diff --git a/share/extensions/tests/data/io/test.eps b/share/extensions/tests/data/io/test.eps new file mode 100644 index 0000000..a2bbae9 --- /dev/null +++ b/share/extensions/tests/data/io/test.eps @@ -0,0 +1,83 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: cairo 1.15.10 (http://cairographics.org) +%%CreationDate: Fri Apr 26 10:51:47 2019 +%%Pages: 1 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%BoundingBox: 0 0 94 98 +%%EndComments +%%BeginProlog +50 dict begin +/q { gsave } bind def +/Q { grestore } bind def +/cm { 6 array astore concat } bind def +/w { setlinewidth } bind def +/J { setlinecap } bind def +/j { setlinejoin } bind def +/M { setmiterlimit } bind def +/d { setdash } bind def +/m { moveto } bind def +/l { lineto } bind def +/c { curveto } bind def +/h { closepath } bind def +/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto + 0 exch rlineto 0 rlineto closepath } bind def +/S { stroke } bind def +/f { fill } bind def +/f* { eofill } bind def +/n { newpath } bind def +/W { clip } bind def +/W* { eoclip } bind def +/BT { } bind def +/ET { } bind def +/BDC { mark 3 1 roll /BDC pdfmark } bind def +/EMC { mark /EMC pdfmark } bind def +/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def +/Tj { show currentpoint cairo_store_point } bind def +/TJ { + { + dup + type /stringtype eq + { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse + } forall + currentpoint cairo_store_point +} bind def +/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore + cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def +/Tf { pop /cairo_font exch def /cairo_font_matrix where + { pop cairo_selectfont } if } bind def +/Td { matrix translate cairo_font_matrix matrix concatmatrix dup + /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point + /cairo_font where { pop cairo_selectfont } if } bind def +/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def + cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def +/g { setgray } bind def +/rg { setrgbcolor } bind def +/d1 { setcachedevice } bind def +/cairo_data_source { + CairoDataIndex CairoData length lt + { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def } + { () } ifelse +} def +/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def +/cairo_image { image cairo_flush_ascii85_file } def +/cairo_imagemask { imagemask cairo_flush_ascii85_file } def +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +%%PageBoundingBox: 0 0 94 98 +%%EndPageSetup +q 0 0 94 98 rectclip +1 0 0 -1 0 98 cm q +0.652941 0.801961 0.511765 rg +1.641 0 m 91.734 0 l 92.645 0 93.375 0.73 93.375 1.641 c 93.375 95.625 +l 93.375 96.535 92.645 97.266 91.734 97.266 c 1.641 97.266 l 0.73 97.266 + 0 96.535 0 95.625 c 0 1.641 l 0 0.73 0.73 0 1.641 0 c h +1.641 0 m f +Q Q +showpage +%%Trailer +end +%%EOF diff --git a/share/extensions/tests/data/io/test.fig b/share/extensions/tests/data/io/test.fig new file mode 100644 index 0000000..4925068 --- /dev/null +++ b/share/extensions/tests/data/io/test.fig @@ -0,0 +1,15 @@ +#FIG 3.2 Produced by xfig version 3.2.6a +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 2700 3285 587 587 2700 3285 3285 3240 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 1890 2565 4950 2565 4950 5175 1890 5175 1890 2565 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 5220 4320 5220 2610 6705 2610 6705 4320 5220 4320 +4 0 0 50 -1 0 12 0.0000 4 165 1260 2520 1890 Hello Inkscape\001 diff --git a/share/extensions/tests/data/io/test.hpgl b/share/extensions/tests/data/io/test.hpgl new file mode 100644 index 0000000..3dc0fed --- /dev/null +++ b/share/extensions/tests/data/io/test.hpgl @@ -0,0 +1 @@ +IN;SP1;PU0,0;PD0,90;PU1543,280;PD1538,279,1534,275,1533,272,1053,358,1054,352,1056,349,713,2,711,7,711,12,714,17,718,19,719,19,653,502,648,500,645,497,210,716,213,720,218,722,223,720,227,717,227,716,666,928,663,932,659,934,733,1416,737,1414,741,1409,741,1404,738,1399,1076,1047,1078,1052,1079,1056,1560,1135,1559,1129,1556,1125,1551,1123,1546,1124,1545,1124,1315,694,1320,693,1324,694,1548,261,1543,260,1537,262,1534,266,1533,271,1533,272,1494,279;PU4234,3332;PD4234,3326,4237,3322,4242,3320,4244,3320,4246,3275,4247,3275,4252,3222,4262,3155,4278,3075,4279,3075,4302,2985,4303,2986,4318,2938,4318,2939,4336,2890,4357,2841,4380,2792,4381,2792,4407,2742,4408,2743,4437,2694,4438,2694,4471,2646,4508,2599,4509,2599,4549,2553,4550,2554,4595,2510,4595,2511,4644,2469,4645,2470,4698,2431,4699,2432,4757,2396,4757,2397,4820,2365,4821,2366,4889,2337,4889,2338,4962,2314,4962,2315,5041,2295,5041,2296,5126,2282,5126,2283,5216,2274,5312,2271,5312,2272,5405,2275,5405,2276,5487,2285,5523,2292,5557,2300,5557,2301,5588,2310,5617,2320,5617,2321,5644,2332,5644,2333,5668,2346,5690,2360,5690,2361,5710,2375,5710,2376,5728,2391,5727,2392,5744,2408,5743,2409,5758,2426,5758,2427,5771,2445,5770,2446,5792,2484,5791,2485,5807,2526,5806,2527,5818,2571,5817,2571,5825,2616,5824,2616,5829,2664,5828,2664,5831,2712,5830,2712,5831,2811,5832,2910,5833,2910,5835,2958,5836,2958,5840,3005,5841,3005,5849,3051,5850,3050,5862,3094,5863,3094,5879,3135,5880,3135,5902,3173,5903,3173,5916,3191,5917,3190,5932,3208,5932,3207,5949,3224,5950,3223,5968,3239,5968,3238,5989,3253,5989,3252,6012,3266,6012,3265,6037,3278,6037,3277,6064,3288,6093,3298,6125,3307,6125,3306,6159,3314,6159,3313,6196,3320,6196,3319,6277,3328,6277,3327,6370,3330,6466,3327,6466,3326,6557,3317,6556,3316,6641,3302,6641,3301,6719,3282,6719,3281,6792,3257,6860,3229,6860,3228,6923,3196,6923,3195,6981,3159,6980,3159,7034,3120,7033,3119,7082,3078,7127,3034,7126,3033,7167,2988,7166,2987,7203,2940,7236,2891,7235,2891,7265,2842,7291,2792,7314,2742,7313,2742,7334,2693,7333,2693,7351,2644,7366,2597,7365,2597,7389,2507,7388,2507,7404,2427,7413,2359,7418,2307,7417,2307,7419,2262;PU11,3320;PD16,3322,17,3323,1076,2265,1078,2269,1078,2275,1076,2279,2134,3337,2137,3332,2137,3327,2134,3323,3192,2265;PU7247,4616;PD7242,4613,7237,4613,7232,4616,7231,4617,7181,4583,7180,4583,7125,4551,7125,4552,7065,4522,7065,4523,7001,4496,7000,4496,6933,4472,6933,4473,6862,4451,6862,4452,6788,4433,6788,4434,6712,4418,6712,4419,6633,4406,6553,4397,6471,4391,6388,4388,6388,4389,6305,4389,6221,4393,6137,4400,6054,4411,5973,4425,5895,4442,5820,4462,5820,4463,5750,4485,5684,4510,5684,4511,5622,4538,5622,4539,5565,4568,5565,4569,5512,4600,5513,4601,5465,4635,5466,4635,5423,4671,5424,4671,5387,4709,5388,4709,5357,4748,5358,4748,5333,4788,5334,4789,5316,4830,5317,4830,5305,4872,5306,4873,5301,4916,5303,4916,5304,4959,5306,4958,5314,5001,5316,5000,5331,5042,5332,5041,5353,5082,5354,5081,5382,5120,5383,5119,5416,5157,5417,5156,5456,5193,5456,5192,5501,5227,5501,5226,5551,5259,5551,5258,5606,5289,5666,5317,5730,5343,5731,5343,5799,5366,5872,5387,5948,5405,5949,5405,6029,5420,6111,5432,6194,5440,6278,5445,6361,5447,6444,5445,6526,5440,6607,5432,6687,5421,6764,5407,6764,5406,6840,5390,6839,5389,6912,5369,6982,5346,6981,5346,7048,5320,7047,5320,7110,5292,7109,5291,7168,5260,7167,5260,7221,5226,7217,5222,7212,5221,7207,5223,7203,5227,7203,5228,6351,4914,6354,4910,6359,4908,6364,4908,6368,4912,6370,4914,7249,4620,7246,4615,7241,4613,7236,4614,7232,4617,7231,4617,7198,4595;SP0;PU0,0;IN; diff --git a/share/extensions/tests/data/io/test.ps b/share/extensions/tests/data/io/test.ps new file mode 100644 index 0000000..0c85986 --- /dev/null +++ b/share/extensions/tests/data/io/test.ps @@ -0,0 +1,120 @@ +%!PS-Adobe-3.0 +%%Creator: cairo 1.15.10 (http://cairographics.org) +%%CreationDate: Fri Apr 26 10:51:29 2019 +%%Pages: 1 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%DocumentMedia: 33x34mm 93 97 0 () () +%%BoundingBox: 0 0 94 98 +%%EndComments +%%BeginProlog +/languagelevel where +{ pop languagelevel } { 1 } ifelse +2 lt { /Helvetica findfont 12 scalefont setfont 50 500 moveto + (This print job requires a PostScript Language Level 2 printer.) show + showpage quit } if +/q { gsave } bind def +/Q { grestore } bind def +/cm { 6 array astore concat } bind def +/w { setlinewidth } bind def +/J { setlinecap } bind def +/j { setlinejoin } bind def +/M { setmiterlimit } bind def +/d { setdash } bind def +/m { moveto } bind def +/l { lineto } bind def +/c { curveto } bind def +/h { closepath } bind def +/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto + 0 exch rlineto 0 rlineto closepath } bind def +/S { stroke } bind def +/f { fill } bind def +/f* { eofill } bind def +/n { newpath } bind def +/W { clip } bind def +/W* { eoclip } bind def +/BT { } bind def +/ET { } bind def +/BDC { mark 3 1 roll /BDC pdfmark } bind def +/EMC { mark /EMC pdfmark } bind def +/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def +/Tj { show currentpoint cairo_store_point } bind def +/TJ { + { + dup + type /stringtype eq + { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse + } forall + currentpoint cairo_store_point +} bind def +/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore + cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def +/Tf { pop /cairo_font exch def /cairo_font_matrix where + { pop cairo_selectfont } if } bind def +/Td { matrix translate cairo_font_matrix matrix concatmatrix dup + /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point + /cairo_font where { pop cairo_selectfont } if } bind def +/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def + cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def +/g { setgray } bind def +/rg { setrgbcolor } bind def +/d1 { setcachedevice } bind def +/cairo_data_source { + CairoDataIndex CairoData length lt + { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def } + { () } ifelse +} def +/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def +/cairo_image { image cairo_flush_ascii85_file } def +/cairo_imagemask { imagemask cairo_flush_ascii85_file } def +/cairo_set_page_size { + % Change paper size, but only if different from previous paper size otherwise + % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size + % so we use the same when checking if the size changes. + /setpagedevice where { + pop currentpagedevice + /PageSize known { + 2 copy + currentpagedevice /PageSize get aload pop + exch 4 1 roll + sub abs 5 gt + 3 1 roll + sub abs 5 gt + or + } { + true + } ifelse + { + 2 array astore + 2 dict begin + /PageSize exch def + /ImagingBBox null def + currentdict end + setpagedevice + } { + pop pop + } ifelse + } { + pop + } ifelse +} def +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +%%PageMedia: 33x34mm +%%PageBoundingBox: 0 0 94 98 +94 98 cairo_set_page_size +%%EndPageSetup +q 0 0 94 98 rectclip +1 0 0 -1 0 98 cm q +0.9 0.5 0.5 rg +1.641 0 m 91.734 0 l 92.645 0 93.375 0.73 93.375 1.641 c 93.375 95.625 +l 93.375 96.535 92.645 97.266 91.734 97.266 c 1.641 97.266 l 0.73 97.266 + 0 96.535 0 95.625 c 0 1.641 l 0 0.73 0.73 0 1.641 0 c h +1.641 0 m f +Q Q +showpage +%%Trailer +%%EOF diff --git a/share/extensions/tests/data/io/test_r12.dxf b/share/extensions/tests/data/io/test_r12.dxf new file mode 100644 index 0000000..7b55631 --- /dev/null +++ b/share/extensions/tests/data/io/test_r12.dxf @@ -0,0 +1,5592 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1009 + 9 +$EXTMIN + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$EXTMAX + 10 +1000.0 + 20 +1000.0 + 30 +0.0 + 9 +$FILLMODE + 70 + 0 + 9 +$SPLFRAME + 70 + 1 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +LAYER + 70 +1 + 0 +LAYER + 2 +0 + 70 + 0 + 62 + 7 + 6 +CONTINUOUS + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +POLYLINE + 8 +0 + 62 + 174 + 66 + 1 + 10 +0 + 20 +0 + 30 +0.0 + 70 + 1 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.04167 + 20 +8.33333 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.08333 + 20 +8.33333 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.08333 + 20 +7.29167 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.04167 + 20 +7.29167 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.04167 + 20 +8.33333 + 30 +0.0 + 40 +0 + 41 +0 + 0 +SEQEND + 8 +0 + 0 +POLYLINE + 8 +0 + 62 + 1 + 66 + 1 + 10 +0 + 20 +0 + 30 +0.0 + 70 + 1 + 40 +12 + 41 +12 + 0 +VERTEX + 8 +0 + 10 +3.125 + 20 +8.33333 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +5.20833 + 20 +8.33333 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +5.20833 + 20 +7.29167 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.125 + 20 +7.29167 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.125 + 20 +8.33333 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +SEQEND + 8 +0 + 0 +POLYLINE + 8 +0 + 62 + 174 + 66 + 1 + 10 +0 + 20 +0 + 30 +0.0 + 70 + 1 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.08333 + 20 +5.72917 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.08333 + 20 +5.70296 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.07269 + 20 +5.62424 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.04237 + 20 +5.52647 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.99436 + 20 +5.43799 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.93078 + 20 +5.36089 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.85368 + 20 +5.29731 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.76519 + 20 +5.24929 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.66743 + 20 +5.21897 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.58871 + 20 +5.20833 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.5625 + 20 +5.20833 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.53624 + 20 +5.20833 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.45751 + 20 +5.21897 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.35975 + 20 +5.24929 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.27127 + 20 +5.29731 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.19417 + 20 +5.36089 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.13059 + 20 +5.43799 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.08257 + 20 +5.52647 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.05225 + 20 +5.62424 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.04167 + 20 +5.70296 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.04167 + 20 +5.72917 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.04167 + 20 +5.75543 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.05225 + 20 +5.83415 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.08257 + 20 +5.93192 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.13059 + 20 +6.0204 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.19417 + 20 +6.0975 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.27127 + 20 +6.16108 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.35975 + 20 +6.2091 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.45751 + 20 +6.23942 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.53624 + 20 +6.25 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.5625 + 20 +6.25 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.58871 + 20 +6.25 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.66743 + 20 +6.23942 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.76519 + 20 +6.2091 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.85368 + 20 +6.16108 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.93078 + 20 +6.0975 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.99436 + 20 +6.0204 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.04237 + 20 +5.93192 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.07269 + 20 +5.83415 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.08333 + 20 +5.75543 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.08333 + 20 +5.72917 + 30 +0.0 + 40 +0 + 41 +0 + 0 +SEQEND + 8 +0 + 0 +POLYLINE + 8 +0 + 62 + 1 + 66 + 1 + 10 +0 + 20 +0 + 30 +0.0 + 70 + 1 + 40 +12 + 41 +12 + 0 +VERTEX + 8 +0 + 10 +5.20833 + 20 +5.72917 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +5.20833 + 20 +5.70296 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +5.18712 + 20 +5.62424 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +5.12646 + 20 +5.52647 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +5.03039 + 20 +5.43799 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +4.90321 + 20 +5.36089 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +4.74903 + 20 +5.29731 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +4.5721 + 20 +5.24929 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +4.37657 + 20 +5.21897 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +4.21913 + 20 +5.20833 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +4.16667 + 20 +5.20833 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +4.11415 + 20 +5.20833 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.95671 + 20 +5.21897 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.76118 + 20 +5.24929 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.58425 + 20 +5.29731 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.43007 + 20 +5.36089 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.3029 + 20 +5.43799 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.20682 + 20 +5.52647 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.14615 + 20 +5.62424 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.125 + 20 +5.70296 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.125 + 20 +5.72917 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.125 + 20 +5.75543 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.14615 + 20 +5.83415 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.20682 + 20 +5.93192 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.3029 + 20 +6.0204 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.43007 + 20 +6.0975 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.58425 + 20 +6.16108 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.76118 + 20 +6.2091 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +3.95671 + 20 +6.23942 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +4.11415 + 20 +6.25 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +4.16667 + 20 +6.25 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +4.21913 + 20 +6.25 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +4.37657 + 20 +6.23942 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +4.5721 + 20 +6.2091 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +4.74903 + 20 +6.16108 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +4.90321 + 20 +6.0975 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +5.03039 + 20 +6.0204 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +5.12646 + 20 +5.93192 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +5.18712 + 20 +5.83415 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +5.20833 + 20 +5.75543 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +5.20833 + 20 +5.72917 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +SEQEND + 8 +0 + 0 +POLYLINE + 8 +0 + 62 + 2 + 66 + 1 + 10 +0 + 20 +0 + 30 +0.0 + 70 + 1 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.15717 + 20 +5.43935 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.13249 + 20 +5.42096 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.05274 + 20 +5.37354 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.93099 + 20 +5.31799 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.79486 + 20 +5.27317 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.64725 + 20 +5.23964 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.49104 + 20 +5.21793 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.32915 + 20 +5.20865 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.16444 + 20 +5.21218 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.04075 + 20 +5.22325 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.99989 + 20 +5.22917 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.96035 + 20 +5.23497 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.84283 + 20 +5.25911 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.70014 + 20 +5.3004 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.57368 + 20 +5.35183 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.46528 + 20 +5.41211 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.37685 + 20 +5.48004 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.31028 + 20 +5.55442 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.26747 + 20 +5.634 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.25119 + 20 +5.69656 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.25028 + 20 +5.71756 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.24935 + 20 +5.73839 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.25999 + 20 +5.80149 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.29568 + 20 +5.88194 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.35558 + 20 +5.95774 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.43782 + 20 +6.02756 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.54075 + 20 +6.09022 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.66249 + 20 +6.14437 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.80138 + 20 +6.18881 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.91672 + 20 +6.21556 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.95557 + 20 +6.22217 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.99599 + 20 +6.22906 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.11843 + 20 +6.24283 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.28267 + 20 +6.25006 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.44526 + 20 +6.24436 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.60325 + 20 +6.22618 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.75369 + 20 +6.19601 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.89367 + 20 +6.15429 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.02024 + 20 +6.10146 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.10417 + 20 +6.05583 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.13043 + 20 +6.03797 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.29167 + 20 +5.72917 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.15717 + 20 +5.43935 + 30 +0.0 + 40 +0 + 41 +0 + 0 +SEQEND + 8 +0 + 0 +POLYLINE + 8 +0 + 62 + 94 + 66 + 1 + 10 +0 + 20 +0 + 30 +0.0 + 70 + 1 + 40 +12 + 41 +12 + 0 +VERTEX + 8 +0 + 10 +8.15717 + 20 +5.43935 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.13249 + 20 +5.42096 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.05274 + 20 +5.37354 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.93099 + 20 +5.31799 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.79486 + 20 +5.27317 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.64725 + 20 +5.23964 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.49104 + 20 +5.21793 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.32915 + 20 +5.20865 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.16444 + 20 +5.21218 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.04075 + 20 +5.22325 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.99989 + 20 +5.22917 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.96035 + 20 +5.23497 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.84283 + 20 +5.25911 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.70014 + 20 +5.3004 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.57368 + 20 +5.35183 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.46528 + 20 +5.41211 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.37685 + 20 +5.48004 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.31028 + 20 +5.55442 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.26747 + 20 +5.634 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.25119 + 20 +5.69656 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.25028 + 20 +5.71756 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.24935 + 20 +5.73839 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.25999 + 20 +5.80149 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.29568 + 20 +5.88194 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.35558 + 20 +5.95774 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.43782 + 20 +6.02756 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.54075 + 20 +6.09022 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.66249 + 20 +6.14437 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.80138 + 20 +6.18881 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.91672 + 20 +6.21556 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.95557 + 20 +6.22217 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.99599 + 20 +6.22906 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.11843 + 20 +6.24283 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.28267 + 20 +6.25006 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.44526 + 20 +6.24436 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.60325 + 20 +6.22618 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.75369 + 20 +6.19601 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.89367 + 20 +6.15429 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.02024 + 20 +6.10146 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.10417 + 20 +6.05583 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.13043 + 20 +6.03797 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.29167 + 20 +5.72917 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.15717 + 20 +5.43935 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +SEQEND + 8 +0 + 0 +POLYLINE + 8 +0 + 62 + 2 + 66 + 1 + 10 +0 + 20 +0 + 30 +0.0 + 70 + 1 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.71815 + 20 +8.33333 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.86518 + 20 +8.33333 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.88878 + 20 +8.33333 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.95975 + 20 +8.32514 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.04769 + 20 +8.3016 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.12722 + 20 +8.26426 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.1965 + 20 +8.21479 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.25358 + 20 +8.15479 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.2966 + 20 +8.08583 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.32383 + 20 +8.0096 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.33333 + 20 +7.94808 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.33333 + 20 +7.92757 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.33333 + 20 +7.69743 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.33333 + 20 +7.67697 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.32383 + 20 +7.61546 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.2966 + 20 +7.53922 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.25358 + 20 +7.47026 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.1965 + 20 +7.41026 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.12722 + 20 +7.36079 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +8.04769 + 20 +7.32346 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.95975 + 20 +7.29992 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.88878 + 20 +7.29167 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +7.86518 + 20 +7.29167 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.71815 + 20 +7.29167 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.6945 + 20 +7.29167 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.62354 + 20 +7.29992 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.5356 + 20 +7.32346 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.45606 + 20 +7.36079 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.38683 + 20 +7.41026 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.32969 + 20 +7.47026 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.28668 + 20 +7.53922 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.25944 + 20 +7.61546 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.25 + 20 +7.67697 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.25 + 20 +7.69743 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.25 + 20 +7.92757 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.25 + 20 +7.94808 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.25944 + 20 +8.0096 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.28668 + 20 +8.08583 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.32969 + 20 +8.15479 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.38683 + 20 +8.21479 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.45606 + 20 +8.26426 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.5356 + 20 +8.3016 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.62354 + 20 +8.32514 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.6945 + 20 +8.33333 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +6.71815 + 20 +8.33333 + 30 +0.0 + 40 +0 + 41 +0 + 0 +SEQEND + 8 +0 + 0 +POLYLINE + 8 +0 + 62 + 94 + 66 + 1 + 10 +0 + 20 +0 + 30 +0.0 + 70 + 1 + 40 +12 + 41 +12 + 0 +VERTEX + 8 +0 + 10 +6.71815 + 20 +8.33333 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.86518 + 20 +8.33333 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.88878 + 20 +8.33333 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.95975 + 20 +8.32514 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.04769 + 20 +8.3016 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.12722 + 20 +8.26426 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.1965 + 20 +8.21479 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.25358 + 20 +8.15479 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.2966 + 20 +8.08583 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.32383 + 20 +8.0096 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.33333 + 20 +7.94808 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.33333 + 20 +7.92757 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.33333 + 20 +7.69743 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.33333 + 20 +7.67697 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.32383 + 20 +7.61546 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.2966 + 20 +7.53922 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.25358 + 20 +7.47026 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.1965 + 20 +7.41026 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.12722 + 20 +7.36079 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +8.04769 + 20 +7.32346 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.95975 + 20 +7.29992 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.88878 + 20 +7.29167 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +7.86518 + 20 +7.29167 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.71815 + 20 +7.29167 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.6945 + 20 +7.29167 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.62354 + 20 +7.29992 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.5356 + 20 +7.32346 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.45606 + 20 +7.36079 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.38683 + 20 +7.41026 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.32969 + 20 +7.47026 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.28668 + 20 +7.53922 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.25944 + 20 +7.61546 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.25 + 20 +7.67697 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.25 + 20 +7.69743 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.25 + 20 +7.92757 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.25 + 20 +7.94808 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.25944 + 20 +8.0096 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.28668 + 20 +8.08583 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.32969 + 20 +8.15479 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.38683 + 20 +8.21479 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.45606 + 20 +8.26426 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.5356 + 20 +8.3016 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.62354 + 20 +8.32514 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.6945 + 20 +8.33333 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +VERTEX + 8 +0 + 10 +6.71815 + 20 +8.33333 + 30 +0.0 + 40 +0.166667 + 41 +0.166667 + 0 +SEQEND + 8 +0 + 0 +POLYLINE + 8 +0 + 62 + 0 + 66 + 1 + 10 +0 + 20 +0 + 30 +0.0 + 40 +7.5 + 41 +7.5 + 0 +VERTEX + 8 +0 + 10 +1.04167 + 20 +4.16667 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +2.08333 + 20 +3.125 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +3.125 + 20 +4.16667 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +4.16667 + 20 +3.125 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +SEQEND + 8 +0 + 0 +POLYLINE + 8 +0 + 62 + 0 + 66 + 1 + 10 +0 + 20 +0 + 30 +0.0 + 40 +7.5 + 41 +7.5 + 0 +VERTEX + 8 +0 + 10 +5.20833 + 20 +4.16667 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.20833 + 20 +4.16667 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.20856 + 20 +4.155 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.21035 + 20 +4.1219 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.21517 + 20 +4.07058 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.22461 + 20 +4.0039 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.24007 + 20 +3.92507 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.26324 + 20 +3.83708 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.29551 + 20 +3.743 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.33854 + 20 +3.64583 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.39372 + 20 +3.54872 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.46262 + 20 +3.45458 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.54682 + 20 +3.36664 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.64779 + 20 +3.28776 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.76704 + 20 +3.22114 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +5.90614 + 20 +3.16976 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.06663 + 20 +3.13672 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.20415 + 20 +3.125 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.25 + 20 +3.125 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.29297 + 20 +3.125 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.42188 + 20 +3.13672 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.55111 + 20 +3.16976 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.64367 + 20 +3.22114 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.70574 + 20 +3.28776 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.74333 + 20 +3.36664 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.76269 + 20 +3.45458 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.76981 + 20 +3.54872 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.77083 + 20 +3.64583 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.77181 + 20 +3.743 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.77897 + 20 +3.83708 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.79829 + 20 +3.92507 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.83594 + 20 +4.0039 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.89794 + 20 +4.07058 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +6.99056 + 20 +4.1219 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +7.11974 + 20 +4.155 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +7.24864 + 20 +4.16667 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +7.29167 + 20 +4.16667 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +7.33746 + 20 +4.16667 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +7.47499 + 20 +4.155 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +7.63547 + 20 +4.1219 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +7.77458 + 20 +4.07058 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +7.89388 + 20 +4.0039 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +7.99479 + 20 +3.92507 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +8.079 + 20 +3.83708 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +8.14789 + 20 +3.743 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +8.20313 + 20 +3.64583 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +8.2461 + 20 +3.54872 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +8.27838 + 20 +3.45458 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +8.30154 + 20 +3.36664 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +8.31706 + 20 +3.28776 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +8.32644 + 20 +3.22114 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +8.33128 + 20 +3.16976 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +8.33306 + 20 +3.13672 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +8.33333 + 20 +3.125 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +8.33333 + 20 +3.125 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +SEQEND + 8 +0 + 0 +POLYLINE + 8 +0 + 62 + 2 + 66 + 1 + 10 +0 + 20 +0 + 30 +0.0 + 70 + 1 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.55035 + 20 +1.15478 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.07807 + 20 +1.23953 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.7405 + 20 +0.898492 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.67518 + 20 +1.37386 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.24653 + 20 +1.58951 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.67844 + 20 +1.79856 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +1.75103 + 20 +2.27285 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.08333 + 20 +1.92671 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.55686 + 20 +2.00418 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.33035 + 20 +1.58122 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +2.55035 + 20 +1.15478 + 30 +0.0 + 40 +0 + 41 +0 + 0 +SEQEND + 8 +0 + 0 +POLYLINE + 8 +0 + 62 + 94 + 66 + 1 + 10 +0 + 20 +0 + 30 +0.0 + 70 + 1 + 40 +7.5 + 41 +7.5 + 0 +VERTEX + 8 +0 + 10 +2.55035 + 20 +1.15478 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +2.07807 + 20 +1.23953 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +1.7405 + 20 +0.898492 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +1.67518 + 20 +1.37386 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +1.24653 + 20 +1.58951 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +1.67844 + 20 +1.79856 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +1.75103 + 20 +2.27285 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +2.08333 + 20 +1.92671 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +2.55686 + 20 +2.00418 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +2.33035 + 20 +1.58122 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +2.55035 + 20 +1.15478 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +SEQEND + 8 +0 + 0 +POLYLINE + 8 +0 + 62 + 2 + 66 + 1 + 10 +0 + 20 +0 + 30 +0.0 + 70 + 1 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +4.63368 + 20 +1.12359 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +4.1614 + 20 +1.20833 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +3.82383 + 20 +0.867296 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +3.75851 + 20 +1.34267 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +3.32986 + 20 +1.55826 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +3.76178 + 20 +1.76736 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +3.83436 + 20 +2.24164 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +4.16667 + 20 +1.89551 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +4.64019 + 20 +1.97299 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +4.41368 + 20 +1.54997 + 30 +0.0 + 40 +0 + 41 +0 + 0 +VERTEX + 8 +0 + 10 +4.63368 + 20 +1.12359 + 30 +0.0 + 40 +0 + 41 +0 + 0 +SEQEND + 8 +0 + 0 +POLYLINE + 8 +0 + 62 + 94 + 66 + 1 + 10 +0 + 20 +0 + 30 +0.0 + 70 + 1 + 40 +7.5 + 41 +7.5 + 0 +VERTEX + 8 +0 + 10 +4.63368 + 20 +1.12359 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +4.1614 + 20 +1.20833 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +3.82383 + 20 +0.867296 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +3.75851 + 20 +1.34267 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +3.32986 + 20 +1.55826 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +3.76178 + 20 +1.76736 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +3.83436 + 20 +2.24164 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +4.16667 + 20 +1.89551 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +4.64019 + 20 +1.97299 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +4.41368 + 20 +1.54997 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +VERTEX + 8 +0 + 10 +4.63368 + 20 +1.12359 + 30 +0.0 + 40 +0.104167 + 41 +0.104167 + 0 +SEQEND + 8 +0 + 0 +ENDSEC + 0 +EOF diff --git a/share/extensions/tests/data/io/test_r14.dxf b/share/extensions/tests/data/io/test_r14.dxf new file mode 100644 index 0000000..7650cad --- /dev/null +++ b/share/extensions/tests/data/io/test_r14.dxf @@ -0,0 +1,1398 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1014 + 9 +$HANDSEED + 5 +FFFF + 9 +$MEASUREMENT + 70 + 1 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 + 4 + 0 +VPORT + 5 +2E +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*ACTIVE + 70 + 0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +210.0 + 22 +148.5 + 13 +0.0 + 23 +0.0 + 14 +10.0 + 24 +10.0 + 15 +10.0 + 25 +10.0 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +341.0 + 41 +1.24 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 + 0 + 72 + 100 + 73 + 1 + 74 + 3 + 75 + 0 + 76 + 0 + 77 + 0 + 78 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +LTYPE + 5 +14 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYBLOCK + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +15 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYLAYER + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +16 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CONTINUOUS + 70 + 0 + 3 +Solid line + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +2 +100 +AcDbSymbolTable + 70 +2 + 0 +LAYER + 5 +50 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +51 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Layer_1 + 70 +0 + 6 +CONTINUOUS + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +STYLE + 5 +11 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +STANDARD + 70 + 0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 + 0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 + 2 + 0 +APPID + 5 +12 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +A +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +DIMSTYLE +105 +27 +330 +A +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +ISO-25 + 70 + 0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874016 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 + 71 + 0 + 72 + 0 + 73 + 0 + 74 + 0 + 75 + 0 + 76 + 0 + 77 + 1 + 78 + 8 +170 + 0 +171 + 3 +172 + 1 +173 + 0 +174 + 0 +175 + 0 +176 + 0 +177 + 0 +178 + 0 +270 + 2 +271 + 2 +272 + 2 +273 + 2 +274 + 3 +340 +11 +275 + 0 +280 + 0 +281 + 0 +282 + 0 +283 + 0 +284 + 8 +285 + 0 +286 + 0 +287 + 3 +288 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +BLOCK_RECORD + 5 +1F +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*MODEL_SPACE + 0 +BLOCK_RECORD + 5 +1B +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*PAPER_SPACE + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +20 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*MODEL_SPACE + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*MODEL_SPACE + 1 + + 0 +ENDBLK + 5 +21 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockBegin + 2 +*PAPER_SPACE + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +LWPOLYLINE + 5 +100 +100 +AcDbEntity + 8 +Layer_1 + 62 +7 +100 +AcDbPolyline + 90 +1 + 70 +1 + 10 +113.097368 + 20 +154.075207 + 30 +0.0 + 0 +LWPOLYLINE + 5 +101 +100 +AcDbEntity + 8 +Layer_1 + 62 +7 +100 +AcDbPolyline + 90 +10 + 70 +1 + 10 +120.275430 + 20 +164.135316 + 30 +0.0 + 10 +121.883216 + 20 +188.782038 + 30 +0.0 + 10 +102.491311 + 20 +204.079099 + 30 +0.0 + 10 +126.428570 + 20 +210.166260 + 30 +0.0 + 10 +134.984511 + 20 +233.336106 + 30 +0.0 + 10 +148.170764 + 20 +212.451457 + 30 +0.0 + 10 +172.850533 + 20 +211.474151 + 30 +0.0 + 10 +157.062824 + 20 +192.479564 + 30 +0.0 + 10 +163.759825 + 20 +168.705709 + 30 +0.0 + 10 +140.816225 + 20 +177.851061 + 30 +0.0 + 0 +SPLINE + 5 +102 +100 +AcDbEntity + 8 +Layer_1 + 62 +7 +100 +AcDbSpline + 70 +0 + 71 +3 + 72 +25 + 73 +21 + 74 +19 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +19.668030 + 40 +39.054581 + 40 +58.722611 + 40 +78.390642 + 40 +97.777192 + 40 +117.445222 + 40 +137.113253 + 40 +156.499803 + 40 +176.167833 + 40 +195.835864 + 40 +215.222414 + 40 +234.890444 + 40 +254.558475 + 40 +273.945025 + 40 +293.613055 + 40 +313.281086 + 40 +332.667636 + 40 +352.335666 + 40 +352.335666 + 40 +352.335666 + 40 +352.335666 + 10 +113.097368 + 20 +154.075207 + 30 +0.0 + 10 +112.423967 + 20 +147.485759 + 30 +0.0 + 10 +111.086802 + 20 +134.401170 + 30 +0.0 + 10 +100.712586 + 20 +117.109684 + 30 +0.0 + 10 +85.522978 + 20 +104.253917 + 30 +0.0 + 10 +66.713509 + 20 +97.435647 + 30 +0.0 + 10 +46.754542 + 20 +97.435647 + 30 +0.0 + 10 +27.971417 + 20 +104.253917 + 30 +0.0 + 10 +12.676624 + 20 +117.109684 + 30 +0.0 + 10 +2.693232 + 20 +134.401170 + 30 +0.0 + 10 +-0.792469 + 20 +154.075207 + 30 +0.0 + 10 +2.693232 + 20 +173.749243 + 30 +0.0 + 10 +12.676624 + 20 +191.040729 + 30 +0.0 + 10 +27.971417 + 20 +203.896496 + 30 +0.0 + 10 +46.754542 + 20 +210.714766 + 30 +0.0 + 10 +66.713509 + 20 +210.714766 + 30 +0.0 + 10 +85.522978 + 20 +203.896496 + 30 +0.0 + 10 +100.712586 + 20 +191.040729 + 30 +0.0 + 10 +111.086802 + 20 +173.749243 + 30 +0.0 + 10 +112.423967 + 20 +160.664654 + 30 +0.0 + 10 +113.097368 + 20 +154.075207 + 30 +0.0 + 11 +113.097368 + 21 +154.075207 + 31 +0.0 + 11 +109.666446 + 21 +134.708736 + 31 +0.0 + 11 +99.973171 + 21 +117.919491 + 31 +0.0 + 11 +84.916776 + 21 +105.264990 + 31 +0.0 + 11 +66.429459 + 21 +98.553020 + 31 +0.0 + 11 +47.042909 + 21 +98.553020 + 31 +0.0 + 11 +28.555593 + 21 +105.264990 + 31 +0.0 + 11 +13.499197 + 21 +117.919491 + 31 +0.0 + 11 +3.805922 + 21 +134.708736 + 31 +0.0 + 11 +0.375001 + 21 +154.075207 + 31 +0.0 + 11 +3.805922 + 21 +173.441678 + 31 +0.0 + 11 +13.499197 + 21 +190.230923 + 31 +0.0 + 11 +28.555593 + 21 +202.885423 + 31 +0.0 + 11 +47.042909 + 21 +209.597394 + 31 +0.0 + 11 +66.429459 + 21 +209.597394 + 31 +0.0 + 11 +84.916776 + 21 +202.885423 + 31 +0.0 + 11 +99.973171 + 21 +190.230923 + 31 +0.0 + 11 +109.666446 + 21 +173.441678 + 31 +0.0 + 11 +113.097368 + 21 +154.075207 + 31 +0.0 + 0 +SPLINE + 5 +103 +100 +AcDbEntity + 8 +Layer_1 + 62 +7 +100 +AcDbSpline + 70 +0 + 71 +3 + 72 +10 + 73 +6 + 74 +4 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +37.795543 + 40 +102.597632 + 40 +154.404451 + 40 +154.404451 + 40 +154.404451 + 40 +154.404451 + 10 +141.965294 + 20 +62.316437 + 30 +0.0 + 10 +150.647125 + 20 +53.810973 + 30 +0.0 + 10 +174.214329 + 20 +30.722525 + 30 +0.0 + 10 +221.078954 + 20 +-13.525722 + 30 +0.0 + 10 +254.105150 + 20 +15.667342 + 30 +0.0 + 10 +268.777976 + 20 +28.637189 + 30 +0.0 + 11 +141.965294 + 21 +62.316437 + 31 +0.0 + 11 +169.038517 + 21 +35.943274 + 31 +0.0 + 11 +224.292337 + 21 +2.085835 + 31 +0.0 + 11 +268.777976 + 21 +28.637189 + 31 +0.0 + 0 +LWPOLYLINE + 5 +104 +100 +AcDbEntity + 8 +Layer_1 + 62 +7 +100 +AcDbPolyline + 90 +2 + 70 +0 + 10 +183.548854 + 20 +142.046895 + 30 +0.0 + 10 +141.965294 + 20 +62.316437 + 30 +0.0 + 0 +LWPOLYLINE + 5 +105 +100 +AcDbEntity + 8 +Layer_1 + 62 +7 +100 +AcDbPolyline + 90 +2 + 70 +0 + 10 +242.659352 + 20 +114.209972 + 30 +0.0 + 10 +183.548854 + 20 +142.046895 + 30 +0.0 + 0 +LWPOLYLINE + 5 +106 +100 +AcDbEntity + 8 +Layer_1 + 62 +7 +100 +AcDbPolyline + 90 +3 + 70 +1 + 10 +227.194404 + 20 +97.026684 + 30 +0.0 + 10 +187.672839 + 20 +86.029373 + 30 +0.0 + 10 +202.450470 + 20 +54.412140 + 30 +0.0 + 0 +SPLINE + 5 +107 +100 +AcDbEntity + 8 +Layer_1 + 62 +7 +100 +AcDbSpline + 70 +0 + 71 +3 + 72 +10 + 73 +6 + 74 +4 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +57.671738 + 40 +87.553151 + 40 +100.924559 + 40 +100.924559 + 40 +100.924559 + 40 +100.924559 + 10 +268.777976 + 20 +28.637189 + 30 +0.0 + 10 +274.336610 + 20 +48.150317 + 30 +0.0 + 10 +282.775336 + 20 +77.773765 + 30 +0.0 + 10 +261.049821 + 20 +109.076545 + 30 +0.0 + 10 +247.002130 + 20 +112.997749 + 30 +0.0 + 10 +242.659352 + 20 +114.209972 + 30 +0.0 + 11 +268.777976 + 21 +28.637189 + 31 +0.0 + 11 +273.614745 + 21 +86.105747 + 31 +0.0 + 11 +255.234979 + 21 +109.665946 + 31 +0.0 + 11 +242.659352 + 21 +114.209972 + 31 +0.0 + 0 +LWPOLYLINE + 5 +108 +100 +AcDbEntity + 8 +Layer_1 + 62 +7 +100 +AcDbPolyline + 90 +4 + 70 +1 + 10 +197.639141 + 20 +200.126412 + 30 +0.0 + 10 +235.442375 + 20 +200.126412 + 30 +0.0 + 10 +235.442375 + 20 +163.010511 + 30 +0.0 + 10 +197.639141 + 20 +163.010511 + 30 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +C +330 +0 +100 +AcDbDictionary + 3 +ACAD_GROUP +350 +D + 3 +ACAD_MLINESTYLE +350 +17 + 0 +DICTIONARY + 5 +D +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +1A +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +17 +330 +C +100 +AcDbDictionary + 3 +STANDARD +350 +18 + 0 +DICTIONARY + 5 +19 +330 +C +100 +AcDbDictionary + 0 +ENDSEC + 0 +EOF diff --git a/share/extensions/tests/data/refs/addnodes.out b/share/extensions/tests/data/refs/addnodes.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/addnodes__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/addnodes__--id__p1__--id__r3.out new file mode 100644 index 0000000..5ea4d95 --- /dev/null +++ b/share/extensions/tests/data/refs/addnodes__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/convert2dashes.out b/share/extensions/tests/data/refs/convert2dashes.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/dhw_input__PAGE_001__DHW.out b/share/extensions/tests/data/refs/dhw_input__PAGE_001__DHW.out new file mode 100644 index 0000000..f6ae72b --- /dev/null +++ b/share/extensions/tests/data/refs/dhw_input__PAGE_001__DHW.out @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/dhw_input__PGLT_161__DHW.out b/share/extensions/tests/data/refs/dhw_input__PGLT_161__DHW.out new file mode 100644 index 0000000..7e90ed5 --- /dev/null +++ b/share/extensions/tests/data/refs/dhw_input__PGLT_161__DHW.out @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/dhw_input__PGLT_162__DHW.out b/share/extensions/tests/data/refs/dhw_input__PGLT_162__DHW.out new file mode 100644 index 0000000..3c0a2e7 --- /dev/null +++ b/share/extensions/tests/data/refs/dhw_input__PGLT_162__DHW.out @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/dhw_input__PGLT_163__DHW.out b/share/extensions/tests/data/refs/dhw_input__PGLT_163__DHW.out new file mode 100644 index 0000000..d02a0b6 --- /dev/null +++ b/share/extensions/tests/data/refs/dhw_input__PGLT_163__DHW.out @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/dimension__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/dimension__--id__p1__--id__r3.out new file mode 100644 index 0000000..039bb62 --- /dev/null +++ b/share/extensions/tests/data/refs/dimension__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/dimension__--id__p1__--id__r3__--type__visual.out b/share/extensions/tests/data/refs/dimension__--id__p1__--id__r3__--type__visual.out new file mode 100644 index 0000000..bc9a26d --- /dev/null +++ b/share/extensions/tests/data/refs/dimension__--id__p1__--id__r3__--type__visual.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/dm2svg.out b/share/extensions/tests/data/refs/dm2svg.out new file mode 100644 index 0000000..643324f --- /dev/null +++ b/share/extensions/tests/data/refs/dm2svg.out @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/share/extensions/tests/data/refs/docinfo.out b/share/extensions/tests/data/refs/docinfo.out new file mode 100644 index 0000000..a09b4f0 --- /dev/null +++ b/share/extensions/tests/data/refs/docinfo.out @@ -0,0 +1,9 @@ +:::SVG document related info::: +version: 0.0 +width: 1000.0 +height: 1000.0 +viewbox: [0.0, 0.0, 1000.0, 1000.0] +document-units: None +units: None +Document has 6 guides +Grid number 1: Units: None diff --git a/share/extensions/tests/data/refs/dpiswitcher.out b/share/extensions/tests/data/refs/dpiswitcher.out new file mode 100644 index 0000000..0b9cd97 --- /dev/null +++ b/share/extensions/tests/data/refs/dpiswitcher.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + diff --git a/share/extensions/tests/data/refs/dpiswitcher__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/dpiswitcher__--id__p1__--id__r3.out new file mode 100644 index 0000000..0b9cd97 --- /dev/null +++ b/share/extensions/tests/data/refs/dpiswitcher__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + diff --git a/share/extensions/tests/data/refs/draw_from_triangle__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/draw_from_triangle__--id__p1__--id__r3.out new file mode 100644 index 0000000..61c34b2 --- /dev/null +++ b/share/extensions/tests/data/refs/draw_from_triangle__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + diff --git a/share/extensions/tests/data/refs/dxf_input__test_r12__dxf.out b/share/extensions/tests/data/refs/dxf_input__test_r12__dxf.out new file mode 100644 index 0000000..1c87d26 --- /dev/null +++ b/share/extensions/tests/data/refs/dxf_input__test_r12__dxf.out @@ -0,0 +1,2 @@ + + test_r12.dxf - scale = 1.000000, origin = (0.000000, 0.000000), method = manual \ No newline at end of file diff --git a/share/extensions/tests/data/refs/dxf_input__test_r14__dxf.out b/share/extensions/tests/data/refs/dxf_input__test_r14__dxf.out new file mode 100644 index 0000000..45d20d0 --- /dev/null +++ b/share/extensions/tests/data/refs/dxf_input__test_r14__dxf.out @@ -0,0 +1,2 @@ + + test_r14.dxf - scale = 1.000000, origin = (0.000000, 0.000000), method = manual \ No newline at end of file diff --git a/share/extensions/tests/data/refs/dxf_outlines.out b/share/extensions/tests/data/refs/dxf_outlines.out new file mode 100644 index 0000000..148cf1b --- /dev/null +++ b/share/extensions/tests/data/refs/dxf_outlines.out @@ -0,0 +1,3412 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1014 + 9 +$HANDSEED + 5 +FFFF + 9 +$MEASUREMENT + 70 + 1 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 + 4 + 0 +VPORT + 5 +2E +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*ACTIVE + 70 + 0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +210.0 + 22 +148.5 + 13 +0.0 + 23 +0.0 + 14 +10.0 + 24 +10.0 + 15 +10.0 + 25 +10.0 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +341.0 + 41 +1.24 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 + 0 + 72 + 100 + 73 + 1 + 74 + 3 + 75 + 0 + 76 + 0 + 77 + 0 + 78 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +LTYPE + 5 +14 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYBLOCK + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +15 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYLAYER + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +16 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CONTINUOUS + 70 + 0 + 3 +Solid line + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +2 +100 +AcDbSymbolTable + 70 +4 + 0 +LAYER + 5 +50 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +51 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Slide3 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +52 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Slide2 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +53 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Slide1 + 70 +0 + 6 +CONTINUOUS + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +STYLE + 5 +11 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +STANDARD + 70 + 0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 + 0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 + 2 + 0 +APPID + 5 +12 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +A +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +DIMSTYLE +105 +27 +330 +A +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +ISO-25 + 70 + 0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874016 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 + 71 + 0 + 72 + 0 + 73 + 0 + 74 + 0 + 75 + 0 + 76 + 0 + 77 + 1 + 78 + 8 +170 + 0 +171 + 3 +172 + 1 +173 + 0 +174 + 0 +175 + 0 +176 + 0 +177 + 0 +178 + 0 +270 + 2 +271 + 2 +272 + 2 +273 + 2 +274 + 3 +340 +11 +275 + 0 +280 + 0 +281 + 0 +282 + 0 +283 + 0 +284 + 8 +285 + 0 +286 + 0 +287 + 3 +288 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +BLOCK_RECORD + 5 +1F +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*MODEL_SPACE + 0 +BLOCK_RECORD + 5 +1B +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*PAPER_SPACE + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +20 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*MODEL_SPACE + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*MODEL_SPACE + 1 + + 0 +ENDBLK + 5 +21 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockBegin + 2 +*PAPER_SPACE + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +LINE + 5 +100 +100 +AcDbEntity + 8 +Slide3 + 62 +7 +100 +AcDbLine + 10 +488.481675 + 20 +484.555043 + 30 +0.0 + 11 +675.000015 + 21 +484.555043 + 31 +0.0 + 0 +LINE + 5 +101 +100 +AcDbEntity + 8 +Slide3 + 62 +7 +100 +AcDbLine + 10 +675.000015 + 20 +484.555043 + 30 +0.0 + 11 +675.000015 + 21 +333.431460 + 31 +0.0 + 0 +LINE + 5 +102 +100 +AcDbEntity + 8 +Slide3 + 62 +7 +100 +AcDbLine + 10 +675.000015 + 20 +333.431460 + 30 +0.0 + 11 +488.481675 + 21 +333.431460 + 31 +0.0 + 0 +LINE + 5 +103 +100 +AcDbEntity + 8 +Slide3 + 62 +7 +100 +AcDbLine + 10 +488.481675 + 20 +333.431460 + 30 +0.0 + 11 +488.481675 + 21 +484.555043 + 31 +0.0 + 0 +SPLINE + 5 +104 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +112.500000 + 20 +450.000000 + 30 +0.0 + 10 +97.332666 + 20 +450.000000 + 30 +0.0 + 10 +83.658805 + 20 +440.863418 + 30 +0.0 + 10 +77.854518 + 20 +426.850629 + 30 +0.0 + 0 +SPLINE + 5 +105 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +77.854518 + 20 +426.850629 + 30 +0.0 + 10 +72.050230 + 20 +412.837839 + 30 +0.0 + 10 +75.258571 + 20 +396.708421 + 30 +0.0 + 10 +85.983496 + 20 +385.983496 + 30 +0.0 + 0 +SPLINE + 5 +106 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +85.983496 + 20 +385.983496 + 30 +0.0 + 10 +96.708421 + 20 +375.258571 + 30 +0.0 + 10 +112.837839 + 20 +372.050230 + 30 +0.0 + 10 +126.850629 + 20 +377.854518 + 30 +0.0 + 0 +SPLINE + 5 +107 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +126.850629 + 20 +377.854518 + 30 +0.0 + 10 +140.863418 + 20 +383.658805 + 30 +0.0 + 10 +150.000000 + 20 +397.332666 + 30 +0.0 + 10 +150.000000 + 20 +412.500000 + 30 +0.0 + 0 +SPLINE + 5 +108 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +150.000000 + 20 +412.500000 + 30 +0.0 + 10 +150.000000 + 20 +422.445618 + 30 +0.0 + 10 +146.049118 + 20 +431.983890 + 30 +0.0 + 10 +139.016504 + 20 +439.016504 + 30 +0.0 + 0 +SPLINE + 5 +109 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +139.016504 + 20 +439.016504 + 30 +0.0 + 10 +131.983890 + 20 +446.049118 + 30 +0.0 + 10 +122.445618 + 20 +450.000000 + 30 +0.0 + 10 +112.500000 + 20 +450.000000 + 30 +0.0 + 0 +LINE + 5 +10a +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbLine + 10 +112.500000 + 20 +450.000000 + 30 +0.0 + 11 +112.500000 + 21 +450.000000 + 31 +0.0 + 0 +SPLINE + 5 +10b +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +300.000000 + 20 +450.000000 + 30 +0.0 + 10 +269.665332 + 20 +450.000000 + 30 +0.0 + 10 +242.317610 + 20 +440.863418 + 30 +0.0 + 10 +230.709035 + 20 +426.850629 + 30 +0.0 + 0 +SPLINE + 5 +10c +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +230.709035 + 20 +426.850629 + 30 +0.0 + 10 +219.100460 + 20 +412.837839 + 30 +0.0 + 10 +225.517142 + 20 +396.708421 + 30 +0.0 + 10 +246.966991 + 20 +385.983496 + 30 +0.0 + 0 +SPLINE + 5 +10d +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +246.966991 + 20 +385.983496 + 30 +0.0 + 10 +268.416841 + 20 +375.258571 + 30 +0.0 + 10 +300.675678 + 20 +372.050230 + 30 +0.0 + 10 +328.701257 + 20 +377.854518 + 30 +0.0 + 0 +SPLINE + 5 +10e +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +328.701257 + 20 +377.854518 + 30 +0.0 + 10 +356.726837 + 20 +383.658805 + 30 +0.0 + 10 +375.000000 + 20 +397.332666 + 30 +0.0 + 10 +375.000000 + 20 +412.500000 + 30 +0.0 + 0 +SPLINE + 5 +10f +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +375.000000 + 20 +412.500000 + 30 +0.0 + 10 +375.000000 + 20 +422.445618 + 30 +0.0 + 10 +367.098237 + 20 +431.983890 + 30 +0.0 + 10 +353.033009 + 20 +439.016504 + 30 +0.0 + 0 +SPLINE + 5 +110 +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +353.033009 + 20 +439.016504 + 30 +0.0 + 10 +338.967780 + 20 +446.049118 + 30 +0.0 + 10 +319.891237 + 20 +450.000000 + 30 +0.0 + 10 +300.000000 + 20 +450.000000 + 30 +0.0 + 0 +LINE + 5 +111 +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbLine + 10 +300.000000 + 20 +450.000000 + 30 +0.0 + 11 +300.000000 + 21 +450.000000 + 31 +0.0 + 0 +SPLINE + 5 +112 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +587.314763 + 20 +391.632142 + 30 +0.0 + 10 +569.111537 + 20 +378.042706 + 30 +0.0 + 10 +535.392878 + 20 +371.919721 + 30 +0.0 + 10 +503.990903 + 20 +376.501328 + 30 +0.0 + 0 +SPLINE + 5 +113 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +503.990903 + 20 +376.501328 + 30 +0.0 + 10 +472.588930 + 20 +381.082935 + 30 +0.0 + 10 +450.747983 + 20 +395.312169 + 30 +0.0 + 10 +450.018645 + 20 +411.663900 + 30 +0.0 + 0 +SPLINE + 5 +114 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +450.018645 + 20 +411.663900 + 30 +0.0 + 10 +449.289307 + 20 +428.015634 + 30 +0.0 + 10 +469.839842 + 20 +442.717568 + 30 +0.0 + 10 +500.802098 + 20 +447.994582 + 30 +0.0 + 0 +SPLINE + 5 +115 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +500.802098 + 20 +447.994582 + 30 +0.0 + 10 +531.764347 + 20 +453.271596 + 30 +0.0 + 10 +565.995413 + 20 +447.906301 + 30 +0.0 + 10 +585.392197 + 20 +434.736135 + 30 +0.0 + 0 +LINE + 5 +116 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +585.392197 + 20 +434.736135 + 30 +0.0 + 11 +525.000000 + 21 +412.500000 + 31 +0.0 + 0 +LINE + 5 +117 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +525.000000 + 20 +412.500000 + 30 +0.0 + 11 +587.314763 + 21 +391.632142 + 31 +0.0 + 0 +LINE + 5 +118 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbLine + 10 +75.000000 + 20 +300.000000 + 30 +0.0 + 11 +150.000000 + 21 +225.000000 + 31 +0.0 + 0 +LINE + 5 +119 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbLine + 10 +150.000000 + 20 +225.000000 + 30 +0.0 + 11 +225.000000 + 21 +300.000000 + 31 +0.0 + 0 +LINE + 5 +11a +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbLine + 10 +225.000000 + 20 +300.000000 + 30 +0.0 + 11 +300.000000 + 21 +225.000000 + 31 +0.0 + 0 +SPLINE + 5 +11b +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +375.000000 + 20 +300.000000 + 30 +0.0 + 10 +375.000000 + 20 +300.000000 + 30 +0.0 + 10 +375.000000 + 20 +225.000000 + 30 +0.0 + 10 +450.000000 + 20 +225.000000 + 30 +0.0 + 0 +SPLINE + 5 +11c +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +450.000000 + 20 +225.000000 + 30 +0.0 + 10 +525.000000 + 20 +225.000000 + 30 +0.0 + 10 +450.000000 + 20 +300.000000 + 30 +0.0 + 10 +525.000000 + 20 +300.000000 + 30 +0.0 + 0 +SPLINE + 5 +11d +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +525.000000 + 20 +300.000000 + 30 +0.0 + 10 +600.000000 + 20 +300.000000 + 30 +0.0 + 10 +600.000000 + 20 +225.000000 + 30 +0.0 + 10 +600.000000 + 20 +225.000000 + 30 +0.0 + 0 +LINE + 5 +11e +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +183.624900 + 20 +83.144963 + 30 +0.0 + 11 +149.620747 + 21 +89.245807 + 31 +0.0 + 0 +LINE + 5 +11f +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +149.620747 + 20 +89.245807 + 30 +0.0 + 11 +125.317537 + 21 +64.692660 + 31 +0.0 + 0 +LINE + 5 +120 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +125.317537 + 20 +64.692660 + 30 +0.0 + 11 +120.611925 + 21 +98.917800 + 31 +0.0 + 0 +LINE + 5 +121 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +120.611925 + 20 +98.917800 + 30 +0.0 + 11 +89.750385 + 21 +114.444180 + 31 +0.0 + 0 +LINE + 5 +122 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +89.750385 + 20 +114.444180 + 30 +0.0 + 11 +120.846315 + 21 +129.495637 + 31 +0.0 + 0 +LINE + 5 +123 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +120.846315 + 20 +129.495637 + 30 +0.0 + 11 +126.076042 + 21 +163.644615 + 31 +0.0 + 0 +LINE + 5 +124 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +126.076042 + 20 +163.644615 + 30 +0.0 + 11 +150.000000 + 21 +138.721785 + 31 +0.0 + 0 +LINE + 5 +125 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +150.000000 + 20 +138.721785 + 30 +0.0 + 11 +184.093680 + 21 +144.300637 + 31 +0.0 + 0 +LINE + 5 +126 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +184.093680 + 20 +144.300637 + 30 +0.0 + 11 +167.783572 + 21 +113.846025 + 31 +0.0 + 0 +LINE + 5 +127 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +167.783572 + 20 +113.846025 + 30 +0.0 + 11 +183.624900 + 21 +83.144963 + 31 +0.0 + 0 +LINE + 5 +128 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +333.624900 + 20 +80.897798 + 30 +0.0 + 11 +299.620747 + 21 +86.998642 + 31 +0.0 + 0 +LINE + 5 +129 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +299.620747 + 20 +86.998642 + 30 +0.0 + 11 +275.317537 + 21 +62.445495 + 31 +0.0 + 0 +LINE + 5 +12a +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +275.317537 + 20 +62.445495 + 30 +0.0 + 11 +270.611925 + 21 +96.670635 + 31 +0.0 + 0 +LINE + 5 +12b +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +270.611925 + 20 +96.670635 + 30 +0.0 + 11 +239.750385 + 21 +112.197015 + 31 +0.0 + 0 +LINE + 5 +12c +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +239.750385 + 20 +112.197015 + 30 +0.0 + 11 +270.846315 + 21 +127.248472 + 31 +0.0 + 0 +LINE + 5 +12d +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +270.846315 + 20 +127.248472 + 30 +0.0 + 11 +276.076042 + 21 +161.397450 + 31 +0.0 + 0 +LINE + 5 +12e +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +276.076042 + 20 +161.397450 + 30 +0.0 + 11 +300.000000 + 21 +136.474620 + 31 +0.0 + 0 +LINE + 5 +12f +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +300.000000 + 20 +136.474620 + 30 +0.0 + 11 +334.093680 + 21 +142.053472 + 31 +0.0 + 0 +LINE + 5 +130 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +334.093680 + 20 +142.053472 + 30 +0.0 + 11 +317.783572 + 21 +111.598860 + 31 +0.0 + 0 +LINE + 5 +131 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +317.783572 + 20 +111.598860 + 30 +0.0 + 11 +333.624900 + 21 +80.897798 + 31 +0.0 + 0 +LINE + 5 +132 +100 +AcDbEntity + 8 +Slide1 + 62 +7 +100 +AcDbLine + 10 +75.000000 + 20 +600.000000 + 30 +0.0 + 11 +150.000000 + 21 +600.000000 + 31 +0.0 + 0 +LINE + 5 +133 +100 +AcDbEntity + 8 +Slide1 + 62 +7 +100 +AcDbLine + 10 +150.000000 + 20 +600.000000 + 30 +0.0 + 11 +150.000000 + 21 +525.000000 + 31 +0.0 + 0 +LINE + 5 +134 +100 +AcDbEntity + 8 +Slide1 + 62 +7 +100 +AcDbLine + 10 +150.000000 + 20 +525.000000 + 30 +0.0 + 11 +75.000000 + 21 +525.000000 + 31 +0.0 + 0 +LINE + 5 +135 +100 +AcDbEntity + 8 +Slide1 + 62 +7 +100 +AcDbLine + 10 +75.000000 + 20 +525.000000 + 30 +0.0 + 11 +75.000000 + 21 +600.000000 + 31 +0.0 + 0 +LINE + 5 +136 +100 +AcDbEntity + 8 +Slide1 + 62 +1 +100 +AcDbLine + 10 +225.000000 + 20 +600.000000 + 30 +0.0 + 11 +375.000000 + 21 +600.000000 + 31 +0.0 + 0 +LINE + 5 +137 +100 +AcDbEntity + 8 +Slide1 + 62 +1 +100 +AcDbLine + 10 +375.000000 + 20 +600.000000 + 30 +0.0 + 11 +375.000000 + 21 +525.000000 + 31 +0.0 + 0 +LINE + 5 +138 +100 +AcDbEntity + 8 +Slide1 + 62 +1 +100 +AcDbLine + 10 +375.000000 + 20 +525.000000 + 30 +0.0 + 11 +225.000000 + 21 +525.000000 + 31 +0.0 + 0 +LINE + 5 +139 +100 +AcDbEntity + 8 +Slide1 + 62 +1 +100 +AcDbLine + 10 +225.000000 + 20 +525.000000 + 30 +0.0 + 11 +225.000000 + 21 +600.000000 + 31 +0.0 + 0 +LINE + 5 +13a +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +483.707420 + 20 +600.000000 + 30 +0.0 + 11 +566.292580 + 21 +600.000000 + 31 +0.0 + 0 +SPLINE + 5 +13b +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +566.292580 + 20 +600.000000 + 30 +0.0 + 10 +575.232344 + 20 +600.000000 + 30 +0.0 + 10 +583.805958 + 20 +596.922200 + 30 +0.0 + 10 +590.127325 + 20 +591.443682 + 30 +0.0 + 0 +SPLINE + 5 +13c +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +590.127325 + 20 +591.443682 + 30 +0.0 + 10 +596.448693 + 20 +585.965163 + 30 +0.0 + 10 +600.000000 + 20 +578.534697 + 30 +0.0 + 10 +600.000000 + 20 +570.786901 + 30 +0.0 + 0 +LINE + 5 +13d +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +600.000000 + 20 +570.786901 + 30 +0.0 + 11 +600.000000 + 21 +554.213099 + 31 +0.0 + 0 +SPLINE + 5 +13e +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +600.000000 + 20 +554.213099 + 30 +0.0 + 10 +600.000000 + 20 +546.465303 + 30 +0.0 + 10 +596.448693 + 20 +539.034837 + 30 +0.0 + 10 +590.127325 + 20 +533.556318 + 30 +0.0 + 0 +SPLINE + 5 +13f +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +590.127325 + 20 +533.556318 + 30 +0.0 + 10 +583.805958 + 20 +528.077800 + 30 +0.0 + 10 +575.232344 + 20 +525.000000 + 30 +0.0 + 10 +566.292581 + 20 +525.000000 + 30 +0.0 + 0 +LINE + 5 +140 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +566.292581 + 20 +525.000000 + 30 +0.0 + 11 +483.707420 + 21 +525.000000 + 31 +0.0 + 0 +SPLINE + 5 +141 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +483.707420 + 20 +525.000000 + 30 +0.0 + 10 +474.767656 + 20 +525.000000 + 30 +0.0 + 10 +466.194042 + 20 +528.077800 + 30 +0.0 + 10 +459.872675 + 20 +533.556318 + 30 +0.0 + 0 +SPLINE + 5 +142 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +459.872675 + 20 +533.556318 + 30 +0.0 + 10 +453.551307 + 20 +539.034837 + 30 +0.0 + 10 +450.000000 + 20 +546.465303 + 30 +0.0 + 10 +450.000000 + 20 +554.213099 + 30 +0.0 + 0 +LINE + 5 +143 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +450.000000 + 20 +554.213099 + 30 +0.0 + 11 +450.000000 + 21 +570.786901 + 31 +0.0 + 0 +SPLINE + 5 +144 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +450.000000 + 20 +570.786901 + 30 +0.0 + 10 +450.000000 + 20 +578.534697 + 30 +0.0 + 10 +453.551307 + 20 +585.965163 + 30 +0.0 + 10 +459.872675 + 20 +591.443682 + 30 +0.0 + 0 +SPLINE + 5 +145 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +459.872675 + 20 +591.443682 + 30 +0.0 + 10 +466.194042 + 20 +596.922200 + 30 +0.0 + 10 +474.767656 + 20 +600.000000 + 30 +0.0 + 10 +483.707420 + 20 +600.000000 + 30 +0.0 + 0 +LINE + 5 +146 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +483.707420 + 20 +600.000000 + 30 +0.0 + 11 +483.707420 + 21 +600.000000 + 31 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +C +330 +0 +100 +AcDbDictionary + 3 +ACAD_GROUP +350 +D + 3 +ACAD_MLINESTYLE +350 +17 + 0 +DICTIONARY + 5 +D +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +1A +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +17 +330 +C +100 +AcDbDictionary + 3 +STANDARD +350 +18 + 0 +DICTIONARY + 5 +19 +330 +C +100 +AcDbDictionary + 0 +ENDSEC + 0 +EOF diff --git a/share/extensions/tests/data/refs/dxf_outlines__--POLY__true.out b/share/extensions/tests/data/refs/dxf_outlines__--POLY__true.out new file mode 100644 index 0000000..34e4875 --- /dev/null +++ b/share/extensions/tests/data/refs/dxf_outlines__--POLY__true.out @@ -0,0 +1,2880 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1014 + 9 +$HANDSEED + 5 +FFFF + 9 +$MEASUREMENT + 70 + 1 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 + 4 + 0 +VPORT + 5 +2E +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*ACTIVE + 70 + 0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +210.0 + 22 +148.5 + 13 +0.0 + 23 +0.0 + 14 +10.0 + 24 +10.0 + 15 +10.0 + 25 +10.0 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +341.0 + 41 +1.24 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 + 0 + 72 + 100 + 73 + 1 + 74 + 3 + 75 + 0 + 76 + 0 + 77 + 0 + 78 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +LTYPE + 5 +14 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYBLOCK + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +15 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYLAYER + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +16 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CONTINUOUS + 70 + 0 + 3 +Solid line + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +2 +100 +AcDbSymbolTable + 70 +4 + 0 +LAYER + 5 +50 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +51 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Slide3 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +52 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Slide2 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +53 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Slide1 + 70 +0 + 6 +CONTINUOUS + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +STYLE + 5 +11 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +STANDARD + 70 + 0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 + 0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 + 2 + 0 +APPID + 5 +12 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +A +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +DIMSTYLE +105 +27 +330 +A +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +ISO-25 + 70 + 0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874016 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 + 71 + 0 + 72 + 0 + 73 + 0 + 74 + 0 + 75 + 0 + 76 + 0 + 77 + 1 + 78 + 8 +170 + 0 +171 + 3 +172 + 1 +173 + 0 +174 + 0 +175 + 0 +176 + 0 +177 + 0 +178 + 0 +270 + 2 +271 + 2 +272 + 2 +273 + 2 +274 + 3 +340 +11 +275 + 0 +280 + 0 +281 + 0 +282 + 0 +283 + 0 +284 + 8 +285 + 0 +286 + 0 +287 + 3 +288 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +BLOCK_RECORD + 5 +1F +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*MODEL_SPACE + 0 +BLOCK_RECORD + 5 +1B +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*PAPER_SPACE + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +20 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*MODEL_SPACE + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*MODEL_SPACE + 1 + + 0 +ENDBLK + 5 +21 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockBegin + 2 +*PAPER_SPACE + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +SPLINE + 5 +100 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +112.500000 + 20 +450.000000 + 30 +0.0 + 10 +97.332666 + 20 +450.000000 + 30 +0.0 + 10 +83.658805 + 20 +440.863418 + 30 +0.0 + 10 +77.854518 + 20 +426.850629 + 30 +0.0 + 0 +SPLINE + 5 +101 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +77.854518 + 20 +426.850629 + 30 +0.0 + 10 +72.050230 + 20 +412.837839 + 30 +0.0 + 10 +75.258571 + 20 +396.708421 + 30 +0.0 + 10 +85.983496 + 20 +385.983496 + 30 +0.0 + 0 +SPLINE + 5 +102 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +85.983496 + 20 +385.983496 + 30 +0.0 + 10 +96.708421 + 20 +375.258571 + 30 +0.0 + 10 +112.837839 + 20 +372.050230 + 30 +0.0 + 10 +126.850629 + 20 +377.854518 + 30 +0.0 + 0 +SPLINE + 5 +103 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +126.850629 + 20 +377.854518 + 30 +0.0 + 10 +140.863418 + 20 +383.658805 + 30 +0.0 + 10 +150.000000 + 20 +397.332666 + 30 +0.0 + 10 +150.000000 + 20 +412.500000 + 30 +0.0 + 0 +SPLINE + 5 +104 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +150.000000 + 20 +412.500000 + 30 +0.0 + 10 +150.000000 + 20 +422.445618 + 30 +0.0 + 10 +146.049118 + 20 +431.983890 + 30 +0.0 + 10 +139.016504 + 20 +439.016504 + 30 +0.0 + 0 +SPLINE + 5 +105 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +139.016504 + 20 +439.016504 + 30 +0.0 + 10 +131.983890 + 20 +446.049118 + 30 +0.0 + 10 +122.445618 + 20 +450.000000 + 30 +0.0 + 10 +112.500000 + 20 +450.000000 + 30 +0.0 + 0 +LWPOLYLINE + 5 +106 +100 +AcDbEntity + 8 +Slide3 + 62 +7 +100 +AcDbPolyline + 90 +4 + 70 +1 + 10 +488.481675 + 20 +484.555043 + 30 +0.0 + 10 +675.000015 + 20 +484.555043 + 30 +0.0 + 10 +675.000015 + 20 +333.431460 + 30 +0.0 + 10 +488.481675 + 20 +333.431460 + 30 +0.0 + 0 +SPLINE + 5 +107 +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +300.000000 + 20 +450.000000 + 30 +0.0 + 10 +269.665332 + 20 +450.000000 + 30 +0.0 + 10 +242.317610 + 20 +440.863418 + 30 +0.0 + 10 +230.709035 + 20 +426.850629 + 30 +0.0 + 0 +SPLINE + 5 +108 +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +230.709035 + 20 +426.850629 + 30 +0.0 + 10 +219.100460 + 20 +412.837839 + 30 +0.0 + 10 +225.517142 + 20 +396.708421 + 30 +0.0 + 10 +246.966991 + 20 +385.983496 + 30 +0.0 + 0 +SPLINE + 5 +109 +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +246.966991 + 20 +385.983496 + 30 +0.0 + 10 +268.416841 + 20 +375.258571 + 30 +0.0 + 10 +300.675678 + 20 +372.050230 + 30 +0.0 + 10 +328.701257 + 20 +377.854518 + 30 +0.0 + 0 +SPLINE + 5 +10a +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +328.701257 + 20 +377.854518 + 30 +0.0 + 10 +356.726837 + 20 +383.658805 + 30 +0.0 + 10 +375.000000 + 20 +397.332666 + 30 +0.0 + 10 +375.000000 + 20 +412.500000 + 30 +0.0 + 0 +SPLINE + 5 +10b +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +375.000000 + 20 +412.500000 + 30 +0.0 + 10 +375.000000 + 20 +422.445618 + 30 +0.0 + 10 +367.098237 + 20 +431.983890 + 30 +0.0 + 10 +353.033009 + 20 +439.016504 + 30 +0.0 + 0 +SPLINE + 5 +10c +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +353.033009 + 20 +439.016504 + 30 +0.0 + 10 +338.967780 + 20 +446.049118 + 30 +0.0 + 10 +319.891237 + 20 +450.000000 + 30 +0.0 + 10 +300.000000 + 20 +450.000000 + 30 +0.0 + 0 +LWPOLYLINE + 5 +10d +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbPolyline + 90 +1 + 70 +1 + 10 +112.500000 + 20 +450.000000 + 30 +0.0 + 0 +SPLINE + 5 +10e +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +587.314763 + 20 +391.632142 + 30 +0.0 + 10 +569.111537 + 20 +378.042706 + 30 +0.0 + 10 +535.392878 + 20 +371.919721 + 30 +0.0 + 10 +503.990903 + 20 +376.501328 + 30 +0.0 + 0 +SPLINE + 5 +10f +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +503.990903 + 20 +376.501328 + 30 +0.0 + 10 +472.588930 + 20 +381.082935 + 30 +0.0 + 10 +450.747983 + 20 +395.312169 + 30 +0.0 + 10 +450.018645 + 20 +411.663900 + 30 +0.0 + 0 +SPLINE + 5 +110 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +450.018645 + 20 +411.663900 + 30 +0.0 + 10 +449.289307 + 20 +428.015634 + 30 +0.0 + 10 +469.839842 + 20 +442.717568 + 30 +0.0 + 10 +500.802098 + 20 +447.994582 + 30 +0.0 + 0 +SPLINE + 5 +111 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +500.802098 + 20 +447.994582 + 30 +0.0 + 10 +531.764347 + 20 +453.271596 + 30 +0.0 + 10 +565.995413 + 20 +447.906301 + 30 +0.0 + 10 +585.392197 + 20 +434.736135 + 30 +0.0 + 0 +LWPOLYLINE + 5 +112 +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbPolyline + 90 +1 + 70 +1 + 10 +300.000000 + 20 +450.000000 + 30 +0.0 + 0 +LWPOLYLINE + 5 +113 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbPolyline + 90 +3 + 70 +0 + 10 +585.392197 + 20 +434.736135 + 30 +0.0 + 10 +525.000000 + 20 +412.500000 + 30 +0.0 + 10 +587.314763 + 20 +391.632142 + 30 +0.0 + 0 +SPLINE + 5 +114 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +375.000000 + 20 +300.000000 + 30 +0.0 + 10 +375.000000 + 20 +300.000000 + 30 +0.0 + 10 +375.000000 + 20 +225.000000 + 30 +0.0 + 10 +450.000000 + 20 +225.000000 + 30 +0.0 + 0 +SPLINE + 5 +115 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +450.000000 + 20 +225.000000 + 30 +0.0 + 10 +525.000000 + 20 +225.000000 + 30 +0.0 + 10 +450.000000 + 20 +300.000000 + 30 +0.0 + 10 +525.000000 + 20 +300.000000 + 30 +0.0 + 0 +SPLINE + 5 +116 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +525.000000 + 20 +300.000000 + 30 +0.0 + 10 +600.000000 + 20 +300.000000 + 30 +0.0 + 10 +600.000000 + 20 +225.000000 + 30 +0.0 + 10 +600.000000 + 20 +225.000000 + 30 +0.0 + 0 +LWPOLYLINE + 5 +117 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbPolyline + 90 +4 + 70 +0 + 10 +75.000000 + 20 +300.000000 + 30 +0.0 + 10 +150.000000 + 20 +225.000000 + 30 +0.0 + 10 +225.000000 + 20 +300.000000 + 30 +0.0 + 10 +300.000000 + 20 +225.000000 + 30 +0.0 + 0 +LWPOLYLINE + 5 +118 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbPolyline + 90 +10 + 70 +1 + 10 +183.624900 + 20 +83.144963 + 30 +0.0 + 10 +149.620747 + 20 +89.245807 + 30 +0.0 + 10 +125.317537 + 20 +64.692660 + 30 +0.0 + 10 +120.611925 + 20 +98.917800 + 30 +0.0 + 10 +89.750385 + 20 +114.444180 + 30 +0.0 + 10 +120.846315 + 20 +129.495637 + 30 +0.0 + 10 +126.076042 + 20 +163.644615 + 30 +0.0 + 10 +150.000000 + 20 +138.721785 + 30 +0.0 + 10 +184.093680 + 20 +144.300637 + 30 +0.0 + 10 +167.783572 + 20 +113.846025 + 30 +0.0 + 0 +LWPOLYLINE + 5 +119 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbPolyline + 90 +10 + 70 +1 + 10 +333.624900 + 20 +80.897798 + 30 +0.0 + 10 +299.620747 + 20 +86.998642 + 30 +0.0 + 10 +275.317537 + 20 +62.445495 + 30 +0.0 + 10 +270.611925 + 20 +96.670635 + 30 +0.0 + 10 +239.750385 + 20 +112.197015 + 30 +0.0 + 10 +270.846315 + 20 +127.248472 + 30 +0.0 + 10 +276.076042 + 20 +161.397450 + 30 +0.0 + 10 +300.000000 + 20 +136.474620 + 30 +0.0 + 10 +334.093680 + 20 +142.053472 + 30 +0.0 + 10 +317.783572 + 20 +111.598860 + 30 +0.0 + 0 +LWPOLYLINE + 5 +11a +100 +AcDbEntity + 8 +Slide1 + 62 +7 +100 +AcDbPolyline + 90 +4 + 70 +1 + 10 +75.000000 + 20 +600.000000 + 30 +0.0 + 10 +150.000000 + 20 +600.000000 + 30 +0.0 + 10 +150.000000 + 20 +525.000000 + 30 +0.0 + 10 +75.000000 + 20 +525.000000 + 30 +0.0 + 0 +LWPOLYLINE + 5 +11b +100 +AcDbEntity + 8 +Slide1 + 62 +1 +100 +AcDbPolyline + 90 +4 + 70 +1 + 10 +225.000000 + 20 +600.000000 + 30 +0.0 + 10 +375.000000 + 20 +600.000000 + 30 +0.0 + 10 +375.000000 + 20 +525.000000 + 30 +0.0 + 10 +225.000000 + 20 +525.000000 + 30 +0.0 + 0 +SPLINE + 5 +11c +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +566.292580 + 20 +600.000000 + 30 +0.0 + 10 +575.232344 + 20 +600.000000 + 30 +0.0 + 10 +583.805958 + 20 +596.922200 + 30 +0.0 + 10 +590.127325 + 20 +591.443682 + 30 +0.0 + 0 +SPLINE + 5 +11d +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +590.127325 + 20 +591.443682 + 30 +0.0 + 10 +596.448693 + 20 +585.965163 + 30 +0.0 + 10 +600.000000 + 20 +578.534697 + 30 +0.0 + 10 +600.000000 + 20 +570.786901 + 30 +0.0 + 0 +LWPOLYLINE + 5 +11e +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbPolyline + 90 +2 + 70 +0 + 10 +483.707420 + 20 +600.000000 + 30 +0.0 + 10 +566.292580 + 20 +600.000000 + 30 +0.0 + 0 +SPLINE + 5 +11f +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +600.000000 + 20 +554.213099 + 30 +0.0 + 10 +600.000000 + 20 +546.465303 + 30 +0.0 + 10 +596.448693 + 20 +539.034837 + 30 +0.0 + 10 +590.127325 + 20 +533.556318 + 30 +0.0 + 0 +SPLINE + 5 +120 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +590.127325 + 20 +533.556318 + 30 +0.0 + 10 +583.805958 + 20 +528.077800 + 30 +0.0 + 10 +575.232344 + 20 +525.000000 + 30 +0.0 + 10 +566.292581 + 20 +525.000000 + 30 +0.0 + 0 +LWPOLYLINE + 5 +121 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbPolyline + 90 +2 + 70 +0 + 10 +600.000000 + 20 +570.786901 + 30 +0.0 + 10 +600.000000 + 20 +554.213099 + 30 +0.0 + 0 +SPLINE + 5 +122 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +483.707420 + 20 +525.000000 + 30 +0.0 + 10 +474.767656 + 20 +525.000000 + 30 +0.0 + 10 +466.194042 + 20 +528.077800 + 30 +0.0 + 10 +459.872675 + 20 +533.556318 + 30 +0.0 + 0 +SPLINE + 5 +123 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +459.872675 + 20 +533.556318 + 30 +0.0 + 10 +453.551307 + 20 +539.034837 + 30 +0.0 + 10 +450.000000 + 20 +546.465303 + 30 +0.0 + 10 +450.000000 + 20 +554.213099 + 30 +0.0 + 0 +LWPOLYLINE + 5 +124 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbPolyline + 90 +2 + 70 +0 + 10 +566.292581 + 20 +525.000000 + 30 +0.0 + 10 +483.707420 + 20 +525.000000 + 30 +0.0 + 0 +SPLINE + 5 +125 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +450.000000 + 20 +570.786901 + 30 +0.0 + 10 +450.000000 + 20 +578.534697 + 30 +0.0 + 10 +453.551307 + 20 +585.965163 + 30 +0.0 + 10 +459.872675 + 20 +591.443682 + 30 +0.0 + 0 +SPLINE + 5 +126 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +459.872675 + 20 +591.443682 + 30 +0.0 + 10 +466.194042 + 20 +596.922200 + 30 +0.0 + 10 +474.767656 + 20 +600.000000 + 30 +0.0 + 10 +483.707420 + 20 +600.000000 + 30 +0.0 + 0 +LWPOLYLINE + 5 +127 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbPolyline + 90 +2 + 70 +0 + 10 +450.000000 + 20 +554.213099 + 30 +0.0 + 10 +450.000000 + 20 +570.786901 + 30 +0.0 + 0 +LWPOLYLINE + 5 +128 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbPolyline + 90 +1 + 70 +1 + 10 +483.707420 + 20 +600.000000 + 30 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +C +330 +0 +100 +AcDbDictionary + 3 +ACAD_GROUP +350 +D + 3 +ACAD_MLINESTYLE +350 +17 + 0 +DICTIONARY + 5 +D +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +1A +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +17 +330 +C +100 +AcDbDictionary + 3 +STANDARD +350 +18 + 0 +DICTIONARY + 5 +19 +330 +C +100 +AcDbDictionary + 0 +ENDSEC + 0 +EOF diff --git a/share/extensions/tests/data/refs/dxf_outlines__--ROBO__true.out b/share/extensions/tests/data/refs/dxf_outlines__--ROBO__true.out new file mode 100644 index 0000000..e793414 --- /dev/null +++ b/share/extensions/tests/data/refs/dxf_outlines__--ROBO__true.out @@ -0,0 +1,3352 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1014 + 9 +$HANDSEED + 5 +FFFF + 9 +$MEASUREMENT + 70 + 1 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 + 4 + 0 +VPORT + 5 +2E +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*ACTIVE + 70 + 0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +210.0 + 22 +148.5 + 13 +0.0 + 23 +0.0 + 14 +10.0 + 24 +10.0 + 15 +10.0 + 25 +10.0 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +341.0 + 41 +1.24 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 + 0 + 72 + 100 + 73 + 1 + 74 + 3 + 75 + 0 + 76 + 0 + 77 + 0 + 78 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +LTYPE + 5 +14 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYBLOCK + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +15 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYLAYER + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +16 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CONTINUOUS + 70 + 0 + 3 +Solid line + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +2 +100 +AcDbSymbolTable + 70 +4 + 0 +LAYER + 5 +50 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +51 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Slide3 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +52 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Slide2 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +53 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Slide1 + 70 +0 + 6 +CONTINUOUS + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +STYLE + 5 +11 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +STANDARD + 70 + 0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 + 0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 + 2 + 0 +APPID + 5 +12 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +A +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +DIMSTYLE +105 +27 +330 +A +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +ISO-25 + 70 + 0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874016 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 + 71 + 0 + 72 + 0 + 73 + 0 + 74 + 0 + 75 + 0 + 76 + 0 + 77 + 1 + 78 + 8 +170 + 0 +171 + 3 +172 + 1 +173 + 0 +174 + 0 +175 + 0 +176 + 0 +177 + 0 +178 + 0 +270 + 2 +271 + 2 +272 + 2 +273 + 2 +274 + 3 +340 +11 +275 + 0 +280 + 0 +281 + 0 +282 + 0 +283 + 0 +284 + 8 +285 + 0 +286 + 0 +287 + 3 +288 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +BLOCK_RECORD + 5 +1F +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*MODEL_SPACE + 0 +BLOCK_RECORD + 5 +1B +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*PAPER_SPACE + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +20 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*MODEL_SPACE + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*MODEL_SPACE + 1 + + 0 +ENDBLK + 5 +21 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockBegin + 2 +*PAPER_SPACE + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +LINE + 5 +100 +100 +AcDbEntity + 8 +Slide3 + 62 +7 +100 +AcDbLine + 10 +488.481675 + 20 +484.555043 + 30 +0.0 + 11 +675.000015 + 21 +484.555043 + 31 +0.0 + 0 +LINE + 5 +101 +100 +AcDbEntity + 8 +Slide3 + 62 +7 +100 +AcDbLine + 10 +675.000015 + 20 +484.555043 + 30 +0.0 + 11 +675.000015 + 21 +333.431460 + 31 +0.0 + 0 +LINE + 5 +102 +100 +AcDbEntity + 8 +Slide3 + 62 +7 +100 +AcDbLine + 10 +675.000015 + 20 +333.431460 + 30 +0.0 + 11 +488.481675 + 21 +333.431460 + 31 +0.0 + 0 +LINE + 5 +103 +100 +AcDbEntity + 8 +Slide3 + 62 +7 +100 +AcDbLine + 10 +488.481675 + 20 +333.431460 + 30 +0.0 + 11 +488.481675 + 21 +484.555043 + 31 +0.0 + 0 +LINE + 5 +104 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbLine + 10 +112.500000 + 20 +450.000000 + 30 +0.0 + 11 +112.500000 + 21 +450.000000 + 31 +0.0 + 0 +SPLINE + 5 +105 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +0 + 71 +3 + 72 +25 + 73 +21 + 74 +19 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +14.719403 + 40 +29.176694 + 40 +43.896097 + 40 +58.615501 + 40 +73.072791 + 40 +87.792195 + 40 +102.511598 + 40 +116.968889 + 40 +131.688292 + 40 +146.407696 + 40 +160.864986 + 40 +175.584390 + 40 +185.400948 + 40 +195.136270 + 40 +204.952828 + 40 +214.769386 + 40 +224.504709 + 40 +234.321267 + 40 +234.321267 + 40 +234.321267 + 40 +234.321267 + 10 +112.500000 + 20 +450.000000 + 30 +0.0 + 10 +107.561852 + 20 +449.430182 + 30 +0.0 + 10 +97.773491 + 20 +448.300694 + 30 +0.0 + 10 +85.299689 + 20 +439.636229 + 30 +0.0 + 10 +76.941599 + 20 +427.247780 + 30 +0.0 + 10 +74.028507 + 20 +412.490567 + 30 +0.0 + 10 +76.955275 + 20 +397.783055 + 30 +0.0 + 10 +85.284776 + 20 +385.284419 + 30 +0.0 + 10 +97.781711 + 20 +376.955346 + 30 +0.0 + 10 +112.495527 + 20 +374.028572 + 30 +0.0 + 10 +127.228881 + 20 +376.941259 + 30 +0.0 + 10 +139.706194 + 20 +385.300972 + 30 +0.0 + 10 +148.042805 + 20 +397.768756 + 30 +0.0 + 10 +150.654153 + 20 +410.870662 + 30 +0.0 + 10 +149.136137 + 20 +422.315952 + 30 +0.0 + 10 +145.348676 + 20 +431.462699 + 30 +0.0 + 10 +139.322768 + 20 +439.331018 + 30 +0.0 + 10 +131.464345 + 20 +445.317462 + 30 +0.0 + 10 +122.317524 + 20 +449.252106 + 30 +0.0 + 10 +115.781560 + 20 +449.750012 + 30 +0.0 + 10 +112.500000 + 20 +450.000000 + 30 +0.0 + 11 +112.500000 + 21 +450.000000 + 31 +0.0 + 11 +98.066642 + 21 +447.112264 + 31 +0.0 + 11 +86.045844 + 21 +439.080224 + 31 +0.0 + 11 +77.854518 + 21 +426.850629 + 31 +0.0 + 11 +74.999030 + 21 +412.410856 + 31 +0.0 + 11 +77.819508 + 21 +398.231358 + 31 +0.0 + 11 +85.983496 + 21 +385.983496 + 31 +0.0 + 11 +98.231358 + 21 +377.819508 + 31 +0.0 + 11 +112.410856 + 21 +374.999030 + 31 +0.0 + 11 +126.850629 + 21 +377.854518 + 31 +0.0 + 11 +139.080224 + 21 +386.045844 + 31 +0.0 + 11 +147.112264 + 21 +398.066642 + 31 +0.0 + 11 +150.000000 + 21 +412.500000 + 31 +0.0 + 11 +148.715230 + 21 +422.232121 + 31 +0.0 + 11 +144.989684 + 21 +431.226386 + 31 +0.0 + 11 +139.016504 + 21 +439.016504 + 31 +0.0 + 11 +131.226386 + 21 +444.989684 + 31 +0.0 + 11 +122.232121 + 21 +448.715230 + 31 +0.0 + 11 +112.500000 + 21 +450.000000 + 31 +0.0 + 0 +LINE + 5 +106 +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbLine + 10 +300.000000 + 20 +450.000000 + 30 +0.0 + 11 +300.000000 + 21 +450.000000 + 31 +0.0 + 0 +SPLINE + 5 +107 +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +0 + 71 +3 + 72 +25 + 73 +21 + 74 +19 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +29.010796 + 40 +54.358617 + 40 +74.802548 + 40 +90.330660 + 40 +105.591018 + 40 +126.002116 + 40 +151.822481 + 40 +180.321389 + 40 +209.341760 + 40 +235.136136 + 40 +255.199891 + 40 +270.745884 + 40 +280.811504 + 40 +292.491213 + 40 +306.753100 + 40 +323.439101 + 40 +341.809374 + 40 +361.315971 + 40 +361.315971 + 40 +361.315971 + 40 +361.315971 + 10 +300.000000 + 20 +450.000000 + 30 +0.0 + 10 +290.305002 + 20 +449.486685 + 30 +0.0 + 10 +272.139122 + 20 +448.524868 + 30 +0.0 + 10 +248.195734 + 20 +440.689710 + 30 +0.0 + 10 +230.602913 + 20 +429.477726 + 30 +0.0 + 10 +222.591013 + 20 +412.489527 + 30 +0.0 + 10 +230.594261 + 20 +395.578337 + 30 +0.0 + 10 +248.211541 + 20 +384.232094 + 30 +0.0 + 10 +272.152557 + 20 +376.690134 + 30 +0.0 + 10 +299.994278 + 20 +374.112398 + 30 +0.0 + 10 +327.837007 + 20 +376.671686 + 30 +0.0 + 10 +351.818560 + 20 +384.253523 + 30 +0.0 + 10 +369.396163 + 20 +395.600689 + 30 +0.0 + 10 +376.552453 + 20 +410.631567 + 30 +0.0 + 10 +372.866676 + 20 +423.160090 + 30 +0.0 + 10 +364.703798 + 20 +432.276506 + 30 +0.0 + 10 +352.528624 + 20 +439.856101 + 30 +0.0 + 10 +337.031906 + 20 +445.559527 + 30 +0.0 + 10 +319.142731 + 20 +449.292775 + 30 +0.0 + 10 +306.507267 + 20 +449.759590 + 30 +0.0 + 10 +300.000000 + 20 +450.000000 + 30 +0.0 + 11 +300.000000 + 21 +450.000000 + 31 +0.0 + 11 +271.133284 + 21 +447.112264 + 31 +0.0 + 11 +247.091689 + 21 +439.080224 + 31 +0.0 + 11 +230.709035 + 21 +426.850629 + 31 +0.0 + 11 +224.998061 + 21 +412.410856 + 31 +0.0 + 11 +230.639016 + 21 +398.231358 + 31 +0.0 + 11 +246.966991 + 21 +385.983496 + 31 +0.0 + 11 +271.462717 + 21 +377.819508 + 31 +0.0 + 11 +299.821712 + 21 +374.999030 + 31 +0.0 + 11 +328.701257 + 21 +377.854518 + 31 +0.0 + 11 +353.160448 + 21 +386.045844 + 31 +0.0 + 11 +369.224529 + 21 +398.066642 + 31 +0.0 + 11 +375.000000 + 21 +412.500000 + 31 +0.0 + 11 +372.430460 + 21 +422.232121 + 31 +0.0 + 11 +364.979367 + 21 +431.226386 + 31 +0.0 + 11 +353.033009 + 21 +439.016504 + 31 +0.0 + 11 +337.452772 + 21 +444.989684 + 31 +0.0 + 11 +319.464242 + 21 +448.715230 + 31 +0.0 + 11 +300.000000 + 21 +450.000000 + 31 +0.0 + 0 +LINE + 5 +108 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +585.392197 + 20 +434.736135 + 30 +0.0 + 11 +525.000000 + 21 +412.500000 + 31 +0.0 + 0 +LINE + 5 +109 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +525.000000 + 20 +412.500000 + 30 +0.0 + 11 +587.314763 + 21 +391.632142 + 31 +0.0 + 0 +LINE + 5 +10a +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbLine + 10 +75.000000 + 20 +300.000000 + 30 +0.0 + 11 +150.000000 + 21 +225.000000 + 31 +0.0 + 0 +LINE + 5 +10b +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbLine + 10 +150.000000 + 20 +225.000000 + 30 +0.0 + 11 +225.000000 + 21 +300.000000 + 31 +0.0 + 0 +LINE + 5 +10c +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbLine + 10 +225.000000 + 20 +300.000000 + 30 +0.0 + 11 +300.000000 + 21 +225.000000 + 31 +0.0 + 0 +SPLINE + 5 +10d +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbSpline + 70 +0 + 71 +3 + 72 +19 + 73 +15 + 74 +13 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +25.229507 + 40 +54.800847 + 40 +86.334975 + 40 +115.121403 + 40 +137.614535 + 40 +154.642643 + 40 +171.262440 + 40 +193.065954 + 40 +221.348998 + 40 +252.783952 + 40 +282.682243 + 40 +308.562793 + 40 +308.562793 + 40 +308.562793 + 40 +308.562793 + 10 +587.314763 + 20 +391.632142 + 30 +0.0 + 10 +579.906492 + 20 +387.528282 + 30 +0.0 + 10 +563.815038 + 20 +378.614312 + 30 +0.0 + 10 +534.823599 + 20 +374.463329 + 30 +0.0 + 10 +504.775620 + 20 +375.231094 + 30 +0.0 + 10 +477.765552 + 20 +382.201524 + 30 +0.0 + 10 +457.561218 + 20 +393.399115 + 30 +0.0 + 10 +447.150586 + 20 +411.387937 + 30 +0.0 + 10 +455.810561 + 20 +430.089688 + 30 +0.0 + 10 +475.313194 + 20 +441.789347 + 30 +0.0 + 10 +501.673589 + 20 +449.310722 + 30 +0.0 + 10 +531.639583 + 20 +450.719221 + 30 +0.0 + 10 +560.964913 + 20 +447.141615 + 30 +0.0 + 10 +577.650386 + 20 +438.667840 + 30 +0.0 + 10 +585.392197 + 20 +434.736135 + 30 +0.0 + 11 +587.314763 + 21 +391.632142 + 31 +0.0 + 11 +564.600175 + 21 +380.651454 + 31 +0.0 + 11 +535.504657 + 21 +375.367987 + 31 +0.0 + 11 +503.990903 + 21 +376.501328 + 31 +0.0 + 11 +476.203738 + 21 +384.020102 + 31 +0.0 + 11 +457.357387 + 21 +396.298376 + 31 +0.0 + 11 +450.018645 + 21 +411.663900 + 31 +0.0 + 11 +455.980074 + 21 +427.177733 + 31 +0.0 + 11 +473.712940 + 21 +439.863896 + 31 +0.0 + 11 +500.802098 + 21 +447.994582 + 31 +0.0 + 11 +532.183467 + 21 +449.829250 + 31 +0.0 + 11 +561.721138 + 21 +445.199587 + 31 +0.0 + 11 +585.392197 + 21 +434.736135 + 31 +0.0 + 0 +LINE + 5 +10e +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +183.624900 + 20 +83.144963 + 30 +0.0 + 11 +149.620747 + 21 +89.245807 + 31 +0.0 + 0 +LINE + 5 +10f +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +149.620747 + 20 +89.245807 + 30 +0.0 + 11 +125.317537 + 21 +64.692660 + 31 +0.0 + 0 +LINE + 5 +110 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +125.317537 + 20 +64.692660 + 30 +0.0 + 11 +120.611925 + 21 +98.917800 + 31 +0.0 + 0 +LINE + 5 +111 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +120.611925 + 20 +98.917800 + 30 +0.0 + 11 +89.750385 + 21 +114.444180 + 31 +0.0 + 0 +LINE + 5 +112 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +89.750385 + 20 +114.444180 + 30 +0.0 + 11 +120.846315 + 21 +129.495637 + 31 +0.0 + 0 +LINE + 5 +113 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +120.846315 + 20 +129.495637 + 30 +0.0 + 11 +126.076042 + 21 +163.644615 + 31 +0.0 + 0 +LINE + 5 +114 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +126.076042 + 20 +163.644615 + 30 +0.0 + 11 +150.000000 + 21 +138.721785 + 31 +0.0 + 0 +LINE + 5 +115 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +150.000000 + 20 +138.721785 + 30 +0.0 + 11 +184.093680 + 21 +144.300637 + 31 +0.0 + 0 +LINE + 5 +116 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +184.093680 + 20 +144.300637 + 30 +0.0 + 11 +167.783572 + 21 +113.846025 + 31 +0.0 + 0 +LINE + 5 +117 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +167.783572 + 20 +113.846025 + 30 +0.0 + 11 +183.624900 + 21 +83.144963 + 31 +0.0 + 0 +LINE + 5 +118 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +333.624900 + 20 +80.897798 + 30 +0.0 + 11 +299.620747 + 21 +86.998642 + 31 +0.0 + 0 +LINE + 5 +119 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +299.620747 + 20 +86.998642 + 30 +0.0 + 11 +275.317537 + 21 +62.445495 + 31 +0.0 + 0 +LINE + 5 +11a +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +275.317537 + 20 +62.445495 + 30 +0.0 + 11 +270.611925 + 21 +96.670635 + 31 +0.0 + 0 +LINE + 5 +11b +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +270.611925 + 20 +96.670635 + 30 +0.0 + 11 +239.750385 + 21 +112.197015 + 31 +0.0 + 0 +LINE + 5 +11c +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +239.750385 + 20 +112.197015 + 30 +0.0 + 11 +270.846315 + 21 +127.248472 + 31 +0.0 + 0 +LINE + 5 +11d +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +270.846315 + 20 +127.248472 + 30 +0.0 + 11 +276.076042 + 21 +161.397450 + 31 +0.0 + 0 +LINE + 5 +11e +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +276.076042 + 20 +161.397450 + 30 +0.0 + 11 +300.000000 + 21 +136.474620 + 31 +0.0 + 0 +LINE + 5 +11f +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +300.000000 + 20 +136.474620 + 30 +0.0 + 11 +334.093680 + 21 +142.053472 + 31 +0.0 + 0 +LINE + 5 +120 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +334.093680 + 20 +142.053472 + 30 +0.0 + 11 +317.783572 + 21 +111.598860 + 31 +0.0 + 0 +LINE + 5 +121 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +317.783572 + 20 +111.598860 + 30 +0.0 + 11 +333.624900 + 21 +80.897798 + 31 +0.0 + 0 +LINE + 5 +122 +100 +AcDbEntity + 8 +Slide1 + 62 +7 +100 +AcDbLine + 10 +75.000000 + 20 +600.000000 + 30 +0.0 + 11 +150.000000 + 21 +600.000000 + 31 +0.0 + 0 +LINE + 5 +123 +100 +AcDbEntity + 8 +Slide1 + 62 +7 +100 +AcDbLine + 10 +150.000000 + 20 +600.000000 + 30 +0.0 + 11 +150.000000 + 21 +525.000000 + 31 +0.0 + 0 +LINE + 5 +124 +100 +AcDbEntity + 8 +Slide1 + 62 +7 +100 +AcDbLine + 10 +150.000000 + 20 +525.000000 + 30 +0.0 + 11 +75.000000 + 21 +525.000000 + 31 +0.0 + 0 +LINE + 5 +125 +100 +AcDbEntity + 8 +Slide1 + 62 +7 +100 +AcDbLine + 10 +75.000000 + 20 +525.000000 + 30 +0.0 + 11 +75.000000 + 21 +600.000000 + 31 +0.0 + 0 +LINE + 5 +126 +100 +AcDbEntity + 8 +Slide1 + 62 +1 +100 +AcDbLine + 10 +225.000000 + 20 +600.000000 + 30 +0.0 + 11 +375.000000 + 21 +600.000000 + 31 +0.0 + 0 +LINE + 5 +127 +100 +AcDbEntity + 8 +Slide1 + 62 +1 +100 +AcDbLine + 10 +375.000000 + 20 +600.000000 + 30 +0.0 + 11 +375.000000 + 21 +525.000000 + 31 +0.0 + 0 +LINE + 5 +128 +100 +AcDbEntity + 8 +Slide1 + 62 +1 +100 +AcDbLine + 10 +375.000000 + 20 +525.000000 + 30 +0.0 + 11 +225.000000 + 21 +525.000000 + 31 +0.0 + 0 +LINE + 5 +129 +100 +AcDbEntity + 8 +Slide1 + 62 +1 +100 +AcDbLine + 10 +225.000000 + 20 +525.000000 + 30 +0.0 + 11 +225.000000 + 21 +600.000000 + 31 +0.0 + 0 +LINE + 5 +12a +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +483.707420 + 20 +600.000000 + 30 +0.0 + 11 +566.292580 + 21 +600.000000 + 31 +0.0 + 0 +SPLINE + 5 +12b +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +0 + 71 +3 + 72 +16 + 73 +12 + 74 +10 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +19.641855 + 40 +60.655252 + 40 +116.900966 + 40 +157.914363 + 40 +194.132154 + 40 +235.145551 + 40 +291.391265 + 40 +332.404662 + 40 +352.046517 + 40 +352.046517 + 40 +352.046517 + 40 +352.046517 + 10 +375.000000 + 20 +300.000000 + 30 +0.0 + 10 +375.648652 + 20 +293.482054 + 30 +0.0 + 10 +377.651731 + 20 +273.354235 + 30 +0.0 + 10 +396.800281 + 20 +236.572428 + 30 +0.0 + 10 +442.770864 + 20 +217.685125 + 30 +0.0 + 10 +496.030996 + 20 +238.007710 + 30 +0.0 + 10 +478.969004 + 20 +286.992290 + 30 +0.0 + 10 +532.229136 + 20 +307.314875 + 30 +0.0 + 10 +578.199719 + 20 +288.427572 + 30 +0.0 + 10 +597.348269 + 20 +251.645765 + 30 +0.0 + 10 +599.351348 + 20 +231.517946 + 30 +0.0 + 10 +600.000000 + 20 +225.000000 + 30 +0.0 + 11 +375.000000 + 21 +300.000000 + 31 +0.0 + 11 +377.777778 + 21 +280.555556 + 31 +0.0 + 11 +397.222222 + 21 +244.444444 + 31 +0.0 + 11 +450.000000 + 21 +225.000000 + 31 +0.0 + 11 +486.111111 + 21 +244.444444 + 31 +0.0 + 11 +488.888889 + 21 +280.555556 + 31 +0.0 + 11 +525.000000 + 21 +300.000000 + 31 +0.0 + 11 +577.777778 + 21 +280.555556 + 31 +0.0 + 11 +597.222222 + 21 +244.444444 + 31 +0.0 + 11 +600.000000 + 21 +225.000000 + 31 +0.0 + 0 +LINE + 5 +12c +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +600.000000 + 20 +570.786901 + 30 +0.0 + 11 +600.000000 + 21 +554.213099 + 31 +0.0 + 0 +SPLINE + 5 +12d +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +0 + 71 +3 + 72 +13 + 73 +9 + 74 +7 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +8.804927 + 40 +17.394705 + 40 +25.802080 + 40 +33.904871 + 40 +41.670676 + 40 +49.339603 + 40 +49.339603 + 40 +49.339603 + 40 +49.339603 + 10 +566.292580 + 20 +600.000000 + 30 +0.0 + 10 +569.234269 + 20 +599.807758 + 30 +0.0 + 10 +575.045765 + 20 +599.427971 + 30 +0.0 + 10 +583.219162 + 20 +596.409240 + 30 +0.0 + 10 +590.291969 + 20 +591.789973 + 30 +0.0 + 10 +595.712344 + 20 +585.676608 + 30 +0.0 + 10 +599.311321 + 20 +578.516472 + 30 +0.0 + 10 +599.771403 + 20 +573.352621 + 30 +0.0 + 10 +600.000000 + 20 +570.786901 + 30 +0.0 + 11 +566.292580 + 21 +600.000000 + 31 +0.0 + 11 +575.040439 + 21 +598.999144 + 31 +0.0 + 11 +583.125065 + 21 +596.096884 + 31 +0.0 + 11 +590.127325 + 21 +591.443682 + 31 +0.0 + 11 +595.496404 + 21 +585.375055 + 31 +0.0 + 11 +598.845166 + 21 +578.368379 + 31 +0.0 + 11 +600.000000 + 21 +570.786901 + 31 +0.0 + 0 +LINE + 5 +12e +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +566.292581 + 20 +525.000000 + 30 +0.0 + 11 +483.707420 + 21 +525.000000 + 31 +0.0 + 0 +SPLINE + 5 +12f +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +0 + 71 +3 + 72 +13 + 73 +9 + 74 +7 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +7.668927 + 40 +15.434731 + 40 +23.537523 + 40 +31.944898 + 40 +40.534676 + 40 +49.339603 + 40 +49.339603 + 40 +49.339603 + 40 +49.339603 + 10 +600.000000 + 20 +554.213099 + 30 +0.0 + 10 +599.771403 + 20 +551.647379 + 30 +0.0 + 10 +599.311321 + 20 +546.483528 + 30 +0.0 + 10 +595.712344 + 20 +539.323392 + 30 +0.0 + 10 +590.291969 + 20 +533.210027 + 30 +0.0 + 10 +583.219162 + 20 +528.590760 + 30 +0.0 + 10 +575.045765 + 20 +525.572029 + 30 +0.0 + 10 +569.234269 + 20 +525.192242 + 30 +0.0 + 10 +566.292581 + 20 +525.000000 + 30 +0.0 + 11 +600.000000 + 21 +554.213099 + 31 +0.0 + 11 +598.845166 + 21 +546.631621 + 31 +0.0 + 11 +595.496404 + 21 +539.624945 + 31 +0.0 + 11 +590.127325 + 21 +533.556318 + 31 +0.0 + 11 +583.125065 + 21 +528.903116 + 31 +0.0 + 11 +575.040439 + 21 +526.000856 + 31 +0.0 + 11 +566.292581 + 21 +525.000000 + 31 +0.0 + 0 +LINE + 5 +130 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +450.000000 + 20 +554.213099 + 30 +0.0 + 11 +450.000000 + 21 +570.786901 + 31 +0.0 + 0 +SPLINE + 5 +131 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +0 + 71 +3 + 72 +13 + 73 +9 + 74 +7 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +8.804927 + 40 +17.394705 + 40 +25.802080 + 40 +33.904871 + 40 +41.670676 + 40 +49.339603 + 40 +49.339603 + 40 +49.339603 + 40 +49.339603 + 10 +483.707420 + 20 +525.000000 + 30 +0.0 + 10 +480.765731 + 20 +525.192242 + 30 +0.0 + 10 +474.954235 + 20 +525.572029 + 30 +0.0 + 10 +466.780838 + 20 +528.590760 + 30 +0.0 + 10 +459.708031 + 20 +533.210027 + 30 +0.0 + 10 +454.287656 + 20 +539.323392 + 30 +0.0 + 10 +450.688679 + 20 +546.483528 + 30 +0.0 + 10 +450.228597 + 20 +551.647379 + 30 +0.0 + 10 +450.000000 + 20 +554.213099 + 30 +0.0 + 11 +483.707420 + 21 +525.000000 + 31 +0.0 + 11 +474.959561 + 21 +526.000856 + 31 +0.0 + 11 +466.874935 + 21 +528.903116 + 31 +0.0 + 11 +459.872675 + 21 +533.556318 + 31 +0.0 + 11 +454.503596 + 21 +539.624945 + 31 +0.0 + 11 +451.154834 + 21 +546.631621 + 31 +0.0 + 11 +450.000000 + 21 +554.213099 + 31 +0.0 + 0 +LINE + 5 +132 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +483.707420 + 20 +600.000000 + 30 +0.0 + 11 +483.707420 + 21 +600.000000 + 31 +0.0 + 0 +SPLINE + 5 +133 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +0 + 71 +3 + 72 +13 + 73 +9 + 74 +7 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +0.000000 + 40 +7.668927 + 40 +15.434731 + 40 +23.537523 + 40 +31.944898 + 40 +40.534676 + 40 +49.339603 + 40 +49.339603 + 40 +49.339603 + 40 +49.339603 + 10 +450.000000 + 20 +570.786901 + 30 +0.0 + 10 +450.228597 + 20 +573.352621 + 30 +0.0 + 10 +450.688679 + 20 +578.516472 + 30 +0.0 + 10 +454.287656 + 20 +585.676608 + 30 +0.0 + 10 +459.708031 + 20 +591.789973 + 30 +0.0 + 10 +466.780838 + 20 +596.409240 + 30 +0.0 + 10 +474.954235 + 20 +599.427971 + 30 +0.0 + 10 +480.765731 + 20 +599.807758 + 30 +0.0 + 10 +483.707420 + 20 +600.000000 + 30 +0.0 + 11 +450.000000 + 21 +570.786901 + 31 +0.0 + 11 +451.154834 + 21 +578.368379 + 31 +0.0 + 11 +454.503596 + 21 +585.375055 + 31 +0.0 + 11 +459.872675 + 21 +591.443682 + 31 +0.0 + 11 +466.874935 + 21 +596.096884 + 31 +0.0 + 11 +474.959561 + 21 +598.999144 + 31 +0.0 + 11 +483.707420 + 21 +600.000000 + 31 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +C +330 +0 +100 +AcDbDictionary + 3 +ACAD_GROUP +350 +D + 3 +ACAD_MLINESTYLE +350 +17 + 0 +DICTIONARY + 5 +D +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +1A +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +17 +330 +C +100 +AcDbDictionary + 3 +STANDARD +350 +18 + 0 +DICTIONARY + 5 +19 +330 +C +100 +AcDbDictionary + 0 +ENDSEC + 0 +EOF diff --git a/share/extensions/tests/data/refs/dxf_outlines__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/dxf_outlines__--id__p1__--id__r3.out new file mode 100644 index 0000000..148cf1b --- /dev/null +++ b/share/extensions/tests/data/refs/dxf_outlines__--id__p1__--id__r3.out @@ -0,0 +1,3412 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1014 + 9 +$HANDSEED + 5 +FFFF + 9 +$MEASUREMENT + 70 + 1 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 + 4 + 0 +VPORT + 5 +2E +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*ACTIVE + 70 + 0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +210.0 + 22 +148.5 + 13 +0.0 + 23 +0.0 + 14 +10.0 + 24 +10.0 + 15 +10.0 + 25 +10.0 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +341.0 + 41 +1.24 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 + 0 + 72 + 100 + 73 + 1 + 74 + 3 + 75 + 0 + 76 + 0 + 77 + 0 + 78 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +LTYPE + 5 +14 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYBLOCK + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +15 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BYLAYER + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +16 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CONTINUOUS + 70 + 0 + 3 +Solid line + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +2 +100 +AcDbSymbolTable + 70 +4 + 0 +LAYER + 5 +50 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +51 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Slide3 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +52 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Slide2 + 70 +0 + 6 +CONTINUOUS + 0 +LAYER + 5 +53 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Slide1 + 70 +0 + 6 +CONTINUOUS + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +STYLE + 5 +11 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +STANDARD + 70 + 0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 + 0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 + 2 + 0 +APPID + 5 +12 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +A +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +DIMSTYLE +105 +27 +330 +A +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +ISO-25 + 70 + 0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874016 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 + 71 + 0 + 72 + 0 + 73 + 0 + 74 + 0 + 75 + 0 + 76 + 0 + 77 + 1 + 78 + 8 +170 + 0 +171 + 3 +172 + 1 +173 + 0 +174 + 0 +175 + 0 +176 + 0 +177 + 0 +178 + 0 +270 + 2 +271 + 2 +272 + 2 +273 + 2 +274 + 3 +340 +11 +275 + 0 +280 + 0 +281 + 0 +282 + 0 +283 + 0 +284 + 8 +285 + 0 +286 + 0 +287 + 3 +288 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +BLOCK_RECORD + 5 +1F +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*MODEL_SPACE + 0 +BLOCK_RECORD + 5 +1B +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*PAPER_SPACE + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +20 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*MODEL_SPACE + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*MODEL_SPACE + 1 + + 0 +ENDBLK + 5 +21 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockBegin + 2 +*PAPER_SPACE + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +LINE + 5 +100 +100 +AcDbEntity + 8 +Slide3 + 62 +7 +100 +AcDbLine + 10 +488.481675 + 20 +484.555043 + 30 +0.0 + 11 +675.000015 + 21 +484.555043 + 31 +0.0 + 0 +LINE + 5 +101 +100 +AcDbEntity + 8 +Slide3 + 62 +7 +100 +AcDbLine + 10 +675.000015 + 20 +484.555043 + 30 +0.0 + 11 +675.000015 + 21 +333.431460 + 31 +0.0 + 0 +LINE + 5 +102 +100 +AcDbEntity + 8 +Slide3 + 62 +7 +100 +AcDbLine + 10 +675.000015 + 20 +333.431460 + 30 +0.0 + 11 +488.481675 + 21 +333.431460 + 31 +0.0 + 0 +LINE + 5 +103 +100 +AcDbEntity + 8 +Slide3 + 62 +7 +100 +AcDbLine + 10 +488.481675 + 20 +333.431460 + 30 +0.0 + 11 +488.481675 + 21 +484.555043 + 31 +0.0 + 0 +SPLINE + 5 +104 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +112.500000 + 20 +450.000000 + 30 +0.0 + 10 +97.332666 + 20 +450.000000 + 30 +0.0 + 10 +83.658805 + 20 +440.863418 + 30 +0.0 + 10 +77.854518 + 20 +426.850629 + 30 +0.0 + 0 +SPLINE + 5 +105 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +77.854518 + 20 +426.850629 + 30 +0.0 + 10 +72.050230 + 20 +412.837839 + 30 +0.0 + 10 +75.258571 + 20 +396.708421 + 30 +0.0 + 10 +85.983496 + 20 +385.983496 + 30 +0.0 + 0 +SPLINE + 5 +106 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +85.983496 + 20 +385.983496 + 30 +0.0 + 10 +96.708421 + 20 +375.258571 + 30 +0.0 + 10 +112.837839 + 20 +372.050230 + 30 +0.0 + 10 +126.850629 + 20 +377.854518 + 30 +0.0 + 0 +SPLINE + 5 +107 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +126.850629 + 20 +377.854518 + 30 +0.0 + 10 +140.863418 + 20 +383.658805 + 30 +0.0 + 10 +150.000000 + 20 +397.332666 + 30 +0.0 + 10 +150.000000 + 20 +412.500000 + 30 +0.0 + 0 +SPLINE + 5 +108 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +150.000000 + 20 +412.500000 + 30 +0.0 + 10 +150.000000 + 20 +422.445618 + 30 +0.0 + 10 +146.049118 + 20 +431.983890 + 30 +0.0 + 10 +139.016504 + 20 +439.016504 + 30 +0.0 + 0 +SPLINE + 5 +109 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +139.016504 + 20 +439.016504 + 30 +0.0 + 10 +131.983890 + 20 +446.049118 + 30 +0.0 + 10 +122.445618 + 20 +450.000000 + 30 +0.0 + 10 +112.500000 + 20 +450.000000 + 30 +0.0 + 0 +LINE + 5 +10a +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbLine + 10 +112.500000 + 20 +450.000000 + 30 +0.0 + 11 +112.500000 + 21 +450.000000 + 31 +0.0 + 0 +SPLINE + 5 +10b +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +300.000000 + 20 +450.000000 + 30 +0.0 + 10 +269.665332 + 20 +450.000000 + 30 +0.0 + 10 +242.317610 + 20 +440.863418 + 30 +0.0 + 10 +230.709035 + 20 +426.850629 + 30 +0.0 + 0 +SPLINE + 5 +10c +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +230.709035 + 20 +426.850629 + 30 +0.0 + 10 +219.100460 + 20 +412.837839 + 30 +0.0 + 10 +225.517142 + 20 +396.708421 + 30 +0.0 + 10 +246.966991 + 20 +385.983496 + 30 +0.0 + 0 +SPLINE + 5 +10d +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +246.966991 + 20 +385.983496 + 30 +0.0 + 10 +268.416841 + 20 +375.258571 + 30 +0.0 + 10 +300.675678 + 20 +372.050230 + 30 +0.0 + 10 +328.701257 + 20 +377.854518 + 30 +0.0 + 0 +SPLINE + 5 +10e +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +328.701257 + 20 +377.854518 + 30 +0.0 + 10 +356.726837 + 20 +383.658805 + 30 +0.0 + 10 +375.000000 + 20 +397.332666 + 30 +0.0 + 10 +375.000000 + 20 +412.500000 + 30 +0.0 + 0 +SPLINE + 5 +10f +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +375.000000 + 20 +412.500000 + 30 +0.0 + 10 +375.000000 + 20 +422.445618 + 30 +0.0 + 10 +367.098237 + 20 +431.983890 + 30 +0.0 + 10 +353.033009 + 20 +439.016504 + 30 +0.0 + 0 +SPLINE + 5 +110 +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +353.033009 + 20 +439.016504 + 30 +0.0 + 10 +338.967780 + 20 +446.049118 + 30 +0.0 + 10 +319.891237 + 20 +450.000000 + 30 +0.0 + 10 +300.000000 + 20 +450.000000 + 30 +0.0 + 0 +LINE + 5 +111 +100 +AcDbEntity + 8 +Slide2 + 62 +1 +100 +AcDbLine + 10 +300.000000 + 20 +450.000000 + 30 +0.0 + 11 +300.000000 + 21 +450.000000 + 31 +0.0 + 0 +SPLINE + 5 +112 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +587.314763 + 20 +391.632142 + 30 +0.0 + 10 +569.111537 + 20 +378.042706 + 30 +0.0 + 10 +535.392878 + 20 +371.919721 + 30 +0.0 + 10 +503.990903 + 20 +376.501328 + 30 +0.0 + 0 +SPLINE + 5 +113 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +503.990903 + 20 +376.501328 + 30 +0.0 + 10 +472.588930 + 20 +381.082935 + 30 +0.0 + 10 +450.747983 + 20 +395.312169 + 30 +0.0 + 10 +450.018645 + 20 +411.663900 + 30 +0.0 + 0 +SPLINE + 5 +114 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +450.018645 + 20 +411.663900 + 30 +0.0 + 10 +449.289307 + 20 +428.015634 + 30 +0.0 + 10 +469.839842 + 20 +442.717568 + 30 +0.0 + 10 +500.802098 + 20 +447.994582 + 30 +0.0 + 0 +SPLINE + 5 +115 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +500.802098 + 20 +447.994582 + 30 +0.0 + 10 +531.764347 + 20 +453.271596 + 30 +0.0 + 10 +565.995413 + 20 +447.906301 + 30 +0.0 + 10 +585.392197 + 20 +434.736135 + 30 +0.0 + 0 +LINE + 5 +116 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +585.392197 + 20 +434.736135 + 30 +0.0 + 11 +525.000000 + 21 +412.500000 + 31 +0.0 + 0 +LINE + 5 +117 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +525.000000 + 20 +412.500000 + 30 +0.0 + 11 +587.314763 + 21 +391.632142 + 31 +0.0 + 0 +LINE + 5 +118 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbLine + 10 +75.000000 + 20 +300.000000 + 30 +0.0 + 11 +150.000000 + 21 +225.000000 + 31 +0.0 + 0 +LINE + 5 +119 +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbLine + 10 +150.000000 + 20 +225.000000 + 30 +0.0 + 11 +225.000000 + 21 +300.000000 + 31 +0.0 + 0 +LINE + 5 +11a +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbLine + 10 +225.000000 + 20 +300.000000 + 30 +0.0 + 11 +300.000000 + 21 +225.000000 + 31 +0.0 + 0 +SPLINE + 5 +11b +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +375.000000 + 20 +300.000000 + 30 +0.0 + 10 +375.000000 + 20 +300.000000 + 30 +0.0 + 10 +375.000000 + 20 +225.000000 + 30 +0.0 + 10 +450.000000 + 20 +225.000000 + 30 +0.0 + 0 +SPLINE + 5 +11c +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +450.000000 + 20 +225.000000 + 30 +0.0 + 10 +525.000000 + 20 +225.000000 + 30 +0.0 + 10 +450.000000 + 20 +300.000000 + 30 +0.0 + 10 +525.000000 + 20 +300.000000 + 30 +0.0 + 0 +SPLINE + 5 +11d +100 +AcDbEntity + 8 +Slide2 + 62 +7 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +525.000000 + 20 +300.000000 + 30 +0.0 + 10 +600.000000 + 20 +300.000000 + 30 +0.0 + 10 +600.000000 + 20 +225.000000 + 30 +0.0 + 10 +600.000000 + 20 +225.000000 + 30 +0.0 + 0 +LINE + 5 +11e +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +183.624900 + 20 +83.144963 + 30 +0.0 + 11 +149.620747 + 21 +89.245807 + 31 +0.0 + 0 +LINE + 5 +11f +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +149.620747 + 20 +89.245807 + 30 +0.0 + 11 +125.317537 + 21 +64.692660 + 31 +0.0 + 0 +LINE + 5 +120 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +125.317537 + 20 +64.692660 + 30 +0.0 + 11 +120.611925 + 21 +98.917800 + 31 +0.0 + 0 +LINE + 5 +121 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +120.611925 + 20 +98.917800 + 30 +0.0 + 11 +89.750385 + 21 +114.444180 + 31 +0.0 + 0 +LINE + 5 +122 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +89.750385 + 20 +114.444180 + 30 +0.0 + 11 +120.846315 + 21 +129.495637 + 31 +0.0 + 0 +LINE + 5 +123 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +120.846315 + 20 +129.495637 + 30 +0.0 + 11 +126.076042 + 21 +163.644615 + 31 +0.0 + 0 +LINE + 5 +124 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +126.076042 + 20 +163.644615 + 30 +0.0 + 11 +150.000000 + 21 +138.721785 + 31 +0.0 + 0 +LINE + 5 +125 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +150.000000 + 20 +138.721785 + 30 +0.0 + 11 +184.093680 + 21 +144.300637 + 31 +0.0 + 0 +LINE + 5 +126 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +184.093680 + 20 +144.300637 + 30 +0.0 + 11 +167.783572 + 21 +113.846025 + 31 +0.0 + 0 +LINE + 5 +127 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +167.783572 + 20 +113.846025 + 30 +0.0 + 11 +183.624900 + 21 +83.144963 + 31 +0.0 + 0 +LINE + 5 +128 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +333.624900 + 20 +80.897798 + 30 +0.0 + 11 +299.620747 + 21 +86.998642 + 31 +0.0 + 0 +LINE + 5 +129 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +299.620747 + 20 +86.998642 + 30 +0.0 + 11 +275.317537 + 21 +62.445495 + 31 +0.0 + 0 +LINE + 5 +12a +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +275.317537 + 20 +62.445495 + 30 +0.0 + 11 +270.611925 + 21 +96.670635 + 31 +0.0 + 0 +LINE + 5 +12b +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +270.611925 + 20 +96.670635 + 30 +0.0 + 11 +239.750385 + 21 +112.197015 + 31 +0.0 + 0 +LINE + 5 +12c +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +239.750385 + 20 +112.197015 + 30 +0.0 + 11 +270.846315 + 21 +127.248472 + 31 +0.0 + 0 +LINE + 5 +12d +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +270.846315 + 20 +127.248472 + 30 +0.0 + 11 +276.076042 + 21 +161.397450 + 31 +0.0 + 0 +LINE + 5 +12e +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +276.076042 + 20 +161.397450 + 30 +0.0 + 11 +300.000000 + 21 +136.474620 + 31 +0.0 + 0 +LINE + 5 +12f +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +300.000000 + 20 +136.474620 + 30 +0.0 + 11 +334.093680 + 21 +142.053472 + 31 +0.0 + 0 +LINE + 5 +130 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +334.093680 + 20 +142.053472 + 30 +0.0 + 11 +317.783572 + 21 +111.598860 + 31 +0.0 + 0 +LINE + 5 +131 +100 +AcDbEntity + 8 +Slide2 + 62 +3 +100 +AcDbLine + 10 +317.783572 + 20 +111.598860 + 30 +0.0 + 11 +333.624900 + 21 +80.897798 + 31 +0.0 + 0 +LINE + 5 +132 +100 +AcDbEntity + 8 +Slide1 + 62 +7 +100 +AcDbLine + 10 +75.000000 + 20 +600.000000 + 30 +0.0 + 11 +150.000000 + 21 +600.000000 + 31 +0.0 + 0 +LINE + 5 +133 +100 +AcDbEntity + 8 +Slide1 + 62 +7 +100 +AcDbLine + 10 +150.000000 + 20 +600.000000 + 30 +0.0 + 11 +150.000000 + 21 +525.000000 + 31 +0.0 + 0 +LINE + 5 +134 +100 +AcDbEntity + 8 +Slide1 + 62 +7 +100 +AcDbLine + 10 +150.000000 + 20 +525.000000 + 30 +0.0 + 11 +75.000000 + 21 +525.000000 + 31 +0.0 + 0 +LINE + 5 +135 +100 +AcDbEntity + 8 +Slide1 + 62 +7 +100 +AcDbLine + 10 +75.000000 + 20 +525.000000 + 30 +0.0 + 11 +75.000000 + 21 +600.000000 + 31 +0.0 + 0 +LINE + 5 +136 +100 +AcDbEntity + 8 +Slide1 + 62 +1 +100 +AcDbLine + 10 +225.000000 + 20 +600.000000 + 30 +0.0 + 11 +375.000000 + 21 +600.000000 + 31 +0.0 + 0 +LINE + 5 +137 +100 +AcDbEntity + 8 +Slide1 + 62 +1 +100 +AcDbLine + 10 +375.000000 + 20 +600.000000 + 30 +0.0 + 11 +375.000000 + 21 +525.000000 + 31 +0.0 + 0 +LINE + 5 +138 +100 +AcDbEntity + 8 +Slide1 + 62 +1 +100 +AcDbLine + 10 +375.000000 + 20 +525.000000 + 30 +0.0 + 11 +225.000000 + 21 +525.000000 + 31 +0.0 + 0 +LINE + 5 +139 +100 +AcDbEntity + 8 +Slide1 + 62 +1 +100 +AcDbLine + 10 +225.000000 + 20 +525.000000 + 30 +0.0 + 11 +225.000000 + 21 +600.000000 + 31 +0.0 + 0 +LINE + 5 +13a +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +483.707420 + 20 +600.000000 + 30 +0.0 + 11 +566.292580 + 21 +600.000000 + 31 +0.0 + 0 +SPLINE + 5 +13b +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +566.292580 + 20 +600.000000 + 30 +0.0 + 10 +575.232344 + 20 +600.000000 + 30 +0.0 + 10 +583.805958 + 20 +596.922200 + 30 +0.0 + 10 +590.127325 + 20 +591.443682 + 30 +0.0 + 0 +SPLINE + 5 +13c +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +590.127325 + 20 +591.443682 + 30 +0.0 + 10 +596.448693 + 20 +585.965163 + 30 +0.0 + 10 +600.000000 + 20 +578.534697 + 30 +0.0 + 10 +600.000000 + 20 +570.786901 + 30 +0.0 + 0 +LINE + 5 +13d +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +600.000000 + 20 +570.786901 + 30 +0.0 + 11 +600.000000 + 21 +554.213099 + 31 +0.0 + 0 +SPLINE + 5 +13e +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +600.000000 + 20 +554.213099 + 30 +0.0 + 10 +600.000000 + 20 +546.465303 + 30 +0.0 + 10 +596.448693 + 20 +539.034837 + 30 +0.0 + 10 +590.127325 + 20 +533.556318 + 30 +0.0 + 0 +SPLINE + 5 +13f +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +590.127325 + 20 +533.556318 + 30 +0.0 + 10 +583.805958 + 20 +528.077800 + 30 +0.0 + 10 +575.232344 + 20 +525.000000 + 30 +0.0 + 10 +566.292581 + 20 +525.000000 + 30 +0.0 + 0 +LINE + 5 +140 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +566.292581 + 20 +525.000000 + 30 +0.0 + 11 +483.707420 + 21 +525.000000 + 31 +0.0 + 0 +SPLINE + 5 +141 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +483.707420 + 20 +525.000000 + 30 +0.0 + 10 +474.767656 + 20 +525.000000 + 30 +0.0 + 10 +466.194042 + 20 +528.077800 + 30 +0.0 + 10 +459.872675 + 20 +533.556318 + 30 +0.0 + 0 +SPLINE + 5 +142 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +459.872675 + 20 +533.556318 + 30 +0.0 + 10 +453.551307 + 20 +539.034837 + 30 +0.0 + 10 +450.000000 + 20 +546.465303 + 30 +0.0 + 10 +450.000000 + 20 +554.213099 + 30 +0.0 + 0 +LINE + 5 +143 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +450.000000 + 20 +554.213099 + 30 +0.0 + 11 +450.000000 + 21 +570.786901 + 31 +0.0 + 0 +SPLINE + 5 +144 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +450.000000 + 20 +570.786901 + 30 +0.0 + 10 +450.000000 + 20 +578.534697 + 30 +0.0 + 10 +453.551307 + 20 +585.965163 + 30 +0.0 + 10 +459.872675 + 20 +591.443682 + 30 +0.0 + 0 +SPLINE + 5 +145 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbSpline + 70 +8 + 71 +3 + 72 +8 + 73 +4 + 74 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +0 + 40 +1 + 40 +1 + 40 +1 + 40 +1 + 10 +459.872675 + 20 +591.443682 + 30 +0.0 + 10 +466.194042 + 20 +596.922200 + 30 +0.0 + 10 +474.767656 + 20 +600.000000 + 30 +0.0 + 10 +483.707420 + 20 +600.000000 + 30 +0.0 + 0 +LINE + 5 +146 +100 +AcDbEntity + 8 +Slide1 + 62 +3 +100 +AcDbLine + 10 +483.707420 + 20 +600.000000 + 30 +0.0 + 11 +483.707420 + 21 +600.000000 + 31 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +C +330 +0 +100 +AcDbDictionary + 3 +ACAD_GROUP +350 +D + 3 +ACAD_MLINESTYLE +350 +17 + 0 +DICTIONARY + 5 +D +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +1A +330 +C +100 +AcDbDictionary + 0 +DICTIONARY + 5 +17 +330 +C +100 +AcDbDictionary + 3 +STANDARD +350 +18 + 0 +DICTIONARY + 5 +19 +330 +C +100 +AcDbDictionary + 0 +ENDSEC + 0 +EOF diff --git a/share/extensions/tests/data/refs/edge3d__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/edge3d__--id__p1__--id__r3.out new file mode 100644 index 0000000..3bb5a06 --- /dev/null +++ b/share/extensions/tests/data/refs/edge3d__--id__p1__--id__r3.out @@ -0,0 +1,44 @@ + + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/embedimage.out b/share/extensions/tests/data/refs/embedimage.out new file mode 100644 index 0000000..6f7540b --- /dev/null +++ b/share/extensions/tests/data/refs/embedimage.out @@ -0,0 +1,16 @@ + + + + + + + image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/export_gimp_palette.out b/share/extensions/tests/data/refs/export_gimp_palette.out new file mode 100644 index 0000000..d5e846b --- /dev/null +++ b/share/extensions/tests/data/refs/export_gimp_palette.out @@ -0,0 +1,12 @@ +GIMP Palette +Name: +# + 0 0 0 #000000 + 0 0 255 BLUE + 46 52 54 #2E3436 + 52 101 164 #3465A4 + 92 53 102 #5C3566 +114 159 207 #729FCF +117 80 123 #75507B +204 0 0 #CC0000 +255 0 0 RED diff --git a/share/extensions/tests/data/refs/export_gimp_palette__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/export_gimp_palette__--id__p1__--id__r3.out new file mode 100644 index 0000000..17b677f --- /dev/null +++ b/share/extensions/tests/data/refs/export_gimp_palette__--id__p1__--id__r3.out @@ -0,0 +1,6 @@ +GIMP Palette +Name: +# + 46 52 54 #2E3436 +204 0 0 #CC0000 +255 0 0 RED diff --git a/share/extensions/tests/data/refs/extractimage__--selectedonly__False__--filepath__TMP_DIR__img__.out b/share/extensions/tests/data/refs/extractimage__--selectedonly__False__--filepath__TMP_DIR__img__.out new file mode 100644 index 0000000..3bc0a06 Binary files /dev/null and b/share/extensions/tests/data/refs/extractimage__--selectedonly__False__--filepath__TMP_DIR__img__.out differ diff --git a/share/extensions/tests/data/refs/extractimage__--selectedonly__True__--id__embeded_image01__--filepath__TMP_DIR__img__.out b/share/extensions/tests/data/refs/extractimage__--selectedonly__True__--id__embeded_image01__--filepath__TMP_DIR__img__.out new file mode 100644 index 0000000..3bc0a06 Binary files /dev/null and b/share/extensions/tests/data/refs/extractimage__--selectedonly__True__--id__embeded_image01__--filepath__TMP_DIR__img__.out differ diff --git a/share/extensions/tests/data/refs/extrude__--id__p1__--id__p2.out b/share/extensions/tests/data/refs/extrude__--id__p1__--id__p2.out new file mode 100644 index 0000000..d6b2b44 --- /dev/null +++ b/share/extensions/tests/data/refs/extrude__--id__p1__--id__p2.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/fig_input.out b/share/extensions/tests/data/refs/fig_input.out new file mode 100644 index 0000000..8200ef9 --- /dev/null +++ b/share/extensions/tests/data/refs/fig_input.out @@ -0,0 +1,22 @@ + + + + + + + + + + + + + +Hello Inkscape + + diff --git a/share/extensions/tests/data/refs/flatten.out b/share/extensions/tests/data/refs/flatten.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/flatten__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/flatten__--id__p1__--id__r3.out new file mode 100644 index 0000000..f05ae54 --- /dev/null +++ b/share/extensions/tests/data/refs/flatten__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/foldablebox__--proportion__0__5__--guide__true.out b/share/extensions/tests/data/refs/foldablebox__--proportion__0__5__--guide__true.out new file mode 100644 index 0000000..34f61cd --- /dev/null +++ b/share/extensions/tests/data/refs/foldablebox__--proportion__0__5__--guide__true.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/foldablebox__--width__20__--height__20__--depth__2__2.out b/share/extensions/tests/data/refs/foldablebox__--width__20__--height__20__--depth__2__2.out new file mode 100644 index 0000000..e4872bc --- /dev/null +++ b/share/extensions/tests/data/refs/foldablebox__--width__20__--height__20__--depth__2__2.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/fractalize__--id__p1__--id__p2.out b/share/extensions/tests/data/refs/fractalize__--id__p1__--id__p2.out new file mode 100644 index 0000000..ec00e57 --- /dev/null +++ b/share/extensions/tests/data/refs/fractalize__--id__p1__--id__p2.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/funcplot__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/funcplot__--id__p1__--id__r3.out new file mode 100644 index 0000000..b0e5bd5 --- /dev/null +++ b/share/extensions/tests/data/refs/funcplot__--id__p1__--id__r3.out @@ -0,0 +1,40 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/gcodetools__06eec9617e749f35cb949d850415f68d.out b/share/extensions/tests/data/refs/gcodetools__06eec9617e749f35cb949d850415f68d.out new file mode 100644 index 0000000..5ccabd0 --- /dev/null +++ b/share/extensions/tests/data/refs/gcodetools__06eec9617e749f35cb949d850415f68d.out @@ -0,0 +1,45 @@ +% +(Header) +(Generated by gcodetools from Inkscape.) +(Using default header. To add your own header create file "header" in the output dir.) +M3 +(Header end.) +G21 (All units in mm) + +(Start cutting path id: p1) +(Change tool to Default tool) + +G00 Z5.000000 +G00 X100.000000 Y400.000000 + +G01 Z-0.125000 F100.0(Penetrate) +G01 X200.000000 Y300.000000 Z-0.125000 F400.000000 +G01 X300.000000 Y400.000000 Z-0.125000 +G01 X400.000000 Y300.000000 Z-0.125000 +G00 Z5.000000 + +(End cutting path id: p1) + + +(Start cutting path id: p1) +(Change tool to Default tool) + +G00 Z5.000000 +G00 X100.000000 Y400.000000 + +G01 Z-0.125000 F100.0(Penetrate) +G01 X200.000000 Y300.000000 Z-0.125000 F400.000000 +G01 X300.000000 Y400.000000 Z-0.125000 +G01 X400.000000 Y300.000000 Z-0.125000 +G00 Z5.000000 + +(End cutting path id: p1) + + +(Footer) +M5 +G00 X0.0000 Y0.0000 +M2 +(Using default footer. To add your own footer create file "footer" in the output dir.) +(end) +% \ No newline at end of file diff --git a/share/extensions/tests/data/refs/gcodetools__2bf3b298fa730dafb8c6fd51921078f0.out b/share/extensions/tests/data/refs/gcodetools__2bf3b298fa730dafb8c6fd51921078f0.out new file mode 100644 index 0000000..8694e43 --- /dev/null +++ b/share/extensions/tests/data/refs/gcodetools__2bf3b298fa730dafb8c6fd51921078f0.out @@ -0,0 +1,40 @@ +% +(Header) +(Generated by gcodetools from Inkscape.) +(Using default header. To add your own header create file "header" in the output dir.) +M3 +(Header end.) +G21 (All units in mm) +(Change tool to Default tool) + +G01 Z 16.000000 F 800.000000 +G01 X 100.000000 Z 16.000000 F 800.000000 +G01 X 100.000000 Z 16.000000 F 800.000000 +G01 X 100.000000 Z 401.000000 F 800.000000 +G01 X 200.000000 Z 301.000000 F 800.000000 +G01 X 300.000000 Z 401.000000 F 800.000000 +G01 X 400.000000 Z 301.000000 F 800.000000 +G01 X 400.000000 Z 16.000000 F 800.000000 +G01 X 100.000000 Z 16.000000 F 800.000000 + +(Fine cutting start) +(Calculating fine cut using Move path) + +(Fine cut 1-th cicle start) +G01 X 100.000000 Z 16.000000 F 800.000000 +G01 X 100.000000 Z 401.000000 F 800.000000 +G01 X 100.000000 Z 400.000000 F 800.000000 +G01 X 100.000000 Z 400.000000 F 800.000000 +G01 X 200.000000 Z 300.000000 F 800.000000 +G01 X 300.000000 Z 400.000000 F 800.000000 +G01 X 400.000000 Z 300.000000 F 800.000000 +G01 Z 16.000000 F 800.000000 +G01 X 100.000000 Z 16.000000 F 800.000000 + +(Footer) +M5 +G00 X0.0000 Y0.0000 +M2 +(Using default footer. To add your own footer create file "footer" in the output dir.) +(end) +% \ No newline at end of file diff --git a/share/extensions/tests/data/refs/gcodetools__4a9fb751baf0533eadd4d394957c966d.out b/share/extensions/tests/data/refs/gcodetools__4a9fb751baf0533eadd4d394957c966d.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/generate_voronoi__--id__r3__--id__p1.out b/share/extensions/tests/data/refs/generate_voronoi__--id__r3__--id__p1.out new file mode 100644 index 0000000..700fba3 --- /dev/null +++ b/share/extensions/tests/data/refs/generate_voronoi__--id__r3__--id__p1.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/gimp_xcf.out b/share/extensions/tests/data/refs/gimp_xcf.out new file mode 100644 index 0000000..9438924 Binary files /dev/null and b/share/extensions/tests/data/refs/gimp_xcf.out differ diff --git a/share/extensions/tests/data/refs/gimp_xcf__-d__true__-r__true.out b/share/extensions/tests/data/refs/gimp_xcf__-d__true__-r__true.out new file mode 100644 index 0000000..183fb9b Binary files /dev/null and b/share/extensions/tests/data/refs/gimp_xcf__-d__true__-r__true.out differ diff --git a/share/extensions/tests/data/refs/grid_cartesian.out b/share/extensions/tests/data/refs/grid_cartesian.out new file mode 100644 index 0000000..1e79428 --- /dev/null +++ b/share/extensions/tests/data/refs/grid_cartesian.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/grid_cartesian__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/grid_cartesian__--id__p1__--id__r3.out new file mode 100644 index 0000000..1e79428 --- /dev/null +++ b/share/extensions/tests/data/refs/grid_cartesian__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/grid_isometric.out b/share/extensions/tests/data/refs/grid_isometric.out new file mode 100644 index 0000000..6d4b675 --- /dev/null +++ b/share/extensions/tests/data/refs/grid_isometric.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/grid_polar.out b/share/extensions/tests/data/refs/grid_polar.out new file mode 100644 index 0000000..7b5037d --- /dev/null +++ b/share/extensions/tests/data/refs/grid_polar.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + 0.015.030.045.060.075.090.0105.0120.0135.0150.0165.0180.0195.0210.0225.0240.0255.0270.0285.0300.0315.0330.0345.0 + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/grid_polar__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/grid_polar__--id__p1__--id__r3.out new file mode 100644 index 0000000..7b5037d --- /dev/null +++ b/share/extensions/tests/data/refs/grid_polar__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + 0.015.030.045.060.075.090.0105.0120.0135.0150.0165.0180.0195.0210.0225.0240.0255.0270.0285.0300.0315.0330.0345.0 + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/guides_creator__--tab__diagonal_guides.out b/share/extensions/tests/data/refs/guides_creator__--tab__diagonal_guides.out new file mode 100644 index 0000000..2c97aca --- /dev/null +++ b/share/extensions/tests/data/refs/guides_creator__--tab__diagonal_guides.out @@ -0,0 +1,40 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/guides_creator__--tab__margins__--start_from_edges__True__--margins_preset__book_left.out b/share/extensions/tests/data/refs/guides_creator__--tab__margins__--start_from_edges__True__--margins_preset__book_left.out new file mode 100644 index 0000000..7fd0af8 --- /dev/null +++ b/share/extensions/tests/data/refs/guides_creator__--tab__margins__--start_from_edges__True__--margins_preset__book_left.out @@ -0,0 +1,40 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/guides_creator__--tab__margins__--start_from_edges__True__--margins_preset__book_right.out b/share/extensions/tests/data/refs/guides_creator__--tab__margins__--start_from_edges__True__--margins_preset__book_right.out new file mode 100644 index 0000000..51cee7d --- /dev/null +++ b/share/extensions/tests/data/refs/guides_creator__--tab__margins__--start_from_edges__True__--margins_preset__book_right.out @@ -0,0 +1,40 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/guides_creator__--tab__margins__--start_from_edges__True__--margins_preset__custom.out b/share/extensions/tests/data/refs/guides_creator__--tab__margins__--start_from_edges__True__--margins_preset__custom.out new file mode 100644 index 0000000..1218e33 --- /dev/null +++ b/share/extensions/tests/data/refs/guides_creator__--tab__margins__--start_from_edges__True__--margins_preset__custom.out @@ -0,0 +1,40 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/guides_creator__--tab__regular_guides__--guides_preset__5__5__--start_from_edges__True.out b/share/extensions/tests/data/refs/guides_creator__--tab__regular_guides__--guides_preset__5__5__--start_from_edges__True.out new file mode 100644 index 0000000..67d930a --- /dev/null +++ b/share/extensions/tests/data/refs/guides_creator__--tab__regular_guides__--guides_preset__5__5__--start_from_edges__True.out @@ -0,0 +1,40 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/guides_creator__--tab__regular_guides__--guides_preset__custom.out b/share/extensions/tests/data/refs/guides_creator__--tab__regular_guides__--guides_preset__custom.out new file mode 100644 index 0000000..50293ea --- /dev/null +++ b/share/extensions/tests/data/refs/guides_creator__--tab__regular_guides__--guides_preset__custom.out @@ -0,0 +1,40 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/guides_creator__--tab__regular_guides__--guides_preset__golden__--delete__True.out b/share/extensions/tests/data/refs/guides_creator__--tab__regular_guides__--guides_preset__golden__--delete__True.out new file mode 100644 index 0000000..2f3669a --- /dev/null +++ b/share/extensions/tests/data/refs/guides_creator__--tab__regular_guides__--guides_preset__golden__--delete__True.out @@ -0,0 +1,34 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/guillotine__--ignore__true__--directory__TMP_DIR__img__.out b/share/extensions/tests/data/refs/guillotine__--ignore__true__--directory__TMP_DIR__img__.out new file mode 100644 index 0000000..f7bcbef Binary files /dev/null and b/share/extensions/tests/data/refs/guillotine__--ignore__true__--directory__TMP_DIR__img__.out differ diff --git a/share/extensions/tests/data/refs/guillotine__--image__f____oo__--directory__TMP_DIR__img__.out b/share/extensions/tests/data/refs/guillotine__--image__f____oo__--directory__TMP_DIR__img__.out new file mode 100644 index 0000000..fc02093 Binary files /dev/null and b/share/extensions/tests/data/refs/guillotine__--image__f____oo__--directory__TMP_DIR__img__.out differ diff --git a/share/extensions/tests/data/refs/handles__--id__curve__--id__quad.out b/share/extensions/tests/data/refs/handles__--id__curve__--id__quad.out new file mode 100644 index 0000000..48f9e0e --- /dev/null +++ b/share/extensions/tests/data/refs/handles__--id__curve__--id__quad.out @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/hershey.out b/share/extensions/tests/data/refs/hershey.out new file mode 100755 index 0000000..27a137a --- /dev/null +++ b/share/extensions/tests/data/refs/hershey.out @@ -0,0 +1,52 @@ + + + + + + + + image/svg+xml + + + + + + +     + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/hershey_encoding.out b/share/extensions/tests/data/refs/hershey_encoding.out new file mode 100755 index 0000000..92979a0 --- /dev/null +++ b/share/extensions/tests/data/refs/hershey_encoding.out @@ -0,0 +1,13 @@ + + + + + + + image/svg+xml + + + + + EMSAllureEMSElfinEMSFelixEMSNixishEMSNixishItalicEMSOsmotronEMSReadabilityEMSReadabilityItalicEMSTechHersheyGothEnglishHersheySans1HersheySansMedHersheyScript1HersheyScriptMedHersheySerifBoldHersheySerifBoldItalicHersheySerifMedHersheySerifMedItalic + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/hershey_fonttable.out b/share/extensions/tests/data/refs/hershey_fonttable.out new file mode 100755 index 0000000..35ed814 --- /dev/null +++ b/share/extensions/tests/data/refs/hershey_fonttable.out @@ -0,0 +1,13 @@ + + + + + + + image/svg+xml + + + + + EMSAllureEMSElfinEMSFelixEMSNixishEMSNixishItalicEMSOsmotronEMSReadabilityEMSReadabilityItalicEMSTechHersheyGothEnglishHersheySans1HersheySansMedHersheyScript1HersheyScriptMedHersheySerifBoldHersheySerifBoldItalicHersheySerifMedHersheySerifMedItalic + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/hershey_glyphtable.out b/share/extensions/tests/data/refs/hershey_glyphtable.out new file mode 100755 index 0000000..cfb0ecc --- /dev/null +++ b/share/extensions/tests/data/refs/hershey_glyphtable.out @@ -0,0 +1,13 @@ + + + + + + + image/svg+xml + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/hershey_loadfont.out b/share/extensions/tests/data/refs/hershey_loadfont.out new file mode 100755 index 0000000..003e932 --- /dev/null +++ b/share/extensions/tests/data/refs/hershey_loadfont.out @@ -0,0 +1,17 @@ + + + + + + + + image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/hershey_partialselection.out b/share/extensions/tests/data/refs/hershey_partialselection.out new file mode 100755 index 0000000..21324f9 --- /dev/null +++ b/share/extensions/tests/data/refs/hershey_partialselection.out @@ -0,0 +1,22 @@ + + + + + + + + image/svg+xml + + + + + + + + + + Text + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/hershey_preservetext.out b/share/extensions/tests/data/refs/hershey_preservetext.out new file mode 100755 index 0000000..b566766 --- /dev/null +++ b/share/extensions/tests/data/refs/hershey_preservetext.out @@ -0,0 +1,22 @@ + + + + + + + + image/svg+xml + + + + + + + + + + Flow Text + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/hpgl_input.out b/share/extensions/tests/data/refs/hpgl_input.out new file mode 100644 index 0000000..b00d209 --- /dev/null +++ b/share/extensions/tests/data/refs/hpgl_input.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/hpgl_output__hpgl_multipen__svg.out b/share/extensions/tests/data/refs/hpgl_output__hpgl_multipen__svg.out new file mode 100644 index 0000000..4ad90c4 --- /dev/null +++ b/share/extensions/tests/data/refs/hpgl_output__hpgl_multipen__svg.out @@ -0,0 +1 @@ +IN;FS24;VS20;PU;SP1;PU0,0;PD0,90;PU855,1988;PD860,1986,864,1983,865,1977,863,1972,859,1969,855,1968,855,1170,850,1172,846,1176,845,1180,0,1180,2,1185,5,1189,10,1190,10,1988,15,1986,19,1983,20,1978,865,1978,863,1973,860,1969,855,1968,855,1928;PU;SP2;PU855,798;PD855,798,855,0,850,2,846,6,845,10,0,10,2,16,5,19,10,20,10,818,15,817,19,813,20,808,865,808,863,803,860,799,855,798,855,758;PU;SP3;PU855,3108;PD855,3108,855,2310,850,2311,846,2315,845,2320,0,2320,2,2325,5,2329,10,2330,10,3128,15,3126,19,3122,20,3118,865,3118,863,3112,860,3109,855,3108,855,3068;SP0;PU0,0;IN; \ No newline at end of file diff --git a/share/extensions/tests/data/refs/hpgl_output__shapes__svg.out b/share/extensions/tests/data/refs/hpgl_output__shapes__svg.out new file mode 100644 index 0000000..378db19 --- /dev/null +++ b/share/extensions/tests/data/refs/hpgl_output__shapes__svg.out @@ -0,0 +1 @@ +IN;FS24;VS20;PU;SP1;PU0,0;PD0,90;PU5966,2652;PD5971,2651,5975,2647,5976,2641,5975,2636,5971,2633,5966,2632,5966,0,5961,1,5957,5,5956,10,3824,10,3825,15,3829,19,3834,20,3834,2652,3839,2651,3843,2647,3844,2642,5976,2642,5975,2637,5971,2633,5966,2632,5966,2592;PU5479,7938;PD5474,7939,5470,7943,5469,7948,5470,7953,5474,7957,5478,7957,5477,7997,5476,7997,5472,8037,5471,8036,5464,8075,5463,8075,5453,8112,5452,8112,5439,8149,5439,8148,5423,8184,5405,8218,5404,8217,5384,8250,5361,8281,5361,8280,5336,8310,5335,8309,5308,8337,5308,8336,5279,8362,5248,8385,5247,8385,5214,8406,5214,8405,5180,8425,5179,8424,5143,8441,5143,8440,5105,8454,5105,8453,5067,8464,5067,8463,5029,8471,5028,8470,4990,8476,4990,8475,4951,8477,4951,8476,4913,8476,4913,8475,4874,8472,4874,8471,4836,8465,4837,8464,4799,8455,4763,8443,4763,8442,4727,8428,4727,8427,4692,8411,4693,8410,4659,8390,4660,8390,4627,8368,4628,8367,4597,8343,4597,8342,4568,8315,4569,8314,4541,8285,4542,8285,4517,8254,4518,8253,4496,8221,4497,8221,4477,8187,4478,8187,4461,8152,4461,8151,4447,8116,4448,8115,4436,8079,4437,8078,4428,8041,4429,8041,4423,8003,4420,7964,4421,7964,4420,7926,4421,7926,4423,7887,4424,7887,4429,7849,4430,7849,4438,7811,4439,7811,4450,7773,4464,7736,4465,7736,4481,7700,4482,7700,4501,7665,4502,7666,4523,7633,4524,7633,4548,7603,4574,7574,4574,7575,4602,7548,4603,7548,4632,7524,4664,7502,4697,7482,4697,7483,4731,7465,4732,7466,4767,7450,4767,7451,4804,7438,4804,7439,4842,7429,4842,7430,4880,7422,4880,7423,4920,7419,4959,7418,4959,7419,5014,7422,5013,7423,5066,7431,5066,7432,5117,7445,5116,7446,5165,7463,5164,7464,5211,7487,5210,7487,5254,7514,5253,7515,5294,7545,5293,7546,5331,7580,5330,7581,5365,7618,5364,7619,5394,7660,5393,7660,5420,7704,5419,7704,5441,7751,5458,7800,5457,7800,5470,7851,5469,7851,5477,7903,5476,7903,5479,7957,5478,7957,5477,7997,5476,7997;PU5478,5312;PD5478,5312,5477,5391,5472,5470,5465,5547,5464,5547,5454,5622,5441,5695,5425,5766,5407,5834,5387,5899,5386,5899,5364,5961,5363,5961,5338,6020,5311,6075,5310,6075,5282,6126,5281,6126,5250,6173,5217,6216,5216,6215,5182,6254,5181,6253,5145,6286,5144,6285,5107,6313,5106,6312,5068,6334,5067,6333,5029,6349,5029,6348,4990,6358,4990,6356,4951,6361,4951,6359,4913,6358,4913,6357,4874,6350,4875,6349,4837,6337,4837,6335,4800,6318,4800,6317,4764,6293,4728,6264,4729,6263,4694,6230,4695,6229,4661,6190,4662,6190,4630,6145,4599,6096,4600,6095,4571,6041,4544,5982,4520,5920,4498,5856,4499,5855,4479,5788,4480,5788,4463,5718,4449,5647,4449,5646,4438,5573,4429,5498,4430,5498,4424,5422,4421,5346,4420,5268,4421,5268,4423,5191,4428,5114,4429,5114,4437,5037,4448,4962,4449,4962,4462,4887,4463,4887,4479,4814,4480,4815,4499,4745,4499,4746,4521,4680,4545,4618,4546,4619,4571,4561,4572,4561,4599,4507,4600,4508,4629,4458,4630,4458,4661,4413,4662,4414,4695,4374,4729,4339,4730,4339,4765,4309,4766,4310,4803,4284,4803,4285,4841,4265,4841,4266,4880,4252,4880,4253,4919,4244,4920,4246,4959,4243,4959,4244,4987,4245,4987,4246,5013,4250,5013,4251,5040,4258,5040,4259,5066,4268,5065,4269,5091,4281,5090,4282,5116,4296,5115,4297,5140,4313,5139,4314,5163,4333,5186,4354,5186,4355,5209,4378,5208,4379,5230,4404,5252,4432,5251,4432,5292,4493,5291,4494,5328,4562,5362,4638,5361,4638,5392,4719,5391,4720,5418,4807,5417,4807,5439,4899,5439,4900,5457,4997,5456,4997,5469,5098,5477,5203,5476,5203,5479,5312,5478,5312,5478,5352;PU4655,1257;PD4650,1256,4649,1256,4615,1306,4615,1307,4583,1362,4584,1362,4554,1422,4555,1422,4528,1486,4528,1487,4504,1554,4505,1554,4483,1625,4484,1625,4465,1699,4466,1699,4450,1775,4451,1775,4438,1854,4429,1934,4423,2016,4420,2099,4421,2099,4421,2182,4425,2266,4432,2350,4443,2433,4457,2514,4474,2592,4494,2667,4495,2667,4517,2737,4542,2803,4543,2803,4570,2865,4571,2865,4600,2922,4601,2922,4632,2975,4633,2974,4667,3022,4667,3021,4703,3064,4703,3063,4741,3100,4741,3099,4780,3130,4780,3129,4820,3154,4821,3153,4862,3171,4862,3170,4904,3182,4905,3181,4948,3186,4948,3184,4991,3183,4990,3181,5033,3173,5032,3171,5074,3156,5073,3155,5114,3134,5113,3133,5152,3105,5151,3104,5189,3071,5188,3070,5225,3031,5224,3031,5259,2986,5258,2986,5291,2936,5290,2936,5321,2881,5349,2821,5375,2757,5375,2756,5398,2688,5419,2615,5437,2539,5437,2538,5452,2458,5464,2376,5472,2293,5477,2209,5479,2126,5477,2043,5472,1961,5464,1880,5453,1800,5439,1723,5438,1723,5422,1647,5421,1648,5401,1575,5378,1505,5378,1506,5352,1439,5352,1440,5324,1377,5323,1378,5292,1319,5292,1320,5258,1266,5254,1270,5253,1275,5255,1280,5259,1284,5260,1284,4946,2136,4942,2133,4940,2128,4940,2123,4944,2119,4946,2117,4652,1238,4647,1241,4645,1246,4646,1251,4649,1255,4649,1256,4627,1289;PU3356,8485;PD3353,8481,3352,8476,3354,8471,3355,8470,2297,7411,2301,7409,2307,7409,2311,7411,3369,6353,3364,6350,3359,6350,3355,6353,2297,5295;PU3355,4236;PD3352,4241,3352,4243,3307,4241,3307,4240,3254,4235,3187,4225,3107,4209,3107,4208,3017,4185,3018,4184,2970,4169,2971,4169,2922,4151,2873,4130,2824,4107,2824,4106,2774,4080,2775,4079,2726,4050,2726,4049,2678,4016,2631,3979,2631,3978,2585,3938,2586,3937,2542,3892,2543,3892,2501,3843,2502,3842,2463,3789,2464,3788,2428,3730,2429,3730,2397,3667,2398,3666,2369,3598,2370,3598,2346,3525,2347,3525,2327,3446,2328,3446,2314,3361,2315,3361,2306,3271,2303,3175,2304,3175,2307,3082,2308,3082,2317,3000,2324,2964,2332,2930,2333,2930,2342,2899,2352,2870,2353,2870,2364,2843,2365,2843,2378,2819,2392,2797,2393,2797,2407,2777,2408,2777,2423,2759,2424,2760,2440,2743,2441,2744,2458,2729,2459,2729,2477,2716,2478,2717,2516,2695,2517,2696,2558,2680,2559,2681,2603,2669,2603,2670,2648,2662,2648,2663,2696,2658,2696,2659,2744,2656,2744,2657,2843,2656,2942,2655,2942,2654,2990,2652,2990,2651,3037,2647,3037,2646,3083,2638,3082,2637,3126,2625,3126,2624,3167,2608,3167,2607,3205,2585,3205,2584,3223,2571,3222,2570,3240,2555,3239,2555,3256,2538,3255,2537,3271,2519,3270,2519,3285,2498,3284,2498,3298,2475,3297,2475,3310,2450,3309,2450,3320,2423,3330,2394,3339,2362,3338,2362,3346,2328,3345,2328,3352,2291,3351,2291,3360,2210,3359,2210,3362,2117,3359,2021,3358,2021,3349,1930,3348,1931,3334,1846,3333,1846,3314,1768,3313,1768,3289,1695,3261,1627,3260,1627,3228,1564,3227,1564,3191,1506,3191,1507,3152,1453,3151,1454,3110,1405,3066,1360,3065,1361,3020,1320,3019,1321,2972,1284,2923,1251,2923,1252,2874,1222,2824,1196,2774,1173,2774,1174,2725,1153,2725,1154,2676,1136,2629,1121,2629,1122,2539,1098,2539,1099,2459,1083,2391,1074,2339,1069,2339,1070,2294,1068;PU292,6943;PD293,6949,297,6952,302,6954,304,6954,390,7434,384,7433,381,7431,34,7774,39,7776,44,7776,49,7773,51,7769,51,7768,534,7834,532,7839,529,7842,748,8277,752,8274,754,8269,752,8264,749,8260,748,8260,960,7821,964,7824,966,7828,1448,7754,1446,7750,1441,7746,1436,7746,1431,7749,1079,7411,1084,7409,1088,7408,1167,6927,1161,6928,1157,6931,1155,6936,1156,6941,1156,6942,726,7172,725,7167,726,7163,293,6939,292,6944,294,6950,298,6953,303,6954,304,6954,311,6993;PU272,4837;PD272,4837,358,5317,353,5316,349,5314,3,5657,7,5660,13,5660,17,5657,20,5652,20,5651,503,5718,501,5723,497,5725,716,6161,720,6157,722,6152,721,6147,717,6143,716,6143,929,5704,933,5708,934,5712,1416,5638,1414,5633,1409,5630,1404,5629,1399,5632,1047,5295,1052,5292,1056,5292,1135,4811,1130,4811,1125,4814,1123,4819,1124,4825,695,5055,693,5050,695,5046,261,4823,260,4828,262,4833,266,4836,271,4837,272,4837,279,4876;PU8793,5312;PD8798,5309,8801,5305,8801,5299,8798,5295,8794,5292,8791,5292,8791,4551,8786,4552,8782,4556,8781,4561,8047,4561,8048,4566,8052,4570,8057,4571,8057,5312,8062,5310,8066,5306,8067,5302,8801,5302,8800,5297,8796,5293,8791,5292,8791,5252;PU7595,8467;PD7595,8467,7595,7408,7590,7410,7587,7414,7585,7418,6527,7418,6528,7423,6532,7427,6537,7428,6537,8487,6542,8485,6546,8481,6547,8477,7605,8477,7604,8472,7600,8468,7595,8467,7595,8427;PU7595,6350;PD7595,6350,7595,4233,7590,4235,7587,4239,7585,4243,6527,4243,6528,4248,6532,4252,6537,4253,6537,6370,6542,6369,6546,6365,6547,6360,7605,6360,7604,6355,7600,6351,7595,6350,7595,6310;PU7595,2699;PD7595,2699,7595,1534,7593,1487,7592,1487,7586,1441,7585,1441,7575,1396,7561,1352,7560,1353,7543,1311,7542,1311,7521,1271,7521,1272,7497,1234,7496,1235,7468,1200,7437,1169,7437,1170,7404,1142,7404,1143,7369,1119,7368,1120,7332,1100,7332,1101,7294,1085,7293,1086,7254,1075,7254,1076,7214,1069,7214,1070,7173,1068,6939,1068,6939,1069,6899,1071,6899,1072,6859,1079,6859,1080,6820,1092,6820,1093,6782,1108,6783,1109,6746,1129,6747,1130,6712,1154,6713,1155,6680,1183,6681,1183,6651,1215,6652,1216,6624,1250,6625,1251,6601,1289,6602,1289,6581,1329,6582,1329,6565,1371,6565,1372,6552,1415,6553,1415,6543,1461,6544,1461,6538,1507,6539,1507,6537,1554,6537,2719,6539,2766,6540,2766,6546,2812,6547,2812,6557,2857,6558,2857,6571,2901,6572,2901,6589,2943,6590,2942,6611,2982,6612,2982,6636,3019,6637,3019,6664,3054,6665,3053,6695,3085,6696,3084,6728,3112,6729,3111,6763,3135,6764,3134,6800,3154,6801,3153,6839,3168,6839,3167,6878,3179,6878,3177,6918,3184,6918,3183,6959,3186,6959,3185,7193,3185,7193,3184,7234,3182,7234,3181,7274,3174,7274,3173,7313,3162,7312,3161,7350,3145,7350,3144,7386,3124,7385,3123,7420,3099,7419,3099,7452,3071,7451,3070,7482,3038,7481,3038,7508,3003,7507,3002,7531,2965,7531,2964,7551,2924,7568,2882,7567,2882,7580,2838,7589,2793,7595,2746,7594,2746,7596,2699,7595,2699,7595,2659;SP0;PU0,0;IN; \ No newline at end of file diff --git a/share/extensions/tests/data/refs/image_attributes.out b/share/extensions/tests/data/refs/image_attributes.out new file mode 100644 index 0000000..90f335e --- /dev/null +++ b/share/extensions/tests/data/refs/image_attributes.out @@ -0,0 +1,18 @@ + + + + + + + image/svg+xml + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/image_attributes__--id__embeded_image01__--image_rendering__optimizeSpeed__--tab____tab_image_rendering__.out b/share/extensions/tests/data/refs/image_attributes__--id__embeded_image01__--image_rendering__optimizeSpeed__--tab____tab_image_rendering__.out new file mode 100644 index 0000000..9dbd633 --- /dev/null +++ b/share/extensions/tests/data/refs/image_attributes__--id__embeded_image01__--image_rendering__optimizeSpeed__--tab____tab_image_rendering__.out @@ -0,0 +1,18 @@ + + + + + + + image/svg+xml + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/image_attributes__--id__image174__--aspect_ratio__xMinYMin__--tab____tab_aspect_ratio__.out b/share/extensions/tests/data/refs/image_attributes__--id__image174__--aspect_ratio__xMinYMin__--tab____tab_aspect_ratio__.out new file mode 100644 index 0000000..2553d2b --- /dev/null +++ b/share/extensions/tests/data/refs/image_attributes__--id__image174__--aspect_ratio__xMinYMin__--tab____tab_aspect_ratio__.out @@ -0,0 +1,18 @@ + + + + + + + image/svg+xml + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/ink2canvas.out b/share/extensions/tests/data/refs/ink2canvas.out new file mode 100644 index 0000000..9080d0f --- /dev/null +++ b/share/extensions/tests/data/refs/ink2canvas.out @@ -0,0 +1,159 @@ + + + + + Inkscape Output + + + + + + diff --git a/share/extensions/tests/data/refs/inkex_extensions_color.out b/share/extensions/tests/data/refs/inkex_extensions_color.out new file mode 100644 index 0000000..cd9b9b2 --- /dev/null +++ b/share/extensions/tests/data/refs/inkex_extensions_color.out @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/inkex_extensions_color__--id__color_svg.out b/share/extensions/tests/data/refs/inkex_extensions_color__--id__color_svg.out new file mode 100644 index 0000000..cd9b9b2 --- /dev/null +++ b/share/extensions/tests/data/refs/inkex_extensions_color__--id__color_svg.out @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/inkex_extensions_color__--id__r1.out b/share/extensions/tests/data/refs/inkex_extensions_color__--id__r1.out new file mode 100644 index 0000000..8cf8e48 --- /dev/null +++ b/share/extensions/tests/data/refs/inkex_extensions_color__--id__r1.out @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/inkex_extensions_color__--id__r1__--id__r2.out b/share/extensions/tests/data/refs/inkex_extensions_color__--id__r1__--id__r2.out new file mode 100644 index 0000000..9f25954 --- /dev/null +++ b/share/extensions/tests/data/refs/inkex_extensions_color__--id__r1__--id__r2.out @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/inkex_extensions_color__--id__r2.out b/share/extensions/tests/data/refs/inkex_extensions_color__--id__r2.out new file mode 100644 index 0000000..56035d9 --- /dev/null +++ b/share/extensions/tests/data/refs/inkex_extensions_color__--id__r2.out @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/inkex_extensions_color__--id__r3.out b/share/extensions/tests/data/refs/inkex_extensions_color__--id__r3.out new file mode 100644 index 0000000..36dea0a --- /dev/null +++ b/share/extensions/tests/data/refs/inkex_extensions_color__--id__r3.out @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/inkex_extensions_color__--id__r4.out b/share/extensions/tests/data/refs/inkex_extensions_color__--id__r4.out new file mode 100644 index 0000000..9a4c73f --- /dev/null +++ b/share/extensions/tests/data/refs/inkex_extensions_color__--id__r4.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/inkscape_follow_link.out b/share/extensions/tests/data/refs/inkscape_follow_link.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/inkscape_follow_link__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/inkscape_follow_link__--id__p1__--id__r3.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/interp__2e7c2144ef5878a5c0824e02c83dc243.out b/share/extensions/tests/data/refs/interp__2e7c2144ef5878a5c0824e02c83dc243.out new file mode 100644 index 0000000..3a9a33b --- /dev/null +++ b/share/extensions/tests/data/refs/interp__2e7c2144ef5878a5c0824e02c83dc243.out @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/tests/data/refs/interp__359f83409ebaa8716afca1081eb4987d.out b/share/extensions/tests/data/refs/interp__359f83409ebaa8716afca1081eb4987d.out new file mode 100644 index 0000000..f257f70 --- /dev/null +++ b/share/extensions/tests/data/refs/interp__359f83409ebaa8716afca1081eb4987d.out @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/interp_att_g__--id__g53__--att__fill__--start-val____050505__--end-val____000000.out b/share/extensions/tests/data/refs/interp_att_g__--id__g53__--att__fill__--start-val____050505__--end-val____000000.out new file mode 100644 index 0000000..aef30dd --- /dev/null +++ b/share/extensions/tests/data/refs/interp_att_g__--id__g53__--att__fill__--start-val____050505__--end-val____000000.out @@ -0,0 +1,27 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/interp_att_g__--id__g53__--att__fill__--start-val____181818__--end-val____000000.out b/share/extensions/tests/data/refs/interp_att_g__--id__g53__--att__fill__--start-val____181818__--end-val____000000.out new file mode 100644 index 0000000..8b968a3 --- /dev/null +++ b/share/extensions/tests/data/refs/interp_att_g__--id__g53__--att__fill__--start-val____181818__--end-val____000000.out @@ -0,0 +1,27 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/interp_att_g__--id__layer1.out b/share/extensions/tests/data/refs/interp_att_g__--id__layer1.out new file mode 100644 index 0000000..14044d2 --- /dev/null +++ b/share/extensions/tests/data/refs/interp_att_g__--id__layer1.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/jessyink_autotexts__--autoText__slideTitle__--id__t1.out b/share/extensions/tests/data/refs/jessyink_autotexts__--autoText__slideTitle__--id__t1.out new file mode 100644 index 0000000..acc2010 --- /dev/null +++ b/share/extensions/tests/data/refs/jessyink_autotexts__--autoText__slideTitle__--id__t1.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/jessyink_effects__--id__p1__--effectIn__fade__--effectOut__pop.out b/share/extensions/tests/data/refs/jessyink_effects__--id__p1__--effectIn__fade__--effectOut__pop.out new file mode 100644 index 0000000..e101ee3 --- /dev/null +++ b/share/extensions/tests/data/refs/jessyink_effects__--id__p1__--effectIn__fade__--effectOut__pop.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + diff --git a/share/extensions/tests/data/refs/jessyink_effects__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/jessyink_effects__--id__p1__--id__r3.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/jessyink_export__--resolution__1.out b/share/extensions/tests/data/refs/jessyink_export__--resolution__1.out new file mode 100644 index 0000000..b9168de Binary files /dev/null and b/share/extensions/tests/data/refs/jessyink_export__--resolution__1.out differ diff --git a/share/extensions/tests/data/refs/jessyink_install.out b/share/extensions/tests/data/refs/jessyink_install.out new file mode 100644 index 0000000..f9d81b7 --- /dev/null +++ b/share/extensions/tests/data/refs/jessyink_install.out @@ -0,0 +1,2766 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + // Copyright 2008, 2009 Hannes Hochreiner +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +// Set onload event handler. +window.onload = jessyInkInit; + +// Creating a namespace dictionary. The standard Inkscape namespaces are taken from inkex.py. +var NSS = new Object(); +NSS['sodipodi']='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'; +NSS['cc']='http://web.resource.org/cc/'; +NSS['svg']='http://www.w3.org/2000/svg'; +NSS['dc']='http://purl.org/dc/elements/1.1/'; +NSS['rdf']='http://www.w3.org/1999/02/22-rdf-syntax-ns#'; +NSS['inkscape']='http://www.inkscape.org/namespaces/inkscape'; +NSS['xlink']='http://www.w3.org/1999/xlink'; +NSS['xml']='http://www.w3.org/XML/1998/namespace'; +NSS['jessyink']='https://launchpad.net/jessyink'; + +// Keycodes. +var LEFT_KEY = 37; // cursor left keycode +var UP_KEY = 38; // cursor up keycode +var RIGHT_KEY = 39; // cursor right keycode +var DOWN_KEY = 40; // cursor down keycode +var PAGE_UP_KEY = 33; // page up keycode +var PAGE_DOWN_KEY = 34; // page down keycode +var HOME_KEY = 36; // home keycode +var END_KEY = 35; // end keycode +var ENTER_KEY = 13; // next slide +var SPACE_KEY = 32; +var ESCAPE_KEY = 27; + +// Presentation modes. +var SLIDE_MODE = 1; +var INDEX_MODE = 2; +var DRAWING_MODE = 3; + +// Mouse handler actions. +var MOUSE_UP = 1; +var MOUSE_DOWN = 2; +var MOUSE_MOVE = 3; +var MOUSE_WHEEL = 4; + +// Parameters. +var ROOT_NODE = document.getElementsByTagNameNS(NSS["svg"], "svg")[0]; +var HEIGHT = 0; +var WIDTH = 0; +var INDEX_COLUMNS_DEFAULT = 4; +var INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT; +var INDEX_OFFSET = 0; +var STATE_START = -1; +var STATE_END = -2; +var BACKGROUND_COLOR = null; +var slides = new Array(); + +// Initialisation. +var currentMode = SLIDE_MODE; +var masterSlide = null; +var activeSlide = 0; +var activeEffect = 0; +var timeStep = 30; // 40 ms equal 25 frames per second. +var lastFrameTime = null; +var processingEffect = false; +var transCounter = 0; +var effectArray = 0; +var defaultTransitionInDict = new Object(); +defaultTransitionInDict["name"] = "appear"; +var defaultTransitionOutDict = new Object(); +defaultTransitionOutDict["name"] = "appear"; +var jessyInkInitialised = false; + +// Initialise char and key code dictionaries. +var charCodeDictionary = getDefaultCharCodeDictionary(); +var keyCodeDictionary = getDefaultKeyCodeDictionary(); + +// Initialise mouse handler dictionary. +var mouseHandlerDictionary = getDefaultMouseHandlerDictionary(); + +var progress_bar_visible = false; +var timer_elapsed = 0; +var timer_start = timer_elapsed; +var timer_duration = 15; // 15 minutes + +var history_counter = 0; +var history_original_elements = new Array(); +var history_presentation_elements = new Array(); + +var mouse_original_path = null; +var mouse_presentation_path = null; +var mouse_last_x = -1; +var mouse_last_y = -1; +var mouse_min_dist_sqr = 3 * 3; +var path_colour = "red"; +var path_width_default = 3; +var path_width = path_width_default; +var path_paint_width = path_width; + +var number_of_added_slides = 0; + +/** Initialisation function. + * The whole presentation is set-up in this function. + */ +function jessyInkInit() +{ + // Make sure we only execute this code once. Double execution can occur if the onload event handler is set + // in the main svg tag as well (as was recommended in earlier versions). Executing this function twice does + // not lead to any problems, but it takes more time. + if (jessyInkInitialised) + return; + + // Making the presentation scalable. + var VIEWBOX = ROOT_NODE.getAttribute("viewBox"); + + if (VIEWBOX) + { + WIDTH = ROOT_NODE.viewBox.animVal.width; + HEIGHT = ROOT_NODE.viewBox.animVal.height; + } + else + { + HEIGHT = parseFloat(ROOT_NODE.getAttribute("height")); + WIDTH = parseFloat(ROOT_NODE.getAttribute("width")); + ROOT_NODE.setAttribute("viewBox", "0 0 " + WIDTH + " " + HEIGHT); + } + + ROOT_NODE.setAttribute("width", "100%"); + ROOT_NODE.setAttribute("height", "100%"); + + // Setting the background color. + var namedViews = document.getElementsByTagNameNS(NSS["sodipodi"], "namedview"); + + for (var counter = 0; counter < namedViews.length; counter++) + { + if (namedViews[counter].hasAttribute("id") && namedViews[counter].hasAttribute("pagecolor")) + { + if (namedViews[counter].getAttribute("id") == "base") + { + BACKGROUND_COLOR = namedViews[counter].getAttribute("pagecolor"); + var newAttribute = "background-color:" + BACKGROUND_COLOR + ";"; + + if (ROOT_NODE.hasAttribute("style")) + newAttribute += ROOT_NODE.getAttribute("style"); + + ROOT_NODE.setAttribute("style", newAttribute); + } + } + } + + // Defining clip-path. + var defsNodes = document.getElementsByTagNameNS(NSS["svg"], "defs"); + + if (defsNodes.length > 0) + { + var existingClipPath = document.getElementById("jessyInkSlideClipPath"); + + if (!existingClipPath) + { + var rectNode = document.createElementNS(NSS["svg"], "rect"); + var clipPath = document.createElementNS(NSS["svg"], "clipPath"); + + rectNode.setAttribute("x", 0); + rectNode.setAttribute("y", 0); + rectNode.setAttribute("width", WIDTH); + rectNode.setAttribute("height", HEIGHT); + + clipPath.setAttribute("id", "jessyInkSlideClipPath"); + clipPath.setAttribute("clipPathUnits", "userSpaceOnUse"); + + clipPath.appendChild(rectNode); + defsNodes[0].appendChild(clipPath); + } + } + + // Making a list of the slide and finding the master slide. + var nodes = document.getElementsByTagNameNS(NSS["svg"], "g"); + var tempSlides = new Array(); + var existingJessyInkPresentationLayer = null; + + for (var counter = 0; counter < nodes.length; counter++) + { + if (nodes[counter].getAttributeNS(NSS["inkscape"], "groupmode") && (nodes[counter].getAttributeNS(NSS["inkscape"], "groupmode") == "layer")) + { + if (nodes[counter].getAttributeNS(NSS["inkscape"], "label") && nodes[counter].getAttributeNS(NSS["jessyink"], "masterSlide") == "masterSlide") + masterSlide = nodes[counter]; + else if (nodes[counter].getAttributeNS(NSS["inkscape"], "label") && nodes[counter].getAttributeNS(NSS["jessyink"], "presentationLayer") == "presentationLayer") + existingJessyInkPresentationLayer = nodes[counter]; + else + tempSlides.push(nodes[counter].getAttribute("id")); + } + else if (nodes[counter].getAttributeNS(NSS['jessyink'], 'element')) + { + handleElement(nodes[counter]); + } + } + + // Hide master slide set default transitions. + if (masterSlide) + { + masterSlide.style.display = "none"; + + if (masterSlide.hasAttributeNS(NSS["jessyink"], "transitionIn")) + defaultTransitionInDict = propStrToDict(masterSlide.getAttributeNS(NSS["jessyink"], "transitionIn")); + + if (masterSlide.hasAttributeNS(NSS["jessyink"], "transitionOut")) + defaultTransitionOutDict = propStrToDict(masterSlide.getAttributeNS(NSS["jessyink"], "transitionOut")); + } + + if (existingJessyInkPresentationLayer != null) + { + existingJessyInkPresentationLayer.parentNode.removeChild(existingJessyInkPresentationLayer); + } + + // Set start slide. + var hashObj = new LocationHash(window.location.hash); + + activeSlide = hashObj.slideNumber; + activeEffect = hashObj.effectNumber; + + if (activeSlide < 0) + activeSlide = 0; + else if (activeSlide >= tempSlides.length) + activeSlide = tempSlides.length - 1; + + var originalNode = document.getElementById(tempSlides[counter]); + + var JessyInkPresentationLayer = document.createElementNS(NSS["svg"], "g"); + JessyInkPresentationLayer.setAttributeNS(NSS["inkscape"], "groupmode", "layer"); + JessyInkPresentationLayer.setAttributeNS(NSS["inkscape"], "label", "JessyInk Presentation Layer"); + JessyInkPresentationLayer.setAttributeNS(NSS["jessyink"], "presentationLayer", "presentationLayer"); + JessyInkPresentationLayer.setAttribute("id", "jessyink_presentation_layer"); + JessyInkPresentationLayer.style.display = "inherit"; + ROOT_NODE.appendChild(JessyInkPresentationLayer); + + // Gathering all the information about the transitions and effects of the slides, set the background + // from the master slide and substitute the auto-texts. + for (var counter = 0; counter < tempSlides.length; counter++) + { + var originalNode = document.getElementById(tempSlides[counter]); + originalNode.style.display = "none"; + var node = suffixNodeIds(originalNode.cloneNode(true), "_" + counter); + JessyInkPresentationLayer.appendChild(node); + slides[counter] = new Object(); + slides[counter]["original_element"] = originalNode; + slides[counter]["element"] = node; + + // Set build in transition. + slides[counter]["transitionIn"] = new Object(); + + var dict; + + if (node.hasAttributeNS(NSS["jessyink"], "transitionIn")) + dict = propStrToDict(node.getAttributeNS(NSS["jessyink"], "transitionIn")); + else + dict = defaultTransitionInDict; + + slides[counter]["transitionIn"]["name"] = dict["name"]; + slides[counter]["transitionIn"]["options"] = new Object(); + + for (key in dict) + if (key != "name") + slides[counter]["transitionIn"]["options"][key] = dict[key]; + + // Set build out transition. + slides[counter]["transitionOut"] = new Object(); + + if (node.hasAttributeNS(NSS["jessyink"], "transitionOut")) + dict = propStrToDict(node.getAttributeNS(NSS["jessyink"], "transitionOut")); + else + dict = defaultTransitionOutDict; + + slides[counter]["transitionOut"]["name"] = dict["name"]; + slides[counter]["transitionOut"]["options"] = new Object(); + + for (key in dict) + if (key != "name") + slides[counter]["transitionOut"]["options"][key] = dict[key]; + + // Copy master slide content. + if (masterSlide) + { + var clonedNode = suffixNodeIds(masterSlide.cloneNode(true), "_" + counter); + clonedNode.removeAttributeNS(NSS["inkscape"], "groupmode"); + clonedNode.removeAttributeNS(NSS["inkscape"], "label"); + clonedNode.style.display = "inherit"; + + node.insertBefore(clonedNode, node.firstChild); + } + + // Setting clip path. + node.setAttribute("clip-path", "url(#jessyInkSlideClipPath)"); + + // Substitute auto texts. + substituteAutoTexts(node, node.getAttributeNS(NSS["inkscape"], "label"), counter + 1, tempSlides.length); + + node.removeAttributeNS(NSS["inkscape"], "groupmode"); + node.removeAttributeNS(NSS["inkscape"], "label"); + + // Set effects. + var tempEffects = new Array(); + var groups = new Object(); + + for (var IOCounter = 0; IOCounter <= 1; IOCounter++) + { + var propName = ""; + var dir = 0; + + if (IOCounter == 0) + { + propName = "effectIn"; + dir = 1; + } + else if (IOCounter == 1) + { + propName = "effectOut"; + dir = -1; + } + + var effects = getElementsByPropertyNS(node, NSS["jessyink"], propName); + + for (var effectCounter = 0; effectCounter < effects.length; effectCounter++) + { + var element = document.getElementById(effects[effectCounter]); + var dict = propStrToDict(element.getAttributeNS(NSS["jessyink"], propName)); + + // Put every element that has an effect associated with it, into its own group. + // Unless of course, we already put it into its own group. + if (!(groups[element.id])) + { + var newGroup = document.createElementNS(NSS["svg"], "g"); + + element.parentNode.insertBefore(newGroup, element); + newGroup.appendChild(element.parentNode.removeChild(element)); + groups[element.id] = newGroup; + } + + var effectDict = new Object(); + + effectDict["effect"] = dict["name"]; + effectDict["dir"] = dir; + effectDict["element"] = groups[element.id]; + + for (var option in dict) + { + if ((option != "name") && (option != "order")) + { + if (!effectDict["options"]) + effectDict["options"] = new Object(); + + effectDict["options"][option] = dict[option]; + } + } + + if (!tempEffects[dict["order"]]) + tempEffects[dict["order"]] = new Array(); + + tempEffects[dict["order"]][tempEffects[dict["order"]].length] = effectDict; + } + } + + // Make invisible, but keep in rendering tree to ensure that bounding box can be calculated. + node.setAttribute("opacity",0); + node.style.display = "inherit"; + + // Create a transform group. + var transformGroup = document.createElementNS(NSS["svg"], "g"); + + // Add content to transform group. + while (node.firstChild) + transformGroup.appendChild(node.firstChild); + + // Transfer the transform attribute from the node to the transform group. + if (node.getAttribute("transform")) + { + transformGroup.setAttribute("transform", node.getAttribute("transform")); + node.removeAttribute("transform"); + } + + // Create a view group. + var viewGroup = document.createElementNS(NSS["svg"], "g"); + + viewGroup.appendChild(transformGroup); + slides[counter]["viewGroup"] = node.appendChild(viewGroup); + + // Insert background. + if (BACKGROUND_COLOR != null) + { + var rectNode = document.createElementNS(NSS["svg"], "rect"); + + rectNode.setAttribute("x", 0); + rectNode.setAttribute("y", 0); + rectNode.setAttribute("width", WIDTH); + rectNode.setAttribute("height", HEIGHT); + rectNode.setAttribute("id", "jessyInkBackground" + counter); + rectNode.setAttribute("fill", BACKGROUND_COLOR); + + slides[counter]["viewGroup"].insertBefore(rectNode, slides[counter]["viewGroup"].firstChild); + } + + // Set views. + var tempViews = new Array(); + var views = getElementsByPropertyNS(node, NSS["jessyink"], "view"); + var matrixOld = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + + // Set initial view even if there are no other views. + slides[counter]["viewGroup"].setAttribute("transform", matrixOld.toAttribute()); + slides[counter].initialView = matrixOld.toAttribute(); + + for (var viewCounter = 0; viewCounter < views.length; viewCounter++) + { + var element = document.getElementById(views[viewCounter]); + var dict = propStrToDict(element.getAttributeNS(NSS["jessyink"], "view")); + + if (dict["order"] == 0) + { + matrixOld = pointMatrixToTransformation(rectToMatrix(element)).mult((new matrixSVG()).fromSVGMatrix(slides[counter].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(element.parentNode.getScreenCTM())).inv()); + slides[counter].initialView = matrixOld.toAttribute(); + } + else + { + var effectDict = new Object(); + + effectDict["effect"] = dict["name"]; + effectDict["dir"] = 1; + effectDict["element"] = slides[counter]["viewGroup"]; + effectDict["order"] = dict["order"]; + + for (var option in dict) + { + if ((option != "name") && (option != "order")) + { + if (!effectDict["options"]) + effectDict["options"] = new Object(); + + effectDict["options"][option] = dict[option]; + } + } + + effectDict["options"]["matrixNew"] = pointMatrixToTransformation(rectToMatrix(element)).mult((new matrixSVG()).fromSVGMatrix(slides[counter].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(element.parentNode.getScreenCTM())).inv()); + + tempViews[dict["order"]] = effectDict; + } + + // Remove element. + element.parentNode.removeChild(element); + } + + // Consolidate view array and append it to the effect array. + if (tempViews.length > 0) + { + for (var viewCounter = 0; viewCounter < tempViews.length; viewCounter++) + { + if (tempViews[viewCounter]) + { + tempViews[viewCounter]["options"]["matrixOld"] = matrixOld; + matrixOld = tempViews[viewCounter]["options"]["matrixNew"]; + + if (!tempEffects[tempViews[viewCounter]["order"]]) + tempEffects[tempViews[viewCounter]["order"]] = new Array(); + + tempEffects[tempViews[viewCounter]["order"]][tempEffects[tempViews[viewCounter]["order"]].length] = tempViews[viewCounter]; + } + } + } + + // Set consolidated effect array. + if (tempEffects.length > 0) + { + slides[counter]["effects"] = new Array(); + + for (var effectCounter = 0; effectCounter < tempEffects.length; effectCounter++) + { + if (tempEffects[effectCounter]) + slides[counter]["effects"][slides[counter]["effects"].length] = tempEffects[effectCounter]; + } + } + + node.setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + counter + ")) { indexSetActiveSlide(" + counter + "); };"); + + // Set visibility for initial state. + if (counter == activeSlide) + { + node.style.display = "inherit"; + node.setAttribute("opacity",1); + } + else + { + node.style.display = "none"; + node.setAttribute("opacity",0); + } + } + + // Set key handler. + var jessyInkObjects = document.getElementsByTagNameNS(NSS["svg"], "g"); + + for (var counter = 0; counter < jessyInkObjects.length; counter++) + { + var elem = jessyInkObjects[counter]; + + if (elem.getAttributeNS(NSS["jessyink"], "customKeyBindings")) + { + if (elem.getCustomKeyBindings != undefined) + keyCodeDictionary = elem.getCustomKeyBindings(); + + if (elem.getCustomCharBindings != undefined) + charCodeDictionary = elem.getCustomCharBindings(); + } + } + + // Set mouse handler. + var jessyInkMouseHandler = document.getElementsByTagNameNS(NSS["jessyink"], "mousehandler"); + + for (var counter = 0; counter < jessyInkMouseHandler.length; counter++) + { + var elem = jessyInkMouseHandler[counter]; + + if (elem.getMouseHandler != undefined) + { + var tempDict = elem.getMouseHandler(); + + for (mode in tempDict) + { + if (!mouseHandlerDictionary[mode]) + mouseHandlerDictionary[mode] = new Object(); + + for (handler in tempDict[mode]) + mouseHandlerDictionary[mode][handler] = tempDict[mode][handler]; + } + } + } + + // Check effect number. + if ((activeEffect < 0) || (!slides[activeSlide].effects)) + { + activeEffect = 0; + } + else if (activeEffect > slides[activeSlide].effects.length) + { + activeEffect = slides[activeSlide].effects.length; + } + + createProgressBar(JessyInkPresentationLayer); + hideProgressBar(); + setProgressBarValue(activeSlide); + setTimeIndicatorValue(0); + setInterval("updateTimer()", 1000); + setSlideToState(activeSlide, activeEffect); + jessyInkInitialised = true; +} + +/** Function to substitute the auto-texts. + * + * @param node the node + * @param slideName name of the slide the node is on + * @param slideNumber number of the slide the node is on + * @param numberOfSlides number of slides in the presentation + */ +function substituteAutoTexts(node, slideName, slideNumber, numberOfSlides) +{ + var texts = node.getElementsByTagNameNS(NSS["svg"], "tspan"); + + for (var textCounter = 0; textCounter < texts.length; textCounter++) + { + if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "slideNumber") + texts[textCounter].firstChild.nodeValue = slideNumber; + else if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "numberOfSlides") + texts[textCounter].firstChild.nodeValue = numberOfSlides; + else if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "slideTitle") + texts[textCounter].firstChild.nodeValue = slideName; + } +} + +/** Convenience function to get an element depending on whether it has a property with a particular name. + * This function emulates some dearly missed XPath functionality. + * + * @param node the node + * @param namespace namespace of the attribute + * @param name attribute name + */ +function getElementsByPropertyNS(node, namespace, name) +{ + var elems = new Array(); + + if (node.getAttributeNS(namespace, name)) + elems.push(node.getAttribute("id")); + + for (var counter = 0; counter < node.childNodes.length; counter++) + { + if (node.childNodes[counter].nodeType == 1) + elems = elems.concat(getElementsByPropertyNS(node.childNodes[counter], namespace, name)); + } + + return elems; +} + +/** Function to dispatch the next effect, if there is none left, change the slide. + * + * @param dir direction of the change (1 = forwards, -1 = backwards) + */ +function dispatchEffects(dir) +{ + if (slides[activeSlide]["effects"] && (((dir == 1) && (activeEffect < slides[activeSlide]["effects"].length)) || ((dir == -1) && (activeEffect > 0)))) + { + processingEffect = true; + + if (dir == 1) + { + effectArray = slides[activeSlide]["effects"][activeEffect]; + activeEffect += dir; + } + else if (dir == -1) + { + activeEffect += dir; + effectArray = slides[activeSlide]["effects"][activeEffect]; + } + + transCounter = 0; + startTime = (new Date()).getTime(); + lastFrameTime = null; + effect(dir); + } + else if (((dir == 1) && (activeSlide < (slides.length - 1))) || (((dir == -1) && (activeSlide > 0)))) + { + changeSlide(dir); + } +} + +/** Function to skip effects and directly either put the slide into start or end state or change slides. + * + * @param dir direction of the change (1 = forwards, -1 = backwards) + */ +function skipEffects(dir) +{ + if (slides[activeSlide]["effects"] && (((dir == 1) && (activeEffect < slides[activeSlide]["effects"].length)) || ((dir == -1) && (activeEffect > 0)))) + { + processingEffect = true; + + if (slides[activeSlide]["effects"] && (dir == 1)) + activeEffect = slides[activeSlide]["effects"].length; + else + activeEffect = 0; + + if (dir == 1) + setSlideToState(activeSlide, STATE_END); + else + setSlideToState(activeSlide, STATE_START); + + processingEffect = false; + } + else if (((dir == 1) && (activeSlide < (slides.length - 1))) || (((dir == -1) && (activeSlide > 0)))) + { + changeSlide(dir); + } +} + +/** Function to change between slides. + * + * @param dir direction (1 = forwards, -1 = backwards) + */ +function changeSlide(dir) +{ + processingEffect = true; + effectArray = new Array(); + + effectArray[0] = new Object(); + if (dir == 1) + { + effectArray[0]["effect"] = slides[activeSlide]["transitionOut"]["name"]; + effectArray[0]["options"] = slides[activeSlide]["transitionOut"]["options"]; + effectArray[0]["dir"] = -1; + } + else if (dir == -1) + { + effectArray[0]["effect"] = slides[activeSlide]["transitionIn"]["name"]; + effectArray[0]["options"] = slides[activeSlide]["transitionIn"]["options"]; + effectArray[0]["dir"] = 1; + } + effectArray[0]["element"] = slides[activeSlide]["element"]; + + activeSlide += dir; + setProgressBarValue(activeSlide); + + effectArray[1] = new Object(); + + if (dir == 1) + { + effectArray[1]["effect"] = slides[activeSlide]["transitionIn"]["name"]; + effectArray[1]["options"] = slides[activeSlide]["transitionIn"]["options"]; + effectArray[1]["dir"] = 1; + } + else if (dir == -1) + { + effectArray[1]["effect"] = slides[activeSlide]["transitionOut"]["name"]; + effectArray[1]["options"] = slides[activeSlide]["transitionOut"]["options"]; + effectArray[1]["dir"] = -1; + } + + effectArray[1]["element"] = slides[activeSlide]["element"]; + + if (slides[activeSlide]["effects"] && (dir == -1)) + activeEffect = slides[activeSlide]["effects"].length; + else + activeEffect = 0; + + if (dir == -1) + setSlideToState(activeSlide, STATE_END); + else + setSlideToState(activeSlide, STATE_START); + + transCounter = 0; + startTime = (new Date()).getTime(); + lastFrameTime = null; + effect(dir); +} + +/** Function to toggle between index and slide mode. +*/ +function toggleSlideIndex() +{ + var suspendHandle = ROOT_NODE.suspendRedraw(500); + + if (currentMode == SLIDE_MODE) + { + hideProgressBar(); + INDEX_OFFSET = -1; + indexSetPageSlide(activeSlide); + currentMode = INDEX_MODE; + } + else if (currentMode == INDEX_MODE) + { + for (var counter = 0; counter < slides.length; counter++) + { + slides[counter]["element"].setAttribute("transform","scale(1)"); + + if (counter == activeSlide) + { + slides[counter]["element"].style.display = "inherit"; + slides[counter]["element"].setAttribute("opacity",1); + activeEffect = 0; + } + else + { + slides[counter]["element"].setAttribute("opacity",0); + slides[counter]["element"].style.display = "none"; + } + } + currentMode = SLIDE_MODE; + setSlideToState(activeSlide, STATE_START); + setProgressBarValue(activeSlide); + + if (progress_bar_visible) + { + showProgressBar(); + } + } + + ROOT_NODE.unsuspendRedraw(suspendHandle); + ROOT_NODE.forceRedraw(); +} + +/** Function to run an effect. + * + * @param dir direction in which to play the effect (1 = forwards, -1 = backwards) + */ +function effect(dir) +{ + var done = true; + + var suspendHandle = ROOT_NODE.suspendRedraw(200); + + for (var counter = 0; counter < effectArray.length; counter++) + { + if (effectArray[counter]["effect"] == "fade") + done &= fade(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]); + else if (effectArray[counter]["effect"] == "appear") + done &= appear(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]); + else if (effectArray[counter]["effect"] == "pop") + done &= pop(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]); + else if (effectArray[counter]["effect"] == "view") + done &= view(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]); + } + + ROOT_NODE.unsuspendRedraw(suspendHandle); + ROOT_NODE.forceRedraw(); + + if (!done) + { + var currentTime = (new Date()).getTime(); + var timeDiff = 1; + + transCounter = currentTime - startTime; + + if (lastFrameTime != null) + { + timeDiff = timeStep - (currentTime - lastFrameTime); + + if (timeDiff <= 0) + timeDiff = 1; + } + + lastFrameTime = currentTime; + + window.setTimeout("effect(" + dir + ")", timeDiff); + } + else + { + window.location.hash = (activeSlide + 1) + '_' + activeEffect; + processingEffect = false; + } +} + +/** Function to display the index sheet. + * + * @param offsetNumber offset number + */ +function displayIndex(offsetNumber) +{ + var offsetX = 0; + var offsetY = 0; + + if (offsetNumber < 0) + offsetNumber = 0; + else if (offsetNumber >= slides.length) + offsetNumber = slides.length - 1; + + for (var counter = 0; counter < slides.length; counter++) + { + if ((counter < offsetNumber) || (counter > offsetNumber + INDEX_COLUMNS * INDEX_COLUMNS - 1)) + { + slides[counter]["element"].setAttribute("opacity",0); + slides[counter]["element"].style.display = "none"; + } + else + { + offsetX = ((counter - offsetNumber) % INDEX_COLUMNS) * WIDTH; + offsetY = Math.floor((counter - offsetNumber) / INDEX_COLUMNS) * HEIGHT; + + slides[counter]["element"].setAttribute("transform","scale("+1/INDEX_COLUMNS+") translate("+offsetX+","+offsetY+")"); + slides[counter]["element"].style.display = "inherit"; + slides[counter]["element"].setAttribute("opacity",0.5); + } + + setSlideToState(counter, STATE_END); + } + + //do we need to save the current offset? + if (INDEX_OFFSET != offsetNumber) + INDEX_OFFSET = offsetNumber; +} + +/** Function to set the active slide in the slide view. + * + * @param nbr index of the active slide + */ +function slideSetActiveSlide(nbr) +{ + if (nbr >= slides.length) + nbr = slides.length - 1; + else if (nbr < 0) + nbr = 0; + + slides[activeSlide]["element"].setAttribute("opacity",0); + slides[activeSlide]["element"].style.display = "none"; + + activeSlide = parseInt(nbr); + + setSlideToState(activeSlide, STATE_START); + slides[activeSlide]["element"].style.display = "inherit"; + slides[activeSlide]["element"].setAttribute("opacity",1); + + activeEffect = 0; + setProgressBarValue(nbr); +} + +/** Function to set the active slide in the index view. + * + * @param nbr index of the active slide + */ +function indexSetActiveSlide(nbr) +{ + if (nbr >= slides.length) + nbr = slides.length - 1; + else if (nbr < 0) + nbr = 0; + + slides[activeSlide]["element"].setAttribute("opacity",0.5); + + activeSlide = parseInt(nbr); + window.location.hash = (activeSlide + 1) + '_0'; + + slides[activeSlide]["element"].setAttribute("opacity",1); +} + +/** Function to set the page and active slide in index view. + * + * @param nbr index of the active slide + * + * NOTE: To force a redraw, + * set INDEX_OFFSET to -1 before calling indexSetPageSlide(). + * + * This is necessary for zooming (otherwise the index might not + * get redrawn) and when switching to index mode. + * + * INDEX_OFFSET = -1 + * indexSetPageSlide(activeSlide); + */ +function indexSetPageSlide(nbr) +{ + if (nbr >= slides.length) + nbr = slides.length - 1; + else if (nbr < 0) + nbr = 0; + + //calculate the offset + var offset = nbr - nbr % (INDEX_COLUMNS * INDEX_COLUMNS); + + if (offset < 0) + offset = 0; + + //if different from kept offset, then record and change the page + if (offset != INDEX_OFFSET) + { + INDEX_OFFSET = offset; + displayIndex(INDEX_OFFSET); + } + + //set the active slide + indexSetActiveSlide(nbr); +} + +/** Event handler for key press. + * + * @param e the event + */ +function keydown(e) +{ + if (!e) + e = window.event; + + code = e.keyCode || e.charCode; + + if (!processingEffect && keyCodeDictionary[currentMode] && keyCodeDictionary[currentMode][code]) + return keyCodeDictionary[currentMode][code](); + else + document.onkeypress = keypress; +} +// Set event handler for key down. +document.onkeydown = keydown; + +/** Event handler for key press. + * + * @param e the event + */ +function keypress(e) +{ + document.onkeypress = null; + + if (!e) + e = window.event; + + str = String.fromCharCode(e.keyCode || e.charCode); + + if (!processingEffect && charCodeDictionary[currentMode] && charCodeDictionary[currentMode][str]) + return charCodeDictionary[currentMode][str](); +} + +/** Function to supply the default char code dictionary. + * + * @returns default char code dictionary + */ +function getDefaultCharCodeDictionary() +{ + var charCodeDict = new Object(); + + charCodeDict[SLIDE_MODE] = new Object(); + charCodeDict[INDEX_MODE] = new Object(); + charCodeDict[DRAWING_MODE] = new Object(); + + charCodeDict[SLIDE_MODE]["i"] = function () { return toggleSlideIndex(); }; + charCodeDict[SLIDE_MODE]["d"] = function () { return slideSwitchToDrawingMode(); }; + charCodeDict[SLIDE_MODE]["D"] = function () { return slideQueryDuration(); }; + charCodeDict[SLIDE_MODE]["n"] = function () { return slideAddSlide(activeSlide); }; + charCodeDict[SLIDE_MODE]["p"] = function () { return slideToggleProgressBarVisibility(); }; + charCodeDict[SLIDE_MODE]["t"] = function () { return slideResetTimer(); }; + charCodeDict[SLIDE_MODE]["e"] = function () { return slideUpdateExportLayer(); }; + + charCodeDict[DRAWING_MODE]["d"] = function () { return drawingSwitchToSlideMode(); }; + charCodeDict[DRAWING_MODE]["0"] = function () { return drawingResetPathWidth(); }; + charCodeDict[DRAWING_MODE]["1"] = function () { return drawingSetPathWidth(1.0); }; + charCodeDict[DRAWING_MODE]["3"] = function () { return drawingSetPathWidth(3.0); }; + charCodeDict[DRAWING_MODE]["5"] = function () { return drawingSetPathWidth(5.0); }; + charCodeDict[DRAWING_MODE]["7"] = function () { return drawingSetPathWidth(7.0); }; + charCodeDict[DRAWING_MODE]["9"] = function () { return drawingSetPathWidth(9.0); }; + charCodeDict[DRAWING_MODE]["b"] = function () { return drawingSetPathColour("blue"); }; + charCodeDict[DRAWING_MODE]["c"] = function () { return drawingSetPathColour("cyan"); }; + charCodeDict[DRAWING_MODE]["g"] = function () { return drawingSetPathColour("green"); }; + charCodeDict[DRAWING_MODE]["k"] = function () { return drawingSetPathColour("black"); }; + charCodeDict[DRAWING_MODE]["m"] = function () { return drawingSetPathColour("magenta"); }; + charCodeDict[DRAWING_MODE]["o"] = function () { return drawingSetPathColour("orange"); }; + charCodeDict[DRAWING_MODE]["r"] = function () { return drawingSetPathColour("red"); }; + charCodeDict[DRAWING_MODE]["w"] = function () { return drawingSetPathColour("white"); }; + charCodeDict[DRAWING_MODE]["y"] = function () { return drawingSetPathColour("yellow"); }; + charCodeDict[DRAWING_MODE]["z"] = function () { return drawingUndo(); }; + + charCodeDict[INDEX_MODE]["i"] = function () { return toggleSlideIndex(); }; + charCodeDict[INDEX_MODE]["-"] = function () { return indexDecreaseNumberOfColumns(); }; + charCodeDict[INDEX_MODE]["="] = function () { return indexIncreaseNumberOfColumns(); }; + charCodeDict[INDEX_MODE]["+"] = function () { return indexIncreaseNumberOfColumns(); }; + charCodeDict[INDEX_MODE]["0"] = function () { return indexResetNumberOfColumns(); }; + + return charCodeDict; +} + +/** Function to supply the default key code dictionary. + * + * @returns default key code dictionary + */ +function getDefaultKeyCodeDictionary() +{ + var keyCodeDict = new Object(); + + keyCodeDict[SLIDE_MODE] = new Object(); + keyCodeDict[INDEX_MODE] = new Object(); + keyCodeDict[DRAWING_MODE] = new Object(); + + keyCodeDict[SLIDE_MODE][LEFT_KEY] = function() { return dispatchEffects(-1); }; + keyCodeDict[SLIDE_MODE][RIGHT_KEY] = function() { return dispatchEffects(1); }; + keyCodeDict[SLIDE_MODE][UP_KEY] = function() { return skipEffects(-1); }; + keyCodeDict[SLIDE_MODE][DOWN_KEY] = function() { return skipEffects(1); }; + keyCodeDict[SLIDE_MODE][PAGE_UP_KEY] = function() { return dispatchEffects(-1); }; + keyCodeDict[SLIDE_MODE][PAGE_DOWN_KEY] = function() { return dispatchEffects(1); }; + keyCodeDict[SLIDE_MODE][HOME_KEY] = function() { return slideSetActiveSlide(0); }; + keyCodeDict[SLIDE_MODE][END_KEY] = function() { return slideSetActiveSlide(slides.length - 1); }; + keyCodeDict[SLIDE_MODE][SPACE_KEY] = function() { return dispatchEffects(1); }; + + keyCodeDict[INDEX_MODE][LEFT_KEY] = function() { return indexSetPageSlide(activeSlide - 1); }; + keyCodeDict[INDEX_MODE][RIGHT_KEY] = function() { return indexSetPageSlide(activeSlide + 1); }; + keyCodeDict[INDEX_MODE][UP_KEY] = function() { return indexSetPageSlide(activeSlide - INDEX_COLUMNS); }; + keyCodeDict[INDEX_MODE][DOWN_KEY] = function() { return indexSetPageSlide(activeSlide + INDEX_COLUMNS); }; + keyCodeDict[INDEX_MODE][PAGE_UP_KEY] = function() { return indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS); }; + keyCodeDict[INDEX_MODE][PAGE_DOWN_KEY] = function() { return indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS); }; + keyCodeDict[INDEX_MODE][HOME_KEY] = function() { return indexSetPageSlide(0); }; + keyCodeDict[INDEX_MODE][END_KEY] = function() { return indexSetPageSlide(slides.length - 1); }; + keyCodeDict[INDEX_MODE][ENTER_KEY] = function() { return toggleSlideIndex(); }; + + keyCodeDict[DRAWING_MODE][ESCAPE_KEY] = function () { return drawingSwitchToSlideMode(); }; + + return keyCodeDict; +} + +/** Function to handle all mouse events. + * + * @param evnt event + * @param action type of event (e.g. mouse up, mouse wheel) + */ +function mouseHandlerDispatch(evnt, action) +{ + if (!evnt) + evnt = window.event; + + var retVal = true; + + if (!processingEffect && mouseHandlerDictionary[currentMode] && mouseHandlerDictionary[currentMode][action]) + { + var subRetVal = mouseHandlerDictionary[currentMode][action](evnt); + + if (subRetVal != null && subRetVal != undefined) + retVal = subRetVal; + } + + if (evnt.preventDefault && !retVal) + evnt.preventDefault(); + + evnt.returnValue = retVal; + + return retVal; +} + +// Set mouse event handler. +document.onmousedown = function(e) { return mouseHandlerDispatch(e, MOUSE_DOWN); }; +document.onmouseup = function(e) { return mouseHandlerDispatch(e, MOUSE_UP); }; +document.onmousemove = function(e) { return mouseHandlerDispatch(e, MOUSE_MOVE); }; + +// Moz +if (window.addEventListener) +{ + window.addEventListener('DOMMouseScroll', function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); }, false); +} + +// Opera Safari OK - may not work in IE +window.onmousewheel = function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); }; + +/** Function to supply the default mouse handler dictionary. + * + * @returns default mouse handler dictionary + */ +function getDefaultMouseHandlerDictionary() +{ + var mouseHandlerDict = new Object(); + + mouseHandlerDict[SLIDE_MODE] = new Object(); + mouseHandlerDict[INDEX_MODE] = new Object(); + mouseHandlerDict[DRAWING_MODE] = new Object(); + + mouseHandlerDict[SLIDE_MODE][MOUSE_DOWN] = function(evnt) { return dispatchEffects(1); }; + mouseHandlerDict[SLIDE_MODE][MOUSE_WHEEL] = function(evnt) { return slideMousewheel(evnt); }; + + mouseHandlerDict[INDEX_MODE][MOUSE_DOWN] = function(evnt) { return toggleSlideIndex(); }; + + mouseHandlerDict[DRAWING_MODE][MOUSE_DOWN] = function(evnt) { return drawingMousedown(evnt); }; + mouseHandlerDict[DRAWING_MODE][MOUSE_UP] = function(evnt) { return drawingMouseup(evnt); }; + mouseHandlerDict[DRAWING_MODE][MOUSE_MOVE] = function(evnt) { return drawingMousemove(evnt); }; + + return mouseHandlerDict; +} + +/** Function to switch from slide mode to drawing mode. +*/ +function slideSwitchToDrawingMode() +{ + currentMode = DRAWING_MODE; + + var tempDict; + + if (ROOT_NODE.hasAttribute("style")) + tempDict = propStrToDict(ROOT_NODE.getAttribute("style")); + else + tempDict = new Object(); + + tempDict["cursor"] = "crosshair"; + ROOT_NODE.setAttribute("style", dictToPropStr(tempDict)); +} + +/** Function to switch from drawing mode to slide mode. +*/ +function drawingSwitchToSlideMode() +{ + currentMode = SLIDE_MODE; + + var tempDict; + + if (ROOT_NODE.hasAttribute("style")) + tempDict = propStrToDict(ROOT_NODE.getAttribute("style")); + else + tempDict = new Object(); + + tempDict["cursor"] = "auto"; + ROOT_NODE.setAttribute("style", dictToPropStr(tempDict)); +} + +/** Function to decrease the number of columns in index mode. +*/ +function indexDecreaseNumberOfColumns() +{ + if (INDEX_COLUMNS >= 3) + { + INDEX_COLUMNS -= 1; + INDEX_OFFSET = -1 + indexSetPageSlide(activeSlide); + } +} + +/** Function to increase the number of columns in index mode. +*/ +function indexIncreaseNumberOfColumns() +{ + if (INDEX_COLUMNS < 7) + { + INDEX_COLUMNS += 1; + INDEX_OFFSET = -1 + indexSetPageSlide(activeSlide); + } +} + +/** Function to reset the number of columns in index mode. +*/ +function indexResetNumberOfColumns() +{ + if (INDEX_COLUMNS != INDEX_COLUMNS_DEFAULT) + { + INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT; + INDEX_OFFSET = -1 + indexSetPageSlide(activeSlide); + } +} + +/** Function to reset path width in drawing mode. +*/ +function drawingResetPathWidth() +{ + path_width = path_width_default; + set_path_paint_width(); +} + +/** Function to set path width in drawing mode. + * + * @param width new path width + */ +function drawingSetPathWidth(width) +{ + path_width = width; + set_path_paint_width(); +} + +/** Function to set path colour in drawing mode. + * + * @param colour new path colour + */ +function drawingSetPathColour(colour) +{ + path_colour = colour; +} + +/** Function to query the duration of the presentation from the user in slide mode. +*/ +function slideQueryDuration() +{ + var new_duration = prompt("Length of presentation in minutes?", timer_duration); + + if ((new_duration != null) && (new_duration != '')) + { + timer_duration = new_duration; + } + + updateTimer(); +} + +/** Function to add new slide in slide mode. + * + * @param afterSlide after which slide to insert the new one + */ +function slideAddSlide(afterSlide) +{ + addSlide(afterSlide); + slideSetActiveSlide(afterSlide + 1); + updateTimer(); +} + +/** Function to toggle the visibility of the progress bar in slide mode. +*/ +function slideToggleProgressBarVisibility() +{ + if (progress_bar_visible) + { + progress_bar_visible = false; + hideProgressBar(); + } + else + { + progress_bar_visible = true; + showProgressBar(); + } +} + +/** Function to reset the timer in slide mode. +*/ +function slideResetTimer() +{ + timer_start = timer_elapsed; + updateTimer(); +} + +/** Convenience function to pad a string with zero in front up to a certain length. + */ +function padString(str, len) +{ + var outStr = str; + + while (outStr.length < len) + { + outStr = '0' + outStr; + } + + return outStr; +} + +/** Function to update the export layer. + */ +function slideUpdateExportLayer() +{ + // Suspend redraw since we are going to mess with the slides. + var suspendHandle = ROOT_NODE.suspendRedraw(2000); + + var tmpActiveSlide = activeSlide; + var tmpActiveEffect = activeEffect; + var exportedLayers = new Array(); + + for (var counterSlides = 0; counterSlides < slides.length; counterSlides++) + { + var exportNode; + + setSlideToState(counterSlides, STATE_START); + + var maxEffect = 0; + + if (slides[counterSlides].effects) + { + maxEffect = slides[counterSlides].effects.length; + } + + exportNode = slides[counterSlides].element.cloneNode(true); + exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer"); + exportNode.setAttributeNS(NSS["inkscape"], "label", "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString("0", maxEffect.toString().length)); + + exportedLayers.push(exportNode); + + if (slides[counterSlides]["effects"]) + { + for (var counter = 0; counter < slides[counterSlides]["effects"].length; counter++) + { + for (var subCounter = 0; subCounter < slides[counterSlides]["effects"][counter].length; subCounter++) + { + var effect = slides[counterSlides]["effects"][counter][subCounter]; + if (effect["effect"] == "fade") + fade(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "appear") + appear(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "pop") + pop(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "view") + view(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); + } + + var layerName = "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString((counter + 1).toString(), maxEffect.toString().length); + exportNode = slides[counterSlides].element.cloneNode(true); + exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer"); + exportNode.setAttributeNS(NSS["inkscape"], "label", layerName); + exportNode.setAttribute("id", layerName); + + exportedLayers.push(exportNode); + } + } + } + + activeSlide = tmpActiveSlide; + activeEffect = tmpActiveEffect; + setSlideToState(activeSlide, activeEffect); + + // Copy image. + var newDoc = document.documentElement.cloneNode(true); + + // Delete viewbox form new imag and set width and height. + newDoc.removeAttribute('viewbox'); + newDoc.setAttribute('width', WIDTH); + newDoc.setAttribute('height', HEIGHT); + + // Delete all layers and script elements. + var nodesToBeRemoved = new Array(); + + for (var childCounter = 0; childCounter < newDoc.childNodes.length; childCounter++) + { + var child = newDoc.childNodes[childCounter]; + + if (child.nodeType == 1) + { + if ((child.nodeName.toUpperCase() == 'G') || (child.nodeName.toUpperCase() == 'SCRIPT')) + { + nodesToBeRemoved.push(child); + } + } + } + + for (var ndCounter = 0; ndCounter < nodesToBeRemoved.length; ndCounter++) + { + var nd = nodesToBeRemoved[ndCounter]; + + // Before removing the node, check whether it contains any definitions. + var defs = nd.getElementsByTagNameNS(NSS["svg"], "defs"); + + for (var defsCounter = 0; defsCounter < defs.length; defsCounter++) + { + if (defs[defsCounter].id) + { + newDoc.appendChild(defs[defsCounter].cloneNode(true)); + } + } + + // Remove node. + nd.parentNode.removeChild(nd); + } + + // Set current layer. + if (exportedLayers[0]) + { + var namedView; + + for (var nodeCounter = 0; nodeCounter < newDoc.childNodes.length; nodeCounter++) + { + if ((newDoc.childNodes[nodeCounter].nodeType == 1) && (newDoc.childNodes[nodeCounter].getAttribute('id') == 'base')) + { + namedView = newDoc.childNodes[nodeCounter]; + } + } + + if (namedView) + { + namedView.setAttributeNS(NSS['inkscape'], 'current-layer', exportedLayers[0].getAttributeNS(NSS['inkscape'], 'label')); + } + } + + // Add exported layers. + while (exportedLayers.length > 0) + { + var nd = exportedLayers.pop(); + + nd.setAttribute("opacity",1); + nd.style.display = "inherit"; + + newDoc.appendChild(nd); + } + + // Serialise the new document. + window.location = 'data:application/svg+xml;base64;charset=utf-8,' + window.btoa(unescape(encodeURIComponent((new XMLSerializer()).serializeToString(newDoc)))); + + // Unsuspend redraw. + ROOT_NODE.unsuspendRedraw(suspendHandle); + ROOT_NODE.forceRedraw(); +} + +/** Function to undo last drawing operation. +*/ +function drawingUndo() +{ + mouse_presentation_path = null; + mouse_original_path = null; + + if (history_presentation_elements.length > 0) + { + var p = history_presentation_elements.pop(); + var parent = p.parentNode.removeChild(p); + + p = history_original_elements.pop(); + parent = p.parentNode.removeChild(p); + } +} + +/** Event handler for mouse down in drawing mode. + * + * @param e the event + */ +function drawingMousedown(e) +{ + var value = 0; + + if (e.button) + value = e.button; + else if (e.which) + value = e.which; + + if (value == 1) + { + history_counter++; + + var p = calcCoord(e); + + mouse_last_x = e.clientX; + mouse_last_y = e.clientY; + mouse_original_path = document.createElementNS(NSS["svg"], "path"); + mouse_original_path.setAttribute("stroke", path_colour); + mouse_original_path.setAttribute("stroke-width", path_paint_width); + mouse_original_path.setAttribute("fill", "none"); + mouse_original_path.setAttribute("id", "path " + Date()); + mouse_original_path.setAttribute("d", "M" + p.x + "," + p.y); + slides[activeSlide]["original_element"].appendChild(mouse_original_path); + history_original_elements.push(mouse_original_path); + + mouse_presentation_path = document.createElementNS(NSS["svg"], "path"); + mouse_presentation_path.setAttribute("stroke", path_colour); + mouse_presentation_path.setAttribute("stroke-width", path_paint_width); + mouse_presentation_path.setAttribute("fill", "none"); + mouse_presentation_path.setAttribute("id", "path " + Date() + " presentation copy"); + mouse_presentation_path.setAttribute("d", "M" + p.x + "," + p.y); + + if (slides[activeSlide]["viewGroup"]) + slides[activeSlide]["viewGroup"].appendChild(mouse_presentation_path); + else + slides[activeSlide]["element"].appendChild(mouse_presentation_path); + + history_presentation_elements.push(mouse_presentation_path); + + return false; + } + + return true; +} + +/** Event handler for mouse up in drawing mode. + * + * @param e the event + */ +function drawingMouseup(e) +{ + if(!e) + e = window.event; + + if (mouse_presentation_path != null) + { + var p = calcCoord(e); + var d = mouse_presentation_path.getAttribute("d"); + d += " L" + p.x + "," + p.y; + mouse_presentation_path.setAttribute("d", d); + mouse_presentation_path = null; + mouse_original_path.setAttribute("d", d); + mouse_original_path = null; + + return false; + } + + return true; +} + +/** Event handler for mouse move in drawing mode. + * + * @param e the event + */ +function drawingMousemove(e) +{ + if(!e) + e = window.event; + + var dist = (mouse_last_x - e.clientX) * (mouse_last_x - e.clientX) + (mouse_last_y - e.clientY) * (mouse_last_y - e.clientY); + + if (mouse_presentation_path == null) + { + return true; + } + + if (dist >= mouse_min_dist_sqr) + { + var p = calcCoord(e); + var d = mouse_presentation_path.getAttribute("d"); + d += " L" + p.x + "," + p.y; + mouse_presentation_path.setAttribute("d", d); + mouse_original_path.setAttribute("d", d); + mouse_last_x = e.clientX; + mouse_last_y = e.clientY; + } + + return false; +} + +/** Event handler for mouse wheel events in slide mode. + * based on http://adomas.org/javascript-mouse-wheel/ + * + * @param e the event + */ +function slideMousewheel(e) +{ + var delta = 0; + + if (!e) + e = window.event; + + if (e.wheelDelta) + { // IE Opera + delta = e.wheelDelta/120; + } + else if (e.detail) + { // MOZ + delta = -e.detail/3; + } + + if (delta > 0) + skipEffects(-1); + else if (delta < 0) + skipEffects(1); + + if (e.preventDefault) + e.preventDefault(); + + e.returnValue = false; +} + +/** Event handler for mouse wheel events in index mode. + * based on http://adomas.org/javascript-mouse-wheel/ + * + * @param e the event + */ +function indexMousewheel(e) +{ + var delta = 0; + + if (!e) + e = window.event; + + if (e.wheelDelta) + { // IE Opera + delta = e.wheelDelta/120; + } + else if (e.detail) + { // MOZ + delta = -e.detail/3; + } + + if (delta > 0) + indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS); + else if (delta < 0) + indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS); + + if (e.preventDefault) + e.preventDefault(); + + e.returnValue = false; +} + +/** Function to set the path paint width. +*/ +function set_path_paint_width() +{ + var svgPoint1 = document.documentElement.createSVGPoint(); + var svgPoint2 = document.documentElement.createSVGPoint(); + + svgPoint1.x = 0.0; + svgPoint1.y = 0.0; + svgPoint2.x = 1.0; + svgPoint2.y = 0.0; + + var matrix = slides[activeSlide]["element"].getTransformToElement(ROOT_NODE); + + if (slides[activeSlide]["viewGroup"]) + matrix = slides[activeSlide]["viewGroup"].getTransformToElement(ROOT_NODE); + + svgPoint1 = svgPoint1.matrixTransform(matrix); + svgPoint2 = svgPoint2.matrixTransform(matrix); + + path_paint_width = path_width / Math.sqrt((svgPoint2.x - svgPoint1.x) * (svgPoint2.x - svgPoint1.x) + (svgPoint2.y - svgPoint1.y) * (svgPoint2.y - svgPoint1.y)); +} + +/** The view effect. + * + * @param dir direction the effect should be played (1 = forwards, -1 = backwards) + * @param element the element the effect should be applied to + * @param time the time that has elapsed since the beginning of the effect + * @param options a dictionary with additional options (e.g. length of the effect); for the view effect the options need to contain the old and the new matrix. + */ +function view(dir, element, time, options) +{ + var length = 250; + var fraction; + + if (!options["matrixInitial"]) + { + var tempString = slides[activeSlide]["viewGroup"].getAttribute("transform"); + + if (tempString) + options["matrixInitial"] = (new matrixSVG()).fromAttribute(tempString); + else + options["matrixInitial"] = (new matrixSVG()).fromSVGElements(1, 0, 0, 1, 0, 0); + } + + if ((time == STATE_END) || (time == STATE_START)) + fraction = 1; + else + { + if (options && options["length"]) + length = options["length"]; + + fraction = time / length; + } + + if (dir == 1) + { + if (fraction <= 0) + { + element.setAttribute("transform", options["matrixInitial"].toAttribute()); + } + else if (fraction >= 1) + { + element.setAttribute("transform", options["matrixNew"].toAttribute()); + + set_path_paint_width(); + + options["matrixInitial"] = null; + return true; + } + else + { + element.setAttribute("transform", options["matrixInitial"].mix(options["matrixNew"], fraction).toAttribute()); + } + } + else if (dir == -1) + { + if (fraction <= 0) + { + element.setAttribute("transform", options["matrixInitial"].toAttribute()); + } + else if (fraction >= 1) + { + element.setAttribute("transform", options["matrixOld"].toAttribute()); + set_path_paint_width(); + + options["matrixInitial"] = null; + return true; + } + else + { + element.setAttribute("transform", options["matrixInitial"].mix(options["matrixOld"], fraction).toAttribute()); + } + } + + return false; +} + +/** The fade effect. + * + * @param dir direction the effect should be played (1 = forwards, -1 = backwards) + * @param element the element the effect should be applied to + * @param time the time that has elapsed since the beginning of the effect + * @param options a dictionary with additional options (e.g. length of the effect) + */ +function fade(dir, element, time, options) +{ + var length = 250; + var fraction; + + if ((time == STATE_END) || (time == STATE_START)) + fraction = 1; + else + { + if (options && options["length"]) + length = options["length"]; + + fraction = time / length; + } + + if (dir == 1) + { + if (fraction <= 0) + { + element.style.display = "none"; + element.setAttribute("opacity", 0); + } + else if (fraction >= 1) + { + element.style.display = "inherit"; + element.setAttribute("opacity", 1); + return true; + } + else + { + element.style.display = "inherit"; + element.setAttribute("opacity", fraction); + } + } + else if (dir == -1) + { + if (fraction <= 0) + { + element.style.display = "inherit"; + element.setAttribute("opacity", 1); + } + else if (fraction >= 1) + { + element.setAttribute("opacity", 0); + element.style.display = "none"; + return true; + } + else + { + element.style.display = "inherit"; + element.setAttribute("opacity", 1 - fraction); + } + } + return false; +} + +/** The appear effect. + * + * @param dir direction the effect should be played (1 = forwards, -1 = backwards) + * @param element the element the effect should be applied to + * @param time the time that has elapsed since the beginning of the effect + * @param options a dictionary with additional options (e.g. length of the effect) + */ +function appear(dir, element, time, options) +{ + if (dir == 1) + { + element.style.display = "inherit"; + element.setAttribute("opacity",1); + } + else if (dir == -1) + { + element.style.display = "none"; + element.setAttribute("opacity",0); + } + return true; +} + +/** The pop effect. + * + * @param dir direction the effect should be played (1 = forwards, -1 = backwards) + * @param element the element the effect should be applied to + * @param time the time that has elapsed since the beginning of the effect + * @param options a dictionary with additional options (e.g. length of the effect) + */ +function pop(dir, element, time, options) +{ + var length = 500; + var fraction; + + if ((time == STATE_END) || (time == STATE_START)) + fraction = 1; + else + { + if (options && options["length"]) + length = options["length"]; + + fraction = time / length; + } + + if (dir == 1) + { + if (fraction <= 0) + { + element.setAttribute("opacity", 0); + element.setAttribute("transform", "scale(0)"); + element.style.display = "none"; + } + else if (fraction >= 1) + { + element.setAttribute("opacity", 1); + element.removeAttribute("transform"); + element.style.display = "inherit"; + return true; + } + else + { + element.style.display = "inherit"; + var opacityFraction = fraction * 3; + if (opacityFraction > 1) + opacityFraction = 1; + element.setAttribute("opacity", opacityFraction); + var offsetX = WIDTH * (1.0 - fraction) / 2.0; + var offsetY = HEIGHT * (1.0 - fraction) / 2.0; + element.setAttribute("transform", "translate(" + offsetX + "," + offsetY + ") scale(" + fraction + ")"); + } + } + else if (dir == -1) + { + if (fraction <= 0) + { + element.setAttribute("opacity", 1); + element.setAttribute("transform", "scale(1)"); + element.style.display = "inherit"; + } + else if (fraction >= 1) + { + element.setAttribute("opacity", 0); + element.removeAttribute("transform"); + element.style.display = "none"; + return true; + } + else + { + element.setAttribute("opacity", 1 - fraction); + element.setAttribute("transform", "scale(" + 1 - fraction + ")"); + element.style.display = "inherit"; + } + } + return false; +} + +/** Function to set a slide either to the start or the end state. + * + * @param slide the slide to use + * @param state the state into which the slide should be set + */ +function setSlideToState(slide, state) +{ + slides[slide]["viewGroup"].setAttribute("transform", slides[slide].initialView); + + if (slides[slide]["effects"]) + { + if (state == STATE_END) + { + for (var counter = 0; counter < slides[slide]["effects"].length; counter++) + { + for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++) + { + var effect = slides[slide]["effects"][counter][subCounter]; + if (effect["effect"] == "fade") + fade(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "appear") + appear(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "pop") + pop(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "view") + view(effect["dir"], effect["element"], STATE_END, effect["options"]); + } + } + } + else if (state == STATE_START) + { + for (var counter = slides[slide]["effects"].length - 1; counter >= 0; counter--) + { + for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++) + { + var effect = slides[slide]["effects"][counter][subCounter]; + if (effect["effect"] == "fade") + fade(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]); + else if (effect["effect"] == "appear") + appear(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]); + else if (effect["effect"] == "pop") + pop(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]); + else if (effect["effect"] == "view") + view(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]); + } + } + } + else + { + setSlideToState(slide, STATE_START); + + for (var counter = 0; counter < slides[slide]["effects"].length && counter < state; counter++) + { + for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++) + { + var effect = slides[slide]["effects"][counter][subCounter]; + if (effect["effect"] == "fade") + fade(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "appear") + appear(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "pop") + pop(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "view") + view(effect["dir"], effect["element"], STATE_END, effect["options"]); + } + } + } + } + + window.location.hash = (activeSlide + 1) + '_' + activeEffect; +} + +/** Convenience function to translate a attribute string into a dictionary. + * + * @param str the attribute string + * @return a dictionary + * @see dictToPropStr + */ +function propStrToDict(str) +{ + var list = str.split(";"); + var obj = new Object(); + + for (var counter = 0; counter < list.length; counter++) + { + var subStr = list[counter]; + var subList = subStr.split(":"); + if (subList.length == 2) + { + obj[subList[0]] = subList[1]; + } + } + + return obj; +} + +/** Convenience function to translate a dictionary into a string that can be used as an attribute. + * + * @param dict the dictionary to convert + * @return a string that can be used as an attribute + * @see propStrToDict + */ +function dictToPropStr(dict) +{ + var str = ""; + + for (var key in dict) + { + str += key + ":" + dict[key] + ";"; + } + + return str; +} + +/** Sub-function to add a suffix to the ids of the node and all its children. + * + * @param node the node to change + * @param suffix the suffix to add + * @param replace dictionary of replaced ids + * @see suffixNodeIds + */ +function suffixNoneIds_sub(node, suffix, replace) +{ + if (node.nodeType == 1) + { + if (node.getAttribute("id")) + { + var id = node.getAttribute("id") + replace["#" + id] = id + suffix; + node.setAttribute("id", id + suffix); + } + + if ((node.nodeName == "use") && (node.getAttributeNS(NSS["xlink"], "href")) && (replace[node.getAttribute(NSS["xlink"], "href")])) + node.setAttribute(NSS["xlink"], "href", node.getAttribute(NSS["xlink"], "href") + suffix); + + if (node.childNodes) + { + for (var counter = 0; counter < node.childNodes.length; counter++) + suffixNoneIds_sub(node.childNodes[counter], suffix, replace); + } + } +} + +/** Function to add a suffix to the ids of the node and all its children. + * + * @param node the node to change + * @param suffix the suffix to add + * @return the changed node + * @see suffixNodeIds_sub + */ +function suffixNodeIds(node, suffix) +{ + var replace = new Object(); + + suffixNoneIds_sub(node, suffix, replace); + + return node; +} + +/** Function to build a progress bar. + * + * @param parent node to attach the progress bar to + */ +function createProgressBar(parent_node) +{ + var g = document.createElementNS(NSS["svg"], "g"); + g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)"); + g.setAttribute("id", "layer_progress_bar"); + g.setAttribute("style", "display: none;"); + + var rect_progress_bar = document.createElementNS(NSS["svg"], "rect"); + rect_progress_bar.setAttribute("style", "marker: none; fill: rgb(128, 128, 128); stroke: none;"); + rect_progress_bar.setAttribute("id", "rect_progress_bar"); + rect_progress_bar.setAttribute("x", 0); + rect_progress_bar.setAttribute("y", 0.99 * HEIGHT); + rect_progress_bar.setAttribute("width", 0); + rect_progress_bar.setAttribute("height", 0.01 * HEIGHT); + g.appendChild(rect_progress_bar); + + var circle_timer_indicator = document.createElementNS(NSS["svg"], "circle"); + circle_timer_indicator.setAttribute("style", "marker: none; fill: rgb(255, 0, 0); stroke: none;"); + circle_timer_indicator.setAttribute("id", "circle_timer_indicator"); + circle_timer_indicator.setAttribute("cx", 0.005 * HEIGHT); + circle_timer_indicator.setAttribute("cy", 0.995 * HEIGHT); + circle_timer_indicator.setAttribute("r", 0.005 * HEIGHT); + g.appendChild(circle_timer_indicator); + + parent_node.appendChild(g); +} + +/** Function to hide the progress bar. + * + */ +function hideProgressBar() +{ + var progress_bar = document.getElementById("layer_progress_bar"); + + if (!progress_bar) + { + return; + } + + progress_bar.setAttribute("style", "display: none;"); +} + +/** Function to show the progress bar. + * + */ +function showProgressBar() +{ + var progress_bar = document.getElementById("layer_progress_bar"); + + if (!progress_bar) + { + return; + } + + progress_bar.setAttribute("style", "display: inherit;"); +} + +/** Set progress bar value. + * + * @param value the current slide number + * + */ +function setProgressBarValue(value) +{ + var rect_progress_bar = document.getElementById("rect_progress_bar"); + + if (!rect_progress_bar) + { + return; + } + + if (value < 1) + { + // First slide, assumed to be the title of the presentation + var x = 0; + var w = 0.01 * HEIGHT; + } + else if (value >= slides.length - 1) + { + // Last slide, assumed to be the end of the presentation + var x = WIDTH - 0.01 * HEIGHT; + var w = 0.01 * HEIGHT; + } + else + { + value -= 1; + value /= (slides.length - 2); + + var x = WIDTH * value; + var w = WIDTH / (slides.length - 2); + } + + rect_progress_bar.setAttribute("x", x); + rect_progress_bar.setAttribute("width", w); +} + +/** Set time indicator. + * + * @param value the percentage of time elapse so far between 0.0 and 1.0 + * + */ +function setTimeIndicatorValue(value) +{ + var circle_timer_indicator = document.getElementById("circle_timer_indicator"); + + if (!circle_timer_indicator) + { + return; + } + + if (value < 0.0) + { + value = 0.0; + } + + if (value > 1.0) + { + value = 1.0; + } + + var cx = (WIDTH - 0.01 * HEIGHT) * value + 0.005 * HEIGHT; + circle_timer_indicator.setAttribute("cx", cx); +} + +/** Update timer. + * + */ +function updateTimer() +{ + timer_elapsed += 1; + setTimeIndicatorValue((timer_elapsed - timer_start) / (60 * timer_duration)); +} + +/** Convert screen coordinates to document coordinates. + * + * @param e event with screen coordinates + * + * @return coordinates in SVG file coordinate system + */ +function calcCoord(e) +{ + var svgPoint = document.documentElement.createSVGPoint(); + svgPoint.x = e.clientX + window.pageXOffset; + svgPoint.y = e.clientY + window.pageYOffset; + + var matrix = slides[activeSlide]["element"].getScreenCTM(); + + if (slides[activeSlide]["viewGroup"]) + matrix = slides[activeSlide]["viewGroup"].getScreenCTM(); + + svgPoint = svgPoint.matrixTransform(matrix.inverse()); + return svgPoint; +} + +/** Add slide. + * + * @param after_slide after which slide the new slide should be inserted into the presentation + */ +function addSlide(after_slide) +{ + number_of_added_slides++; + + var g = document.createElementNS(NSS["svg"], "g"); + g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)"); + g.setAttribute("id", "Whiteboard " + Date() + " presentation copy"); + g.setAttribute("style", "display: none;"); + + var new_slide = new Object(); + new_slide["element"] = g; + + // Set build in transition. + new_slide["transitionIn"] = new Object(); + var dict = defaultTransitionInDict; + new_slide["transitionIn"]["name"] = dict["name"]; + new_slide["transitionIn"]["options"] = new Object(); + + for (key in dict) + if (key != "name") + new_slide["transitionIn"]["options"][key] = dict[key]; + + // Set build out transition. + new_slide["transitionOut"] = new Object(); + dict = defaultTransitionOutDict; + new_slide["transitionOut"]["name"] = dict["name"]; + new_slide["transitionOut"]["options"] = new Object(); + + for (key in dict) + if (key != "name") + new_slide["transitionOut"]["options"][key] = dict[key]; + + // Copy master slide content. + if (masterSlide) + { + var clonedNode = suffixNodeIds(masterSlide.cloneNode(true), "_" + Date() + " presentation_copy"); + clonedNode.removeAttributeNS(NSS["inkscape"], "groupmode"); + clonedNode.removeAttributeNS(NSS["inkscape"], "label"); + clonedNode.style.display = "inherit"; + + g.appendChild(clonedNode); + } + + // Substitute auto texts. + substituteAutoTexts(g, "Whiteboard " + number_of_added_slides, "W" + number_of_added_slides, slides.length); + + g.setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + (after_slide + 1) + ")) { indexSetActiveSlide(" + (after_slide + 1) + "); };"); + + // Create a transform group. + var transformGroup = document.createElementNS(NSS["svg"], "g"); + + // Add content to transform group. + while (g.firstChild) + transformGroup.appendChild(g.firstChild); + + // Transfer the transform attribute from the node to the transform group. + if (g.getAttribute("transform")) + { + transformGroup.setAttribute("transform", g.getAttribute("transform")); + g.removeAttribute("transform"); + } + + // Create a view group. + var viewGroup = document.createElementNS(NSS["svg"], "g"); + + viewGroup.appendChild(transformGroup); + new_slide["viewGroup"] = g.appendChild(viewGroup); + + // Insert background. + if (BACKGROUND_COLOR != null) + { + var rectNode = document.createElementNS(NSS["svg"], "rect"); + + rectNode.setAttribute("x", 0); + rectNode.setAttribute("y", 0); + rectNode.setAttribute("width", WIDTH); + rectNode.setAttribute("height", HEIGHT); + rectNode.setAttribute("id", "jessyInkBackground" + Date()); + rectNode.setAttribute("fill", BACKGROUND_COLOR); + + new_slide["viewGroup"].insertBefore(rectNode, new_slide["viewGroup"].firstChild); + } + + // Set initial view even if there are no other views. + var matrixOld = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + + new_slide["viewGroup"].setAttribute("transform", matrixOld.toAttribute()); + new_slide.initialView = matrixOld.toAttribute(); + + // Insert slide + var node = slides[after_slide]["element"]; + var next_node = node.nextSibling; + var parent_node = node.parentNode; + + if (next_node) + { + parent_node.insertBefore(g, next_node); + } + else + { + parent_node.appendChild(g); + } + + g = document.createElementNS(NSS["svg"], "g"); + g.setAttributeNS(NSS["inkscape"], "groupmode", "layer"); + g.setAttributeNS(NSS["inkscape"], "label", "Whiteboard " + number_of_added_slides); + g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)"); + g.setAttribute("id", "Whiteboard " + Date()); + g.setAttribute("style", "display: none;"); + + new_slide["original_element"] = g; + + node = slides[after_slide]["original_element"]; + next_node = node.nextSibling; + parent_node = node.parentNode; + + if (next_node) + { + parent_node.insertBefore(g, next_node); + } + else + { + parent_node.appendChild(g); + } + + before_new_slide = slides.slice(0, after_slide + 1); + after_new_slide = slides.slice(after_slide + 1); + slides = before_new_slide.concat(new_slide, after_new_slide); + + //resetting the counter attributes on the slides that follow the new slide... + for (var counter = after_slide+2; counter < slides.length; counter++) + { + slides[counter]["element"].setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + counter + ")) { indexSetActiveSlide(" + counter + "); };"); + } +} + +/** Convenience function to obtain a transformation matrix from a point matrix. + * + * @param mPoints Point matrix. + * @return A transformation matrix. + */ +function pointMatrixToTransformation(mPoints) +{ + mPointsOld = (new matrixSVG()).fromElements(0, WIDTH, WIDTH, 0, 0, HEIGHT, 1, 1, 1); + + return mPointsOld.mult(mPoints.inv()); +} + +/** Convenience function to obtain a matrix with three corners of a rectangle. + * + * @param rect an svg rectangle + * @return a matrixSVG containing three corners of the rectangle + */ +function rectToMatrix(rect) +{ + rectWidth = rect.getBBox().width; + rectHeight = rect.getBBox().height; + rectX = rect.getBBox().x; + rectY = rect.getBBox().y; + rectXcorr = 0; + rectYcorr = 0; + + scaleX = WIDTH / rectWidth; + scaleY = HEIGHT / rectHeight; + + if (scaleX > scaleY) + { + scaleX = scaleY; + rectXcorr -= (WIDTH / scaleX - rectWidth) / 2; + rectWidth = WIDTH / scaleX; + } + else + { + scaleY = scaleX; + rectYcorr -= (HEIGHT / scaleY - rectHeight) / 2; + rectHeight = HEIGHT / scaleY; + } + + if (rect.transform.baseVal.numberOfItems < 1) + { + mRectTrans = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + } + else + { + mRectTrans = (new matrixSVG()).fromSVGMatrix(rect.transform.baseVal.consolidate().matrix); + } + + newBasePoints = (new matrixSVG()).fromElements(rectX, rectX, rectX, rectY, rectY, rectY, 1, 1, 1); + newVectors = (new matrixSVG()).fromElements(rectXcorr, rectXcorr + rectWidth, rectXcorr + rectWidth, rectYcorr, rectYcorr, rectYcorr + rectHeight, 0, 0, 0); + + return mRectTrans.mult(newBasePoints.add(newVectors)); +} + +/** Function to handle JessyInk elements. + * + * @param node Element node. + */ +function handleElement(node) +{ + if (node.getAttributeNS(NSS['jessyink'], 'element') == 'core.video') + { + var url; + var width; + var height; + var x; + var y; + var transform; + + var tspans = node.getElementsByTagNameNS("http://www.w3.org/2000/svg", "tspan"); + + for (var tspanCounter = 0; tspanCounter < tspans.length; tspanCounter++) + { + if (tspans[tspanCounter].getAttributeNS("https://launchpad.net/jessyink", "video") == "url") + { + url = tspans[tspanCounter].firstChild.nodeValue; + } + } + + var rects = node.getElementsByTagNameNS("http://www.w3.org/2000/svg", "rect"); + + for (var rectCounter = 0; rectCounter < rects.length; rectCounter++) + { + if (rects[rectCounter].getAttributeNS("https://launchpad.net/jessyink", "video") == "rect") + { + x = rects[rectCounter].getAttribute("x"); + y = rects[rectCounter].getAttribute("y"); + width = rects[rectCounter].getAttribute("width"); + height = rects[rectCounter].getAttribute("height"); + transform = rects[rectCounter].getAttribute("transform"); + } + } + + for (var childCounter = 0; childCounter < node.childNodes.length; childCounter++) + { + if (node.childNodes[childCounter].nodeType == 1) + { + if (node.childNodes[childCounter].style) + { + node.childNodes[childCounter].style.display = 'none'; + } + else + { + node.childNodes[childCounter].setAttribute("style", "display: none;"); + } + } + } + + var foreignNode = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject"); + foreignNode.setAttribute("x", x); + foreignNode.setAttribute("y", y); + foreignNode.setAttribute("width", width); + foreignNode.setAttribute("height", height); + foreignNode.setAttribute("transform", transform); + + var videoNode = document.createElementNS("http://www.w3.org/1999/xhtml", "video"); + videoNode.setAttribute("src", url); + + foreignNode.appendChild(videoNode); + node.appendChild(foreignNode); + } +} + +/** Class processing the location hash. + * + * @param str location hash + */ +function LocationHash(str) +{ + this.slideNumber = 0; + this.effectNumber = 0; + + str = str.substr(1, str.length - 1); + + var parts = str.split('_'); + + // Try to extract slide number. + if (parts.length >= 1) + { + try + { + var slideNumber = parseInt(parts[0]); + + if (!isNaN(slideNumber)) + { + this.slideNumber = slideNumber - 1; + } + } + catch (e) + { + } + } + + // Try to extract effect number. + if (parts.length >= 2) + { + try + { + var effectNumber = parseInt(parts[1]); + + if (!isNaN(effectNumber)) + { + this.effectNumber = effectNumber; + } + } + catch (e) + { + } + } +} + +/** Class representing an svg matrix. +*/ +function matrixSVG() +{ + this.e11 = 0; // a + this.e12 = 0; // c + this.e13 = 0; // e + this.e21 = 0; // b + this.e22 = 0; // d + this.e23 = 0; // f + this.e31 = 0; + this.e32 = 0; + this.e33 = 0; +} + +/** Constructor function. + * + * @param a element a (i.e. 1, 1) as described in the svg standard. + * @param b element b (i.e. 2, 1) as described in the svg standard. + * @param c element c (i.e. 1, 2) as described in the svg standard. + * @param d element d (i.e. 2, 2) as described in the svg standard. + * @param e element e (i.e. 1, 3) as described in the svg standard. + * @param f element f (i.e. 2, 3) as described in the svg standard. + */ +matrixSVG.prototype.fromSVGElements = function(a, b, c, d, e, f) +{ + this.e11 = a; + this.e12 = c; + this.e13 = e; + this.e21 = b; + this.e22 = d; + this.e23 = f; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + + return this; +} + +/** Constructor function. + * + * @param matrix an svg matrix as described in the svg standard. + */ +matrixSVG.prototype.fromSVGMatrix = function(m) +{ + this.e11 = m.a; + this.e12 = m.c; + this.e13 = m.e; + this.e21 = m.b; + this.e22 = m.d; + this.e23 = m.f; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + + return this; +} + +/** Constructor function. + * + * @param e11 element 1, 1 of the matrix. + * @param e12 element 1, 2 of the matrix. + * @param e13 element 1, 3 of the matrix. + * @param e21 element 2, 1 of the matrix. + * @param e22 element 2, 2 of the matrix. + * @param e23 element 2, 3 of the matrix. + * @param e31 element 3, 1 of the matrix. + * @param e32 element 3, 2 of the matrix. + * @param e33 element 3, 3 of the matrix. + */ +matrixSVG.prototype.fromElements = function(e11, e12, e13, e21, e22, e23, e31, e32, e33) +{ + this.e11 = e11; + this.e12 = e12; + this.e13 = e13; + this.e21 = e21; + this.e22 = e22; + this.e23 = e23; + this.e31 = e31; + this.e32 = e32; + this.e33 = e33; + + return this; +} + +/** Constructor function. + * + * @param attrString string value of the "transform" attribute (currently only "matrix" is accepted) + */ +matrixSVG.prototype.fromAttribute = function(attrString) +{ + str = attrString.substr(7, attrString.length - 8); + + str = str.trim(); + + strArray = str.split(","); + + // Opera does not use commas to separate the values of the matrix, only spaces. + if (strArray.length != 6) + strArray = str.split(" "); + + this.e11 = parseFloat(strArray[0]); + this.e21 = parseFloat(strArray[1]); + this.e31 = 0; + this.e12 = parseFloat(strArray[2]); + this.e22 = parseFloat(strArray[3]); + this.e32 = 0; + this.e13 = parseFloat(strArray[4]); + this.e23 = parseFloat(strArray[5]); + this.e33 = 1; + + return this; +} + +/** Output function + * + * @return a string that can be used as the "transform" attribute. + */ +matrixSVG.prototype.toAttribute = function() +{ + return "matrix(" + this.e11 + ", " + this.e21 + ", " + this.e12 + ", " + this.e22 + ", " + this.e13 + ", " + this.e23 + ")"; +} + +/** Matrix nversion. + * + * @return the inverse of the matrix + */ +matrixSVG.prototype.inv = function() +{ + out = new matrixSVG(); + + det = this.e11 * (this.e33 * this.e22 - this.e32 * this.e23) - this.e21 * (this.e33 * this.e12 - this.e32 * this.e13) + this.e31 * (this.e23 * this.e12 - this.e22 * this.e13); + + out.e11 = (this.e33 * this.e22 - this.e32 * this.e23) / det; + out.e12 = -(this.e33 * this.e12 - this.e32 * this.e13) / det; + out.e13 = (this.e23 * this.e12 - this.e22 * this.e13) / det; + out.e21 = -(this.e33 * this.e21 - this.e31 * this.e23) / det; + out.e22 = (this.e33 * this.e11 - this.e31 * this.e13) / det; + out.e23 = -(this.e23 * this.e11 - this.e21 * this.e13) / det; + out.e31 = (this.e32 * this.e21 - this.e31 * this.e22) / det; + out.e32 = -(this.e32 * this.e11 - this.e31 * this.e12) / det; + out.e33 = (this.e22 * this.e11 - this.e21 * this.e12) / det; + + return out; +} + +/** Matrix multiplication. + * + * @param op another svg matrix + * @return this * op + */ +matrixSVG.prototype.mult = function(op) +{ + out = new matrixSVG(); + + out.e11 = this.e11 * op.e11 + this.e12 * op.e21 + this.e13 * op.e31; + out.e12 = this.e11 * op.e12 + this.e12 * op.e22 + this.e13 * op.e32; + out.e13 = this.e11 * op.e13 + this.e12 * op.e23 + this.e13 * op.e33; + out.e21 = this.e21 * op.e11 + this.e22 * op.e21 + this.e23 * op.e31; + out.e22 = this.e21 * op.e12 + this.e22 * op.e22 + this.e23 * op.e32; + out.e23 = this.e21 * op.e13 + this.e22 * op.e23 + this.e23 * op.e33; + out.e31 = this.e31 * op.e11 + this.e32 * op.e21 + this.e33 * op.e31; + out.e32 = this.e31 * op.e12 + this.e32 * op.e22 + this.e33 * op.e32; + out.e33 = this.e31 * op.e13 + this.e32 * op.e23 + this.e33 * op.e33; + + return out; +} + +/** Matrix addition. + * + * @param op another svg matrix + * @return this + op + */ +matrixSVG.prototype.add = function(op) +{ + out = new matrixSVG(); + + out.e11 = this.e11 + op.e11; + out.e12 = this.e12 + op.e12; + out.e13 = this.e13 + op.e13; + out.e21 = this.e21 + op.e21; + out.e22 = this.e22 + op.e22; + out.e23 = this.e23 + op.e23; + out.e31 = this.e31 + op.e31; + out.e32 = this.e32 + op.e32; + out.e33 = this.e33 + op.e33; + + return out; +} + +/** Matrix mixing. + * + * @param op another svg matrix + * @parma contribOp contribution of the other matrix (0 <= contribOp <= 1) + * @return (1 - contribOp) * this + contribOp * op + */ +matrixSVG.prototype.mix = function(op, contribOp) +{ + contribThis = 1.0 - contribOp; + out = new matrixSVG(); + + out.e11 = contribThis * this.e11 + contribOp * op.e11; + out.e12 = contribThis * this.e12 + contribOp * op.e12; + out.e13 = contribThis * this.e13 + contribOp * op.e13; + out.e21 = contribThis * this.e21 + contribOp * op.e21; + out.e22 = contribThis * this.e22 + contribOp * op.e22; + out.e23 = contribThis * this.e23 + contribOp * op.e23; + out.e31 = contribThis * this.e31 + contribOp * op.e31; + out.e32 = contribThis * this.e32 + contribOp * op.e32; + out.e33 = contribThis * this.e33 + contribOp * op.e33; + + return out; +} + +/** Trimming function for strings. +*/ +String.prototype.trim = function() +{ + return this.replace(/^\s+|\s+$/g, ''); +} + +/** SVGElement.getTransformToElement polyfill */ +SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(elem) { + return elem.getScreenCTM().inverse().multiply(this.getScreenCTM()); +}; + diff --git a/share/extensions/tests/data/refs/jessyink_install__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/jessyink_install__--id__p1__--id__r3.out new file mode 100644 index 0000000..5a819a7 --- /dev/null +++ b/share/extensions/tests/data/refs/jessyink_install__--id__p1__--id__r3.out @@ -0,0 +1,2766 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + // Copyright 2008, 2009 Hannes Hochreiner +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +// Set onload event handler. +window.onload = jessyInkInit; + +// Creating a namespace dictionary. The standard Inkscape namespaces are taken from inkex.py. +var NSS = new Object(); +NSS['sodipodi']='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'; +NSS['cc']='http://web.resource.org/cc/'; +NSS['svg']='http://www.w3.org/2000/svg'; +NSS['dc']='http://purl.org/dc/elements/1.1/'; +NSS['rdf']='http://www.w3.org/1999/02/22-rdf-syntax-ns#'; +NSS['inkscape']='http://www.inkscape.org/namespaces/inkscape'; +NSS['xlink']='http://www.w3.org/1999/xlink'; +NSS['xml']='http://www.w3.org/XML/1998/namespace'; +NSS['jessyink']='https://launchpad.net/jessyink'; + +// Keycodes. +var LEFT_KEY = 37; // cursor left keycode +var UP_KEY = 38; // cursor up keycode +var RIGHT_KEY = 39; // cursor right keycode +var DOWN_KEY = 40; // cursor down keycode +var PAGE_UP_KEY = 33; // page up keycode +var PAGE_DOWN_KEY = 34; // page down keycode +var HOME_KEY = 36; // home keycode +var END_KEY = 35; // end keycode +var ENTER_KEY = 13; // next slide +var SPACE_KEY = 32; +var ESCAPE_KEY = 27; + +// Presentation modes. +var SLIDE_MODE = 1; +var INDEX_MODE = 2; +var DRAWING_MODE = 3; + +// Mouse handler actions. +var MOUSE_UP = 1; +var MOUSE_DOWN = 2; +var MOUSE_MOVE = 3; +var MOUSE_WHEEL = 4; + +// Parameters. +var ROOT_NODE = document.getElementsByTagNameNS(NSS["svg"], "svg")[0]; +var HEIGHT = 0; +var WIDTH = 0; +var INDEX_COLUMNS_DEFAULT = 4; +var INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT; +var INDEX_OFFSET = 0; +var STATE_START = -1; +var STATE_END = -2; +var BACKGROUND_COLOR = null; +var slides = new Array(); + +// Initialisation. +var currentMode = SLIDE_MODE; +var masterSlide = null; +var activeSlide = 0; +var activeEffect = 0; +var timeStep = 30; // 40 ms equal 25 frames per second. +var lastFrameTime = null; +var processingEffect = false; +var transCounter = 0; +var effectArray = 0; +var defaultTransitionInDict = new Object(); +defaultTransitionInDict["name"] = "appear"; +var defaultTransitionOutDict = new Object(); +defaultTransitionOutDict["name"] = "appear"; +var jessyInkInitialised = false; + +// Initialise char and key code dictionaries. +var charCodeDictionary = getDefaultCharCodeDictionary(); +var keyCodeDictionary = getDefaultKeyCodeDictionary(); + +// Initialise mouse handler dictionary. +var mouseHandlerDictionary = getDefaultMouseHandlerDictionary(); + +var progress_bar_visible = false; +var timer_elapsed = 0; +var timer_start = timer_elapsed; +var timer_duration = 15; // 15 minutes + +var history_counter = 0; +var history_original_elements = new Array(); +var history_presentation_elements = new Array(); + +var mouse_original_path = null; +var mouse_presentation_path = null; +var mouse_last_x = -1; +var mouse_last_y = -1; +var mouse_min_dist_sqr = 3 * 3; +var path_colour = "red"; +var path_width_default = 3; +var path_width = path_width_default; +var path_paint_width = path_width; + +var number_of_added_slides = 0; + +/** Initialisation function. + * The whole presentation is set-up in this function. + */ +function jessyInkInit() +{ + // Make sure we only execute this code once. Double execution can occur if the onload event handler is set + // in the main svg tag as well (as was recommended in earlier versions). Executing this function twice does + // not lead to any problems, but it takes more time. + if (jessyInkInitialised) + return; + + // Making the presentation scalable. + var VIEWBOX = ROOT_NODE.getAttribute("viewBox"); + + if (VIEWBOX) + { + WIDTH = ROOT_NODE.viewBox.animVal.width; + HEIGHT = ROOT_NODE.viewBox.animVal.height; + } + else + { + HEIGHT = parseFloat(ROOT_NODE.getAttribute("height")); + WIDTH = parseFloat(ROOT_NODE.getAttribute("width")); + ROOT_NODE.setAttribute("viewBox", "0 0 " + WIDTH + " " + HEIGHT); + } + + ROOT_NODE.setAttribute("width", "100%"); + ROOT_NODE.setAttribute("height", "100%"); + + // Setting the background color. + var namedViews = document.getElementsByTagNameNS(NSS["sodipodi"], "namedview"); + + for (var counter = 0; counter < namedViews.length; counter++) + { + if (namedViews[counter].hasAttribute("id") && namedViews[counter].hasAttribute("pagecolor")) + { + if (namedViews[counter].getAttribute("id") == "base") + { + BACKGROUND_COLOR = namedViews[counter].getAttribute("pagecolor"); + var newAttribute = "background-color:" + BACKGROUND_COLOR + ";"; + + if (ROOT_NODE.hasAttribute("style")) + newAttribute += ROOT_NODE.getAttribute("style"); + + ROOT_NODE.setAttribute("style", newAttribute); + } + } + } + + // Defining clip-path. + var defsNodes = document.getElementsByTagNameNS(NSS["svg"], "defs"); + + if (defsNodes.length > 0) + { + var existingClipPath = document.getElementById("jessyInkSlideClipPath"); + + if (!existingClipPath) + { + var rectNode = document.createElementNS(NSS["svg"], "rect"); + var clipPath = document.createElementNS(NSS["svg"], "clipPath"); + + rectNode.setAttribute("x", 0); + rectNode.setAttribute("y", 0); + rectNode.setAttribute("width", WIDTH); + rectNode.setAttribute("height", HEIGHT); + + clipPath.setAttribute("id", "jessyInkSlideClipPath"); + clipPath.setAttribute("clipPathUnits", "userSpaceOnUse"); + + clipPath.appendChild(rectNode); + defsNodes[0].appendChild(clipPath); + } + } + + // Making a list of the slide and finding the master slide. + var nodes = document.getElementsByTagNameNS(NSS["svg"], "g"); + var tempSlides = new Array(); + var existingJessyInkPresentationLayer = null; + + for (var counter = 0; counter < nodes.length; counter++) + { + if (nodes[counter].getAttributeNS(NSS["inkscape"], "groupmode") && (nodes[counter].getAttributeNS(NSS["inkscape"], "groupmode") == "layer")) + { + if (nodes[counter].getAttributeNS(NSS["inkscape"], "label") && nodes[counter].getAttributeNS(NSS["jessyink"], "masterSlide") == "masterSlide") + masterSlide = nodes[counter]; + else if (nodes[counter].getAttributeNS(NSS["inkscape"], "label") && nodes[counter].getAttributeNS(NSS["jessyink"], "presentationLayer") == "presentationLayer") + existingJessyInkPresentationLayer = nodes[counter]; + else + tempSlides.push(nodes[counter].getAttribute("id")); + } + else if (nodes[counter].getAttributeNS(NSS['jessyink'], 'element')) + { + handleElement(nodes[counter]); + } + } + + // Hide master slide set default transitions. + if (masterSlide) + { + masterSlide.style.display = "none"; + + if (masterSlide.hasAttributeNS(NSS["jessyink"], "transitionIn")) + defaultTransitionInDict = propStrToDict(masterSlide.getAttributeNS(NSS["jessyink"], "transitionIn")); + + if (masterSlide.hasAttributeNS(NSS["jessyink"], "transitionOut")) + defaultTransitionOutDict = propStrToDict(masterSlide.getAttributeNS(NSS["jessyink"], "transitionOut")); + } + + if (existingJessyInkPresentationLayer != null) + { + existingJessyInkPresentationLayer.parentNode.removeChild(existingJessyInkPresentationLayer); + } + + // Set start slide. + var hashObj = new LocationHash(window.location.hash); + + activeSlide = hashObj.slideNumber; + activeEffect = hashObj.effectNumber; + + if (activeSlide < 0) + activeSlide = 0; + else if (activeSlide >= tempSlides.length) + activeSlide = tempSlides.length - 1; + + var originalNode = document.getElementById(tempSlides[counter]); + + var JessyInkPresentationLayer = document.createElementNS(NSS["svg"], "g"); + JessyInkPresentationLayer.setAttributeNS(NSS["inkscape"], "groupmode", "layer"); + JessyInkPresentationLayer.setAttributeNS(NSS["inkscape"], "label", "JessyInk Presentation Layer"); + JessyInkPresentationLayer.setAttributeNS(NSS["jessyink"], "presentationLayer", "presentationLayer"); + JessyInkPresentationLayer.setAttribute("id", "jessyink_presentation_layer"); + JessyInkPresentationLayer.style.display = "inherit"; + ROOT_NODE.appendChild(JessyInkPresentationLayer); + + // Gathering all the information about the transitions and effects of the slides, set the background + // from the master slide and substitute the auto-texts. + for (var counter = 0; counter < tempSlides.length; counter++) + { + var originalNode = document.getElementById(tempSlides[counter]); + originalNode.style.display = "none"; + var node = suffixNodeIds(originalNode.cloneNode(true), "_" + counter); + JessyInkPresentationLayer.appendChild(node); + slides[counter] = new Object(); + slides[counter]["original_element"] = originalNode; + slides[counter]["element"] = node; + + // Set build in transition. + slides[counter]["transitionIn"] = new Object(); + + var dict; + + if (node.hasAttributeNS(NSS["jessyink"], "transitionIn")) + dict = propStrToDict(node.getAttributeNS(NSS["jessyink"], "transitionIn")); + else + dict = defaultTransitionInDict; + + slides[counter]["transitionIn"]["name"] = dict["name"]; + slides[counter]["transitionIn"]["options"] = new Object(); + + for (key in dict) + if (key != "name") + slides[counter]["transitionIn"]["options"][key] = dict[key]; + + // Set build out transition. + slides[counter]["transitionOut"] = new Object(); + + if (node.hasAttributeNS(NSS["jessyink"], "transitionOut")) + dict = propStrToDict(node.getAttributeNS(NSS["jessyink"], "transitionOut")); + else + dict = defaultTransitionOutDict; + + slides[counter]["transitionOut"]["name"] = dict["name"]; + slides[counter]["transitionOut"]["options"] = new Object(); + + for (key in dict) + if (key != "name") + slides[counter]["transitionOut"]["options"][key] = dict[key]; + + // Copy master slide content. + if (masterSlide) + { + var clonedNode = suffixNodeIds(masterSlide.cloneNode(true), "_" + counter); + clonedNode.removeAttributeNS(NSS["inkscape"], "groupmode"); + clonedNode.removeAttributeNS(NSS["inkscape"], "label"); + clonedNode.style.display = "inherit"; + + node.insertBefore(clonedNode, node.firstChild); + } + + // Setting clip path. + node.setAttribute("clip-path", "url(#jessyInkSlideClipPath)"); + + // Substitute auto texts. + substituteAutoTexts(node, node.getAttributeNS(NSS["inkscape"], "label"), counter + 1, tempSlides.length); + + node.removeAttributeNS(NSS["inkscape"], "groupmode"); + node.removeAttributeNS(NSS["inkscape"], "label"); + + // Set effects. + var tempEffects = new Array(); + var groups = new Object(); + + for (var IOCounter = 0; IOCounter <= 1; IOCounter++) + { + var propName = ""; + var dir = 0; + + if (IOCounter == 0) + { + propName = "effectIn"; + dir = 1; + } + else if (IOCounter == 1) + { + propName = "effectOut"; + dir = -1; + } + + var effects = getElementsByPropertyNS(node, NSS["jessyink"], propName); + + for (var effectCounter = 0; effectCounter < effects.length; effectCounter++) + { + var element = document.getElementById(effects[effectCounter]); + var dict = propStrToDict(element.getAttributeNS(NSS["jessyink"], propName)); + + // Put every element that has an effect associated with it, into its own group. + // Unless of course, we already put it into its own group. + if (!(groups[element.id])) + { + var newGroup = document.createElementNS(NSS["svg"], "g"); + + element.parentNode.insertBefore(newGroup, element); + newGroup.appendChild(element.parentNode.removeChild(element)); + groups[element.id] = newGroup; + } + + var effectDict = new Object(); + + effectDict["effect"] = dict["name"]; + effectDict["dir"] = dir; + effectDict["element"] = groups[element.id]; + + for (var option in dict) + { + if ((option != "name") && (option != "order")) + { + if (!effectDict["options"]) + effectDict["options"] = new Object(); + + effectDict["options"][option] = dict[option]; + } + } + + if (!tempEffects[dict["order"]]) + tempEffects[dict["order"]] = new Array(); + + tempEffects[dict["order"]][tempEffects[dict["order"]].length] = effectDict; + } + } + + // Make invisible, but keep in rendering tree to ensure that bounding box can be calculated. + node.setAttribute("opacity",0); + node.style.display = "inherit"; + + // Create a transform group. + var transformGroup = document.createElementNS(NSS["svg"], "g"); + + // Add content to transform group. + while (node.firstChild) + transformGroup.appendChild(node.firstChild); + + // Transfer the transform attribute from the node to the transform group. + if (node.getAttribute("transform")) + { + transformGroup.setAttribute("transform", node.getAttribute("transform")); + node.removeAttribute("transform"); + } + + // Create a view group. + var viewGroup = document.createElementNS(NSS["svg"], "g"); + + viewGroup.appendChild(transformGroup); + slides[counter]["viewGroup"] = node.appendChild(viewGroup); + + // Insert background. + if (BACKGROUND_COLOR != null) + { + var rectNode = document.createElementNS(NSS["svg"], "rect"); + + rectNode.setAttribute("x", 0); + rectNode.setAttribute("y", 0); + rectNode.setAttribute("width", WIDTH); + rectNode.setAttribute("height", HEIGHT); + rectNode.setAttribute("id", "jessyInkBackground" + counter); + rectNode.setAttribute("fill", BACKGROUND_COLOR); + + slides[counter]["viewGroup"].insertBefore(rectNode, slides[counter]["viewGroup"].firstChild); + } + + // Set views. + var tempViews = new Array(); + var views = getElementsByPropertyNS(node, NSS["jessyink"], "view"); + var matrixOld = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + + // Set initial view even if there are no other views. + slides[counter]["viewGroup"].setAttribute("transform", matrixOld.toAttribute()); + slides[counter].initialView = matrixOld.toAttribute(); + + for (var viewCounter = 0; viewCounter < views.length; viewCounter++) + { + var element = document.getElementById(views[viewCounter]); + var dict = propStrToDict(element.getAttributeNS(NSS["jessyink"], "view")); + + if (dict["order"] == 0) + { + matrixOld = pointMatrixToTransformation(rectToMatrix(element)).mult((new matrixSVG()).fromSVGMatrix(slides[counter].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(element.parentNode.getScreenCTM())).inv()); + slides[counter].initialView = matrixOld.toAttribute(); + } + else + { + var effectDict = new Object(); + + effectDict["effect"] = dict["name"]; + effectDict["dir"] = 1; + effectDict["element"] = slides[counter]["viewGroup"]; + effectDict["order"] = dict["order"]; + + for (var option in dict) + { + if ((option != "name") && (option != "order")) + { + if (!effectDict["options"]) + effectDict["options"] = new Object(); + + effectDict["options"][option] = dict[option]; + } + } + + effectDict["options"]["matrixNew"] = pointMatrixToTransformation(rectToMatrix(element)).mult((new matrixSVG()).fromSVGMatrix(slides[counter].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(element.parentNode.getScreenCTM())).inv()); + + tempViews[dict["order"]] = effectDict; + } + + // Remove element. + element.parentNode.removeChild(element); + } + + // Consolidate view array and append it to the effect array. + if (tempViews.length > 0) + { + for (var viewCounter = 0; viewCounter < tempViews.length; viewCounter++) + { + if (tempViews[viewCounter]) + { + tempViews[viewCounter]["options"]["matrixOld"] = matrixOld; + matrixOld = tempViews[viewCounter]["options"]["matrixNew"]; + + if (!tempEffects[tempViews[viewCounter]["order"]]) + tempEffects[tempViews[viewCounter]["order"]] = new Array(); + + tempEffects[tempViews[viewCounter]["order"]][tempEffects[tempViews[viewCounter]["order"]].length] = tempViews[viewCounter]; + } + } + } + + // Set consolidated effect array. + if (tempEffects.length > 0) + { + slides[counter]["effects"] = new Array(); + + for (var effectCounter = 0; effectCounter < tempEffects.length; effectCounter++) + { + if (tempEffects[effectCounter]) + slides[counter]["effects"][slides[counter]["effects"].length] = tempEffects[effectCounter]; + } + } + + node.setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + counter + ")) { indexSetActiveSlide(" + counter + "); };"); + + // Set visibility for initial state. + if (counter == activeSlide) + { + node.style.display = "inherit"; + node.setAttribute("opacity",1); + } + else + { + node.style.display = "none"; + node.setAttribute("opacity",0); + } + } + + // Set key handler. + var jessyInkObjects = document.getElementsByTagNameNS(NSS["svg"], "g"); + + for (var counter = 0; counter < jessyInkObjects.length; counter++) + { + var elem = jessyInkObjects[counter]; + + if (elem.getAttributeNS(NSS["jessyink"], "customKeyBindings")) + { + if (elem.getCustomKeyBindings != undefined) + keyCodeDictionary = elem.getCustomKeyBindings(); + + if (elem.getCustomCharBindings != undefined) + charCodeDictionary = elem.getCustomCharBindings(); + } + } + + // Set mouse handler. + var jessyInkMouseHandler = document.getElementsByTagNameNS(NSS["jessyink"], "mousehandler"); + + for (var counter = 0; counter < jessyInkMouseHandler.length; counter++) + { + var elem = jessyInkMouseHandler[counter]; + + if (elem.getMouseHandler != undefined) + { + var tempDict = elem.getMouseHandler(); + + for (mode in tempDict) + { + if (!mouseHandlerDictionary[mode]) + mouseHandlerDictionary[mode] = new Object(); + + for (handler in tempDict[mode]) + mouseHandlerDictionary[mode][handler] = tempDict[mode][handler]; + } + } + } + + // Check effect number. + if ((activeEffect < 0) || (!slides[activeSlide].effects)) + { + activeEffect = 0; + } + else if (activeEffect > slides[activeSlide].effects.length) + { + activeEffect = slides[activeSlide].effects.length; + } + + createProgressBar(JessyInkPresentationLayer); + hideProgressBar(); + setProgressBarValue(activeSlide); + setTimeIndicatorValue(0); + setInterval("updateTimer()", 1000); + setSlideToState(activeSlide, activeEffect); + jessyInkInitialised = true; +} + +/** Function to substitute the auto-texts. + * + * @param node the node + * @param slideName name of the slide the node is on + * @param slideNumber number of the slide the node is on + * @param numberOfSlides number of slides in the presentation + */ +function substituteAutoTexts(node, slideName, slideNumber, numberOfSlides) +{ + var texts = node.getElementsByTagNameNS(NSS["svg"], "tspan"); + + for (var textCounter = 0; textCounter < texts.length; textCounter++) + { + if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "slideNumber") + texts[textCounter].firstChild.nodeValue = slideNumber; + else if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "numberOfSlides") + texts[textCounter].firstChild.nodeValue = numberOfSlides; + else if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "slideTitle") + texts[textCounter].firstChild.nodeValue = slideName; + } +} + +/** Convenience function to get an element depending on whether it has a property with a particular name. + * This function emulates some dearly missed XPath functionality. + * + * @param node the node + * @param namespace namespace of the attribute + * @param name attribute name + */ +function getElementsByPropertyNS(node, namespace, name) +{ + var elems = new Array(); + + if (node.getAttributeNS(namespace, name)) + elems.push(node.getAttribute("id")); + + for (var counter = 0; counter < node.childNodes.length; counter++) + { + if (node.childNodes[counter].nodeType == 1) + elems = elems.concat(getElementsByPropertyNS(node.childNodes[counter], namespace, name)); + } + + return elems; +} + +/** Function to dispatch the next effect, if there is none left, change the slide. + * + * @param dir direction of the change (1 = forwards, -1 = backwards) + */ +function dispatchEffects(dir) +{ + if (slides[activeSlide]["effects"] && (((dir == 1) && (activeEffect < slides[activeSlide]["effects"].length)) || ((dir == -1) && (activeEffect > 0)))) + { + processingEffect = true; + + if (dir == 1) + { + effectArray = slides[activeSlide]["effects"][activeEffect]; + activeEffect += dir; + } + else if (dir == -1) + { + activeEffect += dir; + effectArray = slides[activeSlide]["effects"][activeEffect]; + } + + transCounter = 0; + startTime = (new Date()).getTime(); + lastFrameTime = null; + effect(dir); + } + else if (((dir == 1) && (activeSlide < (slides.length - 1))) || (((dir == -1) && (activeSlide > 0)))) + { + changeSlide(dir); + } +} + +/** Function to skip effects and directly either put the slide into start or end state or change slides. + * + * @param dir direction of the change (1 = forwards, -1 = backwards) + */ +function skipEffects(dir) +{ + if (slides[activeSlide]["effects"] && (((dir == 1) && (activeEffect < slides[activeSlide]["effects"].length)) || ((dir == -1) && (activeEffect > 0)))) + { + processingEffect = true; + + if (slides[activeSlide]["effects"] && (dir == 1)) + activeEffect = slides[activeSlide]["effects"].length; + else + activeEffect = 0; + + if (dir == 1) + setSlideToState(activeSlide, STATE_END); + else + setSlideToState(activeSlide, STATE_START); + + processingEffect = false; + } + else if (((dir == 1) && (activeSlide < (slides.length - 1))) || (((dir == -1) && (activeSlide > 0)))) + { + changeSlide(dir); + } +} + +/** Function to change between slides. + * + * @param dir direction (1 = forwards, -1 = backwards) + */ +function changeSlide(dir) +{ + processingEffect = true; + effectArray = new Array(); + + effectArray[0] = new Object(); + if (dir == 1) + { + effectArray[0]["effect"] = slides[activeSlide]["transitionOut"]["name"]; + effectArray[0]["options"] = slides[activeSlide]["transitionOut"]["options"]; + effectArray[0]["dir"] = -1; + } + else if (dir == -1) + { + effectArray[0]["effect"] = slides[activeSlide]["transitionIn"]["name"]; + effectArray[0]["options"] = slides[activeSlide]["transitionIn"]["options"]; + effectArray[0]["dir"] = 1; + } + effectArray[0]["element"] = slides[activeSlide]["element"]; + + activeSlide += dir; + setProgressBarValue(activeSlide); + + effectArray[1] = new Object(); + + if (dir == 1) + { + effectArray[1]["effect"] = slides[activeSlide]["transitionIn"]["name"]; + effectArray[1]["options"] = slides[activeSlide]["transitionIn"]["options"]; + effectArray[1]["dir"] = 1; + } + else if (dir == -1) + { + effectArray[1]["effect"] = slides[activeSlide]["transitionOut"]["name"]; + effectArray[1]["options"] = slides[activeSlide]["transitionOut"]["options"]; + effectArray[1]["dir"] = -1; + } + + effectArray[1]["element"] = slides[activeSlide]["element"]; + + if (slides[activeSlide]["effects"] && (dir == -1)) + activeEffect = slides[activeSlide]["effects"].length; + else + activeEffect = 0; + + if (dir == -1) + setSlideToState(activeSlide, STATE_END); + else + setSlideToState(activeSlide, STATE_START); + + transCounter = 0; + startTime = (new Date()).getTime(); + lastFrameTime = null; + effect(dir); +} + +/** Function to toggle between index and slide mode. +*/ +function toggleSlideIndex() +{ + var suspendHandle = ROOT_NODE.suspendRedraw(500); + + if (currentMode == SLIDE_MODE) + { + hideProgressBar(); + INDEX_OFFSET = -1; + indexSetPageSlide(activeSlide); + currentMode = INDEX_MODE; + } + else if (currentMode == INDEX_MODE) + { + for (var counter = 0; counter < slides.length; counter++) + { + slides[counter]["element"].setAttribute("transform","scale(1)"); + + if (counter == activeSlide) + { + slides[counter]["element"].style.display = "inherit"; + slides[counter]["element"].setAttribute("opacity",1); + activeEffect = 0; + } + else + { + slides[counter]["element"].setAttribute("opacity",0); + slides[counter]["element"].style.display = "none"; + } + } + currentMode = SLIDE_MODE; + setSlideToState(activeSlide, STATE_START); + setProgressBarValue(activeSlide); + + if (progress_bar_visible) + { + showProgressBar(); + } + } + + ROOT_NODE.unsuspendRedraw(suspendHandle); + ROOT_NODE.forceRedraw(); +} + +/** Function to run an effect. + * + * @param dir direction in which to play the effect (1 = forwards, -1 = backwards) + */ +function effect(dir) +{ + var done = true; + + var suspendHandle = ROOT_NODE.suspendRedraw(200); + + for (var counter = 0; counter < effectArray.length; counter++) + { + if (effectArray[counter]["effect"] == "fade") + done &= fade(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]); + else if (effectArray[counter]["effect"] == "appear") + done &= appear(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]); + else if (effectArray[counter]["effect"] == "pop") + done &= pop(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]); + else if (effectArray[counter]["effect"] == "view") + done &= view(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]); + } + + ROOT_NODE.unsuspendRedraw(suspendHandle); + ROOT_NODE.forceRedraw(); + + if (!done) + { + var currentTime = (new Date()).getTime(); + var timeDiff = 1; + + transCounter = currentTime - startTime; + + if (lastFrameTime != null) + { + timeDiff = timeStep - (currentTime - lastFrameTime); + + if (timeDiff <= 0) + timeDiff = 1; + } + + lastFrameTime = currentTime; + + window.setTimeout("effect(" + dir + ")", timeDiff); + } + else + { + window.location.hash = (activeSlide + 1) + '_' + activeEffect; + processingEffect = false; + } +} + +/** Function to display the index sheet. + * + * @param offsetNumber offset number + */ +function displayIndex(offsetNumber) +{ + var offsetX = 0; + var offsetY = 0; + + if (offsetNumber < 0) + offsetNumber = 0; + else if (offsetNumber >= slides.length) + offsetNumber = slides.length - 1; + + for (var counter = 0; counter < slides.length; counter++) + { + if ((counter < offsetNumber) || (counter > offsetNumber + INDEX_COLUMNS * INDEX_COLUMNS - 1)) + { + slides[counter]["element"].setAttribute("opacity",0); + slides[counter]["element"].style.display = "none"; + } + else + { + offsetX = ((counter - offsetNumber) % INDEX_COLUMNS) * WIDTH; + offsetY = Math.floor((counter - offsetNumber) / INDEX_COLUMNS) * HEIGHT; + + slides[counter]["element"].setAttribute("transform","scale("+1/INDEX_COLUMNS+") translate("+offsetX+","+offsetY+")"); + slides[counter]["element"].style.display = "inherit"; + slides[counter]["element"].setAttribute("opacity",0.5); + } + + setSlideToState(counter, STATE_END); + } + + //do we need to save the current offset? + if (INDEX_OFFSET != offsetNumber) + INDEX_OFFSET = offsetNumber; +} + +/** Function to set the active slide in the slide view. + * + * @param nbr index of the active slide + */ +function slideSetActiveSlide(nbr) +{ + if (nbr >= slides.length) + nbr = slides.length - 1; + else if (nbr < 0) + nbr = 0; + + slides[activeSlide]["element"].setAttribute("opacity",0); + slides[activeSlide]["element"].style.display = "none"; + + activeSlide = parseInt(nbr); + + setSlideToState(activeSlide, STATE_START); + slides[activeSlide]["element"].style.display = "inherit"; + slides[activeSlide]["element"].setAttribute("opacity",1); + + activeEffect = 0; + setProgressBarValue(nbr); +} + +/** Function to set the active slide in the index view. + * + * @param nbr index of the active slide + */ +function indexSetActiveSlide(nbr) +{ + if (nbr >= slides.length) + nbr = slides.length - 1; + else if (nbr < 0) + nbr = 0; + + slides[activeSlide]["element"].setAttribute("opacity",0.5); + + activeSlide = parseInt(nbr); + window.location.hash = (activeSlide + 1) + '_0'; + + slides[activeSlide]["element"].setAttribute("opacity",1); +} + +/** Function to set the page and active slide in index view. + * + * @param nbr index of the active slide + * + * NOTE: To force a redraw, + * set INDEX_OFFSET to -1 before calling indexSetPageSlide(). + * + * This is necessary for zooming (otherwise the index might not + * get redrawn) and when switching to index mode. + * + * INDEX_OFFSET = -1 + * indexSetPageSlide(activeSlide); + */ +function indexSetPageSlide(nbr) +{ + if (nbr >= slides.length) + nbr = slides.length - 1; + else if (nbr < 0) + nbr = 0; + + //calculate the offset + var offset = nbr - nbr % (INDEX_COLUMNS * INDEX_COLUMNS); + + if (offset < 0) + offset = 0; + + //if different from kept offset, then record and change the page + if (offset != INDEX_OFFSET) + { + INDEX_OFFSET = offset; + displayIndex(INDEX_OFFSET); + } + + //set the active slide + indexSetActiveSlide(nbr); +} + +/** Event handler for key press. + * + * @param e the event + */ +function keydown(e) +{ + if (!e) + e = window.event; + + code = e.keyCode || e.charCode; + + if (!processingEffect && keyCodeDictionary[currentMode] && keyCodeDictionary[currentMode][code]) + return keyCodeDictionary[currentMode][code](); + else + document.onkeypress = keypress; +} +// Set event handler for key down. +document.onkeydown = keydown; + +/** Event handler for key press. + * + * @param e the event + */ +function keypress(e) +{ + document.onkeypress = null; + + if (!e) + e = window.event; + + str = String.fromCharCode(e.keyCode || e.charCode); + + if (!processingEffect && charCodeDictionary[currentMode] && charCodeDictionary[currentMode][str]) + return charCodeDictionary[currentMode][str](); +} + +/** Function to supply the default char code dictionary. + * + * @returns default char code dictionary + */ +function getDefaultCharCodeDictionary() +{ + var charCodeDict = new Object(); + + charCodeDict[SLIDE_MODE] = new Object(); + charCodeDict[INDEX_MODE] = new Object(); + charCodeDict[DRAWING_MODE] = new Object(); + + charCodeDict[SLIDE_MODE]["i"] = function () { return toggleSlideIndex(); }; + charCodeDict[SLIDE_MODE]["d"] = function () { return slideSwitchToDrawingMode(); }; + charCodeDict[SLIDE_MODE]["D"] = function () { return slideQueryDuration(); }; + charCodeDict[SLIDE_MODE]["n"] = function () { return slideAddSlide(activeSlide); }; + charCodeDict[SLIDE_MODE]["p"] = function () { return slideToggleProgressBarVisibility(); }; + charCodeDict[SLIDE_MODE]["t"] = function () { return slideResetTimer(); }; + charCodeDict[SLIDE_MODE]["e"] = function () { return slideUpdateExportLayer(); }; + + charCodeDict[DRAWING_MODE]["d"] = function () { return drawingSwitchToSlideMode(); }; + charCodeDict[DRAWING_MODE]["0"] = function () { return drawingResetPathWidth(); }; + charCodeDict[DRAWING_MODE]["1"] = function () { return drawingSetPathWidth(1.0); }; + charCodeDict[DRAWING_MODE]["3"] = function () { return drawingSetPathWidth(3.0); }; + charCodeDict[DRAWING_MODE]["5"] = function () { return drawingSetPathWidth(5.0); }; + charCodeDict[DRAWING_MODE]["7"] = function () { return drawingSetPathWidth(7.0); }; + charCodeDict[DRAWING_MODE]["9"] = function () { return drawingSetPathWidth(9.0); }; + charCodeDict[DRAWING_MODE]["b"] = function () { return drawingSetPathColour("blue"); }; + charCodeDict[DRAWING_MODE]["c"] = function () { return drawingSetPathColour("cyan"); }; + charCodeDict[DRAWING_MODE]["g"] = function () { return drawingSetPathColour("green"); }; + charCodeDict[DRAWING_MODE]["k"] = function () { return drawingSetPathColour("black"); }; + charCodeDict[DRAWING_MODE]["m"] = function () { return drawingSetPathColour("magenta"); }; + charCodeDict[DRAWING_MODE]["o"] = function () { return drawingSetPathColour("orange"); }; + charCodeDict[DRAWING_MODE]["r"] = function () { return drawingSetPathColour("red"); }; + charCodeDict[DRAWING_MODE]["w"] = function () { return drawingSetPathColour("white"); }; + charCodeDict[DRAWING_MODE]["y"] = function () { return drawingSetPathColour("yellow"); }; + charCodeDict[DRAWING_MODE]["z"] = function () { return drawingUndo(); }; + + charCodeDict[INDEX_MODE]["i"] = function () { return toggleSlideIndex(); }; + charCodeDict[INDEX_MODE]["-"] = function () { return indexDecreaseNumberOfColumns(); }; + charCodeDict[INDEX_MODE]["="] = function () { return indexIncreaseNumberOfColumns(); }; + charCodeDict[INDEX_MODE]["+"] = function () { return indexIncreaseNumberOfColumns(); }; + charCodeDict[INDEX_MODE]["0"] = function () { return indexResetNumberOfColumns(); }; + + return charCodeDict; +} + +/** Function to supply the default key code dictionary. + * + * @returns default key code dictionary + */ +function getDefaultKeyCodeDictionary() +{ + var keyCodeDict = new Object(); + + keyCodeDict[SLIDE_MODE] = new Object(); + keyCodeDict[INDEX_MODE] = new Object(); + keyCodeDict[DRAWING_MODE] = new Object(); + + keyCodeDict[SLIDE_MODE][LEFT_KEY] = function() { return dispatchEffects(-1); }; + keyCodeDict[SLIDE_MODE][RIGHT_KEY] = function() { return dispatchEffects(1); }; + keyCodeDict[SLIDE_MODE][UP_KEY] = function() { return skipEffects(-1); }; + keyCodeDict[SLIDE_MODE][DOWN_KEY] = function() { return skipEffects(1); }; + keyCodeDict[SLIDE_MODE][PAGE_UP_KEY] = function() { return dispatchEffects(-1); }; + keyCodeDict[SLIDE_MODE][PAGE_DOWN_KEY] = function() { return dispatchEffects(1); }; + keyCodeDict[SLIDE_MODE][HOME_KEY] = function() { return slideSetActiveSlide(0); }; + keyCodeDict[SLIDE_MODE][END_KEY] = function() { return slideSetActiveSlide(slides.length - 1); }; + keyCodeDict[SLIDE_MODE][SPACE_KEY] = function() { return dispatchEffects(1); }; + + keyCodeDict[INDEX_MODE][LEFT_KEY] = function() { return indexSetPageSlide(activeSlide - 1); }; + keyCodeDict[INDEX_MODE][RIGHT_KEY] = function() { return indexSetPageSlide(activeSlide + 1); }; + keyCodeDict[INDEX_MODE][UP_KEY] = function() { return indexSetPageSlide(activeSlide - INDEX_COLUMNS); }; + keyCodeDict[INDEX_MODE][DOWN_KEY] = function() { return indexSetPageSlide(activeSlide + INDEX_COLUMNS); }; + keyCodeDict[INDEX_MODE][PAGE_UP_KEY] = function() { return indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS); }; + keyCodeDict[INDEX_MODE][PAGE_DOWN_KEY] = function() { return indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS); }; + keyCodeDict[INDEX_MODE][HOME_KEY] = function() { return indexSetPageSlide(0); }; + keyCodeDict[INDEX_MODE][END_KEY] = function() { return indexSetPageSlide(slides.length - 1); }; + keyCodeDict[INDEX_MODE][ENTER_KEY] = function() { return toggleSlideIndex(); }; + + keyCodeDict[DRAWING_MODE][ESCAPE_KEY] = function () { return drawingSwitchToSlideMode(); }; + + return keyCodeDict; +} + +/** Function to handle all mouse events. + * + * @param evnt event + * @param action type of event (e.g. mouse up, mouse wheel) + */ +function mouseHandlerDispatch(evnt, action) +{ + if (!evnt) + evnt = window.event; + + var retVal = true; + + if (!processingEffect && mouseHandlerDictionary[currentMode] && mouseHandlerDictionary[currentMode][action]) + { + var subRetVal = mouseHandlerDictionary[currentMode][action](evnt); + + if (subRetVal != null && subRetVal != undefined) + retVal = subRetVal; + } + + if (evnt.preventDefault && !retVal) + evnt.preventDefault(); + + evnt.returnValue = retVal; + + return retVal; +} + +// Set mouse event handler. +document.onmousedown = function(e) { return mouseHandlerDispatch(e, MOUSE_DOWN); }; +document.onmouseup = function(e) { return mouseHandlerDispatch(e, MOUSE_UP); }; +document.onmousemove = function(e) { return mouseHandlerDispatch(e, MOUSE_MOVE); }; + +// Moz +if (window.addEventListener) +{ + window.addEventListener('DOMMouseScroll', function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); }, false); +} + +// Opera Safari OK - may not work in IE +window.onmousewheel = function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); }; + +/** Function to supply the default mouse handler dictionary. + * + * @returns default mouse handler dictionary + */ +function getDefaultMouseHandlerDictionary() +{ + var mouseHandlerDict = new Object(); + + mouseHandlerDict[SLIDE_MODE] = new Object(); + mouseHandlerDict[INDEX_MODE] = new Object(); + mouseHandlerDict[DRAWING_MODE] = new Object(); + + mouseHandlerDict[SLIDE_MODE][MOUSE_DOWN] = function(evnt) { return dispatchEffects(1); }; + mouseHandlerDict[SLIDE_MODE][MOUSE_WHEEL] = function(evnt) { return slideMousewheel(evnt); }; + + mouseHandlerDict[INDEX_MODE][MOUSE_DOWN] = function(evnt) { return toggleSlideIndex(); }; + + mouseHandlerDict[DRAWING_MODE][MOUSE_DOWN] = function(evnt) { return drawingMousedown(evnt); }; + mouseHandlerDict[DRAWING_MODE][MOUSE_UP] = function(evnt) { return drawingMouseup(evnt); }; + mouseHandlerDict[DRAWING_MODE][MOUSE_MOVE] = function(evnt) { return drawingMousemove(evnt); }; + + return mouseHandlerDict; +} + +/** Function to switch from slide mode to drawing mode. +*/ +function slideSwitchToDrawingMode() +{ + currentMode = DRAWING_MODE; + + var tempDict; + + if (ROOT_NODE.hasAttribute("style")) + tempDict = propStrToDict(ROOT_NODE.getAttribute("style")); + else + tempDict = new Object(); + + tempDict["cursor"] = "crosshair"; + ROOT_NODE.setAttribute("style", dictToPropStr(tempDict)); +} + +/** Function to switch from drawing mode to slide mode. +*/ +function drawingSwitchToSlideMode() +{ + currentMode = SLIDE_MODE; + + var tempDict; + + if (ROOT_NODE.hasAttribute("style")) + tempDict = propStrToDict(ROOT_NODE.getAttribute("style")); + else + tempDict = new Object(); + + tempDict["cursor"] = "auto"; + ROOT_NODE.setAttribute("style", dictToPropStr(tempDict)); +} + +/** Function to decrease the number of columns in index mode. +*/ +function indexDecreaseNumberOfColumns() +{ + if (INDEX_COLUMNS >= 3) + { + INDEX_COLUMNS -= 1; + INDEX_OFFSET = -1 + indexSetPageSlide(activeSlide); + } +} + +/** Function to increase the number of columns in index mode. +*/ +function indexIncreaseNumberOfColumns() +{ + if (INDEX_COLUMNS < 7) + { + INDEX_COLUMNS += 1; + INDEX_OFFSET = -1 + indexSetPageSlide(activeSlide); + } +} + +/** Function to reset the number of columns in index mode. +*/ +function indexResetNumberOfColumns() +{ + if (INDEX_COLUMNS != INDEX_COLUMNS_DEFAULT) + { + INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT; + INDEX_OFFSET = -1 + indexSetPageSlide(activeSlide); + } +} + +/** Function to reset path width in drawing mode. +*/ +function drawingResetPathWidth() +{ + path_width = path_width_default; + set_path_paint_width(); +} + +/** Function to set path width in drawing mode. + * + * @param width new path width + */ +function drawingSetPathWidth(width) +{ + path_width = width; + set_path_paint_width(); +} + +/** Function to set path colour in drawing mode. + * + * @param colour new path colour + */ +function drawingSetPathColour(colour) +{ + path_colour = colour; +} + +/** Function to query the duration of the presentation from the user in slide mode. +*/ +function slideQueryDuration() +{ + var new_duration = prompt("Length of presentation in minutes?", timer_duration); + + if ((new_duration != null) && (new_duration != '')) + { + timer_duration = new_duration; + } + + updateTimer(); +} + +/** Function to add new slide in slide mode. + * + * @param afterSlide after which slide to insert the new one + */ +function slideAddSlide(afterSlide) +{ + addSlide(afterSlide); + slideSetActiveSlide(afterSlide + 1); + updateTimer(); +} + +/** Function to toggle the visibility of the progress bar in slide mode. +*/ +function slideToggleProgressBarVisibility() +{ + if (progress_bar_visible) + { + progress_bar_visible = false; + hideProgressBar(); + } + else + { + progress_bar_visible = true; + showProgressBar(); + } +} + +/** Function to reset the timer in slide mode. +*/ +function slideResetTimer() +{ + timer_start = timer_elapsed; + updateTimer(); +} + +/** Convenience function to pad a string with zero in front up to a certain length. + */ +function padString(str, len) +{ + var outStr = str; + + while (outStr.length < len) + { + outStr = '0' + outStr; + } + + return outStr; +} + +/** Function to update the export layer. + */ +function slideUpdateExportLayer() +{ + // Suspend redraw since we are going to mess with the slides. + var suspendHandle = ROOT_NODE.suspendRedraw(2000); + + var tmpActiveSlide = activeSlide; + var tmpActiveEffect = activeEffect; + var exportedLayers = new Array(); + + for (var counterSlides = 0; counterSlides < slides.length; counterSlides++) + { + var exportNode; + + setSlideToState(counterSlides, STATE_START); + + var maxEffect = 0; + + if (slides[counterSlides].effects) + { + maxEffect = slides[counterSlides].effects.length; + } + + exportNode = slides[counterSlides].element.cloneNode(true); + exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer"); + exportNode.setAttributeNS(NSS["inkscape"], "label", "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString("0", maxEffect.toString().length)); + + exportedLayers.push(exportNode); + + if (slides[counterSlides]["effects"]) + { + for (var counter = 0; counter < slides[counterSlides]["effects"].length; counter++) + { + for (var subCounter = 0; subCounter < slides[counterSlides]["effects"][counter].length; subCounter++) + { + var effect = slides[counterSlides]["effects"][counter][subCounter]; + if (effect["effect"] == "fade") + fade(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "appear") + appear(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "pop") + pop(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "view") + view(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]); + } + + var layerName = "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString((counter + 1).toString(), maxEffect.toString().length); + exportNode = slides[counterSlides].element.cloneNode(true); + exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer"); + exportNode.setAttributeNS(NSS["inkscape"], "label", layerName); + exportNode.setAttribute("id", layerName); + + exportedLayers.push(exportNode); + } + } + } + + activeSlide = tmpActiveSlide; + activeEffect = tmpActiveEffect; + setSlideToState(activeSlide, activeEffect); + + // Copy image. + var newDoc = document.documentElement.cloneNode(true); + + // Delete viewbox form new imag and set width and height. + newDoc.removeAttribute('viewbox'); + newDoc.setAttribute('width', WIDTH); + newDoc.setAttribute('height', HEIGHT); + + // Delete all layers and script elements. + var nodesToBeRemoved = new Array(); + + for (var childCounter = 0; childCounter < newDoc.childNodes.length; childCounter++) + { + var child = newDoc.childNodes[childCounter]; + + if (child.nodeType == 1) + { + if ((child.nodeName.toUpperCase() == 'G') || (child.nodeName.toUpperCase() == 'SCRIPT')) + { + nodesToBeRemoved.push(child); + } + } + } + + for (var ndCounter = 0; ndCounter < nodesToBeRemoved.length; ndCounter++) + { + var nd = nodesToBeRemoved[ndCounter]; + + // Before removing the node, check whether it contains any definitions. + var defs = nd.getElementsByTagNameNS(NSS["svg"], "defs"); + + for (var defsCounter = 0; defsCounter < defs.length; defsCounter++) + { + if (defs[defsCounter].id) + { + newDoc.appendChild(defs[defsCounter].cloneNode(true)); + } + } + + // Remove node. + nd.parentNode.removeChild(nd); + } + + // Set current layer. + if (exportedLayers[0]) + { + var namedView; + + for (var nodeCounter = 0; nodeCounter < newDoc.childNodes.length; nodeCounter++) + { + if ((newDoc.childNodes[nodeCounter].nodeType == 1) && (newDoc.childNodes[nodeCounter].getAttribute('id') == 'base')) + { + namedView = newDoc.childNodes[nodeCounter]; + } + } + + if (namedView) + { + namedView.setAttributeNS(NSS['inkscape'], 'current-layer', exportedLayers[0].getAttributeNS(NSS['inkscape'], 'label')); + } + } + + // Add exported layers. + while (exportedLayers.length > 0) + { + var nd = exportedLayers.pop(); + + nd.setAttribute("opacity",1); + nd.style.display = "inherit"; + + newDoc.appendChild(nd); + } + + // Serialise the new document. + window.location = 'data:application/svg+xml;base64;charset=utf-8,' + window.btoa(unescape(encodeURIComponent((new XMLSerializer()).serializeToString(newDoc)))); + + // Unsuspend redraw. + ROOT_NODE.unsuspendRedraw(suspendHandle); + ROOT_NODE.forceRedraw(); +} + +/** Function to undo last drawing operation. +*/ +function drawingUndo() +{ + mouse_presentation_path = null; + mouse_original_path = null; + + if (history_presentation_elements.length > 0) + { + var p = history_presentation_elements.pop(); + var parent = p.parentNode.removeChild(p); + + p = history_original_elements.pop(); + parent = p.parentNode.removeChild(p); + } +} + +/** Event handler for mouse down in drawing mode. + * + * @param e the event + */ +function drawingMousedown(e) +{ + var value = 0; + + if (e.button) + value = e.button; + else if (e.which) + value = e.which; + + if (value == 1) + { + history_counter++; + + var p = calcCoord(e); + + mouse_last_x = e.clientX; + mouse_last_y = e.clientY; + mouse_original_path = document.createElementNS(NSS["svg"], "path"); + mouse_original_path.setAttribute("stroke", path_colour); + mouse_original_path.setAttribute("stroke-width", path_paint_width); + mouse_original_path.setAttribute("fill", "none"); + mouse_original_path.setAttribute("id", "path " + Date()); + mouse_original_path.setAttribute("d", "M" + p.x + "," + p.y); + slides[activeSlide]["original_element"].appendChild(mouse_original_path); + history_original_elements.push(mouse_original_path); + + mouse_presentation_path = document.createElementNS(NSS["svg"], "path"); + mouse_presentation_path.setAttribute("stroke", path_colour); + mouse_presentation_path.setAttribute("stroke-width", path_paint_width); + mouse_presentation_path.setAttribute("fill", "none"); + mouse_presentation_path.setAttribute("id", "path " + Date() + " presentation copy"); + mouse_presentation_path.setAttribute("d", "M" + p.x + "," + p.y); + + if (slides[activeSlide]["viewGroup"]) + slides[activeSlide]["viewGroup"].appendChild(mouse_presentation_path); + else + slides[activeSlide]["element"].appendChild(mouse_presentation_path); + + history_presentation_elements.push(mouse_presentation_path); + + return false; + } + + return true; +} + +/** Event handler for mouse up in drawing mode. + * + * @param e the event + */ +function drawingMouseup(e) +{ + if(!e) + e = window.event; + + if (mouse_presentation_path != null) + { + var p = calcCoord(e); + var d = mouse_presentation_path.getAttribute("d"); + d += " L" + p.x + "," + p.y; + mouse_presentation_path.setAttribute("d", d); + mouse_presentation_path = null; + mouse_original_path.setAttribute("d", d); + mouse_original_path = null; + + return false; + } + + return true; +} + +/** Event handler for mouse move in drawing mode. + * + * @param e the event + */ +function drawingMousemove(e) +{ + if(!e) + e = window.event; + + var dist = (mouse_last_x - e.clientX) * (mouse_last_x - e.clientX) + (mouse_last_y - e.clientY) * (mouse_last_y - e.clientY); + + if (mouse_presentation_path == null) + { + return true; + } + + if (dist >= mouse_min_dist_sqr) + { + var p = calcCoord(e); + var d = mouse_presentation_path.getAttribute("d"); + d += " L" + p.x + "," + p.y; + mouse_presentation_path.setAttribute("d", d); + mouse_original_path.setAttribute("d", d); + mouse_last_x = e.clientX; + mouse_last_y = e.clientY; + } + + return false; +} + +/** Event handler for mouse wheel events in slide mode. + * based on http://adomas.org/javascript-mouse-wheel/ + * + * @param e the event + */ +function slideMousewheel(e) +{ + var delta = 0; + + if (!e) + e = window.event; + + if (e.wheelDelta) + { // IE Opera + delta = e.wheelDelta/120; + } + else if (e.detail) + { // MOZ + delta = -e.detail/3; + } + + if (delta > 0) + skipEffects(-1); + else if (delta < 0) + skipEffects(1); + + if (e.preventDefault) + e.preventDefault(); + + e.returnValue = false; +} + +/** Event handler for mouse wheel events in index mode. + * based on http://adomas.org/javascript-mouse-wheel/ + * + * @param e the event + */ +function indexMousewheel(e) +{ + var delta = 0; + + if (!e) + e = window.event; + + if (e.wheelDelta) + { // IE Opera + delta = e.wheelDelta/120; + } + else if (e.detail) + { // MOZ + delta = -e.detail/3; + } + + if (delta > 0) + indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS); + else if (delta < 0) + indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS); + + if (e.preventDefault) + e.preventDefault(); + + e.returnValue = false; +} + +/** Function to set the path paint width. +*/ +function set_path_paint_width() +{ + var svgPoint1 = document.documentElement.createSVGPoint(); + var svgPoint2 = document.documentElement.createSVGPoint(); + + svgPoint1.x = 0.0; + svgPoint1.y = 0.0; + svgPoint2.x = 1.0; + svgPoint2.y = 0.0; + + var matrix = slides[activeSlide]["element"].getTransformToElement(ROOT_NODE); + + if (slides[activeSlide]["viewGroup"]) + matrix = slides[activeSlide]["viewGroup"].getTransformToElement(ROOT_NODE); + + svgPoint1 = svgPoint1.matrixTransform(matrix); + svgPoint2 = svgPoint2.matrixTransform(matrix); + + path_paint_width = path_width / Math.sqrt((svgPoint2.x - svgPoint1.x) * (svgPoint2.x - svgPoint1.x) + (svgPoint2.y - svgPoint1.y) * (svgPoint2.y - svgPoint1.y)); +} + +/** The view effect. + * + * @param dir direction the effect should be played (1 = forwards, -1 = backwards) + * @param element the element the effect should be applied to + * @param time the time that has elapsed since the beginning of the effect + * @param options a dictionary with additional options (e.g. length of the effect); for the view effect the options need to contain the old and the new matrix. + */ +function view(dir, element, time, options) +{ + var length = 250; + var fraction; + + if (!options["matrixInitial"]) + { + var tempString = slides[activeSlide]["viewGroup"].getAttribute("transform"); + + if (tempString) + options["matrixInitial"] = (new matrixSVG()).fromAttribute(tempString); + else + options["matrixInitial"] = (new matrixSVG()).fromSVGElements(1, 0, 0, 1, 0, 0); + } + + if ((time == STATE_END) || (time == STATE_START)) + fraction = 1; + else + { + if (options && options["length"]) + length = options["length"]; + + fraction = time / length; + } + + if (dir == 1) + { + if (fraction <= 0) + { + element.setAttribute("transform", options["matrixInitial"].toAttribute()); + } + else if (fraction >= 1) + { + element.setAttribute("transform", options["matrixNew"].toAttribute()); + + set_path_paint_width(); + + options["matrixInitial"] = null; + return true; + } + else + { + element.setAttribute("transform", options["matrixInitial"].mix(options["matrixNew"], fraction).toAttribute()); + } + } + else if (dir == -1) + { + if (fraction <= 0) + { + element.setAttribute("transform", options["matrixInitial"].toAttribute()); + } + else if (fraction >= 1) + { + element.setAttribute("transform", options["matrixOld"].toAttribute()); + set_path_paint_width(); + + options["matrixInitial"] = null; + return true; + } + else + { + element.setAttribute("transform", options["matrixInitial"].mix(options["matrixOld"], fraction).toAttribute()); + } + } + + return false; +} + +/** The fade effect. + * + * @param dir direction the effect should be played (1 = forwards, -1 = backwards) + * @param element the element the effect should be applied to + * @param time the time that has elapsed since the beginning of the effect + * @param options a dictionary with additional options (e.g. length of the effect) + */ +function fade(dir, element, time, options) +{ + var length = 250; + var fraction; + + if ((time == STATE_END) || (time == STATE_START)) + fraction = 1; + else + { + if (options && options["length"]) + length = options["length"]; + + fraction = time / length; + } + + if (dir == 1) + { + if (fraction <= 0) + { + element.style.display = "none"; + element.setAttribute("opacity", 0); + } + else if (fraction >= 1) + { + element.style.display = "inherit"; + element.setAttribute("opacity", 1); + return true; + } + else + { + element.style.display = "inherit"; + element.setAttribute("opacity", fraction); + } + } + else if (dir == -1) + { + if (fraction <= 0) + { + element.style.display = "inherit"; + element.setAttribute("opacity", 1); + } + else if (fraction >= 1) + { + element.setAttribute("opacity", 0); + element.style.display = "none"; + return true; + } + else + { + element.style.display = "inherit"; + element.setAttribute("opacity", 1 - fraction); + } + } + return false; +} + +/** The appear effect. + * + * @param dir direction the effect should be played (1 = forwards, -1 = backwards) + * @param element the element the effect should be applied to + * @param time the time that has elapsed since the beginning of the effect + * @param options a dictionary with additional options (e.g. length of the effect) + */ +function appear(dir, element, time, options) +{ + if (dir == 1) + { + element.style.display = "inherit"; + element.setAttribute("opacity",1); + } + else if (dir == -1) + { + element.style.display = "none"; + element.setAttribute("opacity",0); + } + return true; +} + +/** The pop effect. + * + * @param dir direction the effect should be played (1 = forwards, -1 = backwards) + * @param element the element the effect should be applied to + * @param time the time that has elapsed since the beginning of the effect + * @param options a dictionary with additional options (e.g. length of the effect) + */ +function pop(dir, element, time, options) +{ + var length = 500; + var fraction; + + if ((time == STATE_END) || (time == STATE_START)) + fraction = 1; + else + { + if (options && options["length"]) + length = options["length"]; + + fraction = time / length; + } + + if (dir == 1) + { + if (fraction <= 0) + { + element.setAttribute("opacity", 0); + element.setAttribute("transform", "scale(0)"); + element.style.display = "none"; + } + else if (fraction >= 1) + { + element.setAttribute("opacity", 1); + element.removeAttribute("transform"); + element.style.display = "inherit"; + return true; + } + else + { + element.style.display = "inherit"; + var opacityFraction = fraction * 3; + if (opacityFraction > 1) + opacityFraction = 1; + element.setAttribute("opacity", opacityFraction); + var offsetX = WIDTH * (1.0 - fraction) / 2.0; + var offsetY = HEIGHT * (1.0 - fraction) / 2.0; + element.setAttribute("transform", "translate(" + offsetX + "," + offsetY + ") scale(" + fraction + ")"); + } + } + else if (dir == -1) + { + if (fraction <= 0) + { + element.setAttribute("opacity", 1); + element.setAttribute("transform", "scale(1)"); + element.style.display = "inherit"; + } + else if (fraction >= 1) + { + element.setAttribute("opacity", 0); + element.removeAttribute("transform"); + element.style.display = "none"; + return true; + } + else + { + element.setAttribute("opacity", 1 - fraction); + element.setAttribute("transform", "scale(" + 1 - fraction + ")"); + element.style.display = "inherit"; + } + } + return false; +} + +/** Function to set a slide either to the start or the end state. + * + * @param slide the slide to use + * @param state the state into which the slide should be set + */ +function setSlideToState(slide, state) +{ + slides[slide]["viewGroup"].setAttribute("transform", slides[slide].initialView); + + if (slides[slide]["effects"]) + { + if (state == STATE_END) + { + for (var counter = 0; counter < slides[slide]["effects"].length; counter++) + { + for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++) + { + var effect = slides[slide]["effects"][counter][subCounter]; + if (effect["effect"] == "fade") + fade(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "appear") + appear(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "pop") + pop(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "view") + view(effect["dir"], effect["element"], STATE_END, effect["options"]); + } + } + } + else if (state == STATE_START) + { + for (var counter = slides[slide]["effects"].length - 1; counter >= 0; counter--) + { + for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++) + { + var effect = slides[slide]["effects"][counter][subCounter]; + if (effect["effect"] == "fade") + fade(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]); + else if (effect["effect"] == "appear") + appear(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]); + else if (effect["effect"] == "pop") + pop(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]); + else if (effect["effect"] == "view") + view(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]); + } + } + } + else + { + setSlideToState(slide, STATE_START); + + for (var counter = 0; counter < slides[slide]["effects"].length && counter < state; counter++) + { + for (var subCounter = 0; subCounter < slides[slide]["effects"][counter].length; subCounter++) + { + var effect = slides[slide]["effects"][counter][subCounter]; + if (effect["effect"] == "fade") + fade(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "appear") + appear(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "pop") + pop(effect["dir"], effect["element"], STATE_END, effect["options"]); + else if (effect["effect"] == "view") + view(effect["dir"], effect["element"], STATE_END, effect["options"]); + } + } + } + } + + window.location.hash = (activeSlide + 1) + '_' + activeEffect; +} + +/** Convenience function to translate a attribute string into a dictionary. + * + * @param str the attribute string + * @return a dictionary + * @see dictToPropStr + */ +function propStrToDict(str) +{ + var list = str.split(";"); + var obj = new Object(); + + for (var counter = 0; counter < list.length; counter++) + { + var subStr = list[counter]; + var subList = subStr.split(":"); + if (subList.length == 2) + { + obj[subList[0]] = subList[1]; + } + } + + return obj; +} + +/** Convenience function to translate a dictionary into a string that can be used as an attribute. + * + * @param dict the dictionary to convert + * @return a string that can be used as an attribute + * @see propStrToDict + */ +function dictToPropStr(dict) +{ + var str = ""; + + for (var key in dict) + { + str += key + ":" + dict[key] + ";"; + } + + return str; +} + +/** Sub-function to add a suffix to the ids of the node and all its children. + * + * @param node the node to change + * @param suffix the suffix to add + * @param replace dictionary of replaced ids + * @see suffixNodeIds + */ +function suffixNoneIds_sub(node, suffix, replace) +{ + if (node.nodeType == 1) + { + if (node.getAttribute("id")) + { + var id = node.getAttribute("id") + replace["#" + id] = id + suffix; + node.setAttribute("id", id + suffix); + } + + if ((node.nodeName == "use") && (node.getAttributeNS(NSS["xlink"], "href")) && (replace[node.getAttribute(NSS["xlink"], "href")])) + node.setAttribute(NSS["xlink"], "href", node.getAttribute(NSS["xlink"], "href") + suffix); + + if (node.childNodes) + { + for (var counter = 0; counter < node.childNodes.length; counter++) + suffixNoneIds_sub(node.childNodes[counter], suffix, replace); + } + } +} + +/** Function to add a suffix to the ids of the node and all its children. + * + * @param node the node to change + * @param suffix the suffix to add + * @return the changed node + * @see suffixNodeIds_sub + */ +function suffixNodeIds(node, suffix) +{ + var replace = new Object(); + + suffixNoneIds_sub(node, suffix, replace); + + return node; +} + +/** Function to build a progress bar. + * + * @param parent node to attach the progress bar to + */ +function createProgressBar(parent_node) +{ + var g = document.createElementNS(NSS["svg"], "g"); + g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)"); + g.setAttribute("id", "layer_progress_bar"); + g.setAttribute("style", "display: none;"); + + var rect_progress_bar = document.createElementNS(NSS["svg"], "rect"); + rect_progress_bar.setAttribute("style", "marker: none; fill: rgb(128, 128, 128); stroke: none;"); + rect_progress_bar.setAttribute("id", "rect_progress_bar"); + rect_progress_bar.setAttribute("x", 0); + rect_progress_bar.setAttribute("y", 0.99 * HEIGHT); + rect_progress_bar.setAttribute("width", 0); + rect_progress_bar.setAttribute("height", 0.01 * HEIGHT); + g.appendChild(rect_progress_bar); + + var circle_timer_indicator = document.createElementNS(NSS["svg"], "circle"); + circle_timer_indicator.setAttribute("style", "marker: none; fill: rgb(255, 0, 0); stroke: none;"); + circle_timer_indicator.setAttribute("id", "circle_timer_indicator"); + circle_timer_indicator.setAttribute("cx", 0.005 * HEIGHT); + circle_timer_indicator.setAttribute("cy", 0.995 * HEIGHT); + circle_timer_indicator.setAttribute("r", 0.005 * HEIGHT); + g.appendChild(circle_timer_indicator); + + parent_node.appendChild(g); +} + +/** Function to hide the progress bar. + * + */ +function hideProgressBar() +{ + var progress_bar = document.getElementById("layer_progress_bar"); + + if (!progress_bar) + { + return; + } + + progress_bar.setAttribute("style", "display: none;"); +} + +/** Function to show the progress bar. + * + */ +function showProgressBar() +{ + var progress_bar = document.getElementById("layer_progress_bar"); + + if (!progress_bar) + { + return; + } + + progress_bar.setAttribute("style", "display: inherit;"); +} + +/** Set progress bar value. + * + * @param value the current slide number + * + */ +function setProgressBarValue(value) +{ + var rect_progress_bar = document.getElementById("rect_progress_bar"); + + if (!rect_progress_bar) + { + return; + } + + if (value < 1) + { + // First slide, assumed to be the title of the presentation + var x = 0; + var w = 0.01 * HEIGHT; + } + else if (value >= slides.length - 1) + { + // Last slide, assumed to be the end of the presentation + var x = WIDTH - 0.01 * HEIGHT; + var w = 0.01 * HEIGHT; + } + else + { + value -= 1; + value /= (slides.length - 2); + + var x = WIDTH * value; + var w = WIDTH / (slides.length - 2); + } + + rect_progress_bar.setAttribute("x", x); + rect_progress_bar.setAttribute("width", w); +} + +/** Set time indicator. + * + * @param value the percentage of time elapse so far between 0.0 and 1.0 + * + */ +function setTimeIndicatorValue(value) +{ + var circle_timer_indicator = document.getElementById("circle_timer_indicator"); + + if (!circle_timer_indicator) + { + return; + } + + if (value < 0.0) + { + value = 0.0; + } + + if (value > 1.0) + { + value = 1.0; + } + + var cx = (WIDTH - 0.01 * HEIGHT) * value + 0.005 * HEIGHT; + circle_timer_indicator.setAttribute("cx", cx); +} + +/** Update timer. + * + */ +function updateTimer() +{ + timer_elapsed += 1; + setTimeIndicatorValue((timer_elapsed - timer_start) / (60 * timer_duration)); +} + +/** Convert screen coordinates to document coordinates. + * + * @param e event with screen coordinates + * + * @return coordinates in SVG file coordinate system + */ +function calcCoord(e) +{ + var svgPoint = document.documentElement.createSVGPoint(); + svgPoint.x = e.clientX + window.pageXOffset; + svgPoint.y = e.clientY + window.pageYOffset; + + var matrix = slides[activeSlide]["element"].getScreenCTM(); + + if (slides[activeSlide]["viewGroup"]) + matrix = slides[activeSlide]["viewGroup"].getScreenCTM(); + + svgPoint = svgPoint.matrixTransform(matrix.inverse()); + return svgPoint; +} + +/** Add slide. + * + * @param after_slide after which slide the new slide should be inserted into the presentation + */ +function addSlide(after_slide) +{ + number_of_added_slides++; + + var g = document.createElementNS(NSS["svg"], "g"); + g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)"); + g.setAttribute("id", "Whiteboard " + Date() + " presentation copy"); + g.setAttribute("style", "display: none;"); + + var new_slide = new Object(); + new_slide["element"] = g; + + // Set build in transition. + new_slide["transitionIn"] = new Object(); + var dict = defaultTransitionInDict; + new_slide["transitionIn"]["name"] = dict["name"]; + new_slide["transitionIn"]["options"] = new Object(); + + for (key in dict) + if (key != "name") + new_slide["transitionIn"]["options"][key] = dict[key]; + + // Set build out transition. + new_slide["transitionOut"] = new Object(); + dict = defaultTransitionOutDict; + new_slide["transitionOut"]["name"] = dict["name"]; + new_slide["transitionOut"]["options"] = new Object(); + + for (key in dict) + if (key != "name") + new_slide["transitionOut"]["options"][key] = dict[key]; + + // Copy master slide content. + if (masterSlide) + { + var clonedNode = suffixNodeIds(masterSlide.cloneNode(true), "_" + Date() + " presentation_copy"); + clonedNode.removeAttributeNS(NSS["inkscape"], "groupmode"); + clonedNode.removeAttributeNS(NSS["inkscape"], "label"); + clonedNode.style.display = "inherit"; + + g.appendChild(clonedNode); + } + + // Substitute auto texts. + substituteAutoTexts(g, "Whiteboard " + number_of_added_slides, "W" + number_of_added_slides, slides.length); + + g.setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + (after_slide + 1) + ")) { indexSetActiveSlide(" + (after_slide + 1) + "); };"); + + // Create a transform group. + var transformGroup = document.createElementNS(NSS["svg"], "g"); + + // Add content to transform group. + while (g.firstChild) + transformGroup.appendChild(g.firstChild); + + // Transfer the transform attribute from the node to the transform group. + if (g.getAttribute("transform")) + { + transformGroup.setAttribute("transform", g.getAttribute("transform")); + g.removeAttribute("transform"); + } + + // Create a view group. + var viewGroup = document.createElementNS(NSS["svg"], "g"); + + viewGroup.appendChild(transformGroup); + new_slide["viewGroup"] = g.appendChild(viewGroup); + + // Insert background. + if (BACKGROUND_COLOR != null) + { + var rectNode = document.createElementNS(NSS["svg"], "rect"); + + rectNode.setAttribute("x", 0); + rectNode.setAttribute("y", 0); + rectNode.setAttribute("width", WIDTH); + rectNode.setAttribute("height", HEIGHT); + rectNode.setAttribute("id", "jessyInkBackground" + Date()); + rectNode.setAttribute("fill", BACKGROUND_COLOR); + + new_slide["viewGroup"].insertBefore(rectNode, new_slide["viewGroup"].firstChild); + } + + // Set initial view even if there are no other views. + var matrixOld = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + + new_slide["viewGroup"].setAttribute("transform", matrixOld.toAttribute()); + new_slide.initialView = matrixOld.toAttribute(); + + // Insert slide + var node = slides[after_slide]["element"]; + var next_node = node.nextSibling; + var parent_node = node.parentNode; + + if (next_node) + { + parent_node.insertBefore(g, next_node); + } + else + { + parent_node.appendChild(g); + } + + g = document.createElementNS(NSS["svg"], "g"); + g.setAttributeNS(NSS["inkscape"], "groupmode", "layer"); + g.setAttributeNS(NSS["inkscape"], "label", "Whiteboard " + number_of_added_slides); + g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)"); + g.setAttribute("id", "Whiteboard " + Date()); + g.setAttribute("style", "display: none;"); + + new_slide["original_element"] = g; + + node = slides[after_slide]["original_element"]; + next_node = node.nextSibling; + parent_node = node.parentNode; + + if (next_node) + { + parent_node.insertBefore(g, next_node); + } + else + { + parent_node.appendChild(g); + } + + before_new_slide = slides.slice(0, after_slide + 1); + after_new_slide = slides.slice(after_slide + 1); + slides = before_new_slide.concat(new_slide, after_new_slide); + + //resetting the counter attributes on the slides that follow the new slide... + for (var counter = after_slide+2; counter < slides.length; counter++) + { + slides[counter]["element"].setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) && ( activeSlide != " + counter + ")) { indexSetActiveSlide(" + counter + "); };"); + } +} + +/** Convenience function to obtain a transformation matrix from a point matrix. + * + * @param mPoints Point matrix. + * @return A transformation matrix. + */ +function pointMatrixToTransformation(mPoints) +{ + mPointsOld = (new matrixSVG()).fromElements(0, WIDTH, WIDTH, 0, 0, HEIGHT, 1, 1, 1); + + return mPointsOld.mult(mPoints.inv()); +} + +/** Convenience function to obtain a matrix with three corners of a rectangle. + * + * @param rect an svg rectangle + * @return a matrixSVG containing three corners of the rectangle + */ +function rectToMatrix(rect) +{ + rectWidth = rect.getBBox().width; + rectHeight = rect.getBBox().height; + rectX = rect.getBBox().x; + rectY = rect.getBBox().y; + rectXcorr = 0; + rectYcorr = 0; + + scaleX = WIDTH / rectWidth; + scaleY = HEIGHT / rectHeight; + + if (scaleX > scaleY) + { + scaleX = scaleY; + rectXcorr -= (WIDTH / scaleX - rectWidth) / 2; + rectWidth = WIDTH / scaleX; + } + else + { + scaleY = scaleX; + rectYcorr -= (HEIGHT / scaleY - rectHeight) / 2; + rectHeight = HEIGHT / scaleY; + } + + if (rect.transform.baseVal.numberOfItems < 1) + { + mRectTrans = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + } + else + { + mRectTrans = (new matrixSVG()).fromSVGMatrix(rect.transform.baseVal.consolidate().matrix); + } + + newBasePoints = (new matrixSVG()).fromElements(rectX, rectX, rectX, rectY, rectY, rectY, 1, 1, 1); + newVectors = (new matrixSVG()).fromElements(rectXcorr, rectXcorr + rectWidth, rectXcorr + rectWidth, rectYcorr, rectYcorr, rectYcorr + rectHeight, 0, 0, 0); + + return mRectTrans.mult(newBasePoints.add(newVectors)); +} + +/** Function to handle JessyInk elements. + * + * @param node Element node. + */ +function handleElement(node) +{ + if (node.getAttributeNS(NSS['jessyink'], 'element') == 'core.video') + { + var url; + var width; + var height; + var x; + var y; + var transform; + + var tspans = node.getElementsByTagNameNS("http://www.w3.org/2000/svg", "tspan"); + + for (var tspanCounter = 0; tspanCounter < tspans.length; tspanCounter++) + { + if (tspans[tspanCounter].getAttributeNS("https://launchpad.net/jessyink", "video") == "url") + { + url = tspans[tspanCounter].firstChild.nodeValue; + } + } + + var rects = node.getElementsByTagNameNS("http://www.w3.org/2000/svg", "rect"); + + for (var rectCounter = 0; rectCounter < rects.length; rectCounter++) + { + if (rects[rectCounter].getAttributeNS("https://launchpad.net/jessyink", "video") == "rect") + { + x = rects[rectCounter].getAttribute("x"); + y = rects[rectCounter].getAttribute("y"); + width = rects[rectCounter].getAttribute("width"); + height = rects[rectCounter].getAttribute("height"); + transform = rects[rectCounter].getAttribute("transform"); + } + } + + for (var childCounter = 0; childCounter < node.childNodes.length; childCounter++) + { + if (node.childNodes[childCounter].nodeType == 1) + { + if (node.childNodes[childCounter].style) + { + node.childNodes[childCounter].style.display = 'none'; + } + else + { + node.childNodes[childCounter].setAttribute("style", "display: none;"); + } + } + } + + var foreignNode = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject"); + foreignNode.setAttribute("x", x); + foreignNode.setAttribute("y", y); + foreignNode.setAttribute("width", width); + foreignNode.setAttribute("height", height); + foreignNode.setAttribute("transform", transform); + + var videoNode = document.createElementNS("http://www.w3.org/1999/xhtml", "video"); + videoNode.setAttribute("src", url); + + foreignNode.appendChild(videoNode); + node.appendChild(foreignNode); + } +} + +/** Class processing the location hash. + * + * @param str location hash + */ +function LocationHash(str) +{ + this.slideNumber = 0; + this.effectNumber = 0; + + str = str.substr(1, str.length - 1); + + var parts = str.split('_'); + + // Try to extract slide number. + if (parts.length >= 1) + { + try + { + var slideNumber = parseInt(parts[0]); + + if (!isNaN(slideNumber)) + { + this.slideNumber = slideNumber - 1; + } + } + catch (e) + { + } + } + + // Try to extract effect number. + if (parts.length >= 2) + { + try + { + var effectNumber = parseInt(parts[1]); + + if (!isNaN(effectNumber)) + { + this.effectNumber = effectNumber; + } + } + catch (e) + { + } + } +} + +/** Class representing an svg matrix. +*/ +function matrixSVG() +{ + this.e11 = 0; // a + this.e12 = 0; // c + this.e13 = 0; // e + this.e21 = 0; // b + this.e22 = 0; // d + this.e23 = 0; // f + this.e31 = 0; + this.e32 = 0; + this.e33 = 0; +} + +/** Constructor function. + * + * @param a element a (i.e. 1, 1) as described in the svg standard. + * @param b element b (i.e. 2, 1) as described in the svg standard. + * @param c element c (i.e. 1, 2) as described in the svg standard. + * @param d element d (i.e. 2, 2) as described in the svg standard. + * @param e element e (i.e. 1, 3) as described in the svg standard. + * @param f element f (i.e. 2, 3) as described in the svg standard. + */ +matrixSVG.prototype.fromSVGElements = function(a, b, c, d, e, f) +{ + this.e11 = a; + this.e12 = c; + this.e13 = e; + this.e21 = b; + this.e22 = d; + this.e23 = f; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + + return this; +} + +/** Constructor function. + * + * @param matrix an svg matrix as described in the svg standard. + */ +matrixSVG.prototype.fromSVGMatrix = function(m) +{ + this.e11 = m.a; + this.e12 = m.c; + this.e13 = m.e; + this.e21 = m.b; + this.e22 = m.d; + this.e23 = m.f; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + + return this; +} + +/** Constructor function. + * + * @param e11 element 1, 1 of the matrix. + * @param e12 element 1, 2 of the matrix. + * @param e13 element 1, 3 of the matrix. + * @param e21 element 2, 1 of the matrix. + * @param e22 element 2, 2 of the matrix. + * @param e23 element 2, 3 of the matrix. + * @param e31 element 3, 1 of the matrix. + * @param e32 element 3, 2 of the matrix. + * @param e33 element 3, 3 of the matrix. + */ +matrixSVG.prototype.fromElements = function(e11, e12, e13, e21, e22, e23, e31, e32, e33) +{ + this.e11 = e11; + this.e12 = e12; + this.e13 = e13; + this.e21 = e21; + this.e22 = e22; + this.e23 = e23; + this.e31 = e31; + this.e32 = e32; + this.e33 = e33; + + return this; +} + +/** Constructor function. + * + * @param attrString string value of the "transform" attribute (currently only "matrix" is accepted) + */ +matrixSVG.prototype.fromAttribute = function(attrString) +{ + str = attrString.substr(7, attrString.length - 8); + + str = str.trim(); + + strArray = str.split(","); + + // Opera does not use commas to separate the values of the matrix, only spaces. + if (strArray.length != 6) + strArray = str.split(" "); + + this.e11 = parseFloat(strArray[0]); + this.e21 = parseFloat(strArray[1]); + this.e31 = 0; + this.e12 = parseFloat(strArray[2]); + this.e22 = parseFloat(strArray[3]); + this.e32 = 0; + this.e13 = parseFloat(strArray[4]); + this.e23 = parseFloat(strArray[5]); + this.e33 = 1; + + return this; +} + +/** Output function + * + * @return a string that can be used as the "transform" attribute. + */ +matrixSVG.prototype.toAttribute = function() +{ + return "matrix(" + this.e11 + ", " + this.e21 + ", " + this.e12 + ", " + this.e22 + ", " + this.e13 + ", " + this.e23 + ")"; +} + +/** Matrix nversion. + * + * @return the inverse of the matrix + */ +matrixSVG.prototype.inv = function() +{ + out = new matrixSVG(); + + det = this.e11 * (this.e33 * this.e22 - this.e32 * this.e23) - this.e21 * (this.e33 * this.e12 - this.e32 * this.e13) + this.e31 * (this.e23 * this.e12 - this.e22 * this.e13); + + out.e11 = (this.e33 * this.e22 - this.e32 * this.e23) / det; + out.e12 = -(this.e33 * this.e12 - this.e32 * this.e13) / det; + out.e13 = (this.e23 * this.e12 - this.e22 * this.e13) / det; + out.e21 = -(this.e33 * this.e21 - this.e31 * this.e23) / det; + out.e22 = (this.e33 * this.e11 - this.e31 * this.e13) / det; + out.e23 = -(this.e23 * this.e11 - this.e21 * this.e13) / det; + out.e31 = (this.e32 * this.e21 - this.e31 * this.e22) / det; + out.e32 = -(this.e32 * this.e11 - this.e31 * this.e12) / det; + out.e33 = (this.e22 * this.e11 - this.e21 * this.e12) / det; + + return out; +} + +/** Matrix multiplication. + * + * @param op another svg matrix + * @return this * op + */ +matrixSVG.prototype.mult = function(op) +{ + out = new matrixSVG(); + + out.e11 = this.e11 * op.e11 + this.e12 * op.e21 + this.e13 * op.e31; + out.e12 = this.e11 * op.e12 + this.e12 * op.e22 + this.e13 * op.e32; + out.e13 = this.e11 * op.e13 + this.e12 * op.e23 + this.e13 * op.e33; + out.e21 = this.e21 * op.e11 + this.e22 * op.e21 + this.e23 * op.e31; + out.e22 = this.e21 * op.e12 + this.e22 * op.e22 + this.e23 * op.e32; + out.e23 = this.e21 * op.e13 + this.e22 * op.e23 + this.e23 * op.e33; + out.e31 = this.e31 * op.e11 + this.e32 * op.e21 + this.e33 * op.e31; + out.e32 = this.e31 * op.e12 + this.e32 * op.e22 + this.e33 * op.e32; + out.e33 = this.e31 * op.e13 + this.e32 * op.e23 + this.e33 * op.e33; + + return out; +} + +/** Matrix addition. + * + * @param op another svg matrix + * @return this + op + */ +matrixSVG.prototype.add = function(op) +{ + out = new matrixSVG(); + + out.e11 = this.e11 + op.e11; + out.e12 = this.e12 + op.e12; + out.e13 = this.e13 + op.e13; + out.e21 = this.e21 + op.e21; + out.e22 = this.e22 + op.e22; + out.e23 = this.e23 + op.e23; + out.e31 = this.e31 + op.e31; + out.e32 = this.e32 + op.e32; + out.e33 = this.e33 + op.e33; + + return out; +} + +/** Matrix mixing. + * + * @param op another svg matrix + * @parma contribOp contribution of the other matrix (0 <= contribOp <= 1) + * @return (1 - contribOp) * this + contribOp * op + */ +matrixSVG.prototype.mix = function(op, contribOp) +{ + contribThis = 1.0 - contribOp; + out = new matrixSVG(); + + out.e11 = contribThis * this.e11 + contribOp * op.e11; + out.e12 = contribThis * this.e12 + contribOp * op.e12; + out.e13 = contribThis * this.e13 + contribOp * op.e13; + out.e21 = contribThis * this.e21 + contribOp * op.e21; + out.e22 = contribThis * this.e22 + contribOp * op.e22; + out.e23 = contribThis * this.e23 + contribOp * op.e23; + out.e31 = contribThis * this.e31 + contribOp * op.e31; + out.e32 = contribThis * this.e32 + contribOp * op.e32; + out.e33 = contribThis * this.e33 + contribOp * op.e33; + + return out; +} + +/** Trimming function for strings. +*/ +String.prototype.trim = function() +{ + return this.replace(/^\s+|\s+$/g, ''); +} + +/** SVGElement.getTransformToElement polyfill */ +SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(elem) { + return elem.getScreenCTM().inverse().multiply(this.getScreenCTM()); +}; + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/jessyink_key_bindings__--slide_export__SPACE__--drawing_undo__ENTER__--index_nextPage__LEFT.out b/share/extensions/tests/data/refs/jessyink_key_bindings__--slide_export__SPACE__--drawing_undo__ENTER__--index_nextPage__LEFT.out new file mode 100644 index 0000000..4c87b34 --- /dev/null +++ b/share/extensions/tests/data/refs/jessyink_key_bindings__--slide_export__SPACE__--drawing_undo__ENTER__--index_nextPage__LEFT.out @@ -0,0 +1,61 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + +function getCustomKeyBindingsSub() +{ + var keyDict = new Object(); + keyDict[SLIDE_MODE] = new Object(); + keyDict[INDEX_MODE] = new Object(); + keyDict[DRAWING_MODE] = new Object(); + keyDict[SLIDE_MODE][SPACE_KEY] = function() { slideUpdateExportLayer(); }; + keyDict[DRAWING_MODE][ENTER_KEY] = function() { drawingUndo(); }; + keyDict[INDEX_MODE][LEFT_KEY] = function() { indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS); }; + return keyDict; +} + +function getCustomCharBindingsSub() +{ + var charDict = new Object(); + charDict[SLIDE_MODE] = new Object(); + charDict[INDEX_MODE] = new Object(); + charDict[DRAWING_MODE] = new Object(); + return charDict; +} + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/jessyink_key_bindings__--slide_export__a__--drawing_undo__b__--index_nextPage__c.out b/share/extensions/tests/data/refs/jessyink_key_bindings__--slide_export__a__--drawing_undo__b__--index_nextPage__c.out new file mode 100644 index 0000000..ed3b6bb --- /dev/null +++ b/share/extensions/tests/data/refs/jessyink_key_bindings__--slide_export__a__--drawing_undo__b__--index_nextPage__c.out @@ -0,0 +1,61 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + +function getCustomKeyBindingsSub() +{ + var keyDict = new Object(); + keyDict[SLIDE_MODE] = new Object(); + keyDict[INDEX_MODE] = new Object(); + keyDict[DRAWING_MODE] = new Object(); + return keyDict; +} + +function getCustomCharBindingsSub() +{ + var charDict = new Object(); + charDict[SLIDE_MODE] = new Object(); + charDict[INDEX_MODE] = new Object(); + charDict[DRAWING_MODE] = new Object(); + charDict[SLIDE_MODE]["a"] = function() { slideUpdateExportLayer(); }; + charDict[DRAWING_MODE]["b"] = function() { drawingUndo(); }; + charDict[INDEX_MODE]["c"] = function() { indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS); }; + return charDict; +} + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/jessyink_master_slide.out b/share/extensions/tests/data/refs/jessyink_master_slide.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/jessyink_master_slide__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/jessyink_master_slide__--id__p1__--id__r3.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/jessyink_mouse_handler__--mouseSetting__default.out b/share/extensions/tests/data/refs/jessyink_mouse_handler__--mouseSetting__default.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/jessyink_mouse_handler__--mouseSetting__draggingZoom.out b/share/extensions/tests/data/refs/jessyink_mouse_handler__--mouseSetting__draggingZoom.out new file mode 100644 index 0000000..46d82ea --- /dev/null +++ b/share/extensions/tests/data/refs/jessyink_mouse_handler__--mouseSetting__draggingZoom.out @@ -0,0 +1,475 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + +// Copyright 2008, 2009 Hannes Hochreiner +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +// Add event listener for initialisation. +document.addEventListener("DOMContentLoaded", jessyInk_core_mouseHandler_zoomControl_init, false); + +/** Initialisation function. + * + * This function looks for the objects of the appropriate sub-type and hands them to another function that will add the required methods. + */ +function jessyInk_core_mouseHandler_zoomControl_init() +{ + var elems = document.getElementsByTagNameNS("https://launchpad.net/jessyink", "mousehandler"); + + for (var counter = 0; counter < elems.length; counter++) + { + if (elems[counter].getAttributeNS("https://launchpad.net/jessyink", "subtype") == "jessyInk_core_mouseHandler_zoomControl") + jessyInk_core_mouseHandler_zoomControl(elems[counter]); + } +} + +/** Function to initialise an object. + * + * @param obj Object to be initialised. + */ +function jessyInk_core_mouseHandler_zoomControl(obj) +{ + // Last dragging position. + obj.dragging_last; + // Flag to indicate whether dragging is active currently. + obj.dragging_active = false; + // Flag to indicate whether dragging is working currently. + obj.dragging_working = false; + // Flag to indicate whether the user clicked. + obj.click = false; + + /** Function supplying a custom mouse handler. + * + * @returns A dictionary containing the new mouse handler functions. + */ + obj.getMouseHandler = function () + { + var handlerDictio = new Object(); + + handlerDictio[SLIDE_MODE] = new Object(); + handlerDictio[SLIDE_MODE][MOUSE_DOWN] = obj.mousedown; + handlerDictio[SLIDE_MODE][MOUSE_MOVE] = obj.mousemove; + handlerDictio[SLIDE_MODE][MOUSE_UP] = obj.mouseup; + handlerDictio[SLIDE_MODE][MOUSE_WHEEL] = obj.mousewheel; + + return handlerDictio; + } + + /** Event handler for mouse clicks. + * + * @param e Event object. + */ + obj.mouseclick = function (e) + { + var elem = obj.getAdHocViewBbox(slides[activeSlide]["viewGroup"], obj.getCoords(e)); + + processingEffect = true; + + effectArray = new Array(); + + effectArray[0] = new Object(); + effectArray[0]["effect"] = "view"; + effectArray[0]["dir"] = 1; + effectArray[0]["element"] = slides[activeSlide]["viewGroup"]; + effectArray[0]["options"] = new Object(); + effectArray[0]["options"]["length"] = 200; + + if (elem == null) + effectArray[0]["options"]["matrixNew"] = (new matrixSVG()).fromSVGElements(1, 0, 0, 1, 0, 0); + else + effectArray[0]["options"]["matrixNew"] = obj.pointMatrixToTransformation(obj.rectToMatrix(elem)).mult((new matrixSVG()).fromSVGMatrix(slides[activeSlide].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(elem.parentNode.getScreenCTM())).inv()); + + transCounter = 0; + startTime = (new Date()).getTime(); + lastFrameTime = null; + effect(1); + + return false; + } + + /** Function to search for the element the user clicked on. + * + * This function searches for the element with the highest z-order, which encloses the point the user clicked on + * and which view box fits entierly into the currently visible part of the slide. + * + * @param elem Element to start the search from. + * @param pnt Point where the user clicked. + * @returns The element the user clicked on or null, if no element could be found. + */ + obj.getAdHocViewBbox = function (elem, pnt) + { + var children = elem.childNodes; + + for (var counter = 0; counter < children.length; counter++) + { + if (children[counter].getBBox) + { + var childPointList = obj.projectRect(children[counter].getBBox(), children[counter].getScreenCTM()); + + var viewBbox = document.documentElement.createSVGRect(); + + viewBbox.x = 0.0; + viewBbox.y = 0.0; + viewBbox.width = WIDTH; + viewBbox.height = HEIGHT; + + var screenPointList = obj.projectRect(viewBbox, slides[activeSlide]["element"].getScreenCTM()); + + if (obj.pointsWithinRect([pnt], childPointList) && obj.pointsWithinRect(childPointList, screenPointList)) + return children[counter]; + + child = obj.getAdHocViewBbox(children[counter], pnt); + + if (child != null) + return child; + } + } + + return null; + } + + /** Function to project a rectangle using the projection matrix supplied. + * + * @param rect The rectangle to project. + * @param projectionMatrix The projection matrix. + * @returns A list of the four corners of the projected rectangle starting from the upper left corner and going counter-clockwise. + */ + obj.projectRect = function (rect, projectionMatrix) + { + var pntUL = document.documentElement.createSVGPoint(); + pntUL.x = rect.x; + pntUL.y = rect.y; + pntUL = pntUL.matrixTransform(projectionMatrix); + + var pntLL = document.documentElement.createSVGPoint(); + pntLL.x = rect.x; + pntLL.y = rect.y + rect.height; + pntLL = pntLL.matrixTransform(projectionMatrix); + + var pntUR = document.documentElement.createSVGPoint(); + pntUR.x = rect.x + rect.width; + pntUR.y = rect.y; + pntUR = pntUR.matrixTransform(projectionMatrix); + + var pntLR = document.documentElement.createSVGPoint(); + pntLR.x = rect.x + rect.width; + pntLR.y = rect.y + rect.height; + pntLR = pntLR.matrixTransform(projectionMatrix); + + return [pntUL, pntLL, pntUR, pntLR]; + } + + /** Function to determine whether all the points supplied in a list are within a rectangle. + * + * @param pnts List of points to check. + * @param pointList List of points representing the four corners of the rectangle. + * @return True, if all points are within the rectangle; false, otherwise. + */ + obj.pointsWithinRect = function (pnts, pointList) + { + var pntUL = pointList[0]; + var pntLL = pointList[1]; + var pntUR = pointList[2]; + + var matrixOrig = (new matrixSVG()).fromElements(pntUL.x, pntLL.x, pntUR.x, pntUL.y, pntLL.y, pntUR.y, 1, 1, 1); + var matrixProj = (new matrixSVG()).fromElements(0, 0, 1, 0, 1, 0, 1, 1, 1); + + var matrixProjection = matrixProj.mult(matrixOrig.inv()); + + for (var blockCounter = 0; blockCounter < Math.ceil(pnts.length / 3.0); blockCounter++) + { + var subPnts = new Array(); + + for (var pntCounter = 0; pntCounter < 3.0; pntCounter++) + { + if (blockCounter * 3.0 + pntCounter < pnts.length) + subPnts[pntCounter] = pnts[blockCounter * 3.0 + pntCounter]; + else + { + var tmpPnt = document.documentElement.createSVGPoint(); + + tmpPnt.x = 0.0; + tmpPnt.y = 0.0; + + subPnts[pntCounter] = tmpPnt; + } + } + + var matrixPnt = (new matrixSVG).fromElements(subPnts[0].x, subPnts[1].x, subPnts[2].x, subPnts[0].y, subPnts[1].y, subPnts[2].y, 1, 1, 1); + var matrixTrans = matrixProjection.mult(matrixPnt); + + for (var pntCounter = 0; pntCounter < 3.0; pntCounter++) + { + if (blockCounter * 3.0 + pntCounter < pnts.length) + { + if ((pntCounter == 0) && !((matrixTrans.e11 > 0.01) && (matrixTrans.e11 < 0.99) && (matrixTrans.e21 > 0.01) && (matrixTrans.e21 < 0.99))) + return false; + else if ((pntCounter == 1) && !((matrixTrans.e12 > 0.01) && (matrixTrans.e12 < 0.99) && (matrixTrans.e22 > 0.01) && (matrixTrans.e22 < 0.99))) + return false; + else if ((pntCounter == 2) && !((matrixTrans.e13 > 0.01) && (matrixTrans.e13 < 0.99) && (matrixTrans.e23 > 0.01) && (matrixTrans.e23 < 0.99))) + return false; + } + } + } + + return true; + } + + /** Event handler for mouse movements. + * + * @param e Event object. + */ + obj.mousemove = function (e) + { + obj.click = false; + + if (!obj.dragging_active || obj.dragging_working) + return false; + + obj.dragging_working = true; + + var p = obj.getCoords(e); + + if (slides[activeSlide].viewGroup.transform.baseVal.numberOfItems < 1) + { + var matrix = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + } + else + { + var matrix = (new matrixSVG()).fromSVGMatrix(slides[activeSlide].viewGroup.transform.baseVal.consolidate().matrix); + } + + matrix.e13 += p.x - obj.dragging_last.x; + matrix.e23 += p.y - obj.dragging_last.y; + + slides[activeSlide]["viewGroup"].setAttribute("transform", matrix.toAttribute()); + + obj.dragging_last = p; + obj.dragging_working = false; + + return false; + } + + /** Event handler for mouse down. + * + * @param e Event object. + */ + obj.mousedown = function (e) + { + if (obj.dragging_active) + return false; + + var value = 0; + + if (e.button) + value = e.button; + else if (e.which) + value = e.which; + + if (value == 1) + { + obj.dragging_last = obj.getCoords(e); + obj.dragging_active = true; + obj.click = true; + } + + return false; + } + + /** Event handler for mouse up. + * + * @param e Event object. + */ + obj.mouseup = function (e) + { + obj.dragging_active = false; + + if (obj.click) + return obj.mouseclick(e); + else + return false; + } + + /** Function to get the coordinates of a point corrected for the offset of the viewport. + * + * @param e Point. + * @returns Coordinates of the point corrected for the offset of the viewport. + */ + obj.getCoords = function (e) + { + var svgPoint = document.documentElement.createSVGPoint(); + svgPoint.x = e.clientX + window.pageXOffset; + svgPoint.y = e.clientY + window.pageYOffset; + + return svgPoint; + } + + /** Event handler for scrolling. + * + * @param e Event object. + */ + obj.mousewheel = function(e) + { + var p = obj.projectCoords(obj.getCoords(e)); + + if (slides[activeSlide].viewGroup.transform.baseVal.numberOfItems < 1) + { + var matrix = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + } + else + { + var matrix = (new matrixSVG()).fromSVGMatrix(slides[activeSlide].viewGroup.transform.baseVal.consolidate().matrix); + } + + if (e.wheelDelta) + { // IE Opera + delta = e.wheelDelta/120; + } + else if (e.detail) + { // MOZ + delta = -e.detail/3; + } + + var widthOld = p.x * matrix.e11 + p.y * matrix.e12; + var heightOld = p.x * matrix.e21 + p.y * matrix.e22; + + matrix.e11 *= (1.0 - delta / 20.0); + matrix.e12 *= (1.0 - delta / 20.0); + matrix.e21 *= (1.0 - delta / 20.0); + matrix.e22 *= (1.0 - delta / 20.0); + + var widthNew = p.x * matrix.e11 + p.y * matrix.e12; + var heightNew = p.x * matrix.e21 + p.y * matrix.e22; + + matrix.e13 += (widthOld - widthNew); + matrix.e23 += (heightOld - heightNew); + + slides[activeSlide]["viewGroup"].setAttribute("transform", matrix.toAttribute()); + + return false; + } + + /** Function to project a point to screen coordinates. + * + * @param Point. + * @returns The point projected to screen coordinates. + */ + obj.projectCoords = function(pnt) + { + var matrix = slides[activeSlide]["element"].getScreenCTM(); + + if (slides[activeSlide]["viewGroup"]) + matrix = slides[activeSlide]["viewGroup"].getScreenCTM(); + + pnt = pnt.matrixTransform(matrix.inverse()); + return pnt; + } + + /** Function to convert a rectangle into a point matrix. + * + * The function figures out a rectangle that encloses the rectangle given and has the same width/height ratio as the viewport of the presentation. + * + * @param rect Rectangle. + * @return The upper left, upper right and lower right corner of the rectangle in a point matrix. + */ + obj.rectToMatrix = function(rect) + { + rectWidth = rect.getBBox().width; + rectHeight = rect.getBBox().height; + rectX = rect.getBBox().x; + rectY = rect.getBBox().y; + rectXcorr = 0; + rectYcorr = 0; + + scaleX = WIDTH / rectWidth; + scaleY = HEIGHT / rectHeight; + + if (scaleX > scaleY) + { + scaleX = scaleY; + rectXcorr -= (WIDTH / scaleX - rectWidth) / 2; + rectWidth = WIDTH / scaleX; + } + else + { + scaleY = scaleX; + rectYcorr -= (HEIGHT / scaleY - rectHeight) / 2; + rectHeight = HEIGHT / scaleY; + } + + if (rect.transform.baseVal.numberOfItems < 1) + { + mRectTrans = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1); + } + else + { + mRectTrans = (new matrixSVG()).fromSVGMatrix(rect.transform.baseVal.consolidate().matrix); + } + + newBasePoints = (new matrixSVG()).fromElements(rectX, rectX, rectX, rectY, rectY, rectY, 1, 1, 1); + newVectors = (new matrixSVG()).fromElements(rectXcorr, rectXcorr + rectWidth, rectXcorr + rectWidth, rectYcorr, rectYcorr, rectYcorr + rectHeight, 0, 0, 0); + + return mRectTrans.mult(newBasePoints.add(newVectors)); + } + + /** Function to return a transformation matrix from a point matrix. + * + * @param mPoints The point matrix. + * @returns The transformation matrix. + */ + obj.pointMatrixToTransformation = function(mPoints) + { + mPointsOld = (new matrixSVG()).fromElements(0, WIDTH, WIDTH, 0, 0, HEIGHT, 1, 1, 1); + + return mPointsOld.mult(mPoints.inv()); + } +} + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/jessyink_mouse_handler__--mouseSetting__noclick.out b/share/extensions/tests/data/refs/jessyink_mouse_handler__--mouseSetting__noclick.out new file mode 100644 index 0000000..c21263c --- /dev/null +++ b/share/extensions/tests/data/refs/jessyink_mouse_handler__--mouseSetting__noclick.out @@ -0,0 +1,94 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + +// Copyright 2008, 2009 Hannes Hochreiner +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +// Add event listener for initialisation. +document.addEventListener("DOMContentLoaded", jessyInk_core_mouseHandler_noclick_init, false); + +/** Initialisation function. + * + * This function looks for the objects of the appropriate sub-type and hands them to another function that will add the required methods. + */ +function jessyInk_core_mouseHandler_noclick_init() +{ + var elems = document.getElementsByTagNameNS("https://launchpad.net/jessyink", "mousehandler"); + + for (var counter = 0; counter < elems.length; counter++) + { + if (elems[counter].getAttributeNS("https://launchpad.net/jessyink", "subtype") == "jessyInk_core_mouseHandler_noclick") + jessyInk_core_mouseHandler_noclick(elems[counter]); + } +} + +/** Function to initialise an object. + * + * @param obj Object to be initialised. + */ +function jessyInk_core_mouseHandler_noclick(obj) +{ + /** Function supplying a custom mouse handler. + * + * @returns A dictionary containing the new mouse handler functions. + */ + obj.getMouseHandler = function () + { + var handlerDictio = new Object(); + + handlerDictio[SLIDE_MODE] = new Object(); + handlerDictio[SLIDE_MODE][MOUSE_DOWN] = null; + + return handlerDictio; + } +} + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/jessyink_summary.out b/share/extensions/tests/data/refs/jessyink_summary.out new file mode 100644 index 0000000..0c30d07 --- /dev/null +++ b/share/extensions/tests/data/refs/jessyink_summary.out @@ -0,0 +1,10 @@ +JessyInk script version 1.5.5 installed. + +Slide 1: + Layer name: Slide3 + +Slide 2: + Layer name: Slide2 + +Slide 3: + Layer name: Slide1 diff --git a/share/extensions/tests/data/refs/jessyink_transitions__--layerName__Slide2.out b/share/extensions/tests/data/refs/jessyink_transitions__--layerName__Slide2.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/jessyink_uninstall.out b/share/extensions/tests/data/refs/jessyink_uninstall.out new file mode 100644 index 0000000..f3d83ab --- /dev/null +++ b/share/extensions/tests/data/refs/jessyink_uninstall.out @@ -0,0 +1,39 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/jessyink_video.out b/share/extensions/tests/data/refs/jessyink_video.out new file mode 100644 index 0000000..664de1f --- /dev/null +++ b/share/extensions/tests/data/refs/jessyink_video.out @@ -0,0 +1,61 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + <replace this text with the url of the movie> + + + + + + + JessyInk video element + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/jessyink_view__--id__r3__--viewOrder__1.out b/share/extensions/tests/data/refs/jessyink_view__--id__r3__--viewOrder__1.out new file mode 100644 index 0000000..dd07df6 --- /dev/null +++ b/share/extensions/tests/data/refs/jessyink_view__--id__r3__--viewOrder__1.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__gaussian__--end__false.out b/share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__gaussian__--end__false.out new file mode 100644 index 0000000..523c51f --- /dev/null +++ b/share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__gaussian__--end__false.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__lognorm__--radiusx__100.out b/share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__lognorm__--radiusx__100.out new file mode 100644 index 0000000..6aca453 --- /dev/null +++ b/share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__lognorm__--radiusx__100.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__pareto__--radiusy__100.out b/share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__pareto__--radiusy__100.out new file mode 100644 index 0000000..8c38359 --- /dev/null +++ b/share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__pareto__--radiusy__100.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__uniform__--ctrl__false.out b/share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__uniform__--ctrl__false.out new file mode 100644 index 0000000..f6425d2 --- /dev/null +++ b/share/extensions/tests/data/refs/jitternodes__--id__p1__--dist__uniform__--ctrl__false.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/layer2png.out b/share/extensions/tests/data/refs/layer2png.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/layer2png__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/layer2png__--id__p1__--id__r3.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/layers2svgfont.out b/share/extensions/tests/data/refs/layers2svgfont.out new file mode 100644 index 0000000..dcfd9f4 --- /dev/null +++ b/share/extensions/tests/data/refs/layers2svgfont.out @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/lindenmayer.out b/share/extensions/tests/data/refs/lindenmayer.out new file mode 100644 index 0000000..8c2ec7d --- /dev/null +++ b/share/extensions/tests/data/refs/lindenmayer.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/lindenmayer__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/lindenmayer__--id__p1__--id__r3.out new file mode 100644 index 0000000..8c2ec7d --- /dev/null +++ b/share/extensions/tests/data/refs/lindenmayer__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/lorem_ipsum.out b/share/extensions/tests/data/refs/lorem_ipsum.out new file mode 100644 index 0000000..c4cccd8 --- /dev/null +++ b/share/extensions/tests/data/refs/lorem_ipsum.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nam molestie nisl at metus. Mauris ac massa vestibulum nisl facilisis viverra. Aliquam justo lectus, iaculis a, auctor sed, congue in, nisl. Sed quis elit. Mauris sed nulla quis nisi interdum tempor. Proin dolor sapien, adipiscing id, sagittis eu, molestie viverra, mauris. Integer tempus malesuada pede. Maecenas rhoncus rhoncus ipsum. Mauris ac massa vestibulum nisl facilisis viverra. Suspendisse fermentum. Phasellus magna sem, vulputate eget, ornare sed, dignissim sit amet, pede. Nam id neque. Nulla blandit justo a metus. Nam laoreet dui sed magna. Vivamus posuere, ante eu tempor dictum, felis nibh facilisis sem, eu auctor metus nulla non lorem. Phasellus at purus sed purus cursus iaculis. Vivamus feugiat. Integer fringilla. Praesent a eros. Aliquam sed erat. Nam a nunc. Maecenas rhoncus rhoncus ipsum. Praesent scelerisque. Proin lectus orci, venenatis pharetra, egestas id, tincidunt vel, eros. Etiam cursus purus interdum libero. Nam massa turpis, nonummy et, consectetuer id, placerat ac, ante. Suspendisse fermentum. Sed at turpis vitae velit euismod aliquet. Aliquam justo lectus, iaculis a, auctor sed, congue in, nisl. Nam massa turpis, nonummy et, consectetuer id, placerat ac, ante. Quisque arcu ante, cursus in, ornare quis, viverra ut, justo. Nam a nunc. Duis sem velit, ultrices et, fermentum auctor, rhoncus ut, ligula. Aliquam metus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse potenti. Mauris tincidunt aliquam ante. Mauris et pede. In consectetuer, lorem eu lobortis egestas, velit odio imperdiet eros, sit amet sagittis nunc mi ac neque. Curabitur risus urna, placerat et, luctus pulvinar, auctor vel, orci. Donec ut purus. Integer accumsan. Morbi volutpat. Suspendisse potenti. Vivamus posuere, ante eu tempor dictum, felis nibh facilisis sem, eu auctor metus nulla non lorem. Aenean luctus vulputate turpis. Pellentesque convallis dolor vel libero. Quisque arcu ante, cursus in, ornare quis, viverra ut, justo. Etiam non neque ac mi vestibulum placerat. Mauris urna sem, suscipit vitae, dignissim id, ultrices sed, nunc. Phasellus at purus sed purus cursus iaculis. Phasellus lacinia iaculis mi. Mauris tempor ultrices justo. Nam massa turpis, nonummy et, consectetuer id, placerat ac, ante. Nam id neque. Fusce venenatis ligula in pede. Nullam libero nunc, tristique eget, laoreet eu, sagittis id, ante. Suspendisse lectus. Phasellus nisi metus, tempus sit amet, ultrices ac, porta nec, felis. Pellentesque suscipit accumsan massa. Aliquam metus. Cras ac enim vel dui vestibulum suscipit. In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus eu orci. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse potenti. Integer accumsan. Nam id neque. Donec at diam a tellus dignissim vestibulum. Donec ut urna. Pellentesque ac turpis. Phasellus hendrerit. Nulla sed lacus. Aenean justo ipsum, luctus ut, volutpat laoreet, vehicula in, libero. Morbi turpis arcu, egestas congue, condimentum quis, tristique cursus, leo. Nam pharetra. Nulla sagittis condimentum ligula. Nam malesuada sapien eu nibh. Sed a lorem ut est tincidunt consectetuer. Sed dolor. Donec ut purus. Phasellus hendrerit. Mauris et pede. Donec diam eros, tristique sit amet, pretium vel, pellentesque ut, neque. Aenean turpis ipsum, rhoncus vitae, posuere vitae, euismod sed, ligula. Nulla sagittis condimentum ligula. Aliquam imperdiet lobortis metus. Integer accumsan. Donec interdum vestibulum libero. Curabitur risus urna, placerat et, luctus pulvinar, auctor vel, orci. Donec interdum vestibulum libero. Nam molestie nisl at metus. In consectetuer, lorem eu lobortis egestas, velit odio imperdiet eros, sit amet sagittis nunc mi ac neque. Phasellus magna sem, vulputate eget, ornare sed, dignissim sit amet, pede. Aliquam imperdiet lobortis metus. Nam consectetuer mollis dolor. Curabitur lorem risus, sagittis vitae, accumsan a, iaculis id, metus. Integer tempus malesuada pede. Nam laoreet dui sed magna. Donec sit amet enim. Mauris et dolor. Ut eu metus id lectus vestibulum ultrices. Quisque aliquam, nulla ac scelerisque convallis, nisi ligula sagittis risus, at nonummy arcu urna pulvinar nibh. Pellentesque sit amet dui vel justo gravida auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Fusce venenatis ligula in pede. Suspendisse potenti. Quisque vehicula porttitor odio. Praesent a lacus vitae turpis consequat semper. Praesent scelerisque. Pellentesque viverra dolor non nunc. Aliquam justo lectus, iaculis a, auctor sed, congue in, nisl. Mauris sed nulla quis nisi interdum tempor. Aliquam velit dui, commodo quis, porttitor eget, convallis et, nisi. Donec ut urna. Morbi turpis arcu, egestas congue, condimentum quis, tristique cursus, leo. \ No newline at end of file diff --git a/share/extensions/tests/data/refs/markers_strokepaint__--tab____custom____--id__dimension.out b/share/extensions/tests/data/refs/markers_strokepaint__--tab____custom____--id__dimension.out new file mode 100644 index 0000000..0a85c09 --- /dev/null +++ b/share/extensions/tests/data/refs/markers_strokepaint__--tab____custom____--id__dimension.out @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/markers_strokepaint__--tab____object____--id__dimension.out b/share/extensions/tests/data/refs/markers_strokepaint__--tab____object____--id__dimension.out new file mode 100644 index 0000000..070a6e8 --- /dev/null +++ b/share/extensions/tests/data/refs/markers_strokepaint__--tab____object____--id__dimension.out @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/measure__--id__p1__--id__p2.out b/share/extensions/tests/data/refs/measure__--id__p1__--id__p2.out new file mode 100644 index 0000000..179cbf8 --- /dev/null +++ b/share/extensions/tests/data/refs/measure__--id__p1__--id__p2.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + 123.48 mm141.14 mm + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__FT_bbox__--id__p2.out b/share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__FT_bbox__--id__p2.out new file mode 100644 index 0000000..ec3aa85 --- /dev/null +++ b/share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__FT_bbox__--id__p2.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + 141.14 mm + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__FT_start__--id__p1.out b/share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__FT_start__--id__p1.out new file mode 100644 index 0000000..7b61804 --- /dev/null +++ b/share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__FT_start__--id__p1.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + 123.48 mm + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__TaP_end__--id__p2.out b/share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__TaP_end__--id__p2.out new file mode 100644 index 0000000..6ef1b7c --- /dev/null +++ b/share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__TaP_end__--id__p2.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + 141.14 mm + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__TaP_start__--id__p1.out b/share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__TaP_start__--id__p1.out new file mode 100644 index 0000000..da1b409 --- /dev/null +++ b/share/extensions/tests/data/refs/measure__--method__presets__--presetFormat__TaP_start__--id__p1.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + 123.48 mm + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/measure__--type__area__--id__p1.out b/share/extensions/tests/data/refs/measure__--type__area__--id__p1.out new file mode 100644 index 0000000..65d9a94 --- /dev/null +++ b/share/extensions/tests/data/refs/measure__--type__area__--id__p1.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + 0.0 mm2 + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + diff --git a/share/extensions/tests/data/refs/measure__--type__cofm__--id__c3.out b/share/extensions/tests/data/refs/measure__--type__cofm__--id__c3.out new file mode 100644 index 0000000..9deca59 --- /dev/null +++ b/share/extensions/tests/data/refs/measure__--type__cofm__--id__c3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/media_zip.out b/share/extensions/tests/data/refs/media_zip.out new file mode 100644 index 0000000..03215c1 Binary files /dev/null and b/share/extensions/tests/data/refs/media_zip.out differ diff --git a/share/extensions/tests/data/refs/merge_styles__--id__c2__--id__c3.out b/share/extensions/tests/data/refs/merge_styles__--id__c2__--id__c3.out new file mode 100644 index 0000000..13c7bbc --- /dev/null +++ b/share/extensions/tests/data/refs/merge_styles__--id__c2__--id__c3.out @@ -0,0 +1,46 @@ + + + + + + +.css5815 { + display:inline; + stroke-width:16; +} + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/motion__--id__c3__--id__p2.out b/share/extensions/tests/data/refs/motion__--id__c3__--id__p2.out new file mode 100644 index 0000000..aff7cc7 --- /dev/null +++ b/share/extensions/tests/data/refs/motion__--id__c3__--id__p2.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/new_glyph_layer.out b/share/extensions/tests/data/refs/new_glyph_layer.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/new_glyph_layer__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/new_glyph_layer__--id__p1__--id__r3.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/next_glyph_layer.out b/share/extensions/tests/data/refs/next_glyph_layer.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/next_glyph_layer__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/next_glyph_layer__--id__p1__--id__r3.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/nicechart.out b/share/extensions/tests/data/refs/nicechart.out new file mode 100644 index 0000000..ecf7516 --- /dev/null +++ b/share/extensions/tests/data/refs/nicechart.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv.out b/share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv.out new file mode 100644 index 0000000..3374868 --- /dev/null +++ b/share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv.out @@ -0,0 +1,13 @@ + + + + + + image/svg+xml + + + + + + +JanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctoberNovemberDecember1978 \ No newline at end of file diff --git a/share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv__--type__pie.out b/share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv__--type__pie.out new file mode 100644 index 0000000..5510547 --- /dev/null +++ b/share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv__--type__pie.out @@ -0,0 +1,13 @@ + + + + + + image/svg+xml + + + + + + +JanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctoberNovemberDecember1978 \ No newline at end of file diff --git a/share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv__--type__pie_abs.out b/share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv__--type__pie_abs.out new file mode 100644 index 0000000..541c4bb --- /dev/null +++ b/share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv__--type__pie_abs.out @@ -0,0 +1,13 @@ + + + + + + image/svg+xml + + + + + + +JanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctoberNovemberDecember1978 \ No newline at end of file diff --git a/share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv__--type__stbar.out b/share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv__--type__stbar.out new file mode 100644 index 0000000..d7fe426 --- /dev/null +++ b/share/extensions/tests/data/refs/nicechart__--file__DAT_DIR__io__nicechart_01__csv__--type__stbar.out @@ -0,0 +1,13 @@ + + + + + + image/svg+xml + + + + + + +JanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctoberNovemberDecember1978 \ No newline at end of file diff --git a/share/extensions/tests/data/refs/nicechart__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/nicechart__--id__p1__--id__r3.out new file mode 100644 index 0000000..5e7b9f7 --- /dev/null +++ b/share/extensions/tests/data/refs/nicechart__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/output_scour.out b/share/extensions/tests/data/refs/output_scour.out new file mode 100644 index 0000000..6b54e20 --- /dev/null +++ b/share/extensions/tests/data/refs/output_scour.out @@ -0,0 +1,40 @@ + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps + UPPER + Multi linetextFOO + + Grouped + text + + + + + + + diff --git a/share/extensions/tests/data/refs/param_curves.out b/share/extensions/tests/data/refs/param_curves.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/param_curves__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/param_curves__--id__p1__--id__r3.out new file mode 100644 index 0000000..c5acfe7 --- /dev/null +++ b/share/extensions/tests/data/refs/param_curves__--id__p1__--id__r3.out @@ -0,0 +1,40 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/path_envelope__--id__obj__--id__envelope.out b/share/extensions/tests/data/refs/path_envelope__--id__obj__--id__envelope.out new file mode 100644 index 0000000..37c35bc --- /dev/null +++ b/share/extensions/tests/data/refs/path_envelope__--id__obj__--id__envelope.out @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/path_envelope__--id__text__--id__envelope.out b/share/extensions/tests/data/refs/path_envelope__--id__text__--id__envelope.out new file mode 100644 index 0000000..d82aee7 --- /dev/null +++ b/share/extensions/tests/data/refs/path_envelope__--id__text__--id__envelope.out @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__faces.out b/share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__faces.out new file mode 100644 index 0000000..ea1b992 --- /dev/null +++ b/share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__faces.out @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__gridlines.out b/share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__gridlines.out new file mode 100644 index 0000000..3500bba --- /dev/null +++ b/share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__gridlines.out @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__meshpatches.out b/share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__meshpatches.out new file mode 100644 index 0000000..da2a30b --- /dev/null +++ b/share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__meshpatches.out @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__outline.out b/share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__outline.out new file mode 100644 index 0000000..1a27ecb --- /dev/null +++ b/share/extensions/tests/data/refs/path_mesh_m2p__--id__mesh1__--mode__outline.out @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/path_mesh_p2m__--id__path1__--id__path9.out b/share/extensions/tests/data/refs/path_mesh_p2m__--id__path1__--id__path9.out new file mode 100644 index 0000000..0742961 --- /dev/null +++ b/share/extensions/tests/data/refs/path_mesh_p2m__--id__path1__--id__path9.out @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/path_number_nodes__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/path_number_nodes__--id__p1__--id__r3.out new file mode 100644 index 0000000..c3b1c5d --- /dev/null +++ b/share/extensions/tests/data/refs/path_number_nodes__--id__p1__--id__r3.out @@ -0,0 +1,40 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + 1234 + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/path_to_absolute__--id__c1__--id__c2__--id__c3.out b/share/extensions/tests/data/refs/path_to_absolute__--id__c1__--id__c2__--id__c3.out new file mode 100644 index 0000000..e904237 --- /dev/null +++ b/share/extensions/tests/data/refs/path_to_absolute__--id__c1__--id__c2__--id__c3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/path_to_absolute__--id__p1__--id__p2__--id__s1__--id__u1.out b/share/extensions/tests/data/refs/path_to_absolute__--id__p1__--id__p2__--id__s1__--id__u1.out new file mode 100644 index 0000000..8fbffb2 --- /dev/null +++ b/share/extensions/tests/data/refs/path_to_absolute__--id__p1__--id__p2__--id__s1__--id__u1.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/path_to_absolute__--id__r1__--id__r2__--id__r3__--id__slicerect.out b/share/extensions/tests/data/refs/path_to_absolute__--id__r1__--id__r2__--id__r3__--id__slicerect.out new file mode 100644 index 0000000..33edebd --- /dev/null +++ b/share/extensions/tests/data/refs/path_to_absolute__--id__r1__--id__r2__--id__r3__--id__slicerect.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/path_to_absolute__--id__r1__--id__r2__--id__r3__--id__slicerect1.out b/share/extensions/tests/data/refs/path_to_absolute__--id__r1__--id__r2__--id__r3__--id__slicerect1.out new file mode 100644 index 0000000..72e29de --- /dev/null +++ b/share/extensions/tests/data/refs/path_to_absolute__--id__r1__--id__r2__--id__r3__--id__slicerect1.out @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/pathalongpath__--copymode__Single__--id__p1__--id__p2.out b/share/extensions/tests/data/refs/pathalongpath__--copymode__Single__--id__p1__--id__p2.out new file mode 100644 index 0000000..c727c6f --- /dev/null +++ b/share/extensions/tests/data/refs/pathalongpath__--copymode__Single__--id__p1__--id__p2.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/pathscatter__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/pathscatter__--id__p1__--id__r3.out new file mode 100644 index 0000000..0b4f8e2 --- /dev/null +++ b/share/extensions/tests/data/refs/pathscatter__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/pdflatex__cceb2358b6829feda6d763508a98eaf1.out b/share/extensions/tests/data/refs/pdflatex__cceb2358b6829feda6d763508a98eaf1.out new file mode 100644 index 0000000..f8de97b --- /dev/null +++ b/share/extensions/tests/data/refs/pdflatex__cceb2358b6829feda6d763508a98eaf1.out @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/perfectboundcover.out b/share/extensions/tests/data/refs/perfectboundcover.out new file mode 100644 index 0000000..df88e21 --- /dev/null +++ b/share/extensions/tests/data/refs/perfectboundcover.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/perspective__--id__obj__--id__envelope.out b/share/extensions/tests/data/refs/perspective__--id__obj__--id__envelope.out new file mode 100644 index 0000000..df50e03 --- /dev/null +++ b/share/extensions/tests/data/refs/perspective__--id__obj__--id__envelope.out @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/perspective__--id__p1__--id__p2.out b/share/extensions/tests/data/refs/perspective__--id__p1__--id__p2.out new file mode 100644 index 0000000..b7497dc --- /dev/null +++ b/share/extensions/tests/data/refs/perspective__--id__p1__--id__p2.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/perspective__--id__text__--id__envelope.out b/share/extensions/tests/data/refs/perspective__--id__text__--id__envelope.out new file mode 100644 index 0000000..5093cdd --- /dev/null +++ b/share/extensions/tests/data/refs/perspective__--id__text__--id__envelope.out @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/pixelsnap__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/pixelsnap__--id__p1__--id__r3.out new file mode 100644 index 0000000..ad90237 --- /dev/null +++ b/share/extensions/tests/data/refs/pixelsnap__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/plotter__--serialPort____test__.out b/share/extensions/tests/data/refs/plotter__--serialPort____test__.out new file mode 100644 index 0000000..e5b1337 --- /dev/null +++ b/share/extensions/tests/data/refs/plotter__--serialPort____test__.out @@ -0,0 +1,35 @@ +IN +FS24 +VS20 +PU +SP1 +PU0,0 +PD0,90 +PU5966,2652 +PD5971,2651,5975,2647,5976,2641,5975,2636,5971,2633,5966,2632,5966,0,5961,1,5957,5,5956,10,3824,10,3825,15,3829,19,3834,20,3834,2652,3839,2651,3843,2647,3844,2642,5976,2642,5975,2637,5971,2633,5966,2632,5966,2592 +PU5479,7938 +PD5474,7939,5470,7943,5469,7948,5470,7953,5474,7957,5478,7957,5477,7997,5476,7997,5472,8037,5471,8036,5464,8075,5463,8075,5453,8112,5452,8112,5439,8149,5439,8148,5423,8184,5405,8218,5404,8217,5384,8250,5361,8281,5361,8280,5336,8310,5335,8309,5308,8337,5308,8336,5279,8362,5248,8385,5247,8385,5214,8406,5214,8405,5180,8425,5179,8424,5143,8441,5143,8440,5105,8454,5105,8453,5067,8464,5067,8463,5029,8471,5028,8470,4990,8476,4990,8475,4951,8477,4951,8476,4913,8476,4913,8475,4874,8472,4874,8471,4836,8465,4837,8464,4799,8455,4763,8443,4763,8442,4727,8428,4727,8427,4692,8411,4693,8410,4659,8390,4660,8390,4627,8368,4628,8367,4597,8343,4597,8342,4568,8315,4569,8314,4541,8285,4542,8285,4517,8254,4518,8253,4496,8221,4497,8221,4477,8187,4478,8187,4461,8152,4461,8151,4447,8116,4448,8115,4436,8079,4437,8078,4428,8041,4429,8041,4423,8003,4420,7964,4421,7964,4420,7926,4421,7926,4423,7887,4424,7887,4429,7849,4430,7849,4438,7811,4439,7811,4450,7773,4464,7736,4465,7736,4481,7700,4482,7700,4501,7665,4502,7666,4523,7633,4524,7633,4548,7603,4574,7574,4574,7575,4602,7548,4603,7548,4632,7524,4664,7502,4697,7482,4697,7483,4731,7465,4732,7466,4767,7450,4767,7451,4804,7438,4804,7439,4842,7429,4842,7430,4880,7422,4880,7423,4920,7419,4959,7418,4959,7419,5014,7422,5013,7423,5066,7431,5066,7432,5117,7445,5116,7446,5165,7463,5164,7464,5211,7487,5210,7487,5254,7514,5253,7515,5294,7545,5293,7546,5331,7580,5330,7581,5365,7618,5364,7619,5394,7660,5393,7660,5420,7704,5419,7704,5441,7751,5458,7800,5457,7800,5470,7851,5469,7851,5477,7903,5476,7903,5479,7957,5478,7957,5477,7997,5476,7997 +PU5478,5312 +PD5478,5312,5477,5391,5472,5470,5465,5547,5464,5547,5454,5622,5441,5695,5425,5766,5407,5834,5387,5899,5386,5899,5364,5961,5363,5961,5338,6020,5311,6075,5310,6075,5282,6126,5281,6126,5250,6173,5217,6216,5216,6215,5182,6254,5181,6253,5145,6286,5144,6285,5107,6313,5106,6312,5068,6334,5067,6333,5029,6349,5029,6348,4990,6358,4990,6356,4951,6361,4951,6359,4913,6358,4913,6357,4874,6350,4875,6349,4837,6337,4837,6335,4800,6318,4800,6317,4764,6293,4728,6264,4729,6263,4694,6230,4695,6229,4661,6190,4662,6190,4630,6145,4599,6096,4600,6095,4571,6041,4544,5982,4520,5920,4498,5856,4499,5855,4479,5788,4480,5788,4463,5718,4449,5647,4449,5646,4438,5573,4429,5498,4430,5498,4424,5422,4421,5346,4420,5268,4421,5268,4423,5191,4428,5114,4429,5114,4437,5037,4448,4962,4449,4962,4462,4887,4463,4887,4479,4814,4480,4815,4499,4745,4499,4746,4521,4680,4545,4618,4546,4619,4571,4561,4572,4561,4599,4507,4600,4508,4629,4458,4630,4458,4661,4413,4662,4414,4695,4374,4729,4339,4730,4339,4765,4309,4766,4310,4803,4284,4803,4285,4841,4265,4841,4266,4880,4252,4880,4253,4919,4244,4920,4246,4959,4243,4959,4244,4987,4245,4987,4246,5013,4250,5013,4251,5040,4258,5040,4259,5066,4268,5065,4269,5091,4281,5090,4282,5116,4296,5115,4297,5140,4313,5139,4314,5163,4333,5186,4354,5186,4355,5209,4378,5208,4379,5230,4404,5252,4432,5251,4432,5292,4493,5291,4494,5328,4562,5362,4638,5361,4638,5392,4719,5391,4720,5418,4807,5417,4807,5439,4899,5439,4900,5457,4997,5456,4997,5469,5098,5477,5203,5476,5203,5479,5312,5478,5312,5478,5352 +PU4655,1257 +PD4650,1256,4649,1256,4615,1306,4615,1307,4583,1362,4584,1362,4554,1422,4555,1422,4528,1486,4528,1487,4504,1554,4505,1554,4483,1625,4484,1625,4465,1699,4466,1699,4450,1775,4451,1775,4438,1854,4429,1934,4423,2016,4420,2099,4421,2099,4421,2182,4425,2266,4432,2350,4443,2433,4457,2514,4474,2592,4494,2667,4495,2667,4517,2737,4542,2803,4543,2803,4570,2865,4571,2865,4600,2922,4601,2922,4632,2975,4633,2974,4667,3022,4667,3021,4703,3064,4703,3063,4741,3100,4741,3099,4780,3130,4780,3129,4820,3154,4821,3153,4862,3171,4862,3170,4904,3182,4905,3181,4948,3186,4948,3184,4991,3183,4990,3181,5033,3173,5032,3171,5074,3156,5073,3155,5114,3134,5113,3133,5152,3105,5151,3104,5189,3071,5188,3070,5225,3031,5224,3031,5259,2986,5258,2986,5291,2936,5290,2936,5321,2881,5349,2821,5375,2757,5375,2756,5398,2688,5419,2615,5437,2539,5437,2538,5452,2458,5464,2376,5472,2293,5477,2209,5479,2126,5477,2043,5472,1961,5464,1880,5453,1800,5439,1723,5438,1723,5422,1647,5421,1648,5401,1575,5378,1505,5378,1506,5352,1439,5352,1440,5324,1377,5323,1378,5292,1319,5292,1320,5258,1266,5254,1270,5253,1275,5255,1280,5259,1284,5260,1284,4946,2136,4942,2133,4940,2128,4940,2123,4944,2119,4946,2117,4652,1238,4647,1241,4645,1246,4646,1251,4649,1255,4649,1256,4627,1289 +PU3356,8485 +PD3353,8481,3352,8476,3354,8471,3355,8470,2297,7411,2301,7409,2307,7409,2311,7411,3369,6353,3364,6350,3359,6350,3355,6353,2297,5295 +PU3355,4236 +PD3352,4241,3352,4243,3307,4241,3307,4240,3254,4235,3187,4225,3107,4209,3107,4208,3017,4185,3018,4184,2970,4169,2971,4169,2922,4151,2873,4130,2824,4107,2824,4106,2774,4080,2775,4079,2726,4050,2726,4049,2678,4016,2631,3979,2631,3978,2585,3938,2586,3937,2542,3892,2543,3892,2501,3843,2502,3842,2463,3789,2464,3788,2428,3730,2429,3730,2397,3667,2398,3666,2369,3598,2370,3598,2346,3525,2347,3525,2327,3446,2328,3446,2314,3361,2315,3361,2306,3271,2303,3175,2304,3175,2307,3082,2308,3082,2317,3000,2324,2964,2332,2930,2333,2930,2342,2899,2352,2870,2353,2870,2364,2843,2365,2843,2378,2819,2392,2797,2393,2797,2407,2777,2408,2777,2423,2759,2424,2760,2440,2743,2441,2744,2458,2729,2459,2729,2477,2716,2478,2717,2516,2695,2517,2696,2558,2680,2559,2681,2603,2669,2603,2670,2648,2662,2648,2663,2696,2658,2696,2659,2744,2656,2744,2657,2843,2656,2942,2655,2942,2654,2990,2652,2990,2651,3037,2647,3037,2646,3083,2638,3082,2637,3126,2625,3126,2624,3167,2608,3167,2607,3205,2585,3205,2584,3223,2571,3222,2570,3240,2555,3239,2555,3256,2538,3255,2537,3271,2519,3270,2519,3285,2498,3284,2498,3298,2475,3297,2475,3310,2450,3309,2450,3320,2423,3330,2394,3339,2362,3338,2362,3346,2328,3345,2328,3352,2291,3351,2291,3360,2210,3359,2210,3362,2117,3359,2021,3358,2021,3349,1930,3348,1931,3334,1846,3333,1846,3314,1768,3313,1768,3289,1695,3261,1627,3260,1627,3228,1564,3227,1564,3191,1506,3191,1507,3152,1453,3151,1454,3110,1405,3066,1360,3065,1361,3020,1320,3019,1321,2972,1284,2923,1251,2923,1252,2874,1222,2824,1196,2774,1173,2774,1174,2725,1153,2725,1154,2676,1136,2629,1121,2629,1122,2539,1098,2539,1099,2459,1083,2391,1074,2339,1069,2339,1070,2294,1068 +PU292,6943 +PD293,6949,297,6952,302,6954,304,6954,390,7434,384,7433,381,7431,34,7774,39,7776,44,7776,49,7773,51,7769,51,7768,534,7834,532,7839,529,7842,748,8277,752,8274,754,8269,752,8264,749,8260,748,8260,960,7821,964,7824,966,7828,1448,7754,1446,7750,1441,7746,1436,7746,1431,7749,1079,7411,1084,7409,1088,7408,1167,6927,1161,6928,1157,6931,1155,6936,1156,6941,1156,6942,726,7172,725,7167,726,7163,293,6939,292,6944,294,6950,298,6953,303,6954,304,6954,311,6993 +PU272,4837 +PD272,4837,358,5317,353,5316,349,5314,3,5657,7,5660,13,5660,17,5657,20,5652,20,5651,503,5718,501,5723,497,5725,716,6161,720,6157,722,6152,721,6147,717,6143,716,6143,929,5704,933,5708,934,5712,1416,5638,1414,5633,1409,5630,1404,5629,1399,5632,1047,5295,1052,5292,1056,5292,1135,4811,1130,4811,1125,4814,1123,4819,1124,4825,695,5055,693,5050,695,5046,261,4823,260,4828,262,4833,266,4836,271,4837,272,4837,279,4876 +PU8793,5312 +PD8798,5309,8801,5305,8801,5299,8798,5295,8794,5292,8791,5292,8791,4551,8786,4552,8782,4556,8781,4561,8047,4561,8048,4566,8052,4570,8057,4571,8057,5312,8062,5310,8066,5306,8067,5302,8801,5302,8800,5297,8796,5293,8791,5292,8791,5252 +PU7595,8467 +PD7595,8467,7595,7408,7590,7410,7587,7414,7585,7418,6527,7418,6528,7423,6532,7427,6537,7428,6537,8487,6542,8485,6546,8481,6547,8477,7605,8477,7604,8472,7600,8468,7595,8467,7595,8427 +PU7595,6350 +PD7595,6350,7595,4233,7590,4235,7587,4239,7585,4243,6527,4243,6528,4248,6532,4252,6537,4253,6537,6370,6542,6369,6546,6365,6547,6360,7605,6360,7604,6355,7600,6351,7595,6350,7595,6310 +PU7595,2699 +PD7595,2699,7595,1534,7593,1487,7592,1487,7586,1441,7585,1441,7575,1396,7561,1352,7560,1353,7543,1311,7542,1311,7521,1271,7521,1272,7497,1234,7496,1235,7468,1200,7437,1169,7437,1170,7404,1142,7404,1143,7369,1119,7368,1120,7332,1100,7332,1101,7294,1085,7293,1086,7254,1075,7254,1076,7214,1069,7214,1070,7173,1068,6939,1068,6939,1069,6899,1071,6899,1072,6859,1079,6859,1080,6820,1092,6820,1093,6782,1108,6783,1109,6746,1129,6747,1130,6712,1154,6713,1155,6680,1183,6681,1183,6651,1215,6652,1216,6624,1250,6625,1251,6601,1289,6602,1289,6581,1329,6582,1329,6565,1371,6565,1372,6552,1415,6553,1415,6543,1461,6544,1461,6538,1507,6539,1507,6537,1554,6537,2719,6539,2766,6540,2766,6546,2812,6547,2812,6557,2857,6558,2857,6571,2901,6572,2901,6589,2943,6590,2942,6611,2982,6612,2982,6636,3019,6637,3019,6664,3054,6665,3053,6695,3085,6696,3084,6728,3112,6729,3111,6763,3135,6764,3134,6800,3154,6801,3153,6839,3168,6839,3167,6878,3179,6878,3177,6918,3184,6918,3183,6959,3186,6959,3185,7193,3185,7193,3184,7234,3182,7234,3181,7274,3174,7274,3173,7313,3162,7312,3161,7350,3145,7350,3144,7386,3124,7385,3123,7420,3099,7419,3099,7452,3071,7451,3070,7482,3038,7481,3038,7508,3003,7507,3002,7531,2965,7531,2964,7551,2924,7568,2882,7567,2882,7580,2838,7589,2793,7595,2746,7594,2746,7596,2699,7595,2699,7595,2659 +PU0,0 +SP0 +IN + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/plotter__--serialPort____test____--commandLanguage__DMPL.out b/share/extensions/tests/data/refs/plotter__--serialPort____test____--commandLanguage__DMPL.out new file mode 100644 index 0000000..88033f1 --- /dev/null +++ b/share/extensions/tests/data/refs/plotter__--serialPort____test____--commandLanguage__DMPL.out @@ -0,0 +1,2 @@ + +:HAL0V20EC1U,P1,U0,0,D0,90,U5966,2652,D5971,2651,5975,2647,5976,2641,5975,2636,5971,2633,5966,2632,5966,0,5961,1,5957,5,5956,10,3824,10,3825,15,3829,19,3834,20,3834,2652,3839,2651,3843,2647,3844,2642,5976,2642,5975,2637,5971,2633,5966,2632,5966,2592,U5479,7938,D5474,7939,5470,7943,5469,7948,5470,7953,5474,7957,5478,7957,5477,7997,5476,7997,5472,8037,5471,8036,5464,8075,5463,8075,5453,8112,5452,8112,5439,8149,5439,8148,5423,8184,5405,8218,5404,8217,5384,8250,5361,8281,5361,8280,5336,8310,5335,8309,5308,8337,5308,8336,5279,8362,5248,8385,5247,8385,5214,8406,5214,8405,5180,8425,5179,8424,5143,8441,5143,8440,5105,8454,5105,8453,5067,8464,5067,8463,5029,8471,5028,8470,4990,8476,4990,8475,4951,8477,4951,8476,4913,8476,4913,8475,4874,8472,4874,8471,4836,8465,4837,8464,4799,8455,4763,8443,4763,8442,4727,8428,4727,8427,4692,8411,4693,8410,4659,8390,4660,8390,4627,8368,4628,8367,4597,8343,4597,8342,4568,8315,4569,8314,4541,8285,4542,8285,4517,8254,4518,8253,4496,8221,4497,8221,4477,8187,4478,8187,4461,8152,4461,8151,4447,8116,4448,8115,4436,8079,4437,8078,4428,8041,4429,8041,4423,8003,4420,7964,4421,7964,4420,7926,4421,7926,4423,7887,4424,7887,4429,7849,4430,7849,4438,7811,4439,7811,4450,7773,4464,7736,4465,7736,4481,7700,4482,7700,4501,7665,4502,7666,4523,7633,4524,7633,4548,7603,4574,7574,4574,7575,4602,7548,4603,7548,4632,7524,4664,7502,4697,7482,4697,7483,4731,7465,4732,7466,4767,7450,4767,7451,4804,7438,4804,7439,4842,7429,4842,7430,4880,7422,4880,7423,4920,7419,4959,7418,4959,7419,5014,7422,5013,7423,5066,7431,5066,7432,5117,7445,5116,7446,5165,7463,5164,7464,5211,7487,5210,7487,5254,7514,5253,7515,5294,7545,5293,7546,5331,7580,5330,7581,5365,7618,5364,7619,5394,7660,5393,7660,5420,7704,5419,7704,5441,7751,5458,7800,5457,7800,5470,7851,5469,7851,5477,7903,5476,7903,5479,7957,5478,7957,5477,7997,5476,7997,U5478,5312,D5478,5312,5477,5391,5472,5470,5465,5547,5464,5547,5454,5622,5441,5695,5425,5766,5407,5834,5387,5899,5386,5899,5364,5961,5363,5961,5338,6020,5311,6075,5310,6075,5282,6126,5281,6126,5250,6173,5217,6216,5216,6215,5182,6254,5181,6253,5145,6286,5144,6285,5107,6313,5106,6312,5068,6334,5067,6333,5029,6349,5029,6348,4990,6358,4990,6356,4951,6361,4951,6359,4913,6358,4913,6357,4874,6350,4875,6349,4837,6337,4837,6335,4800,6318,4800,6317,4764,6293,4728,6264,4729,6263,4694,6230,4695,6229,4661,6190,4662,6190,4630,6145,4599,6096,4600,6095,4571,6041,4544,5982,4520,5920,4498,5856,4499,5855,4479,5788,4480,5788,4463,5718,4449,5647,4449,5646,4438,5573,4429,5498,4430,5498,4424,5422,4421,5346,4420,5268,4421,5268,4423,5191,4428,5114,4429,5114,4437,5037,4448,4962,4449,4962,4462,4887,4463,4887,4479,4814,4480,4815,4499,4745,4499,4746,4521,4680,4545,4618,4546,4619,4571,4561,4572,4561,4599,4507,4600,4508,4629,4458,4630,4458,4661,4413,4662,4414,4695,4374,4729,4339,4730,4339,4765,4309,4766,4310,4803,4284,4803,4285,4841,4265,4841,4266,4880,4252,4880,4253,4919,4244,4920,4246,4959,4243,4959,4244,4987,4245,4987,4246,5013,4250,5013,4251,5040,4258,5040,4259,5066,4268,5065,4269,5091,4281,5090,4282,5116,4296,5115,4297,5140,4313,5139,4314,5163,4333,5186,4354,5186,4355,5209,4378,5208,4379,5230,4404,5252,4432,5251,4432,5292,4493,5291,4494,5328,4562,5362,4638,5361,4638,5392,4719,5391,4720,5418,4807,5417,4807,5439,4899,5439,4900,5457,4997,5456,4997,5469,5098,5477,5203,5476,5203,5479,5312,5478,5312,5478,5352,U4655,1257,D4650,1256,4649,1256,4615,1306,4615,1307,4583,1362,4584,1362,4554,1422,4555,1422,4528,1486,4528,1487,4504,1554,4505,1554,4483,1625,4484,1625,4465,1699,4466,1699,4450,1775,4451,1775,4438,1854,4429,1934,4423,2016,4420,2099,4421,2099,4421,2182,4425,2266,4432,2350,4443,2433,4457,2514,4474,2592,4494,2667,4495,2667,4517,2737,4542,2803,4543,2803,4570,2865,4571,2865,4600,2922,4601,2922,4632,2975,4633,2974,4667,3022,4667,3021,4703,3064,4703,3063,4741,3100,4741,3099,4780,3130,4780,3129,4820,3154,4821,3153,4862,3171,4862,3170,4904,3182,4905,3181,4948,3186,4948,3184,4991,3183,4990,3181,5033,3173,5032,3171,5074,3156,5073,3155,5114,3134,5113,3133,5152,3105,5151,3104,5189,3071,5188,3070,5225,3031,5224,3031,5259,2986,5258,2986,5291,2936,5290,2936,5321,2881,5349,2821,5375,2757,5375,2756,5398,2688,5419,2615,5437,2539,5437,2538,5452,2458,5464,2376,5472,2293,5477,2209,5479,2126,5477,2043,5472,1961,5464,1880,5453,1800,5439,1723,5438,1723,5422,1647,5421,1648,5401,1575,5378,1505,5378,1506,5352,1439,5352,1440,5324,1377,5323,1378,5292,1319,5292,1320,5258,1266,5254,1270,5253,1275,5255,1280,5259,1284,5260,1284,4946,2136,4942,2133,4940,2128,4940,2123,4944,2119,4946,2117,4652,1238,4647,1241,4645,1246,4646,1251,4649,1255,4649,1256,4627,1289,U3356,8485,D3353,8481,3352,8476,3354,8471,3355,8470,2297,7411,2301,7409,2307,7409,2311,7411,3369,6353,3364,6350,3359,6350,3355,6353,2297,5295,U3355,4236,D3352,4241,3352,4243,3307,4241,3307,4240,3254,4235,3187,4225,3107,4209,3107,4208,3017,4185,3018,4184,2970,4169,2971,4169,2922,4151,2873,4130,2824,4107,2824,4106,2774,4080,2775,4079,2726,4050,2726,4049,2678,4016,2631,3979,2631,3978,2585,3938,2586,3937,2542,3892,2543,3892,2501,3843,2502,3842,2463,3789,2464,3788,2428,3730,2429,3730,2397,3667,2398,3666,2369,3598,2370,3598,2346,3525,2347,3525,2327,3446,2328,3446,2314,3361,2315,3361,2306,3271,2303,3175,2304,3175,2307,3082,2308,3082,2317,3000,2324,2964,2332,2930,2333,2930,2342,2899,2352,2870,2353,2870,2364,2843,2365,2843,2378,2819,2392,2797,2393,2797,2407,2777,2408,2777,2423,2759,2424,2760,2440,2743,2441,2744,2458,2729,2459,2729,2477,2716,2478,2717,2516,2695,2517,2696,2558,2680,2559,2681,2603,2669,2603,2670,2648,2662,2648,2663,2696,2658,2696,2659,2744,2656,2744,2657,2843,2656,2942,2655,2942,2654,2990,2652,2990,2651,3037,2647,3037,2646,3083,2638,3082,2637,3126,2625,3126,2624,3167,2608,3167,2607,3205,2585,3205,2584,3223,2571,3222,2570,3240,2555,3239,2555,3256,2538,3255,2537,3271,2519,3270,2519,3285,2498,3284,2498,3298,2475,3297,2475,3310,2450,3309,2450,3320,2423,3330,2394,3339,2362,3338,2362,3346,2328,3345,2328,3352,2291,3351,2291,3360,2210,3359,2210,3362,2117,3359,2021,3358,2021,3349,1930,3348,1931,3334,1846,3333,1846,3314,1768,3313,1768,3289,1695,3261,1627,3260,1627,3228,1564,3227,1564,3191,1506,3191,1507,3152,1453,3151,1454,3110,1405,3066,1360,3065,1361,3020,1320,3019,1321,2972,1284,2923,1251,2923,1252,2874,1222,2824,1196,2774,1173,2774,1174,2725,1153,2725,1154,2676,1136,2629,1121,2629,1122,2539,1098,2539,1099,2459,1083,2391,1074,2339,1069,2339,1070,2294,1068,U292,6943,D293,6949,297,6952,302,6954,304,6954,390,7434,384,7433,381,7431,34,7774,39,7776,44,7776,49,7773,51,7769,51,7768,534,7834,532,7839,529,7842,748,8277,752,8274,754,8269,752,8264,749,8260,748,8260,960,7821,964,7824,966,7828,1448,7754,1446,7750,1441,7746,1436,7746,1431,7749,1079,7411,1084,7409,1088,7408,1167,6927,1161,6928,1157,6931,1155,6936,1156,6941,1156,6942,726,7172,725,7167,726,7163,293,6939,292,6944,294,6950,298,6953,303,6954,304,6954,311,6993,U272,4837,D272,4837,358,5317,353,5316,349,5314,3,5657,7,5660,13,5660,17,5657,20,5652,20,5651,503,5718,501,5723,497,5725,716,6161,720,6157,722,6152,721,6147,717,6143,716,6143,929,5704,933,5708,934,5712,1416,5638,1414,5633,1409,5630,1404,5629,1399,5632,1047,5295,1052,5292,1056,5292,1135,4811,1130,4811,1125,4814,1123,4819,1124,4825,695,5055,693,5050,695,5046,261,4823,260,4828,262,4833,266,4836,271,4837,272,4837,279,4876,U8793,5312,D8798,5309,8801,5305,8801,5299,8798,5295,8794,5292,8791,5292,8791,4551,8786,4552,8782,4556,8781,4561,8047,4561,8048,4566,8052,4570,8057,4571,8057,5312,8062,5310,8066,5306,8067,5302,8801,5302,8800,5297,8796,5293,8791,5292,8791,5252,U7595,8467,D7595,8467,7595,7408,7590,7410,7587,7414,7585,7418,6527,7418,6528,7423,6532,7427,6537,7428,6537,8487,6542,8485,6546,8481,6547,8477,7605,8477,7604,8472,7600,8468,7595,8467,7595,8427,U7595,6350,D7595,6350,7595,4233,7590,4235,7587,4239,7585,4243,6527,4243,6528,4248,6532,4252,6537,4253,6537,6370,6542,6369,6546,6365,6547,6360,7605,6360,7604,6355,7600,6351,7595,6350,7595,6310,U7595,2699,D7595,2699,7595,1534,7593,1487,7592,1487,7586,1441,7585,1441,7575,1396,7561,1352,7560,1353,7543,1311,7542,1311,7521,1271,7521,1272,7497,1234,7496,1235,7468,1200,7437,1169,7437,1170,7404,1142,7404,1143,7369,1119,7368,1120,7332,1100,7332,1101,7294,1085,7293,1086,7254,1075,7254,1076,7214,1069,7214,1070,7173,1068,6939,1068,6939,1069,6899,1071,6899,1072,6859,1079,6859,1080,6820,1092,6820,1093,6782,1108,6783,1109,6746,1129,6747,1130,6712,1154,6713,1155,6680,1183,6681,1183,6651,1215,6652,1216,6624,1250,6625,1251,6601,1289,6602,1289,6581,1329,6582,1329,6565,1371,6565,1372,6552,1415,6553,1415,6543,1461,6544,1461,6538,1507,6539,1507,6537,1554,6537,2719,6539,2766,6540,2766,6546,2812,6547,2812,6557,2857,6558,2857,6571,2901,6572,2901,6589,2943,6590,2942,6611,2982,6612,2982,6636,3019,6637,3019,6664,3054,6665,3053,6695,3085,6696,3084,6728,3112,6729,3111,6763,3135,6764,3134,6800,3154,6801,3153,6839,3168,6839,3167,6878,3179,6878,3177,6918,3184,6918,3183,6959,3186,6959,3185,7193,3185,7193,3184,7234,3182,7234,3181,7274,3174,7274,3173,7313,3162,7312,3161,7350,3145,7350,3144,7386,3124,7385,3123,7420,3099,7419,3099,7452,3071,7451,3070,7482,3038,7481,3038,7508,3003,7507,3002,7531,2965,7531,2964,7551,2924,7568,2882,7567,2882,7580,2838,7589,2793,7595,2746,7594,2746,7596,2699,7595,2699,7595,2659,P0,U0,0,Z \ No newline at end of file diff --git a/share/extensions/tests/data/refs/plotter__--serialPort____test____--commandLanguage__KNK.out b/share/extensions/tests/data/refs/plotter__--serialPort____test____--commandLanguage__KNK.out new file mode 100644 index 0000000..9457a75 --- /dev/null +++ b/share/extensions/tests/data/refs/plotter__--serialPort____test____--commandLanguage__KNK.out @@ -0,0 +1,34 @@ +ZG +FS24 +VS20 +PU +SP1 +PU0,0 +PD0,90 +PU5966,2652 +PD5971,2651,5975,2647,5976,2641,5975,2636,5971,2633,5966,2632,5966,0,5961,1,5957,5,5956,10,3824,10,3825,15,3829,19,3834,20,3834,2652,3839,2651,3843,2647,3844,2642,5976,2642,5975,2637,5971,2633,5966,2632,5966,2592 +PU5479,7938 +PD5474,7939,5470,7943,5469,7948,5470,7953,5474,7957,5478,7957,5477,7997,5476,7997,5472,8037,5471,8036,5464,8075,5463,8075,5453,8112,5452,8112,5439,8149,5439,8148,5423,8184,5405,8218,5404,8217,5384,8250,5361,8281,5361,8280,5336,8310,5335,8309,5308,8337,5308,8336,5279,8362,5248,8385,5247,8385,5214,8406,5214,8405,5180,8425,5179,8424,5143,8441,5143,8440,5105,8454,5105,8453,5067,8464,5067,8463,5029,8471,5028,8470,4990,8476,4990,8475,4951,8477,4951,8476,4913,8476,4913,8475,4874,8472,4874,8471,4836,8465,4837,8464,4799,8455,4763,8443,4763,8442,4727,8428,4727,8427,4692,8411,4693,8410,4659,8390,4660,8390,4627,8368,4628,8367,4597,8343,4597,8342,4568,8315,4569,8314,4541,8285,4542,8285,4517,8254,4518,8253,4496,8221,4497,8221,4477,8187,4478,8187,4461,8152,4461,8151,4447,8116,4448,8115,4436,8079,4437,8078,4428,8041,4429,8041,4423,8003,4420,7964,4421,7964,4420,7926,4421,7926,4423,7887,4424,7887,4429,7849,4430,7849,4438,7811,4439,7811,4450,7773,4464,7736,4465,7736,4481,7700,4482,7700,4501,7665,4502,7666,4523,7633,4524,7633,4548,7603,4574,7574,4574,7575,4602,7548,4603,7548,4632,7524,4664,7502,4697,7482,4697,7483,4731,7465,4732,7466,4767,7450,4767,7451,4804,7438,4804,7439,4842,7429,4842,7430,4880,7422,4880,7423,4920,7419,4959,7418,4959,7419,5014,7422,5013,7423,5066,7431,5066,7432,5117,7445,5116,7446,5165,7463,5164,7464,5211,7487,5210,7487,5254,7514,5253,7515,5294,7545,5293,7546,5331,7580,5330,7581,5365,7618,5364,7619,5394,7660,5393,7660,5420,7704,5419,7704,5441,7751,5458,7800,5457,7800,5470,7851,5469,7851,5477,7903,5476,7903,5479,7957,5478,7957,5477,7997,5476,7997 +PU5478,5312 +PD5478,5312,5477,5391,5472,5470,5465,5547,5464,5547,5454,5622,5441,5695,5425,5766,5407,5834,5387,5899,5386,5899,5364,5961,5363,5961,5338,6020,5311,6075,5310,6075,5282,6126,5281,6126,5250,6173,5217,6216,5216,6215,5182,6254,5181,6253,5145,6286,5144,6285,5107,6313,5106,6312,5068,6334,5067,6333,5029,6349,5029,6348,4990,6358,4990,6356,4951,6361,4951,6359,4913,6358,4913,6357,4874,6350,4875,6349,4837,6337,4837,6335,4800,6318,4800,6317,4764,6293,4728,6264,4729,6263,4694,6230,4695,6229,4661,6190,4662,6190,4630,6145,4599,6096,4600,6095,4571,6041,4544,5982,4520,5920,4498,5856,4499,5855,4479,5788,4480,5788,4463,5718,4449,5647,4449,5646,4438,5573,4429,5498,4430,5498,4424,5422,4421,5346,4420,5268,4421,5268,4423,5191,4428,5114,4429,5114,4437,5037,4448,4962,4449,4962,4462,4887,4463,4887,4479,4814,4480,4815,4499,4745,4499,4746,4521,4680,4545,4618,4546,4619,4571,4561,4572,4561,4599,4507,4600,4508,4629,4458,4630,4458,4661,4413,4662,4414,4695,4374,4729,4339,4730,4339,4765,4309,4766,4310,4803,4284,4803,4285,4841,4265,4841,4266,4880,4252,4880,4253,4919,4244,4920,4246,4959,4243,4959,4244,4987,4245,4987,4246,5013,4250,5013,4251,5040,4258,5040,4259,5066,4268,5065,4269,5091,4281,5090,4282,5116,4296,5115,4297,5140,4313,5139,4314,5163,4333,5186,4354,5186,4355,5209,4378,5208,4379,5230,4404,5252,4432,5251,4432,5292,4493,5291,4494,5328,4562,5362,4638,5361,4638,5392,4719,5391,4720,5418,4807,5417,4807,5439,4899,5439,4900,5457,4997,5456,4997,5469,5098,5477,5203,5476,5203,5479,5312,5478,5312,5478,5352 +PU4655,1257 +PD4650,1256,4649,1256,4615,1306,4615,1307,4583,1362,4584,1362,4554,1422,4555,1422,4528,1486,4528,1487,4504,1554,4505,1554,4483,1625,4484,1625,4465,1699,4466,1699,4450,1775,4451,1775,4438,1854,4429,1934,4423,2016,4420,2099,4421,2099,4421,2182,4425,2266,4432,2350,4443,2433,4457,2514,4474,2592,4494,2667,4495,2667,4517,2737,4542,2803,4543,2803,4570,2865,4571,2865,4600,2922,4601,2922,4632,2975,4633,2974,4667,3022,4667,3021,4703,3064,4703,3063,4741,3100,4741,3099,4780,3130,4780,3129,4820,3154,4821,3153,4862,3171,4862,3170,4904,3182,4905,3181,4948,3186,4948,3184,4991,3183,4990,3181,5033,3173,5032,3171,5074,3156,5073,3155,5114,3134,5113,3133,5152,3105,5151,3104,5189,3071,5188,3070,5225,3031,5224,3031,5259,2986,5258,2986,5291,2936,5290,2936,5321,2881,5349,2821,5375,2757,5375,2756,5398,2688,5419,2615,5437,2539,5437,2538,5452,2458,5464,2376,5472,2293,5477,2209,5479,2126,5477,2043,5472,1961,5464,1880,5453,1800,5439,1723,5438,1723,5422,1647,5421,1648,5401,1575,5378,1505,5378,1506,5352,1439,5352,1440,5324,1377,5323,1378,5292,1319,5292,1320,5258,1266,5254,1270,5253,1275,5255,1280,5259,1284,5260,1284,4946,2136,4942,2133,4940,2128,4940,2123,4944,2119,4946,2117,4652,1238,4647,1241,4645,1246,4646,1251,4649,1255,4649,1256,4627,1289 +PU3356,8485 +PD3353,8481,3352,8476,3354,8471,3355,8470,2297,7411,2301,7409,2307,7409,2311,7411,3369,6353,3364,6350,3359,6350,3355,6353,2297,5295 +PU3355,4236 +PD3352,4241,3352,4243,3307,4241,3307,4240,3254,4235,3187,4225,3107,4209,3107,4208,3017,4185,3018,4184,2970,4169,2971,4169,2922,4151,2873,4130,2824,4107,2824,4106,2774,4080,2775,4079,2726,4050,2726,4049,2678,4016,2631,3979,2631,3978,2585,3938,2586,3937,2542,3892,2543,3892,2501,3843,2502,3842,2463,3789,2464,3788,2428,3730,2429,3730,2397,3667,2398,3666,2369,3598,2370,3598,2346,3525,2347,3525,2327,3446,2328,3446,2314,3361,2315,3361,2306,3271,2303,3175,2304,3175,2307,3082,2308,3082,2317,3000,2324,2964,2332,2930,2333,2930,2342,2899,2352,2870,2353,2870,2364,2843,2365,2843,2378,2819,2392,2797,2393,2797,2407,2777,2408,2777,2423,2759,2424,2760,2440,2743,2441,2744,2458,2729,2459,2729,2477,2716,2478,2717,2516,2695,2517,2696,2558,2680,2559,2681,2603,2669,2603,2670,2648,2662,2648,2663,2696,2658,2696,2659,2744,2656,2744,2657,2843,2656,2942,2655,2942,2654,2990,2652,2990,2651,3037,2647,3037,2646,3083,2638,3082,2637,3126,2625,3126,2624,3167,2608,3167,2607,3205,2585,3205,2584,3223,2571,3222,2570,3240,2555,3239,2555,3256,2538,3255,2537,3271,2519,3270,2519,3285,2498,3284,2498,3298,2475,3297,2475,3310,2450,3309,2450,3320,2423,3330,2394,3339,2362,3338,2362,3346,2328,3345,2328,3352,2291,3351,2291,3360,2210,3359,2210,3362,2117,3359,2021,3358,2021,3349,1930,3348,1931,3334,1846,3333,1846,3314,1768,3313,1768,3289,1695,3261,1627,3260,1627,3228,1564,3227,1564,3191,1506,3191,1507,3152,1453,3151,1454,3110,1405,3066,1360,3065,1361,3020,1320,3019,1321,2972,1284,2923,1251,2923,1252,2874,1222,2824,1196,2774,1173,2774,1174,2725,1153,2725,1154,2676,1136,2629,1121,2629,1122,2539,1098,2539,1099,2459,1083,2391,1074,2339,1069,2339,1070,2294,1068 +PU292,6943 +PD293,6949,297,6952,302,6954,304,6954,390,7434,384,7433,381,7431,34,7774,39,7776,44,7776,49,7773,51,7769,51,7768,534,7834,532,7839,529,7842,748,8277,752,8274,754,8269,752,8264,749,8260,748,8260,960,7821,964,7824,966,7828,1448,7754,1446,7750,1441,7746,1436,7746,1431,7749,1079,7411,1084,7409,1088,7408,1167,6927,1161,6928,1157,6931,1155,6936,1156,6941,1156,6942,726,7172,725,7167,726,7163,293,6939,292,6944,294,6950,298,6953,303,6954,304,6954,311,6993 +PU272,4837 +PD272,4837,358,5317,353,5316,349,5314,3,5657,7,5660,13,5660,17,5657,20,5652,20,5651,503,5718,501,5723,497,5725,716,6161,720,6157,722,6152,721,6147,717,6143,716,6143,929,5704,933,5708,934,5712,1416,5638,1414,5633,1409,5630,1404,5629,1399,5632,1047,5295,1052,5292,1056,5292,1135,4811,1130,4811,1125,4814,1123,4819,1124,4825,695,5055,693,5050,695,5046,261,4823,260,4828,262,4833,266,4836,271,4837,272,4837,279,4876 +PU8793,5312 +PD8798,5309,8801,5305,8801,5299,8798,5295,8794,5292,8791,5292,8791,4551,8786,4552,8782,4556,8781,4561,8047,4561,8048,4566,8052,4570,8057,4571,8057,5312,8062,5310,8066,5306,8067,5302,8801,5302,8800,5297,8796,5293,8791,5292,8791,5252 +PU7595,8467 +PD7595,8467,7595,7408,7590,7410,7587,7414,7585,7418,6527,7418,6528,7423,6532,7427,6537,7428,6537,8487,6542,8485,6546,8481,6547,8477,7605,8477,7604,8472,7600,8468,7595,8467,7595,8427 +PU7595,6350 +PD7595,6350,7595,4233,7590,4235,7587,4239,7585,4243,6527,4243,6528,4248,6532,4252,6537,4253,6537,6370,6542,6369,6546,6365,6547,6360,7605,6360,7604,6355,7600,6351,7595,6350,7595,6310 +PU7595,2699 +PD7595,2699,7595,1534,7593,1487,7592,1487,7586,1441,7585,1441,7575,1396,7561,1352,7560,1353,7543,1311,7542,1311,7521,1271,7521,1272,7497,1234,7496,1235,7468,1200,7437,1169,7437,1170,7404,1142,7404,1143,7369,1119,7368,1120,7332,1100,7332,1101,7294,1085,7293,1086,7254,1075,7254,1076,7214,1069,7214,1070,7173,1068,6939,1068,6939,1069,6899,1071,6899,1072,6859,1079,6859,1080,6820,1092,6820,1093,6782,1108,6783,1109,6746,1129,6747,1130,6712,1154,6713,1155,6680,1183,6681,1183,6651,1215,6652,1216,6624,1250,6625,1251,6601,1289,6602,1289,6581,1329,6582,1329,6565,1371,6565,1372,6552,1415,6553,1415,6543,1461,6544,1461,6538,1507,6539,1507,6537,1554,6537,2719,6539,2766,6540,2766,6546,2812,6547,2812,6557,2857,6558,2857,6571,2901,6572,2901,6589,2943,6590,2942,6611,2982,6612,2982,6636,3019,6637,3019,6664,3054,6665,3053,6695,3085,6696,3084,6728,3112,6729,3111,6763,3135,6764,3134,6800,3154,6801,3153,6839,3168,6839,3167,6878,3179,6878,3177,6918,3184,6918,3183,6959,3186,6959,3185,7193,3185,7193,3184,7234,3182,7234,3181,7274,3174,7274,3173,7313,3162,7312,3161,7350,3145,7350,3144,7386,3124,7385,3123,7420,3099,7419,3099,7452,3071,7451,3070,7482,3038,7481,3038,7508,3003,7507,3002,7531,2965,7531,2964,7551,2924,7568,2882,7567,2882,7580,2838,7589,2793,7595,2746,7594,2746,7596,2699,7595,2699,7595,2659 +SP0 +PU0,0 +@ \ No newline at end of file diff --git a/share/extensions/tests/data/refs/plt_output.out b/share/extensions/tests/data/refs/plt_output.out new file mode 100644 index 0000000..b64aac8 --- /dev/null +++ b/share/extensions/tests/data/refs/plt_output.out @@ -0,0 +1 @@ +IN;PU7353,7293;PD10160,7293;PD10160,5019;PD7353,5019;PD7353,7293;PU2258,6209;PD2255,6151;PD2246,6095;PD2232,6041;PD2213,5989;PD2190,5940;PD2161,5893;PD2129,5850;PD2092,5810;PD2052,5773;PD2009,5741;PD1962,5713;PD1913,5689;PD1861,5670;PD1807,5656;PD1751,5647;PD1693,5644;PD1636,5647;PD1580,5656;PD1526,5670;PD1474,5689;PD1424,5713;PD1378,5741;PD1334,5773;PD1294,5810;PD1258,5850;PD1225,5893;PD1197,5940;PD1173,5989;PD1154,6041;PD1140,6095;PD1132,6151;PD1129,6209;PD1132,6267;PD1140,6323;PD1154,6377;PD1173,6429;PD1197,6478;PD1225,6524;PD1258,6568;PD1294,6608;PD1334,6644;PD1378,6677;PD1424,6705;PD1474,6729;PD1526,6748;PD1580,6762;PD1636,6770;PD1693,6773;PD1751,6770;PD1807,6762;PD1861,6748;PD1913,6729;PD1962,6705;PD2009,6677;PD2052,6644;PD2092,6608;PD2129,6568;PD2161,6524;PD2190,6478;PD2213,6429;PD2232,6377;PD2246,6323;PD2255,6267;PD2258,6209;PU5644,6209;PD5639,6151;PD5621,6095;PD5594,6041;PD5556,5989;PD5508,5940;PD5452,5893;PD5387,5850;PD5314,5810;PD5233,5773;PD5147,5741;PD5053,5713;PD4955,5689;PD4851,5670;PD4743,5656;PD4631,5647;PD4516,5644;PD4400,5647;PD4288,5656;PD4180,5670;PD4076,5689;PD3978,5713;PD3885,5741;PD3798,5773;PD3717,5810;PD3645,5850;PD3580,5893;PD3523,5940;PD3475,5989;PD3437,6041;PD3410,6095;PD3392,6151;PD3387,6209;PD3392,6267;PD3410,6323;PD3437,6377;PD3475,6429;PD3523,6478;PD3580,6524;PD3645,6568;PD3717,6608;PD3798,6644;PD3885,6677;PD3978,6705;PD4076,6729;PD4180,6748;PD4288,6762;PD4400,6770;PD4516,6773;PD4631,6770;PD4743,6762;PD4851,6748;PD4955,6729;PD5053,6705;PD5147,6677;PD5233,6644;PD5314,6608;PD5387,6568;PD5452,6524;PD5508,6478;PD5556,6429;PD5594,6377;PD5621,6323;PD5639,6267;PD5644,6209;PU8840,5895;PD7902,6209;PD8840,5895;PU1129,4516;PD2258,3387;PD3387,4516;PD4516,3387;PU5644,4516;PD5647,4467;PD5662,4339;PD5679,4254;PD5704,4158;PD5739,4056;PD5786,3951;PD5814,3898;PD5845,3846;PD5881,3794;PD5920,3744;PD5963,3695;PD6011,3648;PD6064,3604;PD6121,3563;PD6183,3525;PD6250,3491;PD6323,3461;PD6401,3435;PD6485,3415;PD6575,3399;PD6671,3390;PD6773,3387;PD6873,3390;PD6960,3399;PD7035,3415;PD7100,3435;PD7154,3461;PD7200,3491;PD7237,3525;PD7267,3563;PD7291,3604;PD7308,3648;PD7321,3695;PD7329,3744;PD7337,3846;PD7338,3951;PD7339,4056;PD7347,4158;PD7355,4207;PD7368,4254;PD7385,4298;PD7408,4339;PD7438,4377;PD7476,4411;PD7521,4441;PD7576,4467;PD7641,4488;PD7716,4503;PD7803,4512;PD7902,4516;PD8005,4512;PD8101,4503;PD8191,4488;PD8275,4467;PD8353,4441;PD8426,4411;PD8493,4377;PD8555,4339;PD8612,4298;PD8664,4254;PD8712,4207;PD8756,4158;PD8795,4108;PD8830,4056;PD8862,4004;PD8890,3951;PD8937,3846;PD8972,3744;PD8997,3648;PD9013,3563;PD9029,3435;PD9031,3387;PU2764,1251;PD2252,1343;PD1886,974;PD1815,1489;PD1351,1723;PD1819,1949;PD1898,2463;PD2258,2088;PD2771,2172;PD2525,1714;PD2764,1251;PU5022,1218;PD4510,1309;PD4144,940;PD4073,1455;PD3609,1689;PD4077,1915;PD4155,2429;PD4516,2054;PD5029,2138;PD4783,1680;PD5022,1218;PU1161,10281;PD1161,10231;PD1221,10231;PD1221,10281;PD1237,10281;PD1237,10160;PD1221,10160;PD1221,10217;PD1161,10217;PD1161,10160;PD1145,10160;PD1145,10281;PD1161,10281;PU1349,10202;PD1281,10202;PD1284,10188;PD1290,10178;PD1300,10172;PD1313,10170;PD1322,10171;PD1330,10172;PD1338,10175;PD1346,10179;PD1346,10165;PD1338,10162;PD1330,10159;PD1321,10158;PD1312,10158;PD1293,10161;PD1278,10170;PD1269,10185;PD1266,10204;PD1269,10224;PD1278,10240;PD1292,10249;PD1310,10253;PD1326,10250;PD1339,10241;PD1347,10227;PD1349,10209;PD1349,10202;PU1310,10240;PD1299,10238;PD1290,10233;PD1284,10225;PD1282,10213;PD1335,10213;PD1333,10224;PD1328,10233;PD1320,10238;PD1310,10240;PU1391,10286;PD1391,10160;PD1376,10160;PD1376,10286;PD1391,10286;PU1438,10286;PD1438,10160;PD1423,10160;PD1423,10286;PD1438,10286;PU1480,10205;PD1481,10191;PD1487,10180;PD1495,10173;PD1506,10170;PD1516,10173;PD1524,10180;PD1530,10191;PD1531,10205;PD1530,10220;PD1524,10231;PD1516,10238;PD1506,10240;PD1495,10238;PD1487,10231;PD1481,10220;PD1480,10205;PU1547,10205;PD1544,10185;PD1536,10170;PD1523,10161;PD1506,10158;PD1488,10161;PD1475,10170;PD1467,10185;PD1464,10205;PD1467,10225;PD1475,10240;PD1488,10250;PD1506,10253;PD1523,10250;PD1536,10240;PD1544,10225;PD1547,10205;PU1635,10281;PD1660,10179;PD1685,10281;PD1704,10281;PD1729,10179;PD1754,10281;PD1771,10281;PD1740,10160;PD1720,10160;PD1694,10265;PD1669,10160;PD1648,10160;PD1618,10281;PD1635,10281;PU1793,10205;PD1795,10191;PD1800,10180;PD1808,10173;PD1819,10170;PD1830,10173;PD1838,10180;PD1843,10191;PD1845,10205;PD1843,10220;PD1838,10231;PD1830,10238;PD1819,10240;PD1808,10238;PD1800,10231;PD1795,10220;PD1793,10205;PU1861,10205;PD1858,10185;PD1850,10170;PD1837,10161;PD1819,10158;PD1802,10161;PD1789,10170;PD1780,10185;PD1778,10205;PD1780,10225;PD1789,10240;PD1802,10250;PD1819,10253;PD1837,10250;PD1850,10240;PD1858,10225;PD1861,10205;PU1928,10239;PD1917,10237;PD1909,10231;PD1904,10221;PD1902,10208;PD1902,10160;PD1887,10160;PD1887,10251;PD1902,10251;PD1902,10236;PD1908,10244;PD1914,10249;PD1923,10252;PD1933,10253;PD1934,10253;PD1936,10253;PD1938,10252;PD1940,10252;PD1940,10237;PD1938,10238;PD1935,10239;PD1932,10239;PD1928,10239;PU1972,10286;PD1972,10160;PD1958,10160;PD1958,10286;PD1972,10286;PU2064,10286;PD2079,10286;PD2079,10160;PD2064,10160;PD2064,10174;PD2059,10167;PD2052,10162;PD2045,10159;PD2035,10158;PD2020,10161;PD2009,10171;PD2001,10186;PD1998,10205;PD2001,10224;PD2009,10240;PD2020,10249;PD2035,10253;PD2045,10252;PD2052,10249;PD2059,10244;PD2064,10237;PD2064,10286;PU2039,10170;PD2049,10172;PD2057,10179;PD2063,10191;PD2064,10205;PD2063,10220;PD2057,10231;PD2049,10238;PD2039,10240;PD2028,10238;PD2020,10231;PD2015,10220;PD2014,10205;PD2015,10191;PD2020,10179;PD2028,10172;PD2039,10170;PU4516,10306;PD5306,10306;PD5306,9523;PD4516,9523;PD4516,10306;PU2289,10281;PD2289,10207;PD2290,10191;PD2296,10179;PD2305,10173;PD2318,10171;PD2332,10173;PD2341,10179;PD2346,10191;PD2348,10207;PD2348,10281;PD2365,10281;PD2365,10205;PD2362,10185;PD2353,10170;PD2338,10161;PD2318,10158;PD2298,10161;PD2284,10170;PD2275,10185;PD2272,10205;PD2272,10281;PD2289,10281;PU2414,10222;PD2435,10222;PD2445,10223;PD2453,10228;PD2457,10235;PD2459,10245;PD2457,10254;PD2453,10261;PD2445,10266;PD2435,10267;PD2414,10267;PD2414,10222;PU2435,10281;PD2453,10278;PD2466,10272;PD2473,10260;PD2476,10245;PD2473,10229;PD2466,10218;PD2453,10211;PD2435,10209;PD2414,10209;PD2414,10160;PD2398,10160;PD2398,10281;PD2435,10281;PU2517,10222;PD2537,10222;PD2547,10223;PD2555,10228;PD2560,10235;PD2561,10245;PD2560,10254;PD2555,10261;PD2547,10266;PD2537,10267;PD2517,10267;PD2517,10222;PU2537,10281;PD2555,10278;PD2568,10272;PD2576,10260;PD2578,10245;PD2576,10229;PD2568,10218;PD2555,10211;PD2537,10209;PD2517,10209;PD2517,10160;PD2500,10160;PD2500,10281;PD2537,10281;PU2679,10281;PD2679,10267;PD2619,10267;PD2619,10231;PD2676,10231;PD2676,10217;PD2619,10217;PD2619,10174;PD2680,10174;PD2680,10160;PD2603,10160;PD2603,10281;PD2679,10281;PU2787,10193;PD2804,10160;PD2786,10160;PD2771,10191;PD2765,10201;PD2759,10207;PD2753,10210;PD2744,10211;PD2726,10211;PD2726,10160;PD2710,10160;PD2710,10281;PD2747,10281;PD2765,10279;PD2777,10272;PD2785,10261;PD2788,10246;PD2786,10236;PD2782,10227;PD2776,10221;PD2767,10217;PD2772,10214;PD2777,10209;PD2782,10202;PD2787,10193;PU2726,10224;PD2747,10224;PD2757,10226;PD2764,10230;PD2769,10237;PD2770,10246;PD2769,10255;PD2764,10262;PD2757,10266;PD2747,10267;PD2726,10267;PD2726,10224;PU3416,10247;PD3438,10188;PD3461,10247;PD3478,10247;PD3478,10160;PD3467,10160;PD3467,10236;PD3444,10177;PD3432,10177;PD3410,10236;PD3410,10160;PD3398,10160;PD3398,10247;PD3416,10247;PU3502,10225;PD3513,10225;PD3513,10186;PD3514,10178;PD3517,10172;PD3521,10169;PD3528,10168;PD3535,10169;PD3541,10173;PD3545,10180;PD3546,10188;PD3546,10225;PD3557,10225;PD3557,10160;PD3546,10160;PD3546,10170;PD3542,10165;PD3537,10161;PD3532,10159;PD3525,10158;PD3515,10160;PD3508,10165;PD3504,10174;PD3502,10186;PD3502,10225;PU3592,10251;PD3592,10160;PD3581,10160;PD3581,10251;PD3592,10251;PU3626,10225;PD3648,10225;PD3648,10217;PD3626,10217;PD3626,10182;PD3626,10175;PD3628,10171;PD3631,10170;PD3637,10169;PD3648,10169;PD3648,10160;PD3637,10160;PD3626,10161;PD3620,10165;PD3616,10171;PD3615,10182;PD3615,10217;PD3607,10217;PD3607,10225;PD3615,10225;PD3615,10244;PD3626,10244;PD3626,10225;PU3674,10225;PD3674,10160;PD3663,10160;PD3663,10225;PD3674,10225;PU3674,10251;PD3674,10237;PD3663,10237;PD3663,10251;PD3674,10251;PU3747,10251;PD3747,10160;PD3736,10160;PD3736,10251;PD3747,10251;PU3781,10225;PD3781,10160;PD3770,10160;PD3770,10225;PD3781,10225;PU3781,10251;PD3781,10237;PD3770,10237;PD3770,10251;PD3781,10251;PU3858,10160;PD3847,10160;PD3847,10199;PD3847,10207;PD3844,10213;PD3839,10216;PD3833,10218;PD3825,10216;PD3819,10212;PD3815,10205;PD3814,10197;PD3814,10160;PD3803,10160;PD3803,10225;PD3814,10225;PD3814,10215;PD3818,10220;PD3823,10224;PD3829,10226;PD3835,10227;PD3845,10225;PD3852,10220;PD3857,10211;PD3858,10199;PD3858,10160;PU3937,10190;PD3888,10190;PD3890,10180;PD3895,10173;PD3902,10169;PD3911,10167;PD3917,10168;PD3923,10169;PD3929,10171;PD3935,10173;PD3935,10163;PD3929,10161;PD3923,10160;PD3917,10159;PD3911,10158;PD3897,10161;PD3886,10167;PD3879,10178;PD3877,10192;PD3879,10206;PD3885,10217;PD3896,10225;PD3909,10227;PD3921,10225;PD3930,10218;PD3935,10208;PD3937,10195;PD3937,10190;PU3909,10218;PD3901,10217;PD3894,10213;PD3890,10207;PD3888,10198;PD3927,10199;PD3925,10206;PD3922,10213;PD3916,10217;PD3909,10218;PU3967,10225;PD3989,10225;PD3989,10217;PD3967,10217;PD3967,10182;PD3968,10175;PD3969,10171;PD3973,10170;PD3978,10169;PD3989,10169;PD3989,10160;PD3978,10160;PD3968,10161;PD3961,10165;PD3958,10171;PD3956,10182;PD3956,10217;PD3949,10217;PD3949,10225;PD3956,10225;PD3956,10244;PD3967,10244;PD3967,10225;PU4061,10190;PD4011,10190;PD4013,10180;PD4018,10173;PD4025,10169;PD4034,10167;PD4041,10168;PD4046,10169;PD4052,10171;PD4058,10173;PD4058,10163;PD4052,10161;PD4046,10160;PD4040,10159;PD4034,10158;PD4020,10161;PD4009,10167;PD4002,10178;PD4000,10192;PD4002,10206;PD4009,10217;PD4019,10225;PD4032,10227;PD4044,10225;PD4053,10218;PD4059,10208;PD4061,10195;PD4061,10190;PU4032,10218;PD4024,10217;PD4018,10213;PD4013,10207;PD4011,10198;PD4050,10199;PD4048,10206;PD4045,10213;PD4039,10217;PD4032,10218;PU4108,10194;PD4133,10160;PD4120,10160;PD4101,10186;PD4082,10160;PD4069,10160;PD4095,10194;PD4071,10225;PD4084,10225;PD4101,10202;PD4119,10225;PD4131,10225;PD4108,10194;PU4160,10225;PD4182,10225;PD4182,10217;PD4160,10217;PD4160,10182;PD4161,10175;PD4162,10171;PD4166,10170;PD4171,10169;PD4182,10169;PD4182,10160;PD4171,10160;PD4161,10161;PD4154,10165;PD4150,10171;PD4149,10182;PD4149,10217;PD4141,10217;PD4141,10225;PD4149,10225;PD4149,10244;PD4160,10244;PD4160,10225;PU4248,10247;PD4248,10237;PD4210,10237;PD4210,10212;PD4244,10212;PD4244,10202;PD4210,10202;PD4210,10160;PD4198,10160;PD4198,10247;PD4248,10247;PU4276,10203;PD4278,10189;PD4283,10177;PD4292,10170;PD4304,10168;PD4315,10170;PD4324,10177;PD4330,10189;PD4331,10203;PD4330,10218;PD4324,10230;PD4315,10237;PD4304,10239;PD4292,10237;PD4283,10230;PD4278,10218;PD4276,10203;PU4344,10203;PD4341,10185;PD4333,10171;PD4320,10161;PD4304,10158;PD4287,10161;PD4274,10171;PD4266,10185;PD4263,10203;PD4266,10222;PD4274,10236;PD4287,10246;PD4304,10249;PD4320,10246;PD4333,10236;PD4341,10222;PD4344,10203;PU4372,10203;PD4374,10189;PD4380,10177;PD4388,10170;PD4400,10168;PD4411,10170;PD4420,10177;PD4426,10189;PD4428,10203;PD4426,10218;PD4420,10230;PD4411,10237;PD4400,10239;PD4388,10237;PD4380,10230;PD4374,10218;PD4372,10203;PU4440,10203;PD4437,10185;PD4429,10171;PD4416,10161;PD4400,10158;PD4383,10161;PD4371,10171;PD4362,10185;PD4360,10203;PD4362,10222;PD4371,10236;PD4383,10246;PD4400,10249;PD4416,10246;PD4429,10236;PD4437,10222;PD4440,10203;PU5716,10196;PD5696,10196;PD5696,10206;PD5727,10206;PD5727,10168;PD5720,10164;PD5712,10161;PD5704,10159;PD5695,10158;PD5676,10161;PD5663,10170;PD5654,10185;PD5651,10203;PD5654,10222;PD5663,10237;PD5676,10246;PD5695,10249;PD5703,10248;PD5711,10247;PD5718,10244;PD5725,10240;PD5725,10228;PD5718,10233;PD5711,10236;PD5704,10238;PD5696,10239;PD5682,10237;PD5672,10230;PD5666,10219;PD5664,10203;PD5666,10188;PD5672,10177;PD5682,10170;PD5696,10168;PD5702,10168;PD5707,10169;PD5711,10170;PD5716,10172;PD5716,10196;PU5780,10217;PD5772,10216;PD5766,10211;PD5762,10204;PD5761,10194;PD5761,10160;PD5750,10160;PD5750,10225;PD5761,10225;PD5761,10215;PD5765,10220;PD5770,10224;PD5776,10226;PD5783,10227;PD5784,10227;PD5785,10227;PD5787,10227;PD5788,10226;PD5788,10215;PD5786,10216;PD5784,10217;PD5782,10217;PD5780,10217;PU5804,10193;PD5805,10182;PD5809,10174;PD5815,10169;PD5822,10167;PD5830,10169;PD5836,10174;PD5840,10182;PD5841,10193;PD5840,10203;PD5836,10211;PD5830,10216;PD5822,10218;PD5815,10216;PD5809,10211;PD5805,10203;PD5804,10193;PU5852,10193;PD5850,10178;PD5844,10167;PD5835,10161;PD5822,10158;PD5810,10161;PD5800,10167;PD5794,10178;PD5792,10193;PD5794,10207;PD5800,10218;PD5810,10225;PD5822,10227;PD5835,10225;PD5844,10218;PD5850,10207;PD5852,10193;PU5871,10225;PD5882,10225;PD5882,10186;PD5882,10178;PD5885,10172;PD5890,10169;PD5896,10168;PD5904,10169;PD5910,10173;PD5914,10180;PD5915,10188;PD5915,10225;PD5926,10225;PD5926,10160;PD5915,10160;PD5915,10170;PD5911,10165;PD5906,10161;PD5900,10159;PD5894,10158;PD5884,10160;PD5877,10165;PD5872,10174;PD5871,10186;PD5871,10225;PU5960,10135;PD5949,10135;PD5949,10225;PD5960,10225;PD5960,10215;PD5964,10221;PD5968,10224;PD5974,10226;PD5981,10227;PD5992,10225;PD6000,10217;PD6006,10207;PD6008,10193;PD6006,10179;PD6000,10168;PD5992,10161;PD5981,10158;PD5974,10159;PD5968,10161;PD5964,10165;PD5960,10170;PD5960,10135;PU5978,10218;PD5970,10216;PD5965,10211;PD5961,10203;PD5960,10193;PD5961,10182;PD5965,10174;PD5970,10169;PD5978,10167;PD5986,10169;PD5991,10174;PD5995,10182;PD5996,10193;PD5995,10203;PD5991,10211;PD5986,10216;PD5978,10218;PU6083,10190;PD6034,10190;PD6036,10180;PD6040,10173;PD6047,10169;PD6057,10167;PD6063,10168;PD6069,10169;PD6075,10171;PD6080,10173;PD6080,10163;PD6075,10161;PD6069,10160;PD6063,10159;PD6056,10158;PD6042,10161;PD6032,10167;PD6025,10178;PD6022,10192;PD6025,10206;PD6031,10217;PD6041,10225;PD6054,10227;PD6066,10225;PD6075,10218;PD6081,10208;PD6083,10195;PD6083,10190;PU6055,10218;PD6047,10217;PD6040,10213;PD6036,10207;PD6034,10198;PD6072,10199;PD6071,10206;PD6067,10213;PD6062,10217;PD6055,10218;PU6145,10251;PD6156,10251;PD6156,10160;PD6145,10160;PD6145,10170;PD6142,10165;PD6137,10161;PD6131,10159;PD6124,10158;PD6114,10161;PD6105,10168;PD6100,10179;PD6098,10193;PD6100,10207;PD6105,10217;PD6114,10225;PD6124,10227;PD6131,10226;PD6137,10224;PD6142,10221;PD6145,10215;PD6145,10251;PU6127,10167;PD6135,10169;PD6140,10174;PD6144,10182;PD6145,10193;PD6144,10203;PD6140,10211;PD6135,10216;PD6127,10218;PD6119,10216;PD6114,10211;PD6110,10203;PD6109,10193;PD6110,10182;PD6114,10174;PD6119,10169;PD6127,10167;PU5675,9912;PD5705,9912;PD5705,9900;PD5675,9900;PD5675,9851;PD5676,9842;PD5678,9837;PD5682,9835;PD5690,9834;PD5705,9834;PD5705,9821;PD5690,9821;PD5676,9823;PD5666,9828;PD5661,9837;PD5660,9851;PD5660,9900;PD5649,9900;PD5649,9912;PD5660,9912;PD5660,9938;PD5675,9938;PD5675,9912;PU5804,9863;PD5736,9863;PD5738,9850;PD5745,9840;PD5755,9834;PD5768,9832;PD5776,9832;PD5785,9834;PD5793,9836;PD5800,9840;PD5800,9826;PD5792,9823;PD5784,9821;PD5776,9819;PD5767,9819;PD5748,9822;PD5733,9832;PD5723,9846;PD5720,9866;PD5723,9886;PD5732,9901;PD5746,9911;PD5764,9914;PD5781,9911;PD5793,9902;PD5801,9888;PD5804,9870;PD5804,9863;PU5765,9901;PD5753,9900;PD5745,9894;PD5739,9886;PD5736,9875;PD5789,9875;PD5787,9886;PD5782,9894;PD5775,9900;PD5765,9901;PU5869,9868;PD5904,9821;PD5886,9821;PD5860,9857;PD5834,9821;PD5816,9821;PD5851,9869;PD5819,9912;PD5837,9912;PD5861,9880;PD5885,9912;PD5902,9912;PD5869,9868;PU5942,9912;PD5973,9912;PD5973,9900;PD5942,9900;PD5942,9851;PD5943,9842;PD5945,9837;PD5950,9835;PD5957,9834;PD5973,9834;PD5973,9821;PD5957,9821;PD5943,9823;PD5934,9828;PD5929,9837;PD5927,9851;PD5927,9900;PD5916,9900;PD5916,9912;PD5927,9912;PD5927,9938;PD5942,9938;PD5942,9912;PU1129,9031;PD2258,9031;PD2258,7902;PD1129,7902;PD1129,9031;PU3387,9031;PD5644,9031;PD5644,7902;PD3387,7902;PD3387,9031;PU7281,9031;PD8524,9031;PD8576,9029;PD8626,9022;PD8675,9011;PD8721,8997;PD8766,8978;PD8807,8956;PD8846,8931;PD8882,8902;PD8915,8871;PD8944,8837;PD8970,8801;PD8991,8762;PD9008,8722;PD9021,8680;PD9028,8636;PD9031,8591;PD9031,8342;PD9028,8297;PD9021,8253;PD9008,8211;PD8991,8171;PD8970,8132;PD8944,8096;PD8915,8062;PD8882,8031;PD8846,8003;PD8807,7977;PD8766,7955;PD8721,7937;PD8675,7922;PD8626,7911;PD8576,7904;PD8524,7902;PD7281,7902;PD7229,7904;PD7178,7911;PD7130,7922;PD7083,7937;PD7039,7955;PD6997,7977;PD6958,8003;PD6922,8031;PD6889,8062;PD6860,8096;PD6835,8132;PD6813,8171;PD6796,8211;PD6784,8253;PD6776,8297;PD6773,8342;PD6773,8591;PD6776,8636;PD6784,8680;PD6796,8722;PD6813,8762;PD6835,8801;PD6860,8837;PD6889,8871;PD6922,8902;PD6958,8931;PD6997,8956;PD7039,8978;PD7083,8997;PD7130,9011;PD7178,9022;PD7229,9029;PD7281,9031;PU; \ No newline at end of file diff --git a/share/extensions/tests/data/refs/polyhedron_3d__--show__edg__--obj__oct__--r1_ax__z__--r1_ang__45__--th__4.out b/share/extensions/tests/data/refs/polyhedron_3d__--show__edg__--obj__oct__--r1_ax__z__--r1_ang__45__--th__4.out new file mode 100644 index 0000000..1327760 --- /dev/null +++ b/share/extensions/tests/data/refs/polyhedron_3d__--show__edg__--obj__oct__--r1_ax__z__--r1_ang__45__--th__4.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/polyhedron_3d__--show__fce__--obj__cube__--r1_ax__x__--r1_ang__45__--r2_ax__y__--r2_ang__45.out b/share/extensions/tests/data/refs/polyhedron_3d__--show__fce__--obj__cube__--r1_ax__x__--r1_ang__45__--r2_ax__y__--r2_ang__45.out new file mode 100644 index 0000000..5b78bb8 --- /dev/null +++ b/share/extensions/tests/data/refs/polyhedron_3d__--show__fce__--obj__cube__--r1_ax__x__--r1_ang__45__--r2_ax__y__--r2_ang__45.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/polyhedron_3d__--show__fce__--obj__cube__--r1_ax__y__--r1_ang__45__--z_sort__cent.out b/share/extensions/tests/data/refs/polyhedron_3d__--show__fce__--obj__cube__--r1_ax__y__--r1_ang__45__--z_sort__cent.out new file mode 100644 index 0000000..703447a --- /dev/null +++ b/share/extensions/tests/data/refs/polyhedron_3d__--show__fce__--obj__cube__--r1_ax__y__--r1_ang__45__--z_sort__cent.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/polyhedron_3d__--show__fce__--obj__cube__--r1_ax__z__--r1_ang__45__--z_sort__max.out b/share/extensions/tests/data/refs/polyhedron_3d__--show__fce__--obj__cube__--r1_ax__z__--r1_ang__45__--z_sort__max.out new file mode 100644 index 0000000..a7e0a5d --- /dev/null +++ b/share/extensions/tests/data/refs/polyhedron_3d__--show__fce__--obj__cube__--r1_ax__z__--r1_ang__45__--z_sort__max.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/polyhedron_3d__--show__vtx__--obj__methane.out b/share/extensions/tests/data/refs/polyhedron_3d__--show__vtx__--obj__methane.out new file mode 100644 index 0000000..9870376 --- /dev/null +++ b/share/extensions/tests/data/refs/polyhedron_3d__--show__vtx__--obj__methane.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/polyhedron_3d__31c852a9dcfffc92123ff370cba34361.out b/share/extensions/tests/data/refs/polyhedron_3d__31c852a9dcfffc92123ff370cba34361.out new file mode 100644 index 0000000..dc24f34 --- /dev/null +++ b/share/extensions/tests/data/refs/polyhedron_3d__31c852a9dcfffc92123ff370cba34361.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + diff --git a/share/extensions/tests/data/refs/prepare_file_save_as.out b/share/extensions/tests/data/refs/prepare_file_save_as.out new file mode 100644 index 0000000..999f85f --- /dev/null +++ b/share/extensions/tests/data/refs/prepare_file_save_as.out @@ -0,0 +1,115 @@ + + + + + image/svg+xml + + + + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/previous_glyph_layer.out b/share/extensions/tests/data/refs/previous_glyph_layer.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/previous_glyph_layer__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/previous_glyph_layer__--id__p1__--id__r3.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/print_win32_vector.out b/share/extensions/tests/data/refs/print_win32_vector.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/print_win32_vector__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/print_win32_vector__--id__p1__--id__r3.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/printing_marks.out b/share/extensions/tests/data/refs/printing_marks.out new file mode 100644 index 0000000..f720f14 --- /dev/null +++ b/share/extensions/tests/data/refs/printing_marks.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/printing_marks__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/printing_marks__--id__p1__--id__r3.out new file mode 100644 index 0000000..f720f14 --- /dev/null +++ b/share/extensions/tests/data/refs/printing_marks__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/ps_input__test__eps.out b/share/extensions/tests/data/refs/ps_input__test__eps.out new file mode 100644 index 0000000..284919f --- /dev/null +++ b/share/extensions/tests/data/refs/ps_input__test__eps.out @@ -0,0 +1,81 @@ +%PDF-1.4 +%Çì¢ +5 0 obj +<> +stream +xœ]K1†÷œ‚ ô9C7 ÑqÑ1½"ÛÆÒ–ÂìÈ$ÈÍú[68]2®oØ¡±; ãë†W|SŠÎ‡`ù »ì›E²(¾VDAبd²„ÔÙ_ЃÔ{Ê5;Jª“§¨X ³;î:P¥°˜\2m¯¿]–9ĈT09/3ÂSŒ{—b»öþµ¹}²Q؆þÝ­Àão×;œÍ>ÇœAÌendstream +endobj +6 0 obj +171 +endobj +4 0 obj +<> +/Contents 5 0 R +>> +endobj +3 0 obj +<< /Type /Pages /Kids [ +4 0 R +] /Count 1 +>> +endobj +1 0 obj +<> +endobj +7 0 obj +<>endobj +8 0 obj +<> +endobj +9 0 obj +<>stream + + + + + +2019-04-26T21:40:29-04:00 +2019-04-26T21:40:29-04:00 +cairo 1.15.10 (http://cairographics.org) + +Untitled + + + + + +endstream +endobj +2 0 obj +<>endobj +xref +0 10 +0000000000 65535 f +0000000465 00000 n +0000002041 00000 n +0000000406 00000 n +0000000275 00000 n +0000000015 00000 n +0000000256 00000 n +0000000529 00000 n +0000000570 00000 n +0000000599 00000 n +trailer +<< /Size 10 /Root 1 0 R /Info 2 0 R +/ID [] +>> +startxref +2217 +%%EOF diff --git a/share/extensions/tests/data/refs/ps_input__test__ps.out b/share/extensions/tests/data/refs/ps_input__test__ps.out new file mode 100644 index 0000000..a85a26e Binary files /dev/null and b/share/extensions/tests/data/refs/ps_input__test__ps.out differ diff --git a/share/extensions/tests/data/refs/render_barcode__--type__Code93__--text__3332222.out b/share/extensions/tests/data/refs/render_barcode__--type__Code93__--text__3332222.out new file mode 100644 index 0000000..aa4ba53 --- /dev/null +++ b/share/extensions/tests/data/refs/render_barcode__--type__Code93__--text__3332222.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + 3332222 + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_barcode__--type__Ean2__--text__55.out b/share/extensions/tests/data/refs/render_barcode__--type__Ean2__--text__55.out new file mode 100644 index 0000000..5795ee5 --- /dev/null +++ b/share/extensions/tests/data/refs/render_barcode__--type__Ean2__--text__55.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + 5 5 + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_barcode__--type__Upce__--text__123456.out b/share/extensions/tests/data/refs/render_barcode__--type__Upce__--text__123456.out new file mode 100644 index 0000000..334fb2f --- /dev/null +++ b/share/extensions/tests/data/refs/render_barcode__--type__Upce__--text__123456.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + 0 123456 + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__rect8x32__--text__1234Foo.out b/share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__rect8x32__--text__1234Foo.out new file mode 100644 index 0000000..febc9b6 --- /dev/null +++ b/share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__rect8x32__--text__1234Foo.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__sq10.out b/share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__sq10.out new file mode 100644 index 0000000..b270ac6 --- /dev/null +++ b/share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__sq10.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__sq144__--text__HelloTest.out b/share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__sq144__--text__HelloTest.out new file mode 100644 index 0000000..1bdb7da --- /dev/null +++ b/share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__sq144__--text__HelloTest.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__sq96__--text__Sunshine.out b/share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__sq96__--text__Sunshine.out new file mode 100644 index 0000000..be4c31f --- /dev/null +++ b/share/extensions/tests/data/refs/render_barcode_datamatrix__--symbol__sq96__--text__Sunshine.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_barcode_qrcode__--text__0123456789__--typenumber__0.out b/share/extensions/tests/data/refs/render_barcode_qrcode__--text__0123456789__--typenumber__0.out new file mode 100644 index 0000000..b9eaf54 --- /dev/null +++ b/share/extensions/tests/data/refs/render_barcode_qrcode__--text__0123456789__--typenumber__0.out @@ -0,0 +1,2 @@ + + diff --git a/share/extensions/tests/data/refs/render_barcode_qrcode__--text__Blue__Front__Yard__--typenumber__3__--correctionlevel__1.out b/share/extensions/tests/data/refs/render_barcode_qrcode__--text__Blue__Front__Yard__--typenumber__3__--correctionlevel__1.out new file mode 100644 index 0000000..732616a --- /dev/null +++ b/share/extensions/tests/data/refs/render_barcode_qrcode__--text__Blue__Front__Yard__--typenumber__3__--correctionlevel__1.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_barcode_qrcode__--text__BreadRolls__--typenumber__2__--encoding__utf8.out b/share/extensions/tests/data/refs/render_barcode_qrcode__--text__BreadRolls__--typenumber__2__--encoding__utf8.out new file mode 100644 index 0000000..a84e328 --- /dev/null +++ b/share/extensions/tests/data/refs/render_barcode_qrcode__--text__BreadRolls__--typenumber__2__--encoding__utf8.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_barcode_qrcode__--text__ThingOne__--drawtype__symbol__--correctionlevel__2__--symbolid__AirTransportation_Inv.out b/share/extensions/tests/data/refs/render_barcode_qrcode__--text__ThingOne__--drawtype__symbol__--correctionlevel__2__--symbolid__AirTransportation_Inv.out new file mode 100644 index 0000000..50d5c0d --- /dev/null +++ b/share/extensions/tests/data/refs/render_barcode_qrcode__--text__ThingOne__--drawtype__symbol__--correctionlevel__2__--symbolid__AirTransportation_Inv.out @@ -0,0 +1,12 @@ + + + + Air Transportation + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_barcode_qrcode__--text__Waterfall__--typenumber__1__--drawtype__circle.out b/share/extensions/tests/data/refs/render_barcode_qrcode__--text__Waterfall__--typenumber__1__--drawtype__circle.out new file mode 100644 index 0000000..84f55c6 --- /dev/null +++ b/share/extensions/tests/data/refs/render_barcode_qrcode__--text__Waterfall__--typenumber__1__--drawtype__circle.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_barcode_qrcode__--text__groupid__--groupid__testid.out b/share/extensions/tests/data/refs/render_barcode_qrcode__--text__groupid__--groupid__testid.out new file mode 100644 index 0000000..238624b --- /dev/null +++ b/share/extensions/tests/data/refs/render_barcode_qrcode__--text__groupid__--groupid__testid.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_gear_rack.out b/share/extensions/tests/data/refs/render_gear_rack.out new file mode 100644 index 0000000..3c89abc --- /dev/null +++ b/share/extensions/tests/data/refs/render_gear_rack.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_gear_rack__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/render_gear_rack__--id__p1__--id__r3.out new file mode 100644 index 0000000..3c89abc --- /dev/null +++ b/share/extensions/tests/data/refs/render_gear_rack__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_gears.out b/share/extensions/tests/data/refs/render_gears.out new file mode 100644 index 0000000..f6cead9 --- /dev/null +++ b/share/extensions/tests/data/refs/render_gears.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/render_gears__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/render_gears__--id__p1__--id__r3.out new file mode 100644 index 0000000..f6cead9 --- /dev/null +++ b/share/extensions/tests/data/refs/render_gears__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/replace_font__--action__find_replace__--fr_find__sans-serif__--fr_replace__monospace.out b/share/extensions/tests/data/refs/replace_font__--action__find_replace__--fr_find__sans-serif__--fr_replace__monospace.out new file mode 100644 index 0000000..30b778d --- /dev/null +++ b/share/extensions/tests/data/refs/replace_font__--action__find_replace__--fr_find__sans-serif__--fr_replace__monospace.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/replace_font__--action__list_only.out b/share/extensions/tests/data/refs/replace_font__--action__list_only.out new file mode 100644 index 0000000..5883134 --- /dev/null +++ b/share/extensions/tests/data/refs/replace_font__--action__list_only.out @@ -0,0 +1 @@ +Found the following font only: sans-serif diff --git a/share/extensions/tests/data/refs/restack__--tab__positional__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/restack__--tab__positional__--id__p1__--id__r3.out new file mode 100644 index 0000000..7604bd6 --- /dev/null +++ b/share/extensions/tests/data/refs/restack__--tab__positional__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/restack__--tab__z_order__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/restack__--tab__z_order__--id__p1__--id__r3.out new file mode 100644 index 0000000..7604bd6 --- /dev/null +++ b/share/extensions/tests/data/refs/restack__--tab__z_order__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/rtree.out b/share/extensions/tests/data/refs/rtree.out new file mode 100644 index 0000000..eeef67a --- /dev/null +++ b/share/extensions/tests/data/refs/rtree.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/rubberstretch__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/rubberstretch__--id__p1__--id__r3.out new file mode 100644 index 0000000..97961d0 --- /dev/null +++ b/share/extensions/tests/data/refs/rubberstretch__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/scribus_export_pdf.out b/share/extensions/tests/data/refs/scribus_export_pdf.out new file mode 100644 index 0000000..463561d Binary files /dev/null and b/share/extensions/tests/data/refs/scribus_export_pdf.out differ diff --git a/share/extensions/tests/data/refs/setup_typography_canvas.out b/share/extensions/tests/data/refs/setup_typography_canvas.out new file mode 100644 index 0000000..9148166 --- /dev/null +++ b/share/extensions/tests/data/refs/setup_typography_canvas.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/setup_typography_canvas__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/setup_typography_canvas__--id__p1__--id__r3.out new file mode 100644 index 0000000..9148166 --- /dev/null +++ b/share/extensions/tests/data/refs/setup_typography_canvas__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/sk1_output.out b/share/extensions/tests/data/refs/sk1_output.out new file mode 100644 index 0000000..59afc69 --- /dev/null +++ b/share/extensions/tests/data/refs/sk1_output.out @@ -0,0 +1,126 @@ +##sK1 1 2 +document() +layout((800,800),0) +grid((0,0,2.83465,2.83465),0,("RGB",0.83,0.87,0.91),'Grid') +page() +layer('New Layer',1,1,0,0,("RGB",0.196,0.314,0.635)) +G() +fp(("RGB",1.0,0.0,0.0)) +le() +lw(0.895418) +r(198.953,0,0,-161.198,521.047,516.859) +G_() +G() +fp(("RGB",0.0,0.0,0.502)) +le() +lw(0.283286) +e(40,0,0,-40,120,440) +lp(("RGB",1.0,0.0,0.0)) +lw(12.8) +e(80,0,0,-40,320,440) +fp(("RGB",1.0,1.0,0.0)) +lp(("RGB",0.0,0.502,0.0)) +lw(12.8) +b() +bs(626.469,417.741,0) +bs(560,440,0) +bs(626.469,417.741,0) +bC() +lp(("RGB",0.0,0.0,0.0)) +lw(8) +b() +bs(80,320,0) +bs(160,240,0) +bs(240,320,0) +bs(320,240,0) +lp(("RGB",0.0,0.0,0.0)) +lw(8) +b() +bs(400,320,0) +bc(400,320,400,240,480,240,0) +bc(560,240,480,320,560,320,0) +bc(640,320,640,240,640,240,0) +fp(("RGB",1.0,1.0,0.0)) +lp(("RGB",0.0,0.502,0.0)) +lw(8) +b() +bs(195.867,88.688,0) +bs(159.595,95.1955,0) +bs(133.672,69.0055,0) +bs(128.653,105.512,0) +bs(95.7337,122.074,0) +bs(128.903,138.129,0) +bs(134.481,174.554,0) +bs(160,147.97,0) +bs(196.367,153.921,0) +bs(178.969,121.436,0) +bs(195.867,88.688,0) +bC() +fp(("RGB",1.0,1.0,0.0)) +lp(("RGB",0.0,0.502,0.0)) +lw(8) +b() +bs(355.867,86.291,0) +bs(319.595,92.7986,0) +bs(293.672,66.6085,0) +bs(288.653,103.115,0) +bs(255.734,119.677,0) +bs(288.903,135.732,0) +bs(294.481,172.157,0) +bs(320,145.573,0) +bs(356.367,151.524,0) +bs(338.969,119.039,0) +bs(355.867,86.291,0) +bC() +G_() +G() +fp(("RGB",0.0,0.0,0.0)) +le() +lw(0.283286) +Fn('BitstreamVeraSans-Roman') +Fs(14.6667) +txt('\\u048\\u065\\u06c\\u06c\\u06f\\u020\\u057\\u06f\\u072\\u06c\\u064',(0.8,0,0,0.8,80,720),0,0,1,1,1) +fp(("RGB",0.0,0.0,0.0,0.5)) +le() +lw(0.283286) +r(56.0001,0,0,-55.4909,320,730.381) +fp(("RGB",0.0,0.0,0.0)) +le() +lw(0.283286) +Fn('BitstreamVeraSans-Roman') +Fs(14.6667) +txt('\\u055\\u050\\u050\\u045\\u052',(0.8,0,0,0.8,160,720),0,0,1,1,1) +fp(("RGB",0.0,0.0,0.0)) +le() +lw(0.283286) +Fn('BitstreamVeraSans-Roman') +Fs(10.5833) +txt('\\u04d\\u075\\u06c\\u074\\u069\\u020\\u06c\\u069\\u06e\\u065\\u074\\u065\\u078\\u074\\u046\\u04f\\u04f',(0.8,0,0,0.8,240,720),0,0,1,1,1) +G() +fp(("RGB",0.0,0.0,0.0)) +le() +lw(0.283286) +Fn('BitstreamVeraSans-Roman') +Fs(10.5833) +txt('\\u047\\u072\\u06f\\u075\\u070\\u065\\u064',(0.8,0,0,0.8,400,720),0,0,1,1,1) +fp(("RGB",0.0,0.0,0.0)) +le() +lw(0.283286) +Fn('BitstreamVeraSans-Roman') +Fs(14.6667) +txt('\\u074\\u065\\u078\\u074',(0.8,0,0,0.8,400,696),0,0,1,1,1) +G_() +fp(("RGB",0.0,0.0,0.502)) +le() +lw(0.283286) +r(80,0,0,-80,80,640) +lp(("RGB",1.0,0.0,0.0)) +lw(12.8) +r(160,0,0,-80,240,640) +fp(("RGB",1.0,1.0,0.0)) +lp(("RGB",0.0,0.502,0.0)) +lw(12.8) +r(160,0,0,-80,480,640,0.224716,0.389508) +G_() +masterlayer('MasterLayer 1',1,1,0,0,("RGB",0.196,0.314,0.635)) +guidelayer('Guide Lines',1,0,0,1,("RGB",0.0,0.3,1.0)) diff --git a/share/extensions/tests/data/refs/spirograph.out b/share/extensions/tests/data/refs/spirograph.out new file mode 100644 index 0000000..cc27d4d --- /dev/null +++ b/share/extensions/tests/data/refs/spirograph.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/spirograph__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/spirograph__--id__p1__--id__r3.out new file mode 100644 index 0000000..cc27d4d --- /dev/null +++ b/share/extensions/tests/data/refs/spirograph__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/straightseg.out b/share/extensions/tests/data/refs/straightseg.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/straightseg__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/straightseg__--id__p1__--id__r3.out new file mode 100644 index 0000000..f05ae54 --- /dev/null +++ b/share/extensions/tests/data/refs/straightseg__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/svgcalendar.out b/share/extensions/tests/data/refs/svgcalendar.out new file mode 100644 index 0000000..029c6cd --- /dev/null +++ b/share/extensions/tests/data/refs/svgcalendar.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + +2019JanuarySunMonTueWedThuFriSat303112345678910111213141516171819202122232425262728293031123456789FebruarySunMonTueWedThuFriSat272829303112345678910111213141516171819202122232425262728123456789MarchSunMonTueWedThuFriSat242526272812345678910111213141516171819202122232425262728293031123456AprilSunMonTueWedThuFriSat311234567891011121314151617181920212223242526272829301234567891011MaySunMonTueWedThuFriSat2829301234567891011121314151617181920212223242526272829303112345678JuneSunMonTueWedThuFriSat262728293031123456789101112131415161718192021222324252627282930123456JulySunMonTueWedThuFriSat301234567891011121314151617181920212223242526272829303112345678910AugustSunMonTueWedThuFriSat28293031123456789101112131415161718192021222324252627282930311234567SeptemberSunMonTueWedThuFriSat123456789101112131415161718192021222324252627282930123456789101112OctoberSunMonTueWedThuFriSat293012345678910111213141516171819202122232425262728293031123456789NovemberSunMonTueWedThuFriSat27282930311234567891011121314151617181920212223242526272829301234567DecemberSunMonTueWedThuFriSat123456789101112131415161718192021222324252627282930311234567891011 \ No newline at end of file diff --git a/share/extensions/tests/data/refs/svgfont2layers__--count__3.out b/share/extensions/tests/data/refs/svgfont2layers__--count__3.out new file mode 100644 index 0000000..ec11f1d --- /dev/null +++ b/share/extensions/tests/data/refs/svgfont2layers__--count__3.out @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/tar_layers.out b/share/extensions/tests/data/refs/tar_layers.out new file mode 100644 index 0000000..50ce2ee Binary files /dev/null and b/share/extensions/tests/data/refs/tar_layers.out differ diff --git a/share/extensions/tests/data/refs/tar_layers__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/tar_layers__--id__p1__--id__r3.out new file mode 100644 index 0000000..50ce2ee Binary files /dev/null and b/share/extensions/tests/data/refs/tar_layers__--id__p1__--id__r3.out differ diff --git a/share/extensions/tests/data/refs/template__--size__100x50__--grid__true__--orientation__horizontal.out b/share/extensions/tests/data/refs/template__--size__100x50__--grid__true__--orientation__horizontal.out new file mode 100644 index 0000000..6a0bbf9 --- /dev/null +++ b/share/extensions/tests/data/refs/template__--size__100x50__--grid__true__--orientation__horizontal.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/template__--size__100x50__--grid__true__--orientation__vertical.out b/share/extensions/tests/data/refs/template__--size__100x50__--grid__true__--orientation__vertical.out new file mode 100644 index 0000000..ad7607e --- /dev/null +++ b/share/extensions/tests/data/refs/template__--size__100x50__--grid__true__--orientation__vertical.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/template__--size__5mmx15mm__--background__black__--noborder__true.out b/share/extensions/tests/data/refs/template__--size__5mmx15mm__--background__black__--noborder__true.out new file mode 100644 index 0000000..5b92e4d --- /dev/null +++ b/share/extensions/tests/data/refs/template__--size__5mmx15mm__--background__black__--noborder__true.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/template__--size__custom__--width__100__--height__100__--unit__in.out b/share/extensions/tests/data/refs/template__--size__custom__--width__100__--height__100__--unit__in.out new file mode 100644 index 0000000..fb3d8ad --- /dev/null +++ b/share/extensions/tests/data/refs/template__--size__custom__--width__100__--height__100__--unit__in.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/template_dvd_cover__-s__10__-b__10.out b/share/extensions/tests/data/refs/template_dvd_cover__-s__10__-b__10.out new file mode 100644 index 0000000..131054a --- /dev/null +++ b/share/extensions/tests/data/refs/template_dvd_cover__-s__10__-b__10.out @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/template_seamless_pattern__--width__100__--height__100.out b/share/extensions/tests/data/refs/template_seamless_pattern__--width__100__--height__100.out new file mode 100644 index 0000000..b0a40fe --- /dev/null +++ b/share/extensions/tests/data/refs/template_seamless_pattern__--width__100__--height__100.out @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + Seamless pattern + Use the layers "Pattern Foreground" and "Pattern Background" on the pattern page to create your design. The separation into two layers will make it easier for you to create and edit overlapping content like a foreground drawing with a background fill. The layer named "Pattern" is for using the seamless pattern, copying it to other documents, adding opacity etc. Select the group on the page, and use Object->Pattern->Objects to Pattern to convert your creation into a fill pattern. The layer "Preview Background" provides an easy way to preview your creation if it contains transparency.Changing this layer's visibility will not alter the pattern. If an object is moved outside the pattern/page limits, it will be difficult to select it. To move it back onto the page, select the object using the rubberband selection (click and drag a selection box) with the selection tool.Then move it back onto the page, using the arrow keys or the "Align and Distribute" dialog (Shift+Ctrl+A). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Seamless Pattern + Seamless Pattern + Seamless Pattern + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/test_color_list.out b/share/extensions/tests/data/refs/test_color_list.out new file mode 100644 index 0000000..2c7f24c --- /dev/null +++ b/share/extensions/tests/data/refs/test_color_list.out @@ -0,0 +1,9 @@ +7: #75507b +7: red +7: #729fcf +7: #3465a4 +6: #cc0000 +5: #000000 +4: #5c3566 +4: blue +3: #2e3436 diff --git a/share/extensions/tests/data/refs/test_color_list__--id__color_svg.out b/share/extensions/tests/data/refs/test_color_list__--id__color_svg.out new file mode 100644 index 0000000..1ceffa6 --- /dev/null +++ b/share/extensions/tests/data/refs/test_color_list__--id__color_svg.out @@ -0,0 +1,9 @@ +5: #75507b +5: red +5: #729fcf +5: #3465a4 +4: #cc0000 +3: #5c3566 +3: blue +3: #000000 +2: #2e3436 diff --git a/share/extensions/tests/data/refs/test_color_list__--id__r1.out b/share/extensions/tests/data/refs/test_color_list__--id__r1.out new file mode 100644 index 0000000..7afd4b9 --- /dev/null +++ b/share/extensions/tests/data/refs/test_color_list__--id__r1.out @@ -0,0 +1,3 @@ +1: #5c3566 +1: #75507b +1: red diff --git a/share/extensions/tests/data/refs/test_color_list__--id__r1__--id__r2.out b/share/extensions/tests/data/refs/test_color_list__--id__r1__--id__r2.out new file mode 100644 index 0000000..e93ebc8 --- /dev/null +++ b/share/extensions/tests/data/refs/test_color_list__--id__r1__--id__r2.out @@ -0,0 +1,9 @@ +3: #75507b +3: red +3: #729fcf +3: #3465a4 +2: #5c3566 +2: blue +2: #cc0000 +1: #2e3436 +1: #000000 diff --git a/share/extensions/tests/data/refs/test_color_list__--id__r2.out b/share/extensions/tests/data/refs/test_color_list__--id__r2.out new file mode 100644 index 0000000..0803b0f --- /dev/null +++ b/share/extensions/tests/data/refs/test_color_list__--id__r2.out @@ -0,0 +1,6 @@ +1: #5c3566 +1: #75507b +1: red +1: #729fcf +1: #3465a4 +1: blue diff --git a/share/extensions/tests/data/refs/test_color_list__--id__r3.out b/share/extensions/tests/data/refs/test_color_list__--id__r3.out new file mode 100644 index 0000000..168ffca --- /dev/null +++ b/share/extensions/tests/data/refs/test_color_list__--id__r3.out @@ -0,0 +1,8 @@ +2: red +1: #5c3566 +1: #75507b +1: #729fcf +1: #3465a4 +1: blue +1: #2e3436 +1: #cc0000 diff --git a/share/extensions/tests/data/refs/test_color_list__--id__r4.out b/share/extensions/tests/data/refs/test_color_list__--id__r4.out new file mode 100644 index 0000000..77a872b --- /dev/null +++ b/share/extensions/tests/data/refs/test_color_list__--id__r4.out @@ -0,0 +1,9 @@ +2: #75507b +2: red +2: #729fcf +2: #3465a4 +2: #cc0000 +1: #5c3566 +1: blue +1: #2e3436 +1: #000000 diff --git a/share/extensions/tests/data/refs/text_braille.out b/share/extensions/tests/data/refs/text_braille.out new file mode 100644 index 0000000..77f7c60 --- /dev/null +++ b/share/extensions/tests/data/refs/text_braille.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + ⠋⠕⠗⠍⠁⠞⠱ ⠏⠝⠛ +⠙⠏⠊⠱ ⠔⠖ +⠇⠁⠽⠕⠥⠞⠤⠙⠊⠎⠏⠕⠎⠊⠞⠊⠕⠝⠱ ⠃⠛⠤⠑⠇⠤⠝⠕⠗⠑⠏⠑⠁⠞ +⠇⠁⠽⠕⠥⠞⠤⠏⠕⠎⠊⠞⠊⠕⠝⠤⠁⠝⠉⠓⠕⠗⠱ ⠞⠇ + + + + + + + + + + + + + ⠓⠑⠇⠇⠕ ⠺⠕⠗⠇⠙ + ⠋⠇⠕⠺ ⠞⠑⠭⠞ ⠺⠓⠊⠉⠓ ⠺⠗⠁⠏⠎ ⠥⠏⠏⠑⠗ + ⠍⠥⠇⠞⠊ ⠇⠊⠝⠑⠞⠑⠭⠞⠋⠕⠕ + + ⠛⠗⠕⠥⠏⠑⠙ + ⠞⠑⠭⠞ + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/text_extract__--direction__bt.out b/share/extensions/tests/data/refs/text_extract__--direction__bt.out new file mode 100644 index 0000000..6c3dac7 --- /dev/null +++ b/share/extensions/tests/data/refs/text_extract__--direction__bt.out @@ -0,0 +1,8 @@ +text +Grouped +Multi line +text +FOO +flow text which wraps +UPPER +Hello World diff --git a/share/extensions/tests/data/refs/text_extract__--direction__lr.out b/share/extensions/tests/data/refs/text_extract__--direction__lr.out new file mode 100644 index 0000000..2eec938 --- /dev/null +++ b/share/extensions/tests/data/refs/text_extract__--direction__lr.out @@ -0,0 +1,8 @@ +Grouped +text +Hello World +UPPER +Multi line +text +FOO +flow text which wraps diff --git a/share/extensions/tests/data/refs/text_extract__--direction__rl.out b/share/extensions/tests/data/refs/text_extract__--direction__rl.out new file mode 100644 index 0000000..bc3ed2e --- /dev/null +++ b/share/extensions/tests/data/refs/text_extract__--direction__rl.out @@ -0,0 +1,8 @@ +flow text which wraps +Multi line +text +FOO +UPPER +Hello World +text +Grouped diff --git a/share/extensions/tests/data/refs/text_extract__--direction__tb.out b/share/extensions/tests/data/refs/text_extract__--direction__tb.out new file mode 100644 index 0000000..3634b71 --- /dev/null +++ b/share/extensions/tests/data/refs/text_extract__--direction__tb.out @@ -0,0 +1,8 @@ +Hello World +UPPER +flow text which wraps +Multi line +text +FOO +Grouped +text diff --git a/share/extensions/tests/data/refs/text_flipcase.out b/share/extensions/tests/data/refs/text_flipcase.out new file mode 100644 index 0000000..cd9ebc3 --- /dev/null +++ b/share/extensions/tests/data/refs/text_flipcase.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + FORMAT: PNG +DPI: 96 +LAYOUT-DISPOSITION: BG-EL-NOREPEAT +LAYOUT-POSITION-ANCHOR: TL + + + + + + + + + + + + + hELLO wORLD + FLOW TEXT WHICH WRAPS upper + mULTI LINETEXTfoo + + gROUPED + TEXT + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/text_lowercase.out b/share/extensions/tests/data/refs/text_lowercase.out new file mode 100644 index 0000000..b492bd3 --- /dev/null +++ b/share/extensions/tests/data/refs/text_lowercase.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + hello world + flow text which wraps upper + multi linetextfoo + + grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/text_merge.out b/share/extensions/tests/data/refs/text_merge.out new file mode 100644 index 0000000..4ee2b0e --- /dev/null +++ b/share/extensions/tests/data/refs/text_merge.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/text_randomcase.out b/share/extensions/tests/data/refs/text_randomcase.out new file mode 100644 index 0000000..7291480 --- /dev/null +++ b/share/extensions/tests/data/refs/text_randomcase.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + fOrmAT: pNg +DpI: 96 +lAyOUT-DiSpOSiTioN: BG-eL-nOrEPeAt +LayouT-pOsitIOn-ANcHoR: tl + + + + + + + + + + + + + HEllO wOrLD + fLoW TexT WhIch wraPs upPEr + mUlTi lInEtEXtFoo + + GrouPeD + teXt + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/text_sentencecase.out b/share/extensions/tests/data/refs/text_sentencecase.out new file mode 100644 index 0000000..9968709 --- /dev/null +++ b/share/extensions/tests/data/refs/text_sentencecase.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + Format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello world + Flow text which wraps Upper + Multi linetextfoo + + Grouped + Text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/text_split__--id__t1__--id__t3.out b/share/extensions/tests/data/refs/text_split__--id__t1__--id__t3.out new file mode 100644 index 0000000..0c0e4fd --- /dev/null +++ b/share/extensions/tests/data/refs/text_split__--id__t1__--id__t3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + HelloWorldMultilinetextFOO + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/text_titlecase.out b/share/extensions/tests/data/refs/text_titlecase.out new file mode 100644 index 0000000..1e0b50f --- /dev/null +++ b/share/extensions/tests/data/refs/text_titlecase.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + Format: Png +Dpi: 96 +Layout-disposition: Bg-el-norepeat +Layout-position-anchor: Tl + + + + + + + + + + + + + Hello World + Flow Text Which Wraps Upper + Multi LineTextFoo + + Grouped + Text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/text_uppercase.out b/share/extensions/tests/data/refs/text_uppercase.out new file mode 100644 index 0000000..9532266 --- /dev/null +++ b/share/extensions/tests/data/refs/text_uppercase.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + FORMAT: PNG +DPI: 96 +LAYOUT-DISPOSITION: BG-EL-NOREPEAT +LAYOUT-POSITION-ANCHOR: TL + + + + + + + + + + + + + HELLO WORLD + FLOW TEXT WHICH WRAPS UPPER + MULTI LINETEXTFOO + + GROUPED + TEXT + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/triangle.out b/share/extensions/tests/data/refs/triangle.out new file mode 100644 index 0000000..82ff731 --- /dev/null +++ b/share/extensions/tests/data/refs/triangle.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/triangle__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/triangle__--id__p1__--id__r3.out new file mode 100644 index 0000000..82ff731 --- /dev/null +++ b/share/extensions/tests/data/refs/triangle__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/ungroup_deep.out b/share/extensions/tests/data/refs/ungroup_deep.out new file mode 100644 index 0000000..738cc6b --- /dev/null +++ b/share/extensions/tests/data/refs/ungroup_deep.out @@ -0,0 +1,33 @@ + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + Grouped + text + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/ungroup_deep__--id__layer2.out b/share/extensions/tests/data/refs/ungroup_deep__--id__layer2.out new file mode 100644 index 0000000..41aa594 --- /dev/null +++ b/share/extensions/tests/data/refs/ungroup_deep__--id__layer2.out @@ -0,0 +1,39 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/voronoi2svg__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/voronoi2svg__--id__p1__--id__r3.out new file mode 100644 index 0000000..10d57fc --- /dev/null +++ b/share/extensions/tests/data/refs/voronoi2svg__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/web_interactive_mockup__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/web_interactive_mockup__--id__p1__--id__r3.out new file mode 100644 index 0000000..e4c32a9 --- /dev/null +++ b/share/extensions/tests/data/refs/web_interactive_mockup__--id__p1__--id__r3.out @@ -0,0 +1,259 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/web_set_att__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/web_set_att__--id__p1__--id__r3.out new file mode 100644 index 0000000..d0d7ad7 --- /dev/null +++ b/share/extensions/tests/data/refs/web_set_att__--id__p1__--id__r3.out @@ -0,0 +1,259 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + diff --git a/share/extensions/tests/data/refs/web_transmit_att__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/web_transmit_att__--id__p1__--id__r3.out new file mode 100644 index 0000000..09b71c3 --- /dev/null +++ b/share/extensions/tests/data/refs/web_transmit_att__--id__p1__--id__r3.out @@ -0,0 +1,259 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + diff --git a/share/extensions/tests/data/refs/webslicer_create_group__--id__slicerect1.out b/share/extensions/tests/data/refs/webslicer_create_group__--id__slicerect1.out new file mode 100644 index 0000000..ec3ec4c --- /dev/null +++ b/share/extensions/tests/data/refs/webslicer_create_group__--id__slicerect1.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/webslicer_create_rect.out b/share/extensions/tests/data/refs/webslicer_create_rect.out new file mode 100644 index 0000000..6f576e6 --- /dev/null +++ b/share/extensions/tests/data/refs/webslicer_create_rect.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + format: png + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/webslicer_create_rect__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/webslicer_create_rect__--id__p1__--id__r3.out new file mode 100644 index 0000000..6f576e6 --- /dev/null +++ b/share/extensions/tests/data/refs/webslicer_create_rect__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + format: png + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/webslicer_export__--dir__TMP_DIR.out b/share/extensions/tests/data/refs/webslicer_export__--dir__TMP_DIR.out new file mode 100644 index 0000000..e69de29 diff --git a/share/extensions/tests/data/refs/whirl__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/whirl__--id__p1__--id__r3.out new file mode 100644 index 0000000..20b356f --- /dev/null +++ b/share/extensions/tests/data/refs/whirl__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/wireframe_sphere.out b/share/extensions/tests/data/refs/wireframe_sphere.out new file mode 100644 index 0000000..1513d49 --- /dev/null +++ b/share/extensions/tests/data/refs/wireframe_sphere.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/wireframe_sphere__--id__p1__--id__r3.out b/share/extensions/tests/data/refs/wireframe_sphere__--id__p1__--id__r3.out new file mode 100644 index 0000000..1513d49 --- /dev/null +++ b/share/extensions/tests/data/refs/wireframe_sphere__--id__p1__--id__r3.out @@ -0,0 +1,41 @@ + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/refs/wmf_output.out b/share/extensions/tests/data/refs/wmf_output.out new file mode 100644 index 0000000..13d8ebb Binary files /dev/null and b/share/extensions/tests/data/refs/wmf_output.out differ diff --git a/share/extensions/tests/data/svg/colors.svg b/share/extensions/tests/data/svg/colors.svg new file mode 100644 index 0000000..5a91107 --- /dev/null +++ b/share/extensions/tests/data/svg/colors.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/tests/data/svg/complextransform.test.svg b/share/extensions/tests/data/svg/complextransform.test.svg new file mode 100644 index 0000000..c992c19 --- /dev/null +++ b/share/extensions/tests/data/svg/complextransform.test.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/tests/data/svg/css.svg b/share/extensions/tests/data/svg/css.svg new file mode 100644 index 0000000..9ba3bfe --- /dev/null +++ b/share/extensions/tests/data/svg/css.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + diff --git a/share/extensions/tests/data/svg/curves.svg b/share/extensions/tests/data/svg/curves.svg new file mode 100644 index 0000000..6dc5ad8 --- /dev/null +++ b/share/extensions/tests/data/svg/curves.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + diff --git a/share/extensions/tests/data/svg/dash.svg b/share/extensions/tests/data/svg/dash.svg new file mode 100644 index 0000000..997ba7d --- /dev/null +++ b/share/extensions/tests/data/svg/dash.svg @@ -0,0 +1,34 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/share/extensions/tests/data/svg/default-inkscape-SVG.svg b/share/extensions/tests/data/svg/default-inkscape-SVG.svg new file mode 100644 index 0000000..259e13c --- /dev/null +++ b/share/extensions/tests/data/svg/default-inkscape-SVG.svg @@ -0,0 +1,37 @@ + + + + + + + + + image/svg+xml + + + + + + diff --git a/share/extensions/tests/data/svg/default-inkscape-SVG_scoured.svg b/share/extensions/tests/data/svg/default-inkscape-SVG_scoured.svg new file mode 100644 index 0000000..f46f0b3 --- /dev/null +++ b/share/extensions/tests/data/svg/default-inkscape-SVG_scoured.svg @@ -0,0 +1,12 @@ + + + + + + + image/svg+xml + + + + + diff --git a/share/extensions/tests/data/svg/default-plain-SVG.svg b/share/extensions/tests/data/svg/default-plain-SVG.svg new file mode 100644 index 0000000..9c08847 --- /dev/null +++ b/share/extensions/tests/data/svg/default-plain-SVG.svg @@ -0,0 +1,29 @@ + + + + + + + + + image/svg+xml + + + + + + + diff --git a/share/extensions/tests/data/svg/diff.svg b/share/extensions/tests/data/svg/diff.svg new file mode 100644 index 0000000..cf7f6f3 --- /dev/null +++ b/share/extensions/tests/data/svg/diff.svg @@ -0,0 +1,281 @@ + + + + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + *Hello World* + flaw tux whatch wryps *PER + Multi*textFOO + + Grouped + text + + + + + + + diff --git a/share/extensions/tests/data/svg/edge3d.svg b/share/extensions/tests/data/svg/edge3d.svg new file mode 100644 index 0000000..cfb8570 --- /dev/null +++ b/share/extensions/tests/data/svg/edge3d.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/share/extensions/tests/data/svg/empty.svg b/share/extensions/tests/data/svg/empty.svg new file mode 100644 index 0000000..d6c680b --- /dev/null +++ b/share/extensions/tests/data/svg/empty.svg @@ -0,0 +1,13 @@ + + + + + diff --git a/share/extensions/tests/data/svg/font.svg b/share/extensions/tests/data/svg/font.svg new file mode 100644 index 0000000..cc2b68f --- /dev/null +++ b/share/extensions/tests/data/svg/font.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/tests/data/svg/font_layers.svg b/share/extensions/tests/data/svg/font_layers.svg new file mode 100644 index 0000000..251e025 --- /dev/null +++ b/share/extensions/tests/data/svg/font_layers.svg @@ -0,0 +1,3 @@ + + + diff --git a/share/extensions/tests/data/svg/group_interpolate.svg b/share/extensions/tests/data/svg/group_interpolate.svg new file mode 100644 index 0000000..c3d9f44 --- /dev/null +++ b/share/extensions/tests/data/svg/group_interpolate.svg @@ -0,0 +1,110 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/tests/data/svg/guides.svg b/share/extensions/tests/data/svg/guides.svg new file mode 100644 index 0000000..f67b40d --- /dev/null +++ b/share/extensions/tests/data/svg/guides.svg @@ -0,0 +1,233 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/tests/data/svg/hershey_input.svg b/share/extensions/tests/data/svg/hershey_input.svg new file mode 100755 index 0000000..be62e14 --- /dev/null +++ b/share/extensions/tests/data/svg/hershey_input.svg @@ -0,0 +1,1071 @@ + + + + + + + + + image/svg+xml + + + + + + + This text has both a smaller bounding box, a smaller font size, and a large line spacing. The software must handle unusual cases like this.The real problem is that flowing into a text box (flowroot region) is SVG-renderer dependent-- even different web browsers may render flowed text differently. In this particular case, we're dealing with an Inkscape extension which does not have access to information about how the text was flowed before we got to it, so we essentially have to re-flow it from scratch. This data,is also text. However, it hasmany returnseachonitsownlinein order to makeit take up more verticalspace so that wecan see how it handlesweird edge caseslikethisone. +     We are testing an Inkscape extension called Hershey Text 3.0. The purpose of this extension is to take a block of flowed text, and replace it with stroke-based engraving fonts. We've got a number of different single-stroke fonts built in as well, that help to provide some options. However, we need to test that it handles a variety of different formats of text including different input fonts, alignment, flowed and regular text, variations of font size with a given span, returns, non-breaking spaces, transforms, nested groups and so forth. SVG allows text to be formatted in many different ways.This text is in Layer 1. Text, non-flowed,align left. However, it hasmany returnseachonitsownlinein order to makeit take up more verticalspace so that wecan see how it handlesweird edge caseslikethisone. + Text, non-flowed,align center However, it hasmany returnseachonitsownlinein order to makeit take up more verticalspace so that wecan see how it handlesweird edge caseslikethisone. + Text, non-flowed,align right. However, it hasmany returnseachonitsownlinein order to makeit take up more verticalspace so that wecan see how it handlesweird edge caseslikethisone. + Test: Flowed text, aligned left. Can we handle text that is aligned left, aligned right, centered, justified and so forth? Not all of these cases are necessarily useful for all applications (for example justified with stroke based fonts in the styles of handwriting), but that's not really for us to decide.Rather, we need to handle these cases because the text that is handed to us just might be in some unusual format.. This transformed text... is text with a gap... + Test: Flowed text, aligned center. Can we handle text that is aligned left, aligned right, centered, justified and so forth? Not all of these cases are necessarily useful for all applications (for example justified with stroke based fonts in the styles of handwriting), but that's not really for us to decide.Rather, we need to handle these cases because the text that is handed to us just might be in some unusual format.. Test: Flowed text, aligned right. Can we handle text that is aligned left, aligned right, centered, justified and so forth? Not all of these cases are necessarily useful for all applications (for example justified with stroke based fonts in the styles of handwriting), but that's not really for us to decide.Rather, we need to handle these cases because the text that is handed to us just might be in some unusual format.. Test: Flowed text, justified. Can we handle text that is aligned left, aligned right, centered, justified and so forth? Not all of these cases are necessarily useful for all applications (for example justified with stroke based fonts in the styles of handwriting), but that's not really for us to decide.Rather, we need to handle these cases because the text that is handed to us just might be in some unusual format.. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests of font face mapping: + + + Tests of non-text objects: + + + This is a test of centered text, one long line of text flowed into a box, centered, and in a different font. flowStylesToCheck    Flowed text object with multiple returns before start and also       additional sections with more returnsThat was just one return, since the paragraph above this one.That was two returns, since the paragraph above this one.And that, that was three returns.Just for good measure, this paragraph had a return before the word "just" above. And, we're going to finish with a few returns as well. Bold Serif Text Dear Mike,Thanks for pointing out that we need to handle textthat is shaped like this as well.Have a great day! + Flowed text object with multiple sizes before the start and also       additional sections with more returns and non-breaking spaces, which need to be tested.Hershey Text v 3.0 for Inkscape This is an SVG document provided as an input for testing the Hershey Text extension.The purpose of this new extension is to take a block of flowed text, and replace it with stroke-based engraving fonts. It includes a small selection of such fonts, which need to be in the SVG format. Technically, it can work with any SVG font, even ones that are not stroke-based.This text is in the DOCUMENT ROOT. Scripty text Hand-written text Non-FlowedStylesToCheck + Multi-lineText createdWithout x and ybut with sodipodi:roleattributes instead. + Multi-lineText createdwith tspan elementsand x and ypositions + diff --git a/share/extensions/tests/data/svg/hershey_trivial_input.svg b/share/extensions/tests/data/svg/hershey_trivial_input.svg new file mode 100755 index 0000000..946a616 --- /dev/null +++ b/share/extensions/tests/data/svg/hershey_trivial_input.svg @@ -0,0 +1,124 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + Flow Text + + + + diff --git a/share/extensions/tests/data/svg/hpgl_multipen.svg b/share/extensions/tests/data/svg/hpgl_multipen.svg new file mode 100644 index 0000000..d27add9 --- /dev/null +++ b/share/extensions/tests/data/svg/hpgl_multipen.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + diff --git a/share/extensions/tests/data/svg/images.svg b/share/extensions/tests/data/svg/images.svg new file mode 100644 index 0000000..3af6c56 --- /dev/null +++ b/share/extensions/tests/data/svg/images.svg @@ -0,0 +1,73 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/share/extensions/tests/data/svg/img/green.png b/share/extensions/tests/data/svg/img/green.png new file mode 100644 index 0000000..bb606ff Binary files /dev/null and b/share/extensions/tests/data/svg/img/green.png differ diff --git a/share/extensions/tests/data/svg/inkweb-debug.js b/share/extensions/tests/data/svg/inkweb-debug.js new file mode 100644 index 0000000..a79c460 --- /dev/null +++ b/share/extensions/tests/data/svg/inkweb-debug.js @@ -0,0 +1,367 @@ +/* +** InkWeb Debugger - help the development with InkWeb. +** +** Copyright (C) 2009 Aurelio A. Heckert, aurium (a) gmail dot com +** +** ********* Bugs and New Fetures ************************************* +** If you found any bug on this script or if you want to propose a +** new feature, please report it in the inkscape bug tracker +** https://bugs.launchpad.net/inkscape/+filebug +** and assign that to Aurium. +** ******************************************************************** +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published +** by the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program. If not, see . +** +** ******************************************************************** +** +** This script extends InkWeb with methods like log() and viewProperties(). +** So, you must to call this script after the inkweb.js load. +*/ + +InkWeb.debugVersion = 0.1; + +// Prepare InkWeb Debug: +(function (bli, xyz) { + // Add logging calls to all InkWeb methods: + for ( var att in InkWeb ) { + if ( typeof(InkWeb[att]) == "function" ) { + var code = InkWeb[att].toString() + beforeCode = 'this.log(this.__callMethodInfo("'+att+'", arguments));\ntry {'; + afterCode = '} catch(e) { this.log( e, "Ups... There is a problem in InkWeb.'+att+'()" ) }'; + code = code + .replace( /^(function [^{]+[{])/, "$1\n"+ beforeCode +"\n" ) + .replace( /[}]$/, ";\n"+ afterCode +"\n}" ); + eval( "InkWeb."+att+" = "+ code ); + //alert( InkWeb[att] ) + } + } +})(123,456); + +InkWeb.__callMethodInfo = function (funcName, arg) { + var func = arg.callee; + var str = 'Called InkWeb.'+funcName+'() with:' + if ( ! func.argList ) { + func.argList = func.toString() + .replace( /^function [^(]*\(([^)]*)\)(.|\s)*$/, "$1" ) + .split( /,\s*/ ); + } + for ( var a,i=0; a=func.argList[i]; i++ ) { + str += "\n"+ a +" = "+ this.serialize( arg[i], {recursionLimit:2} ); + } + return str; +} + + +InkWeb.copySerializeConf = function (conf) { + return { + recursionStep: conf.recursionStep, + recursionLimit: conf.recursionLimit, + showTagElements: conf.showTagElements + } +} + +InkWeb.serialize = function (v, conf) { + try { + if ( ! conf ) { conf = {} } + if ( ! conf.showTagElements ) { conf.showTagElements = false } + if ( ! conf.recursionLimit ) { conf.recursionLimit = 10 } + if ( ! conf.recursionStep ) { conf.recursionStep = 0 } + if ( conf.recursionLimit == 0 ) { + return '"<>"'; + } + conf.recursionLimit--; + conf.recursionStep++; + switch ( typeof(v) ) { + case "undefined": + v = "undefined"; + break; + case "string": + v = '"'+ v + .replace( /\n/g, "\\n" ) + .replace( /\r/g, "\\r" ) + .replace( /\t/g, "\\t" ) + .replace( /"/g, '"' ) + + '"'; + break; + case "boolean": + case "number": + case "function": + v = v.toString(); + break; + case "object": + if ( v == null ) { + v = "null"; + } else { + if ( v.constructor == Array ) { + try { + v = this.__serializeArray(v, conf); + } catch(e) { + this.log( e, "InkWeb.serialize(): Forced recursion limit in" + + " recursionLimit="+ conf.recursionLimit + + " and recursionStep="+ conf.recursionStep + ); + v += '"<>"' + } + } else { + // A Hash Object + if ( v.tagName && ! conf.showTagElements ) { + // Tags are not allowed. + v = '"<'+ v.tagName +' id=\\"'+ v.id +'\\">"'; + } else { + // Ok, serialize this object: + try { + v = this.__serializeObject(v, conf); + } catch(e) { + this.log( e, "InkWeb.serialize(): Forced recursion limit in" + + " recursionLimit="+ conf.recursionLimit + + " and recursionStep="+ conf.recursionStep + ); + v += '"<>"' + } + } + } + } + break; + default: + v = '"<>"'; + } + return v; + } catch(e) { + this.log( e, "Ups... There is a problem in InkWeb.serialize()." ); + } +} + +InkWeb.__serializeArray = function (v, conf) { + try { + var vStr = "[ "; + var size = v.length; + for ( var i=0; i0 ) { vStr += ", " } + vStr += this.serialize(v[i], this.copySerializeConf(conf)); + } + return vStr +" ]"; + } catch(e) { + this.log( e, "Ups... There is a problem in InkWeb.__serializeArray()." ); + } +} + +InkWeb.__serializeObject = function (obj, conf) { + try { + var vStr = "{ "; + var first = true; + for ( var att in obj ) { + if ( !first ) { vStr += ", " } + vStr += this.serialize(att) +':'+ + this.serialize( obj[att], this.copySerializeConf(conf) ); + first = false; + } + return vStr +" }"; + } catch(e) { + this.log( e, "Ups... There is a problem in InkWeb.__serializeObject()." ); + } +} + +// Allow log configuration: +InkWeb.mustLog = { + error: true, + warning: true, + sequence: true + }; + +// This will keep the log information: +InkWeb.__log__ = []; + +InkWeb.log = function (type, msg) { + /* This method register what was happen with InkWeb + ** ( if mustLog allows that ) + ** + ** --- Usage --- + ** this.log( <"sequence"|"warning"|"warn"|errorObject>, <"logMessage"> ); + ** this.log( <"logMessage"> ); // only for sequences + ** + ** --- Examples --- + ** Sequence log: + ** function foo (bar) { + ** InkWeb.log( 'Call function foo with argument bar="'+bar+'"' ); + ** + ** Warning log: + ** if ( foo == bar ) { + ** foo = other; + ** InkWeb.log( "warn", "foo must not be bar." ); + ** + ** Error log: + ** try { ... some hard thing ... } + ** catch (e) { InkWeb.log( e, "Trying to do some hard thing." ) } + */ + if ( this.mustLog ) { + if( type.constructor == ReferenceError ) { + // in a error logging the type argument is the error object. + var error = type; + type = "error"; + this.addViewLogBt(); + } + if( type == "warn" ) { + // that allows a little simplify in the log call. + type = "warning"; + } + if( msg == undefined ) { + // that allows to log a sequence without tos say the type. + msg = type; + type = "sequence"; + } + var logSize = this.__log__.length + if ( logSize > 0 && + this.__log__[logSize-1].type == type && + this.__log__[logSize-1].msg == msg ) { + this.__log__[logSize-1].happens++ + } else { + if ( type == "error" && this.mustLog.error ) { + this.__log__[logSize] = this.__logError( error, msg ) + } + if ( type == "warning" && this.mustLog.warning ) { + this.__log__[logSize] = this.__logWarning( msg ) + } + if ( type == "sequence" && this.mustLog.sequence ) { + this.__log__[logSize] = this.__logSequence( msg ) + } + } + } +} + +InkWeb.__logError = function ( error, msg ) { + return { type:"error", date:new Date(), msg:msg, error:error, happens:1 }; +} + +InkWeb.__logWarning = function ( msg ) { + return { type:"warning", date:new Date(), msg:msg, happens:1 }; +} + +InkWeb.__logSequence = function ( msg ) { + return { type:"sequence", date:new Date(), msg:msg, happens:1 }; +} + +InkWeb.logToString = function (conf) { + /* Show the log in a formatted string. + ** conf attributes: + ** format: a string to format the log items. + ** formatError: to format the error log items. + ** sep: the log items separator string. + ** format variables: + ** $F: the item date in the format YYYY-MM-DD + ** $T: the item time in the format HH:MM:SS + ** $type: the log type + ** $logmsg: the text argument in the log call + ** $times: how much times this item happens in sequence + ** $error: the error text (if this is a error item) + ** $index: the position of the item in the log list + ** $oddeven: return odd or even based in the index. + */ + if (!conf) { conf = {} } + if (!conf.sep) { conf.sep = "\n\n" } + if (!conf.format) { conf.format = "$F $T - $type - $logmsg - Happens $times." } + if (!conf.formatError) { conf.formatError = "$F $T - ERROR - $logmsg - Happens $times.\n$error" } + /* * * Helper * * */ + function _2d(num) { + return ( ( num < 10 )? "0"+num : ""+num ) + } + function _2dMonth(date) { + var m = date.getMonth() + 1; + return _2d( m ) + } + var str = ""; + var logSize = this.__log__.length; + if ( logSize == 0 ) { + str = "There are no errors."; + } + // View all items to mount the log string: + for ( var item,pos=0; item=this.__log__[pos]; pos++ ) { + var d = item.date; + // Add log line, converting variables: + var line = ( (item.type=="error")? conf.formatError : conf.format ); + str += line + .replace( /\$index/g, pos ) + .replace( /\$oddeven/g, (pos%2 == 1)? "odd" : "even" ) + .replace( /\$type/g, item.type ) + .replace( /\$logmsg/g, item.msg ) + .replace( /\$error/g, (item.error)? item.error.message : "" ) + .replace( /\$times/g, (item.happens>1)? item.happens+" times" : "one time" ) + .replace( /\$F/g, d.getFullYear() +"-"+ _2dMonth(d) +"-"+ _2d(d.getDate()) ) + .replace( /\$T/g, _2d(d.getHours()) +":"+ _2d(d.getMinutes()) +":"+ _2d(d.getSeconds()) ) + // Add separator: + if ( pos < (logSize-1) ) { str += conf.sep } + } + return str; +} + +InkWeb.addViewLogBt = function () { + var svg = document.getElementsByTagName("svg")[0]; + if ( this.__viewLogBt ) { + svg.appendChild( this.__viewLogBt ); + } else { + var g = this.el( "g", { onclick: "InkWeb.openLogWindow()", parent: svg } ); + var rect = this.el( "rect", { x: 10, y: 10, width: 60, height: 17, ry: 5, + style: "fill:#C00; stroke:#800; stroke-width:2", + parent: g } ); + var text = this.el( "text", { x: 40, y: 22, text: "View Log", + style: "fill:#FFF; font-size:10px;" + + "font-family:sans-serif;" + + "text-anchor:middle; text-align:center", + parent: g } ); + this.__viewLogBt = g; + } +} + +InkWeb.__openFormatedWindow = function (bodyHTML) { + var win = window.open("","_blank","width=500,height=500,scrollbars=yes"); + var html = + 'InkWeb' + + ''+ bodyHTML +''; + win.document.write(html); + win.document.close(); + return win; +} + +InkWeb.openLogWindow = function () { + var html = '

InkWeb Log

\n' + + '\n' + + '\n' + + this.logToString({ + format: '' + + '', + formatError: '' + + '\n'+ + '', + sep: '\n\n' + }) + + '\n
TimeMessageHappens
$T$logmsg$times
$T$logmsg$times
$error
' + var win = this.__openFormatedWindow( html ); + win.document.title = "InkWeb Log" +} + + +InkWeb.viewProperties = function () { + // Display object properties. + this.__openFormatedWindow( "coming soon..." ); +} diff --git a/share/extensions/tests/data/svg/inkwebjs-move.svg b/share/extensions/tests/data/svg/inkwebjs-move.svg new file mode 100644 index 0000000..b4aa50f --- /dev/null +++ b/share/extensions/tests/data/svg/inkwebjs-move.svg @@ -0,0 +1,128 @@ + + + + + + + + + + Test 1 - from left to right + Start + + End + + + + + + + + Test 2 - from right to left + Start + + End + + + + + + + + Test 3 - pre-translated + Start + + End + + + + + + + + Test 4 - pre-translated and scaled + Start + + End + + + + + + + + Test 5 - pre-translated and rotated + Start + + End + + + + + + + + Test 6 - with a transformation matrix + Start + + End + + + + + + + + + diff --git a/share/extensions/tests/data/svg/interp_shapes.svg b/share/extensions/tests/data/svg/interp_shapes.svg new file mode 100644 index 0000000..eaff106 --- /dev/null +++ b/share/extensions/tests/data/svg/interp_shapes.svg @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/tests/data/svg/markers.svg b/share/extensions/tests/data/svg/markers.svg new file mode 100644 index 0000000..bf9ca6f --- /dev/null +++ b/share/extensions/tests/data/svg/markers.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + diff --git a/share/extensions/tests/data/svg/mesh.svg b/share/extensions/tests/data/svg/mesh.svg new file mode 100644 index 0000000..cbf42af --- /dev/null +++ b/share/extensions/tests/data/svg/mesh.svg @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/tests/data/svg/minimal-blank-prepare.svg b/share/extensions/tests/data/svg/minimal-blank-prepare.svg new file mode 100644 index 0000000..1fc27fb --- /dev/null +++ b/share/extensions/tests/data/svg/minimal-blank-prepare.svg @@ -0,0 +1 @@ + diff --git a/share/extensions/tests/data/svg/minimal-blank.svg b/share/extensions/tests/data/svg/minimal-blank.svg new file mode 100644 index 0000000..1fc27fb --- /dev/null +++ b/share/extensions/tests/data/svg/minimal-blank.svg @@ -0,0 +1 @@ + diff --git a/share/extensions/tests/data/svg/multilayered-test.svg b/share/extensions/tests/data/svg/multilayered-test.svg new file mode 100644 index 0000000..c02d4ee --- /dev/null +++ b/share/extensions/tests/data/svg/multilayered-test.svg @@ -0,0 +1,156 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + Bottom layer + + + + + + Middle sublayer + + + + Middle layer + + + + Top layer + + + diff --git a/share/extensions/tests/data/svg/perspective.svg b/share/extensions/tests/data/svg/perspective.svg new file mode 100644 index 0000000..47a6f99 --- /dev/null +++ b/share/extensions/tests/data/svg/perspective.svg @@ -0,0 +1,33 @@ + + + + + + + diff --git a/share/extensions/tests/data/svg/perspective_groups.svg b/share/extensions/tests/data/svg/perspective_groups.svg new file mode 100644 index 0000000..33acaa2 --- /dev/null +++ b/share/extensions/tests/data/svg/perspective_groups.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/tests/data/svg/shapes-clipboard.svg b/share/extensions/tests/data/svg/shapes-clipboard.svg new file mode 100644 index 0000000..86b674d --- /dev/null +++ b/share/extensions/tests/data/svg/shapes-clipboard.svg @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + diff --git a/share/extensions/tests/data/svg/shapes.svg b/share/extensions/tests/data/svg/shapes.svg new file mode 100644 index 0000000..eb9caed --- /dev/null +++ b/share/extensions/tests/data/svg/shapes.svg @@ -0,0 +1,284 @@ + + + + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps UPPER + Multi linetextFOO + + Grouped + text + + + + + + + diff --git a/share/extensions/tests/data/svg/shapes_cmyk.svg b/share/extensions/tests/data/svg/shapes_cmyk.svg new file mode 100644 index 0000000..bc35be8 --- /dev/null +++ b/share/extensions/tests/data/svg/shapes_cmyk.svg @@ -0,0 +1,302 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + format: png +dpi: 96 +layout-disposition: bg-el-norepeat +layout-position-anchor: tl + + + + + + + + + + + + + Hello World + flow text which wraps + UPPER + Multi linetextFOO + + Grouped + text + + + + + + + diff --git a/share/extensions/tests/data/svg/simpletransform.test.svg b/share/extensions/tests/data/svg/simpletransform.test.svg new file mode 100644 index 0000000..62876ee --- /dev/null +++ b/share/extensions/tests/data/svg/simpletransform.test.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/share/extensions/tests/data/svg/single_box.svg b/share/extensions/tests/data/svg/single_box.svg new file mode 100644 index 0000000..094233d --- /dev/null +++ b/share/extensions/tests/data/svg/single_box.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/share/extensions/tests/data/svg/slicer.svg b/share/extensions/tests/data/svg/slicer.svg new file mode 100644 index 0000000..a485c35 --- /dev/null +++ b/share/extensions/tests/data/svg/slicer.svg @@ -0,0 +1,75 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/share/extensions/tests/data/svg/symbol.svg b/share/extensions/tests/data/svg/symbol.svg new file mode 100644 index 0000000..a8e20f3 --- /dev/null +++ b/share/extensions/tests/data/svg/symbol.svg @@ -0,0 +1,46 @@ + + + + + + + Air Transportation + + + + + + + + diff --git a/share/extensions/tests/data/svg/with-lpe.svg b/share/extensions/tests/data/svg/with-lpe.svg new file mode 100644 index 0000000..89d590e --- /dev/null +++ b/share/extensions/tests/data/svg/with-lpe.svg @@ -0,0 +1,30 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/share/extensions/tests/dev_requirements.txt b/share/extensions/tests/dev_requirements.txt new file mode 100644 index 0000000..adc4c30 --- /dev/null +++ b/share/extensions/tests/dev_requirements.txt @@ -0,0 +1,10 @@ +# Dev Requirements +pytest +pytest-cov + +# Code requirements +typing +lxml +numpy +scour +pyserial diff --git a/share/extensions/tests/test_addnodes.py b/share/extensions/tests/test_addnodes.py new file mode 100644 index 0000000..482ac2b --- /dev/null +++ b/share/extensions/tests/test_addnodes.py @@ -0,0 +1,20 @@ +# coding=utf-8 +from addnodes import AddNodes +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy, CompareWithPathSpace + +class SplitItBasicTest(ComparisonMixin, TestCase): + effect_class = AddNodes + compare_filters = [ + CompareWithPathSpace(), + CompareNumericFuzzy(), + ] + + def test_basic(self): + args = ['--id=dashme', + self.data_file('svg', 'dash.svg')] + effect = self.effect_class() + effect.run(args) + old_path = effect.original_document.getroot().getElement('//svg:path').path + new_path = effect.svg.getElement('//svg:path').path + assert len(new_path) > len(old_path) diff --git a/share/extensions/tests/test_color_HSL_adjust.py b/share/extensions/tests/test_color_HSL_adjust.py new file mode 100644 index 0000000..bd0753d --- /dev/null +++ b/share/extensions/tests/test_color_HSL_adjust.py @@ -0,0 +1,32 @@ +# coding=utf-8 +from color_HSL_adjust import HslAdjust +from .test_inkex_extensions import ColorBaseCase + +class ColorHSLAdjustTest(ColorBaseCase): + effect_class = HslAdjust + color_tests = [ + ("none", "none"), + ((255, 255, 255), "#ffffff"), + ((0, 0, 0), "#000000"), + ((0, 128, 0), "#008000"), + ((91, 166, 176), "#5a74af", ['-x 10']), + ((91, 166, 176), "#745aaf", ['-x 320']), + ((91, 166, 176), "#5ba6b0", ['-x 0']), + ((91, 166, 176), "#af5a6c", ['-x 12345']), + ((91, 166, 176), "#5aacaf", ['-x -1']), + ((91, 166, 176), "#4eafbb", ['-s 10']), + ((91, 166, 176), "#0be5fe", ['-s 90']), + ((91, 166, 176), "#5ba6b0", ['-s 0']), + ((91, 166, 176), "#0be5fe", ['-s 100']), + ((91, 166, 176), "#0be5fe", ['-s 12345']), + ((91, 166, 176), "#5ba5ae", ['-s -1']), + ((91, 166, 176), "#7cb8bf", ['-l 10']), + ((91, 166, 176), "#ffffff", ['-l 90']), + ((91, 166, 176), "#5ba6b0", ['-l 0']), + ((91, 166, 176), "#ffffff", ['-l 100']), + ((91, 166, 176), "#ffffff", ['-l 12345']), + ((91, 166, 176), "#56a4ad", ['-l -1']), + ((91, 166, 176), '#5a86af', ['--random_h=true']), + ((91, 166, 176), '#cde4e6', ['--random_l=true']), + ((91, 166, 176), '#43b8c6', ['--random_s=true']), + ] diff --git a/share/extensions/tests/test_color_blackandwhite.py b/share/extensions/tests/test_color_blackandwhite.py new file mode 100644 index 0000000..84af005 --- /dev/null +++ b/share/extensions/tests/test_color_blackandwhite.py @@ -0,0 +1,32 @@ +# coding=utf-8 +from color_blackandwhite import BlackAndWhite +from .test_inkex_extensions import ColorBaseCase + +class ColorBlackAndWhiteTest(ColorBaseCase): + effect_class = BlackAndWhite + color_tests = [ + # When converting to black and white the color white should be unchanged + ("none", "none"), + ((0, 0, 0), "#000000"), + ((255, 255, 255), "#ffffff"), + ((192, 192, 192), "#ffffff"), + ((128, 128, 128), "#ffffff"), + ((128, 0, 0), "#000000"), + ((255, 0, 0), "#000000"), + ((128, 128, 0), "#000000"), + ((255, 255, 0), "#ffffff"), + ((0, 128, 0), "#000000"), + ((0, 255, 0), "#ffffff"), + ((0, 128, 128), "#000000"), + ((0, 255, 255), "#ffffff"), + ((0, 0, 128), "#000000"), + ((0, 0, 255), "#000000"), + ((128, 0, 128), "#000000"), + ((255, 0, 255), "#000000"), + # Increasing the threshold means more colors will be black + ((255, 0, 255), "#000000", ['-t 240']), + ((192, 192, 192), "#000000", ['-t 240']), + # Decreasing the threshold means more colors will be white + ((255, 0, 255), "#ffffff", ['-t 80']), + ((192, 192, 192), "#ffffff", ['-t 80']), + ] diff --git a/share/extensions/tests/test_color_brighter.py b/share/extensions/tests/test_color_brighter.py new file mode 100644 index 0000000..cbbdb34 --- /dev/null +++ b/share/extensions/tests/test_color_brighter.py @@ -0,0 +1,26 @@ +# coding=utf-8 +from color_brighter import Brighter +from .test_inkex_extensions import ColorBaseCase + +class ColorBrighterTest(ColorBaseCase): + effect_class = Brighter + color_tests = [ + ("none", "none"), + ((0, 0, 0), "#0a0a0a"), + ((255, 255, 255), "#ffffff"), + ((192, 192, 192), "#d5d5d5"), + ((128, 128, 128), "#8e8e8e"), + ((128, 0, 0), "#8e0000"), + ((255, 0, 0), "#ff0000"), + ((128, 128, 0), "#8e8e00"), + ((255, 255, 0), "#ffff00"), + ((0, 128, 0), "#008e00"), + ((0, 255, 0), "#00ff00"), + ((0, 128, 128), "#008e8e"), + ((0, 255, 255), "#00ffff"), + ((0, 0, 128), "#00008e"), + ((0, 0, 255), "#0000ff"), + ((128, 0, 128), "#8e008e"), + ((255, 0, 255), "#ff00ff"), + ("hsl(33, 92, 128)", "hsl(33, 92, 142)"), + ] diff --git a/share/extensions/tests/test_color_custom.py b/share/extensions/tests/test_color_custom.py new file mode 100644 index 0000000..f5530f0 --- /dev/null +++ b/share/extensions/tests/test_color_custom.py @@ -0,0 +1,49 @@ +# coding=utf-8 + +import inkex +from color_custom import Custom +from .test_inkex_extensions import ColorBaseCase + +class ColorCustomTest(ColorBaseCase): + effect_class = Custom + color_tests = [ + # The default ranges are set to 0, and thus the color should not change. + ("none", "none"), + ((255, 255, 255), "#ffffff"), + ((100, 0, 0), "#c80000", ['-r r*2']), + ((12, 34, 56), "#0c3822", ['-g b', '-b g']), + ((12, 34, 56), "#183822", ['-g b', '-b g', '-r r*2']), + ((0, 0, 0), "#100000", ['-s 255', '-r 16']), + ((0, 0, 0), "#0f0000", ['-s 1', '-r 0.0625']), + ((0, 0, 0), "#ff0000", ['-r 400']), + ((0, 0, 0), "#000000", ['-r -400']), + ("red", "#fe0000", ['-s 400']), + ] + + def test_evil_fails(self): + """ + eval shouldn't allow for evil things to happen + + Here we try and check if a file exists but it could just as easily + overwrite or delete the file + + """ + args = ["-r __import__('os').path.exists('__init__.py')", self.empty_svg] + self.effect.run(args) + + with self.assertRaises(TypeError): + self.effect.modify_color('fill', inkex.Color('black')) + + def test_invalid_operator(self): + args = ["-r r % 100", self.empty_svg] + self.effect.run(args) + + with self.assertRaises(KeyError): + self.effect.modify_color('fill', inkex.Color('black')) + + def test_bad_syntax(self): + args = ["-r r + 100)", self.empty_svg] + self.effect.run(args) + + with self.assertRaises(SyntaxError): + self.effect.modify_color('fill', inkex.Color('black')) diff --git a/share/extensions/tests/test_color_darker.py b/share/extensions/tests/test_color_darker.py new file mode 100644 index 0000000..b5d0eec --- /dev/null +++ b/share/extensions/tests/test_color_darker.py @@ -0,0 +1,27 @@ +# coding=utf-8 +from color_darker import Darker +from .test_inkex_extensions import ColorBaseCase + +class ColorDarkerTest(ColorBaseCase): + effect_class = Darker + color_tests = [ + ("none", "none"), + ((0, 0, 0), "#000000"), + ((255, 255, 255), "#e6e6e6"), + ((192, 192, 192), "#adadad"), + ((128, 128, 128), "#737373"), + ((128, 0, 0), "#730000"), + ((255, 0, 0), "#e60000"), + ((128, 128, 0), "#737300"), + ((255, 255, 0), "#e6e600"), + ((0, 128, 0), "#007300"), + ((0, 255, 0), "#00e600"), + ((0, 128, 128), "#007373"), + ((0, 255, 255), "#00e6e6"), + ((0, 0, 128), "#000073"), + ((0, 0, 255), "#0000e6"), + ((128, 0, 128), "#730073"), + ((255, 0, 255), "#e600e6"), + ("hsl(25, 14, 128)", "hsl(25, 14, 115)"), + ("red", "#e60000"), + ] diff --git a/share/extensions/tests/test_color_desaturate.py b/share/extensions/tests/test_color_desaturate.py new file mode 100644 index 0000000..0a0a031 --- /dev/null +++ b/share/extensions/tests/test_color_desaturate.py @@ -0,0 +1,25 @@ +# coding=utf-8 +from color_desaturate import Desaturate +from .test_inkex_extensions import ColorBaseCase + +class ColorDesaturateTest(ColorBaseCase): + effect_class = Desaturate + color_tests = [ + ("none", "none"), + ((0, 0, 0), "#000000"), + ((255, 255, 255), "#ffffff"), + ((192, 192, 192), "#c0c0c0"), + ((128, 128, 128), "#808080"), + ((128, 0, 0), "#404040"), + ((255, 0, 0), "#7f7f7f"), + ((128, 128, 0), "#404040"), + ((255, 255, 0), "#7f7f7f"), + ((0, 128, 0), "#404040"), + ((0, 255, 0), "#7f7f7f"), + ((0, 128, 128), "#404040"), + ((0, 255, 255), "#7f7f7f"), + ((0, 0, 128), "#404040"), + ((0, 0, 255), "#7f7f7f"), + ((128, 0, 128), "#404040"), + ((255, 0, 255), "#7f7f7f"), + ] diff --git a/share/extensions/tests/test_color_grayscale.py b/share/extensions/tests/test_color_grayscale.py new file mode 100644 index 0000000..9bc7a63 --- /dev/null +++ b/share/extensions/tests/test_color_grayscale.py @@ -0,0 +1,25 @@ +# coding=utf-8 +from color_grayscale import Grayscale +from .test_inkex_extensions import ColorBaseCase + +class ColorGrayscaleTest(ColorBaseCase): + effect_class = Grayscale + color_tests = [ + ("none", "none"), + ((0, 0, 0), "#000000"), + ((255, 255, 255), "#ffffff"), + ((192, 192, 192), "#c0c0c0"), + ((128, 128, 128), "#808080"), + ((128, 0, 0), "#262626"), + ((255, 0, 0), "#4c4c4c"), + ((128, 128, 0), "#717171"), + ((255, 255, 0), "#e2e2e2"), + ((0, 128, 0), "#4b4b4b"), + ((0, 255, 0), "#969696"), + ((0, 128, 128), "#5a5a5a"), + ((0, 255, 255), "#b3b3b3"), + ((0, 0, 128), "#0f0f0f"), + ((0, 0, 255), "#1d1d1d"), + ((128, 0, 128), "#353535"), + ((255, 0, 255), "#696969"), + ] diff --git a/share/extensions/tests/test_color_lesshue.py b/share/extensions/tests/test_color_lesshue.py new file mode 100644 index 0000000..f910dc6 --- /dev/null +++ b/share/extensions/tests/test_color_lesshue.py @@ -0,0 +1,27 @@ +# coding=utf-8 +from color_lesshue import LessHue +from .test_inkex_extensions import ColorBaseCase + +class ColorLessHueTest(ColorBaseCase): + effect_class = LessHue + color_tests = [ + ("none", "none"), + ('hsl(0, 0, 0)', 'hsl(243, 0, 0)'), + ('hsl(255, 255, 255)', 'hsl(243, 255, 255)'), + ((0, 0, 0), "#000000"), + ((255, 255, 255), "#ffffff"), + ((192, 192, 192), "#c0c0c0"), + ((128, 128, 128), "#808080"), + ((128, 0, 0), "#800024"), + ((255, 0, 0), "#fe0047"), + ((128, 128, 0), "#805a00"), + ((255, 255, 0), "#feb300"), + ((0, 128, 0), "#248000"), + ((0, 255, 0), "#47fe00"), + ((0, 128, 128), "#00805a"), + ((0, 255, 255), "#00feb3"), + ((0, 0, 128), "#002480"), + ((0, 0, 255), "#0047fe"), + ((128, 0, 128), "#5a0080"), + ((255, 0, 255), "#b300fe"), + ] diff --git a/share/extensions/tests/test_color_lesslight.py b/share/extensions/tests/test_color_lesslight.py new file mode 100644 index 0000000..a5bf961 --- /dev/null +++ b/share/extensions/tests/test_color_lesslight.py @@ -0,0 +1,27 @@ +# coding=utf-8 +from color_lesslight import LessLight +from .test_inkex_extensions import ColorBaseCase + +class ColorLessLightTest(ColorBaseCase): + effect_class = LessLight + color_tests = [ + ("none", "none"), + ('hsl(0, 0, 0)', 'hsl(0, 0, 0)'), + ('hsl(255, 255, 255)', 'hsl(255, 255, 243)'), + ((0, 0, 0), "#000000"), + ((255, 255, 255), "#f3f3f3"), + ((192, 192, 192), "#b4b4b4"), + ((128, 128, 128), "#747474"), + ((128, 0, 0), "#680000"), + ((255, 0, 0), "#e60000"), + ((128, 128, 0), "#686600"), + ((255, 255, 0), "#e6e300"), + ((0, 128, 0), "#006800"), + ((0, 255, 0), "#00e600"), + ((0, 128, 128), "#006866"), + ((0, 255, 255), "#00e6e3"), + ((0, 0, 128), "#000068"), + ((0, 0, 255), "#0000e6"), + ((128, 0, 128), "#660068"), + ((255, 0, 255), "#e300e6"), + ] diff --git a/share/extensions/tests/test_color_lesssaturation.py b/share/extensions/tests/test_color_lesssaturation.py new file mode 100644 index 0000000..7ffe791 --- /dev/null +++ b/share/extensions/tests/test_color_lesssaturation.py @@ -0,0 +1,27 @@ +# coding=utf-8 +from color_lesssaturation import LessSaturation +from .test_inkex_extensions import ColorBaseCase + +class ColorLessSaturationTest(ColorBaseCase): + effect_class = LessSaturation + color_tests = [ + ("none", "none"), + ('hsl(0, 0, 0)', 'hsl(0, 0, 0)'), + ('hsl(255, 255, 255)', 'hsl(255, 243, 255)'), + ((0, 0, 0), "#000000"), + ((255, 255, 255), "#ffffff"), + ((192, 192, 192), "#c0c0c0"), + ((128, 128, 128), "#808080"), + ((128, 0, 0), "#7c0303"), + ((255, 0, 0), "#f80505"), + ((128, 128, 0), "#7c7b03"), + ((255, 255, 0), "#f8f505"), + ((0, 128, 0), "#037c03"), + ((0, 255, 0), "#05f805"), + ((0, 128, 128), "#037c7b"), + ((0, 255, 255), "#05f8f5"), + ((0, 0, 128), "#03037c"), + ((0, 0, 255), "#0505f8"), + ((128, 0, 128), "#7b037c"), + ((255, 0, 255), "#f505f8"), + ] diff --git a/share/extensions/tests/test_color_list.py b/share/extensions/tests/test_color_list.py new file mode 100644 index 0000000..6f38e6d --- /dev/null +++ b/share/extensions/tests/test_color_list.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from color_list import ListColours +from .test_inkex_extensions import ColorEffectTest + +class ColorListTest(ColorEffectTest): + effect_class = ListColours + effect_name = 'test_color_list' + stderr_output = True + color_tests = [] diff --git a/share/extensions/tests/test_color_morehue.py b/share/extensions/tests/test_color_morehue.py new file mode 100644 index 0000000..9b7f779 --- /dev/null +++ b/share/extensions/tests/test_color_morehue.py @@ -0,0 +1,28 @@ +# coding=utf-8 + +from color_morehue import MoreHue +from .test_inkex_extensions import ColorBaseCase + +class ColorMoreHueTest(ColorBaseCase): + effect_class = MoreHue + color_tests = [ + ("none", "none"), + ('hsl(0, 0, 0)', 'hsl(12, 0, 0)'), + ('hsl(255, 255, 255)', 'hsl(12, 255, 255)'), + ((0, 0, 0), "#000000"), + ((255, 255, 255), "#ffffff"), + ((192, 192, 192), "#c0c0c0"), + ((128, 128, 128), "#808080"), + ((128, 0, 0), "#802400"), + ((255, 0, 0), "#fe4700"), + ((128, 128, 0), "#5d8000"), + ((255, 255, 0), "#b9fe00"), + ((0, 128, 0), "#008024"), + ((0, 255, 0), "#00fe47"), + ((0, 128, 128), "#005d80"), + ((0, 255, 255), "#00b9fe"), + ((0, 0, 128), "#240080"), + ((0, 0, 255), "#4700fe"), + ((128, 0, 128), "#80005d"), + ((255, 0, 255), "#fe00b9"), + ] diff --git a/share/extensions/tests/test_color_morelight.py b/share/extensions/tests/test_color_morelight.py new file mode 100644 index 0000000..496b6d6 --- /dev/null +++ b/share/extensions/tests/test_color_morelight.py @@ -0,0 +1,27 @@ +# coding=utf-8 +from color_morelight import MoreLight +from .test_inkex_extensions import ColorBaseCase + +class ColorMoreLightTest(ColorBaseCase): + effect_class = MoreLight + color_tests = [ + ("none", "none"), + ('hsl(0, 0, 0)', 'hsl(0, 0, 12)'), + ('hsl(255, 255, 255)', 'hsl(255, 255, 255)'), + ((0, 0, 0), "#0c0c0c"), + ((255, 255, 255), "#ffffff"), + ((192, 192, 192), "#cccccc"), + ((128, 128, 128), "#8c8c8c"), + ((128, 0, 0), "#980000"), + ((255, 0, 0), "#fe1717"), + ((128, 128, 0), "#989600"), + ((255, 255, 0), "#fefc17"), + ((0, 128, 0), "#009800"), + ((0, 255, 0), "#17fe17"), + ((0, 128, 128), "#009896"), + ((0, 255, 255), "#17fefc"), + ((0, 0, 128), "#000098"), + ((0, 0, 255), "#1717fe"), + ((128, 0, 128), "#960098"), + ((255, 0, 255), "#fc17fe"), + ] diff --git a/share/extensions/tests/test_color_moresaturation.py b/share/extensions/tests/test_color_moresaturation.py new file mode 100644 index 0000000..4b6a38f --- /dev/null +++ b/share/extensions/tests/test_color_moresaturation.py @@ -0,0 +1,27 @@ +# coding=utf-8 +from color_moresaturation import MoreSaturation +from .test_inkex_extensions import ColorBaseCase + +class ColorMoreSaturationTest(ColorBaseCase): + effect_class = MoreSaturation + color_tests = [ + ("none", "none"), + ('hsl(0, 0, 0)', 'hsl(0, 12, 0)'), + ('hsl(255, 255, 255)', 'hsl(255, 255, 255)'), + ((0, 0, 0), "#000000"), + ((255, 255, 255), "#ffffff"), + ((192, 192, 192), "#c2bdbd"), + ((128, 128, 128), "#857a7a"), + ((128, 0, 0), "#800000"), + ((255, 0, 0), "#fe0000"), + ((128, 128, 0), "#807e00"), + ((255, 255, 0), "#fefb00"), + ((0, 128, 0), "#008000"), + ((0, 255, 0), "#00fe00"), + ((0, 128, 128), "#00807e"), + ((0, 255, 255), "#00fefb"), + ((0, 0, 128), "#000080"), + ((0, 0, 255), "#0000fe"), + ((128, 0, 128), "#7e0080"), + ((255, 0, 255), "#fb00fe"), + ] diff --git a/share/extensions/tests/test_color_negative.py b/share/extensions/tests/test_color_negative.py new file mode 100644 index 0000000..b82aecc --- /dev/null +++ b/share/extensions/tests/test_color_negative.py @@ -0,0 +1,25 @@ +# coding=utf-8 +from color_negative import Negative +from .test_inkex_extensions import ColorBaseCase + +class ColorNegativeTest(ColorBaseCase): + effect_class = Negative + color_tests = [ + ("none", "none"), + ((0, 0, 0), "#ffffff"), + ((255, 255, 255), "#000000"), + ((192, 192, 192), "#3f3f3f"), + ((128, 128, 128), "#7f7f7f"), + ((128, 0, 0), "#7fffff"), + ((255, 0, 0), "#00ffff"), + ((128, 128, 0), "#7f7fff"), + ((255, 255, 0), "#0000ff"), + ((0, 128, 0), "#ff7fff"), + ((0, 255, 0), "#ff00ff"), + ((0, 128, 128), "#ff7f7f"), + ((0, 255, 255), "#ff0000"), + ((0, 0, 128), "#ffff7f"), + ((0, 0, 255), "#ffff00"), + ((128, 0, 128), "#7fff7f"), + ((255, 0, 255), "#00ff00"), + ] diff --git a/share/extensions/tests/test_color_randomize.py b/share/extensions/tests/test_color_randomize.py new file mode 100644 index 0000000..65dd3da --- /dev/null +++ b/share/extensions/tests/test_color_randomize.py @@ -0,0 +1,38 @@ +# coding=utf-8 +from color_randomize import Randomize +from .test_inkex_extensions import ColorBaseCase + +class ColorRandomizeTest(ColorBaseCase): + effect_class = Randomize + python3_only = True + color_tests = [ + ("none", "none"), + # The default ranges are set to 0, and thus the color and opacity should not change. + ((150, 100, 200), "#9564c7"), + # The user selected 0% values, and thus the color should not change. + ((150, 100, 200), "hsl(191, 119, 149)", ['-y 0', '-t 0', '-m 0']), + # Random hue only. Saturation and lightness not changed. + ((150, 100, 200), "hsl(202, 119, 149)", ['-y 50', '-t 0', '-m 0']), + # Random saturation only. Hue and lightness not changed. + ((150, 100, 200), "hsl(190, 235, 149)", ['-y 0', '-t 50', '-m 0']), + # Random lightness only. Hue and saturation not changed. + ((150, 100, 200), "hsl(190, 117, 236)", ['-y 0', '-t 0', '-m 50']), + # The maximum hsl values should be between 0 and 100% of their maximum + ((156, 156, 156), "hsl(70, 134, 227)", ['-y 100', '-t 100', '-m 100']), + ] + + opacity_tests = [ + (5, 5), + # The user selected 0% opacity range, and thus the opacity should not change. + (0.15, 0.15, ['-o 0']), + # The opacity value should be greater than 0 + (0.0, 1.0, ['-o 100']), + # The opacity value should be lesser than 1 + (1.0, 0.43, ['-o 100']), + # Other units are available + ('0.5', 0.654, ['-o 54']), + ] + + def test_bad_opacity(self): + """Bad opacity error handled""" + self.effect.modify_opacity('opacity', 'hello') diff --git a/share/extensions/tests/test_color_removeblue.py b/share/extensions/tests/test_color_removeblue.py new file mode 100644 index 0000000..33d82c8 --- /dev/null +++ b/share/extensions/tests/test_color_removeblue.py @@ -0,0 +1,25 @@ +# coding=utf-8 +from color_removeblue import RemoveBlue +from .test_inkex_extensions import ColorBaseCase + +class ColorRemoveBlueTest(ColorBaseCase): + effect_class = RemoveBlue + color_tests = [ + ("none", "none"), + ((0, 0, 0), "#000000"), + ((255, 255, 255), "#ffff00"), + ((192, 192, 192), "#c0c000"), + ((128, 128, 128), "#808000"), + ((128, 0, 0), "#800000"), + ((255, 0, 0), "#ff0000"), + ((128, 128, 0), "#808000"), + ((255, 255, 0), "#ffff00"), + ((0, 128, 0), "#008000"), + ((0, 255, 0), "#00ff00"), + ((0, 128, 128), "#008000"), + ((0, 255, 255), "#00ff00"), + ((0, 0, 128), "#000000"), + ((0, 0, 255), "#000000"), + ((128, 0, 128), "#800000"), + ((255, 0, 255), "#ff0000"), + ] diff --git a/share/extensions/tests/test_color_removegreen.py b/share/extensions/tests/test_color_removegreen.py new file mode 100644 index 0000000..a45a747 --- /dev/null +++ b/share/extensions/tests/test_color_removegreen.py @@ -0,0 +1,25 @@ +# coding=utf-8 +from color_removegreen import RemoveGreen +from .test_inkex_extensions import ColorBaseCase + +class ColorRemoveGreenTest(ColorBaseCase): + effect_class = RemoveGreen + color_tests = [ + ("none", "none"), + ((0, 0, 0), "#000000"), + ((255, 255, 255), "#ff00ff"), + ((192, 192, 192), "#c000c0"), + ((128, 128, 128), "#800080"), + ((128, 0, 0), "#800000"), + ((255, 0, 0), "#ff0000"), + ((128, 128, 0), "#800000"), + ((255, 255, 0), "#ff0000"), + ((0, 128, 0), "#000000"), + ((0, 255, 0), "#000000"), + ((0, 128, 128), "#000080"), + ((0, 255, 255), "#0000ff"), + ((0, 0, 128), "#000080"), + ((0, 0, 255), "#0000ff"), + ((128, 0, 128), "#800080"), + ((255, 0, 255), "#ff00ff"), + ] diff --git a/share/extensions/tests/test_color_removered.py b/share/extensions/tests/test_color_removered.py new file mode 100644 index 0000000..17652fd --- /dev/null +++ b/share/extensions/tests/test_color_removered.py @@ -0,0 +1,25 @@ +# coding=utf-8 +from color_removered import RemoveRed +from .test_inkex_extensions import ColorBaseCase + +class ColorRemoveRedTest(ColorBaseCase): + effect_class = RemoveRed + color_tests = [ + ("none", "none"), + ((0, 0, 0), "#000000"), + ((255, 255, 255), "#00ffff"), + ((192, 192, 192), "#00c0c0"), + ((128, 128, 128), "#008080"), + ((128, 0, 0), "#000000"), + ((255, 0, 0), "#000000"), + ((128, 128, 0), "#008000"), + ((255, 255, 0), "#00ff00"), + ((0, 128, 0), "#008000"), + ((0, 255, 0), "#00ff00"), + ((0, 128, 128), "#008080"), + ((0, 255, 255), "#00ffff"), + ((0, 0, 128), "#000080"), + ((0, 0, 255), "#0000ff"), + ((128, 0, 128), "#000080"), + ((255, 0, 255), "#0000ff"), + ] diff --git a/share/extensions/tests/test_color_replace.py b/share/extensions/tests/test_color_replace.py new file mode 100644 index 0000000..c7e56c1 --- /dev/null +++ b/share/extensions/tests/test_color_replace.py @@ -0,0 +1,15 @@ +# coding=utf-8 +from color_replace import ReplaceColor +from .test_inkex_extensions import ColorBaseCase + +class ColorReplaceTest(ColorBaseCase): + effect_class = ReplaceColor + color_tests = [ + ("none", "none"), + ((0, 0, 0), "#ff0000", []), + ((128, 0, 0), "#800000", []), + ((0, 0, 0), "#696969", ['-t1768516095']), + ((0, 0, 0), "#000000", ["-f1", "-t1768516095"]), + ((18, 52, 86), "#696969", ["-f305420031", "-t1768516095"]), + ((18, 52, 86), "#ff0000", ["-f305420031"]), + ] diff --git a/share/extensions/tests/test_color_rgbbarrel.py b/share/extensions/tests/test_color_rgbbarrel.py new file mode 100644 index 0000000..9682af1 --- /dev/null +++ b/share/extensions/tests/test_color_rgbbarrel.py @@ -0,0 +1,26 @@ +# coding=utf-8 +from color_rgbbarrel import RgbBarrel +from .test_inkex_extensions import ColorBaseCase + +class ColorBarrelTest(ColorBaseCase): + effect_class = RgbBarrel + color_tests = [ + ("none", "none"), + ((0, 0, 0), "#000000"), + ((255, 255, 255), "#ffffff"), + ((192, 192, 192), "#c0c0c0"), + ((128, 128, 128), "#808080"), + ((128, 0, 0), "#008000"), + ((255, 0, 0), "#00ff00"), + ((128, 128, 0), "#008080"), + ((255, 255, 0), "#00ffff"), + ((0, 128, 0), "#000080"), + ((0, 255, 0), "#0000ff"), + ((0, 128, 128), "#800080"), + ((0, 255, 255), "#ff00ff"), + ((0, 0, 128), "#800000"), + ((0, 0, 255), "#ff0000"), + ((128, 0, 128), "#808000"), + ((255, 0, 255), "#ffff00"), + ("hsl(25, 14, 128)", "#798681"), + ] diff --git a/share/extensions/tests/test_convert2dashes.py b/share/extensions/tests/test_convert2dashes.py new file mode 100644 index 0000000..dc12982 --- /dev/null +++ b/share/extensions/tests/test_convert2dashes.py @@ -0,0 +1,16 @@ +# coding=utf-8 +from convert2dashes import Dashit +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + + +class DashitBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + comparisons = ([],) + effect_class = Dashit + + def test_basic(self): + args = ['--id=dashme', + self.data_file('svg', 'dash.svg')] + self.effect.run(args) + old_dashes = self.effect.original_document.getroot().getElement('//svg:path').path + new_dashes = self.effect.svg.getElement('//svg:path').path + assert len(new_dashes) > len(old_dashes) diff --git a/share/extensions/tests/test_deprecated_simple.py b/share/extensions/tests/test_deprecated_simple.py new file mode 100644 index 0000000..60b2524 --- /dev/null +++ b/share/extensions/tests/test_deprecated_simple.py @@ -0,0 +1,198 @@ +# coding=utf-8 +"""Test deprecated-simple modules""" +from __future__ import absolute_import, print_function + +import warnings +import math +import os +import re + +from pytest import approx + +import inkex +from inkex.tester import TestCase + +class DeprecatedTest(TestCase): + """Tests for Deprecated API (Inkscape 0.92 and below)""" + def setUp(self): + # All the functions in this test suite are deprecated, so + # we don't need the warnings here. + self.warner = warnings.catch_warnings() + self.warner.__enter__() + warnings.simplefilter('ignore', category=DeprecationWarning) + + def tearDown(self): + self.warner.__exit__() + + def test_simple_imports(self): + """Can import each module""" + # TODO add tests for these modules + import bezmisc + import cspsubdiv + import cubicsuperpath + import ffgeom + # pylint: disable=unused-variable + from inkex import debug, errormsg, localize + + def test_simplepath(self): + """Test simplepath API""" + import simplepath + + data = 'M12 34L56 78Z' + path = simplepath.parsePath(data) + self.assertEqual(path, [['M', [12., 34.]], ['L', [56., 78.]], ['Z', []]]) + + d_out = simplepath.formatPath(path) + d_out = d_out.replace('.0', '') + self.assertEqual(data.replace(' ', ''), d_out.replace(' ', '')) + + simplepath.translatePath(path, -3, -4) + self.assertEqual(path, [['M', [9., 30.]], ['L', [53., 74.]], ['Z', []]]) + + simplepath.scalePath(path, 10, 20) + self.assertEqual(path, [['M', [90., 600.]], ['L', [530., 1480.]], ['Z', []]]) + + simplepath.rotatePath(path, math.pi / 2.0, cx=5, cy=7) + approxed = [[code, approx(coords)] for (code, coords) in path] + self.assertEqual(approxed, [['M', [-588., 92.]], ['L', [-1468., 532.]], ['Z', []]]) + + + def test_simplestyle(self): + """Test simplestyle API""" + import simplestyle + + self.assertEqual(simplestyle.svgcolors['blue'], '#0000ff') + self.assertEqual(simplestyle.parseStyle('foo: bar; abc-def: 123em'), { + 'foo': 'bar', + 'abc-def': '123em' + }) + self.assertEqual(simplestyle.formatStyle({'foo': 'bar'}), 'foo:bar') + self.assertTrue(simplestyle.isColor('#ff0000')) + self.assertTrue(simplestyle.isColor('#f00')) + self.assertTrue(simplestyle.isColor('blue')) + self.assertFalse(simplestyle.isColor('none')) + self.assertFalse(simplestyle.isColor('nosuchcolor')) + self.assertEqual(simplestyle.parseColor('#0000ff'), (0, 0, 0xff)) + self.assertEqual(simplestyle.parseColor('red'), (0xff, 0, 0)) + self.assertEqual(simplestyle.formatColoria([0, 0x99, 0]), '#009900') + self.assertEqual(simplestyle.formatColor3i(0, 0x99, 0), '#009900') + self.assertEqual(simplestyle.formatColorfa([0, 1.0, 0]), '#00ff00') + self.assertEqual(simplestyle.formatColor3f(0, 1.0, 0), '#00ff00') + + + def test_simpletransform(self): + """Test simpletransform API""" + import simpletransform + + self.assertEqual(simpletransform.parseTransform('scale(10)'), [[10, 0, 0], [0, 10, 0]]) + self.assertEqual(simpletransform.parseTransform('translate(2,3)'), [[1, 0, 2], [0, 1, 3]]) + self.assertEqual(simpletransform.parseTransform('translate(2,3) rotate(90)'), [ + approx([0, -1, 2]), approx([1, 0, 3]) + ]) + m = simpletransform.formatTransform([[0, -1, 2], [1, 0, 3]]) + self.assertEqual(re.sub(r',', ' ', re.sub(r'\.0*\b', '', m)), 'matrix(0 1 -1 0 2 3)') + self.assertEqual(simpletransform.invertTransform([[1,0,2], [0,1,3]]), [[1,0,-2],[0,1,-3]]) + self.assertEqual(simpletransform.composeTransform( + [[1, 0, 2], [0, 1, 3]], + [[0, -1, 0], [1, 0, 0]]), [[0, -1, 2], [1, 0, 3]]) + + pt = [4, 5] + self.assertEqual(simpletransform.applyTransformToPoint([[0, -1, 2], [1, 0, 3]], pt), None) + self.assertEqual(pt, [-3, 7]) + + self.assertEqual(simpletransform.boxunion([3, 5, 2, 4], [4, 6, 1, 3]), (3, 6, 1, 4)) + self.assertEqual(simpletransform.cubicExtrema(1, 2, 3, 4), (1, 4)) + + # TODO need cubic superpath + self.assertTrue(simpletransform.applyTransformToPath) + self.assertTrue(simpletransform.roughBBox) + self.assertTrue(simpletransform.refinedBBox) + + # TODO need node + self.assertTrue(simpletransform.fuseTransform) + self.assertTrue(simpletransform.composeParents) + self.assertTrue(simpletransform.applyTransformToNode) + self.assertTrue(simpletransform.computeBBox) + self.assertTrue(simpletransform.computePointInNode) + + def test_namespace_pollution(self): + """Test modules with legacy proxies""" + + import optparse + self.assertEqual(optparse.OptionParser, inkex.optparse.OptionParser) + + import lxml.etree + self.assertEqual(lxml.etree.Element, inkex.etree.Element) + + # skip: + # - copy + # - os + # - random + # - re + # - sys + # - math.* + + def test_inkex_namespace(self): + """Test inkex namespace API""" + from inkex import InkOption + self.assertIn('inkbool', InkOption.TYPES) + self.assertIn('inkbool', InkOption.TYPE_CHECKER) + + from inkex import NSS + self.assertEqual(NSS['svg'], 'http://www.w3.org/2000/svg') + + from inkex import addNS + self.assertEqual(addNS('rect', 'svg'), '{http://www.w3.org/2000/svg}rect') + + from inkex import are_near_relative + self.assertTrue(are_near_relative(123.4, 123.5, 1e-3)) + self.assertFalse(are_near_relative(123.4, 123.5, 1e-4)) + + # skip: + # - from inkex import check_inkbool (InkOption implementation detail) + + def test_inkex_effect(self): + """Test original Effect base class""" + from inkex import Effect + + args = [ + '--id', 'curve', + os.path.join(os.path.dirname(__file__), 'data', 'svg/curves.svg'), + ] + + e = Effect() + e.affect(args) + + # assigned in __init__ + self.assertNotEqual(e.document.getroot(), None) + self.assertTrue(isinstance(e.selected, dict)) + self.assertEqual(list(e.selected), ['curve']) + self.assertTrue(isinstance(e.doc_ids, dict)) + self.assertTrue(isinstance(e.options.ids, list)) + self.assertEqual(e.args, args[-1:]) + self.assertNotEqual(e.OptionParser.add_option, None) + + # methods + self.assertEqual(e.getselected(), None) + self.assertEqual(e.getdocids(), None) + node = e.getElementById('arc') + self.assertEqual(node.tag, '{http://www.w3.org/2000/svg}path') + self.assertEqual(node.get('id'), 'arc') + self.assertEqual(e.getParentNode(node).tag, '{http://www.w3.org/2000/svg}g') + self.assertEqual(e.getNamedView().tag, \ + '{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}namedview') + self.assertEqual(e.createGuide(10, 20, 45).tag, \ + '{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}guide') + self.assertTrue(e.uniqueId('foo').startswith('foo')) + self.assertEqual(e.xpathSingle('//svg:path').tag, '{http://www.w3.org/2000/svg}path') + self.assertEqual(e.getDocumentWidth(), '1000') + self.assertEqual(e.getDocumentHeight(), '1000') + self.assertEqual(e.getDocumentUnit(), 'px') + self.assertEqual(e.unittouu('1in'), 96) + self.assertEqual(e.uutounit(192, 'in'), 2) + self.assertEqual(e.addDocumentUnit('3'), '3px') + + # skip: + # - e.ctx + # - e.getposinlayer + # - e.original_document diff --git a/share/extensions/tests/test_dhw_input.py b/share/extensions/tests/test_dhw_input.py new file mode 100644 index 0000000..cd02ea2 --- /dev/null +++ b/share/extensions/tests/test_dhw_input.py @@ -0,0 +1,18 @@ +# coding=utf-8 + +from dhw_input import DhwInput + +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy + + +class TestDxfInput(ComparisonMixin, TestCase): + effect_class = DhwInput + compare_file = [ + 'io/PAGE_001.DHW', + 'io/PGLT_161.DHW', + 'io/PGLT_162.DHW', + 'io/PGLT_163.DHW', + ] + compare_filters = [CompareNumericFuzzy()] + comparisons = [()] diff --git a/share/extensions/tests/test_dimension.py b/share/extensions/tests/test_dimension.py new file mode 100644 index 0000000..97fa232 --- /dev/null +++ b/share/extensions/tests/test_dimension.py @@ -0,0 +1,10 @@ +# coding=utf-8 +from dimension import Dimension +from inkex.tester import ComparisonMixin, TestCase + +class TestDimensionBasic(ComparisonMixin, TestCase): + effect_class = Dimension + comparisons = [ + ('--id=p1', '--id=r3'), + ('--id=p1', '--id=r3', '--type=visual'), + ] diff --git a/share/extensions/tests/test_docinfo.py b/share/extensions/tests/test_docinfo.py new file mode 100644 index 0000000..1390d5b --- /dev/null +++ b/share/extensions/tests/test_docinfo.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from docinfo import DocInfo +from inkex.tester import ComparisonMixin, TestCase + +class TestDocInfo(ComparisonMixin, TestCase): + compare_file = 'svg/guides.svg' + effect_class = DocInfo + stderr_output = True + comparisons = [()] diff --git a/share/extensions/tests/test_dpiswitcher.py b/share/extensions/tests/test_dpiswitcher.py new file mode 100644 index 0000000..176e496 --- /dev/null +++ b/share/extensions/tests/test_dpiswitcher.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from dpiswitcher import DPISwitcher +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy + +class TestDPISwitcherBasic(ComparisonMixin, TestCase): + effect_class = DPISwitcher + compare_filters = [CompareNumericFuzzy()] diff --git a/share/extensions/tests/test_draw_from_triangle.py b/share/extensions/tests/test_draw_from_triangle.py new file mode 100644 index 0000000..4782127 --- /dev/null +++ b/share/extensions/tests/test_draw_from_triangle.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from draw_from_triangle import DrawFromTriangle +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + +class DrawFromTriangleBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = DrawFromTriangle + comparisons = [ + ('--id=p1', '--id=r3'), + ] diff --git a/share/extensions/tests/test_dxf12_outlines.py b/share/extensions/tests/test_dxf12_outlines.py new file mode 100644 index 0000000..f1a6633 --- /dev/null +++ b/share/extensions/tests/test_dxf12_outlines.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from dxf12_outlines import DxfTwelve +from inkex.tester import InkscapeExtensionTestMixin, TestCase + + +class TestDXF12OutlinesBasic(InkscapeExtensionTestMixin, TestCase): + effect_class = DxfTwelve diff --git a/share/extensions/tests/test_dxf_input.py b/share/extensions/tests/test_dxf_input.py new file mode 100644 index 0000000..80cbcb3 --- /dev/null +++ b/share/extensions/tests/test_dxf_input.py @@ -0,0 +1,20 @@ +# coding=utf-8 + +from dxf_input import DxfInput + +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy + +class TestDxfInputBasic(ComparisonMixin, TestCase): + compare_file = ['io/test_r12.dxf', 'io/test_r14.dxf'] + compare_filters = [CompareNumericFuzzy()] + comparisons = [()] + effect_class = DxfInput + + def _apply_compare_filters(self, data, is_saving=None): + """Remove the full pathnames""" + if is_saving is True: + return data + data = super(TestDxfInputBasic, self)._apply_compare_filters(data) + return data.replace((self.datadir() + '/').encode('utf-8'), b'') + diff --git a/share/extensions/tests/test_dxf_outlines.py b/share/extensions/tests/test_dxf_outlines.py new file mode 100644 index 0000000..76a2350 --- /dev/null +++ b/share/extensions/tests/test_dxf_outlines.py @@ -0,0 +1,13 @@ +# coding=utf-8 +from dxf_outlines import DxfOutlines +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + + +class DFXOutlineBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = DxfOutlines + comparisons = [ + (), + ('--id=p1', '--id=r3'), + ('--POLY=true',), + ('--ROBO=true',), + ] diff --git a/share/extensions/tests/test_edge3d.py b/share/extensions/tests/test_edge3d.py new file mode 100644 index 0000000..f96f84e --- /dev/null +++ b/share/extensions/tests/test_edge3d.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# coding=utf-8 +import inkex +from edge3d import Edge3D +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy, CompareWithPathSpace + +class Edge3dBasicTest(ComparisonMixin, TestCase): + effect_class = Edge3D + compare_filters = [CompareNumericFuzzy(), CompareWithPathSpace()] + comparisons = [('--id=p1', '--id=r3'),] + + def test_basic(self): + args = ['--id=edgeme', + self.data_file('svg', 'edge3d.svg')] + self.effect.run(args) + old_paths = self.effect.original_document.getroot()\ + .xpath('//svg:path[@id="edgeme"]') + new_paths = self.effect.svg.xpath('//svg:path[@id="edgeme"]') + self.assertEqual(len(old_paths), 1) + self.assertEqual(len(new_paths), 1) + old_paths = self.effect.original_document.getroot().xpath('//svg:path') + new_paths = self.effect.svg.xpath('//svg:path') + self.assertEqual(len(old_paths), 1) + self.assertEqual(len(new_paths), 4) diff --git a/share/extensions/tests/test_embedimage.py b/share/extensions/tests/test_embedimage.py new file mode 100644 index 0000000..e7e7736 --- /dev/null +++ b/share/extensions/tests/test_embedimage.py @@ -0,0 +1,10 @@ +# coding=utf-8 +from embedimage import EmbedImage +from inkex.tester import ComparisonMixin, TestCase + +class EmbedderBasicTest(ComparisonMixin, TestCase): + effect_class = EmbedImage + compare_file = 'svg/images.svg' + comparisons = ( + (), + ) diff --git a/share/extensions/tests/test_export_gimp_palette.py b/share/extensions/tests/test_export_gimp_palette.py new file mode 100644 index 0000000..c322b31 --- /dev/null +++ b/share/extensions/tests/test_export_gimp_palette.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from export_gimp_palette import ExportGimpPalette +from inkex.tester import ComparisonMixin, TestCase + +class TestExportGplBasic(ComparisonMixin, TestCase): + effect_class = ExportGimpPalette + compare_file = 'svg/colors.svg' diff --git a/share/extensions/tests/test_extractimage.py b/share/extensions/tests/test_extractimage.py new file mode 100644 index 0000000..88044ba --- /dev/null +++ b/share/extensions/tests/test_extractimage.py @@ -0,0 +1,40 @@ +# coding=utf-8 + +import os +from extractimage import ExtractImage +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + +class ExtractImageBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + stderr_protect = False + effect_class = ExtractImage + compare_file = 'svg/images.svg' + comparisons = [ + ('--selectedonly=False',), + ('--selectedonly=True', '--id=embeded_image01'), + ] + + def test_all_comparisons(self): + """Images are extracted to a file directory""" + for args in self.comparisons: + outdir = os.path.join(self.tempdir, 'img') + args += ('--filepath={}/'.format(outdir),) + self.assertEffect(self.compare_file, args=args) + + outfile = os.path.join(outdir, 'embeded_image01.png') + self.assertTrue(os.path.isfile(outfile), "No output file created! {}".format(outfile)) + + with open(outfile, 'rb') as fhl: + data_a = fhl.read() + + self.assertTrue(data_a, "No data produced with {}".format(args)) + + outfile = self.get_compare_outfile(args) + if os.environ.get('EXPORT_COMPARE', False): + with open(outfile + '.export', 'wb') as fhl: + fhl.write(data_a) + print("Written output: {}.export".format(outfile)) + + with open(outfile, 'rb') as fhl: + data_b = fhl.read() + + self.assertEqual(data_a, data_b) diff --git a/share/extensions/tests/test_extrude.py b/share/extensions/tests/test_extrude.py new file mode 100644 index 0000000..b996c2f --- /dev/null +++ b/share/extensions/tests/test_extrude.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# coding=utf-8 +from extrude import Extrude +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentStyleAndPath, CompareWithPathSpace + +class ExtrudeBasicTest(ComparisonMixin, TestCase): + effect_class = Extrude + comparisons = [('--id=p1', '--id=p2')] + compare_filters = [CompareWithPathSpace(), CompareOrderIndependentStyleAndPath()] diff --git a/share/extensions/tests/test_fig_input.py b/share/extensions/tests/test_fig_input.py new file mode 100644 index 0000000..d2c59ac --- /dev/null +++ b/share/extensions/tests/test_fig_input.py @@ -0,0 +1,10 @@ +# coding=utf-8 + +from fig_input import FigInput + +from inkex.tester import ComparisonMixin, TestCase + +class TestFigInput(ComparisonMixin, TestCase): + effect_class = FigInput + compare_file = 'io/test.fig' + comparisons = [()] diff --git a/share/extensions/tests/test_flatten.py b/share/extensions/tests/test_flatten.py new file mode 100644 index 0000000..ff3d63a --- /dev/null +++ b/share/extensions/tests/test_flatten.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from flatten import Flatten +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy, CompareWithPathSpace + +class FlattenBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + compare_filters = [CompareNumericFuzzy(), CompareWithPathSpace()] + effect_class = Flatten diff --git a/share/extensions/tests/test_foldablebox.py b/share/extensions/tests/test_foldablebox.py new file mode 100644 index 0000000..0ef7ac5 --- /dev/null +++ b/share/extensions/tests/test_foldablebox.py @@ -0,0 +1,11 @@ +# coding=utf-8 +from foldablebox import FoldableBox +from inkex.tester import ComparisonMixin, TestCase + +class FoldableBoxArguments(ComparisonMixin, TestCase): + effect_class = FoldableBox + compare_file = 'svg/empty.svg' + comparisons = [ + ('--width=20', '--height=20', '--depth=2.2'), + ('--proportion=0.5', '--guide=true'), + ] diff --git a/share/extensions/tests/test_fractalize.py b/share/extensions/tests/test_fractalize.py new file mode 100644 index 0000000..b01b99a --- /dev/null +++ b/share/extensions/tests/test_fractalize.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from fractalize import Fractalize +from inkex.tester import ComparisonMixin, TestCase + +class PathFractalizeBasicTest(ComparisonMixin, TestCase): + effect_class = Fractalize + comparisons = [('--id=p1', '--id=p2')] diff --git a/share/extensions/tests/test_frame.py b/share/extensions/tests/test_frame.py new file mode 100644 index 0000000..6428065 --- /dev/null +++ b/share/extensions/tests/test_frame.py @@ -0,0 +1,96 @@ +# coding=utf-8 +# +# Copyright (C) 2016 Richard White, rwhite8282@gmail.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" +An Inkscape frame extension test class. +""" +from __future__ import absolute_import, print_function, unicode_literals + +import inkex +from frame import Frame +from inkex.tester import InkscapeExtensionTestMixin, TestCase + + +class FrameTest(InkscapeExtensionTestMixin, TestCase): + effect_class = Frame + + def get_frame(self, svg): + return svg.getElement('//svg:g[@id="layer1"]//svg:path[@inkscape:label="Frame"]') + + def test_single_frame(self): + args = ['--corner_radius=20', + '--fill_color=-16777124', + '--id=rect3006', + '--position=inside', + '--stroke_color=255', + '--tab="stroke"', + '--width=10', + self.data_file('svg', 'single_box.svg')] + uut = Frame() + uut.run(args) + new_frame = self.get_frame(uut.svg) + self.assertIsNotNone(new_frame) + self.assertEqual('{http://www.w3.org/2000/svg}path', new_frame.tag) + new_frame_style = new_frame.attrib['style'].lower() + self.assertTrue('fill-opacity:0.36' in new_frame_style, + 'Invalid fill-opacity in "' + new_frame_style + '".') + self.assertTrue('stroke:#000000' in new_frame_style, + 'Invalid stroke in "' + new_frame_style + '".') + self.assertTrue('stroke-width:10.0' in new_frame_style, + 'Invalid stroke-width in "' + new_frame_style + '".') + self.assertTrue('fill:#ff0000' in new_frame_style, + 'Invalid fill in "' + new_frame_style + '".') + + def test_single_frame_grouped(self): + args = ['--corner_radius=20', + '--fill_color=-16777124', + '--group=True', + '--id=rect3006', + '--position=inside', + '--stroke_color=255', + '--tab="stroke"', + '--width=10', + self.data_file('svg', 'single_box.svg')] + uut = Frame() + uut.run(args) + new_frame = self.get_frame(uut.svg) + self.assertIsNotNone(new_frame) + self.assertEqual('{http://www.w3.org/2000/svg}path', new_frame.tag) + group = new_frame.getparent() + self.assertEqual('{http://www.w3.org/2000/svg}g', group.tag) + self.assertEqual('{http://www.w3.org/2000/svg}rect', group[0].tag) + self.assertEqual('{http://www.w3.org/2000/svg}path', group[1].tag) + self.assertEqual("Frame", group[1].label) + + def test_single_frame_clipped(self): + uut = self.assertEffect( + 'svg', 'single_box.svg', + clip=True, + corner_radius=20, + fill_color=-16777124, + id='rect3006', + position='inside', + stroke_color=255, + tab="stroke", + width=10) + new_frame = self.get_frame(uut.svg) + self.assertIsNotNone(new_frame) + self.assertEqual('{http://www.w3.org/2000/svg}path', new_frame.tag) + orig = list(uut.svg.selected.values())[0] + self.assertEqual('url(#clipPath5815)', orig.get('clip-path')) + clip_path = uut.svg.getElement('//svg:defs/svg:clipPath') + self.assertEqual('{http://www.w3.org/2000/svg}clipPath', clip_path.tag) diff --git a/share/extensions/tests/test_funcplot.py b/share/extensions/tests/test_funcplot.py new file mode 100644 index 0000000..3993c61 --- /dev/null +++ b/share/extensions/tests/test_funcplot.py @@ -0,0 +1,11 @@ +# coding=utf-8 +from funcplot import FuncPlot +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy + +class FuncPlotBasicTest(ComparisonMixin, TestCase): + effect_class = FuncPlot + compare_filters = [CompareNumericFuzzy()] + comparisons = [ + ('--id=p1', '--id=r3'), + ] diff --git a/share/extensions/tests/test_gcodetools.py b/share/extensions/tests/test_gcodetools.py new file mode 100644 index 0000000..f98fbb0 --- /dev/null +++ b/share/extensions/tests/test_gcodetools.py @@ -0,0 +1,83 @@ +# coding=utf-8 + +import sys +import os + +from gcodetools import Gcodetools +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentBytes + +SETTINGS = ( + '--id=p1', '--max-area-curves=100', + '--area-inkscape-radius=-10', '--area-tool-overlap=0', + '--area-fill-angle=0', '--area-fill-shift=0', '--area-fill-method=0', + '--area-fill-method=0', '--area-find-artefacts-diameter=5', + '--area-find-artefacts-action=mark with an arrow', + '--biarc-tolerance=1', '--biarc-max-split-depth=4', + '--path-to-gcode-order=subpath by subpath', + '--path-to-gcode-depth-function=d', + '--path-to-gcode-sort-paths=false', '--Zscale=1', '--Zoffset=0', + '--auto_select_paths=true', '--min-arc-radius=0.05000000074505806', + '--comment-gcode-from-properties=false', '--create-log=false', + '--add-numeric-suffix-to-filename=false', '--Zsafe=5', + '--unit=G21 (All units in mm)', '--postprocessor= ', +) +FILESET = SETTINGS + ('--directory=/home', '--filename=output.ngc',) + +class TestGcodetoolsBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + stderr_protect = False + effect_class = Gcodetools + comparisons = [ + FILESET + ('--active-tab="area_fill"',), + FILESET + ('--active-tab="area"',), + FILESET + ('--active-tab="area_artefacts"',), + FILESET + ('--active-tab="dxfpoints"',), + FILESET + ('--active-tab="orientation"',), + FILESET + ('--active-tab="tools_library"',), + FILESET + ('--active-tab="lathe_modify_path"',), + FILESET + ('--active-tab="offset"',), + ] + compare_filters = [CompareOrderIndependentBytes()] + + def test_all_comparisons(self): + """ + gcodetools tries to write to a folder and filename specified + on the command line, this needs to be handled carefully. + """ + for tab in ( + ('--active-tab="path-to-gcode"',), + #('--active-tab="engraving"',), + #('--active-tab="graffiti"',), + ('--active-tab="lathe"',), + ): + args = SETTINGS + tab + ( + '--directory={}'.format(self.tempdir), + '--filename=output.ngc', + ) + self.assertEffect(self.compare_file, args=args) + + outfile = os.path.join(self.tempdir, 'output.ngc') + self.assertTrue(os.path.isfile(outfile), "No output file created! {}".format(outfile)) + + with open(outfile, 'rb') as fhl: + data_a = fhl.read() + + self.assertTrue(data_a, "No data produced with {}".format(tab)) + + outfile = self.get_compare_outfile(args) + if os.environ.get('EXPORT_COMPARE', False): + with open(outfile + '.export', 'wb') as fhl: + fhl.write(data_a) + print("Written output: {}.export".format(outfile)) + + with open(outfile, 'rb') as fhl: + data_b = fhl.read() + + self.assertEqual(data_a, data_b) + +if sys.version_info[0] == 3: + # This changes output between python2 and python3, we don't know + # why and don't have the gcodetool developers to help us understand. + TestGcodetoolsBasic.comparisons.append( + FILESET + ('--active-tab="plasma-prepare-path"',), + ) diff --git a/share/extensions/tests/test_generate_voronoi.py b/share/extensions/tests/test_generate_voronoi.py new file mode 100644 index 0000000..6d24765 --- /dev/null +++ b/share/extensions/tests/test_generate_voronoi.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from generate_voronoi import GenerateVoronoi +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentStyle + +class TestPatternBasic(ComparisonMixin, TestCase): + effect_class = GenerateVoronoi + comparisons = [('--id=r3', '--id=p1'),] + compare_filters = [CompareOrderIndependentStyle()] diff --git a/share/extensions/tests/test_gimp_xcf.py b/share/extensions/tests/test_gimp_xcf.py new file mode 100644 index 0000000..876b59c --- /dev/null +++ b/share/extensions/tests/test_gimp_xcf.py @@ -0,0 +1,20 @@ +# coding=utf-8 +""" +Unit test file for ../gimp_xcf.py +Revision history: + * 2012-01-26 (jazzynico): checks defaulf parameters and file handling. +""" + +from gimp_xcf import GimpXcf +from inkex.tester import ComparisonMixin, TestCase + +class GimpXcfBasicTest(ComparisonMixin, TestCase): + """Test the Gimp XCF file saving functionality""" + effect_class = GimpXcf + comparisons = [()] + +class GimpXcfGuidesTest(ComparisonMixin, TestCase): + """Test that Gimp XCF output can include guides and grids""" + effect_class = GimpXcf + compare_file = 'svg/guides.svg' + comparisons = [('-d=true', '-r=true'),] diff --git a/share/extensions/tests/test_grid_cartesian.py b/share/extensions/tests/test_grid_cartesian.py new file mode 100644 index 0000000..77cd5be --- /dev/null +++ b/share/extensions/tests/test_grid_cartesian.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from grid_cartesian import GridCartesian +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentStyle + +class GridCartesianBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = GridCartesian + compare_filters = [CompareOrderIndependentStyle()] diff --git a/share/extensions/tests/test_grid_isometric.py b/share/extensions/tests/test_grid_isometric.py new file mode 100644 index 0000000..d73b9d8 --- /dev/null +++ b/share/extensions/tests/test_grid_isometric.py @@ -0,0 +1,10 @@ +# coding=utf-8 + +from grid_isometric import GridIsometric +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentStyle, CompareWithPathSpace + +class TestGridIsometricBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + compare_filters = [CompareOrderIndependentStyle(), CompareWithPathSpace()] + effect_class = GridIsometric + comparisons = [()] diff --git a/share/extensions/tests/test_grid_polar.py b/share/extensions/tests/test_grid_polar.py new file mode 100644 index 0000000..511f55f --- /dev/null +++ b/share/extensions/tests/test_grid_polar.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from grid_polar import GridPolar +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentStyle + +class GridPolarBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + compare_filters = [CompareOrderIndependentStyle()] + effect_class = GridPolar diff --git a/share/extensions/tests/test_guides_creator.py b/share/extensions/tests/test_guides_creator.py new file mode 100644 index 0000000..4d927fb --- /dev/null +++ b/share/extensions/tests/test_guides_creator.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# coding=utf-8 +from guides_creator import GuidesCreator +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy + +class GuidesCreatorBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = GuidesCreator + compare_file = 'svg/guides.svg' + compare_filters = [CompareNumericFuzzy(),] + + comparisons = [ + ('--tab=regular_guides', '--guides_preset=custom'), + ('--tab=regular_guides', '--guides_preset=golden', '--delete=True'), + ('--tab=regular_guides', '--guides_preset=5;5', '--start_from_edges=True'), + ('--tab=diagonal_guides',), + ('--tab=margins', '--start_from_edges=True', '--margins_preset=custom'), + ('--tab=margins', '--start_from_edges=True', '--margins_preset=book_left'), + ('--tab=margins', '--start_from_edges=True', '--margins_preset=book_right'), + ] diff --git a/share/extensions/tests/test_guillotine.py b/share/extensions/tests/test_guillotine.py new file mode 100644 index 0000000..d478491 --- /dev/null +++ b/share/extensions/tests/test_guillotine.py @@ -0,0 +1,58 @@ +# coding=utf-8 +import os +import tarfile +from guillotine import Guillotine +from inkex.tester import ComparisonMixin, TestCase + +class TestGuillotineBasic(ComparisonMixin, TestCase): + """Test the Guillotine extension""" + stderr_protect = False + effect_class = Guillotine + compare_file = 'svg/guides.svg' + comparisons = [ + ('--image=f{}oo',), + ('--ignore=true',), + ] + + def test_all_comparisons(self): + """Images are extracted to a file directory""" + for args in self.comparisons: + # Create a landing directory for generated images + outdir = os.path.join(self.tempdir, 'img') + args += ('--directory={}/'.format(outdir),) + + # But also set this directory into the compare file + compare_file = os.path.join(self.tempdir, 'compare_file.svg') + with open(self.data_file(self.compare_file), 'rb') as rhl: + with open(compare_file, 'wb') as whl: + whl.write(rhl.read().replace(b'{tempdir}', outdir.encode('utf8'))) + + self.assertEffect(compare_file, args=args) + self.assertTrue(os.path.isdir(outdir)) + + infile = self.get_compare_outfile(args) + if os.environ.get('EXPORT_COMPARE', False): + self.export_comparison(outdir, infile) + + with tarfile.open(infile) as tar_handle: + for item in tar_handle: + fileobj = tar_handle.extractfile(item) + with open(os.path.join(outdir, item.name), 'rb') as fhl: + self.assertEqual(fileobj.read(), fhl.read(), "File '{}'".format(item.name)) + + @staticmethod + def export_comparison(outdir, outfile): + """Export the files as a tar file for manual comparison""" + tarname = outfile + '.export' + tar = tarfile.open(tarname, 'w|') + + # We make a tar archive so we can test it. + for name in sorted(os.listdir(outdir)): + with open(os.path.join(outdir, name), 'rb') as fhl: + fhl.seek(0, 2) + info = tarfile.TarInfo(name) + info.size = fhl.tell() + fhl.seek(0) + tar.addfile(info, fhl) + tar.close() + print("Written output: {}.export".format(outfile)) diff --git a/share/extensions/tests/test_handles.py b/share/extensions/tests/test_handles.py new file mode 100644 index 0000000..280aeda --- /dev/null +++ b/share/extensions/tests/test_handles.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +# coding=utf-8 +from handles import Handles +from inkex.tester import ComparisonMixin, TestCase + +class HandlesBasicTest(ComparisonMixin, TestCase): + effect_class = Handles + compare_file = 'svg/curves.svg' + comparisons = ( + ('--id=curve', '--id=quad'), + ) diff --git a/share/extensions/tests/test_hershey.py b/share/extensions/tests/test_hershey.py new file mode 100644 index 0000000..f76eac8 --- /dev/null +++ b/share/extensions/tests/test_hershey.py @@ -0,0 +1,57 @@ +# coding=utf-8 +# tests for the hershey-text extension (hershey.py and hershey.inx) +from lxml import etree + +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy, CompareOrderIndependentStyle +from inkex.tester.svg import svg, svg_file, uu_svg + +from hershey import Hershey + +class HersheyComparisonMixin(ComparisonMixin): + comparisons_outfile_dict = {} # pairs of args and expected outputs + + def setUp(self): + self.effect_class = Hershey + self.compare_filters = [CompareNumericFuzzy(), CompareOrderIndependentStyle()] + + self.comparisons = self.comparisons_outfile_dict.keys() + + def get_compare_outfile(self, args, addout=None): + ''' get the correct outfile to compare from comparisons_dict; ''' + return self.data_file('refs', self.comparisons_outfile_dict[args]) + +class TestHersheyBasic(InkscapeExtensionTestMixin, HersheyComparisonMixin, TestCase): + compare_file = 'svg/hershey_input.svg' # a huge number of inputs + comparisons_outfile_dict = { + # default parameters: + (): 'hershey.out', + # same as above, but explicit parameters. same output: + ('--tab="render"', '--fontface="HersheySans1"', '--preserve="False"'): 'hershey.out', + } + +class TestHersheyTrivialInput(InkscapeExtensionTestMixin, HersheyComparisonMixin, TestCase): + compare_file = 'svg/hershey_trivial_input.svg' + comparisons_outfile_dict = { + # loading a different font: + ('--fontface="EMSAllure"', ): 'hershey_loadfont.out', + # using the "other font" option. same output as above: + ('--fontface="other"', '--otherfont="EMSAllure"'): 'hershey_loadfont.out', + # tests preserve text option + ('--fontface="EMSOsmotron"', '--preserve=true'): 'hershey_preservetext.out', + # tests when just part of the input file is selected + ('--id=A',): 'hershey_partialselection.out', + } + +class TestHersheyTables(InkscapeExtensionTestMixin, HersheyComparisonMixin, TestCase): + compare_file = 'svg/default-inkscape-SVG.svg' + comparisons_outfile_dict = { + # generates a simple font table: + ('--tab="utilities"', '--action="sample"', '--text="I am a quick brown fox"'): 'hershey_fonttable.out', + # generates a simple font table, while testing UTF-8 input + ('--tab="utilities"', '--action="sample"', '--text="Î âm å qù¡çk brõwñ fø×"'): 'hershey_encoding.out', + # generates a glyph table in the font "EMSOsmotron" + ('--tab="utilities"', '--action="table"', '--fontface="other"', '--otherfont="EMSOsmotron"'): 'hershey_glyphtable.out', + } + + diff --git a/share/extensions/tests/test_hpgl_decoder.py b/share/extensions/tests/test_hpgl_decoder.py new file mode 100644 index 0000000..d9c701f --- /dev/null +++ b/share/extensions/tests/test_hpgl_decoder.py @@ -0,0 +1,25 @@ +# coding=utf-8 +from hpgl_decoder import hpglDecoder + + +class Options(object): + """ + A dummy class because hpglDecoder expects an object as it's second argument + TODO: This requirement probably needs to be factored out of the original code. + """ + + def __init__(self): + self.resolutionX = None + self.resolutionY = None + self.docHeight = None + + +class TesthpglDecoderBasic(object): + def test_init_values_scale(self): + x = Options() + x.resolutionX = 25.4 + x.resolutionY = 25.4 + h = hpglDecoder("", x) + + assert h.scaleX == 1 + assert h.scaleY == 1 diff --git a/share/extensions/tests/test_hpgl_input.py b/share/extensions/tests/test_hpgl_input.py new file mode 100644 index 0000000..c6700e4 --- /dev/null +++ b/share/extensions/tests/test_hpgl_input.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from hpgl_input import HpglInput +from inkex.tester import ComparisonMixin, TestCase + +class TestHpglFileBasic(ComparisonMixin, TestCase): + effect_class = HpglInput + compare_file = 'io/test.hpgl' + comparisons = [()] diff --git a/share/extensions/tests/test_hpgl_output.py b/share/extensions/tests/test_hpgl_output.py new file mode 100644 index 0000000..c7512dd --- /dev/null +++ b/share/extensions/tests/test_hpgl_output.py @@ -0,0 +1,12 @@ +# coding=utf-8 +from hpgl_output import HpglOutput +from inkex.tester import ComparisonMixin, TestCase + +class HPGLOutputBasicTest(ComparisonMixin, TestCase): + effect_class = HpglOutput + compare_file = [ + 'svg/shapes.svg', + 'svg/hpgl_multipen.svg' + ] + python3_only = True + comparisons = [()] diff --git a/share/extensions/tests/test_image_attributes.py b/share/extensions/tests/test_image_attributes.py new file mode 100644 index 0000000..e6e523b --- /dev/null +++ b/share/extensions/tests/test_image_attributes.py @@ -0,0 +1,12 @@ +# coding=utf-8 +from image_attributes import ImageAttributes +from inkex.tester import ComparisonMixin, TestCase + +class TestSetAttrImageBasic(ComparisonMixin, TestCase): + effect_class = ImageAttributes + compare_file = 'svg/images.svg' + comparisons = [ + (), # All images in the document (basic) + ('--id=image174', '--aspect_ratio=xMinYMin', '--tab="tab_aspect_ratio"'), + ('--id=embeded_image01', '--image_rendering=optimizeSpeed', '--tab="tab_image_rendering"'), + ] diff --git a/share/extensions/tests/test_ink2canvas_svg.py b/share/extensions/tests/test_ink2canvas_svg.py new file mode 100644 index 0000000..766736e --- /dev/null +++ b/share/extensions/tests/test_ink2canvas_svg.py @@ -0,0 +1,11 @@ +#!/usr/bin/en +# coding=utf-8 +from ink2canvas import Html5Canvas +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentLines + +class Ink2CanvasBasicTest(ComparisonMixin, TestCase): + effect_class = Html5Canvas + compare_file = 'svg/shapes-clipboard.svg' + compare_filters = [CompareOrderIndependentLines()] + comparisons = [()] diff --git a/share/extensions/tests/test_inkex.py b/share/extensions/tests/test_inkex.py new file mode 100644 index 0000000..1f550dd --- /dev/null +++ b/share/extensions/tests/test_inkex.py @@ -0,0 +1,63 @@ +# +# Copyright (C) 2020 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Test API collisions and other inkex portions that don't fit elsewhere. +""" + +from unittest import TestCase as BaseCase + +import inkex +import inkex.paths +import inkex.elements +from inkex.utils import PY3 + +class ProtectiveGlobals(dict): + """Python 3.3 and above globals dictionary""" + def __setitem__(self, name, value): + # This only works because setitem is called during construction It + # does not work for getitem and that's why the python docs discourage + # the use of an inherited dictionary class for exec globals. + if name in self and value is not self[name]: + assert value is self[name], ( + "While importing {} the API name `{}` was re-defined:" + "\n\t1. {}" + "\n\t2. {}" + ).format(self['__name__'], name, repr(value), repr(self[name])) + super(ProtectiveGlobals, self).__setitem__(name, value) + +class TestModuleCollisions(BaseCase): + """Test imports to make sure the API is clean""" + def assertNoCollisions(self, module): # pylint: disable=invalid-name + """Make sure there are no API collisions in the give module on import""" + if not PY3: + self.skipTest("API testing python 3.3 and above only.") + + with open(module.__file__, 'r') as fhl: + # name and package are esential to the exec pretending to + # be an actual module during import (and not a script) + exec(fhl.read(), ProtectiveGlobals({ # pylint: disable=exec-used + '__name__': module.__name__, + '__package__': module.__package__})) + + def test_inkex(self): + """Test inkex API have no collisions""" + self.assertNoCollisions(inkex) + + def test_inkex_elements(self): + """Test elements API have no collisions""" + self.assertNoCollisions(inkex.elements) diff --git a/share/extensions/tests/test_inkex_base.py b/share/extensions/tests/test_inkex_base.py new file mode 100644 index 0000000..0981c3d --- /dev/null +++ b/share/extensions/tests/test_inkex_base.py @@ -0,0 +1,136 @@ +# coding=utf-8 +"""Test base inkex module functionality""" +from __future__ import absolute_import, print_function, unicode_literals + +import os +import sys + +from io import BytesIO + +from inkex.base import InkscapeExtension, SvgThroughMixin +from inkex.tester import TestCase + +class ModExtension(InkscapeExtension): + """A non-svg extension that loads, saves and flipples""" + + def effect(self): + self.document += b'>flipple' + + def load(self, stream): + return stream.read() + + def save(self, stream): + stream.write(self.document) + + +class NoModSvgExtension(SvgThroughMixin, InkscapeExtension): + """Test the loading and not-saving of non-modified svg files""" + + def effect(self): + return True + + +class ModSvgExtension(SvgThroughMixin, InkscapeExtension): + """Test the loading and saving of svg files""" + + def effect(self): + self.svg.set('attr', 'foo') + + +class InkscapeExtensionTest(TestCase): + """Tests for Inkscape Extensions""" + effect_class = InkscapeExtension + + def setUp(self): + self.e = self.effect_class() + + def test_bare_bones(self): + """What happens when we don't inherit""" + with self.assertRaises(NotImplementedError): + self.e.run([]) + with self.assertRaises(NotImplementedError): + prevarg = sys.argv + sys.argv = ['pytest'] + try: + self.e.run() + finally: + sys.argv = prevarg + with self.assertRaises(NotImplementedError): + self.e.effect() + with self.assertRaises(NotImplementedError): + self.e.load(sys.stdin) + with self.assertRaises(NotImplementedError): + self.e.save(sys.stdout) + self.assertEqual(self.e.name, 'InkscapeExtension') + + def test_compat(self): + """Test a few old functions and how we handle them""" + with self.assertRaises(AttributeError): + self.assertEqual(self.e.OptionParser, None) + with self.assertRaises(AttributeError): + self.assertEqual(self.e.affect(), None) + + def test_arg_parser_defaults(self): + """Test arguments for the base class are given defaults""" + options = self.e.arg_parser.parse_args([]) + self.assertEqual(options.input_file, None) + self.assertEqual(options.output, None) + + def test_arg_parser_passed(self): + """Test arguments for the base class are parsed""" + options = self.e.arg_parser.parse_args(['--output', 'foo.txt', self.empty_svg]) + self.assertEqual(options.input_file, self.empty_svg) + self.assertEqual(options.output, 'foo.txt') + + def test_svg_path(self): + """Can get the svg file location""" + output = os.path.join(self.tempdir, 'output.tmp') + ext = ModExtension() + ext.run(['--output', output, self.empty_svg]) + self.assertEqual(ext.svg_path(), os.path.join(self.datadir(), 'svg')) + self.assertEqual(ext.absolute_href('/foo'), '/foo') + self.assertEqual(ext.absolute_href('./foo'), os.path.join(self.datadir(), 'svg', 'foo')) + self.assertEqual(ext.absolute_href('~/foo'), os.path.realpath(os.path.expanduser('~/foo'))) + ext.options.input_file = None + self.assertEqual(ext.absolute_href('./foo'), os.path.realpath(os.path.expanduser('~/foo'))) + tmp_foo = os.path.realpath('/tmp/foo') + self.assertEqual(ext.absolute_href('./foo', '/tmp/'), tmp_foo) + + +class SvgInputOutputTest(TestCase): + """Test SVG Input Mixin""" + + def test_input_mixin(self): + """Test svg input gets loaded""" + obj = NoModSvgExtension() + obj.run([self.empty_svg]) + self.assertNotEqual(obj.document, None) + self.assertNotEqual(obj.original_document, None) + + def test_no_output(self): + """Test svg output isn't saved when not modified""" + obj = NoModSvgExtension() + filename = self.temp_file(suffix='.svg') + obj.run(['--output', filename, self.empty_svg]) + self.assertEqual(type(obj.document).__name__, '_ElementTree') + self.assertEqual(type(obj.svg).__name__, 'SvgDocumentElement') + self.assertFalse(os.path.isfile(filename)) + + def test_svg_output(self): + """Test svg output is saved""" + obj = ModSvgExtension() + filename = self.temp_file(suffix='.svg') + obj.run(['--output', filename, self.empty_svg]) + self.assertTrue(os.path.isfile(filename)) + with open(filename, 'r') as fhl: + self.assertIn(' 3) diff --git a/share/extensions/tests/test_inkex_deprecated.py b/share/extensions/tests/test_inkex_deprecated.py new file mode 100644 index 0000000..b569a76 --- /dev/null +++ b/share/extensions/tests/test_inkex_deprecated.py @@ -0,0 +1,36 @@ +# coding=utf-8 +"""Test base inkex module functionality""" +from __future__ import absolute_import, print_function, unicode_literals + +import warnings + +from inkex.deprecated import _deprecated +from inkex.tester import TestCase + +class DeprecatedTests(TestCase): + """Test ways in which we deprecate code""" + maxDiff = 10000 + + def assertDeprecated(self, call, msg, *args, **kwargs): # pylint: disable=invalid-name + """Catch deprecation warnings and test their output""" + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter("always") + call(*args, **kwargs) + if msg is None: + self.assertFalse(warns, "Expected no warnings, got warnings!") + else: + self.assertTrue(warns, "No warning was returned, expected warning!") + if msg is not False: + self.assertEqual(str(warns[0].category.__name__), "DeprecationWarning") + return warns[0] + + def test_warning(self): + """What happens when we deprecate things""" + self.assertDeprecated(_deprecated, None, "", stack=0, level=0) + self.assertDeprecated(_deprecated, "FOO", "FOO", stack=0, level=1) + + def test_traceback(self): + """Traceback is possible for deprecation warnings""" + warn = self.assertDeprecated(_deprecated, False, "BAR", stack=0, level=2) + self.assertIn("inkex/deprecated.py", str(warn.message)) + self.assertIn("test_inkex_deprecated.py", str(warn.message)) diff --git a/share/extensions/tests/test_inkex_elements.py b/share/extensions/tests/test_inkex_elements.py new file mode 100644 index 0000000..c7a93be --- /dev/null +++ b/share/extensions/tests/test_inkex_elements.py @@ -0,0 +1,496 @@ +#!/usr/bin/env python +# coding=utf-8 +""" +Test specific elements API from svg xml lxml custom classes. +""" + +import pytest +import inkex + +from inkex import ( + Group, Layer, Pattern, Guide, Polyline, Use, Defs, + TextElement, TextPath, Tspan, FlowPara, FlowRoot, FlowRegion, FlowSpan, + PathElement, Rectangle, Circle, Ellipse, Anchor, Line as LineElement, + Transform, Style, LinearGradient, RadialGradient, Stop +) +from inkex.colors import Color +from inkex.paths import Move, Line +from inkex.utils import FragmentError, PY3 + +from .test_inkex_elements_base import SvgTestCase + +class ElementTestCase(SvgTestCase): + """Base element testing""" + tag = 'svg' + + def setUp(self): + super(ElementTestCase, self).setUp() + self.elem = self.svg.getElement('//svg:{}'.format(self.tag)) + + def test_print(self): + """Print element as string""" + self.assertEqual(str(self.elem), self.tag) + + def assertElement(self, elem, compare): # pylint: disable=invalid-name + """Assert an element""" + if PY3: + self.assertEqual(elem.tostring(), compare) + + +class PathElementTestCase(ElementTestCase): + """Test PathElements""" + source_file = 'with-lpe.svg' + tag = 'path' + + def test_new_path(self): + """Test new path element""" + path = PathElement.new(path=[Move(10, 10), Line(20, 20)]) + self.assertEqual(path.get('d'), 'M 10 10 L 20 20') + + def test_original_path(self): + """LPE paths can return their original paths""" + lpe = self.svg.getElementById('lpe') + nolpe = self.svg.getElementById('nolpe') + self.assertEqual(str(lpe.path), 'M 30 30 L -10 -10 Z') + self.assertEqual(str(lpe.original_path), 'M 20 20 L 10 10 Z') + self.assertEqual(str(nolpe.path), 'M 30 30 L -10 -10 Z') + self.assertEqual(str(nolpe.original_path), 'M 30 30 L -10 -10 Z') + + lpe.original_path = "M 60 60 L 5 5" + self.assertEqual(lpe.get('inkscape:original-d'), 'M 60 60 L 5 5') + self.assertEqual(lpe.get('d'), 'M 30 30 L -10 -10 Z') + + lpe.path = "M 60 60 L 15 15 Z" + self.assertEqual(lpe.get('d'), 'M 60 60 L 15 15 Z') + + nolpe.original_path = "M 60 60 L 5 5" + self.assertEqual(nolpe.get('inkscape:original-d', None), None) + self.assertEqual(nolpe.get('d'), 'M 60 60 L 5 5') + +class PolylineElementTestCase(ElementTestCase): + """Test the polyline elements support""" + tag = 'polyline' + + def test_type(self): + """Polyline have their own types""" + self.assertTrue(isinstance(self.elem, inkex.Polyline)) + + def test_polyline_points(self): + """Basic tests for points attribute as a path""" + pol = Polyline(points='10,10 50,50 10,15 15,10') + self.assertEqual(str(pol.path), 'M 10 10 L 50 50 L 10 15 L 15 10') + pol.path = "M 10 10 L 30 9 L 1 2 C 10 45 3 4 45 60 M 35 35" + self.assertEqual(pol.get('points'), '10,10 30,9 1,2 45,60 35,35') + +class PolygonElementTestCase(ElementTestCase): + """Test Polygon Elements""" + tag = 'polygon' + + def test_type(self): + """Polygons have their own types""" + self.assertTrue(isinstance(self.elem, inkex.Polygon)) + + def test_conversion(self): + """Polygones are converted to paths""" + pol = inkex.Polygon(points='10,10 50,50 10,15 15,10') + self.assertEqual(str(pol.path), 'M 10 10 L 50 50 L 10 15 L 15 10 Z') + +class LineElementTestCase(ElementTestCase): + """Test Line Elements""" + tag = 'line' + + def test_new_line(self): + """Line creation""" + line = LineElement.new((10, 10), (20, 20)) + self.assertElement(line, b'') + + def test_type(self): + """Lines have their own types""" + self.assertTrue(isinstance(self.elem, inkex.Line)) + + def test_conversion(self): + """Lines are converted to paths""" + pol = inkex.elements.Line(x1='2', y1='3', x2='4', y2='5') + self.assertEqual(str(pol.path), 'M 2 3 L 4 5 Z') + +class PatternTestCase(ElementTestCase): + """Test Pattern elements""" + tag = 'pattern' + + def test_pattern_transform(self): + """Patterns have a transformation of their own""" + pattern = Pattern() + self.assertEqual(pattern.patternTransform, Transform()) + pattern.patternTransform.add_translate(10, 10) + self.assertEqual(pattern.get('patternTransform'), 'translate(10, 10)') + +class GroupTest(ElementTestCase): + """Test extra functionality on a group element""" + tag = 'g' + + def test_new_group(self): + """Test creating groups""" + svg = Layer.new('layerA', Group.new('groupA', Rectangle())) + self.assertElement(svg,\ + b''\ + b'') + + def test_transform_property(self): + """Test getting and setting a transform""" + self.assertEqual(str(self.elem.transform), 'matrix(1.44985 0 0 1.36417 -107.03 -167.362)') + self.elem.transform = 'translate(12, 14)' + self.assertEqual(self.elem.transform, Transform('translate(12, 14)')) + self.assertEqual(str(self.elem.transform), 'translate(12, 14)') + + def test_groupmode(self): + """Get groupmode is layer""" + self.assertEqual(self.svg.getElementById('A').groupmode, 'layer') + self.assertEqual(self.svg.getElementById('C').groupmode, 'group') + + def test_get_path(self): + """Group path is combined children""" + print(str(self.svg.getElementById('A').get_path())) + self.assertEqual( + str(self.svg.getElementById('A').get_path()), + 'M -108.539 517.61 L -87.6093 496.117 L -98.3066 492.768 L -69.9353 492.301 L -55.5172' + ' 506.163 L -66.2146 502.814 L -87.1446 524.307 M 60.0914 498.694 L 156.784 439.145 L' + ' 240.218 491.183 L 143.526 550.731 z M -176.909 458.816 a 64.2385 38.9175 -7.86457 1' + ' 0 88.3701 -19.0784 a 64.2385 38.9175 -7.86457 0 0 -88.3701 19.0784 z M -300.162' + ' 513.715 L -282.488 509.9 Z M -214.583 540.504 L -209.001 448.77 Z M -193.189 547.201 ' + 'L -238.536 486.266 L -185.049 503.008 L -230.396 442.073 M -193.189 547.201 L -238.536' + ' 486.266 L -185.049 503.008 L -230.396 442.073 Z M 15 15 L 15.5 20 Z') + + def test_bounding_box(self): + """A group returns a bounding box""" + empty = self.svg.add(Group(Group())) + self.assertEqual(empty.bounding_box(), None) + self.assertEqual(int(self.svg.getElementById('A').bounding_box().width), 783) + self.assertEqual(int(self.svg.getElementById('B').bounding_box().height), 114) + +class RectTest(ElementTestCase): + """Test extra functionality on a rectangle element""" + tag = 'rect' + + def test_parse(self): + """Test Rectangle parsed from XML""" + rect = Rectangle(attrib={ + "x": "10px", "y": "20px", + "width": "100px", "height": "200px", + "rx": "15px", "ry": "30px" }) + self.assertEqual(rect.left, 10) + self.assertEqual(rect.top, 20) + self.assertEqual(rect.right, 10+100) + self.assertEqual(rect.bottom, 20+200) + self.assertEqual(rect.width, 100) + self.assertEqual(rect.height, 200) + self.assertEqual(rect.rx, 15) + self.assertEqual(rect.ry, 30) + + def test_compose_transform(self): + """Composed transformation""" + self.assertEqual(self.elem.transform, Transform('rotate(16.097889)')) + self.assertEqual(str(self.elem.composed_transform()), + 'matrix(1.4019 -0.812338 1.20967 0.709877 -542.221 533.431)') + + def test_effetive_stylesheet(self): + """Test the non-parent combination of styles""" + self.assertEqual(str(self.elem.effective_style()),\ + 'fill:#0000ff;stroke-width:1px') + self.assertEqual(str(self.elem.getparent().effective_style()),\ + 'fill:#0000ff;stroke-width:1px;stroke:#f00') + + def test_compose_stylesheet(self): + """Test finding the composed stylesheet for the shape""" + self.assertEqual(str(self.elem.style), 'fill:#0000ff;stroke-width:1px') + self.assertEqual(str(self.elem.composed_style()), + 'fill:#0000ff;stroke:#d88;joker:url(#path1);stroke-width:1px') + + def test_path(self): + """Rectangle path""" + self.assertEqual(self.elem.get_path(), 'M 200.0,200.0 h100.0v100.0h-100.0 z') + self.assertEqual(str(self.elem.path), 'M 200 200 h 100 v 100 h -100 z') + +class PathTest(ElementTestCase): + """Test path extra functionality""" + tag = 'path' + + def test_apply_transform(self): + """Transformation can be applied to path""" + path = self.svg.getElementById('D') + path.transform = Transform(translate=(10, 10)) + self.assertEqual(path.get('d'), 'M30,130 L60,130 L60,120 L70,140 L60,160 L60,150 L30,150') + path.apply_transform() + self.assertEqual(path.get('d'), 'M 40 140 L 70 140 L 70 130 L 80 150 ' + 'L 70 170 L 70 160 L 40 160') + self.assertFalse(path.transform) + +class CircleTest(ElementTestCase): + """Test extra functionality on a circle element""" + tag = 'circle' + + def test_parse(self): + """Test Circle parsed from XML""" + circle = Circle(attrib={"cx": "10px", "cy": "20px", "r": "30px"}) + self.assertEqual(circle.center.x, 10) + self.assertEqual(circle.center.y, 20) + self.assertEqual(circle.radius, 30) + ellipse = Ellipse(attrib={"cx": "10px", "cy": "20px", "rx": "30px", "ry": "40px"}) + self.assertEqual(ellipse.center.x, 10) + self.assertEqual(ellipse.center.y, 20) + self.assertEqual(ellipse.radius.x, 30) + self.assertEqual(ellipse.radius.y, 40) + + def test_new(self): + """Test new circles""" + elem = Circle.new((10, 10), 50) + self.assertElement(elem, b'') + elem = Ellipse.new((10, 10), (15, 10)) + self.assertElement(elem, b'') + + def test_path(self): + """Circle path""" + self.assertEqual(self.elem.get_path(), + 'M 100.0,50.0 a 50.0,50.0 0 1 0 50.0, ' + '50.0 a 50.0,50.0 0 0 0 -50.0, -50.0 z') + +class AnchorTest(ElementTestCase): + """Test anchor tags""" + def test_new(self): + """Anchor tag creation""" + link = Anchor.new('https://inkscape.org', Rectangle()) + self.assertElement(link, b'') + +class NamedViewTest(ElementTestCase): + """Test the sodipodi namedview tag""" + def test_guides(self): + """Create a guide and see a list of them""" + self.svg.namedview.add(Guide().move_to(0, 0, 0)) + self.svg.namedview.add(Guide().move_to(0, 0, '90')) + self.assertEqual(len(self.svg.namedview.get_guides()), 2) + +class TextTest(ElementTestCase): + """Test all text functions""" + def test_append_superscript(self): + """Test adding superscript""" + tap = TextPath() + tap.append(Tspan.superscript('th')) + self.assertEqual(len(tap), 1) + + def test_path(self): + """Test getting paths""" + self.assertFalse(TextPath().get_path()) + self.assertFalse(TextElement().get_path()) + self.assertFalse(FlowRegion().get_path()) + self.assertFalse(FlowRoot().get_path()) + self.assertFalse(FlowPara().get_path()) + self.assertFalse(FlowSpan().get_path()) + self.assertFalse(Tspan().get_path()) + + +class UseTest(ElementTestCase): + """Test extra functionality on a use element""" + tag = 'use' + + def test_path(self): + """Use path follows ref""" + self.assertEqual(str(self.elem.path), 'M 0 0 L 10 10 Z') + + def test_empty_ref(self): + """An empty ref or None ref doesn't cause an error""" + use = Use() + use.set('xlink:href', 'something') + self.assertRaises(FragmentError, getattr, use, 'href') + elem = self.svg.add(Use()) + self.assertEqual(elem.href, None) + elem.set('xlink:href', '') + self.assertEqual(elem.href, None) + elem.set('xlink:href', '#badref') + self.assertEqual(elem.href, None) + elem.set('xlink:href', self.elem.get('xlink:href')) + self.assertEqual(elem.href.get('id'), 'path1') + + def test_unlink(self): + """Test use tag unlinking""" + elem = self.elem.unlink() + self.assertEqual(str(elem.path), 'M 0 0 L 10 10 Z') + self.assertEqual(elem.tag_name, 'path') + self.assertEqual(elem.getparent().get('id'), 'C') + +class StopTests(ElementTestCase): + """Color stop tests""" + black = Color('#000000') + grey50 = Color('#080808') + white = Color('#111111') + + def test_interpolate(self): + """Interpolate colours""" + stl1 = Style({'stop-color': self.black, 'stop-opacity': 0.0}) + stop1 = Stop(offset='0.0', style=str(stl1)) + stl2 = Style({'stop-color': self.white, 'stop-opacity': 1.0}) + stop2 = Stop(offset='1.0', style=str(stl2)) + stop3 = stop1.interpolate(stop2, 0.5) + assert stop3.style['stop-color'] == str(self.grey50) + assert float(stop3.style['stop-opacity']) == pytest.approx(0.5, 1e-3) + + +class GradientTests(ElementTestCase): + """Gradient testing""" + black = Color('#000000') + grey50 = Color('#080808') + white = Color('#111111') + + whiteop1 = Style({'stop-color': white, 'stop-opacity': 1.0}) + blackop1 = Style({'stop-color': black, 'stop-opacity': 1.0}) + whiteop0 = Style({'stop-color': white, 'stop-opacity': 0.0}) + blackop0 = Style({'stop-color': black, 'stop-opacity': 0.0}) + + translate11 = Transform('translate(1.0, 1.0)') + translate22 = Transform('translate(2.0, 2.0)') + + def test_parse(self): + """Gradients parsed from XML""" + values = [ + (LinearGradient, + {'x1': '0px', 'y1': '1px', 'x2': '2px', 'y2': '3px'}, + {'x1': 0.0, 'y1': 1.0, 'x2': 2.0, 'y2': 3.0}, + ), + (RadialGradient, + {'cx': '0px', 'cy': '1px', 'fx': '2px', 'fy': '3px', 'r': '4px'}, + {'cx': 0.0, 'cy': 1.0, 'fx': 2.0, 'fy': 3.0} + )] + for classname, attributes, expected in values: + grad = classname(attrib=attributes) + grad.apply_transform() # identity transform + for key, value in expected.items(): + assert float(grad.get(key)) == pytest.approx(value, 1e-3) + grad = classname(attrib=attributes) + grad = grad.interpolate(grad, 0.0) + for key, value in expected.items(): + assert float(grad.get(key)) == pytest.approx(value, 1e-3) + + def test_apply_transform(self): + """Transform gradients""" + values = [ + (LinearGradient, + {'x1': 0.0, 'y1': 0.0, 'x2': 1.0, 'y2': 1.0}, + {'x1': 1.0, 'y1': 1.0, 'x2': 2.0, 'y2': 2.0}), + (RadialGradient, + {'cx': 0.0, 'cy': 0.0, 'fx': 1.0, 'fy': 1.0, 'r': 1.0}, + {'cx': 1.0, 'cy': 1.0, 'fx': 2.0, 'fy': 2.0, 'r': 1.0} + )] + for classname, orientation, expected in values: + grad = classname().update(**orientation) + grad.gradientTransform = self.translate11 + grad.apply_transform() + val = grad.get('gradientTransform') + assert val is None + for key, value in expected.items(): + assert float(grad.get(key)) == pytest.approx(value, 1e-3) + + def test_stops(self): + """Gradients have stops""" + for classname in [LinearGradient, RadialGradient]: + grad = classname() + stops = [ + Stop().update(offset=0.0, style=self.whiteop0), + Stop().update(offset=1.0, style=self.blackop1)] + grad.add(*stops) + assert [s1.tostring() == s2.tostring() for s1, s2 in zip(grad.stops, stops)] + + def test_stop_styles(self): + """Gradients have styles""" + for classname in [LinearGradient, RadialGradient]: + grad = classname() + stops = [ + Stop().update(offset=0.0, style=self.whiteop0), + Stop().update(offset=1.0, style=self.blackop1)] + grad.add(*stops) + assert [str(s1) == str(s2.style) for s1, s2 in zip(grad.stop_styles, stops)] + + def test_get_stop_offsets(self): + """Gradients stop offsets""" + for classname in [LinearGradient, RadialGradient]: + grad = classname() + stops = [ + Stop().update(offset=0.0, style=self.whiteop0), + Stop().update(offset=1.0, style=self.blackop1)] + grad.add(*stops) + for stop1, stop2 in zip(grad.stop_offsets, stops): + self.assertEqual(float(stop1), pytest.approx(float(stop2.offset), 1e-3)) + + def test_interpolate(self): + """Gradients can be interpolated""" + values = [ + (LinearGradient, + {'x1': 0, 'y1': 0, 'x2': 1, 'y2': 1}, + {'x1': 2, 'y1': 2, 'x2': 1, 'y2': 1}, + {'x1': 1.0, 'y1': 1.0, 'x2': 1.0, 'y2': 1.0}), + (RadialGradient, + {'cx': 0, 'cy': 0, 'fx': 1, 'fy': 1, 'r': 0}, + {'cx': 2, 'cy': 2, 'fx': 1, 'fy': 1, 'r': 1}, + {'cx': 1.0, 'cy': 1.0, 'fx': 1.0, 'fy': 1.0, 'r': 0.5}) + ] + for classname, orientation1, orientation2, expected in values: + # gradient 1 + grad1 = classname() + stops1 = [ + Stop().update(offset=0.0, style=self.whiteop0), + Stop().update(offset=1.0, style=self.blackop1)] + grad1.add(*stops1) + grad1.update(gradientTransform=self.translate11) + grad1.update(**orientation1) + + # gradient 2 + grad2 = classname() + stops2 = [ + Stop().update(offset=0.0, style=self.blackop1), + Stop().update(offset=1.0, style=self.whiteop0)] + grad2.add(*stops2) + grad2.update(gradientTransform=self.translate22) + grad2.update(**orientation2) + grad = grad1.interpolate(grad2, 0.5) + comp = Style({'stop-color': self.grey50, 'stop-opacity': 0.5}) + self.assertEqual(str(grad.stops[0].style), str(Style(comp))) + self.assertEqual(str(grad.stops[1].style), str(Style(comp))) + self.assertEqual(str(grad.gradientTransform), 'translate(1.5, 1.5)') + for key, value in expected.items(): + self.assertEqual(float(grad.get(key)), pytest.approx(value, 1e-3)) + + +class SymbolTest(ElementTestCase): + """Test Symbol elements""" + source_file = 'symbol.svg' + tag = 'symbol' + + def test_unlink_symbol(self): + """Test unlink symbols""" + use = self.svg.getElementById('plane01') + self.assertEqual(use.tag_name, 'use') + self.assertEqual(use.href.tag_name, 'symbol') + # Unlinking should replace symbol with group + elem = use.unlink() + self.assertEqual(elem.tag_name, 'g') + self.assertEqual(str(elem.transform), 'translate(18, 16)') + self.assertEqual(elem[0].tag_name, 'title') + self.assertEqual(elem[1].tag_name, 'rect') + +class DefsTest(ElementTestCase): + """Test the definitions tag""" + source_file = 'shapes.svg' + tag = 'defs' + + def test_defs(self): + """Make sure defs can be seen in the nodes of an svg""" + self.assertTrue(isinstance(self.svg.defs, Defs)) + defs = self.svg.getElementById('defs33') + self.assertTrue(isinstance(defs, Defs)) + +class StyleTest(ElementTestCase): + """Test a style tag""" + source_file = 'css.svg' + tag = 'style' + + def test_style(self): + """Make sure style tags can be loaded and saved""" + css = self.svg.stylesheet + self.assertTrue(css) diff --git a/share/extensions/tests/test_inkex_elements_base.py b/share/extensions/tests/test_inkex_elements_base.py new file mode 100644 index 0000000..02ecd72 --- /dev/null +++ b/share/extensions/tests/test_inkex_elements_base.py @@ -0,0 +1,322 @@ +#!/usr/bin/env python +# coding=utf-8 +""" +Test the element API base classes and basic functionality +""" +from lxml import etree + +from inkex.elements import ( + load_svg, ShapeElement, Group, Rectangle, Tspan, TextElement, Line, +) +from inkex.transforms import Transform +from inkex.styles import Style +from inkex.utils import FragmentError +from inkex.tester import TestCase +from inkex.tester.svg import svg_file + +class FakeShape(ShapeElement): # pylint: disable=abstract-method + """A protend shape""" + tag_name = 'fake' + +class SvgTestCase(TestCase): + """Test SVG""" + source_file = 'complextransform.test.svg' + + def setUp(self): + super(SvgTestCase, self).setUp() + self.svg = svg_file(self.data_file('svg', self.source_file)) + +class OverridenElementTestCase(SvgTestCase): + """Test element overriding functionality""" + def test_tag_names(self): + """ + Test tag names for custom and unknown tags + """ + doc = load_svg(""" + + + Unknown SVG tag + +""") + svg = doc.getroot() + + good = svg.getElementById("good") + self.assertEqual(good.TAG, "g") + bad = svg.getElementById("bad") + self.assertEqual(bad.TAG, "badsvg") + ugly = svg.getElementById("ugly") + self.assertEqual(ugly.TAG, "othertag") + + def test_reference_count(self): + """ + Test inkex.element.BaseElement-derived object type is preserved on adding to group + + See https://gitlab.com/inkscape/extensions/-/issues/81 for details + """ + grp = Group() + for _ in range(10): + rect = Rectangle() + grp.add(rect) + + for elem in grp: + self.assertEqual(type(elem), Rectangle) + + def test_abstract_raises(self): + """Abstract classes cannot be instantiated""" + self.assertRaises(NotImplementedError, FakeShape().get_path) + self.assertRaises(AttributeError, FakeShape().set_path, None) + + def test_add(self): + """Can add single or multiple elements with passthrough""" + elem = self.svg.getElementById('D') + group = elem.add(Group(id='foo')) + self.assertEqual(group.get('id'), 'foo') + groups = elem.add(Group(id='f1'), Group(id='f2')) + self.assertEqual(len(groups), 2) + self.assertEqual(groups[0].get('id'), 'f1') + self.assertEqual(groups[1].get('id'), 'f2') + + def test_creation(self): + """Create elements with attributes""" + group = Group().update(inkscape__label='Foo') + self.assertEqual(group.get('inkscape:label'), 'Foo') + group = Group().update(inkscape__label='Bar') + self.assertEqual(group.label, 'Bar') + + def test_tostring(self): + """Elements can be printed as strings""" + self.assertEqual(Group().tostring(), b'') + elem = Group(id='bar') + path = elem.add(Tspan(id='foo')) + elem.transform.add_translate(50, 50) + path.style['fill'] = 'red' + self.assertEqual(elem.tostring(), \ + b'') + +class AttributeHandelingTestCase(SvgTestCase): + """Test how attributes are handled""" + def test_chained_multiple_attrs(self): + """Set multiple attributes at a time""" + group = Group().update( + attr1='A', + attr2='B' + ).update( + attr3='C', + attr4='D' + ) + self.assertEqual(group.get('attr1'), 'A') + self.assertEqual(group.get('attr2'), 'B') + self.assertEqual(group.get('attr3'), 'C') + self.assertEqual(group.get('attr4'), 'D') + + # remove attributes, setting them to None + group.update( + attr1=None, + attr4=None + ) + + self.assertEqual(group.get('attr1'), None) + self.assertEqual(group.get('attr2'), 'B') + self.assertEqual(group.get('attr3'), 'C') + self.assertEqual(group.get('attr4'), None) + + self.assertEqual(group.pop('attr2'), 'B') + self.assertEqual(group.pop('attr3'), 'C') + + def test_set_wrapped_attribute(self): + """Remove wrapped attribute using .set()""" + group = Group().update( + transform=Transform(scale=2) + ) + self.assertEqual(group.transform.matrix[0][0], 2) + self.assertEqual(group.transform.matrix[1][1], 2) + + group.update( + transform=None + ) + self.assertEqual(group.transform, Transform()) + + def test_pop_wrapped_attribute(self): + """Remove wrapped attribute using .pop()""" + group = Group() + + self.assertEqual(group.pop('transform'), Transform()) + + group.update( + transform=Transform(scale=2) + ) + self.assertEqual(group.pop('transform'), Transform(scale=2)) + self.assertEqual(group.pop('transform'), Transform()) + self.assertRaises(AttributeError, getattr, group, 'foo') + + def test_pop_regular_attribute(self): + """Remove wrapped attribute using .pop()""" + group = Group() + + self.assertEqual(group.get('attr1'), None) + + group.update( + attr1="42" + ) + self.assertEqual(group.pop('attr1'), "42") + self.assertEqual(group.pop('attr1'), None) + + def test_update_consistant(self): + """Update doesn't keep callbacks around""" + elem = self.svg.getElementById('D') + tr_a = Transform(translate=(10, 10)) + tr_b = Transform(translate=(-20, 15)) + elem.transform = tr_a + elem.transform = tr_b + self.assertEqual(str(elem.transform), 'translate(-20, 15)') + tr_a.add_translate(10, 10) + self.assertEqual(str(elem.transform), 'translate(-20, 15)') + elem.set('transform', None) + self.assertEqual(elem.get('transform'), None) + + def test_in_place_style(self): + """Do styles update when we set them""" + elem = self.svg.getElementById('D') + elem.style['fill'] = 'purpleberry' + self.assertEqual(elem.get('style'), 'fill:purpleberry') + elem.style = {'marker': 'flag'} + self.assertEqual(elem.get('style'), 'marker:flag') + elem.style = Style(stroke='gammon') + self.assertEqual(elem.get('style'), 'stroke:gammon') + elem.style.update('grape:2;strawberry:nice;') + self.assertEqual(elem.get('style'), 'stroke:gammon;grape:2;strawberry:nice') + + def test_random_id(self): + """Test setting a random id""" + elem = self.svg.getElementById('D') + elem.set_random_id('Thing') + self.assertEqual(elem.get('id'), 'Thing5815') + elem.set_random_id('Thing', size=2) + self.assertEqual(elem.get('id'), 'Thing85') + elem.set_random_id() + self.assertEqual(elem.get('id'), 'path5392') + # No document root, no random id allowed + self.assertRaises(FragmentError, elem.copy().set_random_id) + + def test_random_ids(self): + """Test setting a tree of ids""" + elem = self.svg.getElementById('D') + self.svg.set_random_ids(prefix='TreeItem') + self.assertEqual(self.svg.get('id'), 'TreeItem5815') + self.assertEqual(self.svg[0].get('id'), 'TreeItem8555') + self.assertEqual(elem.get('id'), 'TreeItem2036') + + def test_set_id_backlinks(self): + """Changing an id can update backlinks""" + elem = self.svg.getElementById('path1') + elem.set_id('plant54', True) + self.assertEqual(self.svg.getElementById('G').get('xlink:href'), '#plant54') + self.assertEqual(self.svg.getElementById('G').href, elem) + self.assertEqual(str(self.svg.getElementById('B').style), 'fill:#eee;joker:url(#plant54)') + + def test_get_element_by_name(self): + """Get elements by name""" + self.assertEqual(self.svg.getElementByName('Key').get('id'), 'K') + self.assertEqual(self.svg.getElementByName('Elm', 'svg:g').get('id'), 'L') + self.assertEqual(self.svg.getElementByName('Mine').get('id'), 'M') + self.assertEqual(self.svg.getElementByName('doesntexist'), None) + self.assertEqual(self.svg.getElementByName('Key', 'rect'), None) + + +class TransformationTestCase(SvgTestCase): + """Test transformative functions""" + def test_bounding_box(self): + """Elements can have bounding boxes""" + elem = self.svg.getElementById('D') + self.assertEqual(tuple(elem.bounding_box()), ((60.0, 100.0), (130.0, 170.00))) + self.assertTrue(elem.bounding_box().center.is_close((80.0, 150.0))) + self.assertEqual(tuple(TextElement(x='10', y='5').bounding_box()), ((10, 10), (5, 5))) + group = Group(elem) + self.assertEqual(elem.bounding_box(), group.bounding_box()) + + def test_transform(self): + """In-place modified transforms are retained""" + elem = self.svg.getElementById('D') + self.assertEqual(str(elem.transform), 'translate(30, 10)') + elem.transform.add_translate(-10, 10) + self.assertEqual(str(elem.transform), 'translate(20, 20)') + + def test_scale(self): + """In-place scaling from blank transform""" + elem = self.svg.getElementById('F') + self.assertEqual(elem.transform, Transform()) + self.assertEqual(elem.get('transform'), None) + elem.transform.add_scale(1.0666666666666667, 1.0666666666666667) + self.assertEqual(elem.get('transform'), Transform(scale=1.06667)) + self.assertIn(b'transform', etree.tostring(elem)) + + def test_in_place_transforms(self): + """Do style and transforms update correctly""" + elem = self.svg.getElementById('D') + self.assertEqual(type(elem.transform), Transform) + self.assertEqual(type(elem.style), Style) + self.assertTrue(elem.transform) + elem.transform = Transform() + self.assertEqual(elem.transform, Transform()) + self.assertEqual(elem.get('transform'), None) + self.assertNotIn(b'transform', etree.tostring(elem)) + elem.transform.add_translate(10, 10) + self.assertIn(b'transform', etree.tostring(elem)) + elem.transform.add_translate(-10, -10) + self.assertNotIn(b'transform', etree.tostring(elem)) + +class RelationshipTestCase(SvgTestCase): + """Test relationships between elements""" + def test_findall(self): + """Findall elements in svg""" + groups = self.svg.findall('svg:g') + self.assertEqual(len(groups), 1) + + def test_copy(self): + """Test copying elements""" + elem = self.svg.getElementById('D') + cpy = elem.copy() + self.assertFalse(cpy.getparent()) + self.assertFalse(cpy.get('id')) + + def test_duplicate(self): + """Test duplicating elements""" + elem = self.svg.getElementById('D') + dup = elem.duplicate() + self.assertTrue(dup.get('id')) + self.assertNotEqual(elem.get('id'), dup.get('id')) + self.assertEqual(elem.getparent(), dup.getparent()) + + def test_replace_with(self): + """Replacing nodes in a tree""" + rect = self.svg.getElementById('E') + path = rect.to_path_element() + rect.replace_with(path) + self.assertEqual(rect.getparent(), None) + self.assertEqual(path.getparent(), self.svg.getElementById('C')) + + def test_descendants(self): + """Elements can walk their descendants""" + self.assertEqual(tuple(self.svg.descendants().ids), ( + 'mydoc', 'path1', 'base', 'metadata7', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', + )) + get = self.svg.getElementById + self.assertEqual(tuple(get('L').descendants().ids), ('L', 'M')) + self.assertEqual(tuple(get('M').descendants().ids), ('M',)) + + def test_ancestors(self): + """Element descendants of elements""" + get = self.svg.getElementById + self.assertEqual(tuple(get('M').ancestors().ids), ('L', 'K', 'A', 'mydoc')) + self.assertEqual(tuple(get('M').ancestors(stop_at=[None]).ids), ('L', 'K', 'A', 'mydoc')) + self.assertEqual(tuple(get('M').ancestors(stop_at=[get('K')]).ids), ('L', 'K')) + self.assertEqual(tuple(get('M').ancestors(stop_at=[get('L')]).ids), ('L',)) + + def test_luca(self): + """Test last common ancestor""" + get = self.svg.getElementById + self.assertEqual(tuple(get('M').ancestors(get('M')).ids), ('L',)) + self.assertEqual(tuple(get('G').ancestors(get('H')).ids), ('C',)) + self.assertEqual(tuple(get('M').ancestors(get('H')).ids), ('L', 'K', 'A')) diff --git a/share/extensions/tests/test_inkex_elements_selections.py b/share/extensions/tests/test_inkex_elements_selections.py new file mode 100644 index 0000000..4482e6c --- /dev/null +++ b/share/extensions/tests/test_inkex_elements_selections.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# coding=utf-8 +""" +Test all selection code. +""" + +from inkex.elements import PathElement +from inkex.elements._selected import ElementList + +from .test_inkex_elements_base import SvgTestCase + + +class ElementListTestCase(SvgTestCase): + """Test Element Selections""" + def setUp(self): + super(ElementListTestCase, self).setUp() + self.svg.selection.set('G', 'B', 'D', 'F') + + def test_creation(self): + """Creating an elementList""" + empty = ElementList(self.svg) + self.assertEqual(tuple(empty.ids), ()) + self.assertEqual(empty.first(), None) + lst = ElementList(self.svg, 'ABC') + self.assertEqual(tuple(lst.ids), ('A', 'B', 'C')) + + def test_to_dict(self): + """test dictionary compact""" + self.assertEqual(tuple(self.svg.selection.id_dict()), ('G', 'B', 'D', 'F')) + + def test_getitem(self): + """Can get an item""" + self.assertEqual(self.svg.selection['B'].xml_path, '/*/*[4]/*[1]') + self.assertRaises(KeyError, self.svg.selection.__getitem__, 'A') + + def test_svg_selection(self): + """Setting an svg selection""" + self.assertEqual(tuple(self.svg.selection.ids), ('G', 'B', 'D', 'F')) + + def test_paint_order(self): + """Test paint order""" + items = self.svg.selection.paint_order() + self.assertTrue(isinstance(items, ElementList)) + self.assertEqual(tuple(items.ids), ('B', 'D', 'F', 'G')) + + def test_set_nothing(self): + """Clear existing selection""" + self.svg.selection.set() + self.assertEqual(tuple(self.svg.selection), ()) + + def test_set_ids(self): + """Set a new selection element ids""" + a_to_g = 'ABCDEFG' + self.svg.selection.set(*a_to_g) + self.assertEqual(tuple(self.svg.selection.ids), tuple(a_to_g)) + + def test_set_elements(self): + """Set a new selection from element objects""" + a_to_g = 'ABCDEFG' + self.svg.selection.set(*[self.svg.getElementById(eid) for eid in a_to_g]) + self.assertEqual(tuple(self.svg.selection.ids), tuple(a_to_g)) + self.assertRaises(ValueError, self.svg.selection.add, None) + self.assertRaises(ValueError, self.svg.selection.__setitem__, 'A', + self.svg.getElementById('B')) + + def test_set_xpath(self): + """Set a new selection from xpath""" + self.svg.selection.set('//svg:g') + self.assertEqual(tuple(self.svg.selection.ids), tuple('ABCKL')) + + def test_set_invalid_ids(self): + """Set invalid ids""" + self.svg.selection.set('X', 'Y', 'Z', 'A') + self.assertEqual(tuple(self.svg.selection.ids), ('A',)) + + def test_pop_items(self): + """Can remove items from the ElementList""" + selection = self.svg.selection + self.assertEqual(tuple(selection.ids), ('G', 'B', 'D', 'F')) + ret = selection.pop() + self.assertEqual(ret.get('id'), 'F') + self.assertEqual(tuple(selection.ids), ('G', 'B', 'D')) + selection.pop(0) + self.assertEqual(tuple(selection.ids), ('B', 'D')) + selection.pop('B') + self.assertEqual(tuple(selection.ids), ('D',)) + self.assertRaises(KeyError, selection.pop, 'B') + selection.set(*'ABDFH') + self.assertEqual(tuple(selection.ids), ('A', 'B', 'D', 'F', 'H')) + selection.pop(selection.first()) + self.assertEqual(tuple(selection.ids), ('B', 'D', 'F', 'H')) + + def test_filtering(self): + """Create a sub-list of selected items""" + selection = self.svg.descendants() + new_list = selection.filter(PathElement) + self.assertEqual(tuple(new_list.ids), ('path1', 'D')) + + def test_getting_recursively(self): + """Create a list of children of the given type""" + selection = self.svg.selection + selection.set('B') + self.assertEqual(tuple(selection.ids), ('B',)) + self.assertEqual(tuple(selection.get().ids), tuple('BCDEFGHIJ')) + + def test_get_bounding_box(self): + """Selection can get a bounding box""" + self.assertEqual(int(self.svg.selection.bounding_box().width), 540) + self.assertEqual(int(self.svg.selection.bounding_box().height), 550) diff --git a/share/extensions/tests/test_inkex_extensions.py b/share/extensions/tests/test_inkex_extensions.py new file mode 100644 index 0000000..0b06142 --- /dev/null +++ b/share/extensions/tests/test_inkex_extensions.py @@ -0,0 +1,59 @@ +# coding=utf-8 +""" +Test each of the extensions base classes (if needed) and sometimes provide +specialised test classes for testers to use. +""" +import inkex +from inkex.tester import ComparisonMixin, TestCase + +class TurnGreenEffect(inkex.ColorExtension): + """Turn everything the purest green!""" + def modify_color(self, name, color): + return inkex.Color('green') + def modify_opacity(self, name, opacity): + if name == 'opacity': + return 1.0 + return opacity + +class ColorEffectTest(ComparisonMixin, TestCase): + """Direct tests for color mechanisms""" + effect_class = TurnGreenEffect + effect_name = 'inkex_extensions_color' + compare_file = 'svg/colors.svg' + python3_only = True + + comparisons = [ + ('--id=r1',), # One shape only + ('--id=r2',), # CSS Styles + ('--id=r3',), # Element Attributes + ('--id=r4',), # Gradient stops + ('--id=r1', '--id=r2'), # Two shapes + ('--id=color_svg',), # Recursive group/children + (), # Process all shapes + ] + +class ColorBaseCase(TestCase): + """Base class for all color effect extensions""" + color_tests = [] + opacity_tests = [] + + def _test_list(self, tsts): + for tst in tsts: + inp, outp, args = (list(tst) + [[]])[:3] + self.effect.parse_arguments([self.empty_svg] + args) + yield inp, outp + + def test_colors(self): + """Run all color tests""" + for x, (inp, outp) in enumerate(self._test_list(self.color_tests)): + outp = inkex.Color(outp) + got = self.effect._modify_color('fill', inkex.Color(inp)) + self.assertTrue(isinstance(got, inkex.Color),\ + "Bad output type: {}".format(type(got).__name__)) + outp, got = str(outp), str(got.to(outp.space)) + self.assertEqual(outp, got,\ + "Color mismatch, test:{} {} != {}".format(x, outp, got)) + for x, (inp, outp) in enumerate(self._test_list(self.opacity_tests)): + got = self.effect.modify_opacity('opacity', inp) + self.assertTrue(isinstance(got, float)) + self.assertAlmostEqual(got, outp, delta=0.1) diff --git a/share/extensions/tests/test_inkex_inx.py b/share/extensions/tests/test_inkex_inx.py new file mode 100644 index 0000000..3cf03c1 --- /dev/null +++ b/share/extensions/tests/test_inkex_inx.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# coding=utf-8 +""" +Test elements extra logic from svg xml lxml custom classes. +""" + +import os +from glob import glob + +from inkex.utils import PY3 +from inkex.tester import TestCase +from inkex.tester.inx import InxMixin + +class InxTestCase(InxMixin, TestCase): + """Test INX files""" + def test_inx_files(self): + """Get all inx files and test each of them""" + if not PY3: + self.skipTest("No INX testing in python2") + return + for inx_file in glob(os.path.join(self._testdir(), '..', '*.inx')): + self.assertInxIsGood(inx_file) diff --git a/share/extensions/tests/test_inkex_paths.py b/share/extensions/tests/test_inkex_paths.py new file mode 100644 index 0000000..c55660d --- /dev/null +++ b/share/extensions/tests/test_inkex_paths.py @@ -0,0 +1,520 @@ +# coding=utf-8 +""" +Test Inkex path parsing functionality. +""" + +import re + +from inkex.paths import ( + InvalidPath, Path, PathCommand, CubicSuperPath, + line, move, curve, smooth, quadratic, tepidQuadratic, arc, vert, horz, zoneClose, + Line, Move, Horz, Vert, Curve, Smooth, Quadratic, TepidQuadratic, Arc, ZoneClose +) +from inkex.transforms import Transform, Vector2d +from inkex.tester import TestCase + + +class SegmentTest(TestCase): + """ + Test specific segment functionality. + """ + + def get_random_cmd(self, Cmd): + import random + return Cmd(*[random.randint(0, 10) for i in range(Cmd.nargs)]) + + def test_equals(self): + """Segments should be equalitive""" + self.assertEqual(Move(10, 10), Move(10, 10)) + self.assertEqual(Line(10, 10), Line(10, 10)) + self.assertEqual(line(10, 10), line(10, 10)) + self.assertNotEqual(line(10, 10), Line(10, 10)) + self.assertEqual(Horz(10), Line(10, 0)) + self.assertEqual(Vert(10), Line(0, 10)) + self.assertNotEqual(Vert(10), Horz(10)) + + def test_to_curves(self): + """Segments can become curves""" + self.assertRaises(ValueError, Move(0, 0).to_curve, None) + self.assertEqual(Line(10, 10).to_curve(Vector2d(10, 5)), (10, 5, 10, 10, 10, 10)) + self.assertEqual(Horz(10).to_curve(Vector2d(10, 5)), (10, 5, 10, 5, 10, 5)) + self.assertEqual(Vert(10).to_curve(Vector2d(5, 10)), (5, 10, 5, 10, 5, 10)) + self.assertEqual(Curve(5, 5, 10, 10, 4, 4).to_curve(Vector2d(0, 0)), (5, 5, 10, 10, 4, 4)) + + self.assertEqual( + Smooth(10, 10, 4, 4).to_curve(Vector2d(4, 4), Vector2d(10, 10)), + (-2, -2, 10, 10, 4, 4), + ) + + self.assertAlmostTuple( + Quadratic(10, 10, 4, 4).to_curve(Vector2d(0, 0)).args, + (6.666666666666666, 6.666666666666666, 8, 8, 4, 4), + ) + + self.assertAlmostTuple( + TepidQuadratic(4, 4).to_curve(Vector2d(14, 19), Vector2d(11, 12)).args, + # (20.666666666666664, 30, 17.333333333333332, 25, 4, 4), + (15.999999999999998, 23.666666666666664, 12.666666666666666, 18.666666666666664, 4, 4), + ) + + curves = list(Arc(50, 50, 0, 0, 1, 85, 85).to_curves(Vector2d(0, 0))) + self.assertEqual(len(curves), 3) + self.assertAlmostTuple(curves[0].args, ( + 19.77590700610636, -5.4865851247611115, 38.18634924829132, -10.4196482558544, 55.44095225512604, + -5.796291314453416)) + self.assertAlmostTuple(curves[1].args, ( + 72.69555526196076, -1.172934373052433, 86.17293437305243, 12.30444473803924, 90.79629131445341, + 29.559047744873958)) + self.assertAlmostTuple(curves[2].args, ( + 95.41964825585441, 46.81365075170867, 90.4865851247611, 65.22409299389365, 77.85533905932738, + 77.85533905932738)) + + def apply_to_curve(obj): + obj.to_curve(Vector2d()) + + def apply_to_curves(obj): + obj.to_curve(Vector2d()) + + self.assertRaises(ValueError, apply_to_curve, ZoneClose()) + self.assertRaises(ValueError, apply_to_curves, zoneClose()) + + self.assertRaises(ValueError, apply_to_curve, Move(0, 0)) + self.assertRaises(ValueError, apply_to_curves, move(0, 0)) + + def test_transformation(self): + t = Transform(matrix=((1, 2, 3), (4, 5, 6))) + + first = Vector2d() + prev = Vector2d(31, 97) + prev_prev = Vector2d(5, 7) + + for Cmd in (Line, Move, Curve, Smooth, Quadratic, TepidQuadratic, Arc): + random_seg = self.get_random_cmd(Cmd) + self.assertTrue(random_seg.transform(t) is not random_seg) # transform returns copy + self.assertEqual(random_seg.transform(t).name, Cmd.name) # transform does not change Command type + + T = Transform() + T.add_translate(10, 20) + A = [ T.apply_to_point(p) for p in random_seg.control_points(first, prev, prev_prev) ] + first2, prev2, prev_prev2 = (T.apply_to_point(p) for p in (first, prev, prev_prev)) + B = list(random_seg.translate(Vector2d(10, 20)).control_points(first2, prev2, prev_prev2)) + self.assertAlmostTuple(A, B) + + T = Transform() + T.add_scale(10, 20) + A = [ T.apply_to_point(p) for p in random_seg.control_points(first, prev, prev_prev) ] + first2, prev2, prev_prev2 = (T.apply_to_point(p) for p in (first, prev, prev_prev)) + B = list(random_seg.scale((10, 20)).control_points(first2, prev2, prev_prev2)) + self.assertAlmostTuple(A, B) + + + T = Transform() + T.add_rotate(35, 15, 28) + A = [ T.apply_to_point(p) for p in random_seg.control_points(first, prev, prev_prev) ] + first2, prev2, prev_prev2 = (T.apply_to_point(p) for p in (first, prev, prev_prev)) + B = list(random_seg.rotate(35, Vector2d(15, 28)).control_points(first2, prev2, prev_prev2)) + self.assertAlmostTuple(A, B) + + + + def test_absolute_relative(self): + absolutes = Line, Move, Curve, Smooth, Quadratic, TepidQuadratic, Arc, Vert, Horz, ZoneClose + relatives = line, move, curve, smooth, quadratic, tepidQuadratic, arc, vert, horz, zoneClose + + zero = Vector2d() + for R, A in zip(relatives, absolutes): + rel = self.get_random_cmd(R) + ab = self.get_random_cmd(A) + + self.assertTrue(rel.is_relative) + self.assertTrue(ab.is_absolute) + + self.assertFalse(rel.is_absolute) + self.assertFalse(ab.is_relative) + + self.assertEqual(type(rel.to_absolute(zero)), A) + self.assertEqual(type(ab.to_relative(zero)), R) + self.assertTrue(rel.to_relative(zero) is not rel) + self.assertTrue(ab.to_absolute(zero) is not ab) + + def test_to_line(self): + self.assertEqual(Vert(3).to_line(Vector2d(5, 11)), Line(5, 3)) + self.assertEqual(Horz(3).to_line(Vector2d(5, 11)), Line(3, 11)) + + self.assertEqual(vert(3).to_line(Vector2d(5, 11)), Line(5, 14)) + self.assertEqual(horz(3).to_line(Vector2d(5, 11)), Line(8, 11)) + + def test_args(self): + + commands = Line, Move, Curve, Smooth, Quadratic, TepidQuadratic, Arc, Vert, Horz, ZoneClose, \ + line, move, curve, smooth, quadratic, tepidQuadratic, arc, vert, horz, zoneClose + + for Cmd in commands: + cmd = self.get_random_cmd(Cmd) + self.assertEqual(len(cmd.args), cmd.nargs) + self.assertEqual(Cmd(*cmd.args), cmd) + + + +class PathTest(TestCase): + """Test path API and calculations""" + + def _assertPath(self, path, want_string): + """Test a normalized path string against a good value""" + return self.assertEqual(re.sub('\\s+', ' ', str(path)), want_string) + + def test_new_empty(self): + """Create a path from a path string""" + self.assertEqual(str(Path()), '') + + def test_invalid(self): + """Load an invalid path""" + self._assertPath(Path('& 10 10 M 20 20'), 'M 20 20') + self.assertRaises(TypeError, Line, [40, ]) + + def test_copy(self): + """Make a copy of a path""" + self.assertEqual(str(Path('M 10 10').copy()), 'M 10 10') + + def test_repr(self): + """Path representation""" + self._assertPath(repr(Path('M 10 10 10 10')), "[Move(10, 10), Line(10, 10)]") + + def test_list(self): + """Path of previous commands""" + path = Path(Path('M 10 10 20 20 30 30 Z')[1:-1]) + self._assertPath(path, 'L 20 20 L 30 30') + + def test_passthrough(self): + """Create a path and test the re-rendering of the commands""" + for path in ( + 'M 50,50 L 10,10 m 10 10 l 2.1,2', + 'm 150 150 c 10 10 6 6 20 10 L 10 10', + ): + self._assertPath(Path(path), path.replace(',', ' ')) + + def test_chained_conversion(self): + """Paths always extrapolate chained commands""" + for path, ret in ( + ('M 100 100 20 20', 'M 100 100 L 20 20'), + ('M 100 100 Z 20 20', 'M 100 100 Z M 20 20'), + ('M 100 100 L 20 20 40 40 30 10 Z', 'M 100 100 L 20 20 L 40 40 L 30 10 Z'), + ('m 50 50 l 20 20 40 40', 'm 50 50 l 20 20 l 40 40'), + ('m 50 50 20 20', 'm 50 50 l 20 20'), + ((('m', (50, 50)), ('l', (20, 20))), 'm 50 50 l 20 20'), + ): + self._assertPath(Path(path), ret) + + def test_create_from_points(self): + """Paths can be made of simple list of tuples""" + arg = ((10, 10), (4, 5), (16, -9), (20, 20)) + self.assertEqual(str(Path(arg)), 'L 10 10 L 4 5 L 16 -9 L 20 20') + + def test_control_points(self): + """Test how x,y points are extracted""" + for path, ret in ( + ('M 100 100', ((100, 100),)), + ('L 100 100', ((100, 100),)), + ('H 133', ((133, 0),)), + ('V 144', ((0, 144),)), + ('Q 40 20 12 99 T 100 100', ((40, 20), (12, 99), (-16, 178), (100, 100),)), + ('C 12 12 15 15 20 20', ((12, 12), (15, 15), (20, 20))), + ('S 50 90 30 10', ((0, 0), (50, 90), (30, 10),)), + ('Q 40 20 12 99', ((40, 20), (12, 99),)), + ('A 1,2,3,0,0,10,20', ((10, 20),)), + ('Z', ((0, 0),)), + ): + points = list(Path(path).control_points) + self.assertEqual(len(points), len(ret), msg=path) + self.assertTrue(all(p.is_close(r) for p, r in zip(points, ret)), msg=path) + + def test_bounding_box_lines(self): + """ + Test the bounding box calculations + + A diagonal line from 20,20 to 90,90 then to +10,+10 "\" + + """ + self.assertEqual((20, 100), (20, 100), Path('M 20,20 L 90,90 l 10,10 Z').bounding_box()) + self.assertEqual((10, 90), (10, 90), Path('M 20,20 L 90,90 L 10,10 Z').bounding_box()) + + def test_bounding_box_curves(self): + """ + Test the bounding box calculations of a curve + """ + + path = Path('M 85,14 C 104.63953,33.639531 104.71989,65.441157' + ' 85,85 65.441157,104.71989 33.558843,104.71989 14,85' + ' -5.7198883,65.441157 -5.6395306,33.639531 14,14' + ' 33.639531,-5.6395306 65.360469,-5.6395306 85,14 Z') + bb_tuple = path.bounding_box() + expected = (-0.760, -0.760 + 100.520), (-0.730, -0.730 + 100.520) + precision = 3 + + self.assertDeepAlmostEqual(tuple(bb_tuple.x), expected[0], places=precision) + self.assertDeepAlmostEqual(tuple(bb_tuple.y), expected[1], places=precision) + + def test_bounding_box_arcs(self): + """ + Test the bounding box calculations with arcs (currently is rough only) + + Bounding box around a circle with a radius of 50 + it should be from 0,0 -> 100, 100 + """ + path = Path('M 85.355333,14.644651 ' + 'A 50,50 0 0 1 85.355333,85.355341' + ' 50,50 0 0 1 14.644657,85.355341' + ' 50,50 0 0 1 14.644676,14.644651' + ' 50,50 0 0 1 85.355333,14.644651 Z') + + bb_tuple = path.bounding_box() + expected = (0, 100), (0, 100) + precision = 4 + + self.assertDeepAlmostEqual(tuple(bb_tuple.x), expected[0], places=precision) + self.assertDeepAlmostEqual(tuple(bb_tuple.y), expected[1], places=precision) + + # self.assertEqual(('ERROR'), Path('M 10 10 S 100 100 300 0').bounding_box()) + # self.assertEqual(('ERRPR'), Path('M 10 10 Q 100 100 300 0').bounding_box()) + + def test_adding_to_path(self): + """Paths can be translated using addition""" + ret = Path('M 20,20 L 90,90 l 10,10 Z').translate(50, 50) + self._assertPath(ret, 'M 70 70 L 140 140 l 10 10 Z') + + def test_extending(self): + """Paths can be extended using addition""" + ret = Path('M 20 20') + Path('L 40 40 9 10') + self.assertEqual(type(ret), Path) + self._assertPath(ret, 'M 20 20 L 40 40 L 9 10') + + ret = Path('M 20 20') + 'C 40 40 9 10 10 10' + self.assertEqual(type(ret), Path) + self._assertPath(ret, 'M 20 20 C 40 40 9 10 10 10') + + def test_subtracting_from_path(self): + """Paths can be translated using addition""" + ret = Path('M 20,20 L 90,90 l 10,10 Z').translate(-10, -10) + self._assertPath(ret, 'M 10 10 L 80 80 l 10 10 Z') + + def test_scale(self): + """Paths can be scaled using the times operator""" + ret = Path('M 10,10 L 30,30 C 20 20 10 10 10 10 l 10 10').scale(2.5, 3) + self._assertPath(ret, 'M 25 30 L 75 90 C 50 60 25 30 25 30 l 25 30') + + ret = Path("M 29.867708,101.68274 A 14.867708,14.867708 0 0 1 15,116.55045 14.867708," + "14.867708 0 0 1 0.13229179,101.68274 14.867708,14.867708 0 0 1 15,86.815031 " + "14.867708,14.867708 0 0 1 29.867708,101.68274 Z") + ret = ret.scale(1.2, 0.8) + self._assertPath(ret, 'M 35.8412 81.3462 ' + 'A 17.8412 11.8942 0 0 1 18 93.2404 ' + 'A 17.8412 11.8942 0 0 1 0.15875 81.3462 ' + 'A 17.8412 11.8942 0 0 1 18 69.452 ' + 'A 17.8412 11.8942 0 0 1 35.8412 81.3462 Z') + + def test_scale_relative_after_close(self): + """Zone close moves current position correctly after transform""" + # expected positions: + # - before scale: + # M to (10,10), l by (+10,+10), Z back to (10,10), l by (+10,+10) + # <=> M to (10,10), L to (20,20), Z back to (10,10), L to (20,20) + # - after scale: + # M to (20,20), L to (40,40), Z back to (20,20), L to (40,40) + # <=> M to (20,20), l by (+20,+20), Z back to (20,20), l by (+20,+20) + ret = Path('M 10,10 l 10,10 Z l 10,10').scale(2, 2) + self._assertPath(ret, 'M 20 20 l 20 20 Z l 20 20') + + def test_absolute(self): + """Paths can be converted to absolute""" + ret = Path("M 100 100 l 10 10 10 10 10 10") + self._assertPath(ret.to_absolute(), "M 100 100 L 110 110 L 120 120 L 130 130") + + ret = Path("M 100 100 h 10 10 10 v 10 10 10") + self._assertPath(ret.to_absolute(), "M 100 100 H 110 H 120 H 130 V 110 V 120 V 130") + + ret = Path("M 150,150 a 76,55 0 1 1 283,128") + self._assertPath(ret.to_absolute(), "M 150 150 A 76 55 0 1 1 433 278") + + ret = Path("m 5 5 h 5 v 5 h -5 z M 15 15 l 5 5 z m 10 10 h 5 v 5 h -5 z") + self._assertPath(ret.to_absolute(), + "M 5 5 H 10 V 10 H 5 Z M 15 15 L 20 20 Z M 25 25 H 30 V 30 H 25 Z") + + ret= Path("m 1 2 h 2 v 1 z m 4 0 h 2 v 1 z m 0 2 h 2 v 1 z") + self._assertPath(ret.to_absolute(), "M 1 2 H 3 V 3 Z M 5 2 H 7 V 3 Z M 5 4 H 7 V 5 Z") + + + def test_relative(self): + """Paths can be converted to relative""" + ret = Path("M 100 100 L 110 120 140 140 300 300") + self._assertPath(ret.to_relative(), "m 100 100 l 10 20 l 30 20 l 160 160") + + ret = Path("M 150,150 A 76,55 0 1 1 433,278") + self._assertPath(ret.to_relative(), "m 150 150 a 76 55 0 1 1 283 128") + + ret = Path("M 1 2 H 3 V 3 Z M 5 2 H 7 V 3 Z M 5 4 H 7 V 5 Z") + self._assertPath(ret.to_relative(), "m 1 2 h 2 v 1 z m 4 0 h 2 v 1 z m 0 2 h 2 v 1 z") + + def test_rotate(self): + """Paths can be rotated""" + ret = Path("M 0.24999949,0.24999949 H 12.979167 V 12.979167 H 0.24999949 Z") + ret = ret.rotate(35, (0, 0)) + self._assertPath(ret, "M 0.0613938 0.348181 L 10.4885 7.64933 L 3.18737 18.0765 L -7.23976 10.7753 Z") + + ret = Path("M 0.24999949,0.24999949 H 12.979167 V 12.979167 H 0.24999949 Z") + ret = ret.rotate(-35, (0, 0)) + self._assertPath(ret, "M 0.348181 0.0613938 L 10.7753 -7.23976 L 18.0765 3.18737 L 7.64933 10.4885 Z") + + ret = Path("M 0.24999949,0.24999949 H 12.979167 V 12.979167 H 0.24999949 Z") + ret = ret.rotate(90, (10, -10)) + self._assertPath(ret, "M -0.249999 -19.75 L -0.249999 -7.02083 L -12.9792 -7.02083 L -12.9792 -19.75 Z") + + ret = Path("M 0.24999949,0.24999949 H 12.979167 V 12.979167 H 0.24999949 Z") + ret = ret.rotate(90) + self._assertPath(ret, "M 12.9792 0.249999 L 12.9792 12.9792 L 0.249999 12.9792 L 0.249999 0.249999 Z") + + def test_to_arrays(self): + """Return the full path as a bunch of arrays""" + ret = Path("M 100 100 L 110 120 H 20 C 120 0 6 10 10 2 Z").to_arrays() + self.assertEqual(len(ret), 5) + self.assertEqual(ret[0][0], 'M') + self.assertEqual(ret[1][0], 'L') + self.assertEqual(ret[2][0], 'H') + self.assertEqual(ret[3][0], 'C') + + def test_transform(self): + """Transform by a whole matrix""" + ret = Path("M 100 100 L 110 120 L 140 140 L 300 300") + ret = ret.transform(Transform(translate=(10, 10))) + self.assertEqual(str(ret), 'M 110 110 L 120 130 L 150 150 L 310 310') + ret = ret.transform(Transform(translate=(-10, -10))) + self.assertEqual(str(ret), 'M 100 100 L 110 120 L 140 140 L 300 300') + ret = Path('M 5 5 H 10 V 15') + ret = ret.transform(Transform(rotate=-10)) + self.assertEqual('M 5.79228 4.0558 ' + 'L 10.7163 3.18756 ' + 'L 12.4528 13.0356', + str(ret)) + ret = Path("M 10 10 A 50,50 0 0 1 85.355333,85.355341 L 100 0") + ret = ret.transform(Transform(scale=10)) + self.assertEqual(str(ret), 'M 100 100 A 500 500 0 0 1 853.553 853.553 L 1000 0') + self.assertRaises(ValueError, Horz([10]).transform, Transform()) + + def test_inline_transformations(self): + path = Path() + self.assertTrue(path is not path.translate(10, 20)) + self.assertTrue(path is not path.transform(Transform(scale=10))) + self.assertTrue(path is not path.rotate(10)) + self.assertTrue(path is not path.scale(10, 20)) + + self.assertTrue(path is path.translate(10, 20, inplace=True)) + self.assertTrue(path is path.transform(Transform(scale=10), inplace=True)) + self.assertTrue(path is path.rotate(10, inplace=True)) + self.assertTrue(path is path.scale(10, 20, inplace=True)) + + def test_transformation_preserve_type(self): + import re + paths = [ + "M 10 10 A 100 100 0 1 0 100 100 C 10 15 20 20 5 5 Z", + "m 10 10 a 100 100 0 1 0 100 100 c 10 15 20 20 5 5 z", + "m 10 10 l 100 200 L 20 30 C 10 20 30 40 11 12", + "M 10 10 Q 12 13 14 15 T 11 32 T 32 11", + "m 10 10 q 12 13 14 15 t 11 32 t 32 11", + ] + t = Transform(matrix=((1, 2, 3), (4, 5, 6))) + for path_str in paths: + path = Path(path_str) + new_path = path.transform(t) + cmds = "".join([cmd.letter for cmd in new_path]) + expected = re.sub(r"\d|\s|,", "", path_str) + + self.assertEqual(expected, cmds) + self.assertAlmostTuple( + [t.apply_to_point(p) for p in path.control_points], + list(new_path.control_points) + ) + + def test_arc_transformation(self): + cases = [ + ("M 10 10 A 100 100 0 1 0 100 100 Z", ((1, 0, 1), (0, 1, 0)), "M 11 10 A 100 100 0 1 0 101 100 Z"), + ("M 10 10 A 100 100 0 1 0 100 100 Z", ((1, 0, 0), (0, 1, 1)), "M 10 11 A 100 100 0 1 0 100 101 Z"), + ("M 10 10 A 100 100 0 1 0 100 100 Z", ((1, 0, 1), (0, 1, 1)), "M 11 11 A 100 100 0 1 0 101 101 Z"), + ("M 10 10 A 100 100 0 1 0 100 100 Z", ((2, 0, 0), (0, 1, 0)), "M 20 10 A 200 100 0 1 0 200 100 Z"), + ("M 10 10 A 100 100 0 1 0 100 100 Z", ((1, 0, 0), (0, 2, 0)), "M 10 20 A 200 100 90 1 0 100 200 Z"), + ("M 10 10 A 100 100 0 1 0 100 100 Z", ((1, 0, 0), (0, -1, 0)), "M 10 -10 A 100 100 0 1 1 100 -100 Z"), + ("M 10 10 A 100 100 0 1 0 100 100 Z", ((1, 2, 0), (0, 2, 0)), "M 30 20 " + "A 292.081 68.4742 41.4375 1 0 300 200 Z"), + ("M 10 10 " + "A 100 100 0 1 0 100 100 " + "A 300 200 0 1 0 50 20 Z", ((1, 2, 0), (5, 6, 0)), "M 30,110 " + "A 810.90492,49.327608 74.368134 1 1 " + "300,1100 1981.2436,121.13604 75.800007 1 1 90,370 Z"), + ] + for path, transform, expected in cases: + expected = Path(expected) + result = Path(path).transform(Transform(matrix=transform)) + self.assertDeepAlmostEqual(expected.to_arrays(), + result.to_arrays(), places=4) + + def test_single_point_transform(self): + from math import sqrt, sin, cos + self.assertAlmostTuple(list(Path("M 10 10 30 20").control_points), ((10, 10), (30, 20))) + self.assertAlmostTuple(list(Path("M 10 10 30 20").transform(Transform(translate=(10,7))) + .control_points), ((20, 17), (40, 27))) + self.assertAlmostTuple(list(Path("M 20 20 5 0 0 7 ").transform(Transform(scale=10)) + .control_points), ((200, 200), (50, 0), (0, 70))) + + self.assertAlmostTuple(list(Path("M 20 20 1 0").transform(Transform(rotate=90)) + .control_points), ((-20, 20), (0, 1))) + + self.assertAlmostTuple(list(Path("M 20 20 1 0").transform(Transform(rotate=45)) + .control_points), ((0, sqrt(20 ** 2 + 20 ** 2)), (sqrt(2)/2, sqrt(2)/2))) + + self.assertAlmostTuple(list(Path("M 1 0 0 1").transform(Transform(rotate=30)) + .control_points), ((sqrt(3)/2, 0.5), (-0.5, sqrt(3)/2) )) + +class SuperPathTest(TestCase): + """Super path tests for testing the super path class""" + def test_closing(self): + """Closing paths create two arrays""" + path = Path("M 0,0 C 1.505,0 2.727,-0.823 2.727,-1.841 V -4.348 C 2.727,-5.363"\ + " 1.505,-6.189 0,-6.189 H -8.3 V 0 Z m -10.713,1.991 h -0.211 V -8.178"\ + " H 0 c 2.954,0 5.345,1.716 5.345,3.83 v 2.507 C 5.345,0.271 2.954,1.991" + " 0,1.991 Z") + csp = path.to_superpath() + self.assertEqual(len(csp), 2) + + def test_closing_without_z(self): + """Closing paths without z create two arrays""" + path = Path("m 51.553104,253.58572 c -11.644086,-0.14509 -4.683516,-19.48876"\ + " 2.096523,-8.48973 1.722993,2.92995 0.781608,6.73867 -2.096523,8.48973"\ + " m -3.100522,-13.02176 c -18.971587,17.33811 15.454875,20.05577"\ + " 6.51412,3.75474 -1.362416,-2.30812 -3.856221,-3.74395 -6.51412,-3.75474") + csp = path.to_superpath() + self.assertEqual(len(csp), 2) + + def test_from_arrays(self): + """SuperPath from arrays""" + csp = CubicSuperPath([[ + [[14, 173], [14, 173], (14, 173)], + [(15, 171), (17, 168), (18, 168)], + ], [ + [(18, 167), (18, 167), [20, 165]], + ((21, 164), [22, 162], (23, 162)), + ]]) + self.assertEqual( + str(csp.to_path()), + 'M 14 173 C 14 173 15 171 17 168 M 18 167 C 20 165 21 164 22 162' + ) + + def test_is_line(self): + """Test is super path segments can detect lines""" + path = Path("m 49,88 70,-1 c 18,17 1,59 1.7,59 "\ + "0,0 -48.7,18 -70.5,-1 18,-15 25,-32.4 -1.5,-57.2 z") + csp = path.to_superpath() + self.assertTrue(csp.is_line(csp[0][0], csp[0][1]), "Should be a line") + self.assertFalse(csp.is_line(csp[0][3], csp[0][4]), "Both controls not detected") + self.assertFalse(csp.is_line(csp[0][1], csp[0][2]), "Start control not detected") + self.assertFalse(csp.is_line(csp[0][2], csp[0][3]), "End control not detected") + # Also tests if zone close is applied correctly. + self.assertEqual(str(csp.to_path()), "M 49 88 L 119 87 C 137 104 120 146 120.7 146 "\ + "C 120.7 146 72 164 50.2 145 C 68.2 130 75.2 112.6 48.7 87.8 Z") diff --git a/share/extensions/tests/test_inkex_styles.py b/share/extensions/tests/test_inkex_styles.py new file mode 100644 index 0000000..224be2c --- /dev/null +++ b/share/extensions/tests/test_inkex_styles.py @@ -0,0 +1,229 @@ +# coding=utf-8 +""" +Test Inkex style parsing functionality. +""" + +import pytest + +from inkex.styles import Style +from inkex.colors import Color +from inkex.tester import TestCase +from inkex.tester.svg import svg_file + +class StyleTest(TestCase): + """Test path API and calculations""" + + def test_new_style(self): + """Create a style from a path string""" + stl = Style("border-color: blue; border-width: 4px;") + self.assertEqual(str(stl), 'border-color:blue;border-width:4px') + + def test_composite(self): + """Test chaining styles together""" + stl = Style("border-color: blue;") + stl += "border-color: red; border-issues: true;" + self.assertEqual(str(stl), 'border-color:red;border-issues:true') + st2 = stl + "border-issues: false;" + self.assertEqual(str(st2), 'border-color:red;border-issues:false') + + def test_inbuilts(self): + """Test inbuild style functions""" + stadd = Style("a: 1") + Style("b: 2") + self.assertTrue(stadd == Style("b: 2; a: 1")) + self.assertFalse(stadd == Style("b: 2")) + self.assertFalse(stadd != Style("b: 2; a: 1")) + self.assertEqual(stadd - "a: 4", "b: 2") + stadd -= "b: 3; c: 4" + self.assertEqual(stadd, Style("a: 1")) + + def test_set_property(self): + """Set the style attribute directly""" + stl = Style() + stl['border-pain'] = 'green' + self.assertEqual(str(stl), 'border-pain:green') + + def test_color_property(self): + """Color special handling""" + stl = Style("fill-opacity:0.7;fill:red;") + self.assertEqual(stl.get_color('fill').alpha, 0.7) + self.assertEqual(str(stl.get_color('fill')), 'rgba(255, 0, 0, 0.7)') + stl.set_color('rgba(0, 127, 0, 0.5)', 'stroke') + self.assertEqual(str(stl), 'fill-opacity:0.7;fill:red;stroke-opacity:0.5;stroke:#007f00') + + def test_interpolate(self): + """Test interpolation method.""" + stl1 = Style({'stroke-width':'0px', 'fill-opacity':1.0,'fill':Color((200, 0, 0))}) + stl2 = Style({'stroke-width':'1pc', 'fill-opacity':0.0,'fill':Color((100, 0, 100))}) + stl3 = stl1.interpolate(stl2, 0.5) + assert stl3['fill-opacity'] == pytest.approx(0.5, 1e-3) + assert stl3['fill'] == [150, 0, 50] + assert stl3['stroke-width'] == '8px' + +class AttribFallbackTest(TestCase): + """Test the fallback style for handling attribute based styles""" + def setUp(self): + self.svg = svg_file(self.data_file('svg', 'css.svg')) + self.elem = self.svg.getElementById('rect2') + + def test_fallback_read_style(self): + """Style comes from style property""" + self.elem.style['fill'] = 'green' + self.elem.set('fill', 'red') + self.assertEqual(self.elem.fallback_style()['fill'], 'green') + + def test_fallback_read_attrib(self): + """Style comes from attribute""" + self.elem.style.pop('stroke', None) + self.assertEqual(self.elem.fallback_style()['stroke'], None) + self.elem.set('stroke', 'green') + self.assertEqual(self.elem.fallback_style()['stroke'], 'green') + + def test_fallback_read_css(self): + """Style from basic css will work""" + elem = self.svg.getElementById('rect1') + self.assertEqual(elem.fallback_style()['fill'], 'blue') + + def test_fallback_write_style(self): + """Styles are set back correctly""" + self.elem.style['fill'] = 'green' + self.elem.set('fill', 'red') + self.elem.fallback_style()['fill'] = 'blue' + self.assertEqual(self.elem.style['fill'], 'blue') + self.assertEqual(self.elem.get('fill'), None) # Removed + + def test_fallback_write_attrib(self): + """Attrib is written back when needed""" + self.elem.style.pop('stroke', None) + self.elem.set('stroke', 'green') + self.elem.fallback_style()['stroke'] = 'blue' + self.assertEqual(self.elem.style.get('stroke', None), None) # Still empty + self.assertEqual(self.elem.get('stroke'), 'blue') + + def test_fallback_write_move(self): + """Style is moved when required""" + self.elem.style.pop('stroke', None) + self.elem.set('stroke', 'green') + self.elem.fallback_style(move=True)['stroke'] = 'blue' + self.assertEqual(self.elem.style['stroke'], 'blue') + self.assertEqual(self.elem.get('stroke'), None) # Moved + + def test_fallback_write_css(self): + """Style can be set into the stylesheet style""" + elem = self.svg.getElementById('rect1') + elem.fallback_style()['fill'] = 'green' + self.assertIn('#rect1 {\n fill:green;\n}', self.svg.getElementById('style1').text) + elem.fallback_style()['fill'] = 'red' + self.assertIn('#rect1 {\n fill:red;\n}', self.svg.getElementById('style1').text) + + def test_no_attr(self): + """Given name doesn't exist anywhere""" + self.elem.style = 'fill:red' + style = self.elem.fallback_style() + self.assertEqual(style.get('fill'), 'red') + self.assertEqual(style.get('jump'), None) + +class StyleSheetTest(TestCase): + """Test parsing style sheets""" + def setUp(self): + super(StyleSheetTest, self).setUp() + self.svg = svg_file(self.data_file('svg', 'css.svg')) + self.css = self.svg.stylesheet + + def test_classes(self): + """Test element class manipulation""" + rect = self.svg.getElementById('rect2') + self.assertEqual(rect.get('class'), 'two') + self.assertEqual(rect.classes, ['two']) + rect.classes[0] = 'twa' + self.assertEqual(rect.get('class'), 'twa') + rect.classes.append('tri') + rect.classes.append('four') + self.assertEqual(rect.get('class'), 'twa tri four') + rect.classes.remove('twa') + self.assertEqual(rect.get('class'), 'tri four') + rect.classes.toggle('toggle') + self.assertEqual(rect.get('class'), 'tri four toggle') + rect.classes.toggle('toggle') + self.assertEqual(rect.get('class'), 'tri four') + + def test_creation(self): + """Stylesheet is created when needed""" + self.svg = svg_file(self.data_file('svg', 'empty.svg')) + self.assertEqual(len(self.svg.stylesheets), 0) + self.assertEqual(len(self.svg.stylesheet), 0) + self.assertEqual(len(self.svg.stylesheets), 1) + self.svg.stylesheet.append('.cls1 { fill: blue; }') + self.assertIn(b'style><', self.svg.tostring()) + + def test_parsing(self): + """SVG parsing provides access to stylesheets""" + sheets = self.svg.stylesheets + self.assertEqual(len(sheets), 3) + self.assertEqual(len(sheets[0]), 7) + self.assertEqual(len(sheets[1]), 0) + self.assertEqual(len(sheets[2]), 2) + + def test_string(self): + """Rendered to a string""" + sheets = self.svg.stylesheets + self.assertEqual(str(sheets[0][0]), '#layer1 {\n stroke:yellow;\n}') + self.assertEqual(str(sheets[2][1]), '.rule {}') + + def test_lookup_by_id(self): + """ID CSS lookup""" + self.assertEqual(self.css[0].to_xpath(), "//*[@id='layer1']") + elem = self.svg.getElement(self.css[0].to_xpath()) + self.assertEqual(elem.get('id'), 'layer1') + + def test_lookup_by_element(self): + """Element name CSS lookup""" + self.assertEqual(self.css[1].to_xpath(), "//svg:circle") + elems = list(self.svg.xpath(self.css[1].to_xpath())) + self.assertEqual(len(elems), 2) + self.assertEqual(elems[0].get('id'), 'circle1') + self.assertEqual(elems[1].get('id'), 'circle2') + + def test_lookup_by_class(self): + """Class name CSS lookup""" + self.assertEqual(self.css[2].to_xpath(),\ + "//*[contains(concat(' ', normalize-space(@class), ' '), ' two ')]") + elem = self.svg.getElement(self.css[2].to_xpath()) + self.assertEqual(elem.get('id'), 'rect2') + + def test_lookup_and(self): + """Multiple CSS lookups""" + self.assertEqual(self.css[3].to_xpath(), "//*[@id='rect3']"\ + "[contains(concat(' ', normalize-space(@class), ' '), ' three ')]") + elem = self.svg.getElement(self.css[3].to_xpath()) + self.assertEqual(elem.get('id'), 'rect3') + + def test_lookup_or(self): + """SVG rules can look up the right elements""" + self.assertEqual(self.css[6].to_xpath(), "//*[@id='circle1']|//*[@id='circle2']|"\ + "//*[contains(concat(' ', normalize-space(@class), ' '), ' two ')]") + elems = self.svg.xpath(self.css[6].to_xpath()) + self.assertEqual(len(elems), 3) + self.assertEqual(elems[0].get('id'), 'rect2') + self.assertEqual(elems[1].get('id'), 'circle1') + self.assertEqual(elems[2].get('id'), 'circle2') + + def test_applied_styles(self): + """Are styles applied to the svg elements correctly""" + self.assertEqual( + str(self.svg.getElementById('rect1').cascaded_style()), + 'fill:blue') + self.assertEqual( + str(self.svg.getElementById('rect2').cascaded_style()), + 'fill:green;font:Homie') + self.assertEqual( + str(self.svg.getElementById('rect3').cascaded_style()), + 'fill:cyan') + self.assertEqual( + str(self.svg.getElementById('rect4').cascaded_style()), + 'fill:grey;stroke:red') + self.assertEqual( + str(self.svg.getElementById('circle1').cascaded_style()), + 'fill:red;font:Homie') + self.assertEqual( + str(self.svg.getElementById('circle2').cascaded_style()), + 'fill:red;font:Homie') diff --git a/share/extensions/tests/test_inkex_svg.py b/share/extensions/tests/test_inkex_svg.py new file mode 100644 index 0000000..4f1e8b7 --- /dev/null +++ b/share/extensions/tests/test_inkex_svg.py @@ -0,0 +1,461 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Test the svg interface for inkscape extensions. +""" +from inkex.transforms import Vector2d +from inkex.utils import addNS +from inkex import Guide +from inkex.tester import TestCase +from inkex.tester.svg import svg, svg_file, uu_svg + +class BasicSvgTest(TestCase): + """Basic svg tests""" + + def test_svg_load(self): + """Test loading an svg with the right parser""" + self.assertEqual(type(svg()).__name__, 'SvgDocumentElement') + + def test_add_ns(self): + """Test adding a namespace to a tag""" + self.assertEqual(addNS('g', 'svg'), '{http://www.w3.org/2000/svg}g') + self.assertEqual(addNS('h', 'inkscape'), '{http://www.inkscape.org/namespaces/inkscape}h') + self.assertEqual(addNS('i', 'sodipodi'), + '{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}i') + self.assertEqual(addNS('{p}j'), '{p}j') + + def test_svg_ids(self): + """Test a list of ids from an svg document""" + self.assertEqual(svg('id="apples"').get_ids(), {'apples'}) + + def test_svg_new_id(self): + """Test generatign a new id for a given tag""" + doc = svg('id="apples"') + usedids = set(['apples']) + for prefix in ['apples'] * 3: + newid = doc.get_unique_id(prefix) + self.assertTrue(newid.startswith(prefix)) + self.assertTrue(newid not in usedids) + usedids.add(newid) + + def test_svg_select_id(self): + """Select an id from the document""" + doc = svg('id="bananas"') + doc.selection.set('bananas') + self.assertEqual(doc.selection['bananas'], doc) + self.assertEqual(doc.selection.first(), doc) + doc = svg('id="apples"') + doc.selected.set(doc.getElementById('apples')) + self.assertEqual(doc.selection['apples'], doc) + self.assertEqual(doc.selection.first(), doc) + + def test_svg_by_class(self): + """Select elements by class""" + doc = svg_file(self.data_file('svg', 'multilayered-test.svg')) + elems = doc.getElementsByClass('frog') + self.assertEqual([elem.get_id() for elem in elems], + ['path3902', 'text3926', 'path3900', 'rect3898']) + elems = doc.getElementsByClass('apple') + self.assertEqual([elem.get_id() for elem in elems], ['text3926', 'rect3898']) + + def test_svg_by_href(self): + """Select element by xlink href""" + doc = svg_file(self.data_file('svg', 'multilayered-test.svg')) + elem = doc.getElementsByHref('path3900')[0] + self.assertEqual(elem.TAG, 'textPath') + self.assertEqual(elem.get_id(), 'textPath3923') + elem = doc.getElementsByHref('path3904')[0] + self.assertEqual(elem.TAG, 'textPath') + self.assertEqual(elem.get_id(), 'textPath3906') + self.assertEqual(doc.getElementsByHref('not-an-id'), []) + + def test_svg_by_url_link(self): + """Select element by urls in styles""" + doc = svg_file(self.data_file('svg', 'markers.svg')) + elem = doc.getElementsByStyleUrl('Arrow1Lend')[0] + self.assertEqual(elem.get_id(), 'dimension') + elem = doc.getElementsByStyleUrl('Arrow1Lstart')[0] + self.assertEqual(elem.get_id(), 'dimension') + self.assertEqual(doc.getElementsByStyleUrl('not-an-id'), []) + + def test_selected_bbox(self): + """Can we get a bounding box from the selected items""" + doc = svg_file(self.data_file('svg', 'multilayered-test.svg')) + doc.selected.set('path3904', 'path3902') + from inkex.transforms import BoundingBox + x, y, w, h = 199.544, 156.412, 377.489, 199.972 # from inkscape --query-all + expected_3904 = BoundingBox((x, x + w), (y, y + h)) + x, y, w, h = 145.358, 478.373, 439.135, 419.142 # from inkscape --query-all + expected_3902 = BoundingBox((x, x + w), (y, y + h)) + expected = list(expected_3902 + expected_3904) + + for x, y in zip(expected, doc.selection.bounding_box()): + self.assertDeepAlmostEqual(tuple(x), tuple(y), delta=1e-3) + + + def test_svg_name(self): + """Can get the sodipodi name attribute""" + doc = svg_file(self.data_file('svg', 'multilayered-test.svg')) + self.assertEqual(doc.name, 'Nouveau document 1') + + def test_svg_nameview(self): + """Can get the sodipodi nameview element""" + doc = svg() + self.assertEqual(doc.namedview.center.x, 0) + self.assertEqual(type(doc.namedview).__name__, 'NamedView') + + def test_svg_layers(self): + """Selected layer is selected""" + doc = svg_file(self.data_file('svg', 'multilayered-test.svg')) + self.assertEqual(doc.get_current_layer().get('id'), 'layer3') + doc = svg('id="empty"') + self.assertEqual(doc.get_current_layer(), doc) + + def test_svg_center_position(self): + """SVG with namedview has a center position""" + doc = svg_file(self.data_file('svg', 'multilayered-test.svg')) + self.assertTrue(doc.namedview.center.is_close((30.714286, 520.0))) + self.assertTrue(svg().namedview.center.is_close(Vector2d())) + + def test_defs(self): + """Can get the defs from an svg file""" + doc = svg_file(self.data_file('svg', 'markers.svg')) + self.assertEqual(len(doc.defs), 2) + doc = svg('id="empty"') + self.assertEqual(len(doc.defs), 0) + + def test_scale(self): + """Scale of a document""" + doc = svg('id="empty" viewBox="0 0 100 100" width="200" height="200"') + self.assertEqual(doc.width, 200.0) + self.assertEqual(doc.get_viewbox()[2], 100.0) + self.assertEqual(doc.scale, 2.0) + doc = svg('id="empty" viewBox="0 0 0 0" width="200" height="200"') + self.assertEqual(doc.scale, 1.0) + +class NamedViewTest(TestCase): + """Tests for the named view functionality""" + + def test_create_guide(self): + """Test creating guides""" + doc = svg_file(self.data_file('svg', 'multilayered-test.svg')) + namedview = doc.namedview + self.assertEqual(len(namedview.get_guides()), 0) + + namedview.add(Guide().move_to(50, 50, 45)) + self.assertEqual(len(namedview.get_guides()), 1) + guide, = namedview.get_guides() + self.assertEqual(guide.get('position'), '50,50') + self.assertEqual(guide.get('orientation'), '0.707107,-0.707107') + + +class GetDocumentWidthTest(TestCase): + """Tests for Effect.width.""" + + def test_no_dimensions(self): + """An empty width value should be default zero width""" + self.assertEqual(svg().width, 0) + + def test_empty_width(self): + """An empty width value should be the same as a missing width.""" + self.assertEqual(svg('width=""').width, 0) + + def test_empty_viewbox(self): + """An empty viewBox value should be the same as a missing viewBox.""" + self.assertEqual(svg('viewBox=""').width, 0) + + def test_empty_width_and_viewbox(self): + """Empty values for both should be the same as both missing.""" + self.assertEqual(svg('width="" viewBox=""').width, 0) + + def test_width_only(self): + """Test a fixed width""" + self.assertAlmostEqual(svg('width="120mm"').width, 453.5433071) + + def test_width_and_viewbox(self): + """If both are present, width overrides viewBox.""" + self.assertAlmostEqual(svg('width="120mm" viewBox="0 0 22 99"').width, 453.5433071) + + def test_viewbox_only(self): + """IF only the viewBox is present""" + self.assertEqual(svg('viewBox="0 0 22 99"').width, 22.0) + + def test_only_valid_viewbox(self): + """An empty width value should be the same as a missing width.""" + self.assertEqual(svg('width="" viewBox="0 0 22 99"').width, 22.0) + + def test_non_zero_viewbox_x(self): + """Demonstrate that a non-zero x value (viewbox[0]) does not affect the width value.""" + self.assertEqual(svg('width="" viewBox="5 7 22 99"').width, 22.0) + + +class GetDocumentHeightTest(TestCase): + """Tests for Effect.height.""" + + def test_no_dimensions(self): + """Test height from blank svg""" + self.assertEqual(svg().height, 0) + + def test_empty_height(self): + """An empty height value should be the same as a missing height.""" + self.assertEqual(svg('height=""').height, 0) + + def test_empty_viewbox(self): + """An empty viewBox value should be the same as a missing viewBox.""" + self.assertEqual(svg('viewBox=""').height, 0) + + def test_empty_height_viewbox(self): + """Empty values for both should be the same as both missing.""" + self.assertEqual(svg('height="" viewBox=""').height, 0) + + def test_height_only(self): + """A simple height only in px""" + self.assertEqual(svg('height="330px"').height, 330) + + def test_height_and_viewbox(self): + """If both are present, height overrides viewBox.""" + self.assertEqual(svg('height="330px" viewBox="0 0 22 99"').height, 330) + + def test_viewbox_only(self): + """Height from viewBox only""" + self.assertEqual(svg('viewBox="0 0 22 99"').height, 99.0) + + def test_no_height_valid_viewbox(self): + """An empty height value should be the same as a missing height.""" + self.assertEqual(svg('height="" viewBox="0 0 22 99"').height, 99.0) + + def test_non_zero_viewbox_y(self): + """Demonstrate that a non-zero y value (viewbox[1]) does not affect the height value.""" + self.assertEqual(svg('height="" viewBox="5 7 22 99"').height, 99.0) + + +class GetDocumentUnitTest(TestCase): + """Tests for Effect.unit.""" + + def test_no_dimensions(self): + """Default units with no arguments""" + self.assertEqual(svg().unit, 'px') + + def test_width_only(self): + """"Units from document width only""" + # TODO: Determine whether returning 'px' in this case is the + # intended behavior. + self.assertEqual(svg('width="100m"').unit, 'px') + + def test_height_only(self): + """Units from document height only""" + # TODO: Determine whether returning 'px' in this case is the + # intended behavior. + self.assertEqual(svg('height="100m"').unit, 'px') + + def test_viewbox_only(self): + """Test viewbox only document units""" + self.assertEqual(svg('viewBox="0 0 377 565"').unit, 'px') + + # Unit-ratio tests. Don't exhaustively test every unit conversion, just + # demonstrate that the logic works. + + def test_width_and_viewbox_px(self): + """100mm is ~377px, so unit should be 'px'.""" + self.assertEqual(svg('width="100mm" viewBox="0 0 377 565"').unit, 'px') + + def test_width_and_viewbox_in(self): + """100mm is ~3.94in, so unit should be 'in'.""" + self.assertEqual(svg('width="100mm" viewBox="0 0 3.94 5.90"').unit, 'in') + + def test_unitless_width_and_viewbox(self): + """Unitless width should be treated as 'px'.""" + # 3779px is ~1m, so unit should be 'm'. + self.assertEqual(svg('width="3779" viewBox="0 0 1 1.5"').unit, 'm') + + def test_height_with_viewbox(self): + """150mm is ~5.90in, so unit should be 'in', but height is ignored""" + # TODO: Determine whether returning 'px' in this case is the intended + # behavior. + self.assertEqual(svg('height="150mm" viewBox="0 0 3.94 5.90"').unit, 'px') + + def test_height_width_and_viewbox(self): + """100mm is ~23.6pc, so unit should be 'pc'.""" + doc = svg('width="100mm" height="150mm" viewBox="0 0 23.6 35.4"') + self.assertEqual(doc.unit, 'pc') + + def test_large_error_reverts_to_px(self): + """'px' instead of using the closest match 'pc'.""" + # 100mm is ~23.6pc; 24.1 is ~2% off from that, so unit should fall back + self.assertEqual(svg('width="100mm" viewBox="0 0 24.1 35.4"').unit, 'px') + + # TODO: Demonstrate that unknown width units are treated as px while + # determining the ratio. + + # 100px fallback tests. Although that value is an arbitrary default, it's + # possible for users of inkex.Effect to depend on this behavior. + # NOTE: Do not treat the existence of these tests as a reason to preserve + # the 100px fallback logic. + + def test_bad_width_number(self): + """Fallback test: Bad numbers default to 100""" + # First, demonstrate that 1in is 2.54cm, so unit should be 'cm'. + self.assertEqual(svg('width="1in" viewBox="0 0 2.54 1"').unit, 'cm') + + # Corrupt the width to contain an invalid number component; note that + # the units change to 'px'. This is because the corrupt number part is + # replaced with 100px, producing a width of "100px"; + self.assertEqual(svg('width="ABCDin" viewBox="0 0 2.54 1"').unit, 'px') + + def test_bad_viewbox_entry(self): + """Fallback test: Bad viewBox default to 100""" + # First, demonstrate that 3779px is 1m, so unit should be 'm'. + self.assertEqual(svg('width="3779px" viewBox="0 0 1 1"').unit, 'm') + + # Corrupt the viewBox to include a non-float value; will default to 'px' + self.assertEqual(svg('width="3779px" viewBox="x 0 1 1"').unit, 'px') + + +class UserUnitTest(TestCase): + """Tests for methods that are based on the value of unit.""" + + def assertToUserUnit(self, user_unit, test_value, expected): # pylint: disable=invalid-name + """Checks a user unit and a test_value against the expected result""" + doc = uu_svg(user_unit) + self.assertEqual(doc.unit, user_unit, msg=svg) + self.assertAlmostEqual(doc.unittouu(test_value), expected) + + def assertFromUserUnit(self, user_unit, value, unit, expected): # pylint: disable=invalid-name + """Check converting from a user unity for the test_value""" + self.assertAlmostEqual(uu_svg(user_unit).uutounit(value, unit), expected) + + # Unit-ratio tests. Don't exhaustively test every unit conversion, just + # demonstrate that the logic works. + + def test_unittouu_in_to_cm(self): + """1in is ~2.54cm""" + self.assertToUserUnit('cm', '1in', 2.54) + + def test_unittouu_yd_to_m(self): + """1yd is ~0.9144m""" + self.assertToUserUnit('m', '1yd', 0.9144) + + def test_unittouu_identity(self): + """If the input and output units are the same, the input and output + values should exactly be the same, too.""" + self.assertToUserUnit('pc', '9.87654321pc', 9.87654321) + + def test_unittouu_unitless_input(self): + """Passing a unitless value to unittouu() should treat the units as 'px'.""" + self.assertToUserUnit('in', '96', 1) # 1in == 96px + + def test_unittouu_empty_input(self): + """Passing an empty string to unittouu() should treat the value as zero.""" + self.assertToUserUnit('in', '', 0) + + def test_unittouu_parsing(self): + """Test user unit parsing forms""" + for value in ( + '100pc', + '100 pc', + ' 100pc', + '100pc ', + '+100pc', + '100.0pc', + '100.0e0pc', + '10.0e1pc', + '10.0e+1pc', + '1000.0e-1pc', + '.1e+3pc', + '+.1e+3pc', + ): + # 100pc is ~3.937in + self.assertToUserUnit('px', value, 1600) + + def test_unittouu_bad_input_number(self): + """Bad input number""" + self.assertToUserUnit('cm', '1in', 2.54) + # Demonstrate that 1in is ~2.54cm. + + # Corrupt the input to contain an invalid number component; note that + # the result changes to zero. + self.assertToUserUnit('cm', 'ABCDin', 0) + + def test_unittouu_bad_input_unit(self): + """Bad input unit""" + # Demonstrate that 1.0in passes through without change. + self.assertToUserUnit('in', '1.0in', 1.0) + + # Corrupt the input to contain an invalid unit component; note that the + # result changes to 0.0, because corrupt parsing is zero px. + # it used to be the ratio between inches and pixels. This was + # because unittouu() treats unknown units as 'px'. + self.assertToUserUnit('in', '1.0ABCD', 0) + + # Unit-ratio tests. Don't exhaustively test every unit conversion, just + # demonstrate that the logic works. + + def test_uutounit_cm_to_in(self): + """Convert 1 user unit ('in') to 'cm'.""" + self.assertFromUserUnit('in', 1, 'cm', 2.54) # 1in is ~2.54cm + + def test_uutounit_m_to_yd(self): + """Convert 1 user unit ('yd') to 'm'.""" + self.assertFromUserUnit('yd', 1, 'm', 0.9144) # 1yd is ~0.9144m + + def test_uutounit_identity(self): + """If the input and output units are the same, the input and output + values should exactly be the same, too.""" + self.assertFromUserUnit('pc', 9.87654321, 'pc', 9.87654) + + def test_uutounit_unknown_unit(self): + """Demonstrate that passing an unknown unit string to uutounit()""" + self.assertEqual(uu_svg('in').uutounit(1, 'px'), 96.0) + + def test_adddocumentunit_common(self): + """Test common add_unit results""" + # For valid float inputs, the output should be the input with the user unit appended. + doc = uu_svg('pt') + cases = ( + # Input, expected output + (100, '100pt'), + ('100', '100pt'), + ('+100', '100pt'), + ('-100', '-100pt'), + ('100.0', '100pt'), + ('100.0e0', '100pt'), + ('10.0e1', '100pt'), + ('10.0e+1', '100pt'), + ('1000.0e-1', '100pt'), + ('.1e+3', '100pt'), + ('+.1e+3', '100pt'), + (' 100', '100pt'), + ('100 ', '100pt'), + (' 100 ', '100pt'), + ) + for input_value, expected in cases: + self.assertEqual(doc.add_unit(input_value), expected) + + def test_adddocumentunit_non_float(self): + """Strings that are invalid floats should pass through unchanged.""" + doc = uu_svg('pt') + inputs = ( + '', + 'ABCD', + '.', + ' ', + ) + for value in inputs: + self.assertEqual(doc.add_unit(value), '') diff --git a/share/extensions/tests/test_inkex_tester.py b/share/extensions/tests/test_inkex_tester.py new file mode 100644 index 0000000..a5ce31a --- /dev/null +++ b/share/extensions/tests/test_inkex_tester.py @@ -0,0 +1,28 @@ +# coding=utf-8 +""" +Test Inkex tester functionality +""" +from inkex.tester import TestCase +from inkex.tester.xmldiff import xmldiff + +class TesterTest(TestCase): + """Ironic""" + maxDiff = 20000 + + def get_file(self, filename): + """Get the contents of a file""" + with open(self.data_file('svg', filename), 'rb') as fhl: + return fhl.read() + + def test_xmldiff(self): + """XML Diff""" + xml_a = self.get_file('shapes.svg') + xml_b = self.get_file('diff.svg') + + xml, delta = xmldiff(xml_a, xml_b) + self.assertFalse(delta) + self.assertEqual(str(delta), '7 xml differences') + #self.assertEqual(str(xml), '') + xml, delta = xmldiff(xml_a, xml_a) + self.assertTrue(delta) + self.assertEqual(str(delta), 'No differences detected') diff --git a/share/extensions/tests/test_inkex_transforms.py b/share/extensions/tests/test_inkex_transforms.py new file mode 100644 index 0000000..07224ca --- /dev/null +++ b/share/extensions/tests/test_inkex_transforms.py @@ -0,0 +1,567 @@ +# coding=utf-8 +""" +Test Inkex transformational logic. +""" +from math import sqrt, pi +from inkex.transforms import ( + Vector2d, ImmutableVector2d, BoundingBox, BoundingInterval, Transform, DirectedLineSegment +) +from inkex.utils import PY3 +from inkex.tester import TestCase +import pytest + +class ImmutableVector2dTest(TestCase): + """Test the ImmutableVector2d object""" + def test_vector_creation(self): + """Test ImmutableVector2d creation""" + vec0 = ImmutableVector2d(15, 22) + self.assertEqual(vec0.x, 15) + self.assertEqual(vec0.y, 22) + + vec1 = ImmutableVector2d() + self.assertEqual(vec1.x, 0) + self.assertEqual(vec1.y, 0) + + vec2 = ImmutableVector2d((17, 32)) + self.assertEqual(vec2.x, 17) + self.assertEqual(vec2.y, 32) + + vec3 = ImmutableVector2d(vec0) + self.assertEqual(vec3.x, 15) + self.assertEqual(vec3.y, 22) + + self.assertRaises(ValueError, ImmutableVector2d, (1)) + self.assertRaises(ValueError, ImmutableVector2d, (1, 2, 3)) + + def test_binary_operators(self): + """Test binary operators for vector2d""" + vec1 = ImmutableVector2d(15, 22) + vec2 = ImmutableVector2d(5, 3) + + self.assertTrue((vec1 - vec2).is_close((10, 19))) + self.assertTrue((vec1 - (5, 3)).is_close((10, 19))) + self.assertTrue(((15, 22) - vec2).is_close((10, 19))) + self.assertTrue((vec1 + vec2).is_close((20, 25))) + self.assertTrue((vec1 + (5, 3)).is_close((20, 25))) + self.assertTrue(((15, 22) + vec2).is_close((20, 25))) + self.assertTrue((vec1 * 2).is_close((30, 44))) + self.assertTrue((2 * vec1).is_close((30, 44))) + self.assertTrue((vec1 / 2).is_close((7.5, 11))) + self.assertTrue((vec1.__div__(2)).is_close((7.5, 11))) + self.assertTrue((vec1 // 2).is_close((7.5, 11))) + + def test_ioperators(self): + """Test operators for vector2d""" + vec0 = vec = ImmutableVector2d(15, 22) + vec += (1, 1) + vec = ImmutableVector2d(vec) + self.assertTrue(vec.is_close((16, 23))) + vec -= (10, 20) + vec = ImmutableVector2d(vec) + self.assertTrue(vec.is_close((6, 3))) + vec *= 5 + vec = ImmutableVector2d(vec) + self.assertTrue(vec.is_close((30, 15))) + vec /= 90 + vec = ImmutableVector2d(vec) + self.assertTrue(vec.is_close((1.0/3, 1.0/6))) + vec //= 1.0/3 + vec = ImmutableVector2d(vec) + self.assertTrue(vec.is_close((1, 0.5))) + self.assertTrue(vec0.is_close((15, 22))) + self.assertFalse(vec0.is_close(vec)) + + def test_unary_operators(self): + """Test unary operators""" + vec = ImmutableVector2d(1, 2) + self.assertTrue((-vec).is_close((-1, -2))) + self.assertTrue((+vec).is_close(vec)) + self.assertTrue(+vec is not vec) # returned value is a copy + + def test_representations(self): + """Test ImmutableVector2d Repr""" + self.assertEqual(str(ImmutableVector2d(1, 2)), "1, 2") + self.assertEqual(repr(ImmutableVector2d(1, 2)), "Vector2d(1, 2)") + self.assertEqual(ImmutableVector2d(1, 2).to_tuple(), (1, 2)) + + def test_assign(self): + """Test ImmutableVector2d assignement""" + vec = ImmutableVector2d(10, 20) + with pytest.raises(AttributeError): + vec.assign(5, 10) + + def test_getitem(self): + """Test getitem for ImmutableVector2d""" + vec = ImmutableVector2d(10, 20) + self.assertEqual(len(vec), 2) + self.assertEqual(vec[0], 10) + self.assertEqual(vec[1], 20) + + +class Vector2dTest(TestCase): + """Test the Vector2d object""" + def test_vector_creation(self): + """Test Vector2D creation""" + vec0 = Vector2d(15, 22) + self.assertEqual(vec0.x, 15) + self.assertEqual(vec0.y, 22) + + vec1 = Vector2d() + self.assertEqual(vec1.x, 0) + self.assertEqual(vec1.y, 0) + + vec2 = Vector2d((17, 32)) + self.assertEqual(vec2.x, 17) + self.assertEqual(vec2.y, 32) + + vec3 = Vector2d(vec0) + self.assertEqual(vec3.x, 15) + self.assertEqual(vec3.y, 22) + + self.assertRaises(ValueError, Vector2d, (1)) + self.assertRaises(ValueError, Vector2d, (1, 2, 3)) + + def test_binary_operators(self): + """Test binary operators for vector2d""" + vec1 = Vector2d(15, 22) + vec2 = Vector2d(5, 3) + + self.assertTrue((vec1 - vec2).is_close((10, 19))) + self.assertTrue((vec1 - (5, 3)).is_close((10, 19))) + self.assertTrue(((15, 22) - vec2).is_close((10, 19))) + self.assertTrue((vec1 + vec2).is_close((20, 25))) + self.assertTrue((vec1 + (5, 3)).is_close((20, 25))) + self.assertTrue(((15, 22) + vec2).is_close((20, 25))) + self.assertTrue((vec1 * 2).is_close((30, 44))) + self.assertTrue((2 * vec1).is_close((30, 44))) + self.assertTrue((vec1 / 2).is_close((7.5, 11))) + self.assertTrue((vec1.__div__(2)).is_close((7.5, 11))) + self.assertTrue((vec1 // 2).is_close((7.5, 11))) + + def test_ioperators(self): + """Test operators for vector2d""" + vec0 = vec = Vector2d(15, 22) + vec += (1, 1) + self.assertTrue(vec.is_close((16, 23))) + vec -= (10, 20) + self.assertTrue(vec.is_close((6, 3))) + vec *= 5 + self.assertTrue(vec.is_close((30, 15))) + vec /= 90 + self.assertTrue(vec.is_close((1.0/3, 1.0/6))) + vec //= 1.0/3 + self.assertTrue(vec.is_close((1, 0.5))) + self.assertFalse(vec0.is_close((15, 22))) + self.assertTrue(vec0.is_close(vec)) + + def test_unary_operators(self): + """Test unary operators""" + vec = Vector2d(1, 2) + self.assertTrue((-vec).is_close((-1, -2))) + self.assertTrue((+vec).is_close(vec)) + self.assertTrue(+vec is not vec) # returned value is a copy + + def test_representations(self): + """Test Vector2D Repr""" + self.assertEqual(str(Vector2d(1, 2)), "1, 2") + self.assertEqual(repr(Vector2d(1, 2)), "Vector2d(1, 2)") + self.assertEqual(Vector2d(1, 2).to_tuple(), (1, 2)) + + def test_assign(self): + """Test vector2d assignement""" + vec = Vector2d(10, 20) + vec.assign(5, 10) + self.assertAlmostTuple(vec, (5, 10)) + vec.assign((7, 11)) + self.assertAlmostTuple(vec, (7, 11)) + + def test_getitem(self): + """Test getitem for Vector2D""" + vec = Vector2d(10, 20) + self.assertEqual(len(vec), 2) + self.assertEqual(vec[0], 10) + self.assertEqual(vec[1], 20) + + +class TransformTest(TestCase): + """Test transformation API and calculations""" + + def test_new_empty(self): + """Create a transformation from two triplets matrix""" + self.assertEqual(Transform(), ((1, 0, 0), (0, 1, 0))) + + def test_new_from_triples(self): + """Create a transformation from two triplets matrix""" + self.assertEqual(Transform(((1, 2, 3), (4, 5, 6))), ((1, 2, 3), (4, 5, 6))) + + def test_new_from_sextlet(self): + """Create a transformation from a list of six numbers""" + self.assertEqual(Transform((1, 2, 3, 4, 5, 6)), ((1, 3, 5), (2, 4, 6))) + + def test_new_from_matrix_str(self): + """Create a transformation from a list of six numbers""" + self.assertEqual(Transform('matrix(1, 2, 3, 4, 5, 6)'), ((1, 3, 5), (2, 4, 6))) + + def test_new_from_scale(self): + """Create a scale based transformation""" + self.assertEqual(Transform('scale(10)'), ((10, 0, 0), (0, 10, 0))) + self.assertEqual(Transform('scale(10, 3.3)'), ((10, 0, 0), (0, 3.3, 0))) + + def test_new_from_translate(self): + """Create a translate transformation""" + self.assertEqual(Transform('translate(12)'), ((1, 0, 12), (0, 1, 0))) + self.assertEqual(Transform('translate(12, 14)'), ((1, 0, 12), (0, 1, 14))) + + def test_new_from_rotate(self): + """Create a rotational transformation""" + self.assertEqual(str(Transform('rotate(90)')), 'rotate(90)') + self.assertEqual(str(Transform('rotate(90 10 12)')), + 'matrix(6.12323e-17 1 -1 6.12323e-17 22 2)') + + def test_new_from_skew(self): + """Create skew x/y transformations""" + self.assertEqual(str(Transform('skewX(10)')), 'matrix(1 0 0.176327 1 0 0)') + self.assertEqual(str(Transform('skewY(10)')), 'matrix(1 0.176327 0 1 0 0)') + + def test_invalid_creation_string(self): + """Test creating invalid transforms""" + self.assertEqual(Transform('boo(4)'), ((1, 0, 0), (0, 1, 0))) + + def test_invalid_creation_matrix(self): + """Test creating invalid transforms""" + self.assertRaises(ValueError, Transform, 0.0) + self.assertRaises(ValueError, Transform, (0.0,)) + self.assertRaises(ValueError, Transform, (0.0, 0.0, 0.0)) + + def test_repr(self): + """Test repr string""" + self.assertEqual(repr(Transform()), 'Transform(((1, 0, 0), (0, 1, 0)))') + + def test_matrix_inversion(self): + """Test the negative of a transformation""" + self.assertEqual(-Transform('rotate(45)'), Transform('rotate(-45)')) + self.assertEqual(-Transform('translate(12, 10)'), Transform('translate(-12, -10)')) + self.assertEqual(-Transform('scale(4)'), Transform('scale(0.25)')) + + def test_apply_to_point(self): + """Test applying the transformation to a point""" + trans = Transform('translate(10, 10)') + self.assertEqual(trans.apply_to_point((10, 10)).to_tuple(), (20, 20)) + self.assertRaises(ValueError, trans.apply_to_point, '') + + def test_translate(self): + """Test making translate specific items""" + self.assertEqual(str(Transform(translate=(10.6, 99.9))), "translate(10.6, 99.9)") + + def test_scale(self): + """Test making scale specific items""" + self.assertEqual(str(Transform(scale=(1.0, 2.2))), "scale(1, 2.2)") + + def test_rotate(self): + """Test making rotate specific items""" + self.assertEqual(str(Transform(rotate=45)), "rotate(45)") + self.assertEqual(str(Transform(rotate=(45, 10, 10))), "matrix(0.707107 0.707107 -0.707107 0.707107 10 -4.14214)") + + def test_add_transform(self): + """Quickly add known transforms""" + tr1 = Transform() + tr1.add_scale(5.0, 1.0) + self.assertEqual(str(tr1), 'scale(5, 1)') + tr1.add_translate(10, 10) + self.assertEqual(str(tr1), 'matrix(5 0 0 1 50 10)') + + def test_is_unity(self): + unity = Transform() + self.assertTrue(unity.is_rotate()) + self.assertTrue(unity.is_scale()) + self.assertTrue(unity.is_translate()) + + def test_is_rotation(self): + rot1 = Transform(rotate=21) + rot2 = Transform(rotate=35) + rot3 = Transform(rotate=53) + + self.assertFalse(Transform(translate=1e-9).is_rotate(exactly=True)) + self.assertFalse(Transform(scale=1+1e-9).is_rotate(exactly=True)) + self.assertFalse(Transform(skewx=1e-9).is_rotate(exactly=True)) + self.assertFalse(Transform(skewy=1e-9).is_rotate(exactly=True)) + + self.assertTrue(Transform(translate=1e-9).is_rotate(exactly=False)) + self.assertTrue(Transform(scale=1+1e-9).is_rotate(exactly=False)) + self.assertTrue(Transform(skewx=1e-9).is_rotate(exactly=False)) + self.assertTrue(Transform(skewy=1e-9).is_rotate(exactly=False)) + + self.assertTrue(rot1.is_rotate()) + self.assertTrue(rot2.is_rotate()) + self.assertTrue(rot3.is_rotate()) + + self.assertFalse(rot1.is_translate()) + self.assertFalse(rot2.is_translate()) + self.assertFalse(rot3.is_translate()) + + self.assertFalse(rot1.is_scale()) + self.assertFalse(rot2.is_scale()) + self.assertFalse(rot3.is_scale()) + + self.assertTrue((rot1 * rot1).is_rotate()) + self.assertTrue((rot1 * rot2).is_rotate()) + self.assertTrue((rot1 * rot2 * rot3 * rot2 * rot1).is_rotate()) + + def test_is_translate(self): + tr1 = Transform(translate=(1.1,)) + tr2 = Transform(translate=(1.3, 2.7)) + tr3 = Transform(translate=(sqrt(2) / 2, pi)) + + self.assertFalse(Transform(rotate=1e-9).is_translate(exactly=True)) + self.assertFalse(Transform(scale=1+1e-9).is_translate(exactly=True)) + self.assertFalse(Transform(skewx=1e-9).is_translate(exactly=True)) + self.assertFalse(Transform(skewy=1e-9).is_translate(exactly=True)) + + self.assertTrue(Transform(rotate=1e-9).is_translate(exactly=False)) + self.assertTrue(Transform(scale=1+1e-9).is_translate(exactly=False)) + self.assertTrue(Transform(skewx=1e-9).is_translate(exactly=False)) + self.assertTrue(Transform(skewy=1e-9).is_translate(exactly=False)) + + self.assertTrue(tr1.is_translate()) + self.assertTrue(tr2.is_translate()) + self.assertTrue(tr3.is_translate()) + self.assertFalse(tr1.is_rotate()) + self.assertFalse(tr2.is_rotate()) + self.assertFalse(tr3.is_rotate()) + self.assertFalse(tr1.is_scale()) + self.assertFalse(tr2.is_scale()) + self.assertFalse(tr3.is_scale()) + + self.assertTrue((tr1 * tr1).is_translate()) + self.assertTrue((tr1 * tr2).is_translate()) + self.assertTrue((tr1 * tr2 * tr3 * tr2 * tr1).is_translate()) + self.assertFalse(tr1 * tr2 * tr3 * -tr1 * -tr2 * -tr3) # is almost unity + + def test_is_scale(self): + s1 = Transform(scale=(1.1,)) + s2 = Transform(scale=(1.3, 2.7)) + s3 = Transform(scale=(sqrt(2) / 2, pi)) + + self.assertFalse(Transform(translate=1e-9).is_scale(exactly=True)) + self.assertFalse(Transform(rotate=1e-9).is_scale(exactly=True)) + self.assertFalse(Transform(skewx=1e-9).is_scale(exactly=True)) + self.assertFalse(Transform(skewy=1e-9).is_scale(exactly=True)) + + self.assertTrue(Transform(translate=1e-9).is_scale(exactly=False)) + self.assertTrue(Transform(rotate=1e-9).is_scale(exactly=False)) + self.assertTrue(Transform(skewx=1e-9).is_scale(exactly=False)) + self.assertTrue(Transform(skewy=1e-9).is_scale(exactly=False)) + + self.assertFalse(s1.is_translate()) + self.assertFalse(s2.is_translate()) + self.assertFalse(s3.is_translate()) + self.assertFalse(s1.is_rotate()) + self.assertFalse(s2.is_rotate()) + self.assertFalse(s3.is_rotate()) + self.assertTrue(s1.is_scale()) + self.assertTrue(s2.is_scale()) + self.assertTrue(s3.is_scale()) + + def test_rotation_degrees(self): + self.assertAlmostEqual(Transform(rotate=30).rotation_degrees(), 30) + self.assertAlmostEqual(Transform(translate=(10, 20)).rotation_degrees(), 0) + self.assertAlmostEqual(Transform(scale=(1, 1)).rotation_degrees(), 0) + + self.assertAlmostEqual(Transform(rotate=35, translate=(10, 20)).rotation_degrees(), 35) + self.assertAlmostEqual(Transform(rotate=35, translate=(10, 20), scale=5).rotation_degrees(), 35) + self.assertAlmostEqual(Transform(rotate=35, translate=(10, 20), scale=(5, 5)).rotation_degrees(), 35) + + def rotation_degrees(**kwargs): + return Transform(**kwargs).rotation_degrees() + + self.assertRaises(ValueError, rotation_degrees, rotate=35, skewx=1) + self.assertRaises(ValueError, rotation_degrees, rotate=35, skewy=1) + self.assertRaises(ValueError, rotation_degrees, rotate=35, scale=(10, 11)) + self.assertRaises(ValueError, rotation_degrees, rotate=35, scale=(10, 11)) + + def test_construction_order(self): + """Test transform kwargs construction order""" + if not PY3: + self.skipTest("Construction order is known to fail on python2 (by design).") + return + + self.assertEqual(str(Transform(scale=2.0, translate=(5, 6))), + 'matrix(2 0 0 2 5 6)') + self.assertEqual(str(Transform(scale=2.0, rotate=45)), + 'matrix(1.41421 1.41421 -1.41421 1.41421 0 0)') + + x, y, angle = 5, 7, 31 + rotation = Transform(rotate=angle) + translation = Transform(translate=(x, y)) + + rotation_then_translation = translation * rotation + translation_then_rotation = rotation * translation + + tr1 = Transform(rotate=angle, translate=(x, y)) + tr2 = Transform(translate=(x, y), rotate=angle) + + self.assertNotEqual(tr1, tr2) + self.assertDeepAlmostEqual(tr1.matrix, rotation_then_translation.matrix) + self.assertDeepAlmostEqual(tr2.matrix, translation_then_rotation.matrix) + + def test_interpolate(self): + """Test interpolate with other transform""" + t1 = Transform((0,0,0,0,0,0)) + t2 = Transform((1,1,1,1,1,1)) + val = t1.interpolate(t2, 0.5) + assert all(getattr(val, a) == pytest.approx(0.5, 1e-3) for a in 'abcdef') + + + +class ScaleTest(TestCase): + """Test scale class""" + + def test_creation(self): + """Creating scales""" + self.assertEqual(BoundingInterval(0, 0), (0, 0)) + self.assertEqual(BoundingInterval(1), (1, 1)) + self.assertEqual(BoundingInterval(10), (10, 10)) + self.assertEqual(BoundingInterval(10, 20), (10, 20)) + self.assertEqual(BoundingInterval((2, 50)), (2, 50)) + self.assertEqual(repr(BoundingInterval((5, 10))), 'BoundingInterval(5, 10)') + + def test_center(self): + """Center of a scale""" + self.assertEqual(BoundingInterval(0, 0).center, 0) + self.assertEqual(BoundingInterval(0, 10).center, 5) + self.assertEqual(BoundingInterval(-10, 10).center, 0) + + def test_neg(self): + """-Span(...)""" + self.assertEqual(tuple(-BoundingInterval(-10, 10)), (-10, 10)) + self.assertEqual(tuple(-BoundingInterval(-15, 2)), (-2, 15)) + self.assertEqual(tuple(-BoundingInterval(100, 110)), (-110, -100)) + self.assertEqual(tuple(-BoundingInterval(-110, -100)), (100, 110)) + + def test_size(self): + """Size of the scale""" + self.assertEqual(BoundingInterval(0, 0).size, 0) + self.assertEqual(BoundingInterval(10, 30).size, 20) + self.assertEqual(BoundingInterval(-10, 10).size, 20) + self.assertEqual(BoundingInterval(-30, -10).size, 20) + + def test_combine(self): + """Combine scales together""" + self.assertEqual(BoundingInterval(9, 10) + BoundingInterval(4, 5), (4, 10)) + self.assertEqual(sum([BoundingInterval(4), BoundingInterval(3), BoundingInterval(10)], None), (3, 10)) + self.assertEqual(BoundingInterval(2, 2) * 2, (4, 4)) + + def test_errors(self): + """Expected errors""" + self.assertRaises(ValueError, BoundingInterval, 'foo') + + +class BoundingBoxTest(TestCase): + """Test bounding box calculations""" + + def test_bbox(self): + """Creating bounding boxes""" + self.assertEqual(tuple(BoundingBox(1, 3)), ((1, 1), (3, 3))) + self.assertEqual(tuple(BoundingBox((1, 2), 3)), ((1, 2), (3, 3))) + self.assertEqual(tuple(BoundingBox(1, (3, 4))), ((1, 1), (3, 4))) + self.assertEqual(tuple(BoundingBox((1, 2), (3, 4))), ((1, 2), (3, 4))) + self.assertEqual(repr(BoundingBox((1, 2), (3, 4))), 'BoundingBox((1, 2),(3, 4))') + + def test_bbox_sum(self): + """Test adding bboxes together""" + self.assertEqual(tuple(BoundingBox((0, 10), (0, 10)) + BoundingBox((-10, 0), (-10, 0))), ((-10, 10), (-10, 10))) + ret = sum([ + BoundingBox((-5, 0), (0, 0)), + BoundingBox((0, 5), (0, 0)), + BoundingBox((0, 0), (-5, 0)), + BoundingBox((0, 0), (0, 5))], None) + self.assertEqual(tuple(ret), ((-5, 5), (-5, 5))) + self.assertEqual(tuple(BoundingBox(-10, 2) + ret), ((-10, 5), (-5, 5))) + self.assertEqual(tuple(ret + BoundingBox(1, -10)), ((-5, 5), (-10, 5))) + + def test_bbox_neg(self): + self.assertEqual(tuple(-BoundingBox(-10, 2)), ((10, 10), (-2, -2))) + self.assertEqual(tuple(-BoundingBox((-10, 15), (2, 10))), ((-15, 10), (-10, -2))) + + def test_bbox_scale(self): + """Bounding Boxes can be scaled""" + self.assertEqual(tuple(BoundingBox(1, 3) * 2), ((2, 2), (6, 6))) + + def test_bbox_anchor_left_right(self): + """Bunding box anchoring (left to right)""" + bbox = BoundingBox((-1, 1), (10, 20)) + self.assertEqual([ + bbox.get_anchor('l', 't', 'lr'), + bbox.get_anchor('m', 't', 'lr'), + bbox.get_anchor('r', 't', 'lr'), + bbox.get_anchor('l', 't', 'rl'), + bbox.get_anchor('m', 't', 'rl'), + bbox.get_anchor('r', 't', 'rl'), + ], [-1, 0.0, 1, 1, -0.0, -1]) + + def test_bbox_anchor_top_bottom(self): + """Bunding box anchoring (top to bottom)""" + bbox = BoundingBox((10, 20), (-1, 1)) + self.assertEqual([ + bbox.get_anchor('l', 't', 'tb'), + bbox.get_anchor('l', 'm', 'tb'), + bbox.get_anchor('l', 'b', 'tb'), + bbox.get_anchor('l', 't', 'bt'), + bbox.get_anchor('l', 'm', 'bt'), + bbox.get_anchor('l', 'b', 'bt'), + ], [-1, 0.0, 1, 1, -0.0, -1]) + + def test_bbox_anchor_custom(self): + """Bounding box anchoring custom angle""" + bbox = BoundingBox((10, 10), (5, 5)) + self.assertEqual([ + bbox.get_anchor('l', 't', 0), + bbox.get_anchor('l', 't', 90), + bbox.get_anchor('l', 't', 180), + bbox.get_anchor('l', 't', 270), + bbox.get_anchor('l', 't', 45), + ], [10, -5, -10, 5, 3.5355339059327378]) + + def test_bbox_anchor_radial(self): + """Bounding box anchoring radial in/out""" + bbox = BoundingBox((10, 10), (5, 5)) + self.assertRaises(ValueError, bbox.get_anchor, 'm', 'm', 'ro') + selbox = BoundingBox((100, 100), (100, 100)) + self.assertEqual(int(bbox.get_anchor('m', 'm', 'ro', selbox)), 130) + +class SegmentTest(TestCase): + """Test special Segments""" + + def test_segment_creation(self): + """Test segments""" + self.assertEqual(DirectedLineSegment((1, 2), (3, 4)), (1, 3, 2, 4)) + self.assertEqual(repr(DirectedLineSegment((1, 2), (3, 4))), 'DirectedLineSegment((1, 2), (3, 4))') + + def test_segment_maths(self): + """Segments have calculations""" + self.assertEqual(DirectedLineSegment((0, 0), (10, 0)).angle, 0) + + +class ExtremaTest(TestCase): + """Test school formula implementation""" + + def test_cubic_extrema_1(self): + from inkex.transforms import cubic_extrema + a, b, c, d = 14.644651000000003194,-4.881549508464541276,-4.8815495084645448287,14.644651000000003194 + cmin, cmax = cubic_extrema(a, b, c, d) + self.assertAlmostEqual(cmin, 0, delta=1e-6) + self.assertAlmostEqual(cmax, a, delta=1e-6) + + def test_quadratic_extrema_1(self): + from inkex.transforms import quadratic_extrema + a, b = 5.0, 12.0 + cmin, cmax = quadratic_extrema(a, b, a) + self.assertAlmostEqual(cmin, 5, delta=1e-6) + self.assertAlmostEqual(cmax, 8.5, delta=1e-6) + + def test_quadratic_extrema_2(self): + from inkex.transforms import quadratic_extrema + a = 5.0 + cmin, cmax = quadratic_extrema(a,a,a) + self.assertAlmostEqual(cmin, a, delta=1e-6) + self.assertAlmostEqual(cmax, a, delta=1e-6) diff --git a/share/extensions/tests/test_inkex_tween.py b/share/extensions/tests/test_inkex_tween.py new file mode 100644 index 0000000..7b48f7f --- /dev/null +++ b/share/extensions/tests/test_inkex_tween.py @@ -0,0 +1,21 @@ +# coding=utf-8 +"""Test units inkex module functionality""" +from inkex.tester import TestCase + +import inkex +import inkex.tween as tween +import pytest + +class TweenTest(TestCase): + """Unit tests for the Inkscape inkex tween library""" + black = inkex.Color('#000000') + grey50 = inkex.Color('#080808') + white = inkex.Color('#111111') + + def test_interpcoord(self): + val = tween.interpcoord(0, 1, 0.5) + assert val == pytest.approx(0.5, 1e-3) + + def test_interppoints(self): + val = tween.interppoints((0,0), (1,1), 0.5) + assert val == pytest.approx((0.5, 0.5), (1e-3, 1e-3)) diff --git a/share/extensions/tests/test_inkex_units.py b/share/extensions/tests/test_inkex_units.py new file mode 100644 index 0000000..aaba871 --- /dev/null +++ b/share/extensions/tests/test_inkex_units.py @@ -0,0 +1,64 @@ +# coding=utf-8 +"""Test units inkex module functionality""" +from inkex.units import are_near_relative, convert_unit, discover_unit, parse_unit, render_unit +from inkex.tester import TestCase + + +class UnitsTest(TestCase): + """Tests for Inkscape Units handling""" + + def test_parse_unit(self): + """Test parsing a unit in a document""" + self.assertEqual(parse_unit('50px'), (50.0, 'px')) + self.assertEqual(parse_unit('50'), (50.0, 'px')) + self.assertEqual(parse_unit('50quaks'), None) + self.assertEqual(parse_unit('50quaks', default_value=10), (10.0, 'px')) + self.assertEqual(parse_unit('50%'), (50.0, '%')) + + def test_near(self): + """Test the closeness of numbers""" + self.assertFalse(are_near_relative(10.0, 5.0)) + self.assertTrue(are_near_relative(10.0, 9.99)) + + def test_discover_unit(self): + """Based on the size of a document and it's viewBox""" + self.assertEqual(discover_unit('50px', 50), 'px') + self.assertEqual(discover_unit('100mm', 3.94), 'in') + self.assertEqual(discover_unit('3779', 1.0), 'm') + self.assertEqual(discover_unit('50quaks', 150), 'px') + + def test_convert_unit(self): + """Convert units from one to another""" + self.assertEqual(convert_unit("10mm", 'px'), 37.79527559055118) + self.assertEqual(convert_unit("1in", 'cm'), 2.54) + self.assertEqual(convert_unit("37.79527559055118px", 'mm'), 10.0) + self.assertEqual(convert_unit("1in", ''), 96.0) + self.assertEqual(convert_unit("96", 'in'), 1.0) + self.assertEqual(convert_unit("10%", 'mm'), 0.0) + self.assertEqual(convert_unit("1in", 'grad'), 0.0) + self.assertEqual(convert_unit("10quaks", 'mm'), 0.0) + self.assertEqual(convert_unit("10mm", 'quaks'), 0.0) + + def test_render_unit(self): + """Convert unit and value pair into rendered unit string""" + self.assertEqual(render_unit(10.0, 'mm'), '10mm') + self.assertEqual(render_unit(10.01, 'mm'), '10.01mm') + self.assertEqual(render_unit(10.000001, 'mm'), '10mm') + self.assertEqual(render_unit('10cm', 'mm'), '10cm') + + def test_number_parsing(self): + """Width number parsing test""" + for value in ( + '100mm', + '100 mm', + ' 100mm', + '100mm ', + '+100mm', + '100.0mm', + '100.0e0mm', + '10.0e1mm', + '10.0e+1mm', + '1000.0e-1mm', + '.1e+3mm', + '+.1e+3mm'): + self.assertEqual(parse_unit(value), (100, 'mm')) diff --git a/share/extensions/tests/test_inkex_utils.py b/share/extensions/tests/test_inkex_utils.py new file mode 100644 index 0000000..350ceb1 --- /dev/null +++ b/share/extensions/tests/test_inkex_utils.py @@ -0,0 +1,98 @@ +# coding=utf-8 +""" +Unit test file for ../inkex.py +""" +# Revision history: +# * 2012-01-27 (jazzynico): check errormsg function. +# +from __future__ import absolute_import, print_function + +from argparse import ArgumentTypeError + +import pytest + +from inkex.utils import addNS, debug, errormsg, filename_arg, Boolean, to, strargs + + +class TestInkexBasic(object): + """Test basic utiltiies of inkex""" + + def test_boolean(self): + """Inkscape boolean input""" + assert Boolean('TRUE') is True + assert Boolean('true') is True + assert Boolean('True') is True + assert Boolean('FALSE') is False + assert Boolean('false') is False + assert Boolean('False') is False + assert Boolean('Banana') is None + + def test_debug(self, capsys): + """Debug messages go to stderr""" + debug("Hello World") + assert capsys.readouterr().err == 'Hello World\n' + + def test_to(self): + """Decorator for generators""" + + @to(list) + def mylist(a, b, c): + """Yield as a list""" + yield a + yield c + yield b + + assert isinstance(mylist(1, 2, 3), list) + assert mylist(1, 2, 3) == [1, 3, 2] + + @to(dict) + def mydict(a, b, c): + """Yield as a dictionary""" + yield ('age', a) + yield ('name', c) + yield ('home', b) + + assert isinstance(mydict(1, 2, 3), dict) + assert mydict(1, 2, 3) == {'age': 1, 'name': 3, 'home': 2} + + def test_filename(self): + """Filename argument input""" + assert filename_arg(__file__) == __file__ + with pytest.raises(ArgumentTypeError): + filename_arg('doesntexist.txt') + + def test_add_ns(self): + """Test addNS function""" + assert addNS('inkscape:foo') == '{http://www.inkscape.org/namespaces/inkscape}foo' + assert addNS('bar', 'inkscape') == '{http://www.inkscape.org/namespaces/inkscape}bar' + assert addNS('url', 'rdf') == '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}url' + assert addNS('{http://www.inkscape.org/namespaces/inkscape}bar') == '{http://www.inkscape.org/namespaces/inkscape}bar' + assert addNS('http://www.inkscape.org/namespaces/inkscape:bar') == '{http://www.inkscape.org/namespaces/inkscape}bar' + assert addNS('car', 'http://www.inkscape.org/namespaces/inkscape') == '{http://www.inkscape.org/namespaces/inkscape}car' + assert addNS('{http://www.inkscape.org/namespaces/inkscape}bar', 'rdf') == '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}bar' + + def test_strargs(self): + """Test strargs function""" + assert strargs('1.0 2.0 3.0 4.0') == [1.0, 2.0, 3.0, 4.0] + assert strargs('1 -2 3 -4') == [1.0, -2.0, 3.0, -4.0] + assert strargs('1,-2,3,-4') == [1.0, -2.0, 3.0, -4.0] + assert strargs('1-2 3-4') == [1.0, -2.0, 3.0, -4.0] + assert strargs('1-2,3-4') == [1.0, -2.0, 3.0, -4.0] + assert strargs('1-2-3-4') == [1.0, -2.0, -3.0, -4.0] + + def test_ascii(self, capsys): + """Parse ABCabc""" + errormsg('ABCabc') + assert capsys.readouterr().err == 'ABCabc\n' + + def test_nonunicode_latin1(self, capsys): + # Py2 has issues with unicode in docstrings. *sigh* + # """Parse Àûïàèé""" + errormsg('Àûïàèé') + assert capsys.readouterr().err, 'Àûïàèé\n' + + def test_unicode_latin1(self, capsys): + # Py2 has issues with unicode in docstrings. *sigh* + # """Parse Àûïàèé (unicode)""" + errormsg(u'Àûïàèé') + assert capsys.readouterr().err, u'Àûïàèé\n' diff --git a/share/extensions/tests/test_inkscape_follow_link.py b/share/extensions/tests/test_inkscape_follow_link.py new file mode 100644 index 0000000..21a44ab --- /dev/null +++ b/share/extensions/tests/test_inkscape_follow_link.py @@ -0,0 +1,6 @@ +# coding=utf-8 +from inkscape_follow_link import FollowLink +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + +class TestFollowLinkBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = FollowLink diff --git a/share/extensions/tests/test_inkwebeffect.py b/share/extensions/tests/test_inkwebeffect.py new file mode 100644 index 0000000..16c0c24 --- /dev/null +++ b/share/extensions/tests/test_inkwebeffect.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from inkwebeffect import InkWebEffect +from inkex.tester import InkscapeExtensionTestMixin, TestCase + + +class InkWebEffectBasicTest(InkscapeExtensionTestMixin, TestCase): + effect_class = InkWebEffect diff --git a/share/extensions/tests/test_interp.py b/share/extensions/tests/test_interp.py new file mode 100644 index 0000000..b9e1876 --- /dev/null +++ b/share/extensions/tests/test_interp.py @@ -0,0 +1,15 @@ +# coding=utf-8 +from interp import Interp +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy + +class InterpBasicTest(ComparisonMixin, TestCase): + effect_class = Interp + comparisons = [ + ('--id=path1', '--id=path2', '--id=path3', '--id=path4', '--id=path5', + '--id=path6', '--id=path7', '--id=path8', '--id=path9', '--id=path10'), + ('--id=path1', '--id=path2', '--id=path3', '--id=path4', '--id=path5', + '--id=path6', '--id=path7', '--id=path8', '--id=path9', '--id=path10', '--method=2') + ] + compare_filters = [CompareNumericFuzzy()] + compare_file = 'svg/interp_shapes.svg' diff --git a/share/extensions/tests/test_interp_att_g.py b/share/extensions/tests/test_interp_att_g.py new file mode 100644 index 0000000..d9e7e06 --- /dev/null +++ b/share/extensions/tests/test_interp_att_g.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# coding=utf-8 +from interp_att_g import InterpAttG +from inkex.tester import ComparisonMixin, TestCase + +class InterpAttGBasicTest(ComparisonMixin, TestCase): + effect_class = InterpAttG + comparisons = [('--id=layer1',)] + +class InterpAttGColorRoundingTest(ComparisonMixin, TestCase): + effect_class = InterpAttG + compare_file = 'svg/group_interpolate.svg' + comparisons = { + # test for truncating/rounding bug inbox#1892 + ('--id=g53', '--att=fill', '--start-val=#181818', '--end-val=#000000'), + # test for clipping of values <= 1 + ('--id=g53', '--att=fill', '--start-val=#050505', '--end-val=#000000')} \ No newline at end of file diff --git a/share/extensions/tests/test_jessyink_autotexts.py b/share/extensions/tests/test_jessyink_autotexts.py new file mode 100644 index 0000000..720d579 --- /dev/null +++ b/share/extensions/tests/test_jessyink_autotexts.py @@ -0,0 +1,8 @@ +#!/usr/bin/en +# coding=utf-8 +from jessyink_autotexts import AutoTexts +from inkex.tester import ComparisonMixin, TestCase + +class JessyInkAutoTextsBasicTest(ComparisonMixin, TestCase): + effect_class = AutoTexts + comparisons = [('--autoText', 'slideTitle', '--id', 't1')] diff --git a/share/extensions/tests/test_jessyink_effects.py b/share/extensions/tests/test_jessyink_effects.py new file mode 100644 index 0000000..23ffc1a --- /dev/null +++ b/share/extensions/tests/test_jessyink_effects.py @@ -0,0 +1,12 @@ +#!/usr/bin/en +# coding=utf-8 +from jessyink_effects import JessyinkEffects +from inkex.tester import ComparisonMixin, TestCase + +class JessyInkEffectsTest(ComparisonMixin, TestCase): + effect_class = JessyinkEffects + comparisons = [ + ('--id=p1', '--id=r3'), + ('--id=p1', '--effectIn=fade', '--effectOut=pop'), + ] + python3_only = True diff --git a/share/extensions/tests/test_jessyink_export.py b/share/extensions/tests/test_jessyink_export.py new file mode 100644 index 0000000..2293c31 --- /dev/null +++ b/share/extensions/tests/test_jessyink_export.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from jessyink_export import Export +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareSize + +class JessyInkExportBasicTest(ComparisonMixin, TestCase): + compare_filters = [CompareSize()] + effect_class = Export + comparisons = [('--resolution=1',)] diff --git a/share/extensions/tests/test_jessyink_install.py b/share/extensions/tests/test_jessyink_install.py new file mode 100644 index 0000000..4bfda51 --- /dev/null +++ b/share/extensions/tests/test_jessyink_install.py @@ -0,0 +1,6 @@ +# coding=utf-8 +from jessyink_install import Install +from inkex.tester import ComparisonMixin, TestCase + +class JessyInkInstallBasicTest(ComparisonMixin, TestCase): + effect_class = Install diff --git a/share/extensions/tests/test_jessyink_keybindings.py b/share/extensions/tests/test_jessyink_keybindings.py new file mode 100644 index 0000000..22aa060 --- /dev/null +++ b/share/extensions/tests/test_jessyink_keybindings.py @@ -0,0 +1,10 @@ +# coding=utf-8 +from jessyink_key_bindings import KeyBindings +from inkex.tester import ComparisonMixin, TestCase + +class JessyInkCustomKeyBindingsBasicTest(ComparisonMixin, TestCase): + effect_class = KeyBindings + comparisons = [ + ('--slide_export=SPACE', '--drawing_undo=ENTER', '--index_nextPage=LEFT'), + ('--slide_export=a', '--drawing_undo=b', '--index_nextPage=c'), + ] diff --git a/share/extensions/tests/test_jessyink_masterslide.py b/share/extensions/tests/test_jessyink_masterslide.py new file mode 100644 index 0000000..98c6c64 --- /dev/null +++ b/share/extensions/tests/test_jessyink_masterslide.py @@ -0,0 +1,6 @@ +# coding=utf-8 +from jessyink_master_slide import MasterSlide +from inkex.tester import ComparisonMixin, TestCase + +class JessyInkMasterSlideBasicTest(ComparisonMixin, TestCase): + effect_class = MasterSlide diff --git a/share/extensions/tests/test_jessyink_mousehandler.py b/share/extensions/tests/test_jessyink_mousehandler.py new file mode 100644 index 0000000..73a1ea1 --- /dev/null +++ b/share/extensions/tests/test_jessyink_mousehandler.py @@ -0,0 +1,12 @@ +# coding=utf-8 +from jessyink_mouse_handler import AddMouseHandler +from inkex.tester import ComparisonMixin, TestCase + +class JessyInkAddMouseHandlerTest(ComparisonMixin, TestCase): + """Test jessy ink mouse handler""" + effect_class = AddMouseHandler + comparisons = [ + ('--mouseSetting=default',), + ('--mouseSetting=noclick',), + ('--mouseSetting=draggingZoom',), + ] diff --git a/share/extensions/tests/test_jessyink_summary.py b/share/extensions/tests/test_jessyink_summary.py new file mode 100644 index 0000000..45c3a52 --- /dev/null +++ b/share/extensions/tests/test_jessyink_summary.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from jessyink_summary import Summary +from inkex.tester import ComparisonMixin, TestCase + +class JessyInkSummaryTest(ComparisonMixin, TestCase): + stderr_output = True + effect_class = Summary + comparisons = [()] diff --git a/share/extensions/tests/test_jessyink_transitions.py b/share/extensions/tests/test_jessyink_transitions.py new file mode 100644 index 0000000..20de7bd --- /dev/null +++ b/share/extensions/tests/test_jessyink_transitions.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from jessyink_transitions import Transitions +from inkex.tester import ComparisonMixin, TestCase + +class JessyInkTransitionsBasicTest(ComparisonMixin, TestCase): + effect_class = Transitions + comparisons = [('--layerName', 'Slide2')] diff --git a/share/extensions/tests/test_jessyink_uninstall.py b/share/extensions/tests/test_jessyink_uninstall.py new file mode 100644 index 0000000..6b71f0c --- /dev/null +++ b/share/extensions/tests/test_jessyink_uninstall.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from jessyink_uninstall import Uninstall +from inkex.tester import ComparisonMixin, TestCase + +class JessyInkUninstallBasicTest(ComparisonMixin, TestCase): + effect_class = Uninstall + comparisons = [()] diff --git a/share/extensions/tests/test_jessyink_video.py b/share/extensions/tests/test_jessyink_video.py new file mode 100644 index 0000000..769f11f --- /dev/null +++ b/share/extensions/tests/test_jessyink_video.py @@ -0,0 +1,9 @@ +# coding=utf-8 + +from jessyink_video import Video +from inkex.tester import ComparisonMixin, TestCase + +class JessyInkEffectsBasicTest(ComparisonMixin, TestCase): + effect_class = Video + comparisons = [()] + python3_only = True diff --git a/share/extensions/tests/test_jessyink_view.py b/share/extensions/tests/test_jessyink_view.py new file mode 100644 index 0000000..6834e41 --- /dev/null +++ b/share/extensions/tests/test_jessyink_view.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from jessyink_view import View +from inkex.tester import ComparisonMixin, TestCase + +class JessyInkEffectsBasicTest(ComparisonMixin, TestCase): + effect_class = View + comparisons = [('--id=r3', '--viewOrder=1')] + python3_only = True diff --git a/share/extensions/tests/test_jitternodes.py b/share/extensions/tests/test_jitternodes.py new file mode 100644 index 0000000..d03edea --- /dev/null +++ b/share/extensions/tests/test_jitternodes.py @@ -0,0 +1,13 @@ +# coding=utf-8 +from jitternodes import JitterNodes +from inkex.tester import ComparisonMixin, TestCase + + +class JitterNodesBasicTest(ComparisonMixin, TestCase): + effect_class = JitterNodes + comparisons = [ + ('--id=p1', '--dist=gaussian', '--end=false'), + ('--id=p1', '--dist=uniform', '--ctrl=false'), + ('--id=p1', '--dist=pareto', '--radiusy=100'), + ('--id=p1', '--dist=lognorm', '--radiusx=100'), + ] diff --git a/share/extensions/tests/test_launch_webbrowser.py b/share/extensions/tests/test_launch_webbrowser.py new file mode 100644 index 0000000..cbc5aaf --- /dev/null +++ b/share/extensions/tests/test_launch_webbrowser.py @@ -0,0 +1,23 @@ +# coding=utf-8 +""" +Make sure the webbrowser extension is working. +""" +import launch_webbrowser +from inkex.tester import TestCase + +class TestWebsiteOpen(TestCase): + """Test Website openner with dummy web browser""" + effect_class = launch_webbrowser.ThreadWebsite + + def setUp(self): + super(TestWebsiteOpen, self).setUp() + launch_webbrowser.BROWSER = 'echo %s' + + def tearDown(self): + super(TestWebsiteOpen, self).tearDown() + launch_webbrowser.BROWSER = None + + def test_open(self): + """Test website opens""" + self.effect_class(['--url=https://inkscape.org/']).run() + # There's no way to test the output yet (stdout). diff --git a/share/extensions/tests/test_layer2png.py b/share/extensions/tests/test_layer2png.py new file mode 100644 index 0000000..a45f3ec --- /dev/null +++ b/share/extensions/tests/test_layer2png.py @@ -0,0 +1,43 @@ +""" +Test export slices of an image. +""" + +from inkex.tester import ComparisonMixin, TestCase +from layer2png import ExportSlices + +class Layer2PNGTest(ComparisonMixin, TestCase): + effect_class = ExportSlices + compare_file = 'svg/slicer.svg' + comparisons = [] + + def test_get_layers(self): + basic_svg = self.data_file('svg', 'slicer.svg') + args = [basic_svg, '--layer=slices'] + self.effect.options = self.effect.arg_parser.parse_args(args) + self.effect.options.input_file = basic_svg + self.effect.load_raw() + nodes = self.effect.get_layer_nodes('slices') + self.assertEqual(len(nodes), 1) + self.assertEqual(nodes[0].tag, '{http://www.w3.org/2000/svg}rect') + + + def test_bad_slice_layer(self): + basic_svg = self.data_file('svg', 'slicer.svg') + args = [basic_svg, '--layer=slices'] + self.effect.options = self.effect.arg_parser.parse_args(args) + self.effect.options.input_file = basic_svg + self.effect.load_raw() + nodes = self.effect.get_layer_nodes('badslices') + self.assertEqual(nodes, None) + + + def test_color(self): + basic_svg = self.data_file('svg', 'slicer.svg') + args = [basic_svg, '--layer=slices'] + self.effect.options = self.effect.arg_parser.parse_args(args) + self.effect.options.input_file = basic_svg + self.effect.load_raw() + nodes = self.effect.get_layer_nodes('slices') + color, kwargs = self.effect.get_color_and_command_kwargs(nodes[0]) + self.assertEqual(color, self.effect.GREEN) + self.assertEqual(kwargs['export-id'], 'slice1') diff --git a/share/extensions/tests/test_layers2svgfont.py b/share/extensions/tests/test_layers2svgfont.py new file mode 100644 index 0000000..a53fd07 --- /dev/null +++ b/share/extensions/tests/test_layers2svgfont.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from layers2svgfont import LayersToSvgFont +from inkex.tester import ComparisonMixin, TestCase + +class TestLayers2SVGFontBasic(ComparisonMixin, TestCase): + effect_class = LayersToSvgFont + compare_file = 'svg/font_layers.svg' + comparisons = [()] diff --git a/share/extensions/tests/test_layout_nup.py b/share/extensions/tests/test_layout_nup.py new file mode 100644 index 0000000..32f663c --- /dev/null +++ b/share/extensions/tests/test_layout_nup.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from layout_nup import Nup +from inkex.tester import InkscapeExtensionTestMixin, TestCase + + +class TestNupBasic(InkscapeExtensionTestMixin, TestCase): + effect_class = Nup diff --git a/share/extensions/tests/test_lindenmayer.py b/share/extensions/tests/test_lindenmayer.py new file mode 100644 index 0000000..2ba4693 --- /dev/null +++ b/share/extensions/tests/test_lindenmayer.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from lindenmayer import Lindenmayer +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentStyle + +class LSystemBasicTest(ComparisonMixin, TestCase): + effect_class = Lindenmayer + compare_filters = [CompareOrderIndependentStyle()] diff --git a/share/extensions/tests/test_lorem_ipsum.py b/share/extensions/tests/test_lorem_ipsum.py new file mode 100644 index 0000000..b41de1c --- /dev/null +++ b/share/extensions/tests/test_lorem_ipsum.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from lorem_ipsum import LoremIpsum +from inkex.tester import ComparisonMixin, TestCase + +class LorumIpsumBasicTest(ComparisonMixin, TestCase): + effect_class = LoremIpsum + comparisons = [()] diff --git a/share/extensions/tests/test_markers_strokepaint.py b/share/extensions/tests/test_markers_strokepaint.py new file mode 100644 index 0000000..5e8d244 --- /dev/null +++ b/share/extensions/tests/test_markers_strokepaint.py @@ -0,0 +1,26 @@ +# coding=utf-8 +# +# Unit test file for ../markers_strokepaint.py +# Revision history: +# * 2012-01-27 (jazzynico): checks defaulf parameters and file handling. +# +from markers_strokepaint import MarkersStrokePaint +from inkex.tester import ComparisonMixin, TestCase + +class MarkerStrokePaintBasicTest(ComparisonMixin, TestCase): + effect_class = MarkersStrokePaint + compare_file = 'svg/markers.svg' + comparisons = [ + ('--tab="object"', '--id=dimension'), + ('--tab="custom"', '--id=dimension'), + ] + + def test_basic(self): + args = ['--id=dimension', + self.data_file('svg', 'markers.svg')] + eff = MarkersStrokePaint() + eff.run(args) + old_markers = eff.original_document.getroot().xpath('//svg:defs//svg:marker') + new_markers = eff.svg.xpath('//svg:defs//svg:marker') + self.assertEqual(len(old_markers), 2) + self.assertEqual(len(new_markers), 4) diff --git a/share/extensions/tests/test_measure.py b/share/extensions/tests/test_measure.py new file mode 100644 index 0000000..4e0f7dd --- /dev/null +++ b/share/extensions/tests/test_measure.py @@ -0,0 +1,18 @@ +# coding=utf-8 +from measure import MeasureLength +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy + +class LengthBasicTest(ComparisonMixin, TestCase): + effect_class = MeasureLength + compare_filters = [CompareNumericFuzzy()] + comparisons = [ + ('--id=p1', '--id=p2'), + ('--method=presets', '--presetFormat=TaP_start', '--id=p1'), + ('--method=presets', '--presetFormat=TaP_end', '--id=p2'), + ('--method=presets', '--presetFormat=FT_start', '--id=p1'), + ('--method=presets', '--presetFormat=FT_bbox', '--id=p2'), + ('--method=presets', '--presetFormat=FT_bbox', '--id=p2'), + ('--type=area', '--id=p1'), + ('--type=cofm', '--id=c3'), + ] diff --git a/share/extensions/tests/test_media_zip.py b/share/extensions/tests/test_media_zip.py new file mode 100644 index 0000000..7d34295 --- /dev/null +++ b/share/extensions/tests/test_media_zip.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from media_zip import CompressedMedia +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareSize + +class CmoBasicTest(ComparisonMixin, TestCase): + effect_class = CompressedMedia + compare_filters = [CompareSize()] + comparisons = [()] diff --git a/share/extensions/tests/test_merge_styles.py b/share/extensions/tests/test_merge_styles.py new file mode 100644 index 0000000..8a9d20f --- /dev/null +++ b/share/extensions/tests/test_merge_styles.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from merge_styles import MergeStyles +from inkex.tester import ComparisonMixin, TestCase + +class TestMergeStylesBasic(ComparisonMixin, TestCase): + """Test merging of styles""" + effect_class = MergeStyles + comparisons = [('--id=c2', '--id=c3')] diff --git a/share/extensions/tests/test_motion.py b/share/extensions/tests/test_motion.py new file mode 100644 index 0000000..24659d1 --- /dev/null +++ b/share/extensions/tests/test_motion.py @@ -0,0 +1,11 @@ +# coding=utf-8 +from motion import Motion +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy, CompareWithPathSpace + +class MotionBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = Motion + compare_filters = [CompareNumericFuzzy(), CompareWithPathSpace()] + comparisons = [ + ('--id=c3', '--id=p2'), + ] diff --git a/share/extensions/tests/test_new_glyph_layer.py b/share/extensions/tests/test_new_glyph_layer.py new file mode 100644 index 0000000..faa3d2e --- /dev/null +++ b/share/extensions/tests/test_new_glyph_layer.py @@ -0,0 +1,6 @@ +# coding=utf-8 +from new_glyph_layer import NewGlyphLayer +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + +class TestNewGlyphLayerBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = NewGlyphLayer diff --git a/share/extensions/tests/test_next_glyph_layer.py b/share/extensions/tests/test_next_glyph_layer.py new file mode 100644 index 0000000..8551e16 --- /dev/null +++ b/share/extensions/tests/test_next_glyph_layer.py @@ -0,0 +1,6 @@ +# coding=utf-8 +from next_glyph_layer import NextLayer +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + +class TestNextLayerBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = NextLayer diff --git a/share/extensions/tests/test_nicechart.py b/share/extensions/tests/test_nicechart.py new file mode 100644 index 0000000..2171fca --- /dev/null +++ b/share/extensions/tests/test_nicechart.py @@ -0,0 +1,20 @@ +# coding=utf-8 +from nicechart import NiceChart +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy + +class TestNiceChartBasic(ComparisonMixin, TestCase): + effect_class = NiceChart + compare_file = 'svg/default-plain-SVG.svg' + compare_filters = [CompareNumericFuzzy()] + + @property + def comparisons(self): + filename = self.data_file('io/nicechart_01.csv') + filearg = '--file={}'.format(filename) + return ( + (filearg,), + (filearg, '--type=pie'), + (filearg, '--type=pie_abs'), + (filearg, '--type=stbar'), + ) diff --git a/share/extensions/tests/test_output_scour.py b/share/extensions/tests/test_output_scour.py new file mode 100644 index 0000000..52c81d4 --- /dev/null +++ b/share/extensions/tests/test_output_scour.py @@ -0,0 +1,10 @@ +# coding=utf-8 +import os + +from output_scour import ScourInkscape +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + +class ScourBasicTests(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + stderr_protect = False + effect_class = ScourInkscape + comparisons = [()] diff --git a/share/extensions/tests/test_param_curves.py b/share/extensions/tests/test_param_curves.py new file mode 100644 index 0000000..f17d375 --- /dev/null +++ b/share/extensions/tests/test_param_curves.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from param_curves import ParamCurves +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy, CompareWithPathSpace + +class TestParamCurvesBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = ParamCurves + compare_filters = [CompareNumericFuzzy(), CompareWithPathSpace()] diff --git a/share/extensions/tests/test_path_envelope.py b/share/extensions/tests/test_path_envelope.py new file mode 100644 index 0000000..b05c93c --- /dev/null +++ b/share/extensions/tests/test_path_envelope.py @@ -0,0 +1,15 @@ +# coding=utf-8 + +from path_envelope import Envelope +from inkex.tester import ComparisonMixin, TestCase + +class PathEnvelopeTest(ComparisonMixin, TestCase): + """Test envelope similar to perspective""" + effect_class = Envelope + comparisons = [('--id=text', '--id=envelope')] + compare_file = 'svg/perspective.svg' + +class PathEnvelopeGroupTest(ComparisonMixin, TestCase): + effect_class = Envelope + comparisons = [('--id=obj', '--id=envelope')] + compare_file = 'svg/perspective_groups.svg' diff --git a/share/extensions/tests/test_path_mesh.py b/share/extensions/tests/test_path_mesh.py new file mode 100644 index 0000000..af08aac --- /dev/null +++ b/share/extensions/tests/test_path_mesh.py @@ -0,0 +1,25 @@ +# coding=utf-8 + +from path_mesh_m2p import MeshToPath +from path_mesh_p2m import PathToMesh + +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy + +class PathToMeshTest(ComparisonMixin, TestCase): + """Test path to mesh with comparisons""" + effect_class = PathToMesh + comparisons = [('--id=path1', '--id=path9'),] + compare_file = 'svg/mesh.svg' + +class MeshToPathTest(ComparisonMixin, TestCase): + """Test mesh to path with comparisons""" + compare_filters = [CompareNumericFuzzy()] + effect_class = MeshToPath + comparisons = [ + ('--id=mesh1', '--mode=outline'), + ('--id=mesh1', '--mode=gridlines'), + ('--id=mesh1', '--mode=meshpatches'), + ('--id=mesh1', '--mode=faces'), + ] + compare_file = 'svg/mesh.svg' diff --git a/share/extensions/tests/test_path_number_nodes.py b/share/extensions/tests/test_path_number_nodes.py new file mode 100644 index 0000000..e1a4fd1 --- /dev/null +++ b/share/extensions/tests/test_path_number_nodes.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from path_number_nodes import NumberNodes +from inkex.tester import ComparisonMixin, TestCase + +class NumberNodesTest(ComparisonMixin, TestCase): + effect_class = NumberNodes + comparisons = [('--id=p1', '--id=r3')] diff --git a/share/extensions/tests/test_path_to_absolute.py b/share/extensions/tests/test_path_to_absolute.py new file mode 100644 index 0000000..ecb25ae --- /dev/null +++ b/share/extensions/tests/test_path_to_absolute.py @@ -0,0 +1,13 @@ +# coding=utf-8 + +from path_to_absolute import ToAbsolute +from inkex.tester import ComparisonMixin, TestCase + +class PathToAbsoluteTest(ComparisonMixin, TestCase): + """Test converting objects to absolute""" + effect_class = ToAbsolute + comparisons = [ + ('--id=c1', '--id=c2', '--id=c3',), + ('--id=r1', '--id=r2', '--id=r3', '--id=slicerect1'), + ('--id=p1', '--id=p2', '--id=s1', '--id=u1'), + ] diff --git a/share/extensions/tests/test_pathalongpath.py b/share/extensions/tests/test_pathalongpath.py new file mode 100644 index 0000000..af442e8 --- /dev/null +++ b/share/extensions/tests/test_pathalongpath.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from pathalongpath import PathAlongPath +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy, CompareWithPathSpace + +class TestPathAlongPathBasic(ComparisonMixin, TestCase): + compare_filters = [CompareNumericFuzzy(), CompareWithPathSpace()] + comparisons = [('--copymode=Single', '--id=p1', '--id=p2')] + effect_class = PathAlongPath diff --git a/share/extensions/tests/test_pathscatter.py b/share/extensions/tests/test_pathscatter.py new file mode 100644 index 0000000..d745f34 --- /dev/null +++ b/share/extensions/tests/test_pathscatter.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from pathscatter import PathScatter +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareWithoutIds + +class TestPathScatterBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = PathScatter + comparisons = [('--id=p1', '--id=r3'),] + compare_filters = [CompareWithoutIds()] diff --git a/share/extensions/tests/test_pdflatex.py b/share/extensions/tests/test_pdflatex.py new file mode 100644 index 0000000..e3873a5 --- /dev/null +++ b/share/extensions/tests/test_pdflatex.py @@ -0,0 +1,34 @@ +# coding=utf-8 +""" +Test calling pdflatex to convert formule to svg. + +This test uses cached output from the `pdflatex` command because this +test is not a test of pdflatex, but of the extension only. The mocked +output also allows testing in the CI builder without dependancies. + +To re-generate the cached files, run the pytest command: + +NO_MOCK_COMMANDS=1 pytest tests/test_pdflatex.py + +This will use pdflatex, but will also store the output of the call +to `tests/data/cmd/pdflatex/[key].msg.output (and also to `cmd/inkscape/...`) + +The key depends on the comparison arguments, so changing them will invalidate +the file and you must regenerate them. + +Remove the `.output` extension from the above file and commit it to the +repository only AFTER all the tests pass and you are happy with them. + +Clean up any old `.msg` files with invalid or old keys. + +(use EXPORT_COMPARE to generate the output svgs, see inkex.tester docs) +""" +from pdflatex import PdfLatex +from inkex.tester import ComparisonMixin, TestCase + +class PdfLatexTest(ComparisonMixin, TestCase): + compare_file = 'svg/empty.svg' + effect_class = PdfLatex + comparisons = [ + ('--formule=\\(\\displaystyle\\frac{\\pi^2}{6}=\\lim_{n \\to \\infty}\\sum_{k=1}^n \\frac{1}{k^2}\\)', '--packages='), + ] diff --git a/share/extensions/tests/test_perfectboundcover.py b/share/extensions/tests/test_perfectboundcover.py new file mode 100644 index 0000000..9b90b9f --- /dev/null +++ b/share/extensions/tests/test_perfectboundcover.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from perfectboundcover import PerfectBoundCover +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + +class PerfectBoundCoverBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = PerfectBoundCover + comparisons = [()] diff --git a/share/extensions/tests/test_perspective.py b/share/extensions/tests/test_perspective.py new file mode 100644 index 0000000..82c165e --- /dev/null +++ b/share/extensions/tests/test_perspective.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# coding=utf-8 + +from perspective import Perspective +from inkex.tester import ComparisonMixin, TestCase + +class PerspectiveBasicTest(ComparisonMixin, TestCase): + effect_class = Perspective + comparisons = [('--id=text', '--id=envelope')] + compare_file = 'svg/perspective.svg' + +class PerspectiveGroupTest(ComparisonMixin, TestCase): + effect_class = Perspective + comparisons = [('--id=obj', '--id=envelope')] + compare_file = 'svg/perspective_groups.svg' diff --git a/share/extensions/tests/test_pixelsnap.py b/share/extensions/tests/test_pixelsnap.py new file mode 100644 index 0000000..1cb8c91 --- /dev/null +++ b/share/extensions/tests/test_pixelsnap.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from pixelsnap import PixelSnap +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentStyle + +class TestPixelSnapEffectBasic(ComparisonMixin, TestCase): + effect_class = PixelSnap + compare_filters = [CompareOrderIndependentStyle()] + comparisons = [('--id=p1', '--id=r3')] diff --git a/share/extensions/tests/test_plotter.py b/share/extensions/tests/test_plotter.py new file mode 100644 index 0000000..b7abcf0 --- /dev/null +++ b/share/extensions/tests/test_plotter.py @@ -0,0 +1,18 @@ +"""Test Plotter extension""" +from plotter import Plot +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareReplacement + +class TestPlotter(ComparisonMixin, TestCase): + """Test the plotter extension""" + stderr_output = True + effect_class = Plot + compare_filter_save = True + compare_filters = [ + CompareReplacement((';', '\n')) + ] + comparisons = [ + ('--serialPort=[test]',), # HPGL + ('--serialPort=[test]', '--commandLanguage=DMPL'), + ('--serialPort=[test]', '--commandLanguage=KNK'), + ] diff --git a/share/extensions/tests/test_polyhedron_3d.py b/share/extensions/tests/test_polyhedron_3d.py new file mode 100644 index 0000000..44bff3a --- /dev/null +++ b/share/extensions/tests/test_polyhedron_3d.py @@ -0,0 +1,14 @@ +# coding=utf-8 +from polyhedron_3d import Poly3D +from inkex.tester import ComparisonMixin, TestCase + +class Poly3DBasicTest(ComparisonMixin, TestCase): + effect_class = Poly3D + comparisons = [ + ('--show=fce', '--obj=cube', '--r1_ax=x', '--r1_ang=45', '--r2_ax=y', '--r2_ang=45'), + ('--show=fce', '--obj=cube', '--r1_ax=y', '--r1_ang=45', '--z_sort=cent'), + ('--show=fce', '--obj=cube', '--r1_ax=z', '--r1_ang=45', '--z_sort=max'), + ('--show=edg', '--obj=oct', '--r1_ax=z', '--r1_ang=45', '--th=4'), + ('--show=vtx', '--obj=methane',), + ] + compare_file = 'svg/empty.svg' diff --git a/share/extensions/tests/test_prepare_file_save_as.py b/share/extensions/tests/test_prepare_file_save_as.py new file mode 100644 index 0000000..fad46e9 --- /dev/null +++ b/share/extensions/tests/test_prepare_file_save_as.py @@ -0,0 +1,9 @@ +# coding=utf-8 +import pytest + +from prepare_file_save_as import PreProcess +from inkex.tester import ComparisonMixin, TestCase + +class TestPrepareFileSaveBasic(ComparisonMixin, TestCase): + effect_class = PreProcess + comparisons = [()] diff --git a/share/extensions/tests/test_previous_glyph_layer.py b/share/extensions/tests/test_previous_glyph_layer.py new file mode 100644 index 0000000..dafad9e --- /dev/null +++ b/share/extensions/tests/test_previous_glyph_layer.py @@ -0,0 +1,6 @@ +# coding=utf-8 +from previous_glyph_layer import PreviousLayer +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + +class TestPreviousLayerBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = PreviousLayer diff --git a/share/extensions/tests/test_print_win32_vector.py b/share/extensions/tests/test_print_win32_vector.py new file mode 100644 index 0000000..7c3bfc1 --- /dev/null +++ b/share/extensions/tests/test_print_win32_vector.py @@ -0,0 +1,11 @@ +# coding=utf-8 +import sys + +import pytest + +from print_win32_vector import PrintWin32Vector +from inkex.tester import InkscapeExtensionTestMixin, TestCase + +@pytest.mark.skipif(sys.platform != 'win32', reason="Only runs on windows") +class TestPrintWin32VectorBasic(InkscapeExtensionTestMixin, TestCase): + effect_class = PrintWin32Vector diff --git a/share/extensions/tests/test_printing_marks.py b/share/extensions/tests/test_printing_marks.py new file mode 100644 index 0000000..cc4051c --- /dev/null +++ b/share/extensions/tests/test_printing_marks.py @@ -0,0 +1,13 @@ +# coding=utf-8 +from printing_marks import PrintingMarks +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy, CompareWithPathSpace, \ + CompareOrderIndependentStyle + +class PrintingMarksBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = PrintingMarks + compare_filters = [ + CompareNumericFuzzy(), + CompareWithPathSpace(), + CompareOrderIndependentStyle(), + ] diff --git a/share/extensions/tests/test_ps_input.py b/share/extensions/tests/test_ps_input.py new file mode 100644 index 0000000..aabc90a --- /dev/null +++ b/share/extensions/tests/test_ps_input.py @@ -0,0 +1,17 @@ +# coding=utf-8 + +import re + +from ps_input import PostscriptInput + +from inkex.tester.filters import CompareSize +from inkex.tester import ComparisonMixin, TestCase + +class TestPostscriptInput(ComparisonMixin, TestCase): + effect_class = PostscriptInput + compare_filters = [CompareSize()] + compare_file = [ + 'io/test.ps', + 'io/test.eps', + ] + comparisons = [()] diff --git a/share/extensions/tests/test_render_alphabetsoup.py b/share/extensions/tests/test_render_alphabetsoup.py new file mode 100644 index 0000000..b14add5 --- /dev/null +++ b/share/extensions/tests/test_render_alphabetsoup.py @@ -0,0 +1,6 @@ +# coding=utf-8 +from render_alphabetsoup import AlphabetSoup +from inkex.tester import InkscapeExtensionTestMixin, TestCase + +class AlphabetSoupBasicTest(InkscapeExtensionTestMixin, TestCase): + effect_class = AlphabetSoup diff --git a/share/extensions/tests/test_render_barcode.py b/share/extensions/tests/test_render_barcode.py new file mode 100644 index 0000000..f9fabf5 --- /dev/null +++ b/share/extensions/tests/test_render_barcode.py @@ -0,0 +1,100 @@ +# coding=utf-8 +# +# Copyright (C) 2018 Martin Owens +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. +# +""" +Written to test the coding of generating barcodes. +""" +from collections import defaultdict + +from barcode import get_barcode +from render_barcode import Barcode + +from inkex.tester import ComparisonMixin, TestCase + +class BarcodeBasicTest(ComparisonMixin, TestCase): + effect_class = Barcode + comparisons = [ + ('--type', 'Ean2', '--text', '55'), + ('--type', 'Code93', '--text', '3332222'), + ('--type', 'Upce', '--text', '123456'), + ] + +class GetBarcodeTest(TestCase): + """Test each available barcode type""" + data = defaultdict(list) + + @classmethod + def setUpClass(cls): + with open(cls.data_file('batches/barcodes.dat'), 'r') as fhl: + for line in fhl: + (btype, text, code) = line.strip().split(':', 2) + cls.data[btype].append((text, code)) + + def test_render_barcode_ian5(self): + """Barcode IAN5""" + self.barcode_test('Ean5') + + def test_render_barcode_ian8(self): + """Barcode IAN5""" + self.barcode_test('Ean8') + + def test_render_barcode_ian13(self): + """Barcode IAN5""" + self.barcode_test('Ean13') + + def test_render_barcode_upca(self): + """Barcode IAN5""" + self.barcode_test('Upca') + + def test_render_barcode_upce(self): + """Barcode UPCE""" + self.barcode_test('Upce') + + def test_render_barcode_code128(self): + """Barcode Code128""" + self.barcode_test('Code128') + + def test_render_barcode_code25i(self): + """Barcode Code25i""" + self.barcode_test('Code25i') + + def test_render_barcode_code39(self): + """Barcode Code39""" + self.barcode_test('Code39') + + def test_render_barcode_code39ext(self): + """Barcode Code39Ext""" + self.barcode_test('Code39Ext') + + def test_render_barcode_ean2(self): + """Barcode Ean2""" + self.barcode_test('Ean2') + + def test_render_barcode_royal_mail(self): + """Barcode RM4CC/RM4SCC""" + self.barcode_test('Rm4scc') + + def barcode_test(self, name): + """Base module for all barcode testing""" + + assert self.data[name.lower()], "No test data available for {}".format(name) + for datum in self.data[name.lower()]: + (text, code) = datum + coder = get_barcode(name, text=text) + code2 = coder.encode(text) + assert code == code2 diff --git a/share/extensions/tests/test_render_barcode_datamatrix.py b/share/extensions/tests/test_render_barcode_datamatrix.py new file mode 100644 index 0000000..8fb29fb --- /dev/null +++ b/share/extensions/tests/test_render_barcode_datamatrix.py @@ -0,0 +1,13 @@ +# coding=utf-8 +from render_barcode_datamatrix import DataMatrix +from inkex.tester import ComparisonMixin, TestCase + +class TestDataMatrixBasic(ComparisonMixin, TestCase): + effect_class = DataMatrix + compare_file = 'svg/empty.svg' + comparisons = [ + ('--symbol=sq10',), + ('--symbol=sq96', '--text=Sunshine'), + ('--symbol=sq144', '--text=HelloTest'), + ('--symbol=rect8x32', '--text=1234Foo'), + ] diff --git a/share/extensions/tests/test_render_barcode_qrcode.py b/share/extensions/tests/test_render_barcode_qrcode.py new file mode 100644 index 0000000..112b21c --- /dev/null +++ b/share/extensions/tests/test_render_barcode_qrcode.py @@ -0,0 +1,24 @@ +# coding=utf-8 +from render_barcode_qrcode import QrCode +from inkex.tester import ComparisonMixin, TestCase + +class TestQRCodeInkscapeBasic(ComparisonMixin, TestCase): + """Test basic use of QR codes""" + effect_class = QrCode + compare_file = 'svg/empty.svg' + comparisons = [ + ('--text=0123456789', '--typenumber=0'), + ('--text=BreadRolls', '--typenumber=2', '--encoding=utf8'), + ('--text=Blue Front Yard', '--typenumber=3', '--correctionlevel=1'), + ('--text=Waterfall', '--typenumber=1', '--drawtype=circle'), + ('--text=groupid', '--groupid=testid'), + ] + +class TestQRCodeInkscapeSymbol(ComparisonMixin, TestCase): + """Test symbols in qr codes""" + effect_class = QrCode + compare_file = 'svg/symbol.svg' + comparisons = [ + ('--text=ThingOne', '--drawtype=symbol', '--correctionlevel=2', + '--symbolid=AirTransportation_Inv'), + ] diff --git a/share/extensions/tests/test_render_gear_rack.py b/share/extensions/tests/test_render_gear_rack.py new file mode 100644 index 0000000..21cedb0 --- /dev/null +++ b/share/extensions/tests/test_render_gear_rack.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from render_gear_rack import RackGear +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentStyle + +class TestRackGearBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = RackGear + compare_filters = [CompareOrderIndependentStyle()] diff --git a/share/extensions/tests/test_render_gears.py b/share/extensions/tests/test_render_gears.py new file mode 100644 index 0000000..34b529a --- /dev/null +++ b/share/extensions/tests/test_render_gears.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from render_gears import Gears +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentStyle + +class GearsBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = Gears + compare_filters = [CompareOrderIndependentStyle()] diff --git a/share/extensions/tests/test_replace_font.py b/share/extensions/tests/test_replace_font.py new file mode 100644 index 0000000..cbd68c4 --- /dev/null +++ b/share/extensions/tests/test_replace_font.py @@ -0,0 +1,18 @@ +# coding=utf-8 +from replace_font import ReplaceFont +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentStyle + +class TestReplaceFontBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = ReplaceFont + compare_filters = [CompareOrderIndependentStyle()] + comparisons =[( + '--action=find_replace', + '--fr_find=sans-serif', + '--fr_replace=monospace', + )] + +class TestFontList(ComparisonMixin, TestCase): + effect_class = ReplaceFont + comparisons = [('--action=list_only',),] + stderr_output = True diff --git a/share/extensions/tests/test_restack.py b/share/extensions/tests/test_restack.py new file mode 100644 index 0000000..9dfd809 --- /dev/null +++ b/share/extensions/tests/test_restack.py @@ -0,0 +1,10 @@ +# coding=utf-8 +from restack import Restack +from inkex.tester import ComparisonMixin, TestCase + +class RestackBasicTest(ComparisonMixin, TestCase): + effect_class = Restack + comparisons = [ + ('--tab=positional', '--id=p1', '--id=r3'), + ('--tab=z_order', '--id=p1', '--id=r3'), + ] diff --git a/share/extensions/tests/test_rtree.py b/share/extensions/tests/test_rtree.py new file mode 100644 index 0000000..b64d5a4 --- /dev/null +++ b/share/extensions/tests/test_rtree.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from rtree import TurtleRtree +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy + +class RTreeTurtleBasicTest(ComparisonMixin, TestCase): + effect_class = TurtleRtree + comparisons = [()] + compare_filters = [CompareNumericFuzzy(),] diff --git a/share/extensions/tests/test_rubberstretch.py b/share/extensions/tests/test_rubberstretch.py new file mode 100644 index 0000000..590c570 --- /dev/null +++ b/share/extensions/tests/test_rubberstretch.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from rubberstretch import RubberStretch +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + +class TestRubberStretchBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = RubberStretch + comparisons = [('--id=p1', '--id=r3')] diff --git a/share/extensions/tests/test_scribus_pdf.py b/share/extensions/tests/test_scribus_pdf.py new file mode 100644 index 0000000..441da55 --- /dev/null +++ b/share/extensions/tests/test_scribus_pdf.py @@ -0,0 +1,13 @@ +# coding=utf-8 +""" +Unit test file for ../scribus_pdf_export.py +""" + +from scribus_export_pdf import Scribus +from inkex.tester import ComparisonMixin, TestCase + +class ScribusBasicTest(ComparisonMixin, TestCase): + """Test the Scribus PDF file saving functionality""" + effect_class = Scribus + compare_file = 'svg/shapes_cmyk.svg' + comparisons = [()] diff --git a/share/extensions/tests/test_setup_typography_canvas.py b/share/extensions/tests/test_setup_typography_canvas.py new file mode 100644 index 0000000..213b955 --- /dev/null +++ b/share/extensions/tests/test_setup_typography_canvas.py @@ -0,0 +1,6 @@ +# coding=utf-8 +from setup_typography_canvas import SetupTypographyCanvas +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + +class TestSetupTypographyCanvasBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = SetupTypographyCanvas diff --git a/share/extensions/tests/test_spirograph.py b/share/extensions/tests/test_spirograph.py new file mode 100644 index 0000000..6189b75 --- /dev/null +++ b/share/extensions/tests/test_spirograph.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from spirograph import Spirograph +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentStyle + +class SpirographBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = Spirograph + compare_filters = [CompareOrderIndependentStyle()] diff --git a/share/extensions/tests/test_straightseg.py b/share/extensions/tests/test_straightseg.py new file mode 100644 index 0000000..86c6993 --- /dev/null +++ b/share/extensions/tests/test_straightseg.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from straightseg import SegmentStraightener +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy, CompareWithPathSpace + +class SegmentStraightenerBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = SegmentStraightener + compare_filters = [CompareNumericFuzzy(), CompareWithPathSpace()] diff --git a/share/extensions/tests/test_svgcalendar.py b/share/extensions/tests/test_svgcalendar.py new file mode 100644 index 0000000..e0c7d8b --- /dev/null +++ b/share/extensions/tests/test_svgcalendar.py @@ -0,0 +1,99 @@ +# coding=utf-8 +""" +All tests for the svg calendar extension +""" +import calendar +import datetime + +from svgcalendar import Calendar +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentStyle, CompareNumericFuzzy +from inkex.tester.mock import MockMixin + +class FrozenDateTime(datetime.datetime): + @classmethod + def today(cls): + return cls(2019, 11, 5) + +class CalendarArguments(ComparisonMixin, TestCase): + """Test arguments to calendar extensions""" + effect_class = Calendar + compare_filters = [CompareOrderIndependentStyle(), CompareNumericFuzzy()] + comparisons = [()] + mocks = [ + (datetime, 'datetime', FrozenDateTime) + ] + + def test_default_names_list(self): + """Test default names""" + effect = self.assertEffect() + self.assertEqual(effect.options.month_names[0], 'January') + self.assertEqual(effect.options.month_names[11], 'December') + self.assertEqual(effect.options.day_names[0], 'Sun') + self.assertEqual(effect.options.day_names[6], 'Sat') + self.assertEqual(effect.options.year, datetime.datetime.today().year) + self.assertEqual(calendar.firstweekday(), 6) + + def test_modifyed_names_list(self): + """Test modified names list""" + effect = self.assertEffect(args=[ + '--month-names=JAN FEV MAR ABR MAI JUN JUL AGO SET OUT NOV DEZ', + '--day-names=DOM SEG TER QUA QUI SEX SAB', + ]) + self.assertEqual(effect.options.month_names[0], 'JAN') + self.assertEqual(effect.options.month_names[11], 'DEZ') + self.assertEqual(effect.options.day_names[0], 'DOM') + self.assertEqual(effect.options.day_names[6], 'SAB') + + def test_starting_names_list(self): + """Starting or ending spaces must not affect names""" + effect = self.assertEffect(args=[ + '--month-names= JAN FEV MAR ABR MAI JUN JUL AGO SET OUT NOV DEZ ', + '--day-names= DOM SEG TER QUA QUI SEX SAB ', + ]) + self.assertEqual(effect.options.month_names[0], 'JAN') + self.assertEqual(effect.options.month_names[11], 'DEZ') + self.assertEqual(effect.options.day_names[0], 'DOM') + self.assertEqual(effect.options.day_names[6], 'SAB') + + def test_inner_extra_spaces(self): + """Extra spaces must not affect names""" + effect = self.assertEffect(args=[ + '--month-names=JAN FEV MAR ABR MAI JUN JUL AGO SET OUT NOV DEZ', + '--day-names=DOM SEG TER QUA QUI SEX SAB', + ]) + self.assertEqual(effect.options.month_names[0], 'JAN') + self.assertEqual(effect.options.month_names[2], 'MAR') + self.assertEqual(effect.options.month_names[11], 'DEZ') + self.assertEqual(effect.options.day_names[0], 'DOM') + self.assertEqual(effect.options.day_names[2], 'TER') + self.assertEqual(effect.options.day_names[6], 'SAB') + + def test_converted_year_zero(self): + """Year equal to 0 is converted to correct year""" + effect = self.assertEffect(args=['--year=0']) + self.assertEqual(effect.options.year, datetime.datetime.today().year) + + def test_converted_year_thousand(self): + """Year equal to 2000 configuration""" + effect = self.assertEffect(args=['--year=2000']) + self.assertEqual(effect.options.year, 2000) + + def test_configuring_week_start_sun(self): + """Week start is set to Sunday""" + self.assertEffect(args=['--start-day=sun']) + self.assertEqual(calendar.firstweekday(), 6) + + def test_configuring_week_start_mon(self): + """Week start is set to Monday""" + self.assertEffect(args=['--start-day=mon']) + self.assertEqual(calendar.firstweekday(), 0) + + def test_recognize_a_weekend(self): + """Recognise a weekend""" + effect = self.assertEffect(args=[ + '--start-day=sun', '--weekend=sat+sun', + ]) + self.assertTrue(effect.is_weekend(0), 'Sunday is weekend in this configuration') + self.assertTrue(effect.is_weekend(6), 'Saturday is weekend in this configuration') + self.assertFalse(effect.is_weekend(1), 'Monday is NOT weekend') diff --git a/share/extensions/tests/test_svgfont2layers.py b/share/extensions/tests/test_svgfont2layers.py new file mode 100644 index 0000000..743f65e --- /dev/null +++ b/share/extensions/tests/test_svgfont2layers.py @@ -0,0 +1,10 @@ +# coding=utf-8 +from svgfont2layers import SvgFontToLayers +from inkex.tester import ComparisonMixin, TestCase + +class TestSVGFont2LayersBasic(ComparisonMixin, TestCase): + effect_class = SvgFontToLayers + compare_file = 'svg/font.svg' + comparisons = [ + ('--count=3',) + ] diff --git a/share/extensions/tests/test_synfig_fileformat.py b/share/extensions/tests/test_synfig_fileformat.py new file mode 100644 index 0000000..4cc4d21 --- /dev/null +++ b/share/extensions/tests/test_synfig_fileformat.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from synfig_fileformat import defaultLayerVersion + + +class TestSynFigDefaultLayerVersion(object): + def test_layer_version(self): + assert defaultLayerVersion("outline") == "0.2" diff --git a/share/extensions/tests/test_synfig_output.py b/share/extensions/tests/test_synfig_output.py new file mode 100644 index 0000000..9cbdaf5 --- /dev/null +++ b/share/extensions/tests/test_synfig_output.py @@ -0,0 +1,6 @@ +# coding=utf-8 +from synfig_output import SynfigExport +from inkex.tester import InkscapeExtensionTestMixin, TestCase + +class TestSynfigExportBasic(InkscapeExtensionTestMixin, TestCase): + effect_class = SynfigExport diff --git a/share/extensions/tests/test_synfig_prepare.py b/share/extensions/tests/test_synfig_prepare.py new file mode 100644 index 0000000..5dacf91 --- /dev/null +++ b/share/extensions/tests/test_synfig_prepare.py @@ -0,0 +1,6 @@ +# coding=utf-8 +from synfig_prepare import SynfigPrep +from inkex.tester import InkscapeExtensionTestMixin, TestCase + +class TestSynfigPrepBasic(InkscapeExtensionTestMixin, TestCase): + effect_class = SynfigPrep diff --git a/share/extensions/tests/test_tar_layers.py b/share/extensions/tests/test_tar_layers.py new file mode 100644 index 0000000..f334380 --- /dev/null +++ b/share/extensions/tests/test_tar_layers.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from tar_layers import TarLayers +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareSize + +class LayersOutputBasicTest(ComparisonMixin, TestCase): + effect_class = TarLayers + compare_filters = [CompareSize()] diff --git a/share/extensions/tests/test_template.py b/share/extensions/tests/test_template.py new file mode 100644 index 0000000..bb73205 --- /dev/null +++ b/share/extensions/tests/test_template.py @@ -0,0 +1,13 @@ +# coding=utf-8 +from template import InxDefinedTemplate +from inkex.tester import ComparisonMixin, TestCase + +class TemplateTestCase(ComparisonMixin, TestCase): + effect_class = InxDefinedTemplate + compare_file = 'svg/empty.svg' + comparisons = [ + ('--size=custom', '--width=100', '--height=100', '--unit=in'), + ('--size=100x50', '--grid=true', '--orientation=horizontal'), + ('--size=100x50', '--grid=true', '--orientation=vertical'), + ('--size=5mmx15mm', '--background=black', '--noborder=true'), + ] diff --git a/share/extensions/tests/test_template_dvd_cover.py b/share/extensions/tests/test_template_dvd_cover.py new file mode 100644 index 0000000..4641b5a --- /dev/null +++ b/share/extensions/tests/test_template_dvd_cover.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from template_dvd_cover import DvdCover +from inkex.tester import ComparisonMixin, TestCase + +class TestDvdCoverBasic(ComparisonMixin, TestCase): + effect_class = DvdCover + compare_file = 'svg/empty.svg' + comparisons = [('-s', '10', '-b', '10')] diff --git a/share/extensions/tests/test_template_seamless_pattern.py b/share/extensions/tests/test_template_seamless_pattern.py new file mode 100644 index 0000000..4701939 --- /dev/null +++ b/share/extensions/tests/test_template_seamless_pattern.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from template_seamless_pattern import SeamlessPattern +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy + +class SeamlessPatternBasicTest(ComparisonMixin, TestCase): + effect_class = SeamlessPattern + compare_filters = [CompareNumericFuzzy()] + comparisons = [('--width=100', '--height=100')] diff --git a/share/extensions/tests/test_text_braille.py b/share/extensions/tests/test_text_braille.py new file mode 100644 index 0000000..c483c41 --- /dev/null +++ b/share/extensions/tests/test_text_braille.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from text_braille import Braille +from inkex.tester import ComparisonMixin, TestCase + +class TestBrailleBasic(ComparisonMixin, TestCase): + effect_class = Braille + python3_only = True + comparisons = [()] diff --git a/share/extensions/tests/test_text_extract.py b/share/extensions/tests/test_text_extract.py new file mode 100644 index 0000000..28a2e4a --- /dev/null +++ b/share/extensions/tests/test_text_extract.py @@ -0,0 +1,13 @@ +# coding=utf-8 +from inkex.tester import ComparisonMixin, TestCase +from text_extract import Extract + +class TestExtractBasic(ComparisonMixin, TestCase): + effect_class = Extract + stderr_output = True + comparisons = [ + ('--direction=tb',), + ('--direction=bt',), + ('--direction=lr',), + ('--direction=rl',), + ] diff --git a/share/extensions/tests/test_text_flipcase.py b/share/extensions/tests/test_text_flipcase.py new file mode 100644 index 0000000..97a89ec --- /dev/null +++ b/share/extensions/tests/test_text_flipcase.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from text_flipcase import FlipCase +from inkex.tester import ComparisonMixin, TestCase + +class TestFlipCaseBasic(ComparisonMixin, TestCase): + effect_class = FlipCase + comparisons = [()] diff --git a/share/extensions/tests/test_text_lowercase.py b/share/extensions/tests/test_text_lowercase.py new file mode 100644 index 0000000..9f49712 --- /dev/null +++ b/share/extensions/tests/test_text_lowercase.py @@ -0,0 +1,56 @@ +# coding=utf-8 +"""Test the lowercase effect""" +import string + +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.word import word_generator +from text_lowercase import Lowercase + +class LowerCase(ComparisonMixin, TestCase): + effect_class = Lowercase + comparisons = [()] + + def test_uppercase(self): + var = word_generator(15) + var_new = var.upper() + self.assertEqual(self.effect.process_chardata(var_new), var.lower()) + + def test_lowercase(self): + var = word_generator(15) + var_new = var.lower() + + self.assertEqual(self.effect.process_chardata(var_new), var.lower()) + + def test_titlecase(self): + var = word_generator(5) + var1 = word_generator(8) + var2 = word_generator(7) + word = var + " " + var1 + " " + var2 + + word_new = word.title() + + self.assertEqual(self.effect.process_chardata(word_new), word_new.lower()) + + def test_sentencecase(self): + var = word_generator(5) + var1 = word_generator(8) + var2 = word_generator(7) + word = var + " " + var1 + " " + var2 + + word_new = word[0].upper() + word[1:] + + self.assertEqual(self.effect.process_chardata(word_new), word_new.lower()) + + def test_numbers_before(self): + var = word_generator(15) + var_upper = var.upper() + var_new = var_upper.zfill(20) + + self.assertEqual(self.effect.process_chardata(var_new), var_new.lower()) + + def test_punctuation_before(self): + var = word_generator(15) + var_upper = var.upper() + var_new = string.punctuation + var_upper + + self.assertEqual(self.effect.process_chardata(var_new), var_new.lower()) diff --git a/share/extensions/tests/test_text_merge.py b/share/extensions/tests/test_text_merge.py new file mode 100644 index 0000000..4dd35e4 --- /dev/null +++ b/share/extensions/tests/test_text_merge.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +from text_merge import Merge +from inkex.tester import ComparisonMixin, TestCase + +class TestMergeBasic(ComparisonMixin, TestCase): + effect_class = Merge + comparisons = [()] diff --git a/share/extensions/tests/test_text_randomcase.py b/share/extensions/tests/test_text_randomcase.py new file mode 100644 index 0000000..3ce8f18 --- /dev/null +++ b/share/extensions/tests/test_text_randomcase.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from inkex.tester import ComparisonMixin, TestCase +from text_randomcase import RandomCase + +class TestRandomCaseBasic(ComparisonMixin, TestCase): + effect_class = RandomCase + comparisons = [()] diff --git a/share/extensions/tests/test_text_sentencecase.py b/share/extensions/tests/test_text_sentencecase.py new file mode 100644 index 0000000..c0f773b --- /dev/null +++ b/share/extensions/tests/test_text_sentencecase.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from inkex.tester import ComparisonMixin, TestCase +from text_sentencecase import SentenceCase + +class TestSentenceCaseBasic(ComparisonMixin, TestCase): + effect_class = SentenceCase + comparisons = [()] diff --git a/share/extensions/tests/test_text_split.py b/share/extensions/tests/test_text_split.py new file mode 100644 index 0000000..98e6311 --- /dev/null +++ b/share/extensions/tests/test_text_split.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from text_split import TextSplit +from inkex.tester import ComparisonMixin, TestCase + +class TestSplitBasic(ComparisonMixin, TestCase): + """Test split effect""" + effect_class = TextSplit + comparisons = [('--id=t1', '--id=t3')] diff --git a/share/extensions/tests/test_text_titlecase.py b/share/extensions/tests/test_text_titlecase.py new file mode 100644 index 0000000..0ad061f --- /dev/null +++ b/share/extensions/tests/test_text_titlecase.py @@ -0,0 +1,67 @@ +# coding=utf-8 +""" +Test titlecase extension +""" + +import string + +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.word import sentencecase, word_generator +from text_titlecase import TitleCase + +class TitleCaseTest(ComparisonMixin, TestCase): + effect_class = TitleCase + comparisons = [()] + + def test_lowercase(self): + var = word_generator(6) + var1 = word_generator(9) + var2 = word_generator(10) + words = var.lower() + " " + var1.lower() + " " + var2.lower() + titlecase = self.effect.process_chardata(words) + self.assertEqual(self.effect.process_chardata(words), titlecase) + + def test_uppercase(self): + var = word_generator(6) + var1 = word_generator(9) + var2 = word_generator(10) + words = var.upper() + " " + var1.upper() + " " + var2.upper() + titlecase = self.effect.process_chardata(words) + self.assertEqual(self.effect.process_chardata(words), titlecase) + + def test_sentencecase(self): + var = word_generator(5) + var1 = word_generator(8) + var2 = word_generator(7) + words = var + " " + var1 + " " + var2 + word_new = sentencecase(words) + titlecase = self.effect.process_chardata(word_new) + self.assertEqual(self.effect.process_chardata(word_new), titlecase) + + def test_numbers_before(self): + words = word_generator(15) + word_new = words.zfill(20) + titlecase = self.effect.process_chardata(word_new) + self.assertEqual(self.effect.process_chardata(word_new), titlecase) + + def test_punctuation_before(self): + words = word_generator(15) + word_new = string.punctuation + words + titlecase = self.effect.process_chardata(word_new) + self.assertEqual(self.effect.process_chardata(word_new), titlecase) + + def test_check_strings(self): + titlecase_strings = [("i love inkscape", "I Love Inkscape"), + ("i LOVE inkscape", "I Love Inkscape"), + ("I love Inkscape", "I Love Inkscape"), + ("I LOVE INKSCAPE", "I Love Inkscape"), + ("ThIs Is VeRy AwEsOmE", "This Is Very Awesome"), + ("!$this is Very awesome.", "!$This Is Very Awesome."), + ("this *is @very ^awesome.", "This *Is @Very ^Awesome."), + ("there is a space.", "There Is A Space."), + ("9these 5are 7numbers", "9These 5Are 7Numbers"), + ("thisworddidnotend", "Thisworddidnotend"), + ("This Should Not Change", "This Should Not Change")] + + for item in titlecase_strings: + self.assertEqual(self.effect.process_chardata(item[0]), item[1]) diff --git a/share/extensions/tests/test_text_uppercase.py b/share/extensions/tests/test_text_uppercase.py new file mode 100644 index 0000000..d77e090 --- /dev/null +++ b/share/extensions/tests/test_text_uppercase.py @@ -0,0 +1,44 @@ +# coding=utf-8 +"""Test string uppercase extension""" +import string + +from text_uppercase import Uppercase +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.word import word_generator + +class UpperCase(ComparisonMixin, TestCase): + effect_class = Uppercase + comparisons = [()] + + def test_lowercase(self): + var = word_generator(15) + var_new = var.lower() + self.assertEqual(self.effect.process_chardata(var_new), var.upper()) + + def test_titlecase(self): + var = word_generator(5) + var1 = word_generator(8) + var2 = word_generator(7) + word = var + " " + var1 + " " + var2 + + word_new = word.title() + self.assertEqual(self.effect.process_chardata(word_new), word_new.upper()) + + def test_sentencecase(self): + var = word_generator(5) + var1 = word_generator(8) + var2 = word_generator(7) + word = var + " " + var1 + " " + var2 + + word_new = word[0].upper() + word[1:] + self.assertEqual(self.effect.process_chardata(word_new), word_new.upper()) + + def test_numbers_before(self): + var = word_generator(15) + var_new = var.zfill(20) + self.assertEqual(self.effect.process_chardata(var_new), var_new.upper()) + + def test_punctuation_before(self): + var = word_generator(15) + var_new = string.punctuation + var + self.assertEqual(self.effect.process_chardata(var_new), var_new.upper()) diff --git a/share/extensions/tests/test_triangle.py b/share/extensions/tests/test_triangle.py new file mode 100644 index 0000000..0c9232e --- /dev/null +++ b/share/extensions/tests/test_triangle.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +from triangle import Triangle +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy, CompareOrderIndependentStyle + +class TriangleBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = Triangle + compare_filters = [CompareNumericFuzzy(), CompareOrderIndependentStyle()] diff --git a/share/extensions/tests/test_ungroup_deep.py b/share/extensions/tests/test_ungroup_deep.py new file mode 100644 index 0000000..9d23097 --- /dev/null +++ b/share/extensions/tests/test_ungroup_deep.py @@ -0,0 +1,12 @@ +# coding=utf-8 +from ungroup_deep import UngroupDeep +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentStyle + +class TestUngroupBasic(ComparisonMixin, TestCase): + effect_class = UngroupDeep + compare_filters = [CompareOrderIndependentStyle()] + comparisons = [ + (), + ('--id=layer2',) + ] diff --git a/share/extensions/tests/test_voronoi.py b/share/extensions/tests/test_voronoi.py new file mode 100644 index 0000000..6f15fca --- /dev/null +++ b/share/extensions/tests/test_voronoi.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from voronoi import Site + + +class TestVoronoiSiteBasic(object): + def test_site_basic(self): + new_site = Site(1, 2) + assert new_site.x == 1 + assert new_site.y == 2 diff --git a/share/extensions/tests/test_voronoi2svg.py b/share/extensions/tests/test_voronoi2svg.py new file mode 100644 index 0000000..901f6cc --- /dev/null +++ b/share/extensions/tests/test_voronoi2svg.py @@ -0,0 +1,9 @@ +# coding=utf-8 +from voronoi2svg import Voronoi +from inkex.tester import ComparisonMixin, TestCase +from inkex.tester.filters import CompareOrderIndependentStyle + +class TestVoronoi2svgBasic(ComparisonMixin, TestCase): + effect_class = Voronoi + compare_filters = [CompareOrderIndependentStyle()] + comparisons = [('--id=p1', '--id=r3')] diff --git a/share/extensions/tests/test_web_interactive_mockup.py b/share/extensions/tests/test_web_interactive_mockup.py new file mode 100644 index 0000000..c6de0d4 --- /dev/null +++ b/share/extensions/tests/test_web_interactive_mockup.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from web_interactive_mockup import InteractiveMockup +from inkex.tester import ComparisonMixin, TestCase + +class TestInkWebInteractiveMockupBasic(ComparisonMixin, TestCase): + effect_class = InteractiveMockup + comparisons = [('--id=p1', '--id=r3')] diff --git a/share/extensions/tests/test_web_set_att.py b/share/extensions/tests/test_web_set_att.py new file mode 100644 index 0000000..2b1e376 --- /dev/null +++ b/share/extensions/tests/test_web_set_att.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from web_set_att import SetAttribute +from inkex.tester import ComparisonMixin, TestCase + +class SetAttributeBasic(ComparisonMixin, TestCase): + effect_class = SetAttribute + comparisons = [('--id=p1', '--id=r3')] diff --git a/share/extensions/tests/test_web_transmit_att.py b/share/extensions/tests/test_web_transmit_att.py new file mode 100644 index 0000000..690afae --- /dev/null +++ b/share/extensions/tests/test_web_transmit_att.py @@ -0,0 +1,7 @@ +# coding=utf-8 +from web_transmit_att import TransmitAttribute +from inkex.tester import ComparisonMixin, TestCase + +class TestInkWebTransmitAttBasic(ComparisonMixin, TestCase): + effect_class = TransmitAttribute + comparisons = [('--id=p1', '--id=r3')] diff --git a/share/extensions/tests/test_webslicer_create_group.py b/share/extensions/tests/test_webslicer_create_group.py new file mode 100644 index 0000000..11b27a0 --- /dev/null +++ b/share/extensions/tests/test_webslicer_create_group.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +from webslicer_create_group import CreateGroup +from inkex.tester import ComparisonMixin, TestCase + +class TestWebSlicerCreateGroupBasic(ComparisonMixin, TestCase): + effect_class = CreateGroup + comparisons = [('--id', 'slicerect1')] diff --git a/share/extensions/tests/test_webslicer_create_rect.py b/share/extensions/tests/test_webslicer_create_rect.py new file mode 100644 index 0000000..979d486 --- /dev/null +++ b/share/extensions/tests/test_webslicer_create_rect.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +from webslicer_create_rect import CreateRect +from inkex.tester import ComparisonMixin, TestCase + +class TestWebSlicerCreateRectBasic(ComparisonMixin, TestCase): + effect_class = CreateRect diff --git a/share/extensions/tests/test_webslicer_export.py b/share/extensions/tests/test_webslicer_export.py new file mode 100644 index 0000000..1d22ad3 --- /dev/null +++ b/share/extensions/tests/test_webslicer_export.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +from webslicer_export import Export +from inkex.tester import ComparisonMixin, TestCase + +class TestWebSlicerExportBasic(ComparisonMixin, TestCase): + stderr_protect = False # Cover lack of ImageMagic in CI builder + effect_class = Export + + @property + def comparisons(self): + return [('--dir', self.tempdir)] diff --git a/share/extensions/tests/test_whirl.py b/share/extensions/tests/test_whirl.py new file mode 100644 index 0000000..3a72dbf --- /dev/null +++ b/share/extensions/tests/test_whirl.py @@ -0,0 +1,10 @@ +# coding=utf-8 +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy, CompareWithPathSpace + +from whirl import Whirl + +class WhirlBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = Whirl + compare_filters = [CompareNumericFuzzy(), CompareWithPathSpace()] + comparisons = [('--id=p1', '--id=r3')] diff --git a/share/extensions/tests/test_wireframe_sphere.py b/share/extensions/tests/test_wireframe_sphere.py new file mode 100644 index 0000000..428d504 --- /dev/null +++ b/share/extensions/tests/test_wireframe_sphere.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from wireframe_sphere import WireframeSphere +from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase +from inkex.tester.filters import CompareNumericFuzzy, CompareOrderIndependentStyle + +class TestWireframeSphereBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + effect_class = WireframeSphere + compare_filters = [CompareNumericFuzzy(), CompareOrderIndependentStyle()] diff --git a/share/extensions/text_braille.inx b/share/extensions/text_braille.inx new file mode 100644 index 0000000..d302bde --- /dev/null +++ b/share/extensions/text_braille.inx @@ -0,0 +1,14 @@ + + + Convert to Braille + org.inkscape.text.braille + + all + + + + + + diff --git a/share/extensions/text_braille.py b/share/extensions/text_braille.py new file mode 100755 index 0000000..67d5b10 --- /dev/null +++ b/share/extensions/text_braille.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Convert the text to braille text""" + +import inkex + +# https://en.wikipedia.org/wiki/Braille_ASCII#Braille_ASCII_values +U2801_MAP = "A1B'K2L@CIF/MSP\"E3H9O6R^DJG>NTQ,*5<-U8V.%[$+X!&;:4\\0Z7(_?W]#Y)=" + +class Braille(inkex.TextExtension): + """Convert to ASCII Braille""" + @staticmethod + def map_char(char): + """Map a single letter to braille""" + assert isinstance(char, str) + try: + mapint = U2801_MAP.index(char.upper()) + except ValueError: + return char + return chr(mapint + 0x2801) + +if __name__ == '__main__': + Braille().run() diff --git a/share/extensions/text_extract.inx b/share/extensions/text_extract.inx new file mode 100644 index 0000000..05dddeb --- /dev/null +++ b/share/extensions/text_extract.inx @@ -0,0 +1,30 @@ + + + Extract + org.inkscape.text.extract + + + + + + + + + + + + + + + + + + all + + + + + + diff --git a/share/extensions/text_extract.py b/share/extensions/text_extract.py new file mode 100755 index 0000000..4544593 --- /dev/null +++ b/share/extensions/text_extract.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2011 Nicolas Dufour (jazzynico) +# Direction code from the Restack extension, by Rob Antonishen +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +""" +Extract text and print it to the error console. +""" + +from lxml.etree import tostring + +import inkex +from inkex import TextElement, FlowRoot +from inkex.utils import KeyDict + +# Old settings, supported because users click 'ok' without looking. +XAN = KeyDict({'l': 'left', 'r': 'right', 'm': 'center_x'}) +YAN = KeyDict({'t': 'top', 'b': 'bottom', 'm': 'center_y'}) + +class Extract(inkex.EffectExtension): + """Extract text and print out""" + select_all = (TextElement, FlowRoot) + + def add_arguments(self, pars): + pars.add_argument("-d", "--direction", default="tb", help="direction to extract text") + pars.add_argument("-x", "--xanchor", default="center_x", help="horiz point to compare") + pars.add_argument("-y", "--yanchor", default="center_y", help="vertical point to compare") + + def effect(self): + # move them to the top of the object stack in this order. + for node in sorted(self.svg.selection.get(TextElement, FlowRoot).values(), key=self._sort): + self.recurse(node) + + def _sort(self, node): + return node.bounding_box().get_anchor( + self.options.xanchor, self.options.yanchor, self.options.direction) + + def recurse(self, node): + """Go through each node and recusively self call for all children""" + if node.text is not None or node.tail is not None: + for child in node: + if child.get('sodipodi:role'): + child.tail = "\n" + inkex.errormsg(tostring(node, encoding='unicode', method='text').strip()) + else: + for child in node: + self.recurse(child) + + +if __name__ == '__main__': + Extract().run() diff --git a/share/extensions/text_flipcase.inx b/share/extensions/text_flipcase.inx new file mode 100644 index 0000000..40e59db --- /dev/null +++ b/share/extensions/text_flipcase.inx @@ -0,0 +1,16 @@ + + + fLIP cASE + org.inkscape.text.flip_case + + all + + + + + + + + diff --git a/share/extensions/text_flipcase.py b/share/extensions/text_flipcase.py new file mode 100755 index 0000000..13043fd --- /dev/null +++ b/share/extensions/text_flipcase.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +"""Flip upper to lower and lower to upper cases""" + +import inkex + +class FlipCase(inkex.TextExtension): + """Change the case, cHANGE THE CASE""" + @staticmethod + def map_char(char): + return char.upper() if char.islower() else char.lower() + +if __name__ == '__main__': + FlipCase().run() diff --git a/share/extensions/text_lowercase.inx b/share/extensions/text_lowercase.inx new file mode 100644 index 0000000..68d27d9 --- /dev/null +++ b/share/extensions/text_lowercase.inx @@ -0,0 +1,16 @@ + + + lowercase + org.inkscape.text.lowercase + + all + + + + + + + + diff --git a/share/extensions/text_lowercase.py b/share/extensions/text_lowercase.py new file mode 100755 index 0000000..a213c02 --- /dev/null +++ b/share/extensions/text_lowercase.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +"""Make text lower case""" + +import inkex + +class Lowercase(inkex.TextExtension): + """Convert to lowercase""" + def process_chardata(self, text): + return text.lower() + +if __name__ == '__main__': + Lowercase().run() diff --git a/share/extensions/text_merge.inx b/share/extensions/text_merge.inx new file mode 100644 index 0000000..567d30f --- /dev/null +++ b/share/extensions/text_merge.inx @@ -0,0 +1,32 @@ + + + Merge + org.inkscape.text.merge + + + + + + + + + + + + + + + + + false + true + + all + + + + + + diff --git a/share/extensions/text_merge.py b/share/extensions/text_merge.py new file mode 100755 index 0000000..5f09b59 --- /dev/null +++ b/share/extensions/text_merge.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2013 Nicolas Dufour (jazzynico) +# Direction code from the Restack extension, by Rob Antonishen +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +""" +Merge text blocks together. +""" + +import inkex +from inkex.utils import KeyDict +from inkex import ( + Rectangle, FlowRoot, FlowPara, FlowRegion, TextElement, Tspan +) + +# Old settings, supported because users click 'ok' without looking. +XAN = KeyDict({'l': 'left', 'r': 'right', 'm': 'center_x'}) +YAN = KeyDict({'t': 'top', 'b': 'bottom', 'm': 'center_y'}) + +class Merge(inkex.EffectExtension): + """Merge text blocks together""" + def add_arguments(self, pars): + pars.add_argument("-d", "--direction", default="tb", help="direction to merge text") + pars.add_argument("-x", "--xanchor", default="center_x", help="horiz point to compare") + pars.add_argument("-y", "--yanchor", default="center_y", help="vertical point to compare") + pars.add_argument("-k", "--keepstyle", type=inkex.Boolean, help="keep format") + pars.add_argument("-t", "--flowtext", type=inkex.Boolean,\ + help="use a flow text structure instead of a normal text element") + + def effect(self): + if not self.svg.selected: + for node in self.svg.xpath('//svg:text | //svg:flowRoot'): + self.svg.selected[node.get('id')] = node + + if not self.svg.selected: + return + + parentnode = self.svg.get_current_layer() + + if self.options.flowtext: + text_element = FlowRoot + text_span = FlowPara + else: + text_element = TextElement + text_span = Tspan + + text_root = parentnode.add(text_element()) + text_root.set('xml:space', 'preserve') + text_root.style = { + 'font-size': '20px', + 'font-style': 'normal', + 'font-weight': 'normal', + 'line-height': '125%', + 'letter-spacing': '0px', + 'word-spacing': '0px', + 'fill': '#000000', + 'fill-opacity': 1, + 'stroke': 'none' + } + + for node in sorted(self.svg.selected.values(), key=self._sort): + self.recurse(text_span, node, text_root) + + if self.options.flowtext: + region = text_root.add(FlowRegion()) + region.set('xml:space', 'preserve') + rect = region.add(Rectangle()) + rect.set('xml:space', 'preserve') + rect.set('height', 200) + rect.set('width', 200) + + def _sort(self, node): + return node.bounding_box().get_anchor( + self.options.xanchor, self.options.yanchor, self.options.direction) + + def recurse(self, text_span, node, span): + """Recursively go through each node self calling on child nodes""" + if not isinstance(node, FlowRegion): + + newspan = span.add(text_span()) + newspan.set('xml:space', 'preserve') + + newspan.set('sodipodi:role', node.get('sodipodi:role')) + if isinstance(node, (TextElement, FlowPara)): + newspan.set('sodipodi:role', 'line') + + if self.options.keepstyle: + newspan.style = node.style + + if node.text is not None: + newspan.text = node.text + for child in node: + self.recurse(text_span, child, newspan) + if node.tail and not isinstance(node, TextElement): + newspan.tail = node.tail + + +if __name__ == '__main__': + Merge().run() diff --git a/share/extensions/text_randomcase.inx b/share/extensions/text_randomcase.inx new file mode 100644 index 0000000..d0c6a20 --- /dev/null +++ b/share/extensions/text_randomcase.inx @@ -0,0 +1,16 @@ + + + rANdOm CasE + org.inkscape.text.random_case + + all + + + + + + + + diff --git a/share/extensions/text_randomcase.py b/share/extensions/text_randomcase.py new file mode 100755 index 0000000..208cade --- /dev/null +++ b/share/extensions/text_randomcase.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +"""Randomise the case of the letters.""" + +import random +import inkex + +class RandomCase(inkex.TextExtension): + """Randomise the case of the text (with bias)""" + previous_case = 1 + + def map_char(self, char): + # bias the randomness towards inversion of the previous case: + # We use this weird way to get from a random set because + # python2 and python3 have different ways of seeding + if self.previous_case > 0: + case = [-2, -1, 1][int(random.random() * 3)] + else: + case = [-1, 1, 2][int(random.random() * 3)] + + if char.isalpha(): + self.previous_case = case + if case > 0: + return char.upper() + elif case < 0: + return char.lower() + return char + +if __name__ == '__main__': + RandomCase().run() diff --git a/share/extensions/text_sentencecase.inx b/share/extensions/text_sentencecase.inx new file mode 100644 index 0000000..8d26ab1 --- /dev/null +++ b/share/extensions/text_sentencecase.inx @@ -0,0 +1,16 @@ + + + Sentence case + org.inkscape.text.sentence_case + + all + + + + + + + + diff --git a/share/extensions/text_sentencecase.py b/share/extensions/text_sentencecase.py new file mode 100755 index 0000000..e63430d --- /dev/null +++ b/share/extensions/text_sentencecase.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +"""Convert to sentence case""" + +import inkex + +class SentenceCase(inkex.TextExtension): + """Convert text to sentence case""" + sentence_start = True + was_punctuation = False + + def map_char(self, char): + """Turn the char into a sentence using class state""" + if char in '.!?': + self.was_punctuation = True + elif ((char.isspace() or self.newline) and self.was_punctuation) or self.newpar: + self.sentence_start = True + self.was_punctuation = False + elif char in '")': + pass + else: + self.was_punctuation = False + + if not char.isspace(): + self.newline = False + self.newpar = False + + if self.sentence_start and char.isalpha(): + self.sentence_start = False + return char.upper() + elif not self.sentence_start and char.isalpha(): + return char.lower() + return char + +if __name__ == '__main__': + SentenceCase().run() diff --git a/share/extensions/text_split.inx b/share/extensions/text_split.inx new file mode 100644 index 0000000..35c3cba --- /dev/null +++ b/share/extensions/text_split.inx @@ -0,0 +1,27 @@ + + + Split text + com.nerdson.text_split + + + + + + + + true + + + + + + + text + + + + + + diff --git a/share/extensions/text_split.py b/share/extensions/text_split.py new file mode 100755 index 0000000..069c797 --- /dev/null +++ b/share/extensions/text_split.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2009 Karlisson Bezerra, contato@nerdson.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import inkex +from inkex import ( + TextElement, FlowRoot, FlowPara, Tspan, TextPath, Rectangle +) + +class TextSplit(inkex.EffectExtension): + """Split text up.""" + def add_arguments(self, pars): + pars.add_argument("--tab", help="The selected UI-tab when OK was pressed") + pars.add_argument("-s", "--splittype", default="word", help="type of split") + pars.add_argument("-p", "--preserve", type=inkex.Boolean, default=True,\ + help="Preserve original") + + def split_lines(self, node): + """Returns a list of lines""" + lines = [] + count = 1 + + for elem in node: + if isinstance(elem, TextPath): + inkex.errormsg("Text on path isn't supported. First remove text from path.") + break + elif not isinstance(elem, (FlowPara, Tspan)): + continue + + text = TextElement(**node.attrib) + + # handling flowed text nodes + if isinstance(node, FlowRoot): + fontsize = node.style.get("font-size", "12px") + fs = self.svg.unittouu(fontsize) + + # selects the flowRegion's child (svg:rect) to get @X and @Y + flowref = node.findone('svg:flowRegion')[0] + + if isinstance(flowref, Rectangle): + text.set("x", flowref.get("x")) + text.set("y", str(float(flowref.get("y")) + fs * count)) + count += 1 + else: + inkex.debug("This type of text element isn't supported. First unflow text.") + break + + # now let's convert flowPara into tspan + tspan = Tspan() + tspan.set("sodipodi:role", "line") + tspan.text = elem.text + text.append(tspan) + + else: + from copy import copy + x = elem.get("x") or node.get("x") + y = elem.get("y") or node.get("y") + + text.set("x", x) + text.set("y", y) + text.append(copy(elem)) + + lines.append(text) + + return lines + + def split_words(self, node): + """Returns a list of words""" + words = [] + + # Function to recursively extract text + def plain_str(elem): + words = [] + if elem.text: + words.append(elem.text) + for n in elem: + words.extend(plain_str(n)) + if n.tail: + words.append(n.tail) + return words + + # if text has more than one line, iterates through elements + lines = self.split_lines(node) + if not lines: + return words + + for line in lines: + # gets the position of text node + x = float(line.get("x")) + y = line.get("y") + + # gets the font size. if element doesn't have a style attribute, it assumes font-size = 12px + fontsize = line.style.get("font-size", "12px") + fs = self.svg.unittouu(fontsize) + + # extract and returns a list of words + words_list = "".join(plain_str(line)).split() + prev_len = 0 + + # creates new text nodes for each string in words_list + for word in words_list: + tspan = Tspan() + tspan.text = word + + text = TextElement(**line.attrib) + tspan.set('sodipodi:role', "line") + + # positioning new text elements + x = x + prev_len * fs + prev_len = len(word) + text.set("x", str(x)) + text.set("y", str(y)) + + text.append(tspan) + words.append(text) + + return words + + def split_letters(self, node): + """Returns a list of letters""" + + letters = [] + + words = self.split_words(node) + if not words: + return letters + + for word in words: + + x = float(word.get("x")) + y = word.get("y") + + # gets the font size. If element doesn't have a style attribute, it assumes font-size = 12px + fontsize = word.style.get("font-size", "12px") + fs = self.svg.unittouu(fontsize) + + # for each letter in element string + for letter in word[0].text: + tspan = Tspan() + tspan.text = letter + + text = TextElement(**node.attrib) + text.set("x", str(x)) + text.set("y", str(y)) + x += fs + + text.append(tspan) + letters.append(text) + return letters + + def effect(self): + """Applies the effect""" + + split_type = self.options.splittype + preserve = self.options.preserve + + # checks if the selected elements are text nodes + for elem in self.svg.selection.get(TextElement, FlowRoot).values(): + if split_type == "line": + nodes = self.split_lines(elem) + elif split_type == "word": + nodes = self.split_words(elem) + elif split_type == "letter": + nodes = self.split_letters(elem) + + for child in nodes: + elem.getparent().append(child) + + # preserve original element + if not preserve and nodes: + parent = elem.getparent() + parent.remove(elem) + +if __name__ == '__main__': + TextSplit().run() diff --git a/share/extensions/text_titlecase.inx b/share/extensions/text_titlecase.inx new file mode 100644 index 0000000..540b4a0 --- /dev/null +++ b/share/extensions/text_titlecase.inx @@ -0,0 +1,16 @@ + + + Title Case + org.inkscape.text.title_case + + all + + + + + + + + diff --git a/share/extensions/text_titlecase.py b/share/extensions/text_titlecase.py new file mode 100755 index 0000000..a3c74da --- /dev/null +++ b/share/extensions/text_titlecase.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +"""Convert to title case""" + +import inkex + +class TitleCase(inkex.TextExtension): + """To titlecase""" + word_ended = True + + def process_chardata(self, text): + ret = "" + newline = True + for char in text: + if char.isspace() or newline: + self.word_ended = True + if not char.isspace(): + newline = False + + if self.word_ended and char.isalpha(): + ret += char.upper() + self.word_ended = False + elif char.isalpha(): + ret += char.lower() + else: + ret += char + + return ret + +if __name__ == '__main__': + TitleCase().run() diff --git a/share/extensions/text_uppercase.inx b/share/extensions/text_uppercase.inx new file mode 100644 index 0000000..7787ca4 --- /dev/null +++ b/share/extensions/text_uppercase.inx @@ -0,0 +1,16 @@ + + + UPPERCASE + org.inkscape.text.uppercase + + all + + + + + + + + diff --git a/share/extensions/text_uppercase.py b/share/extensions/text_uppercase.py new file mode 100755 index 0000000..9f1539e --- /dev/null +++ b/share/extensions/text_uppercase.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +"""Convert text to upper case""" + +import inkex + +class Uppercase(inkex.TextExtension): + """To upper case""" + def process_chardata(self, text): + return text.upper() + +if __name__ == '__main__': + Uppercase().run() diff --git a/share/extensions/tools/generate_argparse_conf.py b/share/extensions/tools/generate_argparse_conf.py new file mode 100644 index 0000000..fda9145 --- /dev/null +++ b/share/extensions/tools/generate_argparse_conf.py @@ -0,0 +1,60 @@ +# coding=utf-8 +import os +from string import Template +import xml.etree.ElementTree as ET +import argparse + +parser = argparse.ArgumentParser(description='Reads an *.inx file and generates initialization code for argparse') +parser.add_argument('input') +args = parser.parse_args() + +if os.path.isabs(args.input): + inpath = args.input +else: + folder = os.path.dirname(os.path.realpath(__file__)) + inpath = os.path.normpath(os.path.join(folder, args.input)) + +templateWithType = Template('self.arg_parser.add_argument("--$param", type=$type, dest="$param", default=$default)') +templateWithoutType = Template('self.arg_parser.add_argument("--$param", dest="$param", default=$default)') +def handle_param_node(node): + if node.attrib["type"] == 'float': + cmd = templateWithType.substitute( + param=node.attrib["name"], + type='float', + default=node.text) + print(cmd) + elif node.attrib["type"] == 'int': + cmd = templateWithType.substitute( + param=node.attrib["name"], + type='int', + default=node.text) + print(cmd) + elif node.attrib["type"] == 'boolean': + cmd = templateWithType.substitute( + param=node.attrib["name"], + type='inkex.inkbool', + default='"' + node.text + '"') + print(cmd) + elif node.attrib["type"] == 'enum': + cmd = templateWithoutType.substitute( + param=node.attrib["name"], + default='"' + node[0].text + '"') + print(cmd) + elif node.attrib["type"] == 'notebook': + cmd = templateWithoutType.substitute( + param=node.attrib["name"], + default='"' + node[0].attrib["name"] + '"') + print(cmd) + else: + # TODO: Implement other types of args + raise NotImplementedError + +def process_node(node): + for child in node: + if child.tag.endswith('param'): + handle_param_node(child) + process_node(child) + +tree = ET.parse(inpath) +root = tree.getroot() +process_node(root) \ No newline at end of file diff --git a/share/extensions/tox.ini b/share/extensions/tox.ini new file mode 100644 index 0000000..c33af70 --- /dev/null +++ b/share/extensions/tox.ini @@ -0,0 +1,12 @@ +[tox] +envlist = py{27,35,36,37}-normal + + +[testenv] +setenv = COVERAGE_FILE=.coverage-{env:TOX_ENV_NAME} + +commands = + pytest --cov=. --cov-report html --cov-report term {posargs} + +deps = + -rtests/dev_requirements.txt diff --git a/share/extensions/triangle.inx b/share/extensions/triangle.inx new file mode 100644 index 0000000..460ed92 --- /dev/null +++ b/share/extensions/triangle.inx @@ -0,0 +1,27 @@ + + + Triangle + math.triangle + 100.0 + 100.0 + 100.0 + 60 + 30 + 90 + + + + + + + + + all + + + + + + diff --git a/share/extensions/triangle.py b/share/extensions/triangle.py new file mode 100755 index 0000000..dc8d6f2 --- /dev/null +++ b/share/extensions/triangle.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2007 John Beard john.j.beard@gmail.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This extension allows you to draw a triangle given certain information + about side length or angles. + +Measurements of the triangle + + C(x_c,y_c) + /`__ + / a_c``--__ + / ``--__ s_a + s_b / ``--__ + /a_a a_b`--__ + /--------------------------------``B(x_b, y_b) + A(x_a,y_a) s_b +""" + +import sys +from math import acos, asin, cos, pi, sin, sqrt + +import inkex + +X, Y = range(2) + +def draw_SVG_tri(point1, point2, point3, offset, width, name, parent): + style = {'stroke': '#000000', 'stroke-width': str(width), 'fill': 'none'} + elem = parent.add(inkex.PathElement()) + elem.update(**{ + 'style': style, + 'inkscape:label': name, + 'd': 'M ' + str(point1[X] + offset[X]) + ',' + str(point1[Y] + offset[Y]) + + ' L ' + str(point2[X] + offset[X]) + ',' + str(point2[Y] + offset[Y]) + + ' L ' + str(point3[X] + offset[X]) + ',' + str(point3[Y] + offset[Y]) + + ' L ' + str(point1[X] + offset[X]) + ',' + str(point1[Y] + offset[Y]) + ' z'}) + return elem + + +def angle_from_3_sides(a, b, c): # return the angle opposite side c + cosx = (a * a + b * b - c * c) / (2 * a * b) # use the cosine rule + return acos(cosx) + + +def third_side_from_enclosed_angle(s_a, s_b, a_c): # return the side opposite a_c + c_squared = s_a * s_a + s_b * s_b - 2 * s_a * s_b * cos(a_c) + if c_squared > 0: + return sqrt(c_squared) + else: + return 0 # means we have an invalid or degenerate triangle (zero is caught at the drawing stage) + + +def pt_on_circ(radius, angle): # return the x,y coordinate of the polar coordinate + x = radius * cos(angle) + y = radius * sin(angle) + return [x, y] + + +def v_add(point1, point2): # add an offset to coordinates + return [point1[X] + point2[X], point1[Y] + point2[Y]] + + +def is_valid_tri_from_sides(a, b, c): # check whether triangle with sides a,b,c is valid + return (a + b) > c and (a + c) > b and (b + c) > a and a > 0 and b > 0 and c > 0 # two sides must always be greater than the third + # no zero-length sides, no degenerate case + + +def draw_tri_from_3_sides(s_a, s_b, s_c, offset, width, parent): # draw a triangle from three sides (with a given offset + if is_valid_tri_from_sides(s_a, s_b, s_c): + a_b = angle_from_3_sides(s_a, s_c, s_b) + + a = (0, 0) # a is the origin + b = v_add(a, (s_c, 0)) # point B is horizontal from the origin + c = v_add(b, pt_on_circ(s_a, pi - a_b)) # get point c + c[1] = -c[1] + + offx = max(b[0], c[0]) / 2 # b or c could be the furthest right + offy = c[1] / 2 # c is the highest point + offset = (offset[0] - offx, offset[1] - offy) # add the centre of the triangle to the offset + + draw_SVG_tri(a, b, c, offset, width, 'Triangle', parent) + else: + inkex.errormsg('Invalid Triangle Specifications.') + + +class Triangle(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("--s_a", type=float, default=100.0, help="Side Length a") + pars.add_argument("--s_b", type=float, default=100.0, help="Side Length b") + pars.add_argument("--s_c", type=float, default=100.0, help="Side Length c") + pars.add_argument("--a_a", type=float, default=60.0, help="Angle a") + pars.add_argument("--a_b", type=float, default=30.0, help="Angle b") + pars.add_argument("--a_c", type=float, default=90.0, help="Angle c") + pars.add_argument("--mode", default='3_sides', help="Side Length c") + + def effect(self): + tri = self.svg.get_current_layer() + offset = self.svg.namedview.center + self.options.s_a = self.svg.unittouu(str(self.options.s_a) + 'px') + self.options.s_b = self.svg.unittouu(str(self.options.s_b) + 'px') + self.options.s_c = self.svg.unittouu(str(self.options.s_c) + 'px') + stroke_width = self.svg.unittouu('2px') + + if self.options.mode == '3_sides': + s_a = self.options.s_a + s_b = self.options.s_b + s_c = self.options.s_c + draw_tri_from_3_sides(s_a, s_b, s_c, offset, stroke_width, tri) + + elif self.options.mode == 's_ab_a_c': + s_a = self.options.s_a + s_b = self.options.s_b + a_c = self.options.a_c * pi / 180 # in rad + + s_c = third_side_from_enclosed_angle(s_a, s_b, a_c) + draw_tri_from_3_sides(s_a, s_b, s_c, offset, stroke_width, tri) + + elif self.options.mode == 's_ab_a_a': + s_a = self.options.s_a + s_b = self.options.s_b + a_a = self.options.a_a * pi / 180 # in rad + + if (a_a < pi / 2.0) and (s_a < s_b) and (s_a > s_b * sin(a_a)): # this is an ambiguous case + ambiguous = True # we will give both answers + else: + ambiguous = False + + sin_a_b = s_b * sin(a_a) / s_a + + if (sin_a_b <= 1) and (sin_a_b >= -1): # check the solution is possible + a_b = asin(sin_a_b) # acute solution + a_c = pi - a_a - a_b + error = False + else: + sys.stderr.write('Error:Invalid Triangle Specifications.\n') # signal an error + error = True + + if not error and (a_b < pi) and (a_c < pi): # check that the solution is valid, if so draw acute solution + s_c = third_side_from_enclosed_angle(s_a, s_b, a_c) + draw_tri_from_3_sides(s_a, s_b, s_c, offset, stroke_width, tri) + + if not error and ((a_b > pi) or (a_c > pi) or ambiguous): # we want the obtuse solution + a_b = pi - a_b + a_c = pi - a_a - a_b + s_c = third_side_from_enclosed_angle(s_a, s_b, a_c) + draw_tri_from_3_sides(s_a, s_b, s_c, offset, stroke_width, tri) + + elif self.options.mode == 's_a_a_ab': + s_a = self.options.s_a + a_a = self.options.a_a * pi / 180 # in rad + a_b = self.options.a_b * pi / 180 # in rad + + a_c = pi - a_a - a_b + s_b = s_a * sin(a_b) / sin(a_a) + s_c = s_a * sin(a_c) / sin(a_a) + + draw_tri_from_3_sides(s_a, s_b, s_c, offset, stroke_width, tri) + + elif self.options.mode == 's_c_a_ab': + s_c = self.options.s_c + a_a = self.options.a_a * pi / 180 # in rad + a_b = self.options.a_b * pi / 180 # in rad + + a_c = pi - a_a - a_b + s_a = s_c * sin(a_a) / sin(a_c) + s_b = s_c * sin(a_b) / sin(a_c) + + draw_tri_from_3_sides(s_a, s_b, s_c, offset, stroke_width, tri) + + +if __name__ == '__main__': + Triangle().run() diff --git a/share/extensions/ungroup_deep.inx b/share/extensions/ungroup_deep.inx new file mode 100644 index 0000000..cc93d04 --- /dev/null +++ b/share/extensions/ungroup_deep.inx @@ -0,0 +1,18 @@ + + + Deep Ungroup + mcepl.ungroup_deep + + 0 + 65535 + 0 + + all + + + + + + diff --git a/share/extensions/ungroup_deep.py b/share/extensions/ungroup_deep.py new file mode 100755 index 0000000..8afdc0c --- /dev/null +++ b/share/extensions/ungroup_deep.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python +# coding=utf-8 +""" +see #inkscape on Freenode and +https://github.com/nikitakit/svg2sif/blob/master/synfig_prepare.py#L370 +for an example how to do the transform of parent to children. +""" + +import inkex +from inkex import ( + Group, Anchor, Switch, NamedView, Defs, Metadata, ForeignObject, + ClipPath, Use, SvgDocumentElement, +) + +class UngroupDeep(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("--startdepth", type=int, default=0, + help="starting depth for ungrouping") + pars.add_argument("--maxdepth", type=int, default=65535, + help="maximum ungrouping depth") + pars.add_argument("--keepdepth", type=int, default=0, + help="levels of ungrouping to leave untouched") + + @staticmethod + def _merge_style(node, style): + """Propagate style and transform to remove inheritance + Originally from + https://github.com/nikitakit/svg2sif/blob/master/synfig_prepare.py#L370 + """ + + # Compose the style attribs + this_style = node.style + remaining_style = {} # Style attributes that are not propagated + + # Filters should remain on the top ancestor + non_propagated = ["filter"] + for key in non_propagated: + if key in this_style.keys(): + remaining_style[key] = this_style[key] + del this_style[key] + + # Create a copy of the parent style, and merge this style into it + parent_style_copy = style.copy() + parent_style_copy.update(this_style) + this_style = parent_style_copy + + # Merge in any attributes outside of the style + style_attribs = ["fill", "stroke"] + for attrib in style_attribs: + if node.get(attrib): + this_style[attrib] = node.get(attrib) + del node.attrib[attrib] + + if isinstance(node, (SvgDocumentElement, Anchor, Group, Switch)): + # Leave only non-propagating style attributes + if not remaining_style: + if "style" in node.keys(): + del node.attrib["style"] + else: + node.style = remaining_style + + else: + # This element is not a container + + # Merge remaining_style into this_style + this_style.update(remaining_style) + + # Set the element's style attribs + node.style = this_style + + def _merge_clippath(self, node, clippathurl): + + if clippathurl: + node_transform = node.transform + if node_transform: + # Clip-paths on nodes with a transform have the transform + # applied to the clipPath as well, which we don't want. So, we + # create new clipPath element with references to all existing + # clippath subelements, but with the inverse transform applied + new_clippath = self.svg.defs.add(ClipPath(clipPathUnits='userSpaceOnUse')) + new_clippath.set_random_id('clipPath') + clippath = self.svg.getElementById(clippathurl[5:-1]) + for child in clippath.iterchildren(): + use = new_clippath.add(Use()) + use.add('xlink:href', '#' + child.get("id")) + use.transform = -node_transform + use.set_random_id('use') + + # Set the clippathurl to be the one with the inverse transform + clippathurl = "url(#" + new_clippath.get("id") + ")" + + # Reference the parent clip-path to keep clipping intersection + # Find end of clip-path chain and add reference there + node_clippathurl = node.get("clip-path") + while node_clippathurl: + node = self.svg.getElementById(node_clippathurl[5:-1]) + node_clippathurl = node.get("clip-path") + node.set("clip-path", clippathurl) + + # Flatten a group into same z-order as parent, propagating attribs + def _ungroup(self, node): + node_parent = node.getparent() + node_index = list(node_parent).index(node) + node_style = node.style + + node_transform = node.transform + node_clippathurl = node.get('clip-path') + for child in reversed(list(node)): + + child.transform *= node_transform + + if node.get("style") is not None: + self._merge_style(child, node_style) + self._merge_clippath(child, node_clippathurl) + node_parent.insert(node_index, child) + node_parent.remove(node) + + # Put all ungrouping restrictions here + def _want_ungroup(self, node, depth, height): + if (isinstance(node, Group) and + node.getparent() is not None and + height > self.options.keepdepth and + self.options.startdepth <= depth <= + self.options.maxdepth): + return True + return False + + def _deep_ungroup(self, node): + # using iteration instead of recursion to avoid hitting Python + # max recursion depth limits, which is a problem in converted PDFs + + # Seed the queue (stack) with initial node + q = [{'node': node, + 'depth': 0, + 'prev': {'height': None}, + 'height': None}] + + while q: + current = q[-1] + node = current['node'] + depth = current['depth'] + height = current['height'] + + # Recursion path + if height is None: + # Don't enter non-graphical portions of the document + if isinstance(node, (NamedView, Defs, Metadata, ForeignObject)): + q.pop() + + # Base case: Leaf node + if not isinstance(node, Group) or not list(node): + current['height'] = 0 + + # Recursive case: Group element with children + else: + depth += 1 + for child in node.iterchildren(): + q.append({'node': child, 'prev': current, + 'depth': depth, 'height': None}) + + # Return path + else: + # Ungroup if desired + if self._want_ungroup(node, depth, height): + self._ungroup(node) + + # Propagate (max) height up the call chain + height += 1 + previous = current['prev'] + prev_height = previous['height'] + if prev_height is None or prev_height < height: + previous['height'] = height + + # Only process each node once + q.pop() + + def effect(self): + if self.svg.selected: + for node in self.svg.selected.values(): + self._deep_ungroup(node) + else: + for node in self.document.getroot(): + self._deep_ungroup(node) + + +if __name__ == '__main__': + UngroupDeep().run() diff --git a/share/extensions/voronoi.py b/share/extensions/voronoi.py new file mode 100755 index 0000000..8740645 --- /dev/null +++ b/share/extensions/voronoi.py @@ -0,0 +1,836 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Voronoi diagram calculator/ Delaunay triangulator +# Translated to Python by Bill Simons +# September, 2005 +# +# Calculate Delaunay triangulation or the Voronoi polygons for a set of +# 2D input points. +# +# Derived from code bearing the following notice: +# +# The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T +# Bell Laboratories. +# Permission to use, copy, modify, and distribute this software for any +# purpose without fee is hereby granted, provided that this entire notice +# is included in all copies of any software which is or includes a copy +# or modification of this software and in all copies of the supporting +# documentation for such software. +# THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +# WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY +# REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY +# OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. +# +# Comments were incorporated from Shane O'Sullivan's translation of the +# original code into C++ (http://mapviewer.skynet.ie/voronoi.html) +# +# Steve Fortune's homepage: http://netlib.bell-labs.com/cm/cs/who/sjf/index.html +# +""" +voronoi - compute Voronoi diagram or Delaunay triangulation + +voronoi [-t -p -d] [filename] + +Voronoi reads from filename (or standard input if no filename given) for a set +of points in the plane and writes either the Voronoi diagram or the Delaunay +triangulation to the standard output. Each input line should consist of two +real numbers, separated by white space. + +If option -t is present, the Delaunay triangulation is produced. +Each output line is a triple i j k, which are the indices of the three points +in a Delaunay triangle. Points are numbered starting at 0. + +If option -t is not present, the Voronoi diagram is produced. +There are four output record types. + +s a b indicates that an input point at coordinates a b was seen. +l a b c indicates a line with equation ax + by = c. +v a b indicates a vertex at a b. +e l v1 v2 indicates a Voronoi segment which is a subsegment of line number l + with endpoints numbered v1 and v2. If v1 or v2 is -1, the line + extends to infinity. + +Other options include: + +d Print debugging info + +p Produce output suitable for input to plot (1), rather than the forms + described above. + +On unsorted data uniformly distributed in the unit square, voronoi uses about +20n+140 bytes of storage. + +AUTHOR +Steve J. Fortune (1987) A Sweepline Algorithm for Voronoi Diagrams, +Algorithmica 2, 153-174. +""" + +############################################################################# +# +# For programmatic use two functions are available: +# +# computeVoronoiDiagram(points) +# +# Takes a list of point objects (which must have x and y fields). +# Returns a 3-tuple of: +# +# (1) a list of 2-tuples, which are the x,y coordinates of the +# Voronoi diagram vertices +# (2) a list of 3-tuples (a,b,c) which are the equations of the +# lines in the Voronoi diagram: a*x + b*y = c +# (3) a list of 3-tuples, (l, v1, v2) representing edges of the +# Voronoi diagram. l is the index of the line, v1 and v2 are +# the indices of the vetices at the end of the edge. If +# v1 or v2 is -1, the line extends to infinity. +# +# computeDelaunayTriangulation(points): +# +# Takes a list of point objects (which must have x and y fields). +# Returns a list of 3-tuples: the indices of the points that form a +# Delaunay triangle. +# +############################################################################# + +from __future__ import print_function + +import getopt +import math +import sys + +TOLERANCE = 1e-9 +BIG_FLOAT = 1e38 + +class CmpMixin(object): + """Upgrade python2 cmp to python3 cmp""" + def __cmp__(self, other): + raise NotImplementedError("Shouldn't there be a __cmp__ method?") + + def __eq__(self, other): + return self.__cmp__(other) == 0 + + def __ne__(self, other): + return self.__cmp__(other) != 0 + + def __lt__(self, other): + return self.__cmp__(other) == -1 + + def __le__(self, other): + return self.__cmp__(other) in (-1, 0) + + def __gt__(self, other): + return self.__cmp__(other) == 1 + + def __ge__(self, other): + return self.__cmp__(other) in (0, 1) + +# ------------------------------------------------------------------ +class Context(object): + def __init__(self): + self.doPrint = 0 + self.debug = 0 + self.plot = 0 + self.triangulate = False + self.vertices = [] # list of vertex 2-tuples: (x,y) + self.lines = [] # equation of line 3-tuple (a b c), for the equation of the line a*x+b*y = c + self.edges = [] # edge 3-tuple: (line index, vertex 1 index, vertex 2 index) if either vertex index is -1, the edge extends to infiinity + self.triangles = [] # 3-tuple of vertex indices + + def circle(self, x, y, rad): + pass + + def clip_line(self, edge): + pass + + def line(self, x0, y0, x1, y1): + pass + + def outSite(self, s): + if self.debug: + print("site (%d) at %f %f" % (s.sitenum, s.x, s.y)) + elif self.triangulate: + pass + elif self.plot: + self.circle(s.x, s.y, cradius) + elif self.doPrint: + print("s %f %f" % (s.x, s.y)) + + def outVertex(self, s): + self.vertices.append((s.x, s.y)) + if self.debug: + print("vertex(%d) at %f %f" % (s.sitenum, s.x, s.y)) + elif self.triangulate: + pass + elif self.doPrint and not self.plot: + print("v %f %f" % (s.x, s.y)) + + def outTriple(self, s1, s2, s3): + self.triangles.append((s1.sitenum, s2.sitenum, s3.sitenum)) + if self.debug: + print("circle through left=%d right=%d bottom=%d" % (s1.sitenum, s2.sitenum, s3.sitenum)) + elif self.triangulate and self.doPrint and not self.plot: + print("%d %d %d" % (s1.sitenum, s2.sitenum, s3.sitenum)) + + def outBisector(self, edge): + self.lines.append((edge.a, edge.b, edge.c)) + if self.debug: + print("line(%d) %gx+%gy=%g, bisecting %d %d" % (edge.edgenum, edge.a, edge.b, edge.c, edge.reg[0].sitenum, edge.reg[1].sitenum)) + elif self.triangulate: + if self.plot: + self.line(edge.reg[0].x, edge.reg[0].y, edge.reg[1].x, edge.reg[1].y) + elif self.doPrint and not self.plot: + print("l %f %f %f" % (edge.a, edge.b, edge.c)) + + def outEdge(self, edge): + sitenumL = -1 + if edge.ep[Edge.LE] is not None: + sitenumL = edge.ep[Edge.LE].sitenum + sitenumR = -1 + if edge.ep[Edge.RE] is not None: + sitenumR = edge.ep[Edge.RE].sitenum + self.edges.append((edge.edgenum, sitenumL, sitenumR)) + if not self.triangulate: + if self.plot: + self.clip_line(edge) + elif self.doPrint: + print("e %d" % edge.edgenum, end=' ') + print(" %d " % sitenumL, end=' ') + print("%d" % sitenumR) + + +# ------------------------------------------------------------------ +def voronoi(siteList, context): + edgeList = EdgeList(siteList.xmin, siteList.xmax, len(siteList)) + priorityQ = PriorityQueue(siteList.ymin, siteList.ymax, len(siteList)) + siteIter = siteList.iterator() + + bottomsite = siteIter.next() + context.outSite(bottomsite) + newsite = siteIter.next() + minpt = Site(-BIG_FLOAT, -BIG_FLOAT) + while True: + if not priorityQ.isEmpty(): + minpt = priorityQ.getMinPt() + + if newsite and (priorityQ.isEmpty() or newsite < minpt): + # newsite is smallest - this is a site event + context.outSite(newsite) + + # get first Halfedge to the LEFT and RIGHT of the new site + lbnd = edgeList.leftbnd(newsite) + rbnd = lbnd.right + + # if this halfedge has no edge, bot = bottom site (whatever that is) + # create a new edge that bisects + bot = lbnd.rightreg(bottomsite) + edge = Edge.bisect(bot, newsite) + context.outBisector(edge) + + # create a new Halfedge, setting its pm field to 0 and insert + # this new bisector edge between the left and right vectors in + # a linked list + bisector = Halfedge(edge, Edge.LE) + edgeList.insert(lbnd, bisector) + + # if the new bisector intersects with the left edge, remove + # the left edge's vertex, and put in the new one + p = lbnd.intersect(bisector) + if p is not None: + priorityQ.delete(lbnd) + priorityQ.insert(lbnd, p, newsite.distance(p)) + + # create a new Halfedge, setting its pm field to 1 + # insert the new Halfedge to the right of the original bisector + lbnd = bisector + bisector = Halfedge(edge, Edge.RE) + edgeList.insert(lbnd, bisector) + + # if this new bisector intersects with the right Halfedge + p = bisector.intersect(rbnd) + if p is not None: + # push the Halfedge into the ordered linked list of vertices + priorityQ.insert(bisector, p, newsite.distance(p)) + + newsite = siteIter.next() + + elif not priorityQ.isEmpty(): + # intersection is smallest - this is a vector (circle) event + + # pop the Halfedge with the lowest vector off the ordered list of + # vectors. Get the Halfedge to the left and right of the above HE + # and also the Halfedge to the right of the right HE + lbnd = priorityQ.popMinHalfedge() + llbnd = lbnd.left + rbnd = lbnd.right + rrbnd = rbnd.right + + # get the Site to the left of the left HE and to the right of + # the right HE which it bisects + bot = lbnd.leftreg(bottomsite) + top = rbnd.rightreg(bottomsite) + + # output the triple of sites, stating that a circle goes through them + mid = lbnd.rightreg(bottomsite) + context.outTriple(bot, top, mid) + + # get the vertex that caused this event and set the vertex number + # couldn't do this earlier since we didn't know when it would be processed + v = lbnd.vertex + siteList.setSiteNumber(v) + context.outVertex(v) + + # set the endpoint of the left and right Halfedge to be this vector + if lbnd.edge.setEndpoint(lbnd.pm, v): + context.outEdge(lbnd.edge) + + if rbnd.edge.setEndpoint(rbnd.pm, v): + context.outEdge(rbnd.edge) + + # delete the lowest HE, remove all vertex events to do with the + # right HE and delete the right HE + edgeList.delete(lbnd) + priorityQ.delete(rbnd) + edgeList.delete(rbnd) + + # if the site to the left of the event is higher than the Site + # to the right of it, then swap them and set 'pm' to RIGHT + pm = Edge.LE + if bot.y > top.y: + bot, top = top, bot + pm = Edge.RE + + # Create an Edge (or line) that is between the two Sites. This + # creates the formula of the line, and assigns a line number to it + edge = Edge.bisect(bot, top) + context.outBisector(edge) + + # create a HE from the edge + bisector = Halfedge(edge, pm) + + # insert the new bisector to the right of the left HE + # set one endpoint to the new edge to be the vector point 'v' + # If the site to the left of this bisector is higher than the right + # Site, then this endpoint is put in position 0; otherwise in pos 1 + edgeList.insert(llbnd, bisector) + if edge.setEndpoint(Edge.RE - pm, v): + context.outEdge(edge) + + # if left HE and the new bisector don't intersect, then delete + # the left HE, and reinsert it + p = llbnd.intersect(bisector) + if p is not None: + priorityQ.delete(llbnd) + priorityQ.insert(llbnd, p, bot.distance(p)) + + # if right HE and the new bisector don't intersect, then reinsert it + p = bisector.intersect(rrbnd) + if p is not None: + priorityQ.insert(bisector, p, bot.distance(p)) + else: + break + + he = edgeList.leftend.right + while he is not edgeList.rightend: + context.outEdge(he.edge) + he = he.right + + +# ------------------------------------------------------------------ +def isEqual(a, b, relativeError=TOLERANCE): + # is nearly equal to within the allowed relative error + norm = max(abs(a), abs(b)) + return (norm < relativeError) or (abs(a - b) < (relativeError * norm)) + + +# ------------------------------------------------------------------ +class Site(CmpMixin): + def __init__(self, x=0.0, y=0.0, sitenum=0): + self.x = x + self.y = y + self.sitenum = sitenum + + def dump(self): + print("Site #%d (%g, %g)" % (self.sitenum, self.x, self.y)) + + def __cmp__(self, other): + if self.y < other.y: + return -1 + elif self.y > other.y: + return 1 + elif self.x < other.x: + return -1 + elif self.x > other.x: + return 1 + return 0 + + def distance(self, other): + dx = self.x - other.x + dy = self.y - other.y + return math.sqrt(dx * dx + dy * dy) + + +# ------------------------------------------------------------------ +class Edge(object): + LE = 0 + RE = 1 + EDGE_NUM = 0 + DELETED = {} # marker value + + def __init__(self): + self.a = 0.0 + self.b = 0.0 + self.c = 0.0 + self.ep = [None, None] + self.reg = [None, None] + self.edgenum = 0 + + def dump(self): + print("(#%d a=%g, b=%g, c=%g)" % (self.edgenum, self.a, self.b, self.c)) + print("ep", self.ep) + print("reg", self.reg) + + def setEndpoint(self, lrFlag, site): + self.ep[lrFlag] = site + if self.ep[Edge.RE - lrFlag] is None: + return False + return True + + @staticmethod + def bisect(s1, s2): + newedge = Edge() + newedge.reg[0] = s1 # store the sites that this edge is bisecting + newedge.reg[1] = s2 + + # to begin with, there are no endpoints on the bisector - it goes to infinity + # ep[0] and ep[1] are None + + # get the difference in x dist between the sites + dx = float(s2.x - s1.x) + dy = float(s2.y - s1.y) + adx = abs(dx) # make sure that the difference in positive + ady = abs(dy) + + # get the slope of the line + newedge.c = float(s1.x * dx + s1.y * dy + (dx * dx + dy * dy) * 0.5) + if adx > ady: + # set formula of line, with x fixed to 1 + newedge.a = 1.0 + newedge.b = dy / dx + newedge.c /= dx + else: + # set formula of line, with y fixed to 1 + newedge.b = 1.0 + if dy <= 0: + dy = 0.01 + newedge.a = dx / dy + newedge.c /= dy + + newedge.edgenum = Edge.EDGE_NUM + Edge.EDGE_NUM += 1 + return newedge + + +# ------------------------------------------------------------------ +class Halfedge(CmpMixin): + def __init__(self, edge=None, pm=Edge.LE): + self.left = None # left Halfedge in the edge list + self.right = None # right Halfedge in the edge list + self.qnext = None # priority queue linked list pointer + self.edge = edge # edge list Edge + self.pm = pm + self.vertex = None # Site() + self.ystar = BIG_FLOAT + + def dump(self): + print("Halfedge--------------------------") + print("left: ", self.left) + print("right: ", self.right) + print("edge: ", self.edge) + print("pm: ", self.pm) + print("vertex: ", end=' ') + if self.vertex: + self.vertex.dump() + else: + print("None") + print("ystar: ", self.ystar) + + def __cmp__(self, other): + if self.ystar > other.ystar: + return 1 + elif self.ystar < other.ystar: + return -1 + elif self.vertex.x > other.vertex.x: + return 1 + elif self.vertex.x < other.vertex.x: + return -1 + else: + return 0 + + def leftreg(self, default): + if not self.edge: + return default + elif self.pm == Edge.LE: + return self.edge.reg[Edge.LE] + else: + return self.edge.reg[Edge.RE] + + def rightreg(self, default): + if not self.edge: + return default + elif self.pm == Edge.LE: + return self.edge.reg[Edge.RE] + else: + return self.edge.reg[Edge.LE] + + # returns True if p is to right of halfedge self + def isPointRightOf(self, pt): + e = self.edge + topsite = e.reg[1] + right_of_site = pt.x > topsite.x + + if right_of_site and self.pm == Edge.LE: + return True + + if not right_of_site and self.pm == Edge.RE: + return False + + if e.a == 1.0: + dyp = pt.y - topsite.y + dxp = pt.x - topsite.x + fast = 0 + if (not right_of_site and e.b < 0.0) or (right_of_site and e.b >= 0.0): + above = dyp >= e.b * dxp + fast = above + else: + above = pt.x + pt.y * e.b > e.c + if e.b < 0.0: + above = not above + if not above: + fast = 1 + if not fast: + dxs = topsite.x - (e.reg[0]).x + above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1.0 + 2.0 * dxp / dxs + e.b * e.b) + if e.b < 0.0: + above = not above + else: # e.b == 1.0 + yl = e.c - e.a * pt.x + t1 = pt.y - yl + t2 = pt.x - topsite.x + t3 = yl - topsite.y + above = t1 * t1 > t2 * t2 + t3 * t3 + + if self.pm == Edge.LE: + return above + else: + return not above + + # -------------------------- + # create a new site where the Halfedges el1 and el2 intersect + def intersect(self, other): + e1 = self.edge + e2 = other.edge + if (e1 is None) or (e2 is None): + return None + + # if the two edges bisect the same parent return None + if e1.reg[1] is e2.reg[1]: + return None + + d = e1.a * e2.b - e1.b * e2.a + if isEqual(d, 0.0): + return None + + xint = (e1.c * e2.b - e2.c * e1.b) / d + yint = (e2.c * e1.a - e1.c * e2.a) / d + if e1.reg[1] < e2.reg[1]: + he = self + e = e1 + else: + he = other + e = e2 + + rightOfSite = xint >= e.reg[1].x + if ((rightOfSite and he.pm == Edge.LE) or + (not rightOfSite and he.pm == Edge.RE)): + return None + + # create a new site at the point of intersection - this is a new + # vector event waiting to happen + return Site(xint, yint) + + +# ------------------------------------------------------------------ +class EdgeList(object): + def __init__(self, xmin, xmax, nsites): + if xmin > xmax: + xmin, xmax = xmax, xmin + self.hashsize = int(2 * math.sqrt(nsites + 4)) + + self.xmin = xmin + self.deltax = float(xmax - xmin) + self.hash = [None] * self.hashsize + + self.leftend = Halfedge() + self.rightend = Halfedge() + self.leftend.right = self.rightend + self.rightend.left = self.leftend + self.hash[0] = self.leftend + self.hash[-1] = self.rightend + + def insert(self, left, he): + he.left = left + he.right = left.right + left.right.left = he + left.right = he + + def delete(self, he): + he.left.right = he.right + he.right.left = he.left + he.edge = Edge.DELETED + + # Get entry from hash table, pruning any deleted nodes + def gethash(self, b): + if b < 0 or b >= self.hashsize: + return None + he = self.hash[b] + if he is None or he.edge is not Edge.DELETED: + return he + + # Hash table points to deleted half edge. Patch as necessary. + self.hash[b] = None + return None + + def leftbnd(self, pt): + # Use hash table to get close to desired halfedge + bucket = int(((pt.x - self.xmin) / self.deltax * self.hashsize)) + + if bucket < 0: + bucket = 0 + + if bucket >= self.hashsize: + bucket = self.hashsize - 1 + + he = self.gethash(bucket) + if he is None: + i = 1 + while True: + he = self.gethash(bucket - i) + if he is not None: + break + he = self.gethash(bucket + i) + if he is not None: + break + i += 1 + + # Now search linear list of halfedges for the correct one + if (he is self.leftend) or (he is not self.rightend and he.isPointRightOf(pt)): + he = he.right + while he is not self.rightend and he.isPointRightOf(pt): + he = he.right + he = he.left + else: + he = he.left + while he is not self.leftend and not he.isPointRightOf(pt): + he = he.left + + # Update hash table and reference counts + if 0 < bucket < self.hashsize - 1: + self.hash[bucket] = he + return he + + +# ------------------------------------------------------------------ +class PriorityQueue(object): + def __init__(self, ymin, ymax, nsites): + self.ymin = ymin + self.deltay = ymax - ymin + self.hashsize = int(4 * math.sqrt(nsites)) + self.count = 0 + self.minidx = 0 + self.hash = [] + for i in range(self.hashsize): + self.hash.append(Halfedge()) + + def __len__(self): + return self.count + + def isEmpty(self): + return self.count == 0 + + def insert(self, he, site, offset): + he.vertex = site + he.ystar = site.y + offset + last = self.hash[self.getBucket(he)] + nxt = last.qnext + while (nxt is not None) and he > nxt: + last = nxt + nxt = last.qnext + he.qnext = last.qnext + last.qnext = he + self.count += 1 + + def delete(self, he): + if he.vertex is not None: + last = self.hash[self.getBucket(he)] + while last.qnext is not he: + last = last.qnext + last.qnext = he.qnext + self.count -= 1 + he.vertex = None + + def getBucket(self, he): + bucket = int(((he.ystar - self.ymin) / self.deltay) * self.hashsize) + if bucket < 0: + bucket = 0 + if bucket >= self.hashsize: + bucket = self.hashsize - 1 + if bucket < self.minidx: + self.minidx = bucket + return bucket + + def getMinPt(self): + while self.hash[self.minidx].qnext is None: + self.minidx += 1 + he = self.hash[self.minidx].qnext + x = he.vertex.x + y = he.ystar + return Site(x, y) + + def popMinHalfedge(self): + curr = self.hash[self.minidx].qnext + self.hash[self.minidx].qnext = curr.qnext + self.count -= 1 + return curr + + +# ------------------------------------------------------------------ +class SiteList(object): + def __init__(self, pointList): + self.__sites = [] + self.__sitenum = 0 + + self.__xmin = pointList[0].x + self.__ymin = pointList[0].y + self.__xmax = pointList[0].x + self.__ymax = pointList[0].y + for i, pt in enumerate(pointList): + self.__sites.append(Site(pt.x, pt.y, i)) + if pt.x < self.__xmin: + self.__xmin = pt.x + if pt.y < self.__ymin: + self.__ymin = pt.y + if pt.x > self.__xmax: + self.__xmax = pt.x + if pt.y > self.__ymax: + self.__ymax = pt.y + self.__sites.sort() + + def setSiteNumber(self, site): + site.sitenum = self.__sitenum + self.__sitenum += 1 + + class Iterator(object): + def __init__(this, lst): + this.generator = (s for s in lst) + + def __iter__(this): + return this + + def next(this): + try: + return next(this.generator) + except StopIteration: + return None + + def iterator(self): + return SiteList.Iterator(self.__sites) + + def __iter__(self): + return SiteList.Iterator(self.__sites) + + def __len__(self): + return len(self.__sites) + + def _getxmin(self): + return self.__xmin + + def _getymin(self): + return self.__ymin + + def _getxmax(self): + return self.__xmax + + def _getymax(self): + return self.__ymax + + xmin = property(_getxmin) + ymin = property(_getymin) + xmax = property(_getxmax) + ymax = property(_getymax) + + +# ------------------------------------------------------------------ +def computeVoronoiDiagram(points): + """ Takes a list of point objects (which must have x and y fields). + Returns a 3-tuple of: + + (1) a list of 2-tuples, which are the x,y coordinates of the + Voronoi diagram vertices + (2) a list of 3-tuples (a,b,c) which are the equations of the + lines in the Voronoi diagram: a*x + b*y = c + (3) a list of 3-tuples, (l, v1, v2) representing edges of the + Voronoi diagram. l is the index of the line, v1 and v2 are + the indices of the vetices at the end of the edge. If + v1 or v2 is -1, the line extends to infinity. + """ + siteList = SiteList(points) + context = Context() + voronoi(siteList, context) + return context.vertices, context.lines, context.edges + + +# ------------------------------------------------------------------ +def computeDelaunayTriangulation(points): + """ Takes a list of point objects (which must have x and y fields). + Returns a list of 3-tuples: the indices of the points that form a + Delaunay triangle. + """ + siteList = SiteList(points) + context = Context() + context.triangulate = True + voronoi(siteList, context) + return context.triangles + + +# ----------------------------------------------------------------------------- +if __name__ == "__main__": + optlist, args = getopt.getopt(sys.argv[1:], "thdp") + + doHelp = 0 + c = Context() + c.doPrint = 1 + for opt in optlist: + if opt[0] == "-d": + c.debug = 1 + if opt[0] == "-p": + c.plot = 1 + if opt[0] == "-t": + c.triangulate = 1 + if opt[0] == "-h": + doHelp = 1 + + if not doHelp: + pts = [] + fp = sys.stdin + if len(args) > 0: + fp = open(args[0], 'r') + for line in fp: + fld = line.split() + x = float(fld[0]) + y = float(fld[1]) + pts.append(Site(x, y)) + if len(args) > 0: + fp.close() + + sl = SiteList(pts) + voronoi(sl, c) diff --git a/share/extensions/voronoi2svg.inx b/share/extensions/voronoi2svg.inx new file mode 100644 index 0000000..febd78d --- /dev/null +++ b/share/extensions/voronoi2svg.inx @@ -0,0 +1,39 @@ + + + Voronoi Diagram + org.inkscape.effect.voronoi + voronoi.py + + + + + + + + + + + + + false + + + + + + + + + + + + + all + + + + + + diff --git a/share/extensions/voronoi2svg.py b/share/extensions/voronoi2svg.py new file mode 100755 index 0000000..6a45ca7 --- /dev/null +++ b/share/extensions/voronoi2svg.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2011 Vincent Nivoliers and contributors +# +# Contributors +# ~suv, +# - Voronoi Diagram algorithm and C code by Steven Fortune, 1987, http://ect.bell-labs.com/who/sjf/ +# - Python translation to file voronoi.py by Bill Simons, 2005, http://www.oxfish.com/ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +""" +Create Voronoi diagram from seeds (midpoints of selected objects) +""" + +import random + +import inkex +from inkex import Group, Rectangle, PathElement, Vector2d as Point + +import voronoi + +class Voronoi(inkex.EffectExtension): + """Extension to create a Voronoi diagram.""" + def add_arguments(self, pars): + pars.add_argument('--tab') + pars.add_argument( + '--diagram-type', + default='Voronoi', dest='diagramType', + choices=['Voronoi', 'Delaunay', 'Both'], + help='Defines the type of the diagram') + pars.add_argument( + '--clip-box', choices=['Page', 'Automatic from seeds'], + default='Page', dest='clip_box', + help='Defines the bounding box of the Voronoi diagram') + pars.add_argument( + '--show-clip-box', type=inkex.Boolean, + default=False, dest='showClipBox', + help='Set this to true to write the bounding box') + pars.add_argument( + '--delaunay-fill-options', default="delaunay-no-fill", + dest='delaunayFillOptions', + help='Set the Delaunay triangles color options') + + def dot(self, x, y): + """Clipping a line by a bounding box""" + return x[0] * y[0] + x[1] * y[1] + + def intersect_line_segment(self, line, vt1, vt2): + """Get the line intersection of the two verticies""" + sc1 = self.dot(line, vt1) - line[2] + sc2 = self.dot(line, vt2) - line[2] + if sc1 * sc2 > 0: + return 0, 0, False + + tmp = self.dot(line, vt1) - self.dot(line, vt2) + if tmp == 0: + return 0, 0, False + und = (line[2] - self.dot(line, vt2)) / tmp + vt0 = 1 - und + return und * vt1[0] + vt0 * vt2[0], \ + und * vt1[1] + vt0 * vt2[1], \ + True + + def clip_edge(self, vertices, lines, edge, bbox): + # bounding box corners + bbc = [ + (bbox[0], bbox[2]), + (bbox[1], bbox[2]), + (bbox[1], bbox[3]), + (bbox[0], bbox[3]), + ] + + # record intersections of the line with bounding box edges + if edge[0] >= len(lines): + return [] + line = (lines[edge[0]]) + interpoints = [] + for i in range(4): + pnt = self.intersect_line_segment(line, bbc[i], bbc[(i + 1) % 4]) + if pnt[2]: + interpoints.append(pnt) + + # if the edge has no intersection, return empty intersection + if len(interpoints) < 2: + return [] + + if len(interpoints) > 2: # happens when the edge crosses the corner of the box + interpoints = list(set(interpoints)) # remove doubles + + # points of the edge + vt1 = vertices[edge[1]] + interpoints.append((vt1[0], vt1[1], False)) + vt2 = vertices[edge[2]] + interpoints.append((vt2[0], vt2[1], False)) + + # sorting the points in the widest range to get them in order on the line + minx = interpoints[0][0] + miny = interpoints[0][1] + maxx = interpoints[0][0] + maxy = interpoints[0][1] + for point in interpoints: + minx = min(point[0], minx) + maxx = max(point[0], maxx) + miny = min(point[1], miny) + maxy = max(point[1], maxy) + + if (maxx - minx) > (maxy - miny): + interpoints.sort() + else: + interpoints.sort(key=lambda pt: pt[1]) + + start = [] + inside = False # true when the part of the line studied is in the clip box + start_write = False # true when the part of the line is in the edge segment + for point in interpoints: + if point[2]: # The point is a bounding box intersection + if inside: + if start_write: + return [[start[0], start[1]], [point[0], point[1]]] + return [] + else: + if start_write: + start = point + inside = not inside + else: # The point is a segment endpoint + if start_write: + if inside: + # a vertex ends the line inside the bounding box + return [[start[0], start[1]], [point[0], point[1]]] + return [] + else: + if inside: + start = point + start_write = not start_write + + def effect(self): + # Check that elements have been selected + if not self.svg.selected: + inkex.errormsg(_("Please select objects!")) + return + + linestyle = { + 'stroke': '#000000', + 'stroke-width': str(self.svg.unittouu('1px')), + 'fill': 'none', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round' + } + + facestyle = { + 'stroke': '#000000', + 'stroke-width': str(self.svg.unittouu('1px')), + 'fill': 'none', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round' + } + + parent_group = self.svg.selection.first().getparent() + trans = parent_group.composed_transform() + + invtrans = None + if trans: + invtrans = -trans + + # Recovery of the selected objects + pts = [] + nodes = [] + seeds = [] + fills = [] + + for node in self.svg.selected.values(): + nodes.append(node) + bbox = node.bounding_box() + if bbox: + center_x, center_y = bbox.center + point = [center_x, center_y] + if trans: + point = trans.apply_to_point(point) + pts.append(Point(*point)) + if self.options.delaunayFillOptions != "delaunay-no-fill": + fills.append(node.style.get('fill', 'none')) + seeds.append(Point(center_x, center_y)) + + # Creation of groups to store the result + if self.options.diagramType != 'Delaunay': + # Voronoi + group_voronoi = parent_group.add(Group()) + group_voronoi.set('inkscape:label', 'Voronoi') + if invtrans: + group_voronoi.transform *= invtrans + if self.options.diagramType != 'Voronoi': + # Delaunay + group_delaunay = parent_group.add(Group()) + group_delaunay.set('inkscape:label', 'Delaunay') + + # Clipping box handling + if self.options.diagramType != 'Delaunay': + # Clipping bounding box creation + group_bbox = sum([node.bounding_box() for node in nodes], None) + + # Clipbox is the box to which the Voronoi diagram is restricted + if self.options.clip_box == 'Page': + svg = self.document.getroot() + width = self.svg.unittouu(svg.get('width')) + height = self.svg.unittouu(svg.get('height')) + clip_box = (0, width, 0, height) + else: + clip_box = (2 * group_bbox[0] - group_bbox[1], + 2 * group_bbox[1] - group_bbox[0], + 2 * group_bbox[2] - group_bbox[3], + 2 * group_bbox[3] - group_bbox[2]) + + # Safebox adds points so that no Voronoi edge in clip_box is infinite + safe_box = (2 * clip_box[0] - clip_box[1], + 2 * clip_box[1] - clip_box[0], + 2 * clip_box[2] - clip_box[3], + 2 * clip_box[3] - clip_box[2]) + pts.append(Point(safe_box[0], safe_box[2])) + pts.append(Point(safe_box[1], safe_box[2])) + pts.append(Point(safe_box[1], safe_box[3])) + pts.append(Point(safe_box[0], safe_box[3])) + + if self.options.showClipBox: + # Add the clip box to the drawing + rect = group_voronoi.add(Rectangle()) + rect.set('x', str(clip_box[0])) + rect.set('y', str(clip_box[2])) + rect.set('width', str(clip_box[1] - clip_box[0])) + rect.set('height', str(clip_box[3] - clip_box[2])) + rect.style = linestyle + + # Voronoi diagram generation + if self.options.diagramType != 'Delaunay': + vertices, lines, edges = voronoi.computeVoronoiDiagram(pts) + for edge in edges: + vindex1, vindex2 = edge[1:] + if (vindex1 < 0) or (vindex2 < 0): + continue # infinite lines have no need to be handled in the clipped box + else: + segment = self.clip_edge(vertices, lines, edge, clip_box) + # segment = [vertices[vindex1],vertices[vindex2]] # deactivate clipping + if len(segment) > 1: + x1, y1 = segment[0] + x2, y2 = segment[1] + cmds = [['M', [x1, y1]], ['L', [x2, y2]]] + path = group_voronoi.add(PathElement()) + path.set('d', str(inkex.Path(cmds))) + path.style = linestyle + + if self.options.diagramType != 'Voronoi': + triangles = voronoi.computeDelaunayTriangulation(seeds) + i = 0 + if self.options.delaunayFillOptions == "delaunay-fill": + random.seed("inkscape") + for triangle in triangles: + pt1 = seeds[triangle[0]] + pt2 = seeds[triangle[1]] + pt3 = seeds[triangle[2]] + cmds = [['M', [pt1.x, pt1.y]], + ['L', [pt2.x, pt2.y]], + ['L', [pt3.x, pt3.y]], + ['Z', []]] + if self.options.delaunayFillOptions == "delaunay-fill" \ + or self.options.delaunayFillOptions == "delaunay-fill-random": + facestyle = { + 'stroke': fills[triangle[random.randrange(0, 2)]], + 'stroke-width': str(self.svg.unittouu('0.005px')), + 'fill': fills[triangle[random.randrange(0, 2)]], + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round' + } + path = group_delaunay.add(PathElement()) + path.set('d', str(inkex.Path(cmds))) + path.style = facestyle + i += 1 + +if __name__ == "__main__": + Voronoi().run() diff --git a/share/extensions/web_interactive_mockup.inx b/share/extensions/web_interactive_mockup.inx new file mode 100644 index 0000000..b429708 --- /dev/null +++ b/share/extensions/web_interactive_mockup.inx @@ -0,0 +1,38 @@ + + + Interactive Mockup + org.inkscape.webdesign.interactive_mockup + + + + + + + + + + + + + + + + + + + + + + + all + + + + + + + + + diff --git a/share/extensions/web_interactive_mockup.py b/share/extensions/web_interactive_mockup.py new file mode 100755 index 0000000..791ca25 --- /dev/null +++ b/share/extensions/web_interactive_mockup.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +# Copyright (C) 2019 Gemy Cedric Activdesign.eu +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +import inkwebeffect +import inkex + +class InteractiveMockup(inkwebeffect.InkWebEffect): + def add_arguments(self, pars): + pars.add_argument("--when", default="onclick", help="Event that will trigger the action") + pars.add_argument("--tab") + + def effect(self): + self.ensureInkWebSupport() + + if len(self.options.ids) < 2: + raise inkex.AbortExtension("You must select at least two elements." + " The last one is the object you want to go to") + + el_from = list(self.svg.selected.values())[:-1] + + ev_code = "InkWeb.moveViewbox({from:this, to:'" + self.options.ids[-1] +"'})" + for elem in el_from: + prev_ev_code = elem.get(self.options.when) + el_ev_code = ev_code +";" + (prev_ev_code or '') + elem.set(self.options.when, el_ev_code) + +if __name__ == '__main__': + InteractiveMockup().run() diff --git a/share/extensions/web_set_att.inx b/share/extensions/web_set_att.inx new file mode 100644 index 0000000..4e0176b --- /dev/null +++ b/share/extensions/web_set_att.inx @@ -0,0 +1,50 @@ + + + Set Attributes + org.inkscape.web.set_attribute + + + fill stroke stroke-width + + + + + + + + + + + + + + red black 5px + + + + + + + + + + + + + + + + + + + all + + + + + + + + diff --git a/share/extensions/web_set_att.py b/share/extensions/web_set_att.py new file mode 100755 index 0000000..e4c0bea --- /dev/null +++ b/share/extensions/web_set_att.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# +# Copyright (C) 2009 Aurelio A. Heckert, aurium (a) gmail dot com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This effect adds a feature visible (or usable) only on a SVG enabled web +browser (like Firefox). This effect sets one or more attributes in the second +selected element, when a defined event occurs on the first selected element. +If you want to set more than one attribute, you must separate this with a space, +and only with aspace. +""" + +# local library +import inkex +from inkex.localization import inkex_gettext as _ +import inkwebeffect + +class SetAttribute(inkwebeffect.InkWebEffect): + """Set a web attribute accross many objects""" + def add_arguments(self, pars): + pars.add_argument("--tab", help="The selected UI-tab when OK was pressed") + pars.add_argument("--att", default="fill", help="Attribute to set.") + pars.add_argument("--val", default="red", help="Values to set.") + pars.add_argument("--when", default="onclick", help="When it must to set?") + pars.add_argument("--from-and-to", dest="from_and_to", default="g-to-one") + pars.add_argument("--compatibility", default="append", + help="Compatibility with previews code to this event.") + + def effect(self): + self.ensureInkWebSupport() + + if len(self.options.ids) < 2: + raise inkex.AbortExtension(_("You must select at least two elements.")) + + # All set the last else The first set all + split = -1 if self.options.from_and_to == "g-to-one" else 1 + el_from = list(self.svg.selected.values())[:split] + id_to = list(self.svg.selected.ids)[split:] + + ev_code = "InkWeb.setAtt({{el:['{}'], att:'{}', val:'{}'}})".format( + "','".join(id_to), self.options.att, self.options.val) + for elem in el_from: + prev_ev_code = elem.get(self.options.when) + if prev_ev_code is None: + prev_ev_code = "" + + if self.options.compatibility == 'append': + el_ev_code = prev_ev_code + ";\n" + ev_code + if self.options.compatibility == 'prepend': + el_ev_code = ev_code + ";\n" + prev_ev_code + + elem.set(self.options.when, el_ev_code) + +if __name__ == '__main__': + SetAttribute().run() diff --git a/share/extensions/web_transmit_att.inx b/share/extensions/web_transmit_att.inx new file mode 100644 index 0000000..3eeef48 --- /dev/null +++ b/share/extensions/web_transmit_att.inx @@ -0,0 +1,48 @@ + + + Transmit Attributes + org.inkscape.web.transmit_attribute + + + fill + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + + + + + + + + diff --git a/share/extensions/web_transmit_att.py b/share/extensions/web_transmit_att.py new file mode 100755 index 0000000..3e48578 --- /dev/null +++ b/share/extensions/web_transmit_att.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# +# Copyright (C) 2009 Aurelio A. Heckert, aurium (a) gmail dot com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import inkex +from inkex.localization import inkex_gettext as _ + +import inkwebeffect + +class TransmitAttribute(inkwebeffect.InkWebEffect): + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("--att", default="fill", help="Attribute to transmitted.") + pars.add_argument("--when", default="onclick", help="When it must to transmit?") + pars.add_argument("--from-and-to", dest="from_and_to", default="g-to-one") + pars.add_argument("--compatibility", default="append", + help="Compatibility with previews code to this event.") + + def effect(self): + self.ensureInkWebSupport() + + if len(self.options.ids) < 2: + raise inkex.AbortExtension(_("You must select at least two elements.")) + + # All set the last else The first set all + split = -1 if self.options.from_and_to == "g-to-one" else 1 + el_from = list(self.svg.selection.values())[:split] + id_to = list(self.svg.selection.ids)[split:] + + ev_code = "InkWeb.transmitAtt({{from:this, to:['{}'], att:'{}'}})".format("','".join(id_to), self.options.att) + for elem in el_from: + prev_ev_code = elem.get(self.options.when, "") + if self.options.compatibility == 'append': + el_ev_code = prev_ev_code + ";\n" + ev_code + if self.options.compatibility == 'prepend': + el_ev_code = ev_code + ";\n" + prev_ev_code + elem.set(self.options.when, el_ev_code) + +if __name__ == '__main__': + TransmitAttribute().run() diff --git a/share/extensions/webslicer_create_group.inx b/share/extensions/webslicer_create_group.inx new file mode 100644 index 0000000..33053fb --- /dev/null +++ b/share/extensions/webslicer_create_group.inx @@ -0,0 +1,37 @@ + + + Set a layout group + org.inkscape.web.slicer.create_group + webslicer_effect.py + + + + + + + + + + + + + + + + + + + + + + all + + + + + + + + diff --git a/share/extensions/webslicer_create_group.py b/share/extensions/webslicer_create_group.py new file mode 100755 index 0000000..59e9cd8 --- /dev/null +++ b/share/extensions/webslicer_create_group.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# +# Copyright (C) 2010 Aurelio A. Heckert, aurium (a) gmail dot com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +"""Create webslicer group""" + +import inkex +from inkex.localization import inkex_gettext as _ +from webslicer_effect import WebSlicerMixin + +class CreateGroup(WebSlicerMixin, inkex.EffectExtension): + """Create new webslicer group""" + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("--html-id", dest="html_id") + pars.add_argument("--html-class", dest="html_class") + pars.add_argument("--width-unity", dest="width_unity") + pars.add_argument("--height-unity", dest="height_unity") + pars.add_argument("--bg-color", dest="bg_color") + + def effect(self): + if not self.svg.selected: + raise inkex.AbortExtension(_('You must to select some "Slicer rectangles" ' + 'or other "Layout groups".')) + + base_elements = self.get_slicer_layer().descendants() + for key, node in self.svg.selected.id_dict().items(): + if node not in base_elements: + raise inkex.AbortExtension(_('The element "{}" is not in the Web Slicer layer'.format(key))) + g_parent = node.getparent() + + group = g_parent.add(inkex.Group()) + desc = group.add(inkex.Desc()) + desc.text = self.get_conf_text_from_list( + ['html_id', 'html_class', 'width_unity', 'height_unity', 'bg_color']) + + for node in self.svg.selected.values(): + group.insert(1, node) + + +if __name__ == '__main__': + CreateGroup().run() diff --git a/share/extensions/webslicer_create_rect.inx b/share/extensions/webslicer_create_rect.inx new file mode 100644 index 0000000..1a2c99c --- /dev/null +++ b/share/extensions/webslicer_create_rect.inx @@ -0,0 +1,71 @@ + + + Create a slicer rectangle + org.inkscape.web.slicer.create_rect + webslicer_effect.py + + + + + + + 96 + + + + + + + + + 85 + + + + + + + + + 256 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + + + + + + + + diff --git a/share/extensions/webslicer_create_rect.py b/share/extensions/webslicer_create_rect.py new file mode 100755 index 0000000..9ce1bd9 --- /dev/null +++ b/share/extensions/webslicer_create_rect.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# +# Copyright (C) 2010 Aurelio A. Heckert, aurium (a) gmail dot com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +from lxml import etree + +import inkex +from webslicer_effect import WebSlicerMixin, is_empty + +class CreateRect(WebSlicerMixin, inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("--name") + pars.add_argument("--format", default="png") + pars.add_argument("--dpi", type=float) + pars.add_argument("--dimension") + pars.add_argument("--bg-color") + pars.add_argument("--quality", type=int) + pars.add_argument("--gif-type") + pars.add_argument("--palette-size", type=int) + pars.add_argument("--html-id") + pars.add_argument("--html-class") + pars.add_argument("--layout-disposition") + pars.add_argument("--layout-position-anchor") + # inkscape param workaround + pars.add_argument("--tab") + + def unique_slice_name(self): + name = self.options.name + el = self.svg.xpath('//*[@id="' + name + '"]') + if len(el) > 0: + if name[-3:] == '-00': + name = name[:-3] + num = 0 + num_s = '00' + while len(el) > 0: + num += 1 + num_s = str(num) + if len(num_s) == 1: + num_s = '0' + num_s + el = self.svg.xpath('//*[@id="' + name + '-' + num_s + '"]') + self.options.name = name + '-' + num_s + + def validate_options(self): + self.options.format = self.options.format.lower() + if not is_empty(self.options.dimension): + self.options.dimension + + def effect(self): + scale = self.svg.unittouu('1px') # convert to document units + self.validate_options() + layer = self.get_slicer_layer(True) + # TODO: get selected elements to define location and size + rect = etree.SubElement(layer, 'rect') + if is_empty(self.options.name): + self.options.name = 'slice-00' + self.unique_slice_name() + rect.set('id', self.options.name) + rect.set('fill', 'red') + rect.set('opacity', '0.5') + rect.set('x', str(-scale * 100)) + rect.set('y', str(-scale * 100)) + rect.set('width', str(scale * 200)) + rect.set('height', str(scale * 200)) + desc = etree.SubElement(rect, 'desc') + conf_txt = "format:" + self.options.format + "\n" + if not is_empty(self.options.dpi): + conf_txt += "dpi:" + str(self.options.dpi) + "\n" + if not is_empty(self.options.html_id): + conf_txt += "html-id:" + self.options.html_id + desc.text = self.get_conf_text_from_list(self.get_conf_list()) + + def get_conf_list(self): + conf_list = ['format'] + if self.options.format == 'gif': + conf_list.extend(['gif_type', 'palette_size']) + if self.options.format == 'jpg': + conf_list.extend(['quality']) + conf_list.extend([ + 'dpi', 'dimension', + 'bg_color', 'html_id', 'html_class', + 'layout_disposition', 'layout_position_anchor' + ]) + return conf_list + +if __name__ == '__main__': + CreateRect().run() diff --git a/share/extensions/webslicer_effect.py b/share/extensions/webslicer_effect.py new file mode 100755 index 0000000..6ba11c9 --- /dev/null +++ b/share/extensions/webslicer_effect.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# +# Copyright (C) 2010 Aurelio A. Heckert, aurium (a) gmail dot com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +Common elements between webslicer extensions +""" + +import inkex +from inkex import Group + +def is_empty(val): + return val in ('', None) + + +class WebSlicerMixin(object): + def get_slicer_layer(self, force_creation=False): + # Test if webslicer-layer layer existis + layer = self.svg.getElement( + '//*[@id="webslicer-layer" and @inkscape:groupmode="layer"]') + if layer is None: + if force_creation: + # Create a new layer + layer = Group(id='webslicer-layer') + layer.set('inkscape:label', 'Web Slicer') + layer.set('inkscape:groupmode', 'layer') + self.document.getroot().append(layer) + else: + layer = None + return layer + + def get_conf_text_from_list(self, conf_atts): + conf_list = [] + for att in conf_atts: + if not is_empty(getattr(self.options, att)): + conf_list.append( + att.replace('_', '-') + ': ' + str(getattr(self.options, att)) + ) + return "\n".join(conf_list) diff --git a/share/extensions/webslicer_export.inx b/share/extensions/webslicer_export.inx new file mode 100644 index 0000000..53ab683 --- /dev/null +++ b/share/extensions/webslicer_export.inx @@ -0,0 +1,27 @@ + + + Export layout pieces and HTML+CSS code + org.inkscape.web.slicer.export + webslicer_effect.py + + + + false + true + + + + + + + all + + + + + + + + diff --git a/share/extensions/webslicer_export.py b/share/extensions/webslicer_export.py new file mode 100755 index 0000000..2149177 --- /dev/null +++ b/share/extensions/webslicer_export.py @@ -0,0 +1,417 @@ +#!/usr/bin/env python +# +# Copyright (C) 2010 Aurelio A. Heckert, aurium (a) gmail dot com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +import os +import sys +import tempfile + +from lxml import etree + +import inkex +from inkex.localization import inkex_gettext as _ +from webslicer_effect import WebSlicerMixin, is_empty + +class Export(WebSlicerMixin, inkex.OutputExtension): + def add_arguments(self, pars): + pars.add_argument("--tab") + pars.add_argument("--dir") + pars.add_argument("--create-dir", type=inkex.Boolean, dest="create_dir") + pars.add_argument("--with-code", type=inkex.Boolean, dest="with_code") + + svgNS = '{http://www.w3.org/2000/svg}' + + def validate_inputs(self): + # The user must supply a directory to export: + if is_empty(self.options.dir): + raise inkex.AbortExtension(_('You must give a directory to export the slices.')) + # No directory separator at the path end: + if self.options.dir[-1] == '/' or self.options.dir[-1] == '\\': + self.options.dir = self.options.dir[0:-1] + # Test if the directory exists: + if not os.path.exists(self.options.dir): + if self.options.create_dir: + # Try to create it: + try: + os.makedirs(self.options.dir) + except Exception as e: + raise inkex.AbortExtension(_("Can't create '{}': {}.".format(self.options.dir, e))) + else: + raise inkex.AbortExtension(_("Dir doesn't exist '{}'.".format(self.options.dir))) + # Check whether slicer layer exists (bug #1198826) + slicer_layer = self.get_slicer_layer() + if slicer_layer is None: + raise inkex.AbortExtension(_('No slicer layer found.')) + else: + self.unique_html_id(slicer_layer) + return None + + def get_cmd_output(self, cmd): + # This solution comes from Andrew Reedick + # http://mail.python.org/pipermail/python-win32/2008-January/006606.html + # This method replaces the commands.getstatusoutput() usage, with the + # hope to correct the windows exporting bug: + # https://bugs.launchpad.net/inkscape/+bug/563722 + if sys.platform != "win32": + cmd = '{ ' + cmd + '; }' + pipe = os.popen(cmd + ' 2>&1', 'r') + text = pipe.read() + sts = pipe.close() + if sts is None: + sts = 0 + if text[-1:] == '\n': + text = text[:-1] + return sts, text + + _html_ids = [] + + def unique_html_id(self, el): + for child in el.getchildren(): + if child.tag in [self.svgNS + 'rect', self.svgNS + 'path', + self.svgNS + 'circle', self.svgNS + 'g']: + conf = self.get_el_conf(child) + if conf['html-id'] in self._html_ids: + inkex.errormsg('You have more than one element with "{}" html-id.'.format(conf['html-id'])) + n = 2 + while (conf['html-id'] + "-" + str(n)) in self._html_ids: + n += 1 + conf['html-id'] += "-" + str(n) + self._html_ids.append(conf['html-id']) + self.save_conf(conf, child) + self.unique_html_id(child) + + def test_if_has_imagemagick(self): + (status, output) = self.get_cmd_output('convert --version') + self.has_magick = (status == 0 and 'ImageMagick' in output) + + def save(self, stream): + self.test_if_has_imagemagick() + error = self.validate_inputs() + if error: + return error + # Register the basic CSS code: + self.reg_css('body', 'text-align', 'center') + # Create the temporary SVG with invisible Slicer layer to export image pieces + self.create_the_temporary_svg() + # Start what we really want! + self.export_chids_of(self.get_slicer_layer()) + # Write the HTML and CSS files if asked for: + if self.options.with_code: + self.make_html_file() + self.make_css_file() + # Delete the temporary SVG with invisible Slicer layer + self.delete_the_temporary_svg() + # TODO: prevent inkex to return svg code to update Inkscape + + def make_html_file(self): + f = open(os.path.join(self.options.dir, 'layout.html'), 'w') + f.write( + '\n\n' + \ + ' Web Layout Testing\n' + \ + ' \n' + \ + '\n\n' + \ + self.html_code() + \ + '

\n' + \ + ' This HTML code is not done to the web.
\n' + \ + ' The automatic HTML and CSS code are only a helper.' + \ + '

\n\n') + f.close() + + def make_css_file(self): + f = open(os.path.join(self.options.dir, 'style.css'), 'w') + f.write( + '/*\n' + \ + '** This CSS code is not done to the web.\n' + \ + '** The automatic HTML and CSS code are only a helper.\n' + \ + '*/\n' + \ + self.css_code()) + f.close() + + def create_the_temporary_svg(self): + (ref, self.tmp_svg) = tempfile.mkstemp('.svg') + layer = self.get_slicer_layer() + current_style = ('style' in layer.attrib) and layer.attrib['style'] or '' + layer.attrib['style'] = 'display:none' + self.document.write(self.tmp_svg) + layer.attrib['style'] = current_style + + def delete_the_temporary_svg(self): + try: + os.remove(self.tmp_svg) + except (IOError, OSError, PermissionError): + pass + + noid_element_count = 0 + + def get_el_conf(self, el): + desc = el.find(self.svgNS + 'desc') + conf = {} + if desc is None: + desc = etree.SubElement(el, 'desc') + if desc.text is None: + desc.text = '' + for line in desc.text.split("\n"): + if line.find(':') > 0: + line = line.split(':') + conf[line[0].strip()] = line[1].strip() + if not 'html-id' in conf: + if el == self.get_slicer_layer(): + return {'html-id': '#body#'} + else: + self.noid_element_count += 1 + conf['html-id'] = 'element-' + str(self.noid_element_count) + desc.text += "\nhtml-id:" + conf['html-id'] + return conf + + def save_conf(self, conf, el): + desc = el.find('{http://www.w3.org/2000/svg}desc') + if desc is not None: + conf_a = [] + for k in conf: + conf_a.append(k + ' : ' + conf[k]) + desc.text = "\n".join(conf_a) + + def export_chids_of(self, parent): + parent_id = self.get_el_conf(parent)['html-id'] + for el in parent.getchildren(): + el_conf = self.get_el_conf(el) + if el.tag == self.svgNS + 'g': + if self.options.with_code: + self.register_group_code(el, el_conf) + else: + self.export_chids_of(el) + if el.tag in [self.svgNS + 'rect', self.svgNS + 'path', self.svgNS + 'circle']: + if self.options.with_code: + self.register_unity_code(el, el_conf, parent_id) + self.export_img(el, el_conf) + + def register_group_code(self, group, conf): + self.reg_html('div', group) + selec = '#' + conf['html-id'] + self.reg_css(selec, 'position', 'absolute') + geometry = self.get_relative_el_geometry(group) + self.reg_css(selec, 'top', str(int(geometry['y'])) + 'px') + self.reg_css(selec, 'left', str(int(geometry['x'])) + 'px') + self.reg_css(selec, 'width', str(int(geometry['w'])) + 'px') + self.reg_css(selec, 'height', str(int(geometry['h'])) + 'px') + self.export_chids_of(group) + + def __validate_slice_conf(self, conf): + if not 'layout-disposition' in conf: + conf['layout-disposition'] = 'bg-el-norepeat' + if not 'layout-position-anchor' in conf: + conf['layout-position-anchor'] = 'mc' + return conf + + def register_unity_code(self, el, conf, parent_id): + conf = self.__validate_slice_conf(conf) + css_selector = '#' + conf['html-id'] + bg_repeat = 'no-repeat' + img_name = self.img_name(el, conf) + if conf['layout-disposition'][0:2] == 'bg': + if conf['layout-disposition'][0:9] == 'bg-parent': + if parent_id == '#body#': + css_selector = 'body' + else: + css_selector = '#' + parent_id + if conf['layout-disposition'] == 'bg-parent-repeat': + bg_repeat = 'repeat' + if conf['layout-disposition'] == 'bg-parent-repeat-x': + bg_repeat = 'repeat-x' + if conf['layout-disposition'] == 'bg-parent-repeat-y': + bg_repeat = 'repeat-y' + lay_anchor = conf['layout-position-anchor'] + if lay_anchor == 'tl': + lay_anchor = 'top left' + if lay_anchor == 'tc': + lay_anchor = 'top center' + if lay_anchor == 'tr': + lay_anchor = 'top right' + if lay_anchor == 'ml': + lay_anchor = 'middle left' + if lay_anchor == 'mc': + lay_anchor = 'middle center' + if lay_anchor == 'mr': + lay_anchor = 'middle right' + if lay_anchor == 'bl': + lay_anchor = 'bottom left' + if lay_anchor == 'bc': + lay_anchor = 'bottom center' + if lay_anchor == 'br': + lay_anchor = 'bottom right' + self.reg_css(css_selector, 'background', + 'url("{}") {} {}'.format(img_name, bg_repeat, lay_anchor)) + else: # conf['layout-disposition'][0:9] == 'bg-el...' + self.reg_html('div', el) + self.reg_css(css_selector, 'background', + 'url("{}") {}'.format(img_name, 'no-repeat')) + self.reg_css(css_selector, 'position', 'absolute') + geo = self.get_relative_el_geometry(el, True) + self.reg_css(css_selector, 'top', geo['y']) + self.reg_css(css_selector, 'left', geo['x']) + self.reg_css(css_selector, 'width', geo['w']) + self.reg_css(css_selector, 'height', geo['h']) + else: # conf['layout-disposition'] == 'img...' + self.reg_html('img', el) + if conf['layout-disposition'] == 'img-pos': + self.reg_css(css_selector, 'position', 'absolute') + geo = self.get_relative_el_geometry(el) + self.reg_css(css_selector, 'left', str(geo['x']) + 'px') + self.reg_css(css_selector, 'top', str(geo['y']) + 'px') + if conf['layout-disposition'] == 'img-float-left': + self.reg_css(css_selector, 'float', 'right') + if conf['layout-disposition'] == 'img-float-right': + self.reg_css(css_selector, 'float', 'right') + + el_geo = {} + + def register_all_els_geometry(self): + ink_cmm = 'inkscape --query-all ' + self.tmp_svg + (status, output) = self.get_cmd_output(ink_cmm) + self.el_geo = {} + if status == 0: + for el in output.split('\n'): + el = el.split(',') + if len(el) == 5: + self.el_geo[el[0]] = {'x': float(el[1]), 'y': float(el[2]), + 'w': float(el[3]), 'h': float(el[4])} + doc_w = self.svg.unittouu(self.document.getroot().get('width')) + doc_h = self.svg.unittouu(self.document.getroot().get('height')) + self.el_geo['webslicer-layer'] = {'x': 0, 'y': 0, 'w': doc_w, 'h': doc_h} + + def get_relative_el_geometry(self, el, value_to_css=False): + # This method return a dictionary with x, y, w and h keys. + # All values are float, if value_to_css is False, otherwise + # that is a string ended with "px". The x and y values are + # relative to parent position. + if not self.el_geo: + self.register_all_els_geometry() + parent = el.getparent() + geometry = self.el_geo[el.attrib['id']] + geometry['x'] -= self.el_geo[parent.attrib['id']]['x'] + geometry['y'] -= self.el_geo[parent.attrib['id']]['y'] + if value_to_css: + for k in geometry: + geometry[k] = str(int(geometry[k])) + 'px' + return geometry + + def img_name(self, el, conf): + return el.attrib['id'] + '.' + conf['format'] + + def export_img(self, el, conf): + if not self.has_magick: + inkex.errormsg(_('You must install the ImageMagick to get JPG and GIF.')) + conf['format'] = 'png' + img_name = os.path.join(self.options.dir, self.img_name(el, conf)) + img_name_png = img_name + if conf['format'] != 'png': + img_name_png = img_name + '.png' + opts = '' + if 'bg-color' in conf: + opts += ' -b "' + conf['bg-color'] + '" -y 1' + if 'dpi' in conf: + opts += ' -d ' + conf['dpi'] + if 'dimension' in conf: + dim = conf['dimension'].split('x') + opts += ' -w ' + dim[0] + ' -h ' + dim[1] + (status, output) = self.get_cmd_output('inkscape {} -i "{}" -e "{}" "{}"'.format(opts, el.attrib['id'], img_name_png, self.tmp_svg)) + if conf['format'] != 'png': + opts = '' + if conf['format'] == 'jpg': + opts += ' -quality ' + str(conf['quality']) + if conf['format'] == 'gif': + if conf['gif-type'] == 'grayscale': + opts += ' -type Grayscale' + else: + opts += ' -type Palette' + if conf['palette-size'] < 256: + opts += ' -colors ' + str(conf['palette-size']) + (status, output) = self.get_cmd_output('convert "{}" {} "{}"'.format(img_name_png, opts, img_name)) + if status != 0: + inkex.errormsg('Upss... ImageMagick error: ' + output) + os.remove(img_name_png) + + _html = {} + + def reg_html(self, el_tag, el): + parent = el.getparent() + parent_id = self.get_el_conf(parent)['html-id'] + if parent == self.get_slicer_layer(): + parent_id = 'body' + conf = self.get_el_conf(el) + el_id = conf['html-id'] + if 'html-class' in conf: + el_class = conf['html-class'] + else: + el_class = '' + if not parent_id in self._html: + self._html[parent_id] = [] + self._html[parent_id].append({'tag': el_tag, 'id': el_id, 'class': el_class}) + + def html_code(self, parent='body', ident=' '): + # inkex.errormsg( self._html ) + if not parent in self._html: + return '' + code = '' + for el in self._html[parent]: + child_code = self.html_code(el['id'], ident + ' ') + tag_class = '' + if el['class'] != '': + tag_class = ' class="' + el['class'] + '"' + if el['tag'] == 'img': + code += ident + '\n' + else: + code += ident + '<' + el['tag'] + ' id="' + el['id'] + '"' + tag_class + '>\n' + if child_code: + code += child_code + else: + code += ident + ' Element ' + el['id'] + '\n' + code += ident + '\n' + return code + + _css = [] + + def reg_css(self, selector, att, val): + pos = i = -1 + for s in self._css: + i += 1 + if s['selector'] == selector: + pos = i + if pos == -1: + pos = i + 1 + self._css.append({'selector': selector, 'atts': {}}) + if not att in self._css[pos]['atts']: + self._css[pos]['atts'][att] = [] + self._css[pos]['atts'][att].append(val) + + def css_code(self): + code = '' + for s in self._css: + code += '\n' + s['selector'] + ' {\n' + for att in s['atts']: + val = s['atts'][att] + if att == 'background' and len(val) > 1: + code += ' /* the next attribute needs a CSS3 enabled browser */\n' + code += ' ' + att + ': ' + (', '.join(val)) + ';\n' + code += '}\n' + return code + +if __name__ == '__main__': + Export().run() diff --git a/share/extensions/whirl.inx b/share/extensions/whirl.inx new file mode 100644 index 0000000..3ce9b07 --- /dev/null +++ b/share/extensions/whirl.inx @@ -0,0 +1,16 @@ + + + Whirl + org.ekips.filter.whirl + 5.0 + true + + path + + + + + + diff --git a/share/extensions/whirl.py b/share/extensions/whirl.py new file mode 100755 index 0000000..e8000ab --- /dev/null +++ b/share/extensions/whirl.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2005 Aaron Spike, aaron@ekips.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +"""Whirl path extension (modify path)""" + +import math +import inkex + +class Whirl(inkex.EffectExtension): + """Modify a path by twisting the nodes around a point""" + def add_arguments(self, pars): + pars.add_argument("-t", "--whirl", type=float,\ + default=1.0, help="amount of whirl") + pars.add_argument("-r", "--rotation", type=inkex.Boolean,\ + default=True, help="direction of rotation") + + def effect(self): + view_center = self.svg.namedview.center + rotation = 1 if self.options.rotation else -1 + whirl = self.options.whirl / 1000 + for node in self.svg.selection.filter(inkex.PathElement).values(): + self.whirl_node(view_center, rotation, whirl, node) + + @staticmethod + def whirl_node(center, direction, ammount, node): + """Apply a whirl to a path given the center, direction and amount""" + path = node.path.to_superpath() + for sub in path: + for csp in sub: + for point in csp: + point[0] -= center[0] + point[1] -= center[1] + dist = math.sqrt((point[0] ** 2) + (point[1] ** 2)) + if dist != 0: + art = direction * dist * ammount + theta = math.atan2(point[1], point[0]) + art + point[0] = (dist * math.cos(theta)) + point[1] = (dist * math.sin(theta)) + point[0] += center[0] + point[1] += center[1] + node.path = path + + +if __name__ == '__main__': + Whirl().run() diff --git a/share/extensions/wireframe_sphere.inx b/share/extensions/wireframe_sphere.inx new file mode 100644 index 0000000..84561bb --- /dev/null +++ b/share/extensions/wireframe_sphere.inx @@ -0,0 +1,20 @@ + + + Wireframe Sphere + org.inkscape.render.wireframe_sphere + 19 + 24 + 35 + 4 + 100.0 + false + + all + + + + + + diff --git a/share/extensions/wireframe_sphere.py b/share/extensions/wireframe_sphere.py new file mode 100755 index 0000000..c26a06e --- /dev/null +++ b/share/extensions/wireframe_sphere.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2009 John Beard john.j.beard@gmail.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This extension renders a wireframe sphere constructed from lines of latitude +and lines of longitude. + +The number of lines of latitude and longitude is independently variable. Lines +of latitude and longtude are in separate subgroups. The whole figure is also in +its own group. + +The whole sphere can be tilted towards or away from the veiwer by a given +number of degrees. If the whole sphere is then rotated normally in Inkscape, +any position can be achieved. + +There is an option to hide the lines at the back of the sphere, as if the +sphere were opaque. +""" +# FIXME: Lines of latitude only have an approximation of the function needed +# to hide the back portion. If you can derive the proper equation, +# please add it in. +# Line of longitude have the exact method already. +# Workaround: Use the Inkscape ellipse tool to edit the start and end +# points of the lines of latitude to end at the horizon circle. +# +# TODO: Add support for odd numbers of lines of longitude. This means breaking +# the line at the poles, and having two half ellipses for each line. +# The angles at which the ellipse arcs pass the poles are not constant and +# need to be derived before this can be implemented. +# TODO: Add support for prolate and oblate spheroids +# +# 0.10 2009-10-25 First version. Basic spheres supported. +# Hidden lines of latitude still not properly calculated. +# Prolate and oblate spheroids not considered. + +from math import acos, atan, cos, pi, sin, tan + +import inkex + +# add a tiny value to the ellipse radii, so that if we get a +# zero radius, the ellipse still shows up as a line +EPSILON = 0.001 + + +class WireframeSphere(inkex.GenerateExtension): + """Writeframe extension, generate a wireframe""" + container_label = 'WireframeSphere' + + def container_transform(self): + transform = super(WireframeSphere, self).container_transform() + if self.options.TILT < 0: + transform *= inkex.Transform(scale=(1, -1)) + return transform + + def add_arguments(self, pars): + pars.add_argument("--num_lat", type=int, dest="NUM_LAT", default=19) + pars.add_argument("--num_long", type=int, dest="NUM_LONG", default=24) + pars.add_argument("--radius", type=float, dest="RADIUS", default=100.0) + pars.add_argument("--tilt", type=float, dest="TILT", default=35.0) + pars.add_argument("--rotation", type=float, dest="ROT_OFFSET", default=4) + pars.add_argument("--hide_back", type=inkex.Boolean, dest="HIDE_BACK", default=False) + + def generate(self): + opt = self.options + + # PARAMETER PROCESSING + if opt.NUM_LONG % 2 != 0: # lines of longitude are odd : abort + inkex.errormsg('Please enter an even number of lines of longitude.') + return + + radius = self.svg.unittouu(str(opt.RADIUS) + 'px') + tilt = abs(opt.TILT) * (pi / 180) # Convert to radians + rotate = opt.ROT_OFFSET * pi / 180 # Convert to radians + + # only process longitudes if we actually want some + if opt.NUM_LONG > 0: + # Yieled elements are added to generated container + yield self.longitude_lines(opt.NUM_LONG, tilt, radius, rotate) + + if opt.NUM_LAT > 0: + # Yieled elements are added to generated container + # Account for the fact that we loop over N-1 elements + yield self.latitude_lines(opt.NUM_LAT + 1, tilt, radius) + + # THE HORIZON CIRCLE - circle, centred on the sphere centre + yield self.draw_ellipse((radius, radius), (0, 0)) + + def longitude_lines(self, number, tilt, radius, rotate): + """Add lines of latitude as a group""" + # GROUP FOR THE LINES OF LONGITUDE + grp_long = inkex.Group() + grp_long.set('inkscape:label', 'Lines of Longitude') + + # angle between neighbouring lines of longitude in degrees + #delta_long = 360.0 / number + + for i in range(0, number // 2): + # The longitude of this particular line in radians + long_angle = rotate + (i * (360.0 / number)) * (pi / 180.0) + if long_angle > pi: + long_angle -= 2 * pi + # the rise is scaled by the sine of the tilt + # length = sqrt(width*width+height*height) #by pythagorean theorem + # inverse = sin(acos(length/so.RADIUS)) + inverse = abs(sin(long_angle)) * cos(tilt) + + rads = (radius * inverse + EPSILON, radius) + + # The rotation of the ellipse to get it to pass through the pole (degs) + rotation = atan( + (radius * sin(long_angle) * sin(tilt)) / + (radius * cos(long_angle)) + ) * (180.0 / pi) + + # remove the hidden side of the ellipses if required + # this is always exactly half the ellipse, but we need to find out which half + start_end = (0, 2 * pi) # Default start and end angles -> full ellipse + if self.options.HIDE_BACK: + if long_angle <= pi / 2: # cut out the half ellispse that is hidden + start_end = (pi / 2, 3 * pi / 2) + else: + start_end = (3 * pi / 2, pi / 2) + + # finally, draw the line of longitude + # the centre is always at the centre of the sphere + elem = grp_long.add(self.draw_ellipse(rads, (0, 0), start_end)) + # the rotation will be applied about the group centre (the centre of the sphere) + elem.transform = inkex.Transform(rotate=(rotation,)) + return grp_long + + def latitude_lines(self, number, tilt, radius): + """Add lines of latitude as a group""" + # GROUP FOR THE LINES OF LATITUDE + grp_lat = inkex.Group() + grp_lat.set('inkscape:label', 'Lines of Latitude') + + # Angle between the line of latitude (subtended at the centre) + delta_lat = 180.0 / number + + for i in range(1, number): + # The angle of this line of latitude (from a pole) + lat_angle = ((delta_lat * i) * (pi / 180)) + + # The width of the LoLat (no change due to projection) + # The projected height of the line of latitude + rads = ( + radius * sin(lat_angle), # major + (radius * sin(lat_angle) * sin(tilt)) + EPSILON, # minor + ) + + # The x position is the sphere center, The projected y position of the LoLat + pos = (0, radius * cos(lat_angle) * cos(tilt)) + + if self.options.HIDE_BACK: + if lat_angle > tilt: # this LoLat is partially or fully visible + if lat_angle > pi - tilt: # this LoLat is fully visible + grp_lat.add(self.draw_ellipse(rads, pos)) + else: # this LoLat is partially visible + proportion = -(acos(tan(lat_angle - pi / 2) \ + / tan(pi / 2 - tilt))) / pi + 1 + # make the start and end angles (mirror image around pi/2) + start_end = (pi / 2 - proportion * pi, pi / 2 + proportion * pi) + grp_lat.add(self.draw_ellipse(rads, pos, start_end)) + + else: # just draw the full lines of latitude + grp_lat.add(self.draw_ellipse(rads, pos)) + return grp_lat + + def draw_ellipse(self, r_xy, c_xy, start_end=(0, 2 * pi)): + """Creates an elipse with all the required sodipodi attributes""" + path = inkex.PathElement() + path.update(**{ + 'style': {'stroke': '#000000', + 'stroke-width': str(self.svg.unittouu('1px')), + 'fill': 'none'}, + 'sodipodi:cx': str(c_xy[0]), + 'sodipodi:cy': str(c_xy[1]), + 'sodipodi:rx': str(r_xy[0]), + 'sodipodi:ry': str(r_xy[1]), + 'sodipodi:start': str(start_end[0]), + 'sodipodi:end': str(start_end[1]), + 'sodipodi:open': 'true', # all ellipse sectors we will draw are open + 'sodipodi:type': 'arc', + }) + return path + +if __name__ == '__main__': + WireframeSphere().run() diff --git a/share/extensions/xaml2svg.inx b/share/extensions/xaml2svg.inx new file mode 100644 index 0000000..a6ccc94 --- /dev/null +++ b/share/extensions/xaml2svg.inx @@ -0,0 +1,15 @@ + + + XAML Input + org.inkscape.input.xaml + + .xaml + text/xml+xaml + Microsoft XAML (*.xaml) + Microsoft's GUI definition format + org.inkscape.output.xaml + + + xaml2svg.xsl + + diff --git a/share/extensions/xaml2svg.xsl b/share/extensions/xaml2svg.xsl new file mode 100644 index 0000000..8c0e4a5 --- /dev/null +++ b/share/extensions/xaml2svg.xsl @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + visible + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + start + middle + end + + + + hanging + + + + + + + + + + + + diff --git a/share/extensions/xaml2svg/animation.xsl b/share/extensions/xaml2svg/animation.xsl new file mode 100644 index 0000000..e51a430 --- /dev/null +++ b/share/extensions/xaml2svg/animation.xsl @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + remove + freeze + + + + + + + + + + + + + + + x2 + opacity + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/xaml2svg/brushes.xsl b/share/extensions/xaml2svg/brushes.xsl new file mode 100644 index 0000000..884d6db --- /dev/null +++ b/share/extensions/xaml2svg/brushes.xsl @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + userSpaceOnUse + + + + + + + boundingBox + + + + + + + userSpaceOnUse + 0 + 0 + + + + + boundingBox + 0 + 0 + 100% + 100% + + + + + + + + userSpaceOnUse + + + + + + + boundingBox + + + + + + + boundingBox + 0 + 0 + 100% + 100% + + + opacity:1 + optimizeSpeed + + + + + + + + + + userSpaceOnUse + + + + + + + boundingBox + + + + + + + userSpaceOnUse + 0 + 0 + + + + + boundingBox + 0 + 0 + 100% + 100% + + + + + + + + + + + + diff --git a/share/extensions/xaml2svg/canvas.xsl b/share/extensions/xaml2svg/canvas.xsl new file mode 100644 index 0000000..e67f0e2 --- /dev/null +++ b/share/extensions/xaml2svg/canvas.xsl @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/xaml2svg/geometry.xsl b/share/extensions/xaml2svg/geometry.xsl new file mode 100644 index 0000000..c28e6a6 --- /dev/null +++ b/share/extensions/xaml2svg/geometry.xsl @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C + + + + + + + + + + + + + + + + + + + + + + + + + + + + + L + + + + + + + + + + + + + + + + + + + + + + + + + Q + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/xaml2svg/properties.xsl b/share/extensions/xaml2svg/properties.xsl new file mode 100644 index 0000000..1cfb8ff --- /dev/null +++ b/share/extensions/xaml2svg/properties.xsl @@ -0,0 +1,286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/xaml2svg/shapes.xsl b/share/extensions/xaml2svg/shapes.xsl new file mode 100644 index 0000000..c28b027 --- /dev/null +++ b/share/extensions/xaml2svg/shapes.xsl @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + nonzero + evenodd + + + + + + + + + + + + + + + nonzero + evenodd + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + nonzero + evenodd + + + + + + + + + + + + + + nonzero + evenodd + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/extensions/xaml2svg/transform.xsl b/share/extensions/xaml2svg/transform.xsl new file mode 100644 index 0000000..3d41cf4 --- /dev/null +++ b/share/extensions/xaml2svg/transform.xsl @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3