soda-svg


Namesoda-svg JSON
Version 2.0.3 PyPI version JSON
download
home_pagehttps://github.com/evtn/soda
SummaryFast SVG generation tool
upload_time2024-10-10 18:11:06
maintainerNone
docs_urlNone
authorDmitry Gritsenko
requires_python<3.13,>=3.8
licenseMIT
keywords soda svg xml
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage
            # soda - a fast SVG generation tool

[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/evtn/soda/build.yml?branch=lord)](https://github.com/evtn/soda/actions/workflows/build.yml)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/evtn/soda/test.yml?branch=lord&label=tests)](https://github.com/evtn/soda/actions/workflows/test.yml)
[![PyPI](https://img.shields.io/pypi/v/soda-svg)](https://pypi.org/project/soda-svg/)
![PyPI - Downloads](https://pepy.tech/badge/soda-svg)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/soda-svg)
[![Coveralls](https://img.shields.io/coverallsCoverage/github/evtn/soda?label=test%20coverage)](https://coveralls.io/github/evtn/soda?branch=lord)
![License](https://img.shields.io/github/license/evtn/soda)
[![wordstreamer badge](https://img.shields.io/badge/renderable-what?label=wordstreamer&color=%2333bb33)](https://github.com/evtn/wordstreamer)
![Badge Count](https://img.shields.io/badge/badges-9-important)

Here's some basic usage:

> [!NOTE]
> Since soda 2.0.0, a Tag instance is also a [wordstreamer.Renderable](https://github.com/evtn/wordstreamer), which makes it possible to use things like:
>
> ```python
> from wordstreamer import Renderer
> from soda import Tag
>
> byte_stream = Renderer().byte_stream(Tag.rect()) # Iterable[bytes]
> ```

```python
from soda import Tag, Root

# root is a custom svg tag
root = Root(viewBox="0 0 10 10")(
    Tag.rect(width=10, height=10, fill="#ff5"),
    Tag.circle(cx=5, cy=5, r=4, fill="#222")
)

print(root.render(pretty=True))
```

## Installation

Install `soda-svg` from PyPI, like `python -m pip install soda-svg`.

Note that `soda` on PyPI is a different package.

## Tag construction

The main class of the module is `Tag`. You can create it with a constructor:

```python
Tag("g")
```

or with a shorthand:

```python
Tag.g
```

You can also pass children and attributes into the constructor:

```python
Tag("g", child1, child2, attr1=1, attr2=2)
```

or as call arguments (this would change tag in place, no additional copies):

```python
Tag("g")(child1, attr1=1)(child2, child3, attr2=10, attr3=3)
# or
Tag.g(child1, attr1=1)(child2, child3, attr2=10, attr3=3)

# '_' to '-' conversion
Tag.g(child1, attr_1=1)(child2, child3, attr_2=10, attr_3=3) # <g attr-1="1" attr-2="10" attr-3="3" >child1child2child3</g>
```

## Float rounding

As floats can be used as attribute values (even though in resulting SVG every value is a string), module will round floats to 3 digits after the decimal point:

```python
from soda import Tag

g = Tag.g(x=1/3)
print(g.render()) # '<g x="0.333"/>'

```

To change this behaviour, edit `soda.config.decimal_length` before value is assigned:

```python
from soda import Tag, config as soda_config

soda_config.decimal_length = 4

g = Tag.g(x=1/3)
print(g.render()) # '<g x="0.3333"/>'

soda_config.decimal_length = 2
g(y=1/3)

print(g.render()) # '<g x="0.3333" y="0.33"/>'

```

## Attribute conversion

For convenience, leading and trailing underscores are removed by default, and underscores in the middle of words are replaced with hyphens:

```python
from soda import Tag

g = Tag.g(cla_ss_="test")

print(g.render()) # <g cla-ss="test"/>

```

To disable replacing behavior, use `config` class:

```python
from soda import Tag, config as soda_config

soda_config.replace_underscores = False

g = Tag.g(cla_ss_="test")

print(g.render()) # <g cla_ss="test"/>

```

...and to disable stripping of leading/traililng underscores:

```python
from soda import Tag, config as soda_config

soda_config.strip_underscores = False

g = Tag.g(cla_ss_="test")

print(g.render()) # <g cla-ss_="test"/>
```

It's important to do that before tag creation, as all conversions are happening at the tag creation time:

```python

from soda import Tag, config as soda_config

g1 = Tag.g(cla_ss_="test") # g.attributes == {"cla-ss": "test"}

soda_config.replace_underscores = False

g2 = Tag.g(cla_ss_="test") # g.attributes == {"cla_ss_": "test"}

print(g1.render()) # <g cla-ss="test"/>
print(g2.render()) # <g cla_ss_="test"/>

```

## Creating a Tag from XML string

_new in 1.1.0_

You can use `Tag.from_str(xml_string)` to parse an XML document in that string.  
Note that currently this doesn't preserve any comments or declarations of original document.

```python
from soda import Tag, Root

root = Root(viewBox="0 0 10 10")(
    Tag.rect(width=10, height=10, fill="#ff5"),
    Tag.circle(cx=5, cy=5, r=4, fill="#222")
)

rendered_root = root.render(pretty=True)
new_root = Tag.from_str(rendered_root)

assert rendered_root == new_root.render(pretty=True)
```

## Text

Basic text handling is pretty straightforward:

```python
from soda import Tag

Tag.text("Hello, World") # just pass a string as a children
```

This code is roughly equivalent to:

```python
from soda import Tag, Literal

Tag.text(Literal("Hello, World"))
```

...except that first piece doesn't create a `Literal` object.

If you need to add unescaped text (such as prerendered XML), you should pass `escape=False` to a `Literal` constructor:

## XML Declarations and comments

To insert an XML declaration (i.e. `<?xml version="1.0" encoding="UTF-8"?>`), use `XMLDeclaration`:

```python
from soda import XMLDeclaration


print(XMLDeclaration(version="2.0", encoding="UTF-8").render()) # '<?xml version="2.0" encoding="UTF-8"?>'
```

Default values for version and encoding are "1.0" and "UTF-8" respectively

XML comments are used similarly:

```python
from soda import XMLComment


print(XMLComment("comment text!!").render()) # '<!-- comment text!! -->'
```

```python
from soda import Tag, Literal

Tag.g(Literal('<path d="M0 0 L10 0 Z"/>', escape=False))
```

## Accessing data

`tag[attr]` syntax can be used to manage tag attributes (where `attr` should be a string).

```python
from soda import Tag

tag = Tag.g
tag["id"] = "yes-thats-an-id" # sets attribute
tag["cool"] = None # deletes attribute if exists, otherwise does nothing
print(tag["id"]) # prints attribute
print(tag["non-existent-attribute"]) # prints None
```

`tag[index]` syntax can be used to manage tag children (where `index` should be either integer or slice).

```python
from soda import Tag

tag = Tag.g(Tag.a)
tag[0]["href"] = "https://github.com/evtn/soda"
print(tag[1]) # IndexError
print(tag[0]) # prints <a href="https://github.com/evtn/soda" />
```

~~Children can also be accessed directly through `tag.children` attribute.~~

This is not necessary for most tasks as of 1.1.6 with new methods and iterator protocol:

-   `Tag.insert(int, Node)` inserts a node on an index. Be aware that `tag.children` is not flattened: `Tag.g("test", ["test1", "test2"]).insert(2, elem)` will insert `elem` _after_ the array.
-   `Tag.append(Node)` appends one node to the tag.
-   `Tag.extend(*Node)` appends several nodes to the tag. *This is not the same as `.append([*Node])`\*
-   `Tag.pop(int?)` pops one node from specified index. If index is not provided, pops the last one.
-   `Tag.iter_raw()` returns an iterable to get every Node of the tag. This doesn't dive into nested arrays, for that behaviour iterate over `Tag`
-   You can also iterate over the `Tag` itself to get every flat node of it (no arrays)

## Fragments

Fragments use concept similar to React's fragment. It renders just it's children:

```python
from soda import Tag, Fragment

tag = Tag.g(
    Fragment(Tag.a, Tag.a)
)
print(tag) # <g><a/><a/></g>

```

## Paths

_new in 0.1.7_

There is a builder for SVG path commands in soda:

<svg viewBox="0 0 100 100">
    <rect width="100%" height="100%" fill="white"/>
    <path d="M 10,30
           A 20,20 0,0,1 50,30
           A 20,20 0,0,1 90,30
           Q 90,60 50,90
           Q 10,60 10,30 z"
    />
</svg>

You can build a list of path commands using descriptive command names:

```python
from soda import Tag, Root, Path

commands = (
    Path.moveto(x=10, y=30),
    Path.arc(
        radius_x=20,
        radius_y=20,
        # for convenience, omitted arguments
        # (here: x_axis_rotation and large_arc_flag) are set to 0
        sweep_flag=1,
        x=50,
        y=30,
    ),
    Path.arc(
        radius_x=20,
        radius_y=20,
        sweep_flag=1,
        x=90,
        y=30,
    ),
    Path.quadratic(
        x1=90,
        y1=60,
        x=50,
        y=90,
    ),
    Path.quadratic(
        x1=10,
        y1=60,
        x=10,
        y=30,
    ),
    Path.close()
)


```

...or using common SVG command names (letter case signifies if command is relative):

```python

# or

commands = (
    Path.M(10, 30),
    Path.A(20, 20, 0, 0, 1, 50, 30),
    Path.A(20, 20, 0, 0, 1, 50, 30),
    Path.Q(90, 60, 50, 90),
    Path.Q(10, 60, 10, 30),
    Path.Z()
)

```

...and render it with `Path.build(*commands, compact=False)` method

```python

root = Root(
    viewBox="0 0 100 100",
    use_namespace=True,
)(
    Tag.rect(width="100%", height="100%", fill="white"),
    Tag.path()(
        d=Path.build(*commands)
    )
)

print(root.render(pretty=True))

"""
yields:

<svg
  viewBox="0 0 100 100"
  version="2.0"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
>
  <rect
    width="100%"
    height="100%"
    fill="white"
  />
  <path
    d="M 10 30 A 20 20 0 0 1 50 30 A 20 20 0 0 1 50 30 Q 90 60 50 90 Q 10 60 10 30 Z"
  />
</svg>
"""
```

You can also optimize resulting path with `compact` argument:

```python
print(Path.build(*commands, compact=True))
# prints M10 30A20 20 0 0 1 50 30A20 20 0 0 1 50 30Q90 60 50 90Q10 60 10 30Z
```

## Points

_new in 1.1.0_

To work with coordinates, you can use `Point` class:

```python
from soda import Point

a = Point(1, 2)
b = Point(4, 5)
```

In any context where a point could be used, "point-like" values can be used:

-   `(1, 2)` <-> `Point(1, 2)`
-   `[1, 2]` <-> `Point(1, 2)`
-   `1` <-> `Point(1, 1)`

You can use coordinates of a point:

```python
print(a) # Point[1, 2]
print(a.x) # 1
print(a.coords) # (1, 2)
print([*a]) # [1, 2] (same as [*a.coords])
```

...perform mathematical operations on points:

```python
print(a + b) # Point[5, 7]
print(a - b) # Point[-3, -3]
print(a * b) # Point[4, 10] (a.x * b.x, a.y * b.y)
print(a / b) # Point[0.25, 0.4]
print(a % b) # Point[1, 2]
```

...and any point-like values:

```python
print(a + 10) # Point[11, 12]
print(a * 2) # Point[2, 4]
```

You also can calculate distance between points and rotate a point around some point:

```python
from math import pi

print(a.distance(b)) # 4.242640687119285
print(a.distance()) # 2.23606797749979 (distance between a and (0, 0), basically the length of a vector)
print(a.rotate(degrees=90)) # Point[-2, 1]
print(a.rotate((10, 10), degrees=90))
print(a.rotate((10, 10), radians=pi / 2)) # Point[18, 1]
```

...and get a normalized vector:

```python
print(a.normalized()) # Point[0.4472135954999579, 0.8944271909999159]
```

You also can get an angle (in radians) between two vectors (with specified starting point):

```python
print(a.angle(b)) # 0.21109333322274684
print(a.angle(b, (10, 10))) # 0.03190406448501816 (second argument specifies starting point (0, 0) by default)
print(a.angle()) # 1.1071487177940904 (angle between `a` and (1, 0) vector)

print(
    a.angle(
        a.rotate(radians=2)
    )
) # 2
```

### Using as attributes

`Point.as_` provides a convenient way of using points as tag attributes:

```python
from soda import Tag, Point

size = Point(100, 200)

print(size.as_()) # {"x": 100, "y": 200}
print(size.as_("width", "height")) # {"width": 100, "height": 200}


print(
    Tag.rect(
        **size.as_("width", "height")
    )
) # <rect width="100" height="200"/>

```

### PointPath

A version of [`Path`](#paths) accepting `Point` instead of some arguments.
Where Path.something(...) accepts coordinates (as two arguments) or some size (like `radius_x` and `radius_y` in `arc`), `PointPath` accepts a point-like object instead.

## Custom components

You can build custom components, using different approaches:

### Building a tree on init

Builds a tree on every component creation

```python
from soda import Tag, Fragment

class CustomComponent(Fragment):
    def __init__(self):
        children = Tag.g(
            Tag.anythingother,
            Tag.lalala(
                Tag.yes,
                Tag.no
            )
        )
        super().__init__(*children)

CustomComponent().render()
```

### Functional approach

Builds a tree on every call

```python
from soda import Tag

def custom_component():
    return Tag.g(
        Tag.anythingother,
        Tag.lalala(
            Tag.yes,
            Tag.no
        )
    )

custom_component().render()
```

## Speed

soda is able to render tens of thousands tags per second, but if you wanna optimize your execution, there are some tips:

### Building a tree efficiently

If you using the same structure many times (especially if it's a heavy one), avoid rebuilds. Rather than building a new tree every time, consider changing specific parts of it when needed. It won't speed up the render time, though (check Prerendering right below for that)

### Prerendering

If you have some static tags, you can use `tag.prerender()` to get a prerendered `Literal`.
This could speed up your render significantly in some complex cases.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/evtn/soda",
    "name": "soda-svg",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<3.13,>=3.8",
    "maintainer_email": null,
    "keywords": "soda, svg, xml",
    "author": "Dmitry Gritsenko",
    "author_email": "soda@evtn.ru",
    "download_url": "https://files.pythonhosted.org/packages/eb/8d/9a2b7885caa68f1bf533f8748a7db9a6526d263579cdb1ae60ae4ce3be74/soda_svg-2.0.3.tar.gz",
    "platform": null,
    "description": "# soda - a fast SVG generation tool\n\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/evtn/soda/build.yml?branch=lord)](https://github.com/evtn/soda/actions/workflows/build.yml)\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/evtn/soda/test.yml?branch=lord&label=tests)](https://github.com/evtn/soda/actions/workflows/test.yml)\n[![PyPI](https://img.shields.io/pypi/v/soda-svg)](https://pypi.org/project/soda-svg/)\n![PyPI - Downloads](https://pepy.tech/badge/soda-svg)\n![PyPI - Python Version](https://img.shields.io/pypi/pyversions/soda-svg)\n[![Coveralls](https://img.shields.io/coverallsCoverage/github/evtn/soda?label=test%20coverage)](https://coveralls.io/github/evtn/soda?branch=lord)\n![License](https://img.shields.io/github/license/evtn/soda)\n[![wordstreamer badge](https://img.shields.io/badge/renderable-what?label=wordstreamer&color=%2333bb33)](https://github.com/evtn/wordstreamer)\n![Badge Count](https://img.shields.io/badge/badges-9-important)\n\nHere's some basic usage:\n\n> [!NOTE]\n> Since soda 2.0.0, a Tag instance is also a [wordstreamer.Renderable](https://github.com/evtn/wordstreamer), which makes it possible to use things like:\n>\n> ```python\n> from wordstreamer import Renderer\n> from soda import Tag\n>\n> byte_stream = Renderer().byte_stream(Tag.rect()) # Iterable[bytes]\n> ```\n\n```python\nfrom soda import Tag, Root\n\n# root is a custom svg tag\nroot = Root(viewBox=\"0 0 10 10\")(\n    Tag.rect(width=10, height=10, fill=\"#ff5\"),\n    Tag.circle(cx=5, cy=5, r=4, fill=\"#222\")\n)\n\nprint(root.render(pretty=True))\n```\n\n## Installation\n\nInstall `soda-svg` from PyPI, like `python -m pip install soda-svg`.\n\nNote that `soda` on PyPI is a different package.\n\n## Tag construction\n\nThe main class of the module is `Tag`. You can create it with a constructor:\n\n```python\nTag(\"g\")\n```\n\nor with a shorthand:\n\n```python\nTag.g\n```\n\nYou can also pass children and attributes into the constructor:\n\n```python\nTag(\"g\", child1, child2, attr1=1, attr2=2)\n```\n\nor as call arguments (this would change tag in place, no additional copies):\n\n```python\nTag(\"g\")(child1, attr1=1)(child2, child3, attr2=10, attr3=3)\n# or\nTag.g(child1, attr1=1)(child2, child3, attr2=10, attr3=3)\n\n# '_' to '-' conversion\nTag.g(child1, attr_1=1)(child2, child3, attr_2=10, attr_3=3) # <g attr-1=\"1\" attr-2=\"10\" attr-3=\"3\" >child1child2child3</g>\n```\n\n## Float rounding\n\nAs floats can be used as attribute values (even though in resulting SVG every value is a string), module will round floats to 3 digits after the decimal point:\n\n```python\nfrom soda import Tag\n\ng = Tag.g(x=1/3)\nprint(g.render()) # '<g x=\"0.333\"/>'\n\n```\n\nTo change this behaviour, edit `soda.config.decimal_length` before value is assigned:\n\n```python\nfrom soda import Tag, config as soda_config\n\nsoda_config.decimal_length = 4\n\ng = Tag.g(x=1/3)\nprint(g.render()) # '<g x=\"0.3333\"/>'\n\nsoda_config.decimal_length = 2\ng(y=1/3)\n\nprint(g.render()) # '<g x=\"0.3333\" y=\"0.33\"/>'\n\n```\n\n## Attribute conversion\n\nFor convenience, leading and trailing underscores are removed by default, and underscores in the middle of words are replaced with hyphens:\n\n```python\nfrom soda import Tag\n\ng = Tag.g(cla_ss_=\"test\")\n\nprint(g.render()) # <g cla-ss=\"test\"/>\n\n```\n\nTo disable replacing behavior, use `config` class:\n\n```python\nfrom soda import Tag, config as soda_config\n\nsoda_config.replace_underscores = False\n\ng = Tag.g(cla_ss_=\"test\")\n\nprint(g.render()) # <g cla_ss=\"test\"/>\n\n```\n\n...and to disable stripping of leading/traililng underscores:\n\n```python\nfrom soda import Tag, config as soda_config\n\nsoda_config.strip_underscores = False\n\ng = Tag.g(cla_ss_=\"test\")\n\nprint(g.render()) # <g cla-ss_=\"test\"/>\n```\n\nIt's important to do that before tag creation, as all conversions are happening at the tag creation time:\n\n```python\n\nfrom soda import Tag, config as soda_config\n\ng1 = Tag.g(cla_ss_=\"test\") # g.attributes == {\"cla-ss\": \"test\"}\n\nsoda_config.replace_underscores = False\n\ng2 = Tag.g(cla_ss_=\"test\") # g.attributes == {\"cla_ss_\": \"test\"}\n\nprint(g1.render()) # <g cla-ss=\"test\"/>\nprint(g2.render()) # <g cla_ss_=\"test\"/>\n\n```\n\n## Creating a Tag from XML string\n\n_new in 1.1.0_\n\nYou can use `Tag.from_str(xml_string)` to parse an XML document in that string.  \nNote that currently this doesn't preserve any comments or declarations of original document.\n\n```python\nfrom soda import Tag, Root\n\nroot = Root(viewBox=\"0 0 10 10\")(\n    Tag.rect(width=10, height=10, fill=\"#ff5\"),\n    Tag.circle(cx=5, cy=5, r=4, fill=\"#222\")\n)\n\nrendered_root = root.render(pretty=True)\nnew_root = Tag.from_str(rendered_root)\n\nassert rendered_root == new_root.render(pretty=True)\n```\n\n## Text\n\nBasic text handling is pretty straightforward:\n\n```python\nfrom soda import Tag\n\nTag.text(\"Hello, World\") # just pass a string as a children\n```\n\nThis code is roughly equivalent to:\n\n```python\nfrom soda import Tag, Literal\n\nTag.text(Literal(\"Hello, World\"))\n```\n\n...except that first piece doesn't create a `Literal` object.\n\nIf you need to add unescaped text (such as prerendered XML), you should pass `escape=False` to a `Literal` constructor:\n\n## XML Declarations and comments\n\nTo insert an XML declaration (i.e. `<?xml version=\"1.0\" encoding=\"UTF-8\"?>`), use `XMLDeclaration`:\n\n```python\nfrom soda import XMLDeclaration\n\n\nprint(XMLDeclaration(version=\"2.0\", encoding=\"UTF-8\").render()) # '<?xml version=\"2.0\" encoding=\"UTF-8\"?>'\n```\n\nDefault values for version and encoding are \"1.0\" and \"UTF-8\" respectively\n\nXML comments are used similarly:\n\n```python\nfrom soda import XMLComment\n\n\nprint(XMLComment(\"comment text!!\").render()) # '<!-- comment text!! -->'\n```\n\n```python\nfrom soda import Tag, Literal\n\nTag.g(Literal('<path d=\"M0 0 L10 0 Z\"/>', escape=False))\n```\n\n## Accessing data\n\n`tag[attr]` syntax can be used to manage tag attributes (where `attr` should be a string).\n\n```python\nfrom soda import Tag\n\ntag = Tag.g\ntag[\"id\"] = \"yes-thats-an-id\" # sets attribute\ntag[\"cool\"] = None # deletes attribute if exists, otherwise does nothing\nprint(tag[\"id\"]) # prints attribute\nprint(tag[\"non-existent-attribute\"]) # prints None\n```\n\n`tag[index]` syntax can be used to manage tag children (where `index` should be either integer or slice).\n\n```python\nfrom soda import Tag\n\ntag = Tag.g(Tag.a)\ntag[0][\"href\"] = \"https://github.com/evtn/soda\"\nprint(tag[1]) # IndexError\nprint(tag[0]) # prints <a href=\"https://github.com/evtn/soda\" />\n```\n\n~~Children can also be accessed directly through `tag.children` attribute.~~\n\nThis is not necessary for most tasks as of 1.1.6 with new methods and iterator protocol:\n\n-   `Tag.insert(int, Node)` inserts a node on an index. Be aware that `tag.children` is not flattened: `Tag.g(\"test\", [\"test1\", \"test2\"]).insert(2, elem)` will insert `elem` _after_ the array.\n-   `Tag.append(Node)` appends one node to the tag.\n-   `Tag.extend(*Node)` appends several nodes to the tag. *This is not the same as `.append([*Node])`\\*\n-   `Tag.pop(int?)` pops one node from specified index. If index is not provided, pops the last one.\n-   `Tag.iter_raw()` returns an iterable to get every Node of the tag. This doesn't dive into nested arrays, for that behaviour iterate over `Tag`\n-   You can also iterate over the `Tag` itself to get every flat node of it (no arrays)\n\n## Fragments\n\nFragments use concept similar to React's fragment. It renders just it's children:\n\n```python\nfrom soda import Tag, Fragment\n\ntag = Tag.g(\n    Fragment(Tag.a, Tag.a)\n)\nprint(tag) # <g><a/><a/></g>\n\n```\n\n## Paths\n\n_new in 0.1.7_\n\nThere is a builder for SVG path commands in soda:\n\n<svg viewBox=\"0 0 100 100\">\n    <rect width=\"100%\" height=\"100%\" fill=\"white\"/>\n    <path d=\"M 10,30\n           A 20,20 0,0,1 50,30\n           A 20,20 0,0,1 90,30\n           Q 90,60 50,90\n           Q 10,60 10,30 z\"\n    />\n</svg>\n\nYou can build a list of path commands using descriptive command names:\n\n```python\nfrom soda import Tag, Root, Path\n\ncommands = (\n    Path.moveto(x=10, y=30),\n    Path.arc(\n        radius_x=20,\n        radius_y=20,\n        # for convenience, omitted arguments\n        # (here: x_axis_rotation and large_arc_flag) are set to 0\n        sweep_flag=1,\n        x=50,\n        y=30,\n    ),\n    Path.arc(\n        radius_x=20,\n        radius_y=20,\n        sweep_flag=1,\n        x=90,\n        y=30,\n    ),\n    Path.quadratic(\n        x1=90,\n        y1=60,\n        x=50,\n        y=90,\n    ),\n    Path.quadratic(\n        x1=10,\n        y1=60,\n        x=10,\n        y=30,\n    ),\n    Path.close()\n)\n\n\n```\n\n...or using common SVG command names (letter case signifies if command is relative):\n\n```python\n\n# or\n\ncommands = (\n    Path.M(10, 30),\n    Path.A(20, 20, 0, 0, 1, 50, 30),\n    Path.A(20, 20, 0, 0, 1, 50, 30),\n    Path.Q(90, 60, 50, 90),\n    Path.Q(10, 60, 10, 30),\n    Path.Z()\n)\n\n```\n\n...and render it with `Path.build(*commands, compact=False)` method\n\n```python\n\nroot = Root(\n    viewBox=\"0 0 100 100\",\n    use_namespace=True,\n)(\n    Tag.rect(width=\"100%\", height=\"100%\", fill=\"white\"),\n    Tag.path()(\n        d=Path.build(*commands)\n    )\n)\n\nprint(root.render(pretty=True))\n\n\"\"\"\nyields:\n\n<svg\n  viewBox=\"0 0 100 100\"\n  version=\"2.0\"\n  xmlns=\"http://www.w3.org/2000/svg\"\n  xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n>\n  <rect\n    width=\"100%\"\n    height=\"100%\"\n    fill=\"white\"\n  />\n  <path\n    d=\"M 10 30 A 20 20 0 0 1 50 30 A 20 20 0 0 1 50 30 Q 90 60 50 90 Q 10 60 10 30 Z\"\n  />\n</svg>\n\"\"\"\n```\n\nYou can also optimize resulting path with `compact` argument:\n\n```python\nprint(Path.build(*commands, compact=True))\n# prints M10 30A20 20 0 0 1 50 30A20 20 0 0 1 50 30Q90 60 50 90Q10 60 10 30Z\n```\n\n## Points\n\n_new in 1.1.0_\n\nTo work with coordinates, you can use `Point` class:\n\n```python\nfrom soda import Point\n\na = Point(1, 2)\nb = Point(4, 5)\n```\n\nIn any context where a point could be used, \"point-like\" values can be used:\n\n-   `(1, 2)` <-> `Point(1, 2)`\n-   `[1, 2]` <-> `Point(1, 2)`\n-   `1` <-> `Point(1, 1)`\n\nYou can use coordinates of a point:\n\n```python\nprint(a) # Point[1, 2]\nprint(a.x) # 1\nprint(a.coords) # (1, 2)\nprint([*a]) # [1, 2] (same as [*a.coords])\n```\n\n...perform mathematical operations on points:\n\n```python\nprint(a + b) # Point[5, 7]\nprint(a - b) # Point[-3, -3]\nprint(a * b) # Point[4, 10] (a.x * b.x, a.y * b.y)\nprint(a / b) # Point[0.25, 0.4]\nprint(a % b) # Point[1, 2]\n```\n\n...and any point-like values:\n\n```python\nprint(a + 10) # Point[11, 12]\nprint(a * 2) # Point[2, 4]\n```\n\nYou also can calculate distance between points and rotate a point around some point:\n\n```python\nfrom math import pi\n\nprint(a.distance(b)) # 4.242640687119285\nprint(a.distance()) # 2.23606797749979 (distance between a and (0, 0), basically the length of a vector)\nprint(a.rotate(degrees=90)) # Point[-2, 1]\nprint(a.rotate((10, 10), degrees=90))\nprint(a.rotate((10, 10), radians=pi / 2)) # Point[18, 1]\n```\n\n...and get a normalized vector:\n\n```python\nprint(a.normalized()) # Point[0.4472135954999579, 0.8944271909999159]\n```\n\nYou also can get an angle (in radians) between two vectors (with specified starting point):\n\n```python\nprint(a.angle(b)) # 0.21109333322274684\nprint(a.angle(b, (10, 10))) # 0.03190406448501816 (second argument specifies starting point (0, 0) by default)\nprint(a.angle()) # 1.1071487177940904 (angle between `a` and (1, 0) vector)\n\nprint(\n    a.angle(\n        a.rotate(radians=2)\n    )\n) # 2\n```\n\n### Using as attributes\n\n`Point.as_` provides a convenient way of using points as tag attributes:\n\n```python\nfrom soda import Tag, Point\n\nsize = Point(100, 200)\n\nprint(size.as_()) # {\"x\": 100, \"y\": 200}\nprint(size.as_(\"width\", \"height\")) # {\"width\": 100, \"height\": 200}\n\n\nprint(\n    Tag.rect(\n        **size.as_(\"width\", \"height\")\n    )\n) # <rect width=\"100\" height=\"200\"/>\n\n```\n\n### PointPath\n\nA version of [`Path`](#paths) accepting `Point` instead of some arguments.\nWhere Path.something(...) accepts coordinates (as two arguments) or some size (like `radius_x` and `radius_y` in `arc`), `PointPath` accepts a point-like object instead.\n\n## Custom components\n\nYou can build custom components, using different approaches:\n\n### Building a tree on init\n\nBuilds a tree on every component creation\n\n```python\nfrom soda import Tag, Fragment\n\nclass CustomComponent(Fragment):\n    def __init__(self):\n        children = Tag.g(\n            Tag.anythingother,\n            Tag.lalala(\n                Tag.yes,\n                Tag.no\n            )\n        )\n        super().__init__(*children)\n\nCustomComponent().render()\n```\n\n### Functional approach\n\nBuilds a tree on every call\n\n```python\nfrom soda import Tag\n\ndef custom_component():\n    return Tag.g(\n        Tag.anythingother,\n        Tag.lalala(\n            Tag.yes,\n            Tag.no\n        )\n    )\n\ncustom_component().render()\n```\n\n## Speed\n\nsoda is able to render tens of thousands tags per second, but if you wanna optimize your execution, there are some tips:\n\n### Building a tree efficiently\n\nIf you using the same structure many times (especially if it's a heavy one), avoid rebuilds. Rather than building a new tree every time, consider changing specific parts of it when needed. It won't speed up the render time, though (check Prerendering right below for that)\n\n### Prerendering\n\nIf you have some static tags, you can use `tag.prerender()` to get a prerendered `Literal`.\nThis could speed up your render significantly in some complex cases.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Fast SVG generation tool",
    "version": "2.0.3",
    "project_urls": {
        "Homepage": "https://github.com/evtn/soda",
        "Repository": "https://github.com/evtn/soda"
    },
    "split_keywords": [
        "soda",
        " svg",
        " xml"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9bf1376caa06081658ab0acee5cbf2b020dc330a0f75fa76092d0ec145682421",
                "md5": "da53581422710764acbb24cff0efc5eb",
                "sha256": "ddd5b91539b7beba425b7b85efd082c018e12fb7d9cfaf5845ee8f5bc542cdc3"
            },
            "downloads": -1,
            "filename": "soda_svg-2.0.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "da53581422710764acbb24cff0efc5eb",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<3.13,>=3.8",
            "size": 16614,
            "upload_time": "2024-10-10T18:11:05",
            "upload_time_iso_8601": "2024-10-10T18:11:05.158884Z",
            "url": "https://files.pythonhosted.org/packages/9b/f1/376caa06081658ab0acee5cbf2b020dc330a0f75fa76092d0ec145682421/soda_svg-2.0.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "eb8d9a2b7885caa68f1bf533f8748a7db9a6526d263579cdb1ae60ae4ce3be74",
                "md5": "63b10919919f85c1b1b6b1a3f0905317",
                "sha256": "376efa12a4115fd5d35b2394ea6b867ca61899716e170baebb20bdb98f5e0c35"
            },
            "downloads": -1,
            "filename": "soda_svg-2.0.3.tar.gz",
            "has_sig": false,
            "md5_digest": "63b10919919f85c1b1b6b1a3f0905317",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<3.13,>=3.8",
            "size": 18355,
            "upload_time": "2024-10-10T18:11:06",
            "upload_time_iso_8601": "2024-10-10T18:11:06.490911Z",
            "url": "https://files.pythonhosted.org/packages/eb/8d/9a2b7885caa68f1bf533f8748a7db9a6526d263579cdb1ae60ae4ce3be74/soda_svg-2.0.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-10-10 18:11:06",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "evtn",
    "github_project": "soda",
    "travis_ci": false,
    "coveralls": true,
    "github_actions": true,
    "lcname": "soda-svg"
}
        
Elapsed time: 0.84925s