diff --git a/README.md b/README.md index 74b217e..b1fc5b9 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ For example, draw 10 circles: ``` c = circle(x=-100, y=0, r=50) -shape = c | Repeat(10, Translate(x=10, y=0) +shape = c | repeat(10, translate(x=10, y=0) show(shape) ``` diff --git a/joy.py b/joy.py index b16af98..210fc13 100644 --- a/joy.py +++ b/joy.py @@ -249,8 +249,7 @@ def render(self): svg_header = render_tag(**attrs)+ "\n" svg_footer = "\n" - # flip the y axis so that y grows upwards - node = Group(self.nodes) | Scale(sx=1, sy=-1) + node = Group(self.nodes) return svg_header + node._svg() + svg_footer @@ -490,6 +489,61 @@ class Group(Shape): def __init__(self, shapes, **kwargs): super().__init__("g", children=shapes, **kwargs) +class Text(Shape): + """Creates a text node. + + Parameters: + position: + The position of the text node. + Defaults to (0, 0) when not specified from the lower + left corner. + + Examples: + + Draw a text node: + + >>> t = text("Hello!") + >>> show(t) + + Draw a text node centered at (50, 50). + + >>> t = text("Hello!", center=Point(x=100, y=100), alignment_baseline="middle", text_anchor="middle") + >>> show(t) + """ + def __init__(self, content, anchor=Point(0, 0), **kwargs): + self.anchor = anchor + # self.width = needs to be determined dynamically + # self.height = needs to be determined dynamically + + cx, cy = self.anchor.x, self.anchor.y + x = cx + y = cy + super().__init__( + tag="text", + children=[Content(content)], + x=x, + y=y, + **kwargs) + +class Content(): + """Content is the holds actual character data for a text node. + + This uses a minimal subset of the Shape interface but cannot be used alone. + """ + def __init__(self, content, **attrs): + """Creates a new character data object. + """ + self.content = content + + def _svg(self, indent="") -> str: + """Returns the svg representation of this node. + + As this object is a leaf and does not have children we only + return its content directly. + """ + return self.content + + def render_tag(tag, *, close=False, **attrs): """Renders a html/svg tag. @@ -793,7 +847,7 @@ def circle(x=0, y=0, r=100, **kwargs): c = circle(x=10, y=20, r=50) show(c) """ - return Circle(center=Point(x=x, y=y), radius=r, **kwargs) + return Circle(center=Point(x=x, y=-y), radius=r, **kwargs) def rectangle(x=0, y=0, w=200, h=100, **kwargs): """Creates a rectangle with center at (x, y), a width of w and a height of h. @@ -815,7 +869,7 @@ def rectangle(x=0, y=0, w=200, h=100, **kwargs): r = rectangle(x=10, y=20, w=100, h=50) show(r) """ - return Rectangle(center=Point(x=x, y=y), width=w, height=h, **kwargs) + return Rectangle(center=Point(x=x, y=-y), width=w, height=h, **kwargs) def ellipse(x=0, y=0, w=200, h=100, **kwargs): """Creates an ellipse with center at (x, y), a width of w and a height of h. @@ -837,7 +891,7 @@ def ellipse(x=0, y=0, w=200, h=100, **kwargs): r = ellipse(x=10, y=20, w=100, h=50) show(r) """ - return Ellipse(center=Point(x=x, y=y), width=w, height=h, **kwargs) + return Ellipse(center=Point(x=x, y=-y), width=w, height=h, **kwargs) def line(x1=None, y1=None, x2=None, y2=None, **kwargs): """Creates a line from point (x1, y1) to point (x2, y2). @@ -856,7 +910,7 @@ def line(x1=None, y1=None, x2=None, y2=None, **kwargs): x1, y1 = -100, 0 x2, y2 = 100, 0 else: - pairs = dict(x1=x1, y1=y1, x2=x2, y2=y2) + pairs = dict(x1=x1, y1=-y1, x2=x2, y2=-y2) missing = [name for name, value in pairs.items() if value is None] if missing: raise Exception("missing arguments for line: ", ", ".join(missing)) @@ -866,7 +920,7 @@ def line(x1=None, y1=None, x2=None, y2=None, **kwargs): def point(x, y): """Creates a Point with x and y coordinates. """ - return Point(x, y) + return Point(x, -y) def polygon(points, **kwargs): """Creates a polygon with given list points. @@ -879,7 +933,7 @@ def polygon(points, **kwargs): triangle = polygon([p1, p2, p3]) show(triangle) """ - points_str = " ".join(f"{p.x},{p.y}" for p in points) + points_str = " ".join(f"{p.x},{-p.y}" for p in points) return Shape(tag="polygon", points=points_str, **kwargs) def polyline(points, **kwargs): @@ -894,9 +948,28 @@ def polyline(points, **kwargs): line = polyline([p1, p2, p3, p4]) show(line) """ - points_str = " ".join(f"{p.x},{p.y}" for p in points) + points_str = " ".join(f"{p.x},{-p.y}" for p in points) return Shape(tag="polyline", points=points_str, **kwargs) +def text(content, x=0, y=0, fill="black", **kwargs): + """Creates a text node using the given content with anchor point at (x, y). + + Examples: + + Draw a text node. + + t = text("Hello!") + show(t) + + Draw a text node centered at (0, 0) with specific font features. + t = text("Hello!", x=0, y=0, font_family="Arial", + font_size="20", fill="black", + alignment_baseline="middle", text_anchor="left") + show(t) + + """ + return Text(content, anchor=Point(x=x, y=-y), **kwargs) + def translate(x=0, y=0): """Translates a shape. @@ -914,7 +987,7 @@ def translate(x=0, y=0): shape = circle() | translate(x=10, y=20) """ - return Translate(x=x, y=y) + return Translate(x=x, y=-y) def scale(s=None, x=1, y=1): """Scales a shape.