Group
A group is a shape that contains multiple shapes, rendered under <g>
tag. For example, one can write,
t.s1 = Circle {}
t.s2 = Rectangle {}
t.s3 = Text {
string: "Hello"
}
t.g = Group {
shapes : [t.s1, t.s2, t.s3]
}
t.s4 = Circle {}
t.g2 = Group {
shapes : [t.s4, t.g]
}
and get something like,
<svg ...>
<g>
<circle ... />
<g>
<circle ... />
<rect ... />
<text ... />
</g>
</g>
</svg>
Properties
Property | Type | Default |
---|---|---|
name | StrV | "defaultGroup" |
ensureOnCanvas | BoolV | true |
shapes | ShapeListV | [] |
clipPath | ClipDataV | {"tag":"NoClip"} |
Constructing a Group
A Group
shape has a property, shapes
, which is an unordered list of paths to previously-defined shapes. In the example above, shape t.g2
is a group that contains shape t.s4
(a Circle
) and shape t.g
(another Group
). As illustrated, Group
shapes can be nested.
We prohibit inline shape declarations within groups. In other words,
-- this is bad
bad_group = Group {
shapes: [ Circle {}, Rectangle {} ]
}
does not compile. All shapes within a group must be declared previously and referred to by their paths.
We follow the convention that a shape cannot be contained within multiple groups. If Penrose detects that multiple groups contain the same shape, it will fire a warning and attempt to find the "best" grouping that does not have this issue.
Layering Semantics
Inherently, shapes within a group are to stay together and be drawn together (meaning rendered on consecutive layers). In other words, if Group g
only contains s1
and s2
, then they must be drawn in the order of either s1, s2
or s2, s1
, consecutively.
Hence, the following layering should not be allowed (and will hence generate a warning),
s1 = // some shape
s2 = // some shape
g = Group {
shapes: [s1, s2]
}
s3 = // some shape
layer s1 above s2
layer s3 above s2
layer s3 below s1
because the layer directives enforce the layer ordering of bottom, s2, s3, s1, top
which breaks apart group g
containing s1, s2
.
To achieve this goal, Group
shapes have the following layering semantics:
- Layering directives on a group applies also to all members of the group.
- Layering directives on a group member applies to the entire group too.
Clipping
A Group
shape also has an optional clipPath
property that allows shapes within the group to be clipped by another shape, analogous to the clipPath
property in SVG (see link). Parts of a Group
shape that lie outside of the region bounded by the clipping shape are not drawn.
In Penrose, the clipPath
property can take in two types of values:
noClip()
denotes that thisGroup
shape is not clipped by any other shape.clip(someShape)
denotes that thisGroup
shape is clipped bysomeShape
, which, just like other group members, must be declared previously and referred to by its path.
Since the SVG standard does not allow Group
shapes (<g>
tags) to clip other shapes, we disallow this too. That is, clip(g)
where g
is a Group
shape is not allowed.
As an example, to define a Group
shape with members s1
and s2
, clipped by s3
, one can write,
g = Group {
shapes: [s1, s2]
clipPath: clip(s3)
}
A shape that is used to clip a Group
is conceptually a member of that group. So the prohibition of multiple groups having the same shape as their member still stands.
Bounding Box
The bounding box of a Group
shape that is not clipped by any other shape is computed as the smallest axis-aligned rectangle that contains the bounding boxes of all its member shapes.
If this group is clipped by another shape, then the bounding box of the clipped Group
shape is computed as the intersection of the following two bounding boxes:
- The bounding box of the
Group
shape, excluding the clipped shape, and - The bounding box of the clipping shape.