Module:Sandbox/Bawolff/canvas explained

-- The of drawing pictures.

local p = local metatable = local methodtable = local create2dContextlocal setDefaultslocal isFinite

function p.getContext(contextType, contextAttributes) if contextType

'2d' then ctx = create2dContext

if type(contextAttributes)

'table' then if contextAttributes.width ~= nil then ctx._width = tonumber(contextAttributes.width) end if contextAttributes.height ~= nil then ctx._height = tonumber(contextAttributes.height) end if type(contextAttributes.containerClass)

'string' then ctx._containerClass = contextAttributes.containerClass end if type(contextAttributes.containerStyle)

'string' then ctx._containerStyle = contextAttributes.containerStyle end if contextAttributes.alpha

false then ctx._alpha = false end end return ctx end error("Unsupported context")end

function p.demo(frame) local ctx = p.getContext('2d',)

-- draw some eyes ctx:moveTo(280, 100) ctx:quadraticCurveTo(290, 110, 280, 120) ctx:quadraticCurveTo(270, 110, 280, 100)

ctx:moveTo(320, 100) ctx:quadraticCurveTo(330, 110, 320, 120) ctx:quadraticCurveTo(310, 110, 320, 100) ctx:fill

-- A mouth ctx.fillStyle = 'pink' ctx:beginPath ctx:moveTo(250, 160) ctx:bezierCurveTo(260, 200, 340, 185, 350, 160) ctx:bezierCurveTo(340, 180, 260, 180, 250, 160) ctx:fill

-- You can also use SVG paths. Taken from Example.svg ctx:setTransform(6.3951354,0,0,6.3951354,-22.626246,-7.1082509) local path = p.Path2D("M 17.026327,63.789847 C 0.7506376,64.058469 13.88279,66.387154 13.113883,69.323258 C 8.0472417,70.287093 3.5936285,63.565714 6.8090451,59.370548 C 8.7591553,55.717791 15.269922,55.198361 16.902068,59.393261 C 17.532581,60.758947 17.628237,62.396589 17.026327,63.789847 z M 15.306463,62.656109 C 18.852566,58.713773 7.6543584,56.609143 10.765803,61.304742 C 12.124789,62.217715 13.961359,61.705342 15.306463,62.656109 z M 31.307931,62.391383 C 27.130518,63.524026 24.669863,68.663004 27.470717,72.229472 C 25.946657,74.052316 24.253697,71.076237 24.857281,69.636909 C 23.737444,67.038428 17.399862,72.254246 19.386636,68.888657 C 23.159719,67.551193 22.398496,63.711301 22.06067,60.848671 C 24.064085,60.375294 24.370376,65.772689 27.167918,63.326048 C 28.350126,62.546369 29.927362,61.067531 31.307931,62.391383 z M 37.66875,70.598623 C 33.467314,66.62264 32.517064,77.972723 37.30626,74.466636 C 38.742523,73.853608 40.55904,70.38932 37.66875,70.598623 z M 41.677321,70.973131 C 42.340669,75.308182 36.926157,78.361257 33.331921,76.223155 C 29.43435,74.893988 30.618698,67.677232 35.003806,68.567885 C 37.137393,70.592854 42.140265,67.002221 37.656192,66.290007 C 35.242233,65.914214 35.166503,62.640757 38.036954,63.926404 C 40.847923,64.744926 43.227838,68.124735 41.677321,70.973131 z M 62.379099,76.647079 C 62.007404,78.560417 61.161437,84.034535 58.890565,82.010019 C 59.769679,79.039958 62.536382,72.229115 56.947899,72.765789 C 53.790416,73.570863 54.908257,80.968388 51.529286,79.496859 C 51.707831,76.559817 55.858125,71.896837 50.8321,70.678504 C 45.898113,69.907818 47.485944,75.735824 45.286883,78.034703 C 42.916393,76.333396 45.470823,71.647155 46.624124,69.414735 C 50.919507,67.906486 63.618534,70.878704 62.379099,76.647079 z M 66.426447,83.84905 C 67.616398,85.777591 62.114624,94.492698 62.351124,90.31711 C 63.791684,86.581961 65.730376,78.000636 67.391891,74.85575 C 71.027815,73.781175 76.383067,75.350289 76.591972,79.751898 C 77.048545,83.793048 73.066803,88.429945 68.842187,86.765936 C 67.624386,86.282034 66.56741,85.195132 66.426447,83.84905 z M 74.086569,81.803435 C 76.851893,78.050524 69.264402,74.310256 67.560734,78.378191 C 65.893402,80.594099 67.255719,83.775746 69.700555,84.718558 C 72.028708,85.902224 73.688639,83.888662 74.086569,81.803435 z M 82.318799,73.124577 C 84.30523,75.487059 81.655015,88.448086 78.247183,87.275736 C 78.991935,82.387828 81.291029,77.949394 82.318799,73.124577 z M 95.001985,87.684695 C 78.726298,87.953319 91.858449,90.281999 91.089542,93.218107 C 86.0229,94.18194 81.569287,87.460562 84.784701,83.265394 C 86.734814,79.612637 93.245582,79.09321 94.877729,83.28811 C 95.508245,84.653796 95.603892,86.291438 95.001985,87.684695 z M 93.282122,86.550957 C 96.828223,82.608621 85.630017,80.503993 88.741461,85.199592 C 90.100447,86.112565 91.937018,85.600192 93.282122,86.550957 z ") ctx.fillStyle = 'red' ctx:beginPath ctx:fill(path) return tostring(ctx)end

metatable.__index = methodtable

metatable.__tostring = function(t) return t:getWikitext end

local pathmethods = local pathmeta = pathmeta.__index = pathmethodssetmetatable(methodtable, pathmeta)

function create2dContext local ctx = setmetatable(ctx, metatable)

ctx._width = 300 ctx._height = 300 ctx._containerClass = nil ctx._containerStyle = nil ctx._alpha = true -- Default values setDefaults(ctx) return ctxend

setDefaults = function(ctx) ctx.__stateStack = ctx.__operations =

ctx._currentTransform = ctx._path = "" ctx._fillRule = "nonzero" ctx.lineWidth = 1.0 ctx.lineCap = 'butt' ctx.lineJoin = 'miter' ctx.miterLimit = 10 ctx.lineDashOffset = 0.0 ctx.font = "10px sans-serif" ctx.textAlign = 'start' ctx.textBaseline = 'alphabetic' ctx.direction = 'inherit' ctx.letterSpacing = '0px' ctx.fontKerning = 'auto' ctx.fontStretch = 'normal' ctx.fontVariantCaps = 'normal' ctx.textRendering = 'auto' ctx.wordSpacing = '0px' ctx.fillStyle = '#000' ctx.strokeStyle = '#000' ctx.shadowBlur = 0 ctx.shadowColor = 'rgb(0 0 0 / 0%)' ctx.shadowOffsetX = 0 ctx.shadowOffsetY = 0 ctx.globalAlpha = 1.0 ctx.globalCompositeOperation = "source-over" ctx.imageSmoothingEnabled = true ctx.imageSmoothingQuality = "low" ctx.canvas = nil ctx.filter = "none" return ctxend

local newOperation = function(t, operation) op = op.name = operation op._path = t._path op._currentTransform = t._currentTransform op._fillRule = t._fillRule

op.lineWidth = t.lineWidth op.lineCap = t.lineCap op.lineJoin = t.lineJoin op.miterLimit = t.miterLimit op.lineDashOffset = t.lineDashOffset op.font = t.font op.textAlign = t.textAlign op.textBaseline = t.textBaseline op.direction = t.direction op.letterSpacing = t.letterSpacing op.fontKerning = t.fontKerning op.fontStretch = t.fontStretch op.fontVariantCaps = t.fontVariantCaps op.textRendering = t.textRendering op.wordSpacing = t.wordSpacing op.fillStyle = t.fillStyle op.strokeStyle = t.strokeStyle op.shadowBlur = t.shadowBlur op.shadowColor = t.shadowColor op.shadowOffsetX = t.shadowOffsetX op.shadowOffsetY = t.shadowOffsetY op.globalAlpha = t.globalAlpha op.globalCompositeOperation = t.globalCompositeOperation op.imageSmoothingEnabled = t.imageSmoothingEnabled op.imageSmoothingQuality = t.imageSmoothingQuality op.canvas = t.canvas op.filter = t.filter return opend

methodtable.setTransform = function(ctx, a, b, c, d, e, f) -- last 0 0 1 row is left implied assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") ctx:resetTransform ctx:transform(a, b, c, d, e, f)end

methodtable.resetTransform = function(ctx) -- last 0 0 1 row is left implied assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") ctx._currentTransform = end

methodtable.transform = function(ctx, a, b, c, d, e, f) assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") -- Do a matrix multiply -- a c e -- b d f -- 0 0 1

local oa = ctx._currentTransform[1] local ob = ctx._currentTransform[2] local oc = ctx._currentTransform[3] local od = ctx._currentTransform[4] local oe = ctx._currentTransform[5] local of = ctx._currentTransform[6] ctx._currentTransform[1] = a*oa + b*oc ctx._currentTransform[3] = c*oa + d*oc ctx._currentTransform[5] = e*oa + f*oc + oe ctx._currentTransform[2] = a*ob + b*od ctx._currentTransform[4] = c*ob + d*od ctx._currentTransform[6] = e*ob + f*od + of end

methodtable.scale = function(ctx, x, y) assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") assert(type(x)

"number", "x argument to scale must be a number") assert(type(y)

"number", "y argument to scale must be a number") ctx:transform(x, 0, 0, y, 0, 0)end

methodtable.rotate = function(ctx, a) assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") assert(type(x)

"number", "Argument a to scale must be number of radians") ctx:transform(math.cos(a), math.sin(a), -math.sin(a), math.cos(a), 0, 0)end

methodtable.translate = function(ctx, x, y) assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") ctx:transform(1, 0, 0, 1, x, y)end

-- Path2D methods

p.Path2D = function(pathDesc) local path = path._path = "" setmetatable(path, pathmeta) if type(pathDesc)

"string" then -- Constructor can take an SVG path description path._path = pathDesc end if type(pathDesc)

'table' and type(pathDesc._path)

'string' then -- Constructor can take a Path2D object. path._path = pathDesc._path end return pathend

-- Technically this is only supposed to be on Path2D and not context.pathmethods.addPath = function(ctx, path, transform) assert(type(ctx)

'table' and type(ctx._path)

'string', "First argument must be a CanvasRenderingContext2D or Path2D object. Did you use '.' instead of ':'?") if transform ~= nil then error("transform argument to addPath is not implemented yet") end

if path

nil or path._path

nil then error("Second argument should be a Path2D object") end ctx._path = ctx._path .. " " .. path._pathend

pathmethods.moveTo = function(ctx, x, y) assert(type(ctx)

'table' and type(ctx._path)

'string', "First argument must be a CanvasRenderingContext2D or Path2D object. Did you use '.' instead of ':'?") ctx._path = ctx._path .. " M " .. x .. ' ' .. yend

pathmethods.lineTo = function(ctx, x, y) assert(type(ctx)

'table' and type(ctx._path)

'string', "First argument must be a CanvasRenderingContext2D or Path2D object. Did you use '.' instead of ':'?") ctx._path = ctx._path .. " L " .. x .. ' ' .. yend

pathmethods.quadraticCurveTo = function(ctx, cx, cy, x, y) assert(type(ctx)

'table' and type(ctx._path)

'string', "First argument must be a CanvasRenderingContext2D or Path2D object. Did you use '.' instead of ':'?") ctx._path = ctx._path .. " Q " .. cx .. " " .. cy .. " " .. x .. " " .. yend

pathmethods.bezierCurveTo = function(ctx, c1x, c1y, c2x, c2y, x, y) assert(type(ctx)

'table' and type(ctx._path)

'string', "First argument must be a CanvasRenderingContext2D or Path2D object. Did you use '.' instead of ':'?") ctx._path = ctx._path .. " C " .. c1x .. " " .. c1y .. " " .. c2x .. " " .. c2y .. " " .. x .. " " .. yend

pathmethods.beginPath = function(ctx) assert(type(ctx)

'table' and type(ctx._path)

'string', "First argument must be a CanvasRenderingContext2D or Path2D object. Did you use '.' instead of ':'?") ctx._path = end

pathmethods.closePath = function(ctx) assert(type(ctx)

'table' and type(ctx._path)

'string', "First argument must be a CanvasRenderingContext2D or Path2D object. Did you use '.' instead of ':'?") ctx._path = ctx._path .. ' Z'end

pathmethods.rect = function(ctx, x, y, w, h) assert(type(ctx)

'table' and type(ctx._path)

'string', "First argument must be a CanvasRenderingContext2D or Path2D object. Did you use '.' instead of ':'?") if not isFinite(x) or not isFinite(y) or not isFinite(w) or not isFinite(h) or w

0 or h

0 then return end ctx:moveTo(x, y) ctx:lineTo(x+w, y) ctx:lineTo(x+w, y+h) ctx:lineTo(x, y+h) ctx:closePathend

pathmethods.arc = function(ctx) assert(type(ctx)

'table' and type(ctx._path)

'string', "First argument must be a CanvasRenderingContext2D or Path2D object. Did you use '.' instead of ':'?") error("FIXME not implemented yet")end

pathmethods.arcTo = function(ctx) assert(type(ctx)

'table' and type(ctx._path)

'string', "First argument must be a CanvasRenderingContext2D or Path2D object. Did you use '.' instead of ':'?") error("FIXME not implemented yet")end

pathmethods.ellipse = function(ctx) assert(type(ctx)

'table' and type(ctx._path)

'string', "First argument must be a CanvasRenderingContext2D or Path2D object. Did you use '.' instead of ':'?") error("FIXME not implemented yet")end

pathmethods.roundRect = function(ctx) assert(type(ctx)

'table' and type(ctx._path)

'string', "First argument must be a CanvasRenderingContext2D or Path2D object. Did you use '.' instead of ':'?") error("FIXME not implemented yet")end

-- End of Path2D methods

-- can be fill(fillRule), fill(path), fill(path, fillRule)methodtable.fill = function(ctx, arg1, arg2) assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") local op = newOperation(ctx, 'fill') if arg2

'evenodd' or arg1

'evenodd' then op._fillRule = 'evenodd' end if type(arg1)

'table' and type(arg1._path)

'string' then op._path = arg1._path end table.insert(ctx.__operations, op)end

methodtable.createWikitextPattern = function(ctx, args) assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") if type(args)

'string' then args = end local res = return resend

methodtable.drawImage = function(ctx, image, sx, sy, sw, sh, dx, dy, dw, dh) -- FIXME this doesn't work properly yet assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") if image

nil then return end if type(image)

'string' then image = mw.title.new(image, 6) end if (not image:inNamespace(6)) or (not image.file.exists) then return end if sx

nil then sx = 0 end if sy

nil then sx = 0 end if sw

nil then sw = image.file.width end if sh

nil then sh = image.file.height end if dx

nil then dx = sx end if dy

nil then dy = sy end if dw

nil then dw = sw end if dh

nil then dh = sh end

ctx:save -- FIXME, this is broken and doesn't work right for all arg types local img if image.file.width > image.file.height then img = '' else img = '' end -- FIXME doesn't work with negative values properly local clip = 'path("M ' .. sx .. ' ' .. sy .. ' L ' .. (sw+sx) .. ' ' .. sy .. ' L ' .. (sw+sx) .. ' ' .. (sh+sy) .. ' L ' .. sx .. ' ' .. (sh+sy) ..')' -- Note in timeless, if the image is linked, then there are css rules that resize it which we don't want. img = '

' .. img .. '

' ctx.fillStyle = ctx:createWikitextPattern ctx:fillRect(dx, dy, dw, dh) ctx:restore

end

isFinite = function(n) return n > -math.huge and n < math.huge end

methodtable.fillRect = function(ctx, x, y, w, h) assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") if not isFinite(x) or not isFinite(y) or not isFinite(w) or not isFinite(h) or w

0 or h

0 then return end local oldPath = ctx._path ctx:beginPath ctx:rect(x, y, w, h) ctx:fill ctx:beginPath ctx._path = oldPathend

methodtable.clearRect = function(ctx, x, y, w, h) assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") -- FXIME, this should make it transparent to html below canvas, not just be white. ctx:save ctx.fillStyle = "var(--background-color-base, '#fff')" ctx:fillRect(x, y, w, h) ctx:restoreend

local doText = function(ctx, text, x, y, maxWidth, stroke) assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") assert(maxWidth

nil, "maxWidth parameter to fillText is not supported") ctx:save local oldStyle = stroke and ctx.strokeStyle or ctx.fillStyle -- Maybe complex background is possible here with mix-blend-mode: screen. Main issue is it seems like -- we couldn't make the non-text part be transparent. assert(type(oldStyle)

'string', 'Complex backgrounds for fillText not currently supported')

local textLayer = mw.html.create('div') :css('position', 'absolute') :css('width', 'max-content') :css('font', ctx.font) :css('text-rendering', ctx.textRendering) :css('font-kerning', ctx.fontKerning) :css('font-stretch', ctx.fontStretch) :css('font-variant-caps', ctx.fontVariantCaps) :css('letter-spacing', ctx.letterSpacing) :css('word-spacing', ctx.wordSpacing) :wikitext(text) -- FIXME should we escape

if ctx.direction ~= 'inherit' then textLayer:attr('dir', ctx.direction) end if ctx.textBaseline

'alphabetic' or ctx.textBaseline

'bottom' then -- This isn't 100% right for alphabetic, but it is the default and this is close textLayer:css('bottom', 'calc(100% - ' .. y .. 'px' .. ')') elseif ctx.textBaseline

'top' then textLayer:css('top', y .. 'px') else -- We can approximate some values, but better to just give an error. error("Unsupported value for textBaseline: " .. ctx.textBaseline) end

-- not perfect, as its supposed to inherit from containing element local realDir = ctx.direction

'inherit' and mw.getContentLanguage:getDir or ctx.direction local realAlign = 'left' if ctx.textAlign

'start' and realDir

'rtl' then realAlign = 'right' elseif ctx.textAlign

'end' and realDir

'ltr' then realAlign = 'right' elseif ctx.textAlign

'center' then error('textAlign = center not currently supported') end if realAlign

'left' then textLayer:css('left', x .. 'px') else textLayer:css('right', 'calc(100% - ' .. x .. 'px)') end

local style = 'color:' .. oldStyle if stroke then style = 'color: transparent; -webkit-text-stroke-color: ' .. oldStyle .. ';text-stroke-color:' .. oldStyle .. '; -webkit-text-stroke-width:' .. ctx.lineWidth .. 'px; text-stroke-width:' .. ctx.lineWidth .. 'px;' end ctx.fillStyle = ctx:createWikitextPattern local op = newOperation(ctx, 'text') table.insert(ctx.__operations, op) ctx:restoreend

methodtable.fillText = function(ctx, text, x, y, maxWidth) ctx:fillTextRaw(mw.text.nowiki(text), x, y, maxWidth)end

-- For use if you want to include wikitext. Note you still need to use frame:preprocess before calling this.methodtable.fillTextRaw = function(ctx, text, x, y, maxWidth) doText(ctx, text, x, y, maxWidth, false)end

-- For use if you want to include wikitext. Note you still need to use frame:preprocess before calling this.methodtable.strokeTextRaw = function(ctx, text, x, y, maxWidth) doText(ctx, text, x, y, maxWidth, true)endmethodtable.strokeText = function(ctx, text, x, y, maxWidth) ctx:strokeTextRaw(mw.text.nowiki(text), x, y, maxWidth)end

methodtable.save = function (ctx) assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") -- Note, path is included in operation, however it is not part of save state. table.insert(ctx.__stateStack, newOperation(ctx, 'save'))end

methodtable.restore = function(ctx) assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") if #ctx.__stateStack

0 then -- spec says silently ignore if no saved state return end op = table.remove(ctx.__stateStack)

ctx._currentTransform = op._currentTransform ctx.lineWidth = op.lineWidth ctx.lineCap = op.lineCap ctx.lineJoin = op.lineJoin ctx.miterLimit = op.miterLimit ctx.lineDashOffset = op.lineDashOffset ctx.font = op.font ctx.textAlign = op.textAlign ctx.textBaseline = op.textBaseline ctx.direction = op.direction ctx.letterSpacing = op.letterSpacing ctx.fontKerning = op.fontKerning ctx.fontStretch = op.fontStretch ctx.fontVariantCaps = op.fontVariantCaps ctx.textRendering = op.textRendering ctx.wordSpacing = op.wordSpacing ctx.fillStyle = op.fillStyle ctx.strokeStyle = op.strokeStyle ctx.shadowBlur = op.shadowBlur ctx.shadowColor = op.shadowColor ctx.shadowOffsetX = op.shadowOffsetX ctx.shadowOffsetY = op.shadowOffsetY ctx.globalAlpha = op.globalAlpha ctx.globalCompositeOperation = op.globalCompositeOperation ctx.imageSmoothingEnabled = op.imageSmoothingEnabled ctx.imageSmoothingQuality = op.imageSmoothingQuality ctx.canvas = op.canvas ctx.filter = op.filterend

methodtable.reset = function (ctx) assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") setDefaults(ctx)end

methodtable.isContextLost = function(t) return falseend

methodtable.getContextAttributes = function(ctx) assert(type(ctx)

'table' and type(ctx.__stateStack)

'table', "First argument must be a CanvasRenderingContext2D. Did you use '.' instead of ':'?") return end

local getBlendMode = function (compositeOp) -- css mix-blend-mode only supports blending not composite operators (except plus-darker and plus-lighter) -- So we don't support the following values: clear | copy | source-over | destination-over | source-in | -- destination-in | source-out | destination-out | source-atop | -- destination-atop | xor | lighter

-- Also this doesn't work for images properly as images have isolated blend modes. validOps = if validOps[compositeOp] then return compositeOp end return "normal"end

local getTransform = function(t) local res = 'matrix(' .. t[1] .. ',' .. t[2] .. ',' .. t[3] .. ',' .. t[4] .. ',' .. t[5] .. ',' .. t[6] .. ')' if res

'matrix(1,0,0,1,0,0)' then return 'none' end return resend

-- TODO this is a hack that doesn't really work.local getAdjustedWidth = function(w, h, t) -- (a x + c y + e, b x + d y + f) -- we should really invert the matrix instead of this hack return math.ceil(w*w/(w*t[1]+h*t[3])+math.abs(t[5]))end

local getAdjustedHeight = function(w, h, t) -- (a x + c y + e, b x + d y + f) -- we should really invert the matrix instead of this hack return math.ceil(h*h/(w*t[2]+h*t[4])+math.abs(t[6]))end

local getFilter = function(op) if op.shadowColor

'transparent' then if op.filter

'none' then return nil end return op.filter end local shadow = " drop-shadow(" .. op.shadowColor .. ' ' .. op.shadowOffsetX .. 'px ' .. op.shadowOffsetY .. 'px ' .. op.shadowBlur .. 'px)' if op.filter

'none' then return shadow end return op.filter .. shadowend

--methodtable.getWikitext = function(ctx) local container = mw.html.create('div') container:attr("role", 'presentation') :attr("aria-hidden", "true") -- not sure if this is right :css('width', ctx._width .. 'px') :css('height', ctx._height .. 'px') :css('overflow', 'hidden') :css('position', 'relative') :cssText(ctx._containerStyle) :addClass(ctx._containerClass)

if ctx._alpha

false then container:css('isolation', 'isolate') end

local layers = for i, op in ipairs(ctx.__operations) do if op['name']

'fill' or op['name']

'text' then local fillPattern = ctx:createWikitextPattern(op.fillStyle) local layer = mw.html.create('div') :cssText(fillPattern.style) :addClass(fillPattern.class) :css('width', getAdjustedWidth(ctx._width, ctx._height, op._currentTransform) .. 'px') -- Should this be adjusted based on fillPattern? :css('height', getAdjustedHeight(ctx._width, ctx._height, op._currentTransform) .. 'px') :css('left', fillPattern.offsetx) -- FIXME i think this is wrong :css('top', fillPattern.offsety) :css('position', 'absolute') :css('filter', getFilter(op)) :css('mix-blend-mode', getBlendMode(op.globalCompositeOperation)) :css('opacity', op.globalAlpha) :css('transform', getTransform(op._currentTransform)) :css('transform-origin', 'top left') :css('pointer-events', 'none') -- Make sure we pass :hover to layer below :tag('div') :css('width', '100%') :css('height', '100%') -- FIXME this isn't really right. Clear should be transparent to the non-canvas background :css('background-color', op.globalCompositeOperation

'clear' and "var(--background-color-base, '#fff')" or fillPattern.background) :css('clip-path', op['name']

'fill' and 'path(' .. op._fillRule .. ', \ .. op._path .. '\')' or 'none') :css('pointer-events', 'all') :wikitext(tostring(fillPattern.content)) :allDone if op.globalCompositeOperation

"source-over" or op.globalCompositeOperation

'clear' or op.globalCompositeOperation

'normal' or getBlendMode(op.globalCompositeOperation) ~= 'normal' then layers = layers .. tostring(layer) elseif op.globalCompositeOperation

'destination-over' then layers = tostring(layer) .. layers else error("Unsupported globalCompositeOperation " .. op.globalCompositeOperation) end else error("unsupported operation " .. v['name']) end end container:wikitext(layers) return tostring(container)end

return p