svg_path_editor =============== .. py:module:: svg_path_editor Submodules ---------- .. toctree:: :maxdepth: 1 /autoapi/svg_path_editor/geometry/index /autoapi/svg_path_editor/intersect/index /autoapi/svg_path_editor/math/index /autoapi/svg_path_editor/path_change_origin/index /autoapi/svg_path_editor/path_offset/index /autoapi/svg_path_editor/path_operations/index /autoapi/svg_path_editor/path_parser/index /autoapi/svg_path_editor/path_round_corners/index /autoapi/svg_path_editor/path_shade/index /autoapi/svg_path_editor/sub_path_bounds/index /autoapi/svg_path_editor/svg/index Attributes ---------- .. autoapisummary:: svg_path_editor.PNG svg_path_editor.WEBP Classes ------- .. autoapisummary:: svg_path_editor.Point svg_path_editor.Precision svg_path_editor.BevelArced svg_path_editor.BevelPolygon svg_path_editor.ImageFormat svg_path_editor.PathShading svg_path_editor.SvgItem svg_path_editor.SvgPath Functions --------- .. autoapisummary:: svg_path_editor.change_path_origin svg_path_editor.bevel_path svg_path_editor.offset_path svg_path_editor.optimize_path svg_path_editor.reverse_path svg_path_editor.round_corners svg_path_editor.lambert_from_angle svg_path_editor.lambert_shading_base64 svg_path_editor.shade_path Package Contents ---------------- .. py:class:: Point(x, y) 2D point with :class:`decimal.Decimal` coordinates. .. py:attribute:: x :type: decimal.Decimal .. py:attribute:: y :type: decimal.Decimal .. py:method:: __iter__() Iterate as ``(x, y)``. .. py:method:: __eq__(other) Compare coordinates for equality. .. py:method:: __ne__(value, /) Compare coordinates for inequality. .. py:property:: vec2 :type: Vec2 Exact conversion to :class:`Vec2`. Coordinates are converted to SymPy rationals via :func:`dec_to_rat`. .. py:method:: __str__() Human-readable representation ``(x, y)`` with decimal formatting. .. py:method:: __repr__() Debug representation ``Point(x, y)`` with decimal formatting. .. py:property:: length :type: decimal.Decimal Euclidean norm :math:`‖v‖_2 = \sqrt{x^2 + y^2}`. .. py:property:: normalized :type: Point Unit vector :math:`v / ‖v‖_2`. The zero vector is returned unchanged. .. py:method:: __neg__() Unary minus :math:`-v`. .. py:method:: __add__(other) Vector addition :math:`v + w`. .. py:method:: __sub__(other) Vector subtraction :math:`v - w`. .. py:method:: __mul__(other) Scalar multiplication :math:`v ⋅ λ`. .. py:method:: __truediv__(other) Scalar division :math:`v / λ`. .. py:class:: Precision Control numerical precision for mixed symbolic/numeric operations. ``baseline`` defines the primary target precision, while ``additional`` can be used to carry extra guard digits during intermediate computations. :ivar baseline: Baseline number of significant digits. :ivar additional: Additional guard digits to be used internally. .. py:attribute:: baseline :type: int .. py:attribute:: additional :type: int .. py:property:: full :type: int Full number of significant digits to use. :return: ``baseline + additional``. .. py:function:: change_path_origin(svg, new_origin_index, subpath = None) Return a new path where the origin of a (sub)path is moved. The command at ``new_origin_index`` becomes the first command of the affected subpath segment; all items of that subpath are rotated accordingly. If ``subpath`` is ``True``, only the subpath containing ``new_origin_index`` is transformed; if ``False``/``None``, the whole path is treated as a single segment. :param svg: Original path to transform. :param new_origin_index: Index of the command that should become the new origin within its subpath. :param subpath: If ``True``, restrict the change to the subpath containing ``new_origin_index``; if ``False`` or ``None``, treat the full path segment as one subpath. :return: A new :class:`~svg_path_editor.SvgPath` instance with the origin moved and the path representation optimized. .. py:class:: BevelArced Bases: :py:obj:`NamedTuple` Planar bevel face containing an elliptical-arc boundary. The path consists of two corresponding arc segments (original and offset) joined by straight segments. :ivar path: Closed :class:`SvgPath` describing the bevel face. :ivar c: Center of the supporting ellipse. :ivar r: Radii vector of the supporting ellipse. :ivar phi: Rotation of the ellipse in degrees. :ivar locally_convex: ``True`` iff the bevel is convex w.r.t. the interior. .. py:attribute:: path :type: svg_path_editor.svg.SvgPath .. py:attribute:: c :type: svg_path_editor.geometry.Point .. py:attribute:: r :type: svg_path_editor.geometry.Point .. py:attribute:: phi :type: decimal.Decimal .. py:attribute:: locally_convex :type: bool .. py:attribute:: ante_ext :type: bool :value: False .. py:attribute:: post_ext :type: bool :value: False .. py:class:: BevelPolygon Bases: :py:obj:`NamedTuple` Planar bevel face bounded by straight segments. :ivar path: Closed :class:`SvgPath` describing the bevel polygon. :ivar outward_normal: Unit normal pointing out of the filled region. .. py:attribute:: path :type: svg_path_editor.svg.SvgPath .. py:attribute:: outward_normal :type: svg_path_editor.geometry.Point .. py:attribute:: ante_ext :type: bool :value: False .. py:attribute:: post_ext :type: bool :value: False .. py:function:: bevel_path(path, *, d, prec = None) Construct bevel faces for an offset of a simple closed path. Unlike :func:`offset_path`, this yields only the auxiliary faces that fill the bevel region between the original path and its offset. :param path: Closed :class:`SvgPath` with exactly one subpath ``M … Z``, using only line and elliptical-arc segments. :param d: Offset distance. Positive values move edges towards the interior. :param prec: Same semantics as in :func:`offset_path`. :return: Closed paths covering the bevel surface between original and offset. :raises AssertionError: If ``path`` is not a single closed subpath of the expected form, or if an offset intersection cannot be computed. .. py:function:: offset_path(path, *, d, prec = None) Offset a simple closed SVG path. The input must be a single closed subpath ``M … Z`` of straight lines and elliptical arcs. Every segment is offset by distance ``d`` (inwards for ``d > 0``), and consecutive offset segments are intersected to form the output path. :param path: Closed :class:`SvgPath` with exactly one subpath ``M … Z``, using only line and elliptical-arc segments. :param d: Offset distance. Positive values move edges towards the interior. :param prec: Intersection and offsetting precision: * ``"auto"``: decimal precision + :data:`additional_digits` for both offset geometry and intersections. * ``"auto-intersections"``: same automatic precision for intersections only; offsets remain symbolic. * :class:`Precision`: use this precision everywhere. * ``None``: purely symbolic where supported. :return: The offset closed path. :raises AssertionError: If ``path`` is not a single closed subpath of the expected form, or if an offset intersection cannot be computed. .. py:function:: optimize_path(svg, *, remove_useless_commands = False, remove_orphan_dots = False, use_shorthands = False, use_horizontal_and_vertical_lines = False, use_relative_absolute = False, use_reverse = False, use_close_path = False) Optimize the representation of an SVG path. The function can apply several optional passes that can be enabled using the parameters. :param svg: Input path. :param remove_useless_commands: Remove redundant ``M``/``Z`` commands and degenerate ``L``/``H``/``V`` segments. :param remove_orphan_dots: Remove empty closed subpaths (``M`` immediately followed by ``Z``). :param use_shorthands: Convert eligible ``C``/``Q`` segments to ``S``/``T`` where possible. :param use_horizontal_and_vertical_lines: Replace ``L`` with ``H`` or ``V`` where possible. :param use_relative_absolute: Choose between relative and absolute commands per segment to minimize size. :param use_reverse: Reverse the path direction if that yields a shorter minified representation. This can affect stroked paths. :param use_close_path: Convert final line segments that return to start into ``Z``. This can affect stroked paths. :return: A new, possibly shorter, but geometrically equivalent path. .. py:function:: reverse_path(svg, subpath_of_item = None) Reverse the drawing direction of a path or sub-path. :param svg: Input path. :param subpath_of_item: Index of an item within the sub-path to reverse, or ``None`` to reverse the entire path. :return: A new path with the selected segment reversed. Geometry is preserved, but command types and relative/absolute representation may change. .. py:function:: round_corners(path, radius, *, selector = lambda a, b, c: True) Round corners between straight segments in closed subpaths. The input must be one or more closed subpaths ``M … Z``. Each corner between two straight segments (``L``/``H``/``V``/``Z``) at point :math:`B` between :math:`AB` and :math:`BC` is replaced by: * a shortened segment from :math:`A` to :math:`P` on :math:`AB`, * a circular arc from :math:`P` to :math:`Q` with radius ``radius``, * a shortened segment from :math:`Q` to :math:`C` on :math:`BC`. Only corners with straight incoming and outgoing segments are modified. The input path is not modified. :param path: :class:`SvgPath` with one or more closed subpaths ``M … Z``. :param radius: Corner radius; must be positive. :param selector: Optional ``selector(a, b, c) -> bool``; if it returns ``False`` for corner :math:`B` between :math:`AB` and :math:`BC`, that corner is left unchanged. :return: New :class:`SvgPath` with chosen line-line corners rounded by arcs. :raises ValueError: If ``radius`` is not positive. .. py:data:: PNG .. py:data:: WEBP .. py:class:: ImageFormat Bases: :py:obj:`Protocol` Abstract RGBA image encoder. :ivar media_type: MIME type of the encoded image. :ivar extension: File extension (without dot). .. py:attribute:: media_type :type: ClassVar[str] .. py:attribute:: extension :type: ClassVar[str] .. py:method:: encode(data) Encode an RGBA image. :param data: ``(H, W, 4)`` uint8 array in RGBA order. :return: Encoded image bytes. .. py:class:: PathShading SVG fragments for shaded bevels. :ivar defs_body: Elements for a document-wide ```` (e.g. shared ```` or ```` definitions). :ivar body: Per-path drawing elements (e.g. ````, ````, ````) that reference :attr:`defs_body`. .. py:attribute:: defs_body :type: list[str] .. py:attribute:: body :type: list[str] .. py:function:: lambert_from_angle(normal, *, z_light = 1) Lambertian diffuse intensity from a 2D surface normal. The 2D normal is interpreted in the :math:`xy`-plane, with an implicit :math:`z`-component 1: .. math:: \mathbf{n} = (n_x, n_y, 1). The light position is .. math:: \mathbf{L}_\text{pos} = (0, 1, z_{\text{light}}), and the light direction is .. math:: \hat{\mathbf{L}} = \frac{\mathbf{L}_\text{pos}}{\|\mathbf{L}_\text{pos}\|}. The Lambert intensity is .. math:: I = \max(0, \hat{\mathbf{n}} \cdot \hat{\mathbf{L}}), where :math:`\hat{\mathbf{n}}` is the normalized surface normal. :param normal: 2D surface normal in the :math:`xy`-plane. :param z_light: :math:`z` height of the light source. :return: Lambertian intensity in :math:`[0, 1]`. .. py:function:: lambert_shading_base64(*, r, phi, locally_convex, resolution, z_light = 1.0, t = 0.5, format = WEBP, seed = None) Render a Lambert–shaded elliptical cone; return encoded bytes and base64 URI. The cone radii are :math:`r = (r_x, r_y)` in image space. For a point :math:`(x, y)` on the grid .. math:: (x, y) \in \left[-\frac{1}{r_x}, \frac{1}{r_x}\right] \times \left[-\frac{1}{r_y}, \frac{1}{r_y}\right], the unnormalized normal is .. math:: \mathbf{n}(x, y) = \left(\frac{x}{r_x^2}, \frac{y}{r_y^2}, 1\right). The Lambert term is .. math:: I(x, y) = \max\left(0, \hat{\mathbf{n}}(x, y)\cdot\hat{\mathbf{L}}\right), where :math:`\hat{\mathbf{L}}` is the unit vector from the light position :math:`(0, s, z_{\text{light}})` with :math:`s = -1` if ``locally_convex`` else :math:`+1`, rotated in the :math:`xy`-plane by :math:`-\varphi` degrees. Grayscale is binary (0 or 255) according to :math:`I > t`. Alpha is a symmetric remap of :math:`I` around :math:`t`: .. math:: \alpha(I) = \begin{cases} \dfrac{I - t}{1 - t}, & I \geq t,\\ \dfrac{t - I}{t}, & I < t. \end{cases} Before 8-bit quantization, uniform noise in :math:`[0, 1]` is added to alpha for dithering. The final RGBA image is encoded with ``format`` and returned both as raw bytes and as a ``data:...;base64,...`` URI. :param r: Ellipse radii in :math:`x` and :math:`y`. :param phi: Rotation in degrees of the light direction in image space (clockwise in screen coordinates). :param locally_convex: If true, the base light position is :math:`(0, -1, z_{\text{light}})`, otherwise :math:`(0, 1, z_{\text{light}})`. :param resolution: Pixels per SVG unit. Image size is :math:`\lceil 2 r_x \, \mathrm{resolution} \rceil \times \lceil 2 r_y \, \mathrm{resolution} \rceil`. :param z_light: :math:`z` height of the light source. :param t: Neutral Lambert intensity in :math:`[0, 1]` (threshold). :param format: Image format to use. :param seed: RNG seed for alpha dithering; ``None`` is non-deterministic. :return: ``(img_bytes, img_data_uri)``; the URI is suitable for SVG ``href``. .. py:function:: shade_path(svg, *, d, resolution, z_light = 1, threshold = 0.5, max_opacity = 1, format = WEBP, shade_offset = 0, clip_offset = 0, seed = None, prec = None, fmt_spec = '') Per-bevel Lambert shading for an SVG path. The path is decomposed into bevel regions via :func:`svg_path_editor.path_offset.bevel_path`. * Flat bevel polygons are shaded analytically with :func:`lambert_from_angle`. * Curved bevel arcs are shaded using a small Lambert RGBA texture from :func:`lambert_shading_base64`, referenced by ```` / ```` and clipped to the arc geometry. For each bevel polygon: * One ```` is emitted with ``fill="white"`` or ``fill="black"`` depending on whether the intensity is above or below ``threshold``. * Opacity is a symmetric remap of the intensity around ``threshold`` in :math:`[0, 1]`, scaled by ``max_opacity``. For each bevel arc: * A Lambert cone texture is generated (or reused from a cache) for ``(r.x, r.y, phi, locally_convex)``. * A base ```` at the origin with size :math:`2 r_x \times 2 r_y` is placed in :attr:`PathShading.defs_body` once per unique key. * For each occurrence, a ```` with the bevel geometry and a ```` of the base image are emitted into :attr:`PathShading.body`, translated so the image origin matches the lower-left corner of the ellipse bounding box, and rotated by ``phi`` around the ellipse center if non-zero. * ```` carries opacity ``max_opacity`` when it is less than 1. To integrate into an SVG, place all ``defs_body`` entries inside a single document-level ```` and insert ``body`` where the path should render. :param svg: Input path to bevel and shade. :param d: Offset distance for :func:`bevel_path`. :param resolution: Pixels per SVG unit for generated textures. :param z_light: :math:`z` height of the light source. :param threshold: Neutral Lambert intensity in :math:`[0, 1]`, shared between flat bevels and textures. :param max_opacity: Global opacity scale in :math:`[0, 1]`. :param format: Texture format, e.g. :data:`WEBP` or :data:`PNG`. :param shade_offset: Starting index for generated shade IDs. :param clip_offset: Starting index for generated clip-path IDs. :param seed: RNG seed for alpha dithering in :func:`lambert_shading_base64`; ``None`` for non-deterministic. :param prec: Precision/geometry mode passed to :func:`bevel_path`. ``"auto"`` and ``"auto-intersections"`` select automatic strategies; ``None`` uses the default. :param fmt_spec: The formatting specification for the paths. :return: :class:`PathShading` with SVG fragments for defs and body. .. py:class:: SvgItem(values, relative) Bases: :py:obj:`abc.ABC` Base class for a single SVG path command and its numeric values. .. py:attribute:: _relative :type: bool .. py:attribute:: values :type: list[decimal.Decimal] .. py:attribute:: previous_point :type: svg_path_editor.geometry.Point .. py:attribute:: absolute_points :type: list[SvgPoint] :value: [] .. py:attribute:: absolute_control_points :type: list[SvgControlPoint] :value: [] .. py:method:: make(raw_item) :staticmethod: Construct the appropriate subclass of :class:`SvgItem` from a parsed command and its parameter strings. :param raw_item: List starting with the command letter followed by numeric parameters as strings (e.g. ``["M", "0", "0"]``). :raises ValueError: If the item is empty or the command is invalid. .. py:method:: make_from(origin, previous, new_type) :staticmethod: Create a new :class:`SvgItem` of type ``new_type`` from an existing item. The new item preserves the current target location and, where possible, the original control point geometry. :param origin: Existing item whose geometry should be preserved. :param previous: Previous item in the path, used for control point defaults. :param new_type: New SVG command letter (e.g. ``"L"`` or ``"c"``). :raises ValueError: If ``new_type`` is not supported. .. py:method:: refresh_absolute_points(origin, previous) Recalculate absolute points from stored values and the previous item. :param origin: Current subpath origin (last ``M``/``m`` or ``Z``). :param previous: Previous item in the path, or ``None`` for the first item. .. py:property:: relative :type: bool Whether this command is stored in relative coordinates. .. py:method:: refresh_absolute_control_points(origin, previous_target) Recalculate absolute control points. The default implementation assumes there are no control points. :param origin: Current subpath origin. :param previous_target: Previous item in the path, if any. .. py:method:: reset_control_points(previous_target) Reset control points to a default geometry between previous and target. Subclasses for curve commands override this to compute reasonable defaults. :param previous_target: Previous item in the path. .. py:method:: refresh(origin, previous) Recompute all absolute points and re-bind back-references. :param origin: Current subpath origin. :param previous: Previous item in the path, or ``None`` for the first item. .. py:method:: clone() Return a shallow clone of this item, retaining its subclass. Values, relativity and :attr:`previous_point` are copied. Absolute points and control points need to be recomputed via :meth:`refresh`, as is done in :meth:`SvgPath.clone`. .. py:method:: translate(x, y, force = False) Translate in place. Relative items are translated only if ``force`` is true; otherwise their stored deltas are left unchanged. :param x: Translation in x direction. :param y: Translation in y direction. :param force: Also adjust relative coordinates. .. py:method:: translated(x, y, force = False) Return a translated copy. See :meth:`translate` for details. :param x: Translation in x direction. :param y: Translation in y direction. :param force: Also adjust relative coordinates. .. py:method:: scale(kx, ky) Scale in place. :param kx: Scale factor for x coordinates. :param ky: Scale factor for y coordinates. .. py:method:: scaled(kx, ky) Return a scaled copy. :param kx: Scale factor for x coordinates. :param ky: Scale factor for y coordinates. .. py:method:: rotate(ox, oy, degrees, force = False) Rotate the item in place around ``(ox, oy)``. For relative items, rotation is performed around ``(0, 0)`` unless ``force`` is true. :param ox: Rotation origin x coordinate. :param oy: Rotation origin y coordinate. :param degrees: Rotation angle in degrees. :param force: Rotate relative coordinates around ``(ox, oy)``. .. py:method:: rotated(ox, oy, degrees, force = False) Return a rotated copy around ``(ox, oy)``. See :meth:`rotate` for details. :param ox: Rotation origin x coordinate. :param oy: Rotation origin y coordinate. :param degrees: Rotation angle in degrees. :param force: Rotate relative coordinates around ``(ox, oy)``. .. py:property:: target_location :type: SvgPoint Final absolute point reached by this item. .. py:method:: set_target_location(pt) Move the geometric target of this command to ``pt``. :param pt: New target location in absolute coordinates. .. py:method:: set_control_location(idx, pt) Move control point ``idx`` to ``pt``. Only meaningful for commands storing Bézier handles. :param idx: Index of the control point to move. :param pt: New control point location in absolute coordinates. .. py:property:: control_locations :type: list[SvgControlPoint] Absolute control points associated with this item. .. py:method:: get_type(ignore_is_relative = False) Return the SVG command letter for this item (e.g. ``"M"`` or ``"l"``). :param ignore_is_relative: Always return the uppercase key regardless of :attr:`relative`. .. py:method:: as_standalone_string() Return a standalone path string for this command. The result starts with an ``M`` to this command’s :attr:`previous_point` followed by the command itself. .. py:method:: as_string(decimals = None, minify = False, trailing_items = ()) Serialize this command into an SVG path fragment. Optionally additional same-typed ``trailing_items`` can be appended in a compact form. :param decimals: Number of decimal places, or ``None`` for default. :param minify: Use a more compact numeric representation. :param trailing_items: Additional items of the same type to serialize in the same command group. .. py:method:: __format__(format_spec) Format this item using :meth:`as_string`. The ``format_spec`` can be used to control decimal places and minification: * ``""`` (empty): use :meth:`as_string` defaults * ``".3"``: ``decimals=3`` * ``"m"``: ``minify=True`` * ``".3m"`` or ``"m.3"``: ``decimals=3``, ``minify=True`` Any other characters are currently ignored. :param format_spec: Format specification string (e.g. ``".3m"``). .. py:method:: __str__() Return :meth:`as_string` with default options. .. py:class:: SvgPath(path) An SVG path as a sequence of :class:`SvgItem`. .. py:method:: clone() Return a deep clone of this path. All contained items are cloned as well, and absolute positions are recomputed. .. py:method:: translate(dx, dy) Translate in place. :param dx: Translation in x direction. :param dy: Translation in y direction. .. py:method:: translated(dx, dy) Return a translated copy of this path. :param dx: Translation in x direction. :param dy: Translation in y direction. .. py:method:: scale(kx, ky) Scale in place. :param kx: Scale factor for x coordinates. :param ky: Scale factor for y coordinates. .. py:method:: scaled(kx, ky) Return a scaled copy of this path. :param kx: Scale factor for x coordinates. :param ky: Scale factor for y coordinates. .. py:method:: rotate(ox, oy, degrees) Rotate in place around ``(ox, oy)``. May also normalize horizontal/vertical segments after rotation. :param ox: Rotation origin x coordinate. :param oy: Rotation origin y coordinate. :param degrees: Rotation angle in degrees. .. py:method:: rotated(ox, oy, degrees) Return a rotated copy of this path. See :meth:`rotate` for details. :param ox: Rotation origin x coordinate. :param oy: Rotation origin y coordinate. :param degrees: Rotation angle in degrees. .. py:property:: relative :type: bool Indicate whether all items are stored as relative commands. Mixed paths (some absolute, some relative) return ``False``. .. py:method:: with_relative(new_relative) Return a new path with all items converted to the requested representation. :param new_relative: Target representation (``True`` for relative). .. py:method:: remove(item) Remove the given item. :param item: Item to remove. :raises ValueError: If the item is not present. .. py:method:: insert(index, item) Insert ``item`` before ``index``. :param index: Index before which to insert. :param item: Item to insert. .. py:method:: change_type(index, new_type) Change the command type of the item at ``index`` in place. :param index: The index of the item whose type should be changed. :param new_type: New SVG command letter (e.g. ``"L"`` or ``"c"``). :return: Newly created :class:`SvgItem` replacing the item at ``index``, or ``None`` if ``index`` is not in the path or is the first item. .. py:method:: as_string(decimals = None, minify = False) Serialize the entire path to an SVG path data string. :param decimals: Number of decimal places, or ``None`` for default. :param minify: Use a compact representation. .. py:property:: target_locations :type: list[SvgPoint] Final absolute points for each item in the path. .. py:property:: control_locations :type: list[SvgControlPoint] Flattened list of all absolute control points for the path. .. py:method:: set_location(pt_reference, to) Move the given point to ``to``. The reference must come from a previously queried point list (e.g. :attr:`target_locations` or :attr:`control_locations`). :param pt_reference: Point (target or control) to be moved. :param to: New absolute location for the point. .. py:method:: refresh_absolute_positions() Recompute absolute positions for all items in the path. This should be called after structural or coordinate changes. .. py:method:: __format__(format_spec) Format this path using :meth:`as_string`. The ``format_spec`` can be used to control decimal places and minification, following the same rules as :meth:`SvgItem.__format__`: * ``""`` (empty): use :meth:`as_string` defaults * ``".3"``: ``decimals=3`` * ``"m"``: ``minify=True`` * ``".3m"`` or ``"m.3"``: ``decimals=3``, ``minify=True`` Any other characters are currently ignored. :param format_spec: Format specification string (e.g. ``".3m"``). .. py:method:: __str__() Return :meth:`as_string` with default options.