Module:Sprite

*Documentation for this module may be created at Module:Sprite/doc*

```
local p = {}
function p.base( f )
 local args = f
 if f == mw.getCurrentFrame() then
 args = require( 'Module:ProcessArgs' ).merge( true )
 else
 f = mw.getCurrentFrame()
 end

 -- Default settings
 local default = {
 scale = 1,
 sheetsize = 256,
 size = 16,
 pos = 1,
 align = 'text-top'
 }

 local defaultStyle = default
 if args.settings then
 local settings = mw.loadData( 'Module:' .. args.settings )
 if not settings.stylesheet then
 -- Make a separate clone of the current default settings
 defaultStyle = mw.clone( default )
 end
 for k, v in pairs( settings ) do
 default[k] = v
 end
 end

 local setting = function( arg )
 return args[arg] or default[arg]
 end

 local sprite = mw.html.create( 'span' ):addClass( 'sprite' )
 sprite:tag( 'br' )

 -- mw.html's css method performs very slow escaping, which doubles the time it takes
 -- to run, so we'll construct the styles manually, and put them in the cssText
 -- method, which only does html escaping (which isn't slow)
 local styles = {}

 if setting( 'stylesheet' ) then
 sprite:addClass(
 setting( 'classname' ) or
 mw.ustring.lower( setting( 'name' ):gsub( ' ', '-' ) ) .. '-sprite'
 )
 else
 table.insert( styles, 'background-image:{{FileUrl|' .. (
 setting( 'image' ) or setting( 'name' ) .. 'Sprite.png'
 ) .. '}}' )
 end
 local class = setting( 'class' )
 if class then
 sprite:addClass( class )
 end

 local size = setting( 'size' )
 local pos = math.abs( setting( 'pos' ) ) - 1
 local sheetWidth = setting( 'sheetsize' )
 local tiles = sheetWidth / size
 local left = pos % tiles \* size
 local top = math.floor( pos / tiles ) \* size
 local scale = setting( 'scale' )
 local autoScale = setting( 'autoscale' )

 if left > 0 or top > 0 then
 table.insert( styles, 'background-position:-' .. left \* scale .. 'px -' .. top \* scale .. 'px' )
 end
 if not autoScale and scale ~= defaultStyle.scale then
 table.insert( styles, 'background-size:' .. sheetWidth \* scale .. 'px auto' )
 end
 if size ~= defaultStyle.size or ( not autoScale and scale ~= defaultStyle.scale ) then
 table.insert( styles, 'height:' .. size \* scale .. 'px' )
 table.insert( styles, 'width:' .. size \* scale .. 'px' )
 end
 local align = setting( 'align' )
 if align ~= defaultStyle.align then
 table.insert( styles, 'vertical-align:' .. align )
 end
 table.insert( styles, setting( 'css' ) )
 sprite:cssText( table.concat( styles, ';' ) )

 local text = setting( 'text' )
 local root
 local spriteText
 if text then
 root = mw.html.create( 'span' ):addClass( 'nowrap' )
 spriteText = mw.html.create( 'span' ):addClass( 'sprite-text' ):wikitext( text )
 end

 local title = setting( 'title' )
 if title then
 ( root or sprite ):attr( 'title', title )
 end

 if not root then
 root = mw.html.create( '' )
 end
 root:node( sprite )
 if spriteText then
 root:node( spriteText )
 end

 local link = setting( 'link' ) or ''
 if link ~= '' and mw.ustring.lower( link ) ~= 'none' then
 -- External link
 if link:find( '//' ) then
 return '[' .. link .. ' ' .. tostring( root ) .. ']'
 end

 -- Internal link
 local linkPrefix = setting( 'linkprefix' ) or ''
 return '[[' .. linkPrefix .. link .. '|' .. tostring( root ) .. ']]'
 end

 return tostring( root )
end

function p.sprite( f )
 local args = f
 if f == mw.getCurrentFrame() then
 args = require( 'Module:ProcessArgs' ).merge( true )
 end

 local categories = {}
 local idData = args.iddata
 if not idData then
 local default = {}
 if args.settings then
 default = mw.loadData( 'Module:' .. args.settings )
 end

 local name = args.name or default.name
 local ids = mw.loadData( 'Module:' .. ( args.ids or default.ids or name .. '/IDs' ) )
 ids = ids.ids or ids
 local id = mw.text.trim( tostring( args[1] or '' ) )
 idData = ids[id] or ids[mw.ustring.lower( id ):gsub( '[%s%+]', '-' )]
 end

 local title = mw.title.getCurrentTitle()
 -- Remove categories on sub-pages, and User\* and \*Talk namespaces
 local disallowCats = title.isSubpage or title.nsText:find( '^User' ) or title.nsText:find( '[Tt]alk$' )
 if idData then
 if type( idData ) == 'table' then
 if idData.deprecated and not disallowCats then
 table.insert( categories, '[[Category:Pages using deprecated sprite names]]' )
 end

 args.pos = idData.pos
 else
 args.pos = idData
 end
 elseif not disallowCats then
 table.insert( categories, '[[Category:Pages with missing sprites]]' )
 end

 return p.base( args ), table.concat( categories, '' )
end

function p.link( f )
 local args = f
 if f == mw.getCurrentFrame() then
 args = require( 'Module:ProcessArgs' ).merge( true )
 end

 local link = args[1]
 if args[1] and not args.id then
 link = args[1]:match( '^(.-)%+' ) or args[1]
 end
 local text = args.text or args[2] or link

 args[1] = args.id or args[1]
 args.link = args.link or link
 args.text = text

 return p.sprite( args )
end

function p.doc( f )
 local args = f
 if f == mw.getCurrentFrame() then
 args = f.args
 else
 f = mw.getCurrentFrame()
 end
 local settingsPage = mw.text.trim( args[1] )
 local settings = mw.loadData( 'Module:' .. settingsPage )
 local idsPage = 'Module:' .. ( settings.ids or settings.name .. '/IDs' )

 local getProtection = function( title, action, extra )
 local protections = { 'edit' }
 if extra then
 table.insert( protections, extra )
 end

 local addProtection = function( protection )
 if protection == 'autoconfirmed' then
 protection = 'editsemiprotected'
 elseif protection == 'sysop' then
 protection = 'editprotected'
 end

 table.insert( protections, protection )
 end

 local direct = title.protectionLevels[action]
 for \_, protection in ipairs( direct ) do
 addProtection( protection )
 end
 if title.cascadingProtection then
 local cascading = title.cascadingProtection.restrictions[action] or {}
 if #cascading > 0 then
 table.insert( protections, 'protect' )
 end
 for \_, protection in ipairs( cascading ) do
 addProtection( protection )
 end
 elseif f:callParserFunction( 'CASCADINGSOURCES', title.prefixedText ) ~= '' then
 -- Just adding the requirement for the protect right should be good enough until MW 1.25
 table.insert( protections, 'protect' )
 end

 return table.concat( protections, ',' )
 end

 local body
 if args.refresh then
 body = mw.html.create( '' )
 else
 local idsTitle = mw.title.new( idsPage )
 local spritesheet = settings.image or settings.name .. 'Sprite.png'
 local spriteTitle = mw.title.new( 'File:' .. spritesheet )
 local idsProtection = getProtection( idsTitle, 'edit' )
 local spriteProtection = getProtection( spriteTitle, 'upload', 'upload,reupload' )
 body = mw.html.create( 'div' ):attr( {
 id = 'spritedoc',
 ['data-idspage'] = idsTitle.id,
 ['data-idsprotection'] = idsProtection,
 ['data-idstimestamp'] = f:callParserFunction( 'REVISIONTIMESTAMP', idsPage ),
 ['data-spritesheet'] = spritesheet,
 ['data-spriteprotection'] = spriteProtection,
 ['data-pos'] = settings.pos or 1,
 ['data-refreshtext'] = mw.text.nowiki( '{{#invoke:sprite|doc|' .. settingsPage .. '|refresh=1}}' )
 } )
 end

 local data = mw.loadData( idsPage )

 local sections = {}
 for \_, sectionData in ipairs( data.sections or { 'Uncategorized' } ) do
 local sectionTag = body:tag( 'div' ):addClass( 'spritedoc-section' ):attr( 'data-section-id', sectionData.id )
 -- https://phabricator.wikimedia.org/T73594
 sectionTag:wikitext( '<h3>', sectionData[1], '</h3>' )
 sections[sectionData.id] = { boxes = sectionTag:tag( 'ul' ):addClass( 'spritedoc-boxes' ) }
 end

 local keyedData = {}
 for name, idData in pairs( data.ids ) do
 table.insert( keyedData, {
 sortKey = mw.ustring.lower( name ),
 name = name,
 data = idData
 } )
 end
 table.sort( keyedData, function( a, b )
 return a.sortKey < b.sortKey
 end )

 for \_, data in ipairs( keyedData ) do
 local idData = data.data
 local pos = idData.pos
 local section = sections[idData.section]
 local names = section[pos]
 if not names then
 local box = section.boxes:tag( 'li' ):addClass( 'spritedoc-box' ):attr( 'data-pos', pos )
 box:tag( 'div' ):addClass( 'spritedoc-image' )
 :wikitext( p.base{ pos = pos, settings = settingsPage } )

 names = box:tag( 'ul' ):addClass( 'spritedoc-names' )
 section[pos] = names
 end
 local nameElem = mw.html.create( 'li' ):addClass( 'spritedoc-name' )
 local codeElem = nameElem:tag( 'code' ):wikitext( data.name )

 if idData.deprecated then
 codeElem:addClass( 'spritedoc-deprecated' )
 end
 names:wikitext( tostring( nameElem ) )
 end

 if args.refresh then
 return '', tostring( body )
 end
 return f:callParserFunction( '#widget:Stylesheet', { page = 'SpriteDoc' } ), tostring( body )
end
return p

```