モジュール:File clip

モジュールの解説[表示] [編集] [履歴] [キャッシュを破棄]

画像やPDFファイルのトリミング表示を行います。このモジュールは{{File clip}}の実装を置き換える目的で作成されました。

現時点では{{File clip/sandbox}}が当モジュールのラッパーテンプレートになっており、Template:File clip/testcasesにて動作テストを行っています。

使い方編集

{{#invoke:File clip|呼び出す関数名|ファイル名|width=表示サイズ(横幅)|その他のオプション ... }}

呼び出す関数名にthumbを指定するとサムネイル形式で表示し、nothumbを指定するとフレームなしの形式で表示します。

ファイル名と表示サイズの指定は省略できません。

ファイル名を指定する際に名前空間プレフィックス(ファイル:やFile:など)を付ける必要はありません。

|width=には表示する際の横幅をピクセル単位で指定します。指定する値は数値のみとし、単位は付けないでください。

その他のオプションには以下の指定が可能です。

  • |t=, |r=, |b=, |l= - それぞれ切り抜く上部分、右部分、下部分、左部分の大きさを百分率で指定します。指定する値は0以上100未満の数値のみとし、%は付けないでください。省略した場合は0を指定した場合と同じになります。
  • |w=, |h= - それぞれ元のファイルの横幅・縦幅をピクセル単位で指定します。指定する値は数値のみとし、単位は付けないでください。省略した場合はScribuntoのファイルメタデータを利用して横幅・縦幅を自動的に取得しますが、これを行うと高負荷構文解析関数使用回数のカウンタが増加します|w=|h=の両方を指定することにより、これを回避することができます。
  • |align= - ファイルの表示位置を指定します。指定可能な値と省略時の挙動については[[ファイル:ファイル名|...]]による画像の表示と同様です。詳細についてはthumb形式の場合はHelp:画像の表示#thumbを、nothumb形式の場合はHelp:画像の表示#配置指定をご覧ください。
  • |caption=(あるいは|c=) - キャプションを指定します。省略時はファイル名が使われます。
  • |alt= - キャプションとは別にimg要素のalt属性の値を設定する場合は指定します。空文字列を指定することもできます。
  • |page= - ファイル形式がDjVuPDFの場合に、表示するページの番号を指定します。省略すると1ページ目を表示します。
  • |link= - 画像をクリックした際のリンクを変更することができます(空文字列も指定可能)。CC BYなど、ファイルのライセンスが帰属表示を求めている場合は、このオプションを使わないでください。詳しくはHelp:画像の表示#画像リンクをご覧ください。

使用例編集

コード

{{#invoke:File clip|thumb|Wikipedia Logo 1.0.png|width=200|align=left|t=0|r=25|b=70|l=35|caption=2003年から2010年まで使われていたウィキペディアのロゴの一部分}}

表示

Wikipedia Logo 1.0.png
拡大
2003年から2010年まで使われていたウィキペディアのロゴの一部分

require('Module:No globals')

local function showImage(filename, caption, options)
	--[=[
		画像を表示するウィキテキスト構文を生成して返す
		filename: ファイル名(文字列型、必須)
		caption(): 画像のキャプション(文字列型、省略可)
		options: その他のオプション(テーブル型、省略可)
	]=]--
	if type(filename) ~= 'string' or filename == '' then
		error('ファイル名が正しく指定されていません。', 2)
	end
	local t = { filename }
	if options then
		for k, v in pairs(options) do
			table.insert(
				t,
				(type(k) == 'string') and mw.ustring.format('%s=%s', k, v) or v
			)
		end
	end
	if caption then table.insert(t, caption) end
	return mw.ustring.format('[[File:%s]]', table.concat(t, '|'))
end

local function isValign(value)
	for _, v in ipairs({'baseline', 'sub', 'super', 'top', 'text-top',
	'middle', 'bottom', 'text-bottom'}) do
		if v == value then return true end
	end
	return false
end

--------------------------------------------------------------------------------
-- fileClipクラス
-- 	
-- fileClipをテーブルのメタテーブルに設定することで、
-- そのテーブルはfileClipクラスのインスタンスであるかのように振る舞う
--------------------------------------------------------------------------------
local fileClip = {}
fileClip.__index = fileClip

function fileClip:_setError(message)
	self._error = mw.html.create('strong')
		:addClass('error')
		:wikitext('エラー:' .. message)
	return self
end

function fileClip._new(args)
	local obj = {}
	setmetatable(obj, fileClip)
	
	-- 配置指定
	obj._align = args.align
	
	if not args[1] then
		return obj:_setError('ファイル名が指定されていません。')
	end
	
	-- 表示サイズ(横幅)
	obj._width = tonumber(args.width)
	if not obj._width then
		obj._width = 200
		return obj:_setError('表示サイズが指定されていません。')
	end
	
	-- 切り抜き範囲(百分率)
	-- 指定省略時は0%
	local crop_top = tonumber(args[2] or args.t) or 0
	local crop_right = tonumber(args[3] or args.r) or 0
	local crop_bottom = tonumber(args[4] or args.b) or 0
	local crop_left = tonumber(args[5] or args.l) or 0
	
	-- 画像の表示範囲(百分率)を計算
	-- 縦方向・横方向のどちらか一方でも0%以下ならエラー
	local v_rest = 100 - (crop_top + crop_bottom)
	local h_rest = 100 - (crop_right + crop_left)
	if v_rest <= 0 then
		return (h_rest <= 0)
			and obj:_setError('縦方向・横方向ともに100%以上切り取っています。')
			or obj:_setError('縦方向に100%以上切り取っています。')
	end
	if h_rest <= 0 then
		return obj:_setError('横方向に100%以上切り取っています。')
	end
	
	-- ファイルページのタイトルオブジェクト
	local filepage = mw.title.new(args[1], 'ファイル')
	
	-- ファイル名(名前空間プレフィックスを含まない)
	obj._filename = filepage.text
	
	-- 元の画像の横幅・縦幅
	-- 画像の横幅・縦幅はファイルメタデータを参照すれば取得可能だが、
	-- ファイルメタデータを取得する処理は高負荷である。
	-- 横幅・縦幅の両方が手動で指定されている場合は、それを使用し、
	-- ファイルメタデータの取得を避ける。
	local orig_width, orig_height
	if args.w and args.h then
		orig_width = tonumber(args.w)
		orig_height = tonumber(args.h)
	else
		local filemetadata = filepage.file	-- 高負荷
		if not filemetadata.exists then
			return obj:_setError(
				mw.ustring.format('[[:File:%s]]は存在しません。', obj._filename)
			)
		end
		if string.find(filemetadata.mimeType, 'image/', 1, true) ~= 1
		and filemetadata.mimeType ~= 'application/pdf' then
			return obj:_setError(
				mw.ustring.format(
					'[[:File:%s]]([[MIMEタイプ]]: %s)は切り抜き表示に対応していません。',
					obj._filename,
					filemetadata.mimeType
				)
			)
		end
		local page = tonumber(args.page)
		if page and page > 1 and filemetadata.pages then
			filemetadata = filemetadata.pages[page]
		end
		orig_width = filemetadata.width
		orig_height = filemetadata.height
	end
	
	-- 切り抜き画像の表示サイズ(縦幅)を計算
	obj._height = math.floor(
		(obj._width * orig_height * v_rest) /
		(orig_width * h_rest)
	)
	
	-- 画像を拡大後、上方向および左方向に何pxずらせばよいか計算
	obj._shift_up = math.floor(
		(obj._width * orig_height * crop_top) / 
		(orig_width * h_rest)
	)
	obj._shift_left = math.floor((obj._width * crop_left) / h_rest)
	
	-- 画像のキャプション
	-- 指定省略時は指定されたファイル名(名前空間プレフィックスを含まない)
	obj._caption = args[6] or args.c or args.caption or obj._filename
	
	-- 拡大後の画像の横幅、その他のオプション
	obj._image_options = {
		math.floor((obj._width * 100) / h_rest) .. 'px',
		alt = args.alt,
		link = args.link,
		page = args.page,
		class = args.class,
		lang = args.lang
	}
	
	return obj
end

function fileClip:thumb()
	local center = mw.html.create('div'):addClass('center')
	local thumb = mw.html.create('div')
	if self._align == 'center' or self._align == 'none' then
		thumb:addClass('thumb fileclip-thumb tnone')
	elseif self._align == 'left' then
		thumb:addClass('thumb fileclip-thumb tleft')
	else
		thumb:addClass('thumb fileclip-thumb tright')
	end
	local thumbinner = mw.html.create('div')
		:addClass('thumbinner fileclip-thumbinner')
		:css('width', (self._width + 2) .. 'px')
	if self._error then
		thumb:node(thumbinner:node(self._error))
		return (self._align == 'center') and center:node(thumb) or thumb
	end
	local clipper = mw.html.create('div')
		:addClass('fileclip-clipper thumbimage')
		:css({ width = self._width .. 'px', height = self._height .. 'px' })
		:cssText('position: relative; overflow: hidden;')
	local shifting = mw.html.create('div')
		:addClass('fileclip-shifting')
		:css({ top = -self._shift_up .. 'px', left = -self._shift_left .. 'px' })
		:cssText('position: absolute;')
		:wikitext(showImage(self._filename, nil, self._image_options))
	clipper:node(shifting)
	local thumbcaption = mw.html.create('div'):addClass('thumbcaption')
	local magnify = mw.html.create('div')
		:addClass('magnify fileclip-magnify')
		:wikitext(
			showImage(
				'Scissors icon black.svg',
				'拡大',
				{ 'text-top|16px', link = 'File:' .. self._filename }
			)
		)
	thumbcaption:node(magnify):wikitext(self._caption)
	thumb:node(thumbinner:node(clipper):node(thumbcaption))
	return (self._align == 'center') and center:node(thumb) or thumb
		
end

function fileClip:nothumb()
	if self._error then return self._error end
	local center = mw.html.create('div'):addClass('center')
	local aligndiv
	local clipper = mw.html.create('span')
		:addClass('fileclip-clipper')
		:css({ width = (self._width .. 'px'), height = (self._height .. 'px') })
		:cssText('position: relative; overflow: hidden; display: inline-block;')
	
	if self._align == 'center' or self._align == 'none' then
		aligndiv = mw.html.create('div'):addClass('floatnone')
	elseif self._align == 'right' or self._align == 'left' then
		aligndiv = mw.html.create('div'):addClass('float' .. self._align)
	elseif self._align and isValign(self._align) then
		clipper:css({ ['vertical-align'] = self._align })
	else
		clipper:css('vertical-align', 'middle')
	end
	
	local shifting = mw.html.create('span')
		:addClass('fileclip-shifting')
		:css({ top = -self._shift_up .. 'px', left = -self._shift_left .. 'px' })
		:cssText('position: absolute;')
		:wikitext(showImage(self._filename, self._caption, self._image_options))
	clipper:node(shifting)
	if not aligndiv then return clipper end
	aligndiv:node(clipper)
	return (self._align == 'center') and center:node(aligndiv) or aligndiv
end

local getArgs

local function _main(mode, frame)
	if not getArgs then getArgs = require('モジュール:Arguments').getArgs end
	local args = getArgs(frame, {
		valueFunc = function (key, value)
			-- alt引数とlink引数については、省略した場合と
			-- 空文字列を指定した場合を区別する
			
			if value then
				value = mw.text.trim(value)
			end
			if value == '' and key ~= 'alt' and key ~= 'link' then
				return nil
			end
			return value
		end,
		wrappers = { 'Template:File clip', 'Template:File clip2' }
	})
	local thumb = fileClip._new(args)
	thumb._export = thumb[mode]
	return frame:extensionTag{
			name = 'templatestyles',
			args = { src = 'File clip/styles.css' }
		}
		.. tostring(thumb:_export())
		.. '[[Category:切り抜き画像]]'
end

local p = {
	thumb = function(frame) return _main('thumb', frame) end,
	nothumb = function(frame) return _main('nothumb', frame) end
}

return p