JanetRossini.github.io

Lua, LSL, Blender, Python in Second Life


Project maintained by JanetRossini

SVG Group

Aug 13, 2025 • [linkagesluamoverstesting]


OK, let’s try to add a Group to Suzanna’s SVG library. How hard could it be?

The basic idea will be to add a new kind of shape, maybe with some parameters, maybe just with the ability to accept the existing messages like translate, depending how the library works now. And we’ll accept a function as a parameter, and when it comes time to draw, we’ll emit the ‘<g>’’ lie, call the function, and emit the ‘</g>’. What happens in the function, stays in between the g’s.

It should be easy. It might even work. I might even try to test it: I’ll put the test right in the linkages project, although a more careful person would create a new test file just for SVG changes. I’ll keep the tests for group in a separate feature function, so they can be moved if need be.

function _:featureSvgGroup()
    _:describe("svg group object", function()
        _:test("group emits g tags", function()
            local f = function()
                svg.Circle{cx=5, cy=5, r=23}
            end
            local group = svg.Group(f)
            group:translate(123,456)
            local s = svg.Shape:draw()
            print(s)
            _:expect(s).has('<g')
            _:expect(s).has('transform="translate(123, 456)"')
            _:expect(s).has('</g>')
        end)
    end)
end

That seems easy enough. It isn’t quite as easy as I’d like: I’m having trouble seeing just how to emit the string at the right moment. I need to think a bit more deeply about just how the SVG code works.

It would be easy to do a group object and a group-closing one. I am not seeing how to get the open and close, plus the contents.

When a Shape subclass is instantiated, the new code in Shape adds it to the instances right then. So if we were to say Group(f) and f creates a Line and a Circle, Shape will not see the line and the circle until we call f. If we call it during draw it will add random objects to the instances list while we iterate it. That will not go sell, because they’ll be at the end.

So we must call the function when the Group is created.

Ah. We will ourselves create two Shape objects, a Group_begin and a Group_end, and we’ll call the function in between those.

OK, we can do this. Probably.

I have to define two new svg subclasses. GroupEnd, and of course Group:

local GroupEnd = {}
GroupEnd.__index = GroupEnd
GroupEnd.attribute_order = {}

setmetatable(GroupEnd, {
    __index = Shape,
    __call = function(class, ...)
        return class:new(...)
    end
})

function GroupEnd:new(props)
    local group_end = Shape.new(self,props)
    return group_end
end

function GroupEnd:__tostring()
    return '</g>'
end

local Group = {}
Group.__index = Group
Group.attribute_order = {}

setmetatable(Group, {
    __index = Shape,
    __call = function(class, ...)
        return class:new(...)
    end
})

function Group:new(contents)
    local group = Shape.new(self, {})
    contents = contents or function() end
    contents()
    GroupEnd()
    return group
end

function Group:__tostring()
    return self:_SVG('g')
end

Reflection

My test is running, and the output is righteous:

  <svg width="640" height="480" xmlns="http://www.w3.org/2000/svg" version="1.1">
<g fill="none" stroke="none" opacity="1" transform="translate(123, 456)" />
<circle cx="5" cy="5" r="23" fill="none" stroke="none" opacity="1" />
</g>  </svg>

We have the g tag, the circle, then the /g tag. All good. However, it’s going to be kind of a pain to create groups, as each group has to be in a function. We’ll see. The tests are OK that way, once you get used to it.

I don’t think “once you get used to it” is a great recommendation for anything, though. Kind of like “it’s an acquired taste”, which means “this tastes really nasty but you get used to it”.

Anyway, I think this is working. I’ll try making two rows now.

The first thing I learn is that we really need a way to reset the Shape code, because if two patches of code try to draw, Shape records all of them, resulting in what we call in the trade: “chaos”. Fine, live and learn, that’s why we’re here.

… the next day …

I return to the computer after starting my morning, and realize that I was suddenly distracted in the middle of yesterday’s work, and drawn away. We’ll pause here and start a new article, because I have recognized an issue to be dealt with.

Safe paths!