local p = {}
local patterns = {
image_key = "^image(%d*)$",
px_val = "^(-?%d+%.?%d*)?$",
digits = "%d+",
not_empty = "%S"
}
local file_dimensions_cache = {}
local function is_not_empty(s)
return s and s:match(patterns.not_empty)
end
local function to_numeric(val)
if type(val) == 'number' then
return val
end
if type(val) ~= 'string' then
return nil
end
local num_str = val:match(patterns.px_val) or val
return tonumber(num_str)
end
local function get_file_dimensions(filename)
if file_dimensions_cache then
return file_dimensions_cache.width, file_dimensions_cache.height
end
local file = mw.title.new('File:' .. filename)
local w, h = 0, 0
if file and file.exists then
w = file.width
h = file.height
end
file_dimensions_cache = { width = w, height = h }
return w, h
end
local function get_images_per_row(per_row_str, image_count)
local per_row_counts = {}
for num in mw.ustring.gmatch(per_row_str or "", patterns.digits) do
table.insert(per_row_counts, tonumber(num))
end
if #per_row_counts == 0 then
return { image_count }
end
local result_rows = {}
local processed_count = 0
local i = 1
local last_row_count = per_row_counts
while processed_count < image_count do
local current_row_spec = per_row_counts or last_row_count
local remaining = image_count - processed_count
local row_count = math.min(current_row_spec, remaining)
table.insert(result_rows, row_count)
processed_count = processed_count + row_count
last_row_count = row_count
i = i + 1
end
return result_rows
end
function p.render(frame)
local args = frame:getParent().args
local has_autoscaled = false
local has_manualscaled = false
local images = {}
for k, v in pairs(args) do
local key = tostring(k)
local num = key:match(patterns.image_key)
if num and is_not_empty(v) then
table.insert(images, {
index = tonumber(num),
file = v,
caption = args,
link = args,
alt = args,
width = to_numeric(args),
height = to_numeric(args),
thumbtime = args
})
end
end
if #images == 0 then
return ""
end
table.sort(images, function(a, b) return a.index < b.index end)
local image_count = #images
local direction = args.direction or 'vertical'
local per_row_str
if direction == 'vertical' then
per_row_str = '1'
else
per_row_str = args.perrow
end
local per_row_layout = get_images_per_row(per_row_str, image_count)
local row_count = #per_row_layout
local align = args.align or (args.border == 'infobox' and 'center' or 'right')
local caption_align = args.caption_align or ''
local total_width = to_numeric(args.total_width)
local default_width = to_numeric(args.width) or 200
local image_gap = to_numeric(args.image_gap) or 1
image_gap = math.max(0, image_gap)
local total_content_width = 0
for i, img in ipairs(images) do
if img.width and img.height then
has_manualscaled = true
else
local w, h = get_file_dimensions(img.file)
img.width = img.width or default_width
img.native_width = w
img.native_height = h
has_autoscaled = true
end
end
if total_width then
if direction == 'vertical' then
local available_width = total_width - 12
for _, img in ipairs(images) do
local aspect_ratio = 1.6
if img.native_height and img.native_height > 0 then
aspect_ratio = img.native_width / img.native_height
elseif img.height and img.height > 0 then
aspect_ratio = img.width / img.height
end
if aspect_ratio > 0 then
img.width = available_width
img.height = math.floor(available_width / aspect_ratio + 0.5)
end
end
else
local k = 1
for r = 1, row_count do
local row_img_count = per_row_layout
local available_width = total_width - (row_img_count - 1) * image_gap - 12
local aspect_ratio_sum = 0
for i = k, k + row_img_count - 1 do
local img = images
if img then
if img.native_height and img.native_height > 0 then
img.aspect_ratio = img.native_width / img.native_height
elseif img.height and img.height > 0 then
img.aspect_ratio = img.width / img.height
else
img.aspect_ratio = 1.6
end
aspect_ratio_sum = aspect_ratio_sum + img.aspect_ratio
end
end
if aspect_ratio_sum > 0 then
local common_height = available_width / aspect_ratio_sum
for i = k, k + row_img_count - 1 do
local img = images
if img then
img.width = math.floor(img.aspect_ratio * common_height + 0.5)
img.height = math.floor(common_height + 0.5)
end
end
end
k = k + row_img_count
end
end
end
local k = 1
for r = 1, row_count do
local row_width = 0
local row_img_count = per_row_layout
for i = k, k + row_img_count - 1 do
local img = images
if img then
row_width = row_width + img.width
end
end
row_width = row_width + (row_img_count - 1) * image_gap
total_content_width = math.max(total_content_width, row_width)
k = k + row_img_count
end
local root = mw.html.create('div')
local thumbclass = { left = 'tleft', none = 'tnone', center = 'tnone', right = 'tright' }
root
:addClass('thumb tmulti')
:addClass(thumbclass or 'tright')
:css('background-color', args.background_color or nil)
if align == 'center' then
root:addClass('center')
end
local thumbinner = root:tag('div')
:addClass('thumbinner multiimageinner')
:css('width', (total_content_width + 8) .. 'px')
:css('background-color', args.background_color or nil)
if args.border == 'infobox' or args.border == 'none' then
thumbinner:css('border', 'none')
end
if is_not_empty(args.header or args.title) then
thumbinner:tag('div')
:addClass('theader')
:css('text-align', args.header_align or nil)
:css('background-color', args.header_background or nil)
:wikitext(args.header or args.title)
end
local image_idx = 1
for r = 1, row_count do
local row_div = thumbinner:tag('div'):addClass('trow')
for c = 1, per_row_layout do
if image_idx > image_count then break end
local img = images
local cell = row_div:tag('div')
:addClass('tsingle')
:css('width', (img.width + 2) .. 'px')
:css('background-color', args.background_color or nil)
if c < per_row_layout and image_gap > 0 then
cell:css('margin-right', image_gap .. 'px')
end
local img_div = cell:tag('div')
:addClass('thumbimage')
:cssText(args.image_style or nil)
local wikitext_parts = { '[[File:', img.file }
local size_str
if img.height and img.height > 0 then
size_str = table.concat({ img.width, 'x', img.height, 'px' })
else
size_str = img.width .. 'px'
end
table.insert(wikitext_parts, '|' .. size_str)
table.insert(wikitext_parts, '|alt=' .. (img.alt or ''))
if img.link then table.insert(wikitext_parts, '|link=' .. img.link) end
if img.thumbtime then table.insert(wikitext_parts, '|thumbtime=' .. img.thumbtime) end
table.insert(wikitext_parts, ']]')
img_div:wikitext(table.concat(wikitext_parts))
if is_not_empty(img.caption) then
cell:tag('div')
:addClass('thumbcaption')
:addClass(is_not_empty(caption_align) and ('text-align-' .. caption_align) or nil)
:wikitext(img.caption)
end
image_idx = image_idx + 1
end
end
if is_not_empty(args.footer) then
thumbinner:tag('div')
:addClass('thumbcaption')
:css('text-align', args.footer_align or nil)
:css('background-color', args.footer_background or nil)
:wikitext(args.footer)
end
local output = {
frame:extensionTag('templatestyles', nil, { src = 'multiple images/styles.css', wrapper = '.tmulti' }),
tostring(root)
}
if has_autoscaled then
table.insert(output, ']')
end
if has_manualscaled then
table.insert(output, ']')
end
return table.concat(output)
end
return p