Documentation for this module may be created at Module:Image/doc

--
-- wikitext Image object
-- Inputs include
----p.new("Tifa.png", {width=400, caption="Lool", link = ""})
----p.new("[[File:Tifa.png|400px|Lool|link=]]"
----p.new("Tifa.png"):setWidth(400):setCpation("Lool"):setLink("")
-- Invoke directly from articles using
----{{File|Tifa.png|400px|Lool|link=}}
-- This module makes alt the same as caption by default
-- It also accepts "|filter=" with a bunch of values that adds
----a wrapper with relevant classes
-- It also accepts "|class=" which adds a class to
----a wrapper. unlike current MW versions which add directly to img
--

local getArgs = require("Dev:Arguments").getArgs

--------------------------------------------------------------------------------
-- Metatable
--------------------------------------------------------------------------------
local meta = {}

meta.parse = {}
meta.__type = "image"
meta.func = {}

function meta.parse.format(value)
    local values = {
        frame = "frame",
        frameless = "frameless",
        thumb = "thumb",
        thumbnail = "thumb",
        [""] = false
    }

    value = mw.text.trim(value:lower())

    return values[value]
end

function meta.parse.class(value)
    local classList = {}
    for className in string.gmatch(value, "%S+") do
        classList[#classList+1] = className
    end
    if #classList==0 then return {} end
    return classList
end

function meta.parse.filter(value)
    local values = {
        gray = "grayscale",
        grayscale = "grayscale",
        monochrome = "grayscale",
        faded = "fade",
        fade = "fade",
        invert = "invert",
        ["horizontal flip"] = "horizontal flip",
        ["flip horizontal"] = "horizontal flip",
        ["horizontal-flip"] = "horizontal flip",
        ["vertical flip"] = "vertical flip",
        ["vertical-flip"] = "vertical flip",
        ["flip vertical"] = "vertical flip",
        ["flip both"] = "horizontal vertical flip",
        ["flip horizontal vertical"] = "horizontal vertical flip",
        ["flip vertical horizontal"] = "horizontal vertical flip",
        ["horizontal vertical flip"] = "horizontal vertical flip",
        ["vertical horizontal flip"] = "horizontal vertical flip",
        ["vertical-flip horizontal-flip"] = "horizontal vertical flip",
        ["horizontal-flip vertical-flip"] = "horizontal vertical flip",
        [""] = false
    }

    value = mw.text.trim(value:lower())

    return values[value]
end

function meta.parse.valign(value)
    local values = {
        baseline = "baseline",
        sub = "sub",
        super = "super",
        top = "top",
        ["text-top"] = "text-top",
        middle = "middle",
        bottom = "bottom",
        ["text-bottom"] = "text-bottom",
        [""] = false
    }

    value = mw.text.trim(value:lower())

    return values[value]
end

function meta.parse.align(value)
    local values = {
        left = "left",
        right = "right",
        none = "none",
        center = "center"
    }

    value = mw.text.trim(value:lower())

    return values[value]
end

function meta.parse.width(value)
    if type(value) == "number" then
        return value
    elseif type(value) == "string" then
        value = mw.text.trim(value:lower())

        local value = value:match("^(%d+)p?x?$")

        return tonumber(value)
    end
end

function meta.parse.height(value)
    if type(value) == "number" then
        return value
    elseif type(value) == "string" then
        value = mw.text.trim(value:lower())

        local value = value:match("^(%d+)p?x?$")

        return tonumber(value)
    end
end

function meta.parse.size(value)
    -- this returns width, height
    if type(value) == "number" then
        return value, nil
    elseif type(value) == "string" then
        value = mw.text.trim(value:lower())

        local test = value:match("^(%d+)p?x?$")

        if test then
            return tonumber(test), nil
        end

        local test = value:match("^x(%d+)p?x?$")

        if test then
            return nil, tonumber(test)
        end

        local va, vb = value:match("^(%d+)x(%d+)p?x?$")

        if value then
            return tonumber(va), tonumber(vb)
        end
    end
end

function meta.__tostring(a)
    local this = a._data
    local parts = {this.filename}
    local size = ""

    if this.width then
        size = this.width
    end

    if this.height then
        size = size .. "x" .. this.height
    end

    if size ~= "" then
        table.insert(parts, size .. "px")
    end

    if this.format then
        table.insert(parts, this.format)
    end

    if this.align then
        table.insert(parts, this.align)
    end

    if this.valign then
        table.insert(parts, this.valign)
    end

    if this.border then
        table.insert(parts, "border")
    end

    local alt = this.alt

    if not alt then
        alt = this.caption
    end

    if alt then
        table.insert(parts, "alt=" .. alt)
    end

    if this.link then
        table.insert(parts, "link=" .. this.link)
    end

    if this.caption then
        table.insert(parts, this.caption)
    end

    local fullfilename = "[[File:" .. table.concat(parts, "|") .."]]"

    if not this.filter and (not this.class or #this.class==0) then
        return fullfilename
    end

    local isblock, isfloat, isleft

    if this.align
    or (this.format and this.format ~= "frameless")
    or (this.format == "frameless" and this.align)
        then isblock = true
    end

    if isblock and this.align ~= "center" and this.align ~= "none" then
        isfloat = true
    end

    if isfloat and this.align == "left" then
        isleft = true
    end

    local filecont = mw.html.create(isblock and "div" or "span")

    if isfloat then
        filecont:css("float", isleft and "left" or "right")
    end

    filecont:addClass(this.filter)
    if this.class then filecont:addClass(table.concat(this.class, " ")) end

    filecont:wikitext(fullfilename)

    return tostring(filecont)
end

function meta.__concat(a, b)
    return tostring(a) .. tostring(b)
end

function meta.__eq(a, b)
    return tostring(a) == tostring(b)
end

function meta.__lt(a, b)
    return tostring(a) < tostring(b)
end

function meta.__le(a, b)
    return tostring(a) <= tostring(b)
end

function meta.__index(obj, k)
    local mt = getmetatable(obj)

    if mt.func[k] then
        return mt.func[k]
    end
end

function meta.func.setClass(obj, v)
    obj._data.class = getmetatable(obj).parse.class(v or "") or nil

    return obj
end

function meta.func.setFormat(obj, v)
    -- "or nil" prevents storing false
    obj._data.format = getmetatable(obj).parse.format(v or "") or nil

    return obj
end

function meta.func.setAlign(obj, v)
    obj._data.align = getmetatable(obj).parse.align(v or "") or nil

    return obj
end

function meta.func.setValign(obj, v)
    obj._data.valign = getmetatable(obj).parse.valign(v or "") or nil

    return obj
end

function meta.func.setFilter(obj, v)
    obj._data.valign = getmetatable(obj).parse.filter(v or "") or nil

    return obj
end

function meta.func.setWidth(obj, v)
    obj._data.width = getmetatable(obj).parse.width(v or "")

    return obj
end

function meta.func.setHeight(obj, v)
    obj._data.height = getmetatable(obj).parse.height(v or "")

    return obj
end

function meta.func.setBorder(obj, v)
    obj._data.border = v or nil

    return obj
end

function meta.func.setSize(obj, w, h)
    if type(w) == "table" then
        if w.width then
            h = w.height
            w = w.width
        else
            h = w[2]
            w = w[1]
        end
    end

    local mt = getmetatable(obj)

    obj._data.width = mt.parse.width(w)
    obj._data.height = mt.parse.height(h)

    return obj
end

function meta.func.setCaption(obj, v)
    obj._data.caption = v or nil

    return obj
end

function meta.func.setAlt(obj, v)
    obj._data.alt = v or nil

    return obj
end

function meta.func.setLink(obj, v)
    obj._data.link = v or nil

    return obj
end

function meta.func.getFormat(obj)
    return obj._data.format
end

function meta.func.getAlign(obj)
    return obj._data.align
end

function meta.func.getValign(obj)
    return obj._data.valign
end

function meta.func.getWidth(obj)
    return obj._data.width
end

function meta.func.getHeight(obj)
    return obj._data.height
end

function meta.func.getSize(obj)
    return {
        width = obj._data.width,
        height = obj._data.height
    }
end

function meta.func.getCaption(obj)
    return obj._data.caption
end

function meta.func.getAlt(obj)
    return obj._data.alt or obj._data.filename:match("(.*)%..+")
end

function meta.func.getLink(obj)
    return obj._data.link
end

function meta.func.getClass(obj)
    return table.concat(obj._data.class, " ")
end

function meta.func.getFilter(obj)
    return obj._data.filter
end

function meta.func.isBorder(obj)
    return not not obj._data.border -- convert nil to false
end

function meta.func.getExtension(obj)
    return obj._data.filename:match(".*%.(.+)")
end

function meta.func.getPagename(obj)
    return obj._data.filename and ("File:" .. obj._data.filename)
end

function meta.func.getFilename(obj)
    return obj._data.filename
end

function meta.func.isExist(obj)
    local frame = mw.getCurrentFrame()

    if frame then
        local parsetext = "{{filepath:" .. obj._data.filename .. "}}"

        return frame:preprocess(parsetext) ~= ""
    else
        -- you're in console, we'll just lie
        return true
    end
end

--------------------------------------------------------------------------------
-- Exports
--------------------------------------------------------------------------------
local p = {}

function p.invoke(frame)
    local args = getArgs(frame)
    local file = args[1]
    
    if not file then return "" end

    for k, v in pairs(args) do
        if k == 1 then
            -- do nothing
        elseif tonumber(k) then
            file = file .. "|" .. v
        else
            file = file .. "|" .. k .. "=" .. v
        end
    end
    
    return p.new("[[" .. file .. "]]")
end

--for Template:Show image
function p.showimage(frame)
    local args = getArgs(frame)
    local filename = args[1]
    local description = args[2]
    local fileobj = p.new(args[1])
        :setSize(1, 1)
        :setClass("clicktoshow")
    if not description then
        description = fileobj:getAlt()
    end
    return fileobj
        :setCaption("Click to show " .. description)
        :setAlt(description)
end

function p.filellink(frame)
    local args = getArgs(frame)
    local file = args[1]
    
    local file = p.new(file)
    
    --this is the only way i know how to record a file as a file usage. but it involves two expensives!
    local _ = mw.title.new(file:getFilename(), "File").fileExists
    
    return "[[:File:" .. file:getFilename() .. "|" .. (args[2] or args[1]) .. "]]"
    
end

function p.new(a, b)
    local filename
    local values = {}
    local fields = {
        format = true,
        border = true,
        align = true,
        valign = true,
        width = true,
        height = true,
        alt = true,
        link = true,
        caption = true,
        class = true,
        filter = true
    }

    if type(a) == "string" then
        if type(b) == "table" then
            for k, v in pairs(b) do
                if fields[k] then
                    local parsefunc = meta.parse[k]

                    if parsefunc then
                        values[k] = parsefunc(v)
                    else
                        values[k] = v
                    end
                end
            end
        else
            local contents = a:match("^%s*%[%[(.*)%]%]%s*$")

            if contents then
                local aHasChanged = false

                for tmp in mw.text.gsplit(contents, "|") do
                    local part = mw.text.trim(tmp)

                    if part == "" then
                        -- do nothing
                    elseif not aHasChanged then
                        a = part
                        aHasChanged = true
                    elseif part:find("^class=") then
                        values.class = meta.parse.class(part:sub(7))
                    elseif part:find("^link=") then
                        values.link = part:sub(6)
                    elseif part:find("^alt=") then
                        values.alt = part:sub(5)
                    elseif part:find("^filter=") then
                        values.filter = meta.parse.filter(part:sub(8))
                    elseif part == "border" then
                        values.border = true
                    else
                        local width, height = meta.parse.size(part)

                        if width or height then
                            values.width = width
                            values.height = height
                        else
                            local align = meta.parse.align(part)

                            if align then
                                values.align = align
                            else
                                local valign = meta.parse.valign(part)

                                if valign then
                                    values.valign = valign
                                else
                                    local format = meta.parse.format(part)

                                    if format then
                                        values.format = format
                                    else
                                        values.caption = part
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end

        local stripfile, filepart = a:match("^%s*(%w+):(.+)")

        if stripfile and
        (stripfile:lower() == "file" or stripfile:lower() == "image") then
            filename = filepart
        else
            filename = a
        end

        values.filename = mw.uri.decode(mw.text.decode(filename, true), "WIKI")
    end

    return setmetatable({_data = values}, meta)
end

return p
Community content is available under CC-BY-SA unless otherwise noted.