Logic for {{FI}} and {{FIS}}


-- This is an module to implement FreedImg
-- Argument to the functions are as described on [[Template:FI]] and [[Template:FIS]].
--
-- 2021-01-13:	Implementation copying original FI and FIS templates, but with
--              added max-width handing to reduce image sizes for very large images
-- 2021-01-16:	Removed complex HTML and reduce complexity
-- 2021-01-21:  Further reduce complexity of captions and allow captions to contain
--              divs (e.g. centered text)
-- 2021-06-03   Handle blank and missing images
require('strict')

local p = {} --p stands for package
local getArgs = require('Module:Arguments').getArgs

-- this is a limit on the max default image size to avoid multi-MB full-size images
-- being loaded. if the image is smaller than this, it is loaded at full resolution
local max_image_size = 1000
-- this is a nominal "max" screen size used when computing the maximum size of an
-- image with a width in percent. For example, on a 2048px screen, an image at 
-- 10% will be 204px at most, so there's no point loading a 1000px image.
local max_screen_size = 2048

local function arg_or_nil(args, name)
	if args[name] ~= nil and args[name] ~= "" then
		return args[name]
	end
	return nil
end

-- add to a table if the variable is not nil or empty
local function add_if(t, key, var)
	if var ~= nil and var ~= "" then
		t[key] = var
	end
end

-- append a CSS style to a table so we can use mw.html:css on it
local function add_style_to_table(t, s)
	
	if s == nil or s == "" then
		return
	end
    for rule in string.gmatch(s, "([^;]+)") do
    	local idx, _
    	idx, _ = string.find(rule, ":")
    	
    	if idx then
	    	t[string.sub(rule, 0, idx - 1)] = string.sub(rule, idx + 1)
	    end
    end
end

local function construct_image_markup(img_name, args)

	-- get the image info
	-- NOTE: we will use the file attribute, this is an expensive function
	local img_title = mw.title.makeTitle("File", img_name)

	-- Whatever we were given it was not a valid file name
	if not img_title then
		local error = "The specified image (" .. img_name .. ") is invalid"
		return require('Module:Error').error({message = error})
	end

	-- The filename given does not exist
	if not img_title.file.exists then
		local error = "The specified image (" .. img_name .. ") does not exist"
		return require('Module:Error').error({message = error})
	end

	local img_width_px
	
	if arg_or_nil(args, "imgwidth") then
		-- the user told us what they want
		-- This assumes the imgwidth will be specified in pixels, which isn't always the case, Nor does the template documentation mention this.
		img_width_px = string.gsub(args['imgwidth'], "px", "")
	elseif arg_or_nil(args, "width") then
		-- if the width parameter is in px or %, use that to get the image as the
		-- image is at most the size of the container
		local arg_px_width = string.match(args["width"], '(%d+)px$')
		local arg_pc_width = string.match(args["width"], '(%d+)%%$')
		if arg_px_width then
			-- use what the parameter said
			img_width_px = arg_px_width
		elseif arg_pc_width then
			-- use a nominal huge screen and find the image size upper bound
			img_width_px = math.floor((max_screen_size * tonumber(arg_pc_width)) / 100)
			-- still limit to the max size as well
			img_width_px = math.min(max_image_size, img_width_px)
		end
	end
	
	if img_width_px == nil then
		-- use the default size, or the image size, whichever is smaller
		img_width_px = math.min(img_title.file["width"], max_image_size)
	end
	
	-- construct the image markup we will use
	local img_markup = "[[File:" .. img_name .. "|" .. img_width_px .. "px"
	if arg_or_nil(args, "alt") then
		img_markup = img_markup .. "|alt=" .. args["alt"]	
	end
	if arg_or_nil(args, "link") then
		img_markup = img_markup .. "|link=" .. args["link"]	
	end
	if arg_or_nil(args, "page") then
		img_markup = img_markup .. "|page=" .. args["page"]	
	end
	img_markup = img_markup .. "|class=freedImg"
	if args["imgclass"]  then
		img_markup = img_markup .. " " .. args["imgclass"]	
	end
	img_markup = img_markup .. "]]"
	return img_markup
end

local function construct_caption(parent, is_div, args, caption, class)
	
	local tag = is_div and "div" or "span"
	
	local pstyle = {}
	add_if(pstyle, "text-align", args["talign"])
	add_if(pstyle, "margin-right", args["tmright"])
	add_if(pstyle, "margin-left", args["tmleft"])
	add_if(pstyle, "text-indent", args["indent"])
	add_style_to_table(pstyle, arg_or_nil(args, "tstyle"))
	
	local para = parent:tag(tag)
	para
		:css(pstyle)
		:addClass("imgCaption wst-freedimg-caption")
		:wikitext(caption)
	
	-- additional classes	
	if class then
		para:addClass(class)
	end

	return para
end

local function freedImg(is_div, args)
	
	local cats = {}

	local img_markup
	-- construct the image markup we will use
	if args['type'] == "math" or args['type'] == "score" or args['type'] == "user" then
		-- math, score and user just place the markup directly
		img_markup = args["file"]
	elseif args.file == nil then
		local error = "The file parameter must be given (use \"missing\" if the image needs to be added later)"
		return require('Module:Error').error({message = error})
	elseif args.file == "missing" then
		img_markup = mw.html.create("span")
			:addClass("wst-freedimg-missing")
			:wikitext('An image should appear at this position in the text.')
		img_markup = tostring(img_markup)
		table.insert(cats, "Pages with missing images")
	elseif args.file == "removed" then
		img_markup = mw.html.create("span")
			:addClass("wst-freedimg-missing")
			:wikitext('A non free image has been removed.<br>It can be viewed in the original at '.. args["srcdoc"] .. '.' )
		img_markup = tostring(img_markup)
		table.insert(cats, "Pages with redacted images")
	else
		img_markup = construct_image_markup(args["file"], args)
	end
	
	local outer_tag = is_div and "div" or "span"
	local caption_tag = is_div and "p" or "span"

	local outer_div_class = {"freedImg", "wst-freedimg"}
	if arg_or_nil(args, "cclass") then
		table.insert(outer_div_class, args["cclass"])
	end

	local outer_div_style = {}
	add_if(outer_div_style, "width", args["width"])
	add_if(outer_div_style, "margin-right", args["margin-right"])
	add_if(outer_div_style, "margin-left", args["margin-left"])
	add_if(outer_div_style, "float", args["float"])
	add_if(outer_div_style, "clear", args["clear"])

	if not is_div then
		outer_div_style["display"] = "inline-block"
	end
	add_style_to_table(outer_div_style, args["cstyle"])

	local outer = mw.html.create(outer_tag)
	outer
		:addClass(table.concat(outer_div_class, " "))
		:css(outer_div_style)
		
	-- add the top caption, if there is one, to the outer div
	if arg_or_nil(args, 'top-caption') then
		construct_caption(outer, is_div, args, args['top-caption'], 'wst-freedimg-caption-top')
	end

	outer:wikitext(img_markup)

	-- add the bottom caption, if there is one, to the outer div
	if arg_or_nil(args, 'caption') then
		construct_caption(outer, is_div, args, args['caption'], 'wst-freedimg-caption-bottom')
	end
	
	outer = tostring(outer)
	
	for k, v in pairs(cats) do
		outer = outer .. "[[Category:" .. v .. "]]"
	end

	return outer

end -- function freedimg

--[=[
Construct a "block" FreedImg
]=]
function p.freedImg(frame)
	local args = getArgs(frame)
	return freedImg(true, args)
end

--[=[
Construct an "inline" FreedImg
]=]
function p.freedImg_span(frame)
	local args = getArgs(frame)
	return freedImg(false, args)
end

return p