From 493870b38da428c246db5e7a4dd51dfebc8528a0 Mon Sep 17 00:00:00 2001 From: Jonathan Neuhauser Date: Fri, 4 Feb 2022 18:09:08 +0100 Subject: [PATCH] apply black formatting and add CI run --- .gitlab-ci.yml | 8 + .pre-commit-config.yaml | 5 + .pylintrc | 2 +- addnodes.py | 43 +- barcode/Base.py | 82 +- barcode/BaseEan.py | 88 +- barcode/Code128.py | 168 +- barcode/Code25i.py | 38 +- barcode/Code39.py | 104 +- barcode/Code39Ext.py | 43 +- barcode/Code93.py | 115 +- barcode/Ean13.py | 10 +- barcode/Ean2.py | 11 +- barcode/Ean5.py | 25 +- barcode/Ean8.py | 8 +- barcode/Rm4scc.py | 102 +- barcode/Upca.py | 7 +- barcode/Upce.py | 55 +- barcode/__init__.py | 11 +- color_HSL_adjust.py | 19 +- color_blackandwhite.py | 9 +- color_brighter.py | 7 +- color_custom.py | 40 +- color_darker.py | 7 +- color_desaturate.py | 11 +- color_grayscale.py | 5 +- color_lesshue.py | 5 +- color_lesslight.py | 5 +- color_lesssaturation.py | 5 +- color_list.py | 5 +- color_morehue.py | 5 +- color_morelight.py | 5 +- color_moresaturation.py | 5 +- color_negative.py | 5 +- color_randomize.py | 39 +- color_removeblue.py | 5 +- color_removegreen.py | 5 +- color_removered.py | 5 +- color_replace.py | 41 +- color_rgbbarrel.py | 5 +- convert2dashes.py | 30 +- dhw_input.py | 33 +- dimension.py | 77 +- doc_ai_convert.py | 23 +- docinfo.py | 23 +- docs/_templates/versions.html | 27 + docs/conf.py | 24 +- dpiswitcher.py | 211 +-- draw_from_triangle.py | 270 ++-- dxf12_outlines.py | 46 +- dxf_input.py | 1375 ++++++++++++----- dxf_outlines.py | 218 ++- edge3d.py | 74 +- export_gimp_palette.py | 25 +- extension-manager-bootstrap.py | 43 +- extrude.py | 87 +- fig_input.py | 9 +- flatten.py | 13 +- foldablebox.py | 197 +-- fractalize.py | 35 +- frame.py | 139 +- funcplot.py | 158 +- generate_voronoi.py | 67 +- gimp_xcf.py | 77 +- grid_cartesian.py | 197 ++- grid_isometric.py | 498 ++++-- grid_polar.py | 245 ++- guides_creator.py | 164 +- guillotine.py | 35 +- handles.py | 30 +- hershey.py | 1018 ++++++------ hpgl_decoder.py | 45 +- hpgl_encoder.py | 377 +++-- hpgl_input.py | 32 +- hpgl_output.py | 71 +- image_attributes.py | 79 +- image_embed.py | 70 +- image_extract.py | 60 +- ink2canvas.py | 12 +- ink2canvas_lib/canvas.py | 20 +- ink2canvas_lib/svg.py | 38 +- inkex/__init__.py | 3 +- inkex/base.py | 126 +- inkex/bezier.py | 153 +- inkex/colors.py | 439 +++--- inkex/command.py | 89 +- inkex/deprecated-simple/bezmisc.py | 4 +- inkex/deprecated-simple/cubicsuperpath.py | 6 + inkex/deprecated-simple/ffgeom.py | 27 +- inkex/deprecated-simple/run_command.py | 11 +- inkex/deprecated-simple/simplepath.py | 41 +- inkex/deprecated-simple/simplestyle.py | 12 +- inkex/deprecated-simple/simpletransform.py | 20 +- inkex/deprecated.py | 308 ++-- inkex/elements/__init__.py | 46 +- inkex/elements/_base.py | 172 ++- inkex/elements/_filters.py | 152 +- inkex/elements/_groups.py | 29 +- inkex/elements/_image.py | 4 +- inkex/elements/_meta.py | 89 +- inkex/elements/_polygons.py | 334 ++-- inkex/elements/_selected.py | 46 +- inkex/elements/_svg.py | 76 +- inkex/elements/_text.py | 63 +- inkex/elements/_use.py | 10 +- inkex/elements/_utils.py | 69 +- inkex/extensions.py | 150 +- inkex/inkscape_env.py | 11 +- inkex/inx.py | 136 +- inkex/localization.py | 11 +- inkex/paths.py | 436 ++++-- inkex/ports.py | 39 +- inkex/properties.py | 395 +++-- inkex/styles.py | 112 +- inkex/tester/__init__.py | 150 +- inkex/tester/decorators.py | 5 +- inkex/tester/filters.py | 56 +- inkex/tester/inx.py | 105 +- inkex/tester/mock.py | 183 ++- inkex/tester/svg.py | 20 +- inkex/tester/word.py | 12 +- inkex/tester/xmldiff.py | 24 +- inkex/transforms.py | 222 ++- inkex/turtle.py | 41 +- inkex/tween.py | 207 ++- inkex/units.py | 88 +- inkex/utils.py | 81 +- inkscape_follow_link.py | 9 +- inkwebeffect.py | 6 +- interp.py | 79 +- interp_att_g.py | 108 +- jessyink_autotexts.py | 9 +- jessyink_effects.py | 43 +- jessyink_export.py | 28 +- jessyink_install.py | 38 +- jessyink_key_bindings.py | 84 +- jessyink_master_slide.py | 27 +- jessyink_mouse_handler.py | 13 +- jessyink_summary.py | 39 +- jessyink_transitions.py | 37 +- jessyink_uninstall.py | 18 +- jessyink_video.py | 41 +- jessyink_view.py | 46 +- jitternodes.py | 34 +- layer2png.py | 73 +- layers2svgfont.py | 17 +- layout_nup.py | 301 ++-- lindenmayer.py | 86 +- lorem_ipsum.py | 378 ++--- markers_strokepaint.py | 96 +- measure.py | 210 ++- media_zip.py | 61 +- merge_styles.py | 18 +- motion.py | 78 +- new_glyph_layer.py | 12 +- next_glyph_layer.py | 11 +- nicechart.py | 337 ++-- other/gcodetools | 2 +- output_scour.py | 41 +- param_curves.py | 115 +- path_envelope.py | 68 +- path_mesh_m2p.py | 154 +- path_mesh_p2m.py | 13 +- path_number_nodes.py | 54 +- path_to_absolute.py | 5 +- pathalongpath.py | 76 +- pathmodifier.py | 36 +- pathscatter.py | 120 +- pdflatex.py | 50 +- perfectboundcover.py | 45 +- perspective.py | 90 +- pixelsnap.py | 166 +- plotter.py | 146 +- polyhedron_3d.py | 160 +- prepare_file_save_as.py | 14 +- previous_glyph_layer.py | 7 +- print_win32_vector.py | 151 +- printing_marks.py | 583 ++++--- ps_input.py | 30 +- raster_output_jpg.py | 20 +- raster_output_png.py | 39 +- raster_output_tiff.py | 18 +- raster_output_webp.py | 20 +- render_alphabetsoup.py | 135 +- render_barcode.py | 14 +- render_barcode_datamatrix.py | 218 ++- render_barcode_qrcode.py | 814 +++++----- render_gear_rack.py | 25 +- render_gears.py | 44 +- replace_font.py | 51 +- restack.py | 23 +- rtree.py | 44 +- rubberstretch.py | 40 +- scribus_export_pdf.py | 110 +- setup.py | 62 +- setup_typography_canvas.py | 21 +- spirograph.py | 120 +- straightseg.py | 51 +- svgcalendar.py | 442 ++++-- svgfont2layers.py | 25 +- synfig_fileformat.py | 168 +- synfig_output.py | 343 ++-- synfig_prepare.py | 68 +- tar_layers.py | 13 +- template.py | 23 +- template_dvd_cover.py | 23 +- template_seamless_pattern.py | 35 +- tests/__init__.py | 2 +- tests/add_pylint.py | 35 +- tests/test_addnodes.py | 32 +- tests/test_color_HSL_adjust.py | 41 +- tests/test_color_blackandwhite.py | 9 +- tests/test_color_brighter.py | 1 + tests/test_color_custom.py | 23 +- tests/test_color_darker.py | 1 + tests/test_color_desaturate.py | 1 + tests/test_color_grayscale.py | 1 + tests/test_color_lesshue.py | 5 +- tests/test_color_lesslight.py | 5 +- tests/test_color_lesssaturation.py | 5 +- tests/test_color_list.py | 3 +- tests/test_color_morehue.py | 5 +- tests/test_color_morelight.py | 5 +- tests/test_color_moresaturation.py | 5 +- tests/test_color_negative.py | 1 + tests/test_color_randomize.py | 60 +- tests/test_color_removeblue.py | 1 + tests/test_color_removegreen.py | 1 + tests/test_color_removered.py | 1 + tests/test_color_replace.py | 24 +- tests/test_color_rgbbarrel.py | 1 + tests/test_convert2dashes.py | 12 +- tests/test_deprecated_simple.py | 187 ++- tests/test_dhw_input.py | 8 +- tests/test_dimension.py | 10 +- tests/test_doc_ai_convert.py | 7 +- tests/test_docinfo.py | 5 +- tests/test_dpiswitcher.py | 8 +- tests/test_draw_from_triangle.py | 3 +- tests/test_dxf12_outlines.py | 10 +- tests/test_dxf_input.py | 39 +- tests/test_dxf_outlines.py | 7 +- tests/test_edge3d.py | 17 +- tests/test_export_gimp_palette.py | 5 +- tests/test_extrude.py | 43 +- tests/test_fig_input.py | 3 +- tests/test_flatten.py | 1 + tests/test_foldablebox.py | 7 +- tests/test_fractalize.py | 3 +- tests/test_frame.py | 96 +- tests/test_funcplot.py | 5 +- tests/test_generate_voronoi.py | 5 +- tests/test_gimp_xcf.py | 10 +- tests/test_grid_cartesian.py | 25 +- tests/test_grid_isometric.py | 1 + tests/test_grid_polar.py | 5 +- tests/test_guides_creator.py | 43 +- tests/test_guillotine.py | 43 +- tests/test_handles.py | 7 +- tests/test_hershey.py | 59 +- tests/test_hpgl_input.py | 3 +- tests/test_hpgl_output.py | 6 +- tests/test_image_attributes.py | 13 +- tests/test_image_embed.py | 8 +- tests/test_image_extract.py | 13 +- tests/test_ink2canvas_svg.py | 11 +- tests/test_inkex.py | 22 +- tests/test_inkex_base.py | 70 +- tests/test_inkex_bounding_box.py | 205 +-- tests/test_inkex_colors.py | 122 +- tests/test_inkex_command.py | 24 +- tests/test_inkex_cubic_paths.py | 75 +- tests/test_inkex_deprecated.py | 10 +- tests/test_inkex_elements.py | 594 ++++--- tests/test_inkex_elements_base.py | 343 ++-- tests/test_inkex_elements_filters.py | 12 +- tests/test_inkex_elements_selections.py | 69 +- tests/test_inkex_extensions.py | 44 +- ...test_inkex_extensions_GenerateExtension.py | 9 +- tests/test_inkex_inx.py | 106 +- tests/test_inkex_paths.py | 622 +++++--- tests/test_inkex_styles.py | 190 ++- tests/test_inkex_styles_complex.py | 199 ++- tests/test_inkex_svg.py | 317 ++-- tests/test_inkex_tester.py | 14 +- tests/test_inkex_transforms.py | 305 ++-- tests/test_inkex_tween.py | 148 +- tests/test_inkex_units.py | 79 +- tests/test_inkex_utils.py | 133 +- tests/test_inkscape_follow_link.py | 1 + tests/test_interp.py | 37 +- tests/test_interp_att_g.py | 56 +- tests/test_jessyink_autotexts.py | 3 +- tests/test_jessyink_effects.py | 5 +- tests/test_jessyink_export.py | 3 +- tests/test_jessyink_install.py | 1 + tests/test_jessyink_keybindings.py | 5 +- tests/test_jessyink_masterslide.py | 1 + tests/test_jessyink_mousehandler.py | 8 +- tests/test_jessyink_summary.py | 1 + tests/test_jessyink_transitions.py | 3 +- tests/test_jessyink_uninstall.py | 1 + tests/test_jessyink_video.py | 1 + tests/test_jessyink_view.py | 3 +- tests/test_jitternodes.py | 8 +- tests/test_layer2png.py | 27 +- tests/test_layers2svgfont.py | 3 +- tests/test_lindenmayer.py | 1 + tests/test_lorem_ipsum.py | 10 +- tests/test_markers_strokepaint.py | 14 +- tests/test_measure.py | 24 +- tests/test_media_zip.py | 1 + tests/test_merge_styles.py | 4 +- tests/test_motion.py | 13 +- tests/test_new_glyph_layer.py | 1 + tests/test_next_glyph_layer.py | 1 + tests/test_nicechart.py | 13 +- tests/test_output_scour.py | 1 + tests/test_param_curves.py | 3 +- tests/test_path_envelope.py | 11 +- tests/test_path_mesh.py | 20 +- tests/test_path_number_nodes.py | 9 +- tests/test_path_to_absolute.py | 12 +- tests/test_pathalongpath.py | 61 +- tests/test_pathscatter.py | 29 +- tests/test_pdflatex.py | 8 +- tests/test_perfectboundcover.py | 1 + tests/test_perspective.py | 10 +- tests/test_pixelsnap.py | 3 +- tests/test_plotter.py | 19 +- tests/test_polyhedron_3d.py | 53 +- tests/test_prepare_file_save_as.py | 1 + tests/test_previous_glyph_layer.py | 1 + tests/test_print_win32_vector.py | 10 +- tests/test_printing_marks.py | 28 +- tests/test_ps_input.py | 5 +- tests/test_render_alphabetsoup.py | 1 + tests/test_render_barcode.py | 35 +- tests/test_render_barcode_datamatrix.py | 11 +- tests/test_render_barcode_qrcode.py | 86 +- tests/test_render_gear_rack.py | 1 + tests/test_render_gears.py | 5 +- tests/test_replace_font.py | 18 +- tests/test_restack.py | 34 +- tests/test_rtree.py | 7 +- tests/test_rubberstretch.py | 11 +- tests/test_scribus_pdf.py | 4 +- tests/test_setup_typography_canvas.py | 7 +- tests/test_spirograph.py | 1 + tests/test_straightseg.py | 5 +- tests/test_svgcalendar.py | 111 +- tests/test_svgfont2layers.py | 7 +- tests/test_synfig_output.py | 1 + tests/test_synfig_prepare.py | 1 + tests/test_tar_layers.py | 1 + tests/test_template.py | 11 +- tests/test_template_dvd_cover.py | 5 +- tests/test_template_seamless_pattern.py | 3 +- tests/test_text_braille.py | 1 + tests/test_text_extract.py | 9 +- tests/test_text_flipcase.py | 1 + tests/test_text_lowercase.py | 1 + tests/test_text_merge.py | 1 + tests/test_text_randomcase.py | 1 + tests/test_text_sentencecase.py | 1 + tests/test_text_split.py | 35 +- tests/test_text_titlecase.py | 25 +- tests/test_text_uppercase.py | 1 + tests/test_triangle.py | 1 + tests/test_ungroup_deep.py | 14 +- tests/test_voronoi2svg.py | 44 +- tests/test_web_interactive_mockup.py | 3 +- tests/test_web_set_att.py | 3 +- tests/test_web_transmit_att.py | 3 +- tests/test_webslicer_create_group.py | 3 +- tests/test_webslicer_create_rect.py | 1 + tests/test_webslicer_export.py | 5 +- tests/test_whirl.py | 3 +- tests/test_wireframe_sphere.py | 1 + text_braille.py | 5 +- text_extract.py | 31 +- text_flipcase.py | 5 +- text_lowercase.py | 5 +- text_merge.py | 73 +- text_randomcase.py | 5 +- text_sentencecase.py | 7 +- text_split.py | 82 +- text_titlecase.py | 5 +- text_uppercase.py | 5 +- tools/generate_argparse_conf.py | 55 +- triangle.py | 92 +- ungroup_deep.py | 81 +- voronoi.py | 71 +- voronoi2svg.py | 145 +- web_interactive_mockup.py | 16 +- web_set_att.py | 25 +- web_transmit_att.py | 21 +- webslicer_create_group.py | 19 +- webslicer_create_rect.py | 62 +- webslicer_effect.py | 14 +- webslicer_export.py | 425 ++--- whirl.py | 22 +- wireframe_sphere.py | 53 +- 403 files changed, 17309 insertions(+), 9500 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 docs/_templates/versions.html diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 801d1ad4..dcf671f0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,6 +27,14 @@ variables: - windows - windows-1809 +codestyle:black: + stage: test + script: + - source /root/pyenv-init + - pyenv shell 3.7.2 + - pip install black + - black . --check --verbose --diff --color --exclude=other/ + test:python37: extends: .tests script: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..9fc39489 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,5 @@ +repos: +- repo: https://github.com/psf/black + rev: 22.1.0 + hooks: + - id: black \ No newline at end of file diff --git a/.pylintrc b/.pylintrc index a3903f9d..4f5d71d8 100644 --- a/.pylintrc +++ b/.pylintrc @@ -103,7 +103,7 @@ ignore-imports=no [FORMAT] # Maximum number of characters on a single line. -max-line-length=100 +max-line-length=88 # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ diff --git a/addnodes.py b/addnodes.py index 30dae3c5..2ccb60fc 100755 --- a/addnodes.py +++ b/addnodes.py @@ -31,14 +31,26 @@ 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=10.0, - help="Number of segments to divide the path into") - pars.add_argument("--method", default='bymax', - help="The kind of division to perform") - pars.add_argument("--unit", default="px", help="Unit for maximum segment length") + pars.add_argument( + "--segments", + type=int, + default=2, + help="Number of segments to divide the path into", + ) + pars.add_argument( + "--max", + type=float, + default=10.0, + help="Number of segments to divide the path into", + ) + pars.add_argument( + "--method", default="bymax", help="The kind of division to perform" + ) + pars.add_argument( + "--unit", default="px", help="Unit for maximum segment length" + ) def effect(self): for node in self.svg.selection.filter(PathElement): @@ -49,20 +61,27 @@ class AddNodes(inkex.EffectExtension): while i <= len(sub) - 1: length = bezier.cspseglength(new[-1][-1], sub[i]) - if self.options.method == 'bynum': + if self.options.method == "bynum": splits = self.options.segments else: - maxlen = self.svg.viewport_to_unit(f"{self.options.max}{self.options.unit}") + maxlen = self.svg.viewport_to_unit( + f"{self.options.max}{self.options.unit}" + ) splits = math.ceil(length / maxlen) 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] + 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=False) -if __name__ == '__main__': + +if __name__ == "__main__": AddNodes().run() diff --git a/barcode/Base.py b/barcode/Base.py index a4680a76..f9b15c3c 100644 --- a/barcode/Base.py +++ b/barcode/Base.py @@ -27,23 +27,26 @@ 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;' +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] + 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)) + "Error encoding '{}' as {} barcode: {}\n".format(text, self.name, msg) + ) return "ERROR" def encode(self, text): @@ -55,53 +58,54 @@ class Barcode(object): def __init__(self, param): param = param or {} - self.document = param.get('document', None) + 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) + 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')) + 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'): + 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) + 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 + + 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': + if string == "ERROR": return - name = self.get_id('barcode') + 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.set("id", name) + barcode.set("style", "fill: black;") barcode.transform.add_translate(self.pos_x, self.pos_y) if self.scale: @@ -116,18 +120,18 @@ class Barcode(object): 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']) + width = int(datum[1]) * int(style["width"]) - if style['write']: - tops.add(style['top']) + if style["write"]: + tops.add(style["top"]) rect = Rectangle() - rect.set('x', str(bar_offset)) - rect.set('y', str(style['top'])) + 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'])) + 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 @@ -139,13 +143,13 @@ class Barcode(object): 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)) + 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.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 @@ -156,11 +160,11 @@ class Barcode(object): def get_style(self, index): """Returns the styles that should be applied to each bar""" - result = {'width': 1, 'top': 0, 'write': True} + result = {"width": 1, "top": 0, "write": True} if index == BLACK_BAR: - result['height'] = int(self.height) + result["height"] = int(self.height) if index == TALL_BAR: - result['height'] = int(self.height) + int(self.font_size / 2) + result["height"] = int(self.height) + int(self.font_size / 2) if index == WHITE_BAR: - result['write'] = False + result["write"] = False return result diff --git a/barcode/BaseEan.py b/barcode/BaseEan.py index d1c4352c..ba38617f 100644 --- a/barcode/BaseEan.py +++ b/barcode/BaseEan.py @@ -29,26 +29,57 @@ except ImportError: MAPPING = [ # Left side of barcode Family '0' - ["0001101", "0011001", "0010011", "0111101", "0100011", - "0110001", "0101111", "0111011", "0110111", "0001011"], + [ + "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"], + [ + "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') +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] + + 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' + guard_bar = "202" + center_bar = "02020" def intarray(self, number): """Convert a string of digits into an array of ints""" @@ -80,13 +111,13 @@ class EanBarcode(Barcode): def space(self, *spacing): """Space out an array of numbers""" - result = '' + result = "" for space in spacing: if isinstance(space, list): for i in space: result += str(i) elif isinstance(space, int): - result += ' ' * space + result += " " * space return result def get_lengths(self): @@ -97,12 +128,12 @@ class EanBarcode(Barcode): def encode(self, code): """Encode any EAN barcode""" - code = code.replace(' ', '').strip() - guide = code.endswith('>') - code = code.strip('>') + 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') + 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 @@ -111,18 +142,27 @@ class EanBarcode(Barcode): 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) + 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]))) + 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.error(code, "Checksum failed, omit for new sum") return self._encode(self.intarray(code), guide=guide) def _encode(self, num, guide=False): @@ -137,7 +177,7 @@ class EanBarcode(Barcode): parts = [self.guard_bar] + left parts.append(self.center_bar) parts += list(right) + [self.guard_bar] - return ''.join(parts) + return "".join(parts) def get_checksum(self, num): """Generate a UPCA/EAN13/EAN8 Checksum""" @@ -146,7 +186,7 @@ class EanBarcode(Barcode): # Modulous result to a single digit checksum checksum = self.magic - (total % self.magic) if checksum < 0 or checksum >= self.magic: - return '0' + return "0" return str(checksum) def append_checksum(self, number): diff --git a/barcode/Code128.py b/barcode/Code128.py index d3d002f9..5c0a9fc5 100644 --- a/barcode/Code128.py +++ b/barcode/Code128.py @@ -29,28 +29,115 @@ 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'] + "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): @@ -58,16 +145,15 @@ def map_extra(data, chars): result = list(data) for char in chars: result.append(chr(char)) - result.append('FNC3') - result.append('FNC2') - result.append('SHIFT') + 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_AB = list(" !\"#$%&'()*+,-./0123456789:;<=>?@" "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_") CHAR_A = map_extra(CHAR_AB, range(0, 31)) # Offset 64 CHAR_B = map_extra(CHAR_AB, range(96, 127)) # Offset -32 @@ -77,35 +163,35 @@ class Code128(Barcode): def encode(self, text): blocks = [] - block = '' + 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): + 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]) + block = "" + blocks.append(["C", datum]) if block: blocks.append(self.best_block(block)) - block = '' + block = "" return self.encode_blocks(blocks) def best_block(self, block): """If this has characters above 63, select B over A""" if any(ord(x) > 63 for x in block): - return ['B', block] - return ['A', block] + return ["B", block] + return ["A", block] def encode_blocks(self, blocks): """Encode the given blocks into A, B or C codes""" - encode = '' + encode = "" total = 0 pos = 0 @@ -118,11 +204,11 @@ class Code128(Barcode): # B : 100, 104 # C : 99, 105 num = 0 - if b_set == 'A': + if b_set == "A": num = 103 - elif b_set == 'B': + elif b_set == "B": num = 104 - elif b_set == 'C': + elif b_set == "C": num = 105 i = pos @@ -135,9 +221,9 @@ class Code128(Barcode): encode = encode + CODE_MAP[num] pos += 1 - if b_set == 'A' or b_set == 'B': + if b_set == "A" or b_set == "B": chars = CHAR_B - if b_set == 'A': + if b_set == "A": chars = CHAR_A for char in datum: @@ -145,7 +231,7 @@ class Code128(Barcode): 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)): + 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 diff --git a/barcode/Code25i.py b/barcode/Code25i.py index e655f6cd..5d00ce94 100644 --- a/barcode/Code25i.py +++ b/barcode/Code25i.py @@ -24,16 +24,16 @@ 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', + "0": "00110", + "1": "10001", + "2": "01001", + "3": "11000", + "4": "00101", + "5": "10100", + "6": "01100", + "7": "00011", + "8": "10010", + "9": "01010", } @@ -48,22 +48,22 @@ class Code25i(Barcode): # 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 = "0" + number # Number is encoded by pairs of 2 figures size = len(number) // 2 - encoded = '1010' + 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' + if black[j] == "1": + encoded += "11" else: - encoded += '1' - if white[j] == '1': - encoded += '00' + encoded += "1" + if white[j] == "1": + encoded += "00" else: - encoded += '0' - return encoded + '1101' + encoded += "0" + return encoded + "1101" diff --git a/barcode/Code39.py b/barcode/Code39.py index a0ee2e28..c6a041e1 100644 --- a/barcode/Code39.py +++ b/barcode/Code39.py @@ -23,50 +23,50 @@ 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', + "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", } @@ -75,24 +75,24 @@ class Code39(Barcode): def encode(self, text): self.text = text.upper() - result = '' + result = "" # It is possible for us to encode code39 # into full ascii, but this feature is # not enabled here - for char in '*' + self.text + '*': + for char in "*" + self.text + "*": if char not in ENCODE: - char = '-' - result = result + ENCODE[char] + '0' + 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 + encoded = "" + colour = "1" # 1 = Black, 0 = White for data in result: - if data == '1': + if data == "1": encoded = encoded + colour + colour else: encoded = encoded + colour - colour = colour == '1' and '0' or '1' + colour = colour == "1" and "0" or "1" return encoded diff --git a/barcode/Code39Ext.py b/barcode/Code39Ext.py index e9e804a0..03c3d311 100644 --- a/barcode/Code39Ext.py +++ b/barcode/Code39Ext.py @@ -22,7 +22,7 @@ Python barcode renderer for Code39 Extended barcodes. Designed for Inkscape. from .Code39 import Code39 -encode = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') +encode = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ") map = {} @@ -44,7 +44,36 @@ def getMap(array): # 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]) # % +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)) # + @@ -53,16 +82,16 @@ mapD = getMap(range(97, 122)) # + class Code39Ext(Code39): def encode(self, text): # We are only going to extend the Code39 barcodes - result = '' + result = "" for char in text: if char in mapA: - char = '%' + mapA[char] + char = "%" + mapA[char] elif char in mapB: - char = '$' + mapB[char] + char = "$" + mapB[char] elif char in mapC: - char = '/' + mapC[char] + char = "/" + mapC[char] elif char in mapD: - char = '+' + mapD[char] + char = "+" + mapD[char] result = result + char return Code39.encode(self, result) diff --git a/barcode/Code93.py b/barcode/Code93.py index e7af072e..8a94556a 100644 --- a/barcode/Code93.py +++ b/barcode/Code93.py @@ -22,12 +22,12 @@ 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') +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))) @@ -43,30 +43,97 @@ def get_map(array): # 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_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', '' + "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)] + bits = ENCODE[MAP.get("MARKER", -1)] # Extend to ASCII charset ( return Array ) text = self.encode_ascii(text) @@ -80,7 +147,7 @@ class Code93(Barcode): bits = bits + ENCODE[MAP.get(char, -1)] # end marker and termination bar - return bits + ENCODE[MAP.get('MARKER', -1)] + '1' + return bits + ENCODE[MAP.get("MARKER", -1)] + "1" def checksum(self, text, mod): """Generate a code 93 checksum""" @@ -102,15 +169,15 @@ class Code93(Barcode): if char in MAP: result.append(char) elif char in MAP_A: - result.append('(%)') + result.append("(%)") result.append(MAP_A[char]) elif char in MAP_B: - result.append('($)') + result.append("($)") result.append(MAP_B[char]) elif char in MAP_C: - result.append('(/)') + result.append("(/)") result.append(MAP_C[char]) elif char in MAP_D: - result.append('(+)') + result.append("(+)") result.append(MAP_D[char]) return result diff --git a/barcode/Ean13.py b/barcode/Ean13.py index 2f41a9ce..e8a98738 100644 --- a/barcode/Ean13.py +++ b/barcode/Ean13.py @@ -27,8 +27,9 @@ from .BaseEan import EanBarcode class Ean13(EanBarcode): """Provide an Ean13 barcode generator""" - name = 'ean13' - extras = {2: 'Ean2', 5: 'Ean5'} + + name = "ean13" + extras = {2: "Ean2", 5: "Ean5"} checks = [13] lengths = [12] @@ -36,8 +37,7 @@ class Ean13(EanBarcode): """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] + '>' + self.text = self.text[:-4] + ">" return self.enclose( - self.encode_interleaved(num[0], num[1:7]), - self.encode_right(num[7:]) + self.encode_interleaved(num[0], num[1:7]), self.encode_right(num[7:]) ) diff --git a/barcode/Ean2.py b/barcode/Ean2.py index 08c990a5..0eae5220 100644 --- a/barcode/Ean2.py +++ b/barcode/Ean2.py @@ -22,18 +22,19 @@ Python barcode renderer for EAN2 barcodes. Designed for use with Inkscape. from .BaseEan import EanBarcode -FAMS = ['00', '01', '10', '11'] -START = '01011' +FAMS = ["00", "01", "10", "11"] +START = "01011" class Ean2(EanBarcode): """Provide an Ean5 barcode generator""" + length = 2 - name = 'ean5' + name = "ean5" def _encode(self, num, guide=False): if len(num) != 2: num = ([0, 0] + num)[-2:] - self.text = ' '.join(self.space(num)) + self.text = " ".join(self.space(num)) family = ((num[0] * 10) + num[1]) % 4 - return START + '01'.join(self.encode_interleaved(family, num, FAMS)) + return START + "01".join(self.encode_interleaved(family, num, FAMS)) diff --git a/barcode/Ean5.py b/barcode/Ean5.py index 074c9702..10d224d1 100644 --- a/barcode/Ean5.py +++ b/barcode/Ean5.py @@ -23,17 +23,28 @@ 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' +FAMS = [ + "11000", + "10100", + "10010", + "10001", + "01100", + "00110", + "00011", + "01010", + "01001", + "00101", +] +START = "01011" class Ean5(EanBarcode): """Provide an Ean5 barcode generator""" - name = 'ean5' + + 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)) + 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/barcode/Ean8.py b/barcode/Ean8.py index d8fb2d84..b9f09d24 100644 --- a/barcode/Ean8.py +++ b/barcode/Ean8.py @@ -25,14 +25,12 @@ from .BaseEan import EanBarcode class Ean8(EanBarcode): """Provide an EAN8 barcode generator""" - name = 'ean8' + + 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:]) - ) + return self.enclose(self.encode_left(num[:4]), self.encode_right(num[4:])) diff --git a/barcode/Rm4scc.py b/barcode/Rm4scc.py index 5687f630..b3bc96cf 100644 --- a/barcode/Rm4scc.py +++ b/barcode/Rm4scc.py @@ -23,47 +23,47 @@ 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', + "(": "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'] +check = ["ZUVWXY", "501234", "B6789A", "HCDEFG", "NIJKLM", "TOPQRS"] (BAR_TRACK, BAR_DOWN, BAR_UP, BAR_FULL, BAR_NONE, WHITE_SPACE) = range(6) @@ -71,13 +71,13 @@ class Rm4scc(Barcode): default_height = 18 def encode(self, text): - result = '' + result = "" text = text.upper() - text.replace('(', '') - text.replace(')', '') + text.replace("(", "") + text.replace(")", "") - text = '(' + text + self.checksum(text) + ')' + text = "(" + text + self.checksum(text) + ")" i = 0 for char in text: @@ -120,17 +120,17 @@ class Rm4scc(Barcode): def get_style(self, index): """Royal Mail Barcodes use a completely different style""" - result = {'width': 2, 'write': True, 'top': 0} + result = {"width": 2, "write": True, "top": 0} if index == BAR_TRACK: # Track Bar - result['top'] = 6 - result['height'] = 5 + result["top"] = 6 + result["height"] = 5 elif index == BAR_DOWN: # Decender Bar - result['top'] = 6 - result['height'] = 11 + result["top"] = 6 + result["height"] = 11 elif index == BAR_UP: # Accender Bar - result['height'] = 11 + result["height"] = 11 elif index == BAR_FULL: # Full Bar - result['height'] = 17 + result["height"] = 17 elif index == WHITE_SPACE: # White Space - result['write'] = False + result["write"] = False return result diff --git a/barcode/Upca.py b/barcode/Upca.py index 77cb4159..c59b3120 100644 --- a/barcode/Upca.py +++ b/barcode/Upca.py @@ -25,7 +25,8 @@ from .BaseEan import EanBarcode class Upca(EanBarcode): """Provides a renderer for EAN12 aka UPC-A Barcodes""" - name = 'upca' + + name = "upca" font_size = 10 lengths = [11] checks = [12] @@ -34,6 +35,6 @@ class Upca(EanBarcode): """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]), + self.encode_left(num[0:6]), + self.encode_right(num[6:12]), ) diff --git a/barcode/Upce.py b/barcode/Upce.py index e341bddd..aeb7cf9f 100644 --- a/barcode/Upce.py +++ b/barcode/Upce.py @@ -24,21 +24,32 @@ 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'] +FAMS = [ + "111000", + "110100", + "110010", + "110001", + "101100", + "100110", + "100011", + "101010", + "101001", + "100101", +] class Upce(EanBarcode): """Generate EAN6/UPC-E barcode generator""" - name = 'upce' + + name = "upce" font_size = 10 lengths = [6, 11] checks = [7, 12] - center_bar = '020' + 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]) + self.text = self.space(["0"], 2, num[:6], 2, num[-1]) code = self.encode_interleaved(num[-1], num[:6], FAMS) return self.enclose(code) @@ -52,7 +63,7 @@ class Upce(EanBarcode): 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 number[0] != "0" or len(number) != 11: # If not then the code is invalid raise ValueError("Invalid UPC Number") @@ -62,19 +73,19 @@ class Upce(EanBarcode): product = number[6:11] # There are 4 cases to convert: - if maker[2:] == '000' or maker[2:] == '100' or maker[2:] == '200': + if maker[2:] == "000" or maker[2:] == "100" or maker[2:] == "200": # Maximum number product code digits can be encoded - if product[:2] == '00': + if product[:2] == "00": return maker[:2] + product[2:] + maker[2] - elif maker[3:5] == '00': + 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': + 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: + 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. @@ -90,11 +101,11 @@ class Upce(EanBarcode): 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] + 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] + return "0" + number[:5] + "0000" + number[5] diff --git a/barcode/__init__.py b/barcode/__init__.py index f68d947f..c7a35a7d 100644 --- a/barcode/__init__.py +++ b/barcode/__init__.py @@ -40,6 +40,7 @@ For supported barcodes see Barcode module directory. # PDF417-Truncated # PDF417-GLI + class NoBarcode(object): """Simple class for no barcode""" @@ -60,9 +61,9 @@ def get_barcode(code, **kw): if not code: return NoBarcode("No barcode format given.") - code = str(code).replace('-', '').strip() - module = 'barcode.' + code - lst = ['barcode'] + code = str(code).replace("-", "").strip() + module = "barcode." + code + lst = ["barcode"] try: return getattr(__import__(module, fromlist=lst), code)(kw) except ImportError as err: @@ -70,4 +71,6 @@ def get_barcode(code, **kw): return NoBarcode("Invalid type of barcode: {}.{}".format(module, code)) raise except AttributeError: - return NoBarcode("Barcode module is missing barcode class: {}.{}".format(module, code)) + return NoBarcode( + "Barcode module is missing barcode class: {}.{}".format(module, code) + ) diff --git a/color_HSL_adjust.py b/color_HSL_adjust.py index 1425ba0d..cbe47e61 100755 --- a/color_HSL_adjust.py +++ b/color_HSL_adjust.py @@ -4,13 +4,19 @@ 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( + "-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") @@ -19,19 +25,20 @@ class HslAdjust(inkex.ColorExtension): if self.options.random_hue: color.hue = int(random.random() * 255.0) elif self.options.hue: - color.hue += (self.options.hue * 256.0/360) + color.hue += self.options.hue * 256.0 / 360 if self.options.random_saturation: color.saturation = int(random.random() * 255.0) elif self.options.saturation: - color.saturation += (self.options.saturation * 2.55) + 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) + color.lightness += self.options.lightness * 2.55 return color -if __name__ == '__main__': + +if __name__ == "__main__": HslAdjust().run() diff --git a/color_blackandwhite.py b/color_blackandwhite.py index 2af6a61c..e5e65b24 100755 --- a/color_blackandwhite.py +++ b/color_blackandwhite.py @@ -3,10 +3,14 @@ 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") + 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) @@ -15,5 +19,6 @@ class BlackAndWhite(inkex.ColorExtension): grey = 255 if lum > self.options.threshold else 0 return inkex.Color((grey, grey, grey)) -if __name__ == '__main__': + +if __name__ == "__main__": BlackAndWhite().run() diff --git a/color_brighter.py b/color_brighter.py index be708974..4ab7eccc 100755 --- a/color_brighter.py +++ b/color_brighter.py @@ -3,12 +3,14 @@ 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': + 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 @@ -20,5 +22,6 @@ class Brighter(inkex.ColorExtension): color.blue = min(int(round(color.blue / factor)), 255) return color -if __name__ == '__main__': + +if __name__ == "__main__": Brighter().run() diff --git a/color_custom.py b/color_custom.py index 43d32044..67fe5a96 100755 --- a/color_custom.py +++ b/color_custom.py @@ -4,13 +4,16 @@ 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} +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): """ @@ -21,7 +24,8 @@ def eval_expr(expr, namespace): >>> eval_expr('1 + 2*3**(4^5) / (6 + -7)') -5.0 """ - return _eval(ast.parse(expr, mode='eval').body, namespace) + return _eval(ast.parse(expr, mode="eval").body, namespace) + def _eval(node, namespace): if isinstance(node, ast.Num): # @@ -29,34 +33,42 @@ def _eval(node, namespace): 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)) + 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") + 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} + "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__': + +if __name__ == "__main__": Custom().run() diff --git a/color_darker.py b/color_darker.py index 6c57d78c..71b487b3 100755 --- a/color_darker.py +++ b/color_darker.py @@ -3,11 +3,13 @@ import inkex + class Darker(inkex.ColorExtension): """Make the colours darker""" + def modify_color(self, name, color): factor = 0.9 - if color.space == 'hsl': + if color.space == "hsl": color.lightness = int(round(max(color.lightness * factor, 0))) else: color.red = int(round(max(color.red * factor, 0))) @@ -15,5 +17,6 @@ class Darker(inkex.ColorExtension): color.blue = int(round(max(color.blue * factor, 0))) return color -if __name__ == '__main__': + +if __name__ == "__main__": Darker().run() diff --git a/color_desaturate.py b/color_desaturate.py index fa79d8a3..1710b1e1 100755 --- a/color_desaturate.py +++ b/color_desaturate.py @@ -3,12 +3,17 @@ 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 + 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__': + +if __name__ == "__main__": Desaturate().run() diff --git a/color_grayscale.py b/color_grayscale.py index 367b4e13..5d134aab 100755 --- a/color_grayscale.py +++ b/color_grayscale.py @@ -3,13 +3,16 @@ 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__': + +if __name__ == "__main__": Grayscale().run() diff --git a/color_lesshue.py b/color_lesshue.py index 62c2d8d6..114ac3c9 100755 --- a/color_lesshue.py +++ b/color_lesshue.py @@ -3,11 +3,14 @@ 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__': + +if __name__ == "__main__": LessHue().run() diff --git a/color_lesslight.py b/color_lesslight.py index 10a42fe7..1bbe1516 100755 --- a/color_lesslight.py +++ b/color_lesslight.py @@ -3,11 +3,14 @@ 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__': + +if __name__ == "__main__": LessLight().run() diff --git a/color_lesssaturation.py b/color_lesssaturation.py index 924efd37..e19b2979 100755 --- a/color_lesssaturation.py +++ b/color_lesssaturation.py @@ -3,11 +3,14 @@ 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__': + +if __name__ == "__main__": LessSaturation().run() diff --git a/color_list.py b/color_list.py index f86db226..5a608677 100755 --- a/color_list.py +++ b/color_list.py @@ -4,8 +4,10 @@ from collections import defaultdict import inkex + class ListColours(inkex.ColorExtension): """Make the colours darker""" + _counts = defaultdict(int) def effect(self): @@ -18,5 +20,6 @@ class ListColours(inkex.ColorExtension): self._counts[color] += 1 return color -if __name__ == '__main__': + +if __name__ == "__main__": ListColours().run() diff --git a/color_morehue.py b/color_morehue.py index 85f4e150..c421d35c 100755 --- a/color_morehue.py +++ b/color_morehue.py @@ -3,11 +3,14 @@ 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__': + +if __name__ == "__main__": MoreHue().run() diff --git a/color_morelight.py b/color_morelight.py index 41436408..99ef4756 100755 --- a/color_morelight.py +++ b/color_morelight.py @@ -3,11 +3,14 @@ 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__': + +if __name__ == "__main__": MoreLight().run() diff --git a/color_moresaturation.py b/color_moresaturation.py index d1afa5d4..6c5498b1 100755 --- a/color_moresaturation.py +++ b/color_moresaturation.py @@ -3,11 +3,14 @@ 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__': + +if __name__ == "__main__": MoreSaturation().run() diff --git a/color_negative.py b/color_negative.py index 60dc84e8..94077065 100755 --- a/color_negative.py +++ b/color_negative.py @@ -3,13 +3,16 @@ 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__': + +if __name__ == "__main__": Negative().run() diff --git a/color_randomize.py b/color_randomize.py index d1cad1c2..a5619c00 100755 --- a/color_randomize.py +++ b/color_randomize.py @@ -4,7 +4,10 @@ from random import randrange, uniform, seed import inkex -def _rand(limit, value, roof=255, method=randrange, circular=False, deterministic=False): + +def _rand( + limit, value, roof=255, method=randrange, circular=False, deterministic=False +): """Generates a random number which is less than limit % away from value, using the method supplied.""" if deterministic: @@ -30,23 +33,40 @@ def _rand(limit, value, roof=255, method=randrange, circular=False, deterministi class Randomize(inkex.ColorExtension): """Randomize the colours of all objects""" - deterministic_output=False + + deterministic_output = False + 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 _rand(self, limit, value, roof=255, method=randrange, circular=False): - return _rand(limit, value, roof, method, circular, deterministic=self.deterministic_output) + 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 _rand(self, limit, value, roof=255, method=randrange, circular=False): + return _rand( + limit, + value, + roof, + method, + circular, + deterministic=self.deterministic_output, + ) def modify_color(self, name, color): hsl = color.to_hsl() if self.options.hue_range > 0: hsl.hue = int(self._rand(self.options.hue_range, hsl.hue, circular=True)) if self.options.saturation_range > 0: - hsl.saturation = int(self._rand(self.options.saturation_range, hsl.saturation)) + hsl.saturation = int( + self._rand(self.options.saturation_range, hsl.saturation) + ) if self.options.lightness_range > 0: hsl.lightness = int(self._rand(self.options.lightness_range, hsl.lightness)) return hsl.to_rgb() @@ -64,5 +84,6 @@ class Randomize(inkex.ColorExtension): return self._rand(orange, opacity, roof=1.0, method=uniform) return opacity -if __name__ == '__main__': + +if __name__ == "__main__": Randomize().run() diff --git a/color_removeblue.py b/color_removeblue.py index c19fd333..ee64f0ec 100755 --- a/color_removeblue.py +++ b/color_removeblue.py @@ -3,10 +3,13 @@ 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__': + +if __name__ == "__main__": RemoveBlue().run() diff --git a/color_removegreen.py b/color_removegreen.py index 2eacf957..319a8cd9 100755 --- a/color_removegreen.py +++ b/color_removegreen.py @@ -3,10 +3,13 @@ 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__': + +if __name__ == "__main__": RemoveGreen().run() diff --git a/color_removered.py b/color_removered.py index d8353f22..ff1b5af5 100755 --- a/color_removered.py +++ b/color_removered.py @@ -3,10 +3,13 @@ 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__': + +if __name__ == "__main__": RemoveRed().run() diff --git a/color_replace.py b/color_replace.py index 9e6b4c1a..f27b9e4e 100755 --- a/color_replace.py +++ b/color_replace.py @@ -3,6 +3,7 @@ import inkex + class ReplaceColor(inkex.ColorExtension): """Replace color in SVG with another""" @@ -10,20 +11,36 @@ class ReplaceColor(inkex.ColorExtension): 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") - pars.add_argument('-i', "--ignore_opacity",\ - default=True, type=inkex.Boolean, - help="Whether color should be replaced regardless of opacity match") + 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", + ) + pars.add_argument( + "-i", + "--ignore_opacity", + default=True, + type=inkex.Boolean, + help="Whether color should be replaced regardless of opacity match", + ) def modify_color(self, name, color_rgba): - if self.options.from_color.to_rgb() == color_rgba.to_rgb() and \ - (self.options.ignore_opacity - or abs(self.options.from_color.to_rgba().alpha - color_rgba.alpha) < 0.01): - return self.options.to_color.to_rgba() + if self.options.from_color.to_rgb() == color_rgba.to_rgb() and ( + self.options.ignore_opacity + or abs(self.options.from_color.to_rgba().alpha - color_rgba.alpha) < 0.01 + ): + return self.options.to_color.to_rgba() return color_rgba -if __name__ == '__main__': + +if __name__ == "__main__": ReplaceColor().run() diff --git a/color_rgbbarrel.py b/color_rgbbarrel.py index 5458265a..fb61a584 100755 --- a/color_rgbbarrel.py +++ b/color_rgbbarrel.py @@ -3,14 +3,17 @@ 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__': + +if __name__ == "__main__": RgbBarrel().run() diff --git a/convert2dashes.py b/convert2dashes.py index d4dd1dc0..2908e492 100755 --- a/convert2dashes.py +++ b/convert2dashes.py @@ -28,6 +28,7 @@ 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 = [] @@ -36,8 +37,11 @@ class Dashit(inkex.EffectExtension): for node in self.svg.selection: self.convert2dash(node) if self.not_converted: - inkex.errormsg(_('Total number of objects not converted: {}\n').format( - len(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) @@ -49,15 +53,15 @@ class Dashit(inkex.EffectExtension): elif isinstance(node, PathElement): self._convert(node) else: - self.not_converted.append(node.get('id')) + self.not_converted.append(node.get("id")) @staticmethod def _convert(node): dashes = [] offset = 0 style = node.specified_style() - dashes = style('stroke-dasharray') - offset = style('stroke-dashoffset') + dashes = style("stroke-dasharray") + offset = style("stroke-dashoffset") if not dashes: return new = [] @@ -75,11 +79,12 @@ class Dashit(inkex.EffectExtension): 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[-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 + else: # splice the curve new[-1].append(nxt[:]) length = length - dash idash = (idash + 1) % len(dashes) @@ -89,10 +94,11 @@ class Dashit(inkex.EffectExtension): else: new[-1].append(sub[i]) i += 1 - style.pop('stroke-dasharray') - node.pop('sodipodi:type') + style.pop("stroke-dasharray") + node.pop("sodipodi:type") node.path = CubicSuperPath(new) node.style = style -if __name__ == '__main__': + +if __name__ == "__main__": Dashit().run() diff --git a/dhw_input.py b/dhw_input.py index 7b749bed..f5cca723 100755 --- a/dhw_input.py +++ b/dhw_input.py @@ -29,10 +29,12 @@ import struct import inkex from inkex import AbortExtension, errormsg, Group, Polyline -inkex.NSS['dm'] = 'http://github.com/nikitakit/DM2SVG' +inkex.NSS["dm"] = "http://github.com/nikitakit/DM2SVG" + class DhwInput(inkex.InputExtension): """Open DHW files and convert to svg on the fly""" + template = """= 0x80: return None - x2, y1, y2 = struct.unpack('BBB', stream.read(3)) + x2, y1, y2 = struct.unpack("BBB", stream.read(3)) x = x1 | x2 << 7 y = y1 | y2 << 7 return x, ymax - y -if __name__ == '__main__': + +if __name__ == "__main__": DhwInput().run() diff --git a/dimension.py b/dimension.py index 423f3fb7..ceeeae64 100755 --- a/dimension.py +++ b/dimension.py @@ -38,32 +38,45 @@ 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=50.0,\ - help="x offset of the vertical dimension arrow") - pars.add_argument("--yoffset", type=float, default=50.0,\ - help="y offset of the horizontal dimension arrow") + pars.add_argument( + "--xoffset", + type=float, + default=50.0, + help="x offset of the vertical dimension arrow", + ) + pars.add_argument( + "--yoffset", + type=float, + default=50.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) + 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 ') + 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)') + 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') + 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): @@ -72,7 +85,7 @@ class Dimension(pathmodifier.PathModifier): 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)) + line.set("d", "M %f %f H %f" % (x1, y1, x2)) return line def vert_line(self, x, xlat, bbox): @@ -81,11 +94,11 @@ class Dimension(pathmodifier.PathModifier): 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)) + 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 + scale = self.svg.unittouu("1px") # convert to document units self.options.xoffset *= scale self.options.yoffset *= scale @@ -98,40 +111,40 @@ class Dimension(pathmodifier.PathModifier): layer = self.svg.get_current_layer() - self.add_marker('Arrow1Lstart', False) - self.add_marker('Arrow1Lend', True) + self.add_marker("Arrow1Lstart", False) + self.add_marker("Arrow1Lend", True) group = Group() layer.append(group) - group.set('fill', 'none') - group.set('stroke', 'black') + 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)) + 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)) + 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)) + 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)) + 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)) + 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)) + line.set("stroke-width", str(0.5 * scale)) group.append(line) for node in self.svg.selected.values(): @@ -141,5 +154,5 @@ class Dimension(pathmodifier.PathModifier): return None -if __name__ == '__main__': +if __name__ == "__main__": Dimension().run() diff --git a/doc_ai_convert.py b/doc_ai_convert.py index 98da3c4a..db85306f 100644 --- a/doc_ai_convert.py +++ b/doc_ai_convert.py @@ -25,6 +25,7 @@ An Inkscape extension to assist with importing AI SVG files. import inkex from inkex import units + class DocAiConvert(inkex.EffectExtension): """ Main class of the doc_ai_convert extension @@ -45,13 +46,14 @@ class DocAiConvert(inkex.EffectExtension): - We re-label these as layers such that Inkscape will recognize them. """ + def effect(self): """ Main entry point of the doc_ai_convert extension """ # 1) Recognize intended dimensions of original document, if given - width_string = self.svg.get('width') - height_string = self.svg.get('height') + width_string = self.svg.get("width") + height_string = self.svg.get("height") if width_string and height_string: width_num, width_units = units.parse_unit(width_string) @@ -61,16 +63,17 @@ class DocAiConvert(inkex.EffectExtension): # for unitless values, and not None. if width_num: - if width_units == 'px': - self.svg.set('width', units.render_unit(width_num, 'pt')) + if width_units == "px": + self.svg.set("width", units.render_unit(width_num, "pt")) if height_num: - if height_units == 'px': - self.svg.set('height', units.render_unit(height_num, 'pt')) + if height_units == "px": + self.svg.set("height", units.render_unit(height_num, "pt")) # 2) Recognize Adobe Illustrator layers. - for node in self.svg.xpath('//svg:g[@data-name]'): - node.set('inkscape:groupmode', 'layer') - node.set('inkscape:label', node.pop('data-name')) + for node in self.svg.xpath("//svg:g[@data-name]"): + node.set("inkscape:groupmode", "layer") + node.set("inkscape:label", node.pop("data-name")) + -if __name__ == '__main__': +if __name__ == "__main__": DocAiConvert().run() diff --git a/docinfo.py b/docinfo.py index 64f1ef1a..81db5ef2 100755 --- a/docinfo.py +++ b/docinfo.py @@ -22,20 +22,31 @@ 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( + "version: " + self.svg.get("inkscape:version", "New Document (unsaved)") + ) self.msg("width: {}".format(self.svg.viewport_width)) self.msg("height: {}".format(self.svg.viewport_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-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'))) + for i, grid in enumerate(namedview.findall("inkscape:grid")): + self.msg( + "Grid number {}: Units: {}".format(i + 1, grid.get("units", "None")) + ) + -if __name__ == '__main__': +if __name__ == "__main__": DocInfo().run() diff --git a/docs/_templates/versions.html b/docs/_templates/versions.html new file mode 100644 index 00000000..476c8d19 --- /dev/null +++ b/docs/_templates/versions.html @@ -0,0 +1,27 @@ +{%- if current_version %} +
+ + Other Versions + v: {{ current_version.name }} + + +
+ {%- if versions.tags %} +
+
Tags
+ {%- for item in versions.tags %} +
{{ item.name }}
+ {%- endfor %} +
+ {%- endif %} + {%- if versions.branches %} +
+
Branches
+ {%- for item in versions.branches %} +
{{ item.name }}
+ {%- endfor %} +
+ {%- endif %} +
+
+{%- endif %} \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 4868e683..52e42312 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,9 +19,9 @@ import datetime # -- Project information ----------------------------------------------------- -project = 'inkex' +project = "inkex" copyright = f"{datetime.datetime.now().year} The Inkscape Project" -author = 'The Inkscape Project' +author = "The Inkscape Project" # -- General configuration --------------------------------------------------- @@ -30,27 +30,27 @@ author = 'The Inkscape Project' # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.viewcode', - 'sphinx.ext.todo', - 'sphinx_rtd_theme', - 'sphinx.ext.napoleon' + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", + "sphinx.ext.todo", + "sphinx_rtd_theme", + "sphinx.ext.napoleon", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = 'en' +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- @@ -58,12 +58,12 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # -- Extension configuration ------------------------------------------------- diff --git a/dpiswitcher.py b/dpiswitcher.py index f3c22e75..48c2c6e4 100755 --- a/dpiswitcher.py +++ b/dpiswitcher.py @@ -49,42 +49,42 @@ from inkex import Use, TextElement, transforms # globals SKIP_CONTAINERS = [ - 'defs', - 'glyph', - 'marker', - 'mask', - 'missing-glyph', - 'pattern', - 'symbol', + "defs", + "glyph", + "marker", + "mask", + "missing-glyph", + "pattern", + "symbol", ] CONTAINER_ELEMENTS = [ - 'a', - 'g', - 'switch', + "a", + "g", + "switch", ] GRAPHICS_ELEMENTS = [ - 'circle', - 'ellipse', - 'image', - 'line', - 'path', - 'polygon', - 'polyline', - 'rect', - 'text', - 'use', + "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' + 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') + text_path = element.find("svg:textPath") if text_path is not None and len(text_path): return True return False @@ -98,7 +98,7 @@ def is_sibling(element1, element2): def is_in_defs(doc, element): """Check whether element is in defs.""" if element is not None: - defs = doc.find('defs') + defs = doc.find("defs") if defs is not None: return element in defs.iterdescendants() return False @@ -117,31 +117,31 @@ def check_3dbox(svg, element, scale_x, scale_y): 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 + 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: + 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 + 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' + prop = "font-size" # outer text - sdict = dict(inkex.Style.parse_str(element.get('style'))) + 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))) + 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'))) + 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))) + child.set("style", str(inkex.Style(sdict))) return skip @@ -153,7 +153,7 @@ def check_use(svg, element, scale_x, scale_y): if is_sibling(element, path): skip = True # scale offset - if 'transform' in element.attrib: + if "transform" in element.attrib: element.transform.add_scale(scale_x, scale_y) return skip @@ -165,33 +165,34 @@ class DPISwitcher(inkex.EffectExtension): units = "px" def add_arguments(self, pars): - pars.add_argument("--switcher", type=str, default="0", - help="Select the DPI switch you want") + 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, + "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, + "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): @@ -201,31 +202,35 @@ class DPISwitcher(inkex.EffectExtension): else: # dpi96to90 known_units = list(self.__uuconv) if percent: - unitmatch = re.compile('(%s)$' % '|'.join(known_units + ['%'])) + 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]+)?)') + 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 + unit = "px" # fallback: assume 'px' unit if p: - val = float(p.string[p.start():p.end()]) + val = float(p.string[p.start() : p.end()]) if u: - unit = u.string[u.start():u.end()] + 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' + 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']) + 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']) + val = val_px / (self.__uuconv[doc_unit] / self.__uuconv["px"]) unit = doc_unit return val, unit @@ -240,28 +245,31 @@ class DPISwitcher(inkex.EffectExtension): 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)) + 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_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)) + 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])) + 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": @@ -290,10 +298,10 @@ class DPISwitcher(inkex.EffectExtension): 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) + 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: @@ -307,24 +315,53 @@ class DPISwitcher(inkex.EffectExtension): """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)) + 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') + 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" + 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" + 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" + 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" + originy = ( + str( + float(re.sub("[a-zA-Z]", "", grid.get("originy"))) + * self.factor_a + ) + + "px" + ) grid.set("originy", str(originy)) def effect(self): @@ -332,8 +369,8 @@ class DPISwitcher(inkex.EffectExtension): 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] + 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": @@ -343,5 +380,5 @@ class DPISwitcher(inkex.EffectExtension): self.scale_root(unit_exponent) -if __name__ == '__main__': +if __name__ == "__main__": DPISwitcher().run() diff --git a/draw_from_triangle.py b/draw_from_triangle.py index 97b34256..d99bfaf1 100755 --- a/draw_from_triangle.py +++ b/draw_from_triangle.py @@ -43,16 +43,26 @@ from inkex import PathElement, Circle # 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 +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} + 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} + 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)} + circ_attribs = {"cx": str(cx), "cy": str(cy), "r": str(r)} elem = parent.add(Circle(**circ_attribs)) elem.style = circ_style elem.label = name @@ -60,13 +70,34 @@ def draw_SVG_circle(rad, centre, params, style, name, parent): # draw an SVG ci # 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 + 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.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 @@ -75,8 +106,12 @@ 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.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 @@ -84,11 +119,14 @@ def draw_SVG_line(a, b, style, name, parent): 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) + 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 @@ -122,30 +160,43 @@ def angle_from_3_sides(a, b, c): # return the angle opposite side c 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]') +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 +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 + 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) @@ -153,9 +204,12 @@ def get_n_points_from_path(node, n): 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 + 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) @@ -178,20 +232,20 @@ def cot(x): # cotangent(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 + 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' + 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' + self.c_th = svg.unittouu("2px") + self.c_fill = "none" + self.c_col = "#000000" class DrawFromTriangle(inkex.EffectExtension): @@ -220,18 +274,20 @@ class DrawFromTriangle(inkex.EffectExtension): 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='cos(a_a):cos(a_b):cos(a_c)') + pars.add_argument("--mode", default="trilin") + pars.add_argument("--cust_str", default="cos(a_a):cos(a_b):cos(a_c)") 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*s_b*s_c/(4*area)') + pars.add_argument("--radius", default="s_a*s_b*s_c/(4*area)") 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 + pts = ( + [] + ) # initialise in case nothing is selected and following loop is not executed for node in self.svg.selection.filter(inkex.PathElement): # find the (x,y) coordinates of the first 3 points of the path pts = get_n_points_from_path(node, 3) @@ -241,19 +297,27 @@ class DrawFromTriangle(inkex.EffectExtension): # 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]) + ')' + 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]]] + 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 + 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) @@ -266,130 +330,164 @@ class DrawFromTriangle(inkex.EffectExtension): 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 + 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 + 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) + draw_SVG_circle(0, pt, params, st, "Circumcentre", layer) if so.circumcircle: - draw_SVG_circle(r, pt, params, st, 'Circumcircle', layer) + 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) + draw_SVG_circle(0, pt, params, st, "Incentre", layer) if so.incircle: r = area / semiperim - draw_SVG_circle(r, pt, params, st, 'Incircle', layer) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + draw_SVG_tri(v_mat, params, st, "SymmedialTriangle", layer) if so.symmedians: - draw_vertex_lines(v_mat, params, st, 'Symmedian', layer) + 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) + 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) + 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) + 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 + if so.mode == "trilin": # if we are receiving from trilinears for i in range(3): - strings = so.cust_str.split(':') # get split string + 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 + 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 + 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) + 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 + func = eval( + "lambda params: " + strings.strip('"') + ) # the function leading to the radius r = func(params) - draw_SVG_circle(r, pt, params, st, 'CustomTrilinearCircle', layer) + 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) + 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) + draw_SVG_circle( + 0, isotomic, params, st, "CustomIsotomicConjugate", layer + ) -if __name__ == '__main__': +if __name__ == "__main__": DrawFromTriangle().run() diff --git a/dxf12_outlines.py b/dxf12_outlines.py index 63ed72ca..13eccfba 100755 --- a/dxf12_outlines.py +++ b/dxf12_outlines.py @@ -28,7 +28,7 @@ import re import inkex from inkex.bezier import cspsubdiv -r12_header = ''' 0 +r12_header = """ 0 SECTION 2 HEADER @@ -54,35 +54,36 @@ ENDSEC SECTION 2 ENTITIES -''' +""" -r12_footer = ''' 0 +r12_footer = """ 0 ENDSEC 0 -EOF''' +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')) + 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("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])) + 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 @@ -108,26 +109,31 @@ class DxfTwelve(inkex.OutputExtension): def save(self, stream): self._stream = stream - self.dxf_insert_code('999', '"DXF R12 Output" (www.mydxf.blogspot.com)') + self.dxf_insert_code("999", '"DXF R12 Output" (www.mydxf.blogspot.com)') self.dxf_add(r12_header) - scale = 1 # TODO this assumes that one user unit corresponds to one mm + scale = 1 # TODO this assumes that one user unit corresponds to one mm h = self.svg.viewbox_height - path = '//svg:path' + path = "//svg:path" for node in self.svg.xpath(path): - layer = node.getparent().label # TODO this assumes that all elements are direct - # descendants of layers + layer = ( + node.getparent().label + ) # TODO this assumes that all elements are direct + # descendants of layers if layer is None: - layer = 'Layer 1' + layer = "Layer 1" node.transform = node.composed_transform() - node.transform = inkex.Transform([[scale, 0, 0], [0, -scale, h * scale]]) @ node.transform + node.transform = ( + inkex.Transform([[scale, 0, 0], [0, -scale, h * scale]]) + @ node.transform + ) node.apply_transform() path = node.path.to_superpath() - if re.search('drill$', layer, re.I) is None: + if re.search("drill$", layer, re.I) is None: # if layer == 'Brackets Drill': self.dxf_path_to_lines(layer, path) else: @@ -136,5 +142,5 @@ class DxfTwelve(inkex.OutputExtension): self.dxf_add(r12_footer) -if __name__ == '__main__': +if __name__ == "__main__": DxfTwelve().run() diff --git a/dxf_input.py b/dxf_input.py index 529fc57e..1fc29847 100644 --- a/dxf_input.py +++ b/dxf_input.py @@ -35,7 +35,7 @@ from lxml import etree import inkex global defs -global block #2021.6 +global block # 2021.6 global layer global svg global scale @@ -46,96 +46,441 @@ global style_font3 global style_direction COLORS = [ - 'PAD', - '#FF0000', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#FF00FF', '#000000', '#808080', - '#C0C0C0', '#FF0000', '#FF7F7F', '#CC0000', '#CC6666', '#990000', '#994C4C', '#7F0000', - '#7F3F3F', '#4C0000', '#4C2626', '#FF3F00', '#FF9F7F', '#CC3300', '#CC7F66', '#992600', - '#995F4C', '#7F1F00', '#7F4F3F', '#4C1300', '#4C2F26', '#FF7F00', '#FFBF7F', '#CC6600', - '#CC9966', '#994C00', '#99724C', '#7F3F00', '#7F5F3F', '#4C2600', '#4C3926', '#FFBF00', - '#FFDF7F', '#CC9900', '#CCB266', '#997200', '#99854C', '#7F5F00', '#7F6F3F', '#4C3900', - '#4C4226', '#FFFF00', '#FFFF7F', '#CCCC00', '#CCCC66', '#989800', '#98984C', '#7F7F00', - '#7F7F3F', '#4C4C00', '#4C4C26', '#BFFF00', '#DFFF7F', '#99CC00', '#B2CC66', '#729800', - '#85984C', '#5F7F00', '#6F7F3F', '#394C00', '#424C26', '#7FFF00', '#BFFF7F', '#66CC00', - '#99CC66', '#4C9800', '#72984C', '#3F7F00', '#5F7F3F', '#264C00', '#394C26', '#3FFF00', - '#9FFF7F', '#33CC00', '#7FCC66', '#269800', '#5F984C', '#1F7F00', '#4F7F3F', '#134C00', - '#2F4C26', '#00FF00', '#7FFF7F', '#00CC00', '#66CC66', '#009800', '#4C984C', '#007F00', - '#3F7F3F', '#004C00', '#264C26', '#00FF3F', '#7FFF9F', '#00CC33', '#66CC7F', '#009826', - '#4C985F', '#007F1F', '#3F7F4F', '#004C13', '#264C2F', '#00FF7F', '#7FFFBF', '#00CC66', - '#66CC99', '#00984C', '#4C9872', '#007F3F', '#3F7F5F', '#004C26', '#264C39', '#00FFBF', - '#7FFFDF', '#00CC99', '#66CCB2', '#009872', '#4C9885', '#007F5F', '#3F7F6F', '#004C39', - '#264C42', '#00FFFF', '#7FFFFF', '#00CCCC', '#66CCCC', '#009898', '#4C9898', '#007F7F', - '#FF0000', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#FF00FF', '#000000', '#808080', - '#C0C0C0', '#FF0000', '#FF7F7F', '#CC0000', '#CC6666', '#990000', '#994C4C', '#7F0000', - '#7F3F3F', '#4C0000', '#4C2626', '#FF3F00', '#FF9F7F', '#CC3300', '#CC7F66', '#992600', - '#995F4C', '#7F1F00', '#7F4F3F', '#4C1300', '#4C2F26', '#FF7F00', '#FFBF7F', '#CC6600', - '#CC9966', '#994C00', '#99724C', '#7F3F00', '#7F5F3F', '#4C2600', '#4C3926', '#FFBF00', - '#FFDF7F', '#CC9900', '#CCB266', '#997200', '#99854C', '#7F5F00', '#7F6F3F', '#4C3900', - '#4C4226', '#FFFF00', '#FFFF7F', '#CCCC00', '#CCCC66', '#989800', '#98984C', '#7F7F00', - '#7F7F3F', '#4C4C00', '#4C4C26', '#BFFF00', '#DFFF7F', '#99CC00', '#B2CC66', '#729800', - '#85984C', '#5F7F00', '#6F7F3F', '#394C00', '#424C26', '#7FFF00', '#BFFF7F', '#66CC00', - '#99CC66', '#4C9800', '#72984C', '#3F7F00', '#5F7F3F', '#264C00', '#394C26', '#3FFF00', - '#9FFF7F', '#33CC00', '#7FCC66', '#269800', '#5F984C', '#1F7F00', '#4F7F3F', '#134C00', - '#2F4C26', '#00FF00', '#7FFF7F', '#00CC00', '#66CC66', '#009800', '#4C984C', '#007F00', - '#3F7F3F', '#004C00', '#264C26', '#00FF3F', '#7FFF9F', '#00CC33', '#66CC7F', '#009826', - '#4C985F', '#007F1F', '#3F7F4F', '#004C13', '#264C2F', '#00FF7F', '#7FFFBF', '#00CC66', - '#66CC99', '#00984C', '#4C9872', '#007F3F', '#3F7F5F', '#004C26', '#264C39', '#00FFBF', - '#7FFFDF', '#00CC99', '#66CCB2', '#009872', '#4C9885', '#007F5F', '#3F7F6F', '#004C39', - '#264C42', '#00FFFF', '#7FFFFF', '#00CCCC', '#66CCCC', '#009898', '#4C9898', '#007F7F', - '#3F7F7F', '#004C4C', '#264C4C', '#00BFFF', '#7FDFFF', '#0099CC', '#66B2CC', '#007298', - '#4C8598', '#005F7F', '#3F6F7F', '#00394C', '#26424C', '#007FFF', '#7FBFFF', '#0066CC', - '#6699CC', '#004C98', '#4C7298', '#003F7F', '#3F5F7F', '#00264C', '#26394C', '#003FFF', - '#7F9FFF', '#0033CC', '#667FCC', '#002698', '#4C5F98', '#001F7F', '#3F4F7F', '#00134C', - '#262F4C', '#0000FF', '#7F7FFF', '#0000CC', '#6666CC', '#000098', '#4C4C98', '#00007F', - '#3F3F7F', '#00004C', '#26264C', '#3F00FF', '#9F7FFF', '#3300CC', '#7F66CC', '#260098', - '#5F4C98', '#1F007F', '#4F3F7F', '#13004C', '#2F264C', '#7F00FF', '#BF7FFF', '#6600CC', - '#9966CC', '#4C0098', '#724C98', '#3F007F', '#5F3F7F', '#26004C', '#39264C', '#BF00FF', - '#DF7FFF', '#9900CC', '#B266CC', '#720098', '#854C98', '#5F007F', '#6F3F7F', '#39004C', - '#42264C', '#FF00FF', '#FF7FFF', '#CC00CC', '#CC66CC', '#980098', '#984C98', '#7F007F', - '#7F3F7F', '#4C004C', '#4C264C', '#FF00BF', '#FF7FDF', '#CC0099', '#CC66B2', '#980072', - '#984C85', '#7F005F', '#7F3F6F', '#4C0039', '#4C2642', '#FF007F', '#FF7FBF', '#CC0066', - '#CC6699', '#98004C', '#984C72', '#7F003F', '#7F3F5F', '#4C0026', '#4C2639', '#FF003F', - '#FF7F9F', '#CC0033', '#CC667F', '#980026', '#984C5F', '#7F001F', '#7F3F4F', '#4C0013', - '#4C262F', '#333333', '#5B5B5B', '#848484', '#ADADAD', '#D6D6D6', '#FFFFFF' + "PAD", + "#FF0000", + "#FFFF00", + "#00FF00", + "#00FFFF", + "#0000FF", + "#FF00FF", + "#000000", + "#808080", + "#C0C0C0", + "#FF0000", + "#FF7F7F", + "#CC0000", + "#CC6666", + "#990000", + "#994C4C", + "#7F0000", + "#7F3F3F", + "#4C0000", + "#4C2626", + "#FF3F00", + "#FF9F7F", + "#CC3300", + "#CC7F66", + "#992600", + "#995F4C", + "#7F1F00", + "#7F4F3F", + "#4C1300", + "#4C2F26", + "#FF7F00", + "#FFBF7F", + "#CC6600", + "#CC9966", + "#994C00", + "#99724C", + "#7F3F00", + "#7F5F3F", + "#4C2600", + "#4C3926", + "#FFBF00", + "#FFDF7F", + "#CC9900", + "#CCB266", + "#997200", + "#99854C", + "#7F5F00", + "#7F6F3F", + "#4C3900", + "#4C4226", + "#FFFF00", + "#FFFF7F", + "#CCCC00", + "#CCCC66", + "#989800", + "#98984C", + "#7F7F00", + "#7F7F3F", + "#4C4C00", + "#4C4C26", + "#BFFF00", + "#DFFF7F", + "#99CC00", + "#B2CC66", + "#729800", + "#85984C", + "#5F7F00", + "#6F7F3F", + "#394C00", + "#424C26", + "#7FFF00", + "#BFFF7F", + "#66CC00", + "#99CC66", + "#4C9800", + "#72984C", + "#3F7F00", + "#5F7F3F", + "#264C00", + "#394C26", + "#3FFF00", + "#9FFF7F", + "#33CC00", + "#7FCC66", + "#269800", + "#5F984C", + "#1F7F00", + "#4F7F3F", + "#134C00", + "#2F4C26", + "#00FF00", + "#7FFF7F", + "#00CC00", + "#66CC66", + "#009800", + "#4C984C", + "#007F00", + "#3F7F3F", + "#004C00", + "#264C26", + "#00FF3F", + "#7FFF9F", + "#00CC33", + "#66CC7F", + "#009826", + "#4C985F", + "#007F1F", + "#3F7F4F", + "#004C13", + "#264C2F", + "#00FF7F", + "#7FFFBF", + "#00CC66", + "#66CC99", + "#00984C", + "#4C9872", + "#007F3F", + "#3F7F5F", + "#004C26", + "#264C39", + "#00FFBF", + "#7FFFDF", + "#00CC99", + "#66CCB2", + "#009872", + "#4C9885", + "#007F5F", + "#3F7F6F", + "#004C39", + "#264C42", + "#00FFFF", + "#7FFFFF", + "#00CCCC", + "#66CCCC", + "#009898", + "#4C9898", + "#007F7F", + "#FF0000", + "#FFFF00", + "#00FF00", + "#00FFFF", + "#0000FF", + "#FF00FF", + "#000000", + "#808080", + "#C0C0C0", + "#FF0000", + "#FF7F7F", + "#CC0000", + "#CC6666", + "#990000", + "#994C4C", + "#7F0000", + "#7F3F3F", + "#4C0000", + "#4C2626", + "#FF3F00", + "#FF9F7F", + "#CC3300", + "#CC7F66", + "#992600", + "#995F4C", + "#7F1F00", + "#7F4F3F", + "#4C1300", + "#4C2F26", + "#FF7F00", + "#FFBF7F", + "#CC6600", + "#CC9966", + "#994C00", + "#99724C", + "#7F3F00", + "#7F5F3F", + "#4C2600", + "#4C3926", + "#FFBF00", + "#FFDF7F", + "#CC9900", + "#CCB266", + "#997200", + "#99854C", + "#7F5F00", + "#7F6F3F", + "#4C3900", + "#4C4226", + "#FFFF00", + "#FFFF7F", + "#CCCC00", + "#CCCC66", + "#989800", + "#98984C", + "#7F7F00", + "#7F7F3F", + "#4C4C00", + "#4C4C26", + "#BFFF00", + "#DFFF7F", + "#99CC00", + "#B2CC66", + "#729800", + "#85984C", + "#5F7F00", + "#6F7F3F", + "#394C00", + "#424C26", + "#7FFF00", + "#BFFF7F", + "#66CC00", + "#99CC66", + "#4C9800", + "#72984C", + "#3F7F00", + "#5F7F3F", + "#264C00", + "#394C26", + "#3FFF00", + "#9FFF7F", + "#33CC00", + "#7FCC66", + "#269800", + "#5F984C", + "#1F7F00", + "#4F7F3F", + "#134C00", + "#2F4C26", + "#00FF00", + "#7FFF7F", + "#00CC00", + "#66CC66", + "#009800", + "#4C984C", + "#007F00", + "#3F7F3F", + "#004C00", + "#264C26", + "#00FF3F", + "#7FFF9F", + "#00CC33", + "#66CC7F", + "#009826", + "#4C985F", + "#007F1F", + "#3F7F4F", + "#004C13", + "#264C2F", + "#00FF7F", + "#7FFFBF", + "#00CC66", + "#66CC99", + "#00984C", + "#4C9872", + "#007F3F", + "#3F7F5F", + "#004C26", + "#264C39", + "#00FFBF", + "#7FFFDF", + "#00CC99", + "#66CCB2", + "#009872", + "#4C9885", + "#007F5F", + "#3F7F6F", + "#004C39", + "#264C42", + "#00FFFF", + "#7FFFFF", + "#00CCCC", + "#66CCCC", + "#009898", + "#4C9898", + "#007F7F", + "#3F7F7F", + "#004C4C", + "#264C4C", + "#00BFFF", + "#7FDFFF", + "#0099CC", + "#66B2CC", + "#007298", + "#4C8598", + "#005F7F", + "#3F6F7F", + "#00394C", + "#26424C", + "#007FFF", + "#7FBFFF", + "#0066CC", + "#6699CC", + "#004C98", + "#4C7298", + "#003F7F", + "#3F5F7F", + "#00264C", + "#26394C", + "#003FFF", + "#7F9FFF", + "#0033CC", + "#667FCC", + "#002698", + "#4C5F98", + "#001F7F", + "#3F4F7F", + "#00134C", + "#262F4C", + "#0000FF", + "#7F7FFF", + "#0000CC", + "#6666CC", + "#000098", + "#4C4C98", + "#00007F", + "#3F3F7F", + "#00004C", + "#26264C", + "#3F00FF", + "#9F7FFF", + "#3300CC", + "#7F66CC", + "#260098", + "#5F4C98", + "#1F007F", + "#4F3F7F", + "#13004C", + "#2F264C", + "#7F00FF", + "#BF7FFF", + "#6600CC", + "#9966CC", + "#4C0098", + "#724C98", + "#3F007F", + "#5F3F7F", + "#26004C", + "#39264C", + "#BF00FF", + "#DF7FFF", + "#9900CC", + "#B266CC", + "#720098", + "#854C98", + "#5F007F", + "#6F3F7F", + "#39004C", + "#42264C", + "#FF00FF", + "#FF7FFF", + "#CC00CC", + "#CC66CC", + "#980098", + "#984C98", + "#7F007F", + "#7F3F7F", + "#4C004C", + "#4C264C", + "#FF00BF", + "#FF7FDF", + "#CC0099", + "#CC66B2", + "#980072", + "#984C85", + "#7F005F", + "#7F3F6F", + "#4C0039", + "#4C2642", + "#FF007F", + "#FF7FBF", + "#CC0066", + "#CC6699", + "#98004C", + "#984C72", + "#7F003F", + "#7F3F5F", + "#4C0026", + "#4C2639", + "#FF003F", + "#FF7F9F", + "#CC0033", + "#CC667F", + "#980026", + "#984C5F", + "#7F001F", + "#7F3F4F", + "#4C0013", + "#4C262F", + "#333333", + "#5B5B5B", + "#848484", + "#ADADAD", + "#D6D6D6", + "#FFFFFF", ] + def get_rgbcolor(dxfcolor): if dxfcolor in range(1, len(COLORS)): rgbcolor = COLORS[dxfcolor] else: - rgbcolor = '#000000' + rgbcolor = "#000000" return rgbcolor + class ValueConstruct(defaultdict): """Store values from the DXF and provide them as named attributes""" + values = { - '1': ('text', 'default'), - '2': ('tag', 'block_name'), - '3': ('mtext',), - '6': ('line_type',), - '7': ('text_style',), - '8': ('layer_name',), - '10': ('x1',), - '11': ('x2',), - '13': ('x3',), - '14': ('x4',), - '20': ('y1',), - '21': ('y2',), - '23': ('y3',), - '24': ('y4',), - '40': ('scale', 'knots', 'radius', 'width_ratio'), - '41': ('ellipse_a1', 'insert_scale_x'), - '42': ('ellipse_a2', 'bulge', 'insert_scale_y'), - '50': ('angle',), - '51': ('angle2',), - '62': ('color',), - '70': ('fill', 'flags'), - '71': ('attach_pos',), - '72': ('edge_type',), - '73': ('sweep',), # ccw - '92': ('path_type',), - '93': ('num_edges',), - '230': ('extrude',), - '370': ('line_weight',), + "1": ("text", "default"), + "2": ("tag", "block_name"), + "3": ("mtext",), + "6": ("line_type",), + "7": ("text_style",), + "8": ("layer_name",), + "10": ("x1",), + "11": ("x2",), + "13": ("x3",), + "14": ("x4",), + "20": ("y1",), + "21": ("y2",), + "23": ("y3",), + "24": ("y4",), + "40": ("scale", "knots", "radius", "width_ratio"), + "41": ("ellipse_a1", "insert_scale_x"), + "42": ("ellipse_a2", "bulge", "insert_scale_y"), + "50": ("angle",), + "51": ("angle2",), + "62": ("color",), + "70": ("fill", "flags"), + "71": ("attach_pos",), + "72": ("edge_type",), + "73": ("sweep",), # ccw + "92": ("path_type",), + "93": ("num_edges",), + "230": ("extrude",), + "370": ("line_weight",), } attrs = dict([(name, a) for a, b in values.items() for name in b]) @@ -147,14 +492,14 @@ class ValueConstruct(defaultdict): return key in cls.values def __getattr__(self, attr): - is_list = attr.endswith('_list') + is_list = attr.endswith("_list") key = attr[:-5] if is_list else attr if key in self.attrs: ret = self[self.attrs[key]] - if not attr.endswith('_list'): + if not attr.endswith("_list"): return ret[0] return ret - if attr.startswith('has_'): + if attr.startswith("has_"): key = attr[4:] if key in self.attrs: return self.attrs[key] in self @@ -169,16 +514,18 @@ class ValueConstruct(defaultdict): def adjust_coords(self, xmin, ymin, scale, extrude, height): """Adjust the x,y coordinates to fit on the page""" - for xgrp in set(['10', '11', '13', '14']) & set(self): # scale/reflect x values + for xgrp in set(["10", "11", "13", "14"]) & set(self): # scale/reflect x values for i in range(len(self[xgrp])): self[xgrp][i] = scale * (extrude * self[xgrp][i] - xmin) - for ygrp in set(['20', '21', '23', '24']) & set(self): # scale y values + for ygrp in set(["20", "21", "23", "24"]) & set(self): # scale y values for i in range(len(self[ygrp])): self[ygrp][i] = height - scale * (self[ygrp][i] - ymin) + export_viewport = False export_endsec = False + def re_hex2unichar(m): # return unichr(int(m.group(1), 16)) return chr(int(m.group(1), 16)) @@ -187,15 +534,17 @@ def re_hex2unichar(m): def formatStyle(style): return str(inkex.Style(style)) + def export_text(vals): # mandatory group codes : (11, 12, 72, 73) (fit_x, fit_y, horizon, vertical) # TODO: position to display at by (x2,y2) according to 72(horizon),73(vertical) # groupcode 72:0(left),1(center),2(right),3(both side),4(middle),5(fit) # grouocode 73:0(standard),1(floor),2(center),3(ceiling) - vals['71'].append(1) # attach=pos left in mtext - vals['70'].append(1) # text: flags=1 + vals["71"].append(1) # attach=pos left in mtext + vals["70"].append(1) # text: flags=1 return export_mtext(vals) + def export_mtext(vals): # mandatory group codes : (1 or 3, 10, 20) (text, x, y) # TODO: text-format: \Font; \W; \Q; \L..\l etc @@ -209,119 +558,127 @@ def export_mtext(vals): size = scale * textscale * vals.scale dx = dy = 0 - if not vals.has_flags: # as mtext, putting in the box + if not vals.has_flags: # as mtext, putting in the box dy = size - anchor = 'start' + anchor = "start" if vals.has_attach_pos: if vals.attach_pos in (2, 5, 8): - anchor = 'middle' + anchor = "middle" elif vals.attach_pos in (3, 6, 9): - anchor = 'end' + anchor = "end" if vals.attach_pos in (4, 5, 6): - dy = size/2 - #if vals.has_text_style and vals.text_style in style_font3 : + dy = size / 2 + # if vals.has_text_style and vals.text_style in style_font3 : # attribs = {'x': '%f' % x, 'y': '%f' % y, 'style': 'font-size: %.3fpx; fill: %s; font-family: %s' \ # % (size, color, style_font3[vals.text_style])} - #else : + # else : # attribs = {'x': '%f' % x, 'y': '%f' % y, 'style': 'font-size: %.3fpx; fill: %s; font-family: %s' % (size, color, options.font)} - attribs = {'x': '%f' % x, 'y': '%f' % y, 'style': 'font-size: %.3fpx; fill: %s; font-family: %s; text-anchor: %s' \ - % (size, color, options.font, anchor)} + attribs = { + "x": "%f" % x, + "y": "%f" % y, + "style": "font-size: %.3fpx; fill: %s; font-family: %s; text-anchor: %s" + % (size, color, options.font, anchor), + } angle = 0 # default angle in degrees bVertical = False if vals.has_angle: # TEXT only if vals.angle != 0: angle = vals.angle - #attribs.update({'transform': 'rotate (%f %f %f)' % (-angle, x, y)}) + # attribs.update({'transform': 'rotate (%f %f %f)' % (-angle, x, y)}) elif vals.has_y2 and vals.has_x2: # MTEXT # recover original data # (x,y)=(scale*(x-xmin), height-scale*(y-ymin) - orgx = vals.x2/scale+xmin - orgy = -(vals.y2-height)/scale+ymin - unit = math.sqrt(orgy*orgy + orgx*orgx) + orgx = vals.x2 / scale + xmin + orgy = -(vals.y2 - height) / scale + ymin + unit = math.sqrt(orgy * orgy + orgx * orgx) if (unit < 1.01) and (unit > 0.99): ang1 = math.atan2(orgy, orgx) angle = 180 * ang1 / math.pi - #attribs.update({'transform': 'rotate (%f %f %f)' % (-angle, x, y)}) + # attribs.update({'transform': 'rotate (%f %f %f)' % (-angle, x, y)}) if vals.has_text_style and vals.text_style in style_direction: if style_direction[vals.text_style] & 4: - #angle = -90 - #attribs.update({'transform': 'rotate (%f %f %f)' % (-angle, x, y)}) + # angle = -90 + # attribs.update({'transform': 'rotate (%f %f %f)' % (-angle, x, y)}) bVertical = True angle = 0 dx = size - attribs = {'x': '%f' % x, 'y': '%f' % y, \ - 'style': 'font-size: %.3fpx; fill: %s; font-family: %s; text-anchor: %s; writing-mode: tb' \ - % (size, color, options.font, anchor)} + attribs = { + "x": "%f" % x, + "y": "%f" % y, + "style": "font-size: %.3fpx; fill: %s; font-family: %s; text-anchor: %s; writing-mode: tb" + % (size, color, options.font, anchor), + } if angle != 0: - attribs.update({'transform': 'rotate (%f %f %f)' % (-angle, x, y)}) + attribs.update({"transform": "rotate (%f %f %f)" % (-angle, x, y)}) node = layer.add(inkex.TextElement(**attribs)) - node.set('sodipodi:linespacing', '125%') - text = '' + node.set("sodipodi:linespacing", "125%") + text = "" if vals.has_mtext: - text = ''.join(vals.mtext_list) + text = "".join(vals.mtext_list) if vals.has_text: text += vals.text lines = 0 - found = text.find(r'\P') # new line + found = text.find(r"\P") # new line while found > -1: tspan = node.add(inkex.Tspan()) if bVertical: - tspan.set('y', '%f' % y) + tspan.set("y", "%f" % y) if lines > 0: - tspan.set('dx', '%f' % size) + tspan.set("dx", "%f" % size) else: - tspan.set('sodipodi:role', 'line') - tspan.set('dx', '%f' % dx) - tspan.set('dy', '%f' % dy) - #tspan.text = text[:found] + tspan.set("sodipodi:role", "line") + tspan.set("dx", "%f" % dx) + tspan.set("dy", "%f" % dy) + # tspan.text = text[:found] text1 = text[:found] mtext_separate(node, tspan, text1) - text = text[(found + 2):] - found = text.find(r'\P') + text = text[(found + 2) :] + found = text.find(r"\P") lines += 1 tspan = node.add(inkex.Tspan()) if bVertical: - tspan.set('y', '%f' % y) + tspan.set("y", "%f" % y) if lines > 0: - tspan.set('dx', '%f' % dx) + tspan.set("dx", "%f" % dx) else: - tspan.set('sodipodi:role', 'line') - tspan.set('dx', '%f' % dx) - tspan.set('dy', '%f' % dy) - #tspan.text = text + tspan.set("sodipodi:role", "line") + tspan.set("dx", "%f" % dx) + tspan.set("dy", "%f" % dy) + # tspan.text = text text1 = text mtext_separate(node, tspan, text1) + def mtext_separate(node, tspan, text): # sparate aaa{bbb}(ccc) -> aaa,bbb.ccc tspanAdd = True - found = text.find(r'{') + found = text.find(r"{") while found > -1: if found == 0: - found1 = text.find(r'}') + found1 = text.find(r"}") if found1 < 1: break - text1 = text[:found1] # tspan - text1 = text1[found+1:] + text1 = text[:found1] # tspan + text1 = text1[found + 1 :] if tspanAdd == False: tspan = node.add(inkex.Tspan()) mtext_ctrl(tspan, text1) - #tspan.text = text1 +'+1' + # tspan.text = text1 +'+1' tspanAdd = False - text = text[found1+1:] - found = text.find(r'{') + text = text[found1 + 1 :] + found = text.find(r"{") else: - text1 = text[:found] # tspan + text1 = text[:found] # tspan if tspanAdd == False: tspan = node.add(inkex.Tspan()) mtext_ctrl(tspan, text1) - #tspan.text = text1 +'+2' + # tspan.text = text1 +'+2' tspanAdd = False text = text[found:] found = 0 @@ -331,17 +688,18 @@ def mtext_separate(node, tspan, text): if tspanAdd == False: tspan = node.add(inkex.Tspan()) mtext_ctrl(tspan, text1) - #tspan.text = text1 +'+3' + # tspan.text = text1 +'+3' tspanAdd = False + def mtext_ctrl(tspan, phrase): - if phrase[0] != '\\': + if phrase[0] != "\\": tspan.text = phrase return # if you'll add the function, you should remove the auto re.sub at setting group code:1 - if phrase[1] in ('C', 'H', 'T', 'Q', 'W', 'A'): + if phrase[1] in ("C", "H", "T", "Q", "W", "A"): # get the value - found = phrase.find(r';') + found = phrase.find(r";") if found > 2: cvalue = phrase[:found] cvalue = cvalue[2:] @@ -351,26 +709,27 @@ def mtext_ctrl(tspan, phrase): done = False else: done = True - if phrase[1] == 'C': + if phrase[1] == "C": i = int(value) color = get_rgbcolor(i) - tspan.set('style', 'stroke: %s' % color) - elif phrase[1] == 'H': + tspan.set("style", "stroke: %s" % color) + elif phrase[1] == "H": value *= scale - tspan.set('style', 'font-size: %.3fpx;' % value) - elif phrase[1] == 'T': - tspan.set('style', 'letter-spacing: %f;' % value) - tspan.text = phrase[found+1:] + tspan.set("style", "font-size: %.3fpx;" % value) + elif phrase[1] == "T": + tspan.set("style", "letter-spacing: %f;" % value) + tspan.text = phrase[found + 1 :] else: tspan.text = phrase else: tspan.text = phrase + def export_point(vals, w): # mandatory group codes : (10, 20) (x, y) if vals.has_x1 and vals.has_y1: - if vals['70']: - inkex.errormsg('$PDMODE is ignored. A point is displayed as normal.') + if vals["70"]: + inkex.errormsg("$PDMODE is ignored. A point is displayed as normal.") if options.gcodetoolspoints: generate_gcodetools_point(vals.x1, vals.y1) else: @@ -383,27 +742,44 @@ def export_line(vals): if vals.has_x1 and vals.has_x2 and vals.has_y1 and vals.has_y2: path = inkex.PathElement() path.style = style - path.path = 'M %f,%f %f,%f' % (vals.x1, vals.y1, vals.x2, vals.y2) + path.path = "M %f,%f %f,%f" % (vals.x1, vals.y1, vals.x2, vals.y2) layer.add(path) + def export_solid(vals): # arrows of dimension # mandatory group codes : (10, 11, 12, 20, 21, 22) (x1, x2, x3, y1, y2, y3) # TODO: 4th point - if vals.has_x1 and vals.has_x2 and vals.has_x3 \ - and vals.has_y1 and vals.has_y2 and vals.has_y3: + if ( + vals.has_x1 + and vals.has_x2 + and vals.has_x3 + and vals.has_y1 + and vals.has_y2 + and vals.has_y3 + ): path = inkex.PathElement() path.style = style - path.path = 'M %f,%f %f,%f %f,%f z' % \ - (vals.x1, vals.y1, vals.x2, vals.y2, vals.x3, vals.y3) + path.path = "M %f,%f %f,%f %f,%f z" % ( + vals.x1, + vals.y1, + vals.x2, + vals.y2, + vals.x3, + vals.y3, + ) layer.add(path) def export_spline(vals): # see : http://www.mactech.com/articles/develop/issue_25/schneider.html # mandatory group codes : (10, 20, 40, 70) (x[], y[], knots[], flags) - if vals.has_flags and vals.has_knots and vals.x1_list \ - and len(vals.x1_list) == len(vals.y1_list): + if ( + vals.has_flags + and vals.has_knots + and vals.x1_list + and len(vals.x1_list) == len(vals.y1_list) + ): knots = vals.knots_list ctrls = len(vals.x1_list) if ctrls > 3 and len(knots) == ctrls + 4: # cubic @@ -412,32 +788,77 @@ def export_spline(vals): if knots[i] != knots[i - 1] and knots[i] != knots[i + 1]: a0 = (knots[i] - knots[i - 2]) / (knots[i + 1] - knots[i - 2]) a1 = (knots[i] - knots[i - 1]) / (knots[i + 2] - knots[i - 1]) - vals.x1_list.insert(i - 1, (1.0 - a1) * vals.x1_list[i - 2] + a1 * vals.x1_list[i - 1]) - vals.y1_list.insert(i - 1, (1.0 - a1) * vals.y1_list[i - 2] + a1 * vals.y1_list[i - 1]) - vals.x1_list[i - 2] = (1.0 - a0) * vals.x1_list[i - 3] + a0 * vals.x1_list[i - 2] - vals.y1_list[i - 2] = (1.0 - a0) * vals.y1_list[i - 3] + a0 * vals.y1_list[i - 2] + vals.x1_list.insert( + i - 1, + (1.0 - a1) * vals.x1_list[i - 2] + a1 * vals.x1_list[i - 1], + ) + vals.y1_list.insert( + i - 1, + (1.0 - a1) * vals.y1_list[i - 2] + a1 * vals.y1_list[i - 1], + ) + vals.x1_list[i - 2] = (1.0 - a0) * vals.x1_list[ + i - 3 + ] + a0 * vals.x1_list[i - 2] + vals.y1_list[i - 2] = (1.0 - a0) * vals.y1_list[ + i - 3 + ] + a0 * vals.y1_list[i - 2] knots.insert(i, knots[i]) for i in range(len(knots) - 6, 3, -2): - if knots[i] != knots[i + 2] and knots[i - 1] != knots[i + 1] and knots[i - 2] != knots[i]: + if ( + knots[i] != knots[i + 2] + and knots[i - 1] != knots[i + 1] + and knots[i - 2] != knots[i] + ): a1 = (knots[i] - knots[i - 1]) / (knots[i + 2] - knots[i - 1]) - vals.x1_list.insert(i - 1, (1.0 - a1) * vals.x1_list[i - 2] + a1 * vals.x1_list[i - 1]) - vals.y1_list.insert(i - 1, (1.0 - a1) * vals.y1_list[i - 2] + a1 * vals.y1_list[i - 1]) + vals.x1_list.insert( + i - 1, + (1.0 - a1) * vals.x1_list[i - 2] + a1 * vals.x1_list[i - 1], + ) + vals.y1_list.insert( + i - 1, + (1.0 - a1) * vals.y1_list[i - 2] + a1 * vals.y1_list[i - 1], + ) ctrls = len(vals.x1_list) - path = 'M %f,%f' % (vals.x1, vals.y1) + path = "M %f,%f" % (vals.x1, vals.y1) for i in range(0, (ctrls - 1) // 3): - path += ' C %f,%f %f,%f %f,%f' % (vals.x1_list[3 * i + 1], vals.y1_list[3 * i + 1], vals.x1_list[3 * i + 2], vals.y1_list[3 * i + 2], vals.x1_list[3 * i + 3], vals.y1_list[3 * i + 3]) + path += " C %f,%f %f,%f %f,%f" % ( + vals.x1_list[3 * i + 1], + vals.y1_list[3 * i + 1], + vals.x1_list[3 * i + 2], + vals.y1_list[3 * i + 2], + vals.x1_list[3 * i + 3], + vals.y1_list[3 * i + 3], + ) if vals.flags & 1: # closed path - path += ' z' - attribs = {'d': path, 'style': style} - etree.SubElement(layer, 'path', attribs) + path += " z" + attribs = {"d": path, "style": style} + etree.SubElement(layer, "path", attribs) if ctrls == 3 and len(knots) == 6: # quadratic - path = 'M %f,%f Q %f,%f %f,%f' % (vals.x1, vals.y1, vals.x1_list[1], vals.y1_list[1], vals.x1_list[2], vals.y1_list[2]) - attribs = {'d': path, 'style': style} - etree.SubElement(layer, 'path', attribs) + path = "M %f,%f Q %f,%f %f,%f" % ( + vals.x1, + vals.y1, + vals.x1_list[1], + vals.y1_list[1], + vals.x1_list[2], + vals.y1_list[2], + ) + attribs = {"d": path, "style": style} + etree.SubElement(layer, "path", attribs) if ctrls == 5 and len(knots) == 8: # spliced quadratic - path = 'M %f,%f Q %f,%f %f,%f Q %f,%f %f,%f' % (vals.x1, vals.y1, vals.x1_list[1], vals.y1_list[1], vals.x1_list[2], vals.y1_list[2], vals.x1_list[3], vals.y1_list[3], vals.x1_list[4], vals.y1_list[4]) - attribs = {'d': path, 'style': style} - etree.SubElement(layer, 'path', attribs) + path = "M %f,%f Q %f,%f %f,%f Q %f,%f %f,%f" % ( + vals.x1, + vals.y1, + vals.x1_list[1], + vals.y1_list[1], + vals.x1_list[2], + vals.y1_list[2], + vals.x1_list[3], + vals.y1_list[3], + vals.x1_list[4], + vals.y1_list[4], + ) + attribs = {"d": path, "style": style} + etree.SubElement(layer, "path", attribs) def export_circle(vals): @@ -445,39 +866,63 @@ def export_circle(vals): if vals.has_x1 and vals.has_y1 and vals.has_radius: generate_ellipse(vals.x1, vals.y1, scale * vals.radius, 0.0, 1.0, 0.0, 0.0) + def export_arc(vals): # mandatory group codes : (10, 20, 40, 50, 51) (x, y, radius, angle1, angle2) - if vals.has_x1 and vals.has_y1 and vals.has_radius and vals.has_angle and vals.has_angle2: - generate_ellipse(vals.x1, vals.y1, - scale * vals.radius, 0.0, 1.0, vals.angle * math.pi / 180.0, - vals.angle2 * math.pi / 180.0) + if ( + vals.has_x1 + and vals.has_y1 + and vals.has_radius + and vals.has_angle + and vals.has_angle2 + ): + generate_ellipse( + vals.x1, + vals.y1, + scale * vals.radius, + 0.0, + 1.0, + vals.angle * math.pi / 180.0, + vals.angle2 * math.pi / 180.0, + ) def export_ellipse(vals): # mandatory group codes : (10, 11, 20, 21, 40, 41, 42) (xc, xm, yc, ym, width ratio, angle1, angle2) - if vals.has_x1 and vals.has_x2 and vals.has_y1 and vals.has_y2 and \ - vals.has_width_ratio and vals.has_ellipse_a1 and vals.has_ellipse_a2: - #generate_ellipse(vals.x1, vals.y1, scale*vals.x2, scale*vals.y2, vals.width_ratio, vals.ellipse_a1, vals.ellipse_a2) + if ( + vals.has_x1 + and vals.has_x2 + and vals.has_y1 + and vals.has_y2 + and vals.has_width_ratio + and vals.has_ellipse_a1 + and vals.has_ellipse_a2 + ): + # generate_ellipse(vals.x1, vals.y1, scale*vals.x2, scale*vals.y2, vals.width_ratio, vals.ellipse_a1, vals.ellipse_a2) # vals are through adjust_coords : recover proper value # (x,y)=(scale*x-xmin, height-scale*y-ymin) - x2 = vals.x2+xmin - y2 = vals.y2+ymin-height - generate_ellipse(vals.x1, vals.y1, x2, y2, vals.width_ratio, vals.ellipse_a1, vals.ellipse_a2) + x2 = vals.x2 + xmin + y2 = vals.y2 + ymin - height + generate_ellipse( + vals.x1, vals.y1, x2, y2, vals.width_ratio, vals.ellipse_a1, vals.ellipse_a2 + ) def export_leader(vals): # mandatory group codes : (10, 20) (x, y) if vals.has_x1 and vals.has_y1: if len(vals.x1_list) > 1 and len(vals.y1_list) == len(vals.x1_list): - path = 'M %f,%f' % (vals.x1, vals.y1) + path = "M %f,%f" % (vals.x1, vals.y1) for i in range(1, len(vals.x1_list)): - path += ' %f,%f' % (vals.x1_list[i], vals.y1_list[i]) - attribs = {'d': path, 'style': style} - etree.SubElement(layer, 'path', attribs) + path += " %f,%f" % (vals.x1_list[i], vals.y1_list[i]) + attribs = {"d": path, "style": style} + etree.SubElement(layer, "path", attribs) + def export_polyline(vals): return export_lwpolyline(vals) + def export_lwpolyline(vals): # mandatory group codes : (10, 20, 70) (x, y, flags) if vals.has_x1 and vals.has_y1 and vals.has_flags: @@ -486,19 +931,19 @@ def export_lwpolyline(vals): iseqs = 0 ibulge = 0 if vals.flags & 1: # closed path - seqs.append('20') + seqs.append("20") vals.x1_list.append(vals.x1) vals.y1_list.append(vals.y1) - while seqs[iseqs] != '20': + while seqs[iseqs] != "20": iseqs += 1 - path = 'M %f,%f' % (vals.x1, vals.y1) + path = "M %f,%f" % (vals.x1, vals.y1) xold = vals.x1 yold = vals.y1 for i in range(1, len(vals.x1_list)): bulge = 0 iseqs += 1 - while seqs[iseqs] != '20': - if seqs[iseqs] == '42': + while seqs[iseqs] != "20": + if seqs[iseqs] == "42": bulge = vals.bulge_list[ibulge] ibulge += 1 iseqs += 1 @@ -510,45 +955,63 @@ def export_lwpolyline(vals): large = 0 # large-arc-flag if bulge > 1: large = 1 - r = math.sqrt((vals.x1_list[i] - xold) ** 2 + (vals.y1_list[i] - yold) ** 2) + r = math.sqrt( + (vals.x1_list[i] - xold) ** 2 + (vals.y1_list[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.x1_list[i], vals.y1_list[i]) + path += " A %f,%f 0.0 %d %d %f,%f" % ( + r, + r, + large, + sweep, + vals.x1_list[i], + vals.y1_list[i], + ) else: - path += ' L %f,%f' % (vals.x1_list[i], vals.y1_list[i]) + path += " L %f,%f" % (vals.x1_list[i], vals.y1_list[i]) xold = vals.x1_list[i] yold = vals.y1_list[i] if vals.flags & 1: # closed path - path += ' z' - attribs = {'d': path, 'style': style} - etree.SubElement(layer, 'path', attribs) + path += " z" + attribs = {"d": path, "style": style} + etree.SubElement(layer, "path", attribs) def export_hatch(vals): # mandatory group codes : (10, 20, 70, 72, 92, 93) (x, y, fill, Edge Type, Path Type, Number of edges) # TODO: Hatching Pattern - if vals.has_x1 and vals.has_y1 and vals.has_fill and vals.has_edge_type \ - and vals.has_path_type and vals.has_num_edges: + if ( + vals.has_x1 + and vals.has_y1 + and vals.has_fill + and vals.has_edge_type + and vals.has_path_type + and vals.has_num_edges + ): if len(vals.x1_list) > 1 and len(vals.y1_list) == len(vals.x1_list): # 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 = '' + path = "" for i in range(0, len(vals.num_edges_list)): xc = vals.x1_list[i10] yc = vals.y1_list[i10] if vals.edge_type_list[i72] == 2: # arc rm = scale * vals.radius_list[i40] a1 = vals.angle_list[i40] - path += 'M %f,%f ' % (xc + rm * math.cos(a1 * math.pi / 180.0), yc + rm * math.sin(a1 * math.pi / 180.0)) + 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) + path += "M %f,%f " % (xc, yc) for j in range(0, vals.num_edges_list[i]): if vals.path_type_list[i] & 2: # polyline if j > 0: - path += 'L %f,%f ' % (vals.x1_list[i10], vals.y1_list[i10]) + path += "L %f,%f " % (vals.x1_list[i10], vals.y1_list[i10]) if j == vals.path_type_list[i] - 1: i72 += 1 elif vals.edge_type_list[i72] == 2: # arc @@ -560,40 +1023,61 @@ def export_hatch(vals): sweep = 1 - vals.sweep_list[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)) + 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)) + 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.edge_type_list[i72] == 1: # line - path += 'L %f,%f ' % (vals.x2_list[i11], vals.y2_list[i11]) + path += "L %f,%f " % (vals.x2_list[i11], vals.y2_list[i11]) i11 += 1 i72 += 1 i10 += 1 path += "z " if vals.has_fill: - style = formatStyle({'fill': '%s' % color}) + 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) + style = formatStyle({"fill": "url(#Hatch)", "fill-opacity": "1.0"}) + attribs = {"d": path, "style": style} + etree.SubElement(layer, "path", attribs) def export_dimension(vals): # mandatory group codes : (10, 11, 13, 14, 20, 21, 23, 24) (x1..4, y1..4) # block_name: dimension definition for 10mm - if vals.has_x1 and vals.has_x2 and \ - vals.has_y1 and vals.has_y2: + if vals.has_x1 and vals.has_x2 and vals.has_y1 and vals.has_y2: if vals.has_block_name: - attribs = {inkex.addNS('href', 'xlink') : \ - '#%s' % (vals.block_name)} # not use quote because name *D2 etc. changed to %2AD2 - tform = 'translate(0 0)' - #if vals.has_angle : + attribs = { + inkex.addNS("href", "xlink"): "#%s" % (vals.block_name) + } # not use quote because name *D2 etc. changed to %2AD2 + tform = "translate(0 0)" + # if vals.has_angle : # tform += ' rotate(%f,%f,%f)' % (vals.angle,vals.x4,vals.y4) - attribs.update({'transform' : tform}) - etree.SubElement(layer, 'use', attribs) + attribs.update({"transform": tform}) + etree.SubElement(layer, "use", attribs) else: # TODO: improve logic when INSERT in BLOCK dx = abs(vals.x1 - vals.x3) @@ -601,15 +1085,19 @@ def export_dimension(vals): if (vals.x1 == vals.x4) and dx > 0.00001: d = dx / scale dy = 0 - path = 'M %f,%f %f,%f' % (vals.x1, vals.y1, vals.x3, vals.y1) + path = "M %f,%f %f,%f" % (vals.x1, vals.y1, vals.x3, vals.y1) elif (vals.y1 == vals.y4) and dy > 0.00001: d = dy / scale dx = 0 - path = 'M %f,%f %f,%f' % (vals.x1, vals.y1, vals.x1, vals.y3) + path = "M %f,%f %f,%f" % (vals.x1, vals.y1, vals.x1, vals.y3) else: return - attribs = {'d': path, 'style': style + '; marker-start: url(#DistanceX); marker-end: url(#DistanceX); stroke-width: 0.25px'} - etree.SubElement(layer, 'path', attribs) + attribs = { + "d": path, + "style": style + + "; marker-start: url(#DistanceX); marker-end: url(#DistanceX); stroke-width: 0.25px", + } + etree.SubElement(layer, "path", attribs) x = vals.x2 y = vals.y2 size = 12 # default fontsize in px @@ -618,13 +1106,19 @@ def export_dimension(vals): size = scale * textscale * DIMTXT[vals.mtext] if size < 2: size = 2 - attribs = {'x': '%f' % x, 'y': '%f' % y, 'style': 'font-size: %.3fpx; fill: %s; font-family: %s; text-anchor: middle; text-align: center' % (size, color, options.font)} + attribs = { + "x": "%f" % x, + "y": "%f" % y, + "style": "font-size: %.3fpx; 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) + 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)) + tspan.set("sodipodi:role", "line") + tspan.text = str(float("%.2f" % d)) + def export_insert(vals): # mandatory group codes : (2, 10, 20) (block name, x, y) @@ -638,8 +1132,8 @@ def export_insert(vals): # 2021.6 translate..ok scale..x rotate X # as scale, the line is wider ->same width -> you should fix global height - cx = scale * xmin # transorm-origin: - cy = scale * ymin + height # center of rotation + cx = scale * xmin # transorm-origin: + cy = scale * ymin + height # center of rotation # x = vals.x1 + scale * xmin y = vals.y1 - scale * ymin - height @@ -648,43 +1142,49 @@ def export_insert(vals): ixscale = vals.insert_scale_x if vals.has_insert_scale_y: iyscale = vals.insert_scale_y - x += cx*(iyscale-1) - y -= cy*(iyscale-1) - #elem = layer.add(inkex.Use()) - #elem.set('xlink:href', '#' + quote(vals.block_name.replace(" ", "_").encode("utf-8"))) - #elem.transform.add_translate(x, y) - #if vals.has_insert_scale_x and vals.has_insert_scale_y: + x += cx * (iyscale - 1) + y -= cy * (iyscale - 1) + # elem = layer.add(inkex.Use()) + # elem.set('xlink:href', '#' + quote(vals.block_name.replace(" ", "_").encode("utf-8"))) + # elem.transform.add_translate(x, y) + # if vals.has_insert_scale_x and vals.has_insert_scale_y: # elem.transform.add_scale(vals.insert_scale_x, vals.insert_scale_y) # - #attribs = {inkex.addNS('href', 'xlink') : + # attribs = {inkex.addNS('href', 'xlink') : # '#' + quote(vals.block_name.replace(" ", "_").encode("utf-8"))} # for reducing thick lines - fwide = abs(0.5/ixscale) # better to use w/ixscale - attribs = {inkex.addNS('href', 'xlink') : - '#' + quote(vals.block_name.replace(" ", "_").encode("utf-8")), 'style': 'stroke-width: %.3fpx' % fwide} + fwide = abs(0.5 / ixscale) # better to use w/ixscale + attribs = { + inkex.addNS("href", "xlink"): "#" + + quote(vals.block_name.replace(" ", "_").encode("utf-8")), + "style": "stroke-width: %.3fpx" % fwide, + } # add style stroke-width=1px 2021.jyuly - tform = '' - tform += 'translate(%f, %f) ' % (x, y) + tform = "" + tform += "translate(%f, %f) " % (x, y) if vals.has_insert_scale_x and vals.has_insert_scale_y: - #tform += 'scale(%f,%f)' % (vals.insert_scale_x, vals.insert_scale_y) - tform += 'scale(%f,%f) ' % (ixscale, iyscale) + # tform += 'scale(%f,%f)' % (vals.insert_scale_x, vals.insert_scale_y) + tform += "scale(%f,%f) " % (ixscale, iyscale) if vals.has_angle: - tform += 'rotate(%f,%f,%f) ' % (360 - vals.angle, -cx, cy) - attribs.update({'transform' : tform}) - etree.SubElement(layer, 'use', attribs) + tform += "rotate(%f,%f,%f) " % (360 - vals.angle, -cx, cy) + attribs.update({"transform": tform}) + etree.SubElement(layer, "use", attribs) + def export_block(vals): # mandatory group codes : (2) (block name) if vals.has_block_name: global block - block = etree.SubElement(defs, 'symbol', - {'id': vals.block_name.replace(" ", "_")}) + block = etree.SubElement( + defs, "symbol", {"id": vals.block_name.replace(" ", "_")} + ) def export_endblk(vals): global block block = defs # initiallize with dummy + def export_attdef(vals): # mandatory group codes : (1, 2) (default, tag) if vals.has_default and vals.has_tag: @@ -694,7 +1194,7 @@ def export_attdef(vals): def generate_ellipse(xc, yc, xm, ym, w, a1, a2): rm = math.sqrt(xm * xm + ym * ym) - a = -math.atan2(ym, xm) # x-axis-rotation + a = -math.atan2(ym, xm) # x-axis-rotation 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 @@ -708,22 +1208,48 @@ def generate_ellipse(xc, yc, xm, ym, w, a1, 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) + 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 0, 0 %f,%f A %f,%f %f 0, 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) + path = "M %f,%f A %f,%f %f 0, 0 %f,%f A %f,%f %f 0, 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) + 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 + class DxfInput(inkex.InputExtension): def add_arguments(self, pars): pars.add_argument("--tab", default="options") @@ -764,12 +1290,67 @@ class DxfInput(inkex.InputExtension): 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'}) + 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) @@ -791,111 +1372,120 @@ class DxfInput(inkex.InputExtension): layer_nodes = {} # store nodes by layer linetypes = {} # store linetypes by name DIMTXT = {} # store DIMENSION text sizes - #style_name = {} # style name - style_font3 = {} # style font 1byte - style_font4 = {} # style font 2byte - style_direction = {} # style display direction + # style_name = {} # style name + style_font3 = {} # style font 1byte + style_font4 = {} # style font 2byte + style_direction = {} # style display direction line = get_line() - if (line[0] == "AutoCAD Binary DXF"): - inkex.errormsg('Inkscape cannot read binary DXF files. \nPlease convert to ASCII format first.' + str(len(line[0])) + " " + str(len(line[1]))) + if line[0] == "AutoCAD Binary DXF": + inkex.errormsg( + "Inkscape cannot read binary DXF files. \nPlease convert to ASCII format first." + + str(len(line[0])) + + " " + + str(len(line[1])) + ) self.document = doc return inENTITIES = False - style_name = '*' + style_name = "*" layername = None linename = None stylename = None style_name = None errno = 0 pdmode_err = False - while line[0] and line[1] != 'BLOCKS': + while line[0] and line[1] != "BLOCKS": line = get_line() - if line[1] == 'ENTITIES': # no BLOCK SECTION + if line[1] == "ENTITIES": # no BLOCK SECTION inENTITIES = True break - if (line[1] == '$PDMODE' and not pdmode_err): - inkex.errormsg('$PDMODE is ignored. A point is displayed as normal.') + if line[1] == "$PDMODE" and not pdmode_err: + inkex.errormsg("$PDMODE is ignored. A point is displayed as normal.") pdmode_err = True - 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': + 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': + if flag == 2 and line[0] == "2": linename = line[1] linetypes[linename] = [] - if flag == 3 and line[0] == '2': + if flag == 3 and line[0] == "2": stylename = line[1] - if flag == 4 and line[0] == '2': + if flag == 4 and line[0] == "2": style_name = line[1] style_font3[style_name] = [] style_font4[style_name] = [] style_direction[style_name] = [] - if line[0] == '2' and line[1] == 'LAYER': + if line[0] == "2" and line[1] == "LAYER": flag = 1 - if line[0] == '2' and line[1] == 'LTYPE': + if line[0] == "2" and line[1] == "LTYPE": flag = 2 - if line[0] == '2' and line[1] == 'DIMSTYLE': + if line[0] == "2" and line[1] == "DIMSTYLE": flag = 3 - if line[0] == '2' and line[1] == 'STYLE': + if line[0] == "2" and line[1] == "STYLE": flag = 4 - if flag == 1 and line[0] == '62': + if flag == 1 and line[0] == "62": if layername is None: errno = 1 break layer_colors[layername] = int(line[1]) - if flag == 2 and line[0] == '49': + if flag == 2 and line[0] == "49": if linename is None: errno = 2 break linetypes[linename].append(float(line[1])) - if flag == 3 and line[0] == '140': + if flag == 3 and line[0] == "140": if stylename is None: errno = 3 break DIMTXT[stylename] = float(line[1]) - if flag == 4 and line[0] == '3': + if flag == 4 and line[0] == "3": if style_name is None: errno = 4 break style_font3[style_name].append(line[1]) - if flag == 4 and line[0] == '4': + if flag == 4 and line[0] == "4": if style_name is None: errno = 4 break style_font4[style_name].append(line[1]) - if flag == 4 and line[0] == '70': # not no of STYLE - if style_name != '*': + if flag == 4 and line[0] == "70": # not no of STYLE + if style_name != "*": style_direction[style_name] = int(line[1]) - if line[0] == '0' and line[1] == 'ENDTAB': + if line[0] == "0" and line[1] == "ENDTAB": flag = 0 if errno != 0: if errno == 1: - errMsg = 'LAYER' + errMsg = "LAYER" elif errno == 2: - errMsg = 'LTYPE' + errMsg = "LTYPE" elif errno == 3: - errMsg = 'DIMSTYLE' - else: #errno == 4 - errMsg = 'STYLE' - inkex.errormsg('Import stopped. DXF is incorrect.\ngroup code 2 ('+errMsg+') is missing') + errMsg = "DIMSTYLE" + else: # errno == 4 + errMsg = "STYLE" + inkex.errormsg( + "Import stopped. DXF is incorrect.\ngroup code 2 (" + + errMsg + + ") is missing" + ) self.document = doc return - if options.scalemethod == 'file': + if options.scalemethod == "file": scale = 25.4 # default inches if measurement == 1.0: scale = 1.0 # use mm - elif options.scalemethod == 'auto': + elif options.scalemethod == "auto": scale = 1.0 if xmax > xmin: scale = 210.0 / (xmax - xmin) # scale to A4 width @@ -903,49 +1493,54 @@ class DxfInput(inkex.InputExtension): scale = float(options.scale) # manual scale factor xmin = float(options.xmin) ymin = float(options.ymin) - svg.desc = '%s - scale = %f, origin = (%f, %f), method = %s' % ( - os.path.basename(options.input_file), scale, xmin, ymin, options.scalemethod) + svg.desc = "%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 textscale = float(options.textscale) - if '0' not in layer_nodes: - layer_nodes['0'] = svg.add(inkex.Layer.new('0')) + if "0" not in layer_nodes: + layer_nodes["0"] = svg.add(inkex.Layer.new("0")) - layer_colors['0'] = 7 + layer_colors["0"] = 7 for linename in linetypes.keys(): # scale the dashed lines - linetype = '' + linetype = "" for length in linetypes[linename]: if length == 0: # test for dot - linetype += ' 0.5,' + linetype += " 0.5," else: - linetype += '%.4f,' % math.fabs(length * scale) - if linetype == '': - linetypes[linename] = 'stroke-linecap: round' + linetype += "%.4f," % math.fabs(length * scale) + if linetype == "": + linetypes[linename] = "stroke-linecap: round" else: - linetypes[linename] = 'stroke-dasharray:' + linetype + linetypes[linename] = "stroke-dasharray:" + linetype - entity = '' + entity = "" block = defs # initiallize with dummy - while line[0] and (line[1] != 'ENDSEC' or not inENTITIES): + while line[0] and (line[1] != "ENDSEC" or not inENTITIES): line = get_line() - if line[1] == 'ENTITIES': + if line[1] == "ENTITIES": inENTITIES = True if entity and vals.is_valid(line[0]): seqs.append(line[0]) # list of group codes - if line[0] in ('1', '2', '3', '6', '7', '8'): # text value + if line[0] in ("1", "2", "3", "6", "7", "8"): # text value # TODO: if add funs of export_mtext, delete the line - 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) {\\C; '}' in mtext - val = re.sub(r'\\S.*;', '', val) - val = re.sub(r'\\W.*;', '', val) + 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) {\\C; '}' in mtext + 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] in ('62', '70', '92', '93'): + val = re.sub(r"\\U\+([0-9A-Fa-f]{4})", re_hex2unichar, val) + elif line[0] in ("62", "70", "92", "93"): val = int(line[1]) else: # unscaled float value val = float(line[1]) @@ -956,32 +1551,42 @@ class DxfInput(inkex.InputExtension): layer = block elif vals.has_layer_name: # use Common Layer Name if not vals.layer_name: - vals.layer_name = '0' # use default name + vals.layer_name = "0" # use default name if vals.layer_name not in layer_nodes: - #attribs = {inkex.addNS('groupmode','inkscape') : + # attribs = {inkex.addNS('groupmode','inkscape') : # 'layer', inkex.addNS('label','inkscape') : '%s' % vals.layer_name} - #layer_nodes[vals.layer_name] = etree.SubElement(doc.getroot(), 'g', attribs) - layer_nodes[vals.layer_name] = svg.add(inkex.Layer.new(vals.layer_name)) + # layer_nodes[vals.layer_name] = etree.SubElement(doc.getroot(), 'g', attribs) + layer_nodes[vals.layer_name] = svg.add( + inkex.Layer.new(vals.layer_name) + ) layer = layer_nodes[vals.layer_name] - color = '#000000' # default color + color = "#000000" # default color if vals.has_layer_name: if vals.layer_name in layer_colors: color = get_rgbcolor(layer_colors[vals.layer_name]) if vals.has_color: # Common Color Number color = get_rgbcolor(vals.color) - style = formatStyle({'stroke': '%s' % color, 'fill': 'none'}) + style = formatStyle({"stroke": "%s" % color, "fill": "none"}) w = 0.5 # default lineweight for POINT if vals.has_line_weight: # Common Lineweight if vals.line_weight > 0: w = 96.0 / 25.4 * vals.line_weight / 100.0 - w *= scale # real wide : line_weight /144 inch + w *= scale # real wide : line_weight /144 inch if w < 0.5: w = 0.5 - if block == defs: # not in a BLOCK for INSERT except stroke-width 2021.july - style = formatStyle({'stroke': '%s' % color, 'fill': 'none', 'stroke-width': '%.3f' % w}) + if ( + block == defs + ): # not in a BLOCK for INSERT except stroke-width 2021.july + style = formatStyle( + { + "stroke": "%s" % color, + "fill": "none", + "stroke-width": "%.3f" % w, + } + ) if vals.has_line_type: # Common Linetype if vals.line_type in linetypes: - style += ';' + linetypes[vals.line_type] + style += ";" + linetypes[vals.line_type] extrude = 1.0 if vals.has_extrude: extrude = float(vals.extrude) @@ -990,70 +1595,73 @@ class DxfInput(inkex.InputExtension): if extrude == -1.0: # reflect angles if vals.has_angle and vals.has_angle2: - vals.angle2, vals.angle = 180.0 - vals.angle, 180.0 - vals.angle2 + vals.angle2, vals.angle = ( + 180.0 - vals.angle, + 180.0 - vals.angle2, + ) exporter = get_export(entity) if exporter: - if entity == 'POINT': + if entity == "POINT": exporter(vals, w) else: exporter(vals) - if line[1] == 'POLYLINE': - inVertexs = False - entity = 'LWPOLYLINE' + if line[1] == "POLYLINE": + inVertexs = False + entity = "LWPOLYLINE" vals = ValueConstruct() seqs = [] - flag70 = 0 # default closed-line or not - val8 = '0' # default layer name - val10 = 0 # x - val20 = 0 # y - val42 = 0 # bulge + flag70 = 0 # default closed-line or not + val8 = "0" # default layer name + val10 = 0 # x + val20 = 0 # y + val42 = 0 # bulge valid = True - while line[0] and (line[1] != 'SEQEND'): + while line[0] and (line[1] != "SEQEND"): line = get_line() - if line[1] == 'VERTEX': + if line[1] == "VERTEX": if inVertexs == True: if valid: - seqs.append('10') - vals['10'].append(val10) - seqs.append('20') - vals['20'].append(val20) - seqs.append('42') - vals['42'].append(val42) + seqs.append("10") + vals["10"].append(val10) + seqs.append("20") + vals["20"].append(val20) + seqs.append("42") + vals["42"].append(val42) val42 = 0 inVertexs = True valid = True if inVertexs == False: - if line[0] == '6': # 6:line style + if line[0] == "6": # 6:line style seqs.append(line[0]) vals[line[0]].append(line[1]) - if line[0] == '8': # 8:layer + if line[0] == "8": # 8:layer val8 = line[1] - if line[0] == '70': # flag + if line[0] == "70": # flag flag70 = int(line[1]) else: - if line[0] == '70': - if int(line[1]) == 16: # control point + if line[0] == "70": + if int(line[1]) == 16: # control point valid = False - if line[0] in ('10', '20', '42'): # vertexs + if line[0] in ("10", "20", "42"): # vertexs val = float(line[1]) - if line[0] == '10': + if line[0] == "10": val10 = val - elif line[0] == '20': + elif line[0] == "20": val20 = val else: val42 = val if valid: - seqs.append('8') # layer_name - vals['8'].append(val8) - seqs.append('10') - vals['10'].append(val10) - seqs.append('20') - vals['20'].append(val20) - seqs.append('42') # bulge - vals['42'].append(val42) - seqs.append('70') # closed line? - vals['70'].append(flag70) + seqs.append("8") # layer_name + vals["8"].append(val8) + seqs.append("10") + vals["10"].append(val10) + seqs.append("20") + vals["20"].append(val20) + seqs.append("42") # bulge + vals["42"].append(val42) + seqs.append("70") # closed line? + vals["70"].append(flag70) continue entity = line[1] @@ -1061,15 +1669,18 @@ class DxfInput(inkex.InputExtension): seqs = [] # for debug - #tree = etree.ElementTree(svg) - #tree.write('c:\Python\svgCH2.xml') + # tree = etree.ElementTree(svg) + # tree.write('c:\Python\svgCH2.xml') self.document = doc + def get_export(opt): - return globals().get('export_' + opt.lower(), None) + return globals().get("export_" + opt.lower(), None) + def has_export(opt): return get_export(opt) is not None -if __name__ == '__main__': + +if __name__ == "__main__": DxfInput().run() diff --git a/dxf_outlines.py b/dxf_outlines.py index 4e3572b1..4c61a4df 100755 --- a/dxf_outlines.py +++ b/dxf_outlines.py @@ -36,24 +36,53 @@ The spec can be found here: http://www.autodesk.com/techpubs/autocad/acadr14/dxf from __future__ import print_function import inkex -from inkex import colors, bezier, Transform, Group, Layer, Use, PathElement, \ - Rectangle, Line, Circle, Ellipse +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]) + 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]) + 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]) + 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] + 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): @@ -67,15 +96,15 @@ class DxfOutlines(inkex.OutputExtension): self.dxf = [] self.handle = 255 # handle for DXF ENTITY - self.layers = ['0'] - self.layer = '0' # mandatory layer + 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.d = [0.0] # knot vector self.poly = [[0.0, 0.0]] # LWPOLYLINE data def save(self, stream): - stream.write(b''.join(self.dxf)) + stream.write(b"".join(self.dxf)) def dxf_add(self, str): self.dxf.append(str.encode(self.options.char_encode)) @@ -83,13 +112,21 @@ class DxfOutlines(inkex.OutputExtension): 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])) + 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 + if ( + abs(csp[0][0] - self.poly[-1][0]) > 0.0001 + or abs(csp[0][1] - self.poly[-1][1]) > 0.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 @@ -101,18 +138,34 @@ class DxfOutlines(inkex.OutputExtension): 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): + if ( + abs(self.poly[0][0] - self.poly[-1][0]) > 0.0001 + or abs(self.poly[0][1] - self.poly[-1][1]) > 0.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)) + 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])) + 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( + " 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): @@ -122,9 +175,15 @@ class DxfOutlines(inkex.OutputExtension): 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): + if ( + abs(csp[0][0] - self.csp_old[3][0]) > 0.0001 + or abs(csp[0][1] - self.csp_old[3][1]) > 0.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]) + ) + > 0.001 + ): self.ROBO_output() # terminate current spline self.xfit = [csp[0][0]] # initiallize new spline self.yfit = [csp[0][1]] @@ -138,7 +197,9 @@ class DxfOutlines(inkex.OutputExtension): 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.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): @@ -146,7 +207,9 @@ class DxfOutlines(inkex.OutputExtension): 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.") + 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: @@ -154,9 +217,9 @@ class DxfOutlines(inkex.OutputExtension): fits = len(self.d) ctrls = fits + 2 knots = ctrls + 4 - self.xfit += 2 * [0.0] # pad with 2 endpoint constraints + 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 += 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) @@ -167,14 +230,25 @@ class DxfOutlines(inkex.OutputExtension): 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] + 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)) + 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): @@ -184,7 +258,7 @@ class DxfOutlines(inkex.OutputExtension): def process_shape(self, node, mat): rgb = (0, 0, 0) - style = node.style('stroke') + style = node.style("stroke") if style is not None and isinstance(style, inkex.Color): rgb = style.to_rgb() hsl = colors.rgb_to_hsl(rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0) @@ -213,9 +287,9 @@ class DxfOutlines(inkex.OutputExtension): 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') + 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) @@ -227,7 +301,7 @@ class DxfOutlines(inkex.OutputExtension): if trans or x or y: self.groupmat.append(Transform(self.groupmat[-1]) @ mat) # get referenced node - refid = node.get('xlink:href') + refid = node.get("xlink:href") refnode = self.svg.getElementById(refid[1:]) if refnode is not None: if isinstance(refnode, Group): @@ -244,17 +318,21 @@ class DxfOutlines(inkex.OutputExtension): """Process group elements""" if isinstance(group, Layer): style = group.style - if style("display") == 'none' and self.options.layer_option and self.options.layer_option == 'visible': + if ( + style("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 self.options.layer_name and self.options.layer_option == "name": if not layer.lower() in self.options.layer_name: return - layer = layer.replace(' ', '_') + layer = layer.replace(" ", "_") if layer in self.layers: self.layer = layer - trans = group.get('transform') + trans = group.get("transform") if trans: self.groupmat.append(Transform(self.groupmat[-1]) @ Transform(trans)) for node in group: @@ -269,46 +347,62 @@ class DxfOutlines(inkex.OutputExtension): 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") + 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(',') + 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: + with open(self.get_resource("dxf14_header.txt"), "r") as fhl: self.dxf_add(fhl.read()) - for node in self.svg.xpath('//svg:g'): + 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: + 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(' ', '_') + 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)) + 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( + " 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') + scale /= self.svg.unittouu("1px") h = self.svg.viewbox_height doc = self.document.getroot() # process viewBox height attribute to correct page scaling - viewBox = doc.get('viewBox') + viewBox = doc.get("viewBox") if viewBox: - viewBox2 = viewBox.split(',') + viewBox2 = viewBox.split(",") if len(viewBox2) < 4: - viewBox2 = viewBox.split(' ') + 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) @@ -316,14 +410,18 @@ class DxfOutlines(inkex.OutputExtension): self.ROBO_output() if self.options.POLY: self.LWPOLY_output() - with open(self.get_resource('dxf14_footer.txt'), 'r') as fhl: + 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': + 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__': +if __name__ == "__main__": DxfOutlines().run() diff --git a/edge3d.py b/edge3d.py index 66788ea0..69a4e258 100755 --- a/edge3d.py +++ b/edge3d.py @@ -26,24 +26,37 @@ 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') + 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""" + 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 + 360.0 return x # rotate all inputs by start, => start = 0 @@ -53,9 +66,9 @@ class Edge3D(inkex.EffectExtension): 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""" + 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. + delta = 360.0 / self.options.shades / 2.0 for node in self.svg.selection.filter(inkex.PathElement): array = node.path.to_arrays() group = None @@ -71,20 +84,21 @@ class Edge3D(inkex.EffectExtension): last = [] result = [] for cmd, params in array: - if cmd == 'Z': + if cmd == "Z": last = [] continue if last: - if cmd == 'V': + if cmd == "V": point = [last[0], params[-2:][0]] - elif cmd == 'H': + 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)) + 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: @@ -95,10 +109,10 @@ class Edge3D(inkex.EffectExtension): group, filt = self.get_group(node) new_node = group.add(node.copy()) new_node.path = result - new_node.style = 'fill:none;stroke-opacity:1;stroke-width:10' + new_node.style = "fill:none;stroke-opacity:1;stroke-width:10" new_node.style += filt - col = 255 - int(255. * level) - new_node.style['stroke'] = inkex.Color((col, col, col)) + col = 255 - int(255.0 * level) + new_node.style["stroke"] = inkex.Color((col, col, col)) def get_group(self, node): """ @@ -110,15 +124,21 @@ class Edge3D(inkex.EffectExtension): new_node = clip.add(node.copy()) clip_group = node.getparent().add(inkex.Group()) group = clip_group.add(inkex.Group()) - clip_group.set('clip-path', clip.get_id(as_url=2)) + clip_group.set("clip-path", clip.get_id(as_url=2)) # 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 = 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) + filt.add_primitive("feGaussianBlur", stdDeviation=self.options.stddev) return group, inkex.Style(filter=filt.get_id(as_url=2)) -if __name__ == '__main__': + +if __name__ == "__main__": Edge3D().run() diff --git a/export_gimp_palette.py b/export_gimp_palette.py index 0c82a0b7..bfdcae38 100755 --- a/export_gimp_palette.py +++ b/export_gimp_palette.py @@ -27,24 +27,27 @@ from inkex import ShapeElement, ColorIdError, ColorError class ExportGimpPalette(inkex.OutputExtension): """Export all colors in a document to a gimp pallet""" + select_all = (ShapeElement,) names = {} def save(self, stream): - name = self.svg.name.replace('.svg', '') - stream.write('GIMP Palette\nName: {}\n#\n'.format(name).encode('utf-8')) + 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')) - + 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): for color in self.process_element(elem): - if str(color).upper() == 'NONE': + if str(color).upper() == "NONE": continue - yield ("{:3d} {:3d} {:3d}".format(*color.to_rgb()), self.names.get(color) or str(color).upper()) + yield ( + "{:3d} {:3d} {:3d}".format(*color.to_rgb()), + self.names.get(color) or str(color).upper(), + ) def process_element(self, elem): """Recursively process elements for colors""" @@ -52,7 +55,7 @@ class ExportGimpPalette(inkex.OutputExtension): for col in inkex.Style.color_props: try: col = inkex.Color(style.get(col)) - if (elem.getparent().get('inkscape:swatch') == "solid"): + if elem.getparent().get("inkscape:swatch") == "solid": self.names[col] = elem.getparent().get_id() yield col except ColorIdError: @@ -61,12 +64,14 @@ class ExportGimpPalette(inkex.OutputExtension): for item in self.process_element(stop): yield item except ColorError: - pass # Bad color + pass # Bad color - if elem.href is not None: # Capture colors of symbols or clones pointing to defs + if ( + elem.href is not None + ): # Capture colors of symbols or clones pointing to defs for color in self.process_element(elem.href): yield color -if __name__ == '__main__': +if __name__ == "__main__": ExportGimpPalette().run() diff --git a/extension-manager-bootstrap.py b/extension-manager-bootstrap.py index cca13120..6b38d2bf 100644 --- a/extension-manager-bootstrap.py +++ b/extension-manager-bootstrap.py @@ -31,7 +31,7 @@ from inkex.base import InkscapeExtension from inkex.command import CommandNotFound, ProgramRunError, call TARGET_DIR = get_user_directory() -FALLBACK_DIR = os.path.join(TARGET_DIR or './', 'org.inkscape.inkman') +FALLBACK_DIR = os.path.join(TARGET_DIR or "./", "org.inkscape.inkman") if os.path.isdir(FALLBACK_DIR): sys.path.insert(0, FALLBACK_DIR) @@ -42,12 +42,13 @@ except ImportError: FALLBACK_URL = "https://media.inkscape.org/static/extensions-manager-fallback.zip" + class Bootstrap(InkscapeExtension): multi_inx = True def add_arguments(self, pars): - pars.add_argument('--tab') - pars.add_argument('--version', default='inkscape-extensions-manager') + pars.add_argument("--tab") + pars.add_argument("--version", default="inkscape-extensions-manager") def load_raw(self): pass @@ -66,12 +67,13 @@ class Bootstrap(InkscapeExtension): def effect(self): fallback = False try: - call('virtualenv', TARGET_DIR, p='python3') + call("virtualenv", TARGET_DIR, p="python3") except CommandNotFound: fallback = True except ProgramRunError as err: raise inkex.AbortExtension( - "There has been a problem creating the python environment:\n" + str(err)) + "There has been a problem creating the python environment:\n" + str(err) + ) if fallback: # Add a fallback for places like windows where python isn't available. @@ -80,14 +82,21 @@ class Bootstrap(InkscapeExtension): raise inkex.AbortExtension( "You must have the python-virtualenv package installed. This should have" " been included with Inkscape, but in some special cases it might not" - " be. Please install this software externally and try again.") + " be. Please install this software externally and try again." + ) try: - call(os.path.join(TARGET_DIR, 'bin', 'pip'), 'install', self.options.version) + call( + os.path.join(TARGET_DIR, "bin", "pip"), "install", self.options.version + ) except CommandNotFound: - raise inkex.AbortExtension("Can't find pip program after environment initialisation!") + raise inkex.AbortExtension( + "Can't find pip program after environment initialisation!" + ) except ProgramRunError as err: - raise inkex.AbortExtension("Error installing extension manager package:\n" + str(err)) + raise inkex.AbortExtension( + "Error installing extension manager package:\n" + str(err) + ) def install_fallback(self): """ @@ -105,12 +114,15 @@ class Bootstrap(InkscapeExtension): if remote and remote.status_code == 200: with zipfile.ZipFile(io.BytesIO(remote.content)) as archive: for filename in archive.namelist(): - if '.inx' in filename: + if ".inx" in filename: done = True - self._install_file(archive.read(filename), - os.path.join(FALLBACK_DIR, filename)) + self._install_file( + archive.read(filename), os.path.join(FALLBACK_DIR, filename) + ) except NewConnectionError: - self.msg("Could not connect to the internet, please check connection and try again!") + self.msg( + "Could not connect to the internet, please check connection and try again!" + ) finally: session.close() return done @@ -121,10 +133,11 @@ class Bootstrap(InkscapeExtension): if not os.path.isdir(filedir): os.makedirs(filedir) if not os.path.isdir(filename): - with open(filename, 'wb') as fhl: + with open(filename, "wb") as fhl: fhl.write(content) -if __name__ == '__main__': + +if __name__ == "__main__": if run_existing is not None: # If the extension manager is already installed # Run it instead of the bootstrap process. diff --git a/extrude.py b/extrude.py index 68ab7998..34eeaf88 100755 --- a/extrude.py +++ b/extrude.py @@ -35,34 +35,58 @@ class Extrude(inkex.EffectExtension): or copies of the path segments. The lines will be inserted between the two elements. """ + def add_arguments(self, pars): pars.add_argument("--tab") - pars.add_argument("-m", "--mode", default="lines", choices=["lines", "polygons", "snug"], - help="Join paths with lines, polygons or copies of the segments (\"snug\")") - pars.add_argument("-s", "--subpaths", default=True, type=inkex.Boolean, - help="""If true, connecting lines will be inserted as subpaths of a single path. + pars.add_argument( + "-m", + "--mode", + default="lines", + choices=["lines", "polygons", "snug"], + help='Join paths with lines, polygons or copies of the segments ("snug")', + ) + pars.add_argument( + "-s", + "--subpaths", + default=True, + type=inkex.Boolean, + help="""If true, connecting lines will be inserted as subpaths of a single path. If false, they will be inserted in newly created group. - Only applies to mode=lines""") + Only applies to mode=lines""", + ) + @staticmethod def _handle_lines(manager, com1, com2): - if not (isinstance(com1.command, (ZoneClose, zoneClose)) or - isinstance(com2.command, (ZoneClose, zoneClose))): + if not ( + isinstance(com1.command, (ZoneClose, zoneClose)) + or isinstance(com2.command, (ZoneClose, zoneClose)) + ): # For a closed subpath, the first line has already been drawn. manager.Move_to(*com1.end_point) manager.Line_to(*com2.end_point) + @staticmethod def _handle_polygons(manager, com1, com2): - if not (isinstance(com1.command, (Move, move)) or - isinstance(com2.command, (Move, move))): + if not ( + isinstance(com1.command, (Move, move)) + or isinstance(com2.command, (Move, move)) + ): # We skip if one of either commands is a "Move" command manager.Move_to(*com1.previous_end_point) - for point in [com1.end_point, com2.end_point, - com2.previous_end_point, com1.previous_end_point]: + for point in [ + com1.end_point, + com2.end_point, + com2.previous_end_point, + com1.previous_end_point, + ]: manager.Line_to(*point) + @staticmethod def _handle_snug(manager, com1, com2): - if not (isinstance(com1.command, (Move, move)) or - isinstance(com2.command, (Move, move))): + if not ( + isinstance(com1.command, (Move, move)) + or isinstance(com2.command, (Move, move)) + ): # We skip if one of either commands is a "Move" command manager.Move_to(*com1.previous_end_point) com1r = com1.command @@ -77,7 +101,7 @@ class Extrude(inkex.EffectExtension): manager.add([com1r, Line(*com2.end_point), com2r, ZoneClose()]) def effect(self): - paths : List[inkex.PathElement] = [] + paths: List[inkex.PathElement] = [] for node in self.svg.selection.rendering_order().filter(inkex.ShapeElement): if isinstance(node, inkex.PathElement): node.apply_transform() @@ -87,25 +111,31 @@ class Extrude(inkex.EffectExtension): lines = self.options.mode.lower() == "lines" subpaths = self.options.subpaths and lines - mode = self._handle_lines if lines else \ - (self._handle_polygons if self.options.mode.lower() == "polygons" - else self._handle_snug) + mode = ( + self._handle_lines + if lines + else ( + self._handle_polygons + if self.options.mode.lower() == "polygons" + else self._handle_snug + ) + ) if lines: style = { - 'fill': 'none', - 'stroke': '#000000', - 'stroke-opacity': 1, - 'stroke-width': '1px', + "fill": "none", + "stroke": "#000000", + "stroke-opacity": 1, + "stroke-width": "1px", } else: style = { - 'fill': '#000000', - 'fill-opacity': 0.3, - 'stroke': '#000000', - 'stroke-opacity': 0.6, - 'stroke-width': '1px', - 'stroke-linejoin': 'round' + "fill": "#000000", + "fill-opacity": 0.3, + "stroke": "#000000", + "stroke-opacity": 0.6, + "stroke-width": "1px", + "stroke-linejoin": "round", } for pa1, pa2 in itertools.combinations(paths, 2): @@ -116,6 +146,5 @@ class Extrude(inkex.EffectExtension): manager.append_next(pa1) - -if __name__ == '__main__': +if __name__ == "__main__": Extrude().run() diff --git a/fig_input.py b/fig_input.py index e5d4c0fe..3661857e 100755 --- a/fig_input.py +++ b/fig_input.py @@ -24,12 +24,15 @@ 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' + + input_ext = "fig" def call(self, input_file, output_file): - call('fig2dev', '-L', 'svg', input_file, output_file) + call("fig2dev", "-L", "svg", input_file, output_file) + -if __name__ == '__main__': +if __name__ == "__main__": FigInput().run() diff --git a/flatten.py b/flatten.py index d6c7e060..31d099fe 100755 --- a/flatten.py +++ b/flatten.py @@ -20,10 +20,14 @@ 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") + pars.add_argument( + "--flatness", type=float, default=10.0, help="Minimum flattness" + ) def effect(self): for node in self.svg.selection.filter(inkex.PathElement): @@ -33,12 +37,13 @@ class Flatten(inkex.EffectExtension): for subpath in path: first = True for csp in subpath: - cmd = 'L' + cmd = "L" if first: - cmd = 'M' + cmd = "M" first = False newpath.append([cmd, [csp[1][0], csp[1][1]]]) node.path = newpath -if __name__ == '__main__': + +if __name__ == "__main__": Flatten().run() diff --git a/foldablebox.py b/foldablebox.py index e8cb6d0f..21a2d854 100755 --- a/foldablebox.py +++ b/foldablebox.py @@ -22,15 +22,23 @@ __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( + "--depth", type=float, default=3.0, help="The Box Depth (z dimention)" + ) pars.add_argument("--unit", default="px", 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") + 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""" @@ -38,32 +46,35 @@ class FoldableBox(inkex.EffectExtension): 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')) + 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') + 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'))} + 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 = 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', []] + ["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 @@ -73,13 +84,13 @@ class FoldableBox(inkex.EffectExtension): self.guide(doc_h - tab_h, True) # Upper Close Tab - line = group.add(inkex.PathElement(id=box_id + '-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', []] + ["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 @@ -90,28 +101,28 @@ class FoldableBox(inkex.EffectExtension): if side_tab_h < tab_h: side_tab_h = tab_h - line = group.add(inkex.PathElement(id=box_id + '-upper-right-tab')) + 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', []] + ["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 = 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', []] + ["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 @@ -120,63 +131,63 @@ class FoldableBox(inkex.EffectExtension): self.guide(doc_h - tab_h - box_d, True) # Right Tab - line = group.add(inkex.PathElement(id=box_id + '-left-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', []] + ["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 = 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', []] + ["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 = 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', []] + ["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 = 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', []] + ["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 = 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', []] + ["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 @@ -187,60 +198,62 @@ class FoldableBox(inkex.EffectExtension): b_tab = box_w / 2.5 # Bottom Front Tab - line = group.add(inkex.PathElement(id=box_id + '-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', []] + ["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 = 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', []] + ["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 = 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', []] + ["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 = 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', []] + ["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)) + group.transform = inkex.Transform( + translate=((doc_w - left_pos) / 2, (doc_h - lower_pos) / 2) + ) -if __name__ == '__main__': # pragma: no cover +if __name__ == "__main__": # pragma: no cover FoldableBox().run() diff --git a/fractalize.py b/fractalize.py index e20c8c0f..dca72de3 100755 --- a/fractalize.py +++ b/fractalize.py @@ -22,6 +22,7 @@ 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 @@ -49,10 +50,16 @@ def calculate_subdivision(smoothness, x1, y1, x2, y2): 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") + 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): @@ -61,11 +68,14 @@ class Fractalize(inkex.EffectExtension): 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': + 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): + 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)) @@ -77,14 +87,19 @@ class Fractalize(inkex.EffectExtension): 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): + 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): + for right_seg in self.fractalize( + subdiv_point[-2:] + coords[-2:], subdivs - 1, smooth + ): yield right_seg -if __name__ == '__main__': + +if __name__ == "__main__": Fractalize().run() diff --git a/frame.py b/frame.py index 9c0603c0..d1c09f8a 100755 --- a/frame.py +++ b/frame.py @@ -24,13 +24,18 @@ 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. + """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) + return ( + box.x.minimum - delta, + box.x.maximum + delta, + box.y.minimum - delta, + box.y.maximum + delta, + ) # Frame maker Inkscape effect extension @@ -38,57 +43,102 @@ 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='stroke') - 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) + pars.add_argument("--tab", default="stroke") + 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. + """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) + 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))) + 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. + 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']) + 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']) + 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 @@ -100,14 +150,14 @@ class Frame(inkex.EffectExtension): """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') + 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': + if self.options.position == "outside": box = size_box(box, (width / 2)) else: box = size_box(box, -(width / 2)) @@ -123,5 +173,6 @@ class Frame(inkex.EffectExtension): layer.append(frame) return None -if __name__ == '__main__': + +if __name__ == "__main__": Frame().run() diff --git a/funcplot.py b/funcplot.py index e5da8041..ceb52424 100755 --- a/funcplot.py +++ b/funcplot.py @@ -31,15 +31,35 @@ import inkex from inkex import ClipPath, Rectangle from inkex.utils import math_eval -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): + +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'") + inkex.errormsg( + "x-interval cannot be zero. Please modify 'Start X value' or 'End X value'" + ) return [] scalex = width / (xend - xstart) xoff = left @@ -51,7 +71,9 @@ def drawfunction(xstart, xend, ybottom, ytop, samples, width, height, left, bott 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'") + 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 @@ -78,7 +100,7 @@ def drawfunction(xstart, xend, ybottom, ytop, samples, width, height, left, bott f = math_eval(fx) fp = math_eval(fpx) - if (f is None or (fp is None and not(fponum))): + if f is None or (fp is None and not (fponum)): raise inkex.AbortExtension(_("Invalid function specification")) # step is the distance between nodes on x @@ -92,13 +114,13 @@ def drawfunction(xstart, xend, ybottom, ytop, samples, width, height, left, bott # check for visibility of x-axis if ybottom <= 0 <= ytop: # xaxis - a.append(['M', [left, coordy(0)]]) - a.append(['l', [width, 0]]) + 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]]) + 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. @@ -109,7 +131,9 @@ def drawfunction(xstart, xend, ybottom, ytop, samples, width, height, left, bott yp0 = y0 * sin(x0) x0 = xp0 y0 = yp0 - if fponum or polar: # numerical derivative, using 0.001*step as the small differential + 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: @@ -125,10 +149,10 @@ def drawfunction(xstart, xend, ybottom, ytop, samples, width, height, left, bott # Start curve if endpts: - a.append(['M', [left, coordy(0)]]) - a.append(['L', [coordx(x0), coordy(y0)]]) + a.append(["M", [left, coordy(0)]]) + a.append(["L", [coordx(x0), coordy(y0)]]) else: - a.append(['M', [coordx(x0), coordy(y0)]]) # initial moveto + a.append(["M", [coordx(x0), coordy(y0)]]) # initial moveto for i in range(int(samples - 1)): x1 = (i + 1) * step + xstart @@ -151,17 +175,25 @@ def drawfunction(xstart, xend, ybottom, ytop, samples, width, height, left, bott 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)] - ]) + 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)]]) + a.append(["L", [left + width, coordy(0)]]) return a @@ -170,19 +202,39 @@ class FuncPlot(inkex.EffectExtension): 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=False, 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=0.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( + "--times2pi", type=inkex.Boolean, default=False, 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=0.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( + "--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=False, help="Draw axis") - pars.add_argument("--endpts", type=inkex.Boolean, default=False, help="Add end points") + 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=False, help="Draw axis" + ) + pars.add_argument( + "--endpts", type=inkex.Boolean, default=False, help="Add end points" + ) def effect(self): newpath = None @@ -190,32 +242,36 @@ class FuncPlot(inkex.EffectExtension): 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')) + 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) + 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) @@ -224,7 +280,7 @@ class FuncPlot(inkex.EffectExtension): clip = self.svg.defs.add(ClipPath()) clip.set_random_id() clip.append(node.copy()) - newpath.set('clip-path', clip.get_id(as_url=2)) + newpath.set("clip-path", clip.get_id(as_url=2)) # option whether to remove the rectangle or not. if self.options.remove: node.getparent().remove(node) @@ -232,5 +288,5 @@ class FuncPlot(inkex.EffectExtension): raise inkex.AbortExtension(_("Please select a rectangle")) -if __name__ == '__main__': +if __name__ == "__main__": FuncPlot().run() diff --git a/generate_voronoi.py b/generate_voronoi.py index 693edf20..317d07b5 100755 --- a/generate_voronoi.py +++ b/generate_voronoi.py @@ -28,6 +28,7 @@ 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] @@ -69,30 +70,36 @@ def clip_line(x1, y1, x2, y2, w, h): 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( + "--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 + 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]) + 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)): + 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 @@ -129,7 +136,14 @@ class GenerateVoronoi(inkex.EffectExtension): 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) + [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] @@ -139,8 +153,17 @@ class GenerateVoronoi(inkex.EffectExtension): 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) + 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 @@ -154,19 +177,27 @@ class GenerateVoronoi(inkex.EffectExtension): 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) + [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) + 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))} + patternstyle = {"stroke": "#000000", "stroke-width": str(scale)} + attribs = {"d": path, "style": str(inkex.Style(patternstyle))} pattern.append(PathElement(**attribs)) # link selected object to pattern - obj.style['fill'] = pattern + obj.style["fill"] = pattern if isinstance(obj, inkex.Group): for node in obj: - node.style['fill'] = pattern + node.style["fill"] = pattern + -if __name__ == '__main__': +if __name__ == "__main__": GenerateVoronoi().run() diff --git a/gimp_xcf.py b/gimp_xcf.py index d667a6b7..eed47d11 100755 --- a/gimp_xcf.py +++ b/gimp_xcf.py @@ -31,20 +31,30 @@ 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-' + + 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") + 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""" @@ -62,7 +72,7 @@ class GimpXcf(TempDirMixin, inkex.OutputExtension): if 0 < guide.point.x < self.svg.viewbox_width: vert_guides.append(str(guide.point.x)) - return ('h', ' '.join(horz_guides)), ('v', ' '.join(vert_guides)) + return ("h", " ".join(horz_guides)), ("v", " ".join(vert_guides)) def get_grid(self): """Get the grid if asked for and return as gimpfu script""" @@ -71,19 +81,22 @@ class GimpXcf(TempDirMixin, inkex.OutputExtension): 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': + 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) + 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' + return self.svg.get("sodipodi:docname") or "document" def save(self, stream): @@ -91,12 +104,12 @@ class GimpXcf(TempDirMixin, inkex.OutputExtension): valid = False for node in self.svg.xpath("/svg:svg/*[name()='g' or @style][@id]"): - if not len(node): # pylint: disable=len-as-condition + if not len(node): # pylint: disable=len-as-condition # Ignore empty layers continue valid = True - node_id = node.get('id') + node_id = node.get("id") name = node.get("inkscape:label", node_id) pngs[name] = take_snapshot( @@ -107,11 +120,11 @@ class GimpXcf(TempDirMixin, inkex.OutputExtension): export_id=node_id, export_id_only=True, export_area_page=True, - export_background_opacity=int(bool(self.options.background)) + export_background_opacity=int(bool(self.options.background)), ) if not valid: - inkex.errormsg(_('This extension requires at least one non empty layer.')) + inkex.errormsg(_("This extension requires at least one non empty layer.")) return xcf = os.path.join(self.tempdir, "{}.xcf".format(self.docname)) @@ -144,10 +157,10 @@ class GimpXcf(TempDirMixin, inkex.OutputExtension): (gimp-image-resize-to-layers img) """.format( - dpi=self.options.dpi, - files='" "'.join(pngs.values()), - names='" "'.join(list(pngs)) -) + dpi=self.options.dpi, + files='" "'.join(pngs.values()), + names='" "'.join(list(pngs)), + ) if self.options.guides: for dim, guides in self.get_guides(): @@ -157,7 +170,9 @@ class GimpXcf(TempDirMixin, inkex.OutputExtension): (gimp-image-add-{d}guide img {d}Guide) ) '({g}) - )""".format(d=dim, g=guides) + )""".format( + d=dim, g=guides + ) # Grid if self.options.grid: @@ -168,12 +183,22 @@ class GimpXcf(TempDirMixin, inkex.OutputExtension): (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: + """.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__': + +if __name__ == "__main__": GimpXcf().run() diff --git a/grid_cartesian.py b/grid_cartesian.py index 981394c9..e2082c13 100755 --- a/grid_cartesian.py +++ b/grid_cartesian.py @@ -31,18 +31,19 @@ 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.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.style = {"stroke": "#000000", "stroke-width": str(width), "fill": fill} rect.label = name @@ -75,17 +76,31 @@ class GridCartesian(inkex.GenerateExtension): pars.add_argument("--y_div_unit", default="px") def generate(self): - self.options.border_th = self.svg.unittouu(str(self.options.border_th) + self.options.border_th_unit) + 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.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) + 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 @@ -120,8 +135,9 @@ class GridCartesian(inkex.GenerateExtension): 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 + 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 @@ -129,42 +145,74 @@ class GridCartesian(inkex.GenerateExtension): 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) + 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) + 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, + 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) + 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) + 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) + 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 @@ -172,45 +220,78 @@ class GridCartesian(inkex.GenerateExtension): 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) + 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) + 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, + 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) + 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) + 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) + 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__': +if __name__ == "__main__": GridCartesian().run() diff --git a/grid_isometric.py b/grid_isometric.py index 495bf376..fa16b692 100755 --- a/grid_isometric.py +++ b/grid_isometric.py @@ -29,51 +29,90 @@ 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.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) + 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=50.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=0.5, - help="Minor X Division Line thickness") - pars.add_argument("--subsubdivs_th", type=float, dest="subsubdivs_th", - default=0.1, help="Subminor X Division Line thickness") - pars.add_argument("--border_th", type=float, dest="border_th", default=3, - help="Border Line thickness") + 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=50.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=0.5, + help="Minor X Division Line thickness", + ) + pars.add_argument( + "--subsubdivs_th", + type=float, + dest="subsubdivs_th", + default=0.1, + 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 + 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') + 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. @@ -85,44 +124,44 @@ class GridIsometric(inkex.GenerateExtension): ymax = self.options.dx * (2 * self.options.y_divs) / 0.866025 # Group for major x gridlines - majglx = inkex.Group.new('MajorXGridlines') + majglx = inkex.Group.new("MajorXGridlines") yield majglx # Group for major y gridlines - majgly = inkex.Group.new('MajorYGridlines') + majgly = inkex.Group.new("MajorYGridlines") yield majgly # Group for major z gridlines - majglz = inkex.Group.new('MajorZGridlines') + 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') + 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') + 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') + 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') + 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') + 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') + mminglz = inkex.Group.new("SubMinorZGridlines") yield mminglz # Border of grid - yield draw_rect(0, 0, xmax, ymax, self.options.border_th, 'none', 'Border') + yield draw_rect(0, 0, xmax, ymax, self.options.border_th, "none", "Border") # X DIVISION # Shortcuts for divisions @@ -154,22 +193,37 @@ class GridIsometric(inkex.GenerateExtension): 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) + 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) + 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) + 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 @@ -177,9 +231,17 @@ class GridIsometric(inkex.GenerateExtension): 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 + 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 @@ -212,34 +274,64 @@ class GridIsometric(inkex.GenerateExtension): 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) + 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) + 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) + 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) @@ -248,110 +340,208 @@ class GridIsometric(inkex.GenerateExtension): 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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__': + 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/grid_polar.py b/grid_polar.py index 4feff2a7..ceefb3d0 100755 --- a/grid_polar.py +++ b/grid_polar.py @@ -27,26 +27,36 @@ 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.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.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.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 @@ -55,31 +65,67 @@ 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( + "--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=2, 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='none', 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") + 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=2, 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="none", 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') + 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)) @@ -88,68 +134,137 @@ class GridPolar(inkex.GenerateExtension): 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 + 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) + 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) + 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) + 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 + 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) + 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) + 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 + 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) + 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) + 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) + draw_circle( + self.options.c_dot_dia / 2.0, 0, 0, 0, "#000000", "CentreDot", grid + ) - if self.options.a_labels == 'deg': + 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" + 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) + 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__': + +if __name__ == "__main__": GridPolar().run() diff --git a/guides_creator.py b/guides_creator.py index 7f640e86..06b96680 100755 --- a/guides_creator.py +++ b/guides_creator.py @@ -35,25 +35,44 @@ 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=2, 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=False, help='Upper left corner') - pars.add_argument('--ur', type=inkex.Boolean, default=False, help='Upper right corner') - pars.add_argument('--ll', type=inkex.Boolean, default=False, help='Lower left corner') - pars.add_argument('--lr', type=inkex.Boolean, default=False, 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="10", help='Header margin') - pars.add_argument('--footer_margin', default="10", help='Footer margin') - pars.add_argument('--left_margin', default="10", help='Left margin') - pars.add_argument('--right_margin', default="10", help='Right margin') - pars.add_argument('--delete', type=inkex.Boolean, help='Delete existing guides') + 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=2, 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=False, help="Upper left corner" + ) + pars.add_argument( + "--ur", type=inkex.Boolean, default=False, help="Upper right corner" + ) + pars.add_argument( + "--ll", type=inkex.Boolean, default=False, help="Lower left corner" + ) + pars.add_argument( + "--lr", type=inkex.Boolean, default=False, 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="10", help="Header margin") + pars.add_argument("--footer_margin", default="10", help="Footer margin") + pars.add_argument("--left_margin", default="10", help="Left margin") + pars.add_argument("--right_margin", default="10", 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 @@ -61,8 +80,8 @@ class GuidesCreator(inkex.EffectExtension): self.height = float(self.svg.viewbox_height) # getting edges coordinates - self.h_orientation = '0,' + str(round(self.width, 4)) - self.v_orientation = str(round(self.height, 4)) + ',0' + 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(): @@ -74,7 +93,7 @@ class GuidesCreator(inkex.EffectExtension): """Generate a regular set of guides""" preset = self.options.guides_preset from_edges = self.options.start_from_edges - if preset == 'custom': + if preset == "custom": h_division = self.options.horizontal_guides v_division = self.options.vertical_guides if from_edges: @@ -84,35 +103,35 @@ class GuidesCreator(inkex.EffectExtension): self.draw_guides(v_division, from_edges, vert=True) self.draw_guides(h_division, from_edges, vert=False) - elif preset == 'golden': + 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)) + 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' + 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) + 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) + 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]) + 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: @@ -128,23 +147,23 @@ class GuidesCreator(inkex.EffectExtension): angle = 45 if self.options.ul: - ul_corner = str(top) + ',' + str(left) - from_ul_to_lr = str(cos(angle)) + ',' + str(cos(angle)) + 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)) + 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)) + 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)) + 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): @@ -158,14 +177,14 @@ class GuidesCreator(inkex.EffectExtension): 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) + 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) + 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': + if self.options.margins_preset == "custom": y_header = self.height y_footer = 0 x_left = 0 @@ -173,53 +192,53 @@ class GuidesCreator(inkex.EffectExtension): if header_margin != 0: y_header = (self.height / header_margin) * (header_margin - 1) - self.draw_guide('0,' + str(y_header), self.h_orientation) + 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) + 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) + 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) + self.draw_guide(str(x_right) + ",0", self.v_orientation) - elif self.options.margins_preset == 'book_left': + 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) + 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) + 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) + 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) + self.draw_guide(str(x_right) + ",0", self.v_orientation) - elif self.options.margins_preset == 'book_right': + 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) + 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) + 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) + 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) + 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 @@ -229,15 +248,23 @@ class GuidesCreator(inkex.EffectExtension): begin_from = y_footer # creating horizontal guides self._draw_guides( - (rectangle_width, rectangle_height), h_subdiv, - edges=0, shift=begin_from, vert=False) + (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) + (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""" @@ -248,7 +275,7 @@ class GuidesCreator(inkex.EffectExtension): return # Vert controls both ort template and vector calculation - ort = '{},0' if vert else '0,{}' + 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 @@ -261,8 +288,9 @@ class GuidesCreator(inkex.EffectExtension): if isinstance(position, tuple): x, y = position elif isinstance(position, str): - x, y = position.split(',') + x, y = position.split(",") self.svg.namedview.add(Guide().move_to(float(x), float(y), orientation)) -if __name__ == '__main__': + +if __name__ == "__main__": GuidesCreator().run() diff --git a/guillotine.py b/guillotine.py index b71caf49..abc9f933 100755 --- a/guillotine.py +++ b/guillotine.py @@ -42,8 +42,10 @@ 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") @@ -126,20 +128,21 @@ class Guillotine(inkex.EffectExtension): 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') + 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!") + "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 @@ -164,22 +167,26 @@ class Guillotine(inkex.EffectExtension): """ 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 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') + 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)) + self.debug( + "The sliced bitmaps have been saved as:" + "\n\n" + "\n".join(output_files) + ) def effect(self): self.export_slices(self.get_slices()) diff --git a/handles.py b/handles.py index 43a636ef..07a19740 100755 --- a/handles.py +++ b/handles.py @@ -25,10 +25,12 @@ 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): result = Path() @@ -39,13 +41,17 @@ class Handles(inkex.EffectExtension): 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), + 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) + 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) @@ -54,10 +60,16 @@ class Handles(inkex.EffectExtension): elem = node.getparent().add(inkex.PathElement()) elem.path = result.transform(node.transform) - elem.style = {'stroke-linejoin': 'miter', 'stroke-width': '1.0px', - 'stroke-opacity': '1.0', 'fill-opacity': '1.0', - 'stroke': '#000000', 'stroke-linecap': 'butt', - 'fill': 'none'} + 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__': +if __name__ == "__main__": Handles().run() diff --git a/hershey.py b/hershey.py index 9897e418..6f2b2b5a 100644 --- a/hershey.py +++ b/hershey.py @@ -17,7 +17,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # -''' +""" Hershey Text 3.0.5, 2021-05-17 Copyright 2021, Windell H. Oskay, www.evilmadscientist.com @@ -47,7 +47,7 @@ Major revisions in Hershey Text 3.0: in the document, replacing it in place. While not every possible method of formatting text is supported, many are. -''' +""" import os import math @@ -57,42 +57,76 @@ from copy import deepcopy import inkex from inkex import Transform, Style, units, AbortExtension -from inkex import load_svg, Group, TextElement, FlowPara, SVGfont, FontFace,\ - FlowSpan, Glyph, MissingGlyph, Tspan, FlowRoot, Rectangle, Use, PathElement, Defs +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="\nThe Quick Brown Fox Jumps Over a Lazy Dog", help="Text to use for font table") + 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="\nThe Quick Brown Fox Jumps Over a Lazy Dog", + help="Text to use for font table", + ) self.font_file_list = dict() self.font_load_fail = False @@ -103,27 +137,29 @@ class Hershey(inkex.Effect): 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.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.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_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.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 ====== + 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 @@ -275,7 +311,7 @@ this extension. (c) 2021 Windell H. Oskay Evil Mad Scientist Laboratories -''' +""" def getlength_inch(self, name): """ @@ -291,14 +327,13 @@ Evil Mad Scientist Laboratories value, unit = units.parse_unit(string_to_parse) if value is None: return None - bad_units = {'%', 'ex', 'em'} # Unsupported units + bad_units = {"%", "ex", "em"} # Unsupported units if unit in bad_units: return None - return units.convert_unit(string_to_parse, 'in') + return units.convert_unit(string_to_parse, "in") return None - def units_to_userunits(self, input_string): """ Custom replacement for the old "unittouu" routine @@ -312,11 +347,10 @@ Evil Mad Scientist Laboratories if value is None: return None - return units.convert_unit(input_string, '') - + 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 @@ -331,38 +365,40 @@ Evil Mad Scientist Laboratories """ if viewbox is None: - return 1, 1, 0, 0 # No viewbox; return default transform - vb_array = viewbox.strip().replace(', ', ' ').split() + 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 + 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 + 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 - + return 1, 1, 0, 0 # invalid viewbox; return default transform + if doc_width is None or doc_height is None: - raise AbortExtension('Width or height attribute missing on toplevel tag') - + raise AbortExtension( + "Width or height attribute missing on toplevel tag" + ) + 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 + 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 + 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 + 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() + par_array = p_a_r.strip().replace(", ", " ").lower().split() if len(par_array) > 0: par0 = par_array[0] if par0 == "defer": @@ -380,7 +416,7 @@ Evil Mad Scientist Laboratories # 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_x = d_width / width s_y = d_height / height o_x = -min_x o_y = -min_y @@ -410,12 +446,13 @@ Evil Mad Scientist Laboratories xminymax xmidymax xmaxymax """ - if(((ar_doc >= ar_vb) and(par_mos == "meet")) - or((ar_doc < ar_vb) and(par_mos == "slice"))): + 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 + s_y = s_x # Uniform aspect ratio o_x = -min_x scaled_vb_height = ar_doc * width @@ -431,7 +468,7 @@ Evil Mad Scientist Laboratories o_y = -min_y + excess_height # OK: tested with Tall-Meet, Wide-Slice - else: # par_align in {"xminymid", "xmidymid", "xmaxymid"}: + 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 @@ -441,7 +478,7 @@ Evil Mad Scientist Laboratories # Case 2: Scale document up until VB fills doc in Y. s_y = d_height / height - s_x = s_y # Uniform aspect ratio + s_x = s_y # Uniform aspect ratio o_y = -min_y scaled_vb_width = height / ar_doc @@ -457,20 +494,19 @@ Evil Mad Scientist Laboratories o_x = -min_x + excess_width # Need test: Tall-Slice, Wide-Meet - else: # par_align in {"xmidymin", "xmidymid", "xmidymax"}: + 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] @@ -479,31 +515,31 @@ Evil Mad Scientist Laboratories 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. + """ + 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. + If the font is not found cannot be parsed, return none. - Notable limitations: + 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. + (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. + (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!) + (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. - ''' + (4) Kerning details are not implemented yet. + """ digest = None @@ -512,10 +548,10 @@ Evil Mad Scientist Laboratories for node in node_list: if isinstance(node, Defs): - return self.parse_svg_font(node) # Recursive call + 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" @@ -561,19 +597,19 @@ Evil Mad Scientist Laboratories 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') + digest["font_id"] = node.get("id") - horiz_adv_x = node.get('horiz-adv-x') + horiz_adv_x = node.get("horiz-adv-x") if horiz_adv_x is not None: - geometry['horiz_adv_x'] = float(horiz_adv_x) + geometry["horiz_adv_x"] = float(horiz_adv_x) # Note: case of no horiz_adv_x value is not handled. for element in node: @@ -581,7 +617,7 @@ Evil Mad Scientist Laboratories if isinstance(element, Glyph): # First, because it is the most common element try: - uni_text = element.get('unicode') + uni_text = element.get("unicode") except: # Can't use this point if no unicode mapping. continue @@ -596,86 +632,85 @@ Evil Mad Scientist Laboratories continue glyph_dict = dict() - glyph_dict['glyph_name'] = element.get('glyph-name') + glyph_dict["glyph_name"] = element.get("glyph-name") - horiz_adv_x = element.get('horiz-adv-x') + horiz_adv_x = element.get("horiz-adv-x") if horiz_adv_x is not None: - glyph_dict['horiz_adv_x'] = float(horiz_adv_x) + glyph_dict["horiz_adv_x"] = float(horiz_adv_x) else: - glyph_dict['horiz_adv_x'] = geometry['horiz_adv_x'] + glyph_dict["horiz_adv_x"] = geometry["horiz_adv_x"] - glyph_dict['d'] = element.get('d') # SVG path data + 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') + 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 + geometry["units_per_em"] = 1000.0 else: - geometry['units_per_em'] = float(units_per_em) + geometry["units_per_em"] = float(units_per_em) - ascent = element.get('ascent') + ascent = element.get("ascent") if ascent is not None: - geometry['ascent'] = float(ascent) + geometry["ascent"] = float(ascent) - descent = element.get('descent') + descent = element.get("descent") if descent is not None: - geometry['descent'] = float(descent) + 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') + horiz_adv_x = element.get("horiz-adv-x") if horiz_adv_x is not None: - missing_glyph['horiz_adv_x'] = float(horiz_adv_x) + 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 + 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["scale"] = 1.0 / geometry["units_per_em"] - digest['glyphs'] = glyphs - digest['geometry'] = geometry + 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. + 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. + 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; @@ -685,21 +720,20 @@ Evil Mad Scientist Laboratories 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)) + 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 @@ -708,12 +742,18 @@ Evil Mad Scientist Laboratories 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' + 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 = Style({'stroke' : 'none', \ - 'font-size':font_size_text, 'fill' : 'black', \ - 'font-family' : 'sans-serif', 'text-anchor': 'end'}) + labeltext_style = 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 @@ -721,42 +761,46 @@ Evil Mad Scientist Laboratories 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. + continue # If the SVG file did NOT contain a font, skip it. - text_attribs = {'x':'0', 'y': str(y), 'hershey-ignore':'true'} + 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 = Style({'stroke' : 'none', \ - 'font-size':font_size_text, \ - 'fill' : 'black', 'font-family' : fontname, \ - 'text-anchor': 'start'}) + text_attribs = {"x": str(x_offset), "y": str(y)} + + sampletext_style = 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 + 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 + 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.') + inkex.errormsg("Font not found; Unable to generate glyph table.") return # Embed in group to make manipulation easier: @@ -765,18 +809,24 @@ Evil Mad Scientist Laboratories # 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: + 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' + 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 = Style({'stroke' : 'none', \ - 'font-size':font_size_text, 'fill' : 'black', \ - 'font-family' : fontname, 'text-anchor': 'start'}) + glyph_style = 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 @@ -785,13 +835,13 @@ Evil Mad Scientist Laboratories draw_position = 0 - for glyph in sorted(self.font_dict[fontname]['glyphs']): - if self.font_dict[fontname]['glyphs'][glyph]['d'] is None: + 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)} + 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 @@ -799,49 +849,47 @@ Evil Mad Scientist Laboratories self.recursively_traverse_svg(group, self.doc_transform) - def find_font_files(self): - ''' - Create list of "plausible" SVG font files + """ + 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. + 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: + 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. + (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. + (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. + (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. - ''' + 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_directory_name = "svg_fonts" - font_dir = os.path.realpath( - os.path.join(os.getcwd(), font_directory_name)) + 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 + 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 @@ -875,7 +923,7 @@ Evil Mad Scientist Laboratories 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 + 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 @@ -885,13 +933,12 @@ Evil Mad Scientist Laboratories 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 + 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: @@ -918,79 +965,83 @@ Evil Mad Scientist Laboratories * If a font is loaded and available, return the font name. Otherwise, return none. - ''' + """ - self.load_font(fontname) # Load the font if available + 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 + 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. + 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 + 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'] + 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'] + 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]["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 + 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_height = self.getlength_inch("height") + self.svg_width = self.getlength_inch("width") self.svg = self.document.getroot() - viewbox = self.svg.get('viewBox') + 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) + 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_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 @@ -1001,20 +1052,19 @@ Evil Mad Scientist Laboratories 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'] + """ + 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 + stroke_scale = chardata["stroke_scale"] * self.vb_scale_factor try: path_string, adv_x, scale_factor = self.get_font_char(font_family, char) @@ -1051,7 +1101,7 @@ Evil Mad Scientist Laboratories prec = int(math.ceil(-log_ten) + 3) width_string = "{0:.{1}f}in".format(stroke_width, prec) - p_style = {'stroke-width': width_string} + p_style = {"stroke-width": width_string} the_transform = Transform(translate=(offset + h_offset, vertoffset + v_offset)) the_transform @= scale_transform @@ -1065,17 +1115,16 @@ Evil Mad Scientist Laboratories 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) + node_transform = node.get("transform", None) if node_transform is None: return parent_transform trans = Transform(node_transform).matrix @@ -1085,41 +1134,40 @@ Evil Mad Scientist Laboratories 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'] + 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: node_style = node.style - font_height = node_style('font-size') + font_height = node_style("font-size") try: font_height_local = self.units_to_userunits(font_height) except TypeError: pass - font_family_local = self.strip_quotes(node_style('font-family')) - + font_family_local = self.strip_quotes(node_style("font-family")) + try: - line_spacing = node_style('line-height') - if "%" in line_spacing: # Handle percentage line spacing(e.g., 125%) + 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 elif line_spacing == "normal": - line_spacing_local = 1.25 # Inkscape default line spacing + line_spacing_local = 1.25 # Inkscape default line spacing else: line_spacing_local = self.units_to_userunits(line_spacing) except TypeError: pass - text_align_local = node_style('text-align') # Use text-anchor in text nodes + text_align_local = node_style("text-align") # Use text-anchor in text nodes if node.text is not None: self.text_string += node.text @@ -1132,20 +1180,20 @@ Evil Mad Scientist Laboratories 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 + 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'] + 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'] + text_align_local = parent_info["align"] self.text_string += node.tail for _ in node.tail: self.text_families.append(font_family_local) @@ -1154,45 +1202,45 @@ Evil Mad Scientist Laboratories self.text_aligns.append(text_align_local) if isinstance(node, FlowPara): - self.text_string += "\n" # Conclude every flowpara with a return + 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'] + 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"] node_style = node.style - font_height = node_style('font-size') + font_height = node_style("font-size") try: font_height_local = self.units_to_userunits(font_height) except TypeError: pass - font_family_local = self.strip_quotes(node_style('font-family')) - anchor_local = node_style('text-anchor') # Use text-anchor in text nodes + font_family_local = self.strip_quotes(node_style("font-family")) + anchor_local = node_style("text-anchor") # Use text-anchor in text nodes try: - x_temp = node.get('x') + x_temp = node.get("x") if x_temp is not None: x_local = x_temp except ValueError: pass try: - y_temp = node.get('y') + y_temp = node.get("y") if y_temp is not None: y_local = y_temp else: @@ -1200,8 +1248,10 @@ Evil Mad Scientist Laboratories # 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 + y_local = ( + float(y_local) + + self.line_number * parent_line_spacing * font_height_local + ) except ValueError: pass @@ -1215,7 +1265,6 @@ Evil Mad Scientist Laboratories 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. @@ -1224,15 +1273,15 @@ Evil Mad Scientist Laboratories # 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 + 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') + role = sub_node.get("sodipodi:role") if role == "line": adv_line = True @@ -1246,11 +1295,11 @@ Evil Mad Scientist Laboratories _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'] + 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) @@ -1259,21 +1308,24 @@ Evil Mad Scientist Laboratories 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'): - ''' + 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 = node.get("visibility", parent_visibility) + if vis == "inherit": vis = parent_visibility - if vis in ('hidden', 'collapse'): + if vis in ("hidden", "collapse"): continue # First apply the current matrix transform to this node's tranform @@ -1283,13 +1335,15 @@ Evil Mad Scientist Laboratories if isinstance(node, Group): recurse_group = True - ink_label = node.get('inkscape:label') + 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 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) @@ -1309,29 +1363,31 @@ Evil Mad Scientist Laboratories refnode = node.href if refnode is None: - continue # missing reference + continue # missing reference local_transform = Transform(_matrix) - x = float(node.get('x', '0')) - y = float(node.get('y', '0')) + 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) + 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 + ref_group = anode_list.add(Group()) # Add a subgroup except AttributeError: - inkex.errormsg('Unable to process text. Consider unlinking cloned text.') + 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: + # if 'id' not in ref_group.attrib: # ref_group.set_random_id('') - ref_group.set('transform', ref_transform) + ref_group.set("transform", ref_transform) ref_group.append(deepcopy(refnode)) @@ -1340,55 +1396,60 @@ Evil Mad Scientist Laboratories # or they will persist if original elements are preserved. self.nodes_to_delete.append(sub_node) - #Preserve original element? + # 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_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') + 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.) + # 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') + hershey_ignore = node.get("hershey-ignore") if hershey_ignore is not None: - continue # If the attribute is present, skip this node. + continue # If the attribute is present, skip this node. except ValueError: pass node_style = node.style try: - font_height_temp = node_style.get('font-size', 16) + font_height_temp = node_style.get("font-size", 16) font_height = self.units_to_userunits(font_height_temp) except TypeError: pass - font_family = self.strip_quotes(node_style('font-family')) + font_family = self.strip_quotes(node_style("font-family")) try: - line_spacing_temp = node_style('line-height') - if "%" in line_spacing_temp: # Handle percentage line spacing(e.g., 125%) + 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 elif line_spacing_temp == "normal": - line_spacing = 1.25 # Inkscape default line spacing + line_spacing = 1.25 # Inkscape default line spacing else: line_spacing = self.units_to_userunits(line_spacing_temp) except TypeError: @@ -1399,10 +1460,10 @@ Evil Mad Scientist Laboratories except ValueError: pass - if(transform is not None): + 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 @@ -1412,62 +1473,75 @@ Evil Mad Scientist Laboratories 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. ¯\_(ツ)_/¯ + """ + + 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') + the_id = node.get("id") - #Initialize text attribute lists for each top-level text object: + # 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_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 + 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' + group.label = "Hershey Text" - style = {'stroke' : '#000000', 'fill' : 'none', \ - 'stroke-linecap' : 'round', 'stroke-linejoin' : 'round'} + 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 - - ''' + 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'] + 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] + # 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 @@ -1517,7 +1591,7 @@ Evil Mad Scientist Laboratories self.warn_unflow = True continue - ''' + """ Recursively loop through content of the flowroot object, looping through text, flowpara, and other things. @@ -1526,21 +1600,21 @@ Evil Mad Scientist Laboratories 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 + 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 == "": + continue # No convertable text in this SVG element. - if(self.text_string.isspace()): - 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 @@ -1559,21 +1633,21 @@ Evil Mad Scientist Laboratories 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. + str_pos_eol = 0 # str_pos after end of previous text_line. - nbsp = u'\xa0' # Unicode non-breaking space character + nbsp = "\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. + 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. + line_start = 0 # Value of i when the current strip started. if line_length == 0: str_pos_temp = str_pos_eol @@ -1582,22 +1656,27 @@ Evil Mad Scientist Laboratories char_v_spacing = charline_spacing * char_height v = v + char_v_spacing else: - while(i < line_length): + while i < line_length: - word_start = i # Value of i at beginning of the current word. + word_start = ( + i # Value of i at beginning of the current word. + ) - while(i < line_length): # Step through the line + 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) + # (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) + _, x_adv, scale_factor = self.get_font_char( + char_family, character + ) except: x_adv = 0 scale_factor = 1 @@ -1606,10 +1685,12 @@ Evil Mad Scientist Laboratories i += 1 if character.isspace() and not character == nbsp: - break # Break at space, except non-breaking + break # Break at space, except non-breaking render_line = False - if w_temp > rect_width: # If the word will overflow the box + 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. @@ -1635,33 +1716,37 @@ Evil Mad Scientist Laboratories j = line_start - while(j < i): # Calculate max height for the strip: + 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_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): + 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 + char_data["vertoffset"] = v + char_data["stroke_scale"] = scale_r j = line_start - while(j < i): # Render the strip on the page + 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 + 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) @@ -1675,34 +1760,44 @@ Evil Mad Scientist Laboratories # 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 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 + 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)) + the_transform = Transform( + translate=(start_x, float(start_y) - y_offs_overall) + ) - else: # If this is a text object, rather than a flowroot object: - ''' + else: # If this is a text object, rather than a flowroot object: + """ CASE B: Handle regular(non-flowroot) text nodes - ''' + """ # Use text-anchor, not text-align, in text(not flowroot) elements text_align = node_style("text-anchor") - - ''' + """ 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.) @@ -1728,15 +1823,15 @@ Evil Mad Scientist Laboratories 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 + 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. @@ -1746,10 +1841,10 @@ Evil Mad Scientist Laboratories 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. + 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) @@ -1758,12 +1853,14 @@ Evil Mad Scientist Laboratories line_group = group.add(Group()) i = 0 - while(i < str_len): # Loop through the entire text of the string. + 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. + 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): + while i < str_len: # Inner while loop, that we will break out of, # back to the outer while loop. @@ -1771,13 +1868,13 @@ Evil Mad Scientist Laboratories charfont_height = self.text_heights[i] char_data = dict() - char_data['char'] = q_val - char_data['font_family'] = self.text_families[i] + 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 + 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 @@ -1788,10 +1885,11 @@ Evil Mad Scientist Laboratories set_alignment = False i_next = i + 1 - if(i_next >= str_len): # End of the string; last character. + 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)): + 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: @@ -1807,9 +1905,9 @@ Evil Mad Scientist Laboratories # 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"): + 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 @@ -1820,13 +1918,15 @@ Evil Mad Scientist Laboratories line_group.transform = the_transform - line_group = group.add(Group()) # Create new group for this line + line_group = group.add( + Group() + ) # Create new group for this line - self.new_line = True # Used for managing indent defects + self.new_line = True # Used for managing indent defects w = 0 i += 1 break - i += 1 # Only executed when set_alignment is false. + i += 1 # Only executed when set_alignment is false. the_transform = Transform() @@ -1834,9 +1934,9 @@ Evil Mad Scientist Laboratories parent = line_group.getparent() parent.remove(line_group) - #End cases A & B. Apply transform to text/flowroot object: + # End cases A & B. Apply transform to text/flowroot object: - if(transform is not None): + if transform is not None: result = Transform(transform) @ the_transform else: result = the_transform @@ -1845,24 +1945,23 @@ Evil Mad Scientist Laboratories if not self.output_generated: parent = group.getparent() - parent.remove(group) #remove empty group + parent.remove(group) # remove empty group - #Preserve original element? + # 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.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]] @@ -1873,7 +1972,7 @@ Evil Mad Scientist Laboratories # 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) + _rendered_stroke_scale = 1 / (self.PX_PER_INCH * 800.0) if self.svg_width is not None: if self.svg_width < self.svg_height: @@ -1893,10 +1992,16 @@ Evil Mad Scientist Laboratories 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) + 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: @@ -1905,13 +2010,16 @@ Evil Mad Scientist Laboratories parent.remove(element_to_remove) if self.font_load_fail: - inkex.errormsg('Warning: unable to load SVG stroke fonts.') + 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.') + 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__': +if __name__ == "__main__": Hershey().run() diff --git a/hpgl_decoder.py b/hpgl_decoder.py index 1e1577e3..22f8593f 100644 --- a/hpgl_decoder.py +++ b/hpgl_decoder.py @@ -25,18 +25,19 @@ 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 + """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.warning = "" self.textMovements = _("Movements") self.textPenNumber = _("Pen ") self.layers = {} @@ -46,40 +47,40 @@ class hpglDecoder(SvgOutputMixin): """Generate an svg document from hgpl data""" actual_layer = 0 # prepare document - doc = self.get_template(width=210.0, height=297.0, unit='mm') + doc = self.get_template(width=210.0, height=297.0, unit="mm") svg = doc.getroot() - svg.namedview.set('inkscape:document-units', 'mm') + 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(';') + 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') + 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 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': + elif command[:2] == "SP": # if Select Pen command actual_layer = int(command[2:]) - elif command[:2] == 'PU': + elif command[:2] == "PU": # if Pen Up command self.parameters_to_path(svg, command[2:], 0, True) - elif command[:2] == 'PD': + elif command[:2] == "PD": # if Pen Down command self.parameters_to_path(svg, command[2:], actual_layer + 1, False) else: - self.warning = 'UNKNOWN_COMMANDS' + 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(',') + parameters = parameters.strip().split(",") if parameters and len(parameters) % 2 == 0: for i, param in enumerate(parameters): # convert params to document units @@ -94,7 +95,15 @@ class hpglDecoder(SvgOutputMixin): 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;' + 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/hpgl_encoder.py b/hpgl_encoder.py index e2676a5d..f6a30f3a 100644 --- a/hpgl_encoder.py +++ b/hpgl_encoder.py @@ -27,46 +27,48 @@ import inkex from inkex.transforms import Transform, DirectedLineSegment, Vector2d from inkex.bezier import cspsubdiv + 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) +FIND_PEN = re.compile(r"\s*pen\s*(\d+)\s*", re.IGNORECASE) # Find the pen speed in the layer number -FIND_SPEED = re.compile(r'\s*speed\s*(\d+)\s*', re.IGNORECASE) +FIND_SPEED = re.compile(r"\s*speed\s*(\d+)\s*", re.IGNORECASE) # Find pen force in the layer name -FIND_FORCE = re.compile(r'\s*force\s*(\d+)\s*', re.IGNORECASE) - +FIND_FORCE = re.compile(r"\s*force\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 + """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.viewbox_width self.docHeight = effect.svg.viewbox_height - self.hpgl = '' - self.divergenceX = 'False' - self.divergenceY = 'False' - self.sizeX = 'False' - self.sizeY = 'False' + 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 @@ -81,14 +83,21 @@ class hpglEncoder(object): scaleXY = (self.scaleX + self.scaleY) / 2 # mm to dots (plotter coordinate system): - self.overcut = effect.svg.viewport_to_unit(str(self.options.overcut) + "mm") * scaleXY - self.toolOffset = effect.svg.viewport_to_unit(str(self.options.toolOffset) + "mm") * scaleXY + self.overcut = ( + effect.svg.viewport_to_unit(str(self.options.overcut) + "mm") * scaleXY + ) + self.toolOffset = ( + effect.svg.viewport_to_unit(str(self.options.toolOffset) + "mm") * scaleXY + ) # scale flatness to resolution: - self.flat = self.options.flat / (1016 / ((self.options.resolutionX + \ - self.options.resolutionY) / 2)) + self.flat = self.options.flat / ( + 1016 / ((self.options.resolutionX + self.options.resolutionY) / 2) + ) if self.toolOffset > 0.0: - self.toolOffsetFlat = self.flat / self.toolOffset * 4.5 # scale flatness to offset + 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 @@ -98,21 +107,37 @@ class hpglEncoder(object): self.viewBoxTransformY = 1 viewBox = effect.svg.get_viewbox() if viewBox and viewBox[2] and viewBox[3]: - self.viewBoxTransformX = self.docWidth / effect.svg.viewport_to_unit(effect.svg.add_unit(viewBox[2])) - self.viewBoxTransformY = self.docHeight / effect.svg.viewport_to_unit(effect.svg.add_unit(viewBox[3])) + self.viewBoxTransformX = self.docWidth / effect.svg.viewport_to_unit( + effect.svg.add_unit(viewBox[2]) + ) + self.viewBoxTransformY = self.docHeight / effect.svg.viewport_to_unit( + 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 = 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], ['', 'False', 0], ['', 'False', 0], ['', 'False', 0]] + self.vData = [ + ["", "False", 0], + ["", "False", 0], + ["", "False", 0], + ["", "False", 0], + ] self.process_group(self.doc, transform) - if self.divergenceX == 'False' or self.divergenceY == 'False' or self.sizeX == 'False' or self.sizeY == 'False': + 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 @@ -125,41 +150,52 @@ class hpglEncoder(object): self.divergenceX = 0.0 self.divergenceY = 0.0 if self.options.center: - if self.options.orientation == '0': + if self.options.orientation == "0": self.offsetX -= (self.docWidth * self.scaleX) / 2 self.offsetY += (self.docHeight * self.scaleY) / 2 - if self.options.orientation == '90': + if self.options.orientation == "90": self.offsetY += (self.docWidth * self.scaleX) / 2 self.offsetX += (self.docHeight * self.scaleY) / 2 - if self.options.orientation == '180': + if self.options.orientation == "180": self.offsetX += (self.docWidth * self.scaleX) / 2 self.offsetY -= (self.docHeight * self.scaleY) / 2 - if self.options.orientation == '270': + 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': + if self.options.orientation == "0": self.offsetY += self.docHeight * self.scaleY - if self.options.orientation == '90': + if self.options.orientation == "90": self.offsetY += self.docWidth * self.scaleX self.offsetX += self.docHeight * self.scaleY - if self.options.orientation == '180': + 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 = 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], ['', 'False', 0], ['', 'False', 0], ['', 'False', 0]] + self.vData = [ + ["", "False", 0], + ["", "False", 0], + ["", "False", 0], + ["", "False", 0], + ] # add move to zero point and precut if self.toolOffset > 0.0 and self.options.precut: if self.options.center: @@ -172,16 +208,40 @@ class hpglEncoder(object): precutY = self.offsetY + self.toolOffset else: precutY = self.offsetY - self.toolOffset - self.processOffset('PU', Vector2d(precutX, precutY), self.options.pen, self.options.speed, self.options.force) - self.processOffset('PD', Vector2d(precutX, precutY + self.toolOffset * 8), self.options.pen, self.options.speed, self.options.force) + self.processOffset( + "PU", + Vector2d(precutX, precutY), + self.options.pen, + self.options.speed, + self.options.force, + ) + self.processOffset( + "PD", + Vector2d(precutX, precutY + self.toolOffset * 8), + self.options.pen, + self.options.speed, + self.options.force, + ) else: - self.processOffset('PU', Vector2d(0, 0), self.options.pen, self.options.speed, self.options.force) - self.processOffset('PD', Vector2d(0, self.toolOffset * 8), self.options.pen, self.options.speed, self.options.force) + self.processOffset( + "PU", + Vector2d(0, 0), + self.options.pen, + self.options.speed, + self.options.force, + ) + self.processOffset( + "PD", + Vector2d(0, self.toolOffset * 8), + self.options.pen, + self.options.speed, + self.options.force, + ) # 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', Vector2d(0, 0), 0, 0, 0) + self.processOffset("PU", Vector2d(0, 0), 0, 0, 0) return self.hpgl def process_group(self, group, transform): @@ -204,7 +264,7 @@ class hpglEncoder(object): def get_pen_number(self, node): """Get pen number for node label (usually group)""" for parent in [node] + list(node.ancestors()): - match = FIND_PEN.search(parent.label or '') + match = FIND_PEN.search(parent.label or "") if match: return int(match.group(1)) return int(self.options.pen) @@ -212,15 +272,15 @@ class hpglEncoder(object): def get_pen_speed(self, node): """Get pen speed for node label (usually group)""" for parent in [node] + list(node.ancestors()): - match = FIND_SPEED.search(parent.label or '') + match = FIND_SPEED.search(parent.label or "") if match: return int(match.group(1)) return int(self.options.speed) - + def get_pen_force(self, node): """Get pen force for node label (usually group)""" for parent in [node] + list(node.ancestors()): - match = FIND_FORCE.search(parent.label or '') + match = FIND_FORCE.search(parent.label or "") if match: return int(match.group(1)) return int(self.options.force) @@ -231,45 +291,59 @@ class hpglEncoder(object): speed = self.get_pen_speed(node) force = self.get_pen_force(node) - path = node.path.to_absolute()\ - .transform(node.composed_transform())\ - .transform(transform)\ - .to_superpath() + 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' + 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)): + if int(round(posX)) != int(round(oldPosX)) or int( + round(posY) + ) != int(round(oldPosY)): self.processOffset(cmd, Vector2d(posX, posY), pen, speed, force) - cmd = 'PD' + 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])): + 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 += (Vector2d(posX, posY) - (oldPosX, oldPosY)).length + if int(round(posX)) != int(round(oldPosX)) or int( + round(posY) + ) != int(round(oldPosY)): + overcutLength += ( + Vector2d(posX, posY) - (oldPosX, oldPosY) + ).length if overcutLength >= self.overcut: - newEndPoint = self.changeLength(Vector2d(oldPosX, oldPosY),\ - Vector2d(posX, posY), - (overcutLength - self.overcut)) - self.processOffset(cmd, newEndPoint, pen, speed, force) + newEndPoint = self.changeLength( + Vector2d(oldPosX, oldPosY), + Vector2d(posX, posY), + -(overcutLength - self.overcut), + ) + self.processOffset( + cmd, newEndPoint, pen, speed, force + ) break - self.processOffset(cmd, Vector2d(posX, posY), pen, speed, force) + self.processOffset( + cmd, Vector2d(posX, posY), pen, speed, force + ) oldPosX = posX oldPosY = posY @@ -277,10 +351,10 @@ class hpglEncoder(object): """change length of line""" if p1.x == p2.x and p1.y == p2.y: # abort if points are the same return p1 - return Vector2d(DirectedLineSegment(p2, p1).point_at_length(- offset)) + return Vector2d(DirectedLineSegment(p2, p1).point_at_length(-offset)) def processOffset(self, cmd, point, pen, speed, force): - """ Calculate offset correction """ + """Calculate offset correction""" if self.toolOffset == 0.0 or self.dryRun: self.storePoint(cmd, point, pen, speed, force) else: @@ -288,77 +362,141 @@ class hpglEncoder(object): self.vData.pop(0) self.vData.insert(3, [cmd, point, pen, speed, force]) # 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], self.vData[2][4]) + 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], + self.vData[2][4], + ) 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 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[2][1], self.toolOffset) - self.storePoint('PD', pointThree, self.vData[2][2], self.vData[2][3], self.vData[2][4]) - elif self.vData[0][1] != 'False': + pointThree = self.changeLength( + self.vData[1][1], self.vData[2][1], self.toolOffset + ) + self.storePoint( + "PD", + pointThree, + self.vData[2][2], + self.vData[2][3], + self.vData[2][4], + ) + 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[1][1], self.toolOffset) + pointThree = self.changeLength( + self.vData[0][1], self.vData[1][1], self.toolOffset + ) pointThree = self.vData[2][1] - (self.vData[1][1] - pointThree) - self.storePoint('PU', pointThree, self.vData[2][2], self.vData[2][3], self.vData[2][4]) + self.storePoint( + "PU", + pointThree, + self.vData[2][2], + self.vData[2][3], + self.vData[2][4], + ) else: # Else just write the 3rd entry pointThree = self.vData[2][1] - self.storePoint('PU', pointThree, self.vData[2][2], self.vData[2][3], self.vData[2][4]) - if self.vData[3][0] == 'PD': + self.storePoint( + "PU", + pointThree, + self.vData[2][2], + self.vData[2][3], + self.vData[2][4], + ) + 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 - originalSegment = DirectedLineSegment(self.vData[2][1], self.vData[3][1]) + originalSegment = DirectedLineSegment( + self.vData[2][1], self.vData[3][1] + ) if originalSegment.length >= self.toolOffset: - pointFour = self.changeLength(originalSegment.end,\ - originalSegment.start, - self.toolOffset) + pointFour = self.changeLength( + originalSegment.end, + originalSegment.start, + -self.toolOffset, + ) else: - pointFour = self.changeLength(originalSegment.start,\ - originalSegment.end, self.toolOffset - originalSegment.length) + pointFour = self.changeLength( + originalSegment.start, + originalSegment.end, + self.toolOffset - originalSegment.length, + ) # get angle start and angle vector - angleStart = DirectedLineSegment(self.vData[2][1], pointThree).angle - angleVector = DirectedLineSegment(self.vData[2][1], pointFour).angle\ - - angleStart + angleStart = DirectedLineSegment( + self.vData[2][1], pointThree + ).angle + angleVector = ( + DirectedLineSegment(self.vData[2][1], pointFour).angle + - angleStart + ) # switch direction when arc is bigger than 180° if angleVector > math.pi: angleVector -= math.pi * 2 - elif angleVector < - math.pi: + 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] + self.toolOffset *\ - Vector2d(math.cos(angle), math.sin(angle)), self.vData[2][2], self.vData[2][3], self.vData[2][4]) + self.storePoint( + "PD", + self.vData[2][1] + + self.toolOffset + * Vector2d(math.cos(angle), math.sin(angle)), + self.vData[2][2], + self.vData[2][3], + self.vData[2][4], + ) angle += self.toolOffsetFlat else: angle = angleStart - self.toolOffsetFlat while angle > angleStart + angleVector: - self.storePoint('PD', self.vData[2][1] + self.toolOffset *\ - Vector2d(math.cos(angle), math.sin(angle)), self.vData[2][2], self.vData[2][3], self.vData[2][4]) + self.storePoint( + "PD", + self.vData[2][1] + + self.toolOffset + * Vector2d(math.cos(angle), math.sin(angle)), + self.vData[2][2], + self.vData[2][3], + self.vData[2][4], + ) angle -= self.toolOffsetFlat - self.storePoint('PD', pointFour, self.vData[3][2], self.vData[2][3], self.vData[2][4]) + self.storePoint( + "PD", + pointFour, + self.vData[3][2], + self.vData[2][3], + self.vData[2][4], + ) def storePoint(self, command, point, pen, speed, force): x = int(round(point.x)) y = int(round(point.y)) # skip when no change in movement - if self.lastPoint[0] == command and self.lastPoint[1] == x and self.lastPoint[2] == y: + 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: + if self.divergenceX == "False" or x < self.divergenceX: self.divergenceX = x - if self.divergenceY == 'False' or y < self.divergenceY: + if self.divergenceY == "False" or y < self.divergenceY: self.divergenceY = y - if self.sizeX == 'False' or x > self.sizeX: + if self.sizeX == "False" or x > self.sizeX: self.sizeX = x - if self.sizeY == 'False' or y > self.sizeY: + if self.sizeY == "False" or y > self.sizeY: self.sizeY = y else: # store point @@ -370,20 +508,19 @@ class hpglEncoder(object): y = 0 # select correct pen if self.lastPen != pen: - self.hpgl += ';PU;SP%d' % pen - if self.lastSpeed != speed: + self.hpgl += ";PU;SP%d" % pen + if self.lastSpeed != speed: if speed > 0: - self.hpgl += ';VS%d' % speed - if self.lastForce != force: + self.hpgl += ";VS%d" % speed + if self.lastForce != force: if force > 0: - self.hpgl += ';FS%d' % force - # do not repeat command - if command == 'PD' and self.lastPoint[0] == 'PD' and self.lastPen == pen: - self.hpgl += ',%d,%d' % (x, y) + self.hpgl += ";FS%d" % force + # 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.hpgl += ";%s%d,%d" % (command, x, y) self.lastPen = pen self.lastSpeed = speed self.lastForce = force self.lastPoint = [command, x, y] - diff --git a/hpgl_input.py b/hpgl_input.py index c4cd05f4..538ff739 100755 --- a/hpgl_input.py +++ b/hpgl_input.py @@ -24,15 +24,24 @@ 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') + 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() + return b";".join(line.strip() for line in stream).decode() def effect(self): # interpret HPGL data @@ -42,7 +51,7 @@ class HpglInput(inkex.InputExtension): try: doc, warnings = myHpglDecoder.get_svg() except Exception as inst: - if inst.args[0] == 'NO_HPGL_DATA': + if inst.args[0] == "NO_HPGL_DATA": # issue error if no hpgl data found inkex.errormsg(_("No HPGL data found.")) exit(1) @@ -51,11 +60,16 @@ class HpglInput(inkex.InputExtension): 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.")) + 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__': + +if __name__ == "__main__": HpglInput().run() diff --git a/hpgl_output.py b/hpgl_output.py index d205c4ce..ee5f681d 100755 --- a/hpgl_output.py +++ b/hpgl_output.py @@ -25,25 +25,47 @@ 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=0, help='Pen force (g)') - pars.add_argument('--speed', type=int, default=0, help='Pen speed (cm/s)') - pars.add_argument('--orientation', type=str, default='0', 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("--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=0, help="Pen force (g)") + pars.add_argument("--speed", type=int, default=0, help="Pen speed (cm/s)") + pars.add_argument( + "--orientation", type=str, default="0", 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 @@ -53,16 +75,17 @@ class HpglOutput(inkex.OutputExtension): hpgl = encoder.getHpgl() except hpgl_encoder.NoPathError: raise inkex.AbortExtension( - _("No paths were found. Please convert objects you want into paths.")) + _("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 = "IN" + # if self.options.force > 0: # hpgl_init += ';FS%d' % self.options.force - # if self.options.speed > 0: + # 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')) + hpgl = hpgl_init + hpgl + ";SP0;PU0,0;IN; " + stream.write(hpgl.encode("utf-8")) -if __name__ == '__main__': - HpglOutput().run() \ No newline at end of file +if __name__ == "__main__": + HpglOutput().run() diff --git a/image_attributes.py b/image_attributes.py index 7645b93a..0e39bff0 100755 --- a/image_attributes.py +++ b/image_attributes.py @@ -29,37 +29,56 @@ 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( + "--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") + 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': + 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'))) + 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": @@ -73,12 +92,12 @@ class ImageAttributes(inkex.EffectExtension): node_style[key] = str(value) else: del node_style[key] - node.set('style', str(inkex.Style(node_style))) + 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'): + for img in node.xpath("descendant-or-self::svg:image"): self.change_attribute(img, attribute) def change_selected_only(self, selected, attribute): @@ -102,23 +121,33 @@ class ImageAttributes(inkex.EffectExtension): 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"), - }) + 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)}) + 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}) + self.options.image_rendering_scope( + self.svg.selected, {"image-rendering": self.options.image_rendering} + ) + -if __name__ == '__main__': +if __name__ == "__main__": ImageAttributes().run() diff --git a/image_embed.py b/image_embed.py index cf6208d4..94b1972b 100755 --- a/image_embed.py +++ b/image_embed.py @@ -40,10 +40,14 @@ except ImportError: 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") + pars.add_argument( + "--selectedonly", type=inkex.Boolean, help="embed only selected images" + ) def effect(self): # if slectedonly is enabled and there is a selection @@ -51,19 +55,21 @@ class EmbedImage(inkex.EffectExtension): if self.options.selectedonly: images = self.svg.selection.get(Image) else: - images = self.svg.xpath('//svg:image') + 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 is not None and xlink[:5] == 'data:'): + xlink = node.get("xlink:href") + if xlink is not None and xlink[:5] == "data:": # No need, data already embedded return if xlink is None: - inkex.errormsg(_('Attribute "xlink:href" not set on node {}.'.format(node.get_id()))) + inkex.errormsg( + _('Attribute "xlink:href" not set on node {}.'.format(node.get_id())) + ) return url = urlparse.urlparse(xlink) @@ -71,14 +77,16 @@ class EmbedImage(inkex.EffectExtension): # Primary location always the filename itself, we allow this # call to search the user's home folder too. - path = self.absolute_href(href or '') + 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) + path = node.get("sodipodi:absref", path) if not os.path.isfile(path): - inkex.errormsg(_('File not found "{}". Unable to embed image.').format(path)) + inkex.errormsg( + _('File not found "{}". Unable to embed image.').format(path) + ) return with open(path, "rb") as handle: @@ -88,37 +96,47 @@ class EmbedImage(inkex.EffectExtension): 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') + 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) + 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'), - ): + (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'), - ): + # 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__': + +if __name__ == "__main__": EmbedImage().run() diff --git a/image_extract.py b/image_extract.py index 32372617..bb282ca9 100755 --- a/image_extract.py +++ b/image_extract.py @@ -30,17 +30,28 @@ try: 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='./images/',\ - help="Location to save the images.") + pars.add_argument( + "-s", + "--selectedonly", + type=inkex.Boolean, + help="Extract only selected images", + default=True, + ) + pars.add_argument( + "--filepath", default="./images/", help="Location to save the images." + ) def effect(self): - elems = self.svg.selection.filter(Image) \ - if self.options.selectedonly else self.svg.xpath('//svg:image') + elems = ( + self.svg.selection.filter(Image) + if self.options.selectedonly + else self.svg.xpath("//svg:image") + ) for elem in elems: self.extract_image(elem) @@ -49,19 +60,19 @@ class ExtractImage(inkex.EffectExtension): 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 '.' + { + part = mime.split("/", 1)[1].split("+")[0] + return "." + { # These are the non-matching ones. - 'svg+xml' : '.svg', - 'jpeg' : '.jpg', - 'icon' : '.ico', + "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 + xlink = node.get("xlink:href") + if not xlink.startswith("data:"): + return # Not embedded image data # This call will raise AbortExtension if the document wasn't saved # and the user is trying to extract them to a relative directory. @@ -72,13 +83,13 @@ class ExtractImage(inkex.EffectExtension): try: data = xlink[5:] - (mimetype, data) = data.split(';', 1) - (base, data) = data.split(',', 1) + (mimetype, data) = data.split(";", 1) + (base, data) = data.split(",", 1) except ValueError: inkex.errormsg("Invalid image format found") return - if base != 'base64': + if base != "base64": inkex.errormsg("Can't decode encoding: {}".format(base)) return @@ -86,16 +97,19 @@ class ExtractImage(inkex.EffectExtension): 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)) + inkex.errormsg( + "Can't extract image, filename already used: {}".format(pathwext) + ) return - self.msg('Image extracted to: {}'.format(pathwext)) + self.msg("Image extracted to: {}".format(pathwext)) - with open(pathwext, 'wb') as fhl: - fhl.write(decodebytes(data.encode('utf-8'))) + 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)) + node.set("xlink:href", os.path.realpath(pathwext)) + -if __name__ == '__main__': +if __name__ == "__main__": ExtractImage().run() diff --git a/ink2canvas.py b/ink2canvas.py index 443b28ed..5a593569 100755 --- a/ink2canvas.py +++ b/ink2canvas.py @@ -25,15 +25,17 @@ 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')) + stream.write(canvas.output().encode("utf-8")) def get_gradient_defs(self, elem): """Return the gradient information""" @@ -56,12 +58,12 @@ class Html5Canvas(inkex.OutputExtension): 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': + prefix, _brace_, command = node.tag.partition("}") + if prefix != "{http://www.w3.org/2000/svg": return None # makes pylint happy - assert _brace_ == '}' + assert _brace_ == "}" cls = getattr(svg, command.capitalize(), None) @@ -82,7 +84,7 @@ class Html5Canvas(inkex.OutputExtension): elem.start(gradient) try: elem.draw() - except ValueError as error: # print out the reason if any element can not be exported + except ValueError as error: # print out the reason if any element can not be exported canvas.write("// " + str(error)) continue self.walk_tree(node, canvas) diff --git a/ink2canvas_lib/canvas.py b/ink2canvas_lib/canvas.py index ef293e37..8b938c64 100644 --- a/ink2canvas_lib/canvas.py +++ b/ink2canvas_lib/canvas.py @@ -22,6 +22,7 @@ Convas module for ink2canvas extension from inkex import Color, Style + class Canvas(object): """Canvas API helper class""" @@ -39,6 +40,7 @@ class Canvas(object): def output(self): from textwrap import dedent + html = """ @@ -69,13 +71,19 @@ class Canvas(object): 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) + 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) + 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)) @@ -121,7 +129,7 @@ class Canvas(object): self.write("ctx.miterLimit = %s;" % value) def setFont(self, value): - self.write("ctx.font = \"%s\";" % value) + self.write('ctx.font = "%s";' % value) def moveTo(self, x, y): self.write("ctx.moveTo(%f, %f);" % (x, y)) @@ -160,7 +168,7 @@ class Canvas(object): 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)) + self.write('ctx.fillText("%s", %f, %f);' % (text, x, y)) def translate(self, cx, cy): self.write("ctx.translate(%f, %f);" % (cx, cy)) diff --git a/ink2canvas_lib/svg.py b/ink2canvas_lib/svg.py index 1aab455a..5e2cb056 100644 --- a/ink2canvas_lib/svg.py +++ b/ink2canvas_lib/svg.py @@ -24,8 +24,10 @@ from __future__ import unicode_literals import inkex + class Element(object): """Base Element""" + def __init__(self, node): self.node = node @@ -88,8 +90,12 @@ class AbstractShape(Element): if hasattr(self.ctx, method) and style[key] != "none": getattr(self.ctx, method)(style[key]) # saves style to compare in next iteration - if hasattr(self.ctx, "style_cache") and self.ctx.style_cache("opacity") != style("opacity"): - self.ctx.setOpacity(style("opacity")) # opacity is kept in memory, need to reset + if hasattr(self.ctx, "style_cache") and self.ctx.style_cache( + "opacity" + ) != style("opacity"): + self.ctx.setOpacity( + style("opacity") + ) # opacity is kept in memory, need to reset self.ctx.style_cache = style def has_transform(self): @@ -161,6 +167,7 @@ class Circle(AbstractShape): def get_data(self): import math + cx = self.attr("cx") cy = self.attr("cy") r = self.attr("r") @@ -177,6 +184,7 @@ class Ellipse(AbstractShape): def draw(self): import math + cx, cy, rx, ry = self.get_data() style = self.get_style() self.ctx.beginPath() @@ -187,10 +195,18 @@ class Ellipse(AbstractShape): 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.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.finishPath() @@ -222,10 +238,12 @@ class Path(AbstractShape): self.set_style(style) # Draws path commands - path_command = {"M": self.pathMoveTo, - "L": self.pathLineTo, - "C": self.pathCurveTo, - "Z": self.pathClose} + path_command = { + "M": self.pathMoveTo, + "L": self.pathLineTo, + "C": self.pathCurveTo, + "Z": self.pathClose, + } # 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: diff --git a/inkex/__init__.py b/inkex/__init__.py index 425a814e..c7f2d33c 100644 --- a/inkex/__init__.py +++ b/inkex/__init__.py @@ -7,9 +7,10 @@ This provides the basis from which you can develop your inkscape extension. # pylint: disable=wildcard-import -__version__ = "1.2.0" # Version number for inkex; may differ from Inkscape version. +__version__ = "1.2.0" # Version number for inkex; may differ from Inkscape version. import sys + MIN_VERSION = (3, 6) if sys.version_info < MIN_VERSION: sys.exit("Inkscape extensions require Python 3.6 or greater.") diff --git a/inkex/base.py b/inkex/base.py index df29ade6..eeee5b71 100644 --- a/inkex/base.py +++ b/inkex/base.py @@ -24,13 +24,25 @@ import os import sys import copy -from typing import Dict, List, Tuple, Type, Optional, Callable, Any, Union, IO, TYPE_CHECKING, cast +from typing import ( + Dict, + List, + Tuple, + Type, + Optional, + Callable, + Any, + Union, + IO, + TYPE_CHECKING, + cast, +) from argparse import ArgumentParser, Namespace from lxml import etree from .utils import filename_arg, AbortExtension, ABORT_STATUS, errormsg, do_nothing -from .elements._base import load_svg, BaseElement # pylint: disable=unused-import +from .elements._base import load_svg, BaseElement # pylint: disable=unused-import from .elements._utils import NSS from .localization import localize @@ -42,24 +54,33 @@ class InkscapeExtension: 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. - extra_nss = {} # type: Dict[str, str] + + multi_inx = False # Set to true if this class is used by multiple inx files. + extra_nss = {} # type: Dict[str, str] def __init__(self): # type: () -> None NSS.update(self.extra_nss) - self.file_io = None # type: Optional[IO] + self.file_io = None # type: Optional[IO] self.options = Namespace() - self.document = None # type: Union[None, bytes, str, etree] + self.document = None # type: Union[None, bytes, str, 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) + "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).") + "--output", + type=str, + default=None, + help="Optional output filename for saving the result (default is stdout).", + ) self.add_arguments(self.arg_parser) @@ -80,7 +101,7 @@ class InkscapeExtension: """Parse the given arguments and set 'self.options'""" self.options = self.arg_parser.parse_args(args) - def arg_method(self, prefix='method'): + 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 @@ -92,16 +113,18 @@ class InkscapeExtension: .. def method_foo(self, arguments): .. # do something """ + def _inner(value): - name = '{}_{}'.format(prefix, value.strip('"').lower()).replace('-', '_') + name = "{}_{}".format(prefix, value.strip('"').lower()).replace("-", "_") try: return getattr(self, name) except AttributeError: - if name.startswith('_'): + if name.startswith("_"): return do_nothing raise AbortExtension(f"Can not find method {name}") + return _inner - + @staticmethod def arg_class(options: List[Type]) -> Callable[[str], Any]: """Used by add_argument to match an option with a class @@ -110,12 +133,14 @@ class InkscapeExtension: Usage: pars.add_argument("--class", type=self.arg_class([ClassA, ClassB]), default="ClassA") """ + def _inner(value: str): name = value.strip('"') for i in options: if name == i.__name__: return i raise AbortExtension(f"Can not find class {name}") + return _inner def debug(self, msg): @@ -155,7 +180,7 @@ class InkscapeExtension: # type: () -> None """Load the input stream or filename, save everything to self""" if isinstance(self.options.input_file, str): - self.file_io = open(self.options.input_file, 'rb') + self.file_io = open(self.options.input_file, "rb") document = self.load(self.file_io) else: document = self.load(self.options.input_file) @@ -166,27 +191,27 @@ class InkscapeExtension: """Save to the output stream, use everything from self""" if self.has_changed(ret): if isinstance(self.options.output, str): - with open(self.options.output, 'wb') as stream: + with open(self.options.output, "wb") as stream: self.save(stream) else: self.save(self.options.output) def load(self, stream): - # type: (IO) -> str + # type: (IO) -> str """Takes the input stream and creates a document for parsing""" raise NotImplementedError(f"No input handle for {self.name}") def save(self, stream): - # type: (IO) -> None + # type: (IO) -> None """Save the given document to the output file""" raise NotImplementedError(f"No output handle for {self.name}") def effect(self): - # type: () -> Any + # type: () -> Any """Apply some effects on the document or local context""" raise NotImplementedError(f"No effect handle for {self.name}") - def has_changed(self, ret): # pylint: disable=no-self-use + 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 @@ -209,7 +234,7 @@ class InkscapeExtension: return os.path.dirname(path) elif default: return default - return path # Return None or '' for context + return path # Return None or '' for context @classmethod def ext_path(cls): @@ -240,10 +265,10 @@ class InkscapeExtension: * Inkscape may have not written the latest changes, leaving you reading old data. * Inkscape will not respect anything you write to the file, causing data loss. """ - return os.environ.get('DOCUMENT_PATH', None) + return os.environ.get("DOCUMENT_PATH", None) @classmethod - def absolute_href(cls, filename, default='~/', cwd=None): + def absolute_href(cls, filename, default="~/", cwd=None): # type: (str, str, Optional[str]) -> str """ Process the filename such that it's turned into an absolute filename @@ -262,9 +287,13 @@ class InkscapeExtension: if cwd is None: cwd = cls.svg_path(default) if cwd is None: - raise AbortExtension(f"Can not use relative path, Inkscape isn't telling us the current working directory.") - elif cwd == '': - raise AbortExtension(f"The SVG must be saved before you can use relative paths.") + raise AbortExtension( + f"Can not use relative path, Inkscape isn't telling us the current working directory." + ) + elif cwd == "": + raise AbortExtension( + f"The SVG must be saved before you can use relative paths." + ) filename = os.path.join(cwd, filename) return os.path.realpath(os.path.expanduser(filename)) @@ -285,8 +314,9 @@ class TempDirMixin(_Base): """ Provide a temporary directory for extensions to stash files. """ - dir_suffix = '' - dir_prefix = 'inktmp' + + dir_suffix = "" + dir_prefix = "inktmp" def __init__(self, *args, **kwargs): self.tempdir = None @@ -296,8 +326,11 @@ class TempDirMixin(_Base): # type: () -> None """Create the temporary directory""" from tempfile import TemporaryDirectory + # Need to hold a reference to the Directory object or else it might get GC'd - self._tempdir = TemporaryDirectory(prefix=self.dir_prefix, suffix=self.dir_suffix) + self._tempdir = TemporaryDirectory( + prefix=self.dir_prefix, suffix=self.dir_suffix + ) self.tempdir = self._tempdir.name super().load_raw() @@ -313,19 +346,30 @@ 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], ...] + select_all = () # type: Tuple[Type[BaseElement], ...] def __init__(self): super().__init__() self.arg_parser.add_argument( - "--id", action="append", type=str, dest="ids", default=[], - help="id attribute of object to manipulate") + "--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") + "--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 @@ -345,6 +389,7 @@ class SvgOutputMixin(_Base): # pylint: disable=too-few-public-methods A template can be specified to kick off the svg document building process. """ + template = """ bool """Return true if the svg document has changed""" original = etree.tostring(self.original_document) diff --git a/inkex/bezier.py b/inkex/bezier.py index 2602d14f..9fb84f28 100644 --- a/inkex/bezier.py +++ b/inkex/bezier.py @@ -33,15 +33,19 @@ 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)) + 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]) + 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): @@ -54,32 +58,35 @@ def root_wrapper(root_a, root_b, root_c, root_d): 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) + 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 = -0.5 + 0.5 * cmath.sqrt(-3.0) + w2 = -0.5 - 0.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) + m1 = pow(complex((m + cmath.sqrt(n)) / 2), 1.0 / 3) + n1 = pow(complex((m - cmath.sqrt(n)) / 2), 1.0 / 3) else: if m + math.sqrt(n) < 0: - m1 = -pow(-(m + math.sqrt(n)) / 2, 1. / 3) + m1 = -pow(-(m + math.sqrt(n)) / 2, 1.0 / 3) else: - m1 = pow((m + math.sqrt(n)) / 2, 1. / 3) + m1 = pow((m + math.sqrt(n)) / 2, 1.0 / 3) if m - math.sqrt(n) < 0: - n1 = -pow(-(m - math.sqrt(n)) / 2, 1. / 3) + n1 = -pow(-(m - math.sqrt(n)) / 2, 1.0 / 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)) + n1 = pow((m - math.sqrt(n)) / 2, 1.0 / 3) + return ( + -1.0 / 3 * (mono_a + m1 + n1), + -1.0 / 3 * (mono_a + w1 * m1 + w2 * n1), + -1.0 / 3 * (mono_a + w2 * m1 + w1 * n1), + ) elif root_b: - det = root_c ** 2.0 - 4.0 * root_b * root_d + 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)) + (-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),) @@ -88,9 +95,11 @@ def root_wrapper(root_a, root_b, root_c, root_d): 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]) + return ( + pointdistance(sp1[1], sp1[2]) + + pointdistance(sp1[2], sp2[0]) + + pointdistance(sp2[0], sp2[1]) + ) def cspbezsplit(sp1, sp2, time=0.5): @@ -182,16 +191,16 @@ def linebezierintersect(arg_a, bez): 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 + 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 + dx = 3 * ax * (t**2) + 2 * bx * t + cx + dy = 3 * ay * (t**2) + 2 * by * t + cy return dx, dy @@ -274,7 +283,7 @@ def addifclose(bez, l, error=0.001): 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 + retval = (ax * (t**2) + bx * t + cx) ** 2 + (ay * (t**2) + by * t + cy) ** 2 return math.sqrt(retval) @@ -293,7 +302,7 @@ def simpson(start, end, maxiter, tolerance, bezier_args): Returns: float: the appoximate length of the bezier curve """ - + n = 2 multiplier = (end - start) / 6.0 endsum = balf(start, bezier_args) + balf(end, bezier_args) @@ -340,11 +349,13 @@ def beziertatlength(bez, l=0.5, tolerance=0.001): 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: @@ -373,10 +384,9 @@ def subdiv(sp, flat, i=1): 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]]) + 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: @@ -384,8 +394,12 @@ def csparea(csp): 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]]) + 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 @@ -393,47 +407,54 @@ def csparea(csp): 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]]) + 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: + if abs(area) < 1.0e-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 + 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]]) + 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) - ]) + + 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/inkex/colors.py b/inkex/colors.py index 225b3058..498221d9 100644 --- a/inkex/colors.py +++ b/inkex/colors.py @@ -25,161 +25,162 @@ Basic color controls from .utils import PY3 # All the names that get added to the inkex API itself. -__all__ = ('Color', 'ColorError', 'ColorIdError') +__all__ = ("Color", "ColorError", "ColorIdError") 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, + "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: @@ -187,20 +188,25 @@ def is_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 + 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]) @@ -208,15 +214,15 @@ class Color(list): 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',))) + 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',))) + 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',))) + 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',))) + lightness = lightness.setter(lambda self, value: self._set(2, value, ("hsl",))) - def __init__(self, color=None, space='rgb'): + def __init__(self, color=None, space="rgb"): super().__init__() if isinstance(color, Color): space, color = color.space, list(color) @@ -247,16 +253,16 @@ class Color(list): """Allow colors to be hashable""" return tuple(self.to_rgba()).__hash__() - def _set(self, index, value, spaces=('rgb', 'rgba')): + 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 self.space == "named": + self.space = "rgb" if not self.space in spaces: - if index == 3 and self.space == 'rgb': + 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')) + 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]) @@ -272,13 +278,13 @@ class Color(list): if isinstance(val, str): val = val.strip() - if val.endswith('%'): - val = float(val.strip('%')) / 100 + if val.endswith("%"): + val = float(val.strip("%")) / 100 else: val = float(val) end_type = int - if len(self) == 3: # Alpha value + if len(self) == 3: # Alpha value val = min([1.0, val]) end_type = float elif isinstance(val, float) and val <= 1.0: @@ -292,32 +298,32 @@ class Color(list): """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] + return "named", Color.parse_str(SVG_COLOR[color.lower()])[1] if color is None: - return 'rgb', None + return "rgb", None - if color.startswith('url('): + 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('#'): + if color.startswith("#"): # Remove any icc or ilab directives # FUTURE: We could use icc or ilab information - col = color.split(' ')[0] + col = color.split(" ")[0] if len(col) == 4: - col = '#{1}{1}{2}{2}{3}{3}'.format(*col) + 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)) + return "rgb", (int(col[1:3], 16), int(col[3:5], 16), int(col[5:], 16)) except ValueError: raise ColorError(f"Bad RGB hex color value {col}") # Handle other css color values - elif '(' in color and ')' in color: - space, values = color.lower().strip().strip(')').split('(') - return space, values.split(',') + elif "(" in color and ")" in color: + space, values = color.lower().strip().strip(")").split("(") + return space, values.split(",") try: return Color.parse_int(int(color)) @@ -329,36 +335,36 @@ class Color(list): @staticmethod def parse_int(color): """Creates an rgb or rgba from a long int""" - space = 'rgb' + space = "rgb" color = [ - ((color >> 24) & 255), # red - ((color >> 16) & 255), # green - ((color >> 8) & 255), # blue - ((color & 255) / 255.), # opacity + ((color >> 24) & 255), # red + ((color >> 16) & 255), # green + ((color >> 8) & 255), # blue + ((color & 255) / 255.0), # opacity ] if color[-1] == 1.0: color.pop() else: - space = 'rgba' + 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) + 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': + 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) + 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(f"Can't print colour space '{self.space}'") def __int__(self): @@ -366,41 +372,46 @@ class Color(list): if not self: return -1 color = self.to_rgba() - return (color[0] << 24) + (color[1] << 16) + (color[2] << 8) + (int(color[3] * 255)) + 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)() + 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'): + if not self and self.space in ("rgb", "named"): return self.to_rgb().to_hsl() - if self.space == 'hsl': + if self.space == "hsl": return self - elif self.space in ('named'): + elif self.space in ("named"): return self.to_rgb().to_hsl() - elif self.space == 'rgb': - return Color(rgb_to_hsl(*self.to_floats()), space='hsl') + elif self.space == "rgb": + return Color(rgb_to_hsl(*self.to_floats()), space="hsl") raise ColorError(f"Unknown color conversion {self.space}->hsl") def to_rgb(self): """Turn this color into a Red/Green/Blue colour space""" - if not self and self.space in ('rgb', 'named'): + if not self and self.space in ("rgb", "named"): return Color([0, 0, 0]) - if self.space == 'rgb': + 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') + 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(f"Unknown color conversion {self.space}->rgb") def to_rgba(self, alpha=1.0): """Turn this color isn't an RGB with Alpha colour space""" - if self.space == 'rgba': + if self.space == "rgba": return self - return Color(self.to_rgb() + [alpha], 'rgba') + return Color(self.to_rgb() + [alpha], "rgba") def to_floats(self): """Returns the colour values as percentage floats (0.0 - 1.0)""" @@ -415,6 +426,7 @@ class Color(list): def interpolate(self, other, fraction): """Iterpolate two colours by the given fraction""" from .tween import ColorInterpolator + return ColorInterpolator(self, other).interpolate(fraction) @staticmethod @@ -424,10 +436,11 @@ class Color(list): if x is None or (isinstance(x, str) and x.lower() == "none"): return True return False + @staticmethod def iscolor(x, accept_none=False): """Checks if a given value can be parsed as a color""" - if isinstance(x, str) and (accept_none or not(Color.isnone(x))): + if isinstance(x, str) and (accept_none or not (Color.isnone(x))): try: Color(x) return True @@ -475,9 +488,11 @@ def hsl_to_rgb(hue, sat, light): 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)] + 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): diff --git a/inkex/command.py b/inkex/command.py index d2ff9587..0f3603ae 100644 --- a/inkex/command.py +++ b/inkex/command.py @@ -40,35 +40,41 @@ from lxml.etree import ElementTree from .elements import SvgDocumentElement -INKSCAPE_EXECUTABLE_NAME = os.environ.get('INKSCAPE_COMMAND') +INKSCAPE_EXECUTABLE_NAME = os.environ.get("INKSCAPE_COMMAND") if INKSCAPE_EXECUTABLE_NAME == None: - if sys.platform == 'win32': + if sys.platform == "win32": # prefer inkscape.exe over inkscape.com which spawns a command window - INKSCAPE_EXECUTABLE_NAME = 'inkscape.exe' + INKSCAPE_EXECUTABLE_NAME = "inkscape.exe" else: - INKSCAPE_EXECUTABLE_NAME = 'inkscape' + INKSCAPE_EXECUTABLE_NAME = "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 - # On Windows, shutil.which may give preference to .py files in the current directory - # (such as pdflatex.py), e.g. if .PY is in pathext, because the current directory is + # On Windows, shutil.which may give preference to .py files in the current directory + # (such as pdflatex.py), e.g. if .PY is in pathext, because the current directory is # prepended to PATH. This can be suppressed by explicitly appending the current directory. try: if sys.platform == "win32": from shutil import which + prog = which(program, path=os.environ["PATH"] + ";" + os.curdir) if prog: return prog @@ -78,27 +84,28 @@ def which(program): try: # Python3 only version of which from shutil import which as warlock + prog = warlock(program) if prog: return prog except ImportError: - pass # python2 - + 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(f"Can not find the command: '{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: + with open(filename, "wb") as fhl: if isinstance(svg, SvgDocumentElement): svg = ElementTree(svg) - if hasattr(svg, 'write'): + if hasattr(svg, "write"): # XML document svg.write(fhl) elif isinstance(svg, bytes): @@ -112,9 +119,9 @@ 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 + arg = "-" + arg if len(arg) > 2 and not oldie: - arg = '-' + arg + arg = "-" + arg if val is True: return arg if val is False: @@ -122,6 +129,7 @@ def to_arg(arg, oldie=False): return f"{arg}={str(val)}" return str(arg) + def to_args(prog, *positionals, **arguments): """Compile arguments and keyword arguments into a list of strings which Popen will understand. @@ -149,9 +157,9 @@ def to_args(prog, *positionals, **arguments): :rtype: ``list[str]`` """ args = [prog] - oldie = arguments.pop('oldie', False) + oldie = arguments.pop("oldie", False) for arg, value in arguments.items(): - arg = arg.replace('_', '-').strip() + arg = arg.replace("_", "-").strip() if isinstance(value, tuple): value = list(value) @@ -164,34 +172,40 @@ def to_args(prog, *positionals, **arguments): 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 to_args_sorted(prog, *positionals, **arguments): """same as to_args, but keyword arguments are sorted beforehand""" return to_args(prog, *positionals, **dict(sorted(arguments.items()))) + def _call(program, *args, **kwargs): - stdin = kwargs.pop('stdin', None) + stdin = kwargs.pop("stdin", None) if isinstance(stdin, str): - stdin = stdin.encode('utf-8') + stdin = stdin.encode("utf-8") inpipe = PIPE if stdin else None args = to_args(which(program), *args, **kwargs) kwargs = {} if sys.platform == "win32": - kwargs["creationflags"] = 0x08000000 # create no console window + kwargs["creationflags"] = 0x08000000 # create no console window 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 - **kwargs + 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 + **kwargs, ) (stdout, stderr) = process.communicate(input=stdin) if process.returncode == 0: return stdout - raise ProgramRunError(f"Return Code: {process.returncode}: {stderr}\n{stdout}\nargs: {args}") + raise ProgramRunError( + f"Return Code: {process.returncode}: {stderr}\n{stdout}\nargs: {args}" + ) + def call(program, *args, **kwargs): """ @@ -206,44 +220,49 @@ def call(program, *args, **kwargs): * All other arguments converted using to_args(...) function. """ # We use this long input because it's less likely to conflict with --binary= - binary = kwargs.pop('return_binary', False) + binary = kwargs.pop("return_binary", False) stdout = _call(program, *args, **kwargs) # Convert binary to string when we wish to have strings we do this here # so the mock tests will also run the conversion (always returns bytes) if not binary and isinstance(stdout, bytes): - return stdout.decode(sys.stdout.encoding or 'utf-8') + return stdout.decode(sys.stdout.encoding or "utf-8") return stdout + def inkscape(svg_file, *args, **kwargs): """ Call Inkscape with the given svg_file and the given arguments, see call() """ 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 tmpdir: - svg_file = write_svg(svg, tmpdir, '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: + with TemporaryDirectory(prefix="inkscape-command") as tmpdir: + svg_file = write_svg(svg, tmpdir, "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): + +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) + 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 diff --git a/inkex/deprecated-simple/bezmisc.py b/inkex/deprecated-simple/bezmisc.py index 19924402..92b63f6b 100644 --- a/inkex/deprecated-simple/bezmisc.py +++ b/inkex/deprecated-simple/bezmisc.py @@ -34,6 +34,7 @@ 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)""" @@ -41,4 +42,5 @@ def Simpson(func, a, b, n_limit, tolerance): """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.""") + broken way of writing python.""" + ) diff --git a/inkex/deprecated-simple/cubicsuperpath.py b/inkex/deprecated-simple/cubicsuperpath.py index 990da317..cb6f1245 100644 --- a/inkex/deprecated-simple/cubicsuperpath.py +++ b/inkex/deprecated-simple/cubicsuperpath.py @@ -20,26 +20,32 @@ 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) diff --git a/inkex/deprecated-simple/ffgeom.py b/inkex/deprecated-simple/ffgeom.py index bef3ba4f..2feb3bd9 100644 --- a/inkex/deprecated-simple/ffgeom.py +++ b/inkex/deprecated-simple/ffgeom.py @@ -23,32 +23,35 @@ from inkex.deprecated import deprecate from inkex.transforms import DirectedLineSegment as NewSeg try: - NaN = float('NaN') + NaN = float("NaN") except ValueError: PosInf = 1e300000 - NaN = PosInf/PosInf + NaN = PosInf / PosInf -class Point(namedtuple('Point', 'x y')): + +class Point(namedtuple("Point", "x y")): __slots__ = () + def __getitem__(self, key): if isinstance(key, str): - key = 'xy'.index(key) + 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']) + e0 = (e0["x"], e0["y"]) if isinstance(e1, dict): - e1 = (e1['x'], e1['y']) + 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} + 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 @@ -56,10 +59,10 @@ class Segment(NewSeg): rise = delta_y def distanceToPoint(self, p): - return self.distance_to_point(p['x'], p['y']) + return self.distance_to_point(p["x"], p["y"]) def perpDistanceToPoint(self, p): - return self.perp_distance(p['x'], p['y']) + return self.perp_distance(p["x"], p["y"]) def angle(self): return super(Segment, self).angle @@ -74,13 +77,15 @@ class Segment(NewSeg): return self.point_at_ratio(ratio) def createParallel(self, p): - self.parallel(p['x'], p['y']) + 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)""" diff --git a/inkex/deprecated-simple/run_command.py b/inkex/deprecated-simple/run_command.py index 71907506..84f4a4b0 100755 --- a/inkex/deprecated-simple/run_command.py +++ b/inkex/deprecated-simple/run_command.py @@ -26,6 +26,7 @@ from subprocess import Popen, PIPE from inkex.deprecated import deprecate + def run(command_format, prog_name): """inkex.commands.call(...)""" svgfile = tempfile.mktemp(".svg") @@ -48,14 +49,19 @@ def run(command_format, prog_name): 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)) + 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 + 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: @@ -71,4 +77,3 @@ def run(command_format, prog_name): # Output error message (if any) and exit. return msg - diff --git a/inkex/deprecated-simple/simplepath.py b/inkex/deprecated-simple/simplepath.py index 97748f78..6eaaf3b7 100644 --- a/inkex/deprecated-simple/simplepath.py +++ b/inkex/deprecated-simple/simplepath.py @@ -12,39 +12,56 @@ 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, [], []] -}) +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))""" diff --git a/inkex/deprecated-simple/simplestyle.py b/inkex/deprecated-simple/simplestyle.py index 33121433..cab6d9d8 100644 --- a/inkex/deprecated-simple/simplestyle.py +++ b/inkex/deprecated-simple/simplestyle.py @@ -6,42 +6,50 @@ 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): +def formatColor3i(r, g, b): """str(inkex.Color((r, g, b)))""" return str(inkex.Color((r, g, b))) + @deprecate -def formatColor3f(r,g,b): +def formatColor3f(r, g, b): """str(inkex.Color((r, g, b)))""" return str(inkex.Color((r, g, b))) diff --git a/inkex/deprecated-simple/simpletransform.py b/inkex/deprecated-simple/simpletransform.py index 3180fcef..2f7b9cf2 100644 --- a/inkex/deprecated-simple/simpletransform.py +++ b/inkex/deprecated-simple/simpletransform.py @@ -14,9 +14,11 @@ 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""" @@ -25,6 +27,7 @@ def parseTransform(transf, mat=None): t = Transform(mat) * t return _lists(t.matrix) + @deprecate def formatTransform(mat): """str(Transform(mat))""" @@ -33,26 +36,31 @@ def formatTransform(mat): 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 """ + """elem.transform = Transform(mat) * elem.transform""" node.transform = Transform(mat) @ node.transform + @deprecate def applyTransformToPoint(mat, pt): """Transform(mat).apply_to_point(pt)""" @@ -62,44 +70,52 @@ def applyTransformToPoint(mat, pt): 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]]): +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)""" diff --git a/inkex/deprecated.py b/inkex/deprecated.py index fc200ef1..f22f9b41 100644 --- a/inkex/deprecated.py +++ b/inkex/deprecated.py @@ -46,23 +46,25 @@ warnings.simplefilter("default") # 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') +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)) + 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()) + 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""" @@ -74,173 +76,267 @@ class DeprecatedEffect(object): # 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'): + if not hasattr(self, "svg"): from .elements import SvgDocumentElement + self.svg = SvgDocumentElement() - if not hasattr(self, 'arg_parser'): + if not hasattr(self, "arg_parser"): self.arg_parser = ArgumentParser() - if not hasattr(self, 'run'): + if not hasattr(self, "run"): self.run = self.affect @classmethod - def _deprecated(cls, name, msg=_('{} is deprecated and should be removed'), stack=3): + 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) + 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.')) + "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': + 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') + 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.')) + 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 SvgDocumentElement class. Use `self.svg.get_current_layer()` instead.')) + self._deprecated( + "current_layer", + _( + "{} is now a method in the SvgDocumentElement class. 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 SvgDocumentElement class. Use `self.svg.get_center_position()` instead.')) + self._deprecated( + "view_center", + _( + "{} is now a method in the SvgDocumentElement class. Use `self.svg.get_center_position()` instead." + ), + ) return self.svg.namedview.center @property def selected(self): - self._deprecated('selected', _('{} is now a dict in the SvgDocumentElement class. Use `self.svg.selected`.')) - return dict([(elem.get('id'), elem) for elem in self.svg.selected]) + self._deprecated( + "selected", + _( + "{} is now a dict in the SvgDocumentElement class. Use `self.svg.selected`." + ), + ) + return dict([(elem.get("id"), elem) for elem in self.svg.selected]) @property def doc_ids(self): - self._deprecated('doc_ids', _('{} is now a method in the SvgDocumentElement class. ' - 'Use `self.svg.get_ids()` instead.')) + self._deprecated( + "doc_ids", + _( + "{} is now a method in the SvgDocumentElement class. " + "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._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')) + self._deprecated("getselected", _("{} has been removed")) def getElementById(self, eid): - self._deprecated('getElementById',\ - _('{} is now a method in the SvgDocumentElement class. Use `self.svg.getElementById(eid)` instead.')) + self._deprecated( + "getElementById", + _( + "{} is now a method in the SvgDocumentElement class. 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 SvgDocumentElement class. ' - 'Use `self.svg.getElement(path)` instead.')) + self._deprecated( + "xpathSingle", + _( + "{} is now a new method in the SvgDocumentElement class. " + "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.')) + 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 SvgDocumentElement class. ' - 'Use `self.svg.namedview` to access this element.')) + self._deprecated( + "getNamedView", + _( + "{} is now a property of the SvgDocumentElement class. " + "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.')) + + 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 + 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._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.')) + 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.')) + 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')() + 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 SvgDocumentElement class. ' - ' Use `self.svg.get_unique_id(old_id)` instead.')) + self._deprecated( + "uniqueId", + _( + "{} is now a method in the SvgDocumentElement class. " + " 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 SvgDocumentElement class. ' - 'Use `self.svg.width` instead.')) - return self.svg.get('width') + self._deprecated( + "getDocumentWidth", + _( + "{} is now a property of the SvgDocumentElement class. " + "Use `self.svg.width` instead." + ), + ) + return self.svg.get("width") def getDocumentHeight(self): - self._deprecated('getDocumentHeight', _('{} is now a property of the SvgDocumentElement class. ' - 'Use `self.svg.height` instead.')) - return self.svg.get('height') + self._deprecated( + "getDocumentHeight", + _( + "{} is now a property of the SvgDocumentElement class. " + "Use `self.svg.height` instead." + ), + ) + return self.svg.get("height") def getDocumentUnit(self): - self._deprecated('getDocumentUnit', _('{} is now a property of the SvgDocumentElement class. ' - 'Use `self.svg.unit` instead.')) + self._deprecated( + "getDocumentUnit", + _( + "{} is now a property of the SvgDocumentElement class. " + "Use `self.svg.unit` instead." + ), + ) return self.svg.unit def unittouu(self, string): - self._deprecated('unittouu', _('{} is now a method in the SvgDocumentElement class. ' - 'Use `self.svg.unittouu(str)` instead.')) + self._deprecated( + "unittouu", + _( + "{} is now a method in the SvgDocumentElement class. " + "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 SvgDocumentElement class. ' - 'Use `self.svg.uutounit(value, unit)` instead.')) + self._deprecated( + "uutounit", + _( + "{} is now a method in the SvgDocumentElement class. " + "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 SvgDocumentElement class. ' - 'Use `self.svg.add_unit(value)` instead.')) + self._deprecated( + "addDocumentUnit", + _( + "{} is now a method in the SvgDocumentElement class. " + "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): r"""Function decorator for deprecation functions which have a one-liner equivalent in the new API. The one-liner has to passed as a string @@ -258,13 +354,15 @@ def deprecate(func): """ def _inner(*args, **kwargs): - _deprecated('{0.__module__}.{0.__name__} -> {0.__doc__}'.format(func), stack=2) + _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): @@ -274,16 +372,19 @@ class DeprecatedDict(dict): 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): @@ -292,58 +393,79 @@ class lazyproxy(object): 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", ) + TYPES = optparse.Option.TYPES + ("inkbool",) TYPE_CHECKER = dict(optparse.Option.TYPE_CHECKER) - TYPE_CHECKER["inkbool"] = lambda _1, _2, v: str(v).capitalize() == 'True' + 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) + _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) + _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) + _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 SvgDocumentElement class. ' - 'Use `self.svg.unittouu(str)` instead.', stack=2) - return inkex.units.convert_unit(string, 'px') + _deprecated( + "inkex.unittouu is now a method in the SvgDocumentElement class. " + "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) + _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 + +argparse.Namespace.ensure_value = ensure_value # type: ignore + @deprecate def zSort(inNode, idList): @@ -358,8 +480,10 @@ def zSort(inNode, idList): sortedList += zSort(child, idList) return sortedList + class DeprecatedSvgMixin(object): """Mixin which adds deprecated API elements to the SvgDocumentElement""" + @property def selected(self): """svg.selection""" @@ -402,6 +526,8 @@ class DeprecatedSvgMixin(object): def description(self, value): """elem.desc = value""" self.desc = value + + BaseElement.description = deprecate(description) @@ -410,6 +536,7 @@ def composed_style(element: ShapeElement): This function has been deprecated in favor of BaseElement.specified_style()""" return element.specified_style() + ShapeElement.composed_style = deprecate(composed_style) @@ -417,26 +544,33 @@ def width(self): """Use BaseElement.viewport_width instead""" return self.viewport_width + def height(self): """Use BaseElement.viewport_height instead""" return self.viewport_height + BaseElement.width = property(deprecate(width)) BaseElement.height = property(deprecate(height)) -def paint_order(selection : ElementList): + +def paint_order(selection: ElementList): """svg.selection.rendering_order()""" return selection.rendering_order() -ElementList.paint_order = deprecate(paint_order) # type: ignore + +ElementList.paint_order = deprecate(paint_order) # type: ignore + def transform_imul(self, matrix): """Use @= operator instead""" return self.__imatmul__(matrix) + def transform_mul(self, matrix): """Use @ operator instead""" return self.__matmul__(matrix) -Transform.__imul__ = deprecate(transform_imul) # type: ignore -Transform.__mul__ = deprecate(transform_mul) # type: ignore \ No newline at end of file + +Transform.__imul__ = deprecate(transform_imul) # type: ignore +Transform.__mul__ = deprecate(transform_mul) # type: ignore diff --git a/inkex/elements/__init__.py b/inkex/elements/__init__.py index d1977159..5e771bea 100644 --- a/inkex/elements/__init__.py +++ b/inkex/elements/__init__.py @@ -10,11 +10,45 @@ 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 ._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, Page -from ._filters import Filter, Pattern, Gradient, LinearGradient, RadialGradient, \ - PathEffect, Stop, MeshGradient, MeshRow, MeshPatch +from ._meta import ( + Defs, + StyleElement, + Script, + Desc, + Title, + NamedView, + Guide, + Metadata, + ForeignObject, + Switch, + Grid, + Page, +) +from ._filters import ( + Filter, + Pattern, + Gradient, + LinearGradient, + RadialGradient, + PathEffect, + Stop, + MeshGradient, + MeshRow, + MeshPatch, +) from ._image import Image diff --git a/inkex/elements/_base.py b/inkex/elements/_base.py index 079c14e7..3b8b4a87 100644 --- a/inkex/elements/_base.py +++ b/inkex/elements/_base.py @@ -38,17 +38,28 @@ from ..units import convert_unit, render_unit, parse_unit from ._utils import ChildToProperty, NSS, addNS, removeNS, splitNS from ..properties import BaseStyleValue, all_properties -#from ..deprecated import DeprecatedShapeElementMixin +# from ..deprecated import DeprecatedShapeElementMixin + +from typing import ( + overload, + DefaultDict, + Type, + Any, + List, + Tuple, + Union, + Optional, +) # pylint: disable=unused-import -from typing import overload, DefaultDict, Type, Any, List, Tuple, Union, Optional # pylint: disable=unused-import 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]] + lookup_table = defaultdict(list) # type: DefaultDict[str, List[Any]] @classmethod def register_class(cls, klass): @@ -60,7 +71,7 @@ class NodeBasedLookup(etree.PythonElementClassLookup): """Find the class for this type of element defined by an xpath""" if isinstance(xpath, type): return xpath - for cls in cls.lookup_table[splitNS(xpath.split('/')[-1])]: + for cls in cls.lookup_table[splitNS(xpath.split("/")[-1])]: # TODO: We could create a apply the xpath attrs to the test element # to narrow the search, but this does everything we need right now. test_element = cls() @@ -68,11 +79,11 @@ class NodeBasedLookup(etree.PythonElementClassLookup): return cls raise KeyError(f"Could not find svg tag for '{xpath}'") - def lookup(self, doc, element): # pylint: disable=unused-argument + 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 + if cls._is_class_element(element): # pylint: disable=protected-access return cls except TypeError: # Handle non-element proxies case @@ -87,15 +98,19 @@ class NodeBasedLookup(etree.PythonElementClassLookup): 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'<')): + 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""" + def __init_subclass__(cls): if cls.tag_name: NodeBasedLookup.register_class(cls) @@ -105,10 +120,10 @@ class BaseElement(etree.ElementBase): """Hook to do more restrictive check in addition to (ns,tag) match""" return True - tag_name = '' + tag_name = "" @property - def TAG(self): # pylint: disable=invalid-name + def TAG(self): # pylint: disable=invalid-name """Return the tag_name without NS""" if not self.tag_name: return removeNS(super().tag)[-1] @@ -125,10 +140,10 @@ class BaseElement(etree.ElementBase): PARSER = SVG_PARSER WRAPPED_ATTRS = ( # (prop_name, [optional: attr_name], cls) - ('transform', Transform), - ('style', Style), - ('classes', 'class', Classes), - ) # type: Tuple[Tuple[Any, ...], ...] + ("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. @@ -159,8 +174,8 @@ class BaseElement(etree.ElementBase): if new_item: self.set(attr, str(new_item)) else: - self.attrib.pop(attr, None) # pylint: disable=no-member - + self.attrib.pop(attr, None) # pylint: disable=no-member + # pylint: disable=no-member value = cls(self.attrib.get(attr, None), callback=_set_attr) if name == "style": @@ -179,7 +194,7 @@ class BaseElement(etree.ElementBase): value = cls(value) self.attrib[attr] = str(value) else: - self.attrib.pop(attr, None) # pylint: disable=no-member + self.attrib.pop(attr, None) # pylint: disable=no-member else: super().__setattr__(name, value) @@ -204,7 +219,7 @@ class BaseElement(etree.ElementBase): if not value: return if value is None: - self.attrib.pop(addNS(attr), None) # pylint: disable=no-member + self.attrib.pop(addNS(attr), None) # pylint: disable=no-member else: value = str(value) super().set(addNS(attr), value) @@ -231,7 +246,7 @@ class BaseElement(etree.ElementBase): value = getattr(self, prop) setattr(self, prop, cls(None)) return value - return self.attrib.pop(addNS(attr), default) # pylint: disable=no-member + return self.attrib.pop(addNS(attr), default) # pylint: disable=no-member def add(self, *children): """ @@ -247,9 +262,10 @@ class BaseElement(etree.ElementBase): # 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] + return svg.tostring().split(b">\n ", 1)[-1][:-6] def set_random_id(self, prefix=None, size=4, backlinks=False): """Sets the id attribute if it is not already set.""" @@ -261,29 +277,32 @@ class BaseElement(etree.ElementBase): 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) + if hasattr(child, "set_random_ids"): + child.set_random_ids( + prefix=prefix, levels=levels - 1, backlinks=backlinks + ) eid = property(lambda self: self.get_id()) + def get_id(self, as_url=0): """Get the id for the element, will set a new unique id if not set. as_url - If set to 1, returns #{id} as a string If set to 2, returns url(#{id}) as a string """ - if 'id' not in self.attrib: + if "id" not in self.attrib: self.set_random_id(self.TAG) - eid = self.get('id') + eid = self.get("id") if as_url > 0: - eid = '#' + eid + eid = "#" + eid if as_url > 1: - eid = f'url({eid})' + eid = f"url({eid})" return eid 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) + 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 @@ -297,6 +316,7 @@ class BaseElement(etree.ElementBase): while parent is not None: root, parent = parent, parent.getparent() from ._svg import SvgDocumentElement + if not isinstance(root, SvgDocumentElement): raise FragmentError("Element fragment does not have a document root!") return root @@ -317,7 +337,15 @@ class BaseElement(etree.ElementBase): def descendants(self): """Walks the element tree and yields all elements, parent first""" from ._selected import ElementList - return ElementList(self.root, [element for element in self.iter() if isinstance(element, (BaseElement, str))]) + + return ElementList( + self.root, + [ + element + for element in self.iter() + if isinstance(element, (BaseElement, str)) + ], + ) def ancestors(self, elem=None, stop_at=()): """ @@ -327,6 +355,7 @@ class BaseElement(etree.ElementBase): 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): @@ -336,17 +365,18 @@ class BaseElement(etree.ElementBase): yield parent if parent in stop_at: break - + 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') + my_id = self.get("id") if my_id is not None: - elems = list(self.root.getElementsByHref(my_id)) \ - + list(self.root.getElementsByStyleUrl(my_id)) + elems = list(self.root.getElementsByHref(my_id)) + list( + self.root.getElementsByStyleUrl(my_id) + ) for elem in elems: - if hasattr(elem, 'backlinks'): + if hasattr(elem, "backlinks"): for child in elem.backlinks(*types): yield child @@ -354,7 +384,9 @@ class BaseElement(etree.ElementBase): """Wrap xpath call and add svg namespaces""" return super().xpath(pattern, namespaces=namespaces) - def findall(self, pattern, namespaces=NSS): # pylint: disable=dangerous-default-value + def findall( + self, pattern, namespaces=NSS + ): # pylint: disable=dangerous-default-value """Wrap findall call and add svg namespaces""" return super().findall(pattern, namespaces=namespaces) @@ -378,8 +410,8 @@ class BaseElement(etree.ElementBase): 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.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() @@ -388,7 +420,7 @@ class BaseElement(etree.ElementBase): def copy(self): """Make a copy of the element and return it""" elem = deepcopy(self) - elem.set('id', None) + elem.set("id", None) return elem def duplicate(self): @@ -402,38 +434,38 @@ class BaseElement(etree.ElementBase): # 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] + return str(self.tag).split("}")[-1] @property def href(self): """Returns the referred-to element if available""" - ref = self.get('xlink:href') + ref = self.get("xlink:href") if not ref: return None - return self.root.getElementById(ref.strip('#')) + 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) + self.set("xlink:href", "#" + elem) @property def label(self): """Returns the inkscape label""" - return self.get('inkscape:label', None) + return self.get("inkscape:label", None) - label = label.setter(lambda self, value: self.set('inkscape:label', str(value))) # type: ignore + label = label.setter(lambda self, value: self.set("inkscape:label", str(value))) # type: ignore def is_sensitive(self): """Return true if this element is sensitive in inkscape""" - return self.get('sodipodi:insensitive', None) != 'true' + return self.get("sodipodi:insensitive", None) != "true" def set_sensitive(self, sensitive=True): """Set the sensitivity of the element/layer""" # Sensitive requires None instead of 'false' - self.set('sodipodi:insensitive', ['true', None][sensitive]) + self.set("sodipodi:insensitive", ["true", None][sensitive]) @property def unit(self): @@ -441,10 +473,10 @@ class BaseElement(etree.ElementBase): try: return self.root.unit except FragmentError: - return 'px' # Don't cache. + return "px" # Don't cache. @staticmethod - def to_dimensional(value, to_unit='px'): + def to_dimensional(value, to_unit="px"): """Convert a value given in user units (px) the given unit type""" return convert_unit(value, to_unit) @@ -453,14 +485,14 @@ class BaseElement(etree.ElementBase): """Convert a length value into user units (px)""" return convert_unit(value, "px") - def uutounit(self, value, to_unit='px'): + def uutounit(self, value, to_unit="px"): """Convert a unit value to a given unit. If the value does not have a unit, "Document" units - are assumed. "Document units" are an Inkscape-specific concept. For most use-cases, + are assumed. "Document units" are an Inkscape-specific concept. For most use-cases, to_dimensional is more appropriate.""" return convert_unit(value, to_unit, default=self.unit) def unittouu(self, value): - """Convert a unit value into document units. "Document unit" is an Inkscape-specific + """Convert a unit value into document units. "Document unit" is an Inkscape-specific concept. For most use-cases, viewport_to_unit (when the size of an object given in viewport units is needed) or to_dimensionless (when the equivalent value without unit is needed) is more appropriate.""" @@ -469,17 +501,19 @@ class BaseElement(etree.ElementBase): def unit_to_viewport(self, value, unit="px"): """Converts a length value to viewport units, as defined by the width/height element on the root""" - return self.to_dimensional(self.to_dimensionless(value) \ - * self.root.equivalent_transform_scale, unit) + return self.to_dimensional( + self.to_dimensionless(value) * self.root.equivalent_transform_scale, unit + ) def viewport_to_unit(self, value, unit="px"): """Converts a length given on the viewport to the specified unit in the user coordinate system""" - return self.to_dimensional(self.to_dimensionless(value) \ - / self.root.equivalent_transform_scale, unit) + return self.to_dimensional( + self.to_dimensionless(value) / self.root.equivalent_transform_scale, unit + ) def add_unit(self, value): - """Add document unit when no unit is specified in the string """ + """Add document unit when no unit is specified in the string""" return render_unit(value, self.unit) def cascaded_style(self): @@ -508,19 +542,23 @@ class BaseElement(etree.ElementBase): style = Style() for key in self.keys(): if key in all_properties and all_properties[key][2]: - style[key] = BaseStyleValue.factory(declaration=key + ": " + self.attrib[key]) + style[key] = BaseStyleValue.factory( + declaration=key + ": " + self.attrib[key] + ) return style 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""" + if none specified the transform is to the root document element""" parent = self.getparent() if parent is not None and isinstance(parent, BaseElement): return parent.composed_transform() @ self.transform return self.transform + 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""" @@ -533,27 +571,31 @@ class ShapeElement(BaseElement): @property def clip(self): """Gets the clip path element (if any)""" - ref = self.get('clip-path') + 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', elem.get_id(as_url=2)) + self.set("clip-path", elem.get_id(as_url=2)) def get_path(self): """Generate a path for this object which can inform the bounding box""" - raise NotImplementedError(f"Path should be provided by svg elem {self.typename}.") + raise NotImplementedError( + f"Path should be provided by svg elem {self.typename}." + ) def set_path(self, path): """Set the path for this object (if possible)""" raise AttributeError( - f"Path can not be set on this element: {self.typename} <- {path}.") + f"Path can not be set on this element: {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() @@ -587,16 +629,16 @@ class ShapeElement(BaseElement): def is_visible(self): """Returns false if the css says this object is invisible""" - if self.style.get('display', '') == 'none': + if self.style.get("display", "") == "none": return False - if not float(self.style.get('opacity', 1.0)): + if not float(self.style.get("opacity", 1.0)): return False return True def get_line_height_uu(self): """Returns the specified value of line-height, in user units""" style = self.specified_style() - font_size = style("font-size") # already in uu + font_size = style("font-size") # already in uu line_height = style("line-height") parsed = parse_unit(line_height) if parsed is None: diff --git a/inkex/elements/_filters.py b/inkex/elements/_filters.py index 0685f29a..397dd2f7 100644 --- a/inkex/elements/_filters.py +++ b/inkex/elements/_filters.py @@ -36,18 +36,28 @@ from ._utils import addNS from ._base import BaseElement -from typing import overload, Iterable, List, Tuple, Union, Optional, TYPE_CHECKING # pylint: disable=unused-import +from typing import ( + overload, + Iterable, + List, + Tuple, + Union, + Optional, + TYPE_CHECKING, +) # pylint: disable=unused-import if TYPE_CHECKING: from inkex import SvgDocumentElement + class Filter(BaseElement): """A filter (usually in defs)""" - tag_name = 'filter' + + 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 = etree.SubElement(self, addNS(fe_type, "svg")) elem.update(**args) return elem @@ -55,89 +65,98 @@ class Filter(BaseElement): pass class Blend(Primitive): - tag_name = 'feBlend' + tag_name = "feBlend" class ColorMatrix(Primitive): - tag_name = 'feColorMatrix' + tag_name = "feColorMatrix" class ComponentTransfer(Primitive): - tag_name = 'feComponentTransfer' + tag_name = "feComponentTransfer" class Composite(Primitive): - tag_name = 'feComposite' + tag_name = "feComposite" class ConvolveMatrix(Primitive): - tag_name = 'feConvolveMatrix' + tag_name = "feConvolveMatrix" class DiffuseLighting(Primitive): - tag_name = 'feDiffuseLighting' + tag_name = "feDiffuseLighting" class DisplacementMap(Primitive): - tag_name = 'feDisplacementMap' + tag_name = "feDisplacementMap" class Flood(Primitive): - tag_name = 'feFlood' + tag_name = "feFlood" class GaussianBlur(Primitive): - tag_name = 'feGaussianBlur' + tag_name = "feGaussianBlur" class Image(Primitive): - tag_name = 'feImage' + tag_name = "feImage" class Merge(Primitive): - tag_name = 'feMerge' + tag_name = "feMerge" class Morphology(Primitive): - tag_name = 'feMorphology' + tag_name = "feMorphology" class Offset(Primitive): - tag_name = 'feOffset' + tag_name = "feOffset" class SpecularLighting(Primitive): - tag_name = 'feSpecularLighting' + tag_name = "feSpecularLighting" class Tile(Primitive): - tag_name = 'feTile' + tag_name = "feTile" class Turbulence(Primitive): - tag_name = 'feTurbulence' + tag_name = "feTurbulence" class Stop(BaseElement): - tag_name = 'stop' + tag_name = "stop" @property def offset(self): # type: () -> float - return self.get('offset') + return self.get("offset") @offset.setter def offset(self, number): - self.set('offset', number) + self.set("offset", number) def interpolate(self, other, fraction): from ..tween import StopInterpolator + return StopInterpolator(self, other).interpolate(fraction) 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),) + + 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, ...] + 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, RadialGradient)) else self - return sorted([child for child in gradcolor if isinstance(child, Stop)], - key=lambda x: parse_percent(x.offset)) + gradcolor = ( + self.href + if isinstance(self.href, (LinearGradient, RadialGradient)) + else self + ) + return sorted( + [child for child in gradcolor if isinstance(child, Stop)], + key=lambda x: parse_percent(x.offset), + ) @property def stop_offsets(self): @@ -146,7 +165,7 @@ class Gradient(BaseElement): return [child.offset for child in self.stops] @property - def stop_styles(self): # type: () -> List[Style] + def stop_styles(self): # type: () -> List[Style] """Return a list of own or linked offset styles""" return [child.style for child in self.stops] @@ -154,9 +173,13 @@ class Gradient(BaseElement): """Remove all orientation attributes from this element""" for attr in self.orientation_attributes: self.pop(attr) - def interpolate(self, other, fraction, svg=None): # type: (LinearGradient, float, SvgDocumentElement) -> LinearGradient + + def interpolate( + self, other, fraction, svg=None + ): # type: (LinearGradient, float, SvgDocumentElement) -> LinearGradient """Interpolate with another gradient.""" from ..tween import GradientInterpolator + return GradientInterpolator(self, other, svg).interpolate(fraction) def stops_and_orientation(self): @@ -169,49 +192,65 @@ class Gradient(BaseElement): class LinearGradient(Gradient): - tag_name = 'linearGradient' - orientation_attributes = ('x1', 'y1', 'x2', 'y2') + tag_name = "linearGradient" + orientation_attributes = ("x1", "y1", "x2", "y2") - def apply_transform(self): # type: () -> None + def apply_transform(self): # type: () -> None """Apply transform to orientation points and set it to identity.""" - trans = self.pop('gradientTransform') - p1 = (self.to_dimensionless(self.get('x1')), self.to_dimensionless(self.get('y1'))) - p2 = (self.to_dimensionless(self.get('x2')), self.to_dimensionless(self.get('y2'))) + trans = self.pop("gradientTransform") + p1 = ( + self.to_dimensionless(self.get("x1")), + self.to_dimensionless(self.get("y1")), + ) + p2 = ( + self.to_dimensionless(self.get("x2")), + self.to_dimensionless(self.get("y2")), + ) p1t = trans.apply_to_point(p1) p2t = trans.apply_to_point(p2) self.update( x1=self.to_dimensionless(p1t[0]), y1=self.to_dimensionless(p1t[1]), x2=self.to_dimensionless(p2t[0]), - y2=self.to_dimensionless(p2t[1])) + y2=self.to_dimensionless(p2t[1]), + ) class RadialGradient(Gradient): - tag_name = 'radialGradient' - orientation_attributes = ('cx', 'cy', 'fx', 'fy', 'r') + tag_name = "radialGradient" + orientation_attributes = ("cx", "cy", "fx", "fy", "r") - def apply_transform(self): # type: () -> None + def apply_transform(self): # type: () -> None """Apply transform to orientation points and set it to identity.""" - trans = self.pop('gradientTransform') - p1 = (self.to_dimensionless(self.get('cx')), self.to_dimensionless(self.get('cy'))) - p2 = (self.to_dimensionless(self.get('fx')), self.to_dimensionless(self.get('fy'))) + trans = self.pop("gradientTransform") + p1 = ( + self.to_dimensionless(self.get("cx")), + self.to_dimensionless(self.get("cy")), + ) + p2 = ( + self.to_dimensionless(self.get("fx")), + self.to_dimensionless(self.get("fy")), + ) p1t = trans.apply_to_point(p1) p2t = trans.apply_to_point(p2) self.update( cx=self.to_dimensionless(p1t[0]), cy=self.to_dimensionless(p1t[1]), fx=self.to_dimensionless(p2t[0]), - fy=self.to_dimensionless(p2t[1])) + fy=self.to_dimensionless(p2t[1]), + ) class PathEffect(BaseElement): """Inkscape LPE element""" - tag_name = 'inkscape:path-effect' + + tag_name = "inkscape:path-effect" class MeshGradient(Gradient): """Usable MeshGradient XML base class""" - tag_name = 'meshgradient' + + tag_name = "meshgradient" @classmethod def new_mesh(cls, pos=None, rows=1, cols=1, autocollect=True): @@ -226,21 +265,24 @@ class MeshGradient(Gradient): for _ in range(cols): meshrow.append(MeshPatch()) # set meshgradient attributes - meshgradient.set('gradientUnits', 'userSpaceOnUse') - meshgradient.set('x', pos[0]) - meshgradient.set('y', pos[1]) + meshgradient.set("gradientUnits", "userSpaceOnUse") + meshgradient.set("x", pos[0]) + meshgradient.set("y", pos[1]) if autocollect: - meshgradient.set('inkscape:collect', 'always') + meshgradient.set("inkscape:collect", "always") return meshgradient class MeshRow(BaseElement): """Each row of a mesh gradient""" - tag_name = 'meshrow' + + tag_name = "meshrow" + class MeshPatch(BaseElement): """Each column or 'patch' in a mesh gradient""" - tag_name = 'meshpatch' + + tag_name = "meshpatch" def stops(self, edges, colors): """Add or edit meshpatch stops with path and stop-color.""" @@ -252,6 +294,6 @@ class MeshPatch(BaseElement): stop = self.add(Stop()) # set edge path data - stop.set('path', str(edge)) + stop.set("path", str(edge)) # set stop color - stop.style['stop-color'] = str(colors[i % 2]) + stop.style["stop-color"] = str(colors[i % 2]) diff --git a/inkex/elements/_groups.py b/inkex/elements/_groups.py index fa299899..f80b76e3 100644 --- a/inkex/elements/_groups.py +++ b/inkex/elements/_groups.py @@ -23,7 +23,7 @@ Interface for all group based elements such as Groups, Use, Markers etc. """ -from lxml import etree # pylint: disable=unused-import +from lxml import etree # pylint: disable=unused-import from ..paths import Path from ..transforms import Transform @@ -36,8 +36,10 @@ try: except ImportError: pass + class GroupBase(ShapeElement): """Base Group element""" + def get_path(self): ret = Path() for child in self: @@ -58,14 +60,14 @@ class GroupBase(ShapeElement): class Group(GroupBase): """Any group element (layer or regular group)""" - tag_name = 'g' + + tag_name = "g" @classmethod def new(cls, label, *children, **attrs): - attrs['inkscape:label'] = label + attrs["inkscape:label"] = label return super().new(*children, **attrs) - def effective_style(self): """A blend of each child's style mixed together (last child wins)""" style = self.style @@ -76,37 +78,40 @@ class Group(GroupBase): @property def groupmode(self): """Return the type of group this is""" - return self.get('inkscape:groupmode', 'group') + return self.get("inkscape:groupmode", "group") class Layer(Group): """Inkscape extension of svg:g""" def _init(self): - self.set('inkscape:groupmode', 'layer') + 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" + return el.attrib.get(addNS("inkscape:groupmode"), None) == "layer" class Anchor(GroupBase): """An anchor or link tag""" - tag_name = 'a' + + tag_name = "a" @classmethod def new(cls, href, *children, **attrs): - attrs['xlink:href'] = href + attrs["xlink:href"] = href return super().new(*children, **attrs) class ClipPath(GroupBase): """A path used to clip objects""" - tag_name = 'clipPath' + + 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' + or polymarkers on a given , , or element.""" + + tag_name = "marker" diff --git a/inkex/elements/_image.py b/inkex/elements/_image.py index efd00d3e..9294d736 100644 --- a/inkex/elements/_image.py +++ b/inkex/elements/_image.py @@ -22,6 +22,8 @@ Image element interface. from ._polygons import RectangleBase + class Image(RectangleBase): """Provide a useful extension for image elements""" - tag_name = 'image' + + tag_name = "image" diff --git a/inkex/elements/_meta.py b/inkex/elements/_meta.py index c2a617f4..93073018 100644 --- a/inkex/elements/_meta.py +++ b/inkex/elements/_meta.py @@ -34,13 +34,17 @@ from ..transforms import Vector2d from ._base import BaseElement + class Defs(BaseElement): """A header defs element, one per document""" - tag_name = 'defs' + + tag_name = "defs" + class StyleElement(BaseElement): """A CSS style element containing multiple style definitions""" - tag_name = 'style' + + tag_name = "style" def set_text(self, content): """Sets the style content text as a CDATA section""" @@ -50,37 +54,47 @@ class StyleElement(BaseElement): """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' + + 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' + + tag_name = "desc" + class Title(BaseElement): """Title element""" - tag_name = 'title' + + 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')) + 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.viewport_to_unit(self.get('inkscape:cx') or 0), - self.root.viewport_to_unit(self.get('inkscape:cy') or 0)) + return Vector2d( + self.root.viewport_to_unit(self.get("inkscape:cx") or 0), + self.root.viewport_to_unit(self.get("inkscape:cy") or 0), + ) def get_guides(self): """Returns a list of guides""" - return self.findall('sodipodi:guide') + return self.findall("sodipodi:guide") def new_guide(self, position, orient=True, name=None): """Creates a new guide in this namedview""" @@ -89,29 +103,32 @@ class NamedView(BaseElement): elif orient is False: elem = Guide().move_to(position, 0, (1, 0)) if name: - elem.set('inkscape:label', str(name)) + elem.set("inkscape:label", str(name)) return self.add(elem) def get_pages(self): """Returns a list of pages""" - return self.findall('inkscape:page') + return self.findall("inkscape:page") def new_page(self, x, y, width, height, label=None): """Creates a new page in this namedview""" elem = Page(width=width, height=height, x=x, y=y) if label: - elem.set('inkscape:label', str(label)) + elem.set("inkscape:label", str(label)) 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'))) + 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): @@ -127,9 +144,9 @@ class Guide(BaseElement): it may be a pair of numbers (tuple) which will set the orientation directly. If not given at all, the orientation remains unchanged. """ - self.set('position', f"{float(pos_x):g},{float(pos_y):g}") + self.set("position", f"{float(pos_x):g},{float(pos_y):g}") if isinstance(angle, str): - if ',' not in angle: + if "," not in angle: angle = float(angle) if isinstance(angle, (float, int)): @@ -140,28 +157,38 @@ class Guide(BaseElement): angle = "{:g},{:g}".format(*angle) if angle is not None: - self.set('orientation', angle) + self.set("orientation", angle) return self + class Metadata(BaseElement): """Inkscape Metadata element""" - tag_name = 'metadata' + + tag_name = "metadata" + class ForeignObject(BaseElement): """SVG foreignObject element""" - tag_name = 'foreignObject' + + tag_name = "foreignObject" + class Switch(BaseElement): """A switch element""" - tag_name = 'switch' + + tag_name = "switch" + class Grid(BaseElement): """A namedview grid child""" - tag_name = 'inkscape:grid' + + tag_name = "inkscape:grid" + class Page(BaseElement): """A namedview page child""" - tag_name = 'inkscape:page' + + tag_name = "inkscape:page" width = property(lambda self: self.to_dimensionless(self.get("width") or 0)) height = property(lambda self: self.to_dimensionless(self.get("height") or 0)) @@ -170,14 +197,14 @@ class Page(BaseElement): @classmethod def new(cls, width, height, x, y): - """ Creates a new page element in the namedview""" + """Creates a new page element in the namedview""" page = super().new() page.move_to(x, y) - page.set('width', width) - page.set('height', height) + page.set("width", width) + page.set("height", height) return page def move_to(self, x, y): - """ Move this page to the given x,y position """ - self.set('position', f"{float(x):g},{float(y):g}") + """Move this page to the given x,y position""" + self.set("position", f"{float(x):g},{float(y):g}") return self diff --git a/inkex/elements/_polygons.py b/inkex/elements/_polygons.py index 1f5390f3..0b931c40 100644 --- a/inkex/elements/_polygons.py +++ b/inkex/elements/_polygons.py @@ -33,9 +33,11 @@ from ..bezier import pointdistance from ._utils import addNS from ._base import ShapeElement + class PathElementBase(ShapeElement): """Base element for path based shapes""" - get_path = lambda self: self.get('d') + + get_path = lambda self: self.get("d") @classmethod def new(cls, path, **attrs): @@ -43,66 +45,82 @@ class PathElementBase(ShapeElement): def set_path(self, path): """Set the given data as a path as the 'd' attribute""" - self.set('d', str(Path(path))) + self.set("d", str(Path(path))) def apply_transform(self): """Apply the internal transformation to this node and delete""" - if 'transform' in self.attrib: + if "transform" in self.attrib: self.path = self.path.transform(self.transform) - self.set('transform', 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)) + 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))) + 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' + + tag_name = "path" + @staticmethod - def _arcpath(cx : float, cy : float, rx : float, ry : float, # pylint: disable=invalid-name - start : float, end : float, arctype : str) -> Optional[Path]: + def _arcpath( + cx: float, + cy: float, + rx: float, + ry: float, # pylint: disable=invalid-name + start: float, + end: float, + arctype: str, + ) -> Optional[Path]: if abs(rx) < 1e-8 or abs(ry) < 1e-8: return None incr = end - start - if incr < 0: - incr += 2*pi - numsegs = min(1 + int(incr*2.0/pi), 4) + if incr < 0: + incr += 2 * pi + numsegs = min(1 + int(incr * 2.0 / pi), 4) incr = incr / numsegs computed = Path() computed.append(Move(cos(start), sin(start))) - for seg in range(1, numsegs+1): - computed.append(Arc(1, 1, 0, 0, 1, cos(start+seg*incr), sin(start+seg*incr))) - if abs(incr*numsegs - 2*pi) > 1e-8 and (arctype in ("slice", "")): # slice is default + for seg in range(1, numsegs + 1): + computed.append( + Arc(1, 1, 0, 0, 1, cos(start + seg * incr), sin(start + seg * incr)) + ) + if abs(incr * numsegs - 2 * pi) > 1e-8 and ( + arctype in ("slice", "") + ): # slice is default computed.append(PathLine(0, 0)) if arctype != "arc": computed.append(ZoneClose()) - computed.transform(Transform().add_translate(cx, cy).add_scale(rx, ry) \ - , inplace=True) + computed.transform( + Transform().add_translate(cx, cy).add_scale(rx, ry), inplace=True + ) return computed.to_relative() @classmethod - def arc(cls, center, rx, ry=None, arctype="", pathonly=False, **kw): # pylint: disable=invalid-name - """Generates a sodipodi elliptical arc (special type). Also computes the path that Inkscape + def arc( + cls, center, rx, ry=None, arctype="", pathonly=False, **kw + ): # pylint: disable=invalid-name + """Generates a sodipodi elliptical arc (special type). Also computes the path that Inkscape uses under the hood. All data may be given as parseable strings or using numeric data types. Args: center (tuple-like): Coordinates of the star/polygon center as tuple or Vector2d rx (Union[float, str]): Radius in x direction - ry (Union[float, str], optional): Radius in y direction. If not given, ry=rx. + ry (Union[float, str], optional): Radius in y direction. If not given, ry=rx. Defaults to None. arctype (str, optional): "arc", "chord" or "slice". Defaults to "", i.e. "slice". - pathonly (bool, optional): Whether to create the path without Inkscape-specific + pathonly (bool, optional): Whether to create the path without Inkscape-specific attributes. Defaults to False. Keyword args: start (Union[float, str]): start angle in radians @@ -112,55 +130,85 @@ class PathElement(PathElementBase): Returns: PathElement : the created star/polygon """ - others = [(name, kw.pop(name, None)) for name in ('start', 'end', 'open')] + 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') + 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") if arctype != "": - elem.set('sodipodi:arc-type', arctype) + elem.set("sodipodi:arc-type", arctype) for name, value in others: if value is not None: - elem.set('sodipodi:'+name, str(value).lower()) - - - path = cls._arcpath(float(center[0]), float(center[1]), float(rx), float(ry or rx), - float(elem.get("sodipodi:start", 0)), - float(elem.get("sodipodi:end", 2*pi)), arctype) + elem.set("sodipodi:" + name, str(value).lower()) + + path = cls._arcpath( + float(center[0]), + float(center[1]), + float(rx), + float(ry or rx), + float(elem.get("sodipodi:start", 0)), + float(elem.get("sodipodi:end", 2 * pi)), + arctype, + ) if pathonly: elem = cls(**kw) if path is not None: - elem.path = path + elem.path = path return elem @staticmethod - def _starpath(c: Tuple[float, float], sides : int, r : Tuple[float, float], # pylint: disable=invalid-name - arg : Tuple[float, float], rounded : float, flatsided : bool): + def _starpath( + c: Tuple[float, float], + sides: int, + r: Tuple[float, float], # pylint: disable=invalid-name + arg: Tuple[float, float], + rounded: float, + flatsided: bool, + ): """Helper method to generate the path for an Inkscape star/ polygon; randomized is ignored.""" + def _star_get_xy(point, index): cur_arg = arg[point] + 2 * pi / sides * (index % sides) return Vector2d(*c) + r[point] * Vector2d(cos(cur_arg), sin(cur_arg)) + def _rot90_rel(origin, other): """Returns a unit length vector at 90 deg from origin to other""" - return 1/pointdistance(other, origin) * \ - Vector2d(other.y - origin.y, other.x - origin.x) - def _star_get_curvepoint(point, index, is_prev : bool): + return ( + 1 + / pointdistance(other, origin) + * Vector2d(other.y - origin.y, other.x - origin.x) + ) + + def _star_get_curvepoint(point, index, is_prev: bool): index = index % sides orig = _star_get_xy(point, index) - previ = (index-1 + sides) % sides - nexti = (index+1) % sides + previ = (index - 1 + sides) % sides + nexti = (index + 1) % sides # neighbors of the current point depend on polygon or star - prev = _star_get_xy(point, previ) if flatsided else \ - _star_get_xy(1-point, index if point == 1 else previ) - nextp = _star_get_xy(point, nexti) if flatsided else \ - _star_get_xy(1-point, index if point == 0 else nexti) + prev = ( + _star_get_xy(point, previ) + if flatsided + else _star_get_xy(1 - point, index if point == 1 else previ) + ) + nextp = ( + _star_get_xy(point, nexti) + if flatsided + else _star_get_xy(1 - point, index if point == 0 else nexti) + ) mid = 0.5 * (prev + nextp) # direction of bezier handles rot = _rot90_rel(orig, mid + 100000 * _rot90_rel(mid, nextp)) - ret = rounded * rot * \ - (-1 * pointdistance(prev, orig) if is_prev else pointdistance(nextp, orig)) + ret = ( + rounded + * rot + * ( + -1 * pointdistance(prev, orig) + if is_prev + else pointdistance(nextp, orig) + ) + ) return orig + ret pointy = abs(rounded) < 1e-4 @@ -172,63 +220,92 @@ class PathElement(PathElementBase): if pointy: result.append(PathLine(*_star_get_xy(1, i))) else: - result.append(Curve(*_star_get_curvepoint(0, i, False), - *_star_get_curvepoint(1, i, True), *_star_get_xy(1, i))) + result.append( + Curve( + *_star_get_curvepoint(0, i, False), + *_star_get_curvepoint(1, i, True), + *_star_get_xy(1, i), + ) + ) # draw to point type 0 for both stars and rectangles - if pointy and i < sides -1: - result.append(PathLine(*_star_get_xy(0, i+1))) + if pointy and i < sides - 1: + result.append(PathLine(*_star_get_xy(0, i + 1))) if not pointy: if not flatsided: - result.append(Curve(*_star_get_curvepoint(1, i, False), - *_star_get_curvepoint(0, i+1, True), *_star_get_xy(0, i+1))) + result.append( + Curve( + *_star_get_curvepoint(1, i, False), + *_star_get_curvepoint(0, i + 1, True), + *_star_get_xy(0, i + 1), + ) + ) else: - result.append(Curve(*_star_get_curvepoint(0, i, False), - *_star_get_curvepoint(0, i+1, True), *_star_get_xy(0, i+1))) + result.append( + Curve( + *_star_get_curvepoint(0, i, False), + *_star_get_curvepoint(0, i + 1, True), + *_star_get_xy(0, i + 1), + ) + ) result.append(ZoneClose()) return result.to_relative() @classmethod - def star(cls, center, radii, sides=5, rounded=0, args=(0,0), flatsided=False, pathonly=False): + def star( + cls, + center, + radii, + sides=5, + rounded=0, + args=(0, 0), + flatsided=False, + pathonly=False, + ): """Generate a sodipodi star / polygon. Also computes the path that Inkscape uses under the hood. The arguments for center, radii, sides, rounded and args can be given as strings or as numeric data. Args: center (Tuple-like): Coordinates of the star/polygon center as tuple or Vector2d - radii (tuple): Radii of the control points, i.e. their distances from the center. - The control points are specified in polar coordinates. - Only the first control point is used for polygons. + radii (tuple): Radii of the control points, i.e. their distances from the center. + The control points are specified in polar coordinates. + Only the first control point is used for polygons. sides (int, optional): Number of sides / tips of the polygon / star. Defaults to 5. - rounded (int, optional): Controls the rounding radius of the polygon / star. + rounded (int, optional): Controls the rounding radius of the polygon / star. For `rounded=0`, only straight lines are used. Defaults to 0. - args (tuple, optional): Angle between horizontal axis and control points. + args (tuple, optional): Angle between horizontal axis and control points. Defaults to (0,0). flatsided (bool, optional): True for polygons, False for stars. Defaults to False. - pathonly (bool, optional): Whether to create the path without Inkscape-specific + pathonly (bool, optional): Whether to create the path without Inkscape-specific attributes. Defaults to False. Returns: PathElement : the created star/polygon """ elem = cls() - elem.set('sodipodi:cx', center[0]) - elem.set('sodipodi:cy', center[1]) - elem.set('sodipodi:r1', radii[0]) - elem.set('sodipodi:r2', radii[1]) - elem.set('sodipodi:arg1', args[0]) - elem.set('sodipodi:arg2', args[1]) - elem.set('sodipodi:sides', max(sides, 3) if flatsided else max(sides, 2)) - elem.set('inkscape:rounded', rounded) - elem.set('inkscape:flatsided', str(flatsided).lower()) - elem.set('sodipodi:type', 'star') - - path = cls._starpath((float(center[0]), float(center[1])), - int(sides), (float(radii[0]), float(radii[1])), - (float(args[0]), float(args[1])), float(rounded), flatsided) + elem.set("sodipodi:cx", center[0]) + elem.set("sodipodi:cy", center[1]) + elem.set("sodipodi:r1", radii[0]) + elem.set("sodipodi:r2", radii[1]) + elem.set("sodipodi:arg1", args[0]) + elem.set("sodipodi:arg2", args[1]) + elem.set("sodipodi:sides", max(sides, 3) if flatsided else max(sides, 2)) + elem.set("inkscape:rounded", rounded) + elem.set("inkscape:flatsided", str(flatsided).lower()) + elem.set("sodipodi:type", "star") + + path = cls._starpath( + (float(center[0]), float(center[1])), + int(sides), + (float(radii[0]), float(radii[1])), + (float(args[0]), float(args[1])), + float(rounded), + flatsided, + ) if pathonly: elem = cls() - #inkex.errormsg(path) + # inkex.errormsg(path) if path is not None: elem.path = path @@ -237,63 +314,80 @@ class PathElement(PathElementBase): class Polyline(ShapeElement): """Like a path, but made up of straight line segments only""" - tag_name = 'polyline' + + tag_name = "polyline" def get_path(self): - return Path('M' + self.get('points')) + return Path("M" + self.get("points")) def set_path(self, path): - points = [f'{x:g},{y:g}' for x, y in Path(path).end_points] - self.set('points', ' '.join(points)) + points = [f"{x:g},{y:g}" 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' + + 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) + + 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().new(x1=start.x, y1=start.y, - x2=end.x, y2=end.y, **attrs) + return super().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: self.to_dimensionless(self.get('x', '0'))) - top = property(lambda self: self.to_dimensionless(self.get('y', '0'))) + + left = property(lambda self: self.to_dimensionless(self.get("x", "0"))) + top = property(lambda self: self.to_dimensionless(self.get("y", "0"))) right = property(lambda self: self.left + self.width) bottom = property(lambda self: self.top + self.height) - width = property(lambda self: self.to_dimensionless(self.get('width', '0'))) - height = property(lambda self: self.to_dimensionless(self.get('height', '0'))) - rx = property(lambda self: self.to_dimensionless(self.get('rx', self.get('ry', 0.0)))) - ry = property(lambda self: self.to_dimensionless(self.get('ry', self.get('rx', 0.0)))) # pylint: disable=invalid-name + width = property(lambda self: self.to_dimensionless(self.get("width", "0"))) + height = property(lambda self: self.to_dimensionless(self.get("height", "0"))) + rx = property( + lambda self: self.to_dimensionless(self.get("rx", self.get("ry", 0.0))) + ) + ry = property( + lambda self: self.to_dimensionless(self.get("ry", self.get("rx", 0.0))) + ) # 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) + 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' + + tag_name = "rect" @classmethod def new(cls, left, top, width, height, **attrs): @@ -307,15 +401,18 @@ class EllipseBase(ShapeElement): """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) + 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(self.to_dimensionless(self.get('cx', '0')), - self.to_dimensionless(self.get('cy', '0'))) + return ImmutableVector2d( + self.to_dimensionless(self.get("cx", "0")), + self.to_dimensionless(self.get("cy", "0")), + ) @center.setter def center(self, value): @@ -325,7 +422,7 @@ class EllipseBase(ShapeElement): def _rxry(self): # type: () -> Vector2d - """Helper function """ + """Helper function""" raise NotImplementedError() @classmethod @@ -338,11 +435,12 @@ class EllipseBase(ShapeElement): class Circle(EllipseBase): """Provide a useful extension for circle elements""" - tag_name = 'circle' + + tag_name = "circle" @property def radius(self): - return self.to_dimensionless(self.get('r', '0')) + return self.to_dimensionless(self.get("r", "0")) @radius.setter def radius(self, value): @@ -355,11 +453,15 @@ class Circle(EllipseBase): class Ellipse(EllipseBase): """Provide a similar extension to the Circle interface""" - tag_name = 'ellipse' + + tag_name = "ellipse" @property def radius(self): - return ImmutableVector2d(self.to_dimensionless(self.get('rx', '0')), self.to_dimensionless(self.get('ry', '0'))) + return ImmutableVector2d( + self.to_dimensionless(self.get("rx", "0")), + self.to_dimensionless(self.get("ry", "0")), + ) @radius.setter def radius(self, value): diff --git a/inkex/elements/_selected.py b/inkex/elements/_selected.py index 51dd08f0..8f6178bb 100644 --- a/inkex/elements/_selected.py +++ b/inkex/elements/_selected.py @@ -26,6 +26,7 @@ from ._base import BaseElement from ..localization import inkex_gettext from ..utils import AbortExtension + class ElementList(OrderedDict): """ A list of elements, selected by id, iterator or xpath @@ -36,6 +37,7 @@ class ElementList(OrderedDict): 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() @@ -54,7 +56,8 @@ class ElementList(OrderedDict): def __setitem__(self, orig_key, elem): from ._base import BaseElement - if orig_key != elem and orig_key != elem.get('id'): + + if orig_key != elem and orig_key != elem.get("id"): raise ValueError(f"Refusing to set bad key in ElementList {orig_key}") if isinstance(elem, str): key = elem @@ -64,7 +67,7 @@ class ElementList(OrderedDict): if isinstance(elem, BaseElement): # Selection is a list of elements to select key = elem.xml_path - element_id = elem.get('id') + element_id = elem.get("id") if element_id is not None: self.ids[element_id] = key super().__setitem__(key, elem) @@ -75,13 +78,14 @@ class ElementList(OrderedDict): 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] != '/': + elif isinstance(key, str) and key[0] != "/": return self.ids.get(key, key) return key @@ -110,29 +114,38 @@ class ElementList(OrderedDict): def pop(self, key=None): """Remove the key item or remove the last item selected""" item = super().pop(self._to_key(key, default=-1)) - self.ids.pop(item.get('id')) + 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('//'): + 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 + self[elem] = elem # This doesn't matter def rendering_order(self): """Get the selected elements by z-order (stacking order), ordered from bottom to top""" new_list = ElementList(self.svg) - # the elements are stored with their xpath index, so a natural sort order + # the elements are stored with their xpath index, so a natural sort order # '3' < '20' < '100' has to be applied - new_list.set(*[elem for _, elem in sorted(self.items(), key=lambda x: natural_sort_key(x[0]))]) + new_list.set( + *[ + elem + for _, elem in sorted( + self.items(), key=lambda x: natural_sort_key(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 if not types or isinstance(e, types)]) + return ElementList( + self.svg, [e for e in self if not types or isinstance(e, types)] + ) def filter_nonzero(self, *types, error_msg: str = None): """Filter selected elements of the given type, returns a new SelectedElements object. @@ -147,20 +160,27 @@ class ElementList(OrderedDict): filtered = self.filter(*types) if not filtered: if error_msg is None: - error_msg = \ - inkex_gettext("Please select at least one element of the following type(s): {}"\ - .format(", ".join([type.__name__ for type in types]))) + error_msg = inkex_gettext( + "Please select at least one element of the following type(s): {}".format( + ", ".join([type.__name__ for type in types]) + ) + ) raise AbortExtension(error_msg) return filtered 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: yield from _recurse(child) - return ElementList(self.svg, [r for e in self for r in _recurse(e) if isinstance(r, (BaseElement, str))]) + + return ElementList( + self.svg, + [r for e in self for r in _recurse(e) if isinstance(r, (BaseElement, str))], + ) def id_dict(self): """For compatibility, return regular dictionary of id -> element pairs""" diff --git a/inkex/elements/_svg.py b/inkex/elements/_svg.py index 15ed87d4..81f501fa 100644 --- a/inkex/elements/_svg.py +++ b/inkex/elements/_svg.py @@ -38,13 +38,14 @@ from ..styles import StyleSheets from ._base import BaseElement from ._meta import StyleElement -if False: # pylint: disable=using-constant-test - import typing # pylint: disable=unused-import +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' + + tag_name = "svg" def _init(self): self.current_layer = None @@ -59,17 +60,17 @@ class SvgDocumentElement(DeprecatedSvgMixin, BaseElement): def get_ids(self): """Returns a set of unique document ids""" if not self.ids: - self.ids = set(self.xpath('//@id')) + self.ids = set(self.xpath("//@id")) return self.ids def get_unique_id(self, prefix, size=None): """Generate a new id from an existing old_id""" ids = self.get_ids() if size is None: - size = max(math.ceil(math.log10(len(ids) or 1000))+1, 4) + size = max(math.ceil(math.log10(len(ids) or 1000)) + 1, 4) new_id = None - _from = 10 ** size - 1 - _to = 10 ** size + _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) @@ -78,11 +79,13 @@ class SvgDocumentElement(DeprecatedSvgMixin, BaseElement): def get_page_bbox(self): """Gets the page dimensions as a bbox""" - return BoundingBox((0, float(self.viewbox_width)), (0, float(self.viewbox_height))) + return BoundingBox( + (0, float(self.viewbox_width)), (0, float(self.viewbox_height)) + ) def get_current_layer(self): """Returns the currently selected layer""" - layer = self.getElementById(self.namedview.current_layer, 'svg:g') + layer = self.getElementById(self.namedview.current_layer, "svg:g") if layer is None: return self return layer @@ -91,27 +94,30 @@ class SvgDocumentElement(DeprecatedSvgMixin, BaseElement): """Gets a single element from the given xpath or returns None""" return self.findone(xpath) - def getElementById(self, eid, elm='*', literal=False): # pylint: disable=invalid-name + def getElementById( + self, eid, elm="*", literal=False + ): # pylint: disable=invalid-name """Get an element in this svg document by it's ID attribute""" if eid is not None and not literal: - eid = eid.strip()[4:-1] if eid.startswith('url(') else eid - eid = eid.lstrip('#') + eid = eid.strip()[4:-1] if eid.startswith("url(") else eid + eid = eid.lstrip("#") return self.getElement(f'//{elm}[@id="{eid}"]') - def getElementByName(self, name, elm='*'): # pylint: disable=invalid-name + def getElementByName(self, name, elm="*"): # pylint: disable=invalid-name """Get an element by it's inkscape:label (aka name)""" return self.getElement(f'//{elm}[@inkscape:label="{name}"]') - def getElementsByClass(self, class_name): # pylint: disable=invalid-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(f".{class_name}").to_xpath()) - def getElementsByHref(self, eid): # pylint: disable=invalid-name + def getElementsByHref(self, eid): # pylint: disable=invalid-name """Get elements by their href xlink attribute""" return self.xpath(f'//*[@xlink:href="#{eid}"]') - def getElementsByStyleUrl(self, eid, style=None): # pylint: disable=invalid-name + def getElementsByStyleUrl(self, eid, style=None): # pylint: disable=invalid-name """Get elements by a style attribute url""" url = f"url(#{eid})" if style is not None: @@ -121,29 +127,29 @@ class SvgDocumentElement(DeprecatedSvgMixin, BaseElement): @property def name(self): """Returns the Document Name""" - return self.get('sodipodi:docname', '') + return self.get("sodipodi:docname", "") @property def namedview(self): """Return the sp namedview meta information element""" - return self.get_or_create('//sodipodi:namedview', prepend=True) + return self.get_or_create("//sodipodi:namedview", prepend=True) @property def metadata(self): """Return the svg metadata meta element container""" - return self.get_or_create('//svg:metadata', prepend=True) + return self.get_or_create("//svg:metadata", prepend=True) @property def defs(self): """Return the svg defs meta element container""" - return self.get_or_create('//svg:defs', prepend=True) + return self.get_or_create("//svg:defs", prepend=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()] + ret = [float(unit) for unit in self.get("viewBox", "0").split()] except ValueError: - ret = '' + ret = "" if len(ret) != 4: return [0, 0, 0, 0] return ret @@ -161,7 +167,7 @@ class SvgDocumentElement(DeprecatedSvgMixin, BaseElement): """Returns the width of the `viewport coordinate system `_ in user units, i.e. the width attribute of the svg element converted to px""" - return self.to_dimensionless(self.get('width')) or self.get_viewbox()[2] + return self.to_dimensionless(self.get("width")) or self.get_viewbox()[2] @property def viewbox_height(self): # getDocumentHeight(self): @@ -176,7 +182,7 @@ class SvgDocumentElement(DeprecatedSvgMixin, BaseElement): """Returns the width of the `viewport coordinate system `_ in user units, i.e. the height attribute of the svg element converted to px""" - return self.to_dimensionless(self.get('height')) or self.get_viewbox()[3] + return self.to_dimensionless(self.get("height")) or self.get_viewbox()[3] @property def scale(self): @@ -188,16 +194,20 @@ class SvgDocumentElement(DeprecatedSvgMixin, BaseElement): """Returns the ratio between the viewBox width (in width/height units) and the page width, which is displayed as "scale" in the Inkscape document properties.""" - viewbox_unit = (parse_unit(self.get("width")) or parse_unit(self.get("height"))\ - or (0, "px"))[1] + viewbox_unit = ( + parse_unit(self.get("width")) or parse_unit(self.get("height")) or (0, "px") + )[1] return self._base_scale(viewbox_unit) - def _base_scale(self, unit="px"): """Returns what Inkscape shows as "user units per `unit`" """ try: - scale_x = self.to_dimensional(self.viewport_width, unit) / self.viewbox_width - scale_y = self.to_dimensional(self.viewport_height, unit) / self.viewbox_height + scale_x = ( + self.to_dimensional(self.viewport_width, unit) / self.viewbox_width + ) + scale_y = ( + self.to_dimensional(self.viewport_height, unit) / self.viewbox_height + ) value = max([scale_x, scale_y]) return 1.0 if value == 0 else value except (ValueError, ZeroDivisionError): @@ -217,11 +227,11 @@ class SvgDocumentElement(DeprecatedSvgMixin, BaseElement): 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.""" - if not hasattr(self, '_unit'): - self._unit = 'px' # Default is px + if not hasattr(self, "_unit"): + self._unit = "px" # Default is px viewbox = self.get_viewbox() if viewbox and set(viewbox) != {0}: - self._unit = discover_unit(self.get('width'), viewbox[2], default='px') + self._unit = discover_unit(self.get("width"), viewbox[2], default="px") return self._unit @property @@ -232,7 +242,7 @@ class SvgDocumentElement(DeprecatedSvgMixin, BaseElement): def stylesheets(self): """Get all the stylesheets, bound together to one, (for reading)""" sheets = StyleSheets(self) - for node in self.xpath('//svg:style'): + for node in self.xpath("//svg:style"): sheets.append(node.stylesheet()) return sheets diff --git a/inkex/elements/_text.py b/inkex/elements/_text.py index f405720f..b73383c6 100644 --- a/inkex/elements/_text.py +++ b/inkex/elements/_text.py @@ -31,63 +31,75 @@ from ..transforms import Transform, BoundingBox from ._base import BaseElement, ShapeElement from ._polygons import PathElementBase + class FlowRegion(ShapeElement): """SVG Flow Region (SVG 2.0)""" - tag_name = 'flowRegion' + + 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' + + tag_name = "flowRoot" @property def region(self): """Return the first flowRegion in this flowRoot""" - return self.findone('svg:flowRegion') + 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' + + 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' + + 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' + + 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: self.to_dimensionless(self.get('x', 0))) - y = property(lambda self: self.to_dimensionless(self.get('y', 0))) + + tag_name = "text" + x = property(lambda self: self.to_dimensionless(self.get("x", 0))) + y = property(lambda self: self.to_dimensionless(self.get("y", 0))) def get_path(self): return Path() def tspans(self): """Returns all children that are tspan elements""" - return self.findall('svg:tspan') + return self.findall("svg:tspan") def get_text(self, sep="\n"): """Return the text content including tspans""" @@ -106,18 +118,22 @@ class TextElement(ShapeElement): bbox += tspan.bounding_box(effective_transform) return bbox + class TextPath(ShapeElement): """A textPath element""" - tag_name = 'textPath' + + tag_name = "textPath" def get_path(self): return Path() + class Tspan(ShapeElement): """A tspan text element""" - tag_name = 'tspan' - x = property(lambda self: self.to_dimensionless(self.get('x', 0))) - y = property(lambda self: self.to_dimensionless(self.get('y', 0))) + + tag_name = "tspan" + x = property(lambda self: self.to_dimensionless(self.get("x", 0))) + y = property(lambda self: self.to_dimensionless(self.get("y", 0))) @classmethod def superscript(cls, text): @@ -134,8 +150,8 @@ class Tspan(ShapeElement): """ effective_transform = Transform(transform) @ self.transform x1, y1 = effective_transform.apply_to_point((self.x, self.y)) - fontsize = self.to_dimensionless(self.style.get('font-size', '12px')) - x2 = self.x + 0 # XXX This is impossible to calculate! + fontsize = self.to_dimensionless(self.style.get("font-size", "12px")) + 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)) @@ -143,16 +159,23 @@ class Tspan(ShapeElement): class SVGfont(BaseElement): """An svg font element""" - tag_name = 'font' + + tag_name = "font" + class FontFace(BaseElement): """An svg font font-face element""" - tag_name = 'font-face' + + tag_name = "font-face" + class Glyph(PathElementBase): """An svg font glyph element""" - tag_name = 'glyph' + + tag_name = "glyph" + class MissingGlyph(BaseElement): """An svg font missing-glyph element""" - tag_name = 'missing-glyph' + + tag_name = "missing-glyph" diff --git a/inkex/elements/_use.py b/inkex/elements/_use.py index e4a5a0e1..8b703302 100644 --- a/inkex/elements/_use.py +++ b/inkex/elements/_use.py @@ -26,16 +26,20 @@ from ..transforms import Transform from ._groups import Group, GroupBase from ._base import ShapeElement + class Symbol(GroupBase): """SVG symbol element""" - tag_name = 'symbol' + + tag_name = "symbol" + class Use(ShapeElement): """A 'use' element that links to another in the document""" - tag_name = 'use' + + tag_name = "use" @classmethod - def new(cls, elem, x, y, **attrs): # pylint: disable=arguments-differ + def new(cls, elem, x, y, **attrs): # pylint: disable=arguments-differ ret = super().new(x=x, y=y, **attrs) ret.href = elem return ret diff --git a/inkex/elements/_utils.py b/inkex/elements/_utils.py index 74aaf97c..d12d8fba 100644 --- a/inkex/elements/_utils.py +++ b/inkex/elements/_utils.py @@ -25,56 +25,60 @@ import re # 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' + "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()) + 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: + if tag.startswith("{") and ns: _, tag = removeNS(tag) - if not tag.startswith('{'): - tag = tag.replace('__', ':') - if ':' in tag: - (ns, tag) = tag.rsplit(':', 1) + 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 + 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 + 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) -def natural_sort_key(s, _nsre=re.compile('([0-9]+)')): + +def natural_sort_key(s, _nsre=re.compile("([0-9]+)")): """Helper for a natural sort, see https://stackoverflow.com/a/16090640/3298143""" - return [int(text) if text.isdigit() else text.lower() - for text in _nsre.split(s)] + return [int(text) if text.isdigit() else text.lower() for text in _nsre.split(s)] + class ChildToProperty(property): """Use when you have a singleton child element who's text - content is the canonical value for the property""" + content is the canonical value for the property""" + def __init__(self, tag, prepend=False): self.tag = tag self.prepend = prepend @@ -94,6 +98,7 @@ class ChildToProperty(property): def __doc__(self): return f"Get, set or delete the {self.tag} property." + class CloningVat(object): """ When modifying defs, sometimes we want to know if every backlink would have @@ -101,6 +106,7 @@ class CloningVat(object): This tracks the def elements, their promises and creates clones if needed. """ + def __init__(self, svg): self.svg = svg self.tracks = defaultdict(set) @@ -108,8 +114,8 @@ class CloningVat(object): 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') + 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)) @@ -122,7 +128,7 @@ class CloningVat(object): 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)]) + 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) @@ -131,6 +137,5 @@ class CloningVat(object): 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) + update(elem.get("id"), clone.get("id"), **upkw) process(clone, **kwargs) - diff --git a/inkex/extensions.py b/inkex/extensions.py index a65105c7..366f1277 100644 --- a/inkex/extensions.py +++ b/inkex/extensions.py @@ -30,18 +30,41 @@ import types from .utils import errormsg, Boolean from .colors import Color, ColorIdError, ColorError -from .elements import load_svg, BaseElement, ShapeElement, Group, Layer, Grid, \ - TextElement, FlowPara, FlowDiv +from .elements import ( + load_svg, + BaseElement, + ShapeElement, + Group, + Layer, + Grid, + TextElement, + FlowPara, + FlowDiv, +) from .elements._utils import CloningVat -from .base import InkscapeExtension, SvgThroughMixin, SvgInputMixin, SvgOutputMixin, TempDirMixin +from .base import ( + InkscapeExtension, + SvgThroughMixin, + SvgInputMixin, + SvgOutputMixin, + TempDirMixin, +) from .transforms import Transform from .properties import all_properties from .elements import LinearGradient, RadialGradient # All the names that get added to the inkex API itself. -__all__ = ('EffectExtension', 'GenerateExtension', 'InputExtension', - 'OutputExtension', 'RasterOutputExtension', - 'CallExtension', 'TemplateExtension', 'ColorExtension', 'TextExtension') +__all__ = ( + "EffectExtension", + "GenerateExtension", + "InputExtension", + "OutputExtension", + "RasterOutputExtension", + "CallExtension", + "TemplateExtension", + "ColorExtension", + "TextExtension", +) stdout = sys.stdout @@ -51,14 +74,17 @@ 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 @@ -67,12 +93,15 @@ class OutputExtension(SvgInputMixin, InkscapeExtension): """But save certainly is, we give a more exact message here""" raise NotImplementedError("Output extensions require a save(stream) method!") + class RasterOutputExtension(InkscapeExtension): """ Takes a PNG from Inkscape and outputs it to another rather format. """ + def load(self, stream): from PIL import Image + self.img = Image.open(stream) def effect(self): @@ -90,6 +119,7 @@ class InputExtension(SvgOutputMixin, InkscapeExtension): Used in functions for `Open` """ + def effect(self): """Effect isn't needed for a lot of Input extensions""" pass @@ -98,13 +128,15 @@ class InputExtension(SvgOutputMixin, InkscapeExtension): """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' + + input_ext = "svg" + output_ext = "svg" def load(self, stream): - pass # Not called (load_raw instead) + pass # Not called (load_raw instead) def load_raw(self): # Don't call InputExtension.load_raw @@ -113,23 +145,23 @@ class CallExtension(TempDirMixin, InputExtension): if not isinstance(input_file, str): data = input_file.read() - input_file = os.path.join(self.tempdir, 'input.' + self.input_ext) - with open(input_file, 'wb') as fhl: + 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) + 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): if not os.path.isfile(document): raise IOError(f"Can't find generated document: {document}") - if self.output_ext == 'svg': - with open(document, 'r', encoding='utf-8') as fhl: + if self.output_ext == "svg": + with open(document, "r", encoding="utf-8") as fhl: document = fhl.read() - if '<' in document: - document = load_svg(document.encode('utf-8')) + if "<" in document: + document = load_svg(document.encode("utf-8")) else: - with open(document, 'rb') as fhl: + with open(document, "rb") as fhl: document = fhl.read() self.document = document @@ -138,12 +170,14 @@ class CallExtension(TempDirMixin, InputExtension): """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_label = "" container_layer = False def generate(self): @@ -205,7 +239,8 @@ class TemplateExtension(EffectExtension): """ Provide a standard way of creating templates. """ - size_rex = re.compile(r'([\d.]*)(\w\w)?x([\d.]*)(\w\w)?') + + size_rex = re.compile(r"([\d.]*)(\w\w)?x([\d.]*)(\w\w)?") template_id = "SVGRoot" def __init__(self): @@ -222,8 +257,9 @@ class TemplateExtension(EffectExtension): """Can be over-ridden with custom svg loading here""" return self.document - def arg_size(self, unit='px'): + 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) @@ -233,26 +269,40 @@ class TemplateExtension(EffectExtension): 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 ( + 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 = ( + 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')) + 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() @@ -264,12 +314,12 @@ class TemplateExtension(EffectExtension): 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)) + 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.set("showgrid", "true") self.svg.namedview.add(Grid(type="xygrid")) @@ -277,7 +327,8 @@ 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. + + process_none = False # should we call modify_color for the "none" color. select_all = (ShapeElement,) pass_rgba = False @@ -299,21 +350,24 @@ class ColorExtension(EffectExtension): """Process one of the selected elements""" style = elem.specified_style() # Colours first - for name in elem.style.associated_props if self.pass_rgba \ - else elem.style.color_props: + for name in ( + elem.style.associated_props if self.pass_rgba else elem.style.color_props + ): if name not in style: - continue # we don't want to process default values + continue # we don't want to process default values try: value = style(name) except ColorError: - continue # bad color value, don't touch. + continue # bad color value, don't touch. if isinstance(value, Color): col = Color(value) if self.pass_rgba: - col = col.to_rgba(alpha= elem.style(elem.style.associated_props[name])) + col = col.to_rgba( + alpha=elem.style(elem.style.associated_props[name]) + ) rgba_result = self._modify_color(name, col) elem.style.set_color(rgba_result, name) - + if isinstance(value, (LinearGradient, RadialGradient)): gradients.track(value, elem, self._ref_cloned, style=style, name=name) if value.href is not None: @@ -324,7 +378,9 @@ class ColorExtension(EffectExtension): for name in elem.style.opacity_props: value = style(name) result = self.modify_opacity(name, value) - if result != value and result != 1: # only modify if not equal to old or default + if ( + result != value and result != 1 + ): # only modify if not equal to old or default elem.style[name] = result def _ref_cloned(self, old_id, new_id, style, name): @@ -332,9 +388,9 @@ class ColorExtension(EffectExtension): style[name] = f"url(#{new_id})" def _xlink_cloned(self, old_id, new_id, linker): - lid = linker.get('id') + lid = linker.get("id") linker = self.svg.getElementById(self._renamed.get(lid, lid)) - linker.set('xlink:href', '#' + new_id) + linker.set("xlink:href", "#" + new_id) def _modify_color(self, name, color): """Pre-process color value to filter out bad colors""" @@ -350,10 +406,12 @@ class ColorExtension(EffectExtension): """Optional opacity modification""" return opacity + class TextExtension(EffectExtension): """ A base effect for changing text in a document. """ + newline = True newpar = True @@ -364,7 +422,7 @@ class TextExtension(EffectExtension): def process_element(self, node): """Reverse the node text""" - if node.get('sodipodi:role') == 'line': + if node.get("sodipodi:role") == "line": self.newline = True elif isinstance(node, (TextElement, FlowPara, FlowDiv)): self.newline = True @@ -383,9 +441,11 @@ class TextExtension(EffectExtension): def process_chardata(self, text): """Replaceable chardata method for processing the text""" - return ''.join(map(self.map_char, 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.") + raise NotImplementedError( + "Please provide a process_chardata or map_char static method." + ) diff --git a/inkex/inkscape_env.py b/inkex/inkscape_env.py index e563feca..7dcafd58 100644 --- a/inkex/inkscape_env.py +++ b/inkex/inkscape_env.py @@ -26,25 +26,28 @@ Always import *before* anything else. import os import sys + def get_bin(fname): """Get a virtualenv binary for execution, returns full filename""" for path in sys.path: - for script in [fname, os.path.join('bin', fname)]: + for script in [fname, os.path.join("bin", fname)]: result = os.path.abspath(os.path.join(path, script)) if os.path.isfile(result): return result return None + def activate_virtualenv(): - """ + """ The python that inkscape uses and the python installed into the virtualenv are different pythons with different libs. To give access to dependencies that are installed within the virtualenv, we activate the available venv. """ - activate_this = get_bin('activate_this.py') + activate_this = get_bin("activate_this.py") if activate_this and os.path.isfile(activate_this): - with open(activate_this, 'r') as fhl: + with open(activate_this, "r") as fhl: exec(fhl.read(), dict(__file__=activate_this)) return + activate_virtualenv() diff --git a/inkex/inx.py b/inkex/inx.py index d9c8cb19..99d86381 100644 --- a/inkex/inx.py +++ b/inkex/inx.py @@ -29,33 +29,40 @@ from .base import InkscapeExtension from .utils import Boolean NSS = { - 'inx': 'http://www.inkscape.org/namespace/inkscape/extension', - 'inkscape': 'http://www.inkscape.org/namespaces/inkscape', + "inx": "http://www.inkscape.org/namespace/inkscape/extension", + "inkscape": "http://www.inkscape.org/namespaces/inkscape", } SSN = dict([(b, a) for (a, b) in NSS.items()]) + class InxLookup(etree.CustomElementClassLookup): """Custom inx xml file lookup""" - def lookup(self, node_type, document, namespace, name): # pylint: disable=unused-argument - if name == 'param': + + def lookup( + self, node_type, document, namespace, name + ): # pylint: disable=unused-argument + if name == "param": return ParamElement return InxElement + INX_PARSER = etree.XMLParser() INX_PARSER.set_element_class_lookup(InxLookup()) + class InxFile: """Open an INX file and provide useful functions""" - name = property(lambda self: self.xml._text('name')) - ident = property(lambda self: self.xml._text('id')) - slug = property(lambda self: self.ident.split('.')[-1].title().replace('_', '')) - kind = property(lambda self: self.metadata['type']) + + name = property(lambda self: self.xml._text("name")) + ident = property(lambda self: self.xml._text("id")) + slug = property(lambda self: self.ident.split(".")[-1].title().replace("_", "")) + kind = property(lambda self: self.metadata["type"]) warnings = property(lambda self: sorted(list(set(self.xml.warnings)))) def __init__(self, filename): - if isinstance(filename, str) and '<' in filename: - filename = filename.encode('utf8') - if isinstance(filename, bytes) and b'<' in filename: + if isinstance(filename, str) and "<" in filename: + filename = filename.encode("utf8") + if isinstance(filename, bytes) and b"<" in filename: self.filename = None self.doc = etree.ElementTree(etree.fromstring(filename, parser=INX_PARSER)) else: @@ -70,84 +77,91 @@ class InxFile: @property def script(self): """Returns information about the called script""" - command = self.xml.find_one('script/command') + command = self.xml.find_one("script/command") if command is None: return {} return { - 'interpreter': command.get('interpreter', None), - 'location': command.get('location', None), - 'script': command.text, + "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) + script = self.script.get("script", None) if script is not None: - name = script[:-3].replace('/', '.') + 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): + 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.xml.find_one('effect') - output = self.xml.find_one('output') - inputs = self.xml.find_one('input') + effect = self.xml.find_one("effect") + output = self.xml.find_one("output") + inputs = self.xml.find_one("input") data = {} if effect is not None: - template = self.xml.find_one('inkscape:templateinfo') + template = self.xml.find_one("inkscape:templateinfo") if template is not None: - data['type'] = 'template' - data['desc'] = self.xml._text('templateinfo/shortdesc', nss='inkscape') - data['author'] = self.xml._text('templateinfo/author', nss='inkscape') + data["type"] = "template" + data["desc"] = self.xml._text("templateinfo/shortdesc", nss="inkscape") + data["author"] = self.xml._text("templateinfo/author", nss="inkscape") else: - data['type'] = 'effect' - data['preview'] = Boolean(effect.get('needs-live-preview', 'true')) - data['objects'] = effect._text('object-type', 'all') + data["type"] = "effect" + data["preview"] = Boolean(effect.get("needs-live-preview", "true")) + data["objects"] = effect._text("object-type", "all") elif inputs is not None: - data['type'] = 'input' - data['extension'] = inputs._text('extension') - data['mimetype'] = inputs._text('mimetype') - data['tooltip'] = inputs._text('filetypetooltip') - data['name'] = inputs._text('filetypename') + data["type"] = "input" + data["extension"] = inputs._text("extension") + data["mimetype"] = inputs._text("mimetype") + data["tooltip"] = inputs._text("filetypetooltip") + data["name"] = inputs._text("filetypename") elif output is not None: - data['type'] = 'output' - data['dataloss'] = Boolean(output._text('dataloss', 'false')) - data['extension'] = output._text('extension') - data['mimetype'] = output._text('mimetype') - data['tooltip'] = output._text('filetypetooltip') - data['name'] = output._text('filetypename') + data["type"] = "output" + data["dataloss"] = Boolean(output._text("dataloss", "false")) + data["extension"] = output._text("extension") + data["mimetype"] = output._text("mimetype") + data["tooltip"] = output._text("filetypetooltip") + data["name"] = output._text("filetypename") return data @property def menu(self): """Return the menu this effect ends up in""" + def _recurse_menu(parent): - for child in parent.xpath('submenu'): - yield child.get('name') + for child in parent.xpath("submenu"): + yield child.get("name") for subchild in _recurse_menu(child): yield subchild - break # Not more than one menu chain? - menu = self.xml.find_one('effect/effects-menu') + break # Not more than one menu chain? + + menu = self.xml.find_one("effect/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.xml.xpath('//param')) + return list(self.xml.xpath("//param")) + class InxElement(etree.ElementBase): def set_warning(self, msg): root = self.get_root() - if hasattr(root, 'warnings'): + if hasattr(root, "warnings"): root.warnings.append(msg) def get_root(self): @@ -158,20 +172,22 @@ class InxElement(etree.ElementBase): def get_default_prefix(self): tag = self.get_root().tag - if '}' in tag: - (url, tag) = tag[1:].split('}', 1) - return SSN.get(url, 'inx') + if "}" in tag: + (url, tag) = tag[1:].split("}", 1) + return SSN.get(url, "inx") self.set_warning("No inx xml prefix.") - return None # no default prefix + return None # no default prefix def apply_nss(self, xpath, nss=None): """Add prefixes to any xpath string""" if nss is None: nss = self.get_default_prefix() + def _process(seg): - if ':' in seg or not seg or not nss: + if ":" in seg or not seg or not nss: return seg return f"{nss}:{seg}" + return "/".join([_process(seg) for seg in xpath.split("/")]) def xpath(self, xpath, nss=None): @@ -186,29 +202,29 @@ class InxElement(etree.ElementBase): def _text(self, name, default=None, nss=None): """Get text content agnostically""" - for pref in ('', '_'): + for pref in ("", "_"): elem = self.find_one(pref + name, nss=nss) if elem is not None and elem.text: - if pref == '_': + if pref == "_": self.set_warning(f"Use of old translation scheme: <_{name}...>") return elem.text return default + class ParamElement(InxElement): """ A param in an inx file. """ - name = property(lambda self: self.get('name')) - param_type = property(lambda self: self.get('type', 'string')) + + 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('page')] - return [option.get('value') - for option in self.xpath('option')] + if self.param_type == "notebook": + return [option.get("name") for option in self.xpath("page")] + return [option.get("value") for option in self.xpath("option")] def __repr__(self): return f"" diff --git a/inkex/localization.py b/inkex/localization.py index 7695b694..3a9a3db2 100644 --- a/inkex/localization.py +++ b/inkex/localization.py @@ -26,11 +26,12 @@ 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') +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') +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""" @@ -50,17 +51,17 @@ def localize(domain=GETTEXT_DOMAIN, localedir=GETTEXT_DIRECTORY): 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' + 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/inkex/paths.py b/inkex/paths.py index 05d6f0f2..54a4167d 100644 --- a/inkex/paths.py +++ b/inkex/paths.py @@ -28,30 +28,53 @@ from math import atan2, cos, pi, sin, sqrt, acos, tan from .transforms import Transform, BoundingBox, Vector2d from .utils import classproperty, strargs -from typing import overload, Any, Type, Dict, Optional, Union, Tuple, List, Iterator, Generator # pylint: disable=unused-import +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") + +Pathlike = TypeVar("Pathlike", bound="PathCommand") +AbsolutePathlike = TypeVar("AbsolutePathlike", bound="AbsolutePathCommand") # All the names that get added to the inkex API itself. __all__ = ( - 'Path', 'CubicSuperPath', + "Path", + "CubicSuperPath", # Path commands: - 'Line', 'line', - 'Move', 'move', - 'ZoneClose', 'zoneClose', - 'Horz', 'horz', - 'Vert', 'vert', - 'Curve', 'curve', - 'Smooth', 'smooth', - 'Quadratic', 'quadratic', - 'TepidQuadratic', 'tepidQuadratic', - 'Arc', 'arc', + "Line", + "line", + "Move", + "move", + "ZoneClose", + "zoneClose", + "Horz", + "horz", + "Vert", + "vert", + "Curve", + "curve", + "Smooth", + "smooth", + "Quadratic", + "quadratic", + "TepidQuadratic", + "tepidQuadratic", + "Arc", + "arc", # errors - 'InvalidPath' + "InvalidPath", ) -LEX_REX = re.compile(r'([MLHVCSQTAZmlhvcsqtaz])([^MLHVCSQTAZmlhvcsqtaz]*)') +LEX_REX = re.compile(r"([MLHVCSQTAZmlhvcsqtaz])([^MLHVCSQTAZmlhvcsqtaz]*)") NONE = lambda obj: obj is not None @@ -105,7 +128,7 @@ class PathCommand: # 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]] + _letter_to_class = {} # type: Dict[str, Type[Any]] @staticmethod def letter_to_class(letter): @@ -172,7 +195,7 @@ class PathCommand: def to_curves(self, prev, prev_prev=Vector2d()): # type: (Vector2d, Vector2d) -> List[Curve] - """Convert command to list of :py:class:`Curve` commands """ + """Convert command to list of :py:class:`Curve` commands""" return [self.to_curve(prev, prev_prev)] def to_line(self, prev): @@ -206,7 +229,9 @@ class RelativePathCommand(PathCommand): 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) + 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 @@ -234,17 +259,23 @@ class AbsolutePathCommand(PathCommand): def is_absolute(self): return True - def to_absolute(self, previous): # type: (AbsolutePathlike, Vector2d) -> AbsolutePathlike + def to_absolute( + self, previous + ): # type: (AbsolutePathlike, Vector2d) -> AbsolutePathlike return self.__class__(*self.args) - def transform(self, transform): # type: (AbsolutePathlike, Transform) -> AbsolutePathlike + 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 + def rotate( + self, degrees, center + ): # type: (AbsolutePathlike, float, Vector2d) -> AbsolutePathlike """ Returns new transformed segment @@ -257,7 +288,9 @@ class AbsolutePathCommand(PathCommand): """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 + def scale( + self, factor + ): # type: (AbsolutePathlike, Union[float, Tuple[float,float]]) -> AbsolutePathlike """Returns new transformed segment :param factor: scale or (scale_x, scale_y) @@ -279,7 +312,9 @@ class Line(AbsolutePathCommand): 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)) + 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] @@ -304,6 +339,7 @@ class Line(AbsolutePathCommand): def reverse(self, first, prev): return Line(prev.x, prev.y) + class line(RelativePathCommand): # pylint: disable=invalid-name """Relative line segment""" @@ -323,6 +359,7 @@ class line(RelativePathCommand): # pylint: disable=invalid-name def reverse(self, first, prev): return line(-self.dx, -self.dy) + class Move(AbsolutePathCommand): """Move pen segment without a line""" @@ -363,6 +400,7 @@ class Move(AbsolutePathCommand): def reverse(self, first, prev): return Move(first.x, first.y) + class move(RelativePathCommand): # pylint: disable=invalid-name """Relative move segment""" @@ -383,8 +421,10 @@ class move(RelativePathCommand): # pylint: disable=invalid-name def reverse(self, first, prev): return move(first.x, first.y) + class ZoneClose(AbsolutePathCommand): """Close segment to finish a path""" + nargs = 0 next_command = Move @@ -418,6 +458,7 @@ class ZoneClose(AbsolutePathCommand): def reverse(self, first, prev): return Line(prev.x, prev.y) + class zoneClose(RelativePathCommand): # pylint: disable=invalid-name """Same as above (svg says no difference)""" @@ -434,13 +475,15 @@ class zoneClose(RelativePathCommand): # pylint: disable=invalid-name def reverse(self, first, prev): return line(prev.x - first.x, prev.y - first.y) + class Horz(AbsolutePathCommand): """Horizontal Line segment""" + nargs = 1 @property def args(self): - return self.x, + return (self.x,) def __init__(self, x): self.x = x @@ -481,6 +524,7 @@ class Horz(AbsolutePathCommand): def reverse(self, first, prev): return Horz(prev.x) + class horz(RelativePathCommand): # pylint: disable=invalid-name """Relative horz line segment""" @@ -488,7 +532,7 @@ class horz(RelativePathCommand): # pylint: disable=invalid-name @property def args(self): - return self.dx, + return (self.dx,) def __init__(self, dx): self.dx = dx @@ -515,7 +559,7 @@ class Vert(AbsolutePathCommand): @property def args(self): - return self.y, + return (self.y,) def __init__(self, y): self.y = y @@ -547,13 +591,16 @@ class Vert(AbsolutePathCommand): """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 + 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 reverse(self, first, prev): return Vert(prev.y) + class vert(RelativePathCommand): # pylint: disable=invalid-name """Relative vertical line segment""" @@ -561,7 +608,7 @@ class vert(RelativePathCommand): # pylint: disable=invalid-name @property def args(self): - return self.dy, + return (self.dy,) def __init__(self, dy): self.dy = dy @@ -573,15 +620,17 @@ class vert(RelativePathCommand): # pylint: disable=invalid-name # type: (Vector2d, Vector2d) -> Line return self.to_line(prev) - def to_line(self, prev): # type: (Vector2d) -> Line + def to_line(self, prev): # type: (Vector2d) -> Line """Return this path command as a line instead""" return Line(prev.x, prev.y + self.dy) def reverse(self, first, prev): return vert(-self.dy) + class Curve(AbsolutePathCommand): """Absolute Curved Line segment""" + nargs = 6 @property @@ -604,16 +653,10 @@ class Curve(AbsolutePathCommand): 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): + 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): + 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): @@ -631,15 +674,20 @@ class Curve(AbsolutePathCommand): 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 + 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 + def to_curve( + self, prev, prev_prev=Vector2d() + ): # type: (Vector2d, Optional[Vector2d]) -> Curve """No conversion needed, pass-through, returns self""" return Curve(*self.args) @@ -650,8 +698,10 @@ class Curve(AbsolutePathCommand): def reverse(self, first, prev): return Curve(self.x3, self.y3, self.x2, self.y2, prev.x, prev.y) + class curve(RelativePathCommand): # pylint: disable=invalid-name """Relative curved line segment""" + nargs = 6 @property @@ -670,20 +720,28 @@ class curve(RelativePathCommand): # pylint: disable=invalid-name 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 + self.dx2 + prev.x, + self.dy2 + prev.y, + self.dx3 + prev.x, + self.dy3 + prev.y, + self.dx4 + prev.x, + self.dy4 + prev.y, ) def reverse(self, first, prev): return curve( - -self.dx4 + self.dx3, -self.dy4 + self.dy3, - -self.dx4 + self.dx2, -self.dy4 + self.dy2, - -self.dx4, -self.dy4 + -self.dx4 + self.dx3, + -self.dy4 + self.dy3, + -self.dx4 + self.dx2, + -self.dy4 + self.dy2, + -self.dx4, + -self.dy4, ) + class Smooth(AbsolutePathCommand): """Absolute Smoothed Curved Line segment""" + nargs = 4 @property @@ -699,7 +757,9 @@ class Smooth(AbsolutePathCommand): 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) + 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] @@ -721,8 +781,7 @@ class Smooth(AbsolutePathCommand): 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 + self.x3 - prev.x, self.y3 - prev.y, self.x4 - prev.x, self.y4 - prev.y ) def transform(self, transform): @@ -749,6 +808,7 @@ class Smooth(AbsolutePathCommand): class smooth(RelativePathCommand): # pylint: disable=invalid-name """Relative smoothed curved line segment""" + nargs = 4 @property @@ -764,8 +824,7 @@ class smooth(RelativePathCommand): # pylint: disable=invalid-name 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 + self.dx3 + prev.x, self.dy3 + prev.y, self.dx4 + prev.x, self.dy4 + prev.y ) def to_non_shorthand(self, prev, prev_control): @@ -775,8 +834,10 @@ class smooth(RelativePathCommand): # pylint: disable=invalid-name def reverse(self, first, prev): return smooth(-self.dx4 + self.dx3, -self.dy4 + self.dy3, -self.dx4, -self.dy4) + class Quadratic(AbsolutePathCommand): """Absolute Quadratic Curved Line segment""" + nargs = 4 @property @@ -797,14 +858,10 @@ class Quadratic(AbsolutePathCommand): 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): + 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): + 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): @@ -815,8 +872,7 @@ class Quadratic(AbsolutePathCommand): 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 + self.x2 - prev.x, self.y2 - prev.y, self.x3 - prev.x, self.y3 - prev.y ) def transform(self, transform): @@ -833,10 +889,10 @@ class Quadratic(AbsolutePathCommand): # 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 + x1 = 1.0 / 3 * prev.x + 2.0 / 3 * self.x2 + x2 = 2.0 / 3 * self.x2 + 1.0 / 3 * self.x3 + y1 = 1.0 / 3 * prev.y + 2.0 / 3 * self.y2 + y2 = 2.0 / 3 * self.y2 + 1.0 / 3 * self.y3 return Curve(x1, y1, x2, y2, self.x3, self.y3) def reverse(self, first, prev): @@ -845,6 +901,7 @@ class Quadratic(AbsolutePathCommand): class quadratic(RelativePathCommand): # pylint: disable=invalid-name """Relative quadratic line segment""" + nargs = 4 @property @@ -859,15 +916,16 @@ class quadratic(RelativePathCommand): # pylint: disable=invalid-name 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 + self.dx2 + prev.x, self.dy2 + prev.y, self.dx3 + prev.x, self.dy3 + prev.y ) def reverse(self, first, prev): return quadratic(-self.x3 + self.x2, -self.y3 + self.y2, -self.x3, -self.y3) + class TepidQuadratic(AbsolutePathCommand): """Continued Quadratic Line segment""" + nargs = 2 @property @@ -879,7 +937,9 @@ class TepidQuadratic(AbsolutePathCommand): 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) + 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] @@ -899,9 +959,7 @@ class TepidQuadratic(AbsolutePathCommand): return self.to_quadratic(prev, prev_control) def to_relative(self, prev): # type: (Vector2d) -> tepidQuadratic - return tepidQuadratic( - self.x3 - prev.x, self.y3 - prev.y - ) + return tepidQuadratic(self.x3 - prev.x, self.y3 - prev.y) def transform(self, transform): # type: (Transform) -> TepidQuadratic @@ -930,6 +988,7 @@ class TepidQuadratic(AbsolutePathCommand): class tepidQuadratic(RelativePathCommand): # pylint: disable=invalid-name """Relative continued quadratic line segment""" + nargs = 2 @property @@ -942,24 +1001,32 @@ class tepidQuadratic(RelativePathCommand): # pylint: disable=invalid-name def to_absolute(self, prev): # type: (Vector2d) -> TepidQuadratic - return TepidQuadratic( - self.dx3 + prev.x, self.dy3 + prev.y - ) + return TepidQuadratic(self.dx3 + prev.x, self.dy3 + prev.y) def to_non_shorthand(self, prev, prev_control): # type: (Vector2d, Vector2d) -> AbsolutePathCommand return self.to_absolute(prev).to_non_shorthand(prev, prev_control) def reverse(self, first, prev): - return tepidQuadratic(-self.dx3, -self.dy3) + return tepidQuadratic(-self.dx3, -self.dy3) + 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 + 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 @@ -983,7 +1050,9 @@ class Arc(AbsolutePathCommand): 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) + 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:] @@ -999,7 +1068,7 @@ class Arc(AbsolutePathCommand): # | c d | detT = a * d - b * c - detT2 = detT ** 2 + detT2 = detT**2 rx = float(self.rx) ry = float(self.ry) @@ -1007,21 +1076,29 @@ class Arc(AbsolutePathCommand): 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 + 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 + 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) + delta = 0.5 * (-(DA**2) - 4 * B**2) / sqrt(l2) half = (A + D) / 2 @@ -1033,20 +1110,36 @@ class Arc(AbsolutePathCommand): if detT > 0: sweep = self.sweep else: - sweep = 0 if self.sweep>0 else 1 + 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) + 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) def reverse(self, first, prev): - return Arc(self.rx, self.ry, self.x_axis_rotation, self.large_arc, 1-self.sweep, prev.x, prev.y) + return Arc( + self.rx, + self.ry, + self.x_axis_rotation, + self.large_arc, + 1 - self.sweep, + prev.x, + prev.y, + ) class arc(RelativePathCommand): # pylint: disable=invalid-name @@ -1056,7 +1149,15 @@ class arc(RelativePathCommand): # pylint: disable=invalid-name @property def args(self): - return self.rx, self.ry, self.x_axis_rotation, self.large_arc, self.sweep, self.dx, self.dy + 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 @@ -1069,10 +1170,26 @@ class arc(RelativePathCommand): # pylint: disable=invalid-name 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) + return Arc( + self.rx, + self.ry, + self.x_axis_rotation, + self.large_arc, + self.sweep, + self.dx + x1, + self.dy + y1, + ) def reverse(self, first, prev): - return arc(self.rx, self.ry, self.x_axis_rotation, self.large_arc, 1-self.sweep, -self.dx, -self.dy) + return arc( + self.rx, + self.ry, + self.x_axis_rotation, + self.large_arc, + 1 - self.sweep, + -self.dx, + -self.dy, + ) PathCommand._letter_to_class = { @@ -1095,7 +1212,7 @@ PathCommand._letter_to_class = { "s": smooth, "z": zoneClose, "q": quadratic, - "t": tepidQuadratic + "t": tepidQuadratic, } @@ -1109,7 +1226,9 @@ class Path(list): 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): + 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 @@ -1141,7 +1260,9 @@ class Path(list): @property def control_points(self): - return self.command.control_points(self.first_point, self.previous_end_point, self.prev2_control_point) + return self.command.control_points( + self.first_point, self.previous_end_point, self.prev2_control_point + ) @property def end_point(self): @@ -1151,11 +1272,15 @@ class Path(list): return self.command.reverse(self.end_point, self.previous_end_point) def to_curve(self): - return self.command.to_curve(self.previous_end_point, self.prev2_control_point) + 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) - + return self.command.to_curves( + self.previous_end_point, self.prev2_control_point + ) + def to_absolute(self): return self.command.to_absolute(self.previous_end_point) @@ -1173,7 +1298,7 @@ class Path(list): elif isinstance(path_d, CubicSuperPath): path_d = path_d.to_path() - for item in (path_d or ()): + for item in path_d or (): if isinstance(item, PathCommand): self.append(item) elif isinstance(item, (list, tuple)) and len(item) == 2: @@ -1182,7 +1307,9 @@ class Path(list): else: self.append(Line(*item)) else: - raise TypeError(f"Bad path type: {type(path_d).__name__}({type(item).__name__}, ...): {item}") + raise TypeError( + f"Bad path type: {type(path_d).__name__}({type(item).__name__}, ...): {item}" + ) @classmethod def parse_string(cls, path_d): @@ -1192,7 +1319,7 @@ class Path(list): cmd = PathCommand.letter_to_class(cmd) i = 0 while i < len(args) or cmd.nargs == 0: - seg = cmd(*args[i:i + cmd.nargs]) + seg = cmd(*args[i : i + cmd.nargs]) i += cmd.nargs cmd = seg.next_command yield seg @@ -1208,10 +1335,14 @@ class Path(list): try: while True: proxy = next(iterator) - proxy.command.update_bounding_box(proxy.first_point, [ - proxy.prev2_control_point, - proxy.previous_end_point, - ], bbox) + proxy.command.update_bounding_box( + proxy.first_point, + [ + proxy.prev2_control_point, + proxy.previous_end_point, + ], + bbox, + ) except StopIteration: return bbox @@ -1240,7 +1371,9 @@ class Path(list): else: center = Vector2d() center = Vector2d(center) - return self.transform(Transform(rotate=(deg, center.x, center.y)), inplace=inplace) + return self.transform( + Transform(rotate=(deg, center.x, center.y)), inplace=inplace + ) @property def control_points(self): @@ -1286,7 +1419,11 @@ class Path(list): seg = seg.to_line(previous) if seg.is_relative: - new_seg = seg.to_absolute(previous).transform(transform).to_relative(previous_new) + new_seg = ( + seg.to_absolute(previous) + .transform(transform) + .to_relative(previous_new) + ) else: new_seg = seg.transform(transform) @@ -1310,21 +1447,20 @@ class Path(list): *_, first = self.end_points # Go through the path in reverse order - for index,command in reversed(list(enumerate(self.proxy_iterator()))): + for index, command in reversed(list(enumerate(self.proxy_iterator()))): if index == 0: - if command.letter == 'M': - result.insert(0,Move(first.x, first.y)) - elif command.letter == 'm': - result.insert(0,move(first.x, first.y)) + if command.letter == "M": + result.insert(0, Move(first.x, first.y)) + elif command.letter == "m": + result.insert(0, move(first.x, first.y)) else: result.append(command.reverse()) - if self[-1].letter.lower() == 'z': + if self[-1].letter.lower() == "z": result.append(self[-1]) return result - def close(self): """Attempt to close the last path segment""" if self and not isinstance(self[-1], (zoneClose, ZoneClose)): @@ -1345,8 +1481,19 @@ class Path(list): if i == 0: 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)): + 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) @@ -1376,7 +1523,9 @@ class Path(list): abspath.append(seg.to_absolute(previous)) else: if abspath and isinstance(abspath[-1], (Curve, Quadratic)): - prev_control = list(abspath[-1].control_points(None, None, None))[-2] + prev_control = list(abspath[-1].control_points(None, None, None))[ + -2 + ] else: prev_control = previous @@ -1448,7 +1597,6 @@ class CubicSuperPath(list): self._prev = Vector2d() self._prev_prev = Vector2d() - if isinstance(items, str): items = Path(items) @@ -1477,7 +1625,9 @@ class CubicSuperPath(list): 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][:]]) + 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) @@ -1490,12 +1640,16 @@ class CubicSuperPath(list): self._prev_prev.assign(x3, y3) return else: - is_quadratic = isinstance(item, (Quadratic, TepidQuadratic, quadratic, tepidQuadratic)) + 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] + 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): @@ -1559,7 +1713,9 @@ class CubicSuperPath(list): 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]): + if segment is subpath[-1] and Vector2d(segment[1]).is_close( + subpath[0][1] + ): yield ZoneClose() else: yield Line(*segment[1][:]) @@ -1574,14 +1730,18 @@ class CubicSuperPath(list): @staticmethod def is_on(a, b, c): """Checks if point a is on the line between points b and c""" - return (CubicSuperPath.collinear(a, b, c) - and (CubicSuperPath.within(a[0], b[0], c[0]) if a[0] != b[0] - else CubicSuperPath.within(a[1], b[1], c[1]))) + return CubicSuperPath.collinear(a, b, c) and ( + CubicSuperPath.within(a[0], b[0], c[0]) + if a[0] != b[0] + else CubicSuperPath.within(a[1], b[1], c[1]) + ) @staticmethod def collinear(a, b, c): """Checks if points a, b, c lie on the same line""" - return abs((b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1])) < 10e-8 + return ( + abs((b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1])) < 10e-8 + ) @staticmethod def within(b, a, c): @@ -1593,18 +1753,21 @@ class CubicSuperPath(list): """Check whether csp segment (two points) has retracted handles or the handles can be retracted without loss of information (i.e. both handles lie on the line)""" - retracted = Vector2d(previous[1]).is_close(previous[2]) and \ - Vector2d(segment[0]).is_close(segment[1]) + retracted = Vector2d(previous[1]).is_close(previous[2]) and Vector2d( + segment[0] + ).is_close(segment[1]) if retracted: return True # Can both handles be retracted without loss of information? - # Definitely the case if the handles lie on the same line as the two nodes and in the + # Definitely the case if the handles lie on the same line as the two nodes and in the # correct order # E.g. cspbezsplitatlength outputs non-retracted handles when splitting a straight line - return CubicSuperPath.is_on(segment[0], segment[1], previous[2]) \ - and CubicSuperPath.is_on(previous[2], previous[1], segment[0]) + return CubicSuperPath.is_on( + segment[0], segment[1], previous[2] + ) and CubicSuperPath.is_on(previous[2], previous[1], segment[0]) + def arc_to_path(point, params): """Approximates an arc with cubic bezier segments. @@ -1639,7 +1802,9 @@ def arc_to_path(point, params): # 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 + 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]] @@ -1660,12 +1825,15 @@ def arc_to_path(point, params): NbSectors = int(abs(start - end) * 2 / pi) + 1 dTeta = (end - start) / NbSectors - v = 4 * tan(dTeta / 4.) / 3. + v = 4 * tan(dTeta / 4.0) / 3.0 # 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)] + 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]) diff --git a/inkex/ports.py b/inkex/ports.py index f8e31091..edf76abf 100644 --- a/inkex/ports.py +++ b/inkex/ports.py @@ -31,6 +31,7 @@ try: except ImportError: serial = None + class Serial: """ Attempt to get access to the computer's serial port. @@ -41,10 +42,12 @@ class Serial: 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]' + self.test = port == "[test]" if self.test: - import pty # This does not work on windows + import pty # This does not work on windows + self.controller, self.peripheral = pty.openpty() port = os.ttyname(self.peripheral) @@ -57,34 +60,36 @@ class Serial: 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()) + 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' + 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") + 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 + 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) + sys.stderr.write(output.decode("utf8")) + # self.com.read(2) self.com.close() @staticmethod @@ -96,5 +101,5 @@ class Serial: @staticmethod def list_ports(): """Return a list of available serial ports""" - Serial.has_serial() # Cause DependencyError error + Serial.has_serial() # Cause DependencyError error return [hw.name for hw in list_ports.comports(True)] diff --git a/inkex/properties.py b/inkex/properties.py index 91748127..18eb8dca 100644 --- a/inkex/properties.py +++ b/inkex/properties.py @@ -29,18 +29,21 @@ from inkex.units import parse_unit from .colors import Color, ColorError -class BaseStyleValue(): - """A class encapsuling a single CSS declaration / presentation_attribute - """ + +class BaseStyleValue: + """A class encapsuling a single CSS declaration / presentation_attribute""" def __init__(self, declaration=None, attr_name=None, value=None, important=False): self.attr_name: str self.value: str self.important: bool - if (declaration is not None and ':' in declaration): - self.attr_name, self.value, self.important = BaseStyleValue.parse_declaration( - declaration) - elif (attr_name is not None): + if declaration is not None and ":" in declaration: + ( + self.attr_name, + self.value, + self.important, + ) = BaseStyleValue.parse_declaration(declaration) + elif attr_name is not None: self.attr_name = attr_name.strip().lower() if isinstance(value, str): self.value = value.strip() @@ -48,8 +51,7 @@ class BaseStyleValue(): # maybe its already parsed? then set it self.value = self.unparse_value(value) self.important = important - _ = self.parse_value() # check that we can parse this value - + _ = self.parse_value() # check that we can parse this value @classmethod def parse_declaration(cls, declaration: str) -> Tuple[str, str, bool]: @@ -65,18 +67,17 @@ class BaseStyleValue(): Returns: Tuple[str, str, bool]: a tuple with key, value and importance """ - if (declaration != "" and ':' in declaration): + if declaration != "" and ":" in declaration: declaration = declaration.replace(";", "") - (name, value) = declaration.split(':', 1) + (name, value) = declaration.split(":", 1) # check whether this is an important declaration important = False - if ("!important" in value): + if "!important" in value: value = value.replace("!important", "") important = True return (name.strip().lower(), value.strip(), important) raise ValueError("Invalid declaration") - def parse_value(self, element=None): """Get parsed property value with resolved urls, color, etc. @@ -94,8 +95,9 @@ class BaseStyleValue(): return None return self._parse_value(self.value, element) - - def _parse_value(self, value: str, element=None) -> object: # pylint: disable=unused-argument, no-self-use + def _parse_value( + self, value: str, element=None + ) -> object: # pylint: disable=unused-argument, no-self-use """internal parse method, to be overwritten by derived classes Args: @@ -108,7 +110,7 @@ class BaseStyleValue(): return value def unparse_value(self, value: object) -> str: - """"Unparses" an object, i.e. converts an object back to an attribute. + """ "Unparses" an object, i.e. converts an object back to an attribute. If the result is invalid, i.e. can't be parsed, an exception is raised. Args: @@ -118,10 +120,10 @@ class BaseStyleValue(): str: the attribute value """ result = self._unparse_value(value) - self._parse_value(result) # check if value can be parsed (value is valid) + self._parse_value(result) # check if value can be parsed (value is valid) return result - def _unparse_value(self, value: object) -> str: # pylint: disable=no-self-use + def _unparse_value(self, value: object) -> str: # pylint: disable=no-self-use return str(value) @property @@ -131,11 +133,21 @@ class BaseStyleValue(): Returns: str: the css declaration, such as "fill: #000 !important;" """ - return self.attr_name + ":" + self.value + (" !important" if self.important else "") + return ( + self.attr_name + + ":" + + self.value + + (" !important" if self.important else "") + ) @classmethod - def factory(cls, declaration: Optional[str] = None, attr_name: Optional[str] = None, \ - value: Optional[object] = None, important: Optional[bool] = False): + def factory( + cls, + declaration: Optional[str] = None, + attr_name: Optional[str] = None, + value: Optional[object] = None, + important: Optional[bool] = False, + ): """Create an attribute Args: @@ -151,15 +163,14 @@ class BaseStyleValue(): Returns: BaseStyleValue: the parsed style """ - if (declaration is not None and ':' in declaration): - attr_name, value, important = BaseStyleValue.parse_declaration( - declaration) - elif (attr_name is not None and value is not None): + if declaration is not None and ":" in declaration: + attr_name, value, important = BaseStyleValue.parse_declaration(declaration) + elif attr_name is not None and value is not None: attr_name = attr_name.strip().lower() if isinstance(value, str): value = value.strip() - if (attr_name in all_properties): + if attr_name in all_properties: valuetype = all_properties[attr_name][0] return valuetype(declaration, attr_name, value, important) return BaseStyleValue(declaration, attr_name, value, important) @@ -167,7 +178,7 @@ class BaseStyleValue(): def __eq__(self, other): if not (isinstance(other, BaseStyleValue)): return False - if (self.declaration != other.declaration): + if self.declaration != other.declaration: return False return True @@ -187,8 +198,9 @@ class BaseStyleValue(): BaseStyleValue: The parsed style """ try: - value = BaseStyleValue.factory(declaration=declaration, \ - attr_name=key, value=value) + value = BaseStyleValue.factory( + declaration=declaration, attr_name=key, value=value + ) key = value.attr_name # Try to parse the attribute _ = value.parse_value(element) @@ -202,11 +214,12 @@ class BaseStyleValue(): # The color parsing methods have their own error type pass + class AlphaValue(BaseStyleValue): """Stores an alpha value (such as opacity), which may be specified as as percentage or absolute value. - Reference: https://www.w3.org/TR/css-color/#typedef-alpha-value """ + Reference: https://www.w3.org/TR/css-color/#typedef-alpha-value""" def _parse_value(self, value: str, element=None): if value[-1] == "%": # percentage @@ -233,6 +246,7 @@ class ColorValue(BaseStyleValue): """Stores a color value Reference: https://drafts.csswg.org/css-color-3/#valuea-def-color""" + def _parse_value(self, value: str, element=None): if value == "currentColor": if element is not None: @@ -265,14 +279,13 @@ def match_url_and_return_element(string: str, svg): """ regex = re.compile(URLREGEX) match = regex.match(string) - if (match): + if match: url = match.group(1) paint_server = svg.getElementById(url) return paint_server raise ValueError("invalid URL format") - class URLNoneValue(BaseStyleValue): """Stores a marker, which is given as url. @@ -282,13 +295,14 @@ class URLNoneValue(BaseStyleValue): if value == "none": return None if value[0:4] == "url(": - if (element is not None and self.element_has_root(element)): + if element is not None and self.element_has_root(element): return match_url_and_return_element(value, element.root) return None raise ValueError("Invalid property value") def _unparse_value(self, value: object): - from inkex import BaseElement # pylint: disable=import-outside-toplevel + from inkex import BaseElement # pylint: disable=import-outside-toplevel + if isinstance(value, BaseElement): return f"url(#{value.get_id()})" return super()._unparse_value(value) @@ -296,9 +310,11 @@ class URLNoneValue(BaseStyleValue): @staticmethod def element_has_root(element) -> bool: "Checks if an element has a root, i.e. if element.root will fail" - from inkex import SvgDocumentElement # pylint: disable=import-outside-toplevel - return not (element.getparent() is None and \ - not isinstance(element, SvgDocumentElement)) + from inkex import SvgDocumentElement # pylint: disable=import-outside-toplevel + + return not ( + element.getparent() is None and not isinstance(element, SvgDocumentElement) + ) class PaintValue(ColorValue, URLNoneValue): @@ -320,7 +336,7 @@ class PaintValue(ColorValue, URLNoneValue): # a letter or a # regex = re.compile(URLREGEX + r"\s*([#\w].*?)?$") match = regex.match(value) - if (match): + if match: url = match.group(1) if element is not None and self.element_has_root(element): paint_server = element.root.getElementById(url) @@ -339,7 +355,6 @@ class PaintValue(ColorValue, URLNoneValue): return super()._unparse_value(value) - class EnumValue(BaseStyleValue): """Stores a value that can only have a finite set of options""" @@ -350,8 +365,10 @@ class EnumValue(BaseStyleValue): def _parse_value(self, value: str, element=None): if value in self.valueset: return value - raise ValueError(f"Value '{value}' is invalid for the property {self.attr_name}. " + - f"Allowed values are: {self.valueset + ['inherit']}") + raise ValueError( + f"Value '{value}' is invalid for the property {self.attr_name}. " + + f"Allowed values are: {self.valueset + ['inherit']}" + ) class ShorthandValue(BaseStyleValue, ABC): @@ -384,8 +401,9 @@ class ShorthandValue(BaseStyleValue, ABC): perform = importance else: # only apply if style overwrites previous with same importance - perform = current_keys.index( - curkey) < current_keys.index(self.attr_name) + perform = current_keys.index(curkey) < current_keys.index( + self.attr_name + ) if perform: style[curkey] = dct[curkey] @@ -404,19 +422,30 @@ class ShorthandValue(BaseStyleValue, ABC): class FontValue(ShorthandValue): """Logic for the shorthand font property""" + def get_shorthand_changes(self): - keys = ["font-style", "font-variant", "font-weight", - "font-stretch", "font-size", "line-height", "font-family"] - options = {key: all_properties[key][4] for key in keys if isinstance( - all_properties[key][4], list)} + keys = [ + "font-style", + "font-variant", + "font-weight", + "font-stretch", + "font-size", + "line-height", + "font-family", + ] + options = { + key: all_properties[key][4] + for key in keys + if isinstance(all_properties[key][4], list) + } result = {key: all_properties[key][1] for key in keys} tokens = [i for i in self.value.split(" ") if i != ""] - if (len(tokens) == 0): + if len(tokens) == 0: return {} # shorthand not set, nothing to do - while not(len(tokens) == 0): + while not (len(tokens) == 0): cur = tokens[0] if cur in options["font-style"]: result["font-style"] = cur @@ -427,7 +456,7 @@ class FontValue(ShorthandValue): elif cur in options["font-stretch"]: result["font-stretch"] = cur else: - if ("/" in cur): + if "/" in cur: result["font-size"], result["line-height"] = cur.split("/") else: result["font-size"] = cur @@ -436,52 +465,57 @@ class FontValue(ShorthandValue): tokens = tokens[1:] # remove first element return result + class MarkerShorthandValue(ShorthandValue, URLNoneValue): - """ Logic for the marker shorthand property""" + """Logic for the marker shorthand property""" + def get_shorthand_changes(self): if self.value == "": - return {} # shorthand not set, nothing to do + return {} # shorthand not set, nothing to do return {k: self.value for k in ["marker-start", "marker-end", "marker-mid"]} + def _parse_value(self, value: str, element=None): # Make sure the parsing routine doesn't choke on an empty shorthand if value == "": return "" return super()._parse_value(value, element) + class FontSizeValue(BaseStyleValue): - """ Logic for the font-size property""" + """Logic for the font-size property""" + def _parse_value(self, value: str, element=None): if element is None: - return value #no additional logic in this case + return value # no additional logic in this case try: return element.to_dimensionless(value) - except ValueError: #unable to parse font size, e.g. font-size:normal + except ValueError: # unable to parse font size, e.g. font-size:normal return element.to_dimensionless("12pt") + class StrokeDasharrayValue(BaseStyleValue): def _parse_value(self, value: str, element=None): if element is None: return value - dashes = re.findall(r'[^,\s]+', value) + dashes = re.findall(r"[^,\s]+", value) if len(dashes) == 0: - return None # no dasharray applied + return None # no dasharray applied if not any([parse_unit(i) is None for i in dashes]): dashes = [element.to_dimensionless(i) for i in dashes] else: return None if any(i < 0 for i in dashes): - return None # one negative value makes the dasharray invalid + return None # one negative value makes the dasharray invalid if len(dashes) % 2 == 1: dashes = 2 * dashes return dashes + def _unparse_value(self, value: object) -> str: if value == None: return "none" if isinstance(value, list): return " ".join(map(str, value)) - return str(value) - - + return str(value) # keys: attributes, right side: @@ -497,24 +531,101 @@ class StrokeDasharrayValue(BaseStyleValue): # pylint: disable=line-too-long -all_properties: Dict[str, Tuple[Type[BaseStyleValue], str, bool, bool, Union[List[str], None]]] = { - "alignment-baseline": (EnumValue, "baseline", True, False, ["baseline", "text-bottom", "alphabetic", "ideographic", "middle", "central", "mathematical", "text-top"]), +all_properties: Dict[ + str, Tuple[Type[BaseStyleValue], str, bool, bool, Union[List[str], None]] +] = { + "alignment-baseline": ( + EnumValue, + "baseline", + True, + False, + [ + "baseline", + "text-bottom", + "alphabetic", + "ideographic", + "middle", + "central", + "mathematical", + "text-top", + ], + ), "baseline-shift": (BaseStyleValue, "0", True, False, None), "clip": (BaseStyleValue, "auto", True, False, None), "clip-path": (URLNoneValue, "none", True, False, None), "clip-rule": (EnumValue, "nonzero", True, True, ["nonzero", "evenodd"]), # only used for currentColor, which is not yet implemented "color": (PaintValue, "black", True, True, None), - "color-interpolation": (EnumValue, "sRGB", True, True, ["sRGB", "auto", "linearRGB"]), - "color-interpolation-filters": (EnumValue, "linearRGB", True, True, ["auto", "sRGB", "linearRGB"]), - "color-rendering": (EnumValue, "auto", True, True, ["auto", "optimizeSpeed", "optimizeQuality"]), + "color-interpolation": ( + EnumValue, + "sRGB", + True, + True, + ["sRGB", "auto", "linearRGB"], + ), + "color-interpolation-filters": ( + EnumValue, + "linearRGB", + True, + True, + ["auto", "sRGB", "linearRGB"], + ), + "color-rendering": ( + EnumValue, + "auto", + True, + True, + ["auto", "optimizeSpeed", "optimizeQuality"], + ), "cursor": (BaseStyleValue, "auto", True, True, None), "direction": (EnumValue, "ltr", True, True, ["ltr", "rtl"]), - "display": (EnumValue, "inline", True, False, ["inline", "block", "list-item", "inline-block", "table", "inline-table", "table-row-group", - "table-header-group", "table-footer-group", "table-row", "table-column-group", "table-column", - "table-cell", "table-caption", "none"]), # every value except none is rendered normally - "dominant-baseline": (EnumValue, "auto", True, True, ["auto", "text-bottom", "alphabetic", "ideographic", "middle", "central", "mathematical", "hanging", "text-top"]), - "fill": (PaintValue, "black", True, True, None), # the normal fill, not the one + "display": ( + EnumValue, + "inline", + True, + False, + [ + "inline", + "block", + "list-item", + "inline-block", + "table", + "inline-table", + "table-row-group", + "table-header-group", + "table-footer-group", + "table-row", + "table-column-group", + "table-column", + "table-cell", + "table-caption", + "none", + ], + ), # every value except none is rendered normally + "dominant-baseline": ( + EnumValue, + "auto", + True, + True, + [ + "auto", + "text-bottom", + "alphabetic", + "ideographic", + "middle", + "central", + "mathematical", + "hanging", + "text-top", + ], + ), + "fill": ( + PaintValue, + "black", + True, + True, + None, + ), # the normal fill, not the one "fill-opacity": (AlphaValue, "1", True, True, None), "fill-rule": (EnumValue, "nonzero", True, True, ["nonzero", "evenodd"]), "filter": (BaseStyleValue, "none", True, False, None), @@ -524,56 +635,162 @@ all_properties: Dict[str, Tuple[Type[BaseStyleValue], str, bool, bool, Union[Lis "font-family": (BaseStyleValue, "sans-serif", True, True, None), "font-size": (FontSizeValue, "medium", True, True, None), "font-size-adjust": (BaseStyleValue, "none", True, True, None), - "font-stretch": (EnumValue, "normal", True, True, ["normal", "ultra-condensed", "extra-condensed", "condensed", - "semi-condensed", "semi-expanded", "expanded", "extra-expanded", - "ultra-expanded"]), + "font-stretch": ( + EnumValue, + "normal", + True, + True, + [ + "normal", + "ultra-condensed", + "extra-condensed", + "condensed", + "semi-condensed", + "semi-expanded", + "expanded", + "extra-expanded", + "ultra-expanded", + ], + ), "font-style": (EnumValue, "normal", True, True, ["normal", "italic", "oblique"]), # a lot more values and subproperties in SVG2 / CSS-Fonts3 "font-variant": (EnumValue, "normal", True, True, ["normal", "small-caps"]), - "font-weight": (EnumValue, "normal", True, True, ["normal", "bold"] + \ - [str(i) for i in range(100, 901, 100)]), + "font-weight": ( + EnumValue, + "normal", + True, + True, + ["normal", "bold"] + [str(i) for i in range(100, 901, 100)], + ), "glyph-orientation-horizontal": (BaseStyleValue, "0deg", True, True, None), "glyph-orientation-vertical": (BaseStyleValue, "auto", True, True, None), - "image-rendering": (EnumValue, "auto", True, True, ["auto", "optimizeQuality", "optimizeSpeed"]), + "image-rendering": ( + EnumValue, + "auto", + True, + True, + ["auto", "optimizeQuality", "optimizeSpeed"], + ), "letter-spacing": (BaseStyleValue, "normal", True, True, None), "lighting-color": (ColorValue, "normal", True, False, None), "line-height": (BaseStyleValue, "normal", False, True, None), - "marker" : (MarkerShorthandValue, "", True, True, None), + "marker": (MarkerShorthandValue, "", True, True, None), "marker-end": (URLNoneValue, "none", True, True, None), "marker-mid": (URLNoneValue, "none", True, True, None), "marker-start": (URLNoneValue, "none", True, True, None), # is a shorthand for a lot of mask-related properties which Inkscape doesn't support "mask": (URLNoneValue, "none", True, False, None), "opacity": (AlphaValue, "1", True, False, None), - "overflow": (EnumValue, "visible", True, False, ["visible", "hidden", "scroll", "auto"]), + "overflow": ( + EnumValue, + "visible", + True, + False, + ["visible", "hidden", "scroll", "auto"], + ), "paint-order": (BaseStyleValue, "normal", True, False, None), - "pointer-events": (EnumValue, "visiblePainted", True, True, ["bounding-box", "visiblePainted", - "visibleFill", "visibleStroke", "visible", "painted", "fill", "stroke", "all", "none"]), - "shape-rendering": (EnumValue, "visiblePainted", True, True, ["auto", "optimizeSpeed", "crispEdges", "geometricPrecision"]), + "pointer-events": ( + EnumValue, + "visiblePainted", + True, + True, + [ + "bounding-box", + "visiblePainted", + "visibleFill", + "visibleStroke", + "visible", + "painted", + "fill", + "stroke", + "all", + "none", + ], + ), + "shape-rendering": ( + EnumValue, + "visiblePainted", + True, + True, + ["auto", "optimizeSpeed", "crispEdges", "geometricPrecision"], + ), "stop-color": (ColorValue, "black", True, False, None), "stop-opacity": (AlphaValue, "1", True, False, None), "stroke": (PaintValue, "none", True, True, None), "stroke-dasharray": (StrokeDasharrayValue, "none", True, True, None), "stroke-dashoffset": (BaseStyleValue, "0", True, True, None), "stroke-linecap": (EnumValue, "butt", True, True, ["butt", "round", "square"]), - "stroke-linejoin": (EnumValue, "miter", True, True, ["miter", "miter-clip", "round", "bevel", "arcs"]), + "stroke-linejoin": ( + EnumValue, + "miter", + True, + True, + ["miter", "miter-clip", "round", "bevel", "arcs"], + ), "stroke-miterlimit": (BaseStyleValue, "4", True, True, None), "stroke-opacity": (AlphaValue, "1", True, True, None), "stroke-width": (BaseStyleValue, "1", True, True, None), - "text-align": (BaseStyleValue, "start", True, True, None), # only HTML property, but used by some unit tests + "text-align": ( + BaseStyleValue, + "start", + True, + True, + None, + ), # only HTML property, but used by some unit tests "text-anchor": (EnumValue, "start", True, True, ["start", "middle", "end"]), # shorthand for text-decoration-line, *-style, *-color "text-decoration": (BaseStyleValue, "none", True, True, None), "text-overflow": (EnumValue, "clip", True, False, ["clip", "ellipsis"]), - "text-rendering": (EnumValue, "auto", True, True, ["auto", "optimizeSpeed", "optimizeLegibility", "geometricPrecision"]), - "unicode-bidi": (EnumValue, "normal", True, False, ["normal", "embed", "isolate", "bidi-override", "isolate-override", "plaintext"]), + "text-rendering": ( + EnumValue, + "auto", + True, + True, + ["auto", "optimizeSpeed", "optimizeLegibility", "geometricPrecision"], + ), + "unicode-bidi": ( + EnumValue, + "normal", + True, + False, + [ + "normal", + "embed", + "isolate", + "bidi-override", + "isolate-override", + "plaintext", + ], + ), "vector-effect": (BaseStyleValue, "none", True, False, None), "vertical-align": (BaseStyleValue, "baseline", False, False, None), "visibility": (EnumValue, "visible", True, True, ["visible", "hidden", "collapse"]), - "white-space": (EnumValue, "normal", True, True, ["normal", "pre", "nowrap", "pre-wrap", "break-spaces", "pre-line"]), + "white-space": ( + EnumValue, + "normal", + True, + True, + ["normal", "pre", "nowrap", "pre-wrap", "break-spaces", "pre-line"], + ), "word-spacing": (BaseStyleValue, "normal", True, True, None), # including obsolete SVG 1.1 values - "writing-mode": (EnumValue, "horizontal-tb", True, True, ["horizontal-tb", "vertical-rl", "vertical-lr", "lr", "lr-tb", "rl", "rl-tb", "tb", "tb-rl"]), - "-inkscape-font-specification": (BaseStyleValue, "sans-serif", False, True, None) + "writing-mode": ( + EnumValue, + "horizontal-tb", + True, + True, + [ + "horizontal-tb", + "vertical-rl", + "vertical-lr", + "lr", + "lr-tb", + "rl", + "rl-tb", + "tb", + "tb-rl", + ], + ), + "-inkscape-font-specification": (BaseStyleValue, "sans-serif", False, True, None), } # pylint: enable=line-too-long diff --git a/inkex/styles.py b/inkex/styles.py index fc16f9d4..1e287689 100644 --- a/inkex/styles.py +++ b/inkex/styles.py @@ -38,6 +38,7 @@ if TYPE_CHECKING: 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): @@ -78,18 +79,22 @@ class Classes(list): class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): """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') - associated_props = {"fill" : "fill-opacity", "stroke" : "stroke-opacity", - "stop-color" : "stop-opacity"} + + color_props = ("stroke", "fill", "stop-color", "flood-color", "lighting-color") + opacity_props = ("stroke-opacity", "fill-opacity", "opacity", "stop-opacity") + unit_props = "stroke-width" + associated_props = { + "fill": "fill-opacity", + "stroke": "stroke-opacity", + "stop-color": "stop-opacity", + } def __init__(self, style=None, callback=None, element=None, **kw): self.element = element # 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()] + style = style or [(k.replace("_", "-"), v) for k, v in kw.items()] if isinstance(style, str): style = self._parse_str(style) # Order raw dictionaries so tests can be made reliable @@ -113,10 +118,11 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): Yields: BaseStyleValue: the parsed attribute """ - for declaration in style.split(';'): + for declaration in style.split(";"): if ":" in declaration: - result = BaseStyleValue.factory_errorhandled(element, \ - declaration=declaration.strip()) + result = BaseStyleValue.factory_errorhandled( + element, declaration=declaration.strip() + ) if result is not None: yield result @@ -169,7 +175,7 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): if not isinstance(other, Style): other = Style(other) # only update - if (isinstance(other, Style)): + if isinstance(other, Style): for key in other.keys(): if not (self.get_importance(key) and not other.get_importance(key)): self[key] = other.get_store(key) @@ -225,8 +231,10 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): # check if the set attribute is valid _ = value.parse_value(self.element) elif key != value.attr_name: - raise ValueError("""You're trying to save a value into a style attribute, - but the provided key is different from the attribute name given in the value""") + raise ValueError( + """You're trying to save a value into a style attribute, + but the provided key is different from the attribute name given in the value""" + ) super().__setitem__(key, value) if self.callback is not None: self.callback(self) @@ -258,12 +266,16 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): if isinstance(value, ShorthandValue): copy = self.copy() copy.apply_shorthands() - if (key in copy): + if key in copy: return copy.get_store(key).parse_value(element or self.element) # style is not set, return the default value if key in all_properties: - defvalue = BaseStyleValue.factory(attr_name=key, value=all_properties[key][1]) - return defvalue.parse_value() # default values are independent of the element + defvalue = BaseStyleValue.factory( + attr_name=key, value=all_properties[key][1] + ) + return ( + defvalue.parse_value() + ) # default values are independent of the element raise KeyError("Unknown attribute") def __eq__(self, other): @@ -272,7 +284,7 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): selfkeys = self.keys() otherkeys = other.keys() if not [i for i, j in zip(sorted(selfkeys), sorted(otherkeys)) if i == j]: - #list of keys is not equal + # list of keys is not equal return False for arg in set(self) | set(other): if self.get_store(arg) != other.get_store(arg): @@ -285,28 +297,28 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): def get_importance(self, key, default=False): """Returns whether the declaration with key is marked as !important""" - if (key in self): + if key in self: return super().__getitem__(key).important return default def set_importance(self, key, importance): """Sets the !important state of a declaration with key key""" - if (key in self): + if key in self: super().__getitem__(key).important = importance else: raise KeyError() if self.callback is not None: self.callback(self) - def get_color(self, name='fill'): + 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)) + color = Color(self.get(name, "none")) + return color.to_rgba(self.get(name + "-opacity", 1.0)) - def set_color(self, color, name='fill'): + 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' and name in Style.associated_props: + if color.space == "rgba" and name in Style.associated_props: self[Style.associated_props[name]] = color.alpha self[name] = color.to_rgb() else: @@ -323,6 +335,7 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): """Interpolate all properties.""" from .tween import StyleInterpolator from inkex.elements import PathElement + if self.element is None: self.element = PathElement(style=str(self)) if other.element is None: @@ -349,7 +362,7 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): # see https://www.w3.org/TR/SVG/styling.html#PresentationAttributes styles.append([element.presentation_style(), (0, 0, 0)]) - # would be (1, 0, 0, 0), but then we'd have to extend every entry + # would be (1, 0, 0, 0), but then we'd have to extend every entry styles.append([element.style, (float("inf"), 0, 0)]) # sort styles by specificity (ascending, so when overwriting it's correct) @@ -380,11 +393,14 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): parent = element.getparent() # import this here, otherwise it will cause circular import problems - from .elements._base import BaseElement # pylint: disable=import-outside-toplevel + from .elements._base import ( + BaseElement, + ) # pylint: disable=import-outside-toplevel + if parent is not None and isinstance(parent, BaseElement): cascaded = Style.add_inherited(cascaded, parent.specified_style()) cascaded.element = element - return cascaded # doesn't have a parent + return cascaded # doesn't have a parent class StyleSheets(list): @@ -395,6 +411,7 @@ class StyleSheets(list): 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().__init__() self.svg = svg @@ -423,44 +440,50 @@ class StyleSheets(list): for style in sheet.lookup_specificity(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().__init__() self.callback = None # Remove comments - content = self.comment_strip.sub('', (content or '')) + content = self.comment_strip.sub("", (content or "")) # Parse rules - for block in content.split('}'): + 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' + return "\n" + "\n".join([str(style) for style in self]) + "\n" - def _callback(self, style=None): # pylint: disable=unused-argument + 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)) + 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) - if rules.strip().startswith("@"): # ignore @font-face and @import + if "{" not in other: + return # Warning? + rules, style = other.strip("}").split("{", 1) + if rules.strip().startswith("@"): # ignore @font-face and @import return - other = ConditionalStyle(rules=rules, style=style.strip(), callback=self._callback) + other = ConditionalStyle( + rules=rules, style=style.strip(), callback=self._callback + ) super().append(other) self._callback() @@ -468,7 +491,7 @@ class StyleSheet(list): """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: + if elem.get("id", None) == element_id: yield style def lookup_specificity(self, element_id, svg): @@ -486,7 +509,7 @@ class StyleSheet(list): for style in self: for rule, spec in zip(style.to_xpaths(), style.get_specificities()): for elem in svg.xpath(rule): - if elem.get('id', None) == element_id: + if elem.get("id", None) == element_id: yield (style, spec) @@ -496,9 +519,10 @@ class ConditionalStyle(Style): conditional rules which places this style in a stylesheet rather than being an attribute style. """ - def __init__(self, rules='*', style=None, callback=None, **kwargs): + + def __init__(self, rules="*", style=None, callback=None, **kwargs): super().__init__(style=style, callback=callback, **kwargs) - self.rules = [ConditionalRule(rule) for rule in rules.split(',')] + self.rules = [ConditionalRule(rule) for rule in rules.split(",")] def __str__(self): """Return this style as a css entry with class""" @@ -513,10 +537,12 @@ class ConditionalStyle(Style): # 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(self.to_xpaths()) + return "|".join(self.to_xpaths()) + def to_xpaths(self): """Gets a list of xpaths for all rules of this ConditionalStyle""" return [rule.to_xpath() for rule in self.rules] + def get_specificities(self): """gets an iterator of the specificity of all rules in this ConditionalStyle""" for rule in self.rules: @@ -525,9 +551,10 @@ class ConditionalStyle(Style): class ConditionalRule: """A single css rule""" + step_to_xpath = [ # namespace addition - (re.compile(r'(::|\/)([a-z]+)(?=\W)(?!-)'), r"\1svg:\2"), + (re.compile(r"(::|\/)([a-z]+)(?=\W)(?!-)"), r"\1svg:\2"), ] def __init__(self, rule): @@ -545,6 +572,7 @@ class ConditionalRule: for matcher, replacer in self.step_to_xpath: ret = matcher.sub(replacer, ret) return ret.strip() + def get_specificity(self): """gets the css specificity of this selector""" return self.selector.specificity() diff --git a/inkex/tester/__init__.py b/inkex/tester/__init__.py index bb3d8af9..975dc6b2 100644 --- a/inkex/tester/__init__.py +++ b/inkex/tester/__init__.py @@ -98,16 +98,17 @@ from ..utils import to_bytes from .xmldiff import xmldiff from .mock import MockCommandMixin, Capture -if False: # pylint: disable=using-constant-test +if False: # pylint: disable=using-constant-test from typing import Type, List from .filters import Compare COMPARE_DELETE, COMPARE_CHECK, COMPARE_WRITE, COMPARE_OVERWRITE = range(4) + 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 + def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called raise NotImplementedError(self.__doc__) def run(self, args=None, output=None): @@ -119,7 +120,8 @@ 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_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 @@ -133,11 +135,11 @@ class TestCase(MockCommandMixin, BaseCase): self._temp_dir = None self._effect = None - def setUp(self): # pylint: disable=invalid-name + def setUp(self): # pylint: disable=invalid-name """Make sure every test is seeded the same way""" self._effect = None super().setUp() - random.seed(0x35f) + random.seed(0x35F) def tearDown(self): super().tearDown() @@ -162,18 +164,20 @@ class TestCase(MockCommandMixin, BaseCase): @classmethod def datadir(cls): """Get the data directory (can be over-ridden if needed)""" - return os.path.join(cls._testdir(), 'data') + 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-') + 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'): + 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) @@ -195,9 +199,11 @@ class TestCase(MockCommandMixin, BaseCase): @property def empty_svg(self): """Returns a common minimal svg file""" - return self.data_file('svg', 'default-inkscape-SVG.svg') + return self.data_file("svg", "default-inkscape-SVG.svg") - def assertAlmostTuple(self, found, expected, precision=8, msg=""): # pylint: disable=invalid-name + def assertAlmostTuple( + self, found, expected, precision=8, msg="" + ): # pylint: disable=invalid-name """ Floating point results may vary with computer architecture; use assertAlmostEqual to allow a tolerance in the result. @@ -213,32 +219,40 @@ class TestCase(MockCommandMixin, BaseCase): 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 + filename should point to a starting svg document, default is empty_svg """ data_file = self.data_file(*filename) if filename else self.empty_svg - os.environ['DOCUMENT_PATH'] = data_file - args = [data_file] + list(kwargs.pop('args', [])) - args += ['--{}={}'.format(*kw) for kw in kwargs.items()] + os.environ["DOCUMENT_PATH"] = data_file + args = [data_file] + list(kwargs.pop("args", [])) + args += ["--{}={}".format(*kw) for kw in kwargs.items()] - effect = kwargs.pop('effect', self.effect_class)() + effect = kwargs.pop("effect", self.effect_class)() # Output is redirected to this string io buffer if self.stderr_output: - with Capture('stderr') as stderr: + 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: + 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") + 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()) + 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!") @@ -258,8 +272,9 @@ class TestCase(MockCommandMixin, BaseCase): """Assert that two transform expressions evaluate to the same transformation matrix. """ - self.assertAlmostTuple(tuple(Transform(lhs).to_hexad()), - tuple(Transform(rhs).to_hexad()), places) + self.assertAlmostTuple( + tuple(Transform(lhs).to_hexad()), tuple(Transform(rhs).to_hexad()), places + ) @property def effect(self): @@ -268,41 +283,46 @@ class TestCase(MockCommandMixin, BaseCase): self._effect = self.effect_class() return self._effect + class InkscapeExtensionTestMixin: """Automatically setup self.effect for each test and test with an empty svg""" - def setUp(self): # pylint: disable=invalid-name + + 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') + 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: """ Add comparison tests to any existing test suite. """ + # This input svg file sent to the extension (if any) - compare_file: Union[List[str], Tuple[str], str] = 'svg/shapes.svg' + compare_file: Union[List[str], Tuple[str], str] = "svg/shapes.svg" # The ways in which the output is filtered for comparision (see filters.py) - compare_filters = [] # type: List[Compare] + 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'), + ("--id=p1", "--id=r3"), ] - compare_file_extension = 'svg' + compare_file_extension = "svg" + @property def _compare_file_extension(self): """The default extension to use when outputting check files in COMPARE_CHECK mode.""" if self.stderr_output: - return 'txt' + return "txt" return self.compare_file_extension def test_all_comparisons(self): @@ -312,8 +332,7 @@ class ComparisonMixin: else: for compare_file in self.compare_file: self._test_comparisons( - compare_file, - addout=os.path.basename(compare_file) + compare_file, addout=os.path.basename(compare_file) ) def _test_comparisons(self, compare_file, addout=None): @@ -324,7 +343,9 @@ class ComparisonMixin: args, ) - def assertCompare(self, infile, cmpfile, args, outfile=None): #pylint: disable=invalid-name + def assertCompare( + self, infile, cmpfile, args, outfile=None + ): # pylint: disable=invalid-name """ Compare the output of a previous run against this one. @@ -336,7 +357,7 @@ class ComparisonMixin: dumps it's output to this filename instead. """ - compare_mode = int(os.environ.get('EXPORT_COMPARE', COMPARE_DELETE)) + compare_mode = int(os.environ.get("EXPORT_COMPARE", COMPARE_DELETE)) effect = self.assertEffect(infile, args=args) @@ -345,36 +366,40 @@ class ComparisonMixin: if not os.path.isfile(cmpfile) and compare_mode == COMPARE_DELETE: raise IOError( - f"Comparison file {cmpfile} not found, set EXPORT_COMPARE=1 to create it.") + f"Comparison file {cmpfile} not found, set EXPORT_COMPARE=1 to create it." + ) if outfile: if not os.path.isabs(outfile): outfile = os.path.join(self.tempdir, outfile) - self.assertTrue(os.path.isfile(outfile), "No output file created! {}".format(outfile)) - with open(outfile, 'rb') as fhl: + self.assertTrue( + os.path.isfile(outfile), "No output file created! {}".format(outfile) + ) + with open(outfile, "rb") as fhl: data_a = fhl.read() else: data_a = effect.test_output.getvalue() write_output = None if compare_mode == COMPARE_CHECK: - _file = cmpfile[:-4] if cmpfile.endswith('.out') else cmpfile + _file = cmpfile[:-4] if cmpfile.endswith(".out") else cmpfile write_output = f"{_file}.{self._compare_file_extension}" - elif (compare_mode == COMPARE_WRITE and not os.path.isfile(cmpfile))\ - or compare_mode == COMPARE_OVERWRITE: + elif ( + compare_mode == COMPARE_WRITE and not os.path.isfile(cmpfile) + ) or compare_mode == COMPARE_OVERWRITE: write_output = cmpfile try: if write_output and not os.path.isfile(cmpfile): raise AssertionError(f"Check the output: {write_output}") - with open(cmpfile, 'rb') as fhl: + with open(cmpfile, "rb") as fhl: data_b = self._apply_compare_filters(fhl.read(), False) self._base_compare(data_a, data_b, compare_mode) except AssertionError: if write_output: if isinstance(data_a, str): - data_a = data_a.encode('utf-8') - with open(write_output, 'wb') as fhl: + data_a = data_a.encode("utf-8") + with open(write_output, "wb") as fhl: fhl.write(self._apply_compare_filters(data_a, True)) print(f"Written output: {write_output}") # This only reruns if the original test failed. @@ -390,18 +415,23 @@ class ComparisonMixin: def _base_compare(self, data_a, data_b, compare_mode): data_a = self._apply_compare_filters(data_a) - if isinstance(data_a, bytes) and isinstance(data_b, bytes) \ - and data_a.startswith(b'<') and data_b.startswith(b'<'): + 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 compare_mode == COMPARE_DELETE: - print('The XML is different, you can save the output using the EXPORT_COMPARE'\ - ' envionment variable. Set it to 1 to save a file you can check, set it to'\ - ' 3 to overwrite this comparison, setting the new data as the correct one.\n' + print( + "The XML is different, you can save the output using the EXPORT_COMPARE" + " envionment variable. Set it to 1 to save a file you can check, set it to" + " 3 to overwrite this comparison, setting the new data as the correct one.\n" ) diff = f"SVG Differences\n\n" - if os.environ.get('XML_DIFF', False): - diff = '<- ' + diff_xml + if os.environ.get("XML_DIFF", False): + diff = "<- " + diff_xml else: for x, (value_a, value_b) in enumerate(delta): try: @@ -428,13 +458,17 @@ class ComparisonMixin: """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) + 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", f"{self.effect_name}{opstr}.out", check_exists=False) + opstr = hashlib.md5(opstr.encode("latin1")).hexdigest() + opstr = "__" + opstr + return self.data_file( + "refs", f"{self.effect_name}{opstr}.out", check_exists=False + ) diff --git a/inkex/tester/decorators.py b/inkex/tester/decorators.py index 92d6b4d1..f27e6381 100644 --- a/inkex/tester/decorators.py +++ b/inkex/tester/decorators.py @@ -4,5 +4,6 @@ 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") +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/inkex/tester/filters.py b/inkex/tester/filters.py index 1680350e..1b96f4fc 100644 --- a/inkex/tester/filters.py +++ b/inkex/tester/filters.py @@ -33,11 +33,13 @@ filters that are being used. import re from ..utils import to_bytes + class Compare: """ Comparison base class, this acts as a passthrough unless the filter staticmethod is overwritten. """ + def __init__(self, **options): self.options = options @@ -49,6 +51,7 @@ class Compare: """Replace this filter method with your own filtering""" return contents + class CompareNumericFuzzy(Compare): """ Turn all numbers into shorter standard formats @@ -57,78 +60,102 @@ class CompareNumericFuzzy(Compare): 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+(e[+-]\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) + func = lambda m: b"%.3f" % (float(m.group(0))) + contents = re.sub(rb"\d+\.\d+(e[+-]\d+)?", func, contents) + contents = re.sub(rb"(\d\.\d+?)0+\b", rb"\1", contents) + contents = re.sub(rb"(\d)\.0+(?=\D|\b)", rb"\1", contents) return contents + class CompareWithoutIds(Compare): """Remove all ids from the svg""" + @staticmethod def filter(contents): - return re.sub(br' id="([^"]*)"', b'', contents) + return re.sub(rb' 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) + new = re.sub(rb"\s*([LZMHVCSQTAatqscvhmzl])\s*", rb" \1 ", match.group(1)) + return b' d="' + new.replace(b",", b" ") + b'"' + + return re.sub(rb' 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';'))) + sty = b";".join(sorted(match.group(1).split(b";"))) return b'style="%s"' % (sty,) - return re.sub(br'style="([^"]*)"', func, contents) + + return re.sub(rb'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)))) + path = b"X".join(sorted(re.split(rb"[A-Z]", match.group(1)))) return b'd="%s"' % (path,) - return re.sub(br'\bd="([^"]*)"', func, contents) + + return re.sub(rb'\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))) + return b"\n".join(sorted(re.split(rb">\s*<", contents))) + class CompareReplacement(Compare): """Replace pieces to make output more comparable""" + def __init__(self, *replacements): self.deltas = replacements super().__init__() @@ -139,6 +166,7 @@ class CompareReplacement(Compare): contents = contents.replace(to_bytes(_from), to_bytes(_to)) return contents + class WindowsTextCompat(CompareReplacement): def __init__(self): - super().__init__(('\r\n', '\n')) \ No newline at end of file + super().__init__(("\r\n", "\n")) diff --git a/inkex/tester/inx.py b/inkex/tester/inx.py index c6dde5df..22882477 100644 --- a/inkex/tester/inx.py +++ b/inkex/tester/inx.py @@ -7,15 +7,16 @@ 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') +INTERNAL_ARGS = ("help", "output", "id", "selected-nodes") ARG_TYPES = { - 'Boolean': 'bool', - 'Color': 'color', - 'str': 'string', - 'int': 'int', - 'float': 'float', + "Boolean": "bool", + "Color": "color", + "str": "string", + "int": "int", + "float": "float", } + class InxMixin(object): """Tools for Testing INX files, use as a mixin class: @@ -23,93 +24,105 @@ class InxMixin(object): def test_inx_file(self): self.assertInxIsGood("some_inx_file.inx") """ - def assertInxIsGood(self, inx_file): # pylint: disable=invalid-name + + 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': + 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)) + 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, + cls.__name__, + inx.slug, "Name of extension class {}.{} is different from ident {}".format( - cls.__module__, cls.__name__, inx.slug)) + cls.__module__, cls.__name__, inx.slug + ), + ) self.assertParams(inx, cls) - def assertParams(self, inx, cls): # pylint: disable=invalid-name + 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)) + 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']: + 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)) - inxdefault = params[param]['default'] - argsdefault = args[param]['default'] + params[param]["type"], + args[param]["type"], + "Type is not the same for {}:param:{}".format(inx.filename, param), + ) + inxdefault = params[param]["default"] + argsdefault = args[param]["default"] if inxdefault and argsdefault: # for booleans, the inx is lowercase and the param is uppercase - if params[param]['type'] == "bool": + if params[param]["type"] == "bool": argsdefault = str(argsdefault).lower() - elif (params[param]['type'] not in ["string", None, "color"] - or args[param]['type'] in ['int', 'float']): + elif params[param]["type"] not in ["string", None, "color"] or args[ + param + ]["type"] in ["int", "float"]: # try to parse the inx value to compare numbers to numbers inxdefault = float(inxdefault) - if args[param]['type'] == "color" or callable(args[param]['default']): + if args[param]["type"] == "color" or callable(args[param]["default"]): # skip color, method types continue - self.assertEqual(argsdefault, - inxdefault, - "Default value is not the same for {}:param:{}" - .format(inx.filename, param)) - - - + self.assertEqual( + argsdefault, + inxdefault, + "Default value 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 ( + 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: + 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, + "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'): + if param.param_type in ("optiongroup", "notebook"): options = param.options return { - 'type': None, - 'choices': options, - 'default': options and options[0] or None, + "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' + if param.param_type in ("path",): + param_type = "string" return { - 'type': param_type, - 'default': param.text, - 'choices': None, + "type": param_type, + "default": param.text, + "choices": None, } diff --git a/inkex/tester/mock.py b/inkex/tester/mock.py index 1655fad4..f7ce0612 100644 --- a/inkex/tester/mock.py +++ b/inkex/tester/mock.py @@ -38,14 +38,16 @@ 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 +if False: # pylint: disable=using-constant-test + from typing import List, Tuple, Callable, Any # pylint: disable=unused-import + +FIXED_BOUNDARY = "--CALLDATA--//--CALLDATA--" -FIXED_BOUNDARY = '--CALLDATA--//--CALLDATA--' class Capture: """Capture stdout or stderr. Used as `with Capture('stdout') as stream:`""" - def __init__(self, io_name='stdout', swap=True): + + def __init__(self, io_name="stdout", swap=True): self.io_name = io_name self.original = getattr(sys, io_name) self.stream = io.StringIO() @@ -62,8 +64,10 @@ class Capture: self.original.write(self.stream.getvalue()) setattr(sys, self.io_name, self.original) + class ManualVerbosity: """Change the verbosity of the test suite manually""" + result = property(lambda self: self.test._current_result) def __init__(self, test, okay=True, dots=False): @@ -71,7 +75,9 @@ class ManualVerbosity: self.okay = okay self.dots = dots - def flip(self, exc_type=None, exc_val=None, exc_tb=None): # pylint: disable=unused-argument + 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 @@ -92,21 +98,26 @@ class MockMixin: 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 + 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 + + 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 + + def _value_function(*args, **kw): # pylint: disable=unused-argument return new + setattr(owner, name, _value_function) else: setattr(owner, name, new) @@ -114,16 +125,18 @@ class MockMixin: # length 4, this stops remocking and reunmocking from taking place. return (owner, name, old, False) - def setUp(self): # pylint: disable=invalid-name + def setUp(self): # pylint: disable=invalid-name """For each mock instruction, set it up and store the return""" super().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!") + 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 + def tearDown(self): # pylint: disable=invalid-name """For each returned stored, tear it down and restore mock instruction""" super().tearDown() try: @@ -140,50 +153,56 @@ class MockMixin: 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'), + (inkex.command, "_call", "mock_call"), + (tempfile, "mkdtemp", "record_tempdir"), ] - recorded_tempdirs = [] # type:List[str] + recorded_tempdirs = [] # type:List[str] - def setUp(self): # pylint: disable=invalid-name + def setUp(self): # pylint: disable=invalid-name super().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') + + 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) + 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""" + def replace(indata, replaced, replacement): if isinstance(indata, str): indata = indata.replace(replaced, replacement) else: indata = [i.replace(replaced, replacement) for i in indata] return indata + try: for fdir in self.recorded_tempdirs: - data = replace(data, fdir, '.') - files = replace(files, fdir, '.') + data = replace(data, fdir, ".") + files = replace(files, fdir, ".") for fname in files: data = replace(data, fname, os.path.basename(fname)) except (UnicodeDecodeError, TypeError): @@ -197,7 +216,7 @@ class MockCommandMixin(MockMixin): if not os.path.isdir(fdir): continue for fname in os.listdir(fdir): - if fname in ('.', '..'): + if fname in (".", ".."): continue path = os.path.join(fdir, fname) # We store the modified time so if a program modifies @@ -209,8 +228,9 @@ class MockCommandMixin(MockMixin): 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 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 @@ -218,12 +238,12 @@ class MockCommandMixin(MockMixin): hash to find a command result. """ # Remove stdin first because it needs to NOT be in the Arguments list. - stdin = kwargs.pop('stdin', None) + stdin = kwargs.pop("stdin", None) args = list(args) # We use email msg = MIMEMultipart(boundary=FIXED_BOUNDARY) - msg['Program'] = self.get_program_name(program) + 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) @@ -231,56 +251,61 @@ class MockCommandMixin(MockMixin): arglst = inkex.command.to_args_sorted(program, *args, **kwargs)[1:] arglst = self.clean_paths(arglst, inputs + outputs) - argstr = ' '.join(arglst) - msg['Arguments'] = argstr.strip() + argstr = " ".join(arglst) + msg["Arguments"] = argstr.strip() if stdin is not None: # The stdin is counted as the msg body - cleanin = self.clean_paths(stdin, inputs + outputs)\ - .replace("\r\n", "\n").replace(".\\", "./") - msg.attach(MIMEText(cleanin, 'plain', 'utf-8')) + cleanin = ( + self.clean_paths(stdin, inputs + outputs) + .replace("\r\n", "\n") + .replace(".\\", "./") + ) + msg.attach(MIMEText(cleanin, "plain", "utf-8")) keystr = msg.as_string() # On Windows, output is separated by CRLF - keystr = keystr.replace('\r\n', '\n') + keystr = keystr.replace("\r\n", "\n") # There is a difference between python2 and python3 output - keystr = keystr.replace('\n\n', '\n') - keystr = keystr.replace('\n ', ' ') - if 'verb' in keystr: + 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('; ', ';') + keystr = keystr.replace("; ", ";") # Generate a unique key for this call based on _all_ it's inputs - key = hashlib.md5(keystr.encode('utf-8')).hexdigest() + 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 + kwargs["stdin"] = stdin before = self.get_all_tempfiles() - stdout = self.old_call('_call')(program, *args, **kwargs) + 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] + 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 + 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') + 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(f"Problem loading call: {program}/{key} use the environment variable "\ - "NO_MOCK_COMMANDS=1 to call out to the external program and generate "\ - f"the mock call file for call {program} {argstr}.") + self.save_key(program, key, keystr, "bad-key") + raise IOError( + f"Problem loading call: {program}/{key} use the environment variable " + "NO_MOCK_COMMANDS=1 to call out to the external program and generate " + f"the mock call file for call {program} {argstr}." + ) def add_call_files(self, msg, args, kwargs): """ @@ -308,10 +333,10 @@ class MockCommandMixin(MockMixin): # or are existing files on the disk. files = [[], []] for value in values: - if os.path.isfile(value): # Input file + 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 + elif os.path.isdir(os.path.dirname(value)): # Output file files[1].append(value) return files @@ -319,20 +344,20 @@ class MockCommandMixin(MockMixin): """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'), []) + if filename.endswith(".svg"): + value = self.clean_paths(fhl.read().decode("utf8"), []) else: value = fhl.read() try: value = value.decode() - except UnicodeDecodeError: # do not attempt to process binary files further + except UnicodeDecodeError: # do not attempt to process binary files further pass if isinstance(value, str): - value = value.replace('\r\n', '\n').replace(".\\", "./") + value = value.replace("\r\n", "\n").replace(".\\", "./") part = MIMEApplication(value, Name=fname) # After the file is closed - part['Content-Disposition'] = 'attachment' - part['Filename'] = fname + part["Content-Disposition"] = "attachment" + part["Filename"] = fname msg.attach(part) def get_call_filename(self, program, key, create=False): @@ -340,7 +365,7 @@ class MockCommandMixin(MockMixin): Get the filename for the call testing information. """ path = self.get_call_path(program, create=create) - fname = os.path.join(path, key + '.msg') + fname = os.path.join(path, key + ".msg") if not create and not os.path.isfile(fname): raise IOError(f"Attempted to find call test data {key}") return fname @@ -348,7 +373,7 @@ class MockCommandMixin(MockMixin): def get_program_name(self, program): """Takes a program and returns a program name""" if program == inkex.command.INKSCAPE_EXECUTABLE_NAME: - return 'inkscape' + return "inkscape" return program def get_call_path(self, program, create=True): @@ -358,9 +383,11 @@ class MockCommandMixin(MockMixin): if create: os.makedirs(command_dir) else: - raise IOError("A test is attempting to use an external program in a test:"\ - f" {program}; but there is not a command data directory which should"\ - f" contain the results of the command here: {command_dir}") + raise IOError( + "A test is attempting to use an external program in a test:" + f" {program}; but there is not a command data directory which should" + f" contain the results of the command here: {command_dir}" + ) return command_dir def load_call(self, program, key, files): @@ -368,16 +395,16 @@ class MockCommandMixin(MockMixin): 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')) + 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'] + 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: + with open(out_file, "wb") as fhl: fhl.write(part.get_payload(decode=True)) part = None if part is not None: @@ -386,14 +413,16 @@ class MockCommandMixin(MockMixin): # 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: + 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 + 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 @@ -401,24 +430,24 @@ class MockCommandMixin(MockMixin): """ 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')) + 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)) + # 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)) + 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')) + 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'): + 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')) + 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/inkex/tester/svg.py b/inkex/tester/svg.py index bb01e45a..324f29bb 100644 --- a/inkex/tester/svg.py +++ b/inkex/tester/svg.py @@ -24,15 +24,20 @@ from lxml import etree from inkex import SVG_PARSER -def svg(svg_attrs=''): + +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. + svg_attrs: A string containing attributes to add to the + root element of a minimal SVG document. """ - return etree.fromstring(str.encode( - '' - f''), parser=SVG_PARSER) + return etree.fromstring( + str.encode( + '' + f"" + ), + parser=SVG_PARSER, + ) def svg_unit_scaled(width_unit): @@ -42,8 +47,9 @@ def svg_unit_scaled(width_unit): """ return svg(f'width="1{width_unit}" viewBox="0 0 1 1"') + def svg_file(filename): """Parse an svg file and return it's document root""" - with open(filename, 'r') as fhl: + with open(filename, "r") as fhl: doc = etree.parse(fhl, parser=SVG_PARSER) return doc.getroot() diff --git a/inkex/tester/word.py b/inkex/tester/word.py index cab7d04f..bf395d7c 100644 --- a/inkex/tester/word.py +++ b/inkex/tester/word.py @@ -9,6 +9,7 @@ Generate words for testing. import string import random + def word_generator(text_length): """ Generate a word of text_length size @@ -16,13 +17,16 @@ def word_generator(text_length): word = "" for _ in range(0, text_length): - word += random.choice(string.ascii_lowercase + \ - string.ascii_uppercase + \ - string.digits + \ - string.punctuation) + 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 = "" diff --git a/inkex/tester/xmldiff.py b/inkex/tester/xmldiff.py index 27d67266..09684ab0 100644 --- a/inkex/tester/xmldiff.py +++ b/inkex/tester/xmldiff.py @@ -11,6 +11,7 @@ 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 @@ -18,12 +19,14 @@ def text_compare(test1, test2): """ if not test1 and not test2: return True - if test1 == '*' or test2 == '*': + if test1 == "*" or test2 == "*": return True - return (test1 or '').strip() == (test2 or '').strip() + 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: @@ -34,13 +37,16 @@ class DeltaLogger(list): def append_attr(self, attr, value_a, value_b): """Record an attribute difference""" + def _prep(val): if val: - if attr == 'd': + if attr == "d": from inkex.paths import Path + return [attr] + Path(val).to_arrays() return (attr, val) return val + # Only append a difference if the preprocessed values are different. # This solves the issue that -0 != 0 in path data. pa = _prep(value_a) @@ -55,6 +61,7 @@ class DeltaLogger(list): 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): @@ -62,20 +69,23 @@ class DeltaLogger(list): return "No differences detected" return f"{len(self)} xml differences" + def to_xml(data): """Convert string or bytes to xml parsed root node""" if isinstance(data, str): - data = data.encode('utf8') + 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 + return xml.tostring(xml1).decode("utf-8"), delta + def _xmldiff(xml1, xml2, delta): if xml1.tag != xml2.tag: @@ -106,9 +116,9 @@ def _xmldiff(xml1, xml2, delta): children_b += [None] * (len(children_a) - len(children_b)) for child_a, child_b in zip(children_a, children_b): - if child_a is None: # child_b exists + if child_a is None: # child_b exists delta.append_tag(child_b.tag, None) - elif child_b is None: # child_a exists + elif child_b is None: # child_a exists delta.append_tag(None, child_a.tag) else: _xmldiff(child_a, child_b, delta) diff --git a/inkex/transforms.py b/inkex/transforms.py index 4b065bac..7487523b 100644 --- a/inkex/transforms.py +++ b/inkex/transforms.py @@ -32,32 +32,54 @@ from math import cos, radians, sin, sqrt, tan, fabs, atan2, hypot, pi, isfinite from .utils import strargs, KeyDict -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 +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 # All the names that get added to the inkex API itself. __all__ = ( - 'BoundingBox', - 'DirectedLineSegment', - 'ImmutableVector2d', - 'Transform', - 'Vector2d', + "BoundingBox", + "DirectedLineSegment", + "ImmutableVector2d", + "Transform", + "Vector2d", ) # 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'}) +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'] +CUSTOM_DIRECTION = {270: "tb", 90: "bt", 0: "lr", 360: "lr", 180: "rl"} +DIRECTION = ["tb", "bt", "lr", "rl", "ro", "ri"] class ImmutableVector2d: """Represents an immutable element of 2-dimensional Euclidean space""" + _x = 0.0 _y = 0.0 @@ -66,7 +88,7 @@ class ImmutableVector2d: @overload def __init__(self): - # type: () -> None + # type: () -> None pass @overload @@ -97,8 +119,8 @@ class 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(',')) + elif isinstance(point, str) and point.count(",") == 1: + x, y = map(float, point.split(",")) else: raise ValueError(f"Can't parse {repr(point)}") return x, y @@ -131,7 +153,7 @@ class ImmutableVector2d: # type: () -> Vector2d return Vector2d(self.x, self.y) - def __floordiv__(self, factor): + def __floordiv__(self, factor): # type: (float) -> Vector2d return Vector2d(self.x / float(factor), self.y / float(factor)) @@ -251,7 +273,7 @@ class Vector2d(ImmutableVector2d): return self def __isub__(self, other): - # type: (VectorLike) -> Vector2d + # type: (VectorLike) -> Vector2d other = Vector2d(other) self.x -= other.x self.y -= other.y @@ -296,7 +318,6 @@ class Vector2d(ImmutableVector2d): return self - class Transform: """A transformation object which will always reduce to a matrix and can then be used in combination with other transformations for reducing @@ -317,15 +338,16 @@ class Transform: 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 + 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): + 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)) @@ -337,11 +359,11 @@ class Transform: self.callback = callback def _set_matrix(self, matrix): - # type: (MatrixLike) -> None + # type: (MatrixLike) -> None """Parse a given string as an svg transformation instruction.""" if isinstance(matrix, str): for func, values in self.TRM.findall(matrix.strip()): - getattr(self, 'add_' + func.lower())(*strargs(values)) + getattr(self, "add_" + func.lower())(*strargs(values)) elif isinstance(matrix, Transform): self.matrix = matrix.matrix elif isinstance(matrix, (tuple, list)) and len(matrix) == 2: @@ -353,11 +375,17 @@ class Transform: row2 = cast("Tuple[float, float, float]", tuple(map(float, row2))) self.matrix = row1, row2 else: - raise ValueError(f"Matrix '{matrix}' is not a valid transformation matrix") + raise ValueError( + f"Matrix '{matrix}' is not a valid transformation matrix" + ) else: - raise ValueError(f"Matrix '{matrix}' is not a valid transformation matrix") + raise ValueError( + f"Matrix '{matrix}' is not a valid transformation matrix" + ) elif isinstance(matrix, (list, tuple)) and len(matrix) == 6: - tmatrix = cast("Union[List[float], Tuple[float,float,float,float,float,float]]", matrix) + 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 @@ -366,7 +394,6 @@ class Transform: else: raise ValueError(f"Matrix '{matrix}' is not a valid transformation matrix") - # These provide quick access to the svg matrix: # # [ a, c, e ] @@ -387,17 +414,17 @@ class Transform: @overload def add_matrix(self, a): - # type: (MatrixLike) -> Transform + # type: (MatrixLike) -> Transform pass @overload def add_matrix(self, a, b, c, d, e, f): - # type: (float, float, float, float, float, float) -> Transform + # type: (float, float, float, float, float, float) -> Transform pass @overload def add_matrix(self, a, b): - # type: (Tuple[float, float, float], Tuple[float, float, float]) -> Transform + # type: (Tuple[float, float, float], Tuple[float, float, float]) -> Transform pass def add_matrix(self, *args): @@ -413,7 +440,7 @@ class Transform: 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) + func = getattr(self, "add_" + key) if isinstance(value, tuple): func(*value) elif value is not None: @@ -498,27 +525,42 @@ class Transform: # 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 + 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) + 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 + 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") + raise ValueError( + "Rotation angle is undefined for non-uniformly scaled or skewed matrices" + ) return atan2(self.b, self.a) * 180 / pi def __str__(self): @@ -540,16 +582,19 @@ class Transform: """String representation of this object""" return "{}((({}), ({})))".format( type(self).__name__, - ', '.join(f"{var:.6g}" for var in self.matrix[0]), - ', '.join(f"{var:.6g}" for var in self.matrix[1])) + ", ".join(f"{var:.6g}" for var in self.matrix[0]), + ", ".join(f"{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())) + 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 @@ -560,13 +605,16 @@ class Transform: # 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)) + 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 __imatmul__(self, matrix): # type: (MatrixLike) -> Transform @@ -596,8 +644,10 @@ class Transform: if isinstance(point, str): raise ValueError(f"Will not transform string '{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) + 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 @@ -614,6 +664,7 @@ class Transform: # type: (Transform, float) -> Transform """Interpolate with another Transform.""" from .tween import TransformInterpolator + return TransformInterpolator(self, other).interpolate(fraction) @@ -642,17 +693,21 @@ class BoundingInterval: # pylint: disable=too-few-public-methods def __init__(self, x=None, y=None): if y is not None: - if isinstance(x, (int, float, Decimal)) and isinstance(y, (int, float, Decimal)): + if isinstance(x, (int, float, Decimal)) and isinstance( + y, (int, float, Decimal) + ): self.minimum = x self.maximum = y else: - raise ValueError(f"Not a number for scaling: {str((x, y))} ({type(x).__name__},{type(y).__name__})") + raise ValueError( + f"Not a number for scaling: {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') + self.minimum, self.maximum = float("+inf"), float("-inf") elif isinstance(value, BoundingInterval): self.minimum = value.minimum self.maximum = value.maximum @@ -661,11 +716,13 @@ class BoundingInterval: # pylint: disable=too-few-public-methods elif isinstance(value, (int, float, Decimal)): self.minimum = self.maximum = value else: - raise ValueError(f"Not a number for scaling: {str(value)} ({type(value).__name__})") + raise ValueError( + f"Not a number for scaling: {str(value)} ({type(value).__name__})" + ) def __bool__(self): # type: () -> bool - return (isfinite(self.minimum) and isfinite(self.maximum)) + return isfinite(self.minimum) and isfinite(self.maximum) __nonzero__ = __bool__ @@ -708,7 +765,7 @@ class BoundingInterval: # pylint: disable=too-few-public-methods 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') + self.minimum, self.maximum = float("+inf"), float("-inf") return self def __rand__(self, other): @@ -796,7 +853,9 @@ class BoundingBox: # pylint: disable=too-few-public-methods elif isinstance(x, BoundingBox): x, y = x.x, x.y else: - raise ValueError(f"Not a number for scaling: {str(x)} ({type(x).__name__})") + raise ValueError( + f"Not a number for scaling: {str(x)} ({type(x).__name__})" + ) self.x = BoundingInterval(x) self.y = BoundingInterval(y) @@ -902,14 +961,15 @@ class BoundingBox: # pylint: disable=too-few-public-methods # 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) + 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 + # 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/float (custom angle), tb/bt (top/bottom), lr/rl (left/right), ri/ro (radial) @@ -921,9 +981,11 @@ class BoundingBox: # pylint: disable=too-few-public-methods return hypot(x, y) * (cos(radians(-direction) - atan2(y, x))) direction = CUSTOM_DIRECTION[direction] - if direction in ('ro', 'ri'): + if direction in ("ro", "ri"): if selbox is None: - raise ValueError("Radial distance not available without selection bounding box") + 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)] @@ -965,7 +1027,7 @@ class DirectedLineSegment: if not args: # overload 0 start, end = Vector2d(), Vector2d() elif len(args) == 1: # overload 1 - other, = args + (other,) = args start, end = other.start, other.end elif len(args) == 2: # overload 2 start, end = args @@ -1039,7 +1101,7 @@ class DirectedLineSegment: return self.x0 + ratio * self.dx, self.y0 + ratio * self.dy def point_at_length(self, length): - # type: (float) -> Tuple[float, float] + # type: (float) -> Tuple[float, float] """Get the point as the length along the line""" return self.point_at_ratio(length / self.length) @@ -1076,10 +1138,12 @@ def cubic_extrema(py0, py1, py2, py3): 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 + 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 @@ -1102,9 +1166,11 @@ def quadratic_extrema(py0, py1, 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 + pyx = ( + py0 * (1 - point) * (1 - point) + + 2 * py1 * point * (1 - point) + + py2 * point * point + ) return min(cmin, pyx), max(cmax, pyx) return cmin, cmax diff --git a/inkex/turtle.py b/inkex/turtle.py index 4fda3978..fa12d6eb 100644 --- a/inkex/turtle.py +++ b/inkex/turtle.py @@ -25,6 +25,8 @@ from .paths import Line, Move, Path, PathCommand from .elements import PathElement, Group from .base import BaseElement from .styles import Style + + class pTurtle: """A Python path turtle""" @@ -37,12 +39,20 @@ class pTurtle: 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)) + 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)) + 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 @@ -69,7 +79,7 @@ class pTurtle: self.setpos(self.__home) def clean(self): - self.__path = '' + self.__path = "" def clear(self): self.clean() @@ -123,9 +133,11 @@ class pTurtle: pu = penup pd = pendown + class PathBuilder: """This helper class can be used to construct a path and insert it into a document.""" - def __init__(self, style : Style): + + def __init__(self, style: Style): """Initializes a PathDrawHelper object Args: @@ -133,6 +145,7 @@ class PathBuilder: """ self.current = Path() self.style = style + def add(self, command: Union[PathCommand, List[PathCommand]]): """Add a Path command to the Helper @@ -141,9 +154,11 @@ class PathBuilder: appended. """ self.current.append(command) + def terminate(self): - """Terminates current subpath. This method does nothing by default and is supposed to be + """Terminates current subpath. This method does nothing by default and is supposed to be overridden in subclasses.""" + def append_next(self, sibling_before: BaseElement): """Insert the resulting Path as :class:`inkex.elements._polygons.PathElement` into the document tree. @@ -155,7 +170,8 @@ class PathBuilder: pth.path = self.current pth.style = self.style sibling_before.addnext(pth) - def Move_to(self, x, y): # pylint: disable=invalid-name + + def Move_to(self, x, y): # pylint: disable=invalid-name """Shorthand to insert an absolute move command: `M x y`. Args: @@ -163,7 +179,8 @@ class PathBuilder: y (Float): y coordinate to move to """ self.add(Move(x, y)) - def Line_to(self, x, y): # pylint: disable=invalid-name + + def Line_to(self, x, y): # pylint: disable=invalid-name """Shorthand to insert an absolute lineto command: `L x y`. Args: @@ -172,11 +189,14 @@ class PathBuilder: """ self.add(Line(x, y)) + class PathGroupBuilder(PathBuilder): """This helper class can be used to construct a group of paths that all have the same style.""" + def __init__(self, style): super().__init__(style) self.result = Group() + def terminate(self): """Terminates the current Path, and appends it to the group if it is not empty.""" if len(self.current) > 1: @@ -185,10 +205,11 @@ class PathGroupBuilder(PathBuilder): pth.style = self.style self.result.append(pth) self.current = Path() + def append_next(self, sibling_before: BaseElement): """Insert the resulting Path as :class:`inkex.elements._groups.Group` into the document tree. Args: sibling_before (BaseElement): The element the resulting group will be appended after. """ - sibling_before.addnext(self.result) \ No newline at end of file + sibling_before.addnext(self.result) diff --git a/inkex/tween.py b/inkex/tween.py index 5109fe31..53a4ec86 100644 --- a/inkex/tween.py +++ b/inkex/tween.py @@ -35,8 +35,9 @@ from inkex.utils import FragmentError try: from typing import Tuple, TypeVar - Value = TypeVar('Value') - Number = TypeVar('Number', int, float) + + Value = TypeVar("Value") + Number = TypeVar("Number", int, float) except ImportError: pass @@ -102,7 +103,7 @@ class AttributeInterpolator(abc.ABC): if attribute in Style.color_props: return StyleInterpolator.create_from_fill_stroke(snode, enode, attribute) if attribute == "d": - if (method is None): + if method is None: method = FirstNodesInterpolator return method(snode.path, enode.path) if attribute == "style": @@ -129,10 +130,13 @@ class StyleInterpolator(AttributeInterpolator): self.interpolators = {} # some keys are always processed in a certain order, # these provide alternative interpolation routes if e.g. Color<->none is interpolated - all_keys = \ - list(dict.fromkeys(["fill", "stroke", "fill-opacity", "stroke-opacity", "stroke-width"] - + list(self.best_style(start_value).keys()) - + list(self.best_style(end_value).keys()))) + all_keys = list( + dict.fromkeys( + ["fill", "stroke", "fill-opacity", "stroke-opacity", "stroke-width"] + + list(self.best_style(start_value).keys()) + + list(self.best_style(end_value).keys()) + ) + ) for attr in all_keys: sstyle = self.best_style(start_value) estyle = self.best_style(end_value) @@ -140,7 +144,8 @@ class StyleInterpolator(AttributeInterpolator): continue try: interp = StyleInterpolator.create( - self.start_value, self.end_value, attr) + self.start_value, self.end_value, attr + ) self.interpolators[attr] = interp except ValueError: # no interpolation method known for this attribute @@ -168,12 +173,16 @@ class StyleInterpolator(AttributeInterpolator): return StyleInterpolator.create_from_fill_stroke(snode, enode, attribute) if attribute in Style.unit_props: - return UnitValueInterpolator(AttributeInterpolator.best_style(snode)(attribute), - AttributeInterpolator.best_style(enode)(attribute)) + return UnitValueInterpolator( + AttributeInterpolator.best_style(snode)(attribute), + AttributeInterpolator.best_style(enode)(attribute), + ) if attribute in Style.opacity_props: - return ValueInterpolator(AttributeInterpolator.best_style(snode)(attribute), - AttributeInterpolator.best_style(enode)(attribute)) + return ValueInterpolator( + AttributeInterpolator.best_style(snode)(attribute), + AttributeInterpolator.best_style(enode)(attribute), + ) raise ValueError("Unknown attribute") @@ -203,12 +212,13 @@ class StyleInterpolator(AttributeInterpolator): for (cur, curstyle) in styles: if curstyle(attribute) is None: cur.style[attribute + "-opacity"] = 0.0 - if (attribute == "stroke"): + if attribute == "stroke": cur.style["stroke-width"] = 0.0 - # check if style is none, unset or a color - if isinstance(sstyle(attribute), (LinearGradient, RadialGradient)) or \ - isinstance(estyle(attribute), (LinearGradient, RadialGradient)): + # check if style is none, unset or a color + if isinstance( + sstyle(attribute), (LinearGradient, RadialGradient) + ) or isinstance(estyle(attribute), (LinearGradient, RadialGradient)): # if one of the two styles is a gradient, use gradient interpolation. try: return GradientInterpolator.create(snode, enode, attribute) @@ -282,8 +292,10 @@ class ArrayInterpolator(AttributeInterpolator): def __init__(self, start_value, end_value): super().__init__(start_value, end_value) - self.interpolators = [ValueInterpolator(cur, other) for (cur, other) in - zip(start_value, end_value)] + self.interpolators = [ + ValueInterpolator(cur, other) + for (cur, other) in zip(start_value, end_value) + ] def interpolate(self, time=0): """Interpolates an array element-wise @@ -324,6 +336,7 @@ class TransformInterpolator(ArrayInterpolator): class ColorInterpolator(ArrayInterpolator): """Class for color interpolation""" + @staticmethod def create(sst, est, attribute): """Creates a ColorInterpolator for either Fill or stroke, depending on the attribute. @@ -344,10 +357,10 @@ class ColorInterpolator(ArrayInterpolator): if not isinstance(cur(attribute), Color) or cur(attribute) is None: cur[attribute] = other(attribute) this = ColorInterpolator( - Color(styles[0](attribute)), Color(styles[1](attribute))) + Color(styles[0](attribute)), Color(styles[1](attribute)) + ) if this is None: - raise ValueError( - "One of the two attribute needs to be a plain color") + raise ValueError("One of the two attribute needs to be a plain color") return this def __init__(self, start_value=Color("#000000"), end_value=Color("#000000")): @@ -373,32 +386,49 @@ class GradientInterpolator(AttributeInterpolator): super().__init__(start_value, end_value) self.svg = svg # If one of the styles is empty, set it to the gradient of the other - if (start_value is None): + if start_value is None: self.start_value = end_value - if (end_value is None): + if end_value is None: self.end_value = start_value - self.transform_interpolator = TransformInterpolator(self.start_value.gradientTransform, - self.end_value.gradientTransform) - self.orientation_interpolator = \ - {attr: UnitValueInterpolator(self.start_value.get(attr), self.end_value.get(attr)) - for attr in self.start_value.orientation_attributes - if self.start_value.get(attr) is not None and self.end_value.get(attr) is not None} - if not(self.start_value.href is not None and self.start_value.href is self.end_value.href): + self.transform_interpolator = TransformInterpolator( + self.start_value.gradientTransform, self.end_value.gradientTransform + ) + self.orientation_interpolator = { + attr: UnitValueInterpolator( + self.start_value.get(attr), self.end_value.get(attr) + ) + for attr in self.start_value.orientation_attributes + if self.start_value.get(attr) is not None + and self.end_value.get(attr) is not None + } + if not ( + self.start_value.href is not None + and self.start_value.href is self.end_value.href + ): # the gradient link to different stops, interpolate between them # add both start and end offsets, then take distict - newoffsets = sorted(list(set(self.start_value.stop_offsets - + self.end_value.stop_offsets))) + newoffsets = sorted( + list(set(self.start_value.stop_offsets + self.end_value.stop_offsets)) + ) def func(start, end, time): return StopInterpolator(start, end).interpolate(time) - sstops = GradientInterpolator.\ - interpolate_linear_list(self.start_value.stop_offsets, list(self.start_value.stops), - newoffsets, func) - ostops = GradientInterpolator.\ - interpolate_linear_list(self.end_value.stop_offsets, list(self.end_value.stops), - newoffsets, func) - self.newstop_interpolator =\ - [StopInterpolator(s1, s2) for s1, s2 in zip(sstops, ostops)] + + sstops = GradientInterpolator.interpolate_linear_list( + self.start_value.stop_offsets, + list(self.start_value.stops), + newoffsets, + func, + ) + ostops = GradientInterpolator.interpolate_linear_list( + self.end_value.stop_offsets, + list(self.end_value.stops), + newoffsets, + func, + ) + self.newstop_interpolator = [ + StopInterpolator(s1, s2) for s1, s2 in zip(sstops, ostops) + ] else: self.newstop_interpolator = None @@ -435,32 +465,34 @@ class GradientInterpolator(AttributeInterpolator): curgrad = None if isinstance(cur(attribute), (LinearGradient, RadialGradient)): curgrad = cur(attribute) - for gradtype, interp in [[LinearGradient, LinearGradientInterpolator], - [RadialGradient, RadialGradientInterpolator]]: - if (curgrad is not None and isinstance(curgrad, gradtype)): + for gradtype, interp in [ + [LinearGradient, LinearGradientInterpolator], + [RadialGradient, RadialGradientInterpolator], + ]: + if curgrad is not None and isinstance(curgrad, gradtype): if interpolator is None: interpolator = interp gradienttype = gradtype - if not(interp == interpolator): + if not (interp == interpolator): raise ValueError("Gradient types don't match") # If one of the styles is empty, set it to the gradient of the other, but with zero # opacity (and stroke-width for strokes) # If one of the styles is a plain color, replace it by a gradient with a single stop - iterator = [[snode, gradienttype(), enode], [ - enode, gradienttype(), snode]] + iterator = [[snode, gradienttype(), enode], [enode, gradienttype(), snode]] for index in [0, 1]: curstyle = AttributeInterpolator.best_style(iterator[index][0]) value = curstyle(attribute) if value is None: # if the attribute of one of the two ends is unset, set the opacity to zero. iterator[index][0].style[attribute + "-opacity"] = 0.0 - if (attribute == "stroke"): + if attribute == "stroke": iterator[index][0].style["stroke-width"] = 0.0 if isinstance(value, Color): # if the attribute of one of the two ends is a color, convert it to a one-stop # gradient. Type depends on the type of the other gradient. - interpolator.initialize_position(iterator[index][1], - iterator[index][0].bounding_box()) + interpolator.initialize_position( + iterator[index][1], iterator[index][0].bounding_box() + ) stop = Stop() stop.style = Style() stop.style["stop-color"] = value @@ -506,7 +538,7 @@ class GradientInterpolator(AttributeInterpolator): positions = list(map(float, positions)) newpositions = list(map(float, newpositions)) for pos in newpositions: - if (len(positions) == 1): + if len(positions) == 1: newvalues.append(values[0]) else: # current run: @@ -514,9 +546,8 @@ class GradientInterpolator(AttributeInterpolator): # p p | p # q q idxl = max(0, bisect_left(positions, pos) - 1) - idxr = min(len(positions)-1, idxl + 1) - fraction = (pos - positions[idxl]) / \ - (positions[idxr] - positions[idxl]) + 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) @@ -538,32 +569,32 @@ class GradientInterpolator(AttributeInterpolator): element has no root or is None """ stops, orientation = gradient.stops_and_orientation() - if (element is None or - (element.getparent() is None and not isinstance(element, SvgDocumentElement))): + if element is None or ( + element.getparent() is None and not isinstance(element, SvgDocumentElement) + ): return gradient element.root.defs.add(orientation) if len(stops) > 0: element.root.defs.add(stops, orientation) - orientation.set('xlink:href', f'#{stops.get_id()}') + orientation.set("xlink:href", f"#{stops.get_id()}") return orientation def interpolate(self, time=0): """Interpolate with another gradient.""" newgrad = self.start_value.copy() # interpolate transforms - newgrad.gradientTransform = self.transform_interpolator.interpolate( - time) + newgrad.gradientTransform = self.transform_interpolator.interpolate(time) # interpolate orientation for attr in self.orientation_interpolator.keys(): - newgrad.set( - attr, self.orientation_interpolator[attr].interpolate(time)) + newgrad.set(attr, self.orientation_interpolator[attr].interpolate(time)) # interpolate stops if self.newstop_interpolator is not None: newgrad.remove_all(Stop) - newgrad.add(*[interp.interpolate(time) - for interp in self.newstop_interpolator]) + newgrad.add( + *[interp.interpolate(time) for interp in self.newstop_interpolator] + ) if self.svg is None: return newgrad return GradientInterpolator.append_to_doc(self.svg, newgrad) @@ -572,8 +603,9 @@ class GradientInterpolator(AttributeInterpolator): class LinearGradientInterpolator(GradientInterpolator): """Class for interpolation of linear gradients""" - def __init__(self, start_value=LinearGradient(), - end_value=LinearGradient(), svg=None): + def __init__( + self, start_value=LinearGradient(), end_value=LinearGradient(), svg=None + ): super().__init__(start_value, end_value, svg) @staticmethod @@ -588,8 +620,9 @@ class LinearGradientInterpolator(GradientInterpolator): class RadialGradientInterpolator(GradientInterpolator): """Class to interpolate radial gradients""" - def __init__(self, start_value=RadialGradient(), - end_value=RadialGradient(), svg=None): + def __init__( + self, start_value=RadialGradient(), end_value=RadialGradient(), svg=None + ): super().__init__(start_value, end_value, svg) @staticmethod @@ -609,9 +642,9 @@ class StopInterpolator(AttributeInterpolator): def __init__(self, start_value, end_value): super().__init__(start_value, end_value) self.style_interpolator = StyleInterpolator(start_value, end_value) - self.position_interpolator = \ - ValueInterpolator(float(start_value.offset), - float(end_value.offset)) + self.position_interpolator = ValueInterpolator( + float(start_value.offset), float(end_value.offset) + ) def interpolate(self, time=0): """Interpolates a gradient stop by interpolating style and offset separately @@ -668,7 +701,9 @@ class PathInterpolator(AttributeInterpolator): # create an interpolated path for each interval interp = [] # process subpaths - for ssubpath, esubpath in zip(self.processed_start_path, self.processed_end_path): + for ssubpath, esubpath in zip( + self.processed_start_path, self.processed_end_path + ): if not (ssubpath or esubpath): break # add a new subpath to the interpolated path @@ -684,8 +719,9 @@ class PathInterpolator(AttributeInterpolator): if not (point1 or point2): break # add a new point to the last bezier command - interp[-1][-1].append(ArrayInterpolator(point1, - point2).interpolate(time)) + interp[-1][-1].append( + ArrayInterpolator(point1, point2).interpolate(time) + ) # remove final subpath if empty. if not interp[-1]: del interp[-1] @@ -694,6 +730,7 @@ class PathInterpolator(AttributeInterpolator): class EqualSubsegmentsInterpolator(PathInterpolator): """Interpolates the path by rediscretizing the subpaths first.""" + @staticmethod def get_subpath_lenghts(path): """prepare lengths for interpolation""" @@ -717,9 +754,8 @@ class EqualSubsegmentsInterpolator(PathInterpolator): other (Path): the second path Returns: - Array: the prepared path description for the intermediate path """ - sp_lenghts, total, _ = EqualSubsegmentsInterpolator.get_subpath_lenghts( - path) + Array: the prepared path description for the intermediate path""" + sp_lenghts, total, _ = EqualSubsegmentsInterpolator.get_subpath_lenghts(path) _, _, lenghts = EqualSubsegmentsInterpolator.get_subpath_lenghts(other) t = 0 s = [[]] @@ -733,8 +769,7 @@ class EqualSubsegmentsInterpolator(PathInterpolator): if lenghts and t > lenghts[0]: while lenghts and lenghts[0] < t: nt = (lenghts[0] - pt) / (t - pt) - bezes = cspbezsplitatlength( - s[-1][-1][:], path[0][0][:], nt) + bezes = cspbezsplitatlength(s[-1][-1][:], path[0][0][:], nt) s[-1][-1:] = bezes[:2] path[0][0] = bezes[2] pt = lenghts.pop(0) @@ -746,10 +781,12 @@ class EqualSubsegmentsInterpolator(PathInterpolator): # rediscretisize both paths start_copy = copy.deepcopy(self.start_value) # TODO find out why self.start_value.copy() doesn't work - self.start_value = EqualSubsegmentsInterpolator.process_path(self.start_value, - self.end_value) + self.start_value = EqualSubsegmentsInterpolator.process_path( + self.start_value, self.end_value + ) self.end_value = EqualSubsegmentsInterpolator.process_path( - self.end_value, start_copy) + self.end_value, start_copy + ) self.truncate_subpaths() @@ -771,15 +808,15 @@ class FirstNodesInterpolator(PathInterpolator): segment = 0 for y in range(len(self.start_value)): for z in range(1, len(self.start_value[y])): - leng = bezlenapprx(self.start_value[y][z - 1], - self.start_value[y][z]) + leng = bezlenapprx( + self.start_value[y][z - 1], self.start_value[y][z] + ) if leng > maxlen: maxlen = leng subpath = y segment = z - sp1, sp2 = self.start_value[subpath][segment - 1:segment + 1] - self.start_value[subpath][segment - - 1:segment + 1] = cspbezsplit(sp1, sp2) + sp1, sp2 = self.start_value[subpath][segment - 1 : segment + 1] + self.start_value[subpath][segment - 1 : segment + 1] = cspbezsplit(sp1, sp2) # if swapped, swap them back if lengthdiff > 0: self.start_value, self.end_value = self.end_value, self.start_value diff --git a/inkex/units.py b/inkex/units.py index 26e39ba4..92121484 100644 --- a/inkex/units.py +++ b/inkex/units.py @@ -29,34 +29,66 @@ 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 + "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'] +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)) +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): +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 @@ -69,10 +101,12 @@ def parse_unit(value, default_unit='px', default_value=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) + return (point_a - point_b <= point_a * eps) and ( + point_a - point_b >= -point_a * eps + ) -def discover_unit(value, viewbox, default='px'): +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) @@ -82,18 +116,20 @@ def discover_unit(value, viewbox, default='px'): # 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 != '': + 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, default='px'): +def convert_unit(value, to_unit, default="px"): """Returns userunits given a string representation of units in another system""" value, from_unit = parse_unit(value, default_unit=default, 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 ( + value * CONVERSIONS[from_unit] / CONVERSIONS.get(to_unit, CONVERSIONS["px"]) + ) return 0.0 @@ -104,4 +140,4 @@ def render_unit(value, unit): (value, unit) = parse_unit(value, default_unit=unit) return f"{value:.6g}{ unit:s}" except TypeError: - return '' + return "" diff --git a/inkex/utils.py b/inkex/utils.py index 12183361..938241dd 100644 --- a/inkex/utils.py +++ b/inkex/utils.py @@ -30,7 +30,7 @@ from itertools import tee from argparse import ArgumentTypeError # All the names that get added to the inkex API itself. -__all__ = ('AbortExtension', 'DependencyError', 'Boolean', 'errormsg') +__all__ = ("AbortExtension", "DependencyError", "Boolean", "errormsg") ABORT_STATUS = -5 @@ -38,36 +38,43 @@ ABORT_STATUS = -5 PY3 = sys.version_info[0] == 3 # Taken from https://www.w3.org/Graphics/SVG/1.1/paths.html#PathDataBNF -DIGIT_REX_PART = r'[0-9]' -DIGIT_SEQUENCE_REX_PART = fr'(?:{DIGIT_REX_PART}+)' +DIGIT_REX_PART = r"[0-9]" +DIGIT_SEQUENCE_REX_PART = rf"(?:{DIGIT_REX_PART}+)" INTEGER_CONSTANT_REX_PART = DIGIT_SEQUENCE_REX_PART -SIGN_REX_PART = r'[+-]' -EXPONENT_REX_PART = fr'(?:[eE]{SIGN_REX_PART}?{DIGIT_SEQUENCE_REX_PART})' -FRACTIONAL_CONSTANT_REX_PART = fr'(?:{DIGIT_SEQUENCE_REX_PART}?\.{DIGIT_SEQUENCE_REX_PART}|{DIGIT_SEQUENCE_REX_PART}\.)' -FLOATING_POINT_CONSTANT_REX_PART = fr'(?:{FRACTIONAL_CONSTANT_REX_PART}{EXPONENT_REX_PART}?|{DIGIT_SEQUENCE_REX_PART}{EXPONENT_REX_PART})' -NUMBER_REX = re.compile(fr'(?:{SIGN_REX_PART}?{FLOATING_POINT_CONSTANT_REX_PART}|{SIGN_REX_PART}?{INTEGER_CONSTANT_REX_PART})') +SIGN_REX_PART = r"[+-]" +EXPONENT_REX_PART = rf"(?:[eE]{SIGN_REX_PART}?{DIGIT_SEQUENCE_REX_PART})" +FRACTIONAL_CONSTANT_REX_PART = rf"(?:{DIGIT_SEQUENCE_REX_PART}?\.{DIGIT_SEQUENCE_REX_PART}|{DIGIT_SEQUENCE_REX_PART}\.)" +FLOATING_POINT_CONSTANT_REX_PART = rf"(?:{FRACTIONAL_CONSTANT_REX_PART}{EXPONENT_REX_PART}?|{DIGIT_SEQUENCE_REX_PART}{EXPONENT_REX_PART})" +NUMBER_REX = re.compile( + rf"(?:{SIGN_REX_PART}?{FLOATING_POINT_CONSTANT_REX_PART}|{SIGN_REX_PART}?{INTEGER_CONSTANT_REX_PART})" +) + def _pythonpath(): - for pth in os.environ.get('PYTHONPATH', '').split(':'): + for pth in os.environ.get("PYTHONPATH", "").split(":"): if os.path.isdir(pth): yield pth + def get_user_directory(): """Return the user directory where extensions are stored.""" - if 'INKSCAPE_PROFILE_DIR' in os.environ: + if "INKSCAPE_PROFILE_DIR" in os.environ: return os.path.abspath( os.path.expanduser( - os.path.join(os.environ['INKSCAPE_PROFILE_DIR'], 'extensions'))) + os.path.join(os.environ["INKSCAPE_PROFILE_DIR"], "extensions") + ) + ) home = os.path.expanduser("~") for pth in _pythonpath(): if pth.startswith(home): return pth + def get_inkscape_directory(): """Return the system directory where inkscape's core is.""" for pth in _pythonpath(): - if os.path.isdir(os.path.join(pth, 'inkex')): + if os.path.isdir(os.path.join(pth, "inkex")): return pth @@ -76,6 +83,7 @@ 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().__getitem__(key) @@ -86,47 +94,50 @@ class KeyDict(dict): def parse_percent(val: str): """Parse strings that are either values (i.e., '3.14159') or percentages (i.e. '75%') to a float.""" val = val.strip() - if val.endswith('%'): + if val.endswith("%"): return float(val[:-1]) / 100 return float(val) - def Boolean(value): """ArgParser function to turn a boolean string into a python boolean""" - if value.upper() == 'TRUE': + if value.upper() == "TRUE": return True - elif value.upper() == 'FALSE': + 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 + +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.) + (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: + Note that this should always be combined with translation: - import inkex - ... - inkex.errormsg(_("This extension requires two selected paths.")) + import inkex + ... + inkex.errormsg(_("This extension requires two selected paths.")) """ try: sys.stderr.write(msg) @@ -142,11 +153,11 @@ def errormsg(msg): # 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')) + 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') + sys.stderr.write("\n") class AbortExtension(Exception): @@ -163,9 +174,11 @@ class AbortExtension(Exception): 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""" + def to(kind): # pylint: disable=invalid-name """ Decorator which will turn a generator into a list, tuple or other object type. @@ -181,8 +194,8 @@ def to(kind): # pylint: disable=invalid-name 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 + """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 NUMBER_REX.findall(string)] @@ -204,6 +217,7 @@ def filename_arg(name): 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) @@ -212,26 +226,31 @@ def pairwise(iterable, start=True): starter = [] return starter + list(zip(first, then)) + EVAL_GLOBALS = {} EVAL_GLOBALS.update(random.__dict__) EVAL_GLOBALS.update(math.__dict__) + def math_eval(function, variable="x"): """Interpret a function string. All functions from math and random may be used. @returns a lambda expression if sucessful; otherwise None. """ try: if function != "": - return eval(f'lambda {variable}: ' + (function.strip('"') or 't'), EVAL_GLOBALS, {}) + return eval( + f"lambda {variable}: " + (function.strip('"') or "t"), EVAL_GLOBALS, {} + ) # handle incomplete/invalid function gracefully except SyntaxError: pass return None + def is_number(string): """Checks if a value is a number""" try: float(string) return True except ValueError: - return False \ No newline at end of file + return False diff --git a/inkscape_follow_link.py b/inkscape_follow_link.py index 426651f9..54dac76c 100755 --- a/inkscape_follow_link.py +++ b/inkscape_follow_link.py @@ -7,8 +7,10 @@ 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 @@ -16,13 +18,16 @@ class ThreadWebsite(threading.Thread): 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 = ThreadWebsite(node.get("xlink:href")) vwswli.start() break -if __name__ == '__main__': + +if __name__ == "__main__": FollowLink().run() diff --git a/inkwebeffect.py b/inkwebeffect.py index 90246daf..f4687806 100755 --- a/inkwebeffect.py +++ b/inkwebeffect.py @@ -25,8 +25,10 @@ import sys import inkex from inkex import Script + class InkWebEffect(inkex.EffectExtension): - reUpdateJS = '/\\*\\s* inkweb.js [^*]* InkWebEffect:AutoUpdate \\s*\\*/' + reUpdateJS = "/\\*\\s* inkweb.js [^*]* InkWebEffect:AutoUpdate \\s*\\*/" + def effect(self): pass @@ -44,7 +46,7 @@ class InkWebEffect(inkex.EffectExtension): def ensureInkWebSupport(self): # Search for the script tag with the inkweb.js code: script = None - for child in self.svg.xpath('//svg:script'): + for child in self.svg.xpath("//svg:script"): if re.search(self.reUpdateJS, child.text): script = child diff --git a/interp.py b/interp.py index 2cf3535a..a056d6e5 100755 --- a/interp.py +++ b/interp.py @@ -23,25 +23,52 @@ import copy import inkex from inkex.utils import pairwise -from inkex.tween import AttributeInterpolator, StyleInterpolator, EqualSubsegmentsInterpolator, FirstNodesInterpolator +from inkex.tween import ( + AttributeInterpolator, + StyleInterpolator, + EqualSubsegmentsInterpolator, + FirstNodesInterpolator, +) from inkex.localization import inkex_gettext as _ + class Interp(inkex.EffectExtension): """Interpolate extension""" + def add_arguments(self, pars): - pars.add_argument("-e", "--exponent", type=float, default=1.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=str, default="equalSubsegments",\ - 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=False,\ - help="try interpolation of some style properties") - pars.add_argument("--zsort", type=inkex.Boolean, default=False,\ - help="use z-order instead of selection order") + pars.add_argument( + "-e", + "--exponent", + type=float, + default=1.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=str, + default="equalSubsegments", + 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=False, + 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): steps = self.get_steps() @@ -56,9 +83,10 @@ class Interp(inkex.EffectExtension): if self.options.method == "firstNodes": method = FirstNodesInterpolator - path_interpolator = AttributeInterpolator.create_from_attribute(elem1, elem2, "d", - method=method) - if (self.options.style): + path_interpolator = AttributeInterpolator.create_from_attribute( + elem1, elem2, "d", method=method + ) + if self.options.style: style_interpolator = StyleInterpolator(elem1, elem2) group = self.svg.get_current_layer().add(inkex.Group()) @@ -66,7 +94,7 @@ class Interp(inkex.EffectExtension): interpolated_path = path_interpolator.interpolate(time) new = group.add(inkex.PathElement()) new.path = interpolated_path - if (self.options.style): + if self.options.style: interpolated_style = style_interpolator.interpolate(time) new.style = interpolated_style else: @@ -76,14 +104,18 @@ class Interp(inkex.EffectExtension): """Returns the interpolation steps as a monotonous array with elements between 0 and 1. 0 and 1 are added as first and last elements if the source paths should be duplicated""" exponent = self.options.exponent - #if exponent >= 0: + # if exponent >= 0: # exponent += 1.0 - #else: + # else: # exponent = 1.0 / (1.0 - exponent) - steps = [((i+1) / (self.options.steps + 1.0))** exponent for i in range(self.options.steps)] + steps = [ + ((i + 1) / (self.options.steps + 1.0)) ** exponent + for i in range(self.options.steps) + ] if self.options.dup: steps = [0] + steps + [1] return steps + def get_copied_path_pairs(self): """Returns deep copies of pairs of subsequent objects of the current selection, either in z order or in selection order. @@ -95,7 +127,9 @@ class Interp(inkex.EffectExtension): # use selection order (default) objects = self.svg.selected - objects = [node for node in objects.values() if isinstance(node, inkex.PathElement)] + objects = [ + node for node in objects.values() if isinstance(node, inkex.PathElement) + ] for node in objects: node.apply_transform() @@ -103,5 +137,6 @@ class Interp(inkex.EffectExtension): objectpairs = pairwise(objects, start=False) return objectpairs -if __name__ == '__main__': + +if __name__ == "__main__": Interp().run() diff --git a/interp_att_g.py b/interp_att_g.py index df8c2da8..348c2bbc 100755 --- a/interp_att_g.py +++ b/interp_att_g.py @@ -26,40 +26,77 @@ from inkex.localization import inkex_gettext as _ from inkex.tween import ColorInterpolator, ValueInterpolator, AttributeInterpolator from inkex.utils import is_number + 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="width", - help="Attribute to be interpolated.") + "-a", + "--att", + type=str, + dest="att", + default="width", + 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).") + "-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=self.arg_class([ColorInterpolator, ValueInterpolator]), - dest="att_other_type", help="The other attribute type.") + "-t", + "--att-other-type", + type=self.arg_class([ColorInterpolator, ValueInterpolator]), + dest="att_other_type", + help="The other attribute type.", + ) self.arg_parser.add_argument( - "-w", "--att-other-where", type=str, dest="att_other_where", default="tag", - help="That is a tag attribute or a style attribute?") + "-w", + "--att-other-where", + type=str, + dest="att_other_where", + default="tag", + 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.") + "-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.") + "-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="none", - help="Values unit.") + "-u", "--unit", type=str, dest="unit", default="none", help="Values unit." + ) self.arg_parser.add_argument( - "--zsort", type=inkex.Boolean, dest="zsort", default=False, - help="use z-order instead of selection order") + "--zsort", + type=inkex.Boolean, + dest="zsort", + default=False, + 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") + "--tab", + type=str, + dest="tab", + help="The selected UI-tab when OK was pressed", + ) def get_elements(self): """Returns a list of elements to work on""" @@ -89,6 +126,7 @@ class InterpAttG(inkex.EffectExtension): self.apply_value(pat1, path, start_value) self.apply_value(pat2, path, end_value) return pat1, pat2 + @staticmethod def apply_value(node, path, value): """Applies a value to a given node. If path starts with "transform/" or "style/", the @@ -99,11 +137,11 @@ class InterpAttG(inkex.EffectExtension): elif path.startswith("transform/"): if not is_number(value): raise inkex.AbortExtension(f"Unable to set attribute {path} to {value}") - if path == 'transform/trans-x': + if path == "transform/trans-x": node.transform.add_translate(value, 0) - elif path == 'transform/trans-y': + elif path == "transform/trans-y": node.transform.add_translate(0, value) - elif path == 'transform/scale': + elif path == "transform/scale": node.transform.add_scale(value) elif path == "transform": node.transform @= value @@ -112,10 +150,11 @@ class InterpAttG(inkex.EffectExtension): def effect(self): method = None - if self.options.att == 'other': + if self.options.att == "other": if self.options.att_other is None: - raise inkex.AbortExtension(\ - _("You selected 'Other'. Please enter an attribute to interpolate.")) + raise inkex.AbortExtension( + _("You selected 'Other'. Please enter an attribute to interpolate.") + ) if self.options.att_other_where == "tag": path = self.options.att_other else: @@ -123,23 +162,27 @@ class InterpAttG(inkex.EffectExtension): method = self.options.att_other_type else: path = self.options.att - if (self.options.att == "height" or self.options.att == "width"): + if self.options.att == "height" or self.options.att == "width": method = inkex.tween.UnitValueInterpolator path1, path2 = self.create_dummy_nodes(path) if path.startswith("transform"): path = "transform" try: - #maybe tween knows what do do with this attribute? - interpolator = AttributeInterpolator.create_from_attribute(path1, path2, path, None) + # maybe tween knows what do do with this attribute? + interpolator = AttributeInterpolator.create_from_attribute( + path1, path2, path, None + ) except: - #okay, apparently not - interpolator = AttributeInterpolator.create_from_attribute(path1, path2, path, method) + # okay, apparently not + interpolator = AttributeInterpolator.create_from_attribute( + path1, path2, path, method + ) collection = self.get_elements() if not collection: - raise inkex.AbortExtension(_('There is no selection to interpolate')) + raise inkex.AbortExtension(_("There is no selection to interpolate")) - steps = [1.0/(len(collection)-1) * i for i in range(len(collection))] + steps = [1.0 / (len(collection) - 1) * i for i in range(len(collection))] for time, node in zip(steps, collection): new_value = interpolator.interpolate(time) @@ -147,5 +190,6 @@ class InterpAttG(inkex.EffectExtension): return True -if __name__ == '__main__': + +if __name__ == "__main__": InterpAttG().run() diff --git a/jessyink_autotexts.py b/jessyink_autotexts.py index 4ce88743..0eef457b 100755 --- a/jessyink_autotexts.py +++ b/jessyink_autotexts.py @@ -21,11 +21,13 @@ 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') + pars.add_argument("--tab", dest="what") + pars.add_argument("--autoText", default="none") def effect(self): self.is_installed() @@ -43,5 +45,6 @@ class AutoTexts(JessyInkMixin, inkex.EffectExtension): elif node.get("jessyink:autoText"): node.set("jessyink:autoText", None) -if __name__ == '__main__': + +if __name__ == "__main__": AutoTexts().run() diff --git a/jessyink_effects.py b/jessyink_effects.py index 68336184..f53547ab 100755 --- a/jessyink_effects.py +++ b/jessyink_effects.py @@ -22,39 +22,48 @@ 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=1) - pars.add_argument('--effectOutDuration', type=float, default=0.8) - pars.add_argument('--effectOut', default='none') + 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=1) + 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")) + _( + "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') + 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) + 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)) + elem.set( + "jessyink:" + name, + inkex.Style(name=effect, order=order, length=duration), + ) # Remove possible view argument. - elem.pop('jessyink:view', None) + elem.pop("jessyink:view", None) else: - elem.pop('jessyink:' + name, None) + elem.pop("jessyink:" + name, None) + -if __name__ == '__main__': +if __name__ == "__main__": JessyinkEffects().run() diff --git a/jessyink_export.py b/jessyink_export.py index 0fe98e2e..171c46f9 100755 --- a/jessyink_export.py +++ b/jessyink_export.py @@ -28,16 +28,18 @@ 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-' + + 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='pdf') - pars.add_argument('--resolution', type=int, default=92) + pars.add_argument("--tab", type=str, dest="what") + pars.add_argument("--type", type=str, dest="type", default="pdf") + pars.add_argument("--resolution", type=int, default=92) def save(self, stream): self.is_installed() @@ -53,21 +55,25 @@ class Export(JessyInkMixin, TempDirMixin, inkex.OutputExtension): for node in layers: # Make all layers invisible - node.style['display'] = "none" + 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') + 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) + 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" + node.style["display"] = "none" -if __name__ == '__main__': +if __name__ == "__main__": Export().run() diff --git a/jessyink_install.py b/jessyink_install.py index d0d47fb9..dee36d3e 100755 --- a/jessyink_install.py +++ b/jessyink_install.py @@ -22,19 +22,24 @@ from inkex import Script from inkex.localization import inkex_gettext as _ -inkex.NSS[u"jessyink"] = u"https://launchpad.net/jessyink" +inkex.NSS["jessyink"] = "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")) + 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""" @@ -60,13 +65,14 @@ class JessyInkMixin(object): @staticmethod def list_to_prop_str(lst): """List of instructions to script string""" - return "; ".join(lst) + ';' + 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') + pars.add_argument("--tab", type=str, dest="what") def effect(self): # Find and delete old script node @@ -78,21 +84,27 @@ class Install(JessyInkMixin, inkex.EffectExtension): 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') + 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(';')] + 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'): + for attr in ( + "effectIn", + "effectOut", + "masterSlide", + "transitionIn", + "transitionOut", + "autoText", + ): self.attr_update(attr) # Create effect instance -if __name__ == '__main__': +if __name__ == "__main__": Install().run() diff --git a/jessyink_key_bindings.py b/jessyink_key_bindings.py index 24c74c05..2d28a95a 100755 --- a/jessyink_key_bindings.py +++ b/jessyink_key_bindings.py @@ -21,16 +21,29 @@ 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') +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') + + 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) + slot, action = opt_str.split("_", 1) keycodes = getattr(namespace, f"{slot}KeyCodes", {}) charcodes = getattr(namespace, f"{slot}CharCodes", {}) if value: @@ -44,7 +57,7 @@ class KeyBindings(JessyInkMixin, inkex.EffectExtension): setattr(namespace, f"{slot}CharCodes", charcodes) actions = { - 'slide': { + "slide": { "export": "slideUpdateExportLayer();", "addSlide": "slideAddSlide(activeSlide);", "resetTimer": "slideResetTimer();", @@ -59,7 +72,7 @@ class KeyBindings(JessyInkMixin, inkex.EffectExtension): "firstSlide": "slideSetActiveSlide(0);", "lastSlide": "slideSetActiveSlide(slides.length - 1);", }, - 'drawing': { + "drawing": { "undo": "drawingUndo();", "switchToSlideMode": "drawingSwitchToSlideMode();", "pathWidthDefault": "drawingResetPathWidth();", @@ -68,17 +81,17 @@ class KeyBindings(JessyInkMixin, inkex.EffectExtension): "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\");", + "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': { + "index": { "selectSlideToLeft": "indexSetPageSlide(activeSlide - 1);", "selectSlideToRight": "indexSetPageSlide(activeSlide + 1);", "selectSlideAbove": "indexSetPageSlide(activeSlide - INDEX_COLUMNS);", @@ -91,24 +104,26 @@ class KeyBindings(JessyInkMixin, inkex.EffectExtension): "decreaseNumberOfColumns": "indexDecreaseNumberOfColumns();", "increaseNumberOfColumns": "indexIncreaseNumberOfColumns();", "setNumberOfColumnsToDefault": "indexResetNumberOfColumns();", - } + }, } def add_arguments(self, pars): - pars.add_argument('--tab') + pars.add_argument("--tab") for slot, actions in self.actions.items(): for action in actions: - pars.add_argument(f'--{slot}_{action}') + pars.add_argument(f"--{slot}_{action}") def effect(self): self.is_installed() for name in list(self.options.__dict__): - if '_' in name: + 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']"): + for node in self.svg.xpath( + "//svg:g[@jessyink:customKeyBindings='customKeyBindings']" + ): node.delete() # Set custom key bindings. @@ -124,7 +139,9 @@ class KeyBindings(JessyInkMixin, inkex.EffectExtension): node_text += f" keyDict[SLIDE_MODE][{key}] = function() {{ {value} }};\n" for key, value in self.options.drawingKeyCodes.items(): - node_text += f" keyDict[DRAWING_MODE][{key}] = function() {{ {value} }};\n" + node_text += ( + f" keyDict[DRAWING_MODE][{key}] = function() {{ {value} }};\n" + ) for key, value in self.options.indexKeyCodes.items(): node_text += f" keyDict[INDEX_MODE][{key}] = function() {{ {value} }};\n" @@ -142,13 +159,19 @@ function getCustomCharBindingsSub() """ for key, value in self.options.slideCharCodes.items(): - node_text += f' charDict[SLIDE_MODE]["{key}"] = function() {{ {value} }};\n' + node_text += ( + f' charDict[SLIDE_MODE]["{key}"] = function() {{ {value} }};\n' + ) for key, value in self.options.drawingCharCodes.items(): - node_text += f' charDict[DRAWING_MODE]["{key}"] = function() {{ {value} }};\n' + node_text += ( + f' charDict[DRAWING_MODE]["{key}"] = function() {{ {value} }};\n' + ) for key, value in self.options.indexCharCodes.items(): - node_text += f' charDict[INDEX_MODE]["{key}"] = function() {{ {value} }};\n' + node_text += ( + f' charDict[INDEX_MODE]["{key}"] = function() {{ {value} }};\n' + ) node_text += " return charDict;" + "\n" node_text += "}" + "\n" @@ -158,10 +181,13 @@ function getCustomCharBindingsSub() 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(); };") + group.set( + "onload", + "this.getCustomCharBindings = function() { " + "return getCustomCharBindingsSub(); }; " + "this.getCustomKeyBindings = function() { return getCustomKeyBindingsSub(); };", + ) -if __name__ == '__main__': +if __name__ == "__main__": KeyBindings().run() diff --git a/jessyink_master_slide.py b/jessyink_master_slide.py index ab5b9ff3..fe5c5339 100755 --- a/jessyink_master_slide.py +++ b/jessyink_master_slide.py @@ -23,11 +23,13 @@ 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='') + self.arg_parser.add_argument("--tab") + self.arg_parser.add_argument("--layerName", default="") def effect(self): self.is_installed() @@ -37,15 +39,24 @@ class MasterSlide(JessyInkMixin, inkex.EffectExtension): # Set new master slide. if self.options.layerName != "": - nodes = self.svg.xpath(f"//*[@inkscape:groupmode='layer' " - f"and @inkscape:label='{self.options.layerName}']") + nodes = self.svg.xpath( + f"//*[@inkscape:groupmode='layer' " + f"and @inkscape:label='{self.options.layerName}']" + ) if not nodes: - inkex.errormsg(_("Layer not found. Removed current master slide selection.\n")) + 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")) + 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__': + +if __name__ == "__main__": MasterSlide().run() diff --git a/jessyink_mouse_handler.py b/jessyink_mouse_handler.py index 9f351956..d600d8fe 100755 --- a/jessyink_mouse_handler.py +++ b/jessyink_mouse_handler.py @@ -21,15 +21,19 @@ import inkex from inkex.elements import BaseElement, Script from jessyink_install import JessyInkMixin + class MouseHandler(BaseElement): """jessyInk mouse handler""" - tag_name = 'jessyink:mousehandler' + + 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') + pars.add_argument("--tab") + pars.add_argument("--mouseSetting", default="default") def effect(self): self.is_installed() @@ -56,5 +60,6 @@ class AddMouseHandler(JessyInkMixin, inkex.EffectExtension): group.append(script) self.svg.append(group) -if __name__ == '__main__': + +if __name__ == "__main__": AddMouseHandler().run() diff --git a/jessyink_summary.py b/jessyink_summary.py index 52c6f366..37eaba11 100755 --- a/jessyink_summary.py +++ b/jessyink_summary.py @@ -22,10 +22,12 @@ 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') + pars.add_argument("--tab") def effect(self): self.is_installed() @@ -49,11 +51,14 @@ class Summary(JessyInkMixin, inkex.EffectExtension): if master_slide is not None: self.msg(_("\nMaster slide:")) - self.describe_node(master_slide, "\t",\ - ["", len(slides), ""]) + self.describe_node( + master_slide, + "\t", + ["", len(slides), ""], + ) for i, slide in enumerate(slides): - self.msg(_("\nSlide {0!s}:").format(i+1)) + 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): @@ -78,15 +83,18 @@ class Summary(JessyInkMixin, inkex.EffectExtension): def describe_autotext(self, node, prefix, dat): """Display information about auto-texts.""" - auto_texts = {"slide_num" : dat[0], "num" : dat[1], "title" : dat[2]} + 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(_(f"\n{prefix}Auto-texts:")) pid = child.getparent().get("id") - val = auto_texts[child.get('jessyink:autoText')] - self.msg(_( - f'{prefix}\t"{child.text}" (object id "{pid}") will be replaced by "{val}".')) + val = auto_texts[child.get("jessyink:autoText")] + self.msg( + _( + f'{prefix}\t"{child.text}" (object id "{pid}") will be replaced by "{val}".' + ) + ) def describe_effects(self, node, prefix): """Display information about effects.""" @@ -103,9 +111,9 @@ class Summary(JessyInkMixin, inkex.EffectExtension): for item in effect: eid = item["id"] if item["type"] == "view": - ret += _(f"{prefix}\tView will be set according to object \"{eid}\"") + ret += _(f'{prefix}\tView will be set according to object "{eid}"') else: - ret += _(f"{prefix}\tObject \"{eid}\"") + ret += _(f'{prefix}\tObject "{eid}"') if item["direction"] == "in": ret += _(" will appear") @@ -113,7 +121,7 @@ class Summary(JessyInkMixin, inkex.EffectExtension): ret += _(" will disappear") if item["name"] != "appear": - ret += _(" using effect \"{0}\"").format(item["name"]) + ret += _(' using effect "{0}"').format(item["name"]) if "length" in item: ret += _(" in {0!s} s").format(int(item["length"]) / 1000.0) @@ -125,25 +133,26 @@ class Summary(JessyInkMixin, inkex.EffectExtension): """Collect information about effects.""" effects = defaultdict(list) for child in node.xpath(".//*[@jessyink:effectIn]"): - effect_data = inkex.Style(child.get('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 = 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 = 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__': + +if __name__ == "__main__": Summary().run() diff --git a/jessyink_transitions.py b/jessyink_transitions.py index 73ec42e5..356a2046 100755 --- a/jessyink_transitions.py +++ b/jessyink_transitions.py @@ -22,15 +22,17 @@ 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) + 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() @@ -38,22 +40,33 @@ class Transitions(JessyInkMixin, inkex.EffectExtension): if not self.options.layerName: raise inkex.AbortExtension(_("Please enter a layer name.")) - node = self.svg.getElement(f"//*[@inkscape:groupmode='layer' " - f"and @inkscape:label='{self.options.layerName}']") + node = self.svg.getElement( + f"//*[@inkscape:groupmode='layer' " + f"and @inkscape:label='{self.options.layerName}']" + ) if node is None: - raise inkex.AbortExtension(_(f"Layer '{self.options.layerName}' not found.")) + raise inkex.AbortExtension( + _(f"Layer '{self.options.layerName}' not found.") + ) 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)) + 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)) + node.set( + "jessyink:transitionOut", + Style(name=self.options.effectOut, length=length), + ) + -if __name__ == '__main__': +if __name__ == "__main__": Transitions().run() diff --git a/jessyink_uninstall.py b/jessyink_uninstall.py index da9f9135..4bdbca07 100755 --- a/jessyink_uninstall.py +++ b/jessyink_uninstall.py @@ -20,16 +20,18 @@ 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) + 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. @@ -63,5 +65,5 @@ class Uninstall(JessyInkMixin, inkex.EffectExtension): # Create effect instance. -if __name__ == '__main__': +if __name__ == "__main__": Uninstall().run() diff --git a/jessyink_video.py b/jessyink_video.py index 0b783d7d..63b0afba 100755 --- a/jessyink_video.py +++ b/jessyink_video.py @@ -23,28 +23,38 @@ 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') + 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.")) + 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")) + 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')) + 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()) + elem = layer.add( + root.getElement("//svg:g[@jessyink:element='core.video']").copy() + ) node_dict = find_internal_links(elem, root, {}) delete_ids(elem) @@ -54,30 +64,33 @@ class Video(JessyInkMixin, inkex.EffectExtension): 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()): + for entry in re.findall(rb"url\(#.*\)", node.tostring()): entry = entry.decode() - link_id = entry[5:len(entry) - 1] + 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 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 "id" in entry.attrib: + del entry.attrib["id"] + -if __name__ == '__main__': +if __name__ == "__main__": Video().run() diff --git a/jessyink_view.py b/jessyink_view.py index d772c338..2283d5e0 100755 --- a/jessyink_view.py +++ b/jessyink_view.py @@ -21,13 +21,15 @@ 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) + 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() @@ -35,31 +37,41 @@ class View(JessyInkMixin, inkex.EffectExtension): 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")) + 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]"): + 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), - )) + 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') + self.attr_remove("effectIn") + self.attr_remove("effectOut") else: - self.attr_remove('view') + self.attr_remove("view") + -if __name__ == '__main__': +if __name__ == "__main__": View().run() diff --git a/jitternodes.py b/jitternodes.py index cea1cf80..7fcc69d0 100755 --- a/jitternodes.py +++ b/jitternodes.py @@ -26,14 +26,23 @@ 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=False, 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") + pars.add_argument( + "--ctrl", type=inkex.Boolean, default=False, 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): @@ -42,8 +51,8 @@ class JitterNodes(inkex.EffectExtension): closed = subpath[0] == subpath[-1] for index, csp in enumerate(subpath): if closed and index == len(subpath) - 1: - subpath[index] = subpath[0] - break + subpath[index] = subpath[0] + break if self.options.end: delta = self.randomize([0, 0]) csp[0][0] += delta[0] @@ -76,20 +85,23 @@ class JitterNodes(inkex.EffectExtension): # 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) + 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) + 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__': + +if __name__ == "__main__": JitterNodes().run() diff --git a/layer2png.py b/layer2png.py index 9b28d31f..1f3e3c9b 100755 --- a/layer2png.py +++ b/layer2png.py @@ -2,21 +2,21 @@ # 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. @@ -54,27 +54,37 @@ 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 + 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( + "--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( + "--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): @@ -83,7 +93,9 @@ class ExportSlices(inkex.EffectExtension): nodes = self.get_layer_nodes(self.options.layer) if nodes is None: - raise inkex.AbortExtension("Slice: '{}' does not exist.".format(self.options.layer)) + raise inkex.AbortExtension( + "Slice: '{}' does not exist.".format(self.options.layer) + ) # set opacity to zero in slices for node in nodes: @@ -92,8 +104,8 @@ class ExportSlices(inkex.EffectExtension): # 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: + (_, 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 @@ -120,17 +132,16 @@ class ExportSlices(inkex.EffectExtension): """ # get layer we intend to slice slice_node = None - slice_layer = self.svg.findall('svg:g') + slice_layer = self.svg.findall("svg:g") for node in slice_layer: - label_value = node.label + 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.findall("svg:rect") return slice_node - def clear_color(self, node): """ set opacity to zero, and stroke to none @@ -144,7 +155,7 @@ class ExportSlices(inkex.EffectExtension): def change_color(self, node): """ set color from color_map and set opacity to 25% - + """ node_id = node.attrib["id"] color = self.color_map[node_id] @@ -156,13 +167,13 @@ class ExportSlices(inkex.EffectExtension): self.color_map[node_id] = color if color == ExportSlices.GREY: # skipping return - svg_file = self.options.input_file + svg_file = self.options.input_file inkscape(svg_file, **kwargs) def get_color_and_command_kwargs(self, node, height=None, width=None): directory = self.options.directory - node_id = node.attrib['id'] - size = '' if height is None else '-{}x{}'.format(width, height) + node_id = node.attrib["id"] + size = "" if height is None else "-{}x{}".format(width, height) file_name = "{}{}.png".format(node_id, size) filename = os.path.join(directory, file_name) color = ExportSlices.GREY # skipping @@ -170,15 +181,19 @@ class ExportSlices(inkex.EffectExtension): color = ExportSlices.RED # overwritten if not os.path.exists(filename): color = ExportSlices.GREEN # new export - kwargs = {'export-id': node_id, 'export-filename': filename, - 'export-dpi': self.options.dpi} + kwargs = { + "export-id": node_id, + "export-filename": filename, + "export-dpi": self.options.dpi, + } if width: - kwargs['export-height'] = str(height) - kwargs['export-width'] = str(width) + kwargs["export-height"] = str(height) + kwargs["export-width"] = str(width) return color, kwargs else: inkex.errormsg("Export exists ({}) not overwriting".format(filename)) return color, {} + if __name__ == "__main__": ExportSlices().run() diff --git a/layers2svgfont.py b/layers2svgfont.py index 601c9d82..be4b62a8 100755 --- a/layers2svgfont.py +++ b/layers2svgfont.py @@ -22,8 +22,10 @@ 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: @@ -47,11 +49,11 @@ class LayersToSvgFont(inkex.EffectExtension): 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 = 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 = 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)) @@ -59,11 +61,13 @@ class LayersToSvgFont(inkex.EffectExtension): fontface.set("ascent", str(ascender)) fontface.set("descent", str(descender)) - for group in self.svg.findall('svg:g'): + 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 = font.get_or_create( + "svg:glyph[@unicode='{}']".format(unicode_char), Glyph + ) glyph.set("unicode", unicode_char) ############################ @@ -89,9 +93,10 @@ class LayersToSvgFont(inkex.EffectExtension): # Using curve description in d attribute of svg:glyph path_d = "" - for path in group.findall('svg:path'): + for path in group.findall("svg:path"): path_d += " " + self.flip_cordinate_system(path, emsize, baseline) glyph.set("d", path_d) -if __name__ == '__main__': + +if __name__ == "__main__": LayersToSvgFont().run() diff --git a/layout_nup.py b/layout_nup.py index 5799f55c..f20e2c71 100755 --- a/layout_nup.py +++ b/layout_nup.py @@ -23,53 +23,71 @@ 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') + 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', ]: + for i in [ + "showHolder", + "showCrosses", + "showInner", + "showOuter", + "showInnerBox", + "showOuterBox", + ]: if getattr(self.options, i): - show_list.append(i.lower().replace('show', '')) + 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), + 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), + padding=( + opt.paddingTop, + opt.paddingRight, + opt.paddingBottom, + opt.paddingLeft, + ), show=show_list, ) if ret: @@ -86,42 +104,51 @@ class Nup(inkex.OutputExtension, SvgOutputMixin): 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)) + 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'], - ): + 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']) + '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) @@ -140,22 +167,39 @@ class Nup(inkex.OutputExtension, SvgOutputMixin): 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] - ) + 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]) + sep = ( + size[width] + margin[right] + margin[left], + size[height] + margin[top] + margin[bottom], + ) - style = 'stroke:#000000;stroke-opacity:1;fill:none;fill-opacity:1;' + style = "stroke:#000000;stroke-opacity:1;fill:none;fill-opacity:1;" padbox = Rectangle( x=str(page_edge[left] + margin[left] + padding[left]), @@ -181,75 +225,92 @@ class Nup(inkex.OutputExtension, SvgOutputMixin): if row == 0 and col == 0: continue use = under.add(Use()) - use.set('xlink:href', '#' + to) + 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: + 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: + 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') + 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"}.intersection(show): + layer = svg.add(inkex.Layer.new("Cut Layer")) - if 'crosses' in show: + if "crosses" in show: crosslen = 12 - group = layer.add(inkex.Group(id='cross')) + 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')) + 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') + 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') + 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')) + layer = svg.add(inkex.Layer.new("Main Layer")) + group = layer.add(inkex.Group(id="main")) - if 'innerbox' in show: + if "innerbox" in show: group.add(padbox) - if 'outerbox' in show: + 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' + 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.0, y) + path += " L{:f} {:f}".format(x + w, y + h / 2.0) + path += " L{:f} {:f}".format(x + w / 2.0, y + h) + path += " L{:f} {:f}".format(x, y + h / 2.0) + path += " Z" group.add(inkex.PathElement(style=style, d=path)) return svg.tostring() -if __name__ == '__main__': +if __name__ == "__main__": Nup().run() diff --git a/lindenmayer.py b/lindenmayer.py index 7ff76e15..04e896a0 100755 --- a/lindenmayer.py +++ b/lindenmayer.py @@ -22,22 +22,39 @@ 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( + "--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( + "--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") + 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]) + 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() @@ -48,32 +65,46 @@ class Lindenmayer(inkex.GenerateExtension): self.turtle.setpos(point) self.turtle.pd() for c in string: - if c in 'ABCDEF': + 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.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.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 == '[': + elif c == "[": self.stack.append([self.turtle.getpos(), self.turtle.getheading()]) - elif c == ']': + 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 = '' + level_string = "" for c in rule: if level < self.options.order: try: - level_string = level_string + self.__recurse(self.rules[c], level + 1) + level_string = level_string + self.__recurse( + self.rules[c], level + 1 + ) except KeyError: level_string = level_string + c else: @@ -81,13 +112,18 @@ class Lindenmayer(inkex.GenerateExtension): 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'} + 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__': +if __name__ == "__main__": Lindenmayer().run() diff --git a/lorem_ipsum.py b/lorem_ipsum.py index f97d6c69..a20e2c9d 100755 --- a/lorem_ipsum.py +++ b/lorem_ipsum.py @@ -28,179 +28,185 @@ import inkex from inkex import Layer, FlowRoot, FlowRegion, FlowPara, Rectangle, TextElement, Tspan 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. ' + "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( + "--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") - pars.add_argument("--svg2", help="Use SVG2 flowed text", default=True, type=inkex.Boolean) + pars.add_argument( + "--svg2", help="Use SVG2 flowed text", default=True, type=inkex.Boolean + ) def make_paragraph(self, text_index=0): """Make a paragraph""" @@ -218,21 +224,26 @@ class LoremIpsum(inkex.EffectExtension): """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)) + para.text = "".join(self.make_paragraph(text_index)) node.append(FlowPara()) def add_text_svg2(self, node): """Add paragraphs to SVG2 flowed text node""" tspan = node.add(Tspan()) - newtext = '\n\n'.join([''.join(self.make_paragraph(text_index)) - for text_index in range(self.options.num)]) + newtext = "\n\n".join( + [ + "".join(self.make_paragraph(text_index)) + for text_index in range(self.options.num) + ] + ) tspan.text = newtext + def get_layer(self): - """Returns the current layer if set, otherwise creates a new layer to the document and - returns it """ + """Returns the current layer if set, otherwise creates a new layer to the document and + returns it""" parent = self.svg.get_current_layer() if parent is None: - parent = self.svg.add(Layer.new('lorum ipsum')) + parent = self.svg.add(Layer.new("lorum ipsum")) return parent def create_text_svg2(self, shape): @@ -250,11 +261,12 @@ class LoremIpsum(inkex.EffectExtension): textelement.style["white-space"] = "pre" textelement.style["font-size"] = self.svg.viewport_to_unit("8pt") self.add_text_svg2(textelement) + def create_text_svg12(self, shape): """Creates a new SVG1.2 flowed text with the given shape inside. If no shape inside was set, the flowed text is appended to the selected layer""" root = FlowRoot() - root.set('xml:space', 'preserve') + root.set("xml:space", "preserve") root.style["font-size"] = self.svg.viewport_to_unit("8pt") region = root.add(FlowRegion()) if shape is not None and not isinstance(shape, TextElement): @@ -264,9 +276,14 @@ class LoremIpsum(inkex.EffectExtension): else: # Nothing selected, create a new flowtext parent = self.get_layer() - shape = region.add(Rectangle(x='0', y='0',\ - width=str(int(self.svg.viewbox_width)),\ - height=str(int(self.svg.viewbox_height)))) + shape = region.add( + Rectangle( + x="0", + y="0", + width=str(int(self.svg.viewbox_width)), + height=str(int(self.svg.viewbox_height)), + ) + ) parent.add(root) self.add_text_svg12(root) @@ -279,20 +296,21 @@ class LoremIpsum(inkex.EffectExtension): for node in self.svg.selection.filter(TextElement): shape = node.style.get("shape-inside") inlinesize = node.style.get("inline-size") - if (shape is not None and self.svg.getElementById(shape[4:-1]) is not None) \ - or inlinesize is not None: + if ( + shape is not None and self.svg.getElementById(shape[4:-1]) is not None + ) or inlinesize is not None: self.add_text_svg2(node) done = True if done: return - + shape = self.svg.selection.first() - if (self.options.svg2): + if self.options.svg2: self.create_text_svg2(shape) else: self.create_text_svg12(shape) -if __name__ == '__main__': +if __name__ == "__main__": LoremIpsum().run() diff --git a/markers_strokepaint.py b/markers_strokepaint.py index d1e9bf9e..aa6a99d9 100755 --- a/markers_strokepaint.py +++ b/markers_strokepaint.py @@ -23,30 +23,71 @@ import inkex from inkex.localization import inkex_gettext as _ -MARKERS = ['marker', 'marker-start', 'marker-mid', 'marker-end'] +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="solid", - 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") + 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="solid", + 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""" @@ -56,8 +97,8 @@ class MarkersStrokePaint(inkex.EffectExtension): def method_object(self, style): """Use object colors""" - fill = style.get_color('fill') - stroke = style.get_color('stroke') + fill = style.get_color("fill") + stroke = style.get_color("stroke") if self.options.fill_type == "solid": fill = stroke @@ -92,16 +133,17 @@ class MarkersStrokePaint(inkex.EffectExtension): marker_node = marker_node.copy() self.svg.defs.append(marker_node) marker_id = self.svg.get_unique_id(marker_id) - marker_node.set('id', marker_id) - marker_node.set('inkscape:stockid', marker_id) + marker_node.set("id", marker_id) + marker_node.set("inkscape:stockid", marker_id) node.style[attr] = marker_node for child in marker_node: if stroke is not None: - child.style.set_color(stroke, 'stroke') + child.style.set_color(stroke, "stroke") if fill is not None: - child.style.set_color(fill, 'fill') + child.style.set_color(fill, "fill") + -if __name__ == '__main__': +if __name__ == "__main__": MarkersStrokePaint().run() diff --git a/measure.py b/measure.py index 8af97d67..718af510 100755 --- a/measure.py +++ b/measure.py @@ -33,35 +33,70 @@ 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="default", 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( + "--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="default", 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=12,\ - 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="px",\ - 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.0,\ - help="Scale Factor (Drawing:Real Length)") + pars.add_argument( + "-f", + "--fontsize", + type=int, + default=12, + 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="px", 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.0, + help="Scale Factor (Drawing:Real Length)", + ) def effect(self): # get number of digits prec = int(self.options.precision) - scale = self.svg.viewport_to_unit("1" + self.svg.document_unit) # convert to document units + scale = self.svg.viewport_to_unit( + "1" + self.svg.document_unit + ) # convert to document units self.options.offset *= scale factor = self.svg.unit_to_viewport(1, self.options.unit) @@ -72,7 +107,7 @@ class MeasureLength(inkex.EffectExtension): raise inkex.AbortExtension("Please select at least one path object.") for node in filtered: csp = node.path.transform(node.composed_transform()).to_superpath() - inverse_parent_transform = - node.getparent().composed_transform() + inverse_parent_transform = -node.getparent().composed_transform() if self.options.mtype == "length": slengths, stotal = csplength(csp) self.group = node.getparent().add(TextElement()) @@ -85,7 +120,7 @@ class MeasureLength(inkex.EffectExtension): 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.group.set("id", "MassCenter_" + node.get("id")) self.add_cross(self.group, xc, yc, scale) self.group.transform = inverse_parent_transform continue @@ -97,47 +132,81 @@ class MeasureLength(inkex.EffectExtension): def method_textonpath(self, node, lenstr): startOffset = self.options.startOffset if startOffset == "custom": - startOffset = str(self.options.startOffsetCustom) + '%' + 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) + 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) + 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') + _id = node.get("id") csp = node.path.transform(node.composed_transform()).to_superpath() if self.options.position == "mass": tx, ty = cspcofm(csp) - anchor = 'middle' + anchor = "middle" elif self.options.position == "center": bbox = node.bounding_box(True) tx, ty = bbox.center - anchor = 'middle' + anchor = "middle" else: # default tx = csp[0][0][1][0] ty = csp[0][0][1][1] - anchor = 'start' + 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) + 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) + 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], + "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 + current_preset = "default_" + self.options.mtype else: current_preset = self.options.presetFormat @@ -151,19 +220,30 @@ class MeasureLength(inkex.EffectExtension): 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)) + 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'} + 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.set("startOffset", startOffset) + new.set("dy", str(dy)) # dubious merit # new.append(tp) if text[-2:] == "^2": new.append(Tspan.superscript("2")) @@ -171,27 +251,35 @@ class MeasureLength(inkex.EffectExtension): else: new.text = str(text) # node.set('transform','rotate(180,'+str(-x)+','+str(-y)+')') - node.set('x', str(x)) - node.set('y', 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': self.svg.viewport_to_unit(self.options.fontsize), - 'fill-opacity': '1.0', 'stroke': 'none', - 'font-weight': 'normal', 'font-style': 'normal', 'fill': '#000000'} + new.set("sodipodi:role", "line") + s = { + "text-align": "center", + "vertical-align": "bottom", + "text-anchor": anchor, + "font-size": self.svg.viewport_to_unit(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)) + 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)) - node.transform = - node.getparent().composed_transform() @ node.transform + node.set("x", str(x)) + node.set("y", str(y)) + node.set("transform", "rotate(%s, %s, %s)" % (angle, x, y)) + node.transform = -node.getparent().composed_transform() @ node.transform + -if __name__ == '__main__': +if __name__ == "__main__": MeasureLength().run() diff --git a/media_zip.py b/media_zip.py index 9881bffb..baf7f528 100755 --- a/media_zip.py +++ b/media_zip.py @@ -54,10 +54,12 @@ except ImportError: # PY3 from urllib.parse import urlparse from urllib.request import url2pathname -ENCODING = "cp437" if os.name == 'nt' else "latin-1" +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") @@ -69,10 +71,10 @@ class CompressedMedia(inkex.OutputExtension): """ 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') + 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) @@ -87,11 +89,13 @@ class CompressedMedia(inkex.OutputExtension): 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)) + z.write( + os.path.join(self.tmp_dir, absref), image_path.encode(ENCODING) + ) else: - inkex.errormsg('Could not locate file: %s' % absref) + inkex.errormsg("Could not locate file: %s" % absref) - node.set('xlink:href', image_path) + node.set("xlink:href", image_path) def collect_svg(self, docstripped, z): """ @@ -99,9 +103,9 @@ class CompressedMedia(inkex.OutputExtension): and add it to the temporary compressed file """ dst_file = os.path.join(self.tmp_dir, docstripped) - with open(dst_file, 'wb') as stream: + with open(dst_file, "wb") as stream: self.document.write(stream) - z.write(dst_file, docstripped + '.svg') + z.write(dst_file, docstripped + ".svg") def is_text(self, node): """ @@ -116,19 +120,19 @@ class CompressedMedia(inkex.OutputExtension): the node is using. """ fonts = [] - s = '' - if 'style' in node.attrib: - s = dict(inkex.Style.parse_str(node.attrib['style'])) + 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']) + 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']) + fonts.append(s["font-family"]) + elif "-inkscape-font-specification" in s: + fonts.append(s["-inkscape-font-specification"]) return fonts def list_fonts(self, z): @@ -147,38 +151,39 @@ class CompressedMedia(inkex.OutputExtension): fonts_found.append(f) findings = sorted(fonts_found) # Write list to the temporary compressed file - filename = 'fontlist.txt' + filename = "fontlist.txt" dst_file = os.path.join(self.tmp_dir, filename) - with open(dst_file, 'w') as stream: + 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)) + 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') + 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', '') + 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: + 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__': + +if __name__ == "__main__": CompressedMedia().run() diff --git a/merge_styles.py b/merge_styles.py index 40c48fcd..018117d9 100755 --- a/merge_styles.py +++ b/merge_styles.py @@ -23,17 +23,24 @@ 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") + 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') + newclass = self.svg.get_unique_id("css") elements = self.svg.selected.values() common = None @@ -48,12 +55,13 @@ class MergeStyles(inkex.EffectExtension): if not common: return inkex.errormsg("There are no common styles between these elements.") - self.svg.stylesheet.add('.' + newclass, inkex.Style(sorted(common))) + 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__': + +if __name__ == "__main__": MergeStyles().run() diff --git a/motion.py b/motion.py index 5bd02d87..9ceab4aa 100755 --- a/motion.py +++ b/motion.py @@ -23,8 +23,19 @@ color can be used for the shadow""" import math import inkex -from inkex.paths import Move, Line, Curve, ZoneClose, Arc, Path, Vert, Horz, TepidQuadratic, \ - Quadratic, Smooth +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 @@ -33,12 +44,27 @@ 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") - pars.add_argument("-f", "--fillwithstroke", type=inkex.Boolean, default=False, \ - help="fill shadow with stroke color if set") + 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", + ) + pars.add_argument( + "-f", + "--fillwithstroke", + type=inkex.Boolean, + default=False, + help="fill shadow with stroke color if set", + ) @staticmethod def makeface(last, segment, facegroup, delx, dely): @@ -49,21 +75,21 @@ class Motion(inkex.EffectExtension): # 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 - ) + 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(), - ]) + 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 @@ -84,7 +110,7 @@ class Motion(inkex.EffectExtension): node.transform = None facegroup.style = node.style - if (self.options.fillwithstroke): + if self.options.fillwithstroke: stroke = facegroup.style("stroke") if stroke is not None and isinstance(stroke, inkex.Color): facegroup.style["fill"] = stroke @@ -99,14 +125,18 @@ class Motion(inkex.EffectExtension): reset_origin = False if isinstance(cmd_proxy.command, ZoneClose): reset_origin = True - self.process_segment(cmd_proxy, facegroup, local_delx, local_dely, first_point) + self.process_segment( + cmd_proxy, facegroup, local_delx, local_dely, first_point + ) @staticmethod def process_segment(cmd_proxy, facegroup, delx, dely, first_point): """Process each segments""" segments = [] - if isinstance(cmd_proxy.command, (Curve, Smooth, TepidQuadratic, Quadratic, Arc)): + 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() @@ -132,11 +162,13 @@ class Motion(inkex.EffectExtension): 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(): + 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__': +if __name__ == "__main__": Motion().run() diff --git a/new_glyph_layer.py b/new_glyph_layer.py index cff9b4d4..983184c9 100755 --- a/new_glyph_layer.py +++ b/new_glyph_layer.py @@ -23,12 +23,13 @@ import sys import inkex + class NewGlyphLayer(inkex.EffectExtension): def add_arguments(self, pars): - self.arg_parser.add_argument("--text", default='', help="Unicode chars") + self.arg_parser.add_argument("--text", default="", help="Unicode chars") self.encoding = sys.stdin.encoding - if self.encoding == 'cp0' or self.encoding is None: + if self.encoding == "cp0" or self.encoding is None: self.encoding = locale.getpreferredencoding() def effect(self): @@ -41,13 +42,14 @@ class NewGlyphLayer(inkex.EffectExtension): 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 + layer = self.svg.add(inkex.Layer.new("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__': + +if __name__ == "__main__": NewGlyphLayer().run() diff --git a/next_glyph_layer.py b/next_glyph_layer.py index ded32b11..2fc8c3b0 100755 --- a/next_glyph_layer.py +++ b/next_glyph_layer.py @@ -20,15 +20,17 @@ 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'): + for group in self.svg.findall("svg:g"): if "GlyphLayer-" in group.label: glyphs.append(group) - if group.style.get('display', '') == "inline": + if group.style.get("display", "") == "inline": count += 1 current = len(glyphs) - 1 @@ -41,7 +43,8 @@ class NextLayer(inkex.EffectExtension): def process_glyphs(glyphs, current): """Process the glyphs""" glyphs[current].set("style", "display:none") - glyphs[(current+1)%len(glyphs)].set("style", "display:inline") + glyphs[(current + 1) % len(glyphs)].set("style", "display:inline") + -if __name__ == '__main__': +if __name__ == "__main__": NextLayer().run() diff --git a/nicechart.py b/nicechart.py index 6f5975a4..1dabc7f3 100755 --- a/nicechart.py +++ b/nicechart.py @@ -57,67 +57,162 @@ 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"], + "red": [ + "#460101", + "#980101", + "#d40000", + "#f44800", + "#fb8b00", + "#eec73e", + "#d9bb7a", + "#fdd99b", + ], "blue": ["#000442", "#0F1781", "#252FB7", "#3A45E1", "#656DDE", "#8A91EC"], - "gray": ["#222222", "#444444", "#666666", "#888888", "#aaaaaa", "#cccccc", "#eeeeee"], + "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"] + "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) + 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='apples:3,bananas:5,oranges:10,pears:4', 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=False, help="Blur Type") + pars.add_argument("--tab") + pars.add_argument("--encoding", default="utf-8") + pars.add_argument( + "-w", + "--what", + default="apples:3,bananas:5,oranges:10,pears:4", + 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=False, 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("-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=False, - 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( + "--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=False, + 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='#000000', help="font color of description") - - pars.add_argument("-V", "--show_values", type=inkex.Boolean, default=False, - help="Show values in chart") + 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="#000000", 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""" @@ -128,7 +223,9 @@ class NiceChart(inkex.GenerateExtension): """Confirm the values from files or direct""" val = float(val) if val < 0: - raise inkex.AbortExtension("Negative values are currently not supported!") + raise inkex.AbortExtension( + "Negative values are currently not supported!" + ) return val if self.options.input_type == "file": @@ -144,12 +241,16 @@ class NiceChart(inkex.GenerateExtension): header = next(reader) title = header[col_val] - values = [(line[col_key], process_value(line[col_val])) for line in reader] + 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]) + (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") @@ -158,15 +259,15 @@ class NiceChart(inkex.GenerateExtension): 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')) + 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') + filt.add_primitive("feGaussianBlur", stdDeviation="1.1") return inkex.Style(filter=filt.get_id(as_url=2)) return inkex.Style() def get_color(self): """Get the next available color""" - if not hasattr(self, '_colors'): + if not hasattr(self, "_colors"): # Generate list of available colours if self.options.colors_override: colors = self.options.colors_override.strip() @@ -174,13 +275,13 @@ class NiceChart(inkex.GenerateExtension): colors = self.options.colors if colors[0].isalpha(): - colors = COLOUR_TABLE.get(colors.lower(), COLOUR_TABLE['red']) + 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'] + colors = COLOUR_TABLE["red"] if self.options.reverse_colors: colors.reverse() @@ -202,8 +303,8 @@ class NiceChart(inkex.GenerateExtension): 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.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: @@ -216,7 +317,7 @@ class NiceChart(inkex.GenerateExtension): 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 = 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 @@ -253,15 +354,19 @@ class NiceChart(inkex.GenerateExtension): # If keys are given, create text elements if keys: - text = self.draw_text(keys[cnt], anchor='end') + 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)) + 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( + "y", str(self.width / 2 + offset + bar_width / 2 + self.fontoff) + ) text.set("x", str(self.height / 2 - self.options.text_offset)) yield text @@ -271,12 +376,21 @@ class NiceChart(inkex.GenerateExtension): 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)) + 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)) + 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)) + 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) @@ -284,7 +398,9 @@ class NiceChart(inkex.GenerateExtension): 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 = Rectangle( + x=str(x + 1), y=str(y + 1), width=str(width), height=str(height) + ) shadow.style = self.blur yield shadow @@ -292,20 +408,20 @@ class NiceChart(inkex.GenerateExtension): rect.set("style", "fill:" + self.get_color()) yield rect - def draw_text(self, text, add_size=0, anchor='start', **kwargs): + 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, + "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 @@ -328,8 +444,8 @@ class NiceChart(inkex.GenerateExtension): # 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.style = self.blur + inkex.Style(fill='#000000') + shadow.set("r", str(pie_radius)) + shadow.style = self.blur + inkex.Style(fill="#000000") yield shadow # Add a grey background circle with a light stroke @@ -362,11 +478,17 @@ class NiceChart(inkex.GenerateExtension): 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 + start -= ( + 0.09 # let the first element overlap into the other direction + ) # then add the slice - pieslice = inkex.PathElement.arc([x, y], pie_radius, pie_radius, start=start, end=end ) - pieslice.set("style", "fill:" + self.get_color() + ";stroke:none;fill-opacity:1") + pieslice = inkex.PathElement.arc( + [x, y], pie_radius, pie_radius, start=start, 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 @@ -376,26 +498,27 @@ class NiceChart(inkex.GenerateExtension): Move( (self.width / 2) + pie_radius * math.cos(ang), (self.height / 2) + pie_radius * math.sin(ang), - ), line( + ), + 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', + "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]) + 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' + anchor = "start" if math.cos(ang) > 0 else "end" text = self.draw_text(label, anchor=anchor) off = pie_radius + self.options.text_offset @@ -440,8 +563,10 @@ class NiceChart(inkex.GenerateExtension): # Create rectangle element shadow = Rectangle( - x=str(x), y=str(shy), - width=str(width), height=str(height), + x=str(x), + y=str(shy), + width=str(width), + height=str(height), ) # Set shadow blur (connect to filter object in xml path) @@ -459,13 +584,13 @@ class NiceChart(inkex.GenerateExtension): # 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("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("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)) @@ -479,25 +604,37 @@ class NiceChart(inkex.GenerateExtension): 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 + 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 + 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 + 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', + "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)) @@ -512,5 +649,5 @@ class NiceChart(inkex.GenerateExtension): yield self.draw_header(self.width / 2 + offset + normedvalue) -if __name__ == '__main__': +if __name__ == "__main__": NiceChart().run() diff --git a/other/gcodetools b/other/gcodetools index 3d697de9..1a28da0e 160000 --- a/other/gcodetools +++ b/other/gcodetools @@ -1 +1 @@ -Subproject commit 3d697de9e3d30c73ef6034e50d260dbe814374b5 +Subproject commit 1a28da0ec0bfcbc89c7e8d15affd364dd8652707 diff --git a/output_scour.py b/output_scour.py index dab0f099..cffeae95 100755 --- a/output_scour.py +++ b/output_scour.py @@ -9,36 +9,44 @@ import inkex try: from packaging.version import Version except ImportError: - raise inkex.DependencyError("""Failed to import module 'packaging'. + raise inkex.DependencyError( + """Failed to import module 'packaging'. Please make sure it is installed (e.g. using 'pip install packaging' or 'sudo apt-get install python3-packaging') and try again. -""") +""" + ) try: import scour from scour.scour import scourString except ImportError: - raise inkex.DependencyError("""Failed to import module 'scour'. + 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 python3-scour') and try again. -""") +""" + ) class ScourInkscape(inkex.OutputExtension): """Scour Inkscape Extension""" - # Scour options + # 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( + "--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-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) @@ -46,12 +54,16 @@ class ScourInkscape(inkex.OutputExtension): 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( + "--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( + "--enable-comment-stripping", type=inkex.Boolean, dest="strip_comments" + ) pars.add_argument("--renderer-workaround", type=inkex.Boolean) # options for internal use of the extension @@ -64,16 +76,19 @@ class ScourInkscape(inkex.OutputExtension): scour_version = scour.__version__ scour_version_min = self.options.scour_version if Version(scour_version) < Version(scour_version_min): - raise inkex.AbortExtension(f""" + raise inkex.AbortExtension( + f""" 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.""") +Note: You can permanently disable this message on the 'About' tab of the extension window.""" + ) 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')) + stream.write(scourString(self.svg.tostring(), self.options).encode("utf8")) + -if __name__ == '__main__': +if __name__ == "__main__": ScourInkscape().run() diff --git a/param_curves.py b/param_curves.py index 25e2f405..3fa30948 100755 --- a/param_curves.py +++ b/param_curves.py @@ -35,8 +35,25 @@ from inkex.utils import math_eval import inkex -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): + +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 @@ -83,17 +100,17 @@ def drawfunction(t_start, t_end, xleft, xright, ybottom, ytop, samples, width, h # check for visibility of x-axis if ybottom <= 0 <= ytop: # xaxis - a.append(['M', [left, coordy(0)]]) - a.append(['l', [width, 0]]) + 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]]) + 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))) + # print("RET: {}".format(f1(1))) x0 = f1(t_start) y0 = f2(t_start) @@ -105,7 +122,7 @@ def drawfunction(t_start, t_end, xleft, xright, ybottom, ytop, samples, width, h dy0 = (y1 - y0) / ds # Start curve - a.append(['M', [coordx(x0), coordy(y0)]]) # initial moveto + 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) @@ -119,11 +136,19 @@ def drawfunction(t_start, t_end, xleft, xright, ybottom, ytop, samples, width, h 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)] - ]) + 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 @@ -136,17 +161,27 @@ 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( + "--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( + "--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=30, 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=False, help="Isotropic scaling") + pars.add_argument( + "--remove", type=inkex.Boolean, default=True, help="Remove rectangle" + ) + pars.add_argument( + "--isoscale", type=inkex.Boolean, default=False, help="Isotropic scaling" + ) pars.add_argument("--drawaxis", type=inkex.Boolean, default=False) pars.add_argument("--tab", default="sampling") @@ -155,31 +190,35 @@ class ParamCurves(inkex.EffectExtension): 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')) + 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.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` + ';' @@ -193,5 +232,5 @@ class ParamCurves(inkex.EffectExtension): node.getparent().remove(node) -if __name__ == '__main__': +if __name__ == "__main__": ParamCurves().run() diff --git a/path_envelope.py b/path_envelope.py index 48ccbce5..3fc263e6 100755 --- a/path_envelope.py +++ b/path_envelope.py @@ -22,8 +22,10 @@ from inkex.transforms import DirectedLineSegment from inkex.localization import inkex_gettext as _ from operator import truediv + 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.")) @@ -36,25 +38,43 @@ class Envelope(inkex.EffectExtension): bbox = obj.bounding_box(obj.getparent().composed_transform()) # distill trafo into four node points - path = envelope.path.transform(envelope.composed_transform()).to_superpath() + path = envelope.path.transform( + envelope.composed_transform() + ).to_superpath() tbox = self.envelope_box_from_path(path) 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.")) + 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.")) + 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 envelope_box_from_path(self, envelope_path): if len(envelope_path) < 1 or len(envelope_path[0]) < 4: - raise inkex.AbortExtension(_("Second 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 envelope_path][0][:4] - #vectors pointing away from the trafo origin + raise inkex.AbortExtension( + _("Second 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 envelope_path][0][ + :4 + ] + # vectors pointing away from the trafo origin tbox = [ DirectedLineSegment(trafo[0], trafo[1]), DirectedLineSegment(trafo[1], trafo[2]), @@ -62,8 +82,15 @@ class Envelope(inkex.EffectExtension): DirectedLineSegment(trafo[0], trafo[3]), ] vects = [segment.vector for segment in tbox] - if 0.0 == vects[0].cross(vects[1]) == vects[1].cross(vects[2]) == vects[2].cross(vects[3]): - raise inkex.AbortExtension(_("The points for the selected envelope must not all be in a line.")) + if ( + 0.0 + == vects[0].cross(vects[1]) + == vects[1].cross(vects[2]) + == vects[2].cross(vects[3]) + ): + raise inkex.AbortExtension( + _("The points for the selected envelope must not all be in a line.") + ) return tbox def process_object(self, obj, tbox, bbox): @@ -80,7 +107,11 @@ class Envelope(inkex.EffectExtension): 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() + points = ( + element.path.to_absolute() + .transform(element.composed_transform()) + .to_superpath() + ) for subs in points: for csp in subs: @@ -96,8 +127,12 @@ class Envelope(inkex.EffectExtension): """Transform algorithm thanks to Jose Hevia (freon)""" vector = (x, y) - bbox.minimum xratio, yratio = map(truediv, vector, (bbox.width, 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)) + 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) + ) denom = horz.vector.cross(vert.vector) if denom == 0.0: # Degenerate cases of intersecting envelope edges @@ -119,5 +154,6 @@ class Envelope(inkex.EffectExtension): intersect_ratio = (vert.start - horz.start).cross(vert.vector) / denom return horz.point_at_ratio(intersect_ratio) -if __name__ == '__main__': + +if __name__ == "__main__": Envelope().run() diff --git a/path_mesh_m2p.py b/path_mesh_m2p.py index 9968c1a7..bb27fec3 100755 --- a/path_mesh_m2p.py +++ b/path_mesh_m2p.py @@ -25,10 +25,8 @@ from inkex.elements import MeshGradient # globals EPSILON = 1e-3 -MG_PROPS = [ - 'fill', - 'stroke' -] +MG_PROPS = ["fill", "stroke"] + def isclose(a, b, rel_tol=1e-09, abs_tol=0.0): """Test approximate equality. @@ -38,7 +36,7 @@ def isclose(a, b, rel_tol=1e-09, abs_tol=0.0): 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) + return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) def reverse_path(csp): @@ -54,8 +52,7 @@ 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)): + 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: @@ -66,7 +63,7 @@ def join_path(csp1, sp1, csp2, sp2): def is_url(val): """Check whether attribute value is linked resource.""" - return val.startswith('url(#') + return val.startswith("url(#") def mesh_corners(meshgradient): @@ -74,10 +71,10 @@ def mesh_corners(meshgradient): 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')) + 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 = [[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): @@ -86,23 +83,23 @@ def mesh_corners(meshgradient): if meshrow == 0: first_corner = corners[meshrow][meshpatch] if meshrow > 0: - first_corner = corners[meshrow][meshpatch+1] + first_corner = corners[meshrow][meshpatch + 1] # parse path of meshpatch edges - path = 'M {},{}'.format(*first_corner) + path = "M {},{}".format(*first_corner) for edge in meshgradient[meshrow][meshpatch]: - path = ' '.join([path, edge.get('path')]) + 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] + 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] + 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] + 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] + corners[meshrow + 1][meshpatch] = csp[0][2][1] # append to list of meshpatch csp meshpatch_csps.append(csp) return corners, meshpatch_csps @@ -113,47 +110,47 @@ def mesh_hvlines(meshgradient): 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)] + 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') + hlines[meshrow][meshpatch] = edge.get("path") edge = meshgradient[meshrow][meshpatch][2] - hlines[meshrow+1][meshpatch] = edge.get('path') + hlines[meshrow + 1][meshpatch] = edge.get("path") if meshrow > 0: edge = meshgradient[meshrow][meshpatch][1] - hlines[meshrow+1][meshpatch] = edge.get('path') + hlines[meshrow + 1][meshpatch] = edge.get("path") # vertical edges if meshrow == 0: edge = meshgradient[meshrow][meshpatch][1] - vlines[meshpatch+1][meshrow] = edge.get('path') + vlines[meshpatch + 1][meshrow] = edge.get("path") if meshpatch == 0: edge = meshgradient[meshrow][meshpatch][3] - vlines[meshpatch][meshrow] = edge.get('path') + vlines[meshpatch][meshrow] = edge.get("path") if meshrow > 0: edge = meshgradient[meshrow][meshpatch][0] - vlines[meshpatch+1][meshrow] = edge.get('path') + vlines[meshpatch + 1][meshrow] = edge.get("path") if meshpatch == 0: edge = meshgradient[meshrow][meshpatch][2] - vlines[meshpatch][meshrow] = edge.get('path') + 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]) + path = "M {},{}".format(*corners[0][0]) for edge_path in hlines[0]: - path = ' '.join([path, edge_path]) + path = " ".join([path, edge_path]) for edge_path in vlines[-1]: - path = ' '.join([path, edge_path]) + path = " ".join([path, edge_path]) for edge_path in reversed(hlines[-1]): - path = ' '.join([path, edge_path]) + path = " ".join([path, edge_path]) for edge_path in reversed(vlines[0]): - path = ' '.join([path, edge_path]) + path = " ".join([path, edge_path]) outline_csps.append(inkex.Path(path).to_superpath()) return outline_csps @@ -164,24 +161,24 @@ def mesh_to_grid(corners, hlines, vlines): cols = len(corners[0]) - 1 gridline_csps = [] # horizontal - path = 'M {},{}'.format(*corners[0][0]) + path = "M {},{}".format(*corners[0][0]) for edge_path in hlines[0]: - path = ' '.join([path, edge_path]) + 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 i in range(1, rows + 1): + path = "M {},{}".format(*corners[i][-1]) for edge_path in reversed(hlines[i]): - path = ' '.join([path, edge_path]) + path = " ".join([path, edge_path]) gridline_csps.append(inkex.Path(path).to_superpath()) # vertical - path = 'M {},{}'.format(*corners[-1][0]) + path = "M {},{}".format(*corners[-1][0]) for edge_path in reversed(vlines[0]): - path = ' '.join([path, edge_path]) + 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 j in range(1, cols + 1): + path = "M {},{}".format(*corners[0][j]) for edge_path in vlines[j]: - path = ' '.join([path, edge_path]) + path = " ".join([path, edge_path]) gridline_csps.append(inkex.Path(path).to_superpath()) return gridline_csps @@ -197,34 +194,34 @@ def mesh_to_faces(corners, hlines, vlines): face = [] # init edge paths edge_t = hlines[row][col] - edge_b = hlines[row+1][col] + edge_b = hlines[row + 1][col] edge_l = vlines[col][row] - edge_r = vlines[col+1][row] + edge_r = vlines[col + 1][row] # top edge, first if row == 0: - path = 'M {},{}'.format(*corners[row][col]) - path = ' '.join([path, edge_t]) + 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]) + 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]) + 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]) + 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]) + 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]) + 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) @@ -233,22 +230,25 @@ def mesh_to_faces(corners, hlines, vlines): 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'): + 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): + if res_type == "meshgradient" and isinstance( + paint_server, MeshGradient + ): result.append(paint_server) return result - def process_style(self, node, res_type='meshgradient'): + def process_style(self, node, res_type="meshgradient"): """Process style of *node*.""" result = node.specified_style() result = self.process_props(result, res_type) @@ -257,7 +257,7 @@ class MeshToPath(inkex.EffectExtension): def find_meshgradients(self, node): """Parse node style, return list with linked meshgradients.""" - return self.process_style(node, res_type='meshgradient') + return self.process_style(node, res_type="meshgradient") # ----- Process meshgradient definitions @@ -269,8 +269,8 @@ class MeshToPath(inkex.EffectExtension): mode = self.options.mode # gradient units - mesh_units = meshgradient.get('gradientUnits', 'objectBoundingBox') - if mesh_units == 'objectBoundingBox': + mesh_units = meshgradient.get("gradientUnits", "objectBoundingBox") + if mesh_units == "objectBoundingBox": # TODO: position and scale based on "objectBoundingBox" units return @@ -280,15 +280,15 @@ class MeshToPath(inkex.EffectExtension): # parse meshpatches, calculate absolute corner coords corners, meshpatch_csps = mesh_corners(meshgradient) - if mode == 'meshpatches': + if mode == "meshpatches": return meshpatch_csps, transform else: hlines, vlines = mesh_hvlines(meshgradient) - if mode == 'outline': + if mode == "outline": return mesh_to_outline(corners, hlines, vlines), transform - elif mode == 'gridlines': + elif mode == "gridlines": return mesh_to_grid(corners, hlines, vlines), transform - elif mode == 'faces': + elif mode == "faces": return mesh_to_faces(corners, hlines, vlines), transform # ----- Convert meshgradient definitions @@ -296,12 +296,12 @@ class MeshToPath(inkex.EffectExtension): 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.viewport_to_unit('1px') - stroke_color = '#000000' + stroke_width = self.svg.viewport_to_unit("1px") + stroke_color = "#000000" style = { - 'fill': 'none', - 'stroke': stroke_color, - 'stroke-width': str(stroke_width), + "fill": "none", + "stroke": stroke_color, + "stroke-width": str(stroke_width), } group = inkex.Group() @@ -313,9 +313,9 @@ class MeshToPath(inkex.EffectExtension): elem = group.add(inkex.PathElement()) elem.style = style elem.path = inkex.CubicSuperPath(csp) - if self.options.mode == 'outline': + if self.options.mode == "outline": elem.path.close() - elif self.options.mode == 'faces': + elif self.options.mode == "faces": if len(csp) == 1 and len(csp[0]) == 5: elem.path.close() return group @@ -339,8 +339,8 @@ class MeshToPath(inkex.EffectExtension): # add result (group) to document if result is not None: index = node.getparent().index(node) - node.getparent().insert(index+1, result) + node.getparent().insert(index + 1, result) -if __name__ == '__main__': +if __name__ == "__main__": MeshToPath().run() diff --git a/path_mesh_p2m.py b/path_mesh_p2m.py index 4171f713..a346fc60 100755 --- a/path_mesh_p2m.py +++ b/path_mesh_p2m.py @@ -24,15 +24,17 @@ 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') + meshgradient.set_random_id("meshgradient") return meshgradient.get_id() def effect(self): @@ -53,14 +55,14 @@ class PathToMesh(inkex.EffectExtension): mesh_id = self.add_mesh(meshgradient) # apply meshgradient to node if mesh_id is not None: - node.style['fill'] = 'url(#{})'.format(mesh_id) + 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'] + 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 @@ -75,7 +77,7 @@ class PathToMesh(inkex.EffectExtension): edges = [] for i, corner in enumerate(subpath[:4]): corners.append(corner[1]) - edge = [list(subpath[i]), list(subpath[i+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]): @@ -85,5 +87,6 @@ class PathToMesh(inkex.EffectExtension): return corners, edges -if __name__ == '__main__': + +if __name__ == "__main__": PathToMesh().run() diff --git a/path_number_nodes.py b/path_number_nodes.py index 76e69cf5..1f7dc1c7 100755 --- a/path_number_nodes.py +++ b/path_number_nodes.py @@ -22,13 +22,21 @@ 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( + "--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( + "--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): @@ -44,18 +52,26 @@ class NumberNodes(inkex.EffectExtension): dot_group = group.add(inkex.Group()) num_group = group.add(inkex.Group()) path_trans_applied = node.path.transform(node.composed_transform()) - group.transform = - node.getparent().composed_transform() + group.transform = -node.getparent().composed_transform() - style = inkex.Style({'stroke': 'none', 'fill': '#000'}) + style = inkex.Style({"stroke": "none", "fill": "#000"}) for step, (x, y) in enumerate(path_trans_applied.end_points): - circle = dot_group.add(Circle(cx=str(x), cy=str(y),\ - r=str(self.svg.unittouu(self.options.dotsize) / 2))) + 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))) + 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() @@ -64,13 +80,15 @@ class NumberNodes(inkex.EffectExtension): 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'} + "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__': + +if __name__ == "__main__": NumberNodes().run() diff --git a/path_to_absolute.py b/path_to_absolute.py index 4db9d988..13d49292 100755 --- a/path_to_absolute.py +++ b/path_to_absolute.py @@ -22,8 +22,10 @@ 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(): @@ -31,5 +33,6 @@ class ToAbsolute(inkex.EffectExtension): node = node.replace_with(node.to_path_element()) node.path = node.path.to_absolute().to_superpath().to_path() -if __name__ == '__main__': + +if __name__ == "__main__": ToAbsolute().run() diff --git a/pathalongpath.py b/pathalongpath.py index 5d7052ff..8a3b1f6c 100755 --- a/pathalongpath.py +++ b/pathalongpath.py @@ -32,7 +32,7 @@ Now move and bend L to make it fit a skeleton, and see what happens to the norma they move and rotate, deforming the pattern. """ import copy -import math +import math import inkex from inkex.bezier import tpoint @@ -43,20 +43,38 @@ import pathmodifier 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( + "-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=True, - help="duplicate pattern before deformation") + pars.add_argument( + "-v", + "--vertical", + type=inkex.Boolean, + default=False, + help="reference path is vertical", + ) + pars.add_argument( + "-d", + "--duplicate", + type=inkex.Boolean, + default=True, + help="duplicate pattern before deformation", + ) pars.add_argument("--tab", help="The selected UI-tab when OK was pressed") - def apply_diffeomorphism(self, bpt, skelcomp, lengths, isclosed, vects=()): """ The kernel of this stuff: @@ -96,7 +114,7 @@ class PathAlongPath(pathmodifier.PathModifier): if len(self.options.ids) < 2: raise inkex.AbortExtension("This extension requires two selected paths.") - self.options.wave = (self.options.kind == "Ribbon") + self.options.wave = self.options.kind == "Ribbon" if self.options.copymode == "Single": self.options.repeat = False self.options.stretch = False @@ -112,7 +130,7 @@ class PathAlongPath(pathmodifier.PathModifier): patterns, skels = self.get_patterns_and_skeletons(True, self.options.duplicate) bboxes = [pattern.bounding_box() for pattern in patterns.values()] - if None in bboxes: # for texts, we can't compute the bounding box + if None in bboxes: # for texts, we can't compute the bounding box raise inkex.AbortExtension("Please convert texts to path first") bbox = sum(bboxes, None) @@ -123,12 +141,16 @@ class PathAlongPath(pathmodifier.PathModifier): 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") + raise inkex.AbortExtension( + "The total length of the pattern is too small\n" + "Please choose a larger object or set 'Space between copies' > 0" + ) for pattern in patterns.values(): if isinstance(pattern, inkex.PathElement): pattern.apply_transform() - pattern.path = self._do_transform(skels, pattern.path.to_superpath(), delta_x, bbox) + pattern.path = self._do_transform( + skels, pattern.path.to_superpath(), delta_x, bbox + ) def _do_transform(self, skeletons, p0, dx, bbox): if self.options.vertical: @@ -142,9 +164,11 @@ class PathAlongPath(pathmodifier.PathModifier): for comp in cur_skeleton: path = copy.deepcopy(p0) skelcomp, lengths = self.linearize(comp) - - skel_closed = all([math.isclose(i, j) for i, j in zip(skelcomp[0], skelcomp[-1])]) - + + skel_closed = all( + [math.isclose(i, j) for i, j in zip(skelcomp[0], skelcomp[-1])] + ) + length = sum(lengths) xoffset = skelcomp[0][0] - bbox.x.minimum + self.options.toffset yoffset = skelcomp[0][1] - bbox.y.center - self.options.noffset @@ -166,13 +190,21 @@ class PathAlongPath(pathmodifier.PathModifier): 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.") + raise inkex.AbortExtension( + "The 'stretch' option requires that the pattern must have non-zero width :\nPlease edit the pattern width." + ) for sub in path: self.stretch(sub, length / bbox.width, 1, skelcomp[0]) for sub in path: for ctlpt in sub: - self.apply_diffeomorphism(ctlpt[1], skelcomp, lengths, skel_closed, (ctlpt[0], ctlpt[2])) + self.apply_diffeomorphism( + ctlpt[1], + skelcomp, + lengths, + skel_closed, + (ctlpt[0], ctlpt[2]), + ) if self.options.vertical: self.flipxy(path) @@ -180,5 +212,5 @@ class PathAlongPath(pathmodifier.PathModifier): return CubicSuperPath(newp) -if __name__ == '__main__': +if __name__ == "__main__": PathAlongPath().run() diff --git a/pathmodifier.py b/pathmodifier.py index 0c453c72..e4716e1a 100755 --- a/pathmodifier.py +++ b/pathmodifier.py @@ -33,10 +33,12 @@ from inkex import PathElement, Group, Use from inkex.bezier import pointdistance, beziersplitatt # This deprecated API is used by some external extensions. -from inkex.deprecated import zSort # pylint: disable=unused-import +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): @@ -44,7 +46,7 @@ class PathModifier(inkex.EffectExtension): for child in node: if transferTransform: child.transform = mat @ child.transform - elements.update(self.expand_groups({child.get('id'): child})) + 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. @@ -61,8 +63,10 @@ class PathModifier(inkex.EffectExtension): elif isinstance(node, Use): newnode = node.unlink() elements.pop(node_id) - newid = newnode.get('id') - elements.update(self.expand_clones({newid: newnode}, transferTransform, replace)) + newid = newnode.get("id") + elements.update( + self.expand_clones({newid: newnode}, transferTransform, replace) + ) return elements def objects_to_paths(self, elements, replace=True): @@ -71,8 +75,8 @@ class PathModifier(inkex.EffectExtension): elem = node.to_path_element() if replace: node.replace_with(elem) - elem.set('id', node.get('id')) - elements[elem.get('id')] = elem + elem.set("id", node.get("id")) + elements[elem.get("id")] = elem def effect(self): raise NotImplementedError("overwrite this method in subclasses") @@ -82,7 +86,7 @@ class PathModifier(inkex.EffectExtension): path = node.path.to_superpath() # do what ever you want with "path"! node.path = path - + @staticmethod def lengthtotime(l, lengths, isclosed): """ @@ -101,7 +105,7 @@ class PathModifier(inkex.EffectExtension): i += 1 t = l / lengths[min(i, len(lengths) - 1)] return i, t - + @staticmethod def flipxy(path): """Swaps x and y coordinate of all path vertices""" @@ -119,7 +123,7 @@ class PathModifier(inkex.EffectExtension): for pt in ctl: pt[0] += dx pt[1] += dy - + @staticmethod def stretch(pathcomp, xscale, yscale, org): """Stretches a subpath by (xscale, yscale) relative to origin org""" @@ -127,7 +131,7 @@ class PathModifier(inkex.EffectExtension): for pt in ctl: pt[0] = org[0] + (pt[0] - org[0]) * xscale pt[1] = org[1] + (pt[1] - org[1]) * yscale - + @staticmethod def linearize(p, tolerance=0.001): """ @@ -147,10 +151,15 @@ class PathModifier(inkex.EffectExtension): 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) + 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]]]) + 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) @@ -168,7 +177,7 @@ class PathModifier(inkex.EffectExtension): elem = skeletons.pop() if duplicate: elem = elem.duplicate() - + if expand_patterns: patterns = {elem.get_id(): elem} self.expand_clones(patterns, True, False) @@ -181,7 +190,6 @@ class PathModifier(inkex.EffectExtension): return patterns, skeletons.id_dict() - 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. diff --git a/pathscatter.py b/pathscatter.py index c09d8d1f..8edd72cf 100755 --- a/pathscatter.py +++ b/pathscatter.py @@ -34,27 +34,84 @@ from inkex.localization import inkex_gettext as _ import pathmodifier + 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=False, - 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("-r", "--rotate", type=inkex.Boolean, dest="vertical", default=False, - help="reference path is vertical") - self.arg_parser.add_argument("-c", "--copymode", type=str, dest="copymode", default="move", - help="""How the pattern is duplicated. Default: 'move', - Options: 'clone', 'duplicate', 'move'""") - self.arg_parser.add_argument("--tab", type=str, dest="tab", - help="The selected UI-tab when OK was pressed") + 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=False, + 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( + "-r", + "--rotate", + type=inkex.Boolean, + dest="vertical", + default=False, + help="reference path is vertical", + ) + self.arg_parser.add_argument( + "-c", + "--copymode", + type=str, + dest="copymode", + default="move", + help="""How the pattern is duplicated. Default: 'move', + Options: 'clone', 'duplicate', 'move'""", + ) + self.arg_parser.add_argument( + "--tab", + type=str, + dest="tab", + help="The selected UI-tab when OK was pressed", + ) def localTransformAt(self, s, skelcomp, lengths, isclosed, follow=True): """ @@ -75,6 +132,7 @@ class PathScatter(pathmodifier.Diffeo): else: mat = [[1, 0, x], [0, 1, y]] return Transform(mat) + def center_node_at_origin(self, node): """Translates a node to the origin and applies translation if requested""" bbox = node.bounding_box() @@ -85,6 +143,7 @@ class PathScatter(pathmodifier.Diffeo): mat.add_translate([0, self.options.noffset]) node.transform = mat @ node.transform return bbox + def effect(self): if len(self.svg.selection) < 2: @@ -109,8 +168,10 @@ class PathScatter(pathmodifier.Diffeo): if dx < 0.01: if isinstance(original_pattern_node, inkex.TextElement): raise inkex.AbortExtension("Please convert texts to path first") - raise inkex.AbortExtension("The total length of the pattern is too small\n"\ - "Please choose a larger object or set 'Space between copies' > 0") + raise inkex.AbortExtension( + "The total length of the pattern is too small\n" + "Please choose a larger object or set 'Space between copies' > 0" + ) # check if group and expand it pattern_list = [] @@ -136,7 +197,9 @@ class PathScatter(pathmodifier.Diffeo): cur_skeleton = skelnode.path.to_superpath() for comp in cur_skeleton: skelcomp, lengths = self.linearize(comp) - skel_closed = all([math.isclose(i, j) for i, j in zip(skelcomp[0], skelcomp[-1])]) + skel_closed = all( + [math.isclose(i, j) for i, j in zip(skelcomp[0], skelcomp[-1])] + ) length = sum(lengths) dx = width + self.options.space @@ -147,11 +210,15 @@ class PathScatter(pathmodifier.Diffeo): s = 0 if self.options.stretch else self.options.toffset * 0.01 * dx while s <= length: - local_transform = self.localTransformAt(s, skelcomp, lengths, skel_closed, \ - self.options.follow) + local_transform = self.localTransformAt( + s, skelcomp, lengths, skel_closed, self.options.follow + ) - pattern_idx = random.randint(0, len(pattern_list) - 1) if \ - self.options.pickmode == "rand" else counter % len(pattern_list) + pattern_idx = ( + random.randint(0, len(pattern_list) - 1) + if self.options.pickmode == "rand" + else counter % len(pattern_list) + ) clone = pattern_list[pattern_idx].copy() @@ -161,5 +228,6 @@ class PathScatter(pathmodifier.Diffeo): s += dx counter += 1 -if __name__ == '__main__': + +if __name__ == "__main__": PathScatter().run() diff --git a/pdflatex.py b/pdflatex.py index 893a1973..2c0a7f39 100755 --- a/pdflatex.py +++ b/pdflatex.py @@ -28,30 +28,41 @@ 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='') + 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') + 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: + 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) + 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") + inkscape( + pdf_file, + export_filename=svg_file, + pdf_page=1, + pdf_poppler=True, + export_type="svg", + ) if not os.path.isfile(svg_file): fn = os.path.basename(svg_file) @@ -59,7 +70,7 @@ class PdfLatex(TempDirMixin, inkex.GenerateExtension): # Inkscape bug detected, file got saved wrong svg_file = fn - with open(svg_file, 'r') as fhl: + with open(svg_file, "r") as fhl: svg = load_svg(fhl).getroot() svg.set_random_ids(backlinks=True) for child in svg: @@ -67,24 +78,27 @@ class PdfLatex(TempDirMixin, inkex.GenerateExtension): yield child elif isinstance(child, Defs): for def_child in child: - #def_child.set_random_id() + # 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 + stream.write( + r"""%% processed with pdflatex.py \documentclass{minimal} \usepackage{amsmath} \usepackage{amssymb} \usepackage{amsfonts} -""") - for package in self.options.packages.split(','): +""" + ) + for package in self.options.packages.split(","): if package: - stream.write('\\usepackage{{{}}}\n'.format(package)) + stream.write("\\usepackage{{{}}}\n".format(package)) stream.write("\n\\begin{document}\n") stream.write(self.options.formule) stream.write("\n\\end{document}\n") stream.flush() -if __name__ == '__main__': + +if __name__ == "__main__": PdfLatex().run() diff --git a/perfectboundcover.py b/perfectboundcover.py index 360d62b4..b2233644 100755 --- a/perfectboundcover.py +++ b/perfectboundcover.py @@ -31,7 +31,7 @@ def caliper_to_ppi(caliper): def bond_weight_to_ppi(bond_weight): - return caliper_to_ppi(bond_weight * .0002) + return caliper_to_ppi(bond_weight * 0.0002) def points_to_ppi(points): @@ -43,19 +43,31 @@ class PerfectBoundCover(inkex.EffectExtension): 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="ppi", - help="""Measurement for determining the thickness of the spine. + pars.add_argument( + "--paperthicknessmeasurement", + default="ppi", + help="""Measurement for determining the thickness of the spine. Options: 'ppi': pages per inch; 'caliper': caliper (inches); 'points': caliper in points (1/1000 in); 'bond_weight': Bond (pounds); - 'width': absolute width of spine (in)""") - pars.add_argument("--paperthickness", type=float, default=0.0, help="paper thickness") - pars.add_argument("--coverthicknessmeasurement", default="ppi", - help="Measurement for determining the thickness of the cover. For " - "available options, see --paperthicknessmeasurement") - pars.add_argument("--coverthickness", type=float, default=0.0, help="cover thickness") + 'width': absolute width of spine (in)""", + ) + pars.add_argument( + "--paperthickness", type=float, default=0.0, help="paper thickness" + ) + pars.add_argument( + "--coverthicknessmeasurement", + default="ppi", + help="Measurement for determining the thickness of the cover. For " + "available options, see --paperthicknessmeasurement", + ) + 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") + pars.add_argument( + "--removeguides", type=inkex.Boolean, default=False, help="remove guide" + ) def effect(self): switch = { @@ -63,14 +75,16 @@ class PerfectBoundCover(inkex.EffectExtension): "caliper": caliper_to_ppi, "bond_weight": bond_weight_to_ppi, "points": points_to_ppi, - "width": lambda x: x + "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) + paper_spine = self.options.pages / switch[ + self.options.paperthicknessmeasurement + ](self.options.paperthickness) else: paper_spine = 0 @@ -78,7 +92,9 @@ class PerfectBoundCover(inkex.EffectExtension): if self.options.coverthicknessmeasurement == "width": cover_spine = self.options.coverthickness else: - cover_spine = 4.0 / switch[self.options.coverthicknessmeasurement](self.options.coverthickness) + cover_spine = 4.0 / switch[self.options.coverthicknessmeasurement]( + self.options.coverthickness + ) else: cover_spine = 0 @@ -111,5 +127,6 @@ class PerfectBoundCover(inkex.EffectExtension): newguide.set("orientation", guide[0]) newguide.set("position", "%f" % (guide[1] * 96)) -if __name__ == '__main__': + +if __name__ == "__main__": PerfectBoundCover().run() diff --git a/perspective.py b/perspective.py index 8b569a28..c6af4037 100755 --- a/perspective.py +++ b/perspective.py @@ -28,6 +28,7 @@ X, Y = range(2) try: import numpy as np import numpy.linalg as lin + FLOAT = np.float64 except ImportError: np = None @@ -35,25 +36,36 @@ except ImportError: 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.")) + _( + "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.")) + raise inkex.AbortExtension( + _("This extension requires two selected objects.") + ) obj, envelope = self.svg.selection if isinstance(obj, (inkex.PathElement, inkex.Group)): if isinstance(envelope, inkex.PathElement): - path = envelope.path.transform(envelope.composed_transform()).to_superpath() + 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.")) + _( + "This extension requires that the second path be four nodes long." + ) + ) dip = np.zeros((4, 2), dtype=FLOAT) for i in range(4): @@ -63,20 +75,36 @@ class Perspective(inkex.EffectExtension): # 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) + 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.")) + 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.")) + 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) @@ -95,10 +123,10 @@ class Perspective(inkex.EffectExtension): 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) + 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) @@ -115,7 +143,11 @@ class Perspective(inkex.EffectExtension): def process_path(self, element, matrix): """Apply the transformation to the selected path""" - point = element.path.to_absolute().transform(element.composed_transform()).to_superpath() + 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) @@ -126,11 +158,13 @@ class Perspective(inkex.EffectExtension): @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])] + 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__': +if __name__ == "__main__": Perspective().run() diff --git a/pixelsnap.py b/pixelsnap.py index 9a7babd0..204018af 100755 --- a/pixelsnap.py +++ b/pixelsnap.py @@ -73,18 +73,21 @@ 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. + """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 @@ -105,14 +108,29 @@ def transform_dimensions(transform, width=None, height=None, inverse=False): 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 (%)") + 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]) @@ -133,17 +151,19 @@ class PixelSnap(inkex.EffectExtension): 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. + """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)) + 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 + 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) @@ -152,31 +172,30 @@ class PixelSnap(inkex.EffectExtension): def stroke_width(self, elem, setval=None): """Get/set stroke-width in pixels, untransformed""" style = elem.style - stroke = style('stroke') + stroke = style("stroke") stroke_width = 0 if stroke and setval is None: - stroke_width = self.svg.unittouu(style('stroke-width').strip()) + stroke_width = self.svg.unittouu(style("stroke-width").strip()) if setval: - style['stroke-width'] = setval + style["stroke-width"] = setval else: return stroke_width def transform_path_node(self, transform, path, i): - """ Modifies a segment so that every point is transformed, including handles - """ + """Modifies a segment so that every point is transformed, including handles""" segtype = path[i][0].lower() - if segtype == 'z': + if segtype == "z": return - elif segtype == 'h': + elif segtype == "h": path[i][1][0] = transform_point(transform, [path[i][1][0], 0])[0] - elif segtype == 'v': + elif segtype == "v": path[i][1][0] = transform_point(transform, [0, path[i][1][0]])[1] else: first_coordinate = 0 - if segtype == 'a': + 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] @@ -185,28 +204,28 @@ class PixelSnap(inkex.EffectExtension): 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. + """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': + if segtype == "z": i = 0 - if segtype == 'h': + if segtype == "h": if setval: path[i][1][0] = setval[0] else: x = path[i][1][0] - elif segtype == 'v': + elif segtype == "v": if setval: path[i][1][0] = setval[1] else: y = path[i][1][0] else: - if setval and segtype != 'z': + if setval and segtype != "z": path[i][1][-2] = setval[0] path[i][1][-1] = setval[1] else: @@ -249,17 +268,22 @@ class PixelSnap(inkex.EffectExtension): 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) + 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) + if elem.get("inkscape:original-d"): + elem.set("inkscape:original-d", path) else: - elem.set('d', path) + elem.set("d", path) def snap_transform(self, elem): # Only snaps the x/y translation of the transform, nothing else. @@ -268,7 +292,9 @@ class PixelSnap(inkex.EffectExtension): 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") + raise TransformError( + "TR: Selection contains transformations with skew/rotation" + ) trm = list(transform.to_hexad()) trm[4] = round(transform.e) @@ -279,24 +305,31 @@ class PixelSnap(inkex.EffectExtension): 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 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") + 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) + 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)) + 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") + raise TransformError( + "Path: Selection contains transformations with skew/rotation" + ) offset = self.stroke_width_offset(elem, parent_transform) % 1 @@ -305,10 +338,11 @@ class PixelSnap(inkex.EffectExtension): for i in range(len(path)): segtype = path[i][0].lower() xy = self.pathxy(path, i) - if segtype == 'z': + if segtype == "z": xy = first_xy - if (i == len(path) - 1) or \ - ((i == len(path) - 2) and path[-1][0].lower() == 'z'): + 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) @@ -346,27 +380,31 @@ class PixelSnap(inkex.EffectExtension): 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[1] = ( + xy[1] - (round(xy[1] - offset) + offset) - self.document_offset + ) fractional_offset = transform_dimensions( - transform, fractional_offset[0], fractional_offset[1], inverse=True) + 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)) + 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") + raise TransformError( + "Rect: Selection contains transformations with skew/rotation" + ) offset = self.stroke_width_offset(elem, parent_transform) % 1 - width = self.svg.to_dimensionless(elem.attrib['width']) - height = self.svg.to_dimensionless(elem.attrib['height']) - x = self.svg.to_dimensionless(elem.attrib['x']) - y = self.svg.to_dimensionless(elem.attrib['y']) + width = self.svg.to_dimensionless(elem.attrib["width"]) + height = self.svg.to_dimensionless(elem.attrib["height"]) + x = self.svg.to_dimensionless(elem.attrib["x"]) + y = self.svg.to_dimensionless(elem.attrib["y"]) width, height = transform_dimensions(transform, width, height) x, y = transform_point(transform, [x, y]) @@ -374,7 +412,9 @@ class PixelSnap(inkex.EffectExtension): # 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 + 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) @@ -383,10 +423,10 @@ class PixelSnap(inkex.EffectExtension): 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) + 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) @@ -426,7 +466,9 @@ class PixelSnap(inkex.EffectExtension): 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 + 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): @@ -435,7 +477,9 @@ class PixelSnap(inkex.EffectExtension): 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 + 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 id, elem in self.svg.selected.items(): try: @@ -444,5 +488,5 @@ class PixelSnap(inkex.EffectExtension): raise inkex.AbortExtension(str(err)) -if __name__ == '__main__': +if __name__ == "__main__": PixelSnap().run() diff --git a/plotter.py b/plotter.py index 3e351391..25d4c437 100755 --- a/plotter.py +++ b/plotter.py @@ -25,36 +25,68 @@ 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='xonxoff', 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=0, help='Pen force (g)') - pars.add_argument('--speed', type=int, default=0, help='Pen speed (cm/s)') - pars.add_argument('--orientation', default='0', 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') + 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="xonxoff", 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=0, help="Pen force (g)") + pars.add_argument("--speed", type=int, default=0, help="Pen speed (cm/s)") + pars.add_argument("--orientation", default="0", 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 @@ -62,16 +94,18 @@ class Plot(inkex.EffectExtension): 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.")) + 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 = "IN" + # if self.options.force > 0: # init += ';FS%d' % self.options.force - # if self.options.speed > 0: + # if self.options.speed > 0: # init += ';VS%d' % self.options.speed - return init + hpgl + ';PU0,0;SP0;IN; ' + return init + hpgl + ";PU0,0;SP0;IN; " def convert_dmpl(self, hpgl): """Convert HPGL to DMPL""" @@ -86,41 +120,43 @@ class Plot(inkex.EffectExtension): # 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' + 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 ' + 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 = "ZG" + # if self.options.force > 0: # init += ';FS%d' % self.options.force - # if self.options.speed > 0: + # if self.options.speed > 0: # init += ';VS%d' % self.options.speed - return init + hpgl + ';SP0;PU0,0;@ ' + 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.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')) + 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() \ No newline at end of file +if __name__ == "__main__": + Plot().run() diff --git a/polyhedron_3d.py b/polyhedron_3d.py index f7281d65..fe544d08 100755 --- a/polyhedron_3d.py +++ b/polyhedron_3d.py @@ -63,24 +63,36 @@ try: 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.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.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} + 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 @@ -99,42 +111,62 @@ 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]) + 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 + 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) + 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 + 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) + 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( @@ -142,6 +174,7 @@ def get_normal(pts, face): (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 @@ -151,39 +184,43 @@ def get_unit_normal(pts, face, cw_wound): 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]]) + 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.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' + 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)) + + name = property(lambda self: self.meta.get("name", None)) def __init__(self, filename): - self.meta = { - 'name': os.path.basename(filename).rsplit('.', 1)[0] - } + self.meta = {"name": os.path.basename(filename).rsplit(".", 1)[0]} self.vtx = [] self.edg = [] self.fce = [] @@ -192,25 +229,25 @@ class WavefrontObj(object): 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: + 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) + 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 + 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()] + return [typ(v.split("/")[0]) for v in line.split()] def add_v(self, line): """Add vertex from parsed line""" @@ -237,7 +274,9 @@ class WavefrontObj(object): """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()) + transformed_pts.append( + (numpy.matmul(trans_mat, numpy.array(vtx).T)).T.tolist() + ) return transformed_pts def get_edge_list(self): @@ -249,16 +288,18 @@ class WavefrontObj(object): 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="common") # MODEL FILE SETTINGS - pars.add_argument("--obj", default='cube') - pars.add_argument("--spec_file", default='great_rhombicuboct.obj') + pars.add_argument("--obj", default="cube") + pars.add_argument("--spec_file", default="great_rhombicuboct.obj") pars.add_argument("--cw_wound", type=inkex.Boolean, default=False) - pars.add_argument("--type", default='face') + pars.add_argument("--type", default="face") # VEIW SETTINGS pars.add_argument("--r1_ax", default="x") pars.add_argument("--r2_ax", default="x") @@ -274,7 +315,7 @@ class Poly3D(inkex.GenerateExtension): 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("--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) @@ -286,17 +327,19 @@ class Poly3D(inkex.GenerateExtension): 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) + 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""" name = "" - if self.options.obj == 'from_file': + if self.options.obj == "from_file": name = self.options.spec_file else: - name = self.options.obj + '.obj' + name = self.options.obj + ".obj" moddir = self.ext_path() - return os.path.join(moddir, 'Poly3DObjects', name) + return os.path.join(moddir, "Poly3DObjects", name) def generate(self): if numpy is None: @@ -305,11 +348,11 @@ class Poly3D(inkex.GenerateExtension): obj = WavefrontObj(self.get_filename()) - scale = self.svg.unittouu('1px') # convert to document units + 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)) + 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) @@ -317,8 +360,8 @@ class Poly3D(inkex.GenerateExtension): # 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 + 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 @@ -331,7 +374,7 @@ class Poly3D(inkex.GenerateExtension): 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) + 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""" @@ -367,7 +410,9 @@ class Poly3D(inkex.GenerateExtension): # 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 + 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 @@ -388,5 +433,6 @@ class Poly3D(inkex.GenerateExtension): """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__': + +if __name__ == "__main__": Poly3D().run() diff --git a/prepare_file_save_as.py b/prepare_file_save_as.py index 0e89d79c..57ad83a9 100755 --- a/prepare_file_save_as.py +++ b/prepare_file_save_as.py @@ -36,12 +36,16 @@ 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'], - )) + self.document = load_svg( + inkscape_command( + self.svg, + verbs=["EditSelectAllInAllLayers", "EditUnlinkClone", "ObjectToPath"], + ) + ) + -if __name__ == '__main__': +if __name__ == "__main__": PreProcess().run() diff --git a/previous_glyph_layer.py b/previous_glyph_layer.py index bdfc28ee..d7f73e29 100755 --- a/previous_glyph_layer.py +++ b/previous_glyph_layer.py @@ -20,12 +20,15 @@ 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") + glyphs[current - 1].set("style", "display:inline") + -if __name__ == '__main__': +if __name__ == "__main__": PreviousLayer().run() diff --git a/print_win32_vector.py b/print_win32_vector.py index 9f143e5a..58e598c3 100755 --- a/print_win32_vector.py +++ b/print_win32_vector.py @@ -37,7 +37,7 @@ import inkex from inkex import PathElement, Rectangle, Group, Use, Transform from inkex.paths import Path -if sys.platform.startswith('win'): +if sys.platform.startswith("win"): myspool = ctypes.WinDLL("winspool.drv") mygdi = ctypes.WinDLL("gdi32.dll") else: @@ -45,47 +45,64 @@ else: mygdi = None LOGBRUSH = ctypes.c_long * 3 -DM_IN_PROMPT = 4 # call printer property sheet -DM_OUT_BUFFER = 2 # write to DEVMODE structure +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 + 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 + 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 "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]]) + 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 @@ -107,22 +124,22 @@ class PrintWin32Vector(inkex.EffectExtension): 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 = 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)) + 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') + 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) @@ -138,7 +155,7 @@ class PrintWin32Vector(inkex.EffectExtension): if refnode is not None: if isinstance(refnode, inkex.Group): self.process_group(refnode) - elif refnode.tag == 'svg:use': + elif refnode.tag == "svg:use": self.process_clone(refnode) else: self.process_shape(refnode, self.groupmat[-1]) @@ -149,10 +166,10 @@ class PrintWin32Vector(inkex.EffectExtension): def process_group(self, group): if isinstance(group, inkex.Layer): style = group.style - if 'display' in style: - if style['display'] == 'none' and self.visibleLayers: + if "display" in style: + if style["display"] == "none" and self.visibleLayers: return - trans = group.get('transform') + trans = group.get("transform") if trans: self.groupmat.append(Transform(self.groupmat[-1]) * Transform(trans)) for node in group: @@ -166,16 +183,20 @@ class PrintWin32Vector(inkex.EffectExtension): self.groupmat.pop() def doc_name(self): - docname = self.svg.xpath('@sodipodi:docname') + docname = self.svg.xpath("@sodipodi:docname") if not docname: - docname = ['New document 1'] - return ctypes.create_string_buffer(('Inkscape ' + docname[0].split('\\')[-1]).encode('ascii', 'replace')) + docname = ["New document 1"] + return ctypes.create_string_buffer( + ("Inkscape " + docname[0].split("\\")[-1]).encode("ascii", "replace") + ) def effect(self): pcchBuffer = ctypes.c_long() - myspool.GetDefaultPrinterA(None, ctypes.byref(pcchBuffer)) # get length of printer name + 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 + 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")) @@ -183,10 +204,19 @@ class PrintWin32Vector(inkex.EffectExtension): # 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) + 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 + if pcchBuffer != 1: # user clicked Cancel exit() # initiallize print document @@ -196,22 +226,25 @@ class PrintWin32Vector(inkex.EffectExtension): 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 + 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]) + 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') + viewBox = doc.get("viewBox") if viewBox: - viewBox2 = viewBox.split(',') + viewBox2 = viewBox.split(",") if len(viewBox2) < 4: - viewBox2 = viewBox.split(' ') + 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__': + +if __name__ == "__main__": PrintWin32Vector().run() diff --git a/printing_marks.py b/printing_marks.py index 5af72e53..272bc02d 100755 --- a/printing_marks.py +++ b/printing_marks.py @@ -37,82 +37,154 @@ class PrintingMarks(inkex.EffectExtension): def add_arguments(self, pars): pars.add_argument("--where", help="Apply crop marks to...", default="canvas") - pars.add_argument("--crop_marks", type=inkex.Boolean, default=True, help="Draw crop Marks") + 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=True, help="Draw Registration Marks?") + pars.add_argument( + "--registration_marks", + type=inkex.Boolean, + dest="reg_marks", + default=True, + 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?", default=True) - pars.add_argument("--page_info", type=inkex.Boolean, help="Draw Page Information?") + pars.add_argument( + "--colour_bars", type=inkex.Boolean, help="Draw Colour Bars?", default=True + ) + 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=5.0, help="Offset") pars.add_argument("--bleed_top", type=float, default=5.0, help="Bleed Top Size") - pars.add_argument("--bleed_bottom", type=float, default=5.0, help="Bleed Bottom Size") - pars.add_argument("--bleed_left", type=float, default=5.0, help="Bleed Left Size") - pars.add_argument("--bleed_right", type=float, default=5.0, help="Bleed Right Size") + pars.add_argument( + "--bleed_bottom", type=float, default=5.0, help="Bleed Bottom Size" + ) + pars.add_argument( + "--bleed_left", type=float, default=5.0, help="Bleed Left Size" + ) + pars.add_argument( + "--bleed_right", type=float, default=5.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)} + 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)} + 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))} + 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'] + 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) + 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) + ')'} + 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' + 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) + 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) + ) elem = parent.add(inkex.PathElement()) elem.label = name elem.transform.add_translate(cx, cy) @@ -121,29 +193,36 @@ class PrintingMarks(inkex.EffectExtension): def draw_coluor_bars(self, cx, cy, rotate, name, parent, bbox): group = parent.add(inkex.Group(id=name)) - group.transform = inkex.Transform(translate=(cx, cy)) @ inkex.Transform(rotate=rotate) + group.transform = inkex.Transform(translate=(cx, cy)) @ inkex.Transform( + rotate=rotate + ) 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} - ]: + 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 = inkex.Color("white") + if bar["c"] == "r" or bar["c"] == "*": color.red = 255 * i - if bar['c'] == 'g' or bar['c'] == '*': + if bar["c"] == "g" or bar["c"] == "*": color.green = 255 * i - if bar['c'] == 'b' or bar['c'] == '*': + if bar["c"] == "b" or bar["c"] == "*": color.blue = 255 * i - r_att = {'fill': str(color), - 'stroke': bar['stroke'], - 'stroke-width': loc/8, - 'x': str((loc * i * 10) + bar['x']), 'y': str(bar['y']), - 'width': loc, 'height': loc} + r_att = { + "fill": str(color), + "stroke": bar["stroke"], + "stroke-width": loc / 8, + "x": str((loc * i * 10) + bar["x"]), + "y": str(bar["y"]), + "width": loc, + "height": loc, + } rect = Rectangle() for att, value in r_att.items(): rect.set(att, value) @@ -151,10 +230,10 @@ class PrintingMarks(inkex.EffectExtension): i += 0.1 def effect(self): - self.mark_size = self.svg.viewport_to_unit('1cm') - self.min_mark_margin = self.svg.viewport_to_unit('3mm') + self.mark_size = self.svg.viewport_to_unit("1cm") + self.min_mark_margin = self.svg.viewport_to_unit("3mm") - if self.options.where == 'selection': + if self.options.where == "selection": bbox = self.svg.selection.bounding_box() if bbox is None: raise inkex.AbortExtension(_("Selection is empty")) @@ -166,12 +245,17 @@ class PrintingMarks(inkex.EffectExtension): svg = self.document.getroot() # Convert parameters to user unit - offset = self.svg.viewport_to_unit(str(self.options.crop_offset) + - self.options.unit) + offset = self.svg.viewport_to_unit( + str(self.options.crop_offset) + self.options.unit + ) bt = self.svg.viewport_to_unit(str(self.options.bleed_top) + self.options.unit) - bb = self.svg.viewport_to_unit(str(self.options.bleed_bottom) + self.options.unit) + bb = self.svg.viewport_to_unit( + str(self.options.bleed_bottom) + self.options.unit + ) bl = self.svg.viewport_to_unit(str(self.options.bleed_left) + self.options.unit) - br = self.svg.viewport_to_unit(str(self.options.bleed_right) + self.options.unit) + br = self.svg.viewport_to_unit( + str(self.options.bleed_right) + self.options.unit + ) # Bleed margin if bt < offset: bmt = 0 @@ -201,205 +285,338 @@ class PrintingMarks(inkex.EffectExtension): middle_horizontal = bbox.left + (bbox.width / 2) # Test if printing-marks layer existis - layer = self.svg.xpath('//*[@id="printing-marks" and @inkscape:groupmode="layer"]') + 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') + 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' + 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) + 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) + 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) + 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) + 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_bleed = layer.add(inkex.Group()) - g_bleed.label = 'BleedMarks' - g_bleed.set('id', 'BleedMarks') + g_bleed.label = "BleedMarks" + g_bleed.set("id", "BleedMarks") # 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) + 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) + 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) + 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) + 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_center = layer.add(inkex.Group()) - g_center.label = 'RegistrationMarks' - g_center.set('id', 'RegistrationMarks') + g_center.label = "RegistrationMarks" + g_center.set("id", "RegistrationMarks") # 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) + 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) + 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) + 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) + 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_center = layer.add(inkex.Group()) - g_center.label = 'StarTarget' - g_center.set('id', 'StarTarget') + g_center.label = "StarTarget" + g_center.set("id", "StarTarget") 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) + 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) + 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) + 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) + 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_center = layer.add(inkex.Group()) - g_center.label = 'ColourBars' - g_center.set('id', 'PrintingColourBars') + g_center.label = "ColourBars" + g_center.set("id", "PrintingColourBars") 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, bbox) + self.draw_coluor_bars( + bbox.left - cx - (self.mark_size / 2), + middle_vertical + self.mark_size, + 90, + "PrintingColourBarsL", + g_center, + bbox, + ) # 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, bbox) + self.draw_coluor_bars( + bbox.right + cx + (self.mark_size / 2), + middle_vertical + self.mark_size, + 90, + "PrintingColourBarsR", + g_center, + bbox, + ) 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, bbox) + self.draw_coluor_bars( + middle_horizontal + self.mark_size, + bbox.top - cy - (self.mark_size / 2), + 0, + "PrintingColourBarsT", + g_center, + bbox, + ) # 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, bbox) + self.draw_coluor_bars( + middle_horizontal + self.mark_size, + bbox.bottom + cy + (self.mark_size / 2), + 0, + "PrintingColourBarsB", + g_center, + bbox, + ) # Page Information if self.options.page_info: # Create a group for Page Information g_pag_info = layer.add(inkex.Group()) - g_pag_info.label = 'PageInformation' - g_pag_info.set('id', 'PageInformation') + g_pag_info.label = "PageInformation" + g_pag_info.set("id", "PageInformation") y_margin = max(bmb + offset, self.min_mark_margin) font_size = self.svg.viewport_to_unit("9pt") txt_attribs = { - 'style': f'font-size:{font_size}px;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) + "style": f"font-size:{font_size}px;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.unit_to_viewport(bbox.width, self.options.unit), 2)) + \ - 'x' + \ - str(round(self.svg.unit_to_viewport(bbox.height, self.options.unit), 2)) + \ - ' ' + self.options.unit - - -if __name__ == '__main__': + txt.text = ( + "Page size: " + + str( + round(self.svg.unit_to_viewport(bbox.width, self.options.unit), 2) + ) + + "x" + + str( + round(self.svg.unit_to_viewport(bbox.height, self.options.unit), 2) + ) + + " " + + self.options.unit + ) + + +if __name__ == "__main__": PrintingMarks().run() diff --git a/ps_input.py b/ps_input.py index 597fdcd5..308cd767 100755 --- a/ps_input.py +++ b/ps_input.py @@ -27,20 +27,32 @@ import os import inkex from inkex.command import call, which + class PostscriptInput(inkex.CallExtension): """Load Postscript/EPS Files by calling ps2pdf program""" - input_ext = 'ps' - output_ext = 'pdf' + + input_ext = "ps" + output_ext = "pdf" multi_inx = True def add_arguments(self, pars): - pars.add_argument('--crop', type=inkex.Boolean, default=False) + pars.add_argument("--crop", type=inkex.Boolean, default=False) def call(self, input_file, output_file): - crop = '-dEPSCrop' if self.options.crop else None + crop = "-dEPSCrop" if self.options.crop else None if sys.platform == "win32": - params = ['-q', '-P-', '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE#pdfwrite', - '-dCompatibilityLevel#1.4', crop, "-sOutputFile#" + output_file, input_file] + params = [ + "-q", + "-P-", + "-dSAFER", + "-dNOPAUSE", + "-dBATCH", + "-sDEVICE#pdfwrite", + "-dCompatibilityLevel#1.4", + crop, + "-sOutputFile#" + output_file, + input_file, + ] gs_execs = ["gswin64c", "gswin32c"] gs_exec = None for executable in gs_execs: @@ -51,8 +63,8 @@ class PostscriptInput(inkex.CallExtension): pass if gs_exec is None: if "PYTEST_CURRENT_TEST" in os.environ: - gs_exec = "gswin64c" # In CI, we have neither available, - # but there are mock files for the 64 bit version + gs_exec = "gswin64c" # In CI, we have neither available, + # but there are mock files for the 64 bit version else: raise inkex.AbortExtension() call(gs_exec, *params) @@ -60,5 +72,5 @@ class PostscriptInput(inkex.CallExtension): call("ps2pdf", crop, input_file, output_file) -if __name__ == '__main__': +if __name__ == "__main__": PostscriptInput().run() diff --git a/raster_output_jpg.py b/raster_output_jpg.py index 1201b028..e3ef3000 100644 --- a/raster_output_jpg.py +++ b/raster_output_jpg.py @@ -5,19 +5,23 @@ Convert PNG to Jpeg using Raster Output extension. import inkex + class JpegOutput(inkex.RasterOutputExtension): - multi_inx = True # XXX Remove this after refactoring + multi_inx = True # XXX Remove this after refactoring def add_arguments(self, pars): - pars.add_argument('--tab') - pars.add_argument('--quality', type=int, default=90) - pars.add_argument('--progressive', type=inkex.Boolean, default=False) + pars.add_argument("--tab") + pars.add_argument("--quality", type=int, default=90) + pars.add_argument("--progressive", type=inkex.Boolean, default=False) def save(self, stream): - self.img.convert('RGB').save(stream, - format='jpeg', + self.img.convert("RGB").save( + stream, + format="jpeg", quality=self.options.quality, - progressive=self.options.progressive) + progressive=self.options.progressive, + ) + -if __name__ == '__main__': +if __name__ == "__main__": JpegOutput().run() diff --git a/raster_output_png.py b/raster_output_png.py index d24028d6..b052bd21 100755 --- a/raster_output_png.py +++ b/raster_output_png.py @@ -8,42 +8,39 @@ import inkex from inkex.extensions import TempDirMixin from inkex.command import call + class PngOutput(TempDirMixin, inkex.RasterOutputExtension): def add_arguments(self, pars): - pars.add_argument('--tab') + pars.add_argument("--tab") # Lossless options - pars.add_argument('--interlace', type=inkex.Boolean, default=True) - pars.add_argument('--level', type=int, default=0) + pars.add_argument("--interlace", type=inkex.Boolean, default=True) + pars.add_argument("--level", type=int, default=0) # Lossy options - pars.add_argument('--bitdepth', type=inkex.Boolean, default=False) - pars.add_argument('--color', type=inkex.Boolean, default=False) - pars.add_argument('--palette', type=inkex.Boolean, default=False) + pars.add_argument("--bitdepth", type=inkex.Boolean, default=False) + pars.add_argument("--color", type=inkex.Boolean, default=False) + pars.add_argument("--palette", type=inkex.Boolean, default=False) def load(self, stream): """Load the PNG file (prepare it for optipng)""" - self.png_file = os.path.join(self.tempdir, 'input.png') - with open(self.png_file, 'wb') as fhl: + self.png_file = os.path.join(self.tempdir, "input.png") + with open(self.png_file, "wb") as fhl: fhl.write(stream.read()) def save(self, stream): """Pass the PNG file to optipng with the options""" options = { - 'o': self.options.level, - 'i': int(self.options.interlace), - 'nb': not self.options.bitdepth, - 'nc': not self.options.color, - 'np': not self.options.palette, + "o": self.options.level, + "i": int(self.options.interlace), + "nb": not self.options.bitdepth, + "nc": not self.options.color, + "np": not self.options.palette, } - call('optipng', - self.png_file, - oldie=True, - clobber=True, - **options) + call("optipng", self.png_file, oldie=True, clobber=True, **options) if os.path.isfile(self.png_file): - with open(self.png_file, 'rb') as fhl: + with open(self.png_file, "rb") as fhl: stream.write(fhl.read()) - -if __name__ == '__main__': + +if __name__ == "__main__": PngOutput().run() diff --git a/raster_output_tiff.py b/raster_output_tiff.py index b1404d3c..c6eb8deb 100755 --- a/raster_output_tiff.py +++ b/raster_output_tiff.py @@ -5,18 +5,22 @@ Convert PNG to Tiff using Raster Output extension. import inkex + class TiffOutput(inkex.RasterOutputExtension): def add_arguments(self, pars): - pars.add_argument('--tab') - pars.add_argument('--compression', default=None) - pars.add_argument('--quality', type=int, default=90) + pars.add_argument("--tab") + pars.add_argument("--compression", default=None) + pars.add_argument("--quality", type=int, default=90) def save(self, stream): - self.img.convert('RGB').save(stream, - format='tiff', + self.img.convert("RGB").save( + stream, + format="tiff", compression=(self.options.compression or None), - quality=100) + quality=100, + ) # TODO: Add other fields such as copyright etc. -if __name__ == '__main__': + +if __name__ == "__main__": TiffOutput().run() diff --git a/raster_output_webp.py b/raster_output_webp.py index d56248c2..01d728f5 100755 --- a/raster_output_webp.py +++ b/raster_output_webp.py @@ -5,19 +5,23 @@ Convert PNG to WebP using Raster Output extension. import inkex + class WebpOutput(inkex.RasterOutputExtension): def add_arguments(self, pars): - pars.add_argument('--tab') - pars.add_argument('--quality', type=int, default=80) - pars.add_argument('--speed', type=int, default=0) - pars.add_argument('--lossless', type=inkex.Boolean, default=True) + pars.add_argument("--tab") + pars.add_argument("--quality", type=int, default=80) + pars.add_argument("--speed", type=int, default=0) + pars.add_argument("--lossless", type=inkex.Boolean, default=True) def save(self, stream): - self.img.save(stream, - format='webp', + self.img.save( + stream, + format="webp", quality=self.options.quality, lossless=self.options.lossless, - method=self.options.speed) + method=self.options.speed, + ) + -if __name__ == '__main__': +if __name__ == "__main__": WebpOutput().run() diff --git a/render_alphabetsoup.py b/render_alphabetsoup.py index d7b6e499..825c285f 100755 --- a/render_alphabetsoup.py +++ b/render_alphabetsoup.py @@ -41,20 +41,22 @@ 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__)) - ) + 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') + 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 + return ( + elem.path.to_arrays(), + width, + height, + ) # Currently we only support a single path def combinePaths(pathA, pathB): @@ -71,11 +73,11 @@ def combinePaths(pathA, pathB): def reverseComponent(c): nc = [] last = c.pop() - nc.append(['M', last[1][-2:]]) + nc.append(["M", last[1][-2:]]) while c: this = c.pop() cmd = last[0] - if cmd == 'C': + 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:]]) @@ -88,9 +90,9 @@ def reversePath(sp): component = [] for p in sp: cmd, params = p - if cmd == 'Z': + if cmd == "Z": rp.extend(reverseComponent(component)) - rp.append(['Z', []]) + rp.append(["Z", []]) component = [] else: component.append(p) @@ -106,6 +108,7 @@ 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) @@ -127,12 +130,15 @@ def flip(sp, cb, param): 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 @@ -153,16 +159,16 @@ 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) + 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) + 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) + y2, y3 = solveQuadratic(1, y1, p + y1**2) if isinstance(y2, complex): # Are y2 and y3 complex? return [y1 - t] @@ -170,7 +176,10 @@ def findRealRoots(a, b, c, d): 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)] + return [ + (-c + math.sqrt(det)) / (2.0 * b), + (-c - math.sqrt(det)) / (2.0 * b), + ] elif c != 0: return [-d / c] return [] @@ -216,7 +225,9 @@ def generate(state): # generate a random tree (in stack form) return stack else: stack.append("[") - path = random.randint(0, (len(syntax[state][1]) - 1)) # choose randomly from next states + 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 @@ -252,7 +263,9 @@ def draw(stack): # draw a character based on a tree 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 + 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: @@ -279,11 +292,13 @@ 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() + 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 +def randomize_input_string( + tokens, zoom +): # generate a glyph starting from each token in the input string imagelist = [] stack = None @@ -291,10 +306,14 @@ def randomize_input_string(tokens, zoom): # generate a glyph starting from each 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 + 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 = 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 @@ -304,7 +323,9 @@ def randomize_input_string(tokens, zoom): # generate a glyph starting from each return imagelist -def generate_random_string(tokens, zoom): # generate a totally random glyph for each glyph in the input string +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 @@ -333,11 +354,11 @@ def optikern(image, width, zoom): # optical kerning algorithm xmax = None for cmd, params in image: - if cmd == 'M': + if cmd == "M": # A move cannot contribute to the bounding box last = params[:] lastctrl = params[:] - elif cmd == 'L': + elif cmd == "L": if (last[1] <= y <= params[1]) or (params[1] <= y <= last[1]): if params[0] == last[0]: x = params[0] @@ -357,7 +378,7 @@ def optikern(image, width, zoom): # optical kerning algorithm last = params[:] lastctrl = params[:] - elif cmd == 'C': + elif cmd == "C": if last: bx0, by0 = last[:] bx1, by1, bx2, by2, bx3, by3 = params[:] @@ -371,10 +392,12 @@ def optikern(image, width, zoom): # optical kerning algorithm 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 + 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: @@ -383,19 +406,21 @@ def optikern(image, width, zoom): # optical kerning algorithm last = params[-2:] lastctrl = params[2:4] - elif cmd == 'Q': + elif cmd == "Q": # Quadratic beziers are ignored last = params[-2:] lastctrl = params[2:4] - elif cmd == 'A': + 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 + right.append( + width - xmax + ) # distance from right edge of region to right edge of bbox else: left.append(width) right.append(width) @@ -403,7 +428,9 @@ def optikern(image, width, zoom): # optical kerning algorithm return left, right -def layoutstring(imagelist, zoom): # layout string of letter-images using optical kerning +def layoutstring( + imagelist, zoom +): # layout string of letter-images using optical kerning kernlist = [] length = zoom for entry in imagelist: @@ -412,7 +439,9 @@ def layoutstring(imagelist, zoom): # layout string of letter-images using optic 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 + kernlist.append( + optikern(image, width, zoom) + ) # append kerning data for this image workspace = None @@ -448,14 +477,14 @@ def tokenize(text): while i < len(text): c = text[i] i += 1 - if c == '\\': # found the beginning of an escape - t = '' + 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 + if c == "\\": # found another escape, stop this one break i += 1 - if c == ' ': # a space terminates this escape + if c == " ": # a space terminates this escape break t += c # stick this character onto the token if t: @@ -467,13 +496,22 @@ def tokenize(text): 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") + 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') + zoom = self.svg.unittouu(str(self.options.zoom) + "px") if self.options.randomize: imagelist = generate_random_string(self.options.text, zoom) @@ -484,20 +522,21 @@ class AlphabetSoup(inkex.EffectExtension): image = layoutstring(imagelist, zoom) if image: - s = {'stroke': 'none', 'fill': '#000000'} + s = {"stroke": "none", "fill": "#000000"} - new = inkex.PathElement( - style=str(inkex.Style(s)), - d=str(inkex.Path(image))) + 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 + 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__': +if __name__ == "__main__": AlphabetSoup().run() diff --git a/render_barcode.py b/render_barcode.py index e836ab93..b15f2522 100755 --- a/render_barcode.py +++ b/render_barcode.py @@ -25,14 +25,16 @@ 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") + 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 @@ -42,9 +44,11 @@ class Barcode(inkex.GenerateExtension): text=self.options.text, height=self.options.height, document=self.document, - x=pos_x, y=pos_y, - scale=self.svg.unittouu('1px'), + x=pos_x, + y=pos_y, + scale=self.svg.unittouu("1px"), ).generate() -if __name__ == '__main__': + +if __name__ == "__main__": Barcode().run() diff --git a/render_barcode_datamatrix.py b/render_barcode_datamatrix.py index 4904c77c..1252336d 100755 --- a/render_barcode_datamatrix.py +++ b/render_barcode_datamatrix.py @@ -59,78 +59,92 @@ INVALID_BIT = 2 # 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), + "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), + "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 = 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 = 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 = 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 + 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 ) + 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 = 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 + 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 = 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 + block2 = reed_solomon( + block2, nd, nc + ) # generate and append the Reed-Solomon codewords blocks = block1 blocks.extend(block2) @@ -151,7 +165,9 @@ def interleave(blocks, inter): 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 + 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 @@ -171,7 +187,9 @@ def combine_interleaved(blocks, inter, nd, nc, size144): return blocks else: result = [] - for i in range(len(blocks) // inter): # for each group of "inter" blocks -> one full datamatrix + for i in range( + len(blocks) // inter + ): # for each group of "inter" blocks -> one full datamatrix data_codewords = [] # interleaved data blocks if size144: @@ -213,13 +231,15 @@ def partition_data(data, rs_data): 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]) + 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 + 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 + 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 @@ -311,7 +331,9 @@ def reed_solomon(data, nd, nc): 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[nd + j] = block[nd + j + 1] ^ prod( + k, c[nc - j - 1], log, alog, gf + ) block.pop() @@ -322,6 +344,7 @@ def reed_solomon(data, nd, nc): # 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 @@ -345,22 +368,62 @@ 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)], - + [ + (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)], + [ + (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) @@ -425,11 +488,15 @@ def add_finder_pattern(array, data_nrow, data_ncol, reg_row, reg_col): 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 + 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) + 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): @@ -438,18 +505,18 @@ def add_finder_pattern(array, data_nrow, data_ncol, reg_row, reg_col): 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 + datamatrix[dest_row][dest_col] = array[i][ + j + ] # transfer from the plain bit array return datamatrix - - class DataMatrix(inkex.GenerateExtension): - container_label = 'DataMatrix' + container_label = "DataMatrix" def add_arguments(self, pars): - pars.add_argument("--text", default='Inkscape') + pars.add_argument("--text", default="Inkscape") pars.add_argument("--symbol", type=self.arg_symbols, required=True) pars.add_argument("--size", type=int, default=4) @@ -463,8 +530,8 @@ class DataMatrix(inkex.GenerateExtension): 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} + 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.") @@ -472,10 +539,12 @@ class DataMatrix(inkex.GenerateExtension): # 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)}) + 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): + 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) @@ -487,9 +556,13 @@ class DataMatrix(inkex.GenerateExtension): 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) + 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)) + module_arrays.append( + add_finder_pattern(bit_array, data_nrow, data_ncol, reg_row, reg_col) + ) return module_arrays @@ -507,7 +580,8 @@ class DataMatrix(inkex.GenerateExtension): 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])) + inkex.errormsg("Invalid bit value, {}!".format(line[y][x])) + -if __name__ == '__main__': +if __name__ == "__main__": DataMatrix().run() diff --git a/render_barcode_qrcode.py b/render_barcode_qrcode.py index 54aaad57..83efa581 100755 --- a/render_barcode_qrcode.py +++ b/render_barcode_qrcode.py @@ -30,10 +30,12 @@ from itertools import product import inkex from inkex import Group, Rectangle, Use, PathElement + class QRLengthError(Exception): def __init__(self, message): self.message = message + class QRCode(object): PAD0 = 0xEC PAD1 = 0x11 @@ -64,8 +66,7 @@ class QRCode(object): return self.qrDataList[index] def isDark(self, row, col): - return (self.modules[row][col] if self.modules[row][col] is not None - else False) + return self.modules[row][col] if self.modules[row][col] is not None else False def getModuleCount(self): return self.moduleCount @@ -87,8 +88,7 @@ class QRCode(object): def _make(self, test, maskPattern): self.moduleCount = self.typeNumber * 4 + 17 - self.modules = [[None] * self.moduleCount - for i in range(self.moduleCount)] + self.modules = [[None] * self.moduleCount for i in range(self.moduleCount)] self._setupPositionProbePattern(0, 0) self._setupPositionProbePattern(self.moduleCount - 7, 0) @@ -103,17 +103,17 @@ class QRCode(object): self._setupTypeNumber(test) data = QRCode._createData( - self.typeNumber, - self.errorCorrectLevel, - self.qrDataList) + 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)] + cols = [ + col - 1 if col <= 6 else col for col in range(self.moduleCount - 1, 0, -2) + ] maskFunc = QRUtil.getMaskFunction(maskPattern) byteIndex = 0 @@ -146,19 +146,28 @@ class QRCode(object): 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)) + 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): + 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)) + (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): @@ -174,10 +183,12 @@ class QRCode(object): 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) + 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) + not test and ((bits >> i) & 1) == 1 + ) def _setupTypeInfo(self, test, maskPattern): @@ -219,12 +230,13 @@ class QRCode(object): buffer.put(data.getLength(), data.getLengthInBits(typeNumber)) data.write(buffer) - totalDataCount = sum(rsBlock.getDataCount() - for rsBlock in rsBlocks) + totalDataCount = sum(rsBlock.getDataCount() for rsBlock in rsBlocks) if buffer.getLengthInBits() > totalDataCount * 8: - raise QRLengthError('code length overflow. (%s>%s)' % - (buffer.getLengthInBits(), totalDataCount * 8)) + raise QRLengthError( + "code length overflow. (%s>%s)" + % (buffer.getLengthInBits(), totalDataCount * 8) + ) # end code if buffer.getLengthInBits() + 4 <= totalDataCount * 8: @@ -266,7 +278,7 @@ class QRCode(object): dcdata[r] = [0] * dcCount for i in range(len(dcdata[r])): - dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset] + dcdata[r][i] = 0xFF & buffer.getBuffer()[i + offset] offset += dcCount rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount) @@ -278,8 +290,7 @@ class QRCode(object): 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) + totalCodeCount = sum(rsBlock.getTotalCount() for rsBlock in rsBlocks) data = [0] * totalCodeCount @@ -303,16 +314,18 @@ class QRCode(object): def getMinimumQRCode(data, errorCorrectLevel): qr = QRCode(correction=errorCorrectLevel) qr.addData(data) - lv=1 - rv=40 - while(rv - lv > 0): + lv = 1 + rv = 40 + while rv - lv > 0: mid = (3 * lv + rv) // 4 qr.setTypeNumber(mid) try: qr.make() except QRLengthError: - if( mid == 40 ): - raise inkex.AbortExtension(_("The string is too large to represent as QR code")) + if mid == 40: + raise inkex.AbortExtension( + _("The string is too large to represent as QR code") + ) lv = mid + 1 else: rv = mid @@ -385,7 +398,7 @@ class QRUtil(object): [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] + [6, 30, 58, 86, 114, 142, 170], ] @staticmethod @@ -398,22 +411,14 @@ class QRUtil(object): @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.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 @@ -438,7 +443,7 @@ class QRUtil(object): if dark == qrcode.isDark(row + r, col + c): sameCount += 1 if sameCount > 5: - lostPoint += (3 + sameCount - 5) + lostPoint += 3 + sameCount - 5 # LEVEL2 for row in range(moduleCount - 1): @@ -458,24 +463,28 @@ class QRUtil(object): # 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)): + 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)): + 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 @@ -490,26 +499,31 @@ class QRUtil(object): 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 = (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))) + 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))) + d ^= QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18)) return (data << 12) | d @staticmethod @@ -545,7 +559,7 @@ class QRData(object): Mode.MODE_NUMBER: 10, Mode.MODE_ALPHA_NUM: 9, Mode.MODE_8BIT_BYTE: 8, - Mode.MODE_KANJI: 8 + Mode.MODE_KANJI: 8, }[self._mode] elif type < 27: # 10 - 26 @@ -553,7 +567,7 @@ class QRData(object): Mode.MODE_NUMBER: 12, Mode.MODE_ALPHA_NUM: 11, Mode.MODE_8BIT_BYTE: 16, - Mode.MODE_KANJI: 10 + Mode.MODE_KANJI: 10, }[self._mode] elif type < 41: # 27 - 40 @@ -561,19 +575,18 @@ class QRData(object): Mode.MODE_NUMBER: 14, Mode.MODE_ALPHA_NUM: 13, Mode.MODE_8BIT_BYTE: 16, - Mode.MODE_KANJI: 12 + Mode.MODE_KANJI: 12, }[self._mode] else: - raise Exception('type:%s' % type) + raise Exception("type:%s" % type) class QR8BitByte(QRData): - def __init__(self, data): super(QR8BitByte, self).__init__(Mode.MODE_8BIT_BYTE, data) if isinstance(data, str): - data = data.encode('ascii', 'ignore') + data = data.encode("ascii", "ignore") if not isinstance(data, bytes): raise ValueError("Data must be in bytes!") @@ -583,35 +596,50 @@ class QR8BitByte(QRData): class QRAlphaNum(QRData): - def __init__(self, data): super(QRAlphaNum, self).__init__(Mode.MODE_ALPHA_NUM, data) def write(self, buffer): i = 0 while i + 1 < len(self.data): - buffer.put(QRAlphaNum._getCode(self.data[i]) * 45 + QRAlphaNum._getCode(self.data[i + 1]), 11) + buffer.put( + QRAlphaNum._getCode(self.data[i]) * 45 + + QRAlphaNum._getCode(self.data[i + 1]), + 11, + ) i += 2 if i < len(self.data): buffer.put(QRAlphaNum._getCode(self.data[i]), 6) @staticmethod def _getCode(c): - if '0' <= c and c <= '9': - return ord(c) - ord('0') - elif 'A' <= c and c <= 'Z': - return ord(c) - ord('A') + 10 + if "0" <= c and c <= "9": + return ord(c) - ord("0") + elif "A" <= c and c <= "Z": + return ord(c) - ord("A") + 10 else: - dct = {' ': 36, '$': 37, '%': 38, '*': 39, '+': 40, '-': 41, '.': 42, '/': 43, ':': 44} - if (c in dct.keys()): + dct = { + " ": 36, + "$": 37, + "%": 38, + "*": 39, + "+": 40, + "-": 41, + ".": 42, + "/": 43, + ":": 44, + } + if c in dct.keys(): return dct[c] else: raise inkex.AbortExtension( - _("Wrong symbol '{}' in alphanumeric representation: Should be [A-Z, 0-9] or {}").format(c, dct.keys())) + _( + "Wrong symbol '{}' in alphanumeric representation: Should be [A-Z, 0-9] or {}" + ).format(c, dct.keys()) + ) class QRNumber(QRData): - def __init__(self, data): super(QRNumber, self).__init__(Mode.MODE_NUMBER, data) @@ -619,7 +647,7 @@ class QRNumber(QRData): i = 0 try: while i + 2 < len(self.data): - num = int(self.data[i:i + 3]) + num = int(self.data[i : i + 3]) buffer.put(num, 10) i += 3 @@ -636,7 +664,6 @@ class QRKanji(QRData): raise RuntimeError("Class QRKanji is not implemented") - class QRMath(object): EXP_TABLE = None LOG_TABLE = None @@ -646,9 +673,14 @@ class QRMath(object): 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.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): @@ -657,7 +689,7 @@ class QRMath(object): @staticmethod def glog(n): if n < 1: - raise Exception('log(%s)' % n) + raise Exception("log(%s)" % n) return QRMath.LOG_TABLE[n] @staticmethod @@ -688,19 +720,20 @@ class Polynomial(object): return len(self.num) def __repr__(self): - return ','.join([str(self.get(i)) - for i in range(self.getLength())]) + 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())]) + 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))) + num[i + j] ^= QRMath.gexp( + QRMath.glog(self.get(i)) + QRMath.glog(e.get(j)) + ) return Polynomial(num) def mod(self, e): @@ -715,251 +748,210 @@ class Polynomial(object): 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], - - # 11 - [4, 101, 81], - [1, 80, 50, 4, 81, 51], - [4, 50, 22, 4, 51, 23], - [3, 36, 12, 8, 37, 13], - - # 12 - [2, 116, 92, 2, 117, 93], - [6, 58, 36, 2, 59, 37], - [4, 46, 20, 6, 47, 21], - [7, 42, 14, 4, 43, 15], - - # 13 - [4, 133, 107], - [8, 59, 37, 1, 60, 38], - [8, 44, 20, 4, 45, 21], - [12, 33, 11, 4, 34, 12], - - # 14 - [3, 145, 115, 1, 146, 116], - [4, 64, 40, 5, 65, 41], - [11, 36, 16, 5, 37, 17], - [11, 36, 12, 5, 37, 13], - - # 15 - [5, 109, 87, 1, 110, 88], - [5, 65, 41, 5, 66, 42], - [5, 54, 24, 7, 55, 25], - [11, 36, 12, 7, 37, 13], - - # 16 - [5, 122, 98, 1, 123, 99], - [7, 73, 45, 3, 74, 46], - [15, 43, 19, 2, 44, 20], - [3, 45, 15, 13, 46, 16], - - # 17 - [1, 135, 107, 5, 136, 108], - [10, 74, 46, 1, 75, 47], - [1, 50, 22, 15, 51, 23], - [2, 42, 14, 17, 43, 15], - - # 18 - [5, 150, 120, 1, 151, 121], - [9, 69, 43, 4, 70, 44], - [17, 50, 22, 1, 51, 23], - [2, 42, 14, 19, 43, 15], - - # 19 - [3, 141, 113, 4, 142, 114], - [3, 70, 44, 11, 71, 45], - [17, 47, 21, 4, 48, 22], - [9, 39, 13, 16, 40, 14], - - # 20 - [3, 135, 107, 5, 136, 108], - [3, 67, 41, 13, 68, 42], - [15, 54, 24, 5, 55, 25], - [15, 43, 15, 10, 44, 16], - - # 21 - [4, 144, 116, 4, 145, 117], - [17, 68, 42], - [17, 50, 22, 6, 51, 23], - [19, 46, 16, 6, 47, 17], - - # 22 - [2, 139, 111, 7, 140, 112], - [17, 74, 46], - [7, 54, 24, 16, 55, 25], - [34, 37, 13], - - # 23 - [4, 151, 121, 5, 152, 122], - [4, 75, 47, 14, 76, 48], - [11, 54, 24, 14, 55, 25], - [16, 45, 15, 14, 46, 16], - - # 24 - [6, 147, 117, 4, 148, 118], - [6, 73, 45, 14, 74, 46], - [11, 54, 24, 16, 55, 25], - [30, 46, 16, 2, 47, 17], - - # 25 - [8, 132, 106, 4, 133, 107], - [8, 75, 47, 13, 76, 48], - [7, 54, 24, 22, 55, 25], - [22, 45, 15, 13, 46, 16], - - # 26 - [10, 142, 114, 2, 143, 115], - [19, 74, 46, 4, 75, 47], - [28, 50, 22, 6, 51, 23], - [33, 46, 16, 4, 47, 17], - - # 27 - [8, 152, 122, 4, 153, 123], - [22, 73, 45, 3, 74, 46], - [8, 53, 23, 26, 54, 24], - [12, 45, 15, 28, 46, 16], - - # 28 - [3, 147, 117, 10, 148, 118], - [3, 73, 45, 23, 74, 46], - [4, 54, 24, 31, 55, 25], - [11, 45, 15, 31, 46, 16], - - # 29 - [7, 146, 116, 7, 147, 117], - [21, 73, 45, 7, 74, 46], - [1, 53, 23, 37, 54, 24], - [19, 45, 15, 26, 46, 16], - - # 30 - [5, 145, 115, 10, 146, 116], - [19, 75, 47, 10, 76, 48], - [15, 54, 24, 25, 55, 25], - [23, 45, 15, 25, 46, 16], - - # 31 - [13, 145, 115, 3, 146, 116], - [2, 74, 46, 29, 75, 47], - [42, 54, 24, 1, 55, 25], - [23, 45, 15, 28, 46, 16], - - # 32 - [17, 145, 115], - [10, 74, 46, 23, 75, 47], - [10, 54, 24, 35, 55, 25], - [19, 45, 15, 35, 46, 16], - - # 33 - [17, 145, 115, 1, 146, 116], - [14, 74, 46, 21, 75, 47], - [29, 54, 24, 19, 55, 25], - [11, 45, 15, 46, 46, 16], - - # 34 - [13, 145, 115, 6, 146, 116], - [14, 74, 46, 23, 75, 47], - [44, 54, 24, 7, 55, 25], - [59, 46, 16, 1, 47, 17], - - # 35 - [12, 151, 121, 7, 152, 122], - [12, 75, 47, 26, 76, 48], - [39, 54, 24, 14, 55, 25], - [22, 45, 15, 41, 46, 16], - - # 36 - [6, 151, 121, 14, 152, 122], - [6, 75, 47, 34, 76, 48], - [46, 54, 24, 10, 55, 25], - [2, 45, 15, 64, 46, 16], - - # 37 - [17, 152, 122, 4, 153, 123], - [29, 74, 46, 14, 75, 47], - [49, 54, 24, 10, 55, 25], - [24, 45, 15, 46, 46, 16], - - # 38 - [4, 152, 122, 18, 153, 123], - [13, 74, 46, 32, 75, 47], - [48, 54, 24, 14, 55, 25], - [42, 45, 15, 32, 46, 16], - - # 39 - [20, 147, 117, 4, 148, 118], - [40, 75, 47, 7, 76, 48], - [43, 54, 24, 22, 55, 25], - [10, 45, 15, 67, 46, 16], - - # 40 - [19, 148, 118, 6, 149, 119], - [18, 75, 47, 31, 76, 48], - [34, 54, 24, 34, 55, 25], - [20, 45, 15, 61, 46, 16] + # 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], + # 11 + [4, 101, 81], + [1, 80, 50, 4, 81, 51], + [4, 50, 22, 4, 51, 23], + [3, 36, 12, 8, 37, 13], + # 12 + [2, 116, 92, 2, 117, 93], + [6, 58, 36, 2, 59, 37], + [4, 46, 20, 6, 47, 21], + [7, 42, 14, 4, 43, 15], + # 13 + [4, 133, 107], + [8, 59, 37, 1, 60, 38], + [8, 44, 20, 4, 45, 21], + [12, 33, 11, 4, 34, 12], + # 14 + [3, 145, 115, 1, 146, 116], + [4, 64, 40, 5, 65, 41], + [11, 36, 16, 5, 37, 17], + [11, 36, 12, 5, 37, 13], + # 15 + [5, 109, 87, 1, 110, 88], + [5, 65, 41, 5, 66, 42], + [5, 54, 24, 7, 55, 25], + [11, 36, 12, 7, 37, 13], + # 16 + [5, 122, 98, 1, 123, 99], + [7, 73, 45, 3, 74, 46], + [15, 43, 19, 2, 44, 20], + [3, 45, 15, 13, 46, 16], + # 17 + [1, 135, 107, 5, 136, 108], + [10, 74, 46, 1, 75, 47], + [1, 50, 22, 15, 51, 23], + [2, 42, 14, 17, 43, 15], + # 18 + [5, 150, 120, 1, 151, 121], + [9, 69, 43, 4, 70, 44], + [17, 50, 22, 1, 51, 23], + [2, 42, 14, 19, 43, 15], + # 19 + [3, 141, 113, 4, 142, 114], + [3, 70, 44, 11, 71, 45], + [17, 47, 21, 4, 48, 22], + [9, 39, 13, 16, 40, 14], + # 20 + [3, 135, 107, 5, 136, 108], + [3, 67, 41, 13, 68, 42], + [15, 54, 24, 5, 55, 25], + [15, 43, 15, 10, 44, 16], + # 21 + [4, 144, 116, 4, 145, 117], + [17, 68, 42], + [17, 50, 22, 6, 51, 23], + [19, 46, 16, 6, 47, 17], + # 22 + [2, 139, 111, 7, 140, 112], + [17, 74, 46], + [7, 54, 24, 16, 55, 25], + [34, 37, 13], + # 23 + [4, 151, 121, 5, 152, 122], + [4, 75, 47, 14, 76, 48], + [11, 54, 24, 14, 55, 25], + [16, 45, 15, 14, 46, 16], + # 24 + [6, 147, 117, 4, 148, 118], + [6, 73, 45, 14, 74, 46], + [11, 54, 24, 16, 55, 25], + [30, 46, 16, 2, 47, 17], + # 25 + [8, 132, 106, 4, 133, 107], + [8, 75, 47, 13, 76, 48], + [7, 54, 24, 22, 55, 25], + [22, 45, 15, 13, 46, 16], + # 26 + [10, 142, 114, 2, 143, 115], + [19, 74, 46, 4, 75, 47], + [28, 50, 22, 6, 51, 23], + [33, 46, 16, 4, 47, 17], + # 27 + [8, 152, 122, 4, 153, 123], + [22, 73, 45, 3, 74, 46], + [8, 53, 23, 26, 54, 24], + [12, 45, 15, 28, 46, 16], + # 28 + [3, 147, 117, 10, 148, 118], + [3, 73, 45, 23, 74, 46], + [4, 54, 24, 31, 55, 25], + [11, 45, 15, 31, 46, 16], + # 29 + [7, 146, 116, 7, 147, 117], + [21, 73, 45, 7, 74, 46], + [1, 53, 23, 37, 54, 24], + [19, 45, 15, 26, 46, 16], + # 30 + [5, 145, 115, 10, 146, 116], + [19, 75, 47, 10, 76, 48], + [15, 54, 24, 25, 55, 25], + [23, 45, 15, 25, 46, 16], + # 31 + [13, 145, 115, 3, 146, 116], + [2, 74, 46, 29, 75, 47], + [42, 54, 24, 1, 55, 25], + [23, 45, 15, 28, 46, 16], + # 32 + [17, 145, 115], + [10, 74, 46, 23, 75, 47], + [10, 54, 24, 35, 55, 25], + [19, 45, 15, 35, 46, 16], + # 33 + [17, 145, 115, 1, 146, 116], + [14, 74, 46, 21, 75, 47], + [29, 54, 24, 19, 55, 25], + [11, 45, 15, 46, 46, 16], + # 34 + [13, 145, 115, 6, 146, 116], + [14, 74, 46, 23, 75, 47], + [44, 54, 24, 7, 55, 25], + [59, 46, 16, 1, 47, 17], + # 35 + [12, 151, 121, 7, 152, 122], + [12, 75, 47, 26, 76, 48], + [39, 54, 24, 14, 55, 25], + [22, 45, 15, 41, 46, 16], + # 36 + [6, 151, 121, 14, 152, 122], + [6, 75, 47, 34, 76, 48], + [46, 54, 24, 10, 55, 25], + [2, 45, 15, 64, 46, 16], + # 37 + [17, 152, 122, 4, 153, 123], + [29, 74, 46, 14, 75, 47], + [49, 54, 24, 10, 55, 25], + [24, 45, 15, 46, 46, 16], + # 38 + [4, 152, 122, 18, 153, 123], + [13, 74, 46, 32, 75, 47], + [48, 54, 24, 14, 55, 25], + [42, 45, 15, 32, 46, 16], + # 39 + [20, 147, 117, 4, 148, 118], + [40, 75, 47, 7, 76, 48], + [43, 54, 24, 22, 55, 25], + [10, 45, 15, 67, 46, 16], + # 40 + [19, 148, 118, 6, 149, 119], + [18, 75, 47, 31, 76, 48], + [34, 54, 24, 34, 55, 25], + [20, 45, 15, 61, 46, 16], ] def __init__(self, totalCount, dataCount): @@ -973,7 +965,7 @@ class RSBlock(object): return self.totalCount def __repr__(self): - return '(total=%s,data=%s)' % (self.totalCount, self.dataCount) + return "(total=%s,data=%s)" % (self.totalCount, self.dataCount) @staticmethod def getRSBlocks(typeNumber, errorCorrectLevel): @@ -993,7 +985,7 @@ class RSBlock(object): 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] + 2: RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3], }[errorCorrectLevel] @@ -1016,7 +1008,7 @@ class BitBuffer(object): if self.length == len(self.buffer) * 8: self.buffer += [0] * self.inclements if bit: - self.buffer[self.length // 8] |= (0x80 >> (self.length % 8)) + self.buffer[self.length // 8] |= 0x80 >> (self.length % 8) self.length += 1 def put(self, num, length): @@ -1024,12 +1016,14 @@ class BitBuffer(object): 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())) + 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 @@ -1072,10 +1066,12 @@ class GridDrawer(object): # 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) + 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)) @@ -1086,36 +1082,45 @@ class GridDrawer(object): 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) + 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("--text", default="www.inkscape.org") pars.add_argument("--typenumber", type=int, default=0) pars.add_argument("--correctionlevel", type=int, default=0) pars.add_argument("--qrmode", type=int, default=0) pars.add_argument("--encoding", default="latin_1") pars.add_argument("--modulesize", type=float, default=4.0) pars.add_argument("--invert", type=inkex.Boolean, default="false") - pars.add_argument("--drawtype", default="smooth", - choices=["smooth", "pathpreset", "selection", "symbol"]) - pars.add_argument("--smoothness", default="neutral", choices=["neutral", "greedy", "proud"]) + pars.add_argument( + "--drawtype", + default="smooth", + choices=["smooth", "pathpreset", "selection", "symbol"], + ) + pars.add_argument( + "--smoothness", default="neutral", choices=["neutral", "greedy", "proud"] + ) pars.add_argument("--pathtype", default="simple", choices=["simple", "circle"]) pars.add_argument("--smoothval", type=float, default=0.2) - pars.add_argument("--symbolid", default='') - pars.add_argument("--groupid", default='') + pars.add_argument("--symbolid", default="") + pars.add_argument("--groupid", default="") def generate(self): - scale = self.svg.unittouu('1px') # convert to document units + scale = self.svg.unittouu("1px") # convert to document units opt = self.options if not opt.text: - raise inkex.AbortExtension('Please enter an input text') + raise inkex.AbortExtension("Please enter an input text") elif opt.drawtype == "symbol" and opt.symbolid == "": - raise inkex.AbortExtension('Please enter symbol id') + raise inkex.AbortExtension("Please enter symbol id") # for Python 3 ugly hack to represent bytes as str for Python2 compatibility text_str = str(opt.text) @@ -1123,9 +1128,9 @@ class QrCode(inkex.GenerateExtension): text_data = cmode(bytes(opt.text, opt.encoding).decode("latin_1")) grp = Group() - grp.set('inkscape:label', 'QR Code: ' + text_str) + grp.set("inkscape:label", "QR Code: " + text_str) if opt.groupid: - grp.set('id', 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: @@ -1158,7 +1163,7 @@ class QrCode(inkex.GenerateExtension): vertsIndexStart = len(verts) - 1 vertsIndexCur = vertsIndexStart ringIndexes = [] - ci={} + ci = {} for i, v in enumerate(verts): ci.setdefault(v[0], []).append(i) while True: @@ -1170,11 +1175,26 @@ class QrCode(inkex.GenerateExtension): 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]])) + 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] + vertsIndexNext = ( + nextIndexes[0] + if (greedy == "g") + == ( + verts[nextIndexes[0]][2] + == (verts[vertsIndexCur][2] + 1) % 4 + ) + else nextIndexes[1] + ) if vertsIndexNext == vertsIndexStart: break @@ -1197,9 +1217,11 @@ class QrCode(inkex.GenerateExtension): 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])) + 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]) @@ -1211,7 +1233,7 @@ class QrCode(inkex.GenerateExtension): del verts[i] path = PathElement() - path.set('d', qrPathStr) + path.set("d", qrPathStr) return path def render_obsolete(self): @@ -1231,8 +1253,9 @@ class QrCode(inkex.GenerateExtension): pathStr += "M %f,%f " % (x, y) + singlePath + " z " path = PathElement() - path.set('d', pathStr) + path.set("d", pathStr) return path + def render_selection(self): if len(self.svg.selection) > 0: self.options.symbolid = self.svg.selection.first().get_id() @@ -1245,31 +1268,41 @@ class QrCode(inkex.GenerateExtension): if symbol is None: raise inkex.AbortExtension(f"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, - )) + transform = inkex.Transform( + scale=( + float(self.boxsize) / bbox.width, + float(self.boxsize) / bbox.height, + ) + ) result = Group() 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 - result.append(Use.new(symbol, x / transform.a, y / transform.d, transform=transform)) + result.append( + Use.new( + symbol, + x / transform.a, + y / transform.d, + transform=transform, + ) + ) return result def render_pathpreset(self): if self.options.pathtype == "simple": return self.render_path("h 1 v 1 h -1") else: - 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' + 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) - render_smooth = lambda self: self.render_adv(self.options.smoothness[0]) def render_svg(self, grp, drawtype): @@ -1283,12 +1316,12 @@ class QrCode(inkex.GenerateExtension): # 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" + 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.style["stroke"] = "none" + qrg.style["fill"] = "white" if self.invert_code else "black" qrg.add(drawer()) def get_svg_pos(self, col, row): @@ -1298,7 +1331,7 @@ class QrCode(inkex.GenerateExtension): result = "" digBuffer = "" for c in pointStr: - if c.isdigit() or c == "-" or c == '.': + if c.isdigit() or c == "-" or c == ".": digBuffer += c else: if len(digBuffer) > 0: @@ -1312,6 +1345,5 @@ class QrCode(inkex.GenerateExtension): return result - -if __name__ == '__main__': +if __name__ == "__main__": QrCode().run() diff --git a/render_gear_rack.py b/render_gear_rack.py index 201df6f4..229713cd 100755 --- a/render_gear_rack.py +++ b/render_gear_rack.py @@ -24,9 +24,10 @@ 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)) + return (sqrt(R**2 - Rb**2) / Rb) - (acos(Rb / R)) def point_on_circle(radius, angle): @@ -41,22 +42,23 @@ def points_to_svgd(p): """ f = p[0] p = p[1:] - svgd = 'M{:.3f},{:.3f}'.format(f[0], f[1]) + svgd = "M{:.3f},{:.3f}".format(f[0], f[1]) for x in p: - svgd += 'L{:.3f},{:.3f}'.format(x[0], x[1]) + svgd += "L{:.3f},{:.3f}".format(x[0], x[1]) return svgd class RackGear(inkex.GenerateExtension): - container_label = 'Rendered Gear Rack' + 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') + 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 @@ -69,13 +71,18 @@ class RackGear(inkex.GenerateExtension): points.append((x + tas, spacing)) points.append((x + spacing, spacing)) points.append((x + spacing + tas, 0)) - x += spacing * 2. + x += spacing * 2.0 path = points_to_svgd(points) # Create SVG Path for gear - style = {'stroke': '#000000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))} + 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__': + +if __name__ == "__main__": RackGear().run() diff --git a/render_gears.py b/render_gears.py index c23521d6..b160bc0e 100755 --- a/render_gears.py +++ b/render_gears.py @@ -29,7 +29,7 @@ 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)) + return (sqrt(R**2 - Rb**2) / Rb) - (acos(Rb / R)) def point_on_circle(radius, angle): @@ -41,28 +41,36 @@ def point_on_circle(radius, angle): def points_to_svgd(p): f = p[0] p = p[1:] - svgd = 'M{:.5f},{:.5f}'.format(f[0], f[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' + svgd += " L{:.5f},{:.5f}".format(x[0], x[1]) + svgd += "z" return svgd class Gears(inkex.GenerateExtension): - container_label = 'Rendered Gears' + 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=20.0, help="Diameter of hole") - pars.add_argument("--unit", default="px", help="unit for pitch and center diameter") + pars.add_argument( + "--centerdiameter", type=float, default=20.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) + 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 @@ -91,7 +99,7 @@ class Gears(inkex.GenerateExtension): tooth = (pi * pitch_diameter) / (2.0 * float(teeth)) # Undercut? - undercut = (2.0 / (sin(radians(angle)) ** 2)) + 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. @@ -106,7 +114,9 @@ class Gears(inkex.GenerateExtension): 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 + 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)] @@ -133,7 +143,9 @@ class Gears(inkex.GenerateExtension): 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) + 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) @@ -149,7 +161,11 @@ class Gears(inkex.GenerateExtension): path = points_to_svgd(points) # Create SVG Path for gear - style = {'stroke': '#000000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))} + style = { + "stroke": "#000000", + "fill": "none", + "stroke-width": str(self.svg.unittouu("1px")), + } gear = PathElement() gear.style = style gear.path = path @@ -161,5 +177,5 @@ class Gears(inkex.GenerateExtension): yield arc -if __name__ == '__main__': +if __name__ == "__main__": Gears().run() diff --git a/replace_font.py b/replace_font.py index d588e0db..1aff7fe0 100755 --- a/replace_font.py +++ b/replace_font.py @@ -27,12 +27,15 @@ 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'] +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): """ @@ -53,6 +56,7 @@ def set_font(node, new_font, style=None): dirty = True return dirty + def find_replace_font(node, find, replace): """ Searches the relevant font attributes/styles of node for find, and @@ -69,12 +73,14 @@ def find_replace_font(node, find, replace): 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 + return node.tag in text_tags and "style" in node.attrib + def is_text(node): """ @@ -98,13 +104,19 @@ def get_fonts(node): 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.')) + inkex.errormsg( + _( + "Couldn't find anything using that font, please ensure the spelling and spacing is correct." + ) + ) + def report_findings(findings): """ @@ -114,14 +126,16 @@ def report_findings(findings): 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]) + inkex.errormsg(_("Found the following font only: %s") % findings[0]) else: - inkex.errormsg(_(u"Found the following fonts:\n%s") % '\n'.join(findings)) + inkex.errormsg(_("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") @@ -197,7 +211,9 @@ class ReplaceFont(inkex.EffectExtension): 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) + action = self.options.action.strip( + '"' + ) # TODO Is this a bug? (Extra " characters) scope = self.options.scope relevant_items = self.relevant_items(scope) @@ -205,19 +221,26 @@ class ReplaceFont(inkex.EffectExtension): 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.")) + 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.")) + 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.")) + 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/restack.py b/restack.py index 9b819689..24a9080c 100755 --- a/restack.py +++ b/restack.py @@ -28,22 +28,28 @@ 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'} +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( + "--tab", type=self.arg_method("restack"), default=self.restack_positional + ) pars.add_argument("--direction", default="lr", help="direction to restack") pars.add_argument("--angle", type=float, default=0.0, help="arbitrary angle") pars.add_argument("--xanchor", default="l", help="horizontal point to compare") pars.add_argument("--yanchor", default="t", 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') + 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: @@ -71,7 +77,7 @@ class Restack(inkex.EffectExtension): 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: + if "custom" in self.options.nb_direction: direction = self.options.angle return node.bounding_box().get_anchor(x, y, direction, selbox) @@ -87,5 +93,6 @@ class Restack(inkex.EffectExtension): parentnode.append(item) return True -if __name__ == '__main__': + +if __name__ == "__main__": Restack().run() diff --git a/rtree.py b/rtree.py index e4ead2e8..728d9311 100755 --- a/rtree.py +++ b/rtree.py @@ -21,27 +21,40 @@ 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=40.0, - help="minimum branch size") - pars.add_argument("--pentoggle", type=inkex.Boolean, default=False, - help="Lift pen for backward steps") + pars.add_argument( + "--size", type=float, default=100.0, help="initial branch size" + ) + pars.add_argument( + "--minimum", type=float, default=40.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') + 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' - }) + 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) @@ -49,5 +62,6 @@ class TurtleRtree(inkex.GenerateExtension): tur.rtree(self.options.size, self.options.minimum, self.options.pentoggle) return inkex.PathElement(d=tur.getPath(), style=str(style)) -if __name__ == '__main__': + +if __name__ == "__main__": TurtleRtree().run() diff --git a/rubberstretch.py b/rubberstretch.py index 3d12a38b..eb1baf99 100755 --- a/rubberstretch.py +++ b/rubberstretch.py @@ -32,6 +32,7 @@ import inkex 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)) @@ -49,17 +50,42 @@ class RubberStretch(Diffeo): by0 = -self.bbox.center.y x, y = (bpt[0] - bx0), (bpt[1] - by0) - sx1 = (1 + self.curve * (x / (self.bbox.width / 2) + 1) * \ - (x / (self.bbox.width / 2) - 1)) * 2 ** self.ratio - sy1 = (1 + self.curve * (y / (self.bbox.height / 2) + 1) * \ - (y / (self.bbox.height / 2) - 1)) * 2 ** self.ratio + sx1 = ( + 1 + + self.curve + * (x / (self.bbox.width / 2) + 1) + * (x / (self.bbox.width / 2) - 1) + ) * 2**self.ratio + sy1 = ( + 1 + + self.curve + * (y / (self.bbox.height / 2) + 1) + * (y / (self.bbox.height / 2) - 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 + 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] diff --git a/scribus_export_pdf.py b/scribus_export_pdf.py index c7a92ce1..4641b9bd 100644 --- a/scribus_export_pdf.py +++ b/scribus_export_pdf.py @@ -40,31 +40,63 @@ VERSION_REGEX = re.compile(r"(\d+)\.(\d+)\.(\d+)") # (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=11, - help="PDF version (e.g. integer numbers between 11 and 15, see Scribus documentation for details)") - arg_parser.add_argument("--bleed", type=float, dest="bleed", default=0.0, - help="Bleed value") - arg_parser.add_argument("--bleed-marks", type=inkex.Boolean, dest="bleedMarks", - default=False, help="Draw bleed marks") - arg_parser.add_argument("--color-marks", type=inkex.Boolean, dest="colorMarks", - default=False, help="Draw color Marks") - arg_parser.add_argument("--intent", type=int, dest="intent", default=0, - help="Rendering intent. Options: 0: Perceptual, 1: Relative Colorimetric, 2: Saturation, 3: Absolute Colorimetric") - arg_parser.add_argument("--title", type=str, dest="title", default="", help="PDF title, required for PDF/X") - #arg_parser.add_argument("--fonts", type=int, dest="fonts", default="1", + arg_parser.add_argument( + "--pdf-version", + type=int, + dest="pdfVersion", + default=11, + help="PDF version (e.g. integer numbers between 11 and 15, see Scribus documentation for details)", + ) + arg_parser.add_argument( + "--bleed", type=float, dest="bleed", default=0.0, help="Bleed value" + ) + arg_parser.add_argument( + "--bleed-marks", + type=inkex.Boolean, + dest="bleedMarks", + default=False, + help="Draw bleed marks", + ) + arg_parser.add_argument( + "--color-marks", + type=inkex.Boolean, + dest="colorMarks", + default=False, + help="Draw color Marks", + ) + arg_parser.add_argument( + "--intent", + type=int, + dest="intent", + default=0, + help="Rendering intent. Options: 0: Perceptual, 1: Relative Colorimetric, 2: Saturation, 3: Absolute Colorimetric", + ) + arg_parser.add_argument( + "--title", + type=str, + dest="title", + default="", + help="PDF title, required for PDF/X", + ) + # 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 + embedFonts = 1 # self.options.fonts bleedMarks = self.options.bleedMarks colorMarks = self.options.colorMarks - if ((bleedMarks or colorMarks) and margin < 7): - raise AbortExtension("You need at least 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(f""" + if (bleedMarks or colorMarks) and margin < 7: + raise AbortExtension( + "You need at least 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( + f""" import scribus import sys icc = "{icc}" @@ -108,46 +140,54 @@ class exportPDF(): pdf.thumbnails = True pdf.save() -exportPDF()""") +exportPDF()""" + ) def save(self, stream): - scribus_version = call(SCRIBUS_EXE, '-g', '--version') + scribus_version = call(SCRIBUS_EXE, "-g", "--version") version_match = VERSION_REGEX.search(scribus_version) if version_match is None: - raise AbortExtension(f"Could not detect Scribus version ({scribus_version})") + raise AbortExtension( + f"Could not detect Scribus version ({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(f"Found Scribus {version_match.group(0)}. This extension requires Scribus 1.5.x.") + raise AbortExtension( + f"Found Scribus {version_match.group(0)}. This extension requires Scribus 1.5.x." + ) input_file = self.options.input_file - py_file = os.path.join(self.tempdir, 'scribus.py') - svg_file = os.path.join(self.tempdir, 'in.svg') + 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("Please select a color profile in the document settings.") + raise AbortExtension( + "Please select a color profile in the document settings." + ) elif len(profiles) > 1: - raise AbortExtension("Please only link a single color profile in the document settings. No output generated.") + raise AbortExtension( + "Please only link a single color profile in the document settings. 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')) + 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: + 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: + 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() +if __name__ == "__main__": + Scribus().run() diff --git a/setup.py b/setup.py index 4af85728..efcb08ba 100755 --- a/setup.py +++ b/setup.py @@ -20,35 +20,35 @@ 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 :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - ], - packages=['inkex', 'inkex.elements', 'inkex.tester'], - install_requires=[ - 'cssselect', - 'lxml', - 'numpy', - 'packaging', - 'pyserial', - 'scour', - ], - setup_requires=["pytest-runner"], - tests_require=["pytest", "pytest-cov"] + 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 :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + ], + packages=["inkex", "inkex.elements", "inkex.tester"], + install_requires=[ + "cssselect", + "lxml", + "numpy", + "packaging", + "pyserial", + "scour", + ], + setup_requires=["pytest-runner"], + tests_require=["pytest", "pytest-cov"], ) diff --git a/setup_typography_canvas.py b/setup_typography_canvas.py index c7c3f9a5..54599bf3 100755 --- a/setup_typography_canvas.py +++ b/setup_typography_canvas.py @@ -29,14 +29,14 @@ 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("-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: str, position: Union[int, float]) \ - -> inkex.BaseElement: + def create_horizontal_guideline( + self, name: str, position: Union[int, float] + ) -> inkex.BaseElement: """Create a horizontal guideline with name and position Args: @@ -46,8 +46,9 @@ class SetupTypographyCanvas(inkex.EffectExtension): Returns: inkex.BaseElement: the created guideline """ - return self.svg.namedview \ - .add(inkex.Guide().move_to(0, position, (0, 1)).update(inkscape__label=name)) + return self.svg.namedview.add( + inkex.Guide().move_to(0, position, (0, 1)).update(inkscape__label=name) + ) def effect(self): # Get all the options @@ -71,10 +72,10 @@ class SetupTypographyCanvas(inkex.EffectExtension): 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)) + 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__': +if __name__ == "__main__": SetupTypographyCanvas().run() diff --git a/spirograph.py b/spirograph.py index 9a09b826..69e69957 100755 --- a/spirograph.py +++ b/spirograph.py @@ -21,32 +21,53 @@ import math import inkex + class Spirograph(inkex.EffectExtension): def add_arguments(self, pars): - pars.add_argument("--primaryr", type=float, default=100.0, - help="The radius of the outer gear") - pars.add_argument("--secondaryr", type=float, default=60.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") + pars.add_argument( + "--primaryr", type=float, default=100.0, help="The radius of the outer gear" + ) + pars.add_argument( + "--secondaryr", + type=float, + default=60.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') + 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'): + if self.options.gearplacement.strip(" ").lower().startswith("outside"): a = self.options.primaryr + self.options.secondaryr flip = -1 else: @@ -58,12 +79,12 @@ class Spirograph(inkex.EffectExtension): return scale = 2 * math.pi / (ratio * self.options.quality) - rotation = - math.pi * self.options.rotation / 180 + rotation = -math.pi * self.options.rotation / 180 new = inkex.PathElement() - new.style = inkex.Style(stroke='#000000', fill='none', stroke_width='1.0') + new.style = inkex.Style(stroke="#000000", fill="none", stroke_width="1.0") - path_string = '' + path_string = "" maxPointCount = 1000 for i in range(maxPointCount): @@ -71,32 +92,61 @@ class Spirograph(inkex.EffectExtension): 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 + 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)) + 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' + 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)) + path_string += " C {},{} ".format(str(x + dx), str(y + dy)) new.path = path_string self.svg.get_current_layer().append(new) -if __name__ == '__main__': + +if __name__ == "__main__": Spirograph().run() diff --git a/straightseg.py b/straightseg.py index ece72edc..55f4d454 100755 --- a/straightseg.py +++ b/straightseg.py @@ -22,13 +22,25 @@ 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=50.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") + pars.add_argument( + "-p", + "--percent", + type=float, + default=50.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): @@ -36,27 +48,36 @@ class SegmentStraightener(inkex.EffectExtension): last = [] sub_start = [] for cmd, params in path: - if cmd == 'C': + 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) + # 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 + # 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': + 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': + if cmd == "M": sub_start = params[-2:] - if cmd == 'Z': + if cmd == "Z": last = sub_start[:] else: last = params[-2:] node.path = path -if __name__ == '__main__': + +if __name__ == "__main__": SegmentStraightener().run() diff --git a/svgcalendar.py b/svgcalendar.py index 4e77a6e9..6339cf64 100755 --- a/svgcalendar.py +++ b/svgcalendar.py @@ -38,6 +38,7 @@ import inkex from inkex import TextElement if sys.version_info[0] > 2: + def unicode(stringlike, encoding): """Compatibility for python 2 strings""" if isinstance(stringlike, bytes): @@ -47,116 +48,225 @@ if sys.version_info[0] > 2: 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.') + "--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( - "--months-per-line", type=int, dest="months_per_line", default=3, - help='Number of months side by side.') + "--auto-organize", + type=inkex.Boolean, + dest="auto_organize", + default=True, + help="Automatically set the size and positions.", + ) pars.add_argument( - "--month-width", type=str, dest="month_width", default="6cm", - help='The width of the month days box.') + "--months-per-line", + type=int, + dest="months_per_line", + default=3, + help="Number of months side by side.", + ) pars.add_argument( - "--month-margin", type=str, dest="month_margin", default="1cm", - help='The space between the month boxes.') + "--month-width", + type=str, + dest="month_width", + default="6cm", + help="The width of the month days box.", + ) pars.add_argument( - "--color-year", type=inkex.Color, dest="color_year", default=inkex.Color(0x808080FF), - help='Color for the year header.') + "--month-margin", + type=str, + dest="month_margin", + default="1cm", + help="The space between the month boxes.", + ) pars.add_argument( - "--color-month", type=inkex.Color, dest="color_month", default=inkex.Color(0x686868FF), - help='Color for the month name header.') + "--color-year", + type=inkex.Color, + dest="color_year", + default=inkex.Color(0x808080FF), + help="Color for the year header.", + ) pars.add_argument( - "--color-day-name", type=inkex.Color, dest="color_day_name", + "--color-month", + type=inkex.Color, + dest="color_month", + default=inkex.Color(0x686868FF), + help="Color for the month name header.", + ) + pars.add_argument( + "--color-day-name", + type=inkex.Color, + dest="color_day_name", default=inkex.Color(0x909090FF), - help='Color for the week day names header.') + help="Color for the week day names header.", + ) pars.add_argument( - "--color-day", type=inkex.Color, dest="color_day", default=inkex.Color(0x000000FF), - help='Color for the common day box.') + "--color-day", + type=inkex.Color, + dest="color_day", + default=inkex.Color(0x000000FF), + help="Color for the common day box.", + ) pars.add_argument( - "--color-weekend", type=inkex.Color, dest="color_weekend", + "--color-weekend", + type=inkex.Color, + dest="color_weekend", default=inkex.Color(0x787878FF), - help='Color for the weekend days.') + help="Color for the weekend days.", + ) pars.add_argument( - "--color-nmd", type=inkex.Color, dest="color_nmd", default=inkex.Color(0xB0B0B0FF), - help='Color for the next month day, in empty day boxes.') + "--color-nmd", + type=inkex.Color, + dest="color_nmd", + default=inkex.Color(0xB0B0B0FF), + help="Color for the next month day, in empty day boxes.", + ) pars.add_argument( - "--color-weeknr", type=inkex.Color, dest="color_weeknr", + "--color-weeknr", + type=inkex.Color, + dest="color_weeknr", default=inkex.Color(0x787878FF), - help='Color for the week numbers.') + help="Color for the week numbers.", + ) pars.add_argument( - "--font-year", type=str, dest="font_year", default="arial", - help='Font for the year string.') + "--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.') + "--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.') + "--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.') + "--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.') + "--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.') + "--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.') + "--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='arabic', - help='The input encoding of the names.') + "--encoding", + type=str, + dest="input_encode", + default="arabic", + help="The input encoding of the names.", + ) def validate_options(self): """Validity check for various text inputs""" # inkex.errormsg( self.options.input_encode ) # Convert string names lists in real lists - month_name = re.match(r'\s*(.*[^\s])\s*', self.options.month_names) - self.options.month_names = re.split(r'\s+', month_name.group(1)) - month_name = re.match(r'\s*(.*[^\s])\s*', self.options.day_names) - self.options.day_names = re.split(r'\s+', month_name.group(1)) + month_name = re.match(r"\s*(.*[^\s])\s*", self.options.month_names) + self.options.month_names = re.split(r"\s+", month_name.group(1)) + month_name = re.match(r"\s*(.*[^\s])\s*", self.options.day_names) + self.options.day_names = re.split(r"\s+", month_name.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'] + 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'] + 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' + self.options.start_day = "mon" # Set the calendar start day - if self.options.start_day == 'sun': + if self.options.start_day == "sun": calendar.setfirstweekday(6) else: calendar.setfirstweekday(0) @@ -171,8 +281,8 @@ class Calendar(inkex.EffectExtension): 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')) + 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: @@ -195,60 +305,62 @@ class Calendar(inkex.EffectExtension): 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 + 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 + "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_weekend["fill"] = self.options.color_weekend self.style_nmd = self.style_day.copy() - self.style_nmd['fill'] = self.options.color_nmd + 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_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_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_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) + self.style_weeknr["fill"] = self.options.color_weeknr + self.style_weeknr["font-size"] = str(self.day_w / 3) def is_weekend(self, pos): """Detect weekend days; weekend values: "sat+sun" or "sat" or "sun" """ - if self.options.start_day == 'sun': - if self.options.weekend == 'sat+sun' and pos == 0: + 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: + if self.options.weekend == "sat+sun" and pos == 6: return True - if self.options.weekend == 'sat' and pos == 6: + if self.options.weekend == "sat" and pos == 6: return True - if self.options.weekend == 'sun' and pos == 0: + if self.options.weekend == "sun" and pos == 0: return True else: - if self.options.weekend == 'sat+sun' and pos == 5: + if self.options.weekend == "sat+sun" and pos == 5: return True - if self.options.weekend == 'sat+sun' and pos == 6: + if self.options.weekend == "sat+sun" and pos == 6: return True - if self.options.weekend == 'sat' and pos == 5: + if self.options.weekend == "sat" and pos == 5: return True - if self.options.weekend == 'sun' and pos == 6: + if self.options.weekend == "sun" and pos == 6: return True return False @@ -263,19 +375,21 @@ class Calendar(inkex.EffectExtension): def write_month_header(self, g, m): """Write month header""" - txt_atts = {'style': str(inkex.Style(self.style_month)), - 'x': str((self.month_w - self.day_w) / 2), - 'y': str(self.day_h / 5)} + 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) + self.options.month_names[m - 1], self.options.input_encode + ) except: - raise ValueError('You must select a correct system encoding.') + raise ValueError("You must select a correct system encoding.") week_group = g.add(inkex.Group()) week_x = 0 - if self.options.start_day == 'sun': + if self.options.start_day == "sun": day_names = self.options.day_names[:] else: day_names = self.options.day_names[1:] @@ -285,49 +399,53 @@ class Calendar(inkex.EffectExtension): 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)} + txt_atts = { + "style": str(inkex.Style(self.style_day_name)), + "x": str(self.day_w * week_x), + "y": str(self.day_h), + } try: week_group.add(TextElement(**txt_atts)).text = unicode( - wday, self.options.input_encode) + wday, self.options.input_encode + ) except: - raise ValueError('You must select a correct system encoding.') + raise ValueError("You must select a correct system encoding.") week_x += 1 def create_month(self, m): """Lay out one month in place on the calendar""" 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)} + "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)) + 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)) + 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)) + 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)) + 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]) @@ -341,21 +459,30 @@ class Calendar(inkex.EffectExtension): 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))): + 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))} + 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: @@ -366,9 +493,11 @@ class Calendar(inkex.EffectExtension): 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))} + 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 @@ -395,11 +524,13 @@ class Calendar(inkex.EffectExtension): self.validate_options() self.calculate_size_and_positions() parent = self.document.getroot() - txt_atts = {'id': 'year_' + str(self.options.year)} + 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)} + 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: @@ -411,5 +542,6 @@ class Calendar(inkex.EffectExtension): return inkex.errormsg(str(err)) return None -if __name__ == '__main__': + +if __name__ == "__main__": Calendar().run() diff --git a/svgfont2layers.py b/svgfont2layers.py index 49c827fd..e9d17100 100755 --- a/svgfont2layers.py +++ b/svgfont2layers.py @@ -21,11 +21,17 @@ 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.") + 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""" @@ -37,15 +43,15 @@ class SvgFontToLayers(inkex.EffectExtension): 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') + 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") + # setwidth = font.get("horiz-adv-x") baseline = font.get("horiz-origin-y") if baseline is None: baseline = 0 - fontface = font.findone('svg:font-face') + fontface = font.findone("svg:font-face") # TODO: where should we save the font family name? # fontfamily = fontface.get("font-family") @@ -66,7 +72,7 @@ class SvgFontToLayers(inkex.EffectExtension): # TODO: missing-glyph count = 0 - for glyph in font.findall('svg:glyph'): + for glyph in font.findall("svg:glyph"): unicode_char = glyph.get("unicode") if unicode_char is None: continue @@ -74,7 +80,7 @@ class SvgFontToLayers(inkex.EffectExtension): 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' + layer.style["display"] = "none" ############################ # Option 1: @@ -87,7 +93,7 @@ class SvgFontToLayers(inkex.EffectExtension): ############################ # Option 2: # Using svg:paths as childnodes of svg:glyph - for elem in glyph.findall('svg:path'): + for elem in glyph.findall("svg:path"): new_path = layer.add(inkex.PathElement()) new_path.path = self.flip_cordinate_system(elem, emsize, baseline) @@ -101,5 +107,6 @@ class SvgFontToLayers(inkex.EffectExtension): if count >= self.options.count: break -if __name__ == '__main__': + +if __name__ == "__main__": SvgFontToLayers().run() diff --git a/synfig_fileformat.py b/synfig_fileformat.py index c92ae95c..b8a45321 100755 --- a/synfig_fileformat.py +++ b/synfig_fileformat.py @@ -37,94 +37,108 @@ default_composite = { } 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["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], -}) +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], + } +) 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] -}) +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] -}) +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["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["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["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["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"] -}) +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"] = { @@ -135,24 +149,24 @@ layers["warp"] = { "dest_br": ["vector", [1, -1]], "dest_bl": ["vector", [-1, -1]], "clip": ["bool", False], - "horizon": ["real", 4.0] + "horizon": ["real", 4.0], } layers["rotate"] = { "origin": ["vector", [0.0, 0.0]], - "amount": ["angle", 0] # + "amount": ["angle", 0], # } -layers["translate"] = { - "origin": ["vector", [0.0, 0.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 -}) +layers["blur"].update( + { + "size": ["vector", [1, 1]], + "type": ["integer", 3], # 1 is fast gaussian, 3 is regular + } +) # ##### Layer versions ##################################### layer_versions = { @@ -160,7 +174,7 @@ layer_versions = { "rectangle": "0.2", "linear_gradient": "0.0", "blur": "0.2", - None: "0.1" # default + None: "0.1", # default } # ##### Blend Methods ###################################### @@ -186,7 +200,7 @@ blend_method_names = { 11: "luminance", 14: "alpha brighten", # deprecated 15: "alpha darken", # deprecated - 19: "alpha over" # deprecated + 19: "alpha over", # deprecated } blend_methods = dict((v, k) for (k, v) in blend_method_names.items()) diff --git a/synfig_output.py b/synfig_output.py index fdc6c87b..2a37c845 100755 --- a/synfig_output.py +++ b/synfig_output.py @@ -27,9 +27,19 @@ from copy import deepcopy from lxml import etree import inkex -from inkex import Group, Layer, Anchor, Switch, PathElement, \ - Metadata, NamedView, Gradient, SvgDocumentElement, \ - Path, Transform +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 @@ -38,6 +48,7 @@ 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 @@ -46,7 +57,7 @@ class SynfigDocument(object): def __init__(self, width=1024, height=768, name="Synfig Animation 1"): self.root_canvas = etree.fromstring( - """ + """ {} - """.format(width, height, name) + """.format( + width, height, name + ) ) self._update_viewbox() @@ -75,10 +88,12 @@ class SynfigDocument(object): 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) + 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): @@ -141,7 +156,9 @@ class SynfigDocument(object): y = self.height - y - assert self.coor_svg2sif([x, y]) == vector, "sif to svg coordinate conversion error" + assert ( + self.coor_svg2sif([x, y]) == vector + ), "sif to svg coordinate conversion error" return [x, y] @@ -407,7 +424,16 @@ class SynfigDocument(object): # ## 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"): + def create_layer( + self, + layer_type, + desc, + params={}, + guids={}, + canvas=None, + active=True, + version="auto", + ): """Create a new layer Keyword arguments: @@ -433,11 +459,15 @@ class SynfigDocument(object): param_guid = None if param_value is not None: - self.build_param(layer, param_name, param_value, param_type, guid=param_guid) + 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): + def set_param( + self, layer, name, value, param_type="auto", guid=None, modify_linked=False + ): """Set a layer parameter Keyword arguments: @@ -480,9 +510,17 @@ class SynfigDocument(object): """ 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) + 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) + 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 @@ -507,7 +545,9 @@ class SynfigDocument(object): elif param_type == "integer": return int(param[0].get("integer", "0")) else: - raise Exception("Getting this type of parameter not yet implemented") + raise Exception( + "Getting this type of parameter not yet implemented" + ) # ## Global defs, and related @@ -517,14 +557,23 @@ class SynfigDocument(object): 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"): + 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 + "spreadMethod": spread_method, } if stops: gradient["stops"] = stops @@ -535,7 +584,17 @@ class SynfigDocument(object): 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"): + 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", @@ -543,7 +602,7 @@ class SynfigDocument(object): "radius": radius, "focus": focus, "mtx": mtx, - "spreadMethod": spread_method + "spreadMethod": spread_method, } if stops: gradient["stops"] = stops @@ -641,8 +700,10 @@ class SynfigDocument(object): # 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])] + 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 @@ -659,12 +720,14 @@ class SynfigDocument(object): g["radius"] = self.distance_svg2sif(g["radius"]) # Delete extra attribs - removed_attribs = ["type", - "stops", - "stops_guid", - "mtx", - "focus", - "spreadMethod"] + removed_attribs = [ + "type", + "stops", + "stops_guid", + "mtx", + "focus", + "spreadMethod", + ] for x in removed_attribs: if x in g.keys(): del g[x] @@ -686,10 +749,11 @@ class SynfigDocument(object): Returns: list of layers """ - blur = self.create_layer("blur", name, params={ - "blend_method": sif.blend_methods["straight"], - "size": [x, y] - }) + blur = self.create_layer( + "blur", + name, + params={"blend_method": sif.blend_methods["straight"], "size": [x, y]}, + ) if is_end: return layers + [blur] @@ -714,7 +778,9 @@ class SynfigDocument(object): return layers overlay_enc = self.op_encapsulate([overlay]) - self.set_param(overlay_enc[0], "blend_method", sif.blend_methods["straight onto"]) + self.set_param( + overlay_enc[0], "blend_method", sif.blend_methods["straight onto"] + ) ret = layers + overlay_enc if is_end: @@ -839,14 +905,18 @@ class SynfigDocument(object): 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) - }) + 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] @@ -858,6 +928,7 @@ class SynfigDocument(object): # ## 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 @@ -910,11 +981,15 @@ def path_to_bline_list(path_d, nodetypes=None, mtx=[[1.0, 0.0, 0.0], [0.0, 1.0, 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)) + 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]) + 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 @@ -925,10 +1000,10 @@ def path_to_bline_list(path_d, nodetypes=None, mtx=[[1.0, 0.0, 0.0], [0.0, 1.0, nt = nt[1:] elif cmd in "LHV": bline_list[-1]["points"].append([lastctrl[:], last[:], last[:], lastsplit]) - if cmd == 'H': + if cmd == "H": last = [params[0], last[1]] lastctrl = [params[0], last[1]] - elif cmd == 'V': + elif cmd == "V": last = [last[0], params[0]] lastctrl = [last[0], params[0]] else: @@ -936,31 +1011,36 @@ def path_to_bline_list(path_d, nodetypes=None, mtx=[[1.0, 0.0, 0.0], [0.0, 1.0, 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]) + 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': + 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] + x1 = 1.0 / 3 * q0[0] + 2.0 / 3 * q1[0] + x2 = 2.0 / 3 * q1[0] + 1.0 / 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] + y1 = 1.0 / 3 * q0[1] + 2.0 / 3 * q1[1] + y2 = 2.0 / 3 * q1[1] + 1.0 / 3 * q2[1] y3 = q2[1] - bline_list[-1]["points"].append([lastctrl[:], [x0, y0], [x1, y1], lastsplit]) + 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': + elif cmd == "A": from inkex.paths import arc_to_path + arcp = arc_to_path(last[:], params[:]) arcp[0][0] = lastctrl[:] last = arcp[-1][1] @@ -981,7 +1061,9 @@ def path_to_bline_list(path_d, nodetypes=None, mtx=[[1.0, 0.0, 0.0], [0.0, 1.0, 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]) + bline_list[-1]["points"].append( + [lastctrl[:], last[:], last[:], lastsplit] + ) # Clear the variables (no more points need to be added) last = [] @@ -1008,6 +1090,7 @@ def path_to_bline_list(path_d, nodetypes=None, mtx=[[1.0, 0.0, 0.0], [0.0, 1.0, # ## Style related + def extract_color(style, color_attrib, *opacity_attribs): if color_attrib in style.keys(): if style[color_attrib] == "none": @@ -1017,7 +1100,12 @@ def extract_color(style, color_attrib, *opacity_attribs): 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] + 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(): @@ -1057,11 +1145,11 @@ class SynfigExport(SynfigPrep): width = get_dimension(svg.get("width", 1024)) height = get_dimension(svg.get("height", 768)) - title = svg.getElement('svg:title') + title = svg.getElement("svg:title") if title: name = title.text else: - name = svg.get('sodipodi:docname', "Synfig Animation 1") + name = svg.get("sodipodi:docname", "Synfig Animation 1") doc = SynfigDocument(width, height, name) @@ -1084,7 +1172,9 @@ class SynfigExport(SynfigPrep): if isinstance(node, SvgDocumentElement): self.parse_defs(node, d) return [] - elif not isinstance(node, (Group, Anchor, Switch, PathElement, Metadata, NamedView)): + elif not isinstance( + node, (Group, Anchor, Switch, PathElement, Metadata, NamedView) + ): # An unsupported element return [] @@ -1131,13 +1221,27 @@ class SynfigExport(SynfigPrep): mtx = node.gradientTransform.matrix - link = node.get('xlink:href', "#")[1:] + 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) + 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) + 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")) @@ -1148,13 +1252,29 @@ class SynfigExport(SynfigPrep): mtx = node.gradientTransform.matrix - link = node.get('xlink:href', "#")[1:] + 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) + 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) + 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 = {} @@ -1174,8 +1294,7 @@ class SynfigExport(SynfigPrep): # 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} + refs = {None: layers, "SourceGraphic": layers} # default encapsulate_result = not is_end for child in node.iterchildren(): @@ -1221,7 +1340,9 @@ class SynfigExport(SynfigPrep): 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") + 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: @@ -1255,7 +1376,7 @@ class SynfigExport(SynfigPrep): style = node.style mtx = node.transform.matrix - blines = path_to_bline_list(node.get("d"), node.get('sodipodi:nodetypes'), mtx) + 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() @@ -1268,18 +1389,27 @@ class SynfigExport(SynfigPrep): 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 - }) + 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] + 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] + layer = d.op_fade([layer], extract_opacity(style, "fill-opacity"))[ + 0 + ] layers.append(layer) @@ -1291,21 +1421,34 @@ class SynfigExport(SynfigPrep): 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 - }) + 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] + 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] + layer = d.op_fade( + [layer], extract_opacity(style, "stroke-opacity") + )[0] layers.append(layer) @@ -1319,18 +1462,24 @@ class SynfigExport(SynfigPrep): return [None] if gradient["type"] == "linear": - layer = d.create_layer("linear_gradient", url_id, - d.gradient_to_params(gradient), - guids={"gradient": gradient["stops_guid"]}) + 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"]}) + 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__': +if __name__ == "__main__": SynfigExport().run() diff --git a/synfig_prepare.py b/synfig_prepare.py index b795aeb7..a4dbd146 100755 --- a/synfig_prepare.py +++ b/synfig_prepare.py @@ -26,11 +26,20 @@ import tempfile from subprocess import PIPE, Popen import inkex -from inkex import load_svg, Group, PathElement, ShapeElement,\ - Anchor, Switch, SvgDocumentElement, Transform +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""" @@ -43,7 +52,9 @@ class MalformedSVGError(Exception): 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) +""" % repr( + self.value + ) class InkscapeActionGroup(object): @@ -129,7 +140,12 @@ class InkscapeActionGroup(object): return cmd = self.init_args + " " + self.command + "--verb=FileSave --verb=FileQuit" - p = Popen('inkscape "{}" {}'.format(filename, cmd), shell=True, stdout=PIPE, stderr=PIPE) + p = Popen( + 'inkscape "{}" {}'.format(filename, cmd), + shell=True, + stdout=PIPE, + stderr=PIPE, + ) rc = p.wait() f = p.stdout err = p.stderr @@ -150,7 +166,7 @@ class InkscapeActionGroup(object): self.run_file(svgfile) # Open the resulting file - with open(svgfile, 'r') as stream: + with open(svgfile, "r") as stream: self.svg_document = load_svg(stream) # Clean up. @@ -187,7 +203,7 @@ class SynfigExportActionGroup(InkscapeActionGroup): "svg:line", "svg:polyline", "svg:polygon", - "svg:text" + "svg:text", ] # Build an xpath command to select these nodes @@ -212,6 +228,7 @@ class SynfigExportActionGroup(InkscapeActionGroup): ### 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() @@ -229,27 +246,27 @@ def fuse_subpaths(path_node): path.remove(["Z", []]) continue - if path[i][0] == 'V': + if path[i][0] == "V": prev_end[0] = path[i][1][0] i += 1 continue - elif path[i][0] == 'H': + elif path[i][0] == "H": prev_end[1] = path[i][1][0] i += 1 continue - elif path[1][0] != 'M' or i == 0: + 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') + assert path[i][0] == "M" # Swap it for a lineto - path[i][0] = 'L' + 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]) + path.insert(i, ["L", initial_point]) i += 1 # Set the initial point of this subpath @@ -261,7 +278,7 @@ def fuse_subpaths(path_node): # Now pop the entire return stack while return_stack: - el = ['L', return_stack.pop()] + el = ["L", return_stack.pop()] path.insert(i, el) i += 1 @@ -286,19 +303,18 @@ def split_fill_and_stroke(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') + 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') + 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(): @@ -334,8 +350,8 @@ def split_fill_and_stroke(path_node): 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("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: @@ -350,7 +366,10 @@ def split_fill_and_stroke(path_node): ### Object related -def propagate_attribs(node, parent_style={}, parent_transform=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]): + +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 @@ -419,6 +438,7 @@ def propagate_attribs(node, parent_style={}, parent_transform=[[1.0, 0.0, 0.0], ### Style related + def get_dimension(s="1024"): """Convert an SVG length string from arbitrary units to pixels""" return inkex.units.convert_unit(s, "px") @@ -436,7 +456,7 @@ class SynfigPrep(inkex.EffectExtension): propagate_attribs(self.document.getroot()) # Fuse multiple subpaths in fills - for node in self.document.getroot().xpath('//svg:path'): + 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] @@ -444,5 +464,5 @@ class SynfigPrep(inkex.EffectExtension): fuse_subpaths(fill) -if __name__ == '__main__': +if __name__ == "__main__": SynfigPrep().run() diff --git a/tar_layers.py b/tar_layers.py index a9ede3d6..3a4732c1 100755 --- a/tar_layers.py +++ b/tar_layers.py @@ -33,8 +33,10 @@ 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) @@ -50,23 +52,24 @@ class TarLayers(inkex.OutputExtension): def io_document(self, name, doc): string = io.BytesIO() doc.write(string) - info = tarfile.TarInfo(name=name+'.svg') + info = tarfile.TarInfo(name=name + ".svg") info.mtime = calendar.timegm(time.gmtime()) - info.size = string.tell() + 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|') + tar = tarfile.open(fileobj=stream, mode="w|") # Switch stdout to binary on Windows. if sys.platform == "win32": import msvcrt + try: msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) except io.UnsupportedOperation: - pass # The .fileno() function is not available during pytest runs + pass # The .fileno() function is not available during pytest runs template = self.make_template() @@ -82,5 +85,5 @@ class TarLayers(inkex.OutputExtension): tar.addfile(**self.io_document(name, template)) -if __name__ == '__main__': #pragma: no cover +if __name__ == "__main__": # pragma: no cover TarLayers().run() diff --git a/template.py b/template.py index 849b7198..82066978 100755 --- a/template.py +++ b/template.py @@ -23,13 +23,15 @@ 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'), + "white": ("#ffffff", "#666666"), + "gray": ("#808080", "#444444"), + "black": ("#000000", "#999999"), } def add_arguments(self, pars): @@ -46,14 +48,15 @@ class InxDefinedTemplate(inkex.TemplateExtension): 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") + 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") + namedview.set("bordercolor", namedview.get("pagecolor")) + namedview.set("borderopacity", "0") + -if __name__ == '__main__': +if __name__ == "__main__": InxDefinedTemplate().run() diff --git a/template_dvd_cover.py b/template_dvd_cover.py index 278a0203..90215cc1 100755 --- a/template_dvd_cover.py +++ b/template_dvd_cover.py @@ -24,14 +24,23 @@ 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") + 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 @@ -40,8 +49,7 @@ class DvdCover(inkex.TemplateExtension): bleed = self.options.bleed spine = self.options.spine - return (width + spine + bleed * 2.0, 'mm', - height + bleed * 2.0, 'mm') + 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) @@ -54,5 +62,6 @@ class DvdCover(inkex.TemplateExtension): self.svg.namedview.new_guide((width + spine) / 2.0, False, "right spline") self.svg.namedview.new_guide(width - bleed, False, "top") -if __name__ == '__main__': + +if __name__ == "__main__": DvdCover().run() diff --git a/template_seamless_pattern.py b/template_seamless_pattern.py index eed0b2bb..b80610e4 100755 --- a/template_seamless_pattern.py +++ b/template_seamless_pattern.py @@ -8,8 +8,10 @@ import os from inkex import load_svg, TemplateExtension, Transform + class SeamlessPattern(TemplateExtension): """Generate a seamless pattern template""" + multi_inx = True @classmethod @@ -34,34 +36,40 @@ class SeamlessPattern(TemplateExtension): for child in self.svg.getElementById("designTop"): child.transform = Transform(scale=scale) - text_preview = self.svg.getElementById('textPreview') + text_preview = self.svg.getElementById("textPreview") if text_preview is not None: x = width / 100.0 / factor y = height / 100.0 if factor <= 1: x *= factor y *= factor - text_preview.transform = Transform(translate=(int(width) * 2, 0), scale=(x, y)) + text_preview.transform = Transform( + translate=(int(width) * 2, 0), scale=(x, y) + ) - info_group = self.svg.getElementById('infoGroup') + info_group = self.svg.getElementById("infoGroup") if info_group is not None: scale = 100 if factor <= 1 else 1000 - info_group.transform = Transform(scale=(width / scale, height / scale * factor)) + info_group.transform = 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)) + 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 = Transform(translate=(x, y)) - clones = [(x, y) for x in (0, width, width * 2) for y in (0, height, height * 2)] + 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 = Transform(translate=(x, y)) - pattern_generator = self.svg.getElementById('fullPatternClone') + pattern_generator = self.svg.getElementById("fullPatternClone") if pattern_generator is not None: pattern_generator.transform = Transform(translate=(width * 2, -height)) pattern_generator.set("inkscape:tile-cx", width / 2) @@ -74,10 +82,11 @@ class SeamlessPattern(TemplateExtension): 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)) + 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__': +if __name__ == "__main__": SeamlessPattern().run() diff --git a/tests/__init__.py b/tests/__init__.py index bf893c06..9bad5790 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +1 @@ -# coding=utf-8 \ No newline at end of file +# coding=utf-8 diff --git a/tests/add_pylint.py b/tests/add_pylint.py index 9ca074e7..94932ae6 100755 --- a/tests/add_pylint.py +++ b/tests/add_pylint.py @@ -30,13 +30,15 @@ from pylint import lint from pylint.reporters.text import TextReporter DIR = os.path.dirname(__file__) -REX = re.compile(r'.+?\">([^<]+\.py).+?\<\/tr\>') +REX = re.compile(r".+?\">([^<]+\.py).+?\<\/tr\>") -ARGS = ["--rcfile=" + os.path.join(DIR, '..', '.pylintrc')] +ARGS = ["--rcfile=" + os.path.join(DIR, "..", ".pylintrc")] stdout = sys.stdout + class WritableObject(object): """dummy output stream for pylint""" + def __init__(self): self.content = [] @@ -48,21 +50,22 @@ class WritableObject(object): "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(os.devnull, 'w') + temp, sys.stderr = sys.stderr, open(os.devnull, "w") try: - lint.Run([fname]+ARGS, reporter=TextReporter(pylint_output), exit=False) - except Exception: # pylint: disable=broad-except + 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) + rates = re.findall(r"rated at (\-?[\d\.]+)", output) for rate in rates: return float(rate) - if ' rated ' in output: + if " rated " in output: print(f"FAIL: {output}") return None @@ -72,8 +75,8 @@ 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()) + 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 @@ -85,25 +88,27 @@ def add_lint(fname): start += adjust end += adjust old_content = html[start:end] - new_content = old_content[:-5] + f'{score}' + 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}') + html = html.replace("coverage", "coveragepylint") + html = html.replace("", f"{total:.2f}") - with open(fname, 'w') as fhl: + 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'): + +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) diff --git a/tests/test_addnodes.py b/tests/test_addnodes.py index 0a895365..2797caa4 100644 --- a/tests/test_addnodes.py +++ b/tests/test_addnodes.py @@ -3,29 +3,45 @@ from addnodes import AddNodes from inkex.tester import ComparisonMixin, TestCase from inkex.tester.filters import CompareNumericFuzzy, CompareWithPathSpace + class SplitItBasicTest(ComparisonMixin, TestCase): effect_class = AddNodes - comparisons = [("--id=p1", "--id=r3", "--max=2.0",)] + comparisons = [ + ( + "--id=p1", + "--id=r3", + "--max=2.0", + ) + ] compare_filters = [ CompareWithPathSpace(), CompareNumericFuzzy(), ] def test_basic(self): - args = ['--id=dashme', - self.data_file('svg', 'dash.svg')] + 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 + 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) + class SplitNodesClosedTest(ComparisonMixin, TestCase): effect_class = AddNodes - comparisons = [("--id=rect723", "--id=rect723-5", "--id=path747", "--id=path1080", - "--id=path1115", "--method=bynum", "--segments=3",)] + comparisons = [ + ( + "--id=rect723", + "--id=rect723-5", + "--id=path747", + "--id=path1080", + "--id=path1115", + "--method=bynum", + "--segments=3", + ) + ] compare_filters = [ CompareWithPathSpace(), CompareNumericFuzzy(), ] - compare_file = "svg/paths_open_closed.svg" \ No newline at end of file + compare_file = "svg/paths_open_closed.svg" diff --git a/tests/test_color_HSL_adjust.py b/tests/test_color_HSL_adjust.py index 5b01f408..d98a04de 100644 --- a/tests/test_color_HSL_adjust.py +++ b/tests/test_color_HSL_adjust.py @@ -2,6 +2,7 @@ from color_HSL_adjust import HslAdjust from .test_inkex_extensions import ColorBaseCase + class ColorHSLAdjustTest(ColorBaseCase): effect_class = HslAdjust color_tests = [ @@ -9,24 +10,24 @@ class ColorHSLAdjustTest(ColorBaseCase): ((255, 255, 255), "#ffffff"), ((0, 0, 0), "#000000"), ((0, 128, 0), "#008000"), - ((91, 166, 176), "#5a98af", ['-x 10']), - ((91, 166, 176), "#5aaf80", ['-x 320']), - ((91, 166, 176), "#5ba6b0", ['-x 0']), - ((91, 166, 176), "#af5a78", ['-x 12345']), - ((91, 166, 176), "#5aa8af", ['-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']), + ((91, 166, 176), "#5a98af", ["-x 10"]), + ((91, 166, 176), "#5aaf80", ["-x 320"]), + ((91, 166, 176), "#5ba6b0", ["-x 0"]), + ((91, 166, 176), "#af5a78", ["-x 12345"]), + ((91, 166, 176), "#5aa8af", ["-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/tests/test_color_blackandwhite.py b/tests/test_color_blackandwhite.py index 84af005e..5f544813 100644 --- a/tests/test_color_blackandwhite.py +++ b/tests/test_color_blackandwhite.py @@ -2,6 +2,7 @@ from color_blackandwhite import BlackAndWhite from .test_inkex_extensions import ColorBaseCase + class ColorBlackAndWhiteTest(ColorBaseCase): effect_class = BlackAndWhite color_tests = [ @@ -24,9 +25,9 @@ class ColorBlackAndWhiteTest(ColorBaseCase): ((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']), + ((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']), + ((255, 0, 255), "#ffffff", ["-t 80"]), + ((192, 192, 192), "#ffffff", ["-t 80"]), ] diff --git a/tests/test_color_brighter.py b/tests/test_color_brighter.py index cbbdb345..0d73eb69 100644 --- a/tests/test_color_brighter.py +++ b/tests/test_color_brighter.py @@ -2,6 +2,7 @@ from color_brighter import Brighter from .test_inkex_extensions import ColorBaseCase + class ColorBrighterTest(ColorBaseCase): effect_class = Brighter color_tests = [ diff --git a/tests/test_color_custom.py b/tests/test_color_custom.py index f5530f00..75b444f2 100644 --- a/tests/test_color_custom.py +++ b/tests/test_color_custom.py @@ -4,20 +4,21 @@ 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']), + ((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): @@ -32,18 +33,18 @@ class ColorCustomTest(ColorBaseCase): self.effect.run(args) with self.assertRaises(TypeError): - self.effect.modify_color('fill', inkex.Color('black')) + 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')) + 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')) + self.effect.modify_color("fill", inkex.Color("black")) diff --git a/tests/test_color_darker.py b/tests/test_color_darker.py index b5d0eec3..b3eb6327 100644 --- a/tests/test_color_darker.py +++ b/tests/test_color_darker.py @@ -2,6 +2,7 @@ from color_darker import Darker from .test_inkex_extensions import ColorBaseCase + class ColorDarkerTest(ColorBaseCase): effect_class = Darker color_tests = [ diff --git a/tests/test_color_desaturate.py b/tests/test_color_desaturate.py index 0a0a0315..a49cf5c5 100644 --- a/tests/test_color_desaturate.py +++ b/tests/test_color_desaturate.py @@ -2,6 +2,7 @@ from color_desaturate import Desaturate from .test_inkex_extensions import ColorBaseCase + class ColorDesaturateTest(ColorBaseCase): effect_class = Desaturate color_tests = [ diff --git a/tests/test_color_grayscale.py b/tests/test_color_grayscale.py index 9bc7a635..2979262b 100644 --- a/tests/test_color_grayscale.py +++ b/tests/test_color_grayscale.py @@ -2,6 +2,7 @@ from color_grayscale import Grayscale from .test_inkex_extensions import ColorBaseCase + class ColorGrayscaleTest(ColorBaseCase): effect_class = Grayscale color_tests = [ diff --git a/tests/test_color_lesshue.py b/tests/test_color_lesshue.py index f910dc6d..e86b54d4 100644 --- a/tests/test_color_lesshue.py +++ b/tests/test_color_lesshue.py @@ -2,12 +2,13 @@ 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)'), + ("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"), diff --git a/tests/test_color_lesslight.py b/tests/test_color_lesslight.py index a5bf9611..c35c38fe 100644 --- a/tests/test_color_lesslight.py +++ b/tests/test_color_lesslight.py @@ -2,12 +2,13 @@ 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)'), + ("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"), diff --git a/tests/test_color_lesssaturation.py b/tests/test_color_lesssaturation.py index 7ffe7911..6396e4b0 100644 --- a/tests/test_color_lesssaturation.py +++ b/tests/test_color_lesssaturation.py @@ -2,12 +2,13 @@ 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)'), + ("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"), diff --git a/tests/test_color_list.py b/tests/test_color_list.py index c05f6469..21e1e899 100644 --- a/tests/test_color_list.py +++ b/tests/test_color_list.py @@ -3,9 +3,10 @@ from color_list import ListColours from .test_inkex_extensions import ColorEffectTest from inkex.tester.filters import WindowsTextCompat + class ColorListTest(ColorEffectTest): effect_class = ListColours - effect_name = 'test_color_list' + effect_name = "test_color_list" stderr_output = True compare_filters = [WindowsTextCompat()] color_tests = [] diff --git a/tests/test_color_morehue.py b/tests/test_color_morehue.py index 9b7f7796..ae4c6e81 100644 --- a/tests/test_color_morehue.py +++ b/tests/test_color_morehue.py @@ -3,12 +3,13 @@ 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)'), + ("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"), diff --git a/tests/test_color_morelight.py b/tests/test_color_morelight.py index 496b6d6b..c0e227c3 100644 --- a/tests/test_color_morelight.py +++ b/tests/test_color_morelight.py @@ -2,12 +2,13 @@ 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)'), + ("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"), diff --git a/tests/test_color_moresaturation.py b/tests/test_color_moresaturation.py index 4b6a38f7..5a3c1967 100644 --- a/tests/test_color_moresaturation.py +++ b/tests/test_color_moresaturation.py @@ -2,12 +2,13 @@ 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)'), + ("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"), diff --git a/tests/test_color_negative.py b/tests/test_color_negative.py index b82aecc5..bf84bfc7 100644 --- a/tests/test_color_negative.py +++ b/tests/test_color_negative.py @@ -2,6 +2,7 @@ from color_negative import Negative from .test_inkex_extensions import ColorBaseCase + class ColorNegativeTest(ColorBaseCase): effect_class = Negative color_tests = [ diff --git a/tests/test_color_randomize.py b/tests/test_color_randomize.py index a0ff9c8e..496e741c 100644 --- a/tests/test_color_randomize.py +++ b/tests/test_color_randomize.py @@ -5,6 +5,7 @@ from inkex.tester import ComparisonMixin, TestCase Randomize.deterministic_output = True + class ColorRandomizeTest(ColorBaseCase): effect_class = Randomize python3_only = True @@ -15,57 +16,78 @@ class ColorRandomizeTest(ColorBaseCase): # for rounding errors) ("hsl(191, 122, 150)", "hsl(191, 122, 149)"), # The user selected 0% values, and thus the color should not change. - ("hsl(191, 122, 150)", "hsl(191, 122, 149)", ['-y 0', '-t 0', '-m 0']), + ("hsl(191, 122, 150)", "hsl(191, 122, 149)", ["-y 0", "-t 0", "-m 0"]), # Random hue only. Saturation and lightness not changed. - ("hsl(191, 122, 150)", "hsl(223, 122, 149)", ['-y 50', '-t 0', '-m 0']), + ("hsl(191, 122, 150)", "hsl(223, 122, 149)", ["-y 50", "-t 0", "-m 0"]), # Same settings, test stationarity of output. - ("hsl(191, 122, 150)", "hsl(223, 122, 149)", ['-y 50', '-t 0', '-m 0']), + ("hsl(191, 122, 150)", "hsl(223, 122, 149)", ["-y 50", "-t 0", "-m 0"]), # Random saturation only. Hue and lightness not changed. - ("hsl(191, 122, 150)", "hsl(191, 146, 149)", ['-y 0', '-t 30', '-m 0']), + ("hsl(191, 122, 150)", "hsl(191, 146, 149)", ["-y 0", "-t 30", "-m 0"]), # Random lightness only. Hue and saturation not changed. - ("hsl(191, 122, 150)", "hsl(190, 120, 190)", ['-y 0', '-t 0', '-m 50']), + ("hsl(191, 122, 150)", "hsl(190, 120, 190)", ["-y 0", "-t 0", "-m 50"]), # The maximum hsl values should be between 0 and 100% of their maximum - ("hsl(190, 122, 150)", "hsl(81, 126, 209)", ['-y 100', '-t 100', '-m 100']), + ("hsl(190, 122, 150)", "hsl(81, 126, 209)", ["-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']), + (0.15, 0.15, ["-o 0"]), # The opacity value should be greater than 0 - (0.0, 0.84, ['-o 100']), + (0.0, 0.84, ["-o 100"]), # The opacity value should be lesser than 1 - (1.0, 0.77, ['-o 100']), + (1.0, 0.77, ["-o 100"]), # Other units are available - ('0.5', 0.654, ['-o 54']), + ("0.5", 0.654, ["-o 54"]), # test that output is deterministic - ('0.500001', 0.654, ['-o 54']), + ("0.500001", 0.654, ["-o 54"]), # Test no opacity # The opacity value should be lesser than 1 ] def test_bad_opacity(self): """Bad opacity error handled""" - self.effect.modify_opacity('opacity', 'hello') + self.effect.modify_opacity("opacity", "hello") + class TestRandomizeGradients(ComparisonMixin, TestCase): """Direct tests for color mechanisms""" + effect_class = Randomize - compare_file = 'svg/colors.svg' + compare_file = "svg/colors.svg" python3_only = True comparisons = [ - ('-y 50', '-t 50', '-m 50', '-o 100', "--id=r1", "--id=r2", "--id=r3", "--id=r4", - "--id=r5", "--id=r6"), + ( + "-y 50", + "-t 50", + "-m 50", + "-o 100", + "--id=r1", + "--id=r2", + "--id=r3", + "--id=r4", + "--id=r5", + "--id=r6", + ), ] + class TestRandomizeOpacity(ComparisonMixin, TestCase): """Direct tests for color mechanisms""" + effect_class = Randomize - compare_file = 'svg/dpiswitcher_96dpi.svg' + compare_file = "svg/dpiswitcher_96dpi.svg" python3_only = True comparisons = [ - ('-y 0', '-t 0', '-m 0', '-o 100', "--id=layer_group_rect_uu2", "--id=layer_group_path", - "--id=root_rect_uu"), - ] \ No newline at end of file + ( + "-y 0", + "-t 0", + "-m 0", + "-o 100", + "--id=layer_group_rect_uu2", + "--id=layer_group_path", + "--id=root_rect_uu", + ), + ] diff --git a/tests/test_color_removeblue.py b/tests/test_color_removeblue.py index 33d82c87..2d4c8c65 100644 --- a/tests/test_color_removeblue.py +++ b/tests/test_color_removeblue.py @@ -2,6 +2,7 @@ from color_removeblue import RemoveBlue from .test_inkex_extensions import ColorBaseCase + class ColorRemoveBlueTest(ColorBaseCase): effect_class = RemoveBlue color_tests = [ diff --git a/tests/test_color_removegreen.py b/tests/test_color_removegreen.py index a45a7472..a00a4da5 100644 --- a/tests/test_color_removegreen.py +++ b/tests/test_color_removegreen.py @@ -2,6 +2,7 @@ from color_removegreen import RemoveGreen from .test_inkex_extensions import ColorBaseCase + class ColorRemoveGreenTest(ColorBaseCase): effect_class = RemoveGreen color_tests = [ diff --git a/tests/test_color_removered.py b/tests/test_color_removered.py index 17652fdd..56758abf 100644 --- a/tests/test_color_removered.py +++ b/tests/test_color_removered.py @@ -2,6 +2,7 @@ from color_removered import RemoveRed from .test_inkex_extensions import ColorBaseCase + class ColorRemoveRedTest(ColorBaseCase): effect_class = RemoveRed color_tests = [ diff --git a/tests/test_color_replace.py b/tests/test_color_replace.py index e541b005..224c1fcc 100644 --- a/tests/test_color_replace.py +++ b/tests/test_color_replace.py @@ -3,20 +3,30 @@ from color_replace import ReplaceColor from .test_inkex_extensions import ColorBaseCase from inkex import Color + 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), "#696969", ["-t1768516095"]), ((0, 0, 0), "#000000", ["-f1", "-t1768516095", "-i=False"]), ((18, 52, 86), "#696969", ["-f305420031", "-t1768516095"]), ((18, 52, 86), "#ff0000", ["-f305420031"]), - (Color([10, 20, 30, 0.2], space="rgba"), - Color([255, 0, 0, 20/255], space="rgba"), ["-f169090611", "-t4278190100"]), - (Color([10, 20, 30, 0.5], space="rgba"), - Color([10, 20, 30, 0.5], space="rgba"), ["-f169090611", "-t4278190100", "-i=False"]), - (Color([10, 20, 30, 0.5], space="rgba"), - Color([255, 0, 0, 20/255], space="rgba"), ["-f169090611", "-t4278190100"]) + ( + Color([10, 20, 30, 0.2], space="rgba"), + Color([255, 0, 0, 20 / 255], space="rgba"), + ["-f169090611", "-t4278190100"], + ), + ( + Color([10, 20, 30, 0.5], space="rgba"), + Color([10, 20, 30, 0.5], space="rgba"), + ["-f169090611", "-t4278190100", "-i=False"], + ), + ( + Color([10, 20, 30, 0.5], space="rgba"), + Color([255, 0, 0, 20 / 255], space="rgba"), + ["-f169090611", "-t4278190100"], + ), ] diff --git a/tests/test_color_rgbbarrel.py b/tests/test_color_rgbbarrel.py index 9682af1f..a59d1d99 100644 --- a/tests/test_color_rgbbarrel.py +++ b/tests/test_color_rgbbarrel.py @@ -2,6 +2,7 @@ from color_rgbbarrel import RgbBarrel from .test_inkex_extensions import ColorBaseCase + class ColorBarrelTest(ColorBaseCase): effect_class = RgbBarrel color_tests = [ diff --git a/tests/test_convert2dashes.py b/tests/test_convert2dashes.py index af50c6cf..609471fa 100644 --- a/tests/test_convert2dashes.py +++ b/tests/test_convert2dashes.py @@ -8,14 +8,16 @@ class DashitBasicTest(ComparisonMixin, TestCase): effect_class = Dashit def test_basic(self): - args = ['--id=dashme', - self.data_file('svg', 'dash.svg')] + 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 + 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) + class DashitCommaTest(ComparisonMixin, TestCase): comparisons = (["--id=dashme2"],) effect_class = Dashit - compare_file = "svg/dash.svg" \ No newline at end of file + compare_file = "svg/dash.svg" diff --git a/tests/test_deprecated_simple.py b/tests/test_deprecated_simple.py index 875fa919..4e397140 100644 --- a/tests/test_deprecated_simple.py +++ b/tests/test_deprecated_simple.py @@ -12,14 +12,16 @@ 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) + warnings.simplefilter("ignore", category=DeprecationWarning) def tearDown(self): self.warner.__exit__() @@ -31,6 +33,7 @@ class DeprecatedTest(TestCase): import cspsubdiv import cubicsuperpath import ffgeom + # pylint: disable=unused-variable from inkex import debug, errormsg, localize @@ -38,84 +41,113 @@ class DeprecatedTest(TestCase): """Test simplepath API""" import simplepath - data = 'M12 34L56 78Z' + data = "M12 34L56 78Z" path = simplepath.parsePath(data) - self.assertEqual(path, [['M', [12., 34.]], ['L', [56., 78.]], ['Z', []]]) + self.assertEqual(path, [["M", [12.0, 34.0]], ["L", [56.0, 78.0]], ["Z", []]]) d_out = simplepath.formatPath(path) - d_out = d_out.replace('.0', '') - self.assertEqual(data.replace(' ', ''), d_out.replace(' ', '')) + 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', []]]) + self.assertEqual(path, [["M", [9.0, 30.0]], ["L", [53.0, 74.0]], ["Z", []]]) simplepath.scalePath(path, 10, 20) - self.assertEqual(path, [['M', [90., 600.]], ['L', [530., 1480.]], ['Z', []]]) + self.assertEqual( + path, [["M", [90.0, 600.0]], ["L", [530.0, 1480.0]], ["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', []]]) - + self.assertEqual( + approxed, [["M", [-588.0, 92.0]], ["L", [-1468.0, 532.0]], ["Z", []]] + ) def test_simplepath_shorthand(self): """simplepath with shorthand notation""" import simplepath - data = 'M10 20v30V30h40H40c 1 2 3 4 5 6S7 8 9 10s7 8 9 10q11 12 13 14t15 16T15 16' + + data = ( + "M10 20v30V30h40H40c 1 2 3 4 5 6S7 8 9 10s7 8 9 10q11 12 13 14t15 16T15 16" + ) path = simplepath.parsePath(data) self.assertEqual( - path, [['M', [10., 20.]], ['L', [10., 50.]], ['L', [10., 30.]], - ['L', [50., 30.]], ['L', [40., 30.]], - ['C', [41., 32., 43., 34., 45., 36.]], - ['C', [47., 38., 7., 8., 9., 10.]], - ['C', [11., 12., 16., 18., 18., 20.]], - ['Q', [29., 32., 31., 34.]], ['Q', [33., 36., 46., 50.]], - ['Q', [59., 64., 15., 16.]]]) - + path, + [ + ["M", [10.0, 20.0]], + ["L", [10.0, 50.0]], + ["L", [10.0, 30.0]], + ["L", [50.0, 30.0]], + ["L", [40.0, 30.0]], + ["C", [41.0, 32.0, 43.0, 34.0, 45.0, 36.0]], + ["C", [47.0, 38.0, 7.0, 8.0, 9.0, 10.0]], + ["C", [11.0, 12.0, 16.0, 18.0, 18.0, 20.0]], + ["Q", [29.0, 32.0, 31.0, 34.0]], + ["Q", [33.0, 36.0, 46.0, 50.0]], + ["Q", [59.0, 64.0, 15.0, 16.0]], + ], + ) 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') - + 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]) - ]) + 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]]) + 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( + 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.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 @@ -134,9 +166,11 @@ class DeprecatedTest(TestCase): """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: @@ -150,16 +184,20 @@ class DeprecatedTest(TestCase): def test_inkex_namespace(self): """Test inkex namespace API""" from inkex import InkOption - self.assertIn('inkbool', InkOption.TYPES) - self.assertIn('inkbool', InkOption.TYPE_CHECKER) + + 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') + + 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') + + 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)) @@ -171,8 +209,9 @@ class DeprecatedTest(TestCase): from inkex import Effect args = [ - '--id', 'curve', - os.path.join(os.path.dirname(__file__), 'data', 'svg/curves.svg'), + "--id", + "curve", + os.path.join(os.path.dirname(__file__), "data", "svg/curves.svg"), ] e = Effect() @@ -181,7 +220,7 @@ class DeprecatedTest(TestCase): # assigned in __init__ self.assertNotEqual(e.document.getroot(), None) self.assertTrue(isinstance(e.selected, dict)) - self.assertEqual(list(e.selected), ['curve']) + 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:]) @@ -190,22 +229,28 @@ class DeprecatedTest(TestCase): # 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') + 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 diff --git a/tests/test_dhw_input.py b/tests/test_dhw_input.py index cd02ea2c..facf02ad 100644 --- a/tests/test_dhw_input.py +++ b/tests/test_dhw_input.py @@ -9,10 +9,10 @@ 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', + "io/PAGE_001.DHW", + "io/PGLT_161.DHW", + "io/PGLT_162.DHW", + "io/PGLT_163.DHW", ] compare_filters = [CompareNumericFuzzy()] comparisons = [()] diff --git a/tests/test_dimension.py b/tests/test_dimension.py index 818f7178..34af0d3f 100644 --- a/tests/test_dimension.py +++ b/tests/test_dimension.py @@ -2,14 +2,16 @@ from dimension import Dimension from inkex.tester import ComparisonMixin, TestCase + class TestDimensionBasic(ComparisonMixin, TestCase): effect_class = Dimension comparisons = [ - ('--id=p1', '--id=r3', '--xoffset=100.0', '--yoffset=100.0'), - ('--id=p1', '--id=r3', '--type=visual', '--xoffset=100.0', '--yoffset=100.0'), + ("--id=p1", "--id=r3", "--xoffset=100.0", "--yoffset=100.0"), + ("--id=p1", "--id=r3", "--type=visual", "--xoffset=100.0", "--yoffset=100.0"), ] + class TestDimensionMillimeters(ComparisonMixin, TestCase): effect_class = Dimension - compare_file = 'svg/css.svg' - comparisons = [('--id=circle1',)] \ No newline at end of file + compare_file = "svg/css.svg" + comparisons = [("--id=circle1",)] diff --git a/tests/test_doc_ai_convert.py b/tests/test_doc_ai_convert.py index 98596bb8..91ccf69b 100644 --- a/tests/test_doc_ai_convert.py +++ b/tests/test_doc_ai_convert.py @@ -4,7 +4,12 @@ from doc_ai_convert import DocAiConvert from inkex.tester import ComparisonMixin, TestCase + class DocAIConvertTest(ComparisonMixin, TestCase): effect_class = DocAiConvert - compare_file = ['svg/shapes.svg', 'svg/doc_ai_conv_mm_in.svg', 'svg/doc_ai_conv_m_in.svg'] + compare_file = [ + "svg/shapes.svg", + "svg/doc_ai_conv_mm_in.svg", + "svg/doc_ai_conv_m_in.svg", + ] comparisons = [()] diff --git a/tests/test_docinfo.py b/tests/test_docinfo.py index 324b4b90..cc33c158 100644 --- a/tests/test_docinfo.py +++ b/tests/test_docinfo.py @@ -3,9 +3,10 @@ from docinfo import DocInfo from inkex.tester import ComparisonMixin, TestCase from inkex.tester.filters import WindowsTextCompat + class TestDocInfo(ComparisonMixin, TestCase): - compare_file = 'svg/guides.svg' + compare_file = "svg/guides.svg" effect_class = DocInfo stderr_output = True comparisons = [()] - compare_filters = [WindowsTextCompat()] \ No newline at end of file + compare_filters = [WindowsTextCompat()] diff --git a/tests/test_dpiswitcher.py b/tests/test_dpiswitcher.py index c1968c3f..923bcfd2 100644 --- a/tests/test_dpiswitcher.py +++ b/tests/test_dpiswitcher.py @@ -16,14 +16,18 @@ from dpiswitcher import DPISwitcher from inkex.tester import ComparisonMixin, TestCase from inkex.tester.filters import CompareNumericFuzzy + class TestDPISwitcherBasic(ComparisonMixin, TestCase): """Default Test with shapes.svg""" + effect_class = DPISwitcher compare_filters = [CompareNumericFuzzy()] + class TestDPIto90to96(ComparisonMixin, TestCase): """Test file with transformed objects in root""" - compare_file = 'svg/dpiswitcher_96dpi.svg' - comparisons = [('--switcher=0',), ('--switcher=1',)] + + compare_file = "svg/dpiswitcher_96dpi.svg" + comparisons = [("--switcher=0",), ("--switcher=1",)] compare_filters = [CompareNumericFuzzy()] effect_class = DPISwitcher diff --git a/tests/test_draw_from_triangle.py b/tests/test_draw_from_triangle.py index 47821273..4c092aef 100644 --- a/tests/test_draw_from_triangle.py +++ b/tests/test_draw_from_triangle.py @@ -2,8 +2,9 @@ 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'), + ("--id=p1", "--id=r3"), ] diff --git a/tests/test_dxf12_outlines.py b/tests/test_dxf12_outlines.py index 6bf81983..eb6a1a42 100644 --- a/tests/test_dxf12_outlines.py +++ b/tests/test_dxf12_outlines.py @@ -3,9 +3,13 @@ from dxf12_outlines import DxfTwelve from inkex.tester import ComparisonMixin, TestCase from inkex.tester.filters import WindowsTextCompat + class TestDXF12OutlinesBasic(ComparisonMixin, TestCase): - compare_file = ["svg/shapes.svg", "svg/preserved-transforms.svg", - "svg/dxf_nested_transforms.svg"] + compare_file = [ + "svg/shapes.svg", + "svg/preserved-transforms.svg", + "svg/dxf_nested_transforms.svg", + ] comparisons = [()] effect_class = DxfTwelve - compare_filters = [WindowsTextCompat()] \ No newline at end of file + compare_filters = [WindowsTextCompat()] diff --git a/tests/test_dxf_input.py b/tests/test_dxf_input.py index 331440c1..9156a82f 100644 --- a/tests/test_dxf_input.py +++ b/tests/test_dxf_input.py @@ -5,17 +5,21 @@ 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', - # Unit test for https://gitlab.com/inkscape/extensions/-/issues/355 - 'io/dxf_with_arc.dxf', - # test polylines - 'io/dxf_polylines.dxf', - # File missing a BLOCKS session - 'io/no_block_section.dxf', - # test placement of graphical objects from BLOCKS section - # ellipses currently are too large - 'io/dxf_multiple_inserts.dxf'] + compare_file = [ + "io/test_r12.dxf", + "io/test_r14.dxf", + # Unit test for https://gitlab.com/inkscape/extensions/-/issues/355 + "io/dxf_with_arc.dxf", + # test polylines + "io/dxf_polylines.dxf", + # File missing a BLOCKS session + "io/no_block_section.dxf", + # test placement of graphical objects from BLOCKS section + # ellipses currently are too large + "io/dxf_multiple_inserts.dxf", + ] compare_filters = [CompareNumericFuzzy()] comparisons = [()] effect_class = DxfInput @@ -25,13 +29,14 @@ class TestDxfInputBasic(ComparisonMixin, TestCase): if is_saving is True: return data data = super()._apply_compare_filters(data) - return data.replace((self.datadir() + '/').encode('utf-8'), b'') + return data.replace((self.datadir() + "/").encode("utf-8"), b"") + class TestDxfInputBasicError(ComparisonMixin, TestCase): TestCase.stderr_protect = False # sample uses POLYLINE,TEXT (R12), LWPOLYLINE,MTEXT (R13, R14) # however has warnings when handling points with a display mode - compare_file = ['io/test2_r12.dxf', 'io/test2_r13.dxf', 'io/test2_r14.dxf'] + compare_file = ["io/test2_r12.dxf", "io/test2_r13.dxf", "io/test2_r14.dxf"] compare_filters = [CompareNumericFuzzy()] comparisons = [()] effect_class = DxfInput @@ -41,12 +46,13 @@ class TestDxfInputBasicError(ComparisonMixin, TestCase): if is_saving is True: return data data = super()._apply_compare_filters(data) - return data.replace((self.datadir() + '/').encode('utf-8'), b'') + return data.replace((self.datadir() + "/").encode("utf-8"), b"") + class TestDxfInputTextHeight(ComparisonMixin, TestCase): - compare_file = ['io/CADTextHeight.dxf'] + compare_file = ["io/CADTextHeight.dxf"] compare_filters = [CompareNumericFuzzy()] - comparisons = [(), ('--textscale=1.411',)] + comparisons = [(), ("--textscale=1.411",)] effect_class = DxfInput def _apply_compare_filters(self, data, is_saving=None): @@ -54,5 +60,4 @@ class TestDxfInputTextHeight(ComparisonMixin, TestCase): if is_saving is True: return data data = super()._apply_compare_filters(data) - return data.replace((self.datadir() + '/').encode('utf-8'), b'') - + return data.replace((self.datadir() + "/").encode("utf-8"), b"") diff --git a/tests/test_dxf_outlines.py b/tests/test_dxf_outlines.py index f830d23c..250e9b83 100644 --- a/tests/test_dxf_outlines.py +++ b/tests/test_dxf_outlines.py @@ -3,12 +3,13 @@ from dxf_outlines import DxfOutlines from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase from inkex.tester.filters import WindowsTextCompat + class DFXOutlineBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): effect_class = DxfOutlines comparisons = [ (), - ('--id=p1', '--id=r3'), - ('--POLY=true',), - ('--ROBO=true',), + ("--id=p1", "--id=r3"), + ("--POLY=true",), + ("--ROBO=true",), ] compare_filters = [WindowsTextCompat()] diff --git a/tests/test_edge3d.py b/tests/test_edge3d.py index f96f84ee..ad1dce2f 100644 --- a/tests/test_edge3d.py +++ b/tests/test_edge3d.py @@ -5,21 +5,24 @@ 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'),] + comparisons = [ + ("--id=p1", "--id=r3"), + ] def test_basic(self): - args = ['--id=edgeme', - self.data_file('svg', 'edge3d.svg')] + 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"]') + 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') + 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/tests/test_export_gimp_palette.py b/tests/test_export_gimp_palette.py index 9f0c9772..b81cbc97 100644 --- a/tests/test_export_gimp_palette.py +++ b/tests/test_export_gimp_palette.py @@ -3,7 +3,8 @@ from export_gimp_palette import ExportGimpPalette from inkex.tester import ComparisonMixin, TestCase from inkex.tester.filters import WindowsTextCompat + class TestExportGplBasic(ComparisonMixin, TestCase): effect_class = ExportGimpPalette - compare_file = 'svg/colors.svg' - compare_filters = [WindowsTextCompat()] \ No newline at end of file + compare_file = "svg/colors.svg" + compare_filters = [WindowsTextCompat()] diff --git a/tests/test_extrude.py b/tests/test_extrude.py index 55ed33fa..7c38c384 100644 --- a/tests/test_extrude.py +++ b/tests/test_extrude.py @@ -2,37 +2,52 @@ # coding=utf-8 from extrude import Extrude from inkex.tester import ComparisonMixin, TestCase -from inkex.tester.filters import CompareOrderIndependentStyleAndPath, CompareWithPathSpace +from inkex.tester.filters import ( + CompareOrderIndependentStyleAndPath, + CompareWithPathSpace, +) + class ExtrudeBasicTest(ComparisonMixin, TestCase): effect_class = Extrude - comparisons = [('--id=p1', '--id=p2')] + comparisons = [("--id=p1", "--id=p2")] compare_filters = [CompareWithPathSpace(), CompareOrderIndependentStyleAndPath()] + class ExtrudeCircleTest(ComparisonMixin, TestCase): effect_class = Extrude compare_file = "svg/extrude.svg" - comparisons = [('--id=c1', '--id=c2'), - ('--id=c1', '--id=c2', '-m=snug'), - ('--id=c1', '--id=c2', '-m=polygons')] + comparisons = [ + ("--id=c1", "--id=c2"), + ("--id=c1", "--id=c2", "-m=snug"), + ("--id=c1", "--id=c2", "-m=polygons"), + ] + class ExtrudePathConversionTest(ComparisonMixin, TestCase): effect_class = Extrude compare_file = "svg/extrude.svg" - comparisons = [('--id=r1', '--id=r2'), - ('--id=r1', '--id=r2', '-s=False'), - ('--id=r1', '--id=r2', '-m=snug')] + comparisons = [ + ("--id=r1", "--id=r2"), + ("--id=r1", "--id=r2", "-s=False"), + ("--id=r1", "--id=r2", "-m=snug"), + ] + class ExtrudeOpenPathTest(ComparisonMixin, TestCase): effect_class = Extrude compare_file = "svg/extrude.svg" - comparisons = [('--id=p1', '--id=p2', "-m=lines"), - ('--id=p1', '--id=p2', '-m=snug')] + comparisons = [ + ("--id=p1", "--id=p2", "-m=lines"), + ("--id=p1", "--id=p2", "-m=snug"), + ] + class ExtrudeMultipleSubpathTest(ComparisonMixin, TestCase): effect_class = Extrude compare_file = "svg/extrude.svg" - comparisons = [('--id=p3', '--id=p4', '-m=snug'), - ('--id=p3', '--id=p4', "-m=lines"), - ('--id=p3', '--id=p4', "-m=polygons"), - ] + comparisons = [ + ("--id=p3", "--id=p4", "-m=snug"), + ("--id=p3", "--id=p4", "-m=lines"), + ("--id=p3", "--id=p4", "-m=polygons"), + ] diff --git a/tests/test_fig_input.py b/tests/test_fig_input.py index d2c59ac1..f1c17188 100644 --- a/tests/test_fig_input.py +++ b/tests/test_fig_input.py @@ -4,7 +4,8 @@ from fig_input import FigInput from inkex.tester import ComparisonMixin, TestCase + class TestFigInput(ComparisonMixin, TestCase): effect_class = FigInput - compare_file = 'io/test.fig' + compare_file = "io/test.fig" comparisons = [()] diff --git a/tests/test_flatten.py b/tests/test_flatten.py index ff3d63ac..f0a32cec 100644 --- a/tests/test_flatten.py +++ b/tests/test_flatten.py @@ -3,6 +3,7 @@ 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/tests/test_foldablebox.py b/tests/test_foldablebox.py index efb40ff7..48990d40 100644 --- a/tests/test_foldablebox.py +++ b/tests/test_foldablebox.py @@ -2,10 +2,11 @@ from foldablebox import FoldableBox from inkex.tester import ComparisonMixin, TestCase + class FoldableBoxArguments(ComparisonMixin, TestCase): effect_class = FoldableBox - compare_file = 'svg/empty.svg' + compare_file = "svg/empty.svg" comparisons = [ - ('--width=20', '--height=20', '--depth=2.2', '--unit=cm'), - ('--proportion=0.5', '--guide=true', '--unit=cm'), + ("--width=20", "--height=20", "--depth=2.2", "--unit=cm"), + ("--proportion=0.5", "--guide=true", "--unit=cm"), ] diff --git a/tests/test_fractalize.py b/tests/test_fractalize.py index b01b99ae..f705e28c 100644 --- a/tests/test_fractalize.py +++ b/tests/test_fractalize.py @@ -2,6 +2,7 @@ from fractalize import Fractalize from inkex.tester import ComparisonMixin, TestCase + class PathFractalizeBasicTest(ComparisonMixin, TestCase): effect_class = Fractalize - comparisons = [('--id=p1', '--id=p2')] + comparisons = [("--id=p1", "--id=p2")] diff --git a/tests/test_frame.py b/tests/test_frame.py index 64280659..ae5d86ca 100644 --- a/tests/test_frame.py +++ b/tests/test_frame.py @@ -29,68 +29,84 @@ class FrameTest(InkscapeExtensionTestMixin, TestCase): effect_class = Frame def get_frame(self, svg): - return svg.getElement('//svg:g[@id="layer1"]//svg:path[@inkscape:label="Frame"]') + 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')] + 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 + '".') + 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')] + 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) + 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("{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', + "svg", + "single_box.svg", clip=True, corner_radius=20, fill_color=-16777124, - id='rect3006', - position='inside', + id="rect3006", + position="inside", stroke_color=255, tab="stroke", - width=10) + 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) + 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) + 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/tests/test_funcplot.py b/tests/test_funcplot.py index c8cd8291..c76f1215 100644 --- a/tests/test_funcplot.py +++ b/tests/test_funcplot.py @@ -4,9 +4,10 @@ from inkex.tester import ComparisonMixin, TestCase from inkex.tester.filters import CompareNumericFuzzy import math + class FuncPlotBasicTest(ComparisonMixin, TestCase): effect_class = FuncPlot compare_filters = [CompareNumericFuzzy()] comparisons = [ - ('--id=p1', '--id=r3', '--times2pi=True', '--ybottom=-1.0', '--drawaxis=True'), - ] \ No newline at end of file + ("--id=p1", "--id=r3", "--times2pi=True", "--ybottom=-1.0", "--drawaxis=True"), + ] diff --git a/tests/test_generate_voronoi.py b/tests/test_generate_voronoi.py index 6d24765a..2b0711f8 100644 --- a/tests/test_generate_voronoi.py +++ b/tests/test_generate_voronoi.py @@ -3,7 +3,10 @@ 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'),] + comparisons = [ + ("--id=r3", "--id=p1"), + ] compare_filters = [CompareOrderIndependentStyle()] diff --git a/tests/test_gimp_xcf.py b/tests/test_gimp_xcf.py index 876b59ce..ee70ea83 100644 --- a/tests/test_gimp_xcf.py +++ b/tests/test_gimp_xcf.py @@ -8,13 +8,19 @@ Revision history: 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'),] + compare_file = "svg/guides.svg" + comparisons = [ + ("-d=true", "-r=true"), + ] diff --git a/tests/test_grid_cartesian.py b/tests/test_grid_cartesian.py index 3e2d3498..cdd8fd1f 100644 --- a/tests/test_grid_cartesian.py +++ b/tests/test_grid_cartesian.py @@ -3,14 +3,29 @@ 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()] - old_defaults = ["--border_th=3", "--border_th_unit=cm", "--dx=100.0", "--dx_unit=cm", - "--x_subdivs=2", "--x_subsubdivs=5", "--x_half_freq=4", "--x_divs_th=2", - "--x_subdivs_th=1", "--x_div_unit=cm", "--dy=100.0", "--dy_unit=cm", - "--y_half_freq=4", "--y_divs_th=2", "--y_subdivs_th=1", "--y_div_unit=cm"] + old_defaults = [ + "--border_th=3", + "--border_th_unit=cm", + "--dx=100.0", + "--dx_unit=cm", + "--x_subdivs=2", + "--x_subsubdivs=5", + "--x_half_freq=4", + "--x_divs_th=2", + "--x_subdivs_th=1", + "--x_div_unit=cm", + "--dy=100.0", + "--dy_unit=cm", + "--y_half_freq=4", + "--y_divs_th=2", + "--y_subdivs_th=1", + "--y_div_unit=cm", + ] comparisons = [ tuple(old_defaults), - tuple(old_defaults + ['--id=p1', '--id=r3']), + tuple(old_defaults + ["--id=p1", "--id=r3"]), ] diff --git a/tests/test_grid_isometric.py b/tests/test_grid_isometric.py index ac816efe..19be08f5 100644 --- a/tests/test_grid_isometric.py +++ b/tests/test_grid_isometric.py @@ -4,6 +4,7 @@ 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 diff --git a/tests/test_grid_polar.py b/tests/test_grid_polar.py index 35ecb9e9..3a524523 100644 --- a/tests/test_grid_polar.py +++ b/tests/test_grid_polar.py @@ -3,10 +3,11 @@ 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 comparisons = [ - ('--a_subdivs_cent=1', '--a_labels=deg'), - ('--id=p1', '--id=r3', '--a_subdivs_cent=1', '--a_labels=deg'), + ("--a_subdivs_cent=1", "--a_labels=deg"), + ("--id=p1", "--id=r3", "--a_subdivs_cent=1", "--a_labels=deg"), ] diff --git a/tests/test_guides_creator.py b/tests/test_guides_creator.py index 043aa10d..03d5c73d 100644 --- a/tests/test_guides_creator.py +++ b/tests/test_guides_creator.py @@ -4,25 +4,42 @@ 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(),] - old_defaults = ('--vertical_guides=3', '--ul=True', '--ur=True', '--ll=True', '--lr=True', - '--header_margin=6', '--footer_margin=6', '--left_margin=6', '--right_margin=6') + compare_file = "svg/guides.svg" + compare_filters = [ + CompareNumericFuzzy(), + ] + old_defaults = ( + "--vertical_guides=3", + "--ul=True", + "--ur=True", + "--ll=True", + "--lr=True", + "--header_margin=6", + "--footer_margin=6", + "--left_margin=6", + "--right_margin=6", + ) comparisons = [ - old_defaults + ('--tab=regular_guides', '--guides_preset=custom'), - old_defaults + ('--tab=regular_guides', '--guides_preset=golden', '--delete=True'), - old_defaults + ('--tab=regular_guides', '--guides_preset=5;5', '--start_from_edges=True'), - old_defaults + ('--tab=diagonal_guides',), - old_defaults + ('--tab=margins', '--start_from_edges=True', '--margins_preset=custom'), - old_defaults + ('--tab=margins', '--start_from_edges=True', '--margins_preset=book_left'), - old_defaults + ('--tab=margins', '--start_from_edges=True', '--margins_preset=book_right'), + old_defaults + ("--tab=regular_guides", "--guides_preset=custom"), + old_defaults + + ("--tab=regular_guides", "--guides_preset=golden", "--delete=True"), + old_defaults + + ("--tab=regular_guides", "--guides_preset=5;5", "--start_from_edges=True"), + old_defaults + ("--tab=diagonal_guides",), + old_defaults + + ("--tab=margins", "--start_from_edges=True", "--margins_preset=custom"), + old_defaults + + ("--tab=margins", "--start_from_edges=True", "--margins_preset=book_left"), + old_defaults + + ("--tab=margins", "--start_from_edges=True", "--margins_preset=book_right"), ] + class GuidesCreatorMillimeterTest(ComparisonMixin, TestCase): effect_class = GuidesCreator - compare_file = 'svg/complextransform.test.svg' + compare_file = "svg/complextransform.test.svg" compare_filters = [CompareNumericFuzzy()] comparisons = [("--vertical_guides=6", "--horizontal_guides=8")] - diff --git a/tests/test_guillotine.py b/tests/test_guillotine.py index fbd85a1c..b4ec3425 100644 --- a/tests/test_guillotine.py +++ b/tests/test_guillotine.py @@ -4,45 +4,49 @@ import tarfile from guillotine import Guillotine from inkex.tester import ComparisonMixin, TestCase + class GuillotineTester(ComparisonMixin): """Test the Guillotine extension""" + effect_class = Guillotine + 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),) + 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'))) + 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_cmpfile(args) - if os.environ.get('EXPORT_COMPARE', False): + 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)) - + 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, cmpfile): """Export the files as a tar file for manual comparison""" - tarname = cmpfile + '.export' - tar = tarfile.open(tarname, 'w|') + tarname = cmpfile + ".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: + with open(os.path.join(outdir, name), "rb") as fhl: fhl.seek(0, 2) info = tarfile.TarInfo(name) info.size = fhl.tell() @@ -51,20 +55,21 @@ class GuillotineTester(ComparisonMixin): tar.close() print("Written output: {}.export".format(cmpfile)) + class TestGuillotineBasic(GuillotineTester, TestCase): stderr_protect = False effect_class = Guillotine - compare_file = 'svg/guides.svg' + compare_file = "svg/guides.svg" comparisons = [ - ('--image=f{}oo',), - ('--ignore=true',), + ("--image=f{}oo",), + ("--ignore=true",), ] + class TestGuillotineMillimeter(GuillotineTester, TestCase): stderr_protect = False effect_class = Guillotine - compare_file = 'svg/guides_millimeter.svg' + compare_file = "svg/guides_millimeter.svg" comparisons = [ - ('--image=output',), + ("--image=output",), ] - \ No newline at end of file diff --git a/tests/test_handles.py b/tests/test_handles.py index 280aedaa..e6492f50 100644 --- a/tests/test_handles.py +++ b/tests/test_handles.py @@ -3,9 +3,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'), - ) + compare_file = "svg/curves.svg" + comparisons = (("--id=curve", "--id=quad"),) diff --git a/tests/test_hershey.py b/tests/test_hershey.py index 895357ad..f698982c 100644 --- a/tests/test_hershey.py +++ b/tests/test_hershey.py @@ -7,8 +7,9 @@ from inkex.tester.filters import CompareNumericFuzzy, CompareOrderIndependentSty from hershey import Hershey + class HersheyComparisonMixin(ComparisonMixin): - comparisons_cmpfile_dict = {} # pairs of args and expected outputs + comparisons_cmpfile_dict = {} # pairs of args and expected outputs def setUp(self): self.effect_class = Hershey @@ -17,40 +18,60 @@ class HersheyComparisonMixin(ComparisonMixin): self.comparisons = self.comparisons_cmpfile_dict.keys() def get_compare_cmpfile(self, args, addout=None): - ''' get the correct cmpfile to compare from comparisons_dict; ''' - return self.data_file('refs', self.comparisons_cmpfile_dict[args]) + """get the correct cmpfile to compare from comparisons_dict;""" + return self.data_file("refs", self.comparisons_cmpfile_dict[args]) + class TestHersheyBasic(InkscapeExtensionTestMixin, HersheyComparisonMixin, TestCase): - compare_file = 'svg/hershey_input.svg' # a huge number of inputs + compare_file = "svg/hershey_input.svg" # a huge number of inputs comparisons_cmpfile_dict = { # default parameters: - (): 'hershey.out', + (): "hershey.out", # same as above, but explicit parameters. same output: - ('--tab="render"', '--fontface="HersheySans1"', '--preserve="False"'): 'hershey.out', + ( + '--tab="render"', + '--fontface="HersheySans1"', + '--preserve="False"', + ): "hershey.out", } -class TestHersheyTrivialInput(InkscapeExtensionTestMixin, HersheyComparisonMixin, TestCase): - compare_file = 'svg/hershey_trivial_input.svg' + +class TestHersheyTrivialInput( + InkscapeExtensionTestMixin, HersheyComparisonMixin, TestCase +): + compare_file = "svg/hershey_trivial_input.svg" comparisons_cmpfile_dict = { # loading a different font: - ('--fontface="EMSAllure"', ): 'hershey_loadfont.out', + ('--fontface="EMSAllure"',): "hershey_loadfont.out", # using the "other font" option. same output as above: - ('--fontface="other"', '--otherfont="EMSAllure"'): 'hershey_loadfont.out', + ('--fontface="other"', '--otherfont="EMSAllure"'): "hershey_loadfont.out", # tests preserve text option - ('--fontface="EMSOsmotron"', '--preserve=true'): 'hershey_preservetext.out', + ('--fontface="EMSOsmotron"', "--preserve=true"): "hershey_preservetext.out", # tests when just part of the input file is selected - ('--id=A',): 'hershey_partialselection.out', + ("--id=A",): "hershey_partialselection.out", } + class TestHersheyTables(InkscapeExtensionTestMixin, HersheyComparisonMixin, TestCase): - compare_file = 'svg/default-inkscape-SVG.svg' + compare_file = "svg/default-inkscape-SVG.svg" comparisons_cmpfile_dict = { # generates a simple font table: - ('--tab="utilities"', '--action="sample"', '--text="I am a quick brown fox"'): 'hershey_fonttable.out', + ( + '--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', + ( + '--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', - } - - + ( + '--tab="utilities"', + '--action="table"', + '--fontface="other"', + '--otherfont="EMSOsmotron"', + ): "hershey_glyphtable.out", + } diff --git a/tests/test_hpgl_input.py b/tests/test_hpgl_input.py index c6700e47..5f476a94 100644 --- a/tests/test_hpgl_input.py +++ b/tests/test_hpgl_input.py @@ -2,7 +2,8 @@ from hpgl_input import HpglInput from inkex.tester import ComparisonMixin, TestCase + class TestHpglFileBasic(ComparisonMixin, TestCase): effect_class = HpglInput - compare_file = 'io/test.hpgl' + compare_file = "io/test.hpgl" comparisons = [()] diff --git a/tests/test_hpgl_output.py b/tests/test_hpgl_output.py index da017b00..0be89b2a 100644 --- a/tests/test_hpgl_output.py +++ b/tests/test_hpgl_output.py @@ -2,10 +2,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' - ] + compare_file = ["svg/shapes.svg", "svg/hpgl_multipen.svg"] comparisons = [("--force=24", "--speed=20", "--orientation=90")] diff --git a/tests/test_image_attributes.py b/tests/test_image_attributes.py index e6e523b8..634a69f7 100644 --- a/tests/test_image_attributes.py +++ b/tests/test_image_attributes.py @@ -2,11 +2,16 @@ from image_attributes import ImageAttributes from inkex.tester import ComparisonMixin, TestCase + class TestSetAttrImageBasic(ComparisonMixin, TestCase): effect_class = ImageAttributes - compare_file = 'svg/images.svg' + 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"'), + (), # 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/tests/test_image_embed.py b/tests/test_image_embed.py index 9eb131c1..3163d639 100644 --- a/tests/test_image_embed.py +++ b/tests/test_image_embed.py @@ -2,10 +2,14 @@ from image_embed import EmbedImage from inkex.tester import ComparisonMixin, TestCase + class EmbedderBasicTest(ComparisonMixin, TestCase): effect_class = EmbedImage - compare_file = 'svg/images.svg' + compare_file = "svg/images.svg" comparisons = [ (), - ( "--id=image174", "--selectedonly=True",) + ( + "--id=image174", + "--selectedonly=True", + ), ] diff --git a/tests/test_image_extract.py b/tests/test_image_extract.py index 0a1e20e2..01571bbc 100644 --- a/tests/test_image_extract.py +++ b/tests/test_image_extract.py @@ -4,18 +4,19 @@ import os from image_extract 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' - compare_file_extension = 'png' + compare_file = "svg/images.svg" + compare_file_extension = "png" comparisons = [ - ('--selectedonly=False',), - ('--selectedonly=True', '--id=embeded_image01'), + ("--selectedonly=False",), + ("--selectedonly=True", "--id=embeded_image01"), ] def test_all_comparisons(self): """Images are extracted to a file directory""" for args in self.comparisons: - args += ('--filepath={}/'.format(self.tempdir),) - self.assertCompare(self.compare_file, None, args, 'embeded_image01.png') + args += ("--filepath={}/".format(self.tempdir),) + self.assertCompare(self.compare_file, None, args, "embeded_image01.png") diff --git a/tests/test_ink2canvas_svg.py b/tests/test_ink2canvas_svg.py index 3401db41..1e2e5cbe 100644 --- a/tests/test_ink2canvas_svg.py +++ b/tests/test_ink2canvas_svg.py @@ -5,22 +5,25 @@ from inkex.tester import ComparisonMixin, TestCase from inkex.tester.filters import CompareOrderIndependentLines from inkex.tester.filters import WindowsTextCompat + class Ink2CanvasBasicTest(ComparisonMixin, TestCase): effect_class = Html5Canvas - compare_file = 'svg/shapes-clipboard.svg' + compare_file = "svg/shapes-clipboard.svg" compare_filters = [CompareOrderIndependentLines()] comparisons = [()] + class Ink2CanvasTestTextPath(ComparisonMixin, TestCase): effect_class = Html5Canvas - compare_file = 'svg/multilayered-test.svg' + compare_file = "svg/multilayered-test.svg" # This file contains a textPath compare_filters = [CompareOrderIndependentLines()] # We don't need a selection for this case, but we need unique filenames for the tester comparisons = [("--id=rect3898",)] + class Ink2CanvasTestClosedPath(ComparisonMixin, TestCase): effect_class = Html5Canvas - compare_file = 'svg/multiple_closed_subpaths.svg' + compare_file = "svg/multiple_closed_subpaths.svg" comparisons = [("--id=path31",)] - compare_filters = [WindowsTextCompat()] \ No newline at end of file + compare_filters = [WindowsTextCompat()] diff --git a/tests/test_inkex.py b/tests/test_inkex.py index 1f550dd6..6a0a597a 100644 --- a/tests/test_inkex.py +++ b/tests/test_inkex.py @@ -26,8 +26,10 @@ 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 @@ -37,22 +39,30 @@ class ProtectiveGlobals(dict): "While importing {} the API name `{}` was re-defined:" "\n\t1. {}" "\n\t2. {}" - ).format(self['__name__'], name, repr(value), repr(self[name])) + ).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 + + 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: + 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__})) + exec( + fhl.read(), + ProtectiveGlobals( + { # pylint: disable=exec-used + "__name__": module.__name__, + "__package__": module.__package__, + } + ), + ) def test_inkex(self): """Test inkex API have no collisions""" diff --git a/tests/test_inkex_base.py b/tests/test_inkex_base.py index 44cacd33..bff7c5df 100644 --- a/tests/test_inkex_base.py +++ b/tests/test_inkex_base.py @@ -11,11 +11,12 @@ from inkex import AbortExtension 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' + self.document += b">flipple" def load(self, stream): return stream.read() @@ -35,11 +36,12 @@ class ModSvgExtension(SvgThroughMixin, InkscapeExtension): """Test the loading and saving of svg files""" def effect(self): - self.svg.set('attr', 'foo') + self.svg.set("attr", "foo") class InkscapeExtensionTest(TestCase): """Tests for Inkscape Extensions""" + effect_class = InkscapeExtension def setUp(self): @@ -51,7 +53,7 @@ class InkscapeExtensionTest(TestCase): self.e.run([]) with self.assertRaises(NotImplementedError): prevarg = sys.argv - sys.argv = ['pytest'] + sys.argv = ["pytest"] try: self.e.run() finally: @@ -62,7 +64,7 @@ class InkscapeExtensionTest(TestCase): self.e.load(sys.stdin) with self.assertRaises(NotImplementedError): self.e.save(sys.stdout) - self.assertEqual(self.e.name, 'InkscapeExtension') + self.assertEqual(self.e.name, "InkscapeExtension") def test_compat(self): """Test a few old functions and how we handle them""" @@ -79,14 +81,14 @@ class InkscapeExtensionTest(TestCase): 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]) + 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') + self.assertEqual(options.output, "foo.txt") def test_get_resource(self): """We can get a resource path, based on where the extension is located""" ext = ModExtension() - self.assertRaises(AbortExtension, ext.get_resource, 'sir-not-apearing.py') + self.assertRaises(AbortExtension, ext.get_resource, "sir-not-apearing.py") # Test relative filename, which fails with AbortExtension if not found. ret = ext.get_resource(__file__) @@ -95,27 +97,33 @@ class InkscapeExtensionTest(TestCase): def test_svg_path(self): """Can get the svg file location""" - output = os.path.join(self.tempdir, 'output.tmp') + output = os.path.join(self.tempdir, "output.tmp") ext = ModExtension() - os.environ['DOCUMENT_PATH'] = self.empty_svg - self.assertEqual(ext.svg_path(), os.path.join(self.datadir(), 'svg')) - self.assertIn(ext.absolute_href('/foo'), ['/foo', "C:\\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'))) + os.environ["DOCUMENT_PATH"] = self.empty_svg + self.assertEqual(ext.svg_path(), os.path.join(self.datadir(), "svg")) + self.assertIn(ext.absolute_href("/foo"), ["/foo", "C:\\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")) + ) def test_svg_no_path(self): - tmp_foo = os.path.realpath('/tmp/foo') - os.environ['DOCUMENT_PATH'] = '' + tmp_foo = os.path.realpath("/tmp/foo") + os.environ["DOCUMENT_PATH"] = "" ext = ModExtension() # Default results in home dir - self.assertEqual(ext.absolute_href('./foo'), os.path.realpath(os.path.expanduser('~/foo'))) + self.assertEqual( + ext.absolute_href("./foo"), os.path.realpath(os.path.expanduser("~/foo")) + ) # Or override the default - self.assertEqual(ext.absolute_href('./foo', '/tmp/'), tmp_foo) + self.assertEqual(ext.absolute_href("./foo", "/tmp/"), tmp_foo) # But we can ask for errors too, this one for "document not saved" - self.assertRaises(AbortExtension, ext.absolute_href, './foo', default=None) + self.assertRaises(AbortExtension, ext.absolute_href, "./foo", default=None) # This covers inkscape old versions - del os.environ['DOCUMENT_PATH'] - self.assertRaises(AbortExtension, ext.absolute_href, './foo', default=None) + del os.environ["DOCUMENT_PATH"] + self.assertRaises(AbortExtension, ext.absolute_href, "./foo", default=None) class SvgInputOutputTest(TestCase): @@ -131,27 +139,27 @@ class SvgInputOutputTest(TestCase): 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') + 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]) + 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/tests/test_inkex_deprecated.py b/tests/test_inkex_deprecated.py index 546f158a..3ec12582 100644 --- a/tests/test_inkex_deprecated.py +++ b/tests/test_inkex_deprecated.py @@ -7,11 +7,15 @@ 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 + 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") @@ -21,7 +25,9 @@ class DeprecatedTests(TestCase): else: self.assertTrue(warns, "No warning was returned, expected warning!") if msg is not False: - self.assertEqual(str(warns[0].category.__name__), "DeprecationWarning") + self.assertEqual( + str(warns[0].category.__name__), "DeprecationWarning" + ) return warns[0] def test_warning(self): diff --git a/tests/test_inkex_elements.py b/tests/test_inkex_elements.py index ee651852..b1bd0eaf 100644 --- a/tests/test_inkex_elements.py +++ b/tests/test_inkex_elements.py @@ -9,10 +9,32 @@ import math import inkex from inkex import ( - Group, Layer, Pattern, Guide, Page, Polyline, Use, Defs, - TextElement, TextPath, Tspan, FlowPara, FlowRoot, FlowRegion, FlowSpan, - PathElement, Rectangle, Circle, Ellipse, Anchor, Line as LineElement, - Transform, Style, LinearGradient, RadialGradient, Stop + Group, + Layer, + Pattern, + Guide, + Page, + 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 import paths from inkex.colors import Color @@ -21,55 +43,58 @@ from inkex.utils import FragmentError from inkex.units import parse_unit from .test_inkex_elements_base import SvgTestCase -from inkex.tester.svg import svg +from inkex.tester.svg import svg + class ElementTestCase(SvgTestCase): """Base element testing""" - tag = 'svg' + + tag = "svg" def setUp(self): super().setUp() - self.elem = self.svg.getElement('//svg:{}'.format(self.tag)) + 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 + def assertElement(self, elem, compare): # pylint: disable=invalid-name """Assert an element""" self.assertEqual(elem.tostring(), compare) class PathElementTestCase(ElementTestCase): """Test PathElements""" - source_file = 'with-lpe.svg' - tag = 'path' + + 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') + 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 = 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') + 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') + 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') - + self.assertEqual(nolpe.get("inkscape:original-d", None), None) + self.assertEqual(nolpe.get("d"), "M 60 60 L 5 5") + def _compare_paths(self, result, reference, precision=4): reference = inkex.Path(reference) result = inkex.Path(result) @@ -77,49 +102,128 @@ class PathElementTestCase(ElementTestCase): for c1, c2 in zip(reference, result): self.assertEqual(c1.letter, c2.letter) self.assertAlmostTuple(c1.args, c2.args, precision=precision, msg=result) + def test_arc(self): """Test arc generation""" + def compare_arc(cx, cy, rx, ry, start, end, reference, type="arc"): arc = PathElement.arc((cx, cy), rx, ry, start=start, end=end, arctype=type) self.assertEqual(arc.get("sodipodi:arc-type"), type) self._compare_paths(arc.get("d"), reference) - compare_arc(10, 20, 5, 5, math.pi/4, math.pi*6/4, - """m 13.535534,23.535534 a 5,5 0 0 1 -6.035534,0.794593 - 5,5 0 0 1 -2.3296291,-5.624222 5,5 0 0 1 4.8296291,-3.705905""") - compare_arc(10, 20, 5, 5, math.pi/4, math.pi*6/4, - """m 13.535534,23.535534 a 5,5 0 0 1 -6.035534,0.794593 + + compare_arc( + 10, + 20, + 5, + 5, + math.pi / 4, + math.pi * 6 / 4, + """m 13.535534,23.535534 a 5,5 0 0 1 -6.035534,0.794593 + 5,5 0 0 1 -2.3296291,-5.624222 5,5 0 0 1 4.8296291,-3.705905""", + ) + compare_arc( + 10, + 20, + 5, + 5, + math.pi / 4, + math.pi * 6 / 4, + """m 13.535534,23.535534 a 5,5 0 0 1 -6.035534,0.794593 5,5 0 0 1 -2.3296291,-5.624222 5,5 0 0 1 4.8296291,-3.705905 z""", - type="chord") - compare_arc(10, 20, 5, 5, math.pi/4, math.pi*28/18, - """m 13.535534,23.535534 a 5,5 0 0 1 -6.2830789,0.641905 5,5 0 0 1 -1.8991927,-6.02347 + type="chord", + ) + compare_arc( + 10, + 20, + 5, + 5, + math.pi / 4, + math.pi * 28 / 18, + """m 13.535534,23.535534 a 5,5 0 0 1 -6.2830789,0.641905 5,5 0 0 1 -1.8991927,-6.02347 5,5 0 0 1 5.5149786,-3.078008 l -0.868241,4.924039 z""", - type="slice") + type="slice", + ) + def test_stars(self): - def compare_star(cx, cy, sides, r1, r2, arg1, arg2, flatsided, rounded, reference, precision=4): - star = PathElement.star((cx, cy), (r1, r2), sides, rounded, (arg1, arg2), flatsided) + def compare_star( + cx, + cy, + sides, + r1, + r2, + arg1, + arg2, + flatsided, + rounded, + reference, + precision=4, + ): + star = PathElement.star( + (cx, cy), (r1, r2), sides, rounded, (arg1, arg2), flatsided + ) self.assertEqual(star.get("inkscape:flatsided"), str(flatsided).lower()) self._compare_paths(star.get("d"), reference, precision=precision) + # Test a simple polygon - compare_star(10, 5, 6, 5.5, 10, 7/8*math.pi, 42, True, 0, - """m 4.9186625,7.1047587 0.7178942,-5.4529467 5.0813373,-2.10475872 - 4.363443,3.34818802 -0.717894,5.4529467 -5.0813372,2.104759 z""") + compare_star( + 10, + 5, + 6, + 5.5, + 10, + 7 / 8 * math.pi, + 42, + True, + 0, + """m 4.9186625,7.1047587 0.7178942,-5.4529467 5.0813373,-2.10475872 + 4.363443,3.34818802 -0.717894,5.4529467 -5.0813372,2.104759 z""", + ) # Test a star - compare_star(5, 10, 7, 35, 17, 0.95545678, 1.4790556, False, 0, - """m 25.203254,38.580212 -18.6458484,-11.651701 -11.3057927,16.6865 + compare_star( + 5, + 10, + 7, + 35, + 17, + 0.95545678, + 1.4790556, + False, + 0, + """m 25.203254,38.580212 -18.6458484,-11.651701 -11.3057927,16.6865 -2.5158293,-21.842628 -20.0950776,1.564637 15.508661,-15.5856099 -13.752359,-14.7354284 21.8548124,2.4076898 2.94616632,-19.9394165 11.74384528,18.5879506 17.426168,-10.1286176 -7.210477,20.7711057 - 18.783909,7.3092373 -20.735163,7.313194 z""") + 18.783909,7.3092373 -20.735163,7.313194 z""", + ) # Test a rounded polygon - compare_star(10, 5, 6, 5.5, 10, 7/8*math.pi, 42, True, 0.1, - """m 4.9186625,7.1047587 c -0.2104759,-0.5081337 0.3830754,-5.0166024 0.7178942,-5.4529467 + compare_star( + 10, + 5, + 6, + 5.5, + 10, + 7 / 8 * math.pi, + 42, + True, + 0.1, + """m 4.9186625,7.1047587 c -0.2104759,-0.5081337 0.3830754,-5.0166024 0.7178942,-5.4529467 0.3348188,-0.4363443 4.5360433,-2.17654814 5.0813373,-2.10475872 0.545295,0.0717894 4.152968,2.84005422 4.363443,3.34818802 0.210476,0.5081337 -0.383075,5.0166024 -0.717894,5.4529467 -0.334819,0.4363443 -4.5360425,2.176548 -5.0813372,2.104759 - -0.5452947,-0.07179 -4.1529674,-2.8400545 -4.3634433,-3.3481883 z""") - compare_star(5, 10, 7, 35, 17, 0.95545678, 1.4790556, False, 1, - """m 25.203254,38.580212 c -18.8526653,11.31401 3.036933,-15.296807 -18.6458484,-11.651701 + -0.5452947,-0.07179 -4.1529674,-2.8400545 -4.3634433,-3.3481883 z""", + ) + compare_star( + 5, + 10, + 7, + 35, + 17, + 0.95545678, + 1.4790556, + False, + 1, + """m 25.203254,38.580212 c -18.8526653,11.31401 3.036933,-15.296807 -18.6458484,-11.651701 -19.8769816,3.341532 7.5786704,23.731873 -11.3057927,16.6865 -20.6000929,-7.685438 13.8530224,-7.163034 -2.5158293,-21.842628 -15.0056106,-13.4570388 -13.8291026,20.721824 -20.0950776,1.564637 @@ -132,13 +236,15 @@ class PathElementTestCase(ElementTestCase): 15.225456,15.86238583 -15.589066,0.44307 -7.210477,20.7711057 7.680797,18.6350633 21.450453,-12.6694955 18.783909,7.3092373 -2.908795,21.793777 -10.066029,-11.91177319 -20.735163,7.313194 - -9.7805797,17.623861 23.27955,8.871339 5.996985,19.243087 z""", precision=2) - - - + -9.7805797,17.623861 23.27955,8.871339 5.996985,19.243087 z""", + precision=2, + ) + + class PolylineElementTestCase(ElementTestCase): """Test the polyline elements support""" - tag = 'polyline' + + tag = "polyline" def test_type(self): """Polyline have their own types""" @@ -146,14 +252,16 @@ class PolylineElementTestCase(ElementTestCase): 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 = 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') + self.assertEqual(pol.get("points"), "10,10 30,9 1,2 45,60 35,35") + class PolygonElementTestCase(ElementTestCase): """Test Polygon Elements""" - tag = 'polygon' + + tag = "polygon" def test_type(self): """Polygons have their own types""" @@ -161,12 +269,14 @@ class PolygonElementTestCase(ElementTestCase): 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') + 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' + + tag = "line" def test_new_line(self): """Line creation""" @@ -179,77 +289,94 @@ class LineElementTestCase(ElementTestCase): 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') + 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' + + 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)') + self.assertEqual(pattern.get("patternTransform"), "translate(10, 10)") + class GroupTest(ElementTestCase): """Test extra functionality on a group element""" - tag = 'g' + + tag = "g" def test_new_group(self): """Test creating groups""" - svg = Layer.new('layerA', Group.new('groupA', Rectangle())) - self.assertElement(svg,\ - b''\ - b'') + 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)') + 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') + 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())) + 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') + 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) + 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' + + 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" }) + 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.right, 10 + 100) + self.assertEqual(rect.bottom, 20 + 200) self.assertEqual(rect.width, 100) self.assertEqual(rect.height, 200) self.assertEqual(rect.rx, 15) @@ -257,45 +384,60 @@ class RectTest(ElementTestCase): 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)') + 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') + 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.specified_style()), - 'fill:#0000ff;stroke-width:1px;stroke:#d88') + self.assertEqual(str(self.elem.style), "fill:#0000ff;stroke-width:1px") + self.assertEqual( + str(self.elem.specified_style()), + "fill:#0000ff;stroke-width:1px;stroke:#d88", + ) 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') + 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' + + tag = "path" def test_apply_transform(self): """Transformation can be applied to path""" - path = self.svg.getElementById('D') + 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') + 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.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' + + tag = "circle" def test_parse(self): """Test Circle parsed from XML""" @@ -303,7 +445,9 @@ class CircleTest(ElementTestCase): 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"}) + 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) @@ -318,53 +462,70 @@ class CircleTest(ElementTestCase): 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') + 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()) + 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.svg.namedview.add(Guide().move_to(0, 0, "90")) self.assertEqual(len(self.svg.namedview.get_guides()), 2) def test_pages(self): """Create some extra pages and see a list of them""" self.assertEqual(len(self.svg.namedview.get_pages()), 0) - self.svg.namedview.add(Page(width='210', height='297', x='0', y='0')) - self.svg.namedview.new_page(x='220', y='0', width='147.5', height='210', label='TEST') + self.svg.namedview.add(Page(width="210", height="297", x="0", y="0")) + self.svg.namedview.new_page( + x="220", y="0", width="147.5", height="210", label="TEST" + ) self.assertEqual(len(self.svg.namedview.get_pages()), 2) - self.assertEqual(self.svg.namedview.get_pages()[0].attrib['x'], '0') - self.assertEqual(self.svg.namedview.get_pages()[1].get('inkscape:label'), 'TEST') - self.assertEqual(self.svg.namedview.get_pages()[1].attrib['width'], '147.5') - self.assertEqual(self.svg.namedview.get_pages()[0].attrib['height'], '297') - self.assertEqual(self.svg.namedview.get_pages()[1].attrib['x'], '220') - self.assertEqual(self.svg.namedview.get_pages()[1].attrib['y'], '0') + self.assertEqual(self.svg.namedview.get_pages()[0].attrib["x"], "0") + self.assertEqual( + self.svg.namedview.get_pages()[1].get("inkscape:label"), "TEST" + ) + self.assertEqual(self.svg.namedview.get_pages()[1].attrib["width"], "147.5") + self.assertEqual(self.svg.namedview.get_pages()[0].attrib["height"], "297") + self.assertEqual(self.svg.namedview.get_pages()[1].attrib["x"], "220") + self.assertEqual(self.svg.namedview.get_pages()[1].attrib["y"], "0") def test_center(self): """Test that the center in mm based documents is correctly computed""" mmbased = svg(f'width="210mm" viewBox="0 0 210 297"') - mmbased.namedview.set("inkscape:cx", 396.57881) # Values of a freshly opened mm document + mmbased.namedview.set( + "inkscape:cx", 396.57881 + ) # Values of a freshly opened mm document mmbased.namedview.set("inkscape:cy", 561.81998) - self.assertAlmostTuple(mmbased.namedview.center, - [mmbased.viewbox_width / 2, mmbased.viewport_height / 2], precision=0) + self.assertAlmostTuple( + mmbased.namedview.center, + [mmbased.viewbox_width / 2, mmbased.viewport_height / 2], + precision=0, + ) + class TextTest(ElementTestCase): """Test all text functions""" + def test_append_superscript(self): """Test adding superscript""" tap = TextPath() - tap.append(Tspan.superscript('th')) + tap.append(Tspan.superscript("th")) self.assertEqual(len(tap), 1) def test_path(self): @@ -380,82 +541,89 @@ class TextTest(ElementTestCase): class UseTest(ElementTestCase): """Test extra functionality on a use element""" - tag = 'use' + + tag = "use" def test_path(self): """Use path follows ref""" - self.assertEqual(str(self.elem.path), 'M 0 0 L 10 10 Z') + 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') + 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', '') + elem.set("xlink:href", "") self.assertEqual(elem.href, None) - elem.set('xlink:href', '#badref') + 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') + 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') + 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') + + 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)) + 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) + 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}) + 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)') + 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} - )] + ( + 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(parse_unit(grad.get(key))[0]) == pytest.approx(value, 1e-3) @@ -463,18 +631,22 @@ class GradientTests(ElementTestCase): 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} - )] + ( + 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') + val = grad.get("gradientTransform") assert val is None for key, value in expected.items(): assert float(grad.get(key)) == pytest.approx(value, 1e-3) @@ -485,7 +657,8 @@ class GradientTests(ElementTestCase): grad = classname() stops = [ Stop().update(offset=0.0, style=self.whiteop0), - Stop().update(offset=1.0, style=self.blackop1)] + Stop().update(offset=1.0, style=self.blackop1), + ] grad.add(*stops) assert [s1.tostring() == s2.tostring() for s1, s2 in zip(grad.stops, stops)] @@ -495,7 +668,8 @@ class GradientTests(ElementTestCase): grad = classname() stops = [ Stop().update(offset=0.0, style=self.whiteop0), - Stop().update(offset=1.0, style=self.blackop1)] + 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)] @@ -505,7 +679,8 @@ class GradientTests(ElementTestCase): grad = classname() stops = [ Stop().update(offset=0.0, style=self.whiteop0), - Stop().update(offset=1.0, style=self.blackop1)] + 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)) @@ -513,21 +688,26 @@ class GradientTests(ElementTestCase): 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}) - ] + ( + 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)] + Stop().update(offset=1.0, style=self.blackop1), + ] grad1.add(*stops1) grad1.update(gradientTransform=self.translate11) grad1.update(**orientation1) @@ -536,51 +716,59 @@ class GradientTests(ElementTestCase): grad2 = classname() stops2 = [ Stop().update(offset=0.0, style=self.blackop1), - Stop().update(offset=1.0, style=self.whiteop0)] + 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}) + 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)') + self.assertEqual(str(grad.gradientTransform), "translate(1.5, 1.5)") for key, value in expected.items(): - self.assertEqual(float(parse_unit(grad.get(key))[0]), pytest.approx(value, 1e-3)) + self.assertEqual( + float(parse_unit(grad.get(key))[0]), pytest.approx(value, 1e-3) + ) class SymbolTest(ElementTestCase): """Test Symbol elements""" - source_file = 'symbol.svg' - tag = 'symbol' + + 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') + 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') + 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' + + 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') + defs = self.svg.getElementById("defs33") self.assertTrue(isinstance(defs, Defs)) + class StyleTest(ElementTestCase): """Test a style tag""" - source_file = 'css.svg' - tag = 'style' + + source_file = "css.svg" + tag = "style" def test_style(self): """Make sure style tags can be loaded and saved""" diff --git a/tests/test_inkex_elements_base.py b/tests/test_inkex_elements_base.py index 33167097..49010ee7 100644 --- a/tests/test_inkex_elements_base.py +++ b/tests/test_inkex_elements_base.py @@ -6,7 +6,13 @@ 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, + load_svg, + ShapeElement, + Group, + Rectangle, + Tspan, + TextElement, + Line, ) from inkex.elements._base import NodeBasedLookup from inkex.transforms import Transform @@ -15,30 +21,38 @@ from inkex.utils import FragmentError from inkex.tester import TestCase from inkex.tester.svg import svg_file -class FakeShape(ShapeElement): # pylint: disable=abstract-method + +class FakeShape(ShapeElement): # pylint: disable=abstract-method """A protend shape""" - tag_name = 'fake' + + tag_name = "fake" + class SvgTestCase(TestCase): """Test SVG""" - source_file = 'complextransform.test.svg' + + source_file = "complextransform.test.svg" def setUp(self): super().setUp() - self.svg = svg_file(self.data_file('svg', self.source_file)) + 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(""" + doc = load_svg( + """ Unknown SVG tag -""") +""" + ) svg = doc.getroot() good = svg.getElementById("good") @@ -64,9 +78,9 @@ class OverridenElementTestCase(SvgTestCase): def test_class_lookup(self): """Can we find element classes with simple xpaths""" - self.assertEqual(NodeBasedLookup.find_class('svg:rect'), Rectangle) - self.assertEqual(NodeBasedLookup.find_class('//svg:svg/svg:g'), Group) - self.assertRaises(KeyError, NodeBasedLookup.find_class, 'svg:g[layer]') + self.assertEqual(NodeBasedLookup.find_class("svg:rect"), Rectangle) + self.assertEqual(NodeBasedLookup.find_class("//svg:svg/svg:g"), Group) + self.assertRaises(KeyError, NodeBasedLookup.find_class, "svg:g[layer]") def test_abstract_raises(self): """Abstract classes cannot be instantiated""" @@ -75,305 +89,328 @@ class OverridenElementTestCase(SvgTestCase): 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')) + 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') + 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') + 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')) + 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'') + 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') + 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 - ) + 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.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') + 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) - ) + 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 - ) + 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()) + 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') + 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) + self.assertEqual(group.get("attr1"), None) - group.update( - attr1="42" - ) - self.assertEqual(group.pop('attr1'), "42") - self.assertEqual(group.pop('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') + 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)') + 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) + 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'] = 'purple' - self.assertEqual(elem.get('style'), 'fill:purple') - elem.style = {'stroke-dashoffset': '1'} - self.assertEqual(elem.get('style'), 'stroke-dashoffset:1') - elem.style = Style(stroke='red') - self.assertEqual(elem.get('style'), 'stroke:red') - elem.style.update('grape:2;strawberry:nice;') - self.assertEqual(elem.get('style'), 'stroke:red;grape:2;strawberry:nice') + elem = self.svg.getElementById("D") + elem.style["fill"] = "purple" + self.assertEqual(elem.get("style"), "fill:purple") + elem.style = {"stroke-dashoffset": "1"} + self.assertEqual(elem.get("style"), "stroke-dashoffset:1") + elem.style = Style(stroke="red") + self.assertEqual(elem.get("style"), "stroke:red") + elem.style.update("grape:2;strawberry:nice;") + self.assertEqual(elem.get("style"), "stroke:red;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 = 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') + 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') + 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)') + 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) + 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) def test_insensitive(self): """Element inkscape sensitivity""" - elem = self.svg.getElementByName('Key') + elem = self.svg.getElementByName("Key") self.assertTrue(elem.is_sensitive()) elem.set_sensitive(False) self.assertFalse(elem.is_sensitive()) - self.assertEqual(elem.get('sodipodi:insensitive'), 'true') + self.assertEqual(elem.get("sodipodi:insensitive"), "true") elem.set_sensitive(True) self.assertTrue(elem.is_sensitive()) - self.assertEqual(elem.get('sodipodi:insensitive'), None) + self.assertEqual(elem.get("sodipodi:insensitive"), None) def test_title_description(self): """Adding a description to an element""" + def _content(elem): return tuple(f"{child.TAG}:{child.text}" for child in elem) - self.svg.getElementById('K').title = "Before" - elem = self.svg.getElementById('L') + + self.svg.getElementById("K").title = "Before" + elem = self.svg.getElementById("L") elem.desc = "Dancing" elem.title = "Start" self.assertEqual(elem.desc, "Dancing") self.assertEqual(elem.title, "Start") - self.svg.getElementById('G').title = "After" + self.svg.getElementById("G").title = "After" # Title and Desc elements have been added - self.assertEqual(_content(elem), ('title:Start', 'desc:Dancing', 'line:None')) + self.assertEqual(_content(elem), ("title:Start", "desc:Dancing", "line:None")) # No extra elements created, no conflicts. elem.title = "Stop" - self.assertEqual(_content(elem), ('title:Stop', 'desc:Dancing', 'line:None')) + self.assertEqual(_content(elem), ("title:Stop", "desc:Dancing", "line:None")) # Deletion works too, try twice so we know it doesn't error on not-found del elem.desc del elem.desc - self.assertEqual(_content(elem), ('title:Stop', 'line:None')) + self.assertEqual(_content(elem), ("title:Stop", "line:None")) + class TransformationTestCase(SvgTestCase): """Test transformative functions""" + def test_bounding_box(self): """Elements can have bounding boxes""" - elem = self.svg.getElementById('D') + 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))) + 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 = 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)') + self.assertEqual(str(elem.transform), "translate(20, 20)") def test_scale(self): """In-place scaling from blank transform""" - elem = self.svg.getElementById('F') + elem = self.svg.getElementById("F") self.assertEqual(elem.transform, Transform()) - self.assertEqual(elem.get('transform'), None) + 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)) + 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') + 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)) + 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)) + self.assertIn(b"transform", etree.tostring(elem)) elem.transform.add_translate(-10, -10) - self.assertNotIn(b'transform', etree.tostring(elem)) + 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') + groups = self.svg.findall("svg:g") self.assertEqual(len(groups), 1) def test_copy(self): """Test copying elements""" - elem = self.svg.getElementById('D') + elem = self.svg.getElementById("D") cpy = elem.copy() self.assertFalse(cpy.getparent()) - self.assertFalse(cpy.get('id')) + self.assertFalse(cpy.get("id")) def test_duplicate(self): """Test duplicating elements""" - elem = self.svg.getElementById('D') + elem = self.svg.getElementById("D") dup = elem.duplicate() - self.assertTrue(dup.get('id')) - self.assertNotEqual(elem.get('id'), dup.get('id')) + 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') + 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')) + 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', - )) + 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',)) + self.assertEqual(tuple(get("L").descendants().ids), ("L", "M")) + self.assertEqual(tuple(get("M").descendants().ids), ("M",)) def test_deep_descendants(self): """Create a very deep svg and test getting decendants""" svg = '' for i in range(1000): svg += f'' - svg = load_svg(svg + ('' * 1000) + '').getroot() - - self.assertEqual(tuple(svg.descendants().ids), tuple(str(i) for i in range(1000))) - self.assertEqual(tuple(svg.getElementById('998').descendants().ids), ('998', '999')) - self.assertEqual(tuple(svg.getElementById('999').descendants().ids), ('999',)) + svg = load_svg(svg + ("" * 1000) + "").getroot() + + self.assertEqual( + tuple(svg.descendants().ids), tuple(str(i) for i in range(1000)) + ) + self.assertEqual( + tuple(svg.getElementById("998").descendants().ids), ("998", "999") + ) + self.assertEqual(tuple(svg.getElementById("999").descendants().ids), ("999",)) 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',)) + 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_deep_ancestors(self): """Create a very deep svg and test getting ancestors""" svg = '' for i in range(1000): svg += f'' - svg = load_svg(svg + ('' * 1000) + '').getroot() - self.assertEqual(tuple(svg.getElementById('999').ancestors().ids), tuple(str(i) for i in range(998, -1, -1))) + svg = load_svg(svg + ("" * 1000) + "").getroot() + self.assertEqual( + tuple(svg.getElementById("999").ancestors().ids), + tuple(str(i) for i in range(998, -1, -1)), + ) 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')) + 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/tests/test_inkex_elements_filters.py b/tests/test_inkex_elements_filters.py index d57663ff..f034d536 100644 --- a/tests/test_inkex_elements_filters.py +++ b/tests/test_inkex_elements_filters.py @@ -8,13 +8,13 @@ from inkex.tester.svg import svg_file class GradientTestCase(TestCase): - source_file = 'gradient_with_mixed_offsets.svg' + source_file = "gradient_with_mixed_offsets.svg" def setUp(self): super().setUp() - self.svg = svg_file(self.data_file('svg', self.source_file)) - + self.svg = svg_file(self.data_file("svg", self.source_file)) + def test_gradient_offset_order(self): - _gradient = self.svg.getElementById('MyGradient') - offsets = [stop.attrib.get('offset') for stop in _gradient.stops] - assert offsets == ["0%", "100%", "50"] \ No newline at end of file + _gradient = self.svg.getElementById("MyGradient") + offsets = [stop.attrib.get("offset") for stop in _gradient.stops] + assert offsets == ["0%", "100%", "50"] diff --git a/tests/test_inkex_elements_selections.py b/tests/test_inkex_elements_selections.py index 78c048b7..b9d3d2c6 100644 --- a/tests/test_inkex_elements_selections.py +++ b/tests/test_inkex_elements_selections.py @@ -13,36 +13,37 @@ from inkex.utils import AbortExtension class ElementListTestCase(SvgTestCase): """Test Element Selections""" + def setUp(self): super().setUp() - self.svg.selection.set('G', 'B', 'D', 'F') + 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')) + 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')) + 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') + 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')) + self.assertEqual(tuple(self.svg.selection.ids), ("G", "B", "D", "F")) def test_rendering_order(self): """Test paint order""" items = self.svg.selection.rendering_order() self.assertTrue(isinstance(items, ElementList)) - self.assertEqual(tuple(items.ids), ('B', 'D', 'F', 'G')) + self.assertEqual(tuple(items.ids), ("B", "D", "F", "G")) def test_set_nothing(self): """Clear existing selection""" @@ -51,52 +52,56 @@ class ElementListTestCase(SvgTestCase): def test_set_ids(self): """Set a new selection element ids""" - a_to_g = 'ABCDEFG' + 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' + 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')) + 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')) + 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',)) + 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')) + 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')) + 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')) + 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')) + 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')) - + self.assertEqual(tuple(new_list.ids), ("path1", "D")) + def test_filternonzero(self): """Filter and raise an AbortException if the list is empty""" selection = self.svg.descendants() @@ -112,9 +117,9 @@ class ElementListTestCase(SvgTestCase): 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')) + 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""" @@ -126,4 +131,4 @@ class ElementListTestCase(SvgTestCase): selection = self.svg.selection self.svg.append(PathElement(id="#asdf")) selection.set("#asdf") - self.assertEqual(tuple(selection.ids), ('#asdf',)) + self.assertEqual(tuple(selection.ids), ("#asdf",)) diff --git a/tests/test_inkex_extensions.py b/tests/test_inkex_extensions.py index 0b061426..56474c9d 100644 --- a/tests/test_inkex_extensions.py +++ b/tests/test_inkex_extensions.py @@ -6,34 +6,41 @@ 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') + return inkex.Color("green") + def modify_opacity(self, name, opacity): - if 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' + 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 + ("--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 = [] @@ -47,13 +54,16 @@ class ColorBaseCase(TestCase): """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__)) + 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)) + 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) + got = self.effect.modify_opacity("opacity", inp) self.assertTrue(isinstance(got, float)) self.assertAlmostEqual(got, outp, delta=0.1) diff --git a/tests/test_inkex_extensions_GenerateExtension.py b/tests/test_inkex_extensions_GenerateExtension.py index eb2d788b..9c125dd4 100644 --- a/tests/test_inkex_extensions_GenerateExtension.py +++ b/tests/test_inkex_extensions_GenerateExtension.py @@ -21,9 +21,8 @@ class TestExtensionGenerate(TestCase): effect_class = MyGenerateExtension def test_layer_transform(self): - effect = self.assertEffect('svg', 'transformed-layer.svg') - item = effect.svg.getElementById( - MyGenerateExtension.TEST_ITEM_ATTRIBS['id']) + effect = self.assertEffect("svg", "transformed-layer.svg") + item = effect.svg.getElementById(MyGenerateExtension.TEST_ITEM_ATTRIBS["id"]) parent = item.getparent() # expect that generated items have not been modified @@ -37,4 +36,6 @@ class TestExtensionGenerate(TestCase): # layer and is positioned at the current view center self.assertTransformEqual( parent.get("transform"), - "scale(0.5) translate(-20,-30) rotate(-30) translate(100,300)", 3) + "scale(0.5) translate(-20,-30) rotate(-30) translate(100,300)", + 3, + ) diff --git a/tests/test_inkex_inx.py b/tests/test_inkex_inx.py index 211b7464..5a55efa2 100644 --- a/tests/test_inkex_inx.py +++ b/tests/test_inkex_inx.py @@ -12,10 +12,13 @@ from inkex.tester import TestCase from inkex.tester.inx import InxMixin from inkex.inx import InxFile + class InxTestCase(InxMixin, TestCase): """Test INX files""" + def test_inx_effect(self): - inx = InxFile(""" + inx = InxFile( + """ TestOne org.inkscape.test.inx_one @@ -30,16 +33,20 @@ class InxTestCase(InxMixin, TestCase): -""") - self.assertEqual(inx.name, 'TestOne') - self.assertEqual(inx.ident, 'org.inkscape.test.inx_one') - self.assertEqual(inx.slug, 'InxOne') - self.assertEqual(inx.metadata, {'type': 'effect', 'preview': False, 'objects': 'all'}) - self.assertEqual(inx.menu, ['Banana', 'Ice Cream', 'TestOne']) +""" + ) + self.assertEqual(inx.name, "TestOne") + self.assertEqual(inx.ident, "org.inkscape.test.inx_one") + self.assertEqual(inx.slug, "InxOne") + self.assertEqual( + inx.metadata, {"type": "effect", "preview": False, "objects": "all"} + ) + self.assertEqual(inx.menu, ["Banana", "Ice Cream", "TestOne"]) self.assertEqual(inx.warnings, []) def test_inx_output(self): - inx = InxFile(""" + inx = InxFile( + """ <_name>TestTwo org.inkscape.test.inx_two @@ -50,22 +57,32 @@ class InxTestCase(InxMixin, TestCase): <_filetypetooltip>The extension extension repention suspension. true -""") - self.assertEqual(inx.name, 'TestTwo') - self.assertEqual(inx.ident, 'org.inkscape.test.inx_two') - self.assertEqual(inx.metadata, { - 'dataloss': True, - 'extension': '.inx', - 'mimetype': 'text/xml+inx', - 'name': 'Extension (*.inx)', - 'tooltip': 'The extension extension repention suspension.', - 'type': 'output'}) - self.assertEqual(inx.warnings, [ - 'Use of old translation scheme: <_filetypetooltip...>', - 'Use of old translation scheme: <_name...>']) +""" + ) + self.assertEqual(inx.name, "TestTwo") + self.assertEqual(inx.ident, "org.inkscape.test.inx_two") + self.assertEqual( + inx.metadata, + { + "dataloss": True, + "extension": ".inx", + "mimetype": "text/xml+inx", + "name": "Extension (*.inx)", + "tooltip": "The extension extension repention suspension.", + "type": "output", + }, + ) + self.assertEqual( + inx.warnings, + [ + "Use of old translation scheme: <_filetypetooltip...>", + "Use of old translation scheme: <_name...>", + ], + ) def test_inx_input(self): - inx = InxFile(""" + inx = InxFile( + """ TestThree org.inkscape.test.inx_three -""") - self.assertEqual(inx.name, 'TestThree') - self.assertEqual(inx.metadata, { - 'extension': '.inx', - 'mimetype': 'text/xml+inx', - 'name': 'Extension (*.inx)', - 'tooltip': 'The extension extension repention suspension.', - 'type': 'input'}) - self.assertEqual(inx.warnings, ['No inx xml prefix.']) +""" + ) + self.assertEqual(inx.name, "TestThree") + self.assertEqual( + inx.metadata, + { + "extension": ".inx", + "mimetype": "text/xml+inx", + "name": "Extension (*.inx)", + "tooltip": "The extension extension repention suspension.", + "type": "input", + }, + ) + self.assertEqual(inx.warnings, ["No inx xml prefix."]) def test_inx_template(self): - inx = InxFile(""" + inx = InxFile( + """ TestFour org.inkscape.test.inx_four @@ -99,16 +122,23 @@ class InxTestCase(InxMixin, TestCase): 2070-01-01 word food strawberry -""") - self.assertEqual(inx.name, 'TestFour') - self.assertEqual(inx.metadata, {'author': 'Donky Oaty', 'desc': 'Something might happen.', 'type': 'template'}) - self.assertEqual(inx.warnings, ['No inx xml prefix.']) - +""" + ) + self.assertEqual(inx.name, "TestFour") + self.assertEqual( + inx.metadata, + { + "author": "Donky Oaty", + "desc": "Something might happen.", + "type": "template", + }, + ) + self.assertEqual(inx.warnings, ["No inx xml prefix."]) 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')): + for inx_file in glob(os.path.join(self._testdir(), "..", "*.inx")): self.assertInxIsGood(inx_file) diff --git a/tests/test_inkex_paths.py b/tests/test_inkex_paths.py index 24769d9d..f8400489 100644 --- a/tests/test_inkex_paths.py +++ b/tests/test_inkex_paths.py @@ -6,9 +6,30 @@ 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 + 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 @@ -21,6 +42,7 @@ class SegmentTest(TestCase): def get_random_cmd(self, Cmd): import random + return Cmd(*[random.randint(0, 10) for i in range(Cmd.nargs)]) def test_equals(self): @@ -36,10 +58,14 @@ class SegmentTest(TestCase): 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( + 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( + 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)), @@ -54,20 +80,51 @@ class SegmentTest(TestCase): 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), + ( + 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)) + 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()) @@ -90,36 +147,84 @@ class SegmentTest(TestCase): 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 + 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)) + 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)) + 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)) + 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 + 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): @@ -146,8 +251,28 @@ class SegmentTest(TestCase): 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 + 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) @@ -155,74 +280,100 @@ class SegmentTest(TestCase): 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) + 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()), '') + 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, ]) + 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') + 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)]") + 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') + 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', + "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(',', ' ')) + 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'), + ("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') + 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),)), + ("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) @@ -235,18 +386,24 @@ class PathTest(TestCase): 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()) + 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') + 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 @@ -261,11 +418,13 @@ class PathTest(TestCase): 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') + 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) @@ -279,38 +438,43 @@ class PathTest(TestCase): 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') + 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') + 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') + self._assertPath(ret, "M 20 20 L 40 40 L 9 10") - ret = Path('M 20 20') + 'C 40 40 9 10 10 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') + 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') + 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 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 = 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') + 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""" @@ -321,8 +485,8 @@ class PathTest(TestCase): # - 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') + 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_scale_multiple_zones(self): """Zone close returns current position to start of zone (not start of path)""" @@ -335,17 +499,23 @@ class PathTest(TestCase): 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") + 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") + 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") + 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""" @@ -356,51 +526,64 @@ class PathTest(TestCase): 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") + 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") + 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") + 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") + 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") + 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], 'L') - self.assertEqual(ret[3][0], 'C') + self.assertEqual(ret[0][0], "M") + self.assertEqual(ret[1][0], "L") + self.assertEqual(ret[2][0], "L") + 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') + 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') + 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)) + 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.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): @@ -417,6 +600,7 @@ class PathTest(TestCase): 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", @@ -434,121 +618,210 @@ class PathTest(TestCase): self.assertEqual(expected, cmds) self.assertAlmostTuple( [t.apply_to_point(p) for p in path.control_points], - list(new_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"), + ( + "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) + 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 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=45)) - .control_points), ((0, sqrt(20 ** 2 + 20 ** 2)), (sqrt(2)/2, sqrt(2)/2))) + self.assertAlmostTuple( + list(Path("M 20 20 1 0").transform(Transform(rotate=90)).control_points), + ((-20, 20), (0, 1)), + ) - 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) )) + 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)), + ) def test_reverse(self): """Paths can be reversed""" """Testing reverse() with relative coordinates, closed path""" - ret = Path("m 10 50 h 40 v -40 l 50 39.9998 c -22 2 -35 12 -50 25 l -40 -15 l 0 -10 z") + ret = Path( + "m 10 50 h 40 v -40 l 50 39.9998 c -22 2 -35 12 -50 25 l -40 -15 l 0 -10 z" + ) ret = ret.reverse() - self._assertPath(ret, "m 10 50 l 0 -0.0002 l -0 10 l 40 15 c 15 -13 28 -23 50 -25 l -50 -39.9998 v 40 h -40 z") + self._assertPath( + ret, + "m 10 50 l 0 -0.0002 l -0 10 l 40 15 c 15 -13 28 -23 50 -25 l -50 -39.9998 v 40 h -40 z", + ) """Testing reverse() with relative coordinates, open path""" - ret = Path("m 10 50 h 40 v -40 l 50 39.9998 c -22 2 -35 12 -50 25 l -40 -15 l 0 -10") + ret = Path( + "m 10 50 h 40 v -40 l 50 39.9998 c -22 2 -35 12 -50 25 l -40 -15 l 0 -10" + ) ret = ret.reverse() - self._assertPath(ret, "m 10 49.9998 l -0 10 l 40 15 c 15 -13 28 -23 50 -25 l -50 -39.9998 v 40 h -40") + self._assertPath( + ret, + "m 10 49.9998 l -0 10 l 40 15 c 15 -13 28 -23 50 -25 l -50 -39.9998 v 40 h -40", + ) """Testing reverse() with absolute coordinates, closed path""" ret = Path("M 100 35 L 100 25 L 60 10 C 45 23 32 33 10 35 L 60 75 L 60 35 Z") ret = ret.reverse() - self._assertPath(ret, "M 100 35 L 60 35 L 60 75 L 10 35 C 32 33 45 23 60 10 L 100 25 L 100 35 Z") + self._assertPath( + ret, + "M 100 35 L 60 35 L 60 75 L 10 35 C 32 33 45 23 60 10 L 100 25 L 100 35 Z", + ) """Testing reverse() with absolute coordinates, open path""" - ret = Path("M 100 35 L 100 25 L 60 10 C 45 23 32 33 10 35 L 60 75 L 60 35 L 100 35") + ret = Path( + "M 100 35 L 100 25 L 60 10 C 45 23 32 33 10 35 L 60 75 L 60 35 L 100 35" + ) ret = ret.reverse() - self._assertPath(ret, "M 100 35 L 60 35 L 60 75 L 10 35 C 32 33 45 23 60 10 L 100 25 L 100 35") + self._assertPath( + ret, + "M 100 35 L 60 35 L 60 75 L 10 35 C 32 33 45 23 60 10 L 100 25 L 100 35", + ) + 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") + 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") + 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)), - ]]) + 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' + "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") + 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][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") + 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", + ) def test_is_line_simplify(self): """Test if super path segments can detect if a segment can be simplified to a line""" path = Path("M 10 10 C 20,20 30,30 40,40 C 100, 100 50, 50 60, 60") csp = path.to_superpath() - self.assertTrue(csp.is_line(csp[0][0], csp[0][1])) # line can be retracted - self.assertFalse(csp.is_line(csp[0][1], csp[0][2])) # is line, but shoots over endpoint + self.assertTrue(csp.is_line(csp[0][0], csp[0][1])) # line can be retracted + self.assertFalse( + csp.is_line(csp[0][1], csp[0][2]) + ) # is line, but shoots over endpoint self.assertEqual(str(csp.to_path()), "M 10 10 L 40 40 C 100 100 50 50 60 60") @@ -574,7 +847,8 @@ class SuperPathTest(TestCase): tempsub = CubicSuperPath(tempsub[0]) self.assertEqual(comparison, str(tempsub)) -class ProxyTest(): + +class ProxyTest: def test_simple_path(self): """Check coordinate computation""" path = Path("M 10 10 h 10 v 10 h -10 Z") diff --git a/tests/test_inkex_styles.py b/tests/test_inkex_styles.py index 926980db..68fdd878 100644 --- a/tests/test_inkex_styles.py +++ b/tests/test_inkex_styles.py @@ -11,21 +11,22 @@ 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') + 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') + 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') + self.assertEqual(str(st2), "border-color:red;border-issues:false") def test_inbuilts(self): """Test inbuild style functions""" @@ -40,79 +41,95 @@ class StyleTest(TestCase): def test_set_property(self): """Set the style attribute directly""" stl = Style() - stl['border-pain'] = 'green' - self.assertEqual(str(stl), 'border-pain:green') + 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') + 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))}) + 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) print(stl3) - self.assertAlmostEqual(stl3('fill-opacity'), 0.5, 1e-3) - assert stl3('fill') == [150, 0, 50] - assert stl3['stroke-width'] == '8px' + self.assertAlmostEqual(stl3("fill-opacity"), 0.5, 1e-3) + assert stl3("fill") == [150, 0, 50] + assert stl3["stroke-width"] == "8px" def test_callback(self): """Test callback.""" calls = 0 + def cb(style): nonlocal calls - self.assertNotIn('fill-opacity', style) + self.assertNotIn("fill-opacity", style) calls += 1 - st = Style({'stroke-width':'0px', 'fill-opacity':1.0,'fill':Color((200, 0, 0))}, callback=cb) + + st = Style( + {"stroke-width": "0px", "fill-opacity": 1.0, "fill": Color((200, 0, 0))}, + callback=cb, + ) self.assertEqual(calls, 0) - st.pop('fill-opacity') + st.pop("fill-opacity") self.assertEqual(calls, 1) + def cb(style): nonlocal calls - self.assertEqual(style['fill-opacity'], '.75') + self.assertEqual(style["fill-opacity"], ".75") calls += 1 + st.callback = cb - st['fill-opacity'] = '.75' + st["fill-opacity"] = ".75" self.assertEqual(calls, 2) 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.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') + 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.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>\n.cls1 {\n fill:blue;\n}\n<', self.svg.tostring()) + self.svg.stylesheet.append(".cls1 { fill: blue; }") + self.assertIn( + b"style>\n.cls1 {\n fill:blue;\n}\n<", self.svg.tostring() + ) def test_parsing(self): """SVG parsing provides access to stylesheets""" @@ -125,73 +142,94 @@ class StyleSheetTest(TestCase): 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 {}') + 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.assertTrue(self.css[0].to_xpath() in \ - ["//*[@id='layer1']", "descendant-or-self::*[@id = 'layer1']"]) + self.assertTrue( + self.css[0].to_xpath() + in ["//*[@id='layer1']", "descendant-or-self::*[@id = 'layer1']"] + ) elem = self.svg.getElement(self.css[0].to_xpath()) - self.assertEqual(elem.get('id'), 'layer1') + self.assertEqual(elem.get("id"), "layer1") def test_lookup_by_element(self): """Element name CSS lookup""" - self.assertTrue(self.css[1].to_xpath() in \ - ["//svg:circle", "descendant-or-self::svg:circle"]) + self.assertTrue( + self.css[1].to_xpath() in ["//svg:circle", "descendant-or-self::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') + 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.assertTrue(self.css[2].to_xpath() in \ - ["//*[contains(concat(' ', normalize-space(@class), ' '), ' two ')]", - "descendant-or-self::*[@class and contains"\ - "(concat(' ', normalize-space(@class), ' '), ' two ')]"]) + self.assertTrue( + self.css[2].to_xpath() + in [ + "//*[contains(concat(' ', normalize-space(@class), ' '), ' two ')]", + "descendant-or-self::*[@class and contains" + "(concat(' ', normalize-space(@class), ' '), ' two ')]", + ] + ) elem = self.svg.getElement(self.css[2].to_xpath()) - self.assertEqual(elem.get('id'), 'rect2') + self.assertEqual(elem.get("id"), "rect2") def test_lookup_and(self): """Multiple CSS lookups""" - self.assertTrue(self.css[3].to_xpath() in ["//*[@id='rect3']"\ - "[contains(concat(' ', normalize-space(@class), ' '), ' three ')]", - "descendant-or-self::*[@id = 'rect3' and "\ - "(@class and contains(concat(' ', normalize-space(@class), ' '), ' three '))]"]) + self.assertTrue( + self.css[3].to_xpath() + in [ + "//*[@id='rect3']" + "[contains(concat(' ', normalize-space(@class), ' '), ' three ')]", + "descendant-or-self::*[@id = 'rect3' and " + "(@class and contains(concat(' ', normalize-space(@class), ' '), ' three '))]", + ] + ) elem = self.svg.getElement(self.css[3].to_xpath()) - self.assertEqual(elem.get('id'), 'rect3') + self.assertEqual(elem.get("id"), "rect3") def test_lookup_or(self): """SVG rules can look up the right elements""" - self.assertTrue(self.css[6].to_xpath() in ["//*[@id='circle1']|//*[@id='circle2']|"\ - "//*[contains(concat(' ', normalize-space(@class), ' '), ' two ')]", - "descendant-or-self::*[@id = 'circle1']|descendant-or-self::*[@id = 'circle2']"\ - "|descendant-or-self::*[@class and contains(concat(' ', "\ - "normalize-space(@class), ' '), ' two ')]"]) + self.assertTrue( + self.css[6].to_xpath() + in [ + "//*[@id='circle1']|//*[@id='circle2']|" + "//*[contains(concat(' ', normalize-space(@class), ' '), ' two ')]", + "descendant-or-self::*[@id = 'circle1']|descendant-or-self::*[@id = 'circle2']" + "|descendant-or-self::*[@class and 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') + 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') + str(self.svg.getElementById("rect1").cascaded_style()), "fill:blue" + ) self.assertEqual( - str(self.svg.getElementById('rect2').cascaded_style()), - 'fill:green;font:Homie') + str(self.svg.getElementById("rect2").cascaded_style()), + "fill:green;font:Homie", + ) self.assertEqual( - str(self.svg.getElementById('rect3').cascaded_style()), - 'fill:cyan') + str(self.svg.getElementById("rect3").cascaded_style()), "fill:cyan" + ) self.assertEqual( - str(self.svg.getElementById('rect4').cascaded_style()), - 'fill:grey;stroke:red') + str(self.svg.getElementById("rect4").cascaded_style()), + "fill:grey;stroke:red", + ) self.assertEqual( - str(self.svg.getElementById('circle1').cascaded_style()), - 'fill:red;font:Homie') + str(self.svg.getElementById("circle1").cascaded_style()), + "fill:red;font:Homie", + ) self.assertEqual( - str(self.svg.getElementById('circle2').cascaded_style()), - 'fill:red;font:Homie') + str(self.svg.getElementById("circle2").cascaded_style()), + "fill:red;font:Homie", + ) diff --git a/tests/test_inkex_styles_complex.py b/tests/test_inkex_styles_complex.py index 7f30b27e..ed673773 100644 --- a/tests/test_inkex_styles_complex.py +++ b/tests/test_inkex_styles_complex.py @@ -26,17 +26,27 @@ from inkex.styles import Style from inkex.colors import Color from inkex.tester import TestCase from inkex.tester.svg import svg_file -from inkex import SvgDocumentElement, BaseElement, \ - ColorError, BaseStyleValue, RadialGradient, Stop, PathElement +from inkex import ( + SvgDocumentElement, + BaseElement, + ColorError, + BaseStyleValue, + RadialGradient, + Stop, + PathElement, +) from inkex import SVG_PARSER + class StyleInheritanceTests(TestCase): - """ Some test cases for css attribute handling """ + """Some test cases for css attribute handling""" + def test_style_sheet_1(self): """File from https://commons.wikimedia.org/wiki/File:Test_only.svg, public domain note that Inkscape fails the same test: https://gitlab.com/inkscape/inbox/-/issues/1929""" doc: SvgDocumentElement = svg_file( - self.data_file('svg', 'style_inheritance.svg')) + self.data_file("svg", "style_inheritance.svg") + ) circles: List[BaseElement] = doc.xpath("//svg:circle") for circle in circles: @@ -53,15 +63,22 @@ class StyleInheritanceTests(TestCase): https://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-styling-css-04-f.html Note that the "good" preview image attached on the site is wrong per the explanation""" doc: SvgDocumentElement = svg_file( - self.data_file('svg', 'styling-css-04-f.svg')) + self.data_file("svg", "styling-css-04-f.svg") + ) rects: List[BaseElement] = doc.xpath("//svg:rect") - results = {"A": "blue", "B": "green", "C": "orange", - "D": "gold", "E": "purple", "F": "red"} + results = { + "A": "blue", + "B": "green", + "C": "orange", + "D": "gold", + "E": "purple", + "F": "red", + } for rect in rects: ident = rect.get_id() - if (len(ident) != 2): + if len(ident) != 2: continue result = results[ident[0]] @@ -75,7 +92,8 @@ class StyleInheritanceTests(TestCase): """ doc: SvgDocumentElement = svg_file( - self.data_file('svg', 'styling-inherit-01-b.svg')) + self.data_file("svg", "styling-inherit-01-b.svg") + ) objects: List[BaseElement] = doc.xpath("//svg:rect|//svg:ellipse") @@ -96,11 +114,10 @@ class StyleInheritanceTests(TestCase): stroke = objects[3].specified_style()("stroke") self.assertEqual(stroke, Color("red")) - def test_marker_style(self): """Check if markers are read and written correctly""" - doc: SvgDocumentElement = svg_file(self.data_file('svg', 'markers.svg')) + doc: SvgDocumentElement = svg_file(self.data_file("svg", "markers.svg")) elem = doc.getElementById("dimension") style = elem.specified_style() marker = style("marker-start") @@ -129,15 +146,13 @@ class StyleInheritanceTests(TestCase): elem.style["marker"] = "" self.assertEqual(elem.style("marker-start"), doc.getElementById("Arrow1Lend")) - def test_get_default(self): - """ Test if the default values are returned for missing attributes """ - doc: SvgDocumentElement = svg_file( - self.data_file('svg', 'interp_shapes.svg')) + """Test if the default values are returned for missing attributes""" + doc: SvgDocumentElement = svg_file(self.data_file("svg", "interp_shapes.svg")) elem = doc.getElementById("path6") - assert(elem.style("stroke-dashoffset") == "0") - assert(elem.style("font") == "") + assert elem.style("stroke-dashoffset") == "0" + assert elem.style("font") == "" def parse_style_and_compare(self, tests: List[Tuple[str, dict]]): """Parses a style and compares the output to a dictionary of attributes""" @@ -149,41 +164,73 @@ class StyleInheritanceTests(TestCase): def test_font_shorthand(self): """Test whether shorthand properties are applied correctly""" tests: List[Tuple[str, dict]] = [ - ('font: ', {"font-size": "medium"}), - (r'font: 12px/14px sans-serif', - {"font-size": "12px", "line-height": "14px", "font-family": "sans-serif"}), - (r'font: 80% sans-serif', - {"font-size": "80%", "font-family": "sans-serif"}), - (r'font: x-large/110% "New Century Schoolbook", serif', - {"font-size": "x-large", "line-height": "110%", - "font-family": '"New Century Schoolbook", serif'}), - (r'font: semi-condensed bold italic large Palatino, serif', - {"font-weight": "bold", "font-style": "italic", "font-size": "large", - "font-family": "Palatino, serif", "font-stretch": "semi-condensed"}), - (r'font: normal small-caps 120%/120% fantasy', - {"font-weight": "normal", "font-style": "normal", "font-variant": "small-caps", - "font-size": "120%", "line-height": "120%", "font-family": "fantasy"})] + ("font: ", {"font-size": "medium"}), + ( + r"font: 12px/14px sans-serif", + { + "font-size": "12px", + "line-height": "14px", + "font-family": "sans-serif", + }, + ), + ( + r"font: 80% sans-serif", + {"font-size": "80%", "font-family": "sans-serif"}, + ), + ( + r'font: x-large/110% "New Century Schoolbook", serif', + { + "font-size": "x-large", + "line-height": "110%", + "font-family": '"New Century Schoolbook", serif', + }, + ), + ( + r"font: semi-condensed bold italic large Palatino, serif", + { + "font-weight": "bold", + "font-style": "italic", + "font-size": "large", + "font-family": "Palatino, serif", + "font-stretch": "semi-condensed", + }, + ), + ( + r"font: normal small-caps 120%/120% fantasy", + { + "font-weight": "normal", + "font-style": "normal", + "font-variant": "small-caps", + "font-size": "120%", + "line-height": "120%", + "font-family": "fantasy", + }, + ), + ] self.parse_style_and_compare(tests) - def test_shorthand_overwrites(self): """Test whether shorthands correctly follow precedence: only overwrite rules which are defined before and not important""" tests: List[Tuple[str, dict]] = [ - ("""font-size: large; + ( + """font-size: large; font-family: Verdana !important; font: bold 12px/14px sans-serif; font-weight: normal;""", - {"font-size": "12px", "font-family": "Verdana", "line-height": "14px", - "font-weight": "normal"})] + { + "font-size": "12px", + "font-family": "Verdana", + "line-height": "14px", + "font-weight": "normal", + }, + ) + ] self.parse_style_and_compare(tests) - - def test_gradient_parsing(self): """Test if the style correctly outputs Gradient objects""" - doc: SvgDocumentElement = svg_file( - self.data_file('svg', 'interp_shapes.svg')) + doc: SvgDocumentElement = svg_file(self.data_file("svg", "interp_shapes.svg")) elem = doc.getElementById("path6") style = elem.style grad = style("stroke") @@ -191,21 +238,23 @@ class StyleInheritanceTests(TestCase): def test_attribute_set(self): """Tests if we can set attributes with parsed values""" - doc: SvgDocumentElement = svg_file( - self.data_file('svg', 'interp_shapes.svg')) + doc: SvgDocumentElement = svg_file(self.data_file("svg", "interp_shapes.svg")) elem = doc.getElementById("path6") style = elem.style tests = [ - ("stroke", doc.getElementById( - "linearGradient847"), "url(#linearGradient847)"), + ( + "stroke", + doc.getElementById("linearGradient847"), + "url(#linearGradient847)", + ), ("fill", Color("red"), "red"), ("stroke", None, "none"), ("opacity", 0.5, "0.5"), ("opacity", 1.2, "1"), ("opacity", -2, "0"), - ("font-variant", "small-caps", "small-caps") + ("font-variant", "small-caps", "small-caps"), ] for attr, value, result in tests: style[attr] = value @@ -227,32 +276,37 @@ class StyleInheritanceTests(TestCase): def test_style_parsing_error(self): """Test if bad attribute data raises an exception during parsing""" - doc: SvgDocumentElement = svg_file( - self.data_file('svg', 'interp_shapes.svg')) + doc: SvgDocumentElement = svg_file(self.data_file("svg", "interp_shapes.svg")) tests: List[Tuple[str, Exception]] = [ - (r'opacity: abc', ValueError), - (r'fill: #GHI', ColorError), - (r'stroke: url(#missing)', ValueError), - (r'fill: ', ColorError), - (r"font-variant: blue", ValueError)] + (r"opacity: abc", ValueError), + (r"fill: #GHI", ColorError), + (r"stroke: url(#missing)", ValueError), + (r"fill: ", ColorError), + (r"font-variant: blue", ValueError), + ] for decl, exceptiontype in tests: with self.assertRaises(exceptiontype): value = BaseStyleValue.factory(declaration=decl) _ = value.parse_value(doc) - self.assertEqual(BaseStyleValue.factory_errorhandled( - element=doc, declaration=decl), None) + self.assertEqual( + BaseStyleValue.factory_errorhandled(element=doc, declaration=decl), None + ) def test_attribute_set_invalid(self): """Test if bad attribute data raises an exception when setting it on a style""" - doc: SvgDocumentElement = svg_file( - self.data_file('svg', 'interp_shapes.svg')) + doc: SvgDocumentElement = svg_file(self.data_file("svg", "interp_shapes.svg")) elem = doc.getElementById("path6") tests = [ - ("fill", "nocolor", 'Unknown color format'), + ("fill", "nocolor", "Unknown color format"), ("opacity", Style(), "Value must be number"), - ("font-variant", "red", "Value 'red' is invalid for the property font-variant"), - ("stroke", "url(#missing)", "Paint server not found")] + ( + "font-variant", + "red", + "Value 'red' is invalid for the property font-variant", + ), + ("stroke", "url(#missing)", "Paint server not found"), + ] style = elem.style for attr, value, errormsg in tests: with self.assertRaisesRegex(Exception, errormsg): @@ -260,9 +314,8 @@ class StyleInheritanceTests(TestCase): def test_gradient_id_fallback(self): """Test if the gradient fallback (color after nonexistent url) works""" - doc: SvgDocumentElement = svg_file( - self.data_file('svg', 'interp_shapes.svg')) - sty = Style(element = doc) + doc: SvgDocumentElement = svg_file(self.data_file("svg", "interp_shapes.svg")) + sty = Style(element=doc) sty["stroke"] = "url(#nonexistent) red" self.assertEqual(sty("stroke"), Color("red")) @@ -315,10 +368,9 @@ class StyleInheritanceTests(TestCase): # set the importance on a value that doesn't exist with self.assertRaises(KeyError): style.set_importance("stroke", True) - + def test_style_exchange(self): - doc: SvgDocumentElement = svg_file( - self.data_file('svg', 'interp_shapes.svg')) + doc: SvgDocumentElement = svg_file(self.data_file("svg", "interp_shapes.svg")) elem = doc.getElementById("path6") style = elem.style @@ -334,8 +386,8 @@ class StyleInheritanceTests(TestCase): self.assertEqual(elem.style("new-attribute"), "test") # callback is set after accessing the element self.assertIsNotNone(elem.style.callback) - #copystyle["new-attribute2"] = "test" - #self.assertEqual(elem.style("new-attribute2"), "test") + # copystyle["new-attribute2"] = "test" + # self.assertEqual(elem.style("new-attribute2"), "test") def test_stop_opacity_inheritance(self): # subtest of pservers-grad-18b SVG1.1 unit test @@ -349,8 +401,12 @@ class StyleInheritanceTests(TestCase): """ doc = etree.fromstring(content, parser=SVG_PARSER) grad = doc.getElementById("MyGradient1") - self.assertEqual(grad[0].specified_style()("stop-opacity"), 1) # assert that stop opacity is overwritten - self.assertEqual(grad[1].specified_style()("stop-opacity"), 1) # assert that stop opacity is not inherited by default + self.assertEqual( + grad[0].specified_style()("stop-opacity"), 1 + ) # assert that stop opacity is overwritten + self.assertEqual( + grad[1].specified_style()("stop-opacity"), 1 + ) # assert that stop opacity is not inherited by default def test_inheritance_second_attribute(self): """Check that the second attribute is also correctly inherited""" @@ -380,6 +436,7 @@ class StyleInheritanceTests(TestCase): doc = etree.fromstring(content, parser=SVG_PARSER) ellipse = doc.getElementById("test") self.assertEqual(ellipse.specified_style()("fill"), Color("red")) + def test_dasharray(self): """test parsing of dasharray""" elem = PathElement() @@ -393,7 +450,7 @@ class StyleInheritanceTests(TestCase): ("", None), ("1 -2", None), (None, None), - ([1, 2, 3], [1, 2, 3, 1, 2, 3]) + ([1, 2, 3], [1, 2, 3, 1, 2, 3]), ] for value, result in tests: style["stroke-dasharray"] = value @@ -401,4 +458,6 @@ class StyleInheritanceTests(TestCase): if result is None: self.assertEqual(result, setvalue, f"got {setvalue}, original: {value}") else: - self.assertAlmostTuple(result, setvalue, msg=f"Expected {result}, got {setvalue}") \ No newline at end of file + self.assertAlmostTuple( + result, setvalue, msg=f"Expected {result}, got {setvalue}" + ) diff --git a/tests/test_inkex_svg.py b/tests/test_inkex_svg.py index fe02c445..238612f6 100644 --- a/tests/test_inkex_svg.py +++ b/tests/test_inkex_svg.py @@ -26,30 +26,35 @@ from inkex.tester import TestCase from inkex.tester.svg import svg, svg_file, svg_unit_scaled from inkex import addNS + 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') + 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') + 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'}) + 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: + 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) @@ -58,48 +63,51 @@ class BasicSvgTest(TestCase): 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) + 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) + 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']) + 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'), []) + 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'), []) + 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') + 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 @@ -109,34 +117,33 @@ class BasicSvgTest(TestCase): 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') + 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') + 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_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')) + 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')) + 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) @@ -150,20 +157,21 @@ class BasicSvgTest(TestCase): 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')) + 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') + (guide,) = namedview.get_guides() + self.assertEqual(guide.get("position"), "50,50") + self.assertEqual(guide.get("orientation"), "0.707107,-0.707107") class GetDocumentWidthTest(TestCase): @@ -174,6 +182,7 @@ class GetDocumentWidthTest(TestCase): doc = svg(creation_string) self.assertAlmostEqual(doc.viewbox_width, viewbox_width) self.assertAlmostEqual(doc.viewport_width, viewport_width) + def test_no_dimensions(self): """An empty width value should be default zero width""" self.assert_svg_sizes("", 0, 0) @@ -262,55 +271,55 @@ class GetDocumentUnitTest(TestCase): def test_no_dimensions(self): """Default units with no arguments""" - self.assertEqual(svg().unit, 'px') + self.assertEqual(svg().unit, "px") def test_width_only(self): - """"Units from document width only""" + """ "Units from document width only""" # TODO: Determine whether returning 'px' in this case is the # intended behavior. - self.assertEqual(svg('width="100m"').unit, 'px') + 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') + 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') + 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') + 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') + 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') + 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') + 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') + 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') + 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. @@ -323,210 +332,221 @@ class GetDocumentUnitTest(TestCase): 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') + 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') + 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') + 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') + 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 + 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 = svg_unit_scaled(user_unit) self.assertEqual(doc.unit, user_unit, msg=svg) self.assertAlmostEqual(doc.to_dimensionless(test_value), expected) - def assertToDocumentUnit(self, user_unit, test_value, expected): # pylint: disable=invalid-name + def assertToDocumentUnit( + self, user_unit, test_value, expected + ): # pylint: disable=invalid-name """Checks a user unit and a test_value against the expected result""" doc = svg_unit_scaled(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 + def assertFromUserUnit( + self, user_unit, value, unit, expected + ): # pylint: disable=invalid-name """Check converting from a user unity for the test_value""" - self.assertAlmostEqual(svg_unit_scaled(user_unit).to_dimensional(value, unit), expected) + self.assertAlmostEqual( + svg_unit_scaled(user_unit).to_dimensional(value, unit), expected + ) - def assertFromDocumentUnit(self, user_unit, value, unit, expected): # pylint: disable=invalid-name + def assertFromDocumentUnit( + self, user_unit, value, unit, expected + ): # pylint: disable=invalid-name """Check converting from a user unity for the test_value""" - self.assertAlmostEqual(svg_unit_scaled(user_unit).uutounit(value, unit), expected) + self.assertAlmostEqual( + svg_unit_scaled(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.assertToDocumentUnit('cm', '1in', 2.54) + self.assertToDocumentUnit("cm", "1in", 2.54) def test_unittouu_yd_to_m(self): """1yd is ~0.9144m""" - self.assertToDocumentUnit('m', '1yd', 0.9144) + self.assertToDocumentUnit("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.assertToDocumentUnit('pc', '9.87654321pc', 9.87654321) + values should exactly be the same, too.""" + self.assertToDocumentUnit("pc", "9.87654321pc", 9.87654321) def test_unittouu_bad_input_number(self): """Bad input number""" - self.assertToDocumentUnit('cm', '1in', 2.54) + self.assertToDocumentUnit("cm", "1in", 2.54) # Corrupt the input to contain an invalid number component; note that # the result changes to zero. - self.assertToDocumentUnit('cm', 'ABCDin', 0) + self.assertToDocumentUnit("cm", "ABCDin", 0) def test_unittouu_bad_input_unit(self): """Bad input unit""" # Demonstrate that 1.0in passes through without change. - self.assertToDocumentUnit('in', '1.0in', 1.0) + self.assertToDocumentUnit("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.assertToDocumentUnit('in', '1.0ABCD', 0) + self.assertToDocumentUnit("in", "1.0ABCD", 0) def test_to_dimensionless_in_to_cm(self): """1in is 96px in a cm based document""" - self.assertToUserUnit('cm', '1in', 96.0) + self.assertToUserUnit("cm", "1in", 96.0) def test_to_dimensionless_yd_to_m(self): """1yd is 3456px""" - self.assertToUserUnit('m', '1yd', 3456.0) + self.assertToUserUnit("m", "1yd", 3456.0) def test_to_dimensionless_no_unit(self): """If no unit is given, the value must not be changed in mm based documents.""" - self.assertToUserUnit('mm', '9.87654321', 9.87654321) + self.assertToUserUnit("mm", "9.87654321", 9.87654321) def test_to_dimensionless_identity(self): """User units are px. If a value is given in px, the value must not change""" - self.assertToUserUnit('px', '9.87654321px', 9.87654321) + self.assertToUserUnit("px", "9.87654321px", 9.87654321) def test_to_dimensionless_unitless_input(self): """Passing a unitless value to unittouu() should treat the units as 'px'.""" - self.assertToUserUnit('in', '96', 96) # user unit = px + self.assertToUserUnit("in", "96", 96) # user unit = px def test_to_dimensionless_empty_input(self): """Passing an empty string to unittouu() should treat the value as zero.""" - self.assertToUserUnit('in', '', 0) + self.assertToUserUnit("in", "", 0) def test_to_dimensionless_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", + "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) + self.assertToUserUnit("px", value, 1600) def test_to_dimensionless_bad_input_number(self): """Bad input number""" - self.assertToUserUnit('cm', '1in', 96.0) + self.assertToUserUnit("cm", "1in", 96.0) # Demonstrate that 1in is ~96px, also in a "cm based" document. # Corrupt the input to contain an invalid number component; note that # the result changes to zero. - self.assertToUserUnit('cm', 'ABCDin', 0) + self.assertToUserUnit("cm", "ABCDin", 0) def test_to_dimensionless_bad_input_unit(self): """Bad input unit""" # Demonstrate that 1.0px passes through without change. - self.assertToUserUnit('mm', '1.0px', 1.0) + self.assertToUserUnit("mm", "1.0px", 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 to_dimensionless() treats unknown units as 'px'. - self.assertToUserUnit('mm', '1.0ABCD', 0) + self.assertToUserUnit("mm", "1.0ABCD", 0) # Unit-ratio tests. Don't exhaustively test every unit conversion, just # demonstrate that the logic works. def test_to_dimensional_cm_to_in(self): """Convert 1 user unit (px) to 'cm' in a in-based document""" - self.assertFromUserUnit('in', 1, 'cm', 2.54/96) # 1in is ~2.54cm + self.assertFromUserUnit("in", 1, "cm", 2.54 / 96) # 1in is ~2.54cm def test_to_dimensional_m_to_yd(self): """Convert 1 user unit (px) to 'm' in a yd-based document""" - self.assertFromUserUnit('yd', 1, 'm', 1/100*2.54/96) + self.assertFromUserUnit("yd", 1, "m", 1 / 100 * 2.54 / 96) def test_to_dimensional_identity(self): """If the input unit is px, output value should be identical""" - self.assertFromUserUnit('pc', 9.87654321, 'px', 9.87654321) + self.assertFromUserUnit("pc", 9.87654321, "px", 9.87654321) def test_to_dimensional_unknown_unit(self): """Demonstrate that passing an unknown unit string to uutounit()""" - self.assertEqual(svg_unit_scaled('in').to_dimensional(1, 'px'), 1) + self.assertEqual(svg_unit_scaled("in").to_dimensional(1, "px"), 1) def test_uutounit_cm_to_in(self): """Convert 1 user unit ('in') to 'cm'.""" - self.assertFromDocumentUnit('in', 1, 'cm', 2.54) # 1in is ~2.54cm + self.assertFromDocumentUnit("in", 1, "cm", 2.54) # 1in is ~2.54cm def test_uutounit_m_to_yd(self): """Convert 1 user unit ('yd') to 'm'.""" - self.assertFromDocumentUnit('yd', 1, 'm', 0.9144) # 1yd is ~0.9144m + self.assertFromDocumentUnit("yd", 1, "m", 0.9144) # 1yd is ~0.9144m def test_uutounit_unknown_unit(self): """Demonstrate that passing an unknown unit string to uutounit()""" - self.assertEqual(svg_unit_scaled('in').uutounit(1, 'px'), 96.0) - + self.assertEqual(svg_unit_scaled("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 = svg_unit_scaled('pt') + doc = svg_unit_scaled("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'), + (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 = svg_unit_scaled('pt') + doc = svg_unit_scaled("pt") inputs = ( - '', - 'ABCD', - '.', - ' ', + "", + "ABCD", + ".", + " ", ) for value in inputs: - self.assertEqual(doc.add_unit(value), '') + self.assertEqual(doc.add_unit(value), "") def test_scales(self): svg1 = svg('width="793.70081" height="1122.5197" viewBox="0 0 105 148.5"') @@ -541,29 +561,46 @@ class UserUnitTest(TestCase): self.assertEqual(svg1.scale, 1) self.assertEqual(svg1.inkscape_scale, 1) + class ViewportUnitTestCase(TestCase): - def assertFromVPUnit(self, width_unit, test_value, unit, expected): # pylint: disable=invalid-name + def assertFromVPUnit( + self, width_unit, test_value, unit, expected + ): # pylint: disable=invalid-name """Checks a viewport unit and a test_value against the expected result""" doc = svg_unit_scaled(width_unit) self.assertEqual(doc.unit, width_unit, msg=svg) self.assertAlmostEqual(doc.viewport_to_unit(test_value, unit), expected) - def assertToVPUnit(self, user_unit, value, unit, expected): # pylint: disable=invalid-name + def assertToVPUnit( + self, user_unit, value, unit, expected + ): # pylint: disable=invalid-name """Check converting from a user unity for the test_value""" - self.assertAlmostEqual(svg_unit_scaled(user_unit).unit_to_viewport(value, unit), expected) + self.assertAlmostEqual( + svg_unit_scaled(user_unit).unit_to_viewport(value, unit), expected + ) def test_unittovp(self): """1in is ~2.54cm""" - self.assertToVPUnit('px', '1in', "px", 96) - self.assertToVPUnit('px', '1', "px", 1) + self.assertToVPUnit("px", "1in", "px", 96) + self.assertToVPUnit("px", "1", "px", 1) # 1 in = 96 px = 96 * 96 px/in / 2.54 cm/in on the viewport - self.assertToVPUnit('cm', '1in', "px", 96 * 96 / 2.54,) - self.assertToVPUnit('cm', '1in', "in", 96 / 2.54, ) - self.assertToVPUnit('mm', '4', "mm", 4) - self.assertToVPUnit('mm', '4', "px", 4 * 96 / 25.4) + self.assertToVPUnit( + "cm", + "1in", + "px", + 96 * 96 / 2.54, + ) + self.assertToVPUnit( + "cm", + "1in", + "in", + 96 / 2.54, + ) + self.assertToVPUnit("mm", "4", "mm", 4) + self.assertToVPUnit("mm", "4", "px", 4 * 96 / 25.4) def test_vptounit(self): - self.assertFromVPUnit('mm', "1m", "px", 1000) - self.assertFromVPUnit('mm', "4mm", 'px', 4) - self.assertFromVPUnit('mm', "4mm", 'mm', 4 * 25.4/96) + self.assertFromVPUnit("mm", "1m", "px", 1000) + self.assertFromVPUnit("mm", "4mm", "px", 4) + self.assertFromVPUnit("mm", "4mm", "mm", 4 * 25.4 / 96) diff --git a/tests/test_inkex_tester.py b/tests/test_inkex_tester.py index 2ebe7302..911d68df 100644 --- a/tests/test_inkex_tester.py +++ b/tests/test_inkex_tester.py @@ -5,24 +5,26 @@ 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: + 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_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), '9 xml differences') - #self.assertEqual(str(xml), '') + self.assertEqual(str(delta), "9 xml differences") + # self.assertEqual(str(xml), '') xml, delta = xmldiff(xml_a, xml_a) self.assertTrue(delta) - self.assertEqual(str(delta), 'No differences detected') + self.assertEqual(str(delta), "No differences detected") diff --git a/tests/test_inkex_transforms.py b/tests/test_inkex_transforms.py index b7ea99f9..fa3dd368 100644 --- a/tests/test_inkex_transforms.py +++ b/tests/test_inkex_transforms.py @@ -4,14 +4,21 @@ Test Inkex transformational logic. """ from math import sqrt, pi from inkex.transforms import ( - Vector2d, ImmutableVector2d, BoundingBox, BoundingInterval, Transform, DirectedLineSegment + 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) @@ -30,7 +37,7 @@ class ImmutableVector2dTest(TestCase): self.assertEqual(vec3.x, 15) self.assertEqual(vec3.y, 22) - vec4 = ImmutableVector2d('-5,8') + vec4 = ImmutableVector2d("-5,8") self.assertEqual(vec4.x, -5) self.assertEqual(vec4.y, 8) @@ -68,8 +75,8 @@ class ImmutableVector2dTest(TestCase): 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 + 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))) @@ -117,8 +124,10 @@ class ImmutableVector2dTest(TestCase): self.assertAlmostEqual(vec1.cross(vec6), -2.0) self.assertAlmostEqual(vec6.cross(vec1), 2.0) + class Vector2dTest(TestCase): """Test the Vector2d object""" + def test_vector_creation(self): """Test Vector2D creation""" vec0 = Vector2d(15, 22) @@ -167,8 +176,8 @@ class Vector2dTest(TestCase): 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 / 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)) @@ -207,35 +216,39 @@ class Vector2dTest(TestCase): def test_polar_operations(self): """Test polar coordinates operations""" # x y r pi - equivilents = [(0, 0, 0, 0), - (0, 0, 0, 1), - (0, 0, 0, -1), - (0, 0, 0, 0.5), - (1, 0, 1, 0), - (0, 1, 1, 0.5), - (0, -1, 1, -0.5), - (3, 0, 3, 0), - (0, 3, 3, 0.5), - (0, -3, 3, -0.5), - (sqrt(2), sqrt(2), 2, 0.25), - (-sqrt(2), sqrt(2), 2, 0.75), - (sqrt(2), -sqrt(2), 2, -0.25), - (-sqrt(2), -sqrt(2), 2, -0.75)] + equivilents = [ + (0, 0, 0, 0), + (0, 0, 0, 1), + (0, 0, 0, -1), + (0, 0, 0, 0.5), + (1, 0, 1, 0), + (0, 1, 1, 0.5), + (0, -1, 1, -0.5), + (3, 0, 3, 0), + (0, 3, 3, 0.5), + (0, -3, 3, -0.5), + (sqrt(2), sqrt(2), 2, 0.25), + (-sqrt(2), sqrt(2), 2, 0.75), + (sqrt(2), -sqrt(2), 2, -0.25), + (-sqrt(2), -sqrt(2), 2, -0.75), + ] for x, y, r, t in equivilents: theta = t * pi if r != 0 else None for ts in [0, 2, -2]: - ctx_msg = 'Test values are x: {} y: {} r: {} θ: {} * pi'.format(x, y, r, t + ts) + ctx_msg = "Test values are x: {} y: {} r: {} θ: {} * pi".format( + x, y, r, t + ts + ) polar = Vector2d.from_polar(r, (t + ts) * pi) cart = Vector2d(x, y) - self.assertEqual(cart.length, r, msg = ctx_msg) - self.assertEqual(polar.length, r, msg = ctx_msg) - self.assertAlmostEqual(cart.angle, theta, msg = ctx_msg, delta = 1e-12) - self.assertAlmostEqual(polar.angle, theta, msg = ctx_msg, delta = 1e-12) - self.assertEqual(cart.to_polar_tuple(), (r, cart.angle), msg = ctx_msg) - self.assertEqual(polar.to_polar_tuple(), (r, polar.angle), msg = ctx_msg) - self.assertEqual(cart.to_tuple(), (x, y), msg = ctx_msg) - self.assertAlmostEqual(polar.to_tuple()[0], x, msg = ctx_msg, delta = 1e-12) - self.assertAlmostEqual(polar.to_tuple()[1], y, msg = ctx_msg, delta = 1e-12) + self.assertEqual(cart.length, r, msg=ctx_msg) + self.assertEqual(polar.length, r, msg=ctx_msg) + self.assertAlmostEqual(cart.angle, theta, msg=ctx_msg, delta=1e-12) + self.assertAlmostEqual(polar.angle, theta, msg=ctx_msg, delta=1e-12) + self.assertEqual(cart.to_polar_tuple(), (r, cart.angle), msg=ctx_msg) + self.assertEqual(polar.to_polar_tuple(), (r, polar.angle), msg=ctx_msg) + self.assertEqual(cart.to_tuple(), (x, y), msg=ctx_msg) + self.assertAlmostEqual(polar.to_tuple()[0], x, msg=ctx_msg, delta=1e-12) + self.assertAlmostEqual(polar.to_tuple()[1], y, msg=ctx_msg, delta=1e-12) # Test special handling of from_polar with None theta self.assertEqual(Vector2d.from_polar(0, None).to_tuple(), (0.0, 0.0)) self.assertIsNone(Vector2d.from_polar(4, None)) @@ -258,32 +271,34 @@ class TransformTest(TestCase): 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))) + 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))) + 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))) + 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)') + 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)') + 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))) + self.assertEqual(Transform("boo(4)"), ((1, 0, 0), (0, 1, 0))) def test_invalid_creation_matrix(self): """Test creating invalid transforms""" @@ -293,23 +308,27 @@ class TransformTest(TestCase): def test_repr(self): """Test repr string""" - self.assertEqual(repr(Transform()), 'Transform(((1, 0, 0), (0, 1, 0)))') + 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)')) + 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)') + trans = Transform("translate(10, 10)") self.assertEqual(trans.apply_to_point((10, 10)).to_tuple(), (20, 20)) - self.assertRaises(ValueError, trans.apply_to_point, '') + 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)") + self.assertEqual( + str(Transform(translate=(10.6, 99.9))), "translate(10.6, 99.9)" + ) def test_scale(self): """Test making scale specific items""" @@ -318,20 +337,36 @@ class TransformTest(TestCase): 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)") + 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): """Test add_TRANSFORM syntax for quickly composing known transforms""" tr1 = Transform() tr1.add_scale(5.0, 1.0) - self.assertEqual(str(tr1), 'scale(5, 1)') + self.assertEqual(str(tr1), "scale(5, 1)") tr1.add_translate(10, 10) - self.assertEqual(str(tr1), 'matrix(5 0 0 1 50 10)') - self.assertEqual(str(Transform().add_scale(5.0, 1.0)), 'scale(5, 1)') - self.assertEqual(str(Transform().add_scale(5.0, 1.0).add_translate(10, 10)), 'matrix(5 0 0 1 50 10)') + self.assertEqual(str(tr1), "matrix(5 0 0 1 50 10)") + self.assertEqual(str(Transform().add_scale(5.0, 1.0)), "scale(5, 1)") + self.assertEqual( + str(Transform().add_scale(5.0, 1.0).add_translate(10, 10)), + "matrix(5 0 0 1 50 10)", + ) tr2 = Transform() - self.assertTrue(tr2.add_scale(1, 1).add_translate(0, 0).add_skewy(0).add_skewx(0).add_rotate(0) is tr2) - self.assertEqual(str(tr2.add_kwargs(translate=(10, 10), scale=(5.0, 1.0))), 'matrix(5 0 0 1 50 10)') + self.assertTrue( + tr2.add_scale(1, 1) + .add_translate(0, 0) + .add_skewy(0) + .add_skewx(0) + .add_rotate(0) + is tr2 + ) + self.assertEqual( + str(tr2.add_kwargs(translate=(10, 10), scale=(5.0, 1.0))), + "matrix(5 0 0 1 50 10)", + ) def test_is_unity(self): """Test that unix matrix looks like rotate, scale, and translate""" @@ -347,12 +382,12 @@ class TransformTest(TestCase): 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(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(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)) @@ -379,12 +414,12 @@ class TransformTest(TestCase): 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(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(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)) @@ -435,9 +470,16 @@ class TransformTest(TestCase): 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) + 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() @@ -453,10 +495,13 @@ class TransformTest(TestCase): 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)') + 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) @@ -477,8 +522,7 @@ class TransformTest(TestCase): 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') - + assert all(getattr(val, a) == pytest.approx(0.5, 1e-3) for a in "abcdef") class ScaleTest(TestCase): @@ -491,7 +535,7 @@ class ScaleTest(TestCase): 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)') + self.assertEqual(repr(BoundingInterval((5, 10))), "BoundingInterval(5, 10)") def test_center(self): """Center of a scale""" @@ -516,13 +560,15 @@ class ScaleTest(TestCase): 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( + 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') + self.assertRaises(ValueError, BoundingInterval, "foo") class BoundingBoxTest(TestCase): @@ -534,24 +580,34 @@ class BoundingBoxTest(TestCase): 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))') + 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(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))) + self.assertEqual( + tuple(-BoundingBox((-10, 15), (2, 10))), ((-15, 10), (-10, -2)) + ) def test_bbox_scale(self): """Bounding Boxes can be scaled""" @@ -563,44 +619,54 @@ class BoundingBoxTest(TestCase): 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]) + 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]) + 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]) + 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') + 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) + self.assertEqual(int(bbox.get_anchor("m", "m", "ro", selbox)), 130) + class SegmentTest(TestCase): """Test special Segments""" @@ -608,14 +674,17 @@ class SegmentTest(TestCase): 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))') + 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) - self.assertAlmostEqual(DirectedLineSegment((0, 0), (0.5 * sqrt(3), 0.5)).angle, - pi/6, delta=1e-6) + self.assertAlmostEqual( + DirectedLineSegment((0, 0), (0.5 * sqrt(3), 0.5)).angle, pi / 6, delta=1e-6 + ) def test_segment_dx(self): """Test segment dx calculation""" @@ -638,7 +707,9 @@ class SegmentTest(TestCase): def test_segment_vector(self): """Test segment delta vector""" self.assertEqual(DirectedLineSegment((0, 0), (2, 3)).vector.to_tuple(), (2, 3)) - self.assertEqual(DirectedLineSegment((-2, -3), (2, 3)).vector.to_tuple(), (4, 6)) + self.assertEqual( + DirectedLineSegment((-2, -3), (2, 3)).vector.to_tuple(), (4, 6) + ) def test_segment_length(self): """Test segment length calculation""" @@ -669,19 +740,26 @@ class SegmentTest(TestCase): self.assertEqual(DirectedLineSegment((0, 0), (-1, -1)).angle, -3 * pi / 4) self.assertEqual(DirectedLineSegment((0, 0), (1, -1)).angle, -pi / 4) + 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 + + 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) @@ -689,6 +767,7 @@ class ExtremaTest(TestCase): 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) diff --git a/tests/test_inkex_tween.py b/tests/test_inkex_tween.py index 6030e3e2..f1eabf11 100644 --- a/tests/test_inkex_tween.py +++ b/tests/test_inkex_tween.py @@ -27,19 +27,18 @@ import numpy as np class TweenTest(TestCase): """Unit tests for the Inkscape inkex tween library""" - black = inkex.Color('#000000') - grey50 = inkex.Color('#080808') - white = inkex.Color('#111111') + + 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) + val = tween.interppoints((0, 0), (1, 1), 0.5) assert val == pytest.approx((0.5, 0.5), (1e-3, 1e-3)) - - def initialize_gradient(self, lg, stoparray): for key, value in stoparray.items(): @@ -48,6 +47,7 @@ class TweenTest(TestCase): stop.style["stop-color"] = value stop.offset = key lg.add(stop) + def initialize_linear_gradient(self, bounding_box, stoparray): lg = inkex.LinearGradient() self.initialize_gradient(lg, stoparray) @@ -56,7 +56,7 @@ class TweenTest(TestCase): lg.set("y1", bounding_box.center.y) lg.set("y2", bounding_box.center.y) return lg - + def initialize_radial_gradient(self, bounding_box, stoparray): grad = inkex.RadialGradient() self.initialize_gradient(grad, stoparray) @@ -64,38 +64,104 @@ class TweenTest(TestCase): grad.set("cy", bounding_box.center.y) grad.set("fx", bounding_box.center.x) grad.set("fy", bounding_box.center.y) - grad.set("r", bounding_box.right- bounding_box.center.x) + grad.set("r", bounding_box.right - bounding_box.center.x) return grad - + def test_fill_interpolator(self): svg = inkex.SvgDocumentElement() - p1 = inkex.PathElement(d="M 5.23564,33.586285 46.410969,-0.94834 89.873815,35.045501 Z") - p2 = inkex.PathElement(d="m 136.32372,31.390088 c 0,0 -65.213764,-4.27631 18.17433,-22.4506304 83.38809,-18.17434 64.14469,35.2795704 64.14469,35.2795704 z") + p1 = inkex.PathElement( + d="M 5.23564,33.586285 46.410969,-0.94834 89.873815,35.045501 Z" + ) + p2 = inkex.PathElement( + d="m 136.32372,31.390088 c 0,0 -65.213764,-4.27631 18.17433,-22.4506304 83.38809,-18.17434 64.14469,35.2795704 64.14469,35.2795704 z" + ) p3 = inkex.Rectangle() svg.add(p1, p2, p3) - g1 = self.initialize_linear_gradient(p1.bounding_box(), {0: "#ff0000", 0.5: "#00ff00", 1: "#0000ff"}) - g2 = self.initialize_linear_gradient(p2.bounding_box(), {0: "#ff0000", 0.25: "#ff0000", 0.75: "#0000ff", 1: "#0000ff"}) - g3 = self.initialize_radial_gradient(p2.bounding_box(), {0: "#ff0000", 0.5: "#00ff00", 1: "#0000ff"}) + g1 = self.initialize_linear_gradient( + p1.bounding_box(), {0: "#ff0000", 0.5: "#00ff00", 1: "#0000ff"} + ) + g2 = self.initialize_linear_gradient( + p2.bounding_box(), + {0: "#ff0000", 0.25: "#ff0000", 0.75: "#0000ff", 1: "#0000ff"}, + ) + g3 = self.initialize_radial_gradient( + p2.bounding_box(), {0: "#ff0000", 0.5: "#00ff00", 1: "#0000ff"} + ) grad1 = tween.GradientInterpolator.append_to_doc(svg, g1) grad2 = tween.GradientInterpolator.append_to_doc(svg, g2) grad3 = tween.GradientInterpolator.append_to_doc(svg, g3) - expected = [[{"fill" : "#640000"}, {"fill" : "#320000"}, {"fill": [75, 0, 0]}], # both only fill - [{"fill" : "none"}, {"fill" : "#320000"}, {"fill": [50, 0, 0], "fill-opacity": 0.5}], # interpolate via fill-opacity - [{"fill" : "none"}, {}, {"fill": [0,0,0]}], # only one fill set to None - [{"fill" : "#00ff00"}, {"fill" : grad1}, {"fill/grad/stops": [[0, "#7f7f00"], [0.5, "#00ff00"], [1, "#007f7f"]], "fill/grad/x1": "5.23564px"}], - [{"fill" : "#00ff00"}, {"fill" : grad3}, {"fill/grad/stops": [[0, "#7f7f00"], [0.5, "#00ff00"], [1, "#007f7f"]], "fill/grad/cx": "106.932px"}], - [{"fill" : grad2}, {"fill" : grad3}, {"fill": grad2}], - [{"fill" : grad1}, {"fill" : grad2}, {"fill/grad/stops": [[0, "#ff0000"], [0.25, "#bf3f00"], [0.5, "#3f7f3f"], [0.75, "#003fbf"], [1, "#0000ff"]]}], - [{"fill" : "none"}, {"fill" : grad1}, {"fill/grad/stops": [[0, "#ff0000"], [0.5, "#00ff00"], [1, "#0000ff"]], "fill/grad/x1": "5.23564px"}]] + expected = [ + [ + {"fill": "#640000"}, + {"fill": "#320000"}, + {"fill": [75, 0, 0]}, + ], # both only fill + [ + {"fill": "none"}, + {"fill": "#320000"}, + {"fill": [50, 0, 0], "fill-opacity": 0.5}, + ], # interpolate via fill-opacity + [{"fill": "none"}, {}, {"fill": [0, 0, 0]}], # only one fill set to None + [ + {"fill": "#00ff00"}, + {"fill": grad1}, + { + "fill/grad/stops": [ + [0, "#7f7f00"], + [0.5, "#00ff00"], + [1, "#007f7f"], + ], + "fill/grad/x1": "5.23564px", + }, + ], + [ + {"fill": "#00ff00"}, + {"fill": grad3}, + { + "fill/grad/stops": [ + [0, "#7f7f00"], + [0.5, "#00ff00"], + [1, "#007f7f"], + ], + "fill/grad/cx": "106.932px", + }, + ], + [{"fill": grad2}, {"fill": grad3}, {"fill": grad2}], + [ + {"fill": grad1}, + {"fill": grad2}, + { + "fill/grad/stops": [ + [0, "#ff0000"], + [0.25, "#bf3f00"], + [0.5, "#3f7f3f"], + [0.75, "#003fbf"], + [1, "#0000ff"], + ] + }, + ], + [ + {"fill": "none"}, + {"fill": grad1}, + { + "fill/grad/stops": [ + [0, "#ff0000"], + [0.5, "#00ff00"], + [1, "#0000ff"], + ], + "fill/grad/x1": "5.23564px", + }, + ], + ] for sstyle, estyle, interpstyle in expected: p1.style = inkex.Style() p1.style.update(sstyle) p2.style = inkex.Style() p2.style.update(estyle) - + interpolator = tween.StyleInterpolator(p1, p2) - p3.style = interpolator .interpolate(0.5) + p3.style = interpolator.interpolate(0.5) for key, value in interpstyle.items(): keys = key.split("/") if keys[0] == "fill": @@ -103,31 +169,47 @@ class TweenTest(TestCase): if key == "fill": assert newfill == value elif keys[1] == "grad": - assert isinstance(newfill, (inkex.LinearGradient, inkex.RadialGradient)) - if (keys[2] == "stops"): + assert isinstance( + newfill, (inkex.LinearGradient, inkex.RadialGradient) + ) + if keys[2] == "stops": assert len(value) == len(newfill.stops) for idx, _ in enumerate(value): - assert newfill.stops[idx].style["stop-color"] == value[idx][1] - self.assertAlmostEqual(float(newfill.stop_offsets[idx]), \ - value[idx][0], 1e-3) + assert ( + newfill.stops[idx].style["stop-color"] + == value[idx][1] + ) + self.assertAlmostEqual( + float(newfill.stop_offsets[idx]), + value[idx][0], + 1e-3, + ) else: assert newfill.get(keys[2]) == value else: assert p3.style(key) == value + def test_path_interpolation(self): p1 = inkex.Path("M 5.23564,33.586285 46.410969,-0.94834 89.873815,35.045501 Z") - p2 = inkex.Path("m 136.32372,31.390088 c 0,0 -65.213764,-4.27631 18.17433,-22.4506304 83.38809,-18.17434 64.14469,35.2795704 64.14469,35.2795704 z") + p2 = inkex.Path( + "m 136.32372,31.390088 c 0,0 -65.213764,-4.27631 18.17433,-22.4506304 83.38809,-18.17434 64.14469,35.2795704 64.14469,35.2795704 z" + ) pel1 = inkex.PathElement() pel2 = inkex.PathElement() pel1.path = p1 pel2.path = p2 result_method_1 = "M 70.7798 32.4882 C 70.7798 32.4882 58.7606 13.0827 100.455 3.99558 C 142.149 -5.09157 154.258 39.6323 154.258 39.6323 Z" result_method_2 = "M 70.7798 32.4882 C 70.7798 32.4882 59.6785 13.1429 98.726 4.37785 C 99.2875 4.25181 100.254 4.44741 101.52 4.87808 C 126.751 3.94755 151.064 21.8659 153.864 27.4179 C 156.66 32.9612 150.143 39.5613 144.479 39.4637 C 131.98 39.2482 70.7798 32.4882 70.7798 32.4882" - calls = [[None, result_method_1], [tween.FirstNodesInterpolator, result_method_1], [tween.EqualSubsegmentsInterpolator, result_method_2]] + calls = [ + [None, result_method_1], + [tween.FirstNodesInterpolator, result_method_1], + [tween.EqualSubsegmentsInterpolator, result_method_2], + ] for arg, expected in calls: - interp = tween.AttributeInterpolator.create_from_attribute(pel1, pel2, "d", method=arg) + interp = tween.AttributeInterpolator.create_from_attribute( + pel1, pel2, "d", method=arg + ) result = interp.interpolate(0.5) print(result) assert np.allclose(result, inkex.CubicSuperPath(inkex.Path(expected))) - diff --git a/tests/test_inkex_units.py b/tests/test_inkex_units.py index aaba8716..7cabf5d3 100644 --- a/tests/test_inkex_units.py +++ b/tests/test_inkex_units.py @@ -1,6 +1,12 @@ # 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.units import ( + are_near_relative, + convert_unit, + discover_unit, + parse_unit, + render_unit, +) from inkex.tester import TestCase @@ -9,11 +15,11 @@ class UnitsTest(TestCase): 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, '%')) + 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""" @@ -22,43 +28,44 @@ class UnitsTest(TestCase): 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') + 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) + 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') + 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')) + "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/tests/test_inkex_utils.py b/tests/test_inkex_utils.py index 498977d1..8917e232 100644 --- a/tests/test_inkex_utils.py +++ b/tests/test_inkex_utils.py @@ -11,28 +11,39 @@ from argparse import ArgumentTypeError import pytest -from inkex.utils import debug, errormsg, filename_arg, Boolean, parse_percent, to, strargs, math_eval, is_number +from inkex.utils import ( + debug, + errormsg, + filename_arg, + Boolean, + parse_percent, + to, + strargs, + math_eval, + is_number, +) from inkex.tester import TestCase from inkex import addNS + 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 + 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' + assert capsys.readouterr().err == "Hello World\n" def test_to(self): """Decorator for generators""" @@ -50,62 +61,99 @@ class TestInkexBasic(object): @to(dict) def mydict(a, b, c): """Yield as a dictionary""" - yield ('age', a) - yield ('name', c) - yield ('home', b) + 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} + 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') + 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' + 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] - assert strargs('+1 +2') == [1.0, 2.0] - assert strargs('1.2.3.4.5') == [1.2, 0.3, 0.4, 0.5] - assert strargs('1. 2..3.4') == [1.0, 2.0, 0.3, 0.4] - assert strargs('1.0e-1 -2.0e-1 3.0e10 -4.0e10') == [0.1, -0.2, 30000000000, -40000000000] - assert strargs('1.0e-1,-2.0e-1,3.0e10,-4.0e10') == [0.1, -0.2, 30000000000, -40000000000] - assert strargs('1.0E-1 -2.0E-1 3.0E10 -4.0E10') == [0.1, -0.2, 30000000000, -40000000000] - assert strargs('1.0E-1,-2.0E-1,3.0E10,-4.0E10') == [0.1, -0.2, 30000000000, -40000000000] - assert strargs('1.E2+.3E+4') == [100, 3000] + 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] + assert strargs("+1 +2") == [1.0, 2.0] + assert strargs("1.2.3.4.5") == [1.2, 0.3, 0.4, 0.5] + assert strargs("1. 2..3.4") == [1.0, 2.0, 0.3, 0.4] + assert strargs("1.0e-1 -2.0e-1 3.0e10 -4.0e10") == [ + 0.1, + -0.2, + 30000000000, + -40000000000, + ] + assert strargs("1.0e-1,-2.0e-1,3.0e10,-4.0e10") == [ + 0.1, + -0.2, + 30000000000, + -40000000000, + ] + assert strargs("1.0E-1 -2.0E-1 3.0E10 -4.0E10") == [ + 0.1, + -0.2, + 30000000000, + -40000000000, + ] + assert strargs("1.0E-1,-2.0E-1,3.0E10,-4.0E10") == [ + 0.1, + -0.2, + 30000000000, + -40000000000, + ] + assert strargs("1.E2+.3E+4") == [100, 3000] def test_ascii(self, capsys): """Parse ABCabc""" - errormsg('ABCabc') - assert capsys.readouterr().err == 'ABCabc\n' + 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' + 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' + errormsg("Àûïàèé") + assert capsys.readouterr().err, "Àûïàèé\n" def test_parse_percent(self): assert parse_percent("75%") == 0.75 @@ -115,17 +163,20 @@ class TestInkexBasic(object): import math + class TestMathFunctions(TestCase): def testExp(self): """Test if the math_eval function works""" function = "exp(x)" f = math_eval(function) self.assertAlmostEqual(f(1), math.exp(1)) + def testErf(self): """Only available in python3""" function = "erf(x)" f = math_eval(function) self.assertAlmostEqual(f(1), math.erf(1)) + def test_is_number(self): self.assertTrue(is_number("155")) self.assertTrue(is_number("12.5")) diff --git a/tests/test_inkscape_follow_link.py b/tests/test_inkscape_follow_link.py index 21a44ab9..5c78438e 100644 --- a/tests/test_inkscape_follow_link.py +++ b/tests/test_inkscape_follow_link.py @@ -2,5 +2,6 @@ from inkscape_follow_link import FollowLink from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + class TestFollowLinkBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): effect_class = FollowLink diff --git a/tests/test_interp.py b/tests/test_interp.py index c1f28bdc..79da66ae 100644 --- a/tests/test_interp.py +++ b/tests/test_interp.py @@ -3,15 +3,38 @@ 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', - '--method=equalSubsegments', '--style=True'), - ('--id=path1', '--id=path2', '--id=path3', '--id=path4', '--id=path5', - '--id=path6', '--id=path7', '--id=path8', '--id=path9', '--id=path10', - '--method=firstNodes', '--style=True') + ( + "--id=path1", + "--id=path2", + "--id=path3", + "--id=path4", + "--id=path5", + "--id=path6", + "--id=path7", + "--id=path8", + "--id=path9", + "--id=path10", + "--method=equalSubsegments", + "--style=True", + ), + ( + "--id=path1", + "--id=path2", + "--id=path3", + "--id=path4", + "--id=path5", + "--id=path6", + "--id=path7", + "--id=path8", + "--id=path9", + "--id=path10", + "--method=firstNodes", + "--style=True", + ), ] compare_filters = [CompareNumericFuzzy()] - compare_file = 'svg/interp_shapes.svg' + compare_file = "svg/interp_shapes.svg" diff --git a/tests/test_interp_att_g.py b/tests/test_interp_att_g.py index 986c53f4..4f07e522 100644 --- a/tests/test_interp_att_g.py +++ b/tests/test_interp_att_g.py @@ -3,37 +3,65 @@ from interp_att_g import InterpAttG from inkex.tester import ComparisonMixin, TestCase + class InterpAttGBasicTest(ComparisonMixin, TestCase): effect_class = InterpAttG - comparisons = [('--id=layer1', '--att=style/fill')] + comparisons = [("--id=layer1", "--att=style/fill")] + class InterpAttGMultipleSelectedTest(ComparisonMixin, TestCase): effect_class = InterpAttG - comparisons = [('--id=c1', '--id=c2', '--id=c3', '--att=style/fill')] + comparisons = [("--id=c1", "--id=c2", "--id=c3", "--att=style/fill")] + class InterpAttGColorRoundingTest(ComparisonMixin, TestCase): effect_class = InterpAttG - compare_file = 'svg/group_interpolate.svg' + compare_file = "svg/group_interpolate.svg" comparisons = [ # test for truncating/rounding bug inbox#1892 - ('--id=g53', '--att=style/fill', '--start-val=#181818', '--end-val=#000000'), + ("--id=g53", "--att=style/fill", "--start-val=#181818", "--end-val=#000000"), # test for clipping of values <= 1 - ('--id=g53', '--att=style/fill', '--start-val=#050505', '--end-val=#000000')] + ("--id=g53", "--att=style/fill", "--start-val=#050505", "--end-val=#000000"), + ] + class InterpAttGOtherAttributeTest(ComparisonMixin, TestCase): - # interpolate other values (test base.arg_class) + # interpolate other values (test base.arg_class) effect_class = InterpAttG - compare_file = 'svg/group_interpolate.svg' - comparisons = [('--id=g53', '--att=other', '--att-other=width', '--start-val=5', '--end-val=10', '--att-other-type=ValueInterpolator'), - ('--id=g53', '--att=other', '--att-other=fill', '--att-other-where=style', '--start-val=red', '--end-val=green', '--att-other-type=ColorInterpolator')] + compare_file = "svg/group_interpolate.svg" + comparisons = [ + ( + "--id=g53", + "--att=other", + "--att-other=width", + "--start-val=5", + "--end-val=10", + "--att-other-type=ValueInterpolator", + ), + ( + "--id=g53", + "--att=other", + "--att-other=fill", + "--att-other-where=style", + "--start-val=red", + "--end-val=green", + "--att-other-type=ColorInterpolator", + ), + ] + class InterpAttGTransformInterpolateTest(ComparisonMixin, TestCase): effect_class = InterpAttG - compare_file = 'svg/group_interpolate.svg' - comparisons = [('--id=g53', '--att=transform/scale', '--start-val=0.2', '--end-val=0.9'), - ('--id=g53', '--att=transform/trans-x', '--start-val=0', '--end-val=20')] + compare_file = "svg/group_interpolate.svg" + comparisons = [ + ("--id=g53", "--att=transform/scale", "--start-val=0.2", "--end-val=0.9"), + ("--id=g53", "--att=transform/trans-x", "--start-val=0", "--end-val=20"), + ] + class InterpAttGUnitsTest(ComparisonMixin, TestCase): effect_class = InterpAttG - compare_file = 'svg/group_interpolate.svg' - comparisons = [('--id=g53', '--att=width', '--start-val=0.02', '--end-val=0.1', '--unit=mm')] \ No newline at end of file + compare_file = "svg/group_interpolate.svg" + comparisons = [ + ("--id=g53", "--att=width", "--start-val=0.02", "--end-val=0.1", "--unit=mm") + ] diff --git a/tests/test_jessyink_autotexts.py b/tests/test_jessyink_autotexts.py index 720d579f..a252ff33 100644 --- a/tests/test_jessyink_autotexts.py +++ b/tests/test_jessyink_autotexts.py @@ -3,6 +3,7 @@ from jessyink_autotexts import AutoTexts from inkex.tester import ComparisonMixin, TestCase + class JessyInkAutoTextsBasicTest(ComparisonMixin, TestCase): effect_class = AutoTexts - comparisons = [('--autoText', 'slideTitle', '--id', 't1')] + comparisons = [("--autoText", "slideTitle", "--id", "t1")] diff --git a/tests/test_jessyink_effects.py b/tests/test_jessyink_effects.py index 7a8e8907..ab9a719b 100644 --- a/tests/test_jessyink_effects.py +++ b/tests/test_jessyink_effects.py @@ -3,9 +3,10 @@ from jessyink_effects import JessyinkEffects from inkex.tester import ComparisonMixin, TestCase + class JessyInkEffectsTest(ComparisonMixin, TestCase): effect_class = JessyinkEffects comparisons = [ - ('--id=p1', '--id=r3', '--effectOutOrder=2'), - ('--id=p1', '--effectIn=fade', '--effectOut=pop', '--effectOutOrder=2'), + ("--id=p1", "--id=r3", "--effectOutOrder=2"), + ("--id=p1", "--effectIn=fade", "--effectOut=pop", "--effectOutOrder=2"), ] diff --git a/tests/test_jessyink_export.py b/tests/test_jessyink_export.py index fb6b9237..3eca7392 100644 --- a/tests/test_jessyink_export.py +++ b/tests/test_jessyink_export.py @@ -3,7 +3,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', '--type=png')] + comparisons = [("--resolution=1", "--type=png")] diff --git a/tests/test_jessyink_install.py b/tests/test_jessyink_install.py index 4bfda51c..f98f7f14 100644 --- a/tests/test_jessyink_install.py +++ b/tests/test_jessyink_install.py @@ -2,5 +2,6 @@ from jessyink_install import Install from inkex.tester import ComparisonMixin, TestCase + class JessyInkInstallBasicTest(ComparisonMixin, TestCase): effect_class = Install diff --git a/tests/test_jessyink_keybindings.py b/tests/test_jessyink_keybindings.py index 22aa060d..28b19be0 100644 --- a/tests/test_jessyink_keybindings.py +++ b/tests/test_jessyink_keybindings.py @@ -2,9 +2,10 @@ 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'), + ("--slide_export=SPACE", "--drawing_undo=ENTER", "--index_nextPage=LEFT"), + ("--slide_export=a", "--drawing_undo=b", "--index_nextPage=c"), ] diff --git a/tests/test_jessyink_masterslide.py b/tests/test_jessyink_masterslide.py index 98c6c642..87fc56e6 100644 --- a/tests/test_jessyink_masterslide.py +++ b/tests/test_jessyink_masterslide.py @@ -2,5 +2,6 @@ from jessyink_master_slide import MasterSlide from inkex.tester import ComparisonMixin, TestCase + class JessyInkMasterSlideBasicTest(ComparisonMixin, TestCase): effect_class = MasterSlide diff --git a/tests/test_jessyink_mousehandler.py b/tests/test_jessyink_mousehandler.py index 73a1ea1f..67452de4 100644 --- a/tests/test_jessyink_mousehandler.py +++ b/tests/test_jessyink_mousehandler.py @@ -2,11 +2,13 @@ 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',), + ("--mouseSetting=default",), + ("--mouseSetting=noclick",), + ("--mouseSetting=draggingZoom",), ] diff --git a/tests/test_jessyink_summary.py b/tests/test_jessyink_summary.py index 4a881dfa..8c5c23f0 100644 --- a/tests/test_jessyink_summary.py +++ b/tests/test_jessyink_summary.py @@ -3,6 +3,7 @@ from jessyink_summary import Summary from inkex.tester import ComparisonMixin, TestCase from inkex.tester.filters import WindowsTextCompat + class JessyInkSummaryTest(ComparisonMixin, TestCase): stderr_output = True effect_class = Summary diff --git a/tests/test_jessyink_transitions.py b/tests/test_jessyink_transitions.py index 20de7bd6..c7626840 100644 --- a/tests/test_jessyink_transitions.py +++ b/tests/test_jessyink_transitions.py @@ -2,6 +2,7 @@ from jessyink_transitions import Transitions from inkex.tester import ComparisonMixin, TestCase + class JessyInkTransitionsBasicTest(ComparisonMixin, TestCase): effect_class = Transitions - comparisons = [('--layerName', 'Slide2')] + comparisons = [("--layerName", "Slide2")] diff --git a/tests/test_jessyink_uninstall.py b/tests/test_jessyink_uninstall.py index 6b71f0c5..6272600d 100644 --- a/tests/test_jessyink_uninstall.py +++ b/tests/test_jessyink_uninstall.py @@ -2,6 +2,7 @@ from jessyink_uninstall import Uninstall from inkex.tester import ComparisonMixin, TestCase + class JessyInkUninstallBasicTest(ComparisonMixin, TestCase): effect_class = Uninstall comparisons = [()] diff --git a/tests/test_jessyink_video.py b/tests/test_jessyink_video.py index 1a11f21a..43c9d95f 100644 --- a/tests/test_jessyink_video.py +++ b/tests/test_jessyink_video.py @@ -3,6 +3,7 @@ from jessyink_video import Video from inkex.tester import ComparisonMixin, TestCase + class JessyInkEffectsBasicTest(ComparisonMixin, TestCase): effect_class = Video comparisons = [()] diff --git a/tests/test_jessyink_view.py b/tests/test_jessyink_view.py index 6d362d85..5b7c4f39 100644 --- a/tests/test_jessyink_view.py +++ b/tests/test_jessyink_view.py @@ -2,6 +2,7 @@ from jessyink_view import View from inkex.tester import ComparisonMixin, TestCase + class JessyInkEffectsBasicTest(ComparisonMixin, TestCase): effect_class = View - comparisons = [('--id=r3', '--viewOrder=1')] + comparisons = [("--id=r3", "--viewOrder=1")] diff --git a/tests/test_jitternodes.py b/tests/test_jitternodes.py index 553a4540..81f8333d 100644 --- a/tests/test_jitternodes.py +++ b/tests/test_jitternodes.py @@ -6,8 +6,8 @@ from inkex.tester import ComparisonMixin, TestCase class JitterNodesBasicTest(ComparisonMixin, TestCase): effect_class = JitterNodes comparisons = [ - ('--id=p1', '--dist=gaussian', '--end=false', '--ctrl=true'), - ('--id=p1', '--dist=uniform', '--ctrl=false'), - ('--id=p1', '--dist=pareto', '--radiusy=100', '--ctrl=true'), - ('--id=p1', '--dist=lognorm', '--radiusx=100', '--ctrl=true'), + ("--id=p1", "--dist=gaussian", "--end=false", "--ctrl=true"), + ("--id=p1", "--dist=uniform", "--ctrl=false"), + ("--id=p1", "--dist=pareto", "--radiusy=100", "--ctrl=true"), + ("--id=p1", "--dist=lognorm", "--radiusx=100", "--ctrl=true"), ] diff --git a/tests/test_layer2png.py b/tests/test_layer2png.py index a45f3ec9..d1d7b84f 100644 --- a/tests/test_layer2png.py +++ b/tests/test_layer2png.py @@ -5,39 +5,38 @@ 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' + compare_file = "svg/slicer.svg" comparisons = [] def test_get_layers(self): - basic_svg = self.data_file('svg', 'slicer.svg') - args = [basic_svg, '--layer=slices'] + 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') + nodes = self.effect.get_layer_nodes("slices") self.assertEqual(len(nodes), 1) - self.assertEqual(nodes[0].tag, '{http://www.w3.org/2000/svg}rect') - + 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'] + 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') + 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'] + 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') + 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') + self.assertEqual(kwargs["export-id"], "slice1") diff --git a/tests/test_layers2svgfont.py b/tests/test_layers2svgfont.py index a53fd078..7cde1b28 100644 --- a/tests/test_layers2svgfont.py +++ b/tests/test_layers2svgfont.py @@ -2,7 +2,8 @@ from layers2svgfont import LayersToSvgFont from inkex.tester import ComparisonMixin, TestCase + class TestLayers2SVGFontBasic(ComparisonMixin, TestCase): effect_class = LayersToSvgFont - compare_file = 'svg/font_layers.svg' + compare_file = "svg/font_layers.svg" comparisons = [()] diff --git a/tests/test_lindenmayer.py b/tests/test_lindenmayer.py index 2ba4693a..7ae54a37 100644 --- a/tests/test_lindenmayer.py +++ b/tests/test_lindenmayer.py @@ -3,6 +3,7 @@ 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/tests/test_lorem_ipsum.py b/tests/test_lorem_ipsum.py index b99932a9..51471148 100644 --- a/tests/test_lorem_ipsum.py +++ b/tests/test_lorem_ipsum.py @@ -2,10 +2,18 @@ from lorem_ipsum import LoremIpsum from inkex.tester import ComparisonMixin, TestCase + class LorumIpsumBasicTest(ComparisonMixin, TestCase): effect_class = LoremIpsum compare_file = "svg/shapes.svg" - comparisons = [(), ["--svg2=false"], ["--id=r1"], ["--id=r1", "--svg2=false"], ["--id=t4"]] + comparisons = [ + (), + ["--svg2=false"], + ["--id=r1"], + ["--id=r1", "--svg2=false"], + ["--id=t4"], + ] + class LoremIpsumMillimeters(ComparisonMixin, TestCase): effect_class = LoremIpsum diff --git a/tests/test_markers_strokepaint.py b/tests/test_markers_strokepaint.py index e8fbc1eb..8a31fec9 100644 --- a/tests/test_markers_strokepaint.py +++ b/tests/test_markers_strokepaint.py @@ -7,20 +7,20 @@ from markers_strokepaint import MarkersStrokePaint from inkex.tester import ComparisonMixin, TestCase + class MarkerStrokePaintBasicTest(ComparisonMixin, TestCase): effect_class = MarkersStrokePaint - compare_file = 'svg/markers.svg' + compare_file = "svg/markers.svg" comparisons = [ - ('--tab="object"', '--id=dimension', '--type=stroke'), - ('--tab="custom"', '--id=dimension', '--type=stroke'), + ('--tab="object"', "--id=dimension", "--type=stroke"), + ('--tab="custom"', "--id=dimension", "--type=stroke"), ] def test_basic(self): - args = ['--id=dimension', - self.data_file('svg', 'markers.svg')] + 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') + 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/tests/test_measure.py b/tests/test_measure.py index c58715c5..81f8b141 100644 --- a/tests/test_measure.py +++ b/tests/test_measure.py @@ -6,26 +6,28 @@ from inkex.tester.filters import CompareNumericFuzzy olddefaults = ("--fontsize=20", "--unit=mm", "--scale=1.1") + class LengthBasicTest(ComparisonMixin, TestCase): effect_class = MeasureLength compare_filters = [CompareNumericFuzzy()] comparisons = [ - ('--id=p1', '--id=p2', '--presetFormat=TaP_start') + olddefaults, - ('--method=presets', '--presetFormat=TaP_start', '--id=p1') + olddefaults, - ('--method=presets', '--presetFormat=TaP_end', '--id=p2') + olddefaults, - ('--method=presets', '--presetFormat=FT_start', '--id=p1') + olddefaults, - ('--method=presets', '--presetFormat=FT_bbox', '--id=p2') + olddefaults, - ('--method=presets', '--presetFormat=FT_bbox', '--id=p2') + olddefaults, - ('--type=area', '--id=p1', '--presetFormat=TaP_start') + olddefaults, - ('--type=cofm', '--id=c3', '--presetFormat=TaP_start') + olddefaults, + ("--id=p1", "--id=p2", "--presetFormat=TaP_start") + olddefaults, + ("--method=presets", "--presetFormat=TaP_start", "--id=p1") + olddefaults, + ("--method=presets", "--presetFormat=TaP_end", "--id=p2") + olddefaults, + ("--method=presets", "--presetFormat=FT_start", "--id=p1") + olddefaults, + ("--method=presets", "--presetFormat=FT_bbox", "--id=p2") + olddefaults, + ("--method=presets", "--presetFormat=FT_bbox", "--id=p2") + olddefaults, + ("--type=area", "--id=p1", "--presetFormat=TaP_start") + olddefaults, + ("--type=cofm", "--id=c3", "--presetFormat=TaP_start") + olddefaults, ] + class LengthComplexTransformTest(ComparisonMixin, TestCase): effect_class = MeasureLength compare_filters = [CompareNumericFuzzy()] compare_file = "svg/complextransform.test.svg" comparisons = [ - ('--method=presets', '--presetFormat=TaP_start', '--id=D') + olddefaults, - ('--method=presets', '--presetFormat=FT_start', '--id=D') + olddefaults, - ('--type=cofm', '--id=D', '--presetFormat=TaP_start') + olddefaults, + ("--method=presets", "--presetFormat=TaP_start", "--id=D") + olddefaults, + ("--method=presets", "--presetFormat=FT_start", "--id=D") + olddefaults, + ("--type=cofm", "--id=D", "--presetFormat=TaP_start") + olddefaults, ] diff --git a/tests/test_media_zip.py b/tests/test_media_zip.py index 7d34295a..cfcd8462 100644 --- a/tests/test_media_zip.py +++ b/tests/test_media_zip.py @@ -3,6 +3,7 @@ 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()] diff --git a/tests/test_merge_styles.py b/tests/test_merge_styles.py index 8a9d20fb..b0a31cd9 100644 --- a/tests/test_merge_styles.py +++ b/tests/test_merge_styles.py @@ -2,7 +2,9 @@ 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')] + comparisons = [("--id=c2", "--id=c3")] diff --git a/tests/test_motion.py b/tests/test_motion.py index 23e77659..23aabbe4 100644 --- a/tests/test_motion.py +++ b/tests/test_motion.py @@ -3,19 +3,28 @@ from motion import Motion from inkex.tester import ComparisonMixin, TestCase from inkex.tester.filters import CompareNumericFuzzy, CompareWithPathSpace + class MotionBasicTest(ComparisonMixin, TestCase): effect_class = Motion compare_filters = [CompareNumericFuzzy(), CompareWithPathSpace()] comparisons = [ - ('--id=c3', '--id=p2'), + ("--id=c3", "--id=p2"), ] + class MotionSubpathsTest(ComparisonMixin, TestCase): """Tests the motion extension on paths with (a) transforms and (b) multiple closed subpaths (b): see https://gitlab.com/inkscape/extensions/-/issues/266""" + compare_file = "svg/motion_tests.svg" effect_class = Motion compare_filters = [CompareNumericFuzzy(), CompareWithPathSpace()] comparisons = [ - ('--magnitude=2', '--angle=45', '--fillwithstroke=True', '--id=path23053', '--id=path28636') + ( + "--magnitude=2", + "--angle=45", + "--fillwithstroke=True", + "--id=path23053", + "--id=path28636", + ) ] diff --git a/tests/test_new_glyph_layer.py b/tests/test_new_glyph_layer.py index faa3d2e1..edeb1129 100644 --- a/tests/test_new_glyph_layer.py +++ b/tests/test_new_glyph_layer.py @@ -2,5 +2,6 @@ from new_glyph_layer import NewGlyphLayer from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + class TestNewGlyphLayerBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): effect_class = NewGlyphLayer diff --git a/tests/test_next_glyph_layer.py b/tests/test_next_glyph_layer.py index 8551e160..48126b28 100644 --- a/tests/test_next_glyph_layer.py +++ b/tests/test_next_glyph_layer.py @@ -2,5 +2,6 @@ from next_glyph_layer import NextLayer from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + class TestNextLayerBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): effect_class = NextLayer diff --git a/tests/test_nicechart.py b/tests/test_nicechart.py index 5f471ece..66bb1e03 100644 --- a/tests/test_nicechart.py +++ b/tests/test_nicechart.py @@ -5,18 +5,19 @@ from inkex.tester.filters import CompareNumericFuzzy old_params = ("--blur=True", "--headings=True", "--font-color=black", "--what=22,11,67") + class TestNiceChartBasic(ComparisonMixin, TestCase): effect_class = NiceChart - compare_file = 'svg/default-plain-SVG.svg' + 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) + filename = self.data_file("io/nicechart_01.csv") + filearg = "--file={}".format(filename) return ( (filearg,) + old_params, - (filearg, '--type=pie') + old_params, - (filearg, '--type=pie_abs') + old_params, - (filearg, '--type=stbar') + old_params, + (filearg, "--type=pie") + old_params, + (filearg, "--type=pie_abs") + old_params, + (filearg, "--type=stbar") + old_params, ) diff --git a/tests/test_output_scour.py b/tests/test_output_scour.py index 52c81d40..9c106661 100644 --- a/tests/test_output_scour.py +++ b/tests/test_output_scour.py @@ -4,6 +4,7 @@ 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 diff --git a/tests/test_param_curves.py b/tests/test_param_curves.py index 46fd2bc5..8869c666 100644 --- a/tests/test_param_curves.py +++ b/tests/test_param_curves.py @@ -3,10 +3,11 @@ 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()] comparisons = [ ("-s=8", "--isoscale=True", "--drawaxis=True"), - ('--id=p1', '--id=r3', "-s=8", "--isoscale=True", "--drawaxis=True"), + ("--id=p1", "--id=r3", "-s=8", "--isoscale=True", "--drawaxis=True"), ] diff --git a/tests/test_path_envelope.py b/tests/test_path_envelope.py index b05c93c6..4b65c802 100644 --- a/tests/test_path_envelope.py +++ b/tests/test_path_envelope.py @@ -3,13 +3,16 @@ 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' + 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' + comparisons = [("--id=obj", "--id=envelope")] + compare_file = "svg/perspective_groups.svg" diff --git a/tests/test_path_mesh.py b/tests/test_path_mesh.py index af08aacd..07c8a430 100644 --- a/tests/test_path_mesh.py +++ b/tests/test_path_mesh.py @@ -6,20 +6,26 @@ 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' + 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'), + ("--id=mesh1", "--mode=outline"), + ("--id=mesh1", "--mode=gridlines"), + ("--id=mesh1", "--mode=meshpatches"), + ("--id=mesh1", "--mode=faces"), ] - compare_file = 'svg/mesh.svg' + compare_file = "svg/mesh.svg" diff --git a/tests/test_path_number_nodes.py b/tests/test_path_number_nodes.py index 6af6a257..4b241826 100644 --- a/tests/test_path_number_nodes.py +++ b/tests/test_path_number_nodes.py @@ -2,14 +2,13 @@ from path_number_nodes import NumberNodes from inkex.tester import ComparisonMixin, TestCase + class NumberNodesTest(ComparisonMixin, TestCase): effect_class = NumberNodes - comparisons = [('--id=p1', '--id=r3')] + comparisons = [("--id=p1", "--id=r3")] + class LengthComplexTransformTest(ComparisonMixin, TestCase): effect_class = NumberNodes compare_file = "svg/complextransform.test.svg" - comparisons = [ - ['--id=D'] - ] - + comparisons = [["--id=D"]] diff --git a/tests/test_path_to_absolute.py b/tests/test_path_to_absolute.py index ecb25aeb..3b4af141 100644 --- a/tests/test_path_to_absolute.py +++ b/tests/test_path_to_absolute.py @@ -3,11 +3,17 @@ 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'), + ( + "--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/tests/test_pathalongpath.py b/tests/test_pathalongpath.py index 206c8955..ab17a212 100644 --- a/tests/test_pathalongpath.py +++ b/tests/test_pathalongpath.py @@ -9,36 +9,69 @@ class TestPathAlongPathBasic(ComparisonMixin, TestCase): compare_filters = [CompareNumericFuzzy(), CompareWithPathSpace()] comparisons = [ # Settings: Repeated, Snake, no distance. Tests a path with fillrule=evenodd - ('--copymode=Repeated', '--kind=Snake', '--id=g3427', '--id=path2551'), + ("--copymode=Repeated", "--kind=Snake", "--id=g3427", "--id=path2551"), # Settings: Repeated, Stretched, Ribbon, Vertical. # Tests a group pattern with multiple nested transforms - ('--copymode=Repeated, stretched', '--kind=Ribbon', '--vertical=True', - '--id=g3961', '--id=path10007'), + ( + "--copymode=Repeated, stretched", + "--kind=Ribbon", + "--vertical=True", + "--id=g3961", + "--id=path10007", + ), # Settings: Repeated, Stretched, Ribbon # Tests a group pattern with multiple nested clones - ('--copymode=Repeated, stretched', '--kind=Ribbon', - '--id=g4054', '--id=path4056'), + ( + "--copymode=Repeated, stretched", + "--kind=Ribbon", + "--id=g4054", + "--id=path4056", + ), # Settings: Single, Stretched, Snake, not duplicated. # Tests putting a text (converted to a path) on a path and stretching it to fit on the # skeleton path - ('--copymode=Single, stretched', '--kind=Snake', - '--id=text4418', '--id=path4412'), + ( + "--copymode=Single, stretched", + "--kind=Snake", + "--id=text4418", + "--id=path4412", + ), # Settings: Single, Stretched, Snake. # Tests selecting multiple sceleton paths, one consisting of multiple subpaths - ('--copymode=Single, stretched', '--kind=Snake', - '--id=path4585', '--id=path4608', '--id=path4610', '--id=path4612'), - # Settings: Repeated, Stretched, Snake, Space between copies=5, Normal offset=5. + ( + "--copymode=Single, stretched", + "--kind=Snake", + "--id=path4585", + "--id=path4608", + "--id=path4610", + "--id=path4612", + ), + # Settings: Repeated, Stretched, Snake, Space between copies=5, Normal offset=5. # Tests putting a path with multiple subpaths with a gradient on a closed path - ('--copymode=Repeated, stretched', '--kind=Snake', '--noffset=5', '--space=5', - '--id=path2408', '--id=path2405'), + ( + "--copymode=Repeated, stretched", + "--kind=Snake", + "--noffset=5", + "--space=5", + "--id=path2408", + "--id=path2405", + ), ] effect_class = PathAlongPath + class TestPathAlongPathCloneTransforms(ComparisonMixin, TestCase): """Tests for issue https://gitlab.com/inkscape/extensions/-/issues/241""" + effect_class = PathAlongPath compare_file = "svg/pattern_along_path_clone_transform.svg" comparisons = [ # a clone with a transform in a group with a transform - ("--copymode=Single", "--duplicate=True", "--kind=Snake", "--id=g5848", "--id=path3336") - ] \ No newline at end of file + ( + "--copymode=Single", + "--duplicate=True", + "--kind=Snake", + "--id=g5848", + "--id=path3336", + ) + ] diff --git a/tests/test_pathscatter.py b/tests/test_pathscatter.py index b490f884..9274ad10 100644 --- a/tests/test_pathscatter.py +++ b/tests/test_pathscatter.py @@ -3,6 +3,7 @@ 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 compare_file = "svg/scatter.svg" @@ -10,13 +11,31 @@ class TestPathScatterBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase # Test simple case ("--id=g12668", "--id=path8143", "--stretch=False", "--follow=False"), # Test follow and stretch of a path around a skeleton with multiple closed subpaths - ("--id=path3990", "--id=path3982", "--stretch=True", "--follow=True", "--copymode=copy"), + ( + "--id=path3990", + "--id=path3982", + "--stretch=True", + "--follow=True", + "--copymode=copy", + ), # Test cloning and rotating - ("--id=g12668", "--id=path8143", "--stretch=True", "--rotate=True", "--copymode=clone"), + ( + "--id=g12668", + "--id=path8143", + "--stretch=True", + "--rotate=True", + "--copymode=clone", + ), # Test picking from a group pattern - ("--id=g12668", "--id=path8143", "--stretch=True", "--copymode=copy", "--grouppick=True", - "--pickmode=seq"), + ( + "--id=g12668", + "--id=path8143", + "--stretch=True", + "--copymode=copy", + "--grouppick=True", + "--pickmode=seq", + ), # Test stretch and spac ("--id=g12668", "--id=path8143", "--stretch=True", "--space=10"), - ] + ] compare_filters = [CompareWithoutIds()] diff --git a/tests/test_pdflatex.py b/tests/test_pdflatex.py index e3873a56..a6758221 100644 --- a/tests/test_pdflatex.py +++ b/tests/test_pdflatex.py @@ -26,9 +26,13 @@ Clean up any old `.msg` files with invalid or old keys. from pdflatex import PdfLatex from inkex.tester import ComparisonMixin, TestCase + class PdfLatexTest(ComparisonMixin, TestCase): - compare_file = 'svg/empty.svg' + 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='), + ( + "--formule=\\(\\displaystyle\\frac{\\pi^2}{6}=\\lim_{n \\to \\infty}\\sum_{k=1}^n \\frac{1}{k^2}\\)", + "--packages=", + ), ] diff --git a/tests/test_perfectboundcover.py b/tests/test_perfectboundcover.py index 9b90b9f7..92b6e511 100644 --- a/tests/test_perfectboundcover.py +++ b/tests/test_perfectboundcover.py @@ -2,6 +2,7 @@ from perfectboundcover import PerfectBoundCover from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + class PerfectBoundCoverBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): effect_class = PerfectBoundCover comparisons = [()] diff --git a/tests/test_perspective.py b/tests/test_perspective.py index 82c165e0..1040d31f 100644 --- a/tests/test_perspective.py +++ b/tests/test_perspective.py @@ -4,12 +4,14 @@ 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' + 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' + comparisons = [("--id=obj", "--id=envelope")] + compare_file = "svg/perspective_groups.svg" diff --git a/tests/test_pixelsnap.py b/tests/test_pixelsnap.py index 1cb8c915..ef6feceb 100644 --- a/tests/test_pixelsnap.py +++ b/tests/test_pixelsnap.py @@ -3,7 +3,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')] + comparisons = [("--id=p1", "--id=r3")] diff --git a/tests/test_plotter.py b/tests/test_plotter.py index d24a7072..3e505c57 100644 --- a/tests/test_plotter.py +++ b/tests/test_plotter.py @@ -5,18 +5,23 @@ from plotter import Plot from inkex.tester import ComparisonMixin, TestCase from inkex.tester.filters import CompareReplacement + @pytest.mark.skipif(sys.platform == "win32", reason="termios not available on Windows") class TestPlotter(ComparisonMixin, TestCase): """Test the plotter extension""" + stderr_output = True effect_class = Plot compare_filter_save = True - compare_filters = [ - CompareReplacement((';', '\n')) - ] - old_defaults = ('--serialFlowControl=0', '--force=24', '--speed=20', '--orientation=90') + compare_filters = [CompareReplacement((";", "\n"))] + old_defaults = ( + "--serialFlowControl=0", + "--force=24", + "--speed=20", + "--orientation=90", + ) comparisons = [ - ('--serialPort=[test]',) + old_defaults, # HPGL - ('--serialPort=[test]', '--commandLanguage=DMPL') + old_defaults, - ('--serialPort=[test]', '--commandLanguage=KNK') + old_defaults, + ("--serialPort=[test]",) + old_defaults, # HPGL + ("--serialPort=[test]", "--commandLanguage=DMPL") + old_defaults, + ("--serialPort=[test]", "--commandLanguage=KNK") + old_defaults, ] diff --git a/tests/test_polyhedron_3d.py b/tests/test_polyhedron_3d.py index 5fa315ec..7e7b6d89 100644 --- a/tests/test_polyhedron_3d.py +++ b/tests/test_polyhedron_3d.py @@ -3,18 +3,51 @@ from polyhedron_3d import Poly3D from inkex.tester import ComparisonMixin, TestCase from inkex.tester.filters import CompareNumericFuzzy + class Poly3DBasicTest(ComparisonMixin, TestCase): effect_class = Poly3D comparisons = [ - ('--show=fce', '--obj=cube', '--r1_ax=x', '--r1_ang=45', '--r2_ax=y', '--r2_ang=45', - '--cw_wound=True'), - ('--show=fce', '--obj=cube', '--r1_ax=y', '--r1_ang=45', '--z_sort=cent', - '--cw_wound=True'), - ('--show=fce', '--obj=cube', '--r1_ax=z', '--r1_ang=45', '--z_sort=max', '--cw_wound=True'), - ('--show=edg', '--obj=oct', '--r1_ax=z', '--r1_ang=45', '--th=4', '--cw_wound=True'), - ('--show=vtx', '--obj=methane', '--cw_wound=True'), - ('--show=edg', '--obj=methane', '--cw_wound=True'), - ('--show=fce', '--obj=from_file', '--spec_file=great_stel_dodec.obj', '--cw_wound=True'), + ( + "--show=fce", + "--obj=cube", + "--r1_ax=x", + "--r1_ang=45", + "--r2_ax=y", + "--r2_ang=45", + "--cw_wound=True", + ), + ( + "--show=fce", + "--obj=cube", + "--r1_ax=y", + "--r1_ang=45", + "--z_sort=cent", + "--cw_wound=True", + ), + ( + "--show=fce", + "--obj=cube", + "--r1_ax=z", + "--r1_ang=45", + "--z_sort=max", + "--cw_wound=True", + ), + ( + "--show=edg", + "--obj=oct", + "--r1_ax=z", + "--r1_ang=45", + "--th=4", + "--cw_wound=True", + ), + ("--show=vtx", "--obj=methane", "--cw_wound=True"), + ("--show=edg", "--obj=methane", "--cw_wound=True"), + ( + "--show=fce", + "--obj=from_file", + "--spec_file=great_stel_dodec.obj", + "--cw_wound=True", + ), ] compare_filters = [CompareNumericFuzzy()] - compare_file = 'svg/empty.svg' + compare_file = "svg/empty.svg" diff --git a/tests/test_prepare_file_save_as.py b/tests/test_prepare_file_save_as.py index fad46e99..d99e339b 100644 --- a/tests/test_prepare_file_save_as.py +++ b/tests/test_prepare_file_save_as.py @@ -4,6 +4,7 @@ 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/tests/test_previous_glyph_layer.py b/tests/test_previous_glyph_layer.py index dafad9e3..7ce961e6 100644 --- a/tests/test_previous_glyph_layer.py +++ b/tests/test_previous_glyph_layer.py @@ -2,5 +2,6 @@ from previous_glyph_layer import PreviousLayer from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + class TestPreviousLayerBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): effect_class = PreviousLayer diff --git a/tests/test_print_win32_vector.py b/tests/test_print_win32_vector.py index ea00e55a..2621deba 100644 --- a/tests/test_print_win32_vector.py +++ b/tests/test_print_win32_vector.py @@ -6,10 +6,12 @@ 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") + +@pytest.mark.skipif(sys.platform != "win32", reason="Only runs on windows") class TestPrintWin32VectorBasic(InkscapeExtensionTestMixin, TestCase): effect_class = PrintWin32Vector + class TestPrintWin32VectorDocumentName(TestCase): effect_class = PrintWin32Vector python3_only = True @@ -19,11 +21,11 @@ class TestPrintWin32VectorDocumentName(TestCase): self.effect.parse_arguments([self.empty_svg]) self.effect.load_raw() lpszDocName = self.effect.doc_name() - self.assertEqual(lpszDocName.value, b'Inkscape New document 1') + self.assertEqual(lpszDocName.value, b"Inkscape New document 1") def test_doc_name_read(self): """Uses document name from svg""" - self.effect.parse_arguments([self.data_file('svg/shapes.svg')]) + self.effect.parse_arguments([self.data_file("svg/shapes.svg")]) self.effect.load_raw() lpszDocName = self.effect.doc_name() - self.assertEqual(lpszDocName.value, b'Inkscape test.svg') + self.assertEqual(lpszDocName.value, b"Inkscape test.svg") diff --git a/tests/test_printing_marks.py b/tests/test_printing_marks.py index e9403e79..f8698f2e 100644 --- a/tests/test_printing_marks.py +++ b/tests/test_printing_marks.py @@ -1,8 +1,12 @@ # coding=utf-8 from printing_marks import PrintingMarks from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase -from inkex.tester.filters import CompareNumericFuzzy, CompareWithPathSpace, \ - CompareOrderIndependentStyle +from inkex.tester.filters import ( + CompareNumericFuzzy, + CompareWithPathSpace, + CompareOrderIndependentStyle, +) + class PrintingMarksBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): effect_class = PrintingMarks @@ -11,6 +15,20 @@ class PrintingMarksBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCa CompareWithPathSpace(), CompareOrderIndependentStyle(), ] - compare_file = 'svg/shapes.svg' - comparisons = {('--crop_marks', 'True', '--bleed_marks', 'True', '--registration_marks', 'True', - '--star_target', 'True', '--colour_bars', 'True', '--page_info', 'True')} + compare_file = "svg/shapes.svg" + comparisons = { + ( + "--crop_marks", + "True", + "--bleed_marks", + "True", + "--registration_marks", + "True", + "--star_target", + "True", + "--colour_bars", + "True", + "--page_info", + "True", + ) + } diff --git a/tests/test_ps_input.py b/tests/test_ps_input.py index aabc90a2..3666497a 100644 --- a/tests/test_ps_input.py +++ b/tests/test_ps_input.py @@ -7,11 +7,12 @@ 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', + "io/test.ps", + "io/test.eps", ] comparisons = [()] diff --git a/tests/test_render_alphabetsoup.py b/tests/test_render_alphabetsoup.py index b14add5c..44cfcc39 100644 --- a/tests/test_render_alphabetsoup.py +++ b/tests/test_render_alphabetsoup.py @@ -2,5 +2,6 @@ from render_alphabetsoup import AlphabetSoup from inkex.tester import InkscapeExtensionTestMixin, TestCase + class AlphabetSoupBasicTest(InkscapeExtensionTestMixin, TestCase): effect_class = AlphabetSoup diff --git a/tests/test_render_barcode.py b/tests/test_render_barcode.py index f9fabf51..e148691d 100644 --- a/tests/test_render_barcode.py +++ b/tests/test_render_barcode.py @@ -26,68 +26,71 @@ 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'), + ("--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: + with open(cls.data_file("batches/barcodes.dat"), "r") as fhl: for line in fhl: - (btype, text, code) = line.strip().split(':', 2) + (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') + self.barcode_test("Ean5") def test_render_barcode_ian8(self): """Barcode IAN5""" - self.barcode_test('Ean8') + self.barcode_test("Ean8") def test_render_barcode_ian13(self): """Barcode IAN5""" - self.barcode_test('Ean13') + self.barcode_test("Ean13") def test_render_barcode_upca(self): """Barcode IAN5""" - self.barcode_test('Upca') + self.barcode_test("Upca") def test_render_barcode_upce(self): """Barcode UPCE""" - self.barcode_test('Upce') + self.barcode_test("Upce") def test_render_barcode_code128(self): """Barcode Code128""" - self.barcode_test('Code128') + self.barcode_test("Code128") def test_render_barcode_code25i(self): """Barcode Code25i""" - self.barcode_test('Code25i') + self.barcode_test("Code25i") def test_render_barcode_code39(self): """Barcode Code39""" - self.barcode_test('Code39') + self.barcode_test("Code39") def test_render_barcode_code39ext(self): """Barcode Code39Ext""" - self.barcode_test('Code39Ext') + self.barcode_test("Code39Ext") def test_render_barcode_ean2(self): """Barcode Ean2""" - self.barcode_test('Ean2') + self.barcode_test("Ean2") def test_render_barcode_royal_mail(self): """Barcode RM4CC/RM4SCC""" - self.barcode_test('Rm4scc') + self.barcode_test("Rm4scc") def barcode_test(self, name): """Base module for all barcode testing""" diff --git a/tests/test_render_barcode_datamatrix.py b/tests/test_render_barcode_datamatrix.py index 8fb29fb6..564f0ee0 100644 --- a/tests/test_render_barcode_datamatrix.py +++ b/tests/test_render_barcode_datamatrix.py @@ -2,12 +2,13 @@ from render_barcode_datamatrix import DataMatrix from inkex.tester import ComparisonMixin, TestCase + class TestDataMatrixBasic(ComparisonMixin, TestCase): effect_class = DataMatrix - compare_file = 'svg/empty.svg' + compare_file = "svg/empty.svg" comparisons = [ - ('--symbol=sq10',), - ('--symbol=sq96', '--text=Sunshine'), - ('--symbol=sq144', '--text=HelloTest'), - ('--symbol=rect8x32', '--text=1234Foo'), + ("--symbol=sq10",), + ("--symbol=sq96", "--text=Sunshine"), + ("--symbol=sq144", "--text=HelloTest"), + ("--symbol=rect8x32", "--text=1234Foo"), ] diff --git a/tests/test_render_barcode_qrcode.py b/tests/test_render_barcode_qrcode.py index a3323a94..2375891a 100644 --- a/tests/test_render_barcode_qrcode.py +++ b/tests/test_render_barcode_qrcode.py @@ -2,50 +2,100 @@ from render_barcode_qrcode import QrCode, 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' + compare_file = "svg/empty.svg" comparisons = [ - ('--text=0123456789', '--typenumber=0', '--modulesize=10', '--drawtype=smooth', - '--smoothness=greedy'), - ('--text=BreadRolls', '--typenumber=2', '--encoding=utf8', '--modulesize=10', - '--drawtype=smooth', '--smoothness=greedy'), - ('--text=Blue Front Yard', '--typenumber=3', '--correctionlevel=1', '--modulesize=10', - '--drawtype=smooth', '--smoothness=greedy'), - ('--text=Waterfall', '--typenumber=1', '--drawtype=pathpreset', '--pathtype=circle', '--modulesize=10'), - ('--text=groupid', '--groupid=testid', '--modulesize=10', '--drawtype=smooth', '--smoothness=greedy'), + ( + "--text=0123456789", + "--typenumber=0", + "--modulesize=10", + "--drawtype=smooth", + "--smoothness=greedy", + ), + ( + "--text=BreadRolls", + "--typenumber=2", + "--encoding=utf8", + "--modulesize=10", + "--drawtype=smooth", + "--smoothness=greedy", + ), + ( + "--text=Blue Front Yard", + "--typenumber=3", + "--correctionlevel=1", + "--modulesize=10", + "--drawtype=smooth", + "--smoothness=greedy", + ), + ( + "--text=Waterfall", + "--typenumber=1", + "--drawtype=pathpreset", + "--pathtype=circle", + "--modulesize=10", + ), + ( + "--text=groupid", + "--groupid=testid", + "--modulesize=10", + "--drawtype=smooth", + "--smoothness=greedy", + ), ] + class TestQRCodeInkscapeSelection(ComparisonMixin, TestCase): """Test QR code with a selection as input""" + effect_class = QrCode compare_file = "svg/shapes.svg" - comparisons = [("--text=test", "--drawtype=selection", "--id=r3", "--modulesize=10")] + comparisons = [ + ("--text=test", "--drawtype=selection", "--id=r3", "--modulesize=10") + ] + class TestQRCodeInkscapeSymbol(ComparisonMixin, TestCase): """Test symbols in qr codes""" + effect_class = QrCode - compare_file = 'svg/symbol.svg' + compare_file = "svg/symbol.svg" comparisons = [ - ('--text=ThingOne', '--drawtype=symbol', '--correctionlevel=2', - '--symbolid=AirTransportation_Inv', '--modulesize=10'), + ( + "--text=ThingOne", + "--drawtype=symbol", + "--correctionlevel=2", + "--symbolid=AirTransportation_Inv", + "--modulesize=10", + ), ] + class TestLargeQRCodes(ComparisonMixin, TestCase): """Test large qr codes with up to 2953 bytes of payload. Also tests numeric mode""" + effect_class = QrCode - compare_file = 'svg/empty.svg' + compare_file = "svg/empty.svg" comparisons = [ # the largest numeric QR code has 7089 characters - ('--text=' + (("12345" * 2000)[0:7089]), "--qrmode=1", "--correctionlevel=1"), + ("--text=" + (("12345" * 2000)[0:7089]), "--qrmode=1", "--correctionlevel=1"), ] + class TestQRCodeClasses(ComparisonMixin, TestCase): """Test alphanumeric barcode""" + effect_class = QrCode - compare_file = 'svg/empty.svg' + compare_file = "svg/empty.svg" comparisons = [ - ('--text=THIS IS A TEST OF AN ALPHANUMERIC QRCODE. IT CAN STORE A LARGER NUMBER OF ' - 'UPPERSPACE CHARACTERS THAN A BYTE-ENCODED QRCODE: 123', '--qrmode=2', '--correctionlevel=1'), + ( + "--text=THIS IS A TEST OF AN ALPHANUMERIC QRCODE. IT CAN STORE A LARGER NUMBER OF " + "UPPERSPACE CHARACTERS THAN A BYTE-ENCODED QRCODE: 123", + "--qrmode=2", + "--correctionlevel=1", + ), ] diff --git a/tests/test_render_gear_rack.py b/tests/test_render_gear_rack.py index 21cedb08..14caa257 100644 --- a/tests/test_render_gear_rack.py +++ b/tests/test_render_gear_rack.py @@ -3,6 +3,7 @@ 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/tests/test_render_gears.py b/tests/test_render_gears.py index ad65e279..9131293f 100644 --- a/tests/test_render_gears.py +++ b/tests/test_render_gears.py @@ -3,10 +3,11 @@ 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()] comparisons = [ - ('--centerdiameter=10.0',), - ('--id=p1', '--id=r3', '--centerdiameter=10.0'), + ("--centerdiameter=10.0",), + ("--id=p1", "--id=r3", "--centerdiameter=10.0"), ] diff --git a/tests/test_replace_font.py b/tests/test_replace_font.py index ea1ed5c8..2150ac9a 100644 --- a/tests/test_replace_font.py +++ b/tests/test_replace_font.py @@ -4,17 +4,23 @@ from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase from inkex.tester.filters import CompareOrderIndependentStyle from inkex.tester.filters import WindowsTextCompat + class TestReplaceFontBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): effect_class = ReplaceFont compare_filters = [CompareOrderIndependentStyle()] - comparisons =[( - '--action=find_replace', - '--fr_find=sans-serif', - '--fr_replace=monospace', - )] + comparisons = [ + ( + "--action=find_replace", + "--fr_find=sans-serif", + "--fr_replace=monospace", + ) + ] + class TestFontList(ComparisonMixin, TestCase): effect_class = ReplaceFont - comparisons = [('--action=list_only',),] + comparisons = [ + ("--action=list_only",), + ] stderr_output = True compare_filters = [WindowsTextCompat()] diff --git a/tests/test_restack.py b/tests/test_restack.py index 3129ec5f..620a9063 100644 --- a/tests/test_restack.py +++ b/tests/test_restack.py @@ -2,21 +2,39 @@ from restack import Restack from inkex.tester import ComparisonMixin, TestCase + class RestackBasicTest(ComparisonMixin, TestCase): effect_class = Restack old_defaults = ("--direction=tb", "--xanchor=m", "--yanchor=m") comparisons = [ - ('--tab=positional', '--id=p1', '--id=r3') + old_defaults, - ('--tab=z_order', '--id=p1', '--id=r3') + old_defaults, - ('--tab=z_order', '--id=r3', '--id=p1', '--id=t5', '--id=r2') + old_defaults, - ('--tab=z_order', '--id=r2', '--id=t5', '--id=p1', '--id=r3') + old_defaults, - ('--nb_direction=custom', '--angle=50.0', '--id=s1', '--id=p1', '--id=c3', - '--id=slicerect1') + old_defaults, + ("--tab=positional", "--id=p1", "--id=r3") + old_defaults, + ("--tab=z_order", "--id=p1", "--id=r3") + old_defaults, + ("--tab=z_order", "--id=r3", "--id=p1", "--id=t5", "--id=r2") + old_defaults, + ("--tab=z_order", "--id=r2", "--id=t5", "--id=p1", "--id=r3") + old_defaults, + ( + "--nb_direction=custom", + "--angle=50.0", + "--id=s1", + "--id=p1", + "--id=c3", + "--id=slicerect1", + ) + + old_defaults, ] + class RestackMillimeterGrouped(ComparisonMixin, TestCase): """Test for https://gitlab.com/inkscape/extensions/-/issues/372""" + effect_class = Restack compare_file = "svg/restack_grouped.svg" - comparisons = [('--id=g20858', '--id=g21085', '--id=g20940', '--id=g26580', '--id=g21081', - '--id=g20854'),] \ No newline at end of file + comparisons = [ + ( + "--id=g20858", + "--id=g21085", + "--id=g20940", + "--id=g26580", + "--id=g21081", + "--id=g20854", + ), + ] diff --git a/tests/test_rtree.py b/tests/test_rtree.py index 15bb494f..78ed19da 100644 --- a/tests/test_rtree.py +++ b/tests/test_rtree.py @@ -3,10 +3,11 @@ 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(),] - comparisons = [ - ("--minimum=4.0",) + compare_filters = [ + CompareNumericFuzzy(), ] + comparisons = [("--minimum=4.0",)] diff --git a/tests/test_rubberstretch.py b/tests/test_rubberstretch.py index 94a367da..3324eda6 100644 --- a/tests/test_rubberstretch.py +++ b/tests/test_rubberstretch.py @@ -2,11 +2,12 @@ from rubberstretch import RubberStretch from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase + class TestRubberStretchBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): effect_class = RubberStretch compare_file = "svg/rubber-stretch-test.svg" - comparisons = [('--id=path3997', '--ratio=50', '--curve=0'), - ('--id=path3997', '--ratio=0', '--curve=50'), - ('--id=path3997', '--ratio=25', '--curve=25')] - - + comparisons = [ + ("--id=path3997", "--ratio=50", "--curve=0"), + ("--id=path3997", "--ratio=0", "--curve=50"), + ("--id=path3997", "--ratio=25", "--curve=25"), + ] diff --git a/tests/test_scribus_pdf.py b/tests/test_scribus_pdf.py index 78e2f1ea..d6b2a212 100644 --- a/tests/test_scribus_pdf.py +++ b/tests/test_scribus_pdf.py @@ -6,8 +6,10 @@ 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' + compare_file = "svg/shapes_cmyk.svg" comparisons = [("--pdf-version=13", "--bleed=0")] diff --git a/tests/test_setup_typography_canvas.py b/tests/test_setup_typography_canvas.py index a930332d..3c283038 100644 --- a/tests/test_setup_typography_canvas.py +++ b/tests/test_setup_typography_canvas.py @@ -2,7 +2,10 @@ from setup_typography_canvas import SetupTypographyCanvas from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase -class TestSetupTypographyCanvasBasic(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + +class TestSetupTypographyCanvasBasic( + ComparisonMixin, InkscapeExtensionTestMixin, TestCase +): effect_class = SetupTypographyCanvas - compare_file = ['svg/empty.svg', 'svg/shapes.svg'] + compare_file = ["svg/empty.svg", "svg/shapes.svg"] comparisons = [()] diff --git a/tests/test_spirograph.py b/tests/test_spirograph.py index 01bd6b63..de4ac8ae 100644 --- a/tests/test_spirograph.py +++ b/tests/test_spirograph.py @@ -3,6 +3,7 @@ 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/tests/test_straightseg.py b/tests/test_straightseg.py index 86c69934..5c4165e9 100644 --- a/tests/test_straightseg.py +++ b/tests/test_straightseg.py @@ -3,6 +3,9 @@ from straightseg import SegmentStraightener from inkex.tester import ComparisonMixin, InkscapeExtensionTestMixin, TestCase from inkex.tester.filters import CompareNumericFuzzy, CompareWithPathSpace -class SegmentStraightenerBasicTest(ComparisonMixin, InkscapeExtensionTestMixin, TestCase): + +class SegmentStraightenerBasicTest( + ComparisonMixin, InkscapeExtensionTestMixin, TestCase +): effect_class = SegmentStraightener compare_filters = [CompareNumericFuzzy(), CompareWithPathSpace()] diff --git a/tests/test_svgcalendar.py b/tests/test_svgcalendar.py index 0cff6436..e69739c4 100644 --- a/tests/test_svgcalendar.py +++ b/tests/test_svgcalendar.py @@ -10,92 +10,113 @@ 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 = [("--color-year=#888", "--color-month=#666", "--color-day-name=#999", - "--color-day=#000", "--color-weekend=#777", "--color-nmd=#BBB", - "--color-weeknr=#808080", "--encoding=UTF-8")] - mocks = [ - (datetime, 'datetime', FrozenDateTime) + comparisons = [ + ( + "--color-year=#888", + "--color-month=#666", + "--color-day-name=#999", + "--color-day=#000", + "--color-weekend=#777", + "--color-nmd=#BBB", + "--color-weeknr=#808080", + "--encoding=UTF-8", + ) ] + 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.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') + 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') + 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') + 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']) + 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']) + 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.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.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') + 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/tests/test_svgfont2layers.py b/tests/test_svgfont2layers.py index 743f65ed..65d615ed 100644 --- a/tests/test_svgfont2layers.py +++ b/tests/test_svgfont2layers.py @@ -2,9 +2,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',) - ] + compare_file = "svg/font.svg" + comparisons = [("--count=3",)] diff --git a/tests/test_synfig_output.py b/tests/test_synfig_output.py index 9cbdaf56..52a8c721 100644 --- a/tests/test_synfig_output.py +++ b/tests/test_synfig_output.py @@ -2,5 +2,6 @@ from synfig_output import SynfigExport from inkex.tester import InkscapeExtensionTestMixin, TestCase + class TestSynfigExportBasic(InkscapeExtensionTestMixin, TestCase): effect_class = SynfigExport diff --git a/tests/test_synfig_prepare.py b/tests/test_synfig_prepare.py index 5dacf91d..c0058dc4 100644 --- a/tests/test_synfig_prepare.py +++ b/tests/test_synfig_prepare.py @@ -2,5 +2,6 @@ from synfig_prepare import SynfigPrep from inkex.tester import InkscapeExtensionTestMixin, TestCase + class TestSynfigPrepBasic(InkscapeExtensionTestMixin, TestCase): effect_class = SynfigPrep diff --git a/tests/test_tar_layers.py b/tests/test_tar_layers.py index f334380f..60cf84ff 100644 --- a/tests/test_tar_layers.py +++ b/tests/test_tar_layers.py @@ -3,6 +3,7 @@ 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/tests/test_template.py b/tests/test_template.py index bb732052..bc88ab4a 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -2,12 +2,13 @@ from template import InxDefinedTemplate from inkex.tester import ComparisonMixin, TestCase + class TemplateTestCase(ComparisonMixin, TestCase): effect_class = InxDefinedTemplate - compare_file = 'svg/empty.svg' + 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'), + ("--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/tests/test_template_dvd_cover.py b/tests/test_template_dvd_cover.py index 4641b5ac..ffcd18cf 100644 --- a/tests/test_template_dvd_cover.py +++ b/tests/test_template_dvd_cover.py @@ -2,7 +2,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')] + compare_file = "svg/empty.svg" + comparisons = [("-s", "10", "-b", "10")] diff --git a/tests/test_template_seamless_pattern.py b/tests/test_template_seamless_pattern.py index 4701939d..343c0273 100644 --- a/tests/test_template_seamless_pattern.py +++ b/tests/test_template_seamless_pattern.py @@ -3,7 +3,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')] + comparisons = [("--width=100", "--height=100")] diff --git a/tests/test_text_braille.py b/tests/test_text_braille.py index c483c416..e18b9b07 100644 --- a/tests/test_text_braille.py +++ b/tests/test_text_braille.py @@ -2,6 +2,7 @@ from text_braille import Braille from inkex.tester import ComparisonMixin, TestCase + class TestBrailleBasic(ComparisonMixin, TestCase): effect_class = Braille python3_only = True diff --git a/tests/test_text_extract.py b/tests/test_text_extract.py index 63f6e582..bdf2a6e6 100644 --- a/tests/test_text_extract.py +++ b/tests/test_text_extract.py @@ -3,13 +3,14 @@ from inkex.tester import ComparisonMixin, TestCase from text_extract import Extract from inkex.tester.filters import WindowsTextCompat + class TestExtractBasic(ComparisonMixin, TestCase): effect_class = Extract stderr_output = True comparisons = [ - ('--direction=tb', '--xanchor=center_x', '--yanchor=center_y'), - ('--direction=bt', '--xanchor=center_x', '--yanchor=center_y'), - ('--direction=lr', '--xanchor=center_x', '--yanchor=center_y'), - ('--direction=rl', '--xanchor=center_x', '--yanchor=center_y'), + ("--direction=tb", "--xanchor=center_x", "--yanchor=center_y"), + ("--direction=bt", "--xanchor=center_x", "--yanchor=center_y"), + ("--direction=lr", "--xanchor=center_x", "--yanchor=center_y"), + ("--direction=rl", "--xanchor=center_x", "--yanchor=center_y"), ] compare_filters = [WindowsTextCompat()] diff --git a/tests/test_text_flipcase.py b/tests/test_text_flipcase.py index 97a89ec6..214fc14c 100644 --- a/tests/test_text_flipcase.py +++ b/tests/test_text_flipcase.py @@ -2,6 +2,7 @@ from text_flipcase import FlipCase from inkex.tester import ComparisonMixin, TestCase + class TestFlipCaseBasic(ComparisonMixin, TestCase): effect_class = FlipCase comparisons = [()] diff --git a/tests/test_text_lowercase.py b/tests/test_text_lowercase.py index 9f497123..b6be323b 100644 --- a/tests/test_text_lowercase.py +++ b/tests/test_text_lowercase.py @@ -6,6 +6,7 @@ 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 = [()] diff --git a/tests/test_text_merge.py b/tests/test_text_merge.py index 4dd35e4a..eba832de 100644 --- a/tests/test_text_merge.py +++ b/tests/test_text_merge.py @@ -2,6 +2,7 @@ from text_merge import Merge from inkex.tester import ComparisonMixin, TestCase + class TestMergeBasic(ComparisonMixin, TestCase): effect_class = Merge comparisons = [()] diff --git a/tests/test_text_randomcase.py b/tests/test_text_randomcase.py index 3ce8f18e..d1d2a34b 100644 --- a/tests/test_text_randomcase.py +++ b/tests/test_text_randomcase.py @@ -2,6 +2,7 @@ from inkex.tester import ComparisonMixin, TestCase from text_randomcase import RandomCase + class TestRandomCaseBasic(ComparisonMixin, TestCase): effect_class = RandomCase comparisons = [()] diff --git a/tests/test_text_sentencecase.py b/tests/test_text_sentencecase.py index c0f773bd..11c43eac 100644 --- a/tests/test_text_sentencecase.py +++ b/tests/test_text_sentencecase.py @@ -2,6 +2,7 @@ from inkex.tester import ComparisonMixin, TestCase from text_sentencecase import SentenceCase + class TestSentenceCaseBasic(ComparisonMixin, TestCase): effect_class = SentenceCase comparisons = [()] diff --git a/tests/test_text_split.py b/tests/test_text_split.py index fa6bd757..944387a4 100644 --- a/tests/test_text_split.py +++ b/tests/test_text_split.py @@ -4,20 +4,33 @@ from text_split import TextSplit from inkex.tester import ComparisonMixin, TestCase from inkex.tester.filters import CompareWithoutIds + class TestSplitBasic(ComparisonMixin, TestCase): """Test split effect""" + effect_class = TextSplit compare_filters = [CompareWithoutIds()] compare_file = "svg/text_types.svg" - all_shapes = ('--id=regular', '--id=regular-transform', '--id=inline-size', '--id=kerning', - '--id=flowroot', '--id=flowroot-abs-lineheight', '--id=flowroot-no-lineheight', - '--id=manual-kerns', '--id=rtl', '--id=shape-inside') - comparisons = [all_shapes + ('--splittype=line', '--preserve=True'), #ad3188 - all_shapes + ('--splittype=line', '--preserve=False'), #c242ad - all_shapes + ('--splittype=word', '--preserve=True'), #547875 - all_shapes + ('--splittype=word', '--preserve=False'), #d8b155 - all_shapes + ('--splittype=word', '--preserve=False', '--separation=0.0'), #897ab8 - all_shapes + ('--splittype=letter', '--preserve=True'), #74947d - all_shapes + ('--splittype=letter', '--preserve=False') #dd77d3 - ] + all_shapes = ( + "--id=regular", + "--id=regular-transform", + "--id=inline-size", + "--id=kerning", + "--id=flowroot", + "--id=flowroot-abs-lineheight", + "--id=flowroot-no-lineheight", + "--id=manual-kerns", + "--id=rtl", + "--id=shape-inside", + ) + comparisons = [ + all_shapes + ("--splittype=line", "--preserve=True"), # ad3188 + all_shapes + ("--splittype=line", "--preserve=False"), # c242ad + all_shapes + ("--splittype=word", "--preserve=True"), # 547875 + all_shapes + ("--splittype=word", "--preserve=False"), # d8b155 + all_shapes + + ("--splittype=word", "--preserve=False", "--separation=0.0"), # 897ab8 + all_shapes + ("--splittype=letter", "--preserve=True"), # 74947d + all_shapes + ("--splittype=letter", "--preserve=False"), # dd77d3 + ] print("test") diff --git a/tests/test_text_titlecase.py b/tests/test_text_titlecase.py index 0ad061f2..85fb6556 100644 --- a/tests/test_text_titlecase.py +++ b/tests/test_text_titlecase.py @@ -9,6 +9,7 @@ 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 = [()] @@ -51,17 +52,19 @@ class TitleCaseTest(ComparisonMixin, TestCase): 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")] + 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/tests/test_text_uppercase.py b/tests/test_text_uppercase.py index d77e090f..3b383175 100644 --- a/tests/test_text_uppercase.py +++ b/tests/test_text_uppercase.py @@ -6,6 +6,7 @@ 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 = [()] diff --git a/tests/test_triangle.py b/tests/test_triangle.py index 0c9232e2..b8138c38 100644 --- a/tests/test_triangle.py +++ b/tests/test_triangle.py @@ -3,6 +3,7 @@ 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/tests/test_ungroup_deep.py b/tests/test_ungroup_deep.py index c9b3aa86..7da73ca6 100644 --- a/tests/test_ungroup_deep.py +++ b/tests/test_ungroup_deep.py @@ -3,13 +3,12 @@ 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',) - ] + comparisons = [(), ("--id=layer2",)] + class TestUngroupComplex(ComparisonMixin, TestCase): effect_class = UngroupDeep @@ -19,14 +18,15 @@ class TestUngroupComplex(ComparisonMixin, TestCase): # first one: Paths with clip-path:none (https://gitlab.com/inkscape/extensions/-/issues/184#note_490847336) # second one: Paths with nested transforms (https://gitlab.com/inkscape/extensions/-/issues/340) # third one: Transformed group with transformed clip-path, https://gitlab.com/inkscape/extensions/-/issues/184 - ('--id=g1935', '--id=g6577', '--id=g115') + ("--id=g1935", "--id=g6577", "--id=g115") ] + class TestUngroupComments(ComparisonMixin, TestCase): effect_class = UngroupDeep compare_filters = [CompareOrderIndependentStyle()] compare_file = "svg/ellipse_group_comment.svg" comparisons = [ # Groups with comment child elements (https://gitlab.com/inkscape/extensions/-/issues/405) - ('--id=g13', ) - ] \ No newline at end of file + ("--id=g13",) + ] diff --git a/tests/test_voronoi2svg.py b/tests/test_voronoi2svg.py index 0c27e5d6..5a26adc4 100644 --- a/tests/test_voronoi2svg.py +++ b/tests/test_voronoi2svg.py @@ -3,23 +3,51 @@ 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=c1", "--id=c2", "--id=c3", "--id=p1", "--id=p2", "--id=s1", "--id=u1", - "--diagram-type=Both", "--clip-box=Automatic from seeds", "--show-clip-box=True"), + ( + "--id=c1", + "--id=c2", + "--id=c3", + "--id=p1", + "--id=p2", + "--id=s1", + "--id=u1", + "--diagram-type=Both", + "--clip-box=Automatic from seeds", + "--show-clip-box=True", + ), + ( + "--id=c1", + "--id=c2", + "--id=c3", + "--id=p1", + "--id=p2", + "--id=s1", + "--diagram-type=Voronoi", + "--clip-box=Page", + ), + ( + "--id=r1", + "--id=r3", + "--id=c1", + "--id=c3", + "--id=s1", + "--diagram-type=Both", + "--delaunay-fill-options=delaunay-fill", + ), + ] - ("--id=c1", "--id=c2", "--id=c3", "--id=p1", "--id=p2", "--id=s1", "--diagram-type=Voronoi", - "--clip-box=Page"), - ("--id=r1", "--id=r3", "--id=c1", "--id=c3", "--id=s1", "--diagram-type=Both", - "--delaunay-fill-options=delaunay-fill"), - ] class TestVoronoi2svgmm(ComparisonMixin, TestCase): """Test voronoi for mm based documents (https://gitlab.com/inkscape/extensions/-/issues/403)""" + effect_class = Voronoi compare_file = "svg/interp_shapes.svg" comparisons = [ - tuple(f"--id=path{i}" for i in range(1, 11)) + ("--diagram-type=Voronoi", "--clip-box=Page") + tuple(f"--id=path{i}" for i in range(1, 11)) + + ("--diagram-type=Voronoi", "--clip-box=Page") ] diff --git a/tests/test_web_interactive_mockup.py b/tests/test_web_interactive_mockup.py index c6de0d49..93d70857 100644 --- a/tests/test_web_interactive_mockup.py +++ b/tests/test_web_interactive_mockup.py @@ -2,6 +2,7 @@ from web_interactive_mockup import InteractiveMockup from inkex.tester import ComparisonMixin, TestCase + class TestInkWebInteractiveMockupBasic(ComparisonMixin, TestCase): effect_class = InteractiveMockup - comparisons = [('--id=p1', '--id=r3')] + comparisons = [("--id=p1", "--id=r3")] diff --git a/tests/test_web_set_att.py b/tests/test_web_set_att.py index 69157b68..cb0b58fc 100644 --- a/tests/test_web_set_att.py +++ b/tests/test_web_set_att.py @@ -2,6 +2,7 @@ from web_set_att import SetAttribute from inkex.tester import ComparisonMixin, TestCase + class SetAttributeBasic(ComparisonMixin, TestCase): effect_class = SetAttribute - comparisons = [('--id=p1', '--id=r3', '--att=fill', '--val=red')] + comparisons = [("--id=p1", "--id=r3", "--att=fill", "--val=red")] diff --git a/tests/test_web_transmit_att.py b/tests/test_web_transmit_att.py index 690afaec..9c7bb117 100644 --- a/tests/test_web_transmit_att.py +++ b/tests/test_web_transmit_att.py @@ -2,6 +2,7 @@ from web_transmit_att import TransmitAttribute from inkex.tester import ComparisonMixin, TestCase + class TestInkWebTransmitAttBasic(ComparisonMixin, TestCase): effect_class = TransmitAttribute - comparisons = [('--id=p1', '--id=r3')] + comparisons = [("--id=p1", "--id=r3")] diff --git a/tests/test_webslicer_create_group.py b/tests/test_webslicer_create_group.py index 11b27a08..e5e5e60c 100644 --- a/tests/test_webslicer_create_group.py +++ b/tests/test_webslicer_create_group.py @@ -2,6 +2,7 @@ from webslicer_create_group import CreateGroup from inkex.tester import ComparisonMixin, TestCase + class TestWebSlicerCreateGroupBasic(ComparisonMixin, TestCase): effect_class = CreateGroup - comparisons = [('--id', 'slicerect1')] + comparisons = [("--id", "slicerect1")] diff --git a/tests/test_webslicer_create_rect.py b/tests/test_webslicer_create_rect.py index 979d4862..807c0fa8 100644 --- a/tests/test_webslicer_create_rect.py +++ b/tests/test_webslicer_create_rect.py @@ -2,5 +2,6 @@ from webslicer_create_rect import CreateRect from inkex.tester import ComparisonMixin, TestCase + class TestWebSlicerCreateRectBasic(ComparisonMixin, TestCase): effect_class = CreateRect diff --git a/tests/test_webslicer_export.py b/tests/test_webslicer_export.py index 1d22ad3f..62a2e867 100644 --- a/tests/test_webslicer_export.py +++ b/tests/test_webslicer_export.py @@ -2,10 +2,11 @@ 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 + stderr_protect = False # Cover lack of ImageMagic in CI builder effect_class = Export @property def comparisons(self): - return [('--dir', self.tempdir)] + return [("--dir", self.tempdir)] diff --git a/tests/test_whirl.py b/tests/test_whirl.py index cc2e5838..eb7a5c20 100644 --- a/tests/test_whirl.py +++ b/tests/test_whirl.py @@ -4,7 +4,8 @@ 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', '--whirl=1.0')] + comparisons = [("--id=p1", "--id=r3", "--whirl=1.0")] diff --git a/tests/test_wireframe_sphere.py b/tests/test_wireframe_sphere.py index 428d5042..b35dd05f 100644 --- a/tests/test_wireframe_sphere.py +++ b/tests/test_wireframe_sphere.py @@ -3,6 +3,7 @@ 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/text_braille.py b/text_braille.py index 70a97d83..1ce2bdd0 100755 --- a/text_braille.py +++ b/text_braille.py @@ -6,8 +6,10 @@ import inkex # https://en.wikipedia.org/wiki/Braille_ASCII#Braille_ASCII_values U2800_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""" @@ -18,5 +20,6 @@ class Braille(inkex.TextExtension): return char return chr(mapint + 0x2800) -if __name__ == '__main__': + +if __name__ == "__main__": Braille().run() diff --git a/text_extract.py b/text_extract.py index 2f23271e..212fde9e 100755 --- a/text_extract.py +++ b/text_extract.py @@ -33,38 +33,49 @@ 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'}) +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="lr", help="direction to extract text") - pars.add_argument("-x", "--xanchor", default="left", help="horiz point to compare") - pars.add_argument("-y", "--yanchor", default="top", help="vertical point to compare") + pars.add_argument( + "-d", "--direction", default="lr", help="direction to extract text" + ) + pars.add_argument( + "-x", "--xanchor", default="left", help="horiz point to compare" + ) + pars.add_argument( + "-y", "--yanchor", default="top", 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), key=self._sort): + for node in sorted( + self.svg.selection.get(TextElement, FlowRoot), 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) + 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'): + if child.get("sodipodi:role"): child.tail = "\n" - inkex.errormsg(tostring(node, encoding='unicode', method='text').strip()) + inkex.errormsg(tostring(node, encoding="unicode", method="text").strip()) else: for child in node: self.recurse(child) -if __name__ == '__main__': +if __name__ == "__main__": Extract().run() diff --git a/text_flipcase.py b/text_flipcase.py index 13043fd5..c1cc4e6d 100755 --- a/text_flipcase.py +++ b/text_flipcase.py @@ -3,11 +3,14 @@ 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__': + +if __name__ == "__main__": FlipCase().run() diff --git a/text_lowercase.py b/text_lowercase.py index a213c02d..866c50f7 100755 --- a/text_lowercase.py +++ b/text_lowercase.py @@ -3,10 +3,13 @@ import inkex + class Lowercase(inkex.TextExtension): """Convert to lowercase""" + def process_chardata(self, text): return text.lower() -if __name__ == '__main__': + +if __name__ == "__main__": Lowercase().run() diff --git a/text_merge.py b/text_merge.py index 8b5c89b0..08e570a2 100755 --- a/text_merge.py +++ b/text_merge.py @@ -28,28 +28,38 @@ Merge text blocks together. import inkex from inkex.utils import KeyDict -from inkex import ( - Rectangle, FlowRoot, FlowPara, FlowRegion, TextElement, Tspan -) +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'}) +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="lr", help="direction to merge text") - pars.add_argument("-x", "--xanchor", default="left", help="horiz point to compare") - pars.add_argument("-y", "--yanchor", default="top", help="vertical point to compare") + pars.add_argument( + "-d", "--direction", default="lr", help="direction to merge text" + ) + pars.add_argument( + "-x", "--xanchor", default="left", help="horiz point to compare" + ) + pars.add_argument( + "-y", "--yanchor", default="top", 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") + 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 + for node in self.svg.xpath("//svg:text | //svg:flowRoot"): + self.svg.selected[node.get("id")] = node if not self.svg.selected: return @@ -64,17 +74,17 @@ class Merge(inkex.EffectExtension): text_span = Tspan text_root = parentnode.add(text_element()) - text_root.set('xml:space', 'preserve') + 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' + "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): @@ -82,26 +92,27 @@ class Merge(inkex.EffectExtension): if self.options.flowtext: region = text_root.add(FlowRegion()) - region.set('xml:space', 'preserve') + region.set("xml:space", "preserve") rect = region.add(Rectangle()) - rect.set('xml:space', 'preserve') - rect.set('height', 200) - rect.set('width', 200) + 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) + 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("xml:space", "preserve") - newspan.set('sodipodi:role', node.get('sodipodi:role')) + newspan.set("sodipodi:role", node.get("sodipodi:role")) if isinstance(node, (TextElement, FlowPara)): - newspan.set('sodipodi:role', 'line') + newspan.set("sodipodi:role", "line") if self.options.keepstyle: newspan.style = node.style @@ -114,5 +125,5 @@ class Merge(inkex.EffectExtension): newspan.tail = node.tail -if __name__ == '__main__': +if __name__ == "__main__": Merge().run() diff --git a/text_randomcase.py b/text_randomcase.py index 208cade6..5a917ece 100755 --- a/text_randomcase.py +++ b/text_randomcase.py @@ -4,8 +4,10 @@ import random import inkex + class RandomCase(inkex.TextExtension): """Randomise the case of the text (with bias)""" + previous_case = 1 def map_char(self, char): @@ -25,5 +27,6 @@ class RandomCase(inkex.TextExtension): return char.lower() return char -if __name__ == '__main__': + +if __name__ == "__main__": RandomCase().run() diff --git a/text_sentencecase.py b/text_sentencecase.py index e63430db..b8bb1360 100755 --- a/text_sentencecase.py +++ b/text_sentencecase.py @@ -3,14 +3,16 @@ 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 '.!?': + if char in ".!?": self.was_punctuation = True elif ((char.isspace() or self.newline) and self.was_punctuation) or self.newpar: self.sentence_start = True @@ -31,5 +33,6 @@ class SentenceCase(inkex.TextExtension): return char.lower() return char -if __name__ == '__main__': + +if __name__ == "__main__": SentenceCase().run() diff --git a/text_split.py b/text_split.py index 78a8a861..1b1b0c17 100755 --- a/text_split.py +++ b/text_split.py @@ -45,8 +45,10 @@ from inkex.localization import inkex_gettext as _ TextLike = Union[FlowRoot, TextElement] + class TextSplit(inkex.EffectExtension): """Split text up.""" + def __init__(self): """Initialize State machine""" super().__init__() @@ -58,15 +60,31 @@ class TextSplit(inkex.EffectExtension): self.process_kerns: bool = True self.current_root: TextLike self.current_fontsize: float = 0 + def add_arguments(self, pars): pars.add_argument("--tab", help="The selected UI tab when OK was pressed") - pars.add_argument("-t", "--splittype", default="line", choices=["letter", "word", "line"], - help="type of split") - pars.add_argument("-p", "--preserve", type=inkex.Boolean, default=True, - help="Preserve original") - pars.add_argument("-s", "--separation", type=float, default=1, - help="Threshold for separating text with manual kerns in multiples of" - "font-size") + pars.add_argument( + "-t", + "--splittype", + default="line", + choices=["letter", "word", "line"], + help="type of split", + ) + pars.add_argument( + "-p", + "--preserve", + type=inkex.Boolean, + default=True, + help="Preserve original", + ) + pars.add_argument( + "-s", + "--separation", + type=float, + default=1, + help="Threshold for separating text with manual kerns in multiples of" + "font-size", + ) def effect(self): """Applies the effect""" @@ -93,7 +111,7 @@ class TextSplit(inkex.EffectExtension): if not preserve and node is not None: elem.getparent().remove(elem) except TypeError as err: - inkex.errormsg(err) # if an element can not be processed + inkex.errormsg(err) # if an element can not be processed @staticmethod def get_font_size(element): @@ -102,28 +120,30 @@ class TextSplit(inkex.EffectExtension): @staticmethod def get_line_height(element: ShapeElement): - """ get the line height of an element""" + """get the line height of an element""" return element.get_line_height_uu() def simplify_child_tspans(self, element: TextElement): """Checks all child tspans if they have manual kerns. If it does, try to find words (characters with a distance > separation * font-size). - Then concatenate the words with spaces, set this string as a new text and """ + Then concatenate the words with spaces, set this string as a new text and""" for child in list(element): # process manual kerns if not isinstance(child, Tspan): continue - xvals = list(map(float, filter(len, regex.split(r"[,\s]", child.get("x") or "")))) + xvals = list( + map(float, filter(len, regex.split(r"[,\s]", child.get("x") or ""))) + ) content = child.text if content not in [None, ""] and len(xvals) >= 2: fsize = self.get_font_size(child) - separation = self.separation*fsize + separation = self.separation * fsize current_word_start = 0 for i in range(1, max(len(content), len(xvals))): - if i >= len(content) -1 or i >= len(xvals) -1: + if i >= len(content) - 1 or i >= len(xvals) - 1: # consume the entire remaining string i = len(content) - if i == len(content) or abs(xvals[i] - xvals[i-1]) > separation: + if i == len(content) or abs(xvals[i] - xvals[i - 1]) > separation: wordspan = Tspan(x=str(xvals[current_word_start])) wordspan.text = content[current_word_start:i] child.add(wordspan) @@ -145,14 +165,18 @@ class TextSplit(inkex.EffectExtension): oldelement.addnext(element) element.style = oldelement.style element.transform = oldelement.transform - flowref = oldelement.findone('svg:flowRegion')[0] + flowref = oldelement.findone("svg:flowRegion")[0] if isinstance(flowref, Rectangle): flowx = element.unittouu(flowref.get("x")) flowy = element.unittouu(float(flowref.get("y"))) first = True else: - raise TypeError(_("Element {} uses a flow region that is not a rectangle. " - "First unflow text.".format(element.get_id()))) + raise TypeError( + _( + "Element {} uses a flow region that is not a rectangle. " + "First unflow text.".format(element.get_id()) + ) + ) for child in oldelement: if isinstance(child, FlowPara): # convert the flowpara "line" (note: no automatic wrapping) @@ -211,14 +235,14 @@ class TextSplit(inkex.EffectExtension): # the element will be appended to the parent of element, but there might be nested # tspans between the prototype and the element. The next line says # "compose transforms until you reach the parent of element" - elem.transform = (- self.current_root.getparent().transform) \ - @ prototype.composed_transform() + elem.transform = ( + -self.current_root.getparent().transform + ) @ prototype.composed_transform() tsp = Tspan(x=str(self.current_x), y=str(self.current_y)) tsp.text = text elem.add(tsp) self.current_root.addnext(elem) - def split_lines(self, element: TextLike) -> TextElement: """Splits a text into its lines""" self.process_kerns = False @@ -233,14 +257,16 @@ class TextSplit(inkex.EffectExtension): def process_plain_text(self, element, splitted): """Appends new text elements to as sibling root for each element of splitted, starting at self.current_x, self.current_y, incrementing those, with prototype element (that - styles and transforms will be taken from) """ + styles and transforms will be taken from)""" if splitted is None: return for word in splitted: if word != "": self.append_splitted_element(word, element) # +1 since for words, we lost a space - self.current_x += self.current_fontsize * (len(word) + 1) * self.fs_multiplier + self.current_x += ( + self.current_fontsize * (len(word) + 1) * self.fs_multiplier + ) def process_plain_words(self, element, text): """Calls process_plain_text for splitting words""" @@ -253,14 +279,18 @@ class TextSplit(inkex.EffectExtension): self.fs_multiplier = 0.25 self.process_plain_text(element, text) - def split_words_or_chars(self, element: TextLike) -> TextElement: """Splits a text into its lines""" self.process_kerns = True preprocessed = self.preprocess_text_element(element) + def process_element(element) -> float: - elem_coords = {i: element.root.unittouu(element.get(i)) - if element.get(i) is not None else None for i in "xy"} + elem_coords = { + i: element.root.unittouu(element.get(i)) + if element.get(i) is not None + else None + for i in "xy" + } if elem_coords["x"] is not None: self.current_x = elem_coords["x"] if elem_coords["y"] is not None: @@ -279,5 +309,5 @@ class TextSplit(inkex.EffectExtension): return preprocessed -if __name__ == '__main__': +if __name__ == "__main__": TextSplit().run() diff --git a/text_titlecase.py b/text_titlecase.py index a3c74dab..818d3534 100755 --- a/text_titlecase.py +++ b/text_titlecase.py @@ -3,8 +3,10 @@ import inkex + class TitleCase(inkex.TextExtension): """To titlecase""" + word_ended = True def process_chardata(self, text): @@ -26,5 +28,6 @@ class TitleCase(inkex.TextExtension): return ret -if __name__ == '__main__': + +if __name__ == "__main__": TitleCase().run() diff --git a/text_uppercase.py b/text_uppercase.py index 9f1539e0..9b6fb6e3 100755 --- a/text_uppercase.py +++ b/text_uppercase.py @@ -3,10 +3,13 @@ import inkex + class Uppercase(inkex.TextExtension): """To upper case""" + def process_chardata(self, text): return text.upper() -if __name__ == '__main__': + +if __name__ == "__main__": Uppercase().run() diff --git a/tools/generate_argparse_conf.py b/tools/generate_argparse_conf.py index fda91451..d99337b7 100644 --- a/tools/generate_argparse_conf.py +++ b/tools/generate_argparse_conf.py @@ -4,8 +4,10 @@ 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') +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): @@ -14,47 +16,54 @@ 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)') +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': + if node.attrib["type"] == "float": cmd = templateWithType.substitute( - param=node.attrib["name"], - type='float', - default=node.text) + param=node.attrib["name"], type="float", default=node.text + ) print(cmd) - elif node.attrib["type"] == 'int': + elif node.attrib["type"] == "int": cmd = templateWithType.substitute( - param=node.attrib["name"], - type='int', - default=node.text) + param=node.attrib["name"], type="int", default=node.text + ) print(cmd) - elif node.attrib["type"] == 'boolean': + elif node.attrib["type"] == "boolean": cmd = templateWithType.substitute( param=node.attrib["name"], - type='inkex.inkbool', - default='"' + node.text + '"') + type="inkex.inkbool", + default='"' + node.text + '"', + ) print(cmd) - elif node.attrib["type"] == 'enum': + elif node.attrib["type"] == "enum": cmd = templateWithoutType.substitute( - param=node.attrib["name"], - default='"' + node[0].text + '"') + param=node.attrib["name"], default='"' + node[0].text + '"' + ) print(cmd) - elif node.attrib["type"] == 'notebook': + elif node.attrib["type"] == "notebook": cmd = templateWithoutType.substitute( - param=node.attrib["name"], - default='"' + node[0].attrib["name"] + '"') + 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'): + 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 +process_node(root) diff --git a/triangle.py b/triangle.py index dc8d6f21..d8abb97b 100755 --- a/triangle.py +++ b/triangle.py @@ -40,16 +40,33 @@ 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'} + 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'}) + 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 @@ -76,12 +93,18 @@ 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 +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 +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) @@ -92,11 +115,14 @@ def draw_tri_from_3_sides(s_a, s_b, s_c, offset, width, parent): # draw a trian 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 + 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) + draw_SVG_tri(a, b, c, offset, width, "Triangle", parent) else: - inkex.errormsg('Invalid Triangle Specifications.') + inkex.errormsg("Invalid Triangle Specifications.") class Triangle(inkex.EffectExtension): @@ -107,23 +133,23 @@ class Triangle(inkex.EffectExtension): 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") + 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') + 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': + 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': + 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 @@ -131,12 +157,14 @@ class Triangle(inkex.EffectExtension): 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': + 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 + 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 @@ -148,20 +176,26 @@ class Triangle(inkex.EffectExtension): a_c = pi - a_a - a_b error = False else: - sys.stderr.write('Error:Invalid Triangle Specifications.\n') # signal an error + 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 + 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 + 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': + 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 @@ -172,7 +206,7 @@ class Triangle(inkex.EffectExtension): draw_tri_from_3_sides(s_a, s_b, s_c, offset, stroke_width, tri) - elif self.options.mode == 's_c_a_ab': + 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 @@ -184,5 +218,5 @@ class Triangle(inkex.EffectExtension): draw_tri_from_3_sides(s_a, s_b, s_c, offset, stroke_width, tri) -if __name__ == '__main__': +if __name__ == "__main__": Triangle().run() diff --git a/ungroup_deep.py b/ungroup_deep.py index d561aaa3..6b578747 100755 --- a/ungroup_deep.py +++ b/ungroup_deep.py @@ -8,18 +8,33 @@ 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, + 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") + 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): @@ -76,8 +91,10 @@ class UngroupDeep(inkex.EffectExtension): # 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') + 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(): new_clippath.add(Use.new(child, 0, 0)) @@ -100,7 +117,7 @@ class UngroupDeep(inkex.EffectExtension): node_style = node.style node_transform = node.transform - node_clippathurl = node.get('clip-path') + node_clippathurl = node.get("clip-path") for child in reversed(list(node)): if not isinstance(child, inkex.BaseElement): continue @@ -114,11 +131,12 @@ class UngroupDeep(inkex.EffectExtension): # 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): + 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 @@ -127,16 +145,13 @@ class UngroupDeep(inkex.EffectExtension): # 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}] + q = [{"node": node, "depth": 0, "prev": {"height": None}, "height": None}] while q: current = q[-1] - node = current['node'] - depth = current['depth'] - height = current['height'] + node = current["node"] + depth = current["depth"] + height = current["height"] # Recursion path if height is None: @@ -146,14 +161,20 @@ class UngroupDeep(inkex.EffectExtension): # Base case: Leaf node if not isinstance(node, Group) or not list(node): - current['height'] = 0 + 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}) + q.append( + { + "node": child, + "prev": current, + "depth": depth, + "height": None, + } + ) # Return path else: @@ -163,10 +184,10 @@ class UngroupDeep(inkex.EffectExtension): # Propagate (max) height up the call chain height += 1 - previous = current['prev'] - prev_height = previous['height'] + previous = current["prev"] + prev_height = previous["height"] if prev_height is None or prev_height < height: - previous['height'] = height + previous["height"] = height # Only process each node once q.pop() @@ -180,5 +201,5 @@ class UngroupDeep(inkex.EffectExtension): self._deep_ungroup(node) -if __name__ == '__main__': +if __name__ == "__main__": UngroupDeep().run() diff --git a/voronoi.py b/voronoi.py index f7d8703f..6015c7ec 100755 --- a/voronoi.py +++ b/voronoi.py @@ -101,8 +101,10 @@ 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?") @@ -124,6 +126,7 @@ class CmpMixin(object): def __ge__(self, other): return self.__cmp__(other) in (0, 1) + # ------------------------------------------------------------------ class Context(object): def __init__(self): @@ -132,8 +135,12 @@ class Context(object): 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.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): @@ -167,14 +174,27 @@ class Context(object): 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)) + 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)) + 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) @@ -193,8 +213,8 @@ class Context(object): if self.plot: self.clip_line(edge) elif self.doPrint: - print("e %d" % edge.edgenum, end=' ') - print(" %d " % sitenumL, end=' ') + print("e %d" % edge.edgenum, end=" ") + print(" %d " % sitenumL, end=" ") print("%d" % sitenumR) @@ -447,7 +467,7 @@ class Halfedge(CmpMixin): print("right: ", self.right) print("edge: ", self.edge) print("pm: ", self.pm) - print("vertex: ", end=' ') + print("vertex: ", end=" ") if self.vertex: self.vertex.dump() else: @@ -509,7 +529,9 @@ class Halfedge(CmpMixin): 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) + 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 @@ -550,8 +572,7 @@ class Halfedge(CmpMixin): e = e2 rightOfSite = xint >= e.reg[1].x - if ((rightOfSite and he.pm == Edge.LE) or - (not rightOfSite and he.pm == Edge.RE)): + 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 @@ -771,17 +792,17 @@ class SiteList(object): # ------------------------------------------------------------------ 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. + """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. """ Edge.EDGE_NUM = 0 siteList = SiteList(points) @@ -792,9 +813,9 @@ def computeVoronoiDiagram(points): # ------------------------------------------------------------------ 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. + """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. """ Edge.EDGE_NUM = 0 siteList = SiteList(points) @@ -825,7 +846,7 @@ if __name__ == "__main__": pts = [] fp = sys.stdin if len(args) > 0: - fp = open(args[0], 'r') + fp = open(args[0], "r") for line in fp: fld = line.split() x = float(fld[0]) diff --git a/voronoi2svg.py b/voronoi2svg.py index 4b35d7cb..fdf509cc 100755 --- a/voronoi2svg.py +++ b/voronoi2svg.py @@ -33,27 +33,39 @@ 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("--tab") pars.add_argument( - '--diagram-type', - default='Voronoi', dest='diagramType', - choices=['Voronoi', 'Delaunay', 'Both'], - help='Defines the type of the diagram') + "--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') + "--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') + "--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') + "--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""" @@ -71,9 +83,7 @@ class Voronoi(inkex.EffectExtension): 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 + 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 @@ -87,7 +97,7 @@ class Voronoi(inkex.EffectExtension): # record intersections of the line with bounding box edges if edge[0] >= len(lines): return [] - line = (lines[edge[0]]) + line = lines[edge[0]] interpoints = [] for i in range(4): pnt = self.intersect_line_segment(line, bbc[i], bbc[(i + 1) % 4]) @@ -154,19 +164,19 @@ class Voronoi(inkex.EffectExtension): return linestyle = { - 'stroke': '#000000', - 'stroke-width': str(self.svg.to_dimensionless('1px')), - 'fill': 'none', - 'stroke-linecap': 'round', - 'stroke-linejoin': 'round' + "stroke": "#000000", + "stroke-width": str(self.svg.to_dimensionless("1px")), + "fill": "none", + "stroke-linecap": "round", + "stroke-linejoin": "round", } facestyle = { - 'stroke': '#000000', - 'stroke-width': str(self.svg.to_dimensionless('1px')), - 'fill': 'none', - 'stroke-linecap': 'round', - 'stroke-linejoin': 'round' + "stroke": "#000000", + "stroke-width": str(self.svg.to_dimensionless("1px")), + "fill": "none", + "stroke-linecap": "round", + "stroke-linejoin": "round", } parent_group = self.svg.selection.first().getparent() @@ -192,42 +202,46 @@ class Voronoi(inkex.EffectExtension): point = trans.apply_to_point(point) pts.append(Point(*point)) if self.options.delaunayFillOptions != "delaunay-no-fill": - fills.append(node.style.get('fill', 'none')) + 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': + if self.options.diagramType != "Delaunay": # Voronoi group_voronoi = parent_group.add(Group()) - group_voronoi.set('inkscape:label', 'Voronoi') + group_voronoi.set("inkscape:label", "Voronoi") if invtrans: group_voronoi.transform @= invtrans - if self.options.diagramType != 'Voronoi': + if self.options.diagramType != "Voronoi": # Delaunay group_delaunay = parent_group.add(Group()) - group_delaunay.set('inkscape:label', 'Delaunay') + group_delaunay.set("inkscape:label", "Delaunay") # Clipping box handling - if self.options.diagramType != 'Delaunay': + 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': + if self.options.clip_box == "Page": width = self.svg.viewbox_width height = self.svg.viewbox_height clip_box = (0, width, 0, height) else: - clip_box = (group_bbox.left, - group_bbox.right, - group_bbox.top, - group_bbox.bottom) + clip_box = ( + group_bbox.left, + group_bbox.right, + group_bbox.top, + group_bbox.bottom, + ) # 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]) + 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])) @@ -236,14 +250,14 @@ class Voronoi(inkex.EffectExtension): 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.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': + if self.options.diagramType != "Delaunay": vertices, lines, edges = voronoi.computeVoronoiDiagram(pts) for edge in edges: vindex1, vindex2 = edge[1:] @@ -255,12 +269,12 @@ class Voronoi(inkex.EffectExtension): if len(segment) > 1: x1, y1 = segment[0] x2, y2 = segment[1] - cmds = [['M', [x1, y1]], ['L', [x2, y2]]] + cmds = [["M", [x1, y1]], ["L", [x2, y2]]] path = group_voronoi.add(PathElement()) - path.set('d', str(inkex.Path(cmds))) + path.set("d", str(inkex.Path(cmds))) path.style = linestyle - if self.options.diagramType != 'Voronoi': + if self.options.diagramType != "Voronoi": triangles = voronoi.computeDelaunayTriangulation(seeds) i = 0 if self.options.delaunayFillOptions == "delaunay-fill": @@ -269,23 +283,28 @@ class Voronoi(inkex.EffectExtension): 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": + 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.to_dimensionless('0.005px')), - 'fill': fills[triangle[random.randrange(0, 2)]], - 'stroke-linecap': 'round', - 'stroke-linejoin': 'round' + "stroke": fills[triangle[random.randrange(0, 2)]], + "stroke-width": str(self.svg.to_dimensionless("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.set("d", str(inkex.Path(cmds))) path.style = facestyle i += 1 + if __name__ == "__main__": Voronoi().run() diff --git a/web_interactive_mockup.py b/web_interactive_mockup.py index ef24d1b1..1af5e925 100755 --- a/web_interactive_mockup.py +++ b/web_interactive_mockup.py @@ -19,24 +19,30 @@ 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( + "--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.") + 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] +"'})" + 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 '') + el_ev_code = ev_code + ";" + (prev_ev_code or "") elem.set(self.options.when, el_ev_code) -if __name__ == '__main__': + +if __name__ == "__main__": InteractiveMockup().run() diff --git a/web_set_att.py b/web_set_att.py index 9ec72165..2e6798fe 100755 --- a/web_set_att.py +++ b/web_set_att.py @@ -29,16 +29,23 @@ 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 stroke stroke-width", help="Attribute to set.") + pars.add_argument( + "--att", default="fill stroke stroke-width", help="Attribute to set." + ) pars.add_argument("--val", default="red black 5px", 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.") + pars.add_argument( + "--compatibility", + default="append", + help="Compatibility with previews code to this event.", + ) def effect(self): self.ensureInkWebSupport() @@ -52,20 +59,22 @@ class SetAttribute(inkwebeffect.InkWebEffect): 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) + "','".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': + if self.options.compatibility == "append": el_ev_code = prev_ev_code + ";\n" + ev_code - if self.options.compatibility == 'prepend': + if self.options.compatibility == "prepend": el_ev_code = ev_code + ";\n" + prev_ev_code - if self.options.compatibility == 'replace': + if self.options.compatibility == "replace": el_ev_code = ev_code elem.set(self.options.when, el_ev_code) -if __name__ == '__main__': + +if __name__ == "__main__": SetAttribute().run() diff --git a/web_transmit_att.py b/web_transmit_att.py index 7ac3a04f..922739a6 100755 --- a/web_transmit_att.py +++ b/web_transmit_att.py @@ -22,14 +22,18 @@ 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.") + pars.add_argument( + "--compatibility", + default="append", + help="Compatibility with previews code to this event.", + ) def effect(self): self.ensureInkWebSupport() @@ -42,16 +46,19 @@ class TransmitAttribute(inkwebeffect.InkWebEffect): el_from = list(self.svg.selection)[:split] id_to = list(self.svg.selection.ids)[split:] - ev_code = "InkWeb.transmitAtt({{from:this, to:['{}'], att:'{}'}})".format("','".join(id_to), self.options.att) + 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': + if self.options.compatibility == "append": el_ev_code = prev_ev_code + ";\n" + ev_code - if self.options.compatibility == 'prepend': + if self.options.compatibility == "prepend": el_ev_code = ev_code + ";\n" + prev_ev_code - if self.options.compatibility == 'replace': + if self.options.compatibility == "replace": el_ev_code = ev_code elem.set(self.options.when, el_ev_code) -if __name__ == '__main__': + +if __name__ == "__main__": TransmitAttribute().run() diff --git a/webslicer_create_group.py b/webslicer_create_group.py index ad253eb7..1c02fab7 100755 --- a/webslicer_create_group.py +++ b/webslicer_create_group.py @@ -22,8 +22,10 @@ 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") @@ -34,23 +36,30 @@ class CreateGroup(WebSlicerMixin, inkex.EffectExtension): def effect(self): if not self.svg.selected: - raise inkex.AbortExtension(_('You must to select some "Slicer rectangles" ' - 'or other "Layout groups".')) + 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(_(f'The element "{key}" is not in the Web Slicer layer')) + raise inkex.AbortExtension( + _(f'The element "{key}" is not in the Web Slicer layer') + ) 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']) + ["html_id", "html_class", "width_unity", "height_unity", "bg_color"] + ) for node in self.svg.selected.values(): group.insert(1, node) -if __name__ == '__main__': +if __name__ == "__main__": CreateGroup().run() diff --git a/webslicer_create_rect.py b/webslicer_create_rect.py index 9ce1bd98..c92a5546 100755 --- a/webslicer_create_rect.py +++ b/webslicer_create_rect.py @@ -22,6 +22,7 @@ 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") @@ -43,17 +44,17 @@ class CreateRect(WebSlicerMixin, inkex.EffectExtension): name = self.options.name el = self.svg.xpath('//*[@id="' + name + '"]') if len(el) > 0: - if name[-3:] == '-00': + if name[-3:] == "-00": name = name[:-3] num = 0 - num_s = '00' + 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 + 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() @@ -61,22 +62,22 @@ class CreateRect(WebSlicerMixin, inkex.EffectExtension): self.options.dimension def effect(self): - scale = self.svg.unittouu('1px') # convert to document units + 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') + rect = etree.SubElement(layer, "rect") if is_empty(self.options.name): - self.options.name = 'slice-00' + 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') + 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" @@ -85,17 +86,24 @@ class CreateRect(WebSlicerMixin, inkex.EffectExtension): 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' - ]) + 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__': + +if __name__ == "__main__": CreateRect().run() diff --git a/webslicer_effect.py b/webslicer_effect.py index 6ba11c9b..ae59d720 100755 --- a/webslicer_effect.py +++ b/webslicer_effect.py @@ -23,21 +23,23 @@ Common elements between webslicer extensions import inkex from inkex import Group + def is_empty(val): - return val in ('', None) + 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"]') + '//*[@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') + layer = Group(id="webslicer-layer") + layer.set("inkscape:label", "Web Slicer") + layer.set("inkscape:groupmode", "layer") self.document.getroot().append(layer) else: layer = None @@ -48,6 +50,6 @@ class WebSlicerMixin(object): for att in conf_atts: if not is_empty(getattr(self.options, att)): conf_list.append( - att.replace('_', '-') + ': ' + str(getattr(self.options, att)) + att.replace("_", "-") + ": " + str(getattr(self.options, att)) ) return "\n".join(conf_list) diff --git a/webslicer_export.py b/webslicer_export.py index 578cf26d..7a3eb52a 100755 --- a/webslicer_export.py +++ b/webslicer_export.py @@ -27,6 +27,7 @@ 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") @@ -34,14 +35,16 @@ class Export(WebSlicerMixin, inkex.OutputExtension): 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}' + 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.')) + 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] == '\\': + 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): @@ -50,13 +53,17 @@ class Export(WebSlicerMixin, inkex.OutputExtension): try: os.makedirs(self.options.dir) except Exception as e: - raise inkex.AbortExtension(_("Can't create '{}': {}.".format(self.options.dir, e))) + raise inkex.AbortExtension( + _("Can't create '{}': {}.".format(self.options.dir, e)) + ) else: - raise inkex.AbortExtension(_("Dir doesn't exist '{}'.".format(self.options.dir))) + 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.')) + raise inkex.AbortExtension(_("No slicer layer found.")) else: self.unique_html_id(slicer_layer) return None @@ -70,7 +77,7 @@ class Export(WebSlicerMixin, inkex.OutputExtension): sts = pipe.returncode if sts is None: sts = 0 - if stdout is not None and stdout[-1:] == '\n': + if stdout is not None and stdout[-1:] == "\n": stdout = stdout[:-1] return sts, stdout @@ -78,22 +85,30 @@ class Export(WebSlicerMixin, inkex.OutputExtension): 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']: + 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'])) + 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: + while (conf["html-id"] + "-" + str(n)) in self._html_ids: n += 1 - conf['html-id'] += "-" + str(n) - self._html_ids.append(conf['html-id']) + 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) + (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() @@ -101,7 +116,7 @@ class Export(WebSlicerMixin, inkex.OutputExtension): if error: return error # Register the basic CSS code: - self.reg_css('body', 'text-align', 'center') + 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! @@ -115,36 +130,38 @@ class Export(WebSlicerMixin, inkex.OutputExtension): # 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 = 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') + "\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 = 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()) + "/*\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') + (ref, self.tmp_svg) = tempfile.mkstemp(".svg") layer = self.get_slicer_layer() current_style = layer.style - layer.style = 'display:none' + layer.style = "display:none" self.document.write(self.tmp_svg) layer.style = current_style @@ -157,140 +174,154 @@ class Export(WebSlicerMixin, inkex.OutputExtension): noid_element_count = 0 def get_el_conf(self, el): - desc = el.find(self.svgNS + 'desc') + desc = el.find(self.svgNS + "desc") conf = {} if desc is None: - desc = etree.SubElement(el, 'desc') + desc = etree.SubElement(el, "desc") if desc.text is None: - desc.text = '' + desc.text = "" for line in desc.text.split("\n"): - if line.find(':') > 0: - line = line.split(':') + if line.find(":") > 0: + line = line.split(":") conf[line[0].strip()] = line[1].strip() - if not 'html-id' in conf: + if not "html-id" in conf: if el == self.get_slicer_layer(): - return {'html-id': '#body#'} + 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'] + 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') + 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]) + 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'] + 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 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 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') + 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.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' + 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' + 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' + 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)) + 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') + 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']) + 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') + 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') + 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 + 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(',') + 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} + 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. @@ -300,87 +331,110 @@ class Export(WebSlicerMixin, inkex.OutputExtension): 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'] + 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' + geometry[k] = str(int(geometry[k])) + "px" return geometry def img_name(self, el, conf): - return el.attrib['id'] + '.' + conf['format'] + 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' + 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 "{}" -o "{}" "{}"'.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' + 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 "{}" -o "{}" "{}"'.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)) + 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) + 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'] + parent_id = self.get_el_conf(parent)["html-id"] if parent == self.get_slicer_layer(): - parent_id = 'body' + parent_id = "body" conf = self.get_el_conf(el) - el_id = conf['html-id'] - if 'html-class' in conf: - el_class = conf['html-class'] + el_id = conf["html-id"] + if "html-class" in conf: + el_class = conf["html-class"] else: - el_class = '' + 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}) + self._html[parent_id].append({"tag": el_tag, "id": el_id, "class": el_class}) - def html_code(self, parent='body', ident=' '): + def html_code(self, parent="body", ident=" "): # inkex.errormsg( self._html ) if not parent in self._html: - return '' - code = '' + 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' + 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' + 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' + code += ident + " Element " + el["id"] + "\n" + code += ident + "\n' return code _css = [] @@ -389,26 +443,27 @@ class Export(WebSlicerMixin, inkex.OutputExtension): pos = i = -1 for s in self._css: i += 1 - if s['selector'] == selector: + 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) + 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 = '' + 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' + 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__': + +if __name__ == "__main__": Export().run() diff --git a/whirl.py b/whirl.py index 23bd5f16..f347f610 100755 --- a/whirl.py +++ b/whirl.py @@ -22,13 +22,21 @@ 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=5.0, help="amount of whirl") - pars.add_argument("-r", "--rotation", type=inkex.Boolean,\ - default=True, help="direction of rotation") + pars.add_argument( + "-t", "--whirl", type=float, default=5.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 @@ -50,12 +58,12 @@ class Whirl(inkex.EffectExtension): 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] = dist * math.cos(theta) + point[1] = dist * math.sin(theta) point[0] += center[0] point[1] += center[1] node.path = path -if __name__ == '__main__': +if __name__ == "__main__": Whirl().run() diff --git a/wireframe_sphere.py b/wireframe_sphere.py index 70089f29..7c4142d3 100755 --- a/wireframe_sphere.py +++ b/wireframe_sphere.py @@ -60,7 +60,8 @@ EPSILON = 0.001 class WireframeSphere(inkex.GenerateExtension): """Writeframe extension, generate a wireframe""" - container_label = 'WireframeSphere' + + container_label = "WireframeSphere" def container_transform(self): transform = super(WireframeSphere, self).container_transform() @@ -74,17 +75,19 @@ class WireframeSphere(inkex.GenerateExtension): 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) + 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.') + inkex.errormsg("Please enter an even number of lines of longitude.") return - radius = self.svg.unittouu(str(opt.RADIUS) + 'px') + 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 @@ -105,10 +108,10 @@ class WireframeSphere(inkex.GenerateExtension): """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') + grp_long.set("inkscape:label", "Lines of Longitude") # angle between neighbouring lines of longitude in degrees - #delta_long = 360.0 / number + # delta_long = 360.0 / number for i in range(0, number // 2): # The longitude of this particular line in radians @@ -124,8 +127,7 @@ class WireframeSphere(inkex.GenerateExtension): # 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)) + (radius * sin(long_angle) * sin(tilt)) / (radius * cos(long_angle)) ) * (180.0 / pi) # remove the hidden side of the ellipses if required @@ -148,20 +150,20 @@ class WireframeSphere(inkex.GenerateExtension): """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') + 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)) + 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 + 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 @@ -172,8 +174,10 @@ class WireframeSphere(inkex.GenerateExtension): 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 + 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)) @@ -184,12 +188,21 @@ class WireframeSphere(inkex.GenerateExtension): 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.arc(c_xy, *r_xy, start=start_end[0], end=start_end[1], - open="true", arctype="arc") - path.style = {'stroke': '#000000', - 'stroke-width': str(self.svg.unittouu('1px')), - 'fill': 'none'} + path = inkex.PathElement.arc( + c_xy, + *r_xy, + start=start_end[0], + end=start_end[1], + open="true", + arctype="arc" + ) + path.style = { + "stroke": "#000000", + "stroke-width": str(self.svg.unittouu("1px")), + "fill": "none", + } return path -if __name__ == '__main__': + +if __name__ == "__main__": WireframeSphere().run() -- GitLab