Last active
October 9, 2023 17:09
-
-
Save cambiata/aa3e151f49b456bec85665872e5df64e to your computer and use it in GitHub Desktop.
SimpleSvg - Proof of concept Fuse for displaying Svg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- SimpleSvg.Fuse | |
-- Proof of concept Fuse for displaying Svg content using the fuse drawing api | |
-- This file should be put in the Fustion/Fuses folder | |
-- (On windows, something like C:\ProgramData\Blackmagic Design\Fusion\Fuses) | |
require('SimpleSvgGraphics') | |
FuRegisterClass("SimpleSvg", CT_SourceTool, { | |
REGS_Name = "SimpleSvg", | |
REGS_Category = "WorkInProgress", | |
REGS_OpIconString = "Min", | |
REGS_OpDescription = "Minimal Fuse example.", | |
REG_Source_GlobalCtrls = true, | |
REG_Source_SizeCtrls = true, | |
}) | |
function Create() | |
-- outputs | |
OutImage = self:AddOutput("Output", "Output", { | |
LINKID_DataType = "Image", | |
LINK_Main = 1, | |
}) | |
InXmlText = self:AddInput("XmlText", "XmlText", { | |
LINKID_DataType = "Text", | |
INPID_InputControl = "TextEditControl", | |
TEC_Lines = 30, -- How many lines high is the Input. | |
}) | |
InScale = self:AddInput("Scale", "Scale", { | |
LINKID_DataType = "Number", | |
INPID_InputControl = "SliderControl", | |
INP_MinAllowed = 0.1, | |
INP_MaxScale = 3, | |
INP_Default = 1, | |
}) | |
InInvert = self:AddInput("Invert", "Invert", { | |
LINKID_DataType = "Number", | |
INPID_InputControl = "CheckboxControl", | |
INP_Integer = true, | |
INP_Default = 1, | |
}) | |
end | |
function NotifyChanged(inp, param, time) | |
end | |
function Process(req) | |
-- Standard set up for Creator tools | |
local realwidth = Width; | |
local realheight = Height; | |
-- We'll handle proxy ourselves | |
Width = Width / Scale | |
Height = Height / Scale | |
Scale = 1 | |
-- Attributes for new images | |
local imgattrs = { | |
IMG_Document = self.Comp, | |
{ IMG_Channel = "Red", }, | |
{ IMG_Channel = "Green", }, | |
{ IMG_Channel = "Blue", }, | |
{ IMG_Channel = "Alpha", }, | |
IMG_Width = Width, | |
IMG_Height = Height, | |
IMG_XScale = XAspect, | |
IMG_YScale = YAspect, | |
IMAT_OriginalWidth = realwidth, | |
IMAT_OriginalHeight = realheight, | |
IMG_Quality = not req:IsQuick(), | |
IMG_MotionBlurQuality = not req:IsNoMotionBlur(), | |
} | |
-- Set up image | |
local img = Image(imgattrs) | |
local out = img:CopyOf() | |
local p = Pixel({ R = 0, G = 0, B = 0, A = 0 }) | |
img:Fill(p) -- Clear the image so the next frame doesn't contain the | |
out:Fill(p) | |
-- Get parameters from controls | |
local aspect = calcAspect(img) | |
local scale = InScale:GetValue(req).Value; | |
local xmlText = InXmlText:GetValue(req).Value; | |
local invert = InInvert:GetValue(req).Value; | |
DrawSimpleSvg(out, xmlText, scale, invert, aspect) | |
OutImage:Set(req, out) | |
end | |
function calcAspect(ref_img) | |
return (ref_img.Height * ref_img.YScale) / (ref_img.Width * ref_img.XScale) | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- SimpleSvgGraphics.lua | |
-- Library file for of SimpleSvg.fuse - proof of concept Fuse for displaying Svg content using the fuse drawing api | |
-- This file should be put in the Fusion/Modules/Lua folder | |
-- (on windows something like C:\ProgramData\Blackmagic Design\Fusion\Modules\Lua) | |
require("SimpleSvgParse") | |
function DrawSimpleSvg(out, xml, scale, invert, aspect) | |
if xml == nil or xml == "" then | |
print('xml is nil') | |
return; | |
end | |
local scaleX = scale * 0.0005 | |
local scaleY = scaleX | |
local moveX = 0.0 | |
local moveY = 0.0 | |
if invert == 1 then | |
scaleY = -scaleY | |
moveY = moveY + aspect | |
end | |
local items = ParseSvgText(xml, scaleX, scaleY, moveX, moveY); | |
DrawItems(out, items) | |
end | |
function GetColor(colorString) | |
if colorString == 'black' then | |
return Pixel({ R = 0, G = 0, B = 0, A = 1 }) | |
elseif colorString == 'white' then | |
return Pixel({ R = 1, G = 1, B = 1, A = 1 }) | |
elseif colorString == 'red' then | |
return Pixel({ R = 1, G = 0, B = 0, A = 1 }) | |
elseif colorString == 'green' then | |
return Pixel({ R = 0, G = 1, B = 0, A = 1 }) | |
elseif colorString == 'blue' then | |
return Pixel({ R = 0, G = 0, B = 1, A = 1 }) | |
elseif colorString == 'yellow' then | |
return Pixel({ R = 1, G = 1, B = 0, A = 1 }) | |
elseif colorString == 'cyan' then | |
return Pixel({ R = 0, G = 1, B = 1, A = 1 }) | |
elseif colorString == 'magenta' then | |
return Pixel({ R = 1, G = 0, B = 1, A = 1 }) | |
end | |
return Pixel({ R = 1, G = 1, B = 1, A = 1 }) | |
end | |
function DrawItemStroke(out, shape, stroke, strokeWidth) | |
assert('stroke' ~= nil, "DrawItemStroke: stroke must not be nil") | |
assert('strokeWidth' ~= nil, "DrawItemStroke: strokeWidth must not be nil") | |
local ic = ImageChannel(out, 8) | |
local cs = ChannelStyle() | |
shape = shape:OutlineOfShape(strokeWidth, "OLT_Solid") | |
cs.Color = GetColor(stroke) | |
ic:ShapeFill(shape) | |
if self.Status == "OK" then | |
ic:PutToImage("CM_Merge", cs) | |
end | |
end | |
function DrawItemFill(out, shape, fill) | |
local ic = ImageChannel(out, 8) | |
local cs = ChannelStyle() | |
cs.Color = GetColor(fill) | |
ic:ShapeFill(shape) | |
if self.Status == "OK" then | |
ic:PutToImage("CM_Merge", cs) | |
end | |
end | |
function DrawItems(out, items) | |
for i, v in pairs(items) do | |
if v[1] == 'line' then | |
local shape = Shape() | |
shape:MoveTo(v[2].x1, v[2].y1) | |
shape:LineTo(v[2].x2, v[2].y2) | |
DrawItemStroke(out, shape, v[2].stroke, v[2].stroke_width) | |
elseif v[1] == 'rect' then | |
if (v[2].fill ~= nil and v[2].fill ~= 'none' and v[2].fill ~= 'transparent') then | |
local shape = Shape() | |
shape:MoveTo(v[2].x, v[2].y) | |
shape:LineTo(v[2].x + v[2].width, v[2].y) | |
shape:LineTo(v[2].x + v[2].width, v[2].y + v[2].height) | |
shape:LineTo(v[2].x, v[2].y + v[2].height) | |
shape:Close() | |
DrawItemFill(out, shape, v[2].fill) | |
end | |
if (v[2].stroke ~= nil and v[2].stroke ~= 'none') then | |
local shape = Shape() | |
shape:MoveTo(v[2].x, v[2].y) | |
shape:LineTo(v[2].x + v[2].width, v[2].y) | |
shape:LineTo(v[2].x + v[2].width, v[2].y + v[2].height) | |
shape:LineTo(v[2].x, v[2].y + v[2].height) | |
shape:Close() | |
DrawItemStroke(out, shape, v[2].stroke, v[2].stroke_width) | |
end | |
elseif v[1] == 'ellipse' then | |
local x = v[2].cx | |
local y = v[2].cy | |
local rx = v[2].rx | |
local ry = v[2].ry | |
if (v[2].fill ~= nil and v[2].fill ~= 'none' and v[2].fill ~= 'transparent') then | |
local shape = Shape() | |
shape:MoveTo(x, y - ry) | |
BezierTo2(shape, { X = x, Y = y - ry }, { X = x - rx, Y = y - ry }, { X = x - rx, Y = y }, | |
{ X = x - rx, Y = y }, 20) | |
BezierTo2(shape, { X = x - rx, Y = y }, { X = x - rx, Y = y + ry }, { X = x, Y = y + ry }, | |
{ X = x, Y = y + ry }, 20) | |
BezierTo2(shape, { X = x, Y = y + ry }, { X = x + rx, Y = y + ry }, { X = x + rx, Y = y }, | |
{ X = x + rx, Y = y }, 20) | |
BezierTo2(shape, { X = x + rx, Y = y }, { X = x + rx, Y = y - ry }, { X = x, Y = y - ry }, | |
{ X = x, Y = y - ry }, 20) | |
shape:Close() | |
DrawItemFill(out, shape, v[2].fill) | |
end | |
if (v[2].stroke ~= nil and v[2].stroke ~= 'none') then | |
local shape = Shape() | |
shape:MoveTo(x, y - ry) | |
BezierTo2(shape, { X = x, Y = y - ry }, { X = x - rx, Y = y - ry }, { X = x - rx, Y = y }, | |
{ X = x - rx, Y = y }, 20) | |
BezierTo2(shape, { X = x - rx, Y = y }, { X = x - rx, Y = y + ry }, { X = x, Y = y + ry }, | |
{ X = x, Y = y + ry }, 20) | |
BezierTo2(shape, { X = x, Y = y + ry }, { X = x + rx, Y = y + ry }, { X = x + rx, Y = y }, | |
{ X = x + rx, Y = y }, 20) | |
BezierTo2(shape, { X = x + rx, Y = y }, { X = x + rx, Y = y - ry }, { X = x, Y = y - ry }, | |
{ X = x, Y = y - ry }, 20) | |
DrawItemStroke(out, shape, v[2].stroke, v[2].stroke_width) | |
end | |
elseif v[1] == 'path' then | |
local prevX = nil; | |
local prevY = nil; | |
for i, item in pairs(v[2].d) do | |
if item[1] == 'M' then | |
prevX = item[2][1] | |
prevY = item[2][2] | |
elseif item[1] == 'L' then | |
prevX = item[2][1] | |
prevY = item[2][2] | |
elseif item[1] == 'Q' then | |
item[2][5] = prevX | |
item[2][6] = prevY | |
prevX = item[2][3] | |
prevY = item[2][4] | |
elseif item[1] == 'C' then | |
item[2][7] = item[2][5] | |
item[2][8] = item[2][6] | |
item[2][5] = prevX | |
item[2][6] = prevY | |
elseif item[1] == 'T' then | |
item[2][1] = prevX | |
item[2][2] = prevY | |
elseif item[1] == 'Z' then | |
end | |
end | |
if (v[2].fill ~= nil and v[2].fill ~= 'none' and v[2].fill ~= 'transparent') then | |
local shape = Shape() | |
for i, v in pairs(v[2].d) do | |
if v[1] == 'M' then | |
shape:MoveTo(v[2][1], v[2][2]) | |
elseif v[1] == 'L' then | |
shape:LineTo(v[2][1], v[2][2]) | |
elseif v[1] == 'Q' then | |
BezierTo2(shape, { X = v[2][5], Y = v[2][6] }, { X = v[2][1], Y = v[2][2] }, | |
{ X = v[2][3], Y = v[2][4] }, { X = v[2][3], Y = v[2][4] }, 20) | |
elseif v[1] == 'T' then | |
-- print('FILL T'); | |
shape:LineTo(v[2][1], v[2][2]) | |
elseif v[1] == 'C' then | |
BezierTo2(shape, { X = v[2][5], Y = v[2][6] }, { X = v[2][1], Y = v[2][2] }, | |
{ X = v[2][3], Y = v[2][4] }, { X = v[2][7], Y = v[2][8] }, 20) | |
end | |
end | |
DrawItemFill(out, shape, v[2].fill) | |
end | |
if (v[2].stroke ~= nil and v[2].stroke ~= 'none') then | |
if v[2].stroke_width == nil then v[2].stroke_width = 1 end | |
local shape = Shape() | |
for i, v in pairs(v[2].d) do | |
if v[1] == 'M' then | |
shape:MoveTo(v[2][1], v[2][2]) | |
elseif v[1] == 'L' then | |
shape:LineTo(v[2][1], v[2][2]) | |
elseif v[1] == 'Q' then | |
BezierTo2(shape, { X = v[2][5], Y = v[2][6] }, { X = v[2][1], Y = v[2][2] }, | |
{ X = v[2][3], Y = v[2][4] }, { X = v[2][3], Y = v[2][4] }, 20) | |
elseif v[1] == 'T' then | |
-- print('STROKE T', v[2][1], v[2][2]); | |
shape:LineTo(v[2][1], v[2][2]) | |
elseif v[1] == 'C' then | |
BezierTo2(shape, { X = v[2][5], Y = v[2][6] }, { X = v[2][1], Y = v[2][2] }, | |
{ X = v[2][3], Y = v[2][4] }, { X = v[2][7], Y = v[2][8] }, 20) | |
end | |
end | |
DrawItemStroke(out, shape, v[2].stroke, v[2].stroke_width) | |
end | |
elseif v[1] == 'group' then | |
print('group') | |
-- PrintItems(v[2], level + 1); | |
end | |
end | |
end | |
-- Shape is a Shape object | |
-- P1 is the start point. P2 is the outgoing handle. P3 is the incoming handle. P4 is the end point. | |
-- subdivs is the number of line segments used to create the curve. | |
-- aspect is necessary to convert Y coordinates for non-square images. Could use convertY instead, but | |
-- that requires passing the img instead. I prefer to calculate the aspect just once. | |
function BezierTo2(shape, p1, p2, p3, p4, subdivs) | |
for i = 0, subdivs do | |
t = SolvePoint(p1, p2, p3, p4, i / subdivs) | |
shape:LineTo(t.X, t.Y) | |
end | |
return shape | |
end | |
-- De Casteljaus equation finds x,y coordinates for a given t | |
-- p1 - p4 are Point DataType: Tables with indices X and Y | |
-- The return value of p is a table in the same format. | |
function SolvePoint(p1, p2, p3, p4, t) | |
-- print('solve--->', p1.X, p1.Y, p2.X, p2.Y, p3.X, p3.Y, p4.X, p4.Y, t) | |
local p = {} | |
p.X = (1 - t) ^ 3 * p1.X + 3 * (1 - t) ^ 2 * t * p2.X + 3 * (1 - t) * t ^ 2 * p3.X + t ^ 3 * p4.X | |
p.Y = (1 - t) ^ 3 * p1.Y + 3 * (1 - t) ^ 2 * t * p2.Y + 3 * (1 - t) * t ^ 2 * p3.Y + t ^ 3 * p4.Y | |
return p | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- SimpleSvgGraphics.lua | |
-- Library file for of SimpleSvg.fuse - proof of concept Fuse for displaying Svg content using the fuse drawing api | |
-- This file should be put in the Fusion/Modules/Lua folder | |
-- (on windows something like C:\ProgramData\Blackmagic Design\Fusion\Modules\Lua) | |
local xmlparser = require("xmlparser"); | |
local function isNumeric(value) | |
if value == tostring(tonumber(value)) then | |
return true | |
else | |
return false | |
end | |
end | |
local function getTransformMatrix(transform) | |
-- print('transform', transform) | |
if transform == nil then | |
return nil | |
end | |
local transform = transform:gsub("matrix%(", ""):gsub("%)", ""); | |
local t = {} | |
for field in transform:gmatch('([^,]+)') do | |
table.insert(t, field) | |
end | |
local r = {} | |
r.scaleX = t[1]; | |
r.scaleY = t[4]; | |
r.moveX = t[5]; | |
r.moveY = t[6]; | |
return r | |
end | |
local function transformRect(rect, trf) | |
if trf == nil then | |
return rect | |
end | |
if trf.scaleX ~= nil then | |
if rect.stroke_width then | |
rect.stroke_width = rect.stroke_width * trf.scaleX | |
end | |
rect.width = rect.width * trf.scaleX | |
rect.x = rect.x * trf.scaleX | |
end | |
if trf.scaleY ~= nil then | |
rect.height = rect.height * trf.scaleY | |
rect.y = rect.y * trf.scaleY | |
end | |
if trf.moveX ~= nil then | |
rect.x = rect.x + trf.moveX | |
end | |
if trf.moveY ~= nil then | |
rect.y = rect.y + trf.moveY | |
end | |
return rect | |
end | |
local function transformEllipse(rect, trf) | |
if trf == nil then | |
return rect | |
end | |
if trf.scaleX ~= nil then | |
if rect.stroke_width then | |
rect.stroke_width = rect.stroke_width * trf.scaleX | |
end | |
rect.cx = rect.cx * trf.scaleX | |
rect.rx = rect.rx * trf.scaleX | |
end | |
if trf.scaleY ~= nil then | |
rect.cy = rect.cy * trf.scaleY | |
rect.ry = rect.ry * trf.scaleY | |
end | |
if trf.moveX ~= nil then | |
rect.cx = rect.cx + trf.moveX | |
end | |
if trf.moveY ~= nil then | |
rect.cy = rect.cy + trf.moveY | |
end | |
return rect | |
end | |
local function transformLine(line, trf) | |
if trf == nil then | |
return line | |
end | |
if trf.scaleX ~= nil then | |
if line.stroke_width then | |
line.stroke_width = line.stroke_width * trf.scaleX | |
end | |
line.x2 = line.x2 * trf.scaleX | |
line.x1 = line.x1 * trf.scaleX | |
end | |
if trf.scaleY ~= nil then | |
line.y2 = line.y2 * trf.scaleY | |
line.y1 = line.y1 * trf.scaleY | |
end | |
if trf.moveX ~= nil then | |
line.x1 = line.x1 + trf.moveX | |
line.x2 = line.x2 + trf.moveX | |
end | |
if trf.moveY ~= nil then | |
line.y1 = line.y1 + trf.moveY | |
line.y2 = line.y2 + trf.moveY | |
end | |
return line | |
end | |
local function transformPath(path, trf) | |
if trf == nil then | |
return path | |
end | |
if path.stroke_width then | |
path.stroke_width = path.stroke_width * trf.scaleX | |
end | |
for i, segment in pairs(path.d) do | |
local t = segment[1] | |
for j, v in pairs(segment[2]) do | |
if j % 2 == 1 then -- y | |
if trf.scaleX then | |
segment[2][j] = segment[2][j] * trf.scaleX | |
end | |
if trf.moveX then | |
segment[2][j] = segment[2][j] + trf.moveX | |
end | |
else | |
if trf.scaleY then | |
segment[2][j] = segment[2][j] * trf.scaleY | |
end | |
if trf.moveY then -- x | |
segment[2][j] = segment[2][j] + trf.moveY | |
end | |
end | |
end | |
end | |
return path | |
end | |
local function transformGroup(group, trf) | |
for j, v in pairs(group) do | |
if v[1] == 'line' then | |
v[2] = transformLine(v[2], trf) | |
elseif v[1] == 'rect' then | |
v[2] = transformRect(v[2], trf) | |
elseif v[1] == 'ellipse' then | |
v[2] = transformEllipse(v[2], trf) | |
elseif v[1] == 'path' then | |
v[2] = transformPath(v[2], trf) | |
elseif v[1] == 'group' then | |
v[2] = transformGroup(v[2], trf) | |
end | |
end | |
return group | |
end | |
function ParseRect(item) | |
local itemTag = item.tag; | |
local itemAttrs = item.attrs; | |
local item = {} | |
item["x"] = tonumber(itemAttrs.x); | |
item["y"] = tonumber(itemAttrs.y); | |
item["width"] = tonumber(itemAttrs.width); | |
item["height"] = tonumber(itemAttrs.height); | |
item["fill"] = itemAttrs.fill; | |
item["stroke"] = itemAttrs.stroke; | |
item["stroke_width"] = itemAttrs["stroke-width"]; | |
local trf = getTransformMatrix(itemAttrs.transform) | |
item = transformRect(item, trf); | |
return item; | |
end | |
function ParseEllipse(item) | |
local itemTag = item.tag; | |
local itemAttrs = item.attrs; | |
local item = {} | |
item["cx"] = tonumber(itemAttrs.cx); | |
item["cy"] = tonumber(itemAttrs.cy); | |
item["rx"] = tonumber(itemAttrs.rx); | |
item["ry"] = tonumber(itemAttrs.ry); | |
item["fill"] = itemAttrs.fill; | |
item["stroke"] = itemAttrs.stroke; | |
item["stroke_width"] = itemAttrs["stroke-width"]; | |
local trf = getTransformMatrix(itemAttrs.transform) | |
item = transformRect(item, trf); | |
return item; | |
end | |
function ParseCircle(item) | |
local itemTag = item.tag; | |
local itemAttrs = item.attrs; | |
local item = {} | |
item["cx"] = tonumber(itemAttrs.cx); | |
item["cy"] = tonumber(itemAttrs.cy); | |
item["rx"] = tonumber(itemAttrs.r); | |
item["ry"] = tonumber(itemAttrs.r); | |
item["fill"] = itemAttrs.fill; | |
item["stroke"] = itemAttrs.stroke; | |
item["stroke_width"] = itemAttrs["stroke-width"]; | |
local trf = getTransformMatrix(itemAttrs.transform) | |
item = transformRect(item, trf); | |
return item; | |
end | |
function ParseLine(line) | |
local lineTag = line.tag; | |
local lineAttrs = line.attrs; | |
local line = {} | |
line["x1"] = tonumber(lineAttrs.x1); | |
line["y1"] = tonumber(lineAttrs.y1); | |
line["x2"] = tonumber(lineAttrs.x2); | |
line["y2"] = tonumber(lineAttrs.y2); | |
line["fill"] = lineAttrs.fill; | |
line["stroke"] = lineAttrs.stroke; | |
line["stroke_width"] = lineAttrs["stroke-width"]; | |
line["style"] = lineAttrs.style; | |
local trf = getTransformMatrix(lineAttrs.transform) | |
line = transformLine(line, trf); | |
return line; | |
end | |
function ParsePath(path) | |
function getNumbers(s) | |
local a = {} | |
for numString in s.gmatch(s, "[^%s]+") do | |
local number = tonumber(numString); | |
table.insert(a, number) | |
end | |
return a | |
end | |
local pathTag = path.tag; | |
local pathAttrs = path.attrs; | |
local s = ""; | |
local t = ""; | |
local segments = {} | |
for c in pathAttrs.d:gmatch "." do | |
if c == "," then | |
s = s .. " " | |
elseif c == "." then | |
s = s .. c; | |
elseif isNumeric(c) or c == " " then | |
s = s .. c; | |
else | |
if s ~= "" then | |
local numbers = getNumbers(s) | |
local segment = { t, numbers } | |
table.insert(segments, segment) | |
end | |
s = ""; | |
t = c; | |
end | |
end | |
if t ~= "" then | |
local numbers = getNumbers(s) | |
local segment = { t, numbers } | |
table.insert(segments, segment) | |
end | |
local path = {} | |
path["d"] = segments; | |
path["fill"] = pathAttrs.fill; | |
path["stroke"] = pathAttrs.stroke; | |
path["stroke_width"] = pathAttrs["stroke-width"]; | |
path["style"] = pathAttrs.style; | |
local trf = getTransformMatrix(pathAttrs.transform) | |
path = transformPath(path, trf); | |
return path; | |
end | |
function ParseElement(parsedElement) | |
local elementTag = parsedElement.tag; | |
local elementAttrs = parsedElement.attrs; | |
local elementChildren = parsedElement.children; | |
local items = {}; | |
for i, child in pairs(elementChildren) do | |
if child.tag == "line" then | |
local item = ParseLine(child); | |
table.insert(items, { 'line', item }); | |
elseif child.tag == "rect" then | |
local item = ParseRect(child); | |
table.insert(items, { 'rect', item }); | |
elseif child.tag == "ellipse" then | |
local item = ParseEllipse(child); | |
table.insert(items, { 'ellipse', item }); | |
elseif child.tag == "circle" then | |
local item = ParseCircle(child); | |
table.insert(items, { 'ellipse', item }); | |
elseif child.tag == "path" then | |
local path = ParsePath(child); | |
table.insert(items, { 'path', path }); | |
elseif child.tag == "g" then | |
local groupTrf = getTransformMatrix(child.attrs.transform) | |
local group = ParseElement(child); | |
group = transformGroup(group, groupTrf); | |
table.insert(items, { 'group', group }) | |
end | |
end | |
if elementTag == 'svg' then | |
local width = elementAttrs.width; | |
assert(type(width) == "string", "svg width is not string") | |
if string.find(width, "px") then | |
width = (string.gsub(width, "px", "")) | |
end | |
local height = elementAttrs.height; | |
assert(type(height) == "string", "svg height is not string") | |
if string.find(height, "px") then | |
height = (string.gsub(height, "px", "")) | |
end | |
print('svg'); | |
print('width', width) | |
print('height', height) | |
if elementAttrs.viewBox then | |
local svgTrf = {} | |
local i = 0; | |
for v in string.gmatch(elementAttrs.viewBox, "%S+") do | |
if i == 0 then | |
svgTrf.moveX = tonumber(v) | |
elseif i == 1 then | |
svgTrf.moveY = tonumber(v) | |
elseif i == 2 then | |
svgTrf.scaleX = width / tonumber(v) | |
elseif i == 3 then | |
svgTrf.scaleY = height / tonumber(v) | |
end | |
i = i + 1 | |
end | |
items = transformGroup(items, svgTrf); | |
end | |
end | |
return items; | |
end | |
function ParseSvgText(xmlText, scaleX, scaleY, moveX, moveY) | |
print("==================================") | |
local parsedElement = xmlparser.parse(xmlText, {}) | |
local items = ParseElement(parsedElement.children[1]) -- root element svg | |
-- PrintItems(items, 0); | |
items = transformGroup(items, { scaleX = scaleX, scaleY = scaleY, moveX = moveX, moveY = moveY }) | |
-- PrintItems(items, 0); | |
return items; | |
end | |
function PrintItems(items, level) | |
for i, v in pairs(items) do | |
if v[1] == 'line' then | |
print(level, 'line', v[2].x1, v[2].y1, v[2].x2, v[2].y2, v[2].fill, v[2].stroke, v[2].stroke_width, | |
v[2].style) | |
elseif v[1] == 'rect' then | |
print(level, 'rect', v[2].x, v[2].y, v[2].width, v[2].height, v[2].fill, v[2].stroke, v[2].stroke_width, | |
v[2].style) | |
elseif v[1] == 'ellipse' then | |
print(level, 'ellipse', v[2].x, v[2].y, v[2].width, v[2].height, v[2].fill, v[2].stroke, v[2].stroke_width, | |
v[2].style) | |
elseif v[1] == 'path' then | |
print(level, 'path', v[2].d, v[2].fill, v[2].stroke, v[2].stroke_width, v[2].style) | |
for i, v in pairs(v[2].d) do | |
print('', v[1]) | |
for j, v2 in pairs(v[2]) do | |
print('', '', v2) | |
end | |
end | |
elseif v[1] == 'group' then | |
print(level, 'group') | |
PrintItems(v[2], level + 1); | |
end | |
end | |
end | |
function Catch(what) | |
return what[1] | |
end | |
function Try(what) | |
status, result = pcall(what[1]) | |
if not status then | |
what[2](result) | |
end | |
return result | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- from https://github.com/jonathanpoelen/lua-xmlparser | |
-- This file should be put in the Fusion/Modules/Lua folder | |
-- (on windows something like C:\ProgramData\Blackmagic Design\Fusion\Modules\Lua) | |
local io, string, pairs = io, string, pairs | |
local slashchar = string.byte('/', 1) | |
local E = string.byte('E', 1) | |
--! Return the default entity table. | |
--! @return table | |
local function defaultEntityTable() | |
return { quot='"', apos='\'', lt='<', gt='>', amp='&', tab='\t', nbsp=' ', } | |
end | |
--! @param[in] s string | |
--! @param[in] entities table : with entity name as key and value as replacement | |
--! @return string | |
local function replaceEntities(s, entities) | |
return s:gsub('&([^;]+);', entities) | |
end | |
--! Add entities to resultEntities then return it. | |
--! Create new table when resultEntities is nul. | |
--! Create an entity table from the document entity table. | |
--! @param[in] docEntities table | |
--! @param[in,out] resultEntities table|nil | |
--! @return table | |
local function createEntityTable(docEntities, resultEntities) | |
local entities = resultEntities or defaultEntityTable() | |
for _,e in pairs(docEntities) do | |
e.value = replaceEntities(e.value, entities) | |
entities[e.name] = e.value | |
end | |
return entities | |
end | |
--! Return a document `table`. | |
--! @code | |
--! document = { | |
--! children = { | |
--! { text=string } or | |
--! { tag=string, | |
--! attrs={ [name]=value ... }, | |
--! orderedattrs={ { name=string, value=string }, ... }, | |
--! children={ ... } | |
--! }, | |
--! ... | |
--! }, | |
--! entities = { { name=string, value=string }, ... }, | |
--! tentities = { name=value, ... } -- only if evalEntities = true | |
--! } | |
--! @endcode | |
--! If `evalEntities` is `true`, the entities are replaced and | |
--! a `tentity` member is added to the document `table`. | |
--! @param[in] s string : xml data | |
--! @param[in] evalEntities boolean | |
--! @return table | |
local function parse(s, evalEntities) | |
-- remove comments | |
s = s:gsub('<!%-%-(.-)%-%->', '') | |
local entities, tentities = {} | |
if evalEntities then | |
local pos = s:find('<[_%w]') | |
if pos then | |
s:sub(1, pos):gsub('<!ENTITY%s+([_%w]+)%s+(.)(.-)%2', function(name, _, entity) | |
entities[#entities+1] = {name=name, value=entity} | |
end) | |
tentities = createEntityTable(entities) | |
s = replaceEntities(s:sub(pos), tentities) | |
end | |
end | |
local t, l = {}, {} | |
local addtext = function(txt) | |
txt = txt:match'^%s*(.*%S)' or '' | |
if #txt ~= 0 then | |
t[#t+1] = {text=txt} | |
end | |
end | |
s:gsub('<([?!/]?)([-:_%w]+)%s*(/?>?)([^<]*)', function(type, name, closed, txt) | |
-- open | |
if #type == 0 then | |
local attrs, orderedattrs = {}, {} | |
if #closed == 0 then | |
local len = 0 | |
for all,aname,_,value,starttxt in string.gmatch(txt, "(.-([-_%w]+)%s*=%s*(.)(.-)%3%s*(/?>?))") do | |
len = len + #all | |
attrs[aname] = value | |
orderedattrs[#orderedattrs+1] = {name=aname, value=value} | |
if #starttxt ~= 0 then | |
txt = txt:sub(len+1) | |
closed = starttxt | |
break | |
end | |
end | |
end | |
t[#t+1] = {tag=name, attrs=attrs, children={}, orderedattrs=orderedattrs} | |
if closed:byte(1) ~= slashchar then | |
l[#l+1] = t | |
t = t[#t].children | |
end | |
addtext(txt) | |
-- close | |
elseif '/' == type then | |
t = l[#l] | |
l[#l] = nil | |
addtext(txt) | |
-- ENTITY | |
elseif '!' == type then | |
if E == name:byte(1) then | |
txt:gsub('([_%w]+)%s+(.)(.-)%2', function(name, _, entity) | |
entities[#entities+1] = {name=name, value=entity} | |
end, 1) | |
end | |
-- elseif '?' == type then | |
-- print('? ' .. name .. ' // ' .. attrs .. '$$') | |
-- elseif '-' == type then | |
-- print('comment ' .. name .. ' // ' .. attrs .. '$$') | |
-- else | |
-- print('o ' .. #p .. ' // ' .. name .. ' // ' .. attrs .. '$$') | |
end | |
end) | |
return {children=t, entities=entities, tentities=tentities} | |
end | |
-- Return a tuple `document table, error file`. | |
-- @param filename[in] string | |
-- @param evalEntities[in] boolean : see \c parse() | |
-- @return table : see parse | |
local function parseFile(filename, evalEntities) | |
local f, err = io.open(filename) | |
if f then | |
local content = f:read'*a' | |
f:close() | |
return parse(content, evalEntities), nil | |
end | |
return f, err | |
end | |
return { | |
parse = parse, | |
parseFile = parseFile, | |
defaultEntityTable = defaultEntityTable, | |
replaceEntities = replaceEntities, | |
createEntityTable = createEntityTable, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment