diff --git a/.darglint b/.darglint new file mode 100644 index 0000000000000000000000000000000000000000..444794938d261e6d16c1817e4f6ccf9e1d4be5ee --- /dev/null +++ b/.darglint @@ -0,0 +1,3 @@ +[darglint] +docstring_style=google +strictness=short \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5e40208910faa245bfa7ee261d3eefb62b2ab19a..e55a4d810a1ee9949bd36094e6f98abb8ede6106 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -101,7 +101,6 @@ sphinx: - pip3 install pytest - pip3 install sphinx-rtd-theme - sphinx-apidoc -F -f -e -P -o docs/source inkex */deprecated.py - - cp .gitlab-ci.sphinx-conf.py docs/conf.py - python3 setup.py build_sphinx -s docs - echo -e "\n\n" "Documentation for inkex module successfully created; you can access the HTML version at\n" diff --git a/.gitlab-ci.sphinx-conf.py b/docs/conf.py similarity index 98% rename from .gitlab-ci.sphinx-conf.py rename to docs/conf.py index 58efe8a1c4dd3c180aecea486aa01ef673a21a94..4868e683fe1582e136c86c4cb1437bbba0018160 100644 --- a/.gitlab-ci.sphinx-conf.py +++ b/docs/conf.py @@ -31,7 +31,6 @@ author = 'The Inkscape Project' # ones. extensions = [ 'sphinx.ext.autodoc', - 'sphinx_autodoc_typehints', 'sphinx.ext.viewcode', 'sphinx.ext.todo', 'sphinx_rtd_theme', diff --git a/inkex/properties.py b/inkex/properties.py index 57fd22e040cdda38cda132c6d95dff8c04b8030d..ccf24ba15ac8b325d4f5e8d4b61a3029fc8c7c83 100644 --- a/inkex/properties.py +++ b/inkex/properties.py @@ -55,7 +55,10 @@ class BaseStyleValue(): Args: declaration (str): a css declaration such as: - "fill: #000 !important;". The trailing semicolon may be ommitted. + "fill: #000 !important;". The trailing semicolon may be ommitted. + + Raises: + ValueError: Unable to parse the declaration Returns: Tuple[str, str, bool]: a tuple with key, value and importance @@ -72,13 +75,13 @@ class BaseStyleValue(): raise ValueError("Invalid declaration") - def parse_value(self, element = None): + def parse_value(self, element=None): """Get parsed property value with resolved urls, color, etc. Args: element (BaseElement): the SVG element to which this style is applied to - currently used for resolving gradients / masks, could be used for computing - percentage attributes or calc() attributes [optional] + currently used for resolving gradients / masks, could be used for computing + percentage attributes or calc() attributes [optional] Returns: object: parsed property value @@ -88,12 +91,15 @@ class BaseStyleValue(): return self._parse_value(self.value, element) - def _parse_value(self, value: str, element = None) -> object: + 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: value (str): unparsed value element (BaseElement): the SVG element to which this style is applied to [optional] + + Returns: + object: the parsed value """ return value @@ -111,7 +117,7 @@ class BaseStyleValue(): self._parse_value(result) # check if value can be parsed (value is valid) return result - def _unparse_value(self, value: object) -> str: + def _unparse_value(self, value: object) -> str: # pylint: disable=no-self-use return str(value) @property @@ -124,21 +130,18 @@ class BaseStyleValue(): return self.attr_name + ":" + self.value + (" !important" if self.important else "") @classmethod - def factory(cls, declaration : str=None, attr_name : str=None, \ - value : object=None, important : 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: - svg (SvgDocumentElement, optional): the parent SVG, required for - looking up urls (gradients, masks...). Defaults to None. declaration (str, optional): the CSS declaration to parse. Defaults to None. attr_name (str, optional): the attribute name. Defaults to None. value (object, optional): the attribute value. Defaults to None. important (bool, optional): whether the attribute is marked !important. - Defaults to False. + Defaults to False. Raises: - ValueError: if the attribute is unknown Errors may also be raised on parsing, so make sure to handle them Returns: @@ -167,7 +170,18 @@ class BaseStyleValue(): @staticmethod def factory_errorhandled(element=None, declaration="", key="", value=""): """Error handling for the factory method: if something goes wrong during parsing, - ignore the attribute""" + ignore the attribute + + Args: + element (BaseElement, optional): The element this declaration is affecting, for + finding gradients ect. Defaults to None. + declaration (str, optional): the CSS declaration to parse. Defaults to "". + key (str, optional): the attribute name. Defaults to "". + value (str, optional): the attribute value. Defaults to "". + + Returns: + BaseStyleValue: The parsed style + """ try: value = BaseStyleValue.factory(declaration=declaration, \ attr_name=key, value=value) @@ -187,9 +201,10 @@ class BaseStyleValue(): 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 """ - def _parse_value(self, value : str, element = None): + def _parse_value(self, value: str, element=None): if value[-1] == "%": # percentage parsed_value = float(value[:-1]) * 0.01 else: @@ -201,7 +216,7 @@ class AlphaValue(BaseStyleValue): return parsed_value def _unparse_value(self, value: object) -> str: - if (isinstance(value, float) or isinstance(value, int)): + if isinstance(value, (float, int)): if value < 0: return "0" if value > 1: @@ -212,8 +227,9 @@ class AlphaValue(BaseStyleValue): 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): + def _parse_value(self, value: str, element=None): if value == "currentColor": if element is not None: style = element.specified_style() @@ -255,9 +271,10 @@ def match_url_and_return_element(string: str, svg): class URLNoneValue(BaseStyleValue): """Stores a marker, which is given as url. + Reference: https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties""" - def _parse_value(self, value: str, element = None): + def _parse_value(self, value: str, element=None): if value == "none": return None if value[0:4] == "url(": @@ -283,9 +300,10 @@ class URLNoneValue(BaseStyleValue): class PaintValue(ColorValue, URLNoneValue): """Stores a paint value (such as fill and stroke), which may be specified as color, or url. + Reference: https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint""" - def _parse_value(self, value : str, element = None): + def _parse_value(self, value: str, element=None): if value == "none": return None if value in ["context-fill", "context-stroke"]: @@ -325,7 +343,7 @@ class EnumValue(BaseStyleValue): self.valueset = all_properties[attr_name][4] super().__init__(declaration, attr_name, value, important) - def _parse_value(self, value : str, element = None): + 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}. " + @@ -341,7 +359,7 @@ class ShorthandValue(BaseStyleValue, ABC): Args: style (Style): the style that the shorthand attribute is contained in, - and that the shorthand attribute will be applied on + and that the shorthand attribute will be applied on """ if self.attr_name not in style: return @@ -420,7 +438,7 @@ class MarkerShorthandValue(ShorthandValue, URLNoneValue): if self.value == "": 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): + def _parse_value(self, value: str, element=None): # Make sure the parsing routine doesn't choke on an empty shorthand if value == "": return "" @@ -467,8 +485,8 @@ all_properties: Dict[str, Tuple[Type[BaseStyleValue], str, bool, bool, Union[Lis "font-size": (BaseStyleValue, "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"]), + "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"]), diff --git a/inkex/styles.py b/inkex/styles.py index 7ed50bd46901e71ffb0baffa5fb179ae9b6b7ac9..bbd3a736365e66981a560a32d528fd8e3fc523ca 100644 --- a/inkex/styles.py +++ b/inkex/styles.py @@ -19,8 +19,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # """ -Two simple functions for working with inline css -and some color handling on top. +Functions for handling styles and embedded css """ import re @@ -37,7 +36,6 @@ if TYPE_CHECKING: from inkex import SvgDocumentElement - class Classes(list): """A list of classes applied to an element (used in css and js)""" def __init__(self, classes=None, callback=None): @@ -101,10 +99,18 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): self.callback = callback @staticmethod - def _parse_str(style: str, element = None) \ - -> Iterable[BaseStyleValue]: - """Create a dictionary from the value of an inline style attribute, including - its !important state, parsing the value if possible """ + def _parse_str(style: str, element=None) -> Iterable[BaseStyleValue]: + """Create a dictionary from the value of a CSS rule (such as an inline style or from an + embedded style sheet), including its !important state, parsing the value if possible. + + Args: + style: the content of a CSS rule to parse + element: the element this style is working on (can be the root SVG, is used for + parsing gradients etc.) + + Yields: + BaseStyleValue: the parsed attribute + """ for declaration in style.split(';'): if ":" in declaration: result = BaseStyleValue.factory_errorhandled(element, \ @@ -113,7 +119,7 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): yield result @staticmethod - def parse_str(style: str, element = None): + def parse_str(style: str, element=None): """Parse a style passed as string""" return Style(style, element=element) @@ -173,6 +179,9 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): """Creates a new Style containing all parent styles with importance "important" and current styles with importance "important" + Args: + parent: the parent style that will be merged into this one (will not be altered) + Returns: Style: the merged Style object """ @@ -197,8 +206,7 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): return ret def apply_shorthands(self): - """Apply all shorthands in this style. - """ + """Apply all shorthands in this style.""" for element in list(self.values()): if isinstance(element, ShorthandValue): element.apply_shorthand(self) @@ -231,10 +239,17 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): def get_store(self, key): """Gets the BaseStyleValue of this key, since the other interfaces - __getitem__ - and __call__ - return the original and parsed value, respectively.""" + and __call__ - return the original and parsed value, respectively. + + Args: + key (str): the attribute name + + Returns: + BaseStyleValue: the BaseStyleValue struct of this attribute + """ return super().__getitem__(key) - def __call__(self, key, element = None): + def __call__(self, key, element=None): # check if there are shorthand properties defined. If so, apply them to a copy copy = self for value in super().values(): @@ -318,6 +333,9 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): see https://www.w3.org/TR/CSS22/cascade.html#cascading-order + Args: + element (BaseElement): the element that the cascaded style will be computed for + Returns: Style: the cascaded style """ @@ -344,6 +362,9 @@ class Style(OrderedDict, MutableMapping[str, Union[str, BaseStyleValue]]): """Returns the specified style of an element, i.e. the cascaded style + inheritance, see https://www.w3.org/TR/CSS22/cascade.html#specified-value + Args: + element (BaseElement): the element that the specified style will be computed for + Returns: Style: the specified style """ @@ -446,7 +467,16 @@ class StyleSheet(list): def lookup_specificity(self, element_id, svg): """Lookup the element_id against all the styles in this sheet - and return the specificity of the match""" + and return the specificity of the match + + Args: + element_id (str): the id of the element that styles are being queried for + svg (SvgDocumentElement): The document that contains both element and the styles + + Yields: + Tuple[ConditionalStyle, Tuple[int, int, int]]: all matched styles and the specificity + of the match + """ for style in self: for rule, spec in zip(style.to_xpaths(), style.get_specificities()): for elem in svg.xpath(rule): @@ -479,6 +509,7 @@ class ConditionalStyle(Style): # this xpath transform and provides no extra functionality for reverse lookups. 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""" diff --git a/inkex/tween.py b/inkex/tween.py index 7c430df8922d3e5e1fa9d6c0249aad4fd6c95028..5109fe3129b2c9fa22033c6002307c3fdb716663 100644 --- a/inkex/tween.py +++ b/inkex/tween.py @@ -69,7 +69,13 @@ class AttributeInterpolator(abc.ABC): During the interpolation process, some nodes are created temporarily, such as plain gradients of a single color to allow solid<->gradient interpolation. These are not attached to the document tree and therefore have no root. Since the only style relevant for them is - the inline style, it is acceptable to fallback to it.""" + the inline style, it is acceptable to fallback to it. + + Args: + node (BaseElement): The node to get the best approximated style of + + Returns: + Style: If the node is rooted, the CSS specified style. Else, the inline style.""" try: return node.specified_style() except FragmentError: @@ -81,12 +87,14 @@ class AttributeInterpolator(abc.ABC): supported Args: - snode (inkex.BaseElement): start element - enode (inkex.BaseElement): end element + snode (BaseElement): start element + enode (BaseElement): end element attribute (str): attribute name (for styles, starting with "style/") - svg (inkex.SvgDocumentElement): the svg document - method (Interpolator, optional): (currently only used for paths). Specifies a method - used to interpolate the attribute. Defaults to None. + method (AttributeInterpolator, optional): (currently only used for paths). Specifies a + method used to interpolate the attribute. Defaults to None. + + Raises: + ValueError: if an attribute is passed that is not a style, path or transform attribute Returns: AttributeInterpolator: an interpolator whose type depends on attribute. @@ -146,8 +154,8 @@ class StyleInterpolator(AttributeInterpolator): - other properties -> ValueInterpolator Args: - snode (inkex.BaseElement): start element - enode (inkex.BaseElement): end element + snode (BaseElement): start element + enode (BaseElement): end element attribute (str): attribute to interpolate Raises: @@ -174,8 +182,8 @@ class StyleInterpolator(AttributeInterpolator): """Creates an Interpolator for a given color-like attribute Args: - snode (inkex.BaseElement): start element - enode (inkex.BaseElement): end element + snode (BaseElement): start element + enode (BaseElement): end element attribute (str): attribute to interpolate Raises: @@ -216,7 +224,7 @@ class StyleInterpolator(AttributeInterpolator): Args: time (int, optional): Interpolation position. If 0, start_value is returned, if 1, - end_value is returned. Defaults to 0. + end_value is returned. Defaults to 0. Returns: inkex.Style: interpolated style @@ -248,7 +256,7 @@ class ValueInterpolator(AttributeInterpolator): Args: time (int, optional): Interpolation position. If 0, start_value is returned, if 1, - end_value is returned. Defaults to 0. + end_value is returned. Defaults to 0. Returns: int: interpolated value @@ -306,10 +314,10 @@ class TransformInterpolator(ArrayInterpolator): Args: time (int, optional): Interpolation position. If 0, start_value is returned, if 1, - end_value is returned. Defaults to 0. + end_value is returned. Defaults to 0. Returns: - inkex.Transform: interpolated transform + Transform: interpolated transform """ return Transform(super().interpolate(time)) @@ -321,8 +329,8 @@ class ColorInterpolator(ArrayInterpolator): """Creates a ColorInterpolator for either Fill or stroke, depending on the attribute. Args: - sst (inkex.Style): Start style - est (inkex.Style): End style + sst (Style): Start style + est (Style): End style attribute (string): either fill or stroke Raises: @@ -350,10 +358,10 @@ class ColorInterpolator(ArrayInterpolator): Args: time (int, optional): Interpolation position. If 0, start_value is returned, if 1, - end_value is returned. Defaults to 0. + end_value is returned. Defaults to 0. Returns: - inkex.Color: interpolatored color + Color: interpolated color """ return Color(list(map(int, super().interpolate(time)))) @@ -381,8 +389,8 @@ class GradientInterpolator(AttributeInterpolator): newoffsets = sorted(list(set(self.start_value.stop_offsets + self.end_value.stop_offsets))) - def func(start, end, f): - return StopInterpolator(start, end).interpolate(f) + 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) @@ -396,7 +404,7 @@ class GradientInterpolator(AttributeInterpolator): @staticmethod def create(snode, enode, attribute): - """Creates a GradientInterpolator for either fill or stroke, depending on attribute. + """Creates a `GradientInterpolator` for either fill or stroke, depending on attribute. Cases: (A, B) -> Interpolator - Linear Gradient, Linear Gradient -> LinearGradientInterpolator @@ -407,13 +415,13 @@ class GradientInterpolator(AttributeInterpolator): - Color or None, Color or None -> ValueError Args: - snode (inkex.BaseElement): start element - enode (inkex.BaseElement): end element + snode (BaseElement): start element + enode (BaseElement): end element attribute (string): either fill or stroke Raises: ValueError: if none of the styles are a gradient or if they are gradients - of different types + of different types Returns: GradientInterpolator: an Interpolator object @@ -467,7 +475,7 @@ class GradientInterpolator(AttributeInterpolator): iterator[index][1] = value # is a gradient if interpolator is None: raise ValueError("None of the two styles is a gradient") - if interpolator == LinearGradientInterpolator or interpolator == RadialGradientInterpolator: + if interpolator in [LinearGradientInterpolator, RadialGradientInterpolator]: return interpolator(iterator[0][1], iterator[1][1], snode) return interpolator(iterator[0][1], iterator[1][1]) @@ -492,7 +500,7 @@ class GradientInterpolator(AttributeInterpolator): func (Callable[[Type, Type, float], Type]): Function to interpolate between values Returns: - :list[Type]: interpolated function values at positions + list[Type]: interpolated function values at positions """ newvalues = [] positions = list(map(float, positions)) @@ -521,12 +529,12 @@ class GradientInterpolator(AttributeInterpolator): and returns the href to the orientation gradient. Args: - element (inkex.BaseElement): an element inside the SVG that the gradient should be - added to - gradient (inkex.Gradient): the gradient to append to the document + element (BaseElement): an element inside the SVG that the gradient should be + added to + gradient (Gradient): the gradient to append to the document Returns: - inkex.Gradient: the orientation gradient, or the gradient object if + Gradient: the orientation gradient, or the gradient object if element has no root or is None """ stops, orientation = gradient.stops_and_orientation() @@ -558,8 +566,7 @@ class GradientInterpolator(AttributeInterpolator): for interp in self.newstop_interpolator]) if self.svg is None: return newgrad - else: - return GradientInterpolator.append_to_doc(self.svg, newgrad) + return GradientInterpolator.append_to_doc(self.svg, newgrad) class LinearGradientInterpolator(GradientInterpolator): @@ -611,10 +618,10 @@ class StopInterpolator(AttributeInterpolator): Args: time (int, optional): Interpolation position. If 0, start_value is returned, if 1, - end_value is returned. Defaults to 0. + end_value is returned. Defaults to 0. Returns: - inkex.Stop: interpolated gradient stop + Stop: interpolated gradient stop """ newstop = Stop() newstop.style = self.style_interpolator.interpolate(time) @@ -689,7 +696,7 @@ class EqualSubsegmentsInterpolator(PathInterpolator): """Interpolates the path by rediscretizing the subpaths first.""" @staticmethod def get_subpath_lenghts(path): - """prepare lenghts for interpolation""" + """prepare lengths for interpolation""" sp_lenghts, total = csplength(path) t = 0 lenghts = [] @@ -703,7 +710,14 @@ class EqualSubsegmentsInterpolator(PathInterpolator): @staticmethod def process_path(path, other): """Rediscretize path so that all subpaths have an equal number of segments, so that - there is a node at the path "times" where path or other have a node""" + there is a node at the path "times" where path or other have a node + + Args: + path (Path): the first path + other (Path): the second path + + Returns: + Array: the prepared path description for the intermediate path """ sp_lenghts, total, _ = EqualSubsegmentsInterpolator.get_subpath_lenghts( path) _, _, lenghts = EqualSubsegmentsInterpolator.get_subpath_lenghts(other)