模块:Multiple image
来自Blockland Wiki
更多操作
此模块的文档可以在模块:Multiple image/doc创建
-- [[Template:Multiple image]]
-- 轻量级多图片拼贴模块
-- 支持:
-- image1...image50
-- total_width
-- perrow = 1/3/2
-- border = infobox / none
-- align = left / center / right
-- image_gap
-- caption1...caption50
-- header / footer
-- link1... / alt1...
local p = {}
local function trim(s)
if s == nil then
return nil
end
s = tostring(s)
s = mw.text.trim(s)
if s == '' then
return nil
end
return s
end
local function number(v, default)
v = trim(v)
if not v then
return default
end
v = v:gsub('[Pp][Xx]$', '')
return tonumber(v) or default
end
------------------------------------------------------------
-- 清理文件名
------------------------------------------------------------
local function cleanFileName(name)
name = trim(name)
if not name then
return nil
end
name = name:gsub('^%s*[Ff]ile%s*:%s*', '')
name = name:gsub('^%s*文件%s*:%s*', '')
name = name:gsub('^%s*檔案%s*:%s*', '')
-- 防止把 [[File:x.jpg|...]] 之类参数带进来
name = name:gsub('|.*$', '')
return mw.text.trim(name)
end
------------------------------------------------------------
-- 获取图片原始宽高
------------------------------------------------------------
local function getDimensions(name, manualWidth, manualHeight)
manualWidth = number(manualWidth)
manualHeight = number(manualHeight)
if manualWidth and manualHeight
and manualWidth > 0
and manualHeight > 0 then
return manualWidth, manualHeight
end
local clean = cleanFileName(name)
if clean then
local title = mw.title.new('File:' .. clean)
if title then
local ok, file = pcall(function()
return title.file
end)
if ok and file
and file.width
and file.height
and file.width > 0
and file.height > 0 then
return file.width, file.height
end
end
end
-- 无法读取尺寸时按 4:3 处理
return 4, 3
end
------------------------------------------------------------
-- 解析 perrow
--
-- 例如:
-- 1/3/2
--
-- 7 张图 =>
-- 1
-- 3
-- 2
-- 1
------------------------------------------------------------
local function parsePerRow(value, imageCount)
local requested = {}
if value then
for n in tostring(value):gmatch('%d+') do
n = tonumber(n)
if n and n > 0 then
table.insert(requested, n)
end
end
end
if #requested == 0 then
requested[1] = imageCount
end
local rows = {}
local remaining = imageCount
local index = 1
local last = requested[1]
while remaining > 0 do
local amount = requested[index]
if amount then
last = amount
else
amount = last
end
amount = math.max(
1,
math.min(amount, remaining)
)
table.insert(rows, amount)
remaining = remaining - amount
index = index + 1
end
return rows
end
------------------------------------------------------------
-- 生成单张图片
------------------------------------------------------------
local function renderImage(
name,
width,
link,
alt,
caption,
captionAlign
)
local cell = mw.html.create('div')
cell
:css('box-sizing', 'border-box')
:css('flex', '0 0 auto')
:css('width', width .. 'px')
:css('max-width', '100%')
local imageBox = cell:tag('div')
imageBox
:css('line-height', '0')
:css('width', '100%')
:css('overflow', 'hidden')
:css('text-align', 'center')
local fileText =
'[[File:' ..
cleanFileName(name) ..
'|' ..
width ..
'px'
if alt then
fileText = fileText .. '|alt=' .. alt
end
if link ~= nil then
fileText = fileText .. '|link=' .. link
end
fileText = fileText .. ']]'
imageBox:wikitext(fileText)
if caption then
local captionBox = cell:tag('div')
captionBox
:css('font-size', '88%')
:css('line-height', '1.35')
:css('padding', '3px 2px 1px')
:css('text-align', captionAlign or 'left')
:wikitext(caption)
end
return tostring(cell)
end
------------------------------------------------------------
-- 主函数
------------------------------------------------------------
function p.render(frame)
local args = frame:getParent().args
local images = {}
-- 搜索 image1 ~ image50
for i = 1, 50 do
local image = trim(args['image' .. i])
if image then
table.insert(images, {
index = i,
name = image,
caption = trim(args['caption' .. i]),
link = args['link' .. i],
alt = trim(args['alt' .. i]),
width = args['width' .. i],
height = args['height' .. i]
})
end
end
local imageCount = #images
if imageCount == 0 then
return ''
end
--------------------------------------------------------
-- 参数
--------------------------------------------------------
local totalWidth = number(args.total_width)
local defaultWidth =
number(args.width, 200)
local gap =
number(args.image_gap, 1)
gap = math.max(0, gap)
local border =
trim(args.border) or ''
local align =
trim(args.align)
if not align then
if border == 'infobox' then
align = 'center'
else
align = 'right'
end
end
local captionAlign =
trim(args.caption_align) or 'left'
local header =
trim(args.header)
or trim(args.title)
local footer =
trim(args.footer)
local rows =
parsePerRow(
args.perrow,
imageCount
)
--------------------------------------------------------
-- 计算每张图宽度
--------------------------------------------------------
local calculatedWidths = {}
local maxRowWidth = 0
local cursor = 1
for r = 1, #rows do
local count = rows[r]
----------------------------------------------
-- total_width 模式:
-- 根据原图宽高比自动使一行图片等高
----------------------------------------------
if totalWidth then
local ratioSum = 0
local ratios = {}
for c = 1, count do
local img = images[cursor + c - 1]
local w, h =
getDimensions(
img.name,
img.width,
img.height
)
local ratio = w / h
ratios[c] = ratio
ratioSum = ratioSum + ratio
end
local usableWidth =
totalWidth - gap * (count - 1)
local rowHeight =
usableWidth / ratioSum
local used = 0
for c = 1, count do
local w
if c == count then
-- 最后一张补齐四舍五入误差
w =
usableWidth - used
else
w =
math.floor(
ratios[c] *
rowHeight +
0.5
)
used = used + w
end
calculatedWidths[
cursor + c - 1
] = math.max(1, w)
end
maxRowWidth =
math.max(
maxRowWidth,
totalWidth
)
else
------------------------------------------
-- 普通固定宽度模式
------------------------------------------
local rowWidth = 0
for c = 1, count do
local img =
images[cursor + c - 1]
local w =
number(
img.width,
defaultWidth
)
calculatedWidths[
cursor + c - 1
] = w
rowWidth =
rowWidth + w
end
rowWidth =
rowWidth +
gap * (count - 1)
maxRowWidth =
math.max(
maxRowWidth,
rowWidth
)
end
cursor = cursor + count
end
--------------------------------------------------------
-- 外框
--------------------------------------------------------
local root = mw.html.create('div')
root
:css('box-sizing', 'border-box')
:css('width', maxRowWidth .. 'px')
:css('max-width', '100%')
:css('font-size', '100%')
if border ~= 'infobox'
and border ~= 'none' then
root
:css('border', '1px solid #c8ccd1')
:css('background', '#f8f9fa')
:css('padding', '4px')
end
--------------------------------------------------------
-- 对齐
--------------------------------------------------------
if align == 'left' then
root
:css('float', 'left')
:css('margin', '0.5em 1em 0.5em 0')
elseif align == 'center'
or align == 'centre' then
root
:css('margin-left', 'auto')
:css('margin-right', 'auto')
elseif align == 'none' then
root
:css('margin', '0')
else
root
:css('float', 'right')
:css('margin', '0.5em 0 0.5em 1em')
end
--------------------------------------------------------
-- 标题
--------------------------------------------------------
if header then
root:tag('div')
:css('font-weight', 'bold')
:css('text-align', args.header_align or 'center')
:css('padding', '2px 4px 5px')
:wikitext(header)
end
--------------------------------------------------------
-- 图片行
--------------------------------------------------------
cursor = 1
for r = 1, #rows do
local count = rows[r]
local row = root:tag('div')
row
:css('display', 'flex')
:css('flex-direction', 'row')
:css('align-items', 'flex-start')
:css('justify-content', 'center')
:css('gap', gap .. 'px')
:css('width', '100%')
:css('box-sizing', 'border-box')
if r > 1 then
row:css('margin-top', gap .. 'px')
end
for c = 1, count do
local img = images[cursor]
row:wikitext(
renderImage(
img.name,
calculatedWidths[cursor],
img.link,
img.alt,
img.caption,
captionAlign
)
)
cursor = cursor + 1
end
end
--------------------------------------------------------
-- 底部说明
--------------------------------------------------------
if footer then
root:tag('div')
:css(
'text-align',
args.footer_align or 'left'
)
:css('font-size', '88%')
:css('line-height', '1.4')
:css('padding', '5px 3px 1px')
:wikitext(footer)
end
return tostring(root)
end
return p