Module:CineMol/style

From Wikipedia, the free encyclopedia
Jump to navigation Jump to search
-- This is a port of CineMol to lua
-- CineMol https://github.com/moltools/CineMol was written by David Meijer, Marnix H. Medema & Justin J. J. van der Hooft and is MIT licensed
-- Please consider any edits made to this page as dual licensed MIT & CC-BY-SA 4.0

local p = {}

local Point2D = require( 'Module:CineMol/geometry' ).Point2D
local checkType = require( 'Module:CineMol/geometry' ).checkType

local colorFunctions = {
	to_hex = function(self)
		checkType( 'color:to_hex', 1, self, 'Color' )
		return string.format( "#%02x%02x%02x", self.r, self.g, self.b)
	end,
	diffuse = function(self, alpha)
		checkType( 'color:diffuse', 1, self, 'Color' )
		checkType( 'color:diffuse', 2, alpha, 'number' )
		alpha = math.max(0, math.min(1, alpha))
		return p.Color(math.floor(self.r*alpha),math.floor(self.g*alpha),math.floor(self.b*alpha))
	end
}

local colorEqual = function(self, other)
	checkType( 'color==', 1, self, 'Color' )
	checkType( 'color==', 2, other, 'Color' )
	return self.r == other.r and self.g == other.g and self.b == other.b
end

function p.Color(r, g, b)
	checkType( 'Color', 1, r, 'number' )
	checkType( 'Color', 2, g, 'number' )
	checkType( 'Color', 3, b, 'number' )
	local obj = {
		r = math.floor(r),
		g = math.floor(g),
		b = math.floor(b),
		_TYPE = 'Color'
	}

	setmetatable( obj,
		{
			__index = colorFunctions,
			__eq = colorEqual,
			__tostring = colorFunctions.to_hex
		}
	)
	return obj
end
local Color = p.Color

-- Corey-Pauling-Koltun coloring convention for atoms.
-- Source: https://en.wikipedia.org/wiki/CPK_coloring
p.CoreyPaulingKoltungAtomColor = {
    H = Color(255, 255, 255), 
    C = Color(80, 80, 80),
    N = Color(0, 0, 255),
    O = Color(255, 0, 0), 
    P = Color(255, 165, 0),  
    S = Color(255, 255, 0), 
    B = Color(245, 245, 220),
    Br = Color(139, 0, 0),
    I = Color(148, 0, 211),  
    Ti = Color(128, 128, 128),
    Fe = Color(255, 140, 0),
    F = Color(0, 128, 0),
    Cl = Color(0, 128, 0),  
    He = Color(0, 255, 255),  
    Ne = Color(0, 255, 255),  
    Ar = Color(0, 255, 255),  
    Kr = Color(0, 255, 255),  
    Xe = Color(0, 255, 255),  
    Li = Color(238, 130, 238), 
    Na = Color(238, 130, 238),  
    K = Color(238, 130, 238),  
    Rb = Color(238, 130, 238),  
    Cs = Color(238, 130, 238),  
    Fr = Color(238, 130, 238),  
    Be = Color(0, 100, 0),  
    Mg = Color(0, 100, 0),
    Ca = Color(0, 100, 0),
    Sr = Color(0, 100, 0),
    Ba = Color(0, 100, 0),
    Ra = Color(0, 100, 0),
    Cd = Color(170, 51, 106),

	_TYPE = 'AtomColoringScheme',

	get_color = function(self, atom_symbol)
		checkType( 'get_color', 1, self, 'AtomColoringScheme' )
		checkType( 'get_color', 2, atom_symbol, 'string' )
		return self[atom_symbol] or Color(255, 192, 203)
	end
}

-- Atomic radii (van der Waals) in picometer from PubChem.
-- Source: https://pubchem.ncbi.nlm.nih.gov/periodic-table/#property=AtomicRadius
p.PubChemAtomRadius = {
    H = 120.0,
    He = 140.0,
    Li = 182.0,
    Be = 153.0,
    B = 192.0,
    C = 170.0,
    N = 155.0,
    O = 152.0,
    F = 135.0,
    Ne = 154.0,
    Na = 227.0,
    Mg = 173.0,
    Al = 184.0,
    Si = 210.0,
    P = 180.0,
    S = 180.0,
    Cl = 175.0,
    Ar = 188.0,
    K = 275.0,
    Ca = 231.0,
    Sc = 211.0,
    Ti = 187.0,
    V = 179.0,
    Cr = 189.0,
    Mn = 197.0,
    Fe = 194.0,
    Co = 192.0,
    Ni = 163.0,
    Cu = 140.0,
    Zn = 139.0,
    Ga = 187.0,
    Ge = 211.0,
    As = 185.0,
    Se = 190.0,
    Br = 183.0,
    Kr = 202.0,
    Rb = 303.0,
    Sr = 249.0,
    Y = 219.0,
    Zr = 186.0,
    Nb = 207.0,
    Mo = 209.0,
    Tc = 209.0,
    Ru = 207.0,
    Rh = 195.0,
    Pd = 202.0,
    Ag = 172.0,
    Cd = 158.0,
    In = 193.0,
    Sn = 217.0,
    Sb = 206.0,
    Te = 206.0,
    I = 198.0,
    Xe = 216.0,
    Cs = 343.0,
    Ba = 268.0,
    Lu = 221.0,
    Hf = 212.0,
    Ta = 217.0,
    W = 210.0,
    Re = 217.0,
    Os = 216.0,
    Ir = 202.0,
    Pt = 209.0,
    Au = 166.0,
    Hg = 209.0,
    Tl = 196.0,
    Pb = 202.0,
    Bi = 207.0,
    Po = 197.0,
    At = 202.0,
    Rn = 220.0,
    Fr = 348.0,
    Ra = 283.0,

	_TYPE = 'AtomRadiusScheme',

	--Return the radius of an atom in angstrom.
	to_angstrom = function (self, atom_symbol)
		assert( type(self) == 'table', 'arg 1 must be table' )
		assert( type(atom_symbol) == 'string', 'arg 2 must be string')
		local factor = 0.01 -- pictometer to angstrom
		return self[atom_symbol] ~= nil and self[atom_symbol]*factor or default*factor
	end
}

-- ==================
-- Art styles
-- ==================

function p.Cartoon( fill_color, outline_color, outline_width, opacity )
	checkType( 'Catroon', 1, fill_color, 'Color' )
	outline_color = outline_color == nil and Color(0,0,0) or outline_color
	outline_width = outline_width == nil and 0.05 or outline_width
	opacity = opacity == nil and 1.0 or opacity
	checkType( 'Cartoon', 2, outline_color, 'Color' )
	checkType( 'Cartoon', 3, outline_width, 'number' )
	checkType( 'Cartoon', 4, opacity, 'number' )
	return {
		_TYPE = 'Depiction',
		name = 'Cartoon',
		fill_color = fill_color,
		outline_width = outline_width,
		outline_color = outline_color,
		opacity = opacity
	}
end

function p.Glossy( fill_color, opacity )
	checkType( 'Catroon', 1, fill_color, 'Color' )
	opacity = opacity == nil and 1.0 or opacity
	checkType( 'Cartoon', 2, opacity, 'number' )
	return {
		_TYPE = 'Depiction',
		name = 'Glossy',
		fill_color = fill_color,
		opacity = opacity
	}

end

-- FillStyle

function p.Wire(stroke_color, stroke_width, opacity)
	checkType( 'Wire', 1, stroke_color, 'Color' )
	checkType( 'Wire', 2, stroke_width, 'number' )
	checkType( 'Wire', 3, opacity, 'number' )
	return {
		_TYPE = 'FillStyle',
		name = 'Wire',
		stroke_color = stroke_color,
		stroke_width = stroke_width,
		opacity = opacity
	}
end

function p.Solid(fill_color, stroke_color, stroke_width, opacity)
	checkType( 'Solid', 1, fill_color, 'Color' )
	checkType( 'Solid', 2, stroke_color, 'Color' )
	checkType( 'Solid', 3, stroke_width, 'number' )
	checkType( 'Solid', 4, opacity, 'number' )
	return {
		_TYPE = 'FillStyle',
		name = 'Solid',
		fill_color = fill_color,
		stroke_color = stroke_color,
		stroke_width = stroke_width,
		opacity = opacity
	}
end

function p.RadialGradient(fill_color, center, radius, opacity)
	checkType( 'Wire', 1, fill_color, 'Color' )
	checkType( 'Wire', 2, center, 'Point2D' )
	checkType( 'Wire', 3, radius, 'number' )
	checkType( 'Wire', 4, opacity, 'number' )
	return {
		_TYPE = 'FillStyle',
		name = 'RadialGradient',
		fill_color = fill_color,
		radius = radius,
		center = center,
		opacity = opacity
	}
end

function p.LinearGradient(fill_color, start, endp, opacity)
	checkType( 'Wire', 1, fill_color, 'Color' )
	checkType( 'Wire', 2, start, 'Point2D' )
	checkType( 'Wire', 3, endp, 'Point2D' )
	checkType( 'Wire', 4, opacity, 'number' )
	return {
		_TYPE = 'FillStyle',
		name = 'LinearGradient',
		fill_color = fill_color,
		start = start,
		endp = endp,
		opacity = opacity
	}
end

function p.Fill( reference, fill_style )
	assert(type(reference) == 'string', 'first argument should be string' )
	checkType( 'Fill', 2, fill_style, 'FillStyle' )
	local obj = {
		reference = reference,
		fill_style = fill_style
	}

	function obj:to_svg()
		if self.fill_style.name == 'Wire' then
            local stroke_color = self.fill_style.stroke_color:to_hex()
            local stroke_width = self.fill_style.stroke_width
            local opacity = self.fill_style.opacity

            local style_str = ( 
                "." .. self.reference ..
                "{stroke:" .. stroke_color .. ";" ..
                string.format( "stroke-width:%.3fpx;", stroke_width ) ..
                string.format( "stroke-opacity:%.1f;", opacity ) ..
                "stroke-linecap:round;" ..
                "stroke-linejoin:round;}"
            )
            local definition_str = nil

            return style_str, definition_str
		elseif self.fill_style.name == 'Solid' then
            local fill_color = self.fill_style.fill_color:to_hex()
            local stroke_color = self.fill_style.stroke_color:to_hex()
            local stroke_width = self.fill_style.stroke_width
            local opacity = self.fill_style.opacity

			local style_str = "." .. self.reference ..
				"{fill:" .. fill_color .. ";" ..
				"stroke:" .. stroke_color .. ";" ..
                string.format( "stroke-width:%.3fpx;", stroke_width ) ..
                string.format( "opacity:%.1f;}", opacity )

			return style_str, nil
		elseif self.fill_style.name == 'RadialGradient' then
            local cx = self.fill_style.center.x
            local cy = self.fill_style.center.y
            local r = self.fill_style.radius

            local stop_color_offset_a = self.fill_style.fill_color:to_hex()
            local stop_color_offset_b = self.fill_style.fill_color:diffuse(0.75):to_hex()

            local style_str = "." .. self.reference .. "{fill:url(#" .. self.reference .. ");}"
            local definition_str = (
                "<radialGradient" ..
                string.format(' id="%s"', self.reference) ..
                string.format(' cx="%.3f" cy="%.3f"', cx, cy ) ..
                string.format(' r="%.3f" fx="%.3f" fy="%.3f"', r, cx, cy ) ..
                ' gradientTransform="matrix(1,0,0,1,0,0)"' ..
                ' gradientUnits="userSpaceOnUse"' ..
				-- force it to be 1.0 for easier diffing results with python version
                string.format(' opacity="%s">', self.fill_style.opacity == 1 and '1.0' or self.fill_style.opacity  ) ..
                string.format('<stop offset="0.00" stop-color="%s"/>', stop_color_offset_a ) ..
                string.format('<stop offset="1.00" stop-color="%s"/>', stop_color_offset_b ) ..
                "</radialGradient>"
            )

            return style_str, definition_str
		elseif self.fill_style.name == 'LinearGradient' then
            local multiplier = 3.0  -- Makes linear gradient look better.
            local x1 = self.fill_style.start.x * multiplier
            local y1 = self.fill_style.start.y * multiplier
            local x2 = self.fill_style.endp.x * multiplier
            local y2 = self.fill_style.endp.y * multiplier

            local stop_color_offset_a = self.fill_style.fill_color:to_hex()
            local stop_color_offset_b = self.fill_style.fill_color:diffuse(0.75):to_hex()

            local style_str = string.format( ".%s{fill:url(#%s);}", self.reference, self.reference )
            local definition_str = (
                "<linearGradient" ..
                string.format( ' id="%s"', self.reference ) ..
                string.format( ' x1="%.3f" y1="%.3f"', x1, y1 ) ..
                string.format( ' x2="%.3f" y2="%.3f"', x2, y2 ) ..
                ' gradientUnits="userSpaceOnUse"' ..
                ' spreadMethod="reflect"' ..
                ' gradientTransform="rotate(90)"' ..
                string.format( ' opacity="%.1f">', self.fill_style.opacity ) ..
                string.format( '<stop offset="0.00" stop-color="%s"/>', stop_color_offset_a ) ..
                string.format( '<stop offset="1.00" stop-color="%s"/>', stop_color_offset_b ) ..
                "</linearGradient>"
            )

            return style_str, definition_str


		else
			error( "Unrecognized fill style - " .. self.fill_style.name )
		end
	end

	return obj
end


return p