Jump to content

မေႃႇၵျူး:multiple images

လုၵ်ႉတီႈ ဝိၵ်ႇသျိၼ်ႇၼရီႇ မႃး

Documentation for this module may be created at မေႃႇၵျူး:multiple images/doc

local export = {}

local M = require("Module:module loader").init({
	require = {
		parameters = "Module:parameters",
		pages = "Module:pages",
	},
})

local LAYOUT = {
	DEFAULT_IMAGE_WIDTH = 250,
	ROW_GAP_BASE = 3,
	ROW_PADDING = 12,
	BODY_WIDTH_OFFSET = 8,
	BODY_WIDTH_MIN = 100,
	CELL_WIDTH_PADDING = 2,
	FALLBACK_ASPECT_HEIGHT = 100,
}

local THUMB_CLASS_BY_ALIGN = {
	["left"] = "tleft",
	["none"] = "tnone",
	["center"] = "tnone",
	["right"] = "tright",
}

local PARAMS_SCHEMA = {
	image = {
		list = true,
		allow_holes = true,
		required = true,
	},
	width = { list = true, allow_holes = true, type = "number" },
	height = { list = true, allow_holes = true, type = "number" },
	caption = { list = true, allow_holes = true },
	link = { list = true, allow_holes = true },
	alt = { list = true, allow_holes = true },
	thumbtime = { list = true, allow_holes = true },
	direction = { set = { "", "vertical", "horizontal" } },
	border = { set = { "", "none", "infobox" } },
	align = { set = { "", "left", "none", "center", "right" } },
	caption_align = true,
	total_width = { type = "number" },
	image_style = true,
	header = true,
	title = { alias_of = "header" },
	footer = true,
	image_gap = { type = "number", default = 1 },
	perrow = true,
	["background color"] = true,
	header_align = { set = { "", "left", "right", "center" } },
	header_background = true,
	footer_align = { set = { "", "left", "right", "center" } },
	footer_background = true,
}

local function is_not_empty(val)
	if val == nil then return false end
	if type(val) == "number" then return true end
	return tostring(val):match("^%s*(.-)%s*$") ~= ""
end

local function image_spec_to_base(image_spec)
	if not is_not_empty(image_spec) then return nil end
	local base = mw.uri.decode(mw.ustring.gsub(image_spec, "%|.*$", ""), "WIKI")
	return base ~= "" and base or nil
end

local function get_width(default_width, override_width)
	local width
	if is_not_empty(default_width) then
		width = tonumber(default_width)
	elseif is_not_empty(override_width) then
		width = tonumber(override_width)
	end
	return width or LAYOUT.DEFAULT_IMAGE_WIDTH
end

local function get_per_row(per_row_string, image_count)
	local parts = mw.text.split(per_row_string or "", "[^%d][^%d]*")
	if #parts < 1 then
		parts = { tostring(image_count) }
	end
	local row_index = 1
	local images_in_row = tonumber(parts[1] or image_count) or image_count
	local images_per_row = {}
	while image_count > 0 do
		images_per_row[row_index] = images_in_row
		image_count = image_count - images_in_row
		row_index = row_index + 1
		images_in_row = math.min(tonumber(parts[row_index] or images_in_row) or image_count, image_count)
	end
	return images_per_row
end

local function render_image_cell(image, width, height, link, alt, thumbtime, caption, caption_align, image_style)
	local cell_root = mw.html.create("")
	local alt_param = "|alt=" .. (alt or "")
	local link_param = link and ("|link=" .. link) or ""
	local width_param = "|" .. tostring(width) .. "px"
	local thumbtime_param = ""
	if width_param == "|-nanpx" or (width and width ~= width) then
		width_param = ""
	end
	if is_not_empty(thumbtime) then
		thumbtime_param = "|thumbtime=" .. thumbtime
	end
	local image_div = cell_root:tag("div")
	image_div:addClass("thumbimage")
	image_div:cssText(image_style)
	if height then
		image_div:css("height", tostring(height) .. "px")
		image_div:css("overflow", "hidden")
	end
	image_div:wikitext("[[file:" .. image .. width_param .. link_param .. alt_param .. thumbtime_param .. "]]")
	if is_not_empty(caption) then
		local caption_div = cell_root:tag("div")
		caption_div:addClass("thumbcaption")
		if is_not_empty(caption_align) then
			caption_div:addClass("text-align-" .. caption_align)
		end
		caption_div:wikitext(caption)
	end
	return tostring(cell_root)
end

local function get_dimensions(image_spec, width_arg, height_arg)
	if tonumber(width_arg) and tonumber(height_arg) then
		return tonumber(width_arg), tonumber(height_arg), "manual"
	end
	local base = image_spec_to_base(image_spec)
	local title = base and mw.title.new("File:" .. base)
	local file = title and title.file or { width = 0, height = 0 }
	local width = tonumber(file.width) or 0
	local height = tonumber(file.height) or 0
	return width, height, "auto"
end

local function render_multiple_images(args)
	local autoscaled = false
	local nonautoscaled = false
	local default_width = args.width and args.width[1]
	local layout_direction = args.direction or "vertical"
	local border = args.border or ""
	local align = args.align or (border == "infobox" and "center" or "")
	local caption_align = args.caption_align or ""
	local total_width = args.total_width
	local image_style = args.image_style
	local header = args.header or ""
	local footer = args.footer or ""
	local gap_px = math.max(0, args.image_gap or 0)

	local image_param_indices = {}
	local image_count = 0
	local image_list = args.image
	local max_image_index = image_list and image_list.maxindex or 0
	for param_index = 1, max_image_index do
		if is_not_empty(image_list and image_list[param_index]) then
			table.insert(image_param_indices, param_index)
			image_count = image_count + 1
		end
	end
	table.sort(image_param_indices)

	if (args.perrow or args.total_width) and not args.direction then
		layout_direction = "horizontal"
	end

	local images_per_row = get_per_row(layout_direction == "vertical" and "1" or args.perrow, image_count)
	local row_count = #images_per_row

	local heights = {}
	local widths = {}
	local max_row_width = 0
	local row_width_sums = {}
	local image_index = 0
	for row_index = 1, row_count do
		row_width_sums[row_index] = 0
		for _ = 1, images_per_row[row_index] do
			image_index = image_index + 1
			if image_index <= image_count then
				local param_index = image_param_indices[image_index]
				local image_spec = args.image and args.image[param_index]
				if total_width then
					local w, h, scale = get_dimensions(image_spec, args.width and args.width[param_index], args.height and args.height[param_index])
					widths[image_index], heights[image_index] = w, h
					if scale == "auto" then autoscaled = true elseif scale == "manual" then nonautoscaled = true end
				else
					widths[image_index] = get_width(default_width, args.width and args.width[param_index])
				end
				row_width_sums[row_index] = row_width_sums[row_index] + widths[image_index]
			end
		end
		max_row_width = math.max(max_row_width, row_width_sums[row_index])
	end

	if total_width then
		max_row_width = 0
		image_index = 0
		for row_index = 1, row_count do
			local image_index_at_row_start = image_index
			local available_row_width = total_width - (LAYOUT.ROW_GAP_BASE + gap_px) * (images_per_row[row_index] - 1) - LAYOUT.ROW_PADDING
			local aspect_ratios = {}
			local sum_aspect_ratios = 0
			for cell_index = 1, images_per_row[row_index] do
				image_index = image_index + 1
				if image_index <= image_count then
					local height_val = heights[image_index] or 0
					if height_val > 0 then
						aspect_ratios[cell_index] = widths[image_index] / height_val
						heights[image_index] = height_val
					else
						aspect_ratios[cell_index] = widths[image_index] / LAYOUT.FALLBACK_ASPECT_HEIGHT
					end
					sum_aspect_ratios = sum_aspect_ratios + aspect_ratios[cell_index]
				end
			end
			local row_width_sum = 0
			if sum_aspect_ratios > 0 then
				local uniform_row_height = available_row_width / sum_aspect_ratios
				image_index = image_index_at_row_start
				for cell_index = 1, images_per_row[row_index] do
					image_index = image_index + 1
					if image_index <= image_count then
						widths[image_index] = math.floor(aspect_ratios[cell_index] * uniform_row_height + 0.5)
						row_width_sum = row_width_sum + widths[image_index]
						if heights[image_index] then
							heights[image_index] = math.floor(uniform_row_height)
						end
					end
				end
			end
			row_width_sums[row_index] = row_width_sum
			max_row_width = math.max(max_row_width, row_width_sums[row_index])
		end
	end

	if image_count == 0 then
		return "", false, false
	end

	local body_width = LAYOUT.BODY_WIDTH_MIN
	for row_index = 1, row_count do
		if max_row_width == row_width_sums[row_index] then
			body_width = math.max(LAYOUT.BODY_WIDTH_MIN, max_row_width + (LAYOUT.ROW_GAP_BASE + gap_px) * (images_per_row[row_index] - 1) + LAYOUT.ROW_PADDING - LAYOUT.BODY_WIDTH_OFFSET)
			break
		end
	end

	local background_color = args["background color"] or ""
	local thumb_root = mw.html.create("div")
	thumb_root:addClass("thumb")
	thumb_root:addClass("tmulti")
	thumb_root:addClass(THUMB_CLASS_BY_ALIGN[align] or "tright")
	if align == "center" then
		thumb_root:addClass("center")
	end
	if background_color ~= "" then
		thumb_root:css("background-color", background_color)
	end

	local thumb_inner = thumb_root:tag("div")
	thumb_inner:addClass("thumbinner multiimageinner")
	thumb_inner:css("width", tostring(body_width) .. "px"):css("max-width", tostring(body_width) .. "px")
	if background_color ~= "" then
		thumb_inner:css("background-color", background_color)
	end
	if border == "infobox" or border == "none" then
		thumb_inner:css("border", "none")
	end

	if is_not_empty(header) then
		thumb_inner:tag("div")
			:addClass("trow")
			:tag("div")
			:addClass("theader")
			:css("text-align", is_not_empty(args.header_align) and args.header_align or nil)
			:css("background-color", is_not_empty(args.header_background) and args.header_background or nil)
			:wikitext(header)
	end

	image_index = 0
	for row_index = 1, row_count do
		local row_div = thumb_inner:tag("div"):addClass("trow")
		for cell_index = 1, images_per_row[row_index] do
			image_index = image_index + 1
			if image_index <= image_count then
				local cell_div = row_div:tag("div")
				cell_div:addClass("tsingle")
				if background_color ~= "" then
					cell_div:css("background-color", background_color)
				end
				if (gap_px > 1) and (cell_index < images_per_row[row_index]) then
					cell_div:css("margin-right", tostring(gap_px) .. "px")
				end
				local param_index = image_param_indices[image_index]
				local image_spec = args.image and args.image[param_index]
				local cell_width = widths[image_index]
				cell_div:css("width", tostring(LAYOUT.CELL_WIDTH_PADDING + cell_width) .. "px"):css("max-width", tostring(LAYOUT.CELL_WIDTH_PADDING + cell_width) .. "px")
				cell_div:wikitext(
					render_image_cell(
						image_spec,
						cell_width,
						heights[image_index],
						args.link and args.link[param_index],
						args.alt and args.alt[param_index],
						args.thumbtime and args.thumbtime[param_index],
						args.caption and args.caption[param_index],
						caption_align,
						image_style
					)
				)
			end
		end
	end

	if is_not_empty(footer) then
		local footer_align = string.lower(args.footer_align or "left")
		thumb_inner:tag("div")
			:addClass("trow")
			:css("display", (footer_align ~= "left") and "flow-root" or "flex")
			:tag("div")
			:addClass("thumbcaption")
			:css("text-align", (footer_align ~= "left") and footer_align or nil)
			:css("background-color", is_not_empty(args.footer_background) and args.footer_background or nil)
			:wikitext(footer)
	end

	return tostring(thumb_root), autoscaled, nonautoscaled
end

function export.render(frame)
	local args = M.parameters.process(frame:getParent().args, PARAMS_SCHEMA)
	local html, autoscaled, nonautoscaled = render_multiple_images(args)
	local categories = {}
	if M.pages.is_content_page(mw.title.getCurrentTitle()) then
		if autoscaled then table.insert(categories, "Pages using multiple image with auto scaled images") end
		if nonautoscaled then table.insert(categories, "Pages using multiple image with manual scaled images") end
	end
	local output = {
		frame:extensionTag("templatestyles", nil, { src = "multiple images/styles.css", wrapper = ".tmulti" }),
		html,
	}
	if #categories > 0 then
		for _, cat in ipairs(categories) do
			table.insert(output, "[[Category:" .. cat .. "]]")
		end
	end
	return table.concat(output)
end

return export