跳转到内容
打开/关闭菜单
  • 451 条目
  • 802 文件
  • 22 用户
  • 9001 编辑
Blockland Wiki
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

模块: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