// ==================== |-------| ====================
// ==================== | DuESF | ====================
// ==================== |-------| ====================
// ==================== |------| ====================
// ==================== | core | ====================
// ==================== |------| ====================
/* License
DuESF - Duduf ExtendScript Framework
Copyright (c) 2021 Nicolas Dufresne, RxLaboratory
https://rxlaboratory.org
This file is part of DuESF.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* The Duduf ExtendScript Framework.<br />
* by {@link https://rxlaboratory.org RxLaboratory} and {@link http://duduf.com Duduf}.<br />
* WARNING: all objects marked deprecated will be removed in the version 1.1.0 of the framework.
* @example
* // Encapsulate everything to avoid global variables!
* // The parameter is either undefined (stand alone script) or the panel containing the ui (ScriptUI)
* (function(thisObj)
* {
* // Include the framework
* #include "DuESF.jsxinc";
*
* // Running the init() method of DuAEF is required to setup everything properly.
* DuESF.init( "YourScriptName", "1.0.0", "YourCompanyName" );
*
* // These info can be used by the framework to improve UX, but they're optional
* DuESF.chatURL = 'http://chat.rxlab.info'; // A link to a live-chat server like Discord or Slack...
* DuESF.bugReportURL = 'https://github.com/RxLaboratory/DuAEF_Dugr/issues/new/choose'; // A link to a bug report form
* DuESF.featureRequestURL = 'https://github.com/RxLaboratory/DuAEF_Dugr/issues/new/choose'; // A link to a feature request form
* DuESF.aboutURL = 'http://rxlaboratory.org/tools/dugr'; // A link to the webpage about your script
* DuESF.docURL = 'http://dugr.rxlab.guide'; // A link to the documentation of the script
* DuESF.scriptAbout = 'Duduf Groups: group After Effects layers!'; // A short string describing your script
* DuESF.companyURL = 'https://rxlaboratory.org'; // A link to your company's website
* DuESF.rxVersionURL = 'http://version.rxlab.io' // A link to an RxVersion server to check for updates
*
* // Build your UI here, declare your methods, etc.
*
* // This will be our main panel
* var ui = DuScriptUI.scriptPanel( thisObj, true, true, new File($.fileName) );
* ui.addCommonSettings(); // Automatically adds the language settings, location of the settings file, etc
*
* DuScriptUI.staticText( ui.settingsGroup, "Hello world of settings!" ); // Adds a static text to the settings panel
* DuScriptUI.staticText( ui.mainGroup, "Hello worlds!" ); // Adds a static text to the main panel
*
* // When you're ready to display everything
* DuScriptUI.showUI(ui);
*
* // Note that if you don't have a UI or if you don't use DuScriptUI to show it,
* // you HAVE TO run this method before running any other function:
* // DuESF.enterRunTime();
*
* })(this);
* @namespace
* @author Nicolas Dufresne and contributors
* @copyright 2021-2023 Nicolas Dufresne, RxLaboratory
* @version 1.0.0
* @license GPL-3.0 <br />
* This program is free software: you can redistribute it and/or modify<br />
* it under the terms of the GNU General Public License as published by<br />
* the Free Software Foundation, either version 3 of the License, or<br />
* (at your option) any later version.<br />
*<br />
* This program is distributed in the hope that it will be useful,<br />
* but WITHOUT ANY WARRANTY; without even the implied warranty of<br />
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the<br />
* GNU General Public License for more details.<br />
*<br />
* You should have received a copy of the GNU General Public License<br />
* along with this program. If not, see {@link http://www.gnu.org/licenses/}.
* @category DuESF
*/
var DuESF = {};
/**
* The Current DuESF Version
* @readonly
* @memberof DuESF
* @type {DuVersion}
*/
DuESF.version = new DuVersion('1.0.0');
/**
* Set to true and enable debug mode if you're a developper
* @memberof DuESF
* @type {Boolean}
*/
DuESF.debug = false;
/**
* The name of the script using this instance of DuESF. Must be set by {@link DuESF.init}.
* @memberof DuESF
* @type {string}
* @readonly
*/
DuESF.scriptName = "DuESF";
/**
* The version of the script using this instance of DuESF. Must be set by {@link DuESF.init}.
* @memberof DuESF
* @type {DuVersion}
* @readonly
*/
DuESF.scriptVersion = new DuVersion('0.0.0');
/**
* An icon for this script
* @type {string}
*/
DuESF.scriptIcon = '';
/**
* The url for the forum about the script
* @type {string}
*/
DuESF.forumURL = '';
/**
* The url for a chat server about the script
* @type {string}
*/
DuESF.chatURL = '';
/**
* The url for reporting bugs
* @type {string}
*/
DuESF.bugReportURL = '';
/**
* The url for donations, support, like...
* @type {string}
*/
DuESF.donateURL = 'http://donate.rxlab.info';
/**
* The url about the script
* @type {string}
*/
DuESF.aboutURL = '';
/**
* The url to the documentation of the script
* @type {string}
*/
DuESF.docURL = '';
/**
* The text about the script
* @type {string}
*/
DuESF.scriptAbout = '';
/**
* The name of the company/organisation/individual developping this tool
* @type {string}
*/
DuESF.companyName = 'RxLaboratory';
/**
* The URL to the company's website
* @type {string}
*/
DuESF.companyURL = 'https://rxlaboratory.org';
/**
* An icon for this company
* @type {string}
*/
DuESF.companyIcon = '';
/**
* The URL to a rxVersion server used to check for updates
* @type {string}
*/
DuESF.rxVersionURL = '';
/**
* A URL where people can help localizing the script
* @type {string}
*/
DuESF.translateURL = 'http://translate.rxlab.io'
/**
* Is this script a prerelease version? This info is used when checking for updates.
* @type {bool}
*/
DuESF.isPreRelease = false;
/**
* The current DuESF File
* @readonly
* @memberof DuESF
* @type {File}
*/
DuESF.file = new File( $.fileName );
/**
* Enum for states.
* @readonly
* @enum {int}
*/
DuESF.State = {
NOT_SET: -1,
INIT: 0,
RUNTIME: 1
};
/**
* The current state of the script
* @type {DuESF.State}
* @default DuESF.State.NOT_SET
*/
DuESF.state = DuESF.State.NOT_SET;
/**
* True if the script is run for the first time (new or the version has changed since last run)
* @type {boolean}
* @readonly
*/
DuESF.scriptFirstRun = true;
/**
* The applicatiosns DuESF can be used with.
* @readonly
* @enum {string}
*/
DuESF.HostApplication = {
AFTER_EFFECTS: 'aftereffects',
ILLUSTRATOR: 'illustrator',
PHOTOSHOP: 'photoshop',
INDESIGN: 'indesign'
}
DuESF.hostIsBeta = false;
/**
* The host application
* @type {DuESF.HostApplication}
*/
DuESF.host = null;
/**
* The version of the host application
*/
DuESF.hostVersion = new DuVersion();
/**
* The list of functions to run with {@link DuESF.init}. You can add your own methods here.<br />
* They're run after the settings have been loaded, but before everything else (especially the translator).
* @type {function[]}
*/
DuESF.preInitMethods = [];
/**
* The list of functions to run with {@link DuESF.init}. You can add your own methods here.<br />
* They're run after the settings and translator have been loaded.
* @type {function[]}
*/
DuESF.initMethods = [];
/**
* The list of functions to run with {@link DuESF.enterRunTime}. You can add your own methods here.<br />
* They're run after the UI has been created and just before runtime
* @type {function[]}
*/
DuESF.enterRunTimeMethods = [];
/**
* This method has to be called once at the very beginning of the script, just after the inclusion of DuESF <code>#include DuESF.jsxinc</code>
* @param {DuESF.host} hostApplication - The host application of the script.
* @param {string} [scriptName="DuESF"] - The name of your script, as it has to be displayed in the UI and the filesystem
* @param {string} [scriptVersion="0.0.0"] - The version of your script, in the form "XX.XX.XX-Comment", for example "1.0.12-Beta". The "-Comment" part is optional.
* @param {string} [companyName="RxLaboratory"] - The name of the company/organisation/individual developping this script.
*/
DuESF.init = function( scriptName, scriptVersion, companyName )
{
var host = BridgeTalk.appName;
if (host.indexOf('beta') == host.length -4) {
DuESF.hostIsBeta = true;
host = host.slice(0, host.length - 4);
}
DuESF.host = host;
DuESF.hostVersion = new DuVersion( BridgeTalk.appVersion );
if (DuESF.host == DuESF.HostApplication.AFTER_EFFECTS) {
DuESF.hostVersion = new DuVersion( app.version );
}
DuESF.state = DuESF.State.INIT;
scriptName = def( scriptName, "DuESF" );
scriptVersion = def( scriptVersion, "0.0.0" );
DuESF.scriptName = scriptName;
DuESF.scriptVersion = new DuVersion(scriptVersion);
DuESF.companyName = def( companyName, "RxLaboratory" );
// Init settings
DuSettings.init();
// check if this script has already been run once
// Check the version from latest run
var prevVersion = DuESF.settings.get("version");
if ( prevVersion == scriptVersion ) DuESF.scriptFirstRun = false;
DuDebug.log( "DuESF: loading preinit methods." );
// Pre translator init
for( var i = 0; i < DuESF.preInitMethods.length; i++) {
DuESF.preInitMethods[i]();
}
DuDebug.log( "DuESF: init translator." );
// Init translator
translationEngine.init();
DuDebug.log( "DuESF: loading init methods." );
// Init everything else
for( var i = 0; i < DuESF.initMethods.length; i++) {
DuESF.initMethods[i]();
}
}
/**
* This method has to be called once at the end of the script, when everything is ready and the main UI visible (after any prompt or setup during startup).
*/
DuESF.enterRunTime = function()
{
DuDebug.log( "DuESF: Entering runtime..." );
DuDebug.log( "DuESF: Language is set to: " + i18n.getLocale());
DuDebug.log( "DuESF: Getting script version..." );
DuESF.settings.data.version = DuESF.scriptVersion.fullVersion;
DuESF.settings.save();
DuESF.state = DuESF.State.RUNTIME;
// Everything which needs to be run once
for( var i = 0; i < DuESF.enterRunTimeMethods.length; i++) {
DuESF.enterRunTimeMethods[i]();
}
DuDebug.log( "DuESF: Runtime!" );
}
/**
* Removes all DuESF parts from memory. Call this if you've updated before you reload DuESF.
*/
DuESF.delete = function()
{
delete tr;
delete def;
delete jstype;
delete DuBinary;
delete DuColor;
delete DuDate;
delete DuDebug;
delete DuDebugLog;
delete DuESF;
delete DuFile;
delete DuFolder;
delete DuList;
delete DuMath;
delete DuNumber;
delete DuOCA;
delete DuPath;
delete DuProcess;
delete DuProcessQueue;
delete DuRegExp;
delete DuRSS;
delete DuRSSChannel;
delete DuRSSItem;
delete DuSettings;
delete DuSystem;
delete DuString;
delete DuVersion;
delete DuXML;
delete DuXMP;
delete DuZip;
delete DuESF;
}
/**
* Assigns a default value to a var if it's undefined.
* @global
* @param {object} val - The variable to set
* @param {object} defaultVal - The default value
* @return {object} The var
* @example foo = def( foo, 12 ); // if foo was undefined, it is now 12, else it's unchanged.
* @category DuESF
*/
function def( val, defaultVal )
{
return isdef(val) ? val : defaultVal;
}
/**
* Checks if a value is defined. Convenience replacement for `typeof val !== 'undefined`
* @param {any} val The value
* @return {boolean}
* @category DuESF
*/
function isdef(val) {
return typeof val !== 'undefined';
}
/**
* Improved typeof which returns the type of object instead of 'object'
* @global
* @param {any} exp - The expression to check.
* @return {string} The type always in lower case.
* @category DuESF
*/
function jstype(value) {
var type = typeof value;
if (type == 'object') {
try {
type = value === null ? 'null' : Object.prototype.toString.call(value).match(/^\[object (.*)\]$/)[1].toLowerCase();
} catch (e) {}
}
return type.toLowerCase();
}
// ==================== |--------| ====================
// ==================== | system | ====================
// ==================== |--------| ====================
/**
* System tools
* @namespace
* @category DuESF
*/
var DuSystem = {};
(function()
{
/**
* The current OS, true if we're on Mac OS
* @memberof DuSystem
* @readonly
* @type {boolean}
*/
DuSystem.mac = $.os.toLowerCase().indexOf( "mac" ) >= 0;
/**
* The current OS, true if we're on Windows
* @memberof DuSystem
* @readonly
* @type {boolean}
*/
DuSystem.win = !DuSystem.mac ;
/**
* A string identifying the version of the OS; may vary depending on the host application
* @memberof DuSystem
* @readonly
* @type {string}
*/
DuSystem.osVersion = $.os.toLowerCase()
.replace('windows/','')
.replace('windows','')
.replace('win', '')
.replace('mac os','')
.replace('macintosh','')
.replace('mac','');
})();
/**
* Opens a URL in the default browser.<br />
* @param {string} url - The URL
* @method
*/
DuSystem.openURL = function ( url )
{
if (DuESF.host == DuESF.HostApplication.AFTER_EFFECTS)
{
if ( DuSystem.mac )
{
system.callSystem( 'open "' + url + '"' );
}
else
{
system.callSystem( 'explorer "' + url + '"' );
}
return;
}
var timestamp = Date.now();
var shortcut = new File(Folder.temp + '/duesf_shortcut_' + timestamp + '.url');
shortcut.open('w');
shortcut.writeln('[InternetShortcut]');
shortcut.writeln('URL=' + url);
shortcut.writeln();
shortcut.close();
shortcut.execute();
shortcut.remove();
}
// ==================== |------| ====================
// ==================== | path | ====================
// ==================== |------| ====================
/**
* Paths related methods
* @namespace
* @category DuESF
*/
var DuPath = {}
/**
* Units to use for sizes.
* @readonly
* @enum {string}
*/
DuPath.SizeUnit = {
BIT: "b",
BYTE: "B",
KB: "kB",
MB: "MB",
GB: "GB",
TB: "TB"
}
/**
* Joins multiple paths togetther.
* @param {string[]} [parts] - The parts to join togehter
* @param {String} [sep="/"] - The separator to use. If not defined, will look for the first sep in the path.
* @return {String} The final path
*/
DuPath.join = function(parts, sep)
{
var separator = sep || false;
if(!separator)
{
// Find first "/" or "\"
if(parts[0].indexOf('/') != -1) separator = '/';
if(parts[0].indexOf('\\') != -1) separator = '\\';
}
if(!separator) separator = '/';
var replace = new RegExp(separator+'{1,}', 'g'); // Replace ///// with /
return parts.join(separator).replace(replace, separator);
}
/**
* Converts a number of Bytes to kB or MB or GB or TB
* For conversion to bits, a Byte is considered to be 8 bits.
* @param {int} size The size in Bytes to convert
* @param {DuPath.SizeUnit} [to=DuPath.SizeUnit.MB] The unit to convert to, one of: b, kB, MB, GB, TB
* @param {int} [decimals=2] The number of decimals to round the result
* @return {float} The result of the conversion
*/
DuPath.sizeFromBytes = function(size, to, decimals )
{
to = def(to, DuPath.SizeUnit.MB);
decimals = def(decimals, 0);
var result = size;
if (to == DuPath.SizeUnit.BIT) result = result * 8;
if (to == DuPath.SizeUnit.KB || to == DuPath.SizeUnit.MB || to == DuPath.SizeUnit.GB || to == DuPath.SizeUnit.TB) result = result / 1024;
if (to == DuPath.SizeUnit.MB || to == DuPath.SizeUnit.GB || to == DuPath.SizeUnit.TB) result = result / 1024;
if (to == DuPath.SizeUnit.GB || to == DuPath.SizeUnit.TB) result = result / 1024;
if (to == DuPath.SizeUnit.TB) result = result / 1024;
var round = 1;
for (var i = 0; i < decimals; i++)
{
round = round * 10;
}
result = Math.round(result*round)/round;
return result;
}
/**
* Returns the name of the given path or file, as displayed by the filesystem
* @example
* DuPath.getName(new File("D:\\code\\open\\Duik\\shape test.test")) // "shape test.test"
* DuPath.getName("D:/code/open/Duik/other shape.test") // "other shape.test"
* @param {String|File|Folder} [pathOrFile] - The file or the path
* @return {String} The basename
*/
DuPath.getName = function(pathOrFile)
{
if (pathOrFile instanceof File || pathOrFile instanceof Folder) pathOrFile = pathOrFile.fsName;
else {
//make sure it's the fsName we use
pathOrFile = new File(pathOrFile).fsName;
}
var name = pathOrFile.split("/").pop();
name = name.split("\\").pop();
return name;
}
/**
* Returns the basename of the given path or file
* @example
* DuPath.getBasename(new File("D:/code/open/Duik/shape.test")) // "shape"
* DuPath.getBasename("D:/code/open/Duik/shape.test") // "shape"
* @param {String|File} [pathOrFile] - The file or the path
* @return {String} The basename
*/
DuPath.getBasename = function(pathOrFile)
{
if (pathOrFile instanceof File) pathOrFile = pathOrFile.fsName;
else {
//make sure it's the fsName we use
pathOrFile = new File(pathOrFile).fsName;
}
var name = pathOrFile.split("/").pop();
name = name.split("\\").pop();
if(name.lastIndexOf(".") > 0) return name.slice(0, name.lastIndexOf("."));
return name;
}
/**
* Returns the extension of the given path or file
* @example
* DuPath.getExtension(new File("D:/code/open/Duik/shape.other.test")) // "test"
* DuPath.getExtension("D:/code/open/Duik/shape.test") // "test"
* @param {String|File} [pathOrFile] - The file or the path
* @return {String} The extension
*/
DuPath.getExtension = function(pathOrFile)
{
if (pathOrFile instanceof Folder) return '';
if (pathOrFile instanceof File) pathOrFile = pathOrFile.absoluteURI;
var name = pathOrFile.split("/").pop();
name = name.split("\\").pop();
if(name.indexOf(".") > 0) return name.split(".").pop();
return '';
}
/**
* Switch the extension of the given path. Create a new path from a given path and a given extension.
* @param {String|File} [pathOrFile] - The file or the path
* @param {String} [newExtension] - The new extension
* @return {String} The new path
*/
DuPath.switchExtension = function(pathOrFile, newExtension)
{
// Remove leading .
if (newExtension.indexOf('.') == 0) newExtension = newExtension.substring(1);
if (pathOrFile instanceof File) pathOrFile = pathOrFile.absoluteURI;
var point = pathOrFile.lastIndexOf(".");
if (point == -1) return pathOrFile + "." + newExtension;
return pathOrFile.slice(0, point) + "." + newExtension;
}
/**
* Generates a new unique name for a file
* @param {string} newName - The wanted new name
* @param {Folder} folder - The folder
* @param {boolean} [increment=true] - true to automatically increment the new name if it already ends with a digit
* @return {string} The unique name, with a new number at the end if needed.
*/
DuPath.newUniqueName = function ( newName, folder ) {
var file = new File(folder.absoluteURI + "/" + newName);
while (file.exists)
{
file = new File(folder.absoluteURI + "/" + DuPath.incrementName(file) );
}
return DuPath.getName(file);
}
/**
* Increments the last number before the extension in a filename
* @param {File} file The file
* @return {string} The incremented fileName
*/
DuPath.incrementName = function (file) {
var ext = DuPath.getExtension(file);
var baseName = DuPath.getBasename(file);
var regex = /(\d*)$/
var match = baseName.match(regex);
var num = '_01';
var numDigits = 0;
if (match) {
var n = match[0];
numDigits = n.length;
var n = parseInt(n);
if (!isNaN(n)) {
n++;
num = DuNumber.toString(n,numDigits);
}
}
return baseName.substring(0, baseName.length - numDigits) + num + '.' + ext;
}
/**
* Removes all forbidden characters from a string to be used as a filename
* @param {string} name - The filename to fix
* @param {string} [placeholder='.'] - The string used to replace the forbidden characters
* @return {string} The fixed filename
*/
DuPath.fixName = function (name, placeholder ) {
placeholder = def( placeholder, '.');
return name.replace( /([\/\\:*?"<>|])/g, placeholder);
}
/**
* Checks if the given path represents an existing file
* @param {File|Folder|string} path The path to check
* @return {bool}
*/
DuPath.isFile = function(path) {
if (jstype(path) == 'string')
{
path == new File(path);
if (path.exists) return true;
return false;
}
if (path instanceof File) return path.exists;
return false;
}
/**
* Checks if the given path represents an existing folder
* @param {File|Folder|string} path The path to check
* @return {bool}
*/
DuPath.isFolder = function(path) {
if (jstype(path) == 'string')
{
path == new Folder(path);
if (path.exists) return true;
return false;
}
if (path instanceof Folder) return path.exists;
return false;
}
// ==================== |--------| ====================
// ==================== | folder | ====================
// ==================== |--------| ====================
/**
* Folder related methods
* @namespace
* @category DuESF
*/
var DuFolder = {};
/**
* Recursively remove all content from folder and the folder itself. Warning, this does not move files to the recycle bin and can not be undone.
* @param {string|Folder} folder - the path or Folder object to wipe.
*/
DuFolder.wipeFolder = function(folder)
{
if ( jstype(folder) == 'string' && folder != '') folder = new Folder(folder);
if ( !folder.exists ) return;
//get content and remove
var files = folder.getFiles();
var it = new DuList(files);
it.do(function (file)
{
if ( jstype(file) == 'folder' ) DuFolder.wipeFolder(file);
else file.remove();
});
folder.remove();
}
/**
* Recursively gets all files in a folder using a name filter
* Returns an array of File objects.
* @param {Folder} folder - The Folder
* @param {string|function} [filter="*"] - A search mask for file names, specified as a string or a function.
* A mask string can contain question mark (?) and asterisk (*) wild cards. Default is "*", which matches all file names.
* Can also be the name of a function that takes a File or Folder object as its argument. It is called for each file or folder found in the search; if it returns true, the object is added to the return array.
* @return {Array} The files found.
*/
DuFolder.getFiles = function (folder,filter)
{
function isFolder(f) { return f instanceof Folder; }
if (!isdef( folder )) return [];
if (!(folder instanceof Folder)) return [];
var files = folder.getFiles(filter);
if (files === null) files = [];
var folders = folder.getFiles( isFolder );
for (var i = 0, num = folders.length ; i < num ; i++)
{
files = files.concat(DuFolder.getFiles(folders[i],filter));
}
return files;
}
/**
* Tests if a folder can be read
* @param {Folder|string} folder The folder
* @return {bool}
*/
DuFolder.canRead = function (folder) {
if ( jstype(folder) == 'string' && folder != '') folder = new Folder(folder);
if ( !folder.exists ) return false;
function isFile(f) { return f instanceof File; }
// Try to list files
try {
f = folder.getFiles( isFile );
/*if (f.length > 0) {
if (f[0].open('r')){
f.read();
f.close();
return true;
}
return false;
}//*/
return true;
}
catch (e) { return false; }
}
/**
* Tests if a folder can be written
* @param {Folder|string} folder The folder
* @return {bool}
*/
DuFolder.canWrite = function (folder) {
if ( jstype(folder) == 'string' && folder != '') folder = new Folder(folder);
if ( !folder.exists ) return false;
var content = 'test-write-access';
var f = new File(folder.absoluteURI + '/' + content);
if (f.open('w')) {
try {
f.write(content);
f.close();
f.remove();
return true;
}
catch(e) { return false };
} else return false;
}
// Low-level undocumented function. Called by DuESF.init()
DuFolder.init = function()
{
DuFolder.duesfData = new Folder( DuPath.join( [
DuFolder.userData.absoluteURI,
DuESF.companyName,
'AdobeScripts',
DuESF.scriptName
] ) );
}
DuESF.initMethods.push( DuFolder.init );
/**
* The default user data folder.<br />
* Replaces the Folder.userData provided by ESTK which does not work properly with mac network sessions.<br />
* In windows, the value of %APPDATA% ("C:\Documents and Settings\username\Application Data")<br />
* In Mac OS, "~/Library/Application Support"
* @type {Folder}
*/
DuFolder.userData = Folder.userData;
if (DuSystem.mac) DuFolder.userData = new Folder('~/Library/Application Support');
// We absolutely need write access... If not, use a temp folder
if (!DuFolder.canWrite(DuFolder.userData)) DuFolder.userData = Folder.temp;
DuFolder.duesfData = new Folder( DuPath.join( [
DuFolder.userData.absoluteURI,
DuESF.companyName,
'AdobeScripts',
DuESF.scriptName
] ) );
// ==================== |--------| ====================
// ==================== | object | ====================
// ==================== |--------| ====================
/**
* JS Objects related methods
* @namespace
* @category DuESF
*/
var DuJSObj = {};
/**
* Sorts object keys and returns a new sorted object.
* @param {object} obj The object to sort.
* @param {bool} [reverse=false] Set to true to sort in the reverse order.
* @param {function} [compareFn] Specifies a function that defines the sort order. Note that this parameter is ignored if reversed is true.<br />
* If omitted, the keys are converted to strings, then sorted according to each character's Unicode code point value.
* @return {object} The new sorted object.
*/
DuJSObj.sort = function( obj, reverse, compareFn ) {
reverse = def(reverse, false);
if (reverse) {
compareFn = function(a, b) {
if (b > a) return 1;
if (b < a) return -1;
if (b == a) return 0;
}
}
var newObj = {};
var tempArray = [];
for (i in obj) {
tempArray.push(i);
}
if (isdef( compareFn )) tempArray.sort(compareFn);
else tempArray.sort();
for (var i = 0, n = tempArray.length; i < n; i++)
{
var key = tempArray[i];
newObj[key] = obj[key];
}
return newObj;
}
/**
* Deletes all properties from an object, except a predefined list.
* @param {object} obj The object to clear.
* @param {string[]} [ignoredProperties] The properties to keep
*/
DuJSObj.clear = function( obj, ignoredProperties ) {
ignoredProperties = def(ignoredProperties, []);
// Remove existing items
for (i in obj) {
if (!obj.hasOwnProperty(i)) continue;
// ignore properties
var ok = true;
for (var j = 0, n = ignoredProperties.length; j < n; j++)
{
if (i == ignoredProperties[j]) {
ok = false;
break;
}
}
if (ok) delete obj[i];
}
}
DuJSObj.equals = function( obj1, obj2 ) {
for( key in obj1 ) {
if (obj2[key] !== obj1[key]) return false
}
return true;
}
// ==================== |-------| ====================
// ==================== | debug | ====================
// ==================== |-------| ====================
/**
* Tools for debugging
* @namespace
* @category DuESF
*/
var DuDebug = {};
/**
* Set this attribute to a DuDebugLog you have created to automatically add some debug infos to the log (like catched errors).
* @type {DuDebugLog|null}
*/
DuDebug.debugLog = null;
/**
* The log levels
* @enum {int}
* @readonly
*/
DuDebug.LogLevel =
{
VERBOSE: 0,
DEBUG: 1,
WARNING: 2,
CRITICAL: 3,
FATAL: 4,
NO_DEBUG: 5
}
/**
* Logs a message to DuDebug.DuDebugLog if it has been set.
* @param {string} message - The message to log
* @param {DuDebug.LogLevel} [level=DuDebug.LogLevel.DEBUG] - The level of the message
*/
DuDebug.log = function (message,level)
{
if ( DuDebug.debugLog != null ) DuDebug.debugLog.log( message,level );
}
/**
* This methods shows an alert with an error thrown if DuESF.debug is set to true, and logs it in DuESF.debug.DuDebugLog if it has been set.<br />
* The error is actually thrown only if JS Debugging is enabled in the host application, otherwise it is just shown in an alert.
* @param {Error|string} error - An error thrown and catched
*/
DuDebug.error = function (error, msg)
{
msg = def(msg, "");
var file = new File( error.fileName );
var errorString = 'An error has occured in file\n\n' + file.name + '\n\nlocated in:\n' + file.parent.fsName + '\n\nat line ' + error.line + '\n\n';
if (msg == "") errorString += error.description;
else errorString += msg;
DuDebug.log( errorString, DuDebug.LogLevel.WARNING );
if ( DuESF.debug )
{
alert( errorString );
// Throw if debugger enabled
if (DuESF.host == DuESF.HostApplication.AFTER_EFFECTS)
if (DuAE.isDebuggerEnabled()) throw error;
}
}
/**
* Runs a method/script safely, i.e. enclosed in a try...catch, and logs any error thrown.<br />
* arguments can be added after the first parameter, they will be passed to the callback.<br />
* The callback is enclosed in a function to make sure it does not create global variables.
* @param {function|string} callback - The method to run, or a script as a string
* @return {any} The return of the callback, null if it fails.
*/
DuDebug.safeRun = function (callback)
{
if (!isdef( callback )) throw "Error: DuDebug.safeRun(callback): Invalid parameter: callback cannot be undefined";
if (callback == '') throw "Error: DuDebug.safeRun(callback): Invalid parameter: callback cannot be an empty string";
var call = '';
if (jstype(callback) != 'string' && arguments.length > 1)
{
var args = [];
for (var i = 1, num = arguments.length; i < num; i++)
{
args.push(arguments[i]);
}
var params = args.join(',');
call = 'var result = callback(' + params + '); result;'
}
else if (jstype(callback) == 'string') call = callback;
try
{
if (call != '')
{
(function(){
return eval(call);
})();
}
else
{
return callback();
}
}
catch ( error )
{
DuDebug.error(error);
return null;
}
}
/**
* Inspects a javascript object and outputs all of its attributes
* @param {object} obj - The object to inspect.
* @param {boolean} [ownProperties=true] - Whether to inspect only the own properties of the object.
* @return {string} The report.
*/
DuDebug.inspect = function(obj, ownProperties)
{
ownProperties = def( ownProperties, true );
var report = "";
for (var key in obj)
{
if (ownProperties && !obj.hasOwnProperty(key)) continue;
report += key + " | type: " + typeof(obj[key]) + " | value: " + obj[key].toString() + "\n";
}
return report;
}
/**
* Alerts a nice TypeError description, if {@link DuESF.debug} is true, and logs it if there's a log.<br />
* Note that it does not actually throws an error.
* @param {any} variable The failing argument
* @param {string} varName The failing argument name
* @param {string} varType The expected type
* @param {string} [functionName] The name or description of the function throwing the error.
* @return {string} The (multiline) description for the error
*/
DuDebug.throwTypeError = function (variable, varName, varType, functionName)
{
functionName = def (functionName, '');
if (typeof functionName === 'Function') {
functionName = func.toString().split('\n')[0];
}
var e = 'TypeError:\n';
if (functionName != '') e += "in function " + functionName + "\n";
e += "\n" + varName + " should be of type " + varType + "\n";
e += "but it is of type " + jstype(variable);
if (isdef( variable )) try { e += " and has value: " + variable.toSource(); } catch(e) {};
//ADD stack trace
e += '\n\nStack:\n' + $.stack;
DuDebug.log(e, DuDebug.LogLevel.CRITICAL);
if ( DuESF.debug )
{
alert( e );
// Throw if debugger enabled
if (DuESF.host == DuESF.HostApplication.AFTER_EFFECTS)
if (DuAE.isDebuggerEnabled()) throw e;
}
return e;
}
/**
* Alerts a nice UndefinedError description, if {@link DuESF.debug} is true, and logs it if there's a log..<br />
* Note that it does not actually throws an error.
* @param {any} varName The name of the arg which should not be undefined
* @param {any} [functionName] The name or description of the function throwing the error.
* @return {string} The (multiline) description for the error
*/
DuDebug.throwUndefinedError = function( varName, functionName )
{
if (!DuESF.debug) return;
functionName = def (functionName, '');
if (typeof functionName === 'Function') {
functionName = func.toString().split('\n')[0];
}
var e = 'UndefinedError:\n';
if (functionName != '') e += "in function " + functionName + "\n";
e += "\n" + varName + " cannot be undefined.";
DuDebug.log(e, DuDebug.LogLevel.CRITICAL);
if ( DuESF.debug )
{
alert( e );
// Throw if debugger enabled
if (DuESF.host == DuESF.HostApplication.AFTER_EFFECTS)
if (DuAE.isDebuggerEnabled()) throw e;
}
return e;
}
/**
* Alerts a nice Error description, if {@link DuESF.debug} is true, and logs it if there's a log..<br />
* Note that it does not actually throws an error.
* @param {any} message The message to show
* @param {any} [functionName] The name or description of the function throwing the error.
* @param {Error} [error] A JS error.
* @param {Boolean} [neverThrow=false] If true, will prevent actually throwing the error even if the debugger is enabled.
* @return {string} The (multiline) description for the error
*/
DuDebug.throwError = function( message, functionName, error, neverThrow )
{
functionName = def (functionName, '');
neverThrow = def(neverThrow, false);
if (typeof functionName === 'Function') {
functionName = func.toString().split('\n')[0];
}
var e = 'Error:\n';
if (functionName != '') e += "in function " + functionName + "\n";
e += "\n" + message;
if (isdef( error ))
{
var file = new File(error.fileName);
e += '\n\nIn file\n\n' + file.name + '\n\nlocated in:\n' + file.parent.fsName + '\n\nat line ' + error.line + '\n\n' + error.description;
}
DuDebug.log(e, DuDebug.LogLevel.CRITICAL);
if ( DuESF.debug )
{
alert( e );
if (!neverThrow)
{
// Throw if debugger enabled
if (DuESF.host == DuESF.HostApplication.AFTER_EFFECTS)
if (DuAE.isDebuggerEnabled())
{
if (isdef( error )) throw error;
else throw e;
}
}
}
return e;
}
/**
* Checks if the given variable has the correct type,<br />
* alerts a nice error if not using {@link DuDebug.throwUndefinedError()} or {@link DuDebug.throwTypeError()}.
* @param {any} variable The variable to check
* @param {string} argName The variable name
* @param {string} [argType] The expected type name (as return by {@link jstype()}). If not provided, checks only if the var is undefined
* @param {any} [functionName] The name or description of the function throwing the error.
* @return {string|bool} The (multiline) description for the error if any and {@link DuESF.debug} is true, false if there's an error and {@link DuESF.debug} is false, true if everything is ok.
*/
DuDebug.checkVar = function ( variable, varName, varType, functionName )
{
// Check if undefined
if ( !isdef( variable ) )
{
if (DuESF.debug) return DuDebug.throwUndefinedError( varName, functionName );
else return false;
}
if (isdef( varType ))
if ( jstype( variable ) != varType )
{
if (DuESF.debug) return DuDebug.throwTypeError( variable, varName, varType, functionName );
else return false;
}
return true;
}
/**
* Constructs a new debug logger
* @class DuDebugLog
* @classdesc A logger for debugging purposes: writes debug messages to a file, with date and type
* @param {string|File} [pathOrFile] - The log file. By default, located next to the script settings file
* (if constructed <strong>after</strong> {@link DuESF.init()}).
* @param {boolean} [clear=true] - Whether to clear the previous log file before starting
* @param {DuDebug.LogLevel} [logLevel=DuDebug.LogLevel.DEBUG] - The log level.
* @param {boolean} [enabled=DuESF.debug] - true to enable the log and start recording
* @property {boolean} running - true if the timer is running, false if it is stopped.
* @category DuESF
*/
function DuDebugLog(pathOrFile,clear,logLevel,enabled)
{
/**
* The log file.
* @memberof DuDebugLog
* @readonly
* @name file
* @type {File}
*/
this.file = null;
if (!isdef( pathOrFile ))
{
var folder = DuESF.scriptSettings.file.parent;
pathOrFile = new File( folder.absoluteURI + '/' + DuESF.scriptName + '_debug_log.txt');
}
if (!(pathOrFile instanceof File)) this.file = new File(pathOrFile);
else this.file = pathOrFile;
this.file.encoding = 'UTF-8';
/**
* The log level.
* @memberof DuDebugLog
* @type {DuDebug.LogLevel}
* @name level
* @default DuDebug.LogLevel.DEBUG
*/
this.level = def (logLevel, DuDebug.LogLevel.DEBUG);
/**
* true to enable the log and record logs.
* @memberof DuDebugLog
* @type {DuDebug.LogLevel}
* @name enabled
* @default DuESF.debug
*/
this.enabled = def( enabled, DuESF.debug );
clear = def (clear, true);
var append = !clear && this.file.exists;
if (this.level <= DuDebug.LogLevel.DEBUG)
{
var currentDate = new Date();
DuFile.writeln (
this.file,
'[' + currentDate.toString() + '] == Debugger started. ==',
append );
}
this.elapsed = 0;
this.running = false;
this.log( "\n=========== NEW LOG =============\n=================================" , DuDebug.LogLevel.NO_DEBUG);
}
/**
* Logs a message
* @param {string} message - The message to log
* @param {DuDebug.LogLevel} [level=DuDebug.LogLevel.DEBUG] - The level of the message
*/
DuDebugLog.prototype.log = function (message,level)
{
level = def( level, DuDebug.LogLevel.DEBUG );
if (!this.enabled) return;
if (level < this.level) return;
var currentDate = new Date();
var levelString = " || Debug info || ";
if (level == DuDebug.LogLevel.WARNING) levelString = " || Warning || ";
else if (level == DuDebug.LogLevel.CRITICAL) levelString = " || Critical || ";
else if (level == DuDebug.LogLevel.FATAL) levelString = " || Fatal || ";
DuFile.writeln (
this.file,
'[' + currentDate.toString() + ']' + levelString + message,
true );
}
/**
* Starts the debugger timer.
* @param {string} [message] - A message to display in the log
* @param {DuDebug.LogLevel} [level=DuDebug.LogLevel.VERBOSE] - The level of the message
*/
DuDebugLog.prototype.startTimer = function(message, level)
{
if (!this.enabled) return;
level = def(level, DuDebug.LogLevel.VERBOSE );
message = def (message, '');
if (level >= this.level)
{
DuDebug.safeRun( function()
{
if (this.file.open('a'))
{
var currentDate = new Date();
this.file.writeln('[' + currentDate.toString() + '] == Timer started. == ' + message);
this.file.close();
}
});
}
if (!this.running) this.elapsed = 0;
this.running = true;
$.hiresTimer;
}
/**
* Stops the debugger timer.
* @param {string} [message] - A message to display in the log
* @param {DuDebug.LogLevel} [level=DuDebug.LogLevel.DEBUG] - The level of the message
*/
DuDebugLog.prototype.stopTimer = function(message, level)
{
if (!this.enabled) return;
if (!this.running) return;
level = def( level, DuDebug.LogLevel.DEBUG );
this.elapsed += Math.round($.hiresTimer/1000);
if (!isdef( message )) message = '';
if (level >= this.level)
{
DuDebug.safeRun( function()
{
if (this.file.open('a'))
{
var currentDate = new Date();
this.file.writeln('[' + currentDate.toString() + '] == Timer stopped == Time elapsed | ' + this.elapsed + 'ms | == ' + message);
this.file.close();
}
});
}
this.elapsed = 0;
this.running = false;
$.hiresTimer;
}
/**
* Checks the time elapsed since the timer has started<br />
* If the timer is not running, it will be started.
* @param {string} message - A message to display in the log
* @param {DuDebug.LogLevel} [level=DuDebug.LogLevel.DEBUG] - The level of the message
* @return {int} The time elapsed in milliseconds
*/
DuDebugLog.prototype.checkTimer = function(message, level)
{
level = def( level, DuDebug.LogLevel.DEBUG );
if (!this.running) this.startTimer();
this.elapsed += Math.round($.hiresTimer/1000);
if (!this.enabled) return -1;
if (level >= this.level)
{
DuDebug.safeRun(function()
{
if (this.file.open('a'))
{
message = def(message, '');
var currentDate = new Date();
this.file.writeln('[' + currentDate.toString() + '] == Time elapsed | ' + this.elapsed + 'ms | == ' + message);
this.file.close();
}
});
}
return this.elapsed;
}
// ==================== |------| ====================
// ==================== | list | ====================
// ==================== |------| ====================
/**
* Constructs a new DuList object
* @class
* @name DuList
* @classdesc DuList helps manipulating JS Arrays and After Effects Collections the same way. It is faster than converting a Collection to an Array.<br />
* Other specific Adobe Lists in other Applications may be supported later.<br />
* The first item of a DuList is always at index 0, the last at index length-1.<br />
* Accessor is the {@link DuList#at DuList.at()} method instead of <code>Array</code>.<br />
* List length is given by the {@link DuList#length DuList.length()} method instead of a property.<br />
* DuList does not reimplement methods to add or remove items, use the source list instead with the {@link DuList#list DuList.list()} property
* and the original methods (from the <code>Collection</code> instance or <code>Array</code>).<br />
* DuList can also be used as an iterator with iterator-like methods like {@link DuList#next DuList.next()}.<br />
* You can also run a function on each item with {@link DuList#do DuList.do()}.
* @property {boolean} isCollection - true if the original list is an After Effects Collection, false otherwise
* @property {boolean} isArray - true if the original list is an Array, false otherwise
* @category DuESF
*/
function DuList(obj) {
obj = def(obj, []);
var newList = obj;
// Copy constructor
if (obj instanceof DuList)
newList = obj.list;
/**
* True if the original list is an After Effects Collection, false otherwise.
* @memberof DuList.prototype
* @name isCollection
* @readonly
* @type {Boolean}
*/
this.isCollection = false;
/**
* True if the original list is an Array, false otherwise
* @memberof DuList.prototype
* @name isArray
* @readonly
* @type {Boolean}
*/
this.isArray = true;
//check if list is an Array or a Collection
if (DuESF.host == DuESF.HostApplication.AFTER_EFFECTS) {
if (
newList instanceof ItemCollection ||
newList instanceof LayerCollection ||
newList instanceof OMCollection ||
newList instanceof RQItemCollection
) {
this.isCollection = true;
this.isArray = false;
}
}
//if not a list, store it as a one-item array
if (!(
newList instanceof Array ||
newList instanceof ItemCollection ||
newList instanceof LayerCollection ||
newList instanceof OMCollection ||
newList instanceof RQItemCollection ||
newList instanceof DuList
)) {
newList = [newList];
}
/**
* The original list, an Array or an After Effects Collection.
* @memberof DuList.prototype
* @name list
* @readonly
* @type {Array|Collection}
*/
this.list = newList;
/**
* The current index of the iterator.
* @memberof DuList.prototype
* @name current
* @readonly
* @type {int}
*/
this.current = -1;
/**
* True if the iterator is at the start.
* @memberof DuList.prototype
* @name atStart
* @readonly
* @type {Boolean}
*/
this.atStart = false;
/**
* True if the iterator has reached the end.
* @memberof DuList.prototype
* @name atEnd
* @readonly
* @type {Boolean}
*/
this.atEnd = false;
/**
* True if the iterator is between 0 and length-1. Note that on creation, the iterator is always invalid, but calling {@link DuList#next DuList.next()} moves it to the beginning and makes it valid.
* @memberof DuList.prototype
* @name valid
* @readonly
* @type {Boolean}
*/
this.valid = false;
}
/**
* Returns the number of items in the list
* @return {int} The number of items.
*/
DuList.prototype.length = function() {
return this.list.length;
}
/**
* Alias for {@link DuList#length DuList.length()}
* @alias DuList.length()
* @name count
* @memberof DuList.prototype
* @function
*/
DuList.prototype.count = DuList.prototype.length;
/**
* Accessor. First item is always 0, last is always length()-1<br />
* Does not move the iterator to the index; to move the iterator use {@link DuList.goTo} instead.
* @param {int} index The index of the item. If it is out of range (negative or > length()-1), returns null
* @return {object} The item at the given index.
*/
DuList.prototype.at = function(index) {
var count = this.length();
if (count == 0) return null;
var min, max;
if (this.isCollection) {
min = 1;
index++;
max = count;
} else {
min = 0;
max = count - 1;
}
if (index < min) return null;
if (index > max) return null;
return this.list[index];
}
/**
* Gets the first index of a value, or -1 if not found
* @memberof DuList
* @param {*} value - The value to find. Must be compatible with the == operand if you don't provide a comparison function
* @param {function} [comparisonFunction] - A function which compares two values which returns true if the values are considered the same.
* @return {int} The index of the value, -1 if not found
*/
DuList.prototype.indexOf = function(value, comparisonFunction) {
if (DuDebug.checkVar(value, 'value', undefined, 'DuList.indexOf( value, comparisonFunction )') !== true)
return -1;
var useFunction = isdef( comparisonFunction );
for (var i = 0, n = this.length(); i < n; i++) {
if (!useFunction && this.at(i) == value) return i;
else if (useFunction && comparisonFunction(this.at(i), value)) return i;
}
return -1;
}
/**
* Checks if the list contains the item
* @memberof DuList
* @param {*} value - The value to find. Must be compatible with the == operand if you don't provide a comparison function
* @param {function} [comparisonFunction] - A function which compares two values which returns true if the values are considered the same.
* @return {Boolean}
*/
DuList.prototype.contains = function(value, comparisonFunction) {
return this.indexOf(value, comparisonFunction) >= 0;
}
/**
* Checks if the list has duplicated values
* @memberof DuList
* @return {boolean} true if the list has duplicated values. Always false for Ae Collections as they can't have duplicated items.
*/
DuList.prototype.hasDuplicates = function() {
for (var i = 0, n = this.length(); i < n; i++) {
for (var j = i + 1; j < n; j++) {
if (this.at(i) === this.at(j)) return true;
}
}
return false;
}
/**
* Returns all duplicated values found in the list
* @memberof DuList
* @return {DuList} The duplicated values. An empty list for Ae Collections as they can't have duplicated items.
*/
DuList.prototype.getDuplicates = function() {
if (this.isCollection) return new DuList();
var duplicates = [];
for (var i = 0, n = this.length(); i < n; i++) {
for (var j = i + 1; j < n; j++) {
if (this.at(i) === this.at(j)) duplicates.push(this.at(j));
}
}
duplicates = new DuList(duplicates);
duplicates.removeDuplicates(duplicates);
return duplicates;
}
/**
* Removes all duplicated values from the list, and returns them.<br />
* The internal list may be converted to an Array if needed.<br />
* The iterator invalidated.
* @memberof DuList
* @param {function} [comparisonFunction] - A function which compares two values which returns true if the values are considered the same.
* @return {Array} The duplicated (and removed) values. An empty list for Ae Collections as they can't have duplicated items.
*/
DuList.prototype.removeDuplicates = function(comparisonFunction) {
// Convert to an Array
this.convertToArray();
var useFunction = isdef( comparisonFunction );
var removed = [];
for (var i = 0, n = this.length(); i < n; i++) {
for (var j = n - 1; j > i; j--) {
if (!useFunction && this.at(i) == this.at(j)) {
removed = removed.concat(this.list.splice(j, 1));
} else if (useFunction && comparisonFunction(this.at(i), this.at(j))) {
removed = removed.concat(this.list.splice(j, 1));
}
}
}
this.valid = false;
this.current = -1;
return new DuList(removed);
}
/**
* Compares two arrays.<br />
* The items in the arrays must be compatible with the == operand if you don't provide a comparison function
* @memberof DuList
* @param {Array|Collection|DuList} other - The other list
* @param {int} [floatPrecision=-1] - The precision for (float) number comparison, number of decimals. Set to -1 to not use.
* @return {boolean} true if the two arrays contain the same values
*/
DuList.prototype.equals = function(other, comparisonFunction, floatPrecision) {
var useFunction = typeof comparisonFunction === 'function';
if (!DuList.isList(other)) return false;
other = new DuList(other);
if (this.length() != other.length()) return false;
var originalIndex = this.current;
this.reinitIterator();
var ok = true;
var item1;
while (item1 = this.next()) {
var item2 = other.at(this.current);
// A list, recursive
if (DuList.isList(item1)) {
var test = new DuList(item1);
if (!test.equals(item2, comparisonFunction, floatPrecision)) {
ok = false;
break;
}
continue;
}
// Comparison function
if (useFunction) {
if (!comparisonFunction(item1, item2)) {
ok = false;
break;
}
continue;
}
// Numbers
if (jstype(item1) == 'number') {
if (!DuMath.equals(item1, item2, floatPrecision)) {
ok = false;
break;
}
continue;
}
// Other
if (item1 != item2) {
ok = false;
break;
}
}
// Restore original iterator index
this.goTo(originalIndex);
return ok;
}
/**
* Returns true if the list is empty
* @return {boolean}
*/
DuList.prototype.isEmpty = function() {
return this.length() == 0;
}
/**
* Gets the last item in the list
* @return {*} The last item, null if the list is empty.
*/
DuList.prototype.last = function() {
if (this.isEmpty()) return null;
return this.at(this.length() - 1);
}
/**
* Gets the first item in the list
* @return {*} The first item, null if the list is empty.
*/
DuList.prototype.first = function() {
if (this.isEmpty()) return null;
return this.at(0);
}
/**
* Converts the stored list to an Array if it was a Collection.<br />
* This enables the modification of the list, but changes in the list will not be reflected in the application (if a layer is removed from the list, it is not removed in the application).
*/
DuList.prototype.convertToArray = function(index) {
// Nothing to do
if (!this.isCollection) return;
// The new array
var arr = [];
for (var i = 1, n = this.list.length; i <= n; i++) {
arr.push(this.list[i]);
}
this.isCollection = false;
this.isArray = true;
this.list = arr;
}
/**
* Removes the item at the given index. The internal list may be converted to an Array if needed.<br />
* The iterator invalidated.
* @param {int} index The index.
*/
DuList.prototype.remove = function(index) {
// Convert to an Array
this.convertToArray();
this.list.splice(index, 1);
this.valid = false;
this.current = -1;
}
/**
* Removes the first value from the list. The value must be checkable with the <code>==</code> operator if no <code>comparisonFunction</code> is provided.<br />
* The internal list may be converted to an Array if needed.<br />
* The iterator invalidated.
* @param {int} index The index.
* @param {function} [comparisonFunction] A function which compares two values which returns true if the values are considered the same.
*/
DuList.prototype.removeOne = function(value, comparisonFunction) {
// Convert to an Array
this.convertToArray();
var index = this.indexOf(value, comparisonFunction);
if (index >= 0) this.remove(index);
this.valid = false;
this.current = -1;
}
/**
* Removes all occurences of the value from the list. The value must be checkable with the <code>==</code> operator if no <code>comparisonFunction</code> is provided.<br />
* The internal list may be converted to an Array if needed.<br />
* The iterator invalidated.
* @param {int} index The index.
* @param {function} [comparisonFunction] A function which compares two values which returns true if the values are considered the same.
* @return {int} The number of items removed.
*/
DuList.prototype.removeAll = function(value, comparisonFunction) {
// Convert to an Array
this.convertToArray();
var count = 0;
var index;
do {
index = this.indexOf(value, comparisonFunction);
if (index >= 0) this.remove(index);
count++;
}
while (index >= 0);
this.valid = false;
this.current = -1;
return count;
}
/**
* Reimplements the Array.sort() method.<br />
* The internal list may be converted to an Array if needed.<br />
* The iterator is invalidated.
* @param {function} compareFunction Specifies a function that defines the sort order. If omitted, the array elements are converted to strings, then sorted according to each character's Unicode code point value.
* @return {Array} The sorted array. Note that the array is sorted in place, and no copy is made.
*/
DuList.prototype.sort = function(compareFunction) {
// Convert to an Array
this.convertToArray();
if (!isdef( compareFunction )) this.list.sort();
else this.list.sort(compareFunction);
this.valid = false;
this.current = -1;
return this.list;
}
/**
* Adds one or more elements to the end of the list and returns the new length of the list.<br />
* The internal list may be converted to an Array if needed.
* @param {any} element0 The element(s) to add to the end of the array.
* @param {any} element1 The element(s) to add to the end of the array.
* @param {any} element... The element(s) to add to the end of the array.
* @param {any} elementN The element(s) to add to the end of the array.
* @return {int} The new length of the list.
*/
DuList.prototype.push = function() {
this.convertToArray();
for (var i = 0, n = arguments.length; i < n; i++) {
this.list.push(arguments[i]);
}
return this.list.length;
}
/**
* Mergeserge two lists.<br />
* This method does not change the existing list, but instead returns a new list.
* @param {Array|DuList} other The other list.
* @return {DuList} The new list.
*/
DuList.prototype.concat = function(other) {
// Make copies to not modify existing lists
other = new DuList(other);
current = new DuList(this);
// Convert to arrays
other.convertToArray();
current.convertToArray();
return new DuList( current.list.concat(other.list) );
}
/**
* Adds one or more elements to the end of the list and returns the new length of the list, only if the value is not already in the list<br />
* The internal list may be converted to an Array if needed.
* @param {function} comparisonFunction A function which compares two values which returns true if the values are considered the same.
* @param {any} element0 The element(s) to add to the end of the array.
* @param {any} element1 The element(s) to add to the end of the array.
* @param {any} element... The element(s) to add to the end of the array.
* @param {any} elementN The element(s) to add to the end of the array.
* @return {int} The new length of the list.
*/
DuList.prototype.pushUnique = function(comparisonFunction) {
this.convertToArray();
for (var i = 1, n = arguments.length; i < n; i++) {
var arg = arguments[i];
if (this.indexOf(arg, comparisonFunction) >= 0) continue;
this.push(arg);
}
return this.list.length;
}
/**
* Alias for {@link DuList#push DuList.push()}
* @alias DuList.push()
* @name append
* @memberof DuList.prototype
* @function
*/
DuList.prototype.append = DuList.prototype.push;
/**
* Alias for {@link DuList#pushUnique DuList.pushUnique()}
* @alias DuList.pushUnique()
* @name appendUnique
* @memberof DuList.prototype
* @function
*/
DuList.prototype.appendUnique = DuList.prototype.pushUnique;
/**
* Merges the new array to the current list.<br />
* Note that to the contrary of the <code>Array.concat()</code> method, this does not create a new list, but modifies the current one in place.<br />
* The internal list may be converted to an Array if needed.
* @param {any[]} arr The other array.
* @return {int} The new length of the list.
*/
DuList.prototype.merge = function(arr) {
this.convertToArray();
this.list = this.list.concat(arr);
return this.list.length;
}
/**
* Merges the new array to the current list.<br />
* Will only add items if they're not already in the list.<br />
* Note that to the contrary of the <code>Array.concat()</code> method, this does not create a new list, but modifies the current one in place.<br />
* The internal list may be converted to an Array if needed.
* @param {any[]|DuList} arr The other array/list.
* @param {function} [comparisonFunction] A function which compares two values which returns true if the values are considered the same.
* @return {int} The new length of the list.
*/
DuList.prototype.mergeUnique = function(arr, comparisonFunction) {
arr = new DuList(arr);
this.convertToArray();
for (var i = 0, n = arr.length(); i < n; i++) {
this.pushUnique(comparisonFunction, arr.at(i));
}
return this.list.length;
}
/**
* Reimplementation of the <code>Array.join</code> function.<br />
* For collections, the name of the item will be used, or any other property which makes sense as a string list.
* @param {string} separator The separator to use
* @return {string} The new length of the list.
*/
DuList.prototype.join = function(separator) {
if (this.isArray) return this.list.join(separator);
// Collection without "name" property
if (this.list instanceof RQItemCollection) {
var str = '';
this.do(function(item) {
if (!this.current == 0) str += separator;
str += item.comp.name;
if (item.status == RQItemStatus.WILL_CONTINUE) str += ' (will continue)';
else if (item.status == RQItemStatus.NEEDS_OUTPUT) str += ' (no output)';
else if (item.status == RQItemStatus.UNQUEUED) str += ' (disabled)';
else if (item.status == RQItemStatus.QUEUED) str += ' (ready)';
else if (item.status == RQItemStatus.RENDERING) str += ' (rendering)';
else if (item.status == RQItemStatus.USER_STOPPED) str += ' (stopped)';
else if (item.status == RQItemStatus.ERR_STOPPED) str += ' (rendering error)';
else if (item.status == RQItemStatus.DONE) str += ' (done)';
});
return str;
}
// Other collections have a "name" property
var str = '';
this.do(function(item) {
if (!this.current == 0) str += separator;
str += item.name;
});
return str;
}
/**
* Replaces all occurences of a value with another value.<br />
* The internal list may be converted to an Array if needed.
* @param {any} what The current value
* @param {any} with The new value
* @param {function} [comparisonFunction] A function which compares two values which returns true if the values are considered the same.
* @returns {int} The number of occurences which were updated.
*/
DuList.prototype.replace = function(previousValue, newValue, comparisonFunction) {
this.convertToArray();
var useFunction = isdef( comparisonFunction );
var count = 0;
for (var i = 0, n = this.length(); i < n; i++) {
if (!useFunction && this.at(i) == previousValue) {
this.list[i] = newValue;
count++;
} else if (useFunction && comparisonFunction(this.at(i), previousValue)) {
this.list[i] = newValue;
count++;
}
}
return count;
}
/**
* Removes the last element of the list and returns it.<br />
* The internal list may be converted to an Array if needed.
* @return {any|null} The last element or null if the list is empty.
*/
DuList.prototype.pop = function() {
this.convertToArray();
return this.list.pop();
}
// ================ ITERATOR METHODS ================
/**
* Reinits the iterator, i.e. go to index -1 so that the next call to {@link DuList.next} returns the first item.
*/
DuList.prototype.reinitIterator = function() {
this.goTo(-1);
}
/**
* Sets the iterator on the index
* @param {int} index - The index
* @return {*} The item at index, or null if the index is invalid
*/
DuList.prototype.goTo = function(index) {
this.current = index;
var count = this.length();
if (this.current < 0 || this.current >= count) this.valid = false;
else this.valid = true;
if (this.current == 0) this.atStart = true;
else this.atStart = false;
if (this.current == count - 1) this.atEnd = true;
else this.atEnd = false;
if (this.valid) return this.at(this.current);
else return null;
}
/**
* Goes to the end of the Iterator
* @return {Object} The item at the end, or null if length is 0
*/
DuList.prototype.goToEnd = function() {
if (this.isEmpty()) return null;
return this.goTo(this.length() - 1);
}
/**
* Goes to the start of the Iterator
* @return {Object} The item at the start, or null if length is 0
*/
DuList.prototype.goToStart = function() {
if (this.isEmpty()) return null;
return this.goTo(0);
}
/**
* Increments the Iterator<br />
* Must be called at least once to validate the iterator
* @return {any} The next item, or null if there isn't
*/
DuList.prototype.next = function() {
if (this.isEmpty()) return null;
if (!this.valid) {
return this.goToStart();
}
if (this.current < this.length() - 1) {
return this.goTo(this.current + 1);
}
this.goTo(this.current + 1);
return null;
}
/**
* Decrements the Iterator
* if it's called while valid is false, goes to the end
* @return {Object} The previous item, or null if there isn't
*/
DuList.prototype.previous = function() {
if (this.isEmpty()) return null;
if (!this.valid) {
return this.goToEnd();
}
if (this.current > 0) {
return this.goTo(this.current - 1);
}
return null;
}
/**
* Executes a function on each item of the List
* @param {function} callBack - The function to execute, which takes one parameter: an item of the list
* @param {Bool} [reverse=false] - Set this to true to iterate from the end.
*/
DuList.prototype.do = function(callBack, reverse) {
if (this.isEmpty()) return;
reverse = def(reverse, false);
// Keep the current index
var current = this.current;
if (reverse)
{
for (var i = this.length() - 1; i >= 0; i--) {
this.valid = true;
this.current = i;
callBack(this.at(i));
}
}
else
{
for (var i = 0, n = this.length(); i < n; i++) {
this.valid = true;
this.current = i;
callBack(this.at(i));
}
}
// Restore the current index
this.goTo(current);
}
// ================ STATIC ==========================
/**
* Checks if this is an Array or an After Effects collection type or a {@link DuList}
* @static
* @param {Array|Collection} list - The list to check
* @return {boolean} true if this is a Cllection or an Array or a {@link DuList}
*/
DuList.isList = function(list) {
return (
list instanceof Array ||
list instanceof ItemCollection ||
list instanceof LayerCollection ||
list instanceof OMCollection ||
list instanceof RQItemCollection ||
list instanceof DuList
);
}
// ================ DEPRECATED ======================
/**
* A function to compare two elements in an array
* @callback DuList.indexOf~comparisonFunction
* @memberof DuList.indexOf
* @param {*} elementInArray The element in the array
* @param {*} value The value to compare
* @return {bool} true if the two values should be considered the same
*/
/**
* Gets the first index of a value in an Array, or -1 if not found
* @memberof DuList
* @static
* @deprecated
* @param {Array} arr - The array
* @param {*} value - The value to find. Must be compatible with the == operand, or provide a comparison function
* @param {function} [comparisonFunction] - A function which compares two values which returns true if the values are considered the same.
* @return {int} The index of the value, -1 if not found
*/
/*DuList.indexOf = function(arr, value, comparisonFunction) {
if (value == undefined) throw "Value can not be undefined";
var useFunction = typeof comparisonFunction === 'function';
for (var i = 0; i < arr.length; i++) {
if (!useFunction && arr[i] == value) return i;
if (useFunction && comparisonFunction(arr[i], value)) return i;
}
return -1;
}//*/
/**
* Checks if the array has duplicate values
* @memberof DuList
* @static
* @deprecated
* @param {Array} arr - The array
* @return {boolean} true if the array has duplicate values
*/
/*DuList.hasDuplicates = function(arr) {
for (var i = 0; i < arr.length - 1; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) return true;
}
}
return false;
}//*/
/**
* Returns all duplicated values found in the array
* @memberof DuList
* @static
* @deprecated
* @param {Array} arr - The array
* @return {Array} The duplicated values
*/
/*DuList.getDuplicates = function(arr) {
var duplicates = [];
for (var i = 0; i < arr.length - 1; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) duplicates.push(arr[j]);
}
}
DuList.removeDuplicates(duplicates);
return duplicates;
}//*/
/**
* Removes all duplicated values from the Array, and returns them
* @memberof DuList
* @static
* @deprecated
* @param {Array} arr - The array
* @param {function} [comparisonFunction] - A function which compares two values which returns true if the values are considered the same.
* @return {Array} The duplicated (and removed) values
*/
/*DuList.removeDuplicates = function(arr, comparisonFunction) {
var useFunction = typeof comparisonFunction === 'function';
var removed = [];
for (var i = 0; i < arr.length - 1; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (!useFunction && arr[i] === arr[j]) removed = removed.concat(arr.splice(j, 1));
if (useFunction && comparisonFunction(arr[i], arr[j])) removed = removed.concat(arr.splice(j, 1));
}
}
//sometimes the two last items may still be the same (for a reason I don't understand)
if (!useFunction && arr[arr.length - 2] === arr[arr.length - 1]) removed.push(arr.pop());
if (useFunction && comparisonFunction(arr.length - 2, arr.length - 1)) removed.push(arr.pop());
return removed;
}//*/
/**
* Compares two arrays.<br />
* The items in the arrays must be compatible with the == operand
* @memberof DuList
* @static
* @deprecated
* @param {Array|Collection} array1 - The array
* @param {Array|Collection} array2 - The array
* @param {int} [floatPrecision=-1] - The precision for (float) number comparison, number of decimals. Set to -1 to not use.
* @return {boolean} true if the two arrays contain the same values
*/
/*DuList.compare = function(array1, array2, floatPrecision) {
if (!DuList.isList(array1) && !DuList.isList(array2)) {
if (typeof array1 == 'number' && typeof array2 == 'number') {
return DuMath.equals(array1, array2, floatPrecision)
} else return array1 == array2;
}
if (DuList.isList(array1) && !DuList.isList(array2)) return false;
if (!DuList.isList(array1) && DuList.isList(array2)) return false;
if (array1.length != array2.length) return false;
var it = new DuList(array1);
var ok = true;
it.do(function(item1) {
var item2 = array2[it.current];
if (!DuList.compare(item1, item2, floatPrecision)) {
ok = false;
return;
}
});
return ok;
}
//*/
// ==================== |-------| ====================
// ==================== | color | ====================
// ==================== |-------| ====================
/**
* Constructs a new color object
* @class
* @name DuColor
* @classdesc A simple class to manage colors and convert them.
* @param {float[]} [floatRGBA=[0,0,0,1]] An [R,G,B,A] float Array.<br />
* Negative values are clamped to 0.<br />
* Alpha > 1 is clamped to 1.<br />
* Colors are stored in 32 bit float to keep the maximum precision.
* @property {float} red The red value
* @property {float} green The green value
* @property {float} blue The blue value
* @property {float} alpha The alpha value
* @category DuESF
*/
function DuColor( floatRGBA )
{
floatRGBA = def(floatRGBA, [0,0,0,0]);
this.red = floatRGBA[0];
if (this.red < 0) this.red = 0;
if (!jstype( (this.red ) == 'number') ) this.red = 0;
if ( isNaN(this.red) ) this.red = 0;
this.green = floatRGBA[1];
if (this.green < 0) this.green = 0;
if (!jstype( (this.green ) == 'number') ) this.green = 0;
if ( isNaN(this.green) ) this.green = 0;
this.blue = floatRGBA[2];
if (this.blue < 0) this.blue = 0;
if (!jstype( (this.blue ) == 'number') ) this.blue = 0;
if ( isNaN(this.blue) ) this.blue = 0;
if (floatRGBA.length > 3) this.alpha = floatRGBA[3];
else this.alpha = 1.0;
if (this.alpha < 0) this.alpha = 0;
if (this.alpha > 1) this.alpha = 1;
if (!jstype( (this.alpha ) == 'number') ) this.alpha = 0;
if ( isNaN(this.alpha) ) this.alpha = 0;
}
/**
* Returns the color as a float Array with alpha
* @param {bool} [clamped=true] Set to false to keep the values > 1.0
* @return {float} an [R,G,B,A] Array.
*/
DuColor.prototype.floatRGBA = function(clamped)
{
clamped = def( clamped, true );
var col = this.floatRGB();
var alpha = this.alpha;
if (clamped) alpha = DuMath.clamp( alpha );
col.push(alpha);
return col;
}
/**
* Returns the color as a float Array without alpha
* @param {bool} [clamped=true] Set to false to keep the values > 1.0
* @return {float} an [R,G,B] Array.
*/
DuColor.prototype.floatRGB = function(clamped)
{
clamped = def( clamped, true );
var col = [
this.red,
this.green,
this.blue
]
if (clamped) return DuMath.clamp( col );
return col;
}
/**
* Returns the color as an 8-bit int Array with alpha
* @return {int} an [R,G,B,A] Array.
*/
DuColor.prototype.intRGBA = function()
{
var col = this.intRGB();
col.push( parseInt( this.alpha * 255 ) );
return col;
}
/**
* Returns the color as an 8-bit int Array without alpha
* @return {int} an [R,G,B] Array.
*/
DuColor.prototype.intRGB = function()
{
return [
parseInt( this.red * 255 ),
parseInt( this.green * 255 ),
parseInt( this.blue * 255 )
]
}
/**
* true if [R,G,B,A] in the range 0.0 ... 1.0
* @return {boolean} true if the color is valid
*/
DuColor.prototype.isValid = function()
{
if (this.red > 1) return false;
if (this.green > 1) return false;
if (this.blue > 1) return false;
return true;
}
/**
* Returns the hexcode for this color
* @return {string} The hex code, without the leading '#'.
*/
DuColor.prototype.hex = function()
{
var red = this.red*255;
var green = this.green*255;
var blue = this.blue*255;
var hexR = red.toString(16);
var hexG = green.toString(16);
var hexB = blue.toString(16);
if (hexR.length == 1) hexR = "0" + hexR;
if (hexG.length == 1) hexG = "0" + hexG;
if (hexB.length == 1) hexB = "0" + hexB;
return hexR+hexG+hexB;
}
/**
* Returns the hexcode for this color, including the alpha (at the end)
* @return {string} The hex code, without the leading '#'.
*/
DuColor.prototype.hexA = function()
{
var alpha = this.alpha*255;
var hexA = alpha.toString(16);
if (hexA.length == 1) hexA = "0" + hexA;
return this.hex() + hexA;
}
/**
* Returns the HSL values
* @return {float[]} the HSL
*/
DuColor.prototype.floatHSL = function()
{
var r = this.red;
var g = this.blue;
var b = this.green;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if(max == min)
{
h = s = 0; // achromatic
}
else
{
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch(max)
{
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, l];
}
/**
* Returns the HSLA values
* @return {float[]} the HSL
*/
DuColor.prototype.floatHSLA = function()
{
var hsla = this.floatHSL();
hsla.push( this.alpha );
return hsla;
}
/**
* Creates a new color darker than the current one
* @param {int} [ratio=200] A percentage: 200 means twice darker, 50 twice lighter
* @returns {DuColor} The new color
*/
DuColor.prototype.darker = function( ratio )
{
ratio = def(ratio, 200);
ratio = ratio / 100;
return new DuColor([
this.red/ratio,
this.green/ratio,
this.blue/ratio,
this.alpha,
]);
}
/**
* Creates a new color lighter than the current one
* @param {int} [ratio=200] A percentage: 200 means twice lighter, 50 twice darker
* @returns {DuColor} The new color
*/
DuColor.prototype.lighter = function( ratio )
{
ratio = def(ratio, 200);
ratio = ratio / 100;
return new DuColor([
this.red*ratio,
this.green*ratio,
this.blue*ratio,
this.alpha,
]);
}
/**
* Compares two colors
* @param {Boolean} [ignoreAlpha=false] Set to true to consider colors to be equal if they differ only by their alpha.
* @param {int} [precision=4] The precision to use (the number of decimals to compare).
* @return {Boolean} true if the colors are the same.
*/
DuColor.prototype.equals = function( other, ignoreAlpha, precision )
{
if (!DuMath.equals( this.red, other.red, precision )) return false;
if (!DuMath.equals( this.green, other.green, precision )) return false;
if (!DuMath.equals( this.blue, other.blue, precision )) return false;
if (!DuMath.equals( this.alpha, other.alpha, precision )) return false;
return true;
}
/**
* Creates a JSON string representation of the color; Actually an RGBA float Array.
* @return {string} The JSON
*/
DuColor.prototype.toJSON = function ()
{
return '[' + this.red + ',' + this.green + ',' + this.blue + ',' + this.alpha + ']';
}
/**
* Creates a color adjusted according to the brightness setting of the application.<br />
* For now, works only in After Effects
* @return The new color
*/
DuColor.prototype.adjusted = function( )
{
if (DuESF.host == DuESF.HostApplication.AFTER_EFFECTS)
{
//gets the main application color
var q = app.themeColor( 78 )[ 0 ];
//the darkest one is 0.15000000596046
q = q - 0.15000000596046;
var newColor = this.floatRGBA() + [ q, q, q, 0 ];
return new DuColor( newColor );
}
return this;
}
// ============== STATIC ===============
/**
* Enum for predefined colors. float [R,G,B,A]
* @readonly
* @static
* @enum {DuColor[]}
*/
DuColor.Color =
{
TRANSPARENT: new DuColor([0,0,0,0]),
BLACK: new DuColor([0,0,0,1]),
WHITE: new DuColor([1,1,1,1]),
OBSIDIAN: new DuColor([.082,.082,.082,1]),
ABYSS_GREY: new DuColor([0.109,0.109,0.109,1]),
VERY_DARK_GREY:new DuColor([0.199,0.199,0.199,1]),
DARK_GREY: new DuColor([.262,.262,.262,1]),
LIGHT_GREY: new DuColor([.65,.65,.65,1]),
VERY_LIGHT_GREY: new DuColor([.85,.85,.85,1]),
RAINBOX_RED: new DuColor([.925,.094,.094,1]),
ORANGE: new DuColor([.925,.471,.094,1]),
YELLOW_ORANGE: new DuColor([.925,.655,.094,1]),
YELLOW: new DuColor([.925,.839,.094,1]),
GREEN: new DuColor([.094,.925,.094,1]),
LIGHT_GREEN: new DuColor([.541,.925,.569,1]),
LIGHT_BLUE: new DuColor([.471,.847,.925,1]),
LIGHT_PURPLE: new DuColor([.471,.471,.925,1]),
AE_DARK_GREY: new DuColor([0.199,0.199,0.199,1]),
AFTER_EFFECTS_BLUE: new DuColor([.1764,.5490,.9215,1]),
RX_PURPLE: new DuColor([.7411,.1843,.7686,1]),
APP_BACKGROUND_COLOR: new DuColor([0.199,0.199,0.199,1]),
APP_HIGHLIGHT_COLOR: new DuColor([.7411,.1843,.7686,1]),
APP_TEXT_COLOR: new DuColor( [ 0.6941, 0.6941, 0.6941, 1]),
AE_ORANGE: new DuColor( [ .7804, .5725, 0, 1])
};
// Get the app colors
DuColor.init = function()
{
var highlightSelection = DuESF.scriptSettings.get("common/highlightSelection", -1);
var highlightColor = new DuColor( DuESF.scriptSettings.get("common/highlightColor", DuColor.Color.APP_HIGHLIGHT_COLOR) );
DuDebug.log("DuColor: Highlight color selection: " + highlightSelection);
// Highlight color
if (highlightSelection > 0)
{
if (highlightSelection == 1) DuColor.Color.APP_HIGHLIGHT_COLOR = DuColor.Color.RX_PURPLE;
else if (highlightSelection == 2) DuColor.Color.APP_HIGHLIGHT_COLOR = DuColor.Color.RAINBOX_RED;
else if (highlightSelection == 3) DuColor.Color.APP_HIGHLIGHT_COLOR = DuColor.Color.AE_ORANGE;
else if (highlightSelection > 3) DuColor.Color.APP_HIGHLIGHT_COLOR = highlightColor;
return;
DuDebug.log("DuColor: Highlight color is: " + DuColor.Color.APP_HIGHLIGHT_COLOR.hex());
}
// ScriptUIGraphics object does not contain foreground & background colors in After Effects...
if (DuESF.host == DuESF.HostApplication.AFTER_EFFECTS)
{
DuColor.Color.APP_TEXT_COLOR = new DuColor( [ 0.6941, 0.6941, 0.6941, 1]);
var bgColor = app.themeColor(78) - [0.0127, 0.0127, 0.0127];
bgColor.push(1);
DuColor.Color.APP_BACKGROUND_COLOR = new DuColor( bgColor );
DuColor.Color.APP_HIGHLIGHT_COLOR = DuColor.Color.AFTER_EFFECTS_BLUE;
DuColor.Color.DARK_GREY = new DuColor( [.2, .2, .2, 1] );
DuColor.Color.OBSIDIAN = new DuColor( [.0862, .0862, .0862, 1] );
}
else if (DuESF.host == DuESF.HostApplication.PHOTOSHOP)
{
DuColor.Color.APP_TEXT_COLOR = new DuColor( [ 0.6941, 0.6941, 0.6941, 1]);
DuColor.Color.APP_BACKGROUND_COLOR = DuColor.Color.VERY_DARK_GREY;
DuColor.Color.APP_HIGHLIGHT_COLOR = DuColor.Color.DARK_GREY;
DuColor.Color.DARK_GREY = new DuColor( [.2, .2, .2, 1] );
DuColor.Color.OBSIDIAN = new DuColor( [.0862, .0862, .0862, 1] );
}
else
{
// create a label to (try to) get the color
var dummyWin = new Window( 'palette' );
var dummyLabel = dummyWin.add('statictext', undefined,'text');
var textPen = dummyLabel.graphics.foregroundColor;
var bgBrush = dummyWin.graphics.backgroundColor;
if (textPen) DuColor.Color.APP_TEXT_COLOR = new DuColor( textPen.color );
else DuColor.Color.APP_TEXT_COLOR = DuColor.Color.LIGHT_GREY;
if (bgBrush) DuColor.Color.APP_BACKGROUND_COLOR = new DuColor( bgBrush.color );
else DuColor.Color.APP_BACKGROUND_COLOR = DuColor.Color.DARK_GREY;
DuColor.Color.APP_HIGHLIGHT_COLOR = DuColor.Color.RX_PURPLE;
}
}
DuESF.initMethods.push(DuColor.init);
/**
* Creates a new DuColor from a hex code/array
* @static
* @memberof DuColor
* @param {string|int[]} hexColor - The hexadecimal color
* @return {DuColor} The color
*/
DuColor.fromHex = function( hexColor )
{
var isString = jstype( hexColor ) == 'string';
if (isString)
{
if (hexColor.indexOf("#") == 0) hexColor = hexColor.replace("#","");
var red = parseInt(hexColor.substr(0,2),16)/255.0;
var green = parseInt(hexColor.substr(2,2),16)/255.0;
var blue = parseInt(hexColor.substr(4,2),16)/255.0;
return new DuColor( [red,green,blue,1] );
}
else
{
var r = hexColor >> 16;
var g = (hexColor & 0x00ff00) >> 8;
var b = hexColor & 0xff;
return new DuColor( [r/255,g/255,b/255,1] );
}
}
/**
* Creates a new color from HSL(A) values
* @static
* @param {float[]} hsl the HSL(A) values
* @return {DuColor} the color.
*/
DuColor.fromHSL = function( hsl )
{
var r, g, b, a;
var h = hsl[0];
var s = hsl[1];
var l = hsl[2];
if(s == 0)
{
r = g = b = l; // achromatic
}
else
{
function hue2rgb(p, q, t)
{
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
if (hsl.length > 3) a = hsl[3];
else a = 1;
return new DuColor( [r, g, b, a] );
}
/**
* Creates a new color from an 8-bit int RGB(A) array
* @static
* @param {int[]} source The 8-bit int RGB(A) array
* @return {DuColor}
*/
DuColor.fromInt = function( intRGB )
{
var floatColor = [];
for (var i = 0, numChannels = color.length; i < numChannels; i++)
{
floatColor.push( color[ i ] / 255.0 );
}
return new DuColor( floatColor );
}
/**
* Creates a color from a JSON string; Actually a JSON RGBA float Array.
* @static
* @param {string} json The JSON string
* @return {DuColor} The color
*/
DuColor.fromJSON = function (json)
{
return new DuColor( JSON.parse(json) );
}
/**
* Generates a random color, with the alpha == 1.0
* @static
* @return {DuColor} The color
*/
DuColor.random = function()
{
var color = [0,0,0,1];
for (var i = 0 ; i < 3 ; i++)
{
color[i] = DuMath.random();
}
return new DuColor(color);
}
// ========= DEPRECATED ==============
/**
* Checks if this color is valid ([R,G,B,A] in the range 0.0 ... 1.0)
* @static
* @deprecated
* @param {float[]} color - The floatRGBA color to validate
* @param {boolean} [ignoreAlpha=false] - Will return true even if the array does not have any alpha value
* @return {boolean} true if the color is valid
*/
DuColor.isValid = function (color, ignoreAlpha)
{
ignoreAlpha = def( ignoreAlpha, false, false );
if ( !jstype(color) == 'array' ) return false;
var numChannels = color.length;
if ( numChannels != 4 && !ignoreAlpha ) return false;
if ( numChannels != 4 && numChannels != 3 ) return false;
for (var i = 0, numChannels; i < numChannels; i++)
{
var c = color[i];
if (isNaN(c)) return false;
if (!jstype(color) == 'number') return false;
if (c < 0) color[i] = 0;
if (c > 1) color[i] = 1;
}
return true;
}
/**
* Generates a random color
* @static
* @deprecated
* @memberof DuColor
* @return {float[]} The color as an [R,G,B,A] Array with float values between 0.0 and 1.0
*/
DuColor.randomFloatRGBA = function ()
{
var color = [0,0,0,1];
for (var i = 0 ; i < 3 ; i++)
{
color[i] = DuMath.random();
}
return color;
}
/**
* Converts an hexadecimal color to a floatRGBA Array
* @static
* @deprecated
* @memberof DuColor
* @param {string|int[]} hexColor - The hexadecimal color
* @param {boolean} [isString=true] - Whether hexColor is a string or an Array of int of base 16
* @return {float[]} The color as an [R,G,B,A] Array with float values between 0.0 and 1.0
*/
DuColor.hexToRGB = function (hexColor,isString)
{
if (isString == undefined) isString = true;
if (isString)
{
if (hexColor.indexOf("#") == 0) hexColor = hexColor.replace("#","");
var red = parseInt(hexColor.substr(0,2),16)/255.0;
var green = parseInt(hexColor.substr(2,2),16)/255.0;
var blue = parseInt(hexColor.substr(4,2),16)/255.0;
return [red,green,blue,1];
}
else
{
var r = hexColor >> 16;
var g = (hexColor & 0x00ff00) >> 8;
var b = hexColor & 0xff;
return [r/255,g/255,b/255,1];
}
}
/**
* Converts an RGB color to a hex string
* @memberof DuColor
* @deprecated
* @static
* @param {float[]} rgbColor - The rgb color
* @return {string} The color as an hex string
*/
DuColor.rgbToHex = function (rgbColor)
{
var red = rgbColor[0]*255;
var green = rgbColor[1]*255;
var blue = rgbColor[2]*255;
var hexR = red.toString(16)
var hexG = green.toString(16)
var hexB = blue.toString(16);
if (hexR.length == 1) hexR = "0" + hexR;
if (hexG.length == 1) hexG = "0" + hexG;
if (hexB.length == 1) hexB = "0" + hexB;
var hex = hexR+hexG+hexB;
return hex;
}
/**
* Converts an HSL color to RGB
* @deprecated
* @static
* @param {float[]} color - The RGBA color array
* @return {float[]} the HSLA color array
*/
DuColor.hslToRgb = function (color)
{
var r, g, b;
var h = color[0];
var s = color[1];
var l = color[2];
if(s == 0)
{
r = g = b = l; // achromatic
}
else
{
function hue2rgb(p, q, t)
{
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [r, g, b, color[3]];
}
/**
* Converts an RGB color to HSL
* @deprecated
* @static
* @param {float[]} color - The HSLA color array
* @return {float[]} the RGBA color array
*/
DuColor.rgbToHsl = function (color)
{
var r = color[0];
var g = color[1];
var b = color[2];
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if(max == min)
{
h = s = 0; // achromatic
}
else
{
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch(max)
{
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, l, color[3]];
}
/**
* Converts an 8bpc color array to a 32bpc float color array
* @deprecated
* @static
* @param {int[]} color - The RGB(A) color array in 8bpc (0-255 range)
* @return {float[]} the RGBA color array
*/
DuColor.eightBpcToFloat = function (color)
{
var floatColor = [];
for (var i = 0, numChannels = color.length; i < numChannels; i++)
{
floatColor.push( color[ i ] / 255 );
}
return floatColor;
}
// ==================== |----------| ====================
// ==================== | settings | ====================
// ==================== |----------| ====================
/**
* Constructs a new DuSettings instance.
* @class DuSettings
* @classdesc A class to manage settings.<br />
* Warning: DuESF stores the settings in json files and needs the right to write files, but it does not check if it has write access on the files, you should check that first using methods specific to the host application.
* @param {string} [namespace=DuESF.scriptName] - A namespace to group your settings.
* @param {File|string} [file=Folder.myDocuments/namespace/namespace_settings.json] - The file to store the settings
* @property {string} namespace - A namespace to group your settings.
* @property {File} file - The file to store the settings
* @property {Object} data - The settings as a js object
* @category DuESF
*/
function DuSettings( namespace, file, dontSavePath )
{
dontSavePath = def(dontSavePath, false);
namespace = def(namespace, DuESF.scriptName);
if ( DuDebug.checkVar( namespace, 'namespace', 'string', DuSettings ) !== true ) throw "Sorry, an error has occured in `new DuSettings()`.";
if (namespace == "") throw "new Dusettings( namespace, file ): error\n\nnamespace can not be an empty string";
// Get path from duesf settings
if ( !isdef( file ) )
{
var filePath = DuESF.settings.get(namespace + "/scriptSettingsFile");
if ( filePath ) file = new File( filePath );
}
// Generate default path
if (!isdef( file ))
{
if (DuFolder.canWrite(Folder.myDocuments))
file = new File( Folder.myDocuments.absoluteURI + "/" + namespace + "/" + namespace + "_settings.json" );
else
file = new File( Folder.temp.absoluteURI + "/" + namespace + "/" + namespace + "_settings.json" );
}
this.namespace = namespace;
if (file instanceof File) this.file = file;
else this.file = new File(file);
this.file.encoding = 'UTF-8';
this.folder = this.file.parent;
if (!dontSavePath)
{
DuESF.settings.set(namespace + "/scriptSettingsFile", this.file.absoluteURI);
DuESF.settings.save();
}
this.data = {};
this.load();
}
/**
* loads data from the settings file
* @memberof DuSettings
*/
DuSettings.prototype.load = function()
{
//load file
if (this.file.exists)
{
this.data = DuFile.parseJSON( this.file );
if (!this.data) this.data = {};
}
}
/**
* Saves data to the file.<br />
* Warning: DuESF does not check if it has write access on the files, you should check that first using methods specific to the host application.
* @memberof DuSettings
* @return {boolean} true if the file has been correctly written
*/
DuSettings.prototype.save = function()
{
try
{
if ( !this.folder.exists ) this.folder.create();
}
catch (e)
{
DuDebug.throwError("Can't create folder", 'DuSettings.prototype.save', e);
return false;
}
return DuFile.saveJSON(this.data, this.file);
}
/**
* Sets the file to be used to save the settings
* @param {File|string} file The file or path to the file
*/
DuSettings.prototype.setFile = function(file, dontSavePath)
{
if (file instanceof File) this.file = file;
else this.file = new File(file);
if (file.exists) this.load();
else this.save();
if (!dontSavePath) {
DuESF.settings.set(namespace + "/scriptSettingsFile", this.file.absoluteURI);
DuESF.settings.save();
}
}
/**
* Reset the settings to their default values (removes the settings file!)
*/
DuSettings.prototype.reset = function()
{
this.file.remove();
this.data = {};
}
/**
* Gets a value from the settings. The key can be a path separated by /
* @property {string} key The setting to get
* @property {*} [defaultValue=null] The default value if the key is not set in the settings
* @return {*} The value
*/
DuSettings.prototype.get = function( key, defaultValue )
{
defaultValue = def(defaultValue, null);
var keyArray = key.split("/")
var numKeys = keyArray.length;
var data = this.data;
for( var i = 0; i < numKeys -1; i++) {
data = def( data[keyArray[i]], {});
}
return def( data[keyArray[numKeys-1]], defaultValue);
}
/**
* Sets a value to the settings. The key can be a path separated by /
* @property {string} key The setting to set
* @property {*} value The value to set
*/
DuSettings.prototype.set = function( key, value )
{
var keyArray = key.split("/")
var numKeys = keyArray.length;
var data = this.data;
for( var i = 0; i < numKeys -1; i++) {
data[keyArray[i]] = def( data[keyArray[i]], {});
data = data[keyArray[i]]
}
data[keyArray[numKeys-1]] = value;
}
// Low-level undocumented function. Called by DuESF.init()
DuSettings.init = function() {
DuDebug.log("DuSettings: loading settings...");
DuESF.settings = new DuSettings( 'DuESF', DuFolder.duesfData.absoluteURI + '/duesf_settings.json', true );
/**
* The settings to store the script specific settings.
* @type {DuSettings}
* @memberof DuESF
* @static
*/
DuESF.scriptSettings = new DuSettings( DuESF.scriptName );
// Load defaults
var debug = DuESF.scriptSettings.get("common/debug", false);
// Debug mode
DuESF.debug = debug;
// Create a debug log
if (DuESF.debug) DuDebug.debugLog = new DuDebugLog();
DuDebug.log("DuSettings: Settings ready!");
}
// ==================== |------------| ====================
// ==================== | translator | ====================
// ==================== |------------| ====================
/**
* gettext.jsxinc.<br />
* by {@link http://duduf.com Duduf} and Guillaume Potier.<br />
* @example
* // Encapsulate everything to avoid global variables!
* (function()
* {
* #include "../lib/gettext.js"
*
* i18n = i18n();
*
* i18n.setMessages('messages', 'en', {
* 'Test failed!': 'Test successfull!',
* }, 'nplurals=2; plural=n>1;');
*
* i18n.setLocale('en');
*
* alert(i18n._('Test failed!'));
* })();
* @namespace
* @author Nicolas Dufresne and contributors
* @copyright 2022 Nicolas Dufresne, RxLaboratory
* @version 1.0.0
* @license MIT
* @category gettext.jsxinc
*/
var translationEngine = function (options) {
options = options || {};
this && (this.__version = '1.1.1');
// default values that could be overriden in i18n() construct
var defaults = {
domain: 'messages',
locale: 'en',
plural_func: function (n) {
return {
nplurals: 2,
plural: (n != 1) ? 1 : 0
};
},
ctxt_delimiter: String.fromCharCode(4), // \u0004
folder: DuESF.scriptSettings.file.parent.absoluteURI + '/',
prefix: DuESF.scriptName + "_",
suffix: ".json"
};
// handy mixins taken from underscode.js
var _ = {
isObject: function (obj) {
var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
},
isArray: function (obj) {
return toString.call(obj) === '[object Array]';
}
};
var
_plural_funcs = {},
_locale = options.locale || defaults.locale,
_domain = options.domain || defaults.domain,
_dictionary = {},
_plural_forms = {},
_ctxt_delimiter = options.ctxt_delimiter || defaults.ctxt_delimiter,
_folder = options.folder || defaults.folder,
_prefix = options.prefix || defaults.prefix,
_suffix = options.suffix || defaults.suffix;
if (options.messages) {
_dictionary[_domain] = {};
_dictionary[_domain][_locale] = options.messages;
}
if (options.plural_forms) {
_plural_forms[_locale] = options.plural_forms;
}
// language codes / language names associative array
// Generated from https://github.com/unicode-org/cldr
var _languageNames = {"pd": "Ch'ti / Picard", "af": "Afrikaans", "agq": "Aghem", "ak": "Akan", "am": "\u12a0\u121b\u122d\u129b", "ar": "\u0627\u0644\u0639\u0631\u0628\u064a\u0629", "as": "\u0985\u09b8\u09ae\u09c0\u09af\u09bc\u09be", "asa": "Kipare", "ast": "Asturianu", "az": "Az\u0259rbaycan", "bas": "\u0181\u00e0s\u00e0a", "be": "\u0411\u0435\u043b\u0430\u0440\u0443\u0441\u043a\u0430\u044f", "bem": "Ichibemba", "bez": "Hibena", "bg": "\u0411\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438", "bm": "Bamanakan", "bn": "\u09ac\u09be\u0982\u09b2\u09be", "bo": "\u0f56\u0f7c\u0f51\u0f0b\u0f66\u0f90\u0f51\u0f0b", "br": "Brezhoneg", "brx": "\u092c\u0930\u2019", "bs": "Bosanski", "ca": "Catal\u00e0", "ccp": "\ud804\udd0c\ud804\udd0b\ud804\udd34\ud804\udd1f\ud804\udd33\ud804\udd26", "ce": "\u041d\u043e\u0445\u0447\u0438\u0439\u043d", "ceb": "Binisaya", "cgg": "Rukiga", "chr": "\u13e3\uab83\uab79", "ckb": "\u06a9\u0648\u0631\u062f\u06cc\u06cc \u0646\u0627\u0648\u06d5\u0646\u062f\u06cc", "cs": "\u010ce\u0161tina", "cy": "Cymraeg", "da": "Dansk", "dav": "Kitaita", "de": "Deutsch", "dje": "Zarmaciine", "doi": "\u0921\u094b\u0917\u0930\u0940", "dsb": "Dolnoserb\u0161\u0107ina", "dua": "Du\u00e1l\u00e1", "dyo": "Joola", "dz": "\u0f62\u0fab\u0f7c\u0f44\u0f0b\u0f41", "ebu": "K\u0129embu", "ee": "E\u028begbe", "el": "\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac", "en": "English", "eo": "Esperanto", "es": "Espa\u00f1ol", "et": "Eesti", "eu": "Euskara", "ewo": "Ewondo", "fa": "\u0641\u0627\u0631\u0633\u06cc", "ff": "Pulaar", "fi": "Suomi", "fil": "Filipino", "fo": "F\u00f8royskt", "fr": "Fran\u00e7ais", "fur": "Furlan", "fy": "Frysk", "ga": "Gaeilge", "gd": "G\u00e0idhlig", "gl": "Galego", "gsw": "Schwiizert\u00fc\u00fctsch", "gu": "\u0a97\u0ac1\u0a9c\u0ab0\u0abe\u0aa4\u0ac0", "guz": "Ekegusii", "gv": "Gaelg", "ha": "Hausa", "haw": "\u02bb\u014dlelo hawai\u02bbi", "he": "\u05e2\u05d1\u05e8\u05d9\u05ea", "hi": "\u0939\u093f\u0928\u094d\u0926\u0940", "hr": "Hrvatski", "hsb": "Hornjoserb\u0161\u0107ina", "hu": "Magyar", "hy": "\u0540\u0561\u0575\u0565\u0580\u0565\u0576", "ia": "Interlingua", "id": "Indonesia", "ig": "Igbo", "ii": "\ua188\ua320\ua259", "is": "\u00cdslenska", "it": "Italiano", "ja": "\u65e5\u672c\u8a9e", "jgo": "Nda\ua78ca", "jmc": "Kimachame", "jv": "Jawa", "ka": "\u10e5\u10d0\u10e0\u10d7\u10e3\u10da\u10d8", "kab": "Taqbaylit", "kam": "Kikamba", "kde": "Chimakonde", "kea": "Kabuverdianu", "kgp": "Kanhg\u00e1g", "khq": "Koyra ciini", "ki": "Gikuyu", "kk": "\u049a\u0430\u0437\u0430\u049b \u0442\u0456\u043b\u0456", "kkj": "Kak\u0254", "kl": "Kalaallisut", "kln": "Kalenjin", "km": "\u1781\u17d2\u1798\u17c2\u179a", "kn": "\u0c95\u0ca8\u0ccd\u0ca8\u0ca1", "ko": "\ud55c\uad6d\uc5b4", "kok": "\u0915\u094b\u0902\u0915\u0923\u0940", "ks": "\u06a9\u0672\u0634\u064f\u0631", "ksb": "Kishambaa", "ksf": "Rikpa", "ksh": "K\u00f6lsch", "ku": "Kurd\u00ee", "kw": "Kernewek", "ky": "\u041a\u044b\u0440\u0433\u044b\u0437\u0447\u0430", "lag": "K\u0268laangi", "lb": "L\u00ebtzebuergesch", "lg": "Luganda", "lkt": "Lak\u021f\u00f3l\u02bciyapi", "ln": "Ling\u00e1la", "lo": "\u0ea5\u0eb2\u0ea7", "lrc": "\u0644\u06ca\u0631\u06cc \u0634\u0648\u0645\u0627\u0644\u06cc", "lt": "Lietuvi\u0173", "lu": "Tshiluba", "luo": "Dholuo", "luy": "Luluhia", "lv": "Latvie\u0161u", "mai": "\u092e\u0948\u0925\u093f\u0932\u0940", "mas": "Maa", "mer": "K\u0129m\u0129r\u0169", "mfe": "Kreol morisien", "mg": "Malagasy", "mgh": "Makua", "mgo": "Meta\u02bc", "mi": "Te reo m\u0101ori", "mk": "\u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438", "ml": "\u0d2e\u0d32\u0d2f\u0d3e\u0d33\u0d02", "mn": "\u041c\u043e\u043d\u0433\u043e\u043b", "mni": "\u09ae\u09c8\u09a4\u09c8\u09b2\u09cb\u09a8\u09cd", "mr": "\u092e\u0930\u093e\u0920\u0940", "ms": "Melayu", "mt": "Malti", "mua": "Munda\u014b", "my": "\u1019\u103c\u1014\u103a\u1019\u102c", "mzn": "\u0645\u0627\u0632\u0631\u0648\u0646\u06cc", "naq": "Khoekhoegowab", "nd": "Isindebele", "nds": "Neddersass\u2019sch", "ne": "\u0928\u0947\u092a\u093e\u0932\u0940", "nl": "Nederlands", "nmg": "Kwasio", "nnh": "Shw\u00f3\u014b\u00f2 ngiemb\u0254\u0254n", "no": "Norsk", "nus": "Thok nath", "nyn": "Runyankore", "om": "Oromoo", "or": "\u0b13\u0b21\u0b3c\u0b3f\u0b06", "os": "\u0418\u0440\u043e\u043d", "pa": "\u0a2a\u0a70\u0a1c\u0a3e\u0a2c\u0a40", "pcm": "Naij\u00edri\u00e1 p\u00edjin", "pl": "Polski", "ps": "\u067e\u069a\u062a\u0648", "pt": "Portugu\u00eas", "qu": "Runasimi", "rm": "Rumantsch", "rn": "Ikirundi", "ro": "Rom\u00e2n\u0103", "rof": "Kihorombo", "ru": "\u0420\u0443\u0441\u0441\u043a\u0438\u0439", "rw": "Kinyarwanda", "rwk": "Kiruwa", "sa": "\u0938\u0902\u0938\u094d\u0915\u0943\u0924 \u092d\u093e\u0937\u093e", "sah": "\u0421\u0430\u0445\u0430 \u0442\u044b\u043b\u0430", "saq": "Kisampur", "sat": "\u1c65\u1c5f\u1c71\u1c5b\u1c5f\u1c72\u1c64", "sbp": "Ishisangu", "sc": "Sardu", "sd": "\u0633\u0646\u068c\u064a", "se": "Davvis\u00e1megiella", "seh": "Sena", "ses": "Koyraboro senni", "sg": "S\u00e4ng\u00f6", "shi": "\u2d5c\u2d30\u2d5b\u2d4d\u2d43\u2d49\u2d5c", "si": "\u0dc3\u0dd2\u0d82\u0dc4\u0dbd", "sk": "Sloven\u010dina", "sl": "Sloven\u0161\u010dina", "smn": "Anar\u00e2\u0161kiel\u00e2", "sn": "Chishona", "so": "Soomaali", "sq": "Shqip", "sr": "\u0421\u0440\u043f\u0441\u043a\u0438", "su": "Basa sunda", "sv": "Svenska", "sw": "Kiswahili", "ta": "\u0ba4\u0bae\u0bbf\u0bb4\u0bcd", "te": "\u0c24\u0c46\u0c32\u0c41\u0c17\u0c41", "teo": "Kiteso", "tg": "\u0422\u043e\u04b7\u0438\u043a\u04e3", "th": "\u0e44\u0e17\u0e22", "ti": "\u1275\u130d\u122d\u129b", "tk": "T\u00fcrkmen dili", "to": "Lea fakatonga", "tr": "T\u00fcrk\u00e7e", "tt": "\u0422\u0430\u0442\u0430\u0440", "twq": "Tasawaq senni", "tzm": "Tamazi\u0263t n la\u1e6dla\u1e63", "ug": "\u0626\u06c7\u064a\u063a\u06c7\u0631\u0686\u06d5", "uk": "\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430", "ur": "\u0627\u0631\u062f\u0648", "uz": "O\u2018zbek", "vai": "\ua559\ua524", "vi": "Ti\u1ebfng vi\u1ec7t", "vun": "Kyivunjo", "wae": "Walser", "wo": "Wolof", "xh": "Isixhosa", "xog": "Olusoga", "yav": "Nuasue", "yi": "\u05d9\u05d9\u05b4\u05d3\u05d9\u05e9", "yo": "\u00c8d\u00e8 yor\u00f9b\u00e1", "yrl": "Nhe\u1ebdgatu", "yue": "\u4e2d\u6587 (\u7cb5\u8a9e)", "zgh": "\u2d5c\u2d30\u2d4e\u2d30\u2d63\u2d49\u2d56\u2d5c", "zh": "\u666e\u901a\u8bdd", "zu": "Isizulu"};
var _languageCodes= {"Ch'ti / Picard": "pd", "Afrikaans": "af", "Aghem": "agq", "Akan": "ak", "\u12a0\u121b\u122d\u129b": "am", "\u0627\u0644\u0639\u0631\u0628\u064a\u0629": "ar", "\u0985\u09b8\u09ae\u09c0\u09af\u09bc\u09be": "as", "Kipare": "asa", "Asturianu": "ast", "Az\u0259rbaycan": "az", "\u0181\u00e0s\u00e0a": "bas", "\u0411\u0435\u043b\u0430\u0440\u0443\u0441\u043a\u0430\u044f": "be", "Ichibemba": "bem", "Hibena": "bez", "\u0411\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438": "bg", "Bamanakan": "bm", "\u09ac\u09be\u0982\u09b2\u09be": "bn", "\u0f56\u0f7c\u0f51\u0f0b\u0f66\u0f90\u0f51\u0f0b": "bo", "Brezhoneg": "br", "\u092c\u0930\u2019": "brx", "Bosanski": "bs", "Catal\u00e0": "ca", "\ud804\udd0c\ud804\udd0b\ud804\udd34\ud804\udd1f\ud804\udd33\ud804\udd26": "ccp", "\u041d\u043e\u0445\u0447\u0438\u0439\u043d": "ce", "Binisaya": "ceb", "Rukiga": "cgg", "\u13e3\uab83\uab79": "chr", "\u06a9\u0648\u0631\u062f\u06cc\u06cc \u0646\u0627\u0648\u06d5\u0646\u062f\u06cc": "ckb", "\u010ce\u0161tina": "cs", "Cymraeg": "cy", "Dansk": "da", "Kitaita": "dav", "Deutsch": "de", "Zarmaciine": "dje", "\u0921\u094b\u0917\u0930\u0940": "doi", "Dolnoserb\u0161\u0107ina": "dsb", "Du\u00e1l\u00e1": "dua", "Joola": "dyo", "\u0f62\u0fab\u0f7c\u0f44\u0f0b\u0f41": "dz", "K\u0129embu": "ebu", "E\u028begbe": "ee", "\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac": "el", "English": "en", "Esperanto": "eo", "Espa\u00f1ol": "es", "Eesti": "et", "Euskara": "eu", "Ewondo": "ewo", "\u0641\u0627\u0631\u0633\u06cc": "fa", "Pulaar": "ff", "Suomi": "fi", "Filipino": "fil", "F\u00f8royskt": "fo", "Fran\u00e7ais": "fr", "Furlan": "fur", "Frysk": "fy", "Gaeilge": "ga", "G\u00e0idhlig": "gd", "Galego": "gl", "Schwiizert\u00fc\u00fctsch": "gsw", "\u0a97\u0ac1\u0a9c\u0ab0\u0abe\u0aa4\u0ac0": "gu", "Ekegusii": "guz", "Gaelg": "gv", "Hausa": "ha", "\u02bb\u014dlelo hawai\u02bbi": "haw", "\u05e2\u05d1\u05e8\u05d9\u05ea": "he", "\u0939\u093f\u0928\u094d\u0926\u0940": "hi", "Hrvatski": "hr", "Hornjoserb\u0161\u0107ina": "hsb", "Magyar": "hu", "\u0540\u0561\u0575\u0565\u0580\u0565\u0576": "hy", "Interlingua": "ia", "Indonesia": "id", "Igbo": "ig", "\ua188\ua320\ua259": "ii", "\u00cdslenska": "is", "Italiano": "it", "\u65e5\u672c\u8a9e": "ja", "Nda\ua78ca": "jgo", "Kimachame": "jmc", "Jawa": "jv", "\u10e5\u10d0\u10e0\u10d7\u10e3\u10da\u10d8": "ka", "Taqbaylit": "kab", "Kikamba": "kam", "Chimakonde": "kde", "Kabuverdianu": "kea", "Kanhg\u00e1g": "kgp", "Koyra ciini": "khq", "Gikuyu": "ki", "\u049a\u0430\u0437\u0430\u049b \u0442\u0456\u043b\u0456": "kk", "Kak\u0254": "kkj", "Kalaallisut": "kl", "Kalenjin": "kln", "\u1781\u17d2\u1798\u17c2\u179a": "km", "\u0c95\u0ca8\u0ccd\u0ca8\u0ca1": "kn", "\ud55c\uad6d\uc5b4": "ko", "\u0915\u094b\u0902\u0915\u0923\u0940": "kok", "\u06a9\u0672\u0634\u064f\u0631": "ks", "Kishambaa": "ksb", "Rikpa": "ksf", "K\u00f6lsch": "ksh", "Kurd\u00ee": "ku", "Kernewek": "kw", "\u041a\u044b\u0440\u0433\u044b\u0437\u0447\u0430": "ky", "K\u0268laangi": "lag", "L\u00ebtzebuergesch": "lb", "Luganda": "lg", "Lak\u021f\u00f3l\u02bciyapi": "lkt", "Ling\u00e1la": "ln", "\u0ea5\u0eb2\u0ea7": "lo", "\u0644\u06ca\u0631\u06cc \u0634\u0648\u0645\u0627\u0644\u06cc": "lrc", "Lietuvi\u0173": "lt", "Tshiluba": "lu", "Dholuo": "luo", "Luluhia": "luy", "Latvie\u0161u": "lv", "\u092e\u0948\u0925\u093f\u0932\u0940": "mai", "Maa": "mas", "K\u0129m\u0129r\u0169": "mer", "Kreol morisien": "mfe", "Malagasy": "mg", "Makua": "mgh", "Meta\u02bc": "mgo", "Te reo m\u0101ori": "mi", "\u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438": "mk", "\u0d2e\u0d32\u0d2f\u0d3e\u0d33\u0d02": "ml", "\u041c\u043e\u043d\u0433\u043e\u043b": "mn", "\u09ae\u09c8\u09a4\u09c8\u09b2\u09cb\u09a8\u09cd": "mni", "\u092e\u0930\u093e\u0920\u0940": "mr", "Melayu": "ms", "Malti": "mt", "Munda\u014b": "mua", "\u1019\u103c\u1014\u103a\u1019\u102c": "my", "\u0645\u0627\u0632\u0631\u0648\u0646\u06cc": "mzn", "Khoekhoegowab": "naq", "Isindebele": "nd", "Neddersass\u2019sch": "nds", "\u0928\u0947\u092a\u093e\u0932\u0940": "ne", "Nederlands": "nl", "Kwasio": "nmg", "Shw\u00f3\u014b\u00f2 ngiemb\u0254\u0254n": "nnh", "Norsk": "no", "Thok nath": "nus", "Runyankore": "nyn", "Oromoo": "om", "\u0b13\u0b21\u0b3c\u0b3f\u0b06": "or", "\u0418\u0440\u043e\u043d": "os", "\u0a2a\u0a70\u0a1c\u0a3e\u0a2c\u0a40": "pa", "Naij\u00edri\u00e1 p\u00edjin": "pcm", "Polski": "pl", "\u067e\u069a\u062a\u0648": "ps", "Portugu\u00eas": "pt", "Runasimi": "qu", "Rumantsch": "rm", "Ikirundi": "rn", "Rom\u00e2n\u0103": "ro", "Kihorombo": "rof", "\u0420\u0443\u0441\u0441\u043a\u0438\u0439": "ru", "Kinyarwanda": "rw", "Kiruwa": "rwk", "\u0938\u0902\u0938\u094d\u0915\u0943\u0924 \u092d\u093e\u0937\u093e": "sa", "\u0421\u0430\u0445\u0430 \u0442\u044b\u043b\u0430": "sah", "Kisampur": "saq", "\u1c65\u1c5f\u1c71\u1c5b\u1c5f\u1c72\u1c64": "sat", "Ishisangu": "sbp", "Sardu": "sc", "\u0633\u0646\u068c\u064a": "sd", "Davvis\u00e1megiella": "se", "Sena": "seh", "Koyraboro senni": "ses", "S\u00e4ng\u00f6": "sg", "\u2d5c\u2d30\u2d5b\u2d4d\u2d43\u2d49\u2d5c": "shi", "\u0dc3\u0dd2\u0d82\u0dc4\u0dbd": "si", "Sloven\u010dina": "sk", "Sloven\u0161\u010dina": "sl", "Anar\u00e2\u0161kiel\u00e2": "smn", "Chishona": "sn", "Soomaali": "so", "Shqip": "sq", "\u0421\u0440\u043f\u0441\u043a\u0438": "sr", "Basa sunda": "su", "Svenska": "sv", "Kiswahili": "sw", "\u0ba4\u0bae\u0bbf\u0bb4\u0bcd": "ta", "\u0c24\u0c46\u0c32\u0c41\u0c17\u0c41": "te", "Kiteso": "teo", "\u0422\u043e\u04b7\u0438\u043a\u04e3": "tg", "\u0e44\u0e17\u0e22": "th", "\u1275\u130d\u122d\u129b": "ti", "T\u00fcrkmen dili": "tk", "Lea fakatonga": "to", "T\u00fcrk\u00e7e": "tr", "\u0422\u0430\u0442\u0430\u0440": "tt", "Tasawaq senni": "twq", "Tamazi\u0263t n la\u1e6dla\u1e63": "tzm", "\u0626\u06c7\u064a\u063a\u06c7\u0631\u0686\u06d5": "ug", "\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430": "uk", "\u0627\u0631\u062f\u0648": "ur", "O\u2018zbek": "uz", "\ua559\ua524": "vai", "Ti\u1ebfng vi\u1ec7t": "vi", "Kyivunjo": "vun", "Walser": "wae", "Wolof": "wo", "Isixhosa": "xh", "Olusoga": "xog", "Nuasue": "yav", "\u05d9\u05d9\u05b4\u05d3\u05d9\u05e9": "yi", "\u00c8d\u00e8 yor\u00f9b\u00e1": "yo", "Nhe\u1ebdgatu": "yrl", "\u7cb5\u8a9e": "yue", "\u4e2d\u6587 (\u7cb5\u8a9e)": "yue", "\u2d5c\u2d30\u2d4e\u2d30\u2d63\u2d49\u2d56\u2d5c": "zgh", "\u4e2d\u6587": "zh", "\u666e\u901a\u8bdd": "zh", "Isizulu": "zu"}
// sprintf equivalent, takes a string and some arguments to make a computed string
// eg: strfmt("%1 dogs are in %2", 7, "the kitchen"); => "7 dogs are in the kitchen"
// eg: strfmt("I like %1, bananas and %1", "apples"); => "I like apples, bananas and apples"
// NB: removes msg context if there is one present
var strfmt = function (fmt) {
var args = arguments;
return fmt
// put space after double % to prevent placeholder replacement of such matches
.replace(/%%/g, '%% ')
// replace placeholders
.replace(/%(\d+)/g, function (str, p1) {
return args[p1];
})
// replace double % and space with single %
.replace(/%% /g, '%')
};
var removeContext = function (str) {
// if there is context, remove it
if (str.indexOf(_ctxt_delimiter) !== -1) {
var parts = str.split(_ctxt_delimiter);
return parts[1];
}
return str;
};
var normalizeLocale = function (locale) {
// Convert locale to BCP 47. If the locale is in POSIX format, locale variant and encoding is discarded.
locale = locale.replace('_', '-');
var i = locale.search(/[.@]/);
if (i != -1) locale = locale.slice(0, i);
return locale;
};
var getPluralFunc = function (plural_form) {
// Plural form string regexp
// taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js
// plural forms list available here http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html
var pf_re = new RegExp('^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_\(\)])+');
if (!pf_re.test(plural_form))
throw new Error(strfmt('The plural form "%1" is not valid', plural_form));
// Careful here, this is a hidden eval() equivalent..
// Risk should be reasonable though since we test the plural_form through regex before
// taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js
// TODO: should test if https://github.com/soney/jsep present and use it if so
return new Function("n", 'var plural, nplurals; ' + plural_form + ' return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };');
};
// Proper translation function that handle plurals and directives
// Contains juicy parts of https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js
var t = function (messages, n, options /* ,extra */ ) {
// Singular is very easy, just pass dictionnary message through strfmt
if (!options.plural_form)
return strfmt.apply(this, [removeContext(messages[0])].concat(Array.prototype.slice.call(arguments, 3)));
var plural;
// if a plural func is given, use that one
if (options.plural_func) {
plural = options.plural_func(n);
// if plural form never interpreted before, do it now and store it
} else if (!_plural_funcs[_locale]) {
_plural_funcs[_locale] = getPluralFunc(_plural_forms[_locale]);
plural = _plural_funcs[_locale](n);
// we have the plural function, compute the plural result
} else {
plural = _plural_funcs[_locale](n);
}
// If there is a problem with plurals, fallback to singular one
if ('undefined' === typeof plural.plural || plural.plural > plural.nplurals || messages.length <= plural.plural)
plural.plural = 0;
return strfmt.apply(this, [removeContext(messages[plural.plural]), n].concat(Array.prototype.slice.call(arguments, 3)));
};
return {
strfmt: strfmt, // expose strfmt util
// Declare shortcuts
tr: function () {
return this.gettext.apply(this, arguments);
},
_: function () {
return this.gettext.apply(this, arguments);
},
__: function () {
return this.gettext.apply(this, arguments);
},
_n: function () {
return this.ngettext.apply(this, arguments);
},
_p: function () {
return this.pgettext.apply(this, arguments);
},
setMessages: function (domain, locale, messages, plural_forms) {
if (!domain || !locale || !messages)
throw new Error('You must provide a domain, a locale and messages');
if ('string' !== typeof domain || 'string' !== typeof locale || !_.isObject(messages))
throw new Error('Invalid arguments');
locale = normalizeLocale(locale);
if (plural_forms)
_plural_forms[locale] = plural_forms;
if (!_dictionary[domain])
_dictionary[domain] = {};
DuDebug.log("Loaded " + locale + " dictionnary.");
_dictionary[domain][locale] = messages;
return this;
},
loadJSON: function (jsonData, domain) {
if (!_.isObject(jsonData))
jsonData = JSON.parse(jsonData);
if (!jsonData[''] || !jsonData['']['language'] || !jsonData['']['plural-forms'])
{
DuDebug.log('gettext: Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');
return;
}
var headers = jsonData[''];
delete jsonData[''];
return this.setMessages(domain || defaults.domain, headers['language'], jsonData, headers['plural-forms']);
},
setLanguage: function(lang, loadJson) {
this.setLocale( _languageCodes[lang], loadJson);
return this;
},
setLocale: function (locale, loadJson) {
if (locale == "en") {
DuDebug.log("gettext: Using default (en - English)")
_locale = "en";
return;
}
DuDebug.log("gettext: Using " + locale);
if ( def(loadJson, false) ) {
// Get file and load it
var jsonFile = new File( DuPath.join([_folder, _prefix + locale + _suffix]) );
if (!jsonFile.exists)
{
DuDebug.log("gettext: Can't find translation file: " + jsonFile.fsName)
return;
}
var jsonData = DuFile.parseJSON(jsonFile);
this.loadJSON(jsonData);
}
_locale = normalizeLocale(locale);
DuDebug.log("gettext: Ready! " + this.getLanguage());
return this;
},
getLanguage: function() {
return _languageNames[_locale];
},
getLanguageForLocale: function (locale) {
return _languageNames[locale];
},
getLocale: function () {
return _locale;
},
getLocaleForLanguage: function (language) {
return _languageCodes[language];
},
getAvailableLanguages: function( defaultLocale ) {
defaultLocale = def(defaultLocale, "en");
folder = new Folder(_folder);
files = folder.getFiles( _prefix + "*" + _suffix);
var languages = {};
languages.count = 0;
languages[defaultLocale] = _languageNames[defaultLocale];
for (var i = 0; i < files.length; i++)
{
var locale = files[i].name.replace(_prefix, "").replace(_suffix, "");
if (locale.length <= 3)
{
languages[locale] = _languageNames[locale];
languages.count++;
}
}
return languages;
},
// getter/setter for domain
textdomain: function (domain) {
if (!domain)
return _domain;
_domain = domain;
return this;
},
gettext: function (msgid /* , extra */ ) {
return this.dcnpgettext.apply(this, [undefined, undefined, msgid, undefined, undefined].concat(Array.prototype.slice.call(arguments, 1)));
},
ngettext: function (msgid, msgid_plural, n /* , extra */ ) {
return this.dcnpgettext.apply(this, [undefined, undefined, msgid, msgid_plural, n].concat(Array.prototype.slice.call(arguments, 3)));
},
pgettext: function (msgctxt, msgid /* , extra */ ) {
return this.dcnpgettext.apply(this, [undefined, msgctxt, msgid, undefined, undefined].concat(Array.prototype.slice.call(arguments, 2)));
},
dcnpgettext: function (domain, msgctxt, msgid, msgid_plural, n /* , extra */ ) {
domain = domain || _domain;
if ('string' !== typeof msgid)
throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string', msgid));
var
translation,
options = {
plural_form: false
},
key = msgctxt ? msgctxt + _ctxt_delimiter + msgid : msgid,
exist,
locale;
locale = _locale;
if (locale != "en") exist = _dictionary[domain];
else exist = false;
// Find corresponding locale in dict
// Ignore en to improve perf
if (exist && locale != "en") {
for (j in _dictionary[domain]) {
if (locale == j) break;
// If first part of the locale is ours, use it
if (j.indexOf(locale) == 0) {
locale = j;
break;
}
}
}
exist = exist && _dictionary[domain][locale] && _dictionary[domain][locale][key];
// because it's not possible to define both a singular and a plural form of the same msgid,
// we need to check that the stored form is the same as the expected one.
// if not, we'll just ignore the translation and consider it as not translated.
if (msgid_plural) {
exist = exist && "string" !== typeof _dictionary[domain][locale][key];
} else {
exist = exist && "string" === typeof _dictionary[domain][locale][key];
}
if (!exist) {
translation = msgid;
options.plural_func = defaults.plural_func;
} else {
translation = _dictionary[domain][locale][key];
}
// Singular form
if (!msgid_plural)
return t.apply(this, [
[translation], n, options
].concat(Array.prototype.slice.call(arguments, 5)));
// Plural one
options.plural_form = true;
return t.apply(this, [exist ? translation : [msgid, msgid_plural], n, options].concat(Array.prototype.slice.call(arguments, 5)));
}
};
};
// Init the engine
/**
* The translation engine
*/
var i18n = {};
translationEngine.init = function() {
var trFolder = DuESF.scriptSettings.file.parent.absoluteURI + '/';
i18n = translationEngine({
folder: trFolder,
prefix: DuESF.scriptName + "_"
});
var currentLocale = DuESF.scriptSettings.get("common/language", 'eo');
i18n.setLocale(currentLocale, true);
}
// ==================== |------| ====================
// ==================== | JSON | ====================
// ==================== |------| ====================
/**
* JSON parser.
* @name JSON
* @namespace
* @see {@link http://www.JSON.org/js.html|Json2}
* @license Public-Domain
* @category DuESF
* @subcategory Third Party
*/
JSON = {};
(function () {
"use strict";
var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
var rx_escapable = /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
function f(n) {
// Format integers to have at least two digits.
return (n < 10)
? "0" + n
: n;
}
function this_value() {
return this.valueOf();
}
if (typeof Date.prototype.toJSON !== "function") {
Date.prototype.toJSON = function () {
return isFinite(this.valueOf())
? (
this.getUTCFullYear()
+ "-"
+ f(this.getUTCMonth() + 1)
+ "-"
+ f(this.getUTCDate())
+ "T"
+ f(this.getUTCHours())
+ ":"
+ f(this.getUTCMinutes())
+ ":"
+ f(this.getUTCSeconds())
+ "Z"
)
: null;
};
Boolean.prototype.toJSON = this_value;
Number.prototype.toJSON = this_value;
String.prototype.toJSON = this_value;
}
var gap;
var indent;
var meta;
var rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
rx_escapable.lastIndex = 0;
return rx_escapable.test(string)
? "\"" + string.replace(rx_escapable, function (a) {
var c = meta[a];
return typeof c === "string"
? c
: "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
}) + "\""
: "\"" + string + "\"";
}
function str(key, holder) {
// Produce a string from holder[key].
var i; // The loop counter.
var k; // The member key.
var v; // The member value.
var length;
var mind = gap;
var partial;
var value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (
value
&& typeof value === "object"
&& typeof value.toJSON === "function"
) {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === "function") {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case "string":
return quote(value);
case "number":
// JSON numbers must be finite. Encode non-finite numbers as null.
return (isFinite(value))
? String(value)
: "null";
case "boolean":
case "null":
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce "null". The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is "object", we might be dealing with an object or an array or
// null.
case "object":
// Due to a specification blunder in ECMAScript, typeof null is "object",
// so watch out for that case.
if (!value) {
return "null";
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === "[object Array]") {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || "null";
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0
? "[]"
: gap
? (
"[\n"
+ gap
+ partial.join(",\n" + gap)
+ "\n"
+ mind
+ "]"
)
: "[" + partial.join(",") + "]";
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === "object") {
length = rep.length;
for (i = 0; i < length; i += 1) {
if (typeof rep[i] === "string") {
k = rep[i];
v = str(k, value);
if (v) {
partial.push(quote(k) + (
(gap)
? ": "
: ":"
) + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (
(gap)
? ": "
: ":"
) + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0
? "{}"
: gap
? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}"
: "{" + partial.join(",") + "}";
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== "function") {
meta = { // table of character substitutions
"\b": "\\b",
"\t": "\\t",
"\n": "\\n",
"\f": "\\f",
"\r": "\\r",
"\"": "\\\"",
"\\": "\\\\"
};
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = "";
indent = "";
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === "number") {
for (i = 0; i < space; i += 1) {
indent += " ";
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === "string") {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== "function" && (
typeof replacer !== "object"
|| typeof replacer.length !== "number"
)) {
throw new Error("JSON.stringify");
}
// Make a fake root object containing our value under the key of "".
// Return the result of stringifying the value.
return str("", {"": value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== "function") {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k;
var v;
var value = holder[key];
if (value && typeof value === "object") {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
text = String(text);
rx_dangerous.lastIndex = 0;
if (rx_dangerous.test(text)) {
text = text.replace(rx_dangerous, function (a) {
return (
"\\u"
+ ("0000" + a.charCodeAt(0).toString(16)).slice(-4)
);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with "()" and "new"
// because they can cause invocation, and "=" because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with "@" (a non-JSON character). Second, we
// replace all simple value tokens with "]" characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or "]" or
// "," or ":" or "{" or "}". If that is so, then the text is safe for eval.
if (
rx_one.test(
text
.replace(rx_two, "@")
.replace(rx_three, "]")
.replace(rx_four, "")
)
) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The "{" operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval("(" + text + ")");
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return (typeof reviver === "function")
? walk({"": j}, "")
: j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError("JSON.parse");
};
}
}());
// ==================== |--------| ====================
// ==================== | Matrix | ====================
// ==================== |--------| ====================
/*!
2D Transformation Matrix v2.7.5 LT
(c) Epistemex.com 2014-2018
License: MIT
*/
/**
* 2D transformation matrix object initialized with identity matrix.
* @class
* @name Matrix
* @classdesc 2D Transformation Matrix.
* @prop {number} a - scale x
* @prop {number} b - shear y
* @prop {number} c - shear x
* @prop {number} d - scale y
* @prop {number} e - translate x
* @prop {number} f - translate y
* @author Epistemex
* @version 2.7.5
* @license MIT license (header required)
* @copyright Epistemex.com 2014-2018
* @category DuESF
* @subcategory Third Party
*/
function Matrix() {
var me = this, _el;
me._t = me.transform;
me.a = me.d = 1;
me.b = me.c = me.e = me.f = 0;
}
/**
* Create and transform a new matrix based on given matrix values, or
* provide SVGMatrix or a (2D) DOMMatrix, WebKitCSSMatrix or another
* instance of a generic matrix.
*
* @example
*
* var m = Matrix.from(1, 0.2, 0, 2, 120, 97);
* var m = Matrix.from(domMatrix, ctx);
* var m = Matrix.from(svgMatrix);
* var m = Matrix.from(matrix);
*
* @param {*} a - number representing a (scale x) in [a-f], or a Matrix object containing properties a-f.
* @param {*} [b] - b property (shear y) if a is not a matrix object, or optional canvas 2D context.
* If vector is input this will be pre-translate for x.
* @param {number} [c] - c property (shear x)
* @param {number} [d] - d property (scale y)
* @param {number} [e] - e property (translate x)
* @param {number} [f] - f property (translate y)
* @param {CanvasRenderingContext2D} [context] - optional canvas context to synchronize
* @returns {Matrix} - new Matrix instance
* @static
*/
Matrix.from = function(a, b, c, d, e, f, context) {
var m = new Matrix(context), scale, dist, q;
if (typeof a === "number")
m.setTransform(a, b, c, d, e, f);
else {
if (typeof a.is2D === "boolean" && !a.is2D) throw "Cannot use 3D DOMMatrix.";
if (b) m.context = b;
m.multiply(a)
}
return m
};
Matrix.prototype = {
/**
* Short-hand to reset current matrix to an identity matrix.
* @returns {Matrix}
*/
reset: function() {
return this.setTransform(1, 0, 0, 1, 0, 0)
},
/**
* Rotates current matrix by angle (accumulative).
* @param {number} angle - angle in degrees
* @returns {Matrix}
*/
rotate: function(angle) {
angle = DuMath.toRadians(angle);
var
cos = Math.cos(angle),
sin = Math.sin(angle);
return this._t(cos, sin, -sin, cos, 0, 0)
},
/**
* Converts a vector given as `x` and `y` to angle, and
* rotates (accumulative). x can instead contain an object with
* properties x and y and if so, y parameter will be ignored.
* @param {number|*} x
* @param {number} [y]
* @returns {Matrix}
*/
rotateFromVector: function(x, y) {
return this.rotate(typeof x === "number" ? Math.atan2(y, x) : Math.atan2(x.y, x.x))
},
/**
* Scales current matrix accumulative.
* @param {number[]} s - scale factor [x, y]. 1 does nothing, any third value (Z) is ignored.
* @returns {Matrix}
*/
scale: function(s) {
return this._t(s[0], 0, 0, s[1], 0, 0);
},
/**
* Apply shear to the current matrix accumulative.
* @param {number} sx - amount of shear for x
* @param {number} sy - amount of shear for y
* @returns {Matrix}
*/
shear: function(sx, sy) {
return this._t(1, sy, sx, 1, 0, 0)
},
/**
* Apply skew to the current matrix accumulative. Angles in radians.
* Also see [`skewDeg()`]{@link Matrix#skewDeg}.
* @param {number} ax - angle of skew for x
* @param {number} ay - angle of skew for y
* @returns {Matrix}
*/
skew: function(ax, ay) {
return this.shear(Math.tan(ax), Math.tan(ay))
},
/**
* Set current matrix to new absolute matrix.
* @param {number} a - scale x
* @param {number} b - shear y
* @param {number} c - shear x
* @param {number} d - scale y
* @param {number} e - translate x
* @param {number} f - translate y
* @returns {Matrix}
*/
setTransform: function(a, b, c, d, e, f) {
var me = this;
me.a = a;
me.b = b;
me.c = c;
me.d = d;
me.e = e;
me.f = f;
return me._x()
},
/**
* Translate current matrix accumulative.
* @param {number[]} t - translation [x, y]. Any third value (Z) is ignored.
* @returns {Matrix}
*/
translate: function(t) {
return this._t(1, 0, 0, 1, t[0], t[1]);
},
/**
* Multiplies current matrix with new matrix values. Also see [`multiply()`]{@link Matrix#multiply}.
*
* @param {number} a2 - scale x
* @param {number} b2 - skew y
* @param {number} c2 - skew x
* @param {number} d2 - scale y
* @param {number} e2 - translate x
* @param {number} f2 - translate y
* @returns {Matrix}
*/
transform: function(a2, b2, c2, d2, e2, f2) {
var
me = this,
a1 = me.a,
b1 = me.b,
c1 = me.c,
d1 = me.d,
e1 = me.e,
f1 = me.f;
/* matrix column order is:
* a c e
* b d f
* 0 0 1
*/
me.a = a1 * a2 + c1 * b2;
me.b = b1 * a2 + d1 * b2;
me.c = a1 * c2 + c1 * d2;
me.d = b1 * c2 + d1 * d2;
me.e = a1 * e2 + c1 * f2 + e1;
me.f = b1 * e2 + d1 * f2 + f1;
return me._x()
},
/**
* Multiplies current matrix with source matrix.
* @param {Matrix|Matrix|DOMMatrix|SVGMatrix} m - source matrix to multiply with.
* @returns {Matrix}
*/
multiply: function(m) {
return this._t(m.a, m.b, m.c, m.d, m.e, m.f)
},
/**
* Get an inverse matrix of current matrix. The method returns a new
* matrix with values you need to use to get to an identity matrix.
* Context from parent matrix is not applied to the returned matrix.
*
* @param {boolean} [cloneContext=false] - clone current context to resulting matrix
* @throws Exception is input matrix is not invertible
* @returns {Matrix} - new Matrix instance
*/
inverse: function(cloneContext) {
var
me = this,
m = new Matrix(cloneContext ? me.context : null),
dt = me.determinant();
if (dt === 0) throw "Matrix not invertible.";
m.a = me.d / dt;
m.b = -me.b / dt;
m.c = -me.c / dt;
m.d = me.a / dt;
m.e = (me.c * me.f - me.d * me.e) / dt;
m.f = -(me.a * me.f - me.b * me.e) / dt;
return m
},
/**
* Decompose the current matrix into simple transforms using QR.
*
* @returns {*} - an object containing current decomposed values (translate, rotation, scale, skew)
* @see {@link https://en.wikipedia.org/wiki/QR_decomposition|More on QR decomposition}
*/
decompose: function() {
var
me = this,
a = me.a,
b = me.b,
c = me.c,
d = me.d,
acos = Math.acos,
atan = Math.atan,
sqrt = Math.sqrt,
pi = Math.PI,
translate = {x: me.e, y: me.f},
rotation = 0,
scale = {x: 1, y: 1},
skew = {x: 0, y: 0},
determ = a * d - b * c, // determinant(), skip DRY here...
r, s;
// Apply the QR-like decomposition.
if (a || b) {
r = sqrt(a * a + b * b);
rotation = b > 0 ? acos(a / r) : -acos(a / r);
scale = {x: r, y: determ / r};
skew.x = atan((a * c + b * d) / (r * r));
}
else if (c || d) {
s = sqrt(c * c + d * d);
rotation = pi * 0.5 - (d > 0 ? acos(-c / s) : -acos(c / s));
scale = {x: determ / s, y: s};
skew.y = atan((a * c + b * d) / (s * s));
}
else { // a = b = c = d = 0
scale = {x: 0, y: 0};
}
return {
translate: translate,
rotation : rotation,
scale : scale,
skew : skew
}
},
/**
* Returns the determinant of the current matrix.
* @returns {number}
*/
determinant: function() {
return this.a * this.d - this.b * this.c
},
/**
* Apply current matrix to `x` and `y` of a point.
* Returns a point object.
*
* @param {number[]} pt - the point to transform ([x, y]).<br />
* If an optionnal Z value is provided, it will be kept without transformation.
* @returns {number[]} A new transformed point [x, y]. If pt had a third value, it is returned too, as it was without transformation.
*/
applyToPoint: function(pt) {
var me = this;
var x = pt[0] * me.a + pt[1] * me.c + me.e;
var y = pt[0] * me.b + pt[1] * me.d + me.f;
var result = [x,y];
if (pt.length == 3) result.push(pt[2]);
return result;
},
/**
* Returns true if matrix is an identity matrix (no transforms applied).
* @returns {boolean}
*/
isIdentity: function() {
var me = this;
return me.a === 1 && !me.b && !me.c && me.d === 1 && !me.e && !me.f
},
/**
* Returns true if matrix is invertible
* @returns {boolean}
*/
isInvertible: function() {
return !this._q(this.determinant(), 0)
},
/**
* The method is intended for situations where scale is accumulated
* via multiplications, to detect situations where scale becomes
* "trapped" with a value of zero. And in which case scale must be
* set explicitly to a non-zero value.
*
* @returns {boolean}
*/
isValid: function() {
return !(this.a * this.d)
},
/**
* Compares current matrix with another matrix. Returns true if equal
* (within epsilon tolerance).
* @param {Matrix|Matrix|DOMMatrix|SVGMatrix} m - matrix to compare this matrix with
* @returns {boolean}
*/
isEqual: function(m) {
var
me = this,
q = me._q;
return q(me.a, m.a) &&
q(me.b, m.b) &&
q(me.c, m.c) &&
q(me.d, m.d) &&
q(me.e, m.e) &&
q(me.f, m.f)
},
/**
* Clones current instance and returning a new matrix.
* @param {boolean} [noContext=false] don't clone context reference if true
* @returns {Matrix} - a new Matrix instance with identical transformations as this instance
*/
clone: function(noContext) {
return new Matrix(noContext ? null : this.context).multiply(this)
},
/**
* Generates a string that can be used with CSS `transform`.
* @example
* element.style.transform = m.toCSS();
* @returns {string}
*/
toCSS: function() {
return "matrix(" + this.toArray() + ")"
},
/**
* Generates a `matrix3d()` string that can be used with CSS `transform`.
* Although the matrix is for 2D use you may see performance benefits
* on some devices using a 3D CSS transform instead of a 2D.
* @example
* element.style.transform = m.toCSS3D();
* @returns {string}
*/
toCSS3D: function() {
var me = this;
return "matrix3d(" + me.a + "," + me.b + ",0,0," + me.c + "," + me.d + ",0,0,0,0,1,0," + me.e + "," + me.f + ",0,1)"
},
/**
* Returns a JSON compatible string of current matrix.
* @returns {string}
*/
toJSON: function() {
var me = this;
return '{"a":' + me.a + ',"b":' + me.b + ',"c":' + me.c + ',"d":' + me.d + ',"e":' + me.e + ',"f":' + me.f + '}'
},
/**
* Compares floating point values with some tolerance (epsilon)
* @param {number} f1 - float 1
* @param {number} f2 - float 2
* @returns {boolean}
* @private
*/
_q: function(f1, f2) {
return Math.abs(f1 - f2) < 1e-14
},
/**
* Apply current absolute matrix to context if defined, to sync it.
* Apply current absolute matrix to element if defined, to sync it.
* @returns {Matrix}
* @private
*/
_x: function() {
var me = this;
if (me.context)
me.context.setTransform(me.a, me.b, me.c, me.d, me.e, me.f);
return me
}
};
// ==================== |------------| ====================
// ==================== | SeedRandom | ====================
// ==================== |------------| ====================
/**
* Adds a new Math.seedRandom() method, used as a workaround for a bug in Math.random() with After Effects on Mac OS
* @name seedRandom
* @author David Bau
* @copyright David Bau
* @license MIT
* @category DuESF
* @subcategory Third Party
*/
!function(a,b){function c(c,j,k){var n=[];j=1==j?{entropy:!0}:j||{};var s=g(f(j.entropy?[c,i(a)]:null==c?h():c,3),n),t=new d(n),u=function(){for(var a=t.g(m),b=p,c=0;q>a;)a=(a+c)*l,b*=l,c=t.g(1);for(;a>=r;)a/=2,b/=2,c>>>=1;return(a+c)/b};return u.int32=function(){return 0|t.g(4)},u.quick=function(){return t.g(4)/4294967296},u["double"]=u,g(i(t.S),a),(j.pass||k||function(a,c,d,f){return f&&(f.S&&e(f,t),a.state=function(){return e(t,{})}),d?(b[o]=a,c):a})(u,s,"global"in j?j.global:this==b,j.state)}function d(a){var b,c=a.length,d=this,e=0,f=d.i=d.j=0,g=d.S=[];for(c||(a=[c++]);l>e;)g[e]=e++;for(e=0;l>e;e++)g[e]=g[f=s&f+a[e%c]+(b=g[e])],g[f]=b;(d.g=function(a){for(var b,c=0,e=d.i,f=d.j,g=d.S;a--;)b=g[e=s&e+1],c=c*l+g[s&(g[e]=g[f=s&f+b])+(g[f]=b)];return d.i=e,d.j=f,c})(l)}function e(a,b){return b.i=a.i,b.j=a.j,b.S=a.S.slice(),b}function f(a,b){var c,d=[],e=typeof a;if(b&&"object"==e)for(c in a)try{d.push(f(a[c],b-1))}catch(g){}return d.length?d:"string"==e?a:a+"\0"}function g(a,b){for(var c,d=a+"",e=0;e<d.length;)b[s&e]=s&(c^=19*b[s&e])+d.charCodeAt(e++);return i(b)}function h(){try{if(j)return i(j.randomBytes(l));var b=new Uint8Array(l);return(k.crypto||k.msCrypto).getRandomValues(b),i(b)}catch(c){var d=k.navigator,e=d&&d.plugins;return[+new Date,k,e,k.screen,i(a)]}}function i(a){return String.fromCharCode.apply(0,a)}var j,k=this,l=256,m=6,n=52,o="random",p=b.pow(l,m),q=b.pow(2,n),r=2*q,s=l-1;if(b["seed"+o]=c,g(b.random(),a),"object"==typeof module&&module.exports){module.exports=c;try{j=require("crypto")}catch(t){}}else"function"==typeof define&&define.amd&&define(function(){return c})}([],Math);
// ==================== |-------------| ====================
// ==================== | colorPicker | ====================
// ==================== |-------------| ====================
;(function(){
/**
* @classdesc ColorPicker v2.0 for Adobe scripting.<br />
* 2016-5-11 -> 2016-7-24<br />
* This is a rebuilt color picker for Adobe scripting.<br />
* Support all Adobe softwares such as PS,AI,PR and so on.<br />
* See usage on {@link http://github.com/Smallpath/AdobeColorPicker}
* @class
* @name colorPicker
* @author smallpath
* @copyright smallpath
* @license MIT
* @link https://github.com/Smallpath/AdobeColorPicker
* @category DuESF
* @subcategory Third Party
*/
function colorPicker(inputColour,options){
if(!(this instanceof colorPicker))
return new colorPicker(inputColour,options)
this.options = {
name : "Adobe Color Picker ",
version : "v2.0",
shouldUpdateCursor: false,
backupLocation : [],
windowType:"dialog", // "dialog","palette" and the reference of Panel
}
if(options && colorPicker.isType(options,"Object")){
for(var i in options)
this.options[i] = options[i];
}
this.inputColour = colorPicker.parseColor(inputColour);
this.outputColour = this.inputColour.slice(0);
this.initSetting();
return this.showColorPicker();
}
colorPicker.parseColor = function(inputValue){
if(!inputValue)
return [1,1,1];
if(colorPicker.isRgb(inputValue)) //[0,0,0] - [1,1,1]
return colorPicker.parseRgb(inputValue);
else if(colorPicker.isLargeRgb(inputValue)) //[0,0,0] - [255,255,255]
return colorPicker.parseLargeRgb(inputValue);
else if(colorPicker.isHex(inputValue) ) //FFFFFF
return colorPicker.parseHex(inputValue);
else if(colorPicker.isShortHex(inputValue)) //FFF
return colorPicker.parseShortHex(inputValue);
else if(colorPicker.isHsb(inputValue)) //[0,0,0,'hsb'] - [360,100,100,'hsb']
return colorPicker.parseHsb(inputValue);
else
return [1,1,1];
}
colorPicker.prototype.showColorPicker = function(){
var win = this.initWindow();
if(win.type == "dialog" ||win.type == "palette" ){
if(this.haveSetting ("location")){
win.location = this.getSetting ("location").split(",");
if(win.location.length != 2)
win.center();
else if(win.location[0]<0 || win.location[1]<0)
win.center();
}
win.show();
this.saveSetting ("location", win.location);
}else if(win.type == "panel"){
win.layout.layout(true);
}
if(!this.isSmallMode){
this.outputColour.hex = colorPicker.RgbToHex(this.outputColour);
this.outputColour.hsb = colorPicker.RgbToHsb(this.outputColour);
this.outputColour.rgb = this.outputColour.slice(0);
return this.outputColour;
}else{
return {
window:win,
colorPicker:this
};
}
}
colorPicker.prototype.initWindow = function(){
var _this = this;
var type = this.options["windowType"];
if(type=="palette"){
var win = new Window("palette", this.options["name"] + this.options["version"], undefined, {
maximizeButton: false,
minimizeButton: false,
});
this.size = this.options["size"] = 90;
}else if(type instanceof Panel){
var win = type;
this.size = this.options["size"] = 90;
}else{
var win = new Window("dialog", this.options["name"] + this.options["version"], undefined, {
maximizeButton: false,
minimizeButton: false,
closeButton:false
});
this.size = this.options["size"] = 130;
}
var isSmallMode = this.isSmallMode = this.size != 130;
var targetImage = isSmallMode ? this.options["smallColorWheel"]: this.img ;
var targetSize = this.size*2;
var colourGroup = win.add('group');
colourGroup.orientation = "stack";
win.image = colourGroup.add("image", undefined,targetImage);
var colourCursorGroup = this.colourCursorGroup = colourGroup.add('customBoundedValue',[0,0,targetSize+2,targetSize+2]);
colourCursorGroup.fillColour = [0,0,0,0];
var colourSelectCursor = this.colourSelectCursor = colourCursorGroup.colourSelectCursor = {};
colourSelectCursor.size = [12,12];
colourSelectCursor.strokeWidth = 1;
colourSelectCursor.strokeColour = [0,0,0];
this.setCursorLocation (this.inputColour);
colourCursorGroup.onDraw = function () {
this.graphics.drawOSControl();
this.graphics.newPath();
this.graphics.ellipsePath(0,0,this.size[0],this.size[1]);
this.graphics.fillPath(colourCursorGroup.graphics.newBrush(colourCursorGroup.graphics.BrushType.SOLID_COLOR, colourCursorGroup.fillColour));
this.graphics.newPath();
this.graphics.ellipsePath(this.colourSelectCursor.location[0]+this.colourSelectCursor.strokeWidth/2+1,
this.colourSelectCursor.location[1]+this.colourSelectCursor.strokeWidth/2+1,
this.colourSelectCursor.size[0]-this.colourSelectCursor.strokeWidth,
this.colourSelectCursor.size[1]-this.colourSelectCursor.strokeWidth);
this.graphics.strokePath(this.graphics.newPen(this.graphics.PenType.SOLID_COLOR,
this.colourSelectCursor.strokeColour, this.colourSelectCursor.strokeWidth));
};
win.brightGroup = win.add("group");
win.staticBright = win.brightGroup.add("statictext",undefined,"Bright:");
win.editBright = win.brightGroup.add("edittext{text:'0',characters:3,justify:'center',active:1}");
win.slider = win.brightGroup.add("slider",undefined,100,0,100);
if(!isSmallMode){
win.slider.size = "width:160,height:20";
var spacing = 10;
var character = 4;
}else{
win.slider.size = "width:100,height:20";
win.brightGroup.spacing = 2;
win.spacing = 5;
win.margins = 2;
var spacing = 0;
var character = 3;
}
var editor = win.add('group');
editor.orientation = 'column';
editor.gulu = editor.add('group');
editor.gulu.uni = editor.gulu.add('group');
editor.gulu.uni.spacing = spacing;
editor.gulu.uni.Ed = editor.gulu.uni.add('statictext', undefined, '#');
editor.gulu.uni.unicode = editor.gulu.uni.add('edittext', undefined, 'FF0000')
editor.gulu.uni.unicode.characters = 6;
editor.gulu.uni.unicode.justify = 'center';
editor.gulu.color = editor.gulu.add('customBoundedValue', undefined, 'Redraw original image');
editor.gulu.color.size = [80, 25];
editor.colorHolder = editor.add('group');
editor.colorHolder.orientation = 'row';
editor.colorHolder.colorCol1 = editor.colorHolder.add('group');
editor.colorHolder.colorCol1.orientation = 'column';
editor.colorHolder.colorCol1.hGroup = editor.colorHolder.colorCol1.add('group');
editor.colorHolder.colorCol1.hGroup.spacing = spacing;
editor.colorHolder.colorCol1.hGroup.hRad = editor.colorHolder.colorCol1.hGroup.add('statictext', undefined, 'H:');
editor.colorHolder.colorCol1.hGroup.hValue = editor.colorHolder.colorCol1.hGroup.add('edittext', undefined, '0');
editor.colorHolder.colorCol1.hGroup.hValue.characters = character;
editor.colorHolder.colorCol1.hGroup.hValue.justify = 'center';
editor.colorHolder.colorCol1.hGroup.hValue._index = 0;
editor.colorHolder.colorCol1.rGroup = editor.colorHolder.colorCol1.add('group');
editor.colorHolder.colorCol1.rGroup.spacing = spacing;
editor.colorHolder.colorCol1.rGroup.rRad = editor.colorHolder.colorCol1.rGroup.add('statictext', undefined, 'R:');
editor.colorHolder.colorCol1.rGroup.rValue = editor.colorHolder.colorCol1.rGroup.add('edittext', undefined, '0');
editor.colorHolder.colorCol1.rGroup.rValue.characters = character;
editor.colorHolder.colorCol1.rGroup.rValue.justify = 'center';
editor.colorHolder.colorCol1.rGroup.rValue._index = 0;
editor.colorHolder.colorCol2 = editor.colorHolder.add('group');
editor.colorHolder.colorCol2.orientation = 'column';
editor.colorHolder.colorCol2.sGroup = editor.colorHolder.colorCol2.add('group');
editor.colorHolder.colorCol2.sGroup.spacing = spacing;
editor.colorHolder.colorCol2.sGroup.sRad = editor.colorHolder.colorCol2.sGroup.add('statictext', undefined, 'S:');
editor.colorHolder.colorCol2.sGroup.sValue = editor.colorHolder.colorCol2.sGroup.add('edittext', undefined, '0');
editor.colorHolder.colorCol2.sGroup.sValue.characters = character;
editor.colorHolder.colorCol2.sGroup.sValue.justify = 'center';
editor.colorHolder.colorCol2.sGroup.sValue._index = 1;
editor.colorHolder.colorCol2.gGroup = editor.colorHolder.colorCol2.add('group');
editor.colorHolder.colorCol2.gGroup.spacing = spacing;
editor.colorHolder.colorCol2.gGroup.gRad = editor.colorHolder.colorCol2.gGroup.add('statictext', undefined, 'G:');
editor.colorHolder.colorCol2.gGroup.gValue = editor.colorHolder.colorCol2.gGroup.add('edittext', undefined, '0');
editor.colorHolder.colorCol2.gGroup.gValue.characters = character;
editor.colorHolder.colorCol2.gGroup.gValue.justify = 'center';
editor.colorHolder.colorCol2.gGroup.gValue._index = 1;
editor.colorHolder.colorCol3 = editor.colorHolder.add('group');
editor.colorHolder.colorCol3.orientation = 'column';
editor.colorHolder.colorCol3.lGroup = editor.colorHolder.colorCol3.add('group');
editor.colorHolder.colorCol3.lGroup.spacing = spacing;
editor.colorHolder.colorCol3.lGroup.lRad = editor.colorHolder.colorCol3.lGroup.add('statictext', undefined, 'B:');
editor.colorHolder.colorCol3.lGroup.lValue = editor.colorHolder.colorCol3.lGroup.add('edittext', undefined, '0');
editor.colorHolder.colorCol3.lGroup.lValue.characters = character;
editor.colorHolder.colorCol3.lGroup.lValue.justify = 'center';
editor.colorHolder.colorCol3.lGroup.lValue._index = 2;
editor.colorHolder.colorCol3.bGroup = editor.colorHolder.colorCol3.add('group');
editor.colorHolder.colorCol3.bGroup.spacing = spacing;
editor.colorHolder.colorCol3.bGroup.bRad = editor.colorHolder.colorCol3.bGroup.add('statictext', undefined, 'B:');
editor.colorHolder.colorCol3.bGroup.bValue = editor.colorHolder.colorCol3.bGroup.add('edittext', undefined, '0');
editor.colorHolder.colorCol3.bGroup.bValue.characters = character;
editor.colorHolder.colorCol3.bGroup.bValue.justify = 'center';
editor.colorHolder.colorCol3.bGroup.bValue._index = 2;
win.editor = editor;
if(win.type == "dialog"){
editor.oc = win.editor.oc = win.add("Group{ok:Button{text:'Ok'},can:Button{text:'Cancel'}}");
editor.oc.ok.onClick = function(){
win.close();
}
editor.oc.can.onClick = function(){
colorPicker.copyArr(_this.outputColour,_this.inputColour);
win.close();
}
}
editor.gulu.color.onDraw = function(draw){
var targetColour = _this.outputColour;
var gfxs=this.graphics;
gfxs.newPath();
gfxs.rectPath(0, 0, this.size[0], this.size[1]);
gfxs.fillPath(gfxs.newBrush (gfxs.BrushType.SOLID_COLOR, targetColour));
};
this.updateCursor(win);
this.setDefaultValue(win)
this.bindingKeydown(win);
this.bindingHandler(win);
return win;
}
colorPicker.prototype.setDefaultValue = function(win){
var pi = win.editor;
var startColour = this.outputColour;
pi.gulu.uni.unicode.text= colorPicker.RgbToHex (startColour);
pi.gulu.uni.unicode.active = true;
pi.colorHolder.colorCol1.rGroup.rValue.text=Math.round(startColour[0]*255);
pi.colorHolder.colorCol2.gGroup.gValue.text=Math.round(startColour[1]*255);
pi.colorHolder.colorCol3.bGroup.bValue.text=Math.round(startColour[2]*255);
var hsbHere= colorPicker.RgbToHsb([
startColour[0]*255,
startColour[1]*255,
startColour[2]*255
]);
pi.colorHolder.colorCol1.hGroup.hValue.text=hsbHere[0];
pi.colorHolder.colorCol2.sGroup.sValue.text=hsbHere[1];
pi.colorHolder.colorCol3.lGroup.lValue.text=hsbHere[2];
win.slider.value=hsbHere[2];
win.editBright.text=hsbHere[2];
this.colourCursorGroup.fillColour[3] = 1 - (hsbHere[2])/100;
this.colourCursorGroup.notify("onDraw");
}
colorPicker.prototype.bindingHandler = function(win){
var _this = this;
win.editor.colorHolder.colorCol1.hGroup.hValue.onChange =
win.editor.colorHolder.colorCol2.sGroup.sValue.onChange =
win.editor.colorHolder.colorCol3.lGroup.lValue.onChange = function(){
_this.options.backupLocation.length = 0;
this.text=Math.round(this.text);
if(this._index ==0 ){
if( this.text<0 || this.text>360 || isNaN(this.text)==true ){
this.text= colorPicker.RgbToHsb([
_this.outputColour[0]*255,
_this.outputColour[1]*255,
_this.outputColour[2]*255,
])[this._index];
}
}else{
if( this.text<0 || this.text>100 || isNaN(this.text)==true ){
this.text= colorPicker.RgbToHsb([
_this.outputColour[0]*255,
_this.outputColour[1]*255,
_this.outputColour[2]*255,
])[this._index];
}
}
var hsbArr = [
Math.round(win.editor.colorHolder.colorCol1.hGroup.hValue.text),
Math.round(win.editor.colorHolder.colorCol2.sGroup.sValue.text),
Math.round(win.editor.colorHolder.colorCol3.lGroup.lValue.text)
];
var rgbArr = colorPicker.HsbToRgb (hsbArr);
var hexStr = colorPicker.RgbToHex ([rgbArr[0]/255,rgbArr[1]/255,rgbArr[2]/255]);
win.editor.gulu.uni.unicode.text = hexStr;
win.editor.gulu.uni.unicode.notify("onChange");
}
win.editor.colorHolder.colorCol1.rGroup.rValue.onChange =
win.editor.colorHolder.colorCol2.gGroup.gValue.onChange =
win.editor.colorHolder.colorCol3.bGroup.bValue.onChange = function (){
_this.options.backupLocation.length = 0;
this.text=Math.round(this.text);
if( this.text<0 || this.text>255 || isNaN(this.text)==true ){
this.text=Math.round(_this.outputColour[this._index]*255);
}
if(this._index ==0)
win.editor.gulu.uni.unicode.text= colorPicker.RgbToHex ([this.text/255,_this.outputColour[1],_this.outputColour[2]]);
else if(this._index ==1)
win.editor.gulu.uni.unicode.text= colorPicker.RgbToHex ([_this.outputColour[0],this.text/255,_this.outputColour[2]]);
else if(this._index ==2)
win.editor.gulu.uni.unicode.text= colorPicker.RgbToHex ([_this.outputColour[0],_this.outputColour[1],this.text/255]);
win.editor.gulu.uni.unicode.notify("onChange");
}
win.editBright.onChange = win.editBright.onChanging = function () {
_this.options.backupLocation.length = 0;
if (this.text < 0)
this.text = 0;
if (this.text > 100)
this.text = 100;
if (isNaN(this.text)==true)
this.text = 100;
win.slider.value = parseInt(this.text);
if(isNaN(win.slider.value))
return;
win.slider.notify('onChange');
};
win.slider.onChange = win.slider.onChanging = function(){
var thisColor= colorPicker.HsbToRgb ([
Math.round(win.editor.colorHolder.colorCol1.hGroup.hValue.text),
Math.round(win.editor.colorHolder.colorCol2.sGroup.sValue.text),
Math.round(this.value)
]);
if(this.value != 0){
if(_this.options.backupLocation.length != 0 && _this.options.shouldUpdateCursor == true){
_this.getColor({
type:"mouseup",
clientX: _this.options.backupLocation[0]+6,
clientY: _this.options.backupLocation[1]+6,
});
_this.options.backupLocation.length = 0;
_this.colourCursorGroup.fillColour[3] = 1 - (this.value)/100;
_this.colourCursorGroup.notify("onDraw");
return;
}
}
colorPicker.copyArr(_this.outputColour,[thisColor[0]/255,thisColor[1]/255,thisColor[2]/255]);
_this.setDefaultValue(win);
_this.notifyColor(win);
_this.updateCursor(win);
_this.setCursorLocation (_this.outputColour);
if(this.value == 0){
_this.options.shouldUpdateCursor = true;
}else{
_this.options.shouldUpdateCursor = false;
}
if(_this.options.backupLocation.length == 0){
colorPicker.copyArr(_this.options.backupLocation,_this.colourSelectCursor.location);
}
_this.colourCursorGroup.fillColour[3] = 1 - (this.value)/100;
_this.colourCursorGroup.notify("onDraw");
}
win.editor.gulu.uni.unicode.onChange= function(){
var hexHere="0x"+this.text;
var eV=0;
if(colorPicker.isHex (this.text) == false && colorPicker.isShortHex(this.text) == false){
this.text = colorPicker.RgbToHex (_this.outputColour);
eV=1;
}
if(eV==0){
var rgbHere= (this.text.length == 6) ? colorPicker.parseHex (this.text): colorPicker.parseShortHex (this.text);
colorPicker.copyArr(_this.outputColour,rgbHere);
_this.setDefaultValue(win);
_this.setCursorLocation(rgbHere);
_this.notifyColor(win);
}
}
}
colorPicker.prototype.updateCursor = function(win) {
if (colorPicker.arraysEqual(this.colourSelectCursor.strokeColour,[1,1,1])) {
if (win.slider.value > 63)
this.colourSelectCursor.strokeColour = [0,0,0];
} else if (colorPicker.arraysEqual(this.colourSelectCursor.strokeColour,[0,0,0])) {
if (win.slider.value <= 63)
this.colourSelectCursor.strokeColour = [1,1,1];
}
};
colorPicker.prototype.notifyColor = function(win){
win.editor.gulu.color.notify("onDraw");
}
colorPicker.prototype.setCursorLocation= function(inputColor){
this.colourSelectCursor.location = (function(_this){
var hsb = colorPicker.RgbToHsb(inputColor);
hsb = colorPicker.convertHsbToKulerHsb(hsb);
var angle = Math.round(hsb[0]);
var length = Math.round(hsb[1]/100*_this.size);
var point = [length*Math.cos(angle*2*Math.PI/360),length*Math.sin(angle*2*Math.PI/360)];
return [point[0]+_this.size,_this.size-point[1]]
})(this)
this.colourSelectCursor.location = [this.colourSelectCursor.location[0]-this.colourSelectCursor.size[0]/2,
this.colourSelectCursor.location[1]-this.colourSelectCursor.size[1]/2];
};
colorPicker.prototype.bindingKeydown = function(win){
var _this = this;
var keyDownHandle1 = function(k){
if (k.keyName == "Up") {
if (k.shiftKey == false) {
this.text = parseFloat(this.text) + 1;
} else {
this.text = parseFloat(this.text) + 10;
}
} else if (k.keyName == "Down") {
if (k.shiftKey == false) {
this.text = parseFloat(this.text) - 1;
} else {
this.text = parseFloat(this.text) - 10;
}
}
}
win.editor.colorHolder.colorCol1.rGroup.rValue.addEventListener('keydown', keyDownHandle1);
win.editor.colorHolder.colorCol2.gGroup.gValue.addEventListener('keydown', keyDownHandle1);
win.editor.colorHolder.colorCol3.bGroup.bValue.addEventListener('keydown', keyDownHandle1);
win.editBright.addEventListener('keydown', keyDownHandle1);
win.addEventListener('keydown', function (k) {
if (k.keyName == "Escape") {
win.close();
}
});
var leftPressed = false;
var getColor = this.getColor = function (k) {
_this.options.backupLocation.length = 0;
if(k.type == "mouseup"){
leftPressed = false;
}else if(k.type == "mousemove"){
if(leftPressed == false)
return;
}else if(k.type == "mousedown"){
leftPressed = true;
}
var point = [k.clientX, k.clientY];
if(!_this.isInCircle(point)) return;
var thisColor = _this.getColorFromPoint(point);
thisColor = colorPicker.RgbToHsb(thisColor);
thisColor[2] = win.slider.value;
thisColor = colorPicker.HsbToRgb(thisColor);
colorPicker.copyArr(_this.outputColour ,[
thisColor[0]/255,
thisColor[1]/255,
thisColor[2]/255
]);
_this.setCursorLocation (_this.outputColour);
_this.setDefaultValue(win);
win.editor.gulu.color.notify("onDraw");
}
this.colourCursorGroup.addEventListener ('mouseup', getColor)
this.colourCursorGroup.addEventListener ('mousemove',getColor)
this.colourCursorGroup.addEventListener ('mousedown',getColor)
}
colorPicker.prototype.isInCircle = function(point){
return Math.pow (point[0]-this.size, 2) + Math.pow(point[1]-this.size,2) <= Math.pow(this.size,2);
}
colorPicker.prototype.getColorFromPoint = function(point){
var transformedPoint = this.transformPoint(point);
var hAndS = this.getAngleAndLength(transformedPoint);
return this.CoreGetColorFromPoint(hAndS[0],hAndS[1]);
}
colorPicker.prototype.getAngleAndLength = function(point){
var angle,length;
var x = point[0],y=point[1];
length =Math.sqrt( x*x + y*y);
angle = Math.atan2(y,x)/Math.PI*180
if(angle<=0)
angle += 360;
return [angle,length/this.size];
}
colorPicker.prototype.transformPoint = function(point){
var x = point[0];
var y = point[1];
return [x-this.size,this.size-y];
},
colorPicker.prototype.CoreGetColorFromPoint = function(h,s){
var i;
var f, p, q, t;
var r=1,g=1,b=1;
var v = 1;
if( s == 0 ) {
v = Math.floor(v*255);
return [v,v,v];
}
var originHeight = h;
var condition;
var tempI
if(originHeight<45 && originHeight>=0){
i = 0;
f = originHeight/90;
}else if(originHeight<120 && originHeight>=45){
i = 1
f = (originHeight-45)/(120-45);
}else if(originHeight<180 && originHeight>=120){
i = 2
f = (originHeight-120)/(180-120);
}else if(originHeight<220 && originHeight>=180){
i = 3
f = (originHeight-180)/(220-180);
}else if(originHeight<275 && originHeight>=220){
i = 4
f = (originHeight-220)/(275-220);
}else if(originHeight<320 && originHeight>=275){
i = 5
f = (originHeight-275)/(320-275);
}else if(originHeight<360 && originHeight>=320){
i = 6
f = (originHeight-320)/(360-320)
}
p = 1 - s ;
q = 1 - s * f ;
t = 1 - s * ( 1 - f );
switch( i ) {
case 0:r = v;g = t;b = p;break;
case 1:r = v;g = 0.5+t/2;b = p;break;
case 2:r = q;g = v;b = p;break;
case 3:r = p;g = v;b = t;break;
case 4:r = p;g = q;b = v;break;
case 5:r = t;g = p;b = v;break;
case 6:r = v;g = p;b = q;break;
}
return [r,g,b];
}
colorPicker.copyArr = function(defaultArr,otherArr){
while(defaultArr.length!=0){
defaultArr.pop();
}
defaultArr.push(otherArr[0]);
defaultArr.push(otherArr[1]);
defaultArr.push(otherArr[2]);
return defaultArr;
}
colorPicker.HexToRgb = function(hex){
var ccolorhex = hex.toString(16);
ccolorb = parseInt(ccolorhex.substr(-2), 16);
ccolorg = parseInt(ccolorhex.substr(-4).substr(0, 2), 16);
ccolorr = parseInt(ccolorhex.substr(-6).substr(0, 2), 16);
return [ccolorr / 255, ccolorg / 255, ccolorb / 255];
}
colorPicker.RgbToHex = function(rgb){
var a=(rgb[0]*255).toString(16);
var b=(rgb[1]*255).toString(16);
var c=(rgb[2]*255).toString(16);
if(a.length!=2){
a="0"+a;
}
if(b.length!=2){
b="0"+b;
}
if(c.length!=2){
c="0"+c;
}
return (a+b+c).toUpperCase();
}
colorPicker.HsbToRgb = function(hsb){
var rgb = [];
hsb = [hsb[0], hsb[1] / 100, hsb[2] / 100];
for (var offset = 240, i = 0; i < 3; i++, offset -= 120) {
x = Math.abs((hsb[0] + offset) %360 - 240);
if (x <= 60) {
rgb[i] = 255;
} else if (60 < x && x < 120) {
rgb[i] = ((1 - (x - 60) / 60) * 255);
} else {
rgb[i] = 0;
}
}
for (var i = 0; i < 3; i++) {
rgb[i] += (255 - rgb[i]) * (1 - hsb[1]);
}
for (var i = 0; i < 3; i++) {
rgb[i] *= hsb[2];
}
return [rgb[0], rgb[1], rgb[2]]
}
colorPicker.RgbToHsb = function(rgb){
rgb = colorPicker.parseColor (rgb);
rgb = [rgb[0]*255,rgb[1]*255,rgb[2]*255]
var hsb = [];
var rearranged=rgb.slice(0);
var maxIndex = 0;
var minIndex = 0;
var tmp;
rearranged.sort(function(a, b) {
return a - b;
})
for (var i = 0; i < 3; i++) {
if (rearranged[0] == rgb[i]) minIndex = i;
if (rearranged[2] == rgb[i]) maxIndex = i;
}
if(rearranged[2] !=0 ){
hsb[2] = rearranged[2] / 255;
hsb[1] = 1 - rearranged[0] / rearranged[2];
if(hsb[1]!=0){
hsb[0] = maxIndex * 120 + 60 * (rearranged[1] / hsb[1] / rearranged[2] + (1 - 1 / hsb[1])) * ((maxIndex - minIndex + 3) % 3 == 1 ? 1 : -1);
hsb[0] = (hsb[0] + 360) % 360;
}else{
hsb[0] =0;
}
}else{
hsb[2]=0;
hsb[1]=0;
hsb[0]=0;
}
return [Math.round(hsb[0]), Math.round(hsb[1] * 100), Math.round(hsb[2] * 100)];
}
colorPicker.convertHsbToKulerHsb = function(hsb){
var originHeight = hsb[0];
var s = hsb[1], b = hsb[2];
var i,f,h;
if(originHeight<30 && originHeight>=0){
h = (originHeight-0)/(30-0)*(45-0)+0;
}else if(originHeight<60 && originHeight>=30){
h = (originHeight-30)/(60-30)*(120-45)+45;
}else if(originHeight<120 && originHeight>=60){
h = (originHeight-60)/(120-60)*(180-120)+120;
}else if(originHeight<180 && originHeight>=120){
h = (originHeight-120)/(180-120)*(220-180)+180;
}else if(originHeight<240 && originHeight>=180){
h = (originHeight-180)/(240-180)*(275-220)+220;
}else if(originHeight<300 && originHeight>=240){
h = (originHeight-240)/(300-240)*(320-275)+275;
}else if(originHeight<360 && originHeight>=300){
h = (originHeight-300)/(360-300)*(360-320)+320;
}else{
return hsb;
}
return [h,s,b];
}
colorPicker.isType = function(content,type){
return Object.prototype.toString.call(content) == "[object "+type+"]"
}
colorPicker.isRgb = function(rgbArr){
if(!rgbArr)
return false;
if(!colorPicker.isType(rgbArr,"Array"))
return false;
if(rgbArr.length != 3)
return false;
for(var i =0,len = rgbArr.length;i<len;i++){
if(rgbArr[i]>1 || rgbArr[i]<0)
return false;
}
return true;
}
colorPicker.isLargeRgb = function(rgbArr){
if(!rgbArr)
return false;
if(!colorPicker.isType(rgbArr,"Array"))
return false;
if(rgbArr.length != 3)
return false;
for(var i =0,len = rgbArr.length;i<len;i++){
if(rgbArr[i]>255 || rgbArr[i]<0)
return false;
}
return true;
}
colorPicker.isHex = function(hexStr){
if(!hexStr)
return false;
if(!colorPicker.isType(hexStr,"String"))
return false;
if(hexStr.length != 6)
return false;
var arr = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"];
var isHex = true;
hexStr = hexStr.toUpperCase();
for(var i=0,len = hexStr.length;i<len;i++){
if(this.arrayIndexOf(arr,hexStr[i]) == false){
isHex = false;
break;
}
}
return isHex;
}
colorPicker.isShortHex = function(hexStr){
if(!hexStr)
return false;
if(!colorPicker.isType(hexStr,"String"))
return false;
if(hexStr.length != 3)
return false;
var arr = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"];
var isShortHex = true;
hexStr = hexStr.toUpperCase();
for(var i=0,len = hexStr.length;i<len;i++){
if(colorPicker.arrayIndexOf(arr,hexStr[i]) == false){
isShortHex = false;
break;
}
}
return isShortHex;
}
colorPicker.isHsb = function(hsbArr){
if(!hsbArr)
return false;
if(!colorPicker.isType(hsbArr,"Array"))
return false;
if(hsbArr.length!=4)
return false;
if(hsbArr[3] !="hsb") return false;
if(hsbArr[0]>360 || hsbArr[0]<0) return false;
if(hsbArr[1]>100 || hsbArr[1]<0) return false;
if(hsbArr[2]>100 || hsbArr[2]<0) return false;
return true;
}
colorPicker.parseRgb = function(inputValue){
return inputValue;
}
colorPicker.parseLargeRgb = function(inputValue){
var arr = [inputValue[0]/255,inputValue[1]/255,inputValue[2]/255];
return arr;
}
colorPicker.parseHex = function(inputValue){
return colorPicker.HexToRgb("0x"+inputValue);
}
colorPicker.parseShortHex = function(inputValue){
inputValue = inputValue.toUpperCase();
var hex = "0x"+inputValue[0].toString() + inputValue[0].toString()
+inputValue[1].toString() +inputValue[1].toString()
+inputValue[2].toString() +inputValue[2].toString();
return colorPicker.HexToRgb(hex);
}
colorPicker.parseHsb = function(inputValue){
var hsb = [inputValue[0],inputValue[1],inputValue[2]];
return colorPicker.parseLargeRgb(colorPicker.HsbToRgb (hsb));
}
colorPicker.arrayIndexOf = function(arr,str){
for(var i=0,len = arr.length;i<len;i++){
if(arr[i] == str)
return true;
}
return false;
}
colorPicker.arraysEqual = function(a, b) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length != b.length) return false;
for (var i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
colorPicker.prototype.initSetting = function(){
this.img ="\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x01\x04\x00\x00\x01\x04\b\x06\x00\x00\x00\u00CE\bJ\n\x00\x00\x00\tpHYs\x00\x00\x00\x01\x00\x00\x00\x01\x018\"\u00F4@\x00\x00\x00$zTXtCreator\x00\x00\b\u0099sL\u00C9OJUpL+I-RpMKKM.)\x06\x00Az\x06\u00CEjz\x15\u00C5\x00\x00 \x00IDATx\u009C\u00EC\u00BD{\u00BCeWU&\u00FA\u008D\u00B9\u00F6>\u00E7T*\x02I\u0088\x04A\u00D4hLT\u00BC\b-\bb\x0B\x11[\u00EDn\x04\u00FBj\u00BC\u00F8k\x105\u00B7\u00EDn\x1Ft\u00B7^_\r\u00AD\x17\u00C5\u00A6\x01\u00DB\u009F\u00B7\u00B1\x1BE\u00BD`@\x1E\"\u00D7\u0080\u00A1\u0095\u0097B\b\x10\f\x1D\b\t\x18\b\x04\b\u00AF<I\u00AAR\u008Fs\u00CE\u00DEk\u008E\u00FB\u00C7\x18\u00DF\x18c\u00EDS\u0095\u00A4\u008A$\u0095\u0084\u009ApR{\u00EF\u00B5\u00D6\\s\u00CD5\u00C7\x18\u00DFxN\u00C1\u00F1v\u00AFn\u00AA\u00FBw\u00A3\u00DF\u00F8\u00B5\u0090\u008F}\r\u00B6\u00AE}\u00D8\u00E6\u0081[\x1E\u00D2\u00C6\x1B\x1F\u00DC\u00967\u009E\u00A6\u00BA\u00E7T\u00F4}\u00A7\u00E88\u009C\u008C>\u00DEO\u00C6[\x06\u00E8\u008D\u0090\u00C5-h\u008B\u00BD\u0090\u00BE\x1F2\u008E\u00806\x00\x02@\x01\b\u00BA\u009C\x00\u009D\u00ED\u0086\u00B6\x07`9<\x10\u0083\u00EC\x1E\u0081\u008D\u00BD\u00E3\u00AC\x7F\u00B1\u00C9\u00EE\u009B\u00BA|\u00C5\r\u00D2\u00BE\u00F2Zi\u00A7~a~\u00E2\u00FD>'\u00F7\u00FB\u00AAk0\u00FF\u00FAO\u008F\u00FA\u00D0O\u00CD\u0086]\u00FB\u008F\u00F1\u0094\x1Co_B\u0093c=\u0080\u00E3\u00ED\u008E\u00B5\u00DE?\u00F5 \x197\u00BFu\\^\u00FE\u00F0\u00BE\u00F5\u00C1o\u0092\u00C5\u00CDg\u00E9\u00F2\u008630\u00DEx\u009An\u00DE \u00C3\u00E2F\u00A8\u00DE\x02\u00C56\u00A0\u008A\u00D6gh\u00AA\x10\u0085\u00D19:\u00A0\x02h\x07\x7F\x12\x00\u00D2\u00FD\u008B\u008A\u00FD\u00A2\u0080\u00EA\b\u0085@\u00B4\x01\u00BDC\u00BA\x02*PQ(\x06@;D\u00ED\u00B2\u00A5\u00AE\x01r?\u00C8\u00FCd\f\u00B3Su\u00B9~\u00EA\u00B5m~\u00D2U2\u00BF\u00FF\u0095\u008B\u00FB\u009F\u00F9\x0F\u00EB\x0Fx\u00CC\x152?\u00E5r\u0091\u00AF\u00B9\u00EEX\u00CD\u00DD\u00F1v\u00C7\u00DBq\u0086p\x0Fl\u00AA\u00B7<P\u00F5\u00EF\x1F+\x07\u00AEy\u00F4x\u00F0#\u00DF\u00DE\u00FB\x17\x1E%\u00DB\u009F9M\x16\u009F\u00C4\u00B8\u00BC\x193l\x03\u00B2\u0084\u008Cpb\u00E7kT@\u00C4\u0088\u00BB7\u0088*T\u009DrU\u00ED\u00AC`\x00~\u009D\u00AA\u00FDu\x01\u00A4\u00F9\u00B5\u00A3\u00FD\x16\x03r\u00F4\u00D0\u00F3^\u00DD\u00FBU\x00\u00AD[\u00BF\x12\u00C7\x05\u00A36\u008Ch\u00C0\u00B0\x1B\u00BA\u00FB\u00EB\u00D0\u00D6\u00BE\u00E1\u00DA\u00E5\u00C6)\u0097\u00CEv\u009D\u00FE\u00FE\u00F9i\u00DFr\t6\u00BE\u00FDb\u0091\u00AF\u00BC\u00F1\u00AE\u009E\u00CB\u00E3\u00ED\u00C8\u00DAq\u0086p\x0Fh\u00AAW?\f\u00CB\x1B\u009F\u00B8\u00DC|\u00F3\x13d\u00F3\x0B\u00DF\u00D5\u00C7\u008F\u009E\u00A1\u008B\u00ABd\u00B6}=\x1A\u0096\x00:\u00D0;T\x05]\x14\u00A2\x02\u0081K\u00FF.\u00DE\u0087N\u00FA\u0094n\u008C\u00A1\u00BB4\x07\x0Fk\u00B3\u00EFPC\tD\x07\n'x\x04\x03a\u00BF\u0093\u00EB\x01h\u00CF\u00AF\u00810\u00BA3\rm\u00CE|$\x18\u008F\u00F5!q\u00ED8;\t\u00F8\u008A\u00B3\u00B4\u00AF\u009Fq\x15v=\u00F8\u00A2\u00F9\u0083\u00FF\u00F1;\u00E5\u00FEg\u00BDC\u00DA\u00E9\u00D7\u00DCi\u0093z\u00BC\x1DU;\u00CE\x10\u008EAS\u00DD{\u00A2\u00F6\x0F=i{\u00EB\u00B2\u00EF\u009Fm_\u00FA\u00BD\u00D8\u00FE\u00F8\x19}\u00FB\u0083\x10=\x00\u00C1\x12\u00AD\u00CF\u00A1:\u00A2\x05\u00B1\x1ACX\x11\u00D2.\u0099\x1D\x11\u00A8B\u00A5R- *\u00E8\u00DD~\x13%\x1Ahf-P\u00A3V\u00FB]\u0092\th^\x0F\x15\u00A8vg\"\u00C9\x15\u00B4\u00E7m\u00D0\u00ADoU\u00C4qh2\u00A4`4\u0093\u008B\x00\u008C\u00E2\u00C0\u00C4m\x16\u00BB\u00BF\t\u00FD\x01_\x7F\u0095\u00EC\u00FE\u00A6\u00B7\u00AD?\u00F8\u00F1o\u00D6]\u008Fz{\u009B\u009D\u00BC\u00EFN\u0099\u00F0\u00E3\u00ED\x0E\u00B7\u00E3\f\u00E1nj\u00DA?\u00FD\u00D0qq\u00D9Se\u00EB\u00A2\u00A7\u00E8\u00F8\u0091'\u008C[\u0097\u00AF\u008B~\x1Es\u00C2\u00F3\x15\t\u008F.P'(C\x02\u00C6\x10H\u00D7\x00\u00D0\u00D4\u00E8L\x0Eq\u00B9\u00DD\u00B4\x15\u00E9,\u00E8\u00DD\u0098\u00CC\u00E4\u00FC\u00EE\u008B@\x04\u00E8\u0085!t^\u00DF\u00ED\x18\x00\u00ED\u008A\u00D6\u00A7\u00F72\x1E\u00D4\u00FC\x11\x12\u008D\u0088#\u0097\u00E8\u00B32\x1Ax\x1F\u00CA\x05(\u0086:D\u00A0P\u008Cr\x1A\u00DA\u00C9\u008F\u00D8\u00D2\u00DD\u00DF\u00F8N|\u00E5\u00B7\u00BDq\u00FE\u00A0\u00EF~\u0083\u00B4o\u00F8\u00EC\u0091\u00CF\u00FA\u00F1v\u00A4\u00ED8C\u00B8\x0B\u009B\u00EA\u00E7\x1F\u00A6\u00E3\u00C5\u00E7\u00F4}\x17\u009E#\u00FD\u0083\u008F\u00D1\u00ED\u00F7\u008B\u00E8\x01\u0093\u00EC\nhs8n'\x17\u00E3\x1E\u0092\u0088*J \u00BD\u00FA\u00BFBf\u00A1I`\u00D3\x01\x14\u0086\u0082f\u00EACg\x07\u0085`A\x04\u0081r\x03\u00DA\"4\f\u0090\u00DAs\u00BC\x13\x06\u00B4\u008A0t\u00B5?8\u0083\tS\u00A6\u00DB6\u00AA:\"9|\x15H\x17ho\x18e\x06}\u00C0\u00B7h\x7F\u00C0#\u00FE~v\u00D2\u00E3^'\x0Fy\u00FC\u00EB\u0086\u00F97\x1DW-\u00EE\u00A2v\u009C!\u00DC\u00C9M\u00F5\u00B3'a\u00FC\u00F0\u008F\u00EA\u00E6\u00DB\u009F\u00DE\x17\u0097<\x1E[\u0097\u0089\u00C8^4\b:\x16\u00C1\fr\u00F5K\u00BD\x18\u00E8-\u00A9-\u0088L\u00F28\x7Fr\x04\u00DE\u0082Z\u0091\u00A8\x02T\x07\u00F8\u00D1\x11\u0082\u00A3\x05\u00A2\x06\u0081\u00A2Ol\x11\u00E2\u00E8\u0081\u00D7\u00B6D/ac\u00D0\u00B0E\x06_\u00F1c\u00D5\u00D6`*\x03\u009F\u00CB\u00FB\u00D7\x0E\u00A09\u00CFQc\x01\u009C\x0F\x01\u00B4O\u00E7B|.\u0094\fMf\x18\u00BF\u00E2\u00E1:\u009C\u00F4\u00A8w\u00E3k\u00BE\u00F7\u0095\u00C3\u00C9\u008F\u00FAs\x19\u00CE\u00B8\u00F9(_\u00D5\u00F1v\u0088v\u009C!\u00DC\tm\u00B9\u00D8\u00D7\x06\\\u00F6\u00A4\u00AE\x17\u009E\u00DB\u00B7/~j\u00DBz\u00D7\u0086\u00F4\u00BD\x10\x1D\u00FD\u008CJP(\x04\u00CC/~\u0096\x16\u00A3\\X\u00FC\x10\u00C4j\x12Z\x0F\u00CF\x10T\x12\u00E6\x17\u00B8@\u00C2\u00A2\u00B4\u00AF\u00E8\u00A2\u00A3\x10\u00B28C\x18\x15\u0082\x06\u00A0%3Pu\u0095@\u00CAE\u00C0\u00A8H5$\u00DC\u0096\u008A6J\x1D\u0082\u00DD{\u0082*\u00A8\na\u00C2\x10\u00BAx\x7F\u00BD2GI\u00A68\u008EP\x05\u00C6a7\u0096\x0F|\u00DC&N\u00F9\u008E7\u00AC\x7F\u00ED\u00D9\x7F\"\u00BB\x1F\u00FFv\x19v\u00AD\x18*\u008E\u00B7#m\u00C7\x19\u00C2\u0097\u00D0T\u00AF:\u00B5/\u00DE\u00FFS\u00BAx\u00C7O\u00CB\u00E2\x1D\u00A7k\u00FF\x14\u009A*\u0080\u00D1\u008CpA\x01+\f\x01HB\u00A8\f\u00A1\u00FC^\u00ED\u0083\u008C\x0F q\u00AA\u00EB\u00E5\u00EA\u00E7E\x0Fa\u00C9\u009F\u00EA\x0ER\u008D{\u00D0T/(\u00DE\u00DD0!$\u00BC\u009E\u00D0~\u00CA\x10\u00EC\u00B3t\u0089\x01\u00F6^\u00EF\u00EF\x06F\x1F\x1F\u008D\u008D\x02^o\u00F7\u0094 \u00F2|&\x1Bw\u00CE\x07c\x1Fv\u00B8H\u00F9\u00EC\u00BD\u00C5\u00B9\u00DBk\x0F\u0081<\u00F8\u00B1W\u00B7\x07=\u00F6\u00A5\u00B3\u00D3\x7F\u00F8\u00FF\x15\u00F9\u009A\x1Bn\u00E7\u00D5\x1Do\u0087i\u00C7\x19\u00C2Q\u00B4\u00BE\u00FC\u00C0#\u00D1/|\u0096.\u00DE\u00F64]\u00BC{\u00A3\u00F5\u00BD\x10\u008CyB\u00D1\u00A5\u00A7\x17\x02\u0080\x13\x1C!\u00F5\u00EA\x1B\u00A8\u00C4\x1A\u00FC\u00A4X\x12\x01wA\u00DA\u00EF\u00AA\u008A\u00A6\u00D4\u00CB{\u00DC~\u0095\u00A1H\x10\u00BA\u008D\u0083h$\"\x18h_\u00D06}\u0086\x18SJx\x19WPM\x19\u00BBT\u00A3\u00A3\x1B5\u009B\u00E6\u00B9\u0081VB\r\u00EAq\u00ADj2\u00AFP[\u00F8\u00FC\x00z\u00D7psNb*`\u00F6\u0086e\u00DB\u0080\u009E\u00F4\u00E8M<\u00F81\u00AFY\u00FB\u00FA\x7F\u00F1\u00DF\u00E4+\x1E\u00F7\x01\x1CoG\u00D4\u008E3\u0084;\u00D8T\u00AF\x17\u00E8U?0.\u00DE\u00F8K\u00B2x\u00D7\u00D9\u00B2|?\x04K\u00A4B\u008D)\u00D1R\x02\u00F6\x02\x0B\u00C8\x10&F8\u00CD\u00F3\u00E3^\u00E9\x19\u0098\u00A0\n\x12\u00D2\nC\x00\u00DC\u0095\u0088\x1E\u009E\u0087\x1D\u00CC\b\b\u00E9\u00CE\u00A0\"\u00C2|\t\u00C9\u00EF\u00E3\u0090\x12\u00B0\x14\u0092\u00DC\u00A5}Hz\u009D0\x1D%R\u00E0e\n\u00A0\u00B7\x15\u00C2V\u008F~\u009C\"\x04u\u00DD\u00C5\u00AE\u0097\x00TfG\u00F0>\u0091\u00F7\u0097\u008E\u00F0\u00AELl1*\u00E8\x1E\u0086\u00BD}\u00E27b8\u00ED\u00BB\u00FF\u00AE\u009D\u00FE/^4<\u00F0\u00B1\x7F#\u00ED~\u0087\u009A\u0091\u00E3m\u00A5\x1Dg\b\u00B7\u00D3Tu\u00D0\u00E5[\x7Fd\u00EC\x17\u00FC\u009A.\u00FE\u00FA\x11\u00D2?\u0085\x06E\u00EB\u0087\u0080\x00U\u00AA\x020\u0082.Q<\x13\u0098\u008E\t\u00EC\u008F\u00F3\u00E1\f\u00A1\u00C2w\u0091 \x10\u00B3\u00C0\x17\u0086\u00D0\u008D!Xs\u0086\u00B0\u00AA\u009E\u0094\u00E1I7\u00D7^\u00C4\x0F\u0088\x13\x1D\x10\u00FF\u00D2\u00E2OF\x116\fu\x1BF\u00C0w\u00F7Hja\b(\fI\x0BC\x0B&\u0092\f&\x18\u0082O\u0090I})\u00FCU\u008B'%\u00BD/\u00DA\x01\x14\u00B4\u0090\x0F\u00E8j\u0084\x02\x18\u00E7PUl\u009Ep\n\u00F0\u00E0\u00EF\u00B9l\u00FEuO~\u00FE\u00EC\u00C1\u00FF\u00E4/d8\u00A5@\u00B9\u00E3m\u00B5\x1Dg\b\u0087i\u00AA\u00D7\x0F\u00D0\x0F?M\x17\u00AF\x7FN_\u00BE\u00F1\u00AC\u00A1_\x0B\u00C8\u00B6/H\u0087\u00E0h\x00z\x12+\u00CA\u0084V=~\x07\u00E4OF\u00D0\x1D\u00FE\u008AN\u00AE\u009E\u00C2k0\u008F\u00C0m\x06\u00BD\x10g\u0091\u00DE\u00ADx!v\u00A8,D\x1C\x107\u00D8\u00F9u\x18w\u00BA\x12\x15\x19i\u00B8\u00D4\u00B8n\u00E2\u00FA\u00F4F\u00F5\u0081\u00AAC\u00C48L\u0090\u008D\u00DB\x1DbNJ|D\x18:]\u00B5\u00D0\x16@H\u00DCf\x10h\u00896\x14\u00F5\u00F9%##\u00E3\u0099\u00DC3U.\u00ED\u00C0b~\n\u00F0\u0090\u00B3\u00AF\x1C\u00BE\u00FE\u009F?o\u00F8\u00EA\x1F|\u008D\u00C8\u00C9\u00C7\x19\u00C3!\u00DAq\u0086\u00B0\u00D2TU\u00FA\u00F8\u008E\x1F\u00D6\u00F1\u0082\u00E7\x02\u00AF\u00FDf\x19o@\u00EB\u00DB)\u00C6}\u00D1\u0086qK\u00A6D\u00DC\u0082Z\x14\x13\u00A3Xa\b\n\x14(\\\u00AC\u00ED\u00AB\u00AF\u00A3\u00B8(\u00ABI\u0081\u00C4%+\f \rx\u00E5F;`u\u00D1\u00F3\u00CB\u00B8\u00C4\u00EFA\u00DBF\u00C0w\x12\u00F1\x04\u00D9PUQg\b9\u008E\n\u00E3\u0095\u00FF\u00E9S7\u00A4B\u0092\u00D0\u00E3\u00F9z\u00AA,Z\u0080E\u00D7\u00F0b\u00D49RG(D:\u0093\u00A0\u00AA\x1A}\u00C9\u0081i\x03\u00C6\u008E\u00ED\u00F9\x03\u00D0\x1F\u00F6O?2\u00FF\u00FA\x1F\u00FC\u008D~\u00EA\u00F7\u00BD~m\u00D7)\u00C7U\u0089\u00D2\u008E3\u0084\u00D2\u00C6\u00F1\u00D2\u00B3u|\u00D5\x0B\u00B4\u00BF\u00E1\u00D1\rW\u00FB\u00E2\x1E'\u00BA\u00B2-22\x04\u0087\u00CFe\u0091N\x18\x02m\x00\u00AB\fA\u009C!t\u00B1K\u008B\u00B4\u009B\u00BC\x12^W \u00B3\u00B2\u0083\u00C2\x10\u00E0\u00A7u]\u00F5: u\u00ED\u00C9\u0083\u008A\u008F\u00DD\u00CE\u00A9\u00C6;\t\u00D5\x00\u00888\u0084\u00A2\u00E2\u0098+\x12\u0080v'J\u0089\u0090\u00A2\u00C9\u00BD\u009COtOz\u0092\x12c@\x04!\u00D5\u00CB\u0081>\u0091\u00F0]\u00BD\u00D7C1\x04\u00D1D!=\x19\u00E4\x04\u00BA\u00F4\u00B6\u00C2\x10`>RiP\fX\u00CENA\u00FF\u00EA\u00EF\u00BF\x04\u00DFr\u00CE\u00AFl<\u00E8\u00C9\x7F\u0087\u00E3\r\u00C0q\u0086\x00\x00\u00E8\u00FA\u00E13\u00FB\u00F8W\u00BF\u0083\u00EDW=\u00B9\rW\x01\u00D8\u0086\u00D0\u00FAM\x1D\u00BEH\u00D2$\x14\u0093`\u00D5\x18\x16m\u0095\bU\u00A0\u00BDO\u0088\x15\u00B1\u00C8W~\u00B3;\u00D9OaX+\x04\x15\u0092\u00BE\x01\u00BA\u00F4\u00F3\u008C \u00D3\u00A2O\u0082\u00F3{\u00D7\x1B\x15\u00EB?\x01\u00800\nq\x15U\u00D0\u00C0X=\x10\u00B4a\u00DC\u00CE\u00B3P\u00B5\u0089\u009F\x14h+\u00AAC\x18]\u00FBT\u00AD\u00D22\u009F\u0093x\x05m\u00C6a\u00C2\u00FD\u0088dj<\u00DE\u0089X\u008C\u00D9H\u00A7\u00E7\x04\u00C1\u00C8\u00CC%\u00DA\u00B0\u00BD\u00FBk\u00A0_}\u00F6\x05\x1B\x0F\u00FF\u00F1\u00FFKN~\u00C2G\u00F1e\u00DE\u00BE\u00AC\x19\u0082\u00EA\u00A7\u00EE?\u008Eo\u00FB\r\u00ED\u00AF\u00FD\u00B9\u0086w\u00CE\x1B:\u0080\x11\x1A\u00BEr$\u00E1\u00C3\u008Cq\u0093\u00EC@\u0097\u00E0i\u00F4\u0092\u00C95\u00AB\fbjq/\u00FFR\bWi]\u00FB\u00AA\u00F7\u008C\u00B1\u008B\u00DF\u0097H\x05\u00A0:0\u0085\u00CE\u00DEw\u00B9\u0098\f\u00A5Q(ke\b+\u00CF0\u0089E :\u00C8\u0098\x04E\u00BA\nWU\u0098I\u00CE\u0084\x16\u0086\u0080\u00D2\x1F\x19\x02\u009F\u00BF\u00CC\x17m#S\u0082'\u00F4\u00D0\u00B8\x06\u00F5\u009C\u00BE\u00C2\x10 \u00AE\u00D6\u00D0k\u00D3\x11\x01Vj\u0091\u009D#\u0080\u00F1\u00E4oY\u00C8\u00D7\u00FD\u00E0\u00EF\u00AF}\u00EB\u008F?W\u00D6\u00CF\u00DC\u0083/\u00D3\u00F6e\u00C9\x10\u00C6qK\u009A\u00BE\u00ED\x19\x1D\x7F\u00FE\u00C2\u00AEox\u00D0L\u00F6B\u00A5\u00E7\"\u00EE2\u00CD1\x00\u00A0\u00AD\u00A1\u00FB\u00E2m!\x11\u009DI8\u0094\x15\x14\t*\u0098\x12\u00BF\x1B\x00W\u00E1;\u00D5\x07\x1DI\\\u00F6\u00BB\x14D\u0090\x12\u00D8\x16\u00B8\rK]\u00EFoa|c\u00EB\x0E\u00C7\x05\u0088 \u00A1\t3q\u0084\u00DE\u00BA\u00A07u\u00C8\r\u0084[\x10\u0080\u00D6\u00EC\u00C4\u009A\x13\u00A1\u0080\u00A2\x01}\u00CCHG\u0097\u00EE4\u00A70@s5\u0089\u008A\f!\u00DC\u008C\u0095!`\u00FA\u00FC6^\u0081\u008EN\u00D4\u0095\u00A1\x16\u00DBF\f\u0093Lal\u0089J\u0098\u00A7\u00C1\u00CC\u00CB`\bD\b\u00D3\u00F7\u00B1\u00A5s\u00E8iO\u00BC\u00AE\u009Du\u00CE/\u00EB\u00D7\u00FE\u00D0+66N]y[\u00F7\u00FD\u00F6e\u00C7\x10\u00B4_qV\u00D7\u00BFx\t\u00FA+\u009E\u0088\u00E1\u0093\u0096[P\u00CD\u00EC\u00AB\b .\u00AC\x04M\u00A3\u0095QVD\u00F0\u00D5\u00A0\x1E\u0094\u00EB\x15\x0E\u00BD\u00D9w1pS\x1D \u0095\u00C6}\x0FaK\u0088\u00CF2\u00FDL\u0094P\u00A5\u00AA\u009FP\u00DDv\t\u00DF\u0093@\u00A4{\x05%WM\u00EATD\u00CA4\u00C7\u00C2\u0080*\u00B1\u00AAI\u00F0k\u00D9\u00A8\u00DA\u00D4\u00FBs\u00CE\u0098CAU\"]\u00B45_B\u00A6\u00CF\n\u0089\u00E8E\x16{YuA\u00C2]\u0090;T\u0096.1\u0096L\u00E8B0>\u00DE&\f\u009B\u0081Z\x06\u008C\u00EDT,N\u00FF\u00A7\u00EFX\u00FF\u00DF\u009E\u00FEo\u0087\x07=\u00E9J|\x19\u00B5/\x1B\u0086\u00A0\u00BAw\u00DE\u00FB\x1B~u\u00C4\u009F>[\u00E5]\u00EB\x03\u00B6\u00C3\x1C\u00B8#\u00E30\u008C\u0081\u00E9\u00DF?$CpW\u009C\u00C6\"n\t\u00E3\u0081)C\u00A1\x1B/\u00EE\u00B3B\u00E0\u00BCn\u00C2\u0088n\u00DB\u00C08\u00D5\u00F7ih3\u00C2\u00AB\b\u00A5\x05\u00A1\u00EEd\b\u00E1Jt\u00A9)u\f\u00D5]*\x00F\u00DE{\u0088\u00EB\u008C!\u00FA)\u00E5>\u00BC\u00FF\u00E1\x19B\u00CE\x19B\x05Xa\b\x13\u00E6\x027\u00A4\u0096\u00E7\u0087\u008F\u00A9\u0097w\x13\u0088$\x19B\u00A83\u00EC\u00BB\x0B\u00BA[U\u009A\u00A3\u008DV\u00E7s\\G\u00EFKl\u009D\u00F2\u00AD[\u00C3YO\u00FB\u00ED\u00B53\u00FF\u00E5\x7F\u0091\x13\u00BFz\u0081/\u0083\u00F6e\u00C1\x10T/}\u00E4v\x7F\u00F9\u00CB\u00C6\u00FE\u00DAG\u00CCf7`\x00Cy:F\u00E4\u0082\x05\x17q\r\u00ACA\u00EA\u00C6\u0094\u0096\u00D5\x16\u00C0\f=\u00E1a^3\u0089C\u00C0\u00E4z\u0088CeU\u00FBL\u00C8\u00DF\u00F34\u00A9\u00D7y\u00F4\u00A0z\u0090S\u00B5\u00D8\x1Bn\u0097B\u00D4\u0085\u00DFh\u009E2\u00D1\u00C5\u00EB\u00DC\u0090\u0089\x157\u00E0\u00A4\u00C2\x11k\x1C\u0084-A\x10\u00B6\x04\u009B\u0080D'%8\u00A9\u00D7{\u00D5\u00FB\x1F\u00C2\u00E3\u00C0\u00FB'\u0093J\u0086Y\u00F3\x1Bb\u00DE\u00BB\u00E4\u00C2-\f!\u00CE\x1B=\u009C\u00DB\u009F\u00C5\u0090P\u00CE\u0087M\u00A33\u00A7C1\u0084\u00CE\u00C7j\u00E8\u00B2\x0B\u008B\u00D3\u00FF\u00D9e\u00B3o>\u00E7'\u00D7\u00BE\u00EEG\u00EF\u00F3\u00A1\u00D0\u00F7i\u00860\u00EA\u00FE\u00D9\u00F6\u00F2u\u00FFq\u00D9^\u00F6\x1C\u0095w\u00CF\u009B\u008C\u0098\x01\u0098\u00C1\x18\u0082\u00D0\u00D4\u00A6\f4*R\u00A4\"\x05\u00B6j4\u00F3\u0096\u00C4\u00E6zt@\u00D0\u00DB20\u00D6E_\u00EE;1\b\u00A2\u00F4#y\u0080\x16\x7F\u00F6\x13\u00FD!\u00FA\\5\u00E4\u00ED0\u0084N\f\n-$(\x0F\u00D1\u00F7_\u0087/\x15\u0095T\u00AF\u0087GlV\x064E.+\u00DF\u00CB_\u00CD\u00AF\u00A8\u00E3\\5\u00BE\u00F2\x1D\u00C5\u00FD\u0082\u00D9H\x06$\u00D1\u00C8\x19\u008Cg\u0085!\u00F8\u009C\u00D7w\x156\u0099nk\x01\u00CEl8\x07\u00E8\u00E6\u00A1\u00E8\u00DA\u00B0}\u00CA7.\u00E4\u00E1?\u00FA\u00BC\u00F5\u0087\u00FF\u00EB\u00FF,\u00EB\u00A7-q\x1Fm\u00F7Y\u00860\u008E\u0097\u009F\u00B1\x1F\x7F\u00F6\u008A\x03\u00F2\u00F2\u00EFX\u00C3u\x18\f\x00`\x06\u00C5\f\x02\u00F3F{\u008B\u0090W\u00EA\u00F0\u0085\u0088\u008B\u00D4?\x1CC\x00\x1C)L\x16\u00F2a\u00EC\t\u00F0\u00FE\x0E\x15\u00FA\x1C\u00F6\x04\u009D\u00DE\u00B72\x04 u\u00E6\x18W\u00C6\x06L\x18\x02\u0092\u00D0\x12\u00A6\u00AF\u00BA \u0093Xz\x19+m\x0F;\x19\u0082\x06\u00D1\u00C4X\u0090C\x16\x7F\u008EP\x11\u00D8\u00DF\u00E1\\\u0090\u0085\u0090s.W\u0090\u008C\x02\x1Ah\ra\u00EF\b5\u008C\f\u00A1\x04rU\u00A6\x15\u00B6\x02\u00C0S\u00C8\x0BCX\u00AD\u00F9@d\u00D1\x01\x19\u00ED\u00B7\u008E\x01\u00AA\x02\u00C5.\u008Cg<\u00E5}k\u00FF\u00E8'\u009F\u00D1\u00BE\u00EA{\u00AF\u00C2}\u00B0\u00DD'\x19\u00C2\u00A6\u00FE\u00E5O\u00EC\u00D5\u0097\u00BEx\u0081\u00B7\u009F8\u0093%\x06tg\x02\u0086\fLeP\u00CC\x14hu\x06V\nt\u00C4B\u00ED\u00AB\u00D3\u00B4s\x11\u00D7H\u00BE\u0094v\u00A5\u00D8I\u00B94\u00FA\u00C7\u008A\u0095\u00BD\x10!\u00EF\u00A8\u00BD\x10c\u009C\u00A7IP\u0093\x03\u0094\u008A\x19\u00E4\u0093:|J\u00CFU>\x04PZ\u00D3X\u00DA\u0083\u00B8\u00E9\u00CB\u00AF\u00EAG\u00CC\u00D5\u00A4\b\u008AzAW/\u00D3\u00EE\x07\u00E8\x05\u0088\u00BA\u008B\n0\u0097\u0082\x1E\u0087\f=&GX\u00B5%\u00DC\x06C\x00\u0092A\u00B2\n\u00F5\u00EAs\u0086\u0097\u00E1(\x18\u00C2d^\x15\u00AA\r\u009B\x0F|\u00CC\u00BE\u00D9\u00B7\u00FD\x1F?\u00BF\u00FEm\u00FF\u00E1\u00E5;g\u00F2\u00DE\u00DD\u00EES\f\u00A1\u00EBU'\u00DE\u00DA\u00DF\u00F4\u0092\u00BD\u00F2\u00E2\u00A7\u008F\u00F8\x14\x06\u00E9\u0098A1\u00A8\u00AB\t\x02\fh\u00CE\x10z0\u0084\u00A9\u00DE[\u00F4SMC\x1DP\u00A5\u0096\x11\u0089\u00ADWM\u00F8\u00AA\u00E5\x1A\x05\u00CC\u00C8\u00A8+\u00E8 \u00FB\x06\x122\u00F3\x10m\x18S\u0086\u00E0\u00FD\u00C7y\u008A\u00ACPT\u0099\x18\u008D\u00A1=nQ\x19B\u00DC\u00B6\u008E\u00B7\\+\u008E4*\u00CC\u00D6J,X\u00C9\u00C2\f\x02\"#\u00EC\u00F1]\u00DC\u00A8\u00A2\u0085\u00C8\x15E\u00A5\u00F2\u00F1\u00F3\u0099'\u0092\x1E8D\u00A0\x12\u00C7\u0088T)\u00F8\x1C4*\u00D6|\x06\u008Em\u00C5-\x19\f\u0096\u00CF\u00B5\u00C2\u00D4\u00AA\u00CA\x10\u00C9^nS`\u009F\x1D\r\u00E3\u00DA)\u00C07=\u00E3\u0095k\u00DF\u00F5o\u00FE\u00AD\u009Cp\u00C6}\u00A6\x18\u00EC}\u0086!l\u00E9\u00DF?\u00FC\u0096\u00F1O_w\x13^q\u00D6l\u00B8\x15\r\u00C0\\\u00A9\"\x00\x03\x04\u0083\x003/f&P\u00AC)m\t\u00B0\x1A\u00A3\x00@\u00E9L?\u00FE\u008A\u00E4\u0098X\u00B2U\u00D0\u00D1\u00C3S\u00D1'\x0B\u0096\u00BA?\u00AF)\u00D4W\u00BFW\x1BE=\u00CE\\\u0087\u0090\u00CC+\u00AFj\u0087\u00DA\u00C01\u00EA\u008E{\u00A9\x1B\x02E\x07\u00C0]y\u0082\x16L\u00CDG\t\u00BA?\u00ED7~\u00F6\x10\u00E9\u00F8k;\u00EF\u00EB\f*m\x18|\u00B6\u009E\u008C\b\u00F4\x04\u00D896\u00C7\u009A\u00CF\x11:|_\u00E9\u00BF0\u0085\u00A2\u008AL\x18\x02\u0099X\u00ECSAC\u00E5j\u0090\u0092\u00A3\u00BD.9\u00CE\u0082\u00B22\u00E9+\u00E7\u00D3\x18\u0083\x04C\u00A0\u00F1R\u00D1\u00A0}\u008E\u00ED\u00AF\u00FB\u009E+7\u00BE\u00FD'\u00CE\x19N?\u00E7\u008AC\u00BC\u00C9{]\u00BBO0\u0084}\u00FD\u008D?v}\x7F\u00C9\x1F\u00ED\u0093\u00B7\u00EC\u009EA14\u00B3\x0F\u00CC\u00D4U\x03\x18Ch\x02\u00CC\u0091L`\u00EEv\u0084\u00AC\u00BDS\u00BC\x055\x18\u00C6\u008F)-\u00E4Q/\x00\x18\u00A1\u0093\x12b;\u00A5j\u00EA\u00E8\u00D1\u00AAd\x0E=\u0098\x18\x7F\u00A5@I\u00FC[\u00CE\u009F\x1Co\u00D9\x15\x17q,\u00FE9h\u00B57\x1B\u00C7\x00\u00E8,\u009F\u00CF\u00AF\x11\u00DE7\u00AE_a@\u0094\u00AE!\u00A5\u00D5C&\u0082\u00DA\u00D3\u00D7\x0Fx0T\u00CEA\x10?\f1\u00D8\u009C*DGG\x15=\u00BD*\u009C\x06g&\u00AB\u00F3\u009F^\u0088\u00A9\u00B1\u0094j\x14\x19\u0085\u00A1\u0085\x12\u00A4\u00C4k<\u0098,\x10\u00C5\u00B8\u00CA\x10\u00AA\r\u00A7\u0099g\u00A7\u00A3xr\u00CA\u00BCu@\u00BB`\u00FB\u0094o\u00DD\u008FG\u009F\u00FB\u00AF6\x1E\u00F5\u00F3\u00AF\u00C6\u00BD\u00BC\u00DD\u00AB\x19\u00C2Ro\x1En\u00EE\u00AFz\u00E1u\u00FD\x0F~a{v\x15\x1A\u00B6ME\u0080\x18C\u0080\u00BA\u00CD@\\= s\u00B0s\x1A\x14s\u00FF\u0097d\x15\f\u00A1 \x04JPJ\x0B4\u0087\u00FA\u00AAQ^\u00B02\x04(-\u00F6\\\u00B8\u00AB\u00F0\u00BC|\x16\u00B8>~\x1B\f\u0081\u00D7P\"\u00FAw\x052N\x1F\u00CD\t\u00BE\x19\u00E1\u0088B\u00FAZH}\u00AA0\u00CCc\u00A8\x03\u0091p%\u0096sw\u008CYr\fa|M\u00E1n\x04R~<\u00DC\u00F8u4\"#aw\u00AF\u00D5\b\u00B7\u00F4w\u0086\u008F\u008F\u00A0\u00CA!\u00D0\u00A9[\u00D7\u00FB]\rR\u00EA\u00BD\x04b9\x11O\x1E\u00D5\u0089\u00B8\u00FB\u00FC\u00EEd\b\u0098x,\u00A6\bA\u00C1P\u00E7\f\u00F62T\u00A2\x10,\u00D7O\u0083~\u00FB\u00D3\x7Fw\u00ED\x1F?\u00EB\u0097ex\u00E8\u00BD6\u00B5\u00FA^\u00CB\x10\x0E\u00EA'O\u00BA\u00BE\u00BF\u00EC5\u00D7\u00EB\x7F\u00FF\u00BE\u00DE\u00F6\u00B8}\u00A0\u00FB\u009F`\x0E\u00C1\u00CC%\u00D1\x00\u00C1\f\u0082A\u00D4\u0098\u0084\u00C2\u00D5\x07\t\u00840\x14\u00A6p(\u00E2c\u00E8\u00AD\u00D9\x05\u00AA\u0095\u00DE\u00FF\r]\u00B9\u00F8\u00E8A\x1D\u00FA0(A\u00F3\x16v\u008F\u0095\u00E3E\u008FN\u00D4\u00C2\u00E2\u00A7\u00FC\u00D7\u00EEi\u00F9\tm\u0082\x00Z$#\u00B5\u00F2<\u00AD\u00D4\\\u00A4-\u00C0\u00F5\u00FE\x18\u00BF\u0096gpc\u009C3\u008B\u0088\u00AF\u00A0z\x00E\u00A6\u0083\u00AF$H\u00A1F\x17\u00F22\u008D\u00E8H\x06\x1A1\u0095\x1APHw\u008F^\x1F\u00BD\u00EF\x0EQ\u00CB?P\u00EF\u00AFi\u0086N\u00C7s\u0089\u0098\u00CA0\u00F6\t\u00D2\u00C8\u00B9l\u008E \u00EC\u00DA(#\x07\u0084=\u00C3\u00C2\u00B0Q\x10\x06\x19\u00BFLl!\u0093\u00EC\u00CA\u00D2\u00C7\x12\u00BB\u00B1x\u00F8\u008F\u00BCe\u00E3;\u00FF\u00DD\u00D3\u00DAW>\u00EA^Y\r\u00FA^\u00C9\x106\u00F5\u00B2o\u00F8\u00F4\u00F8\u00E2\x0B>\u0083W\u009E9o\u00DB\u0098\u00A1\x192\x10\r\u00860\u0080\u00EA\u0081\x19\x15\x07\x11\fP\u00FB\u00CD\x19\u0082\u009D\u00A7\u00E9\u0081P\u00844\u009A2\u0084b`(\u008B\u00DB\u008E\x15\t\u00C8s\u00AB\u00F4\u009C\x14,\x05d\u0095)H\u00E9s\u00D5\u009E@F\u00E4\u00A3\u00B2HB\u00C38F\u00B8\u00CD\u0089}\u00F0\u00FE\u00E9G\u00A1E\u00DF\x18\u00C2\u0084p*\x13@\u00F3b\u00A5q\u00A3x\u00D6\u0088\u00CB\x00b\u00EC\u00D3}\"\u008DP\u00BA\u00DF\u00C7\u00A4\u00B2)\u00F1B\u00D8@&\u00B7\x03)\u0088\u00A3\u008C\u00D1\u00C7\u0099\u00D7di4\u00BAPml\u00A2#\x14\x0B\u00A8v\u00B4b\u00A7\u0090\u0092\u00DF\u0081\x11\x11\u00BCU\u00FFjH\u00B5\u00A83\u008E\u0089\u00AD\u00E30\fa\x15!\u00F8\u00FB\u00AD\f!\x12\u00DE:\u00A0\u00E3\u0080\x0E\u00C1\u00F2\u00F4\x1F\u00F8\u00E8\u00FAc\u00FF\u00CF'\u00CB\x19?\u00F4q\u00DC\u00CB\u00DA\u00BD\u008E!\u00EC\u00D1\u008B\u00BE\u00EB\u00EA\u00E5\u00EF\u009E\x7F\u00ED\u00F0\u00A6S\x06,\u00B0\u00AE\u00DD<\x07\u008D\u00AA\u0080\u00D9\x05\u00A6\x7Fb\u00AA\u0082*f\u0082\u00C2 \u00D2\x0598S\x18T\u00D1\\0\u0087\x1E\x1D\u0095\u008B\u00FCo\u00E2\u00EE#\u00E5\u00AFD0N\u00A2\u00FE\u00EC8\u00ED\x12\x13\u00A4P\u0098L\u00E8\u00C0q\u00C8\u0089^\x07'\u00FA6\u00FDW\u00C5\u00C7F\u0086\x01\x7F\u009A\u00FCl\u00E3ru\u00A0,^2\u0090\u0090\u00D4\x00\x18\u0085\x18F\u00D1\t!3\u00BEA1\u0089\u00E0,\u00EED\u00D0\x00\t>\u00B7&\u00DA\u0098\x140\u00D1\u00F2\x07\u00D4B\u00AB\u0093\u00DF\u00B4g\u00BC\u0086.m\u00EA\u00B5\u00A31\\\u00DA\u00D1\x03\u009Ci\u00A0\u00AB\u00AB#\u00F9\u00FEt\u00A4\x1A\u00C1\u00F7\u00E3hf\x05!\u00A4\n\u0086\u009D\f\u0081\b\u00C1\u0099\u00D9\u00E1\x18\x02T\u00D0G`\u00D4\u0086\u00E5\u0083\x1FyS{\u00DCO\u00FE\u00D0\u00C6#~\u00F6\u00A2\u009D\u00AB\u00F8\u009E\u00DB\u00EEU\f\u00E1\u00DA\u00F1/\u00CE\u00B9\u00BA\u00BF\u00F8\u00BC\x1B\u0087wm4\u0098+q\r\u00DD\u0088\u00BC\u00D8\x04\u008C\u00D0i/\x00\x06m\x18\u00C4T\x05C\x12<\u00D7Hf\u00A6\u0082A\x1A\x1Az\u00D8\x1381]=\u00DE\u009DR\r\u00C8p\u00E5\x15\u00A4P\u00E9<\u00F7L\\\u0085\u0096\u0094\u00C8\u00BC.\u00AEp}\u00BA!\u00CA\u00B3\x05\u00F1\u00CF\u00CBg\u00AA\n\u0082\u00D4\u009F\u00AB\u00E5\u00BF\u00D4\x15\fd\u00D0\x10~\u00FC\x12\u00A0d\u00D7\u00A69\u00B5\u00C6\x1Cd\u00E0\x0F\t\u0099\u00CFa\f\u0083?5'\u008A\u0089\u00A4\u00AFAW\x135bu.4%\u00B5\u00C7.h\t\x0E\u00CB\u00A4$'>\x1D\u00C1\u00BD.\u00B42\x03\u00ED\u00E8:\u00A2\u00C5\x1E\u0098Z\u0090\t\u00E7\u0096]\u00B14\u009D\u00A1\x1B\u00E9c\x18\x18\u00A5\u00BA\u0081i\u0083`\u00DC\x02]\u008F\u009C\u00C3\u008A|z\u00F6\u00CFu\u00A0\u00E3\u0080\u00C5Ign\u00EAw\u00FE\u00C4\u008F\u00EFz\u00DC/\u00BF\x0E\u00F7\u0092v\u00AFa\b\u00D7\u008D\u00E7\u00FD\u00FCe\u00FA_\x7Fo\u00DFpEk\x1Ehd\u0084\u00DF=\x1C9\u0089<U\x01`-\u00BE\x1B\x01O\u008C\u008D\u00C2\u00EFfc\u0090@\x17I\u00B0\u00944\u00A1F(\u008B\u008C\u00DA\u00F7i\u009A4\u00F5\u00ED\u0095\u00BA\t<\u00EE\x04\u0095\u00E7\u00B3Q\u00A5\x10\u00A0\u00CF\x00]+\u00A8\u0080\u00CC\x01 #\u00982\u008DrmA\x05S\u00D8,\u00B9\u0098\u00D1V\x02\u00B0\u00CA\u00FD\u00B9\u0093tx\tVT\x0E\u008F3PZ\u00F8V\u009E\u009Fi\u00D7\x13\x04\u00E0\u00C7\u00CC`\u00A8\u00D1\x7F\u0086\n;S\b\u00B5e\u008C\u00DF'\u00E1\u00D2N\u00F8\u00DCNN1B\u00B0\u00F49\u00EB\u00CE,\u00C8 Fg\f\u00FC~\u00FB\f\x01d\u00FC+\f\u00A1\u00D3\u0090\\\u0098\u00FB\u00ED1\x04\x00\u00BE\u0099\u00ED\u0080\u00ED\u00DD\u00A7\u00F5\u00F6\u00D8s\u00FF\u00FD\u00DA\u00D9\u00CF}1\u00EE\x05\u00ED\x1E\u00CF\x10\u00C6q\x0B\u009F\u00EB\u00AF\u00FE\u00CD\u00CB\u00F0\u00BC\u00FF\u00B4o\u00B8\u00DA\r\u0083\u00A6\x02\u0098\u0087\u00A0O\u00BC\x06\u00F6\u00BB\u0091\u00851\u0083\u00E9\u009F\u00A1\u0081\u00AA*0\u00B7\u00A1M\u00BEG\u00A4\x1D\x10\u00903'K\u00BD\u00F2\u00EF*Rh~\u00AC\u00F8\u00DF\t3\u0085\u0084\u00D6L\x12Q\u00EA\u00C9\u00CC\t\x7Fpb\u009A\u00D9_ \x04)\u00D6\u00F2J\u00FC\x02\u00F4\x01\"m\u00B2X\u00AB\rAz=\u009F\x03j\u00A5/8Q\u00AF\u00F6QS\u00A9i\u00BB\u0088\u0087\u0099.\u00FC\u00F2\u0099nA\u00EE0U\u0099\u00C2\u0084q2\u0090\u00A8|\u0087\u00AAmu\u00DFG\x1F\x13\x19\u00883Z\u00BF\u00B7\u008E\u00DD\u00FB\x1B\u00E1~``tOE\x07\u00C8T\fu,\u00D2\u00F8\u00C8\u00CDr'\fAK\x05\u00EB\u00C33\x04\u009Bk7\u00BC\x1E\u008E!8?\u008B\u00B9\u00F0>{o\u00D0\u00F5\u00D3\u00D0\u00FF\u00D13\x7Fk\u00EDI\u00BF\u00F4\u00EB\u00B2~\u00F2!\u00D7\u00F9=\u00A5\u00CD\u008E\u00F5\x00n\u00AB\u008Dz\u00B3\\\u00D3_\u00FE{\u00EF\u00D7\u00E7=k\u00FF\u00EC\u00B3\u0098\u00F9\u00E2h\x10\u00F3\u00FF\u009B\u00AC\u00B6\u0080\u00A0X\u00DF\u0096\u00DA*H\x1B\u009D/I\u00C0\t\u009E\u00EF\u0090\u00E0]\u00CBy\u00BE\u00EC\x02\x11\u00D4H\u00C6z\u0092\x00n\u0089\u00AA\u00D2\u0096w\u00948Y\u00D8\u0091\u00A64\u00B7\u00FB\u00B9\u00D2\u00A3\u00FCwf\u00BA>\x1A,\x03\u00D2q\u008D#\x01\u00BB_G\u00E8\u00FD\u00D2 \u00E2\fC\u00A4\u00D0\u008C\u00E4\u00F8\u009Am`\"~\u008E\x11\u00A1\u00D7M$\u008A\u0080B\u00C5~\u00A3\u00C8\u00D7\u00CE\u00F9l\u00E1N\u00E5V\u00F3\u00A2\u00CD9$\u00E2\u00FA` \u00E2\bA(\u00F5;l{8I\u00CD\u00A4\u00DAb8\u00F1\u00CD~o]\x1D\u00BB)\u0080\u00D1\u00F5\x11\u008E\u0093\u00CA}\u00DAa\u00B3o5\u00C6\u00E5\u00FFF\u009C\b\u00D4\u009F\u00DB\u00DC\u009A\u00D6\x15\u00DF\u0089\u0097\u00ACG\u00B0Y\u00F0\u00D2\u00AA\u00C61\x01N@F$\x01\u00AA\x025\x06\u00B7[\u0091\u00AF]0t\u0085\u00EE\u00BF\x0E\u00FD\u00BD/\u00FEO[\u00BA\u00E7\u00FE\u00BA\u00BC\u00F6\u00DF\u00CB\u00EC4\u00C5=\u00B4\u00DDc\x19\u00C2R\x0F\u00B6\u00AB\u00C7\u0097\u00BF\u00F4\u00DD\u00FD\u00B7\u00CE\u00DD\u009A]\u008B9\x14\x0B8\u0087\u00F7\u0085\u00EB\u00B5}<\x1F\u00C1\x16\x12]\u00C9\u00A2\u00C0R\u0082\u00EC\u00D0}\u00A9\x19\x13w\x15\u00C2\u00A1\u00EF\u00E8o\u00B6\u00A9\x1A\u00B3\u00F1\u00F7\x1A\u00B6\u0084\u00A4ooE\u00E2N\x1A\u0097W\u00ADG\u00E00\u009CT\u00AA\u00F4\x10x\u00DEex\x06\u0086\u00F2\u00AF r2\u00E3\u00D9\x04*\u00CD\u00D8a!\u00A4P\x1Fj\x1C\x01\u00F2\u00BE\x12{:\u00B8\u008E\u00EC\u00A3\u00B1\u00E7\u00F2\u00B1\u00D2&\x00\u00EBK\u00A4\u00F4#:}\u00FCbK\u00B1\x1F]4J\u0092\x16\u00DF\u008F\bc\x1E\u008A\u00EA\u0084\x0Ei@\u00D4\u009F\u00AF\u00AEVa\u00C5&\u0085\x15a\u0081\x13\u00B3\u0082\t\u00EB\x12o\u00B8\u0099\u009A\u00E0oU\u00D0,/\x02\n\u00C5h\u00F3\x0F\x010\x022B\u00C5\u00FBq\u00F7\u00A5\u0084\u009A\u00A2\u00ABd<\u0099\u00BF\u00E0\u0093\u00A8\u00AF\u00BF0\u00B3rN=i\x12{\"\u008A6\u00EEG\x7F\u00EFy\u00CF:\u00B8\u00DC\u00DE\u00AD\u00E3M?-\u00C3)\u0087\u008AQ=\u00E6\u00ED\x1E\u00C9\x10\u0096\u00BAg\u00B8r|\u00F1\u009F\\\u00A8/zf\u009F}\x11\x03\u0080\u00A5\u00BB\r;\u0080\u00AE\u00CA\u00EA\u0087q\rQ\u00C1\b_:\u00B1 \u0092I(\u0080%\u00CC\u00A8(\x10\u008C\x10\u0080\x0B\u0085D\f\u00BBX\\\u0092\u00F4\u00E8\u008F\u0092\u0082\x12\b&\x05\u0093n@\u0098\u00C2u\u00A1\x11E\u00D8\u00A0b\u0084/\u00E1\u00E4\u009C\u00C5\u00EF\n\u00B7\x17\u0088\u00A3\x01f_F\u0092v\x04X\u00DBgJb\u00C0T\x06\u008E)\f\u0081\x1C\u00A3\x11\u0097LV\u00EA\n\u0083PJA\u0081\u008A\u00AB\b1\x7F6{\u00C67\u008D\u00C3\u00A9\u008B\u00D2`.\x05I\x00$\u00AF,\u00A7\u00C6\u0098\u008D\u00C8\u00B0\u0094\x01\u00E1NQMi^)\u008Eh\u0086\x1AO \x11\u00F8\u00DC\u008D\x10\u00C9\u00E0&\u00F1\u00B8b\u00CD\u00B2'\u00C68\u00B4\u008A\u0083\x0E\u00C5\x12\r\x1D\u0090%\u00B4\u0099\u00D12\u00EC!\x13\n\u00BF\u00BDv\x18Dp\u0098&\x10\u00CCT\u00A0\u00DB\u00FB\u0080\u00F7\u00BE\u00F2\u00DC\u00ADq>S\u00DDs\u00AE\u00C8\u00FD\u00EFq\x01L\u00F78\u0086\u00B0\u00A5\x07\u00DB\u0087\x17\u00BF\u00FF'\x7F+/x\u00E6r\u00D8\u008B\u00B9sc\n \u0093\x07d\f\u00F6\u00EA\x15U\x05\u00B0%\u00EC\u00CB\x12\u00C5\\er\u00C4\x17XG\u00B7eSP\u00BFi\x00\x1A\u00B0\u00DAH\u0092\u008C\u00C0\u009Ae\u00E7\u00F1\x07\u00814\u00B7*\u00A3\u00D4a\u00CC\u00C30\u00CC\u00E1\u008EO\u009D#\u00AB1\u00E4_\u00E4a\nU\x04\u0087\u00F8\u00F08\u0083\u0080\u00F9\u00EC\u00D9\u0088\u00D7\u00B2%+s\x00\"\u00BC*\u00F0o2\u0096\u00C9\u00D8\u009C\x19$\u00AD\u00B9\x11\u00AF0\u00BB\u00C9\u00D9b\u00E8\f\"@\x1B\u00C2//\u00BC\x17\u00FB\x10\u009B\u00F1@H\"6\u00F3\u00AC\u00D5\u0088\x02>2\u00DA\x0B\x14\u00C3\u00EA8>\u008C\u008E\u00BC>E/R\u00DD\u00E8\u00C6H\u00A1P,\u0091f\u00E1\x0ES\u00A9J\u00FF\x02Ge\u00BD\u00A0\x1CgD\u00BE7\u00DC4\u00A2tU-J\u00E45\u00F1.\u00DD\u00D1\u00E6hqm{\x0B\u00CB\u008B\u00FF\u00F4\u0099\u009B\u00E3\x12z\u00F0s?%\u00BB\x1Er\u008FB\n\u00F7(\u00860\u00EA\u00ADr\u00E9\u00F2\u008F_\u00FA\x0E\u00BC\u00E8\u0099\x07\u00DB\x1E\u00ACa\u0089\u00A6\u00E4\u00F7)\u00E7T\u009C!\u00888X\x04\x00\r\u00E2\x1E\u00E1h\u00D4\u00DFn\u0087\u00A9\u00A3K\u00AA\x06\u00E29\f\u00CA\u00FB\u00DA\u00BF\x12L\u0085\u00CC\u0084\u00EB\u00A3\u00FE\u00B7\u0090\x16\u00E1\u00A4\u00B8J\u0090+\x1D\u00E9\x02\u009CC1\u00F7\u0080\u00A2\u0099\x11=\x04i'pI.\u00E9Q \u00A9I\x1B\\b\x1398t\x0E\u00D1I\u00DD\u00DCg\u00A0\u00AA(\u00BE\u00B5;%xz7\u009A\x11\x00!@\x18Z\u00B4\x1COB\u0097B\x18R\u00E7Bh\x18,\u0093G\x03\u00A2T\x17\u00AC\u00C2\u00D4 \u0083\u00EB9\u0089!\u009E\x1D\x11\u00B8\u00F1\x12\x1AR{\u00BA\u00AD=\x19\u00A11l\u00A5\u00FBQ\u009A\x1Fi@\u00EF\x10LK\u00DA\x01\x1D\u00AC\u00A2\u00CD\u00BC\t3\u00DCv@\x17\u00BEz\x16\u00C6\u00C0V=\x05\u00AB\u00A8a\u00F2Uv\x1E?ls&\u00D7\x01H\u00C3l\u00DC\u00C2\u00F8\u00F7\x7F\u00F6\u00CC\u00ADa\u00B6\u00D4\u00C5-\u00FFJ\u00E6\x0F8\x1A\x16s\u0097\u00B4{\fC\u00D0\u00AD\x11\u0097-_\u00FE{\x7F\u0083\u00DF>\u00F7\u00C0\u00FCzxZ\x0E\x06 \u00F6U\u00E6\u00FA]\u0080\u0081\u0083\u00B1Ja\u0084\u009E\u00F2\u0090\u0088\x00\u00FEy\u0094\u00A9\u00A4\x17X\x18;\rl\u009DF# P\u0085\u008B\u00F8\u00EC7`B\u00ED\u00C9\u00FB\x13\u00B8ZA?\u0085\u00FB5t\x0E\u00E1w)\x01E\u0081\x10\u00F8\u0094D\t\x04\u00E3$b\x0F:\u00A2>\x1E^\x05J\u00B3\u00A2R\x04s\u00A0\u00B4GJ\u00D9\u0096q\x06\u00C6\u009C\\\x01j\u00AE\x0E\x18lr\x06\u00E0\u00E2]\u0087\u0088\u00E1\x07\u00ABR\u00C3\u008E9y\"8bw\x1C\u00A6\u0085\u00B7\u00C0&-=\f-$\u00B3\u00EA\x00\u00B4fx\u00AE'\u00A22\x17\u00A5]-\u0092\x18M\u00BDc\u0081@\u00DBhs\u00C2\u0098\x0E\u00D1\u009C\u0093@\\\u009E8%\x03\u00C4sO\x04n\x10E\u0087\u0088[\u0095\u00B4\x032\x03t\x01\u0095\x11\"N\u00B9\u00BD2#\x7F\u009A\u0098\u00CF\u00FC\u00E9h\u00DB\u00FAr\x13\u00DB\u00EFy\u00D9\u00B9\u00FBe}\u00BF.n\u00FDw2\u00FF\u008A\u00A3\u00EF\u00ECNl\u00F7\x18\u0086\u00F0\u00A1\u00F6\u0087\u00BFy~\x7F\u00FE\u00B3n\u009E\u00DF\u0080\r\u0095p\u00ED.\u008B\u0084\x18\x1C\u00D2Sr\u008Dj\u00CB\u00C5\b\u00DE\u00DE\u00D3\x120i@\u00FA0`\x1A\u008B\u008C\u009F\u0093i\u00E4yD\x15\u00FCNx\u00ABJ\u00C9d\u00AD;\u00BD\u00ACj\u0090*\u00E2\x10v\u00CD\u0099@a\x04\u00D5X\x18\u0084\u00DB\u00CC\u00CE\u00E0\u00C7\r\r{\u00CC\u00814\u0087\u00BD\u00C2\u00CE!\u00AD\x01}@\u00D8\x01\\\u0095\u0080\x1B\x013i\u00A9\u00C4\"\u00F8yS\u0089V\u00D4\u0089\bT2\u00C9n\u00F7*\u00B1\x16\x01\u00CB\u00DA\u00B4o\u00F4\u0082\u0084\u009C\u0088\u0088.B\u00C2W\x1D\u008AP_\u00CAW\u0093\u00DC\x12F\u00C9\u00A2\x0Ex\x1F\u0089*\u009C\u00E1\u00C0\x18\u00958S\u0096\u00F0&\u00901\u00E43ReP\u00FF\u00ACq/z|L\u00BD\x10\x1A\x1A\u00A5\u0085\u00FA\u00E0b\x04\u0081\x15w\u00A8\x0FD\u008De\x0F\u008F\u00BA\x16@\u00DBMY%\x02\u00EF\u00BBA\x1B0loB\u00DF\u00F5\u00B2g\x1D\x18\u0096{\x00\u00FC\u00FA\u008EN\u008EA[]\u00D3\u00C7\u00A4]\u00BE|\u00FD\u00CF\u00BF\u00B6\u00FF\u00DA\x7F\u00BBi\u00F6q\u00CCDqB\x17\u00CC\x04\u0098\u0089bM\x1531S\u00DC\x1AJ$\u00A2*\u00E6\u00B0c\x19ld\tM$\u00C5\u00994Ot\u00B2\u0090f\u00F1\u00F3\"&\x01-\n\u00A8X\u00BD\u0084\x15\u00F9\u00AE\x04\u00FDN\u00BE\u0092\u00E5\u00BB\x01\u0094\u00C0\x19z\r\x06\u0088\x0E@\u00F7\b\b\u009D\u0095\u0085G\"u\u00CF\u00C2J\"\u00920\u00F8(\b/%^\u00A2\x01z(d\u00B2\u00F0\u00E3\u009C\x12\u00C5[\u00C3\u00A5\u00F3\u00BCr\x0E\u0088\x16\u00A8~\u00A0\x04\u00D7\u00F8aU\b}s\u00B5`IHc\u008D\u0090]\u0085Z^A\u00A0\x01\u009D0\u0085\u00DCI\u00DA\u00FF\u008D\x18\x05\u00F6\u00AB\u00C8\u009D\u00B2=\u00A7\u00C13\x1FA/\u0088\u00C7\r\x00\u00DEOw\u0086\u00C1\u00A6c\u00C44\u00D89\x16\u00AB\u00A0,\x19\u00DF\x01\x19\x1D7v5\u00B5\u00A3\u00BBQR\u0097\x1E\u00CB\u00D03\u00A6A\x17\u0088 \u00A7\u00D1\u00E7\u00A3\u00FA\u00B2\u00BB\u00F8\x1E\x1E\x1C\x0B\u0091\u0092]\x16\u0099\u00A8\u00BD\u008C7\n\u00B2\u00F8s/\x05\u00DB\u00EB\u00A7B\u00BF\u00FB'\u009F\u00B5\u00F1\u0094\u00FFr\u00CC\u0083\u0097\u008E9C\u00B8r\u00FC\u00FF\u00CEy\u00F5\u00F8\u00BC\u00D7|j\u00FE\u0081\u00B6\u00A6\u00C0\\\x04\x1B\u00AAX\u0093\u0086\x19:\u00D6\u009C\u0080\u00E7h\u00CE\x10j$\u00A2\x05)\x19(\u00AF\u0084\u00CE\u00C2(\u00CD\u00CDy\x1A\u00D9\u008C5\u00A4\u0099\u00A9\u00CF3\u0098\x15XD'\u00C7\u00C3\x1F\u00A0\u0099\u00E3\x00\u00A0\u00ACaIu@\u00E7\x16e\u0088\u0099\u00A5\x1DC\u0090a\u00C65\u00A2\u0090\u00C1D\u00E6YP\u00FF]\"\b\u00C8n\x10j\u0080\u00BA\u00D7\u00A1J\u00E8\x12D\x14)\u00D7\u0090\b\u00CCc\x1F\u00D3\u00AD\u00DC\u00E0\u00C4\u00E9\u00B1\x05N\u0090\u0096\x1198\u00F1J\b\u00FE\u00E4|I\u008C\n\u0084\u00E1\u008D\x12>\u00F7\u00A4@\u00BA\x10W\u0082\u0092\u00F2\u00B7<F=?j\x1E\u0094\x00%\u00F0\u00FE\u00BE\u00A9\u008A\u00E16\x1B\u008B\u00D594\u00E6\u0091\u00F5\x12$\u00CE7\x061\x16\u00A6\u00B0\u00B4>z\u00B7\u00FEGEl\u00E7\x16\x01T~~Wg\"dN#\u00B4/\x01\u00ED\u0090\t\x03I&\u00F9%3\u0084Q\u00A1}\u00C0b\u00F7\u0083\u00FB\u00F2\u00EC\u009Fy\u00DA\u00EE\x1F\u00F8\u008F\u00C74\u00CC\u00F9\u0098\u00AA\f\u009F\u00D3\u008B\u00BF\u00EB\u008F\u00B7\u00FE\u00C6\u0092\u00E1\u00EF\x00\x00 \x00IDAT\u00C3yW\u00CD.kM\u00CD)\u00D4`j\u0082 \u00EB\u00FA\x01\u00AE\x1A\u00B8\u00BBQ\\\u00CF^\"d+\u00A8y\u008B\u009A\u0083\u00C9T\b3E\u00A6\fQ\u00F7J\u00A8\u00DB\"XhUC\u0093W\x15\u00F74\u00D0\u00ADiq\t*\u0099/A\u00B7\u00A6b\x06\u00E8:\u0098U\u00D1\x18G\x10*\x02\u00D5\x04\u00EF]\x05\u00E6I\x18,~`\"\u00C1\u00DBDUI\u0086R\u00EC\x0B<7\x14\u00EE\u00C2h\u00AAB\x1Bq\x04U\u00E9\u0095\u00BC\u00954\u0097\u00E6\u00E5\u00B8\u00B8\u009F\u00BF\u00B1\u00D0\b\u00FB\x0F\u00B8\x10s\u009C0\u00A2\u008E\u009B\u0090}\u00F58\u00A6\u00D7(h\x11\u0088w<a\x1E\u00E5_\u0083\u00FA\u0080\u00A2\u0083\x1B\u00B5\u0084j\u00D1X\u00DE\u008E(AA\u00E3j\u00DE\u00DE\u0099\x1C:,\u0094\u008Dc\u00F2\x10i~\u00A7\u00D7abL\u0094\u00F2G\u00F5\u0081\u00ABB\u00CB\u00AAJ\u00A5\u00F3\u0088\u009B\u00C2\u00E7]1\u00DB\u00FB\u00F9\u00B6\u00BC\u00E8e\u00E7m\u00BD\u00FB\u00C5_X\x7F\u00FC\u00CF\x1F\u00B3\u0084\u00A8c\u00C6\x10n\u00D2+\u00BF\u00E1\u00A5[\u00BFr\u00FE\u00C5k\x7F\u00BF\u00B1K\x15k*\x18\u009A@\u00DD\u00BC\u00D7U1\u008A\u00ED\u00BBg\u009E\x063&\u009A\u0085\u00D9\u00D6|Ss\x1F\u009A\u00C6\u00D7\u00CC\u00A5(\u00D5\u00CB`\u00CB`\x12\u009D\x17\u00CBB\u00E2\u00DB\b\u00AF\u0087\u00E0:0e\x12\u00ED\f\u00C2\u00FB\u0082\u00C6\u00B4\u00E6\u00ACa\u00DD0\x0BC\u008D#\f\u00D9\u0089\t\u00E5\u00B3x\u009C\u0081\u00CE|\u00BD6\u00D8\u00FE\x02$j\x06!\u0099\u0094\x11\u00A1m\u00C1\u008E\u00F1\u00CEP\u0084\u00B5\x1A\u0092L\x04>'\u00A1\x02x_\u00E1*\u00F49H\u00BF}\x03Z\u009B\u0084s\u00B2/\u00EA\u00E7\u009C\u008B\u00EC\u0093\u00A4?\u0086\u00E7f\u00E2\u0082\u00F3/v\u00F5\n: \x1Bh0\u00C9,\u00FE\u00CE\u00F8@\u00A4A\u00AA(\u00D4aD\u0081n\x1E\x16\u0086 \u00AB\u00F4\x18\u008BMFs\u0091L&\u00EA\x04\u00CE\u00F7\x1EF\u00D8\u009E\u00B6\x07\x7F\u008E\x12B\u0089p\u00B9b\u00CC\u00B9\u00E7\"@2D-\u00CC@\u00F8\u00DC+<ygc\u00DF-\x19`\u00D7x\u0087\x02\u00C5\u00DA\u008D\u009F\u00DC\u00D8z\u00FB\x1F\u009E?^\u00F6\u00CA\u00C7\x0E\u008Fx\u00FA1I\u009D>&\fa\u00AF~\u00EE\u00A4\u0097m\u00FD\u00E6\x05\x17\fo:eC:\u00EA\u00B2o\u00BE2m\u008E-\u0085\u00A5I\u00C6\u00A9\u0091\u0088\x07h\x18\u00F7\u00962\u00F5\u00D2\x03\u00C9\u00D3k\x135oC:0=b1z\x15\u008C*\u00E8b\tSc\u00C82.\u00AD\u00E6\u0098\u00C3c\nt#z\x00\u0086\b>\u0082\x1B\x17Y\u00C3\u0090\u00A8\u00A0Ju\x01\u00DD\u008A\x1A\x18\x07\u00EA\x01L\u00E4l\u009A\u00AB,\u00D0\u0083\f\u00D0F4T$\x13\u00E3\x12\u00FCX\u00EC\u00CD\u00C8\u00D8\u0086\u009E\u00BD$zh\x11\u0092L\b\x1BFJ\u00E1y\u00D5MN\u00F68\u00C0\u0082\u0094\u00C4\r\u0091\u00CE\u00CC\u009C\u00F0\x05\x1A\u009Fsl\u00894\u00D2;+\x1E\x04\u00A2P\u00A9o\u00CB\u0098\u0096\x05]u\u00A7}I\u0081\u009D\u00AC\x0B\u008AE\x12\u009A \x11\u0084\u0088{\n\u008A\u00DB\u00D6v\u0089\x01\u00BDS;\u0098\u0099\u00ABTTO\u00AA\u0091R\u00FD\x02\x11q\u0086\u00BA\u00F4a\u00F4\u00C9{:\u00E2V\u009C\x193U\f\u009F\u00FB\u00C8)\x07\u00DE\u00FC\x07\x17\u00E8\u00E7.|\u009C<\u00E4\u00BB\u00EF\u00F6\"+w;CX\u00E8bx\u00CD\u00F6\u00EF\u00BC\u00E6\u00E5\u00F8\u00B33g2\u00A2\u00F9\"\u00A4\u0083\u008D\u0084_u\u00FE\u00D1\x7F#`\u00B3(C\x7F\u00FF\x0E\u00B9\u00C4\t\u00BC9q\u0093\u00EB2\x1C\u00BEC\u00B0\u0094\u00EE\u00A1\u00B9\u00F6\x06\u00E8\u00A10\x15\u00C3\u00A4\u00E9\f\x04\u0086\u00F4\u00E33/\u00C2=\x01X\x03\x03\u008DDf\u00E6\u00C2\x13\u00D3\u00F15P\u0082\u00BB\u00BE\x1A\u00F5\u00FD\u00C1\b\u0093\u00CC\u0080D\u00A4\u00A6\u00B0$K\u00F4\u0085.-\u00FC\u00F2\u00D3\u00A0\"\u0097xe\u009FH\x1B\"\u00CFQ\u0097l\u00B47\x18\u00D22\x15'\u0099L\u008F\u00D9\u00B3\x19h\u00AE\u00D3\u00D6\u00A8g\u00A6?\x13_\x01pz\u00A6;\u00D0\u008C\r]Z\u00BC3;\u00E6~\u00FF \u0094\u00AA\u00BD\u00D8{\u00EA\u00EE\u009ElE'Tm%\u008F\u00A0\u00A8\x0F\u00E2\u00DF\u0093\u0087#\u00FCF\u00AA\u0080\f\u00E6.D\x0BI\x1E\x1E\u0087\u0098\x13G!\u009D\u00F3I\x1F@\u0089\u00C9\b\u00A2v/\x00Fgl\u00CD\u00C3\u009CGD\x15Ww\u00C9&|PL\u00F6\u00EB\u00BC\u00CDv(Uj\u00FA\u00DC\u00F3\u008F\u00BF\u00E7\u00CC\u00AD7\u00FD\u00FEk\u00F4\u00C0g\u00FF\u0099\u009Cp\u00F7\u0096c\u00BB\u00DB\x19\u00C2\u009B\u0097\x7F\u00F6\u00C2g\u00E3w\u00BF\u00EF\u00E0\u00DAA\u009C\u00EC\u00849\u00F8+\x1E\u00A1Xz\u00ED\u0082\x11\u0082\u00B1\u00DBk\u00A0\u00CD`\u008Cs\u00D5\u00ED\x07\u00BEdI\x1F\u00C8 $\u00F5\x05\u00DE\u00C5Q\u0087;\x14M\u00E6\u00FB\u00A2piU\u00DD\u008F\u00DD\r\u008B\nE\x17[4\x1A\u00BE\u00865\x003\u008C:G\u0093\x19\u0086\u0082\x06B\x12\u00AB\u00D9\x10\u00CC\u00D8\u00C7p\u00E4\u0082\x10\u0084z\u00AE\u00A9\x11\u00A2\u00EAIJ-\x17\x1A\u00C3\u00FC\u00DA\u00AA\u00AB\x0F\u00A6fpM5\u00F3L\u00A8C\x7F\u0091b\u00C0\u00F4\u00C8\u00BB\x18#<\u00BB\x0F\u0080\u0088Ey\u00A4\u00844\u0082\u00A0\u00A1\x0F\u00C1^}\u00A8\u00E2\u00D2X\x11\u00CCT(\u0091\u009B'\x16\u00C5~\x07\u00AErHs;\u00A8\"\n\u00B0\b|\u00F6\x07W;\u008C\x15B\x05\u008D\u00B9\x0BN\x14\x16p\u00A4\u00B0\u00C2(+\u00E2\u00DC\x19S\x07\u00D0d\x16H\u00C2\u00E2@<\u00AA\u0091h\u0081U\u0099\x18\u008F\u00A0\u008C\u00A7\u00F0't&b\u00D6%\u008F[@)\u00A4\"#\u0088R\u00C2;\u0083f\u00B9\x11tO\u00EA\x12io\u00F1\x19%wsf\u009B\u008C$\u00BF\u00FA\x0BE\u0085+\u00D2\x05k]\u00B1\u00BC\u00E4\u0082\u00EF\u00DB\u00DE\u00F5U/\x04\u00F0\u008B\u00B8\x1B\u00DB\u00DD\u00CA\x10.\x19\u00FF\u00E6\u00C7~v\u00F9\x0B\u00BFp\u00DD\u00B0\x17\u00A2\x1Dk\x10\u00CC\x05\u0098k\u00C7\u00DC%Q\u00A4&\u008B\x04B\u00E0,\u008A\u0087\r\u00AB\x18\u00D1\x0F\n,]\x124\x15\u008C\u00CE8Dh1\u0090@\u008F\u0084\u00B1\x1D\rcx\f\b\u0093\u00BD\x1F!\u00BA \u0082\x00:\x064\u009D\u00A3\u00CB:\u00809\u00BA\x17r763\u00A0\u00D68\u00AC5\u0098\x02\u00C2\u00F3)\u00C2\u0088\u0098\nR\x1A\u00C1\u00F8\u0099\u00AB\u00858\u00C5\u00C7\b\u00AA\x01\u0094\u00CE\x1C;|!\u00931%2\b\u0098Mb\x17\u00FFQ\u00A4\u00AC[\x0F\u00EEqF\x10\u0091\u00CE\u008AHpj\u00E1\x1D\x002\x00\bH\u00DF\u00A63\x10i\u0099\u00D9Y\u00D4\u009C0\x04R{\bJ\u00A8\x19\x16\u00DD\x19MB\u00F4tw\u00F8|uC%6\fC Lz\"\u0083\x17\u00D0>\u00B2,\x04\u0097R<\u00A2\u00B9+\u00BB\u008B\u00B1y\x16L\u00B5)\x04\"\u00E0@MY\r\u00F1\u00A4\u00DD\u00D1\u0089\u00A0\u0084\u00CB\u00A1\u00DC\u00FC0\u00AD\u00B2\u00DBC\x1F\u009E\u008D[\u00D8|\u00DF+~a\u00F3M/x\u00FF\u00C6?\u00FF\u0095\u00BB\u00AD\u009A\u00F3\u00DD\u00C6\x10\u00AE\u00EE\x1F|\u00F8\u00AFn\u00FF\u00EA\x1F]\u00BA\u00F6\t\u00A8,\x00\x15\u00EC\u0083b\x03\u0082\r\u00C0+\x1AYu\u00E49\u008C\u00B8G\u00E9\x18\u00FD}\u008C\u00A2\x1E\u0098\u0084\x00oKAl\u00D1f 8\u0093\u009E\x04\x12\u00A1\u00C9U\u00F5\u00A4i\u00D1\x12\u009B8:;q\u00F4\u00C5lK\u00B0ad\u00D8\u00B1\u00B1.d9\x16E:>[2\x05\x1A\x0F\x03\u00C2\u00B3\u00B2Bs\u0098O\u00C8\u00EF\x06A\u00F5\u00DF\u009D\u0098\u00A3\u00CAR\u00E8\u00C3\u00FC\u00EE\f\u00C1Q\u008Ee<\x0E\u00C8\u00D0b\u00B1\u00AC\u00C6@\x0F\u008A\u0089\u0087\u00C3\u00C5\u009BiW\u0092nG\u00CE\u008E\u00DF+\u00A6\u00C3\u00A0\x12\nwp~\u00A5N[\u00DE\x7F\u00C4&9Y7\u00A60k\u008C\u0083\u00C6X{\x00'\x04\u00CD\u00F7\u00D1\u00DD(\u00C8B\u00AB\u0089@\u00FC9\u00AA\u009D$xM\x03\u00D5\u008F\u00A9\u008D\u00C3\u008Fu\u008F\u00C4t&ch\u008D\u00CA:W\u0089\u00C6<\u00C7|\u00A2;\"(\x0B\u00C6\u00DF\u0087B\u0090\u00E9\u00E9\u00DD\u0099l\u00F7\u009A\x13#B\u00D2+\x19Im\u00B7\u00C7 \x0E\u00D1\u00A4c\u00E3\u00C0\x17q\u00F0\u009D/\u00FF\u00A3\u00E5%\u00E7]>{\u00F4\u008F\u00DF-\u00FB>\u00DC-\f\u00E1:\u00BD\u00F6\u00C4\u00FFg\u00EB\u00FF~\u00DD\u00EBgo\u00DB=zv\u00A1\n\u00B0\x1F\u008A\r\x00\u00BB\u009D\u00D8\u00E7\u00A2\u0098\u00AB\u00B9\x13\u00E1\u0084\u00EBk\x18J\u00F0\u00AEf7\x1B\u0085\x0E/\u00F7\x06\u0088\u0086l\u00CF?_L\u00AA\u00EE\u00FEe-\u0085\u0089\u00DD\u00DE\u0092\u00A4\u00C4\u00E4\u00CF\x00\u00AF\u00C7\u00A4\x16\u00E1\x00Y\u0087\u00CA\x1C\u00AAV@\u00B3\u0083\u009E\u0081\u00E6\f\x01\x0E3kHr\u00F54\u0098\x112\f\u0080\"\u00A9\u00F3\u00ABDxn\u00D4i\u00A2\u00FB/\u0098\x03\u00E2\\\u00B7\u00C9\u0083\u0099\u0089\u00ADY_\u00EA1\f\x00\x11j\u0089\u0092s\u00C8*\u00AB\u00C8\u0080\u00A9\u00C6\x0EL\f\u0095;\u00EA\u0098l\u00B0\"\u00FE\f\u00E2\b\u00A20R\u008F\x11`\u00BD\u00C2\u00A6-\u008D\u0099\x1C\r\u00D1\x19\u00BF\u00A7Ng$\u00E9\u0086K\u00A6`k\u00F3\u00D4\u00E4\x0E3\nJ\x0B[\u008E\u00BD,\u00CE\u0087\x02\u00DA\u00ED\u00B9\u009A\u00D9\x07\u0094\f\u0085\u00CCK\u00C9\u00E0\u008DX5\u0090\u00D2\x10\u00C7L\u00CDr\x13\u00B4$Z\u00E2\u00D8\u00B5\u00D3q=:j\"\u009B$\u00D4\u00A7w\u00C8\u00D5\x0BzL\u0094\u00C7{\u00BC\u0083T\x1FJ+?O7\u00E31\u00E6\u00B5~\u00DD\u0095\u00BB\u00B7\u00DF\u00FA\u00B2\u00D7\u00E9\u008DW>Z\x1Ex\u00D6]\u00BEC\u00D4\u00DD\u00C2\x10\u00DE\u00BC|\u00FD\x1F\u00FC\x0Fy\u00C5Y\u00E3@\u008E\r\x00f\x07\u00D8\x03`\x17\f\u00A22\u00B8\u00A8\t\x17\u008B\u00BB\x1A\u00D5\x10\x04\u00E3:h?`_\x16\u00A3 XJz\u0088=\u0080\x0Es\x00py>\u00C0\u00FA0f\u00C2Q$\u00CA\u00B0W?@\u00B0\u008E\u00E6\u00EA\x01\u00D3\u0095\u00BB'\x1FY1\u0096\x01c\u00893hn`J\u0095\u0080jDa\f\u008CjR\u00C2{\tn\u00A7\u0091P4\u00F89dU\u00C3!\x16Q\u00DA\x13\u00D4\u00A5;},\x12\u00C7\u0081\u00F0\u00CD\x16\u00C6B\u00A1\x17\u008B\u0090\u00D2\u0096C\u00F3'Hwg\u0089\u00E2H|\u009F\\:\x16;\u00BB\u0090\u00A2\u00D2\u00F8\u00AF$\u008C\u00F8\u00C9G*H\u00C4`\u00D4\u00E0\u00844P\u00A6\u00DB\x7F\u00C5\u00C2\u00C6\u008D\x10\u009D\x00\u00B9\u00D7]\x10ZU\u00B7\u00AA4\u00EE9\u00E7\u00EA1)\u00EAy\f\u0081\u008A\u00A8\u00D2\x144F5\u00C9\u0089\\\u00C3`H\u00C6\u00C2\u00ACI+\u00D3#D\x04\u00E1\u0095I\u00C7\u00F74\u00AA\u00B4\u00BEK\u009D\u00FE\u00A4\u0089\u00A7j\u00D3\u00A6\u0098]y\u00D1Y\x07\u00CE\x7F\u00FEK\x00<\x03wqk\u00B7\x7F\u00CA\u0097\u00D6\u00FEn|\u00DBO<\u00BF\u00FF\u00DE\u00BF\u00BCem\u00D3\x7F\u0099.\u0098-\x007w\u00C1\x01\x05\u00B6\u00E1\x7F*X@\u00B0\x00\u00B0\x10S\r\u009605b\u00E1\u009F\u0097Hc \u00D5\u0087\u00B12\x03\x18\u00BB\u00E8\u00E5\u00BC\u00E2\u00E1\t\u0090\u0097\x7F\u0086\f:\u00D61b\rK\u0099\u00FBw\u00EE\u00F6\u00D0\u00CC\u00D0)3\u008C\x180\u00A2a\u0084\u0095e\u00EDbj\u0080\u00B6\x01*\x03\u00D0\u00D2\u008E\u00E0\u009A-hs\u00C8\u0090ff?\u00CE\u00D2\u00A8\b\x01\u00CB\u00A9A\x06\u00A0\u0089\u00B9\x18K\x1A\u00B3\u00D9\x0BL5\u00D1\u00E6\u00FF\u008A\u00C7gj\u00AA\t*\u0083\x1Fs\x06\u00E4}(\x1A\u00BA\u00D7`0\x17g\u00B3\u00F1\u008A\u00A9/v\x1DK\u00B8!\u00CB\x12\u00C2\u00A4\u00AB\u00D9,\u00FD\u00D9\u00A4\u00F9\u00BD%\u00FAS\u0099#b=e\x06\u00C8\u00DC\u00FF\u00EC\u00B3\u00CA\x1C\u00C6\u00A6\x1D\u00811\x03\u00D4\u009F\t\u008E\u00C0\x04\x16\u00D1\u0099\u00CFkc\x17&j\u0089\u00C4\u00BCU4e\u00CF\u00E0\u00CF\x1B\u00CF\u00E4\u009F\u0083i'\u00A3\u00CF\u0087J[\u00D0\x0E;\u008Fr<\u00F6\u00A7\u008E\x14w2\x7F\u00FE1\u00CD}\u0096(5\u00DA\u0091\u00A9\x0FV\x1D\u00BC\u00A3\u00BD\u00EF\u008DO?\u00F8\u00C6\u00DF\u00FE\u0089#\u00BA\u00F8(\u00DA]\u008A\x10>\u00AB\u009F>\u00E3Y\u00DB?\u00F3\u00E2\u008F\u00AD]\u008D\u009D\u00BA^B\u00DC}M\u00B1\x0B\u0082u\b\u00E6\u00A2\u00E6\u008F\u0085\x05\x05/\u00E0S\u00AE,u!\x1Epd\x10\u00BF\x03\u00F1]\u00DCX\u00D8\u00E2\x18\x19D\u00AA\x07K\u00CFGH\u00B3\x1D\u00E3\x0B\u00E6\u00E8:\u00C3(\u00EB`\u00D4!\x03\u009E\x15\x03\u00BA641\x06\u00D0\u00D4\u0088\u00CA\u00CD\u00A0h\u0093\u0085\u00D4\u00A0\x13\u008F\u0082\x13\u00B2\x0ENX-~\u0097N\u00E3\u00A3K?\u00D8B\u008E\u0094gM\u0087l\u00CC\x19\u00DDs!\u00D6K>\x02]h\u0094\u009A\u008A\u00D0\u00C9\u00AD\u00C0\t2\u00AC9R\u00A8{~\u00DF\x11\u00D7\u0080@6R~WwwB\u00A8Z\fn\u008F-\u00EA\x04u\u0082bhS\u00A2\x02A\tSN4\x02\u00B1\u00DC\t\u00D6Q\u0088x\u00D40Lj\x10\u00B0\u0094d&cVn\u0098,v\u008A\u0090\u00F6\x00Pl\b6_\u008CTl\u00B9\x1CUa\u00F5.V\u0095N\x12\u00FD\u0098\u00EF\u00C4\u008B\u00C1\u008A\u00BB)s\u00DE\u00DD\u00D8\b\u0081H\t\u00A1\x0E\u0083K\u00A6=\x11D\u0088j<\u00E2\u00A4)\x02\u00B5l\x1C\u00B8\x05\u00FB\u00DE\u00F9\u008A\x17/\u00AEx\u00CD\u00BB\u00E7\x0F\x7F\u00DA]\u00B6\x15\u00FD]\u00C6\x10\x0E\u00EA\u00DE\u00D9\u008B\u00B6^\u00F8\u008A\x0B\u00E6o9\u00D1\u00EA\u00F0\u00D5$f\u00FE\u00D9bQ\x00\u00B7\u00C0\u0098\u00C2\u00CC'\u00A5\u00A9\u00D9\x15\u00C8\u0087\x07\u00A4\u00FA\u00D0\u00C4\u00C3\u0093\x15\u0090\u00F0F \u00EA#0]vt\u00AB|\u00B5g7G\u00D2\u00B6<\x1A\u0080\x19T-K\u00A23\u00BE\x00n/\x10+\u00BD\u00DAK\u00A6b\u00C3\f#\u00A5\u0086\u00B8g\u0081\u00AE\u00BE *'\u00FA0k\u00D3\u00ED\bp\u00F1h,\u00FA\"\u0091\u008A\u00AB*\u00AD\u00D9T18}\x1E\u009D\x18\x1E\u0085JT.\u00AD\u00C3bo\u00F3\u00AD\u00939\u00AFYx\u0084\u00EE|/pI\fD\u0090R\x10\u00B9C\u00F5\u00D0M\u00CC\x06aR\u009BC\u0099\u00E8\x0F9\u0086J\u0097\u00F5p\u00B42\x16\x0E\u00A8\u00FA\u0092\u00C3\x1E\u00C0\u009C\x06'\x1E\u00B7#\u00D8}\x06\u00EFi\u0084\u00EA2\u0099G0\u00A4\u00F46\x04*\x10\u0080\u00D9\u0087\u00D9\u00F7\u0088(\u0082\x13\u009E\u008E2\u00C7\u00D2<\u00F1\u00C9\\\u0094\x18\x07c\x022&\x12\u00D3% \u00DD\u008D\u0090\x03R\x1D\u00CA\u00F5\x1E\u008F\u00E9?L\u0083\u00A4\u0080Pa\x14f\x10m\u0082]\u009F\u00FF\u00F8\u0089\x07\u00DF\u00F4\u00A7\u00AF\u00D0[>\u00F3]\u00F2\u0080\u00AF^\u00EE\u0098\u00C6;\u00A1\r\u00B7\x7F\u00CA\u00D1\u00B5o\x7F\u00CE\u00A3\u009E\u00F3\u009Bx\u00DE\u00D3o\u009D\u00DD\n\u0095\u008E|\u00DE\u00E9\u00E2\u0084\u00FF\u00D7\u00DC\u00C1\u008254/q\u00A6\u00BE=\u009B3\x04\x117vk\u00AC\x1D\u00BA\x0E\x05\x00\u008B\u0087\u0088\u00A4\u00DB0\u00D5\u00D9B\x02\u00C2o\u00C6\f\u00809D\u00D6\x00Y\u0083A\u00E5L\u009F\u00CA\x14\u00E6\u0099\u00FB\u00EE\u00FDw\u0087\u00AB\u0082\x01\u0083\u00E7.\b\x04M\x04\x01u\u00A5\u00C0^G\f\u00BC&\u00E11\u00AD\u00D6\u008E\x06\u00DC\u00C0H\u0098ly\u009A\u00CE\u00D0\b\u00AB\u00A3\u009E\u0082\u00CFeQ'\u00D8\u00AF\u0090\u00D1DNE+4\u00CF-i\x1C\u00867\u00EF[[<\u0087\x05<\u0091\u00B90\u00D8\u00AB\u00F2\r\u00BBg\u00D4\\\u00F0\u00E3\u00B9\u00E35]\u0081-\u0098\u009E\x10\u00AE\x0B \u00EEuI\u00ECA\u00A3g\u00FE\u00C6O\x11v,\x16\u00C5\u00D9d%~\x02\u00EE\u00E5q\u00E9+\u00F0\u00C5\u00C4\u00D4q\u00A6\u00AD\x07s\u00A1\u00DA@\u00E4\u0094\u00C4\x18k\u0083a\u00D2<Ki\u00BE\u00AE\u00AB\u00B8\u00A8*\u00BC\x01\u00EFA\x01\u00E2L+7\u00BD\u00C9>'\u008C \u008EI\u009EP\u008D\u00CA\n\u00A0+Z\u00EFh7\\\u00F3\u00D0\u0083\x1D\u00E3\x7F\u00FE\u00CB\u00F7\u00BC\x13wA\u00BBKl\b\u009F\u00D2+\x1E\u00F9?\u0096\u00BF\u00FF\u009C\x1B\u00E6_\u00F4_\x0E\u0089\u0085&\u00BFt\x00\u00B7\u008Ab/\u0080\u0083*\u00D8\x12`\u00E16\u0083\x11\u0096\u00A46\"\u00ED\x07\u00F69\u0099I\u00A8\x10\n\x0B?\u008Ec\u00F4<\u00A4}\u00816\u0083\x11st\u00DB\u00EA\u00C5m\b\f\u009E\u009E\u00C1\u0092\u00AB\u00D7L\u0095\u0088|\u00C9T!\u00EA\u00F7\x0E\u00C1\u00E8\u00EA\u00C4\u00A4\u00E8{\u00842\u00A7\u009A\u0090\u00B5\x15\u00CB\u00B1\u00D5]\u0099\u0088(\u0088@\"\u00CA1\u00ED\x01\u00A9\u00BFS\x0F\u0097<G\x19Z\u00CD])\u008C)\u0089\u009A\u009D!k46H\u009FC:\x170%\u00DA\u00E0\u00C4T\u00C6\tVy\u00AE\u00EE\u00CC\u0086d\u009E\u00ABI]\u00E5Y\u00CA\x1F\u00B7\u00A2c\u00D2z-&cP|6\u00BD\x07\u00EF\x13\u00C4\u00BA\u00F3\x1E\u00E9\u008F\"cL\u009B\f\u00CD\u00C9\u00B9\x7FW\u00CB>\u0094\u00D7S%\u0090$F\u00BE\x0F/{\u00C7\u00C4x\u0089=1\x1C\u0091M\u00E2MZ\u008C]&\u00E3\u00A4\u00F0\u00E1\u00B3\u00DFN\u00A3As\u0095n\x04\u0098\u008DK\u00B4\u00F7\u00BE\u00F19[\u00EF\u00FE\u00E3G\u00DE^7G\u00D3\u00EEt\u0095a\u00EF\u00C1/\u00CC\u009F\u00BF\u00FD\u00C2\u0097\u00BDc\u00ED]\u00F31\u0092]\x0E5\x05\u00AB\x18\u00C9\u0088z\u00AF\x00\u00BBT\u00B1\x0E\u00C16\u00CA\u0094\u008A\u00A1\x00\u00BE\u00E6\u00EEn\u00AA\u00A5d\u00A6\u00E2\b\u00DA\x10\f&\u00AF\u0094\u00D8\x07m\u00B8\x1D\x03D\u00E7hn8\x1C}\u0081Z\u00C2\u00B5%M's\u0090\u00C2\x04\u009C\u00A8\u00C1\x1D \u009A\u00D9\u0097}\u0081\u008Chh\\\x10\u00AE2\u00984pi\\-\u00D9~\u008E(\x17\u00BCd\u0096\u00A1b\"\u00C5s\u00BA\b\u008D)\u0095|\u00EE(]C\u00CAH^\u00EBY\u008DR\u00CF\u00ED\u00D3\u00EAQ$\u009C\u0094\u00F6~\r\u00AD\u00F1 |e?\u00E9y\u0090IG:\u00F9\u0087'\u00D9)\u009E\u0090\x14\u00FA9\u0093\u00C5\x04\u008C/\u00A7\u00A4\u008F-\u00E3\u00A3\u00E2r\u00C4\u0091z\u00DF-\u00FAQ\u00F4\x12\u00C8H\u00BB\u0089\u00C3v\x00\u00A1\u0086\u00E5\u00E3\u00DB\u00DD\u00F9\x1EZ\u00B3~\x18g\x15\u00EB\u00B1\u008CSl\u00E3\\\u009B^\u00CD\u00E35\u00A9\u00C9k;\u009A\u00AA\u0090o!m\x14\u00EA\u00FE\u00F2\x05b'\u00A8\u00FA^W\u009B\u00A3\u0082\u00A2\u008F\u00F9.b\x1D\u00B3\x1B?1_\u00BC\u00E3\u00CF_\u00A6\x07>\u00FFh9\u00E1\u00AB\x16;/>\u00FAv\u00A7\u00AB\f\u008F\u00FD\u008Do{\u00F6\u00AF\u00CB\u008B~l\u00FFl\x13\u00AB.\u0094;\u00D2FX\x04\u00E2L|\u00F7f\u00E1r\u00A5\u00AD\u00A0\u0085<`H-\u00A1(\u00E1s\u00FC\u00D7\u00ED\x0BI?\u00ACz\u00BC\u00E6\x7F\u00EBP\u00B1\x12*\u00C9\u00BD\u00D3B\x1CP\u0097\u00B9\t\x10'x\u00C2\u00DFT\x0B\u00E0\x16\u00E8p\x1DJIv\x02%.s \u00AA\x04\u00F3\x01\u00B2\x1F0X\u0089\u00D2:\u00F7U\u0088x\u0085\x12\u00B0\u0094\u00C9\u00DF\u0084\u00AF\u0094\u00D4\u00DE\x187\x10(\u00C5\u00A5ZQeR\u00DA\u00C3\u008D\u00A5\r\x11\x06\x1DR\u00CF\u00E1\x7F\u0098&\b\u00EB\u0093\u00F9X\u00F0N\u00C2t\u008B]\u00E0\u0082vD\u00C2\u008F@a|dB~P\u00CBx\u00A9\x12Q\rb\f\x07\u00D5+M\x0BId\u00B4P\u00D2\x07\u00DDfNF\u00A2sJ\u00F1\u00A2\x06\x043\u00B6\x0B\x15,\u00F0\u00E2\u00EA\x11U\t\u008FgH\u00B8\u00EF6\x0EWIb\u00F3[\u00DA!\u00AAJ@B\u00E7\u00F3\u0087zP&\u00A6\u00AA\x11aK\u00D1`4\x03\x04\u00B8\u00EE\u00DA\u00D3\x0E.\x16\u008B\u00E7\u009F\u00FF\u009E\x0Bq'\u00B6;\x15!\\\u00ADW\u009D\u00F53[\u00FF\u00E6\u00D9{\u00E77\u00E3\u0090\\\u00EF\x0E4\x05\u00B0G\x14\u00BB0`\x1D\u0096x3\x13C\x0B\x16s\u00D2\u0083A\x00S\u00B0F\u00B3Q\u0087\u00E7@\u00C4\x12\u00F0\x00\x18ihj1\x06\u0090\x19D-`\u00BA\u00EB\x1C\"t-\u009A\u00E4\x17w-\x02\n\u0096J\x1F\u00FC.44j%F\x1F\u0095\x11T\u00A6+\x19\u0081\x0Fe:x\r\u00B7p\u00F7\u00E5\\\u00F4\u00EF\\\u00CD<\u00C6\u00DE\u00AA\u00FF\u00DD\u0089^\u00F3Nv\u00AC\u0095\u00FB\u00D8\u008C\x06yN\x02_lB#\u00F7S\u0090\u00CC,F@\u00E9\u00E6\u00BF\u00A8\u00AF\u00DD@\nH\u00B1Z\u00B3))\u00C1\u008B\u00FF=\f\u009D \x120\u008FBTb\"qNR\u00AF5\u0098\u009C1\u00A2\x1A\u009D\u00C8\u00FE\u0086\u00F2\u0099\u00CF\u00DB\x1D\u00BD$\u00E7!+\u00A8\u00BE\u00A7@\\\u009A\u0088\u0083\u00E5\u00D32\u00EF\u00C5v\u009B\u00AE\u0088!\u0099W\u00CFk\u00F9\u00BD\u009E\u00A7\u00FE\u00DE\u00D5\u009D\u00DC\u00EA\u00F1\t\f\u0080\u00C2\u00EA5\u0085)\u00D6\u00B9\u00F5\u0093\x03\u00EC(0\x1B\x0F`\u00F3\u00E27>{\u00F3\u00D27\u00BEn\u00E3QO\u00B9\x12wR\u00BB\u00D3\x18\u00C28\u008E\u00F2\u0082\u00AD\u00E7\u00BE\u00E4\u00EF\u00E6\x17\u00AE/\u009AW\u00C4=\u008A~\x14f;\u00B8\tK\u00AC\u0089\u00A1\u0085Z:mA\u00A9\u00EA\u00FFI\u00CD\u0091\x1E\u00FF\u00EA\x04R41\u00F2\\\u00CA\u0080\x01\u00A6&\bf\u0098\u00D3\x06\u00A03LU\x02\u0097\u00D2\u00FE\u00D7\u0089\n\u00C2\u00E8f\u00DC\u009F\x06\u00C0\x0E\u00A0\u00EB\f\u00CD!\u00BF\u00A5d\u009B+R\u00A4\u00EC\u00B1P!\u00AA\u00A6\u00B1\r\x00\u00B4{\u00A4\u009FL\x19\fd\x06\u00ED\x1DQA\u00D9\u00F5\u00DC\u00C8] \u00BBc\u00E0Q\x18\x18\u00EB\u0084\u0096\u00F3}\u00CEr\x0Bu\x12\t\u00E7\u00DE\u00C3\u00BD]\x1AUo&\u0085X\x0B\u00B4\u00C4.z\u00B9\x1A\u00C82\u00EC4\x14\u00BA:@f&\x00\u0098?\x01&$y\u00DF\u00A2\u00CE\u00F3\u009C\x00v \x07\u008E\u0094L\u00B3\u0097\u00CFD\u008D\u00B5\u00B0\x0E\u0099M\x12\u009C\u00B8\u0084\u00AE\u00E6K\u008B\u0094d\u00D7bN\x035\u00C5\u00D3J\u00F4\x17OE\r228\u0092\u00DFc>*c\u00D7r\u00CC\u00D7\u00816\x00\u00BEq\u00AD\u00F2=\x10\tHv\u00ED*\x16Yz\u00DE\u00D7\u00C6\x7F\u00E2\x17>\u00B1~\u00F0m/\x7F\u0089\x1E\u00B8\u00E1{\u00E4\u0084S\u008FN\x02\u00AF\u00B4;\u008D!\\\u00A4o\x7F\u00C6Kq\u00DE\x13\x17mt\u00B9\u00FC\u00A5\u008D\u00EF \u0080[`\u00B5\x15\u00D7\u0094\x1A{\u00A9\x1F\u00A4fN\u00EBPw72\x1B\x12\x1E\u008DH\u00DB\u0082I\u00F3\x063\u00B2)\u00E6P\x190b\x0E\u00C1<\u0082_\f\x11T\u0083VZ\u00E3G'*\u00D1\u00861`,\u00F7[\x10tF.\n3\r\u00FC\u009E\u00DA\u00FC}K\x10p\x1A\u00A4\u009C\u008D\t,\x0BO\u0080\u00DC\u00D8\x05\u00A0e;\u00B6k\u008B\x05V\x10\x01\u00FB\u0093\u00B2\u0088\u00B8\u00B0\u00C85\u00E9\u00EB\u00E7\x16l\n\u00B0\u00A4\u00BDAz\"\u00D2\u00BA\u00E8\u00BC?Z\u00D0\u00C3\r\u00C7G\u0091X\u00E7\x14[\u00CA\u00F8\x01\u00B8\x1F\u009F\u00CC\u00A7\u00C0nk\u00FE\x1C\u0082\x18w\x12>Q\u0090F\u00FF\u00E1\u00C5U\u00B8*\u00A5EpV\x06\u00E8\u00F51z\u009B\u00C2y\u00A2\x0E\u00A5\u0094\u0095\u0098\x16a\u00DDu\u00E5\x1E\x0EH\x06J\u00A6\x14\u00CC\u00C8\x11\u0094(\u00AC\u00DC;%}\u00F7q\regm\u00A2\x1C\u00CDg\u00ED\u0092\u00CC\"\u00F6\u00A8(s>)9\x7F\u00C7\u009A@1\\\u00F2\u00F6'n\u00BE\u00E5%\u00CF\x00p\u00DE\x11]|\u0098v\u00A70\u0084\u00EB\u00FA\x17\u00EF\u00FF\u008B[\u00BF\u00F8\u00C2k\u00D6?\u00F3%\u00B2\u0081l\x02\u00E0V\x00\u00BB\u00B4a\x1D\x1D3\u00A1\u00F4\u00CF\x05\u00D0\u0080\x02%\u0093\x0El\u00BD\x12\u00B8[(\u00B2`\x06\u0088%)\u0089G\u00AAu\u00C9(\u00C4\u00B4t\u00DBw\u0093i)y\u00BB6,\u00C5l\x10<wBX\u00E5\x7F\u0090\x06fDr\u00B9G\t\u00B5B\x18\x1D\u00E2\u00E8\u00B8\u00A1(7\u0091\x19\u00C9\u00B5(a\rG\u00CC@\u00CAP2\t'V\u0095\u00E9y\u00A2\u00C9\u0084\u0094k^b\u009D\u009A\u00CB\u00B1O$}\u00BE\x03\x12~\u00D1\u00F3\u0085\x05N\u00A8'\u00BB\u00E4\u00E5Eua\u00FB\u00F1\u00D8+\u00A1\u00CA9-\u00E7\u00C65\u0096\u00BB\u00B0J\x18\u0081\u00C8\u00E1\u00B1\x0F\u00CC\u00D5\x10^\u00EBs\x13\u00B0\u00BF2P.\b~V'\u00E4)\x1C7\u00F2\x1A\x0B\u00A1[\u00DF\u00A6\u00DA1/\u00A1>\u009F\x1B\u008D\u00B5\x01\u00BAD\u0098H\u00E39\x15\u00901\u0088?\u008D\u00A0\x1C\u008E\x0B\x04H\u00B8\u00CCw\x06\u00EF\u00DD\x1E\u008BP\u00ACm\u00ED\u00C3\u00FE\x0B\u00CF\x7F\u00A1\u00DEt\u00E5\x1B\u00E4\u0094\u00B3\u00F6\u00DC\u00E6\u00E9w\u00A0\u00DD)F\u00C5oy\u00F6\u0099\u00CF\u00FF\u00DD\u00E1\u00F7\u00BEw9l}\u00C9\u00C8\u0080-\u00C2C\x05X\x070\x17/v*40j\u0098\u00F7X-\u00B9z\u00BC\u00F9m\u00C0\x1C\u00A2s\fX\x03d\u008E\x06n\x1C\u00DF\x1C\u00DA'\u00A4\u0096\u00E8\x01.\u00E7\u008D\x014\x1D\u00D0\u00E8\u00BE\u00E39\u008E\x12Z\x04\x10\u00F1~t\u00AF\u00F9\u00B9\u00D2\u00DC\u00D8\u0098\u00D7\x00\u00F4\u00EFs\u00C7\u00E74\u00E0\u0089G\"\u00DA\u00F9\u00C5\u00855qw9k\u009C\u00C4\x01\u00B0\u00FF!\u00C6\b\u00BF?&\u00A8\u00C6\u009E*~W\u00B2\u00B5\u00EA\u00E2\u00F4\u00F1G<\u0085\u00DB;\u00C2\rI#f+\u0092^\x12\u00E6\u0086J\u00E1\u00E8\u00A5DuJ\u00B8M\u0081\u00AA\u00FAX\u00FA\u00B5\x11\x19\u00B8\u00FD:\u00EC\u00DFF5-\u00882\u00E3\x14r\u00B5p(I\u00E0t\u00EBN\u00EC\x0EZ\u00C6\u00AB\u00CEP'(\u00A0el\x02\u00A3;\u00A3w2 )\u00E7\u0093q\u0092)\u00FA\u00C8\u0098\x19\n\u00A4\x1A@\x04\u00E5\u00EFN\u00E2>\u00D8\u00C9\x1Ci\u00A7\u00E9Ts\u00C4\x02\u00A2F\u00AAa\u00A9\u00A2\r_\u00BC\u00FE\u00C4\u00ED}\u00DB\u00EB\u00BF\u00FD\u00D7\u00FF\u00EB\u00CDw\u009C\u00C2\x0E\u00DD\u00DA\u00ED\u009Fr\u00DB\u00ED\x1F\u00F4cg\u009E\u00D7_\u00F9s\u009B\u00B3}w\x1A:\u00A8\u00EDV(n\x05\u00B0\t\u008BM\u00D8V\u008BI\u00E8\u00EA\u00F1\tb\u00DF\u0097\u009A\u00A1\u00CC\u00A33\u0093\x0ES\rF\u00CC\u00B1\u0088X\u0082\u0099\u00C7\x15\u00CC\u00D0\u00A5a\u0094\x01\u00A3\u00D0\u0097M\u00A16\u00B3\u00ECF\u00CF9P\f\u00A6\x16\u0088\u00A9\f\u008C\u0088\u00E4\u00BD\u0098\u00D7`\x7F\u0083\u00E7<\u00D8_\u00C4A\x04\u00E1f\u00AEBz!$\x18R\u00AEl\u0093\u00E4\u0096#\u00C0`&\u00DA\x02<F\u00BF%\u00B2\u0099\u00C61T+\u008AD\u00FF\u00D3\u0080(\x1E/)\u00D8q\x0E\u00CF+}\u00FB\u00EF\u008C\x7FPa\u00EEB\u00BAg%\u009E\u00A7x'\u00D4\u00C7\u00A5\x19\u0097\u00A1\u00931\u00D6\x7F[\u00B9\x7F\u00F9\u00C3\u00CA\u0098\"\x7F\u0083\u00B1\f~\x1F2\u00A9\u00959\u00B0V\u00ECC\u00BAz^\u00AAaL\u00E5\u008E\x1C\b\u00CE\u0081\u00AE\u008E\u00D7\x19\x1B\u0099%\u008FM\u00EC\x04\u009C\x03\u00FB\x0B&Z\u00FB\u00AA\u00DF'\f\x1F\t\x0Fn\u0087\u00B0\x06U\u00E8\u00A5o\u00F9\u00B9\u00F1\u0083\u00AF:\u00F3\u00B6\u00CF\u00BC\u00FD\u00F6%\u00AB\f\u00E7o\u009D\u00FF;\x17\x0E\u00EF\u00993d\u00F8\u00CEl\u009C\u009A\u00BD\x10\x0Bk\u0086o\u00C3\u00A6\u00BEc\u00A2\u00D6\x18\x05C\x10\u00A3\b\x06\x15\u008C\u0098a\u00C0\x1A\u0096\x1E\u0081(\x18\u00B0\u00C4\fC\u0091\u00A0\u00DD=\t\u008CAXj\u0091\u0082\u00FEoS)v\x062\u0082D\n,\u00EF\u00A6\u00E5\u00BB8\u00E1'\u00AA\x00\x02f\u0092\u00F0(\u00F9]\u008F\u00B6\u00A2\u00AE\x02\x0B\x1C2\u0083\\D\u00E0\x11U\x14\x1B\u0082\x04\u00E4,\u00FAz\u00A0\u008D\x0E\x15\u00F7\u008E\u00F8\\M\u00D0\u0084\x12BK\u0098\x16\u00A0\x16\u00FA\u00DD4\u0080\u00AD\u009B\x07\\Jz\u00DC\x07\x04\u00B1\u00FF\u0082\b#L]b\u00D1\u00C5\u00C8\u00EB\u008A\x14\u00A7}C\u00E3\x1B_2\u00E7\u00DC\u009FS,t\u0098u\u00AD\u00C2\u00D7F{\u00C0\x04\u00EE\u00FB=z\u0095\u00DE\u008A\u00CC6\u00F4\u00CF>'\u00A0F\x11\u0092\u00BD\u0095k\x1C]u@\u00B1\u008D\u009D\x16\u0095\u0096\u00D7\x07\u00E2\u00A8\u00CFE$\u00E2\u0089W\x05E\u00D8\u0089\u0082pG\u00A2\u00C3\x18E\u00A5x\u00CF\u00A0\u008C\u00F1W\x1BFm\u0087\u00A6\u00B1\u00B5\u00EB?3?\u00F87o\u00F8\x1D\x00?x\u00C8\x13\u00EE`\u00FB\u0092\x10\u00C2\x07\u00FB\u00A5g\u009F\u0087\u00F3\u009E\u009C\u00BBW\u00DC\u00B9\u008D\x12\x7FS\u0080/B\u00B1\t`\u00A1\u008A\u0085\u00A2dC\u00AA\u00A3\x03\u008FPT\u00C5(\x03\x14\x1B\u00E8X\u00F3D#\u00F3&\u008ChX\u00AAI\u00F1\u00AE\u00CD\u00AB\u00E6\u0099d_\u00C2\u00D1\x02f\u00FEo\u00C3\x12\u009Em\t\u00BE*\u0093\u00D8d\n\u00E9\x04#q\u00D2n\u00D1\u00CAw\u00B8\x141cf\u0084\x15O$\u00B3\u00C3u\f\u00E1\u00E2\u00D4\bw\u0096\u008C\x1Ad\u00B90\x15\u0087\u009C\x02f\u00E0e\u00B5&\x01\u00EB34\u0088\u00BB[%$9\x17\u00AC6\u0097\u00F4T\u00BE\u00C2x\u00C9\u00F0\u00EC6E1B\u00D5\b\u00B6!\u00AB\x0B\u00C3\u00E6cD\u0093\u00AC\u00A9\u00E2F\u00B6\u0088\u00D6\x14\u00AA?\u00B0g\u00F2{(w\u00DE\u0085\u00C0jQ\x02\u00DC\u00B7\u00A2\u0081\u0090\u00DC\u00A0}\u0087\u00A1\x11{n\x1F\x0B\x03\u0083\u009A#\u009BT\"\u008Da\x07\u0083*\u00E3\u00C1`\u00E5\u00DA|\u00AE\x12\u00AD\u00C0\u00BC\"\u0082xV\u00DA\u00A8\u00A6.a\u00F6O\u0081\u0091q*LG\u00B7\u00E7\u00E1x\u00D8\nbp\u00C4\u00A1b\x1E.D\u0086)\u00EDSE\u0088\x04_\u00A2\r\u00C4\u009F\u00A7\u00B9\u00DD\u00A4\x03\u00E8&\u00DA\u00E4\u00D2\u00BF}\u00F2\u00D6\u009B\u00FF\u00E8\u00EC;H^\u0087lG\u008D\x10\x0E\u008E\x07\u00E4\u00B9[\u00BF\u00F5\u0082O\u00CC>j\x05*\nG\u00BD3\x1B\u00F9\u00E1>(\u00F6B\u00AC\u00AA\u00A1\u00B8\u009C\u008C\u00C9O\u00AE\u00D9\u009D\x19\u00A8\u00AC{\u00AC@\u00C2\u00CC\x0E\u009B\u00F8\u00CE\u0097\u0080\x06`\u00869\u00A6)\u00CAF\u0090N\u00ECB\u009Em\x0B!\u00CBm\u00B0z\x12\x1Cm\u00D0\x1A\u00C1\rh\u00D3(\u00D9\u009D<c\u008E\u00DC\x1D\u00A9\x13\u0088h\x12\u00DDBw\u00B12\u009F\u00BE\x18\x05\u0098d;\u00D6\u00B8\x02\nD\u00B1\u0085\x12\u00F1\u00F9P\u00E4vq\u00D4\u00975\u00AE\u00B5\u00FA\u0086\u009Cg\u00FF$(\u00E7\u00DAYI\u00AC\u00BD\u00E8\u00F7\u00C5\x10\u00A6\u00D9\u008B\u00DDXR\u00DF\x16 \u00B6z\u00AB\u0083\u00D3\x01\u00AA\u00CC\u0098\u00D4r\r\u00AF\u00BF\r\u00C4\x19\x1E\u0099\u009A\u00EC.ak\u00C8\u00AAH%N\u0081\tv%\u00EE\u0080\u0086X\u0080\u0092\x1Fn\u00D4\u00E5\u00A4\u00F2\u00F9\u0090\u00F3=\x19W\u00DA\x14\x18\u00BC\u0094\b\u00AC\u00B8E\u00A5\u00CCQE\r\u0081\u00FA,z\u00C6\u00FA`\u00CE\u00F9r\u00BA\fx\x19\u00F8\u00BA\u00D3\u0088\u00A9\x006n\u00BD\t\x07.\u00FC\u00CB\x17\u00E8\u00BE\u00EB\u00BECN|\u00D0Q\u00C1\u00F5\u00A3f\b\u00EF\u00D3\u00CB\x7F\u00F8\u00D5\u00F2\u00BAG/fGu\u00DF#j\u00ACy\u00FBE\u00B5\u0092k.K1Sc\x0E#hS\x104]\u00C7\fs\u00ABQ\u0080\u00E6q\x063\u00E4\u009E\t\x06\u00D3US\u00BFVm\x18\x1B\u00F3\u00E5\u0093\u00C97'\u00F4\u0091\u00FA\u00B9KK\x0Bc\u00B5\u0085>\x03C\u00A5\bBSG\x16g4\r4P\x02\u00DC\x1E\x1E\u0080I\u00E1\u00CE\u0085H)\u009C\x069\u00B35y\u00AD(\u00C9\u00FEke\u009F\u00EE\u00C93\u00A2\u00CD%\u00AE\u00C4\u009A3)J\x15\u00C5I\u00A6y\x1C\x02\x17Z\u00B7\u00E3\u00DD-i\x1D\u00E2^\u008D\x18\u0086\u00AF\u00E3\x1EFK\u00B0\u00B2\u00B2G7\u009AV\u00A3\x163\u0081\u00B2\u0086=\x07\u00A1\u0086\u00E0\u008Ad\u0081\x1CSm\x1AI\u00D9\x18E\x17Xm\u0087\f&\x120%\u00D9\u00C3\u009F#\x18\u00AA\u00C2n\u00EF3\u00EEns\u00C1\u00CA\u00CA*K \b\x1D\u00A0\u0092F:\u00CFRh\u00DE\x07\x03\u00A6\u009C\u0099\u0091\x17\u0084\x17\u00C1\u00A3E3\u00A2\u0081\f\u00D7\u0082\u00E8\u00C9\u00BC\u00CD9>B=5]\u00F8n'\u00A8\u00C3\u0099e\u00A8\"\u00FC\u00CC\u00E7+\u00AA\u0085\u00BFwn\u00C2\u00CB\u00B9#\x0F\u009F}\u00E8\u009D\u008F\u00DE|\u00DB\x1F\u00FD0\u0080\u00BF\u00C0Q\u00B4\u00A3R\x19\u00B6t\u00FF\u00F0\u00DA\u00C5\u00AB\u009E\u00FB\u0099\u00B5O\u0082\u00E9C\u0099Nt\u00D7\u00B5-\u00B1\nK\u009B\"\u00D8tMo\u00E9\u008C`[\x1A\x16\u00B2fFDI5`!\x16\u0094\u00B4\u0080`)\u00E2\u00C7\u00868Gai\u00CE\x195oU\u0091\u00BA\x0E\x18\u00B5E\u00D2\x13\u00D0\u00D0\u00B5\u00A1\u00AB\x17\x19\u00E1y@\u00E6=\u00A8\u00B8\x1Ab\x7F\u00A32\u00B8)\u00D5\u0089\f\u009Dr\u00C4\u00D0\f\u00B5\u00D0gR\"-\x10!\u00C4\u00C5\u0086\u0091(\x03\u00F9\u00BB\x1B\x1D\u0099xcp\u00D9\u00D5\x00I\u0088*Q\u00C4\u0083Q\x1D\u00EC\u00CEJ\u008F%\u00C6)\u0085[\u00985\u00D8\u00A8>P\x1D\u00A9R\u00D4\u00FF\u00FC|\u00A5\u00AD$\x1A\u00D5\x02S-x?\u0086\u00A5\u00F3\u0098\u00F8^\x16i\x0Ba\x02\u0092\u0087\u0093s_L^\x13\u00A1\u00DA\u0086\u00DC\u00C2\u00A8\u00E9\u0089E5iI&\x04\u0098\u00FFf9\u00BCz\x7F2f\u009E7\u00E4\u00BB\u00E0\u00EFJ^\u00C4\u00F7\u00C5\u00C7\u00F4\u00F7\u00C1\u0094xJpE2\u00E7\u0089\u00D4\u00F7\u00FB3\u0097#\f\u00B1+\x06O\u00E5\u00F6w\u00AE*Z\u00AF`\f\x07\u00C3\u00BB\u00D6\u00B6\x0E\u00A0\u00BF\u00FBo\u009F\u00AB{\u00AE=*\x0F\u00E2Q!\u0084\u008B\u0097\u0097<\u00ED|\u00FD\u008Bo\u00F6G=\u009A.\u008E\u00BA\u00ED\u0085b\u00B7\u0096\u00DC1q\t\u00ACs,\u00B1\u0086\x19\u00E6X\u00AAW\u00E8\u00C1\f\x03f\x18\u00A9\u0097\u00AAy\x00\u00A8\u00C7\x0BfX\u00C0\u00CB\u00A3\u00A9\u00EB\u00B5B\u00C8_\u0089\u00D9\u00EC\x0BT\x03h-\u00CE\u00CC\u00C9d\x02\u00AA\u00D6\u0087*U\x10[l#\u00A6.Q\u0084\u00A4\u00A1^\u00AF\u00C8\x1C\u0087\x1E\u0092H\u0084\u008B5-\u00D8*\u00C4\x14\u00B9\u00A0\x01\u00F8\ne\u00BCCK\u00D5W\u00E9\u00EB&a\x02\u00DA\u00E7\u0081dW\u00DF\u00A1]\u00E7\x12H\x11\u0084\u00CB\x05\u00E9g\u00D9\u00FF]\u00C2\u00C7\u00BD\u00BA\u00DF\u00A7\u00BB\u00C2TC\x1EaRXH\u00CC\u00A1\u00AC\u00B4<\x17\u00FC\u009DF\u00BB\u0094\u00C1\u00C4R\u00A6n1\u00B88\u008Dyv\u00FD8\u0091K\x19\u00FCS=\x00<\u00CFK\u00AE\x03\u00A6b\u00B0\u00A8\u00CC$\u009C\u0098\u0090\u00DF\u00DF\u0099\b \u00A3\u00DB\"+\u00E2(\u00A8\u00CB\u00E3\x13\u00D2\u0080\u00E8\u00EF\x03\x00d,9\x14n\x00\u00AE\u008C\u00BE\u00C4P\u00A8_?\u00D9\u0087\u00C2_M<#U\x11g>\u0082\u0086\u00F9\x15\u0097|\u00F3\u00D6\u00DF\u009E\u00F74\x00\x7F\u0086#lG\u00CC\x10Tu\u00F8\u00D9\u0083?\u00FB\u009C\u00EB7\u00AEu\x18tH5\u00E7.i\n\u00DB\u00A5\u00E9F\u00B0(+\u00A3\x18\x07\fXC\u00935\u00CCt\x0E\x11&'\u00CD0b\u0086&&\u00D1Fa\u00DC\u0080y\x00F\u00D0\u00AFN\x1F\u00FEDi\x00`\x1E\x0B\x11[z45\u00D1\x7F^\u00D1\u00C1\b7zQ\u00AATKsi#\u00EF\u00D7`\u00BEeg\n\u00E1U\u00D8\x01\u00DA$\x18\x15\x02\u0086*\u009A[\u00F0\u0094\x12<\x16\x14U\"\u00E4o\x023B1b\u00AF\x19\u00B3b\u00982O\u00C9O$\x07z\x16\u00C8P\x00\u0090qu~v5@5rv\u0092\u00D1\u0095\u00A8\u00C3\u00B8~\x063B\x1B4\u00AE~\u0087\u00D8iZ\x19\u00AF\u0090\u00CCJ\u0099\x02\u00DD\u00FD\u00DF\u00D8\u009B\u00D1G-\u0095\u00C1\u0098\u00AD\u00C0\u00C2\u008E\r-51U'\u008B\u00A0:\u00E3\u00B5\u00CEB\u00FD\tB\rX\u00CF\u00C2|~\u008C\u0084\u00A7\fX\"\u00D3HT\u00A4|n\u00C9\u00FB\u0090)\u0088o\u00FAjU\u0092\u00B8;\x15\u00D5\u00A0\u00CA8\u00874\u008D\u00F5\u00C1\x18\u00EC\u00A4\u0080\u00AD\u008F\u00A8\u00DA,\u00BA1\u00B3\u00F9\u00D6\u00AD8\u00F8\u008E\x0B\u009E\u00A3\u00DB7\u00BEF\u00D6\x1ExD\x1B\u00BD\x1C1Cx\u00DB\u00F2\u00A2\x1Fy\x03\u00DEpV/n\u0097\u00BB\u00BBmA\u00B0G\u00D4\x03\u0096\u00B2\u00CAQ\u00D3\x01s\u00CF#\u0098\u00C1\u00CA\u009C\u008D\n/\\\".\u00F7\u00FD|e\u0088q\x03\u00D3\u0091\b\u00EBI\u009C\u00DDa\"\x11C/\x7F\u00B4*\u00AB\u00AB\x12Le\u00EEE\u00D7\x0F\x1D\x15\u00A9Tqy4\u0080\u00C2\u00DA\u009B\u00BB\u00ABVtT.\u008E\tq3\\9\"\x1A+|\u00AF\f\u00C5\u00DF\u008E\u00D6s)I\u009Cx\x18z\\\u0089\x19(v\u00DA\u00B0F\u00F8\u0093d\u00B7\u00F1\u00C5C\t[q\u00EDY3\u00A2\x11.\u00F6\u00C8\n\u0094\t\u00AA\u0091\u00B8\x7FYM\u00D2\u009Cq\u0090\u00E0Sb&\u00FB\u00E2x\x14\x19i\u00C99\u00F0-\u00E0\u00E8^-Oa\u00FD\u00F9y\u00B1\u00E9m+D\u00C9q4\u00C4N\u00CF1A\u00EE2\u008C\u0099R\u00D8\u0096\u00F1d\u00C8\u00EA\u00F3\u00CBq8\u00EAP\u00B1\u00F3\u00C2>\u00C0s<\u00E9I\u00C9D\x15i\u00F4\u00E4\u00A9\u00E5])\x19d\u00FE\f\u00AA\x12Dm\x00p\u00E5\u00FB\u00CF\u00DA\u00FC\u00AB?\u00FE\x11\x00\u00AF\u00C5\x11\u00B4#b\b\u00B7\u00EA\u00A6\u00FC\u00D2\u00E6\u00AF\u00FD\u00DA\u00B5\u00EB\u00D7q\u00CAA\x18ww5\u00F5\u00FB\u00EE\u0085`C\x05\x1BX\u00C3\u00809\u00E6\u0098a\x01\u00C16\x18\x13g:\u00F3\u00E8\u008C\u00C2T\x06\u008F\x13\x103\u00D4u\u00D0v\u0090\u00CC\u0080\fB\u00C5\\\x7FZ\u00CEcP\r=\n\u00C6\f\\\u00CE021\x10\u0085\u00DB\x1E\u0094\u00B5\x18m\u00C1\u00D8\u00B5\u008C5pfA\u00D7\u00A0/\u00A4\u0088\u00D8\u00A3\u00F4p\u00C6\u00A4Z\u00B2\u008Cb<izk\\\u00E4\u00B1\u0080\u00AB\u00C4\u00A1k\rf\u00A8\u00F4Eg\u00C2%\u00D3\u0095c\u0092)\u00D0\u00F3c\u00F9\u00C4\u00B0j\u0085p\u00FF\u00C4\x02\u0084\x18\x1A\u00ADN\u00C4y\u00CC\x19B-G\u00C4\u00BD\x16\u00E3\u0086\x03\"\u009BR\u00B8\x1D\u00BC\u00C6b\u00CF\u00C1\f\u00C8\u00A2\x02\u00F4| \x18\u008D\u00D0c!\u0085q\u0088\x13\u00BD{J\u008CQ\x15C\u00A2\x1FWe\u009C\u00AC1\u00AExVN\b\u00FC\x19B\u00FD\u00D0\x1CK\u00BC\x1FH\u00CF|h\x00\x00 \x00IDAT2)\x7F\x7F\x1C<\u00BD\b\u00BC'g\u00D9kn\u00E6\x032\u00E4\u00DC\u00FB\x0EF?\u00B5\u00D5I\u00E9)z\x14`}q\x10\u00FB\u00DF\u00FD\u00E6_\u00D3\u00CD[\u00FE\\6\x1Ep\u0087I\u00F4\u0088\x18\u00C2\u00C7\u00FA'\x7F\u00E0\u00AF\u00DB_=bl\f\u00C79vm\u0084`\x0F\x04\u00BB\u00C4\u00EA\x1C\u00CE0\u0083e32'\u00C1\fg\u00AD\x18\u00EBf\x10c\x10\u00C8=\x16\u00AC@\u00AB1\u0091\x11\u00E9\x11\u00C8\u00FA\x04<f\u009F\u00C7X\u00E0R\x00/\u00E2Md\u00C0\u00921\u00A4\u00D0\u00FF\x1D9\u00B0\u00CA\u00D3\f\u0082\u0081Y\u008F\u00D5_\u00AE\u00E2{\x01\u00B4\u0082\x10\\\u00AD\u00E0\u00FD\x1A\u00DC5V\u00AC\u00FC(\u008CB\u0090P\x14\u00F9\u009B\u00AAB:\u00DD\u0096\x1A\u00BF\x1B:\u00F1\x05\u00C8 \x1F\u00C2__\u00BC\x11\u00DE\u00CFE/.\u00F5\u008A\u00CBW\u00D1\u0080\u00D6c\x1Cy<%\\\u00F0\u0083\u00C2{\u00F2\u00FA\u00CA\u00EC\u0090D\u00EEscS(\u00C9T\u00C4\u00C4Q\u00E8\u00E4\u00A1V\u00A1\u00F4SB\u0096i\u0083\u00C12\u0099\x02\x00f\x1F\x16\u00A3K\x19\u00D8*j`\u00BF\u00C5\u00E5\n\u00F3\x10\u00E5\u00FE\u008E\u0095<\u00CBg\u00AA\x0F\x15!\u00C5\u00F7\u00BE\u00D2o\u00B9'\u00DD\u00B5\u00DEO0\u00DA\u0095Qq\u00B8\u00CCg\u0099\x7F\u00E4\u0092Gl\u00BE\u00F5U?\x00\u00E0\u00AFWO;\\;\"\u0086\u00F0\u00EA\u00CD\u00D7\u00FE\u00D2\x17v}z\x02\u00BD\u008E]k8 k\u00D8\u00A3s\u00CCd\u00869\x1A\u00E6\x180s\u00A8O/\u00C2\u00A8\x1E\x13\u00E0\u00DF\u00850^\u008C`\u00C3=\t\x16S\u00B5z\u00CC3\u00A4=\u00A0*\x1C\u00F4H\u0088\u0087(CX\u008E\u00ADM\x18\u0087@0J\u0095\u00E3l\u00CC\u00A5\u00ABe\u00C1\u00EDy\u00F2\u00AF\u00C0X\u00BF\u0086\u00E7\b\u00A6n\u00CEC.< \u0098\u00CB\u00E4\u00FA\u00D2'\u00A5\x1E\u00C7\x1A;9\u00C5@\u009D\u0089q\x0B5Bog&\u00C9\x07\u008A\u008A\"\x15\x0Ep1S\x15J\u00C8\x1D\u00DF\x05\u0098\u00D6\x13 %\u0093\u00C0c\u00A0S\u00E6P\u00FA\u00B1\u00CAHb\x04\u00C9\u00B4n2\u008D\u0089\u00C4\u00ADh\u0096(\u00CA\\\u008F\x10xl\x1D\u00E7\u00A3>W%\u00E0\u00AA\u0096\bl\u00AD\u00AC\x14\x0FV\x1ASW\u0086\u008BJ\u00F0\u00E5\u0084>\u0094gc\u00FF3djt\u00B9\x07U\x03E\u00B9o\u00B6:C\r\u0082\u00F5\u00AD\u00FD\u00B8\u00F5\u00A2\u00F3\x7F\tG\u00C0\x10\u00EE\u00B0\u00DB\u00F1\u00E3\u00FD\u00EAG\u00BEux\u00D3\u00D9KYB\u00CB\u00FF\u008EE3\x0B\u00FE\x00\u00C5:\u00F6\u00A0a?f8\u0088\x01\u00DB\x10l\u00A3a[\x1A\u00B6\x01l\x01\u00D8\u0086E$.\x01l\x0B\u00F7o\u00F0hEa\u00F8\u00B2\u00FD\u00D1\u009E\u00ABJ\u0097\u00A2\u00AB\x12d2\u00A8\u00C1K\u00B5fB\u008D^l\u0081:\u00F2x+\u00E7\x0F\x18\u00DD\u00DB\x11\x05R\"\u00B1I\\j\t\u00D0\u009C i0\u00EC\u00EE\u0092r\x03\u00A9%\u00FA\t\u00CCHg.B\u00EE\r\u00D1\u0099\u00F3\u00D0\u00FC\u00CFU\x17\t\u00B7\u00A0\u00F5),\x0E\x02\t\u00F7&\u00C2\u00AB\u00A7N\u0093\x19\u0089g\x11\u0084\u008C\u00A9\u00F0\u00F14\u00AB\u00C0\u00C4\u00F8\nV\u0084b\u00A2T\u00BAS\x19\u0088\u00D5\u00C2\u00E8\u00CB\u00F8~i\rh\x12\x02^buq\u00FC\u00C0D\u00AD a\u0085\u00EE\u00EF\x7F\u00F1y\u00C8\u00F9\u00EC\x00m.dzT\u00EF\x10\u00EB\u00C8\u009F\u0085\u00E4P\\\u0083\fI'z\u008A\u00F8\x01\u0097\u00DA\x1A\u00E7\x0Bv\u00D6\u0093l\u00D8\u00C1\u0094\u00E3\u00B3\u00C4\u00DC\x07\x02,\x11\u00A8\u00F9\"xIA\u008C\u008C\u008E\u00E4\u00B9\u00EA\u00E3\u00AA\u0081Sn\u00BC\x1C>|\u00F9\u00D9\u00E3\u00BB^}\u0087\u00EB/\u00DEa\u0084\u00F0\u009A\u0083\u00AF{\u00D6G\u00D6?\u00E4\u0093w\u00D7\u0084*\u00DF\u00B1F\u00AEi\x05D\u00972\u00C7\x1E\x15l`\u00C0L\u00E6\u00B0\u00C0\u00A1\x19\x06\x190x\u00CA\u00F2\u00B6\u00FB\u00B3\x1B\x06,]\u00CF\x1EP\u008D\u0084%\u00B6\u00C0\u0089\x19\x11b\u00EC/\u00CB?\u00B7\u00A4\u0098\u00F2?#\x06\u00BB\u00D6j9u4,]M\u00D1bl\x14HT\u008B\u00EER\u00FAP&\u00D3\u00F8\u00BD\u00B9(\x14\u00BE\u00D8J\x04\u00A2\u0087\u00AE\u00DA\x16WC\u00AA\u00B3\u00CD\u00B3\x02C\u00FAJ\\_\u00AB;\u00D9\u00FE\u00D9U\x1E\u00DAB\u00CA\u00FD\x1E\u00FDwJ\u00FBf\u00C4\u00A4]3.H\u00D2\u00A1G_\u00F8\u00A1\u00E5\u0083\u0093\u00B8 \u00A1~Qv\x15\u00D5\u0080\u00D6\fvG\u00BEB\u00CFc\u0085\b\x01@\x19\u00A4t8\u0099\u00E4\u00E1\u00DE\fr\u009A\")\x1F\u00B5\u00D8;\u00B0@\u00DB\x15[\x06\u00EF)\u00E4'R\x0EW\x14\u00B4\u0082\u00CC\u0082\u00C9{\u00B5\u00A4\u0089)\u00B9\u008Ew\u008Ax\u00D4\x03\u00CCRm\u00C9\u00B9\u00CBHF\u00AEI\x18\u00BA\u00D1j[\u00C8!e\u00AE\x06\u00B0~\u00CB\r\u00D8\u00F7?\u00DF\u00F4,\x00?y\u0098\u0099\u009A\u00B4;\u0084\x10>\u00A7\u009F9\u00F5\u00A2\u00E1\u00A2\u00A7\u00F5\u00B6<D\u00B2\u00C5\u00DD\u00DD\x1A,P\u00C5j()\x1A\u00F6\u00A3a\x0F\x06\x1CD\u00C3\u00A64l\u00A2a\x1B\rK\u0098\u0091q[\x04\x0B4,\u00D0\u00B0\u00D4!*7/a;D-1\u00D81\u00FF\x1Ba\u009E\u0083Q%\u00BE\u00DB\x06\u00B3\u00E2N(\x1A!3\x16>\u00AB0\u00F38\x02Ce\\\u0082\u00EC@\x12,\u00E3\u0082\u00F8\f3 \u008A@=?\u00A03\u00AB\u0090\x01+2\x00\u00ADAZ#]\u00D9\u00F9\u00B1x\x1A,\x04\u009A\u00EAAap\u00DE\x17\x19]\u0081\x04N\u009C)\u00BD#8F\u009A#\x11{F\"+\u008D\u00BE\u00AC\u00D8LV\u008F\u00AE\u00FD\x16U\u00A8d;\u00C6_0\u00AB\x1A4\u00C51\u0094\u00B1\u00C0\u0088\u009B\u00D7\u00B115|zO\u009F\u0094*\u00A9\u00CBsR\u00C5\u00D06C\u00A8\x07\u00BC>\u00E2=*\u00E3\u00F79.\u00EF{\u0095\u00A0\x03\u00ADL\u00A4\u00BB\u008F!\u00CA\u00B4U\x02'\x1AA9\u00B7\u00F1e\u0096\u00E7\u00A9\u0081JD\x00\u00F5y\x15\u0099FM\x04\u0091c\x12\fh\u009F\u00FC\u00C0\u00D3\u00F4\u00AA\u00CBO\u00C5\x1Dhw\b!\u00FC\u00ED\u00F6\u00C5?ua{\u00D7F\u0096!96\u00CD\u008Cj\x1E=\u00C7\x04\x19X\u00C5\"C\t\u00CD}\x0E\x19\u00C1\u00D8\\j\u00E7&.$\u00CC\u00C1\u00A59\u008D\u0089\bb\u00A6\rB\u00D4\u00E0q]\b\u00A6B\b\u0096\x0E\u00E1\u00E8A\x10 \f\u008E)u\x1D\x11\u00B8[\x12\n4\u0087\u00EC\u00A3/\u009C\u00EER\u00D8\x02\u00AC\u009C0Cof\u008C\u0081C\u00C4\u0080\u00A5\u00BE\t\u008D\u00B2\u00EC\x1A\r\u00A1@\u00C1\u00DD\u00DE\u0097\u00BA\u00F7.%\u008F\u00C8\u00E0k\u0089\x12\u00DB\u00A0u\u00F3\u00EB\x1DX\x17\u00C9\u00ED\u00A8#\x10\u0093\u00AD\u0082Fh\x1A\x02\u00D8-\u00FF\u00F5\u009D\u00B5\u009Ee\u00D1\u00C4;\u008B\u00BF\x0E\u00D1\u00B9I\u00BBncQ\u00D7\u00FFm\u00F8\r\u00DA\u00DDU\u00E7\u00AB^\u00D0l3Z\x12\x1F\u0091\x07\u00FC\u00DF \u008AUi\u00EF\u00CF\u00AD<\u00DF\u00F2\x1D\u00C2\u00DB\x02\u0084qR\u00DD0\u0087.\u00E6\u00FB\u00F7\u00AEl\u0095\u008C\u00D0\u0088_h\u0085\x10]4\u00A3\u00C16m\u00B1\u00F1\u0087]#\b\u00DE\u00CFS\x18Qw\u00D8<0N\u0082\u00E7\u00F0\u009A\u00E2\x16\x15\u00F5\u00FE\u00B8\u00DB\x16\u008D\u00C0\u00BD\u00C73\x11aj\x17HW\u00B4q\u0081\u00F5O\x7Flc\u00FF\u009B\u00FE\u00F8\u00A7\x00\u00BC\u00E00\u00A4\x15\u00EDv\x19\u00C2\u00D6\u00F2@\u00FB\u00D7\u008B_\u00F8\u00E9\u00AD\u00B5\u00BDH\x7F\u00F7\u00B1jU\u0082\f\u0093\u00BFmi\u00B8Q\x15\u00EB\u00DA0\u0097\u0086-\u00D7y)M\u00A2\u00DC\u0089\x13\u00961\u008A\x04\u00D1\u0091\u0081H\u00E2\u00F2\u00F3\u00A8\x0E\u00D4\u00C2 \u00EA\u00C46\u0080\u00B6\x06\x7F\tN\u00B0\n\u00EE\x1C\u00E8\u0088@H\x10\u008C9K)\u0093\u009E\x02\u00C5\x10\u009B\u00C6\x12\u0099\u00B2\x10k\x03\x03\u009D\u00D2Ah\u008B\u009D\u00C7\x03\u00E2\u00FB\u00FAa\u0090S\b\u00A0p\x11\u00B8z\x000\x04\x01\u0088s\x10\u00C7\x11gec\u00E4\u00BCS\x07b\u00B7e\u00EA2A\x1C@\u00D8\u0097\x18OP\x02\u009BHaY-\u00C8]\u0080\x11}\u00C8-\u00D58\x1E#B\u00EB;\x0B\u00BA\u00A0\u00E4E\x04\u00C3!\u008C.\x1B\u00D1N\x1F'\x1F:B\u00A4\u00C3\u00E0gk,\\\u0084\u0084\u00FB\u00CED\u00ECJF \u00FA\\2\u00DD\\\u00BB\x07Ka\u00F2{DG\x06\u00AA\u00F1\u00BE\u00AB1\u0095\u00EE\u00CBp3R-(\u0088F+\u00B3@\u009E\x17\u00C6S\u00FF\u008Dh!fE1h\x07>|\u00E9O\u00EB\u00DE/\u00BEH\u00EEw\u00F2m\u00EA\u00FB\u00B7\u00CB\x10\u00AE\x18>\u00F1\u00A4\u00BF[\u00BC\u00F5\u00F4\u00DE\x00\u00A0\u00EF\u0098\u00E3\u00BB\u00AFQ:\u00CE\x01\u009D\u0081\u00A9\u00BB\u00B99\u00EA\f\x0B\x01n\x15\u00B3.\x18A\x0E\u0098\x17\u0083\u00E0\u0082\u0084\u00AA\x1E\u00B4\u00E4}2\u008Dut\u00A2[B\u00C0-\u00E0\x1A\u00EAn\u00D3\u00FE\u00AA\u00D4<\b\u00CB\u0088\x104\u00F7\u00E6\x00\x17dRC\u00953\x17\"\u00D3PZ\b7[BL[\u00CE\u00BA\u00C7\x11\u00B5X\x16\x14mF\u00CCW0\u00B4Rc\b\u00A8\x1Ad\x16\u009F\u00F9\u00D4\u009B\u00AB\u00A1I$<>\r \x00i}:\u00EFQ\u00E3P\u00DD\u0080\u00EF\u0096tqw%\u00D1\u0086\u00EAd\u00B9F?\u00E2\x01J<&d-\n\x0B\u0097\u00E4\u00D8\u008C\u00E0+\u00E1\u009A\u00FA\u00E4\u00DE\x00h\u00CE\x0BLR\u00AA\u00BB\x1Fm\u0088C\u00DC\u00DD\u00EE\u00C5 \u00A2\u008E\u00F0\u00FD\x0B\u0090\u00AEK\u00B7~T\u00C3\u00DF!9e\x12\u00A5\u00ACfY2\"\u00B1\u00AF\u009C\x1B\u00AB\u00A5\u00A0\x178\u0093\u0088w\u00CE\u00E1\u0092q\x14\u0084\x03gT\u0093\u00EFd4dF<V]\u00AB|\x17\x1C\u008F\u00A1\u00BB\u00E1\u00C3\x1F<}\u00EB\u009D\u00E7?\t\u00C0[q\x1B\u00EDv\x19\u00C2_\x1D\u00B8\u00E0\u00DC\u00CF\u00AD\x7F\n\u00C7\u00CA\u00A3\u0090-\u00E3\x0B&\u00BB(\x17i\u00DB!\u00B8\x19\u008A\u00B96\u00CC\u00BC\u00E8\x063\u00D7\x07\x15\f\u00D2\u00B0\u00D4\u00C8\u0096\u008FpUb\x00\x06+\u008D\u0092\u00B6\u00F4\u008E\x16D\x1D\u00DF\u0085F\u00C3|\u00F9\u00E6\u00AC\u00E4&\u00B0\fE1o\u0086p\u00FB\u00B5\u00E6\u00EA\u00853\u008D\x0E&#Y7m\u00B2\u00A0\x04f\u0085\x07ha6!\u00CB@\x19;G\u00DD\x00\u0099\u00B1\u00F3\u00D6\u0099]\u00A7\x11Al\u008Bgp:3\u00A6\u0091&\u00A96]\u00C7\u0094\u0080nL\x14\x1D\u009C\u00E2\u00B8\u00D0lQ\u00A6+\u00D1\u00CEW\x12\u00B8z\u00BF\u00CCk\u00F0p\u00E90\x12v\u0089\u00F8 \u00BB\u00B4\u0085!\u00CC\x1Em4\x03\u00A7\u00C0\r\u009D\u0083=\u00B5\u00AA1\u008E\b\x02\u0082\u00ABt\f\u00A1\u0097\u0082\x12lL\u0089\u00A5\u00EA\u00D4:\x1AQ\u00CFbd9\u00F8|\x14\u00FE'\u0099\u00F8\u00A4\u0093\x16\u00CFaII\u00EA|\u0095\u00BFs\u00D2\u00F3\u00A6\u0096\u00DA>\u00C6\x1C\n\t\u009EHG\u0099\u00B3A\u00A9\u00AFN\u00E3\u00BD\u0080\u00AA\u00D5~\u00BD\u00BE$#\x1C+\u008D\u00BA\u00AA\u00C8\u009A\u00A3\u00EB\u00CB\u00FD\u00B8\u00E5\u00C27\u009D\u008B/\u0085!\\\u00A3\u00D7\u009Dt\u00EE\u00E6\u00B9O\u00EDm5\u0093\u00F1\u00EEW\x1C\x04\u00BE=\x19\u00B7>\u008F\u00B7\u009B\u00C6 \u0095\u0086\x05\x06|\x11\x03v\u00A1\u0099r!3\u00CB\u00CD\u00F3\u00AAI\u0083\u00DB\x06b\x03\x14\"\x044\u00B0\u00B8i\x0B\u0084\u0090\x0E\u00B6\u00E0\u00C3*\u008E$\u00CC\u00E0\u00A3j/\u00BBF;\u00DA\u00B9C \x03\u00AB\u00C8l\u00E3\u00ED\u00A0{\u008D9\x11\u0088gY\u00F2\u00EEb(fbd\u00E2i\u00A17\x0B&\x1B\u00BF\u0086[\u008D~\u00F4B\u00E1d\x1E\r\u0099\x0B\x00\u0097\u00AE\x15\u0089D\u00D3\u0089\n\x10\u00D6mF\u00FD)-\u00FCIx\u00B9:8>\u00EFg\u00D2\u00AF\x13\x06\u00C7O\u00A9\x06M\u00A9\x1D\x1D\u00D5<\x01B\u00EC\u00ACM\u00C0\u00E4&S\u0097\u00B4\u009C\u00EB\u00F7Q\x14\u0086UU\u00AD\u00D2\u00BF\x02\u00CAd#\x12\u00DFd\u00D1\u0089?\x7FM\u00BEb\u00AA\u00B2\u00A3\u0096\n\u00D7\x0F\x19qE\"g\u00FF2\u00F9=\n\u00B8\x06\u00A6j\u00E5x\u0099\u00A7\u00C9q&b9\u00E3\b\u00F5&\u0091\u008D\"U\x07\u00AA8kW\x7F\u00F8\u00A9\u00FA\u00F9\u008F\u009D$_\u00F5\u008D7\u00E30\u00ED6\x19\u00C2\u00FF\u00DA\u00BE\u00E2G\u00DF\u0087\u00F7l\u0088\u00CE\x00\u00B9Sw\u008C:\u00C2f\x15\u008F\u00B8\x11+K\u00A21;P\u0095\u00B6\x02\u00B3\u00E8n\u008Ab\u008F6\u00AC\u00C9\u00CC\u00E2\x15\x15\x18$}\x01\x10\u00B5]\u00A0}\u00F1\u00B2@\u00C6L@\u00EC\x00q\u00D4\u00D0a\x1E\x07\x06%\u008D\u0092R\u00A3\x01\x101\u00CF\x04\u00B9;\u0097g\u0087y(\x06\u00B4\u00A0\u0091\u0080\u00F9HGT\u00DE\u00D1\x18\u00C4\b+Y\u00A2M`{1j\u00BA#\t\u00FF\u0095R\u00CF1\u0085\x1B\u00B7\u00B4\x19R\u00E0\x1Aa\u00B5\u00A4\u0088e\u0080\u0086kR\u0089>J\u00B3{\"U\x0B\u00A1\u00DE\u00CCPe\x01\x0B\x1D\u0087\u00FBs\u0082\x12J#4'\u00DD\x13\u00DD\u0088B\u0086\u00F2\u00A3\u00DFk\"1\u00C3m\u00D7\u0091\n[\u0081\u00F0\u00D2`%\u00D4\u00FD\u009C\x1D1\n\u00F53\\\u00AD \u00F3\x17d\u00895\u0087\u00E0\u00F5\u00BB]Q\u00FE\u00F1\u00C8N\u009A\u00F2\u0083\u00D1\x19^\u00A4*S\u00DFh\u00BD\u00F7\u00E4_\u00A2\u00B4\x18\u009F\u00AFY q\u00CC\u00AA\u00CA0Q\x1D\x14\u00C9\u00DC\u00E2\u00E9l\u008D\u0094\u00BD&\x0E\u00D5T\x15\u00C35\u009F\u00DC\u00B8\u00F5/\u00CF\u00FBQ\x00\x7Fx\u00B8\u00F3n\u0093!\u00BCe|\u00EB\u00D3\u00F7m\u00DC\u008A.U\u008B\u00BE\u00BB\x1B_d\rr\u00A9\u0093\u00E6\u00A4F\x03\u009C\u00BB\u00DFn\x11\u00C5\t\u00AE&\f\u00D20\u0087\u00A55\u00CD\u00B5\u00C4\x1C\u0088\u00A4\u00FB\u00AC\u00FC\u00AE\x1E\x13\u0090\x11\u0088TQ\x10\u00D2\u00DD\x18\u00D1,\u00DCf\u00D3\u0094ic2\u00CD\u00EF\u00D1\u008A\r!\u00AB)Q\u00EE\u00D4c\u00F463\x10\u0088l$\tz\x12\u00AB\x0F8<\u00A5A3\u0099\x1B\u00AB\x1B\x07\u00C4wv\u00C4\u00FC\u0088\u0080\u00C1&\u00FA|\u00AE)R(\u0095\u00ED\u00B70\u00A4z\u00EE\u0083m6\u0083P\t4\u00A4\u00A3&\u00BC\u00ADR\u009F\u00F7\u0086\x16T\u00E1\u00C3\u00EA\u00E5^\u00E1?K\u00F4!A@=\u00C7\x05\u00D7/te\u00DC\x05N\u00A7\u00BF>\u00F36R\u00F7N\u00C9\u009E\u00DB\u00B7\u00AD\u00A2`#D\u00FA\u009F\x02\u00B2+@+\x7F\u00C6\u00E4Ti^\u00A4\u00BDq\u00D1r\x1FC9\u00E6\x1D\u00A0\u00FA@\u00A3$\u00C7\u00E1\u008CC\u00810pr>\u00CB\u00BA\u00B7\u0090mu E\u00EF\u008E3\x07N\u00A3??\u00A7k\u00DE\x17\u00D8\u00FA\u00D0%O\u00C7\u00D10\u0084\u00EBu\u00DF\u00C3~x\u00F3\u00A9\u008F\u00EF\u0093\u0082\u0095\u00C7\u00A25\u00E4\u008E\u00C0\u00B5\u00EA\x11an\u0081\u00D6\u0085\u00E3.z\u00C3\r\u00E2\u00FB.\u00A9:B\u0080o5_!\u00B9\u00873\u00D3\u00BD\x07I}0\u008EcBLD#\u00AC\u008A<C\u00CD\u008Bp\t\\\u00DC\u0091D\f-\u00A4\u00A4\u00F8{\u009A\x16+\u00B1:\x07\u0095\u00E1\u00A1\u00C0k\u00FF]\u00A6\x0B-\x17_e\x1E\u0088\u00B1&\x13\u0091\x102\u00A1s\u009Ae\u0091O\u0097:\u00BD\u00F0?\u0089\x00\u00EC\u00D2\u008C\u00DD'I0/B\u00BB)H\x13)jGR\u00FA\u00F5d\x1A\u00B1\u009A\x1A\"<:\u009FSs\u00C9\x15t\x13\x03\u00E9\x1E\u00A4\x15\u00DC\u0087\u00CF\u008A$|Jc\x1Da\u00A9\u00C6\u008A\u00C8W\u00E0\u00C6\u00ACT7xM/\u0088\u00A3zE&\u00C9H\u00F5\u00CF\x07\x14\u00F5\rP\x1E\u00AC0\u00AENU\u0087\b!\u0099\"\u00ED?vz\u00EEx&\u00D1\u00EF0\u00ED\u00DBm*\u0093\u00A4,\u00AE}\u009D\u00868\u0093\u00FF\u00A6\u00EA \x18>\u00F9\x0F\u008F\u00D7\u00CB\u00DF\u00F30\u00F9\u00D6\u00EF\u00BC\x06\u0087h\u0087e\b\x17,\u00DE|\u00CE\u00A5\u00B8\u00F4X\u00C1\u0082\u00D2\x06\x00\u00EB\u00B0\u0082'T\x0B\u008CP\u00E9\u00C3O\u0089@\u00A9`\u00BA\u00EAAi\u00D8\u008B\x01\u00BB X\u00D3\u00C1\u00B3!\r1\u0088\x133\u00C9\u0088\fB\u0085\u0092:\u00E3\x16F\u00CF\u0094\u00CC<\u0082V\u00CE\u0099\x02Nj\u00DC\u0095M\x13\u0094\x0E\x18\x10!\u00F3\u00B0\u00D4lH\u008F1\x18w\u00EFf\u0098\u0094d\x02M\u00B88\x04\x16PC\u00AEoz}x.T3\u00E8\u00C8\u00E7\u00A5\u0092s\u00A0\x064wo8\u00D1\u00BA$\x0F\u0095As+\u00B6j\u00C0\u008B0\u00E7p\x0F\u00E6\u00ECW\u009BB\u008F\x1C\t\u00D8\u00EFf|q\u00E3\u00DD\f\u00DDk\x1B\u00C6NG\u0085iA\u0096\u0080\u0088mQ/\n\x1DK=o\x0E?f\u00D5\u00F3\"\u009C\u0089\x18O44\x1B\u008E\u00826\x00\u00A3\u00BF\u00A5\u0089\x04w\u00A2\u009A0\u00D7\n\u00D5\u0081\u0084\u00E9\u00D3\x16\x01\u00A0$\u00BC\u00B0\u00C1tT\u0094\u0092\u00E8\u008CL\x0B\u0098\u00AA\x15~\u00A6\u00B62\u009B\u00FE,\u00AAa\u00C7\u009DVkv\u0083\u00EC\u00C4\u00C0Xb\x14z\u00BE\u00F5\u00C9\u00F8; :b\u00D7\u00F5\u009F\u0093[\u00DF\u00FC\u00FAs\x00\u00FC\u00D7\x1D\x03\u00C1m0\u0084\u008B\u00B7\u00DFs\u00CE\u00D6\t{o3:\u00F4\u00AEo3\b\u00E6\u0080\u00B8\u00ED\u00A0J\u00CFp\u0099\u00D5(?\x12\u008E\u009D\u00D7\u00B5a\u00AF4\u009C\u0080\u0086\u00990\u00A2\u00C0\\\u0091\u0083\u00D7JX\u008A\u0095Z_\u0082\u0081A\u00FE_\x157\u00EE\x05\u00B9\x01ji\u00D5]\u00C4K\u00A9\u0099ps\u00F9\x1E\u00B2p\x14\u00C6\u00C8\x0F\u00CEt<\u0091I\u00AA\u00CA\x00\u008F\u00F8K5\u0083\u00F5\x12\u00C9\"\u00FC\u0097\u0088S\u0097\u00F2\u00FCA\x17\u0093\b\u00C5\u009E\x04\x16\x11n\\\u00EE\u00C5\u00AFO\u00AA\u00AA\u00EE5!\u00CA@\u00FE6! \u00F5s|\u00F1\u0092\"\nl\u00CF\u00CB\u00C9A\u00EA8y.k\fT\t\u00CC\x11\u008E\x10\f&\u00CDc+xx?V\u0090\x154hv\u0092\x10359\u00DE\x01;\u00A4\u00BB\u00F8\u00F5A\u0098\u009C\u00A7\x01,r\"Er[\u00A1\u009B\u00AA\n ?\u00C7\u00FC\u00F3\u0098\x14T\u00E2\u00FDO\u00EA\x19\u00F8\u00F3\u00D1\u009B\u00A4\u00D9\u00C7\x04Y\u00F0;]\u0090q\u00E3D\x7F\u00C9|\u00C6@\x01Y\u00AF\u00A1\u00A2\x14\u00C5\u0084\u00F1P\u008DTE\x1B;\u00DAG/?,Ch\u0087\u00FA\u00F1\x1A\u00BD\u00FE\u00A1\x1F\x1A.}L\u008F\u009B\x1C\x1B\u0096 \u00A8nE\u00FF<\t\u00D6\u00A0\u0091h\n\u00B3Eh\x12\u00B4R\u00EA7\x03\u00D8\u00AF\u0082\u00ED>`\u00A1V\u009Bq\x01\u00DF\u00E8E\u008Dq\u00A8\n\x16RB\u009A\u00DDE\u00B9\u0084y\x15\x16\u00C2\u00D8\x03\x0Fe\u0096\u0086\u00A50\u00F4\u00D9\u00A2!\u0097:\u00F3$)\u00BAH[\u00B8}\x00\r\u00C4A\x06\u00C6d\u009F\u00F8\u008E\x06\u00AB\u00C1\u00C0Me2\u00CC\u00B9BCF\u00C9\u00E5\u00C6-\u0096\u00AA\x1D\u00B1\u008E%\u00ECX\u00A4\u0095\u0099\u00F1F\u009F\u00B94d\u00D9u\x0F\u00BE\u00F2MP\x04Y\u008A\u00CDB\u00A5g\u00906\u00C45\u0093\u00C4%\u008F\u008B\u00C8$##\u00CCI\rB\u008E=\u00DE\r\u00AB@\re\x0EL\u0085\u008A\u00E7\u00AD*a\u009C\u0097HP\u00B50! %\u00A7r>\u00B9\\\u008C\tN\x0B\u009B\x165\n\u00E2\u00F3\u009Eu\x18i\u0093\u00D1\u0080\u00DF-\u00FE\x15w%KOi\u00AE\x11\u00B1\u00C9\u00A45^\u0087\u00BCg\u008Dw\u00F0\u00B9\u00B3\rar\u00C5\u00C7\u00F9\u00BA\u00F2\u00D6\u00D8/\u00F2x\u0084\u00B2\u00ABL\u00E9\x02b\u00B6\x19gJ\u00E1\x1C\u00F1Ml\u00F5\u00EA\u008F>F\u00AF|\u00EFCq\u0088vH\u0084\u00F0\u00FE\u00AD\u00CB\u009E\u00FA\u00A1\u00E1\u00FD\u00A2my\f\u00D1A\u00F1\x1C\u00AC,\u00AA\u0086\x06\x15\"\u0083\u00BA\u00E8\x10\u00E7\u00E4\u00AEH\r\u00FB\u00C5\\\u00913X|\u00C2L\x1B\u00E6\u00F0\u009D\u00A4=\x18H`Kq\tF X\x12\x12\u00F3\x17\u008C\u00B9\u00A4\u008C\x1E}<FF\u00A6\u008E\f\u00B5\u00A0\tG,\b\u0082Q\x0F\u00BD\rP')o\u00A8\\t\u0091\u00E8\u009F\u00FF\u0083H\u00EE\f]\x02\u0095\b\u008D\u00CD\u00B6$h\u00AD\u0081U\u0094\u00F9TF4\u00D58\u00D5\u00F2\u00C6\u00FC\u00B7\u00B3\u0090\u0089\u008F:\u00F2\u00ED\x05\u00A5.\x1A\u00A0c\u00A8\x1D\u0099.\u00DDL\x15PK\u00CEQt\u00CB^,\u00E89z\x10xucJ\u00E9\"\u00CD\u00A9\u00D2(\u00D0\x18\u00B4#\x007aM\u00D5\u0087L\u00DF\u00CEQ\u00ED\u0089\u00CD\u009A\u008D\u00B7\u00EE:\r4\u00D7\u00E1}N\u0094\x03+h\x01\x05)9Q**J\u00E0q\u00A2\x01DH\u0085\u00C6\u0088\u00B0B\u0094\u00AD\\#\u0098n\x01\u00E7\u00CFD\x06S\u00DD\u00AD\u0081.\u0088,r\x0E%\u00D4\u00E5\x11\u00B9[H\u0099O\u009B\u00C5\x12I\u00D9,\u00D6\x02\u00B5\t\u00D6\u00AE\u00FB\u0082\u00EC\u00F9\u009F\u00E7?\x15\u00C0\x7F\u00C7J;$Cxk\x7F\u00FBS\u00B6\u00E6\x07\u008F!3 \u00F4\u00F3\u00D4^\x12~$xP\u008A\u0090a\u00AC\u00A0\x04\u00F7\u00CBW\u008Ey+\x14'H\u00C7\u00BA\u00D2\u00F9d5\x17\x15\u0088JC\u00AA&)c\u00C7\u00C7P%l'gi\fF\u00F2\u0080\"\u00C9\u00F0d\u00D6C`\u00BA\u00F3\u00E8\u008Ce\x04\x00\u00E1\x1E\r\u00F4Z\u00D83f\u00D5&\u0080L\u0084\u009E\b\u00EA\u0085M&\u00EC\x05N%\u00C8\x04\u0097l\x1Dno\u0090\u00B2\x10\u00FD^\\\u00B8\u0091\u00E8T\x16\u009B\u00D9!\u00D4\b\x14(\u0090;.B\x12\x10\x10*G\u00FC\u00EE\u008C\u0085\u00CCj\x02Y\u00B5tT\u0089\u00A2\u00EA\u00D6\x0E\u00DB'>w\u00EF/\u008Cg<\u00B7\u009E#\u00C6\u00A4xn0\u0097\u00D1\u00CF*\u00F7v\x0FCT7V\u00C4oY^\x0E\u00D1w\u00A3\u00F1N\u0081Iy6\u0091\x12\u00C4\u00A4\u00E6\x1A\u00F6\u00D4\u00B7\u00BCW\u00CE\u00F7\u00C4\u0086\x10\u00DE\x0E\u0081j\u00DDQJP\x19\u0081\u00D0u\u00CC\u00F9\u00E2\u0098{QM\u00B49\n\x00&5\x19BUi\u00C1\u0084\u00AA%\t\u00DA1W\u00C5\u00F2\u008A\x0F>\x05\u0087`\b;T\u0086\u00EBu\u00CF\u0089\x1F\u0097\u008F>\u00A1\u00B7#\u00AA\u00CDx'6\u00BE\u00F8\x1606e8\x00pwf\u00FA\u00FE\u008B\u00B4\x04w0\x02\u00A6j\u0086\u00A1\u008D\u00BD*\u00D8'\x03\x0E\u00C8\u0080M\x01\u00B6\u00A0\x18;0v\u00C1B\u00ADL\u00BB\x11\u00B7\x0B\x01\x11\u00DF!\u00DA\u00D2\u00A8\u00BB\u00C34F3\u009A\u00CF\u00BF\u00B9\u00CAQ\u00BE\u00D3.\u00A04P\u00DA\u00A6.\u00DD\u00EB p\x03\u0099t5f\u00F6\x1F\u00F7\u0098P(zx\x1D\u00CA_0:>\u00A63&\u00F1\x04*i\u00E6\x16\u0094f\x06\u00B5\u00C6\u00D0*GU~N\x17Af;\u00BA\u00B7#j\txt%\u00B8\u00AFd\u00E6\u00F8\u0087\u009A#T\t\u00CAq\u00AA.m\u0080xv\u00A41\u00F4\u0096\u00EF\u00D1\u00C7FH\x0E(\x10\u00AACaR\u00C1\u00D0\u008A\u00AA ^\u0086\u00DD\u00E1\x15\u008D\u00C1\u00B6#t\u0081\u00CD\u00C5\u00B5Ji\u00A9\u0091.\u00EC}\u0093=\u00F6Da\x02\u0098\u00C14\u00F4~7\u00D6\u00F9|\u00D3\x13\x05\u0087~\u00A9\u00A6\u00B1\u00BE\u0082\u00A2\x06k\u00E5\u00C6\u00ADpDAAW\x18\u009B?\u008B\r\u00B3\u00AC\u00E5b\x06I\u00A3\u00ADf2Y\u00D7\x12z\u00CE\u00E3\u00E2\u00D1\u0093\x151#\u00F9g\u00ED\u00F3\u00BA\u00CF?A\u00BF\u00F0\u00F1\x13\u00B1\u00D2v \u0084\u00CB\u00F5\u00D3O\u00FA\x00\u00DE\u00BB~\u00CC\u00D1\u0081\u00EBsf\f\x02rQ &\u00B1\u00EA\u0082)u\u00EAb\u00B3\u00F3\x18SpP\x047\u00A9\x15g\u009D\t0\x13\u00AB\u008C\u00C4x\x01\u00C0\u00F6\u008B\x14\x01\x18\u00A3\u00D8`\u00F6\x03\u0086\u00D6r\u00A9\u00D9\u00F6b\u0099\u00B3\u0090\u00A8@\x10\u008BQh=f\u00F2\u008D\u00A2a\u00C0\u00A8\u008C\u00844\u00CE?\u0082\u00BA\u00B7\x19\x11\u00AD\u00D2\u0092\u00E5G0\u00C6\u00813\u00C0\bE\u00A2\u0084L\x00\u00F2\u0081\u00C3\t&\u0082\u0099\u0098\x1D\u008A \b\x03\x0E\x1E;Q|\u00F21cQ\u00FD\x18\u00C8\u0083\u009Ak\u0098\u0096f\u00F1\u00DF\u0095\u00F7\u00F6]\u00A2\u00E0\u0086<\u00BE\u00AB\u00A8oP\u00DF[\x05\u00DBD\x0F<w,\u00E7M\u00C3\u00B8\x12\x1DQ\u00ADa_c>{\x18\u00F7xOw/JJ\u00CB\u0098\u009B\u008A\u00C9\u00B5\u008E\u008B\u0086\u00C7\u00D2\u008F\u00C3\u00EF\u00A9\u0085\u00BF0\"~\u00A6D\u00AFT\u00C8\u0089\u00F7\u00ECE\x06\u00A9\u00D9\x14\u00B6\u008C\u00AA\u00D4\u00B2\u00D9M \u00AE\x1CN\u00AE\u00EC\u0082|\u0094/\u00B5E\x1F\u00C1l&\u00C75\u009EE\u00AF\u00FE\u00C4\u00FA\u00D6\u00DB\u00DE\u00F8$\x00o@i;\x18\u00C2\u0085\u009Bo\u00FD\u00FE\u009B7n\u00C2\u00A0\r\u00E3]\u00B4g\u00E3\u00ED6ui\u00E00\u00DE\x16\t\u00E3\x10l\"\n\bBr|\x0F\u008D]\u00F9\r>\u00F9#\x04\u00FBD\u00B0\x07\x16\u00CE<\u0087{\x1F\x14\u00BEAl\x1A\x1A\u00B7\x0B\u0083\u00E8jD\u00CA\u00A4\u00A7\u00E0\u00C8(\u00DE\rg\x02\u0099\u00F3`)\u00CE,\u009C\u00D2\u00C5\x7F\u00F7B-\u00AA-<\x14D<\u00BC\u008E\u00EEM\x16de<\x03\u00C3\u009E\u00E1\u00E7X \x14\u00A1v\u00DA/\u008Cp\u00D3hF\u008B8\x19YX\u009D\u00A1\u00E9%\u00D1:gH\u00CB}e\b\x10\u00C0c\u00E7\u0095:.\u0095i0\u00A8\u00AA\x07dV?\x1Fp/M,b }3F\x1A\u0091\u00BC\u00B4\u00CA\x00\u00F89\x18\u0097\x07(\u0085NNX]\u00C0n\u00C0e\x12%R\u0090\u0094J\u00CEq@K\u00B2UxH\u00FCZ\u00E3\x00\u00E5\u0092U\u00C3\b\u009B\u00CF;\u0098R\u00BDb\x7F\u0098\u00CC\u00AF3\u00F3`\x18\x1C+\u0092\u0080s\u00C0E\u00C2\u00F37\u00CEe\u00B9\u008FGv\n4\u00DFq\\\u00A7\u00F9\u00DD\u00EF\u00B11.\u00B0\u00EF\u00E2\u00F7}\x1Fn\u008F!\\\u00D5>\u00FA\u00BD\u0090\x05\u00D2\u00A2y76Up\u00E7\u009B\u00A6^\fClkv\u0080\u00D0\u0089\u00E8\u00A10\x035\b\u009B\u00CC\u0080\x05V\x1D1\u00805\r\u00CC\u0092\u00FDE\u00ED\u0098K\u00C3\x1C\u00DD\\\u0090hh\x1Eq8hn\u009BjD\u00ED\u0089NJw\u00A3\u0085/\x13\x15\u00C4\x06\u00AA\u0090\u00D8\u00A2\u009ED\u00DB0x\u00A4b\u00B8\u0088\u00C1\u00ADAX\u00B9\u00B9C#\u00E6\u0080\u009E\x06\u00CA\u00B14\x13\u0099\x04\u00B1\u00BB\rf`t&\u00C0 *\u0085\u00D7=\bfe\u0086\u00BD\u00E6?q\u0091\u008B\u0087,\u00B3\u009Aql\x0B\x07\u009D\u00D2\u00E0\u00AA\x0F^\u00C4\u00E2\x14\\\u008F\u008D:\x01\x00\u0098\x04\u00D5\u0088^\u00D0\x1C\u00D2\u00F2\t`\u00EF\u008D\x0BS\u0080\u0088\u00E0\x10\u00C0\u00AA\x1F\u00F9Vq$l\u00D2\u0082.!\u009E\u00A4\u00C5\u00ED\u00CB\u00C8PRS\u00AF\u0084\u00E6\u00918\u00EAv'I\x17]\x18J\x0B\u0086\u00E6\u009E\x1BtcN\x10\u0087x\x1A\u00B6\u00F9\u0088\u00D3\u00F6@q=r\r\u0092y\u00F0\u00F9\u0097\u00F6\u00AC4\u00A0\u00F6\u00CA\u0084$\u009F/~\u00CE=\x1E\x04j\u00A5\u00F1T2i\u00CD\x14I\x0B\u00C4\u00EA\u00D6\u00AF\u00F6\x16\x15\u00AF\u00C3\u00FE`\u00D5t\u008A7Y\u00C0\x04,u\u0084\u00D7F\u00F1\u00F7\u00DC\u00D1\u00AE\u00BB\u00E6\u009F`\u00A5M\x18\u00C2g\u00F5\x0B\x0F\u00FB\u00C1\u00CD\u00FF\u00FD\u008Cx\u0089\u00C7\u00A4\u0091\u00E0\u0087 b\u00E3\u0084\u0083?(\u00D3\u0080\x0BD\x02\u00BF\u00A7\u0084\x03\x10\u00D2;\u00FD\u00F469[\"\u00D8\u0083\u008E\r\f^\u0098\x04`Y\u00B1&\u00D5^\u00A1h\u00A2Pt_\u0087\\\u0084\u00B5w.\"c^)\u008F\x19\u00FC\u00E4\x05Q\u00A4\u0085\u00DCqYm\u00D7\u00BA\u00EA0\u0082\u0092\u0094s/\u0091\u0084\u00D5\u00D1|\u00D3\u00D9L\u00D1\u0099\u00F8\u00C1\u00E1\u00EAIC\u00EA\u00BC\x13)\u0083$T\u00F7\x00d`\u0091f)7\u00E3\x14\u00B6\u00A8\u009C<\u00C4\x17\x12{\u00CB\u00D8d1\u00EFB\u00CF\u00F9\u00A8\u00FF\n\u00D3\u0082I@~M\u00C6\fp\u00F85B\x11\u0087\x18;+W\u00F7\u00A2\x1E\u00D5V%&\u00FB\t|\x12\u00F3c\u00EF\u009E^\u0086:^)\u00BF\u00D5.I\u0094TCh\u00FC#\x04/\u008C+\u00F6e\u00A8\f\x02\x05Y\x1C\u008A\u009A$\u00EEa\u00CC\u0093\f\u00B3\x04Eiy\x1E\u00AAAPLj8h\u00C3\u00B4\u00FC\u00DA\u008A\u00A1\x12H\u00E4S\u008C\u00CC\u00ADw\u00CC>\u00FD\u00E93\u00F4\u00A3\x1Fx\u0098\u009C\u00F9\u00C8k8\u00AA\tC\u00B8b\u00F9\u0089'^9\u00BB\u00FC\x182\u0083\x06\u00F3,x\u00C9\u00B0\t\u00C1\u00D7i\u009DB\u00AF\u00E9wJ\u00C8zM\x1Ecb\u00D2>4\u00DF\u00F8m\u00865\u00D5\u00D8\x12n\x11\u008ED\u00F3,4'\u00CA\u009A|D\u0092\u00A4'\u00A11\u00E6\x00@W#~2\x06\x120\u0097iw\t9e`F\u00ACV\u00C9\u0099\u00B5\u008F2Yetd\u0090\u0091\u0092\u00E6a3?x\u00E9\u008B\u00A1\u00D5A\u00C3+\u00C6\u00A50\u008A\x11-\x18\u00B3\u00A3\u00D1\u00AA7w\x1DR\u00DA\u00C75(\x0B{*a\x11Q\u0093\\t+\u00EF\u00D3\u00BB\bD\u00A2\u00F5\u0098\u0096\u0085[\u00FB>\x04Q\u0093(+\u0093\x03\x10\u00B5\x0B\u00AB\x00\u00D6\x12\u00EB\u00CF\u0083\u00F4(D\u00DFTE\x10\u00F9\x1Fe\u00A2\u00CA\u00B9\u00E9\u00DAS\u00E6!T\u00E3e\x04\x12\u00F1>u-\u00E6\u00D4\u00C9\u00EA\u00B8\u00E3\x1C\u00FD\u00FF\u0089{\u00B7^\u00DB\u00B2\u00E3<\u00EC\u00AB1\u00E7\u00DE\u00E7\u009C\u00BE\u00F2*6E\u009ATD5E\u00C5V\b\u00EB\x02:1\x1D\u00CB@\"8\u0092\x03%A\x12+\x01b\x04A\x00\u00FF\u0080<\u00E5=\u0081\x1F\x14 \u0080\u00DFb\u00C0\x0E\u0090\u00F8\u00C1\u0090^L#\x12\x02\u00CB\u0092%@\u0092\x05\u00CA\u00B2@\u008B&e\u00D1\u00A1\u00A9\u00B6D5\u00D9d\u00F3\u00D6\u0097s\u00CE^sT\x1E\u00EA\u00FB\u00AAj\u00CC\u00BD\u009Bl\u00C1\u00BA\u00AC\u00EEu\u00D6\u00DAs\u00CD9\u00E6\x18c\u008E\u00AA\u00FA\u00EA:\x1AZ\u00A8u\u00D3\u00EF\u00EF\u00B29\u0088\u00F0\u00DB\u00DC:=\fr\u00B3\u00F6\u00A2\u00AB*>\u0083d\x18\u00FD\u00F6\u0086\u00ED\u00E5/\u00E0k?\u00F3\u0093?\x04\u00E0\u00FF\u00D2\u00E1\u0085!\u00FC\u00C2\u00CD\u00CF\u00FF\u00C5\u009B\u00FB\u00AF\u00E1O\u00EC\u0095\u00FE\u00F3^\u00B8\u009C\u00E4\u00A4\x00\u008E\x1CT,\u00A4\u00DA\u00E0\x13-\u00D0\u00A5$\u008E\u00A9-\u00B7<\u00D7\x11\u00EE\u00C3\u00AF\u00BB\u00E3I\u00F3,\u009A\u00B2a\u00C3\x05\u00B1\u00ED\u00D7cJ\u00DD*\u00B0*\u00A3\u00DF`\u00AB\u0083\u00E9\u00CA\f\x7F\u0096\u00A1P\u00D26U\u0080-\u009FwH\u00FA\u008A\\<\u00F2\u00CC\u00AA\u00B1X\t\u00B2\u0081\u0084\u00C2\u00F0\x17\u00C1Q\u00A5npd\x06\u00A4;\x0B\u0096\u00B6\u0084\u00A0{/\u00A9\u00E5c\u00A5#\u00931-\u00DA\u00AE\u009CF\u00E9\u00C1*)\u00D6\u009E\r\x17\u00AE\u00A0'\u00CCs[2\u009E\u0080Jr\u00C2\u0089\x18\u00A5\x1C\x11\u00F6fX\u00F7\u00A0JO\x1B\x01\u008B78\u009D\u00BBz\u00CCI\u00C0mw\u00EB5:\u00D0\u00D9n\u00D93\x14\u00F7Q\u008C@s\u00A5\x10f\u0084\u00E0\x11\u00F7t\u00A1+\x05\x18X\u008D\t\u009D\u00BDw\u009B\x06\x0B\u009F\x0E\x00\x07%\u00F5l\f\x03\u00D1'\u00F3\x1E\x14\u00C5y\u00F4\u00AE\u00947\u00CE1\u00F9\u00BC\u00BCv\u00D3\u008A{7\x17hhX\u00C9\x002\u00D5\"\x19\u00A3\x10\u009E\u00AF\u00E8!\u00D5\n\x07&\u00B0\u00CF\u0089\u00FD\u0093\u009F\u00FE\u008Bx#\u0086\u00F09{\u00E9\u00A3\x07zG\u00FF\u00B8_\x03\u00EB\u00AE\u00BDUG \t;\u00A5\x7FI\u00D6\u00EE\u00AE\u00A97\u0090aD^@_\u00D7\u008Eix4\u0080\u0097\u00DC\u00C3\u0086\x000\u00AE\x00\u00D8\u00DC#\x12\x11*\u0093\x16\u00F1\u00F9\x13\x1E\u00AED\u00AB\u00D2i\x07\u00E2\u00FA\u0083\u00EBj\u0098\x12\u009Dr[SXJwp\u00A9g\u0089\x16DmGY\x01\u00C2 T\x1B\u00CEV\u0088\u00B3l\x16\u00950\u00C5\u00D2ln9\u00CE\u00B4\u00FEC\u008CA\u0088i,\u008B\x05\u00C9H\x10-\u00E5z\u008C\\\b\x11I\u00AE\u0083D\x07]\x14#\u0088\u00DA.1\u00BF\u008C\x7F\u00D0B\x0F\u00E2G#\u00AC\u00B6\u00AAD\u00C8\x16*A\x1C;?\u00D7\u008E(\u00C4\u00CDJ\u00FDK\u00F5&\u00D5\u0091\x16\u00A4\u00E3\u0086\u00C5\u00D2\u00AE\u00B5\u00D5\u0099\x14\u00DB\u008C\x023rm\u0093\u00D0\u009B\x07\u00A1\x03\u008F\u00EA\x7F\x19\x1Cm1\x1E\nI\u00E8J#\u0081w&\u00D1Q\u0087\u00B5{\u00F5;\u00E9\u009C\u0086j\\;II\u00ED\u00A9\u00B0\x00\u00AD\u0087\u00A5\u008DTi\u00BA\u00D7\u00C2k\u00F8\u00D3\u0082\u00A9|\u00F1\u00C5\u008F\u00F6\u00A1%&\u00FF\u00DA|\u00F5\x1D\u009F\x1B\u009Fz\u00BE\u00FC\u00F8\x7F\u00DC\u00AFB\x07\u00F1\u008E\u00A0\u00A4[*\x01\u00CF\u00AD\u00CFB\x03\u00BD\u00AD\u0080\u008D\x15\x11x~\x05\u00DA\x0EW\u00E4\u00D71\u00F0\u009A\u00EDx\u00E4\u0086G\u0088J\u00CD7\x0E\\(\u00AB.\x16\x01J\x17\u00C6*\x1C\u0088\u00B0\u00E5xo\u00B8Al/\u00DF\u0093\u009D\x0E\"\u0080x\u00EBX1\x11\u00C5\u009A\x1D\u0098\u00BC.v\u00A9\u008E}#\u0090^\x06\u00B93\x0F\u00AF\u00EBj\u00D7\u00A9\u0088-P\u00C5\u00E5x\u00CE\u008Dq\u009A\u0085\u00CD@s\u00AA8\x03#\u00A3\u00D5\u00DF\u0083\u009F\fWVT\u00E4\u00F2V[ryj\u00F1\u00DB\x0E_\u00FC\u00F1#\u00ECzz.C\u00EA\x1F\u0090\u00D9{\u00EA\x1B\x14\u00B6\u00CB6\u00B9`U\u00C9\u00CA\x19Ay\u00FB!\u00B6>\x01\u0090\x01\u00B1b6\x001\u00BA\b1n\u00EB(\u00C3}G;fi|C\u009F;\u00FD\u0086\u0090\u00E8\u00BDL}_g\u00C8$\u00ADF\u00F8\u00F2\u00F2\u00F4k\x14f\u00CC\u00FE'\u009F\u00D5gB|\u00B5\u0081BD4l\u00D6\u00AB\u00D90Z?S\x006&\x14\u00CC 6\u00DBq\u00AAO9\u009E\x17_|\u00DE\u00FF\u00D5\u00AF\u00BFC\u00AD&B\u00F8\u00B8}\u00F6\u00CF\u00FD\u00D6\u00FC\u00B4\u00C1\u00FE\u00A4\x02\u0092\u00A2|I1\x05@\u008B\u00C0\u00F3\u00BB\x1E8\x07\u00DA\u0085N\x0B\u00D81J\u00CB5\x07@\u00EF*\u0098\x1Ar\u00CC\u00F1U\x00\u00F7\u00A6c\x1B\u00B1I\u00EC\u00EE\u00AC\u00A3\u00C0H\u00C1\r!E6T\u00AC\u00C1\u00C0\u00A0\x07b\u00E3.Q(\u0082 \u00E2P\u0090\u0093\x1EK\x14M\u00A9\x07w\u0085\nb\u00BA``\u00E3\x18\u00D4~\u00EE3\t\u00ED\u00E1P5\x15\x00\u0085k)@K#\u00D7Bi\u00D2\u00B2Gmv]\u00D24\u00BB\u009C\u00B7\u00F4U\u0083\u00F7j\u00B3o\r\u00E66\u009F\u00F9j\u008B\u0088\u00A6,\u00A1=c\u00FC\x01\x06,90\u008F`>)\u00C1%\u00C1\x06\u00BB7\u00B9\u00CB\u00DA@l\x0Bg\u00C8\u009D\u009E\u00A8\u00AFgr\u00D4t\x12/J\"\u00C2q\u00CE?\u008D\u00B1\x15a\u00BA\u00EA\u00CA\x11B;\u00A1w\x15ia\u00D7Dd9g\x16v\u0084\x05o\u00A6u\u00E8\u008E\u00F8\x042@?\b\u00ED=\x04\u00D5\u00AD\u00E2\u00AB\u00CD\x0B\u00E1=Q\u008B\u00EA\u00C0\u00ECc\u00D15\u0097\u0094\u00F4\u00B9\u008B8\u0091Dz`\u00BA\u00BB\u00D1\u00DB\u00E3NDh\x18/}\u00C1\x1E\u00FD\u00FC?\u00FAs\x00\u00FE\x1F\u00A01\u0084\x7F\u00FE\u00E8\u009F\u00FC\u00E0\u00D7\u00EF\u00BDae\u00A5?\u00E2\u0097\x16eS\x17\u00806ae\u00F3\u0086o\u0094H\u008Al\u00B3\u00C6\f\u00EA\u00FC7\u00FF\u008E<\u0084/Z\x04)_aFz4{f^E\u00D6v'\x02\u00B0\u0088O\u0088\u00A5\u00E7\u0084\u00FB[\u00EC\u00F0d\u00D1\x03\u00ED\u00F3\u00AB\u00D8\u0082\u00D5\u00D5\x18\u008B\u00EF\u00E0&\u00B4\x07\u00C7\x11*\u00CBF\x15O\u0089Ib\u008A}?\u0083x\u0098!P\x14\x04\u00A3\u0099\u00ECsUR1\u00D5fI\x1E\u00A0\b\u00A9UN.\u0097{ \b\x11t\u00FC|R\x19\u00B4\u00F0\x10\u00C5>XG\x19J\u008ENv\u009E\u00CA0\u0099\u0093\x03k\x1E\u00836\u008E\x19dJ\"\u00AA\r\u0081k\u00AD5#)Z\u00AB<\u00F8X\u0093\u00BC^\u00B3\x10]\u00E0\u008C\u00E8\u0092D\b\u0092\u0096\u00D9H\u00B47\u00EB\u00DC\u0094\u00D4yO\u00F5`\u00B6\u00F6\u0089h\u00D3C\u00D0\u00D5\x1D}\u00F7;l\f\x03\u0085\nd\u00B4\u00CC\x07\u0089\x1E\u00C8\u0094\u00E7&\u0081\u00B7k\u00F5\u00AC;\u00A3\u00F6\u0081,\\\u00DBJ\u00CF\u00F5\u0097\x01\u00B8w\u00B9\u00E0\u00CB\u009F\u00FA\u00E4\x0F\u00E2\x16C\u00B8\u00F9\u009D\x1F\u00C0\u00F5\u009FP \x12\ts%f\u00A0\u00A0n{(\x0B\u00E1\u00F7\u00D78]\x7Fn\u00AB\u00D3\u00C1\u008A6\u00DC\f\u008Fp\x0F/;p\u00C5\u00E8E\u00B3H\u00BE~\u00DC\u0096x\u00B46K\u00EAzd\u00DBE\u00CEC\u00DCJ\u00E8c\u00A0\x1B\tU\u00E19z\u0093\u00DF[.\u0084\u00BA9\u00E0\x11\x19\u009981\u00FAY\u008A\\\u0090\u00DA\u00C6\u00B3{]FS%\u00EA\u00A9\u00FD\rF\x0E\u00DA\u00BC\u00E6Bkd\u00B8\u0095\u00A1\u00AE\u0095G7\u0080\u00BEk\x00C\u0090\x13\u00B0\u00D12\u00FB\u00E0\u00B0a\x19>k\x18\u0098\u00D4\u00E9\x1D\u0091\x04\u00E5\u0083\u00D2\u00CE\u0082\u00C8\u00E7\u009C\x19\u0093\u009F\u0088\u009C\x022\u00E0\u00BD\x10\u00CA\u00A8\u0085\u009FO\u00CE\u00B1\u00DA\x01,\x17\u00BD\u00B9a\u00FAAb\u0092~/\x0E\u00A2ur\u00D2\u00F5s=9\x0B\u00D7\u00D0;C\u00F5!\x18\b\r\u0082\x1A\u0082m\u00E9\u00FAt\u00868\u00CB\u00BA\u00EFS\u00F1\n\u0088\u00FB\x1E\u0081`l\"7\u00EF\x1D\u00B0@\x0B\x1CG\u00DAc'\u00DA\u00EA\u00908\u00C9\u00C5\x1A\u008F\u00E7\u00F0\x005'\x03\u00A2\u00C2\u00AC\u00DDQ\u00FBo\u00A4\u00C1\u00D5\u00D8\x1Fk\u00E7\u0098,\u00DC\tL\u009E\u00F8\u00D7\u009F\u00FD\x01\u00DD.\x19\u00C2W\u00AE_\u00F8>7e7\x16\x11\u00FD\u00D1\u00BE\b\u00A9\u00B0\x01\u00B8\u008FH6:\x13<\u00FF\u00EE\u00BA\u00B1\u008C\u008D~\u009B\u00E8MF\u00B4s\x1B\u00E7\x1C\u0080\u00BC?-\u00B5\x06\u00BC\u0086\r_\u0081\u00E1\u00BEE\u00DD\x04\u00ED\x13\x14F\u00C3\u00C8\u00D6?\u00BCX\u008D\u008C\u0086\u00B1wdH7\x19\x15o\u00C8\u00E4\x14\u0087\u00A0\u00FEt\u00AFB\u00A6\u00E8\u0082Q\u0088\u00A88\u00BDH\u0085\x0E\u00AF\u0084\u00E4pW!\u009C\u008C\u00CC]\u00E5\u00DDc\u009C\\\u00E2\bT\u00A7@\u008A\x04\x00\x00 \x00IDAT\u00C5\u00DF\u00C8\u00BC\u00CAR\x0E\u0098\u0095A-\u00E6\u0087\u0090\\sfB\x04E\u0098\u00AE2iMj\u00E7K\x014\u008E\u008Cp\\\x11E\u0087\u00F4Hi&tcy\u00E1@\u00ED\u00A9\x10\u00CF\u00D8\x16\u00C9[\u00CC\u00DEM\u0086\u00CB\x16U\u0099\u00AE\u00CF2\u00FC%\u0093I\u00DF\u00BD\u00C6eK4\u00A6\u00DD)}i\u00DA\u00F5\u0089\u00AA=\u0080ZK\x1Ak[S\u0096\x15\u0099t\x02\x11\u0080DA\u0081\u009B\u00BA\u00D72\u00CE\u00D6\u00B6\u00B71%\u00D3\f\x06\u009B\u00EB\u00CA\u00DB\u00F8rA\t\u0089\u008Cv\u00BD\x15(\u00CB>O\x1C\u00AF\u00BE\u00FE}\u00D5{\x00\u009F\u00F3\x17\u00DE\u00F5{\u00F3w\u009E\u00FB\u00E3-\u00B5\u00BE\x01\u00FE\x00\u00C3\u009F\u0086\u00F9\u00D3\x00\u00EE\u00C3\u00F1\x00\u00F0\x07\u0080\u00DF\x03\u00FC\n\u00C0\u008E\u009E?\x7F\u00CB\u00AF\x0E\u00E0\u008C\x18j\u00F1\u00B7W\u00B3\u00C4\u00AF\f\u00A4?\u00B4\u00A8B\u00FC\r\x07\u00BE\u00E2\u0086G\u00BE\u00E3\u00B1ox\u00E8\x1B\x1E\u00FB\u008E\u00C7\u00D8\u00F1\bWxl;nl\u00E4\u00FB\u0082\u0081\x1B\x03n\fx\u00CC\u00BF\u00C3\x18H\x15\x02R\x17D\u00C8\x15\u0096\u00AC2\u00ED\x077\u009F\t\u00B5\u0083oh\u00A7i\u00C5\u00B0\u00A9\u00DEc\u00B0\u00A8If\x13F\u00C60\n.IDRXZ\u00A8\u00B5\x18\u0089I\x1D\x11\u00E3L\u00D5\u008CjX\x1A\x0E\x17VKU\u00AD\u00BBV\u00DB|\x12%X\x12Z\x19\u00EEn\u00D9r\u00DA\u00F4\u00C7\u00B3Q\u0082\u0094-\u00A7\x14hQ\x1F\u00D9\u008Fdt]\u00A0\u0088)\u00CE$\x00\u00CD\u00F7*\x10\x1A\u00D1\u00E7\u00F76\x07\u00D2\u00C7\u00BB\r&\tJ\u00EB\u0085B\u00C99\x1F\u00DEB\u00E0\f\u00D5\u00EE\u00C2\u00A6\x151hK$\u00A2\x1C\u00CF+\u0083U\u00BF\u00C44\u00E2\u009Fr\u0082\u00AA\u00D5Q\u00FD\u00F0& [\u009F\u00A5\":\u0099\u0081/\u00F7\u0089\u0095\u00B9\x7F\u00E1\u00F3\u00CF\u00F9\u00EF\u00FC\u00E6\u00BB\x00\"\u0084\u0087\u00B0\u00EF\u00FD\u00ED\u00F1/\u00D1\u00CE\u00FC#zQ\"c\x0B\u00E2\u00B7k\u00A8(H\u00EE\u00E3\u0097\u00BB2\r.\u00E2^$\u00A5G\x11\x02\u0099}\u00D6\u008F\u00A1\u00AD\u009D;_Z\x18*\u00CB\u00D5$\x0E\u00C2{\u00F0\u00B2\x03O\x00\u00D8|`\u00B3\u0089G\u0092\u00B2\by\u00D1\x1F\u00B4\x02\u009D6\u008FH\u00D6\x1B(\u00847\u00D0x\u009C!5\"\u00DE\u008A?\u0088\u00AC\u00CAZ\x10[\u00BB\u008F\u00BC\x0B\x002\x19*\u00AA@\u00D7\u00EB\u00E0\x1C\x1D}n=T\x07\x10\u00F5\u00C0\u009C\u00D2b\u00A3\u00E6\u00D5\x12\u00AE\\\u00BBJ\u0099\u00A2}\u00A1-\u00E7\u00AA\u0090\u0091\u00E5\u00BC\u00C6\u009C\u008F\u0092\u00AAF\u00FD\u009B\u00D5\u0099cj\u0084H8\u00B7Q\u00C6y\u00F1\u00AFG T\u00E8\t\x11\u008E;\u0097\u00A5\u00E7Z\u00C8w2\x7FO\x02\u009Es\u00B6\u00E71\x1A\x11\x11s\u00B9\u00B2\x1CWC]Iw\u00AD)\u0086P\u00BB\u00A3\u00D4S\"\u00A4\u00CC\x00=\u00EA\u00BAY\u00D1\u0096\u00B5U\u00DE\u0089v\u00C8@\u00B4\u00A5\x1A\x1C-\u00E6\u00A3\u00ADC\u0097*@&1i\u00B8\u0094\u00BDb\u00DA\x1Dm7u@1\u00F1\u009Da-\u00A7\x13\u00E5P\u00BD\u00F0\u00D9\u00F7y\u0088\u00F3\u00B7\u0097^\u00C2+?\u00FF\x0B\u00DF\x0B\u00E0\x0B;\x00\u00FC\u00C2k\u00BF\u00F9g^\u00BB\u00FAFM\u00F8\x1F\u00EAK\u00D0kC\u0096Q\u00C7\x15\f\x0F\u00E0\u00B8\u00E2X\u00E3\u00D8`\x1A\u00EE\\\u00D4\u0081\u00BD]_AG\u00EE\u0092b\u00FD\u00DD\f\u0092}\u00E2\u00AD,\x00+B\u00E8RC\u00B6\u008A\u0081G\x06|\x15\x07\u00AE1\u00B01\x1FrO\u00B7\u00DFl[\u00C7\x07A\x1D\u00EE\u00B8 \x16\u00C9D\u00B8\b\x07\u0099\u008B\u0081y\x10$p[\\z\u00C0\x15\u009Ca\u00CBA\x02e#\x0E\u0089\u00A2\u00FA\u008AR)\x0E\u0084\u00F4\u009D)\u00917\x14\x18\u008Ex\u00C6\u0090\u00D6X$_n\x06K\u00A3\u009DBr\u00E5\u00E2\u00CB\u00B8\x00o\u00D3\u00E3[-\u0089\u00E4\b\u00FA\u0083\u00C4\u00BE$\u00FE\x14Z\u00A86\u009B\x1D#\u00F5x\x05\u00F6\u00B4\u0086]\u00D9\u008AvZ\u00D4\u00DD\x1F_\u00CC<\bL\x12\u00D1C\u00A0\u00F8\rR\u00AC\u008A\x01e\x1B\u0093:\u00B4\x18\x07\u008D\u00A0KVb{;Pp\u00BB\u008D9a\u00BD<\x1E\u0082\u00EA\rI\u00E8\u00EC;\u00D0M \u00A2 h\u00CF\u00FC\b19\u00E54\u00A8\x1B1\u00DE<\x078uSk\u00DE\u00D7{w\u008FI\u00F7\u00AE\u00A8O\x1A\u009F\x0F`\x02W\x07\u00F0\u00F0\u00E3\u00BF\u00F2g\x00\u00FC\u00A3\x1D\x00>5~\u00FD{\x06.\u0098\u00EE\u00DFB\u00BA\u00FEA_\u00E4N\u00D8`\u00B8_\u00D2\u00DE\u00AF\u00E1\x16;8'\u00B13\u00AB1\u00BD\fK\u00C9,\x11\u00AC~\u00EB\u0088Ae\u00BCF\u00BB^\x13\u00C2\u00AA:X\u00890^\u00CA\u00A3\u00EF\u00A8\u0083\x10\u00D3\u0081\u0097\u00CD\u00B1Q\u00F2\u00EDn\u00B8\u00C2\u0086*\u008C\u00A6jM\u00D1\u00CEN\u00C6\u00A0`\u00A4\u00D8N>\u00DA\u00DB\u0081T\x19\u00CA\u008E#\u00DB\u00C1m;B &1\x1B\u00C1W\x16\x7Fu\x05>\x11q\u00E88\u00F5\u00DCn\u00AF\ba\u00DD\x18\x1D\u00CAh\u00B6\x14%\x05\u00FD\x01\x14\u00DA\x19\u00C8\x04TT!\u0098\u00D3\u0091\u00AA\u00D8\u00D0\u00CA\u00AA\u009C\u0085|\u00E4\u00AA\u009B03h\u00C7\u00A4#s\u0091\x062St!8\u00FA\u00C1\u00C9\t\u00A3[FD\u0082\u00AAJc<q\u0098s\u00ACtb\x0F\u00C7\u00E7H~!&2\u00A0\nE\u00F1\u00CC\u00E86\x14\u00A3S\u00F0\u0096\u00A3\x11'\u00FB4y\u00B7i\u00E5y\u00A0\u009B/\u00CFI\u0084y4\u00A9my?\u00C8V3k\u0085\u0095tv\u0094\u00FB\u00B5\u00FA\u00DB\u0099Vy\t\u0082\u0089\u009Av\x7FR\u00B1\x14M\u0088Y\u00B5\u00D3\f\u0089\x1D5\u00C4\u00DE\r\u00C8u\u00A0\u00DF\x07\x1C\u00E3\u008B_\u00F9\x1E\u0080*\u00C3\u0097\u00F1\u00E5\x0F]\u00B6\u00CB\x1F23\x00\u0082 \u00AFQ\u00AA\u00C0\x15\u009C\u00C7\u00ACm\u00D8\u00AA\x1D\u009Dg\x12{ \x03k\f!\u00D4\x03]\u00A3\u0087\u00A0\u009A\u008A'f\u0092\u0088\u00E2\u00AE\x01u4q\u00F7K\x15\u0099\u00BF\x06\u00E0\t8\u00AEp\u00C1\x0E\u0099\x03\u00D5g\u00B5\u0094\u00C1\u00CBI\u00D8\n\x1C5\u008E\u00D6\u00CDi\u00BD\u00B5\u00BCF\x06\u00C6s\u00BF\u00B2\u00EC\u008BEf%\u00AC\u00DB\x01\x1C=>M{Q&\u00E3t\u00F5R\u00B0\\h\u00AA\u00A4vx\x19K\x1B\u00F5d\u009A\x0E\u008C\x16\u00E6Jf\x01x\u00C6\x00,`\x00\u0092\u0080\u00CDXg\n\u00B7\x15w\u00D1\u0089d \u00B9\u0093\u00B2\u00AEQk\u00A6\u0089\u008Fs'PQ\u0083\u008E\x15%\u009C\u008F\u00E9\u00A5\u00E8E\x1A6\u00C9\b|\n\x05\u00A0\u00FA\u00C9\u00A1\u0099\u00DCz\u0082\u00E5\u00A9\u00877\x15&\u00F7\u0087\u00B0UJ\u00AB\u00DF\x1A\u00EFb\u00DD?\u008D\u00EB\u0096T\x17\u00DAh\b\u0087\u00E7\u00D6\u009E\u009E\u009Dq\u00A0\u0098L\u00A2'\u00DE\u00A3\u00AB\fh\u00E7\u00F4\bI\u00AA\x16b\x06\x19\u00E3\x00\u00CF8\u008C\u00F9\u00CA7>\x04\u0090!\u00BC\u008C\u00AF<\u00EF\u00D6&\u00EB\u00DF\u00FA\x15\x04\x19j\u00C1u,:\x17s\u00D8`\u00B8\u0086\u00F2\u00FC\u00CB^\u00D0\u00A5\u00FE\u009A?Pp\u00BF3\x04\x19\u0092J\u0095X\u00CA\u00AC-\x05>\u00CF\u00AFv\u00DE\u009D}\u008FE\u00F7\b\u0086/9\x03\u008F-\u00AE\u0089\u00AB&\u0086E]\u00C6\x0B<\u00F7\u0082\x149\x1B\x03i\x06\u00A2&\u00C2`\x1E\u0086B\u009F\x0F[\u00FB\u00A2\u00B1.\u00EEI\u00E2;\u0095\u0089\u00A9\u00A8\u0086b'\x15\x1E\u00CD\x14%sd\u00BC\x03U\u008A\x10\u00FEc\x11&\x159X\u008C\x06\u00D2\u009F\u00D1\u00C8\u00D4\u00FB\n,\u00DB\x03l6\u00B4P\u00BE\u00EE\u00CC\u00C30Gn\u00C0\"\u00D6(co\u00BA\x16\u00CFs?0\u00CD\u00D3`Y\u0084?\x1A!PZ\u00BA\x18\u00C2\u00FA\u00DC*\u00F6b\r\u00F4)\u0082U\u009B\t# F\u00AC\x04\u00A2\u00BAg\u00F5\u00BD\u00A4m\u00BB\u00DD\"\u0081\u00C5\u00FA\u00F5\u00F4\u00CE\f(\u00DA\x121.F\u00CB\u00F4+\u0089\u00C1\u0096\u00A8)\x06\u00D2\u00EF\u00D9\u00A6\u00D5\u0085\u008386\u00A7\u00AD\x06\u008D\u00A9\t\x1D\u00F8\u00CA\x104<\u00B3\u00E0\u00A1W\u00AF\u00BC\u00F6<\x00\u00EC\u0097\u00E3\u00F1\u0093\x7F\u00FE\u00F2\u00A3\u00CF\u00E1\u00DF\u00FA%\"e\u00E9\u00F4D\x05\u00C1\x04`\x1B\u00DC\u00AFQq\u00FD\x0600\u00C7\u0093\t\u0088\u00E8\u008BQ\u00DC\u0096\u00FA!\u00A5\u00B5o\u00E2\\\u00EE]\b#c\u00DEq\u00F60lm\u0092\u00AD\u00DE\u00A7\u00C2*bB\u00AF\u00C2\u00F1\x15l\u00DC@\u00D6\u00B2^\u00C2\x15\f\x17\x1F\x186pL\u0095/\u0093V\u00CF\u00BD\x1C`\u00B8\u00B4 \u00AA\u00D1\u00A4\u00FAX\u00D4\u0085JZR\u00C4\u00E3A\u00DB\u0083\u009B\u00EA5\u00D6\u00F9\u00CA\u00A84DN\u00C5A\x06\u00A0\u00EF\u009B\u00957!^#-\u00E1\u00AB\u00FB5\x16\u00C6\u0098\u0083,%\u00DC\u00B9\u0086Iz\u0093\u00B4s\u00C0\u009Ar\u00E3\x05}'\u00801\x1C8\x18\u00DB\u00D8k d\u00CC\u00FF\u0084\u00CD\x19\u00CAB/\u00BCB\u00C9\u00EDd2c\u00C6\u00B9KYu\u00DB\u00A1\u00A8<\u00CB\b\u00BF#\bx\x0E\x1A\u00C9\u00C8 &\u00EF)~\u00C1\u00C8\u00C0,\u00B2\u00EA\u00CC\x15q\u0083Mn\u00E1\x0Ea6\u008B\u00F32\u00D0\u00D0\u009A\u00DF\x7F\u00D6\u00D85\u00A7\u008B\x04\x17\u0083\u00E3\u00BD\u00D2\u0088\u0089\u00AC\x1F\u00A1 #\u00D5V\u0080\u00EA\x19\u00F0o;\u00C8\\foS\u00E8,\u00C6#\u00E3\u00A4\"+\u00E5\u00EAM\x16\u0097\u00A8Gv\x1B\u00CF6\u00CD\u00A5\u0092]\u008Aq\u00CC\u0098{\x1B\u008E\u00F1\u00F5\u00AF>\u00E7_\u00FC\u00EC\u0093\u00FB\u00A7\u00C7\u00A3\u00EF\u00F8\u00A2\x7F\u00FE\x0F\x01\x1A\u0088\u00F8w \u008A\u0094\u00C1\u00A9&\u00C8m\u00A8\u00B0\u00DEr\x05m(/B#j\x18b\u00CB\u00F7b\x06Ys\x00\u00C5@\u00EA:\x1A\u00A3\u00D2\u00EE\u0080\u00DB\u00BF/oq`\u00E9\u00D7\u00FD\u00BC\u00D5\x009\u00CD\u00F05\x18\x1E\u00D0\u008E\u00B0\u00DBZ\u00A9\u00C1a\u00F4&\u00C4\u00FB\u00B2\u00DCE\u009E\u0085 \u00F2KJ!\u00D9\x1F\u00C4\u0088\u008AY\x053(r\u0096\x17#\u00A2t\x15\u00B6\x1Cm\u008B\x11\u0084\u00CD!\u00C6=\u00DB}\x13\x05\u0084\u00D9\u00BEIP=\u00EE8\u00ABB\u00AE:\u00FE\x00\u00C4\bJ\u00B4\u0080\x0B^\u0092-\u00B6\u009F)\u0089\u00DD\u00C5\u0097\u00B5\b\u00C2\u00F0\u00B4D\u00A0\u00A2\x16\u00BB\u00AE\u00EBbW\u008B\u00BC!\u0080\u00F4\u00B1c\u00E9w1\x1E\u009D\u00DFavk/\tx\"\u00ED\x05]2w\u00C2\u00D6Ks\u0096(b\u00A0vE\u00EA\u00C7\x1A\u008A\u00C8\u00FEKZ{^n\u00D9\u00DE\x1D\u00AF\u00C5`\u00D8\u00D1\u0088\u00A4|\u00FD\u00AD\u00FD\u00BA\r\u00ED\x19L\u00F6M]!#\x17\u00D3H\u008FMGG\u0099\u00F8D\x0F\u00C8\x04n\u00BE\u00F0\u0092\u00DD\u00FF\u00C4\u00A7\u00BFc\u00FF<>\u00FB\u00FE/\u00F9\x17\u00EF\u00EE\u00EC\u00B7|IrQ\x1D\u00F0\x07\u008D\u00C8O6\x02/\u0089\x1F:q7\b6\u00EF\u0081\b$\u0089[\u0099\u0081\"\u00D6\x1E\u00DELF\u0090\x0F\u00BE\x18B\u00A1\u0082~\u00AEBxuNg\x06\u009C\u00F8\u008C\x1D\u00AD\u00C5t\u00C0\u00F1\x12.\u0098\u0088\u008A\u00CCW\x1E[\u00C2)\x1D9\u008Dv\u0090\u00F6^\u00FDm\u00A4\u0085^\x19\u00C9@wb\x12\u00A1X\u0091\u00DA\x03\u00C3\u00A6\u0091\u00C7\f(\u0086\u00E0#\u00C3\u009E\x15\u00E2\u00EC(\u0086\x03\x18v\u00EF\x006\u0098\u008Fc\u00CBEb\u00CAl\u00D4\u00FAu\u00D4\x02\x1A\u0088]\u0093\u0086\u0091/H\u00E4\u00B5\u00CAG1\u008A6\x7F\u00BC\x0F\u00A5tm\u00D3.\x04\"f\u00AF2\u00B6@\x11~\u00BF^\u00F3\x0F\u00DA\x00R\x06V\u00FB\u00BA\u00D6\x00WPOJ\u00EC\u00B6D\r\u00D0\u00EEN\u00B9;\u00B6\u00B7D\u00A6\u0084\u00DE\u00ED>\u00CD\x1EP\x04\u00AD\u00A0\x1E\x06f\u00BB\u0096JS\u008D\x1C\u00C9^\x01@\tDRE\\\x1E\u0086[/^\u0091\u00FDg\x1Fd\u00D4\u00F4B\u00BC\u00F5;r\f\u00C6s+\u00C7\x03\u00AB\u0087#\x19R\u00C4\u00B1\x18Z\u00B1\x19\u00A2\u008E\u00ABG7x\u00F5W\x7F\u00ED\u00FD\u00FBg/\u00BF\u00FD\u00BE\u0087\u00FBk\u00B9\b\u00DF\u00D4\u00CB\u00B50\u00AE\x11U\u00826D \u00D1\x15\u00E0\u00D7X\u00F5w\x12~\x0B\"\u00A9\u00C1\u00B5\x1D\u009C\u0093\x19\u00A8\\Z@\u00ED\u00DA\x01I\u0093/\u00D5\"\u00CE\u00ED{2\u0096\u0085\u00BDK\u00DD\u00CEpl\u00F1,\u00B8\x01*d\u00EAP\u00AAu\u009A\u00F5\u00EAA!\n\u00A7|\u00CD\x1DO\u00C0\u00B1g\u0089\u00B6\u00E8\u00C5\u00EE\x11\u00BA\u00AC\u00F4\u00E9\b\x16r\x1C\u00A0\u00C7\u00C0T\u00CE\u00BDBe&\u00FFR:u\u0094/pL\u00D7\u00B6\u00B1u\u00B6\u00DESnH\x10\t\u00CC\u00B0mD\u00FE\x04\u00F3\b,\u0097'\u00F9\u009B!\u00C2\u008Fe)7\u00AA7q\u0082\x10\u00861\u00F0 G\u00ED@n7\u009F-\u00D2\u00C2\u00DD\u00D2\u00A9\u00C52k\u00FD\u0094\u00F45\u00B7Zx\u0090\x7F}\u00A2j\":\r\u00E8R#\u0088\u00D1\x13:\u00D3\u00BB`\x07b\u00EF\b\x0F\u0089\u0096(\u00B0\u00A0yA\u00E4r\u00DB%\u00E9Ir6\u00D8\x1D\u00C7\u00E2|m\u00C9\u00EE\u00C3aG\u00EB\x03\u00DD\u008AF\u0088mB\nn\x11\u00ADi\u00CE*\u00F1TA&\u00A0\x023\u00C6\u00EF\u0091\u00BB0\u00819\u00A9Ry\u00FE\x1DoKFU\u00B1\x131\u00E42$\u00F2\u00F9%\u00C3\u0098\u00A7\u00B1\u00AB_\u0089;\u00D7E\u00C0\u0083>g\u00C4\u00C8\u00A4Z\u00E59?\u00F7\u00E6\r^\u00FE\u00FC\u00E7\u00DE\u00B7?|\u00FC\u00D2{\u00E6\u00BD?\u00C8V\u00EF\u0082\u00D9\u00D7\x00\x1E Kx\u00F3m\u00A6\u009D\u009Ae\u00DD\u00BE\x02\x16\u0098\u00DF\u00DC\u00840\u0094j\u00D0Q\x03\u00DA\u00B9=B\u00B1\u009F\u00D7\u00A2\u00D0\u00F2S\u008B\u00E5\u00BC\u00CB\u0093\u00D4\rk\u00F7\u00B76\u0096\u00DEn\x1B\u00A7\x10\u008AK\u00C2\u0084A\u00F0u\u00D3]Fco\u00AB\u0084\x16\u0083\x1A\u0088b\u00AE\x0E\u00A4\u00FD\u00E1b%\u00F5uMh\u0083\x03\u00C3\x02\x11\u00A9\u00A2s~w\u00E6G4\u00B5\u00C3\u00C8\f\u00A2\u00D2\x12U2\u00DA\x1D\u00E4\u00BA4\u009B\u0090)tAD\t\u009D\u00C1\u00C5\x16+\u00A8\u008B\u0085UD4h\u00DC\u0092\u00A9j\u00DEY\x7F0\x11B${\u00AF\u00D7\u0091\u009F\u00F4\\\u0085^\fU\x0B\u00B5\u00FB\u00FE;\u00B17Cb\u00EA\u00D0\u00DD\u00AA/i\u0098]n\\!\u00F5\u00F8Q\u00B6\x05\u009F\u00CD\u00D0\u00D7\u00AF\u00EF#W\u00FF\x1B\u008CR{b~D\n\u00A6\u00A4\"U\u008D\u00EE\x1E\u0083\u00DE\u00C6R#\u00A1\u00882\u00E7\u00B3\u00CF\x031G\u00CD\u0099~\x17s\u00E1\u00FD\u00D2\u00D5\u00D8\u00E6LjKc\u0088\u0098-Xj2/\u0085v\u008EG/\u00FD\u00FE{\u00F6O\u00F9+\u00EF\u009Eo:\u00E5yC0\x01\x1A\rMq\x04\n8\"3p\u00C1\u00FA\r\u00A6\u0092h9!\u00A3P\u00C1\u00ADHD\u009D\x13\u00F0vv\u0086\u0090\u008Bp\u00AC\u00ED5\u0082\u00B7\u00EC\u00E3\u008E\u0082\u009D\u00B4\x00\u00F7\u00FC\u0086\u0096\x058\u009B11\u00F5{\u0097\u00F7b\u00B57h\u0087\u0081\u00CD\u0081a\u00DCW\u0081B\u00E2\u0086\u0090~\u00A3WasK\u009D\u00D9\x11\u00A6\u00D6\r\u00ABGA\u00F7\x1CP\r\u0082\x0E\u00FD\u00F5\u00AB\u00E5\\\u00E9\u00E5\u00CE]\u00A2\u00BA\u00BB\n\u008EK\u009Eb\u0080\u0089\u00DCc\x1E\x07\u008C\x05\u0089\u009A\u009D\u00C5-\u0093\x1D\u00E5%Lz\u0092\u0094\x06\x00G\u00ED\u00C6\u00C4\x13K75\x04\x02\u00EBk\u00C8\u00D9\x16\u008D\u0096\u00EAS\u00E1\u008F|\u0096f\x1B\u00A15Z\u009C\u00FE\u0089\x19d\u0092\x16W6\u00F5\u00DE\u00E9\u009EO\u00BD\u0090\t\u00E1\u00F4\u00D4\u00B3t:Q\x18\u00B5\bo\u00FD\x11CQ\f\u0081%a)y)\u00F7A\u00D05\u00D4\u00C5}\u0081\u00E9\u00BC\u00F5\x1C\u00C0lR\u00DE\x03\x04X\u00CF\u009E\u00F4>\x0FA\u00C0\x15\u00F5\u00D8\u00C7_\u00F7*\u00E2/\u00F5\x05\u00ED\x1E]\u00B5(\u00A60\u00D9?2\u00C1\u00F4\u00864\u00A6\u00A6\u00A7\u00C1\u00B6\u009E\u00FA\u00CA\u00D7\u00DE\u00BD;^~\u008E\u00A7}\u008B\u00D7\x15\x02\x15\u0084\u00C10\u0088\u00EE\x1A\u00A5\u00E7Sjg\u00ECz\x10\u00A6\u00DBJ\u00EC\u00B5\u00AF_?n\u00AD\u009DA\u00C9\u00B9\u00EA\u00FEk\u00C2R\x19\x19\u00E3\u00D5\u0099\x06\u00D3\u00A3\u00CD\u00DA\x02\x13Zimb\u0090n\u00D4\u00AE\x16\u009C\u0098A\u008D)\u00C8\r,\u00DE\u00EE\u00DCR\u00BET\u0097\u0091\x12W\x11\u0086b\u00C6m\u009Bx\u00D3Vp\u00C6\rdK\u00BD\u00E831\x01\x19\u00B811\u00D3\u00AD(\u00CF\u00CC$\u00F3\u00C8\u00E4\u00A6T-\u00E2)N\u008F\u0080\u009F0:V\u0095\u00E7$\u0083\u00AC\u00D8\u00CC\x05\u00A1\x07o\r\u0096\u0082\u00D9\u0092,\u00A2*\x0B{2\x01\u0084z\u00E4\u00A6\x055P\x16q\x11P,J-8x\u0099I]\u0090\x17\bk\u00BB\x18\u00CA\u00C9\u00D3#\u00E5K\u00CC\u00A1\u00EA\x19\x02n\n\u00C1\u00F5bJ\u009D\u0099\u00F9d\u00DB^\u00DE\u0089\u00DE\u0097DH\"\u00E8\u00D9$7\u00C8\x14\x1A\u0081\nb\u0093I\u0098\u0098\u008ES\u0086\u00BB\u00E7\u00DC\x01\u00C5X\u00D2\u008A\u00D4\x19\x0F\u00E7\u00ABl\x00\u00EDY8\u00D0\x0B\u00A1\u0094\x04G1\x13\u00CECg$\x19T\x06+F!f\u00E1\u00F5N\x03\u00E7\u00D43\x15rr\u00EC\u00AF\u00FBs\u00FB\u00EF]\u00BF\u00FC\u00CE76\x1F\x18$\u00DB\"\u00A6`Gls\u00B2\u00C2\u00FF5\x16\u00A0\u00DB\x0B\u00E8BL\u00CB\u00B6\u00AE\x0BY\u00EB\u009D\u00A0}\u00A06\x10\u00ED\u0092\\\u00FC\u00BC\x1B\x13\x0B\x15tDQjDW\x11\u00C4\u0088\u00F4\u0080\u00E3>\u00B5z\u00D4\u00AF\"\u00FE\u0090\\T=\x04\x07\u00CD\u00B1\u00DB\u0086\u00E1\u008E\u00E1{\u00C9x\x0B\u00F7\u00E0P\u00A6\x1E\u00F6\b-\u0086\x12\u009AT;1<\x10]!\u009A\u00C6\u008D^|\u00D0Uit\x0F\x1A.\u00AD?\x1A\u0091\u0092\u00A2\u00B8\u0099z\u00FC\u00E2\u00D1\x7F7\u009A_\u008D\u00BA;\u00E3<\u00CC\u0094!IF\u00BA\x18\x05{\u00F0\u0090\u00DA\u0094e\x1A\u00F0\u00BE\u00A2\u00F2\u00FC&\u00C1\r%m\u00D2(\u0088|\u00E6\u00B1(e\x11\x07R\u00DA\u0081\x16\x7F\u00BD8\u0086\u00D2\u00DD5C\u0088s\x17c\u009F\u00EENt\u00E0\u00FDw\x11\x1A\u00AF\u00EF}\u00C3\u00865kR\u00C7G\x0EG\x03H\u00EF~\x0F\x06\"\u0083\u00ABk\u00D5w\u0096L\u00A3\u00A1T\u00C6<'s,5\r\u00C8\x1C\u0085\u008Cv\x14\u0083\u00CB\u00C6\u009AAP\x03\u00ED\u008CVcT\x07t\f\u00E8\u008CF\u00CC\u00CCN}-F\x11c\u00A9jN\u00C1\\\u00E6\u00CD\u00F1\u00CE}\u00C7\u00E5\u00ED\u00B1L/X_\u0086@\x03\x0F\u0090\u009B\u00A4`\x07p\x1F]\u00E7\u00D6\u00C6\u00ABE\u00C8*E.c\u00A3\x16,?\u00DB\u00E2\f\u00E9ZFG\u00B9\u00CE\u00C2\u00D8\u00D8\u0089\u00FCl/\u00E8de\u00E8\u00E1\u00C7J\x7F.\u00EB\u00FF\u00DE\u00AE\x03\u00D6\u008AI\rnz\u00DB\u00AD\u00B8\u00E5>8\t\u00E5\n\x03W\u00D8\u00B1\u00D9\u00C4\u00E6;v\u00CC\u0088K\x00\u00F7uH\u0099OF\u0097\x1E\x05\u008DA\u00C9O\u00F1\u00BA\u00A9\x1E\u0087\u00E1\f\u0091\u00FC\u00DD\u0093\u0087\u00D6 \x1D=\u00D7b\u0086\u0096\u00AADy+\u00C2\u00EB \u00B7c\u0095Z\u0093d\u009F\u00EE\x18C\u00F3\u00D7\u0088\u00D85_\u00B1\u00D0Ls\t@\u00F5\x12\u00D9@#\u00D87R5\u009B$C\u00A4\u00FDj\u00E1f\u00FD\u00C2)\u00E2'#\u00CD\u00E2\u00A7lw!\u008AQ\u00DF\u00F5\u00C4\u00D2-W\u00AB]\u00F6\u0080,4\u0092HH\u00E3\u0092pJ\b\u00B7H_\u00C5(\u00C0Uu\u00BA\u00F5\u00A7\u00EE\u009CR5U\u00A6\u00C9\x1D\u00AB\u00E4\nmFI\u00F5\u00AD\f\u00A5\u00D5W\u0099I\u00CC{_\u00BC\u008D\u00B53\x13\u00CEIG\x0BT\u009D\u0084\x0E\u00DC\u00F5]H\u00AE1\x1C?\u00BD\u00F9|=\u00D5$\u00C7\u00E5\u00F1\u00ABo\u00DF\u00E1\u008F\u00DE6M\u00C0\u00D4['\u00AE\x11\u00F9\x07\x11[\x10\u008B\u00BC\u00D2k\u009Dh`\u00A2\x16h7\u00C4I\u00C2gFb\u00CBE(\u008F\u00C0\u00EA\u0092\u00AC\x1A\x07\u00E5\u00BE\u00F3\u00A5\u00FD\u0093n/\x0EkM\u00A5\u00B0\u0086,Nj\u00C6\u00AAj\b\u0095\u00F4\u0089\u00D7\u00BD\u00E3X!\u008C\u008Dx'R\u009D\u00E2\u0092\u00D8E\u00CAhK\u0080\u00CB\u00FDW)\u00C0\u00D0o\u00F9x\u009AM\x00\u00E1a\u00F0\f(*5\u00E2B\x06(\u0095\u00C5x.,f\u00EF@\u00CDc\n\u00EAT\x1F\u00E45Q\u00B95@EZ\u00D4\u00BB\"\u0094Q\u0084\u0092\u0084\x03\u0094\x18N\u00F1X\u0084)I\u00B9\\\u00D7(5\u00D1\x03\x19\u008C\u00AF\u0086\u00CA\u0080\u00D3\u0092\u00CA\u008A\u0082hm5x[\x018\u008D\u00B8\u00FA\u00EBdsX\n:'\x1C\u00A7\u00D1o\u00F6q7\u00A9\u009C\x12\u00D6\x01g0X7\u00DA-p\x1Dd\x06\u00EC-\u00DB\x15sr\x12\u00ECZ7\x01\u0089\br]pU\u00F4\u00B1v\u00E9\u00DE\u00D1A\u00ADs!\x1EM\u00CE<\u009DKD\u00A09\u009B\u00A76\u00DB\u009E\u0090i\x7F\u0098\u0086\u00AE\u00E6\u0099\u00FB\u00DB\u00F6/\u00CEG\u00CF\u0098S2I\u00BF\u00C7= \u0093\u009168\x17\u00FF\u00C0\u0086HS\u00EE.\u00C2\"P[\u00D4\x04\u00C2\u00C0\u0085\u00D8*\"\u00D1\u0093\u00F8\u00B6\u00F5\u00BC\u00D4\u00F3\u00B1\u00A0\u0082$d/\u00E2N\u00A2\u00F5-\u00D5\u0080\u0094\u00A2\u00D9\u00BF\u00CE\b\u0080\u0085\x19\u00F4\u00E3V\u008C\u00C8\u00FBy\x00\u0080-\x1D\u008D\u00AB_\u00A0\u00F7GD\x1A\u00D7f\u00FEA{\u00F70%\u00CD\u0091\u0082\u0081\u00B6\u00A6\u00E6D\u0095e@E\u00C9\u00E2X\\3\u00C1 %\u00AE3c\u00D8\u00F4\u00C4\u00A0\u00AA2pX\u00CD\u00CD\u0086\u00B2Y$\u00EAZ\f[T\x03d\u00B8\u00E0K\u00E5\u00D0\u0082\u00A8\u009C\x0BZ\u00BA>o\u009E\u00B0\\\u0084v\u00BB\u00D2OJ+;/p\u00A1\u0093\ri\x02o\u0084\u0094a\u00D2\u00A9\x1B\u00FBz\u00DDd\u00F8\u00B4\\\u0098$ \u00DDf)E\u0096\u00D1\x7FXxWyV\u00E4\u00C6\x0B\u00C2\u00B3D\x19\u009Di\x18\x19\u00C5\u0091\u008Cx\u0089*\u00840\u0098l-\r\u00D1\u00A4\u00CE\u00DE\x11C!D\u009F#*\"\u009D\u008C\u0082\u00B59-n3F\x11\u00BDxs3\u00B6\u00BE\u0091\u00B7!\x19JC\x12u=\u00B0=\u00BCyf\x7F\x1D\u00AFmS\u00FA2\u00EEC\tIB\x06)\u00BDIx\u008EkT\u00F8k\u00CFV\u0094\u00B4\x152\u00E8\x04\u00D9\t]a\u00C7\u00ED\u00BA\u0085iti~\x1B}\u0094zPRX\u00DF\u00CDV\u00BB\u00C1\u00DA\u0086\x18\x14\u00892\u0083\u00A3\u0092_\u00C7\u00C3\u00B9C51\f\u00EC\x0En\u00CDN7$\u0089+\u00D2\u009B\u00AB\u00ACz\u00B4\u00AAH\u0082\u00DA\u00C2E\x7F[~G\u00A8\x04&\u00B5\u00A9\u0098h\u00852\u008B\u00D8\x01\x05\u00CF\u00EA\u00BAD\bdB\"\u00BA\u00B346p\u00E3\u0098&ae\x14\u008DS\u00A4\u00D6y<&\u00D7\u0095y6\u009F\u009F\bVL`\u00A2\x16<\u009F{\u00A78Z\u00F4\u00D3\n>u\u008E\u00EC\x10\u0094\u0094\\\u00A8\u00DE$kAi=\u00E7\u00F5xy\x02f\u009E\u00A7\u0082\u00A8\u00F2(\u00A8\u00ED@\x19\u00B3]\x1F\fN<$\u00CF\u00F7\u00D6\x17Il\x00\u00C9\u00A8\x14c\u0080fsX\u00D4\u008D\u00D6_(\x00\u00C8\u008B\u00B1\b\u00BDt\x067\u00AB?\u0080\u00FA\u00AA9\u00AD\u00FE\x04\u00A1\u00CF\u00AAe\u00C0P\u0089\u00B2\x07\u00F8\x12\u00B3\u00B00\x1E\x04\u0093\u00AB@\u00ACv^\u00DA\x10\u00E25^\x7Fe\u00DB_\u00B5\u0087\x18\u00B8\u008F\u00E9\x0F`\u00B8\n\u0088\u00EF*\u0081\u00BE\"\u0080i\"\u00E0\u0093\u00E4\u00F7\u00FA\u00F4,(q:\u00AF\x1B\x16\u0097\u00CDTn\x13lq\u00DB\u00C6\f\u00BC3\x17\x0END\u00DDR|;C\u00A9\u00EF\u00DD\u009Eayn\u00FAw\r\u00B8\x1D\u00D3\u00B0\"\f\u00E5\x11\x02\u0083EXO\u009E\x0E\x03\u008A\x01\u009D\u00D1\u00C0\u00E0}*\u00EAR5\x04\x07\u0090\u00C7\u00F5\u00F0:Z\u0091\n\u0093\u00FB4x\u0086\u00D10OB\u0085\u00B9\u00E8\u0089p\u0087<\x06\u008CIc\u00EC\u0082\x18\x07\u0099\u0092\u00F5\x05\u00AC\u0085\u00C4\u00A1\u00A8\u0084Y\x1E\u00D2\"\x12%\u0088X\n\u00C2\x06AtH+\u00F8Z\u00A5W\u00EB\u0092\u00C6\u00B6|\x04\u00CCNHOi\u009B\x0B\u0096Ld1\u0080\x19\x1Cs%\bJ\u00CC4\u0094y\u00BF\u00D7@\u00A6'/\u00F7\u00D6\u0083\x0BE\u00AC\u00FA.b\u00B3\u00A6\u00DE\x00\u00DAt%!7\f\u00C9YZ\u00CA\u00B2\u00A1\u00D56\u00E2\u009Cx\x1F\u00BB\x1B\u00B2^C\u00AB\u0095X.E1N_\u00AE1\x18\u0094\u009D)\u00E6$1\x11\u00ED\x0B\u00BDx\u00A9\x06sf\u00FE\u0084\u00EB\u00BE\u009C\u00A7\u00F0\u00C0\x14\u00F3\u00D8_\x7F\u008C\u00FDw\u00AF^\u0081\u00F9\x13\u0088\u00A8C\x11\u00FC\u0080T\u0083a\u00FBj'\u00B0\u00C6$(\u0095a\u00BDv`\u00A0\r\u00E5.\u0088\u00F0|\u00D1\u00E7\x1B\u0083H\u00BBBG\x07\u00FA\x04\x16\u0084\u00A2\u00C5`\u00E5\u00BD0\u0093\u00C1\x12\u0089\x0En\u0087-\u00F3X\x0BB\u00EA\u00F9\n\u00C5Pz`T\u00FF\u00BD\b\u00BC\b^\u00E3\u00E3\u00B9\x0E\u0084aT\u00FD\u00DC2\u00A48\u00E2\u00F8k>\u00AC1\u00A5\u00F5\x1C}\u00E3\u00BD|5\x1A\x02\u008A\u00F2'Q\u00FB\u00A4\u00ED \u0096\\\u00C4\u00DC\x05\u00E3\u00AB:\t@\u00B7'\fhWjFCz\u00A9\x06\u008B\u00C4`\u0084`\u00FD\u008Ez^\u008Cjt\u009F\u00CDY \u00C2\u00D4\u009F\u00C6\"\u00AE[$\u00F1\u00A4\u00D8g\u00DB<\u00A4'w\u00FB\u009B\b\u00A3\u00FAd4\u00AC\u0095*\u0082$H1\u0087\u00B9\u0094!?Z\u009F\u0094\x03\u0080\u00B4'd$\u00A1\u00A2\x1Fy\u00BE\u00A9#\t\u00BFQL\u00C0CZg\u00A2\u0091\u00E6M\u00F7\u0091Q\u00CF\u00C1\u0098\x04\u00A4\u00E1\u00CF%\u00E53\u00DD\u00BAT\u009DDI\x19\u00FB1\u00A2\u00AD\u00E3(\x03\u00A8n \x14\u0093%\u00E4\u00C5L\x06\u00E0\x17\u009E\x1F\u00CCL\u00F75%y\u00B1\x7F\u00A3\x1B.i\u00D0\u00B4\u00CB\r\u00F6\u008B=\u00C2a\x0F\u0082\u0087\u009B$\u00F1\u0080\u00BC\x04k\u00E0\u00D0\u008E\x0E\u00E3k\u00FF\u00C5N\u00CC\x05\u0092\x01y\x0E\x1Aq)\u00A2\u00CE\u00BA\x0E\u00AFs\x1C+\u00C3 Ax\u009A\u00C3Je\u00F0\x1E\x06-\x02VnEh\u00CFKTbC\x05\u00F5\u00A9\u00DC\u0086\u008E\b\u00C6\u00D2\u009E\u00D6c\x10\u00E9\u00C6\u00C5\u00A2\u00F6\n\u00AEG\u00BD\u008E\u00DA\u00EA\u00CD\u00BAjs\u008Ai\u00E8o\x15N\u00D5}\u00CB0\u00B9\u00A1*\x1A\u0097$7\x14\u00A3\t\u00A3\\\u008D'\u00CA\u00C0\u00C7\u00DF\x17\u0088}X\u00CE\u00EC\u00EE#\u00B20\u00AD\x10G\x19@\u0081\u00B2\tt$P\u00C5<s\u00EC\u00F97\x17\u0099`3F\x0B\u00CA\u00D1\x02\u00AD\u00DF\u0093i\u00A0\u00AA7\u00F7\u00D0c\u00A7\x0B/\b\u00A8\x13\x0B\u00AA\u008D<f\u008B\u00E1\u00CC\u0093({\u009B\u00D6\u00FC\u00F1\u00A1&\u0088\x18\u0095\u00F8\u0093\bB:~\u0087\u00D3\x0B\x02A\x12\u00F7\u00DA\u00973\u00C3\u00A0\x17\u00E2T\x7F\u00A0\f\u00A7\u009A\x07\u00AA\x15\u00CD8X*\u0092\u00DA$\u0083\u00EC\u00A8g\u009E\u00FA4\u0085b\u008A\u00F9\u00D5\u00FC\u0083\u00FD\u00E6:\u00CE\x00%\u0090\u0099pL\b\x01\u00B2=\u00BA`\u009F\u00CAX\u00C3N\u00F8\u00AA]n\u0094C w\u00E3\n\u0081\x157P\x7FK\u00DAWA\u00D4\n\x15n\u00E7`\x00g\u00FBB\x12 \u0089\u00B8[\u00C1\x13\u00F2\u0091\u00D0\\y\r\u00ED\x1C\x11\u0093\u0088z\u00AA/\u00EB9kt\u00E3\x1B\u00A3\x15,\x0B\u009F\u00BD\u00B3\u009D\u00FE}\x1A\u00B2\u009A\u00814\u00CE\x0F\u00D5Cd\u00E6\u00E8h\u00A2#\u0095\"`\u00CF\u00BE5\x14\u00E0;\u0096\u0082\u00A3}n\u00BC\u0098^0\u00C9\u00F5zEN\u0086}\u00B0R\u008B\u00A6\x01\u00C3\u0099\u0086D\u00FB\u0087\u00B6\u0085\x1B\u008C\u00B0L\bl'b\u00D0k1\u00EC\r.\u00E03\u00C1\u00B3\u00AF\u00AEg7sQ\u00EAx,p\u00AC\u00ED\u00B3\u00CDe3\u00D5\u00B6\u00A0W\u00F8o0\x12\u00B7\u00A0\u00B57\u0086\x10L\u00CB\u00D7\u00EB:u\t6\u0083L\u0081\u00C7M\u00CF\x10b\x14\"\u00DD\u00AE\u00CB[#*1\t\u00F5U\u0081F@\u00D6V\u0090\x04\u00AFU\u00C4\u00F6W&\u00E7\u009C\u00AF*\u00ED\x1E(b\r|j\u00ED\u00B2?\u0099ZM&QQ\u0099\x14\x1E\u00ECS\u0096^\u00CF\u00FE\u008A9\u00D5\x031\x18ps\u00C1\u00EE\u00B8\x00\u00CEb\u00A7\u00A9\x16\u0094\u00C7\u00C0Z\x1ArL\u00F7~\"\x1E\x12\x07\u00FF\x1E\x103\u00E9D\u00A1v\u0081\u00DBR\u00BD\x13\u00BC\x18@\u008B\x12\x1Bb.h\u00F7)\u0089\u00BEz\x0F\u00A2\u00CD\u00AA(\u00DCT\x16\u00FE\u00BE0\x05\u0096m[\u0092\u00B1\u00BC\u00DA\u0089\u00B6hg\u00C0\u008EaLP\u009A\u00D6\u0098\u00A1\u00D5=Q\u00BB*\r\u0096\u008AO\u00B5\u00A91\u00C3\u00D4\u00E7\u009B\u009B\u00D2NsR*\u0084\u0088I\x06H2\x03\u00AA8\u0092*1\u00E6bH#\u00DB\u00D2\u00DA#\u00A2\u00CBHR\u00CD\tK|i\u00CDJ\u00FA\u00BAQ\u00CA\x19J\u009F\x15aI\u00BA\u00D1\u00FF>\x0F\u00C0\x0F\u00E6\u00F4\u00CB\u0088\u00E7\u00CB\"\u00CD\u00EF\u00DC\u00AF\x00\u00CE'6\u008F<W\u00D08K\u00C9&\u008C\u009E\u0081D\u00E0'\u00F7\u00A1\u00B7MU\u00D8\u00EF\u00C3\u0088<\u00A8\u0098\u00A3\u00FA\u00A1k\u00CA\"o\u00D5\u00B6\u00AA+\x1DF\u00D5\u0084\u00E3\u00CF\u00B1\x10\u008E\u00FB\u008C\u0080\x12\u00F7\u00E8\u00FB4hC\x141\u00BA\u00D1\u00D0\u00CB\u0098\x06\u009FG0\u0092y\u00B0M\x12\u00FA\u00BC@\u009B\u00AFV\u00F5\u00A6\u009A\u008Bs\u00B4\u00A1\u00B5\u00D8\u0089\u00AA\u00B3\x00\u00C8\u00C0kto\x0E=\x13\u00DEO6\x05\u00B9#=\r\u0090\u009A\u008B\u00B2\u00D1\u00EC1\u00B5W\u009C*\u00AA\f2,\u00B6x\u0082[\f@n;\u00DB\x1A\u0091U\u00E1\u0092U\u008D\u00E8\u00D6\u00FB3t\u00D6=\u00E46\u00E4\u00C3V\x1F\u009A\x11\u00B3\u00EC\x10w\u00BD[\u00DF\u00DA1_>\u00B1\u00DE\u00B338 \u00C7\u00B4\u00B6\u00D9\u0088\u009E\x10ZRZ\u00A1\u00B8\u00BA\u00B7\b\u00BBB\u0089;\u00F3\u00AA\u00F7\u00D9\x13R\b\n\u00ED\u009CB\x1F\x15\u00E2\u00DA\u00E6\"\u00D5#$\u00C3\x0En\x7F\u00F7\u00DCd\u009F\x18tu\u00B4\u00DF\u00B6\x06\x04\x16\u00E9\u00DA\u0099A\u00FEnPH\u00B2R}\u009D\u00AB'\u00A5?\ne\u0094\u00BB\fm#\x11\u0090N9&\u00EA\u00D3\u0096\u00CC\u00C3\u00E0MeX\u00AA&w}\x1D\u00E5A(C\x1C\x19\u00BF\x0B\u00B2{k\u00B7\u008FO\u00D0\u00B9\u0090Cm\u00F7&)\u00DA\u00FA+i\n\u00ACs\"i\u00DB\x11M\u008B\x17.\u0084\u0081\u00EAgJ\u00E8\r\u00E5Z\u00D4\u00F9\x1AKC4hm\u00A7\u00D1\u0091\u00F7q\u00CF\u00AB\x01\u00CE7\u00D6\u0082\u00ED\u00D5\u00D7\x1Ag=\u00ABz\u0099;\u00F6\u00F2&\x10\u00EAZ\u008B3\u00F0N\u00C0\u00D2\u00C9iSh6\u0080\u0095\u00D8\u008B8V\x03\u00DEh\u00BF\u00AD\u0086\u00C2(\u00FE\u00C0\u00EF=\u00F3q\u00F1\u0099\u00DF\u0086\u00DD+\u00D3i\u0086IC\u00B6}\u009B\u00F8\x1A\u00D3\u00D0\u00BD\x16\u00B8\x1B\bia\u0080\u0086\u00C6\x14\u0089\x1A:C\u00B0>\u00B6\u0090\u00D6JbN\u00C6`\x1D\t4\u00B5\u00C4:\u00E3\x00\u00AA\u00DEC\x11\u00B4[p\u00FE2\u00A06\u00F4chc\u00D4\u00B8\u00CFh\u00A7\x10\u009B\u0082\u0094\u0092q%\u00E1\x17iW\u009F<\x17J\u00FCNK\u00BC$\u00E0ls;\u00AFR\"{JAk\u00EE\u00B0\x18\u00AF\u0082~R=\u00C88\x04C\u00E8\u00C1\u00A3I\u00C7\tO\u00EF\u0092\x0E\x02i'\u0090;S)\u00C6\x1D\x0E{\u00FB\u008D\u00D22\u00E3\u00F6\x1BS0\u00CE\u0099{\u00B5+\tj\u00D9_\u00D6\u0093\u00CCpl\u009E3\x0F,AH\u0089\u008A\u0090\u00CC\u00A8\x18\x0E\u00C0\x1D\u0086O\u00CCc *@\u00A9oV\u00F7Y\bY\u00E7\x04\u00E37\u00DEn\u0099\x07^\x13)\u00E5\x03!\u00D4{\u00D4g\x13n\u00CB<i\u00ED8vw\u00C5\x12h\u0091U\u00A8\u00AF\u00A5\u00AA\u00D0\u00A5\u00AF\u00D4\x02!\u0082\u00BA\u00B6\x16{\u008F\u00D0[\u0099\u00C3\x1A\u00C5\u00C8c\u00860\u00C6,\u008CB\u00C4X\u008B\u00AEt\u00F1.\u0095O\u00CC\x05\x15\u00E5\u00D8\u008D\x7F8\u008D\u00A1\x18A\x11h$\u00ECt\u00E2\u00D6y1\u0092\u00C8h\x17\u00C4\x16r\"Wn\f\u00F2\u00B6G\u00A2\u00A2\x0E\u00B3O.oB\u008B\x1E\u00EC*\x06\u00D6h\u00C58\u00E5\u008C\u00BAb\u0087)\x03U\u0092\u00C5\x00I\x0FPcrg\u00C6\x1C\u00BC\u00C0\u00B2\u0084\x00\x18\u00B9\u00B6\u00FA\u00B8k\u00A1I\u0092\u008B\u00F9\u0088\u00A0#Ym\u00C2\u00EC@5\u00D6\x18m\n\u00DBH\u00B5\u00A2\u00F9\u009C\u00CF\u00A8\x11\u00B2\u0091Xl@n\u00C2|\u008Eb\n\u00D5X;\x1E\fhbFb\u0097\u00F8\x17\x06\u00EB\x16\u00C4\u00F8-#,\u00E3\u00FE%\u00B0jL\u0080lD\u00E9\u0093\u00E1X\u0088\u008A\\\x1E\u00A6\u00F8\u00DD\u00AD\u00DC\u0099i8t\x07\x18\u009B\u00E2\u00D9g>\u00F7I\u00E9\u009D\x15\u00A3f\u00CCa\"\u008B\x18gHp\u00AA1I\u0093\u00AA\x18=\u0081\u00A9m\u0083\u00F5,jz\u00CA\u009D|i\u00AA\u0092\u00CE\u00A5\x10rC\u00D6\x0172\x06\x18v\u00C3\u00D3p\u00BC\u0086\u00F2\u00D5\u008BH\u00EFH42Y\u00F6\u00CF\x04\u00A6\u00C9\u00EC\f\u00E4,\u0095\u0081\u00D5hhK\u00DB\u00D9\u00A6\u00A4Z+\u0096R\u008Cde(\u008B\u0094O\"9Cr\u00D4\u00B8\x14\x1F\u00B1\x18\fW\x02[s\x1Ft}\u00EB;\x1CU\u00C6M\u00C7\u0081u\u008C\u008D\u0090\u00DB\u00B1@\u0081M\u0092g\u0088\u00F5y>8\u00FF\u008B\u00AA\u00D6\u00C7X\u00FD^\r\u00B75\u008En\u00C8\\6\u00B4q\u00A5\u009DG;\u00C7\x04F\u00A3\u0087\u00DA\x01\b)\u00D5\u00D7\u00A4\x1EJq8*\u00E3\u00B0\u00E5\x0E$\u0081s\u00B1q\u00E1\u00BB_P6\u0088\u008E8\u008Cs\u00AAx\u0085K.\u00CE0\x18N\u00C4\u00EE\u00C9$R\x17c\u00D0b\u0096\u009D \u00CE\u008B>6\u00FB\x01\u00A3'=\tJ\u00D7n\u00F5\u00BB\x18\u009D\u0088\u00D4[\u00FB\x18\b\u00DB\u0081\u0098{C!b`\u00CE\u00FD,\u00F3\u00DE[\u00EB\u00CFl\u00ED\u00F5\u00ECM\u00D9I.\u00EC\u00D3V\u00EB\u00C0\u00F5tf\u00FE\u00BD0\x10\u00D5[H\u00E9?\u0090\u00B9HY_\x02\u0081\x10\u00CE\u00F3\u00A5k0\u00E1Y\u00B3\u00E2&\u00E6\u00D1\f\u00BB\u00F9\x13p{\u008C\u00C8](\"\u00A8\x14f-Z\u00E9\u00AC\u00DDfp&\u009A\nI^\u00A0q?\u00B7\u00BB\x13\u00B5\u0098E\u00FC\x0Bt\u00D7\x04\x01\u00E9\u00AAL\x03\u009A&n\u00CFI\x14\x1F\\\u00DC\u008A-7\x02\x19\x07\u00B0\u00A1x\u00A61\u00BAqE\x18\u00854jl\u00B91K\u00FE\u00C6\u00BE\u00B8\u0082BH\u0084\u00E7LJG\u008EC.AW\u00F2V\x1B\u008B\u00E7\u00F9\u00B4\x0Bd\x7F\u00DA+\x11\u008F^\u009DY\u00C4\u00F5\x13*\u00ED62\x0EA\u00F1\f\u0086\b\u0081\u008E\u0080,\u0086\x0ByT\u0082\x0E\u00C1S9\u00F6\x15\u00B02\x1A\u00A1Sh\u0098\u00C7b\u00B2\x03>[\u00A5\u00E1\u00D4\u00ED\u00D1\u00E6R\x12-qs|\u00FA\u008E\u0089\r\u00C3\x0E\u00C08\u00B3\x0E8v>\u008A\t\u008C\u0090\u00E21\u00C7A\u0094Q\u00D2\u00ED\u00C8\u00F1$\u00F15Cd>\u00CBD8!\u00AB]\f\u00C0\x0E\u00B8\u008A\u00C6\u009A%\u008A\u008859\u00AB\u00DF\"H\u00F5'%\u00BA\x18\u00CFF\u00E8\u00ADJ\u009AD\x15\u00D6\u00F7\u00FE>\u00C88\x1D*\x18\u00EBv\u00C4F\u00B9\u00A9rq\u00D2\u00FDB\x06G\u00D5,\x11\u00DFd7\u00CE\u008CH\u00EA\x16\u00E9t\x1C\u00A8\u00CAI\u009C\u00F7dH\u00D1\u008Fb\u00A2\u0091n\u00EE\u0089\u00D4.\u00F0}\u00C3>\u00FC-\u0098x5&\u009A\x0F\u00B2$J!\x04[\x18A\u00FD\u00DEc\u00F3o\u00FF~\u0092\u00D0:O\x12\u00C0z\u00FB\u008DI\x106.\u0081?\"\x1EB\u00B1\x18\f\u008A\u0090\x00\u00DCfT\u0092\u00BEg\u00E6e\u00F9\u00DB\u0099\x19h_e\u00EFR\u00D7\x10\u00D2\u009AR>\u00FAM\"\u00B0\u00B0$[O\u00AA\x02\u008A\u00DB7\u00A8\x1E\x0B\u009E\f\u00D1\u00BB\u00F1Q\u00F3R\u0084\u00AB\u00E7057h\u0086A\u0097qp\u009C$\u00FEj\u00D0T\u00F9\u0090\u00F2\x12\u00898L\u0086g\u00C0\u008D\u009F\x01\u00BBG\u00D2m\u00E8\u009CA_q\u009F\n\u00F6!JH.\u00ACE}\u00C0\u0099~-\t\u00CF\t\u00E3\u00B3\u0093t\u00F3x\u00B6)M/(\b/D\x00\u0082\u0089\"pK\u00E9\bd\u008CC\u00C2l\u00AD_y\x05\u00D8'!\u0097\u00A6n\u00B8\fo6I\u00E7b\u0090Z\x1B\u00ABj\x14\x0B@\u00C6\u00CB#\u009E\x7F\u00D6\u0085\u009C\u00C8\u00F8\x1C!\u0082\u00F6\u0092Z\x06\u00CCl-\u00C7\u0092\b\u00A4I}Ix\"\x15\u00B7\u00A6\x1A\u0098\u00A4;\u009F\u0089]\u00F8y \u00D3\f\u009C\u00CFhr\x1E\u008D\u00B9'\x14\x02\u00EA\u00A3\x00\u00DF\u00A2\u00E6\u00EF\u00D7\u00D8\u00DF=\u009F\u00C5\u00BF\u00D9_\u00E4$(W\x00(\u0088,\u00F8\u00D9\u00D0\x02\f&c\u00CF\u009D\u0085N\u0080\u00B2G4x\u009F\u008FD\u00AAC\u00AB\u00B8l\u00ED:\u00C3\u00C2hV5\u00A11\x1C\u008B\u00F3K\u0092\u00EB\u00DCv\u008D\u00EE\u0097\u00D2U\u00D7v\u00C4\"\u0086s\x1B\u009E/\u00E3q\u00A9\f[\u00B9\x11\u00B3\x1FE\u00A4\u0082\u00BF1w\u00C5XJ\u00BF\u008F\x07\x1ALA\u009F\u0080v|\u00CE\u00D4i\x17\u00E3;\u00ABa\u00BC\x1F\x06k,\x0E\u00DA\u00F9d\u00C8\u00EC\u00DF\u0089N@\u00C9\u00CF\u00AD\u00D8\u00C5\f\u00E0\u00F2\u00E8\x19\u00B4\x11\u00CB\u0096\x06C\u00DEILB\u00EA\u0084k>\u009C\u00CF\u00F0\x12\x12\x06@\u00AE4\x12\u00A0t\u00E8\u00B5\u009C\u009A$\x17\u0089/\u00E1{\u00F4:R\u0090\u00C5t\x1C\u00A52 \u00EE\u00B3\u0084\u00F6\x1E@\u00AFS\u00ED\u0092\u00B0\u00C6\u00CF=\u00A4\u00A0\u00E2\x07\x16Uc\u00C0-$\u00B1\u00A2'\x0BY\x18\u00F9\t\x05\u0094ww\u00A8\u00D6\u00D6\u0081\u00CEF\u0096\u00D2f\u0098|\u00AE\u00CC$\u00A6\u009B\x16\u0086e\x0F\u009BZ\u0093\u009CS1\x0E\u00F7Z\u009F2V\u00E2\x06\u00B5?x\u00F7Nh\x1E/\x10b\u00E8q0\x19\x0B1\u00D5\u00FF\x15\u00D5\u00C7<\x1D\u00B8\\=\u0089\u00FD-\u0097\u00A7\x1E\u00DDN\u00DE\x00\x00 \x00IDAT\u00F1\u00BB\u00BB\fL\u00B5\x11J\u00ED\u00B8\u00AC\x12\u00DF\u00AB\u00E4W}\u0083U\u00BA\u008E[\u00C7\u0096\x14hM&\u008D<\x0Bj\x00P\x04\u00D3\u00A5z\u00F5\x07\u00AD\x1D\x11\u00D0mD\u00D2uz\u00DD\x13y^\u00D5*\u00A0\u00AE\x7F\u0087\r\u00E2\u008Cp\x1C\u00C0AC\u00CC\u00E4#\u009BV*\x04\u00CBg\x06\u00E1\u00F2\u00BCE\u00F2C^\x07\u00E4xT|uRzn\u00A97\u00F7\u00F9C\u00F6\u00C5y\u00CF-\u008F\x15\n\u0098\x10\u00BB)\u00A4T\x1E\u0085x2\rNaN`\x10\x19d\u0099/\u0099\b<l\n\u00A9:\u00F4k'\u008A!\u00A4>k\u0094L\"\u00A4\u00C9\u00D3\u00A5\x1B\u00EBaI\u00AF\u00F6\u00D6\u00F7\u00F8{\u00A9]`\u0093\u00F5!b\u0081\u0097QO\u00C7b-\u00B8]\u00E0C#\x0F\u00E2\u00B4\u0083\x062\x11\u00B8\u00ED!ai$\u00B4\u00B4\u00BC\x1D\u00C0T\x7F\u00D0\u0088\u0098\x10\u00DC\x1C>\u008E\u00C83@\u0084\u009D\u00C3z\u0081\u00FD\u0099\u00E0#\u00B4\x0B\u00A3\u00BCi6\x020\u008E\u00C4\u00E5Z<\u00F2:]\u00EC\x12~\u00C3\u00E3^D\n\u00B1\u008E\u008Ed \u0081\x00\x0E\u00C0\u00E9\u00C9i\u00AE\u00D6\u00F0\u00D6\x1C\b;\x00i$\u00A8\u00BF\x18\u00B8\u0091\u00F1\u008DIM\u0081\u00E3\u00CD\x02\u00AF1\u00AEy\u00EF\x19\u00EC\u00D7v\u00EF0\u00EC[-\u00BC2\u00AA-F\u00C5\u0093\u00AAp\u00F7q\u00CEPOD\"\u00F2H\x04 \u00A2N\u00E9p&@\u00B5S\tI\u0096H\u0080\u00CB\u00DB5\u00E8\u008E\x04z\u00DF\u00F8\u00DB\x12dt\u008A\u009C\u00F4\u00DB\u00CC\u00E0.t 2;<vh\u008A\x1C>\u00E4\u00A2V\u00C4\u009F\x02\u0092\x02\u00F6#!\u00BE\u00F2\x0E\u00BC3\r\u008D\u00B3y'\u0082\u009E\x14\u00845R\u00AD\u00AA\x10k\u00A9\b\x1Di\u00E9\u00DFb@\x07F\u00DA\x10\u0084\u009CD\u00F8\"tAqkLAE\u0084\u008D\u00F41g\u009B\t\u00AD\u00F1\u0094\u009C\u0092Z$\u00EA\u009CoI5\u00B9\x1D\x19\u00B0\u00B4\u00B8\u00E6\u00AER\u00EA\u0097>n\u00EA\x14*\u00ED8\"h5\u00D7\u00850t/\u00F5g\x12\x1E\x13\u00C63mX^\x12\u00EBF5\x0F\u00C4\x12*X\u00D5\u009DR\u00AB]\x00\x14\u0092\u00A1\u00B0\u00B2=\u00DAm\x06\u00DAtK\u009E\u00FBF\u009D\u00BFl/D8\u0089z\u009A\u00D1\u00D1UR.\u0098\u0099LD\u00B2\x1D$\"\u00A0\u00D1/\u00D7h\u00A2\u00B3\u00A62\u00A5q\u00F4\u00E0:\u0092\x11Q\u00CF\u00A6\f\u0099I\u00A3i\x006\x1C\u00D7\u00F7\u008F\u00FDm\u00E3\u00FE\u00D7\u00CD\u00F6\u00B7\n\u00A2O\u00EC)a\u00CE\u0092>\r%-\u0087\u00A1\u00A4\u00FAI\u00B2\u00B1\u00BE\u0082+\u00966\t\u00B1!\x04=X\u00EB\u00B6\bU\\\u0092*\u00D1\t\u00B4\x19\x0BA\u0083\u00DEyS\u00D8\u009C\u009C\x13a\u00AB\u00CF\u00DE\x1F\u00A6E\u00F1\u00D0&Y\u00CF\x16{\u00F5\u00E3\u00B0\u00C9\u00EB\u00A92\u0088\u00E0\u008C\u00BBG\u00D9`\u00C1\u0093\u00D2\u00F5E\u00DC\u00F2\u00F9O\u0093{\u0090\x0B\u00DD\u00CB6\u00A0\u00B1U\u009D\bd\x1B\u0089\x10P\u00F5\x1A\u00A6\u00D3]i{\u00DE/\u00B2\x0E\u0082a\u008E6\u00BF\u00EE\u00E1M\x00\u0099\x02\u00BC\u0098\u0084+j\u0090\u00BE\u00F7aB\x10|4\x0E\u008C\u008C\u008C\x03\u00FBY\u00D1vK5\u00E0)\u009D\u00DC\u00A1\u00F8yo\x121\u00D6\u0085\u00D3\x1F/\u009B\u0080`>\tc\x16\u00B3\u00E9B&\u00B2\x1B=\x16s\u00EA\u00DE`?6\u00C0o\u0088j:\u0081\n\u00F6\u0084\u008A\u00E0\u00C9h\u00DA\u00DA\x13\u0094O\x042\x11\u00C8Dc\u00D5\u00B3!\u00D7\u00C4\u0085\u00CB\u008B\x0Ec\u00F7\u0088sp!\u0088#\u00EF\u0095\u00C8\u0085\u00AB+.\x14S\u00B8\u00D4\u00FD\u0098\u00C3a|\u00E6\u00F5)b\u00A7A\u00D7y\u00FF\u00B4?hN\u00B5\u0084\x1Bs6Gz>8V\u00938cL\u00CB\u00E4\u00D6\u00C4\u00C4\u0090\u00C0\u00FD{_\u00DF\u00AFa/O\u00C7[{|\u00FEm\x02+i\u00EAw\x11\u00DFI\u00C7\u0097q0 \u00D5\u00C6\x07\x06fF\u009E\u00DC\u0082\u00E9{o\u00EDf\u00C1\u00CC\x15\u00A9\x14\u0091i\u00D0gTq\x1B\x15\u00E8\u00BC\x1E<e\u00B0X\u00F9I\u008C\u00B85\u00DEU\u0082\x0F\u00DC \u00F6V\u00B8\u00B1`\u009A]]\x00\x1F\u00E0a\u00F2P\u00AFLAEP\u00E2A7\u00E3`{\u00A2\u0093s,\u00D5\u00C2\u00A9N\u008Cv\x0F\u00C0p\u00B18\x16\u0089J\x01\u009F\u0095\x04\u00EB\u0090-\u009D}\u0093\u0094\u00CF\u00C5&[\u0082\x07\u00C1\u00CB\u00FEFUH\x06\u00C5\x0B\u00E9w\u00F0zxEU\u0084p\u00B7\n\u008AIUAL\u00987rC\u00BA\u00BD2\u00848\u00CEq\u009B\u00D4\u00A9%\u00A1(=\x11\u008B\u00BA\u009E\tPu\x1D\u0095\u00B4\u00D6\"\u00FF\x00\u00A4\u00A5\u009D\x0B>\u00E3\x1B\u0092hX\x17\u00D3I\u00B4\u00DE\u00CA\u009D\u00A5\u00BD\u00A33\u0083b\x14\u00E9\u00E2K&\u00A3u\u00DDvJ\u00EA\u0082\u008A\x04I\u00B3-\u00A5:\u00D7\u0092\u00A9\x1D\u00A9%Z\u00F74n\u0082\u00D0\u00DFG\u00EB\u009F\u00DEZ\u009B\u00B2\u00BBL\u0084\x01\u00B7?U\u00F5Qs\u00A0\u00B1\r\x18.\u00E4\u00FEuO\u00F8\rF\x06\u0085\x05Z\u00BBl\u00F7_\u00DE_\u00C3\u0093_\x06\u00AE?P\u0092\u00AC\x11\u0099w\u0082\u00EAj@'\"\u0085\x17sj\u00F3\x1A>hS\u00C4\u00E1\u0099P%%\u00BA\u00F1O\u00EA\x06\u00D6~t&\u00B0\u00F4\u00A1\u00A9/KlA\u00FB-\u00FB\u00D1\b\u00DE\u00CE}\u00E9\u00E39\u00C7\x15\u00C42<|\u00CFi\u008E\u00E2\u00A8\u00A1.\u00B8\u008F\u00F6H\f\u00B7J\u0098\u00F9\u00A0\u00B4\x16\u009D\fLZ\u00A53\u00C1\b\u00C5(tLRbM+\u00AF\nK\u00B2\u00F9%\x12O\u00A6\u00B0G\x7F&p\u0099\x1C\u0085P\x01b\u008D\u008Eiw \x044\"\u00E3gg(\u0084\u00E0~\u00BAq\u00EEUH\u00C4\u00B5Z\u00F7\u00E99\u00C8\u0085\u00ED\u0094\u00C8\x07Ty\u00B9\x16\u00B2\x18\u0087\u00A4\u00DC\u00D1\u009E\u00A9\u008ESM\u00A1\x1A\u00E2i\u00C7\u00E8*CAe\u00F3~\u009D\f\u0097V\u009F\u00B2\u00A2j0\x0B\u0083j\u00C4\u00A8\u00BE\u00CF\u00F2\u00B0\x18&\u00D2\u00DB``_\x19\u00F3\u0090\u00A8\u00A3o\u008A\u00AB\u0095D\u00F4#\u00A3\u00B4;\u00C7\u00D2\u008C\u00A8\u008D\u00B0\u008B1\u00A9\u00EFV\u00E3\u00C9{L\u00D8\u0094\u00D7\u00E3\u0080\u00D3\u00F6bhh\u00AB#\u0092|\u00C6\u00CEg\u00B4\x018\u00B0\u008F\u00AB/\u00EF\u00CF\x1D\u00CF\u00BE\u0084k-b$A\u0084kO\u0092\u00F9.\u00DD\u00FA\u00C44`\t{\x04\u0095\u00CBp\x18\u0084\u00E9\u00CB\u00B5'i\f\x04sH$\x02t\u00C8v\u008B\x01\u00F4\u00F7\u0089\u00D9X\u008F\u00CA\u00F3\u00B1\u00DE\u00D3\u00B0\x10Xg\b\u00E3\x1C\u00F5\u00A8\u00E8C\u00DFh\x00Dn\u00A8\u00AA\u008DP\u0090S<\u00A0\u00AD\u00BC\u008B8\u00B38W\u008E%=\n\r\u00B5\u00F4c=\x0E\u00C1\u00AD3\u0083\u00EEv\u00C4\u0082\u00C2\n(\x0E\x1C\u00EE\u00B4bKN:\u00B6T\x11\f\u00F3@Z\u00B9\x0F\t1G\u00E6\x0E\u0089\\<\u00AFAJd\x03\u00DD\u0092\u00B31\u0088\u00B4\x17\x00KJq\u00C0\u00C3\u00B6\u0090I\u00C8$\u0088\u009C\u00B9<\x0F\x15m\u00A7\x05\u00EB\x06k\u00C4\u00E9\u00E9q\x10\x10\u009E\\\u00E8\u008C%@\u00B3\u00EF\u00B0\u00BF\u00D2\u00F1\u00B5\"B\"\u0087\x0F\u00DE\u009A\u00C4\u00F5|\u009A\x16\u00C6\u00C7\u008C\x13\u00E1\u00BArURB\u00F5=\u0091\x02\u00BDn\\\u00EB.\"%\u00B3*$\u00A8I\u00EE\u00F3\u00E6\u00C1\x14\x18\u009E^\u00ABFj\u0091T\x1C\u008E\u00D7d_:`\u00B8\u00D0\u0088\u00ED9'A\u00B3\u00B3\u00BE\u00AB\x1Ax\u00AAb\x07*\u00F6 tC\u0085D_\u00AE\u00AF_\u00DA\u00AFn\x1E\u00BC\u00E8\x0F\u009Ad\u00CC0\u00D7\u00AE\x1A\u009CR\u009F\x11\u00D0\x1B\u00BEU,Q\u00BA\u00C86J\u0095-\u00F4\u00FBn\b\u00C9\x7F\u008B\u00DB\u00AD\b\u00A1\x11\u008E\u00EE\u00D1\u008E\u00DF\u00A5\u00CA\u00DC\u008Az\u00ECD\u00E4\u00AD\u00AF\u00C9\u00A7\u00EFj\u00E7\u00AE7\u00E7\u0084D>\x01<\x060\u00AD,\u00FEQ\x12\u00BDvY\u00DA\u00B1a\u00D2\u0096\u00A09\x14$\x17*qF \u00A6G\x02\u008A7\u00A0\u009B\u00D0\u0094\u00C2\\\u00E5\u00D3\u00CBKA\u00C3\u00A1\u009E=\x1F\u00AD\x02i\u00E1\u00C0&\u00A2\x04m\x01\b\u00C2\u00DF(\u00D9e\n\x18\x0El\"b\tQ\u00A7\u00B1\u009BD,\u00CF\u00C3\u00C1\u00DF\u0086\x18\x04\u0080J8\x02\u00E9\u00DA\u0080\u00C6\x1C\n\r\b-\x00\u008Bz\u00B0d#\u008A0\u00B5\u0096t\u00AE\x0E\u0088\u00F17\u00BD9\u00D7P#\u0080\u00FCI\u00C2J\u00C4E+}\u00CC\x00*\u00BA\u00F0\u00EC\u00AAT\u00BBjC(eFP\u00CF\u0082\x1E\x1A\u00EA\u00F5\u00AD\u00AC\u00F5\x1C\u00D7r_\u00D7\u00BA\u00EE\u0091\u0092\u00ECKC'1\u00A2f\x07\u00D0\x10\x15\u00C0\u0094FC!\x06y\u00E0@\u00B4\u00A3\u00DF\u0091\u00CCW\u008C\u00CE\u00C7\b\x1B\x0F\x06r\x1F\f\x06@E\x0F6\u00F8\u00F6\u00C4\u008B\u00FB\x07\u00F6g~?R\u009C\u00F9\x10\u00D2g\u00DF\u00A5q'\x1E1\x0E\u00E35\u008D\u0098\x15\u00DA\u00ECa\u00B4P6^\u00A9\x07\u00FA\u00DEl\x0E\u00B6\x12\u00BD/\u00D7\u0094G\u00A0$\u00B6\u00CEGNtF56w\u00E2\u00A2B$\u00B3\u00AA\u00BET\u00FB\u008E\u00BB\u0099\x03%2%\u0084\u00C1\u00F0\u00C8\x1D7VL\x00\u00B0|\u00C4Q\u0081\u00BA\n\u00A4H\u008FW\t\u0095\u00A8\u00B1\u00D7\u00BC\x05yN\u00F3\x17\u00EBoC^\u00BFVP6-aLlY\u00A99%7%v\u00D8\t\"\x02^\u00C1E\u0092n\x13M\u00E2\u008B&[j\u00EC\x007{\u00C9\u00A7\u00E2i\u0080\u00D55\u0083\u00CC@9\u00FB\u0099\u00DF\x0Fkk\\\u008CA\u00CFI\u00CF\u008C\u00CF]\u009D\u00F2\x1B\x14,\x16\u00B3\x00J\u008D\u00B8\x0B\u00CA\u00AB\u00DD\u00A6\u00E3\u00E7w]\u00D3U\x11\u009D/\u00EB~\u00BDs\x13Z\u00A9\x1C\u0099\n,\u00F4\u00B2\u00BA\u0090\x13\u00B1\u00A0$7\u00D0}Ozi\u00DD\u00CA \u00DA\u00D5\x10\u00A0\u0082\u00B1\u00D2\u0090\u00C3\u00F9ij\x16,\u00CE3\u00CD\u00CBy.v\u00C0/0y\x1B\u00D2{\x13\u00F3\x1CZ\u00C1%\u00FB\u00EB\u0090%KQ\u00C1D\x0ED:\u00AF>\u00F3\u00CC\u00EF\u00EFo\x1Do\u00FB=;\u00EE\x01\u00FB\r\u00BA\u00AD\u00A0b\nDDEL\u00B91\u00AA\x1B\"vAS1\u00928\x0Ba\u00E8\u00D7Fhs0\x7F\x7F;\u00B5\u00DBU\x04\x12\u00A9:\u00DE\u00F5\u00FE\\`\u00F4\x1E\u009Cb\u00F5\x03\u00EA\u00D5\u00FD\x15\x1B\u00B0\x04?\u00F1\u00FCq\u00BE\u009F\x18\u0081\x16\u00B8U<\u00C6a\u00A5\u009D\x1D\x1E\u00B1\t{\u00DA\x11\x14\u00C9\u00B9%\u008C\u00B7\u00BC\u00F7\u0096\f$\u0090\u00C1\u00C6\u00CA\u00C9e#0\u00A2\x101\x14A\u00D6\u00BEE{\u00CD#\u00AD\u00D2\x19\u00F3 \u00A1a\u0090\u00A4\u00EF\u00C2\x07m\u009D\b\u009A\u00BB{,\x11\x0F\u008F\u008B\u00BC\x03a\u00EF\u008A67g\u008F\u00E9\u00B3\u00CE\u00B6\u00A6P\x02\x19\x11\u0089.\u00DC\u00F0Z\u00D8:\u009F\u009F\x07\u0090\u00A9\u00CE\b\u00B9d\u00CE\u0080\u00A1\u00B6\u00D7b\u00BDF\u00DD,\t\u00B0\u0090e\u00D4/\x10\u008A\u00E9\u0088\u00B3\u0090\u0087\u00DB\u00803\tHi\u00C1u\u009FK\u00AD%\u008E\u00B3\u008C\x7FL.b\u0096TT\u00AF:\u00E0\u0098\u00E4\u00A2\x1A[1\r\u00A3@\u008D0\u00E8\u0089\u00A8\nMF\u00C15l\u00A6:V\x13\u00DA\u00C0x\u00D8`\u00D7\u00C94l\x0B{\u0082]8O\u00D71Of\u0088pkn\u0098\u00CB\u0087\u0091\u00D0_\u0091\u0093\u008AC\u00B0#<C\u00B6sLV\u00AB\u00D2\x1CU\\76\x1Fr\\\u00B0\u00BD\u00ED\u00A9\u00DF\u00DB\u00BF\u00FD\u00FE{_x\u00E2\u00F1\x03\u00BC\u009AK\u00B8\u00A9\t\u00A9\x12T\u00F6b\x11\u00FEz\u00EEB\u00BC\x0B$\u00EF\\3\u00A2\x13C\u0098\x17Q\u00BFQ.ATG:!\u0094\u00C5^\x10\x7F/a\u00CF\u00A7w\u00A2\u0082S\u008A\u00F2z\u00AF\u00BE\b\u009B\u00FD\u00A3y7\f\u00C0\r\x06\x1Ec\u00C3\x05Q#cR\u00A5p7\x1C#\u008A\u0099N_4@T\u00ACB\u00F4\u00F3 \x132\u008B\u009A\u0083\u00DAXe\u0082\u00AEPp\x07'\u00C8\u00C3P\u00FDL\u009DX\u00B9\x11\x1E\x15q\u00B4.\u00E5.T\u009D\u008D\u008B\x03\u00BB#\u00AB\u009C\r\u00A1\u0082&\u00E1\u00C1:~\u00E3\u00E0\x1D\u00A8\x1A\x00\u00B41\u00A4\u0084\u008D\u00CF1\u00A3\u00ED\u00EE\u00A50\u00D1dJ9^C\u00DA\\\u00EB\x01\u00F0\u0099M1\u0099.\u00B5\u0081\u008AS\u00F0\u0092\u0092\u00D9\u00BET\u008B\u00A6[\u00DF\u00B2\u00C87\u00E2\u00F0\u00C95\u00C7\u00F5\u00D2\u0082\u009B\u00A2=qI\x14\u00B1hM\u00A5$\u0096Z#\u00E4\u00A1k.\u00C8\u00BD\u00B8Z\u00C0\u00D5\x12\u0086\u00AC\u00BE\x13\u00CE\x07\u00F3\u0092\x17\u0084B$m\r\x1A\u008B\u00C6\x15\u00EDZ\u00BA@\u0085\x12\u00B8\x1E;\u00D7\u00B7\u00AE\u00FE\u008C\u008A\u008F`\u00BF\u009C\u0088\u00C3\u0096\u0084+C\u0097\x1A\u0097ax\u00EA=\u00EF\x7Fa\x7F\x1F\u00BE\u00EDw\u009E\u00F5\u00A7\u00F1*^\u00BFE\u00F4\u00D2qW&\u00A0A\nz\u0090\u00D0,\u00A4\u00A9\u0088\u00D5\x16b\u0093NG)\u00B7\x18+\u00D1\u00E0XIF\u00F3\u00B6_d2\x1B=\u00D8\u0091\u00E7\u00B9m\u00B1\u00E1\x04\x1A\u00AC3k\u00E7\u00B5\u00FB\u008B\u00D1,6\u008B\u00C6\u00D4\x18Gp\u009B9IZ\x03\u008F)\u00D1\x0F>\u00AA\x1BD$\u00FBt\u00EE\u00DDh\u00B1\u00D1\u00AB\u00B6n\u008F}\x12\u00AA\x16B\u00EE\u00A1\u0080\u00F0V\u00C8\u00AD(\u00BB\u00C4\u00C1\u00FB\x1E\u009C7\u0095r\x17\u00969\u00E0\u00DCg!\u0088+\u0089\u0093\u00EFd\n$\u00A2\u00C3\u0081\u00CD\u009D\u0085sbQ\u0087!\u00D1\u00C3\u00AD\u0098UxCj\u008F\u0089\u00F4r\x04c\x18!\u00F4e\u0088\u009ChFJ\u0084\u0097\u0082k>\u00E8\u00AA\x13\u00F0\u0099\x19T;\u00B9\u0096\u00F4|\u00FC\u00B22\u0095\u0084\u00FB\u009D0\u00BB\u00FE\u00AC6$\u00F5\u008F:\x1FjG\u00D7v\u00C8\u00DDaw\u00CFI\u00A0\x14\u009DB&\u0092\u00EC\x1D\u00D2\u008Bi\u00CC\u0090\u00BC\u008DH\u00B3?i_`\x1E\u00C1\f}\u00DDR\u00E7\u00AF\x1C\u00880\u00B0^\x1A\u0093\u00E9\u00CC\u00AE\"K\x16\u00E9\u009EL\u00D2s\u00CD\u008A)\u00AE^\u008C\x1E\u0089\x19\u00F1\x06\u00DER\u00B9\u00CBs\x11\u00DF_\u00BF\u00DE\u00F0\u00CC\u00F7\x7F\u00FF\u00EF\u00EC\u00DF\u00EDW\u009F{\u00CE\u00DE\u00E6\u009F\u00C7\u0097\u00CCN\u0084\x13\u00D2WD\u00B2\u0086\x0E\u00DF\u0085\b\u00ECD@uLL\x03\u00D9~\u00A9\x07\u00C8\u00F3U\u009C\x05\u00BAg\u00DA)Z\u009B>\u0080q\"\u00EEqF\x05\u00EC\u00D7\u00A2\"4{DC'\u00A9\u00E6\u00C0V;Vcj\u00F2\x1A\x18\x19\u00C0\u008D\x1B\u00EE\u0091x\x0F\u00C4\u00CE\u00CF\u00831\b\u00E5k\x0FUBh\u00C1A\u0081\u00E8H\x15&\u0093\u0094,\x18\u008AQ\u00D5: i-\u00E6\u00A6\u00D8t0\x0F\u00A1\fz\u0087;\u00B6\\\u00B3\u0096h =j$z\u00B9\u00E9\u00B2|V\u00EA\u00FE\u00D1\u00A9\u0083\x06\u00C19-#\x7F\x01_\u0099MC\x06b6zml3\u00D1\u0082\u00D6\u00BD\u00DE@\x11\u00F2\u00B4\u008A~<t\\\u00F6\u00AA\u00B8\u0099\u00EA\x07\u00CA;\u00B2\x18\u00FF\u00FC@\x16N\u00C9E\x1D$\x14j\x14\u00B1!\u008B\u008Ff\u00B2\x15]*!\u0090.A \x19\x0E\f(\u00A6\u00A1\u008C\x7FB\x17\"`1\x0B\u00ADI\u00D1\u0084\u00A4/\u00EA\u00B7d.\u0092\u00C6JO\u0096\u00B4\u0096\u00FA$\x15qk\u00E7\"\u00DBKuqr\u00B51\u00932\x12\u00B38o\x1E\"\u00AAB\u009F\u00DBs\u0080W\u00BF\x01\u00AA\x1D\u00A2\u0083x\x00\x0E\u0083\u00BD\u00FDi\u00C7G?\u00FC\u00B9\u00FD\u00D91^\u00FD\u00E1\u0087\u00FF\u00C7\u008B\x06{wZ\u00F6\u0097\u00D0c\u0094\u00C7`\u00D1\u00E1Hp\u009A\u0098v~}?\u00878\u00EB\u00B7\u00DB\u009E\u0085z\u0097[Q\x06\u00BDx\u00F5\u0089\x13\u00DA8\x13w;o\u00B9\x7F\u00DD\u00A7\u00F4q\u00E3\"\x12\u00E3\u00A0\u009DA\u008CF\tF\u00A7>>\x06\u00F0:\f\u00F71p\x10\x05\u0084aMR~\u00B0\u00F4=U\x05\u00E5<\u00B8\u00BC\x04\u008C3\u00B0\u00B6\u009B3\u00DBQ\u00C9\x14\x00\u00A9JH\u00CD\u00D0k\u008A\u00F0\b\u00DF\u0093&$\u00C4\u00B8\x10f IHJ\u008FYR?a<\u0089\u00D1\x0E$c\u00B0Yl0\u00D7\u00BE\u00DA\x012\x11j\u00B0\u00AD\u0083\u00C7\u00D3\u00D8\u00B8TH\x02\u00D6\u008E\u00C7\u00FD\u00E2\u00FE\u00D6\x10\u00EBh\u009FM\x1A\u00EB\x128l\u0091\u00EC\u0082\u00C3\u00DD\u00D0\u00A6k\x1Ba&\u00D4\u0097\x14\u00A7\u0084\x16\x03\u00CA\u00F9\x1Ad\x1E4\u00BEI\u00E7\u0097\u00AD&\u008D\u0094\u0092\u00BE\r\u008E{\u00CB\x02\u009D\u00DDS1\u00B0\u00D8=|o\u00E3\u00D2\u00B5^\u008CA\u00EAH\u00F6U\u00A2\u00A4\x19/S]Rx6\u00DB\u00C8\u0089\u008A\u00F9\u00B1^/a\u00A1Y\u00A9&\u00BE\u00B4mp\u00DC{\u00F2\u0099\x17\u00ED\x03O\u00BD\u00BA\x03\u00C0\u0093\u00FE\u00E03\u00C0\u00FE\u00EE\u00AA/\u00D0uvC\x05\u00F5H\u0097m\u00A5\u00BC\u00A8\x0B\x17\u00F1\u00DC\u00A5\u00A7\x17\u00F2\u00E8\x16\u00F6\u0085\x11\u009C\u0083\u0087\x12\u0081\u0088@\u00CBh\u00B7\u00A8.K;w\u00C3\u00FD*w\x06\u00F4\u00E2\u00A8\u00F34\u00A6\u00BE\u00FDZ\u00E9y\u00C5\u00C5\x1D\x03\x17\x00\u008F\u00A0\u00C8\u00C5\u00B0\u00F4\x1FV\u00D1\u0081\u00B1\u00ED\u00FBP\u00AF\u00A1\u00E0\u00A2d\f\u00A6\u00DD\u0098\u00C2V\x10\x19\u00F3UeIp\u00DD\u00C4,\u00C0\u00D0hg0\u00AB\u00ECZ)\u00B1=\u0093\u0093\u00FA\x1Bn\x18s\u00F2\u00B1S\u00DEM\u00C31\x01;\u0082\u00C0\u00E6\u0094\"C\u00B67\r\u00F3p\u00C0,\u008Dp\u00CA\u008A\x17})U\u009A]\nd\u00E0\u0080O\u00C3\u00D1\u0098\u0081\u00D8\u00B3\u008B1\u00F1\u00FA\u00CE\u00B4\u00C4p\u0096\u00C8CHB\n\x0E\u008B\u00E8\u0081e\u00B3\x15\u00BD\u009B\x1B\u00B1\u00CEkR\x1F\u0088\u00B5\u00EA\u00CA\"\x14\x01v\u00DB\u0085\u00B8\u00A3\u00DA\u0094\rB\u008C\u00CA\u00EB\\\u00AD\u00AB\fzj*L2 \u00ED\u0092\u00E1\u0080_\u00C1z\u00DFx\u009DCi\u00C84\u0084v.\u00DA\u00ED\"BF\x0B\x13\u008C\u00B9P\x1B5\u00EE\x01\u00D8\rJ\u00BDo\x13\u009ELS\u00C7\x15P\x15(\u00E6\u00F1\u00BDg?\x03D\u00E2\x00\u00DE\u00E6\u00EF\u00FC-\u00F3\u00AB\u00FF0Cd\u00E5Ah\u00D23\u009E\\\u00AF\u008B\u00D0\u00B8\u00A8e`k\x12TO:\u00EA1\rk\u00AArSQ\u00EEHG\u00EE\u00E9\u00CA\u00E5%(\u00CF\u0084e;\u009D\u00C1(\u00B0\u00AA\b_\x0E\u00CFz\x15cZ34\u0089\u0088\u00B2=\u00DC\u00D9\u00A7G6p\u00E3\u0083\x06\u00C4 \u00E8\u00C3\u00C2\u0096p\u00806\x04>\u00E6\u0083\f \u00C3M<\u00A2\f\u00E5a\u0098\x1EhA\u00A6&\x19\x1DG_&'\u00EFA\u00B7\u00B9i\u008DM\x12a\u00DA\u00C0P\u00814N\x06Q\u0081F\u0096\u00C1E\u00DD\u00B8\u00A8\bF\u00E7q-\u00D1\f\x04Jf\x13m\x0F\x00\u0098\u0091\u00A9)\u00D4!\x17dn 2{\x1F\u00B40k\u00ED\u00C7\u00EF\u00BC\u00D9\u0082.\u0082p\u0087[X\u00EByN\x19\u00FBZ[\\\u009F\u0095\x1BA7\u00AF+\u009DZ\u00D7!t\u00FA\u0094\u00C6\x17(|+\u0089\u0099\u00CF_\u008CB\x15\u00A1J=\u0096\x0E\u00CF\u0088\u00C5\u00B4A\u0088V\u00C4\u00B1\x15)(D\u00A2\u00F3\u0080>\u00FE\u00A4\u00B5zp\u00CB\u00BA\u008E99\u008A\x1E\u0092\u00A1\u0095\u00D8P\u0088\u00B4\u00FB%b\fR\u00E7\x13s\x0Btc4>z\u00B8|8\u00CE`]7\u00CF\u00DC\u00FB-\u0080\f\u00E1;\u00E7;?\u00BDM\u00C3\u00DC|!\u00DE\u00D0\u00D7\u00B7\u009C\u00A8\u00D5#\u00D0\u00E3\x15z~\u0082-\u00C7o\x13l'4C\u00ED\x04e\u00ED>\u00D4\x053\u00F3\u00B23\u00A0\u0080\u00F6\u0096i\u009Et\u0083\u009E\x19L\x12\u00BB\u00DA\u0094\u00AAQ\u00F6\x02\u00F5S[\x7FW\t\x00\u00E3sk*Q\x1A\u008E\u0080G\x03x\u00DD\f\x0F\u00B0\u00E1\u00C6\"\u009B\u00E0\u00A0\n\u00B1\u00A11\u0083\u0094\u00F0\u00F1\x1EtgM(\u00E2@\u00F9\b\u00F4@44\u0096\u00E6+\x1A\t\x1DXlj\u00D3\u00C1\u00EC\u00C5\u0090\u00FA\u009E\u0092\f\u0099\u0093 \"\u00E8\u0086\u00C79\u00B5f\u00A9v\u00A0\u00CE\u009D\u00EE8<\\\u0089\u0083\u00F4\x19\f\u00C8\x0B\u00B9\u00CC\u0098\u009F\u00C1\u00FEH5\u0091w\u00C2i(I\x06\u00A0\u00CDX\u00D2 \u00BE2\u0084[\x04\u0082bH\u009A{m\u00BE\u00BA0\x00\x11rJz\x11qAi\u00C1{KI\u00DA\u00A5\u00BC>\u00BD\u00AD\u00BDhgIv\u00D2\u009AHBT8qC\x12\x1C\u0084\u0083kR\u0097\u0085o\u00B8\u00EE\u00D5=\x10\u00EA[2\x02\u0096i[<,\x13\u00BEx\x19\u00E2\u0098a\u00D6\u00FCr\f\u00F1\u00DF\u0080\u00C9H\u0099\u00E8b\u00AF\u00FB\u00BAvyo\u00F3\u00CE\u00CE\u008Eg\u009E\u00FD4@\u0086\u00F0\x1F=\u00F9\u0081O\u00FE\u00AF7\u00F7\u00F1x<B\u00AA\f\u00D6\u00D0\u0081\u0095\u00B4\u00CF\u00E4 \u00D4\u00E2\x15\x11e\u00D4aJv$d\x06\u00D6sW\x14\u00C1cM]Y\x19\u00C8\u00D6R\u0095\x01\x1B\u00CD\u009D\u00D8\x19\x14\u0099\u00C2\u00B8u\u00AFB\x1F\u00B3\u00A1\u008E5\u00FD\u00B9Im\x04\u00C20\u00A9\f9\u0083\u0080\u00A2\n\x1F\"\\\u0090W>p\x05\u00C7Aubs\u00C3f#5\u00BF\u00CDA\u00A2G\x04k%\u00BC\u008C{o\\\\\u00B1\u00D6=\x17Q\x12\u009F\u00D7\x1A\u00E9\u00DB\x13\x18\u00D7\u00CC\u00CC\u008E\u00AD#\u008E5\x15\u008B\u00DD\u00A7\u00D5\u00B9\u00B2\x1F\u00E82\u00D2HT\u00D5\u00B2(\u00A3\u00D6\u00DA\x01=\x14\u00C2Y\u00DDk \u00C4\u0090\u00F6\nh\f\u0084\u00C2D\t[g\x02\u00B3}vt\u00A0\u009BNp\x7F\u0087\u00FA\u00BB$\u00BC\x04\u008E\x17gL\u00A8\u008F *T\u00FF\ny\x04AL\u00D6h\u00A814\"\x17SH\u0095A\u00F7\u00E9u\t\u00F5\u00DCnP5 \u00AC\u00EE\u0093\u00D1\u008E\u0086\u00CA5\u00B8\u00E8\u0089\u00C4\u00BA2\u00E1ON\u0082_h{\u00D8\u00D6{\u00E78v\u00DC\u00AE\u00C4\x14\u00A82B\u00FDew\u00F0\u00D6\u00FFH\u00EFN\x1BC\u00DA=\x06\x1C\x17\u00A2\u00BET\u00EApc\x13\u00C7\u00F7}\u00D7'\u00F13d\b\u00EF\u00C03\u00BF\u00F9\x1D7\u00EF\u00C7\u00A7\u00F7\u00CFB\u00D5{\u0093\u008B%\u0091v\u0088]Du\u0097\u008D\u00C1s9\u00E9\u00A6g\u00DD\u00BE\u00C1\u00F0;U\u0084~^cP9\u00D9\u00FD\u00DC\u00BB\u00FAV\x13\u00A7\x18\nW\u00FB\x19\u00B8THe\n-,}U\u00BB^\x7F\x1B\u00B2\u00FD\u0087\u00EEx\b\u00E0\n\x03;\x0E\u00EE\u0095\x1D}r\u00A7aQ\x01Q\nU\u00E6xB\u00C8\t\t\u00B0\u0090\x06,\u00F4u\u00EA\u00AD}\u00CD\u00A7\u008B\u00AF3\x07.\x11\u00E5\x16\x00@\u00ED\x1C\u0084\"\u00E2N\u00C0\\d\u00B9\u00A1\u00C8A\u00A6\u00E1\u0096R\x1E\u00B3?\u00E1\u00F8\u00ED\u00F0\x16SI/D!\u0094b\n\u00EE\u00CE$)2X\x17r\b\u0082It\u00D0R\u00A9\u00B9&qr\u00F1\u00D4wO<\u00A2A\"P\u0080H\u008CD\r\u00CD\u009D\u0092x\u00BC\u008E#\u00D0\u0082y\u0095\u008AO\u00FBDN^\u00BBq\x164\u00E5D'z\u0090\u00E4\u00BEB&\x1E\u00A5\u00B7@\u00C8\x04\u00C8\u00BC\x01\u00F6\u0081\u00D6\u009F\u00EAS\u0089wT\u00AC\u00CBD\x16\u0083\u0091\n\u0092O\u0099\u00E8\x07\u00ED\u009D\u00AE\u00C3\"\u00EC\\\u00AB\u00F2\u00CA\u00A4-\u0084c\u00B4`p\u0096\u00DE\u0094(U\u00E7\u00CF<\u00C0\u00DB~\u00EC\u0087~\x13\u00FF\x0B\x19\u00C2\x07l\u00FB\u00C2\x7F\u00FC\u00E8\u00FF~\u00F1S6\u009E+X]\u009DM}\u00C5\u0094\x11)\"!|o\u00D2\u00F6\u0096\u008D`a0\u0094\u00E4Y\x0Fad\u00DBqE\u00E9\u00F6\u00D9\x0F2\u009CE\u00A7\u00CF\u00F6\u00D7D\u00A7\u00F2 \u00D4\u00C3Mf\u00C0v\u00D4\u00CF\u00C8\x06\u0095\u00E5c\x14?pF?vx\u00DA\u00CA\u0094\u00C9xz\u00B1@\t\u00F7a\u00B8\u00F1\u0081\u00C7\x00\u0086\u00E7\u00EE\u008F\u00D0>\f7\u00ECS\u0094 \u00A7\u00A11\x13\u0093b'\u00E92>\x16\u00D1\x06\u00D4\u00F7\u00ACc\u00A0\u009D\u00A2S\u00A0\u00BA\u00B6fkk\u0086k\u00ED\u00A0\u009A\u00A1\u009A\u009AZ\u0087\u00C9<x\u00A2)\u00EBq\u00D6\u00B9\u00AA$\x16\u00EAD\u00F4I\u00B1\f\u008Ep[N\u008B\u00DF\u008F\u0089B\r\u00B3\b>J\u00A2\u00F3)\u0092QT@\u00945B\u00D5X\u00AD\u00EC\x16\u00CB\x1E\u008B\x1C\u00BB\u00C60\u0089\x14\u00B8\u00D3r\u008D\u00CF\u00B1\x06(\u00A1\x11\x0F\u009Fa&Tu\x1D\u00BC\u00C1x\x07!\u00B7\u0084@\u00B4\u00A5\f\u00CE\u00B0E\u00CCbP\u00BAG\u0086]k\u00ECr\u00FF\u0089\u00F0\u00F7\u00EC\u0097\u00A5\u008A \u0098$\u009Ap0\u00C2\x04f\x17z\x0ED\u00C4m]\u00B4\u00B5\u00BE\u00BC\x1C\u00A8\u00BD34~]\u00A1\u00B9\u00E6\u00DF\u0099y{pz\x1C\u0097\u00E7\u00DE\u00F6\u00A2\u00FD\u00E0\u00BB\u00BE\x00\u0090!\x00\u00C0[\u008E\u00A7\u00FE\x19\x1C?\x12\u00C4z\u00CA(4\u0085\u00C6D\x03=y\u00C8F\u00C5\x1AT\x1E\x02\u0089\u00D3\u00B5\u00F8)m[\u00F4\u00DF\u0082\f\u00B2\u00EB\u00ABZb\u0094\u00EAk\u0080\u00D4\u008AHFg K{\n'\u00E6\u00FDN\x1E\u0084\u00F4\"\u00901 \u00ED\x12\u00A3\x18x2.\u00E4\u00B8\u0090\u00E3\x1A\u00F8\x06\u0080\u00FB\x00\u00AEh\x14\u00BC\u00B1\u0081\u00DD\u00A3'\x1B\u0098\u0080jat\u009BpL\u0097\x0B\u0091nJ\u00B3&\x17\u00BCT\u0082\u00C6\x1AK\u00A8\u009C\u00A2\x12i\u00F0R4\u00A1\u00898I\u00DC\u0092\u00E0\u00A0\x17B%\u00D3\u00CAR\x191\x07\x00Je\u00F0\u0088n\u00DCH\u009C\\^\u008C\u00A0\u0094j\u00C0\u00CC\u00BC\u0086\n\u00DC\u00A3\x0E\u00E3\u0080\u00A5\u00BD\u00C3y\u00FF\u00890\u00A4f\u00F1V\u008E.\x11\u0081\u0090\u00BF\x16\u00F6lK\u00B8\u008D\u00DB\u008EB6\u00EB\u00F3n\u0093\u0084\x01\u00CCk6\u00AA\u0098\u00FE\u0093\u00C4o\u00E1\u00C6\u00D1\u0087\u00EE~l\x1DZB\u00A6;\u00A1q\u0080\"\u00F4d}\u008A \x04\u00D2;\u00A1~\u00E4}*V\u00A0{A\u00D2\x16\u0092\f\u00E2R\u00E3\u00C9\u00F6\u00B6\u00B5_\u00E9\u00A2T\x1F\u00B5P\u0080\u00F2\u00B2\u0088\u00F9\u0089\u00C9\t5\x1C\x00\u00A3\u0080\u00DD.\x18O\u00DC\u00FFg\u00EAI2\u0084\x0F\u00D8;\u00FF)\u00FC\u0089\x1F\x01<c\u00D9\x0B\u008A+0\u00E4\u0094\u00C7\u0090l\u00A2\u0095\x05\u00E3#\u00AF2\u00EE+\u00AC\u00EF\u00B5\x13\u00E2o\x11,\u00EF\u00D7\u008A\u00B7\u00BA\u00D4\u0084\x16\u0099\u00B8\"\u0083\u00B3\u008A\u00A0>K%9\u00AB<\u00FA\u00DD\x1B\u0093i\u00EA\x0BP\x0BU\u00FD[\x18M\u00FD-\x17\u00E4+nx\x02\x03\u008F\x11Pzo\u00E1]Y\x18\u0085\x06V\u00A5\u00D2z\"\u00AA\u00E8S_\u00F8\x11X\x14=\u008B-\x07-U\x00\u00BD\u00E1h\x01C\x1EL\u0081D\u00E73\u00A4\u00F9\u0090\u008B\u0091\u00D27\u00A4\u00BFC1\f\u00EE\u0091\u008F\u0091u\x10BH\u00E1\u00D2\u00DA\u008F$\u00A7r\u0083&AK\u00BD`;)\u00AD\u00D5gt4\u0092\u0082\b\u00B2\u00DAo\u00F4r\u00A0\u00D3\x04\u00C7R\u008C\u00C2\u0093a \u00CB\u00C3[ \u0088Ts\u00880s\u009F\u00C3T\u00A4\u00C2\u009B\u00C0D\u00A5\u00A8\u009F\u00D0`F\x0E\x18$\x1C\u00D9\x03\x1C\x151Y\u00CC\u00DF\\\u009CZD\u0086F\u00D4'\x06\u0092\u00D1gg\u00E6\u00B3J\u00EA\\\x07\u00A9z\u00B4k2UY\u0084\u00DF\u00DAj\u00C6\u00C1X\u00BB=\u00A1K\x13&f\b\x1E\x13\ni\u00B4\u0091\u008Cd\u00E0\u0095\u00E7\u009E\u00FE\u00A7z\f\u00C9\x10\u00BE\u00FF\u00DE\u009F\u00FE\u00B5'\x1F\u00FD\"^\u00BB\u00F7\b\u009D\u00D0\u008B\u00FB!\u00A1\u00AE\u00A1\x13h/\x03\x06,\u00C4z\u00B2-\u00DC\u0082<\u008A\u00CE\x13\u008C\x17\u00B19\u0090Qg@#jMB\x1C\x1B\u00D96\u00D6\u00B6=\u0098\u0089'\u00A7\u0096\u00CD\u00A0\u00A3\x07\u00DDCVlF\u00EE\x199\u00BE\n\u00BB\u00C8\u00D8\u00E3\x00\u0088\u0086BPD\u00DF\x1E\u009A\u00E1U\u0084\u00FD\u00E0\u00CA\u00C3\u00D2;\x19y\u00B8A5\f*\bIy\n\u0086\u00B2\u00E6\u00870\u00A1\u00B4\u00D6:\u00A5\u00D5_H \u0098BA|\x07R\u00FF\u0097\u00B4\x06%\u00BB\"\x01\u00A3b\u0092W`\u00D2$\x13\u00A0%}6bN\u0095\u00D9\u00CB\u00FD\x18\u00A8\t\x19\u00B5(\x04\u00A2\u00E5\bGl\u00DE\n\u00D1d\x10\u00BAT\x1B\u00A5_\u0097A3\u00C6\u009Cq\x13<oU9$\u00E5x\u00FF\\\u00D8m|I\u00CC\ri\u00E4w1\u00D9 \"\u00CFMV\u009A\u00AA\u00C0|n[,\u00FB\u00FA\fx\u00E53\x18\u00CE\u00B2n\u0092Q\u00E8S\tR\u00B3\u00FA\u0098\u009D\x173\u0090Z\\\u00CF\u00AA\u00CC\u00B6\u00DAcq\u00B2?RA\u00F4\u00BB\u00D6\u00FC\u00C15\u00D1\u00A2\f5\u00D6Y\x04n\x19\u00D6\u00CC\u00F5\u0094\u00EA\u0088\u00A1\u00B2(c\u00E2\u009D\u008B\u00EDf8\u009E|\u00FE\u00BD\u00BF\u00C6\x1B\x16C\u00F8\u0088\u00BD\u00E5W?8\u00DE\u00E5\u009F\u00B0\x17\u00AC\u0092\u0099Dl\u00E5ehql(\u0086p&v]\u00BB\u00DA \x12\u00B2K:\u00B7\u00DA\u008C\u008B\u00AB\u0092ha\u00CD|l\f\u00C1\u00CE\x06\u00C4Vl\u00846\n[\u0098\u0080P\f h'\u00A4\x11\u0084\u00C0\u0087\x10YWXb\"\f\u00EDo2\u00C5\u00B6H.\x18\u00F8:\u0080k\f\\Y\x18\x19eS\u00B1Y\u00FDH\u0094\u00C5o\x1B\u0098\u00AA\u00EC\x01\u00B5'J\u00D8\f1E\u00E5\x19\u00A0\b\u00B6\u00DB\u00A4\u0084(\f\u0096\u00B1\b\u0092\u00DC\x17\x0Fc\u00DE\u00F0b\x0E! \u009D\u00E7[&*eD#\u0085\u00E0D\u0085%\x1B\x17\u00F0>\u0085\x1A,\u009F<fx\x10\u0084\x00D\u00EC\\kdZ\u00B9\x06oy(R5\u00E9j\u00B5\u00D4\u00FE4@\u00B6\u00DF\u0092\u00F0\u00C5\u00E8\u00FB\u00B1\u00F8\u00D4\u00E6\u00A9\u00C6uQ\u00D2\x7FV\u00870\x11!\u00CB\x1B-\u00FC\u0095_\x00\u00BF\u00C6\u00A2.t}&p[\u00DD;\x13\u009B\u00B8\u00862h\u00A8\u00A1\x01G\u00B5\u00DF\u0098VV\u008D\u00EA\x12\u00BC\u0095\u0083\u00CB\x01\u00A7\u0084\u00E0\x18\u00D6\u0081c\u008D\u009C\u00A4\x1A\x012\u00AB\u00DEN\u00D2a\u00ACu#3{\u00FC\u00F6k\x7F\u00FA\u00C7\u00FF\u00F2\u00AF\u00E2\x7F\u008FV\u0093!\u00BC\u00CF\u00ECK?\u00F6\u00FA\u00DF\u00FF\u00CC'\u00F0\u00E2\x07ej+H\u00CD`\u009F;\tL\u00D3\u00AF:\t\"^\u00C5y\u00EBw\u00BEO^\u008B\u0085q\u00E4\u00EF\u00FD}\x0E\u008B\u0096\u00DD\x00K\x1BI\u00E4\u00B9+\u00D2\u00AA\u009A\u0094\u00FF\u00B5\u00A3\x15qo\u00E3\u00FFb\x16=\x1A\u0092D\u00BC\u00A8Qu\u00DF\t\u00C3C\x07^\u00B7\r\u00D7pl\x1E\u00EE\u00A0a\u00A1>l\x1E6\x02\u00DE\"\u00D9B\x1A-\u00D3&\u0090\b\u00B7\u00D4\u0082F\u00E4\"0\u00C5\x14\u00A4t\u00A7Tw\x0F\x02\u00DFR\x1A\u00C7=\u00BA\u00EBR\u00A877Q:\x1AA\u00F2S\u00C6\u00BF\u00AA\x1F\u00C4>\u008B05\x16\u00E7\u00BD:\"H)Y}\u00D6\u00BDG\u00BB\x0F\u009C*\u00D5\\\u008F\u00F5\u009D\u0098\u00D3\u008E#U\u00A71\u00C1\u00BE\u00A3t\x11\x14\u008A\u00B97\u00D5\u00C3\x16b\x01\u0096*\u00C4\u00A9\x16\u00E8\x1C\u00BD%}\x19\u00AE\u009F\u00FAz\u00D3\u00FDS\u009A\u00AB\u00B8js\u00EF\u00E1\u0092\u00F7\u00EF\x10]\u00F5!Cu\u00DB`\u00CE\u00CDl\u00BB\x0BuQA4\x0E\x11\u00B9\u0090\u0081~\x13\u00C7\u00F4\u00EC\u00C3:6\u00A9\x1B\u00CD~!\u00E6#Zy\u00FB[?c\x1Fy\u00CB\u0097D)\u00C9\x10\x00\u00E0\u00BD\u00FE\u00F6_2\u00B7\x0F\u008Ah3\u008A\u00CFD\b@\u00A1\x07 \u00FD\u00F4\u00A0\u00CF\u0099\x06\u00C9\u0099\u0093`yM\u00955\x0B\u00FEtk\x0F\u0087[\x05L\u008A \u0097\u00D0\u00E3\x055D{\u00B3\u009D\u0087\u0093j\u00D0c\x0B:\u00C3pI\u0096\u00AC\x16\u00B2\u0096+\x13*\u00E8\u00C6\u00C7\u008A\u0094\u00F4\u00E5\u00FE\u0087\x01\u00AF\u00B8\u00E3\u00DA\f\u00BB\x19v\x0F\u00C6p\x03$:\x00h\u00AD\u00EFk\x7Fz\u00956s\x12\u00E2\u008C~\u00DA\x01\u00AA\f\"\x18K\u00AF\u00C2\u00A1.O\u00A1\u00DFF\u00B0d\x12\x10s\u00F0 \u00A8\u0083D\u0099\x16}\u009E\u00DFK\u00B0\u00FBD\u00C4\x00\u00E8\u00E5\u00C0\u00B0\u00C6\u0090<l\u00E1rM\u00CEYL+\u0097dcf@yBrW\u00B1\u00C6\u00D8\u00D4\u00D7286\x15A\u00CC\"\u009F\x1BNo1\u00D4\u00FA-\u00E3+d\u00A3\u00E8\u00E7\u0083*\u00E1\u00E4:q\x12:\u00AA\u00AD\u008A\u00AF\u00D9B\u0082J\u00D7q\x0B5,\u0083\u00DB\u00C44\u00E4.\u00D4+\b\u00CE'\u00C5\x0F\u00ED\x01\f\\\u0084\b\u00D1\u00DD\u0083V\u00BC%9e\x0E\u00C3\u0085\u00B5\x15\u00A4\u00E7\u008Bx\u00D5H#\u00F4\u009C\u00F5\u0081^\u00DD\u00A9\x06m\u00C5h$\u009CY/\u00CF\u00E0p\x03.\u00CF=\u00F5K\u00F8T=\u00F2\u0085!|\u009F}\u00C7/\u00DE\u00BB\u00F9\u0095\u00FF\u00E1\u00D1\u0095\u00A3\u0082\u0091\u00F8@nA\u00EE\u0095p\u009D\u00C5V\u00ED\u00D6o\u00ED\u00A1v\u0082KH\u00DE8_2\u009E&\u0099\u00FB\u00F9\u00F9\u00E2\u00C4*A\u00A5\u00D5:\x18\u00D4!\u00D7\u00ED\u00CD8\u00C1\u0089\"z\u00BF\u00C6r}\u00D9(\u00AA\u00DF\u00B1\u009EVd\u0091\u00F5\x13\u00B9j_7\u00E05\x18\u00EEa`\u0087\u00A5;\x11*)\u0097\u00FD\x103X\u00AD\u00EF!\u00C1\u00ED\x14{\x10v\u0084\u0084\u00E3(\u00E8\u009F\u00926\x19\u0081S\u0087\u00AFLHy\x16\x04\u00E5\u00C3\u00F2\u00EFI0\u009D(\u0085\x0Ez^\u00C1\u0092F\r \u00CAzX\u00D9\u00BF\u00BCl\u00EC\u00AA\u00C3\x004\x1E\u008B\"\u00F8\u00AE\u00CEt\u00C4\u00D0]\u00E4\u0098\x1E\u00DC\u00B5\u00F5\u00A1\u008F\u00D5[\x1B\u0089\x0E\u00A4\u0097\u00CF:&\x06\\\u008C\u0083\t{\x0E\u00944\u00E6\x059wh\u0086G\u00E7\u00F9#\u00CE\u00CB\u00F5\u00A2\u008E4\"\u00CE\u00AD\u00E8[\u00DB\u008B\u00D1\u008E\x1C\u00D0\x07\u00A0\u008D\\\x12>\x01\x05\u00BB\u00B8\u00BEr\\\u00FCM\u00ED\u00E6\u00EF\u00CD\u00CE\u0090\u00A8\u00C2\u00EA\x1Egd\u00A1\u00F3'\u00DA\u0084\u0087 \x7F\u00BC\x01\u00F6\u00FEw\u00FFb#\u0086\u0095!|\u00F4\u00FE\u00BB~\u00E1}\u00AF\u00BF\x03\u00BF}\u00F5\u00E5vt\u0095\u00C6\u009E\u00C7:\u00DC\x1F\u00C8-\u00C7\u00FC\u00FC;\u00FFn\u00C4\x1F>\u00DD \u00CC,n\u0092\u00CC\u00E0\u008D\x18\u00C2\u008A\x1ATqF\u00D7\u00A4m\u00C3\n\u00AA\u00A9\n\u00D1r\u00AD\u00B7<\u0085\u00B4\x17l\u00B7\u00EE\x1B\u0083\x1D\u008D\x19l\u00CB\u00FD\u00FB\u00F6\u00EF\u00B1L\f\u00DFp\u00E0\x1Evlp\u00EC\x18\u00D8<\u0082\u008D.\x1E\u00E5\u00CC\u00A4\x16\x04\u0093\u008E\u00E4\"-M\u00D5\x18H\x15\x01Z\u00ABA\u00D4\x07\x17\u00D0\u00D1\u00A4\u00A2\b9\u00D2\u009A\u00D9e\x14\u0081\u00E4\u008ELd\x00Z\u0087C\u00D2\u00B7\u00A1\n\x1D\u00D3\u0086\u00AFB$\u00C3%\u0093\u00C2\u00FBdIG\u00B1\u008F\u00C2\u0086-=\x1E:\u00B7\u00EE\u00CB>u\u00D5g\x16\"\u00A8\u00BC\b>=\x07\"\u008B\u008F\ts\x0B\u00E1\u00AB\u00CF'\u00A2!1;\x12_\u00AC\u00AF<O\u00CFT\u008C\u00E1\u00A8\u00CF)\u0089?Q;:\u00B5\u00CE\u00FB\u00A5\u00D94\u00F8w\x12\u00B4\u0088\x10+\u00A3\x01\u00EF\u0099\u00F0\u009D\u008Cb\nRq2\u00BA1S\b\u00C3\u00B5\x07EW\x11f1\"\u00B9%\u0093qh\u00CD{1\u00B1T\u00EB/\u00A8jQU\x0F\u00C1|\u00E0\u00F2\u00F4\u008Eg\u00FF\u00AB\x1F\u00FC\x05\u00FC\u009F5]\x0BC\u00F8n\u00BB~\u00E1G\x1E\u00FE\u00F4g\u00FE%^~\u00BE\u00D7\x02\u00C0\u00F2\x1Dy\u00B3\u0090\u00EA\u008D\u0098\u0092\u00936I\u00DC\u00F6?\u00E8j\u0082\u00BC\x0B\u0095r\u00DC\x19\f\u009A\u009A\u00D0\u00B9\u00F3\u0086\u00D9\u00C3\u009D\u00A5\u00A64\u0086\u00E1\u00E9\u00D6T@\u0087\f\u008E\u00C0Rm)\u0099A\u00BF?\u00ED \u00C9TP\b\u00A6\u00A3\u0083\u00A5_\x15\u00DCt\u00B1\u0081\u00AF\u00C33\x16\u00C1\f,f\u00CA\x18\x03\x00GZ\u00DA-\u0098\x02\u00AF\u00EF\u00D0^~\u00FF1[P\x10\u00AF\x1F\u00D3\x1B\u0081\u0095\x0BQQ\u0081Z\u0093\u00DDX\x18\u00C2\u00C4)\u0095-\u00897\u0089qz\"\u008F\u00E1\u0096\u00CCE\u00C2-\x10\u0080\u0095\u0080rP\u00ED\x1A\u008B\u00E4\u00EFu\x1A\u00A09\u00C8\u00BFcN7\u00ADc/\u0084\u00B2%\u00BA\u0089\u00B5\u00B5\u00A0\u0080\u00E6r\u00ACc\u00D5\u00DEtz\x04H\u00A0v\u00CB\x18\x19\u0081tB+\u0091\u00F4E\u00CC\u00E6\u00DA\u00EBA\u0091\u0084\u0094\u00BC\u00F3dO\u00A0\u00A2\u0097\tV\x18pS\u00A1\x19I`\u00C6Kx\u00EC\u00BF\u00AD\n\u00CA\u009AD\u00F3\u0083\u0091\u0092\u00E5F\u008C\u00BC\u00A8\u009Dm4\u00FA\u00E9\u008C\x105VU\u00D3\u008Cc\u00EA\x1F\u00DAuz\u00F8\u00FA\u00EE\u0088\u00BD-\u0095\u00D8\u00C4\u00B9\u0085a\u009A\x03\u00DF\u00FE\u00F6\u00CF\u00D8\u008F\u00BC\u00FF\x05\u00B4\u00D7\u00C2\x10\x00\u00E0{\u00E6;~\u00F6gl</\u00E2\u008C.u\u00B8>8\u0091\u009D\x10[\u00A7:\u00F1t(\u009E\u00E9\u00CD\u00BD\u00AD\u008E\"\u00B6\u00D3\u00F1\u00DB\u00EF\u00DB\u009B\u00C4\u00C4\u00F7\u00E1\u0083s\u00C6\u00A4%\u00FD\u00A6\u00CC/-\u00A6\x1E1\u0099{ET?\u00D3^\u0080vn2\u0081\r\u0089.\u00AC\u0098\u00CD:f\u00C7C\x18\u00BEj\x03\u00FB\x04\u00EE9\x0B\u00A5R\u008A\u00B1FN\x12\u00F4\u00A4J\u00A0\u00C8>\u00C0\u00D2((&!\u00D5\u00C2\u0088\u00DD\u008F\x19.\u00D2M\u00FA\u00BF\x0Bq`\u00B1!dfb'tIq\u00AE\u00A5(\u0092\u00EA\u00CC\x1Dp8\u0089o\u00CCbJ{5Q\x11\u008D a\u00B1\u008Fi\u008Bh\u00FD\u00D7\u00A2\u00B6\u00F6]R\u00DE\t\u00D5\x1Dq\u009F\u0091q5\u0085|\u00DC\x11a\u00FAn,\x04\u0082$t\u009F\u0091\u00BE\u00ED\u00D2\u00ED3\x14:\u00A4q\x06Z\u00E5\u00B9\u0096L\u00B1\u0092\u00AB(\u00A4\u00BA\u0084NC]\t\u0084 rI\u00EA\u00BBb\x06&m#\u009D@\u00DBy\u00B7tz\u00D4y\u00ECo\u00ACC]\u0083Z\u00AF\u00F9\u00BD\u00DFW*\u00C2D\u00EE\u00A9\u00B0\u00C4\"\u00F4s\r\u00E6\x17T\u00887\u00F2\x1E\u00E6\x1B\u008Eo{\u00FAgqz\u008D\u00F3\u0081\u00FF\u00E0\u00FEw\u00FD\u00C3g\x1F\u00DD\u00C7\u0098w\u00D4QDd&\u009A]a\u00D8\x0E\x187iI\u00FD\u00BC%7\u0099\u00D0C\u0087\u00E3\"\u00C8\u00E4\x1B\u00B5A\x00\x00 \x00IDAT*\u00E6\x1A.\u00C2\u00BD\u00A1\x07\x15\r\u00EBz}\u00B4\u00E3\u00CB\u00DF\u00CDGl\x1CJ\u00BAU\u00E4\u00FA\u00B3\u00E6\u00A9!\u00E13|9\u008C\u00A0[\"\x1C\u00A91\u00E9eX\u00DE,^\u00D2B\u009F\u0085z\u00AA\u00F2\u0093\u008C\u0097\x03\u00AF{\x04*\x1Dn\u00B8x\u00F8\u00FB\u00C3F\u00E0\u008B'\u00A1\u00DE\u008E\u00C9E\u00ADu\")/f//\u00C1\u009C\u0091[p\u00F0\u009A9\u0083\u0090\u00E7\u00E9\u00AD\u00B5\u00AAuz\u00F0^B\u00C8\x19\u00DF3\x19\u00EF0\u0091\x04\x16*J]w\u00CC~\u008DS\u00F5\u0089\u00C0\u00A7c:n\u00DC\u0097\x02IP\x7FI\x0E\x1D!\u00EB\u00DA\u00E9V(\u00A9\u00CD\u0083\u00CF@I*\u00FD\u00BE@b\u00D7*%\u0093\u00F6\n\u009A\x1A@\u0096\u0083[\u00DD\u009B\u0085\x18neZfM\u00C7\r\u00F0\x1D\u0098;07\u00F8l\u00C7\u00F2S\x1B!\u00C7\u00DF\u00A6\u00E8A\u00EC\u00A8\u00AD\u00D8u\u00DEu\u009E\x17\u00EE\u00CD\u00ABjK\x05\u0088\u00F4\u00EE\u00D7\u00EB\x1A\u00EDk\u00BA\u00D0^[\u00FB\u00DA}\u00BA\u00F5\u00CF\u00FB\u00F5\u00DE\u00CE\u00CF\u00FB_5Z\x1C\u00B8l\x03\u00E3\u00FB\u00DF\u00F7\x0F\u00CF\u00F4\x7F\x0B!\u00FCy{\u00F6\u00E7\u00FE]\u00BC\u00E7\u00D1\u00AF\u00DA\u00EF\u00DE\u00EB\u00B0\u00B8\u0088zG\x16\u00FAL\x02\x04\x1F\u008B\u00A1P\u0085\u00A44P\u00D27\u00AEY\u00C2\u0099\u00F9\u00C0+\u00D9H\u009F\u00ED\u00E1;\u00D1\u00C1\u00B2\x07\u00A4\u00E5\u00B92\x1C\u00F6\u00C0\u00A9RE\u00D8Fk?@b\x0F\u00A1V7\u00FA\u00C4\x03\u00C5\\\u00BA\u00BA\u00A4>j\f\u008E\u00B6$\u0089\x13\x1C\x17g\x1E\u0085#\u00BD\tA\u00DC\u00D4yi\x1B\u0088JJ\n>\u00F2B\x04\u0094\u0094\u0087\x16\u00F8\u008C\b?\u009Fq\u0097\u00CE$J\u00AA:#\x15u\x1FKB\u00CC\x1C\x05\u009D?\u00CB \u00A9\u00DC\x07\u0097D%\x172X\u00AA1Q\u0088K^\x10!\x02\u00B2E\u00A7\u00AD]\u0089Y3f\x19^\x06\u00C5\u0090\u00B4\u00D1\t\u00C1\u00F7\u00C12\u00EE\u00E9\x01\u00A14W\u0094\u00A3\x10s\"\u009DE\x05\u00AA\u00EFe\u0098;3\u008E\u00F6J&P\u00DF\u00EB\u0089\u00EA:\u00A3T\u00D5X\x1CY*=\u00DB\x10\u00F4\u00E8\u00A1\u00C4\n\x18\x02T\u00D2,\u008D\u0094\u00D3\u00E1\u00C9@\u0094:}\u00E4*\u00F2D%\u00CCjT?:\u00A2\u0080\u00A4\u00BF\x10E\x1B\u00A3;j\u00C3]\u00F6\x07\u00EC'\u008D\u008F\u00F2x\u0098\u00E0\u009A9n\u00DE\u00FB\u00F4\u00A3\u00A7\u00FE\u00BB\u00BF\u00F0s\u00F8\u0089u\u009An1\u0084\u00E7l\x7F\u00E5\u00BF|\u00F8s\u00BF\u00E86~\u00B8\u008E\x0E\u009E*C\u00C5\u00A8\x0E\u00A5\u00CAP\u00C5RB\u00B7\u00E7r\u00D1\u0080\u0086\u008C~\u00DDx\u00A7c\x01\u00BFWVP\u00F5\x0B\u009C\u00C3Z`?V\x02\u00F7$\u00EE\x16\u00FF\u0090\u00C8@j\u0081\u00AE\u00DB\u00E0]e\u00C8qX\u009B\x12\u00E3\u00BF\u00C5\u00F4\x16F\u00D7\x18agnN\x11yxHP\x11\u00BD\x13*O!\x06\x1A\x15\u00A7\x1E\x19\u00AF\u00D5nJ\u00C3\x11\u00D5\u0090\u00C8\x14@\u00B5A\x19\u0086\u008E\u00D2\u00FB\x15\u008E\u009C;(u&\u00D1\u0088\u00DD\u0080\u00DC\u00D9I\x11\u008D8\u00BC\u00DC\u009E-\u00F3\u00D1!b\u00B6\x1Ce\u00E4^h\u00B1\u00B3\r\x0B\u00C2\x0E\x1E\x12\u008B0\u0092\u0098|\u0089\u00A3\b&`\x00fTnj\u00AE\u00C1n\u00EB0\u00F6\u00CFip\u00D5\x18\x143\u00A1\x07\x11\f\u00816\u008B\u0084\u00DE\u00FC\u009C\u00F5\u00B7\u00C3\x19\u00E2\u008Cb*qi2\u009Ae\u00D3\u0099\u00CC\u00C2\u00CD\u0089F\u0096Ws\u008B\u008E\u00E4\u00C6\u00AB\u0084\u00ED\u00D3\u00B1Z\u00F8y.K\u00B1+\u00DE!\u0093\u00962\u00B0J6\x01^\u00DF\u00C2\u00AD\u0093\u00DB\u00FB\x01\u00CCKk\u00B7\u00F5#!%\x10\x1B\bKl\u00B5\u00E8Z8*J\u00F1\n\u00C0\u0084\u009B\u00E3x\u00F7\x13\u00BFh\u00FF\u009E\u00BD\u0082\u00D3\u00EB\x16C\x00\u0080\u00BF\u00E0\u00DF\u00F5\x0F>v\u00F9\u00FF~\u00F8f\u00D7\u00CD\u00A5\u00FF\x17\u00A1\u00E8e\u00F2\u00DF\u00B3F`\x11\x19\u0089\u00C4\u0080\u008A18C 0\u0083\u00F2.\u00BB\x02=\t\u008B~\u00DF\u0091\n+\x17\u00C7\u00D2E7\x1E\u00CAJ^5\x1D\u00A4N\u00C4\u00C4\u00DD2&.\u0086I\u00B4\u00F6;\u00E3\x11S\x18\u00CB1Y\u00B6\u0095\u00C9\u00E7,2\u00F8h\x1An\x1ATO\u00D8\u00EC\u0096\x06F\x00\u00CCh\f5@\u0086\u00C0L0\x02\x16\x03\u009A\b\u00C4dg\u0090\u00AE\u00CF\u00B5\u0093\u00C4\u00E4\x15\u00B9\u00E8It\u008C\x18\u0099u\x1D\u00846\u00DA\u00B1@3\u00B1\u00FD\u00DB!\x15\u008A\u008B.\ri\u009C\u0081m\x063\u00BF\u00C0\u00B1\x13\u008EOg\u00D19\x0Fu \u00D9*\u0089=\x19\x1B<m#\x19k!\u0083\u00AB\u008C\u00A5\x1A/\u00AF\u00DD\u009A\u008D!dh\x10\u00BD\u00F2lJ\x1D\u0090@\u00A0\u00B4\u00EFK+\x16\r2\u00D8\t\u0086\u00B21\x00UC\u0080s?\u0085\x16J\u00F0E\x04\u00E9\x01LUS\u008E\u0095\u0091\bb\x1A,a}/\u0088\u0082\u00C6H\u00FA\u00C2h5\rP\u00CD\u0095\u00ADA\u008B\u00FA\u00A8\u00BE$s\u00D2\u00B9^c'zq\x16e\r\u00C0\x11\u0093f>p\x19\u00C0\u00CDw\u00BE\u00FD\x1F\u00E0\u009F\u00E0\u00D6\u00EB\u0096\r\x01\x00\u00FE\u0093\u00FB\u00EF\u00FD\u00D8\x07\u00E6;|\u00CCU\u008F\u00AE\b>\x12ZfE\u008E;\u00DE\"\u00C0~\u00AB\u00B3M\u00A1\b\u00AA\u00AE\u00DB\x11[\u00D2K\u00AF:\x13&JE\u00A0\u00B1/\f~\u00E2\u00EC\u00D4\u00B1ny\x10\x06\u00A2tv\u00BB\u00FF9\u00FE u\u00C4\u00DB\u009E\u0084*\u00F4\u00D2\u00DA\u00CB{\u00C6;\u009F\u00E74\\(\u00D9\u00F5\u00CC\u00F3;\u00A5c\u00EA\u00CF\u0093L\u00C1\u0095Oa\u00A9\u00EE$\u00E4\u00EF\u00C2Az\u00BD\x17\u00C1\u00CF\u00A3\u00E9\u00E6\u00A9\u00DB\u00CB\u00E6@\u009B\x02\x17N!\u0096B\x1F\u0093\u00E7\x1FG\u00BC'\u00EB#\u00CAF0\u00B3-\u00ABX\u0089n\x0F\u0098#\u008D\u009D\u00C1\u008C\u00C8\u0090P\u00B6\u0082 \u00A2\u00F8\u009C\x138\x0Ek}\u00D4\u00FCx\u00DA=\x0E\tH\u00BD\x1D\u00CB8\u009C\u00C6\tsh\u0093\u008C|{\x12\x1B\u00EEx'w\x03\u00AB\u00E1\u009E\u00D4\x11\u00CB\x18\u0093e-S\u008D\u0090g\u00A7J\u00B9\u00C5zr\u00A1S\u00DFB\u00D27;\u0081\u00B9\u00EC\x0E\u00D7\u0080_\x03\u00F3:\u00EC\x15\u008B\u00DE\u00AF\u00B2\u00EE\u00ED\u009D\u00F7\x1E\u00ED<}\u00B2\x1D\u00D9\x07\u00E65l\u00B6\u00F6}\x0F\u00BA\u0090\u00DD\x0F;l\u00EEp\f\u00DC\u00BC\u00E5\u00CA\u009F\u00F9o>\u00FC\u00B1\u00BBh\u00FFN\u0084\u00F0A\u00DB~\u00F7\u00BF~\u00F8K\x1F\u00FFW\u00FE\u00D5\u008F\u00CC\u00A5nA\x10\u00AEe\x0EB\u00B9%m!\u00B23\f/+\u00FDZ\u00DB\u00E0,\u00F5\x19l\u0094\x04\u00DC\u00EB&\u00A2\u00A1\x00\u009D\x1B&HG\x11\x11 \u0097\u00A6\u00FA\u008D\u00ECWff\u00BA\x173XB\u00A9\u00FB8\u00D1\u00EE\u00D3\x19K\u00EF;\u009A\x0BO\x12&\u00A4\u00CD\r3\x07':J\u00B0\u008A\u00CEu\u00E0\u009C\u00C9\x18Q\u0088\u0094\u00F2\u00D0o\x11\u0085\u0098\u0082%\u00D1E;N\u00E2\u00A9\x00$\u00CF\u00F6A\u00C4\u0090:\u00F3\u008C\bFg\x00P\u00EE\u00FE\u00EC\u008C\u00E4\u00F3\u00AA\u00BB\x10\u00B6\x03/\u00CF\x01\u00CA\u00D87\u00E0\u0090{V\u0099\u0091@\u00FB\x1B\u00CA\u00B3@Ju\x17\u0082\u0090\u00CA\u00D2\u00E6m\u00B4\u00B9\x03\u00D1VO\u00BA\u0082\u00AB&\u00A4\u00B7\x18\x05e?\u00A2\u00A9\x1D\u00D1`\x0F\u00DB\u00C6\u00F2\u00DB\u00E9\u00D8<}w\u00F0\u0081\u0090\u00C1\x1B#\x0F}T\u00C9x\u00D9\r\x14\u00A3 \u00AEE\u008B\u00BFv\u00A7\x06c*\u00EAw\u00D9\x11\u00C8\u00B4R\u00FA\u00CB\u00BE\u00E2\u0088HJM\x16\u00E0SnI\u00B95\u00E3\u00D9G\u00ACB<\u00D0r[:J\r\t\u00E1\x1A\u00DB\u00BC\u00D5\u00F6\u00F06\x07\u00E6\u009Fz\u00FA\u00E3W\x7F\u00E5\u00B9\u00DF\u00C5\x1D\u00AF;\x19\x02\x00\u00FC\x00\u00DE\u00F9S\x1F\u00B3\x7F\u00FD\u0091\u00AAfUDy[\u00C2\x07\u0087\u00B4\u00D4\u00CBA\u0082\x04:\u0087\x1DY\x7FQ\u00EDm\u00D9\u00AE\u009F\u0099\u00C1be\rh;{{I\u00DC@VC\u0082\x01v\u00F2\u008E\x18\x00\u00A2\b=\u00E0\u00DB\u00DB\u00C1\u00F7\u00B2pg\x15\x01\u00D5Vg^\r\u00A2NJ*\u0083\u00D5\u00A2\x02p\x03\u00C7\r\u0089T\u0096{\u0085/\u008B\t\u008CR7\u00C38\u00D7\bG>{\x05\u00FA\fG\x06\x0B\x01\u00F1\u00DB\u0085\u0084\u00B4\u00CFb*N5\u00C3\u00F8\u0099q\x0B@\u00EA\u00F5\x1A\u009C\u0098\u0098\u00F4v\x19\x1F\u00BB\u00F1o\u00BAs\u0099\u00D5\u0098\u00C3U\u00CA4m\u00AF1\u009B\u00CF\u00A62 \u00F5~#\u00C3\x19\u008C\x19\x18d\f\x19\u00FC$\u00E64G\u00AA\n@\u00AF\u00CD\u00C0\x152\u0095m\u008A\u00AA\u00CE\u00D4\b\u00BE\x04\u0083\x06\u00D9\u00C6\u00AAx\u0085\u00C6\u00BC\u00CE\u00E7d\u00ECB\u00DA'\u0082).\u00A1\u00D2\x13\u008D\u00B9lM\u00AD\u00F0\x12(\u00F9}\u00AE7\u00E8\u00AEE\u00D9 \u00A4\u00DFg\u00CE\x01 [\u0081)\x122C\u0093\x072E[}N\u009BF\u00CF]\x10zi\u00CC\u00C2\x01\u00C7\u00C4\u00FCw\u009E\u00FA)\u00FC:\u00EE|\u00DD\u00A92\x00\u00C0\u008F\u00DE\u00FB\u00E0O}\u00F0\u00E6-\u00BE\x10\u0081`\u00B3 \u00CCbl$l61\x03\x11<\u00DF\x19\u00C0\u00D4a7\u00BB\u00E0;\u0099A\x18\x11\u00ED\u00D69\u00E7p\u00E9jG\u008B#N;K\u00F2\u00DEO;\u00FD^\u00FD\u00B1\u00E5\x1A\u00AC}\u00EB\u00EA\u0092\u00EE+#\x1E%SE\u00F9\u00A1`\u00EB\x04\x1EM\u00C7\r@\x17\u00A1\u0098\u0080K\u00FDL{\u00D4\u00CC\u009C\x16K\x1B\u0083O\u00C3<,az\u00BEy\u00CC\u008F\u0080\u00F5B\x1D\u00F9NhO(~4\u00F5\u00E1\u00A0\n\u0090\u00AA\u0081G{\x07\u00DD\u009ErW\u00EAw^{L\u00F0\u00BE\u00C5\u00A0\x0E\u00DA\x1Az\u00DF|:\u00D5\u0085~\u00CC\u00B8e\u0081T\u008F\u00F35\u00D5\u00EF\u00EE\x02\u009D\u00D3\u00DB=J]\u0099\u00AE~G\u00BFr\u00D76\u0097:ABey\u00B8\u0088\u00AD\u00A8\u00DFJ\r\u0093\x04\u00D6\x066\u00BC\u0096\u00EB\u00C9\u00D4Tz]\u0096\u0085F\u00C6P\u00AA\u00A6\u00DDrU\u0096*\u00E0R\t\u00F2-\u0098\u00AF\u00BF\u00EFP\u00B9\u00FD\u00B4\u0096{\u00DBrUN\u00B9\x14\u00AF\x01\u00BF\u00C7\u00F75\x7F\u0097\u009BQ;\u00A0E\u00B1\u00A0\u00D7\u00DF\u00BE\u00F93\u00FF\u00C5\u009F\u00FD\u00A97 \u00FB7F\b\x7F\u00DA\u00EC\u0085\x1F\x7F\u00FDW~\u00F9_\u00E0\u0095\u008F\u00C2YJ\u00AAy\nn\u0085\x14\u00B7\u00FA\u0081\u00E7\u00A2$\u008A:\\#\x1D\x01\u00E9\u00EB\u00B3\u00B9\x0B\u00D3H\t\u00B9\x07\u0085 4I\u008Ct\u0084\u009E\u009D5\u00A9/]\x0B\u00AD\x0F@\x1A\x18\u00D1\u008E\u0091)\u00DC\u00AE\u00C3\u00B8A\u008F>$\u00A4T\x0B.\x07.\x0EA\u00D9\n\rnp\u0094R\u00F6r8\x0Es\u00CC9\u00B8\u00CFb\x15AI\u0084\u00C0\u00CB\u00F2;%|W#F;\u0096\x05Mx\u008D<\x12\x12|K*q\u00EB\u00A3\u00DC\u0097F\"\u00DCH\u0080\u0092z\u0087\u00EE\u00D5\u00AE-\u00A3=U\x01\u008D\x1F\u0092\u00F8H\u0097\u00A190\x19\u00DA,dQ\x11\u00C0U\x02\u00DE=\"\x13\u00D30*\u00F4\u0091\u00F7\x1F\x10\x021_\u00C7\u00BCTt\u0086\u00B3\u00DC\u00BDW(\u00F4\"\u00F5%\u00A9\u00DB|\b9,\u00A8\u00A2\u00A9*\u0089\u00F0|}\u00B62\u00C0\u00E6\u009C\u00B6\u00B61\u008A\u0091\u00A4\u00D1C\u00AE\u00C7\u0086\x0E\x16\u0086R\u00D0$\u009E\u0097\u00D0\u0085\u00AE\x15\u00E4?\u00A0\u00DCu\u00CF\u00FCu\u00F5O\u00E8ChH.M\u00A1W\"\x03\u00C5\x04\u00F9\u00C4\x1C\u00F7p\u00F3\u009DW\u00BFl\u00FF\u00ED;_\u00C0\x1B\u00BC\u00DE\u0090!\x00\u00C0G\u00ECO\u00FD\u00DD\u00BF\x7F\u00F3{\x1F}\u00BC]\x16\"\u00AF=\x12:q\u00F5c\u00C5\u00D9\u00AA\x0E\u00C0*\u00F5\u00B3\u00E8\u00E8\u00A9\\\u009B\u00B6\u00DD\u00AA\u0088\u00C0j#\u00A7\u00B1\x0F\u00B4\u00DF\u00FF.$\u0090v\u008B\u0095!\f\x17\u0091\u009FQAj[q\u00EF\u00BE/E.\x12\u00AC\x0BD\u00D0U\u00DFy\u00F8\u0082\u0081\u00C3A\x03c\x04\u00EF\x1C$\u00AC\u00E9U\u009D\u00C8QD==\x12j\x05SwJho\u00E7ed\u00A0W\u00E6\u00E3\u00A51\x0F\u00E88\u00FD\u00FAJ3\x16\u00C1\u00F7\u00A8FL\u00CE\u009A$hk[\x0B\u00FF\u00A0\u00E7@%\u00C2&G\u00E8\u008D\u00C8\u009D\u00E3rxf:b\x02\u009BE\u00F4\"\u00FA9\u00C9\u0080\x14\u00EBPm`\"c\x17\u00E4M\u00E8\u00CC\u00AD\u00CF\u00BDP\u0095\u00C6\t1D\u00AC\u00E3[?m\u00B5\x1B\u00DCq\u00CE\u00CA\u00ECQ\x04\u00B8x2\u00B8fRU\x00\u00B2&\x02\b\u00D1U\x1C\u008F\u00B1\x07\u0091'b\u00A8m\u00DF\u00FA\u00F5z\u00C8\u0082\u00FD:F\x18\u00E4\x07\u0096\u00ED\u00E0 \x15aba\x10d\x04A?\u00CC\u00A6\u00E4\u00E0|8\u00F0\u00DD\u00EF\u00F8\u00BB\u00F88\u00DE\u00F0\u00F5\u0086*\x03\x00\u00FC\u00A7\u00F7\u00DE\u00FB\u0093\x1F\u009E\u00EFx\x18\u00C4Qq\b\ti8 opz\u00F8\u0086\u00D1\u00AC\u00F4+\x1C\u0097\x0E\u00B4\u009F\u00DA\u00D3y\u0081\x16n\x1B\u00F1h\u0094LuE\u00CFF\u0088A\u00D1]c=\u0097\u0086\u00C9\u00DBpl\u0087\u009B\u00FA\u008D\u00EC\x03\u00D2h)\u00A4\u00D1l\x1Cm!\u00F4\u00A2\u009F\t==\u00D4\u0081\\\u00B03\u00DCv\u008F\x0E\u00C3\u008Dw\u00CF\u00C2\u00EA%\u0098h\x7F\x0B\u00E6;\u00D5\u0081YB'\u00ED\x10\u00B3\u009D\u00D7>/^P^P\u00DB\x1D8\u00E6\f\u0082\x16\u00C3Q{\u00BA\x7F\u00FB\u00EE\u00ED\u009E\u00A1J \u00A3\n\u00D3s\u00A0hH\u00AA\x15\x19U\u0098\u00D0\u009Fj\u008FGM\u00C9\u009B\u00D6\u00DF\u00F3\u00BB\u00E6\u00C4\u009AJ\u00C2m\u00D4\u00BDy7\u00BC\u00F7\u00B7\f\u00C8a\u00E0l\u00A5\u00E7\u00F8\u00EE\x04\u00EEd\u008Cg\u0086\u0082~\x1C\u00ED\u00B7\u00E4L\u00BE0)\u009D\u00B3\u00A8%\u00C9\x14\u00F8\u0096-`^#\u00EA\x1AF\u0084\u00A2\u00F9\x06\u00CC+\u00B8\u0087\u00F5\x7FQ\u00A53\u00E2\u00F1\u00A4\x0E\u00EBxF4n\u0080\u00BC\x14\u008A\u00AA<\u00AB\"\u00E9\u00B9 \u00CAV\u008A\x01\u00D5\u008C\u0087\u00DF\u00BE=|\u00F6\u00AF\x7F\u00EFO~3\u009A\u00FF\u00A6\f\u00E1\u00BB\u00CC\u00BE\u00F2!\x7F\u00EB\u00C7`{K\"\u00A2\u00F1P\u0095\u00833\b(\f\x19\u00D3\x00U,\u00AAP\u00A3u\u00A0\u00B2H\x1BTL\u00B5\u00D0\u00831\u00F6 \x02\u0087\u00C2]\x02l\u0084\u00AD\u00BC\u00EF8\u00D9#\\L\u0083;K\u00A5j\u00D1\u0099\x00\u00F2\u00F7^\u00E8%\u00D3\u00A5q\u00DB\u00FB\u0091\x15j\u00B5\u00C8\u00A6g\u00DC|H\u00E30\u0092i\u00B1\u0098\u00BE\u00F3Ca\u00BF\u00C7\u00F4 X\u00EF\u00E1\u00C5\u0080\u00C2\u008E1'\u00E6\x11\x15\u0096/\x13\u00B418\x0E\u00BE/\u00D4\u00EDU\u00C2_\x06\u00CA\u00EE\u00A1\x10\u00B1\x1E\u008D\u0080\u0093\u00A9t[@cHB\x1E\u00E1Z\u009C\u00D0\u00FE\u00C1\u00D3\u008D\f\u00AAl\n\u00EA\u00B3\u0098\u0095\x18\u0090\u00B3\u00E0\u0090\u00BE;\u00EF\u0083\u00A3\u00C6z\u0099\u009EnWW_$\f\u00B3]\u00B5QL\u00A2\u00E6O\u008C\u00C1\u00E9\u00A2\u008Dy\n\u0082\u00ED;\\\x05u\u008B\u0088+l]' \u00E3y4qFA[\u00BB\u00A2ibi\u00D0\u0093K\u0093}\u00CDm\u00EC\u00B5\u00EE\x16w+\u0088^\f\u0098#\x18\u00C1\u00B1\u00F1;\u00D5\u008EcPE\bB\u00CF\u00CD\u0088f\u00B9+\u00FD\x0E\u00B7\u00A5\u00DE>7\"\x0EC\x0F[\u008E>\u00D0\u00A6g;\x06\u00AE`\u00B6\u00C3,\u00B6]\u00F4a\u00C0{\u009F\u00FE\u0098}\u00F4\u00C1W\u00BE\x19\u00CD\x7FS\u0095\x01\x00\u00FE\u00CA\u00F5\x07\u00FE\u00F6O_\u00FE\u00CD_\u00FD\u00F2\u0095f\u00B7$wF\x1D\u009E\u0092\u0084\n~\u00F7\u0097\u0091\u00E8\u00B68\u00F5\u0096\x1A\x11\u00D7\u00CC\u00A4\u00A8R\x01\u0094*\r2\x1B\u00E49D*M\u00ED\b\u00A6}R!h8\u00CC\u009A\x04@\x12\u00FF\u00BC\x15\u00AFP\x0F\u00DB\x05Y\u00C5\x10\u00A8:\u00C8\x00%\x1D\\\u00D0\u00B9K\x0EG,\u0080\u00C7\x1E\u00BBCk\u00D7\u00E8\u00DD=k\x1B\u00CE\x19\u00FBB\x0E\u00BA\u00B8X\u00AEcI.\u00F2l\x0F\u00F2D\u00C59 \u00F4O\u0083\x19\x16U\"\u0089APZ\u00C7f\u00D9$\u00B2\u00BF\x13\t\u00A5\u009Djk\x1E\u00E3l\x01\u0080\u009B\u00A0i\u00CC\u00E8\u009C\u00C8=\x1A\u00A4\u00EF'\u00CC\u009F\u00B6\u00F6\u0093\u00CDHmI\x01-\u00FB\u0084#\x03\u0092\u00A4\u00C2H\u00F5H;Bs\u00D3f\u00FD\x05G\x1Av\x0F\u00D9r\u00A0:\x13~z>\u00D5\u008F86\u009A\u00A4oCms^\u0093\u00DF\u00D4\u00A8\u009Cs[\u00ED\n\u00D3s\u00BE\u00E3\u00D2\r\x19\u00D7\u0090\u0081G\x1E\u0084O.S\u00D5\u00A0f[{\u00EC\u00A07\u009AKw\u00B8\u00D4\f\u00A9%\x13\u0085\u00D69\u00B1T\u00DE\u00CA\u00E2z\u00C1\u00E3\u00AB\x1B\u00F8\u00BF\u00FFm\x7F\x1B\u00BF\u008Co\u00FA\u00FA\u0096\f\u00E1/\u008F\u00A7~\u00EE#7\u00EF\u00FC\u00EC\u00CF\u00E0\x0B\u00DF\u00D9\u0089\u00BFB\u0090\x15\x04\x04\f\u009C\x19\x02\x7F\u00A7\x04w%\x17%0\u00E9\u009F\u00F2\x18P\u0085PI\u00F4\u00D9\u009E\u00D6\x1B\u00DA-Z\u00FEB\u00DA::\f\u00B3\u00AC\\\u00E4\u00FDZ\u009C\u0083\u008D\u00F40\u00F5p\u0083\u00B0\u00B3\u00BA\x10\u00D0\u00A4\x0BO\u00F5R!\u00D2G,\x1F\u009F\x03\u008F\u00A7\u00E3\x06e\u0080;\u00DC\x18\x05\x18\u009Bt\u00D9\x01\u00EC\x13\u0094\u00CCqMU:\n\u00A9X\u0099\u008B\u00B18\u00E6\u00AC\u00C2*e{h\u00BA8U\x16K\x1Ff[\u00B8\u0094\u009C\u00C6v#Z\u00B06\u008C\x15\u00D2\u00B0\u00C9(B\x032C\u00E3\u00E4R3\tK1\"\u0080FB\x16f\u00E1\u00A9\u00AA\u00C4l@\u00DA3z\u00AD\x05w\u00CD\x01\x055\u00C7\u00A5\x10\u00EA\u00CA\u00DA\u008C\x1B\u00AAv\x04\x10\u00C7\u00D3\u0095\u00AAq\u00A3\x13\u00AC\u0097\u008A\u0090\u0084^\u0084\u009BL\u009F\u00EDed\u00A3\x18Bc\u00AAu\u008D-mZg\x06\u00B3\x11\u00B0\x03\u00ABZ\u00A0\x10dU[\u00AE\u00B9L\u00A9C\u00FB@\u00C6)\u00CC\u0081\u00AA\u00D1\u00A02h\u008D\u0089\u00B86wU5%\u00B4\u00DF\u00C4\x18\u00AEp<\x7F\u00FD\u00D9\u00A7\u00FF\u00A7\u00E7\x7F\x0E\u00FF\x1B\u00BE\u00E9\u00EB\u009B\u00AA\f\x00\u00F0\u00B4\u00D9\u00FC\u00C8|\u00EEo]\x1F\x01Q\x12\u00CE/\u00C4n\x18\u00BDNA\u00B2\u00C8P\x11\u00A2\x12\u00B1&\u00D4\u00DA\u00EF\x1A\u0082\n\u00B1m\u0091\u0081\u0088\u0088kp\x05\u00E0\u00A7\u009A\u00D0\u00F4\u00AE\u00B4\x1D\u00F0\x1E\u00CE\u00F3\u0096~\u00C4\x1D\u0082\x118f\u00E2\x0F*4\u00BEg\u00D4\u00D9\u00F2\u009F\u008E\u00F9Z\u009A\u00AC\u00A2NipL\u00A9\u00D1\u00E3\x10h\x01\u00A1;\u00EB\u00E2\u008EG\u0097\u0089\u008B\u00D4\u0080\u0093\u00CA\u0090P\u009Cn9\x1C\u00A5\u0093w\u0097\\@\u00E6\u00E6\u00AA\x13\u00FCOH\u00ED\u00B4\x1F\u0094}\u00E2\u00E2H\u00DD\x7F\u0085\u00DD\u00D4\u00DF\x1D\u00CD\u0085\x17\u00EE@EX\u008A\u0081\u00F5\u00FBkM*\x1AR\u00EE\u00D3y\x04\u0084?\u00A8\u00F6\u0094\u00AB\u0092\u00EA\u008A\u009F\u00FA\u009B}C\u00DA\u00CB\u00CE\u0091\u0089]\u008D\u0088\u00EF\u009E.V\u0085y\u00CFv^Fn\u00CA.\u00E1\u0096c\u00C5a\x01\u00F9\u00C5m\x14\u008F\u0080\u00CElPB\u0080j\u0080\x1D\u00FA\u00EE\u008BJ\u00D2\u00F1o\u00DA\x0E\u00C5\x00L\u00EB;\u00B8M\u00AA\x18\x13\u00E5\u009A\u0094^?\u00B7\"\u00F8)w\u00A4\u00D4\x04\u00ED4%\u00D5A\u00E7m\u00B0\u00B977\u00E75lR\u00A5P\u0096\u00A4m\u0080\u00B2\u0091m\u00C0\u0087\u00E1\u00D1w=\u00F1\u00B7\u00EC\u00DDU\u0094\u00EE\u008D^\u00DF\u0092!\x00\u00C0\u008F?x\u00DF\u00DF\u00F9\u009E\u009B\u00B7?,\x12\x161vC\u009F\u009D~\u00BB\u0082\u00FB\x0EW\n\u00F4\x1D\u0086\u0093\nF\u00EA\u00BA\u00BE8\u00EA\u00C9\u00D8\u00B2\u00F8j\u00E3\u00DE\u00A6{\u009F\u00DD\u008E\u00A8{\u00DA\u00E9\u009E]=\u00A9\u00FEJ5)\u00A2\u00EF\u00D24%\x0F\to\u00958\x12%\u00A0O[a\u00BA\x00\u00A8\x0F?\u0086\u00E1\x11\"\u00E6\u00FFp\x11\u008Dt\u00FC\u00B1\u0084!\u00CB\u00B7~\u00D1Bg\u00A8\u00F3b\u0094$\u00A38\x1C\x19\u0097p\x1CE 3u\u00FA5\u0094Y\be:\u00D5\u00E6\u00C6h\u0082\u0090*m\u00BA\u00EE\u00AF\u00F3\u00C2\u00C8\u00B8\x18&\u00F3\u00FA\u0095a-\u0084\u00DD\u0098[o\u00FB\x1C#\u00B1\u00F4\u009F\f\u00A9\u00DBJd+\u0088\u00A9/uj\u00E6\u00EF\u00EDoh\u00BC\u00B4\u00A7\u00F07x\u0095\u0099\u008B\x07\u0086\u00E5\u00B7\u00C5\u009B\u00C0\u009BIUY\x10\u0083\u00C8\u00CA\u009D5\x1B$\u00B1\u00F5\u00AEPmE\u008E\u0096\u00910\b\u00DB\u00E7\x06?\x18\u0082,C\u00A3\u008C\u0082\u00F9\u00EE\u00EB\u009E\u00B6\u0083\u009E6\u00ED\u00BAFo\x1A3q\x05\u00D9\x12\u00E0;\x1E}\u00FB\u00FE\u00F0\u00D9\u00BF\u00F6\u00A1\u00BF\u00F3fh\u00FD[\u00AA\f\x00\u00F0A\x1B/\u00FD\u00F5\u00D7>\u00F9\u00F7\u00FE9\u00BE\u00FA\u00DF\u009F\x0B\u00A3\u008C;\u00F6^\u00C8\u00ECC\u00EE\u00F8\u00B4\u0086 \u0087\x14\u008E9\u00ED\u00A9\u00D2[\u00D9\n\u00A42\u00C0\u00A8:\u008D<\x1E\u00BFm|v]E8C\u00FF^\u00BB@\u00CF\u009F\u00AA\u008B\u008EY\u00FBu6i\u0081\u00A6G\u00FBz\\a\u00B3g\u00BD]\r*\u00DC\u00B6*\x00\x01\u008F\u00CC\"\u0094\x19\u0086\u00CB\x04vI\u00B9i\u00B5A\u00AC\u0087\u00F1m\u00D3\"d\u00CFv\x07z\u00D5\u00E1\u0088;\u00A8\u0084&\u00A1\x13\u009F\u00EB\u00B6lZ\u00E3\u0087G\u00A8o\u00BA#\u00D9\u00BFHk\u00B6$\x06\u009B\u0081\u0086\u00E4\u0096\u008C\u0094f\u00A2\x1E\u00B6\x0B\x04mt\u00FD]\u0084\u0092\u00FB\x1A:\u00FB\u009E\u00E8)'\u00BF!\u00AA8t\x00-6A*R\u009D\u00A3B(\u0080e\\B\u00CE/\u0090\u00E3U\u00FF\u00F4\u009B!\u00FA8\u00C8p\u00C0\u00F6\u00C11\u00AEj@t\u00CE\u00F9\u00BB\u00EE\u00DD\u00ABUk\u008E\u00D3k\u0091s\u00A6\u00E7oy\u00AC\u00E7\u00B4\u00E4\u00B2\u00E8\u00F7\u009A\u00DA\x12N\u00C2\u00D49_\u00E2\u0098\x1Bl\x0E8\u00EB5z\"\x1A\n?fP\u00AArs\u00C6.\u00A4RF\u00F5.V+\u00DC&.\u00EF\u00BF\u00F7\u00F7\u009E\u00F8\u00CF\u009Fx\to\u00E2\u00F5\u00A6\x10\x02\x00\u00FC\u00C8\u0083\u00F7\u00FF\u00CD\u00F7\u00DF<\u0085\b\fVRQW\x11\u00A2C\u00DE\n\u0099\u00ACII\u00F5\u00D9\u00A3\x0E3\x01#\u008D&N\u00FA\u0097~\u00DF#\x1D{\br\u00B7\x13\x14\x17U\u00BF\u00AA\u00BE\u0082\u00F3\u00DF\u00D1\u00EE\u00D9\u00E2#\x16\u00CE\x0E41T\x0B\u00DEyXPu\u0091,@F\u00BFI\u00C5\u009B\u0088\u00AD\u00C7\u00F8\u00FD8\u0080\u009B\x19\u00EFC\u0089m\u00CDr\x1FR\u00D3R\x15\u00C9\u009C\u0087\u0094\u0098k\u00F4\u009FP\u0080\x13\u00D2V\x04\x1Fc\x1E\x1C\u0098\u0097RC\u00E6,(/F2\x19A\x18\u00EB\u00D0Jb\x1F\u00CA\u0083@F\x18\x1E\x07\u008B\u0096\u00A8\u009D\x03\u00B7\" \u00FD\u00F0j\u00CB;\u008C/\x17h\u00CE\u00A5\u00BC-\u00BE\u00BAC\u0085~\u00D2\u00BD\u00E9B\x1D5\u00B7\u00D3\x03a\\\u009A\u00E7\u00A4\u008F\u00CD\u0081\u00F4\\4 \u00B0\x18g\u00B1\x100\u009F\u00D7\u00AC\u00AC\u00C7s\u00BD\u00C9d\u008Ah\u00F8\u0092\fd\u00AA\u00ED\u00E9\u00CB}t}\u00A8\u00A3\u00DE:'\x06\u00D2\u00EA.\u00CC\u00EEy\u0090\x0B\u00F1\x1A\u00E6\u00D7Eg\x1E\u0095\u00CA\fJ\u0096:#k\u00EER\u00ED\u00FCN\u00DAx\u00F5\u0099k\u00DC\u00FF\u00D1\u00F7\u00FC\u00CD7K\u00E7o\n!\x00\u00C0\u008F\u00D9\u00D3\u00BF\u00F1W_\u00FF\u00C4?~a\u00DE\u00FC%\x1BgO\u0082ES^\u00F6\u0085\u00D0\u00C5\x03\x19\u00A4z\u0095\u00E8\u00A1\u00A9\x19D\x0F.\u00C9\u00D2\u0083\u008AT\u0084%#\x19\u00CF\f&\x18R$p\u00F4k\u00FB\u00EB\u00B6\u00E1Pq\x0E2\x02\u00F6\x12\u00E1\x19\u00BF\u00DE$\u0095\u00A4V\x04\u009E\u0085\x14\u0088`\u0098Z\x1C\u00A5n\u00B4\u00DCz\x18\u00C2\x1B\x11\u00DE\u0086\x0B\u00DF74,\u00CE\u00C9uC\x1D\x1A@\u00A9'(\t\u00D8%\u00B2\u00E0\u00B4\u00A4a\x1A\u00D3\u00DA\u00F7\u00F4&L\u00CB\u00FE\u00A9M\u00B5\u008FI\u0097)\u00BF\u0087\x0Bn4\u00A4TFC\u00ED\u00D1\bXz\f\f\u00C1%g\u00DAR\u00A2\u008C]&?9\x18x\u00E4u]RL\u00A1\u0085\u00990\u009D\u00FD\u00E4\u00E7\u0098\u008D\b\u009Bd\u009Em\u00ECzV\u00D3#ZQ\x06[m\u00A7&/P\u009FG]\u00A7<\u008F\u0090\u00FE\u0096\u00EDg9\u00B6Y\u0092\u00BE\u0090\x04\u008A\u00D0i[\u00B0\u00BE\u00DE\u00D2\u00B8\u00A8\u00F9+\u0086S\fA\u00CC\u00A0\u00AFQ\u00A9\u00C9\u00E2\u00C4\u00E2nz~jw\u00B6s$\u0081,\u00C7\x11\u00D6\u00E2cY\u00C0\u00F6\u0081\u00ED\x1F_\u00FF\u00CF\u00DF\u00F6\x1Bx\u0093\u00AF7\u00CD\x10\x00\u00E0?\u00DB\u00DF\u00F7\x13?{\u00F3\u00D5\u00BF\u00F4\u00D5{\x05M\u00E2E\u0097b\u00D6.\u00E8jD\f\u00C8\u00AC%'5\x17\u008A\u00AC\u00C1\u0090\u00F1\u00F0\x1CD\x04$\u00F3\u00A80\u00E5\u009E\x04E\u00E6c\u00AA\u00B0\u00DC_2\u00FEu\u00B7(\u00EF-\t\x00\u0084\u008E7\u00FB\u0083m\u00C4\x02-@\u00BF\u00E3\u00FB\u00AA>\u00A4\u00CB\u00AF?|\u00B6\u00F3\x18\u00C1\x14\x1E\x1C\x01\u0095/\u0088<\x07\u00A3\u00CF_\u00D6\u00F1\u00D4\u00A3\x1B\u00F1az\u00EE\u0087xq`\u009B\u008E\u008B\u00DC\u00A2\x1E\u00A1\u00C088#\u00B3\u00AEs\u00AB\u00FE\u00C8\u0095\u0089\u00C9\u00DD\u009C\u00DCr\u00BE\u00D4\u00CE\u0098m\u0083\x16W%\u00E75:q\u00A2\b\u00E5jJ\u00AD\u0088\u00F1O\u0094:\u00A3\u00E4\u00A0,\x18\u00CB{\x1Cd\u0092\u00C1P\x18\u00AB\u0092\u008C\u00AC\u0092\u00A5\u00AE\u008EZa\x07}\u00FC\x06\u0086\\\u0093\x01\\<\u0098\u00D5v\u00A86eA\u00FB!\u00BF?j\u0099\u00CA})\x0F\u00CC\u0098\u0094\u00FE\u00C9\x04\u009D0\u009D\u00CF]\u008C \u0099G\u00FB{A\u0091d\u00A8\u00CD\u00D8,\u00A6\u00D0\u00ED\n\u00B9\u00BC4g\u00BC\u0097\u00EADD?\u0085\u00BA'\u00B2|\u00FA\x042\x14Y.\x1BeWB\u00A8@\x0B\u0091\u00DE\f\x00\u008F\u00AE\x1E\u00C3\x7F\u00E8\u0099\u009F@\u00EE\u00DC\u00F8\u00AD_gq\u00FAM__p\u00B7\u00FF\u00F1\u00E1\u00BF\u00F8\u008D\u009F\u00BE\u00FF\u00F2\u0087c\u008EE\u00A8\x1D\u008E\u009F\u008Cx\u0092\u0094}#\x14\u00D9\x03\u00BAo\u00F5\rK\u00A2\u00A3\u00CEK/\x02\x11\x01\u00CF\u00A9\u00A8\u00C8\u00AC\u00AF\u008B\u008E\x04\u00EA\u009ChK\x10/\u0082\u008BJ:\u00A0\x11\u00B3\u00A4d\u00D7\x19\u0081UZ\u00A9\u00A8\u0088\fG\u00E5\x1A\u00E4\u00EF\u00ED\u0081\x0E\x07\u009Eq\u00E0-\u0087\u00E3\x01\u0080{0\u00DC\u009F\u00C0\u00B5\x03\u00F7\u008E\u00B0\x15\u00EC\b\u00F7\u00DB\u00EE\u00F1\u00B9\u0091HwJ\u00DC}rvf\x10\u00C66\u0099\x10>\u0083I\u0084W\u00C4*L9\t=\u00DAQ.\u00C38(\u00BD\x13-\x00v8\u00FD\u00F9\u0096\x12iL\u00C7\u0086\x11\u0095\u00A3\x01dL*\u00DDd\u00D7\u00B3\x14\u00C3m\x02c \u00E3\b6\u008Fcz\u008A;\u00DD\u0085\x0EVS\u00E28\x06\u00DB\u00AF\u009D\u009D\u0082\u00D8\u00AFy\u00AD\u00F3\x19mm,Q`\u00A5\u00C68fI\u00FD\u00A5\u00F8\u00AC\u00BE\u00EB^\u00B3\u00EE\u0093L\u0081RX\u00A8\u00A9\u00F6@Q\u00B6(V\u00E2g\x00S\u00E9\u008E\x03r9\u00CA\u00DD\u009B\x06\u008E\u00A3\u00AEW\x1E\u0086\x1D1\u00B7i\u00B3\u00D09\u00D2K\u0099b\x1D\u00C5o\u00BDt2\u00EA@~\u00A8D\u00FB\x05e\u00DD\u00E5\u00FD\u00A2Q8\u00E1\u00EE\u00EB\x1F~\u00F4\u0089'~\u00F6\u00F9?k\u00EF\\\u00F1\u00C87{\u00BDi\x1B\x02\x00\u00BC\u00CB\u00CC\x7Fx\u00FF\u00B6\u00BF\u00F1\u00E4\u00A5\u0092\u0095\x14\x15\u00A8B%\u00A1&4\u00A2dm\x02w\u00C3R\u00CC\u00C4\u008BX\u00A1b\u00A7\u00EDx\u0096a\u00EF\u00E7\u00C9\x0B\u0081\u00D2\u00BFz\u00B0\u0091\u00AE\u00CB<\u0089\u0093\u00CD\u00A0\u00B6O\u00F3|\u00C0\u0081\u00C4\u00BC8\u00B1\"\u00D6\u00E6\u00CA(\u00F4\u00C0U\u0088T\u00EF\u0092\u0086\r\u00EDH?\u00CC\u00C5\x13\u00F7y\x1D\u00C0CC\u00BA\u00F6z\x01\u0095\u00D9\u008E\u00F5\u00D0by\n\u00E0Ul\u00E4\u00A0\x15;P\"\u00ED\x0B\u00C7H\x17\u00E3\"\u00B8\u00B2-E0\x06\u00C4\u00CE(F\u008D+\u00C3\u0096U\u00B8\u00C5\u00E9\u00D9\u00C0\u00E2\x06\u00D4y\u00EE\u00A3\u00DC{i\u00C3P_*\x13\u00D3a\u00E5\u00D9`?\u009Czs\u00B4\u0081\u00C5~\u0090\x1E\u008F\u00E5]\u00E3\u0090\u009A$\u00D5\u00A9\u00FF\u00DD\u00DD\u009A\u00B7\u008A\u00D3\u009C\u00BC\x185'm\u00AE\u0093p\u0091\x02\u00A0G?:\t\u00B5\u00EF@\u009D\u009E%/4V\u00D9\u0095\u00ED=\u00D1\x10C\u00AC\u009D\u0085yt\u00C6##\"6D\x14\u00A2\u00EC\x02\u00F1vlp\u00BF\u0082\u00DFU\x00\u0096\u0086\u00FC\u009Bk\u00C7\u00CDG\u009E\u00FA\x1B\x7F\x10f\x00\u00FC\x01\x11\x02\x00|\u00D9}\u00FBk\x0F\x7F\u00FB\u0093\u00FF\u00EF\u00F5W>t\f@\u0084*&0\u00E8\x06\u009CwH\u00E8\u0098$\u00C1w\u00D9\b\u0090D,\u0084`\u00A7k\u00AB\u009B\u00C1|\x06\u00B4\tK\x1C\u00F7\u0085\u00AF\u009D\u00BC\r\x10\u00E3@F\u00E7\t\u00CA\u00A6\u00DE\u00CA\x18\u0091\u00E4\u00E6\u00FC.\tu\u00F66T<\u0082PC\u00B5\u0095\u00C5R\x1A\u00AC\u00D4b\x1A0\u00BC\u00D5\u0081g'\u00F0\u0084\x03\x0Ff8\u0088\u00EEM\u00E0j\x06L\x1E\b\u00E98< \u00F9\x15\u0083\u00906\u0084D\u00DB&35\u0088\x18\x14\u00F7puXIZ/\x14\u00E1\u00EE\u00D8\u008E\n\u00D0\u00D9fH(\x13b8\x06\u00B6\u00A3\u00A4\u00D98\x1CR\u00E5\u00C6\u00E1\u00CDL\x0B\fL\\90h\x19\u00DF<\u009E\u0086\x01\u00D8|\u0086\u00A9\u00B9\u00F5as`X\u00AC\u008C}VV\u00E2F\u0084`>R\u00C2\u0083c:\u00BFAuG\u0088 \x11\x00b,\u008AR\x14JP\u00E99!\x00\u00A9Q\u00FD\u00F7\u00B4\u00D3\u00B4~voEHr\u00AA;\u00ED\u00B8\u0093\u00AB\u0099PT\u00DA\b\u009A\x1A\u00AAR\x05D]\u0081*\u00EC\u00842<U\u00C9\u00FF\u00BF\u00BDo\u008F\u00BA\u00F4\u00AC\u00EA\u00FB\u00ED\u00E7=\u00E7\u00FB\u00BE\u0099df2\u0099\u00DC\x00\t\u0082@\u00B8#wQ,\u00B1,\u00AC\b\u0088\u00B5\u00A6\x0B\u00A8\u00B4R\u0090.\u00DAJ\u00ADE\u00B0\u00DAbU\u00BCVY\n\u00AC\u00A5b\u00B5\x15X\u008A\u008D-\u00A2(\u0082J\u00B1R*\x17A@0\x01\u008A\x10\b\u00B9\x13&s\u00F9.\u00E7\u00BC\u00CF\u00EE\x1F{\u00FF\u00F6\u00DE\u00EF\u0099Q\u0099\u00CC$\u0099$\u00F3\u00CC\u00FA\u00D6\u00F7\u00CD9\u00EF\u00FD}\u00F6\u00ED\u00B7\x7F{?\u0093\u00F0\u00A3\u00FE\u009F\u009C\t\u0085e\x15\u00C6\u00B1P\u00A7\u00D5\u00BD\u00881\u00BD\x05^\u008B\u00EF|\u00F4\u00E1\u00E3\x15\u00BB\x7F\u00E7^\x0F\u0093\u00FB\x10T\u00F8\u00F2\u00C6\ta\b\x00p@d\u00FC\u00A9\u00C5\u00F5\u00AF|w?\u00F8\u00C6[D\u00A12mJ:u\u00D5Kh %|\x00\x10x\u0081\x00\\oO+-\u00B9\u0080\u0089S!?\x1E\u009F\x00Hg\u00E7X\u00AE\u00C1\u00B4BQR\u00F8G\u00FFC\u00A7\u0096>\u00C3\x07\u00B8\"H+\x10\u00BD-\u00A9\u00E9I9\u00E5\u00A4)\u00E7b,J\u00C5a\u00BC~\u0099\u00E4\u00F8E\u00DC\u00DD\u00A6\u00FB\u00DA\u00FD\u00B2h\u00ADy\u00AEn\u00E9\u00C9UPMz.\u00B1\u00CE\u00EB\u0092\u00B2O(3\x07\u00B9\u00A4+0f\n/\u00C2\x06\u009F\u00D8\u00AAE\x10z}\u00AF\u0080\n\x1B\u00B1\u00F8\u0092!\u009Ao\u00A4\u00FB\u00BB%\x00\u00DA\u00FC\u00D1v\x17l\u00A68\u00A3\x0B3\u00F2\u00B92M\u008A\u00F2}Z\u00D6\x12\u0087\x03\u0093\u00F2\u00EAi\u00F5\u00A6WN\u00D6\u0090A\u00CB\u00B3\u00F1wA\u00F7\u00BAi\u00A6 \u00A3\u0081L(\x043I\u0083\u00F2\u00C6\x11\u009EA\u00D3:\u00E7$CE\x1A\x01\u00E2\x01\u00EEm\u00C4w|/~\u00F3\u0089\x1Bh\u00CC\u009Fc\x7F\u0088\u00E7\u0090\u008BS^jP\u009D)W|\u0089\x03\u0096\u00F3\u008E\u00C5c\u00F1\u00CA\x13U\x06\u00D3\u00B7}\x02\u00E3\x0B\u00BA\x18^\u00B4\u00F9\u00E9\u008F\u00BC}\u00FD\u0096\u0087,\u009B\u00A5\x19\u00DD?\u0080\u00A0\u00D9\u0082\u00A0H\u00D7\u00DF\u0098\u0084\u00C5\u00CA\u00B7\"\u00B4\u00D1)\u0099\\\u0085\u00BA\u00B0j\u00C1\n\u00FC\u00B3\u00D5^\u0087\u0088\u00F0@\u00FC\u0081\x14\u00BC\u00A0\x1B\u00AA\x10\u008A`Uh\u00FD%M\u0084?0\u0080\u00E2:\x16w/\u00F6\x05\u00C2\u00D7\u00CC\x05;}\x7F\x0F\x1B\x02\u00E7\u00E9y\u009E55\x0Fa\u008F*vw\u00C1\x1A\u00CCCX\u00EF6\x01\u00D7F\u00D8\u00DA\u0090\u00DD,\u00E9\u009Ag\n\u0086\x11\u0098+-\u00A4\u00FA\u008APn\u00FD\u00FC\u00F3\x16\u00F7c\x13w\u00E6!K\u00C4\u00CDj\u00C7i%\u00B6m]18\u00D3\u00AE{\u008CKv\u00A68.1\u0083\u00BD\u00B2\x06\u00BB\u009E\u00D65\u00B1\x03\u00A0`\f\u00C9'\x18\u0090\u00D6\u009A\u00D7e\u009EB\u00BE9Z\u00E7Yw\u00E6\u008A\u008Ay0\u00E58\u00D2\u00BB\x0B\u00AF\u00C4\u00B1\x1A\u00EF\u009Fx\u0089\u00DF[S5\u0080U\u00E1\u00CA\u0095\x19\u0090\x04\x1C\x07\u00EF\u00B7@\x0FJ\u00CA\u00BBi\u00E5o>\u00F3\u00C8.-\u00F9~\u0085\u00DAob\u00FDY6~\u008C\u00B5WD\x19\u0083yd\u00A6\u0090c\u009B\u00C0\x10\u00CA>\u00AB\u009F\u00BB\u00B7`J\u00BAC\"^r\u00ED\u00989h\x00\u00C0\u00D1\u0087\u008E\x1F\u00DF\u00FD\x1B\x07\x1E!\x0F;q\u0085p\u00C2\x1E\x02\x00\u00DCS\u00E6\u00E3kw\x0E\u00FE\u00D0\u00FF\x19\u008F^~s\u00AB\x00\u009E\u00CB-\u00B9\x02\u00E5sS\x04\u00C5\u00DA\u00D3k(\x05Q\x1A\u00D9\u0086\n.\u00C2~\u00C7*\u00CA\u00AB\u00CA\u00A0z#\u00A5i+s\u00CAD\u009Bu\u00E5\u0085izY\u0093\u0094\x1C\u00B5\u00B8 4=_\u00A0\u00D2\u008CUE2A\u0092\u00E1J\b\u00F1\u0082\u00EB9\u009B\x02;\u00AA8\u00D2\x15k\x00\u00E6\x0E\u0096Y\u0097e\u00EF]\u00E0\u00E7\u00E9n!\u00BA\u00FA\x1D\u00AAF\u00AE=\u00D2gL\u00CDi\u00C6\u00D7\u0083O(\u00C6\u00CESo\u00C2~:\u0080\u00ACe\u00E0\u00F5i(\u00CC\b\x7F\u00FC\u00F3.\x06~u!\u00C5Y\u00CB\u00DB\u00A1\x153o\u00A7\u00BB{\u00DF\u00F9\x19\u00CA\u00F9a\u00E0'\x1F\u00CF\u00A4E\x1C\u00DC;p7\x7F\u00D4$\x13\u00B1AK\u00EC\u00D8\u00D3\u0083\u00A9\u00DE\u0082yq\u00DE8\u00A5\u00A3x\x13\x02\u00AEX=IwvL<\u0085\u00CE\u00E7\u00A7\u00E4\x14\x14\u00B7\u009Es\u0089\u0083\u00DFQ\u0081\u00AC\u0086\x0F\u00FE\\\u00DD9,D'\u00C99\u00C1\u00FD15\x1C\u00AA9\x1F'\u00DE\u0082\x02\x1AM[\u00A9\u00A9\x04\u00C0\u00C2\x1D\x13\u00C1\u00CE\u00DA\x02x\u00FC\u00EC\u0087n\u008D2\u00C8\u00B7y+\u00C6-\u00AA\u00F2\u00DC\u009DO\u00BC\u00F7\u00F7\u00D6\u00B6\x1E\u0097\u0082\u00EF\u00F1:\u00D7l\u008C\x07\u00E8\u0082\u00DD\u00DC\u00A6(\u00C3\u0085\u00B2H+\u0095D`\t5T\u00A8\u00DD\u0099\u00EDxZ\u0095E\u0080\u0087vK\u00C7\u00B8g=_^6\u00D0\u00F0\u0085T\u0090/+&KW\u00D0E\u00D3N\u00E4\x1D`<g\u00FB\u00DB\x04\u00E9\u00A3\u009D\u0093\u0096yBs-\u00EE)]JU`C;\u00CE\u00E9\u0082\u00B3 \u00D8\u00D5\u0081uu\x1CA\x15k\u00A3+\u008Anw9Wz\x02\u00863\x04\u00B2\u00AE\u00F6\x7FNh\u00C6\u00D7\u009C\u00D0\u00D2\u00BD1\t\u00F9\x06\u00AE\x00fK\u00BB\u00DE(\x1E\x1A\u0081\u0099s\u00A7\r\bM7wX\u00BA%\u0096\\Lf\u00EEnx\x13s\u00D1\u0087\u009E\u00DE@\x13\x04\u0086\u00D0\u0080(^b\u00E6A\u00A0\u0098\u008D\u00E5\u00AD\u00BA\u00C0\u00F2\u00FEV\u00BD\u0089yX\u00F0\u00E4S\u00D0\x1B\u00AAx\x020\u00C5\x11f\u008E;\u00B0n\u0086a\x15\u00D4\u00EE5\u00B1\x03Stl:\x03\x0F\x13\u00E8\u00E5\u00CD\u00AA\x07\u00C2y\u00A3H\f@\u0091\u00C0\u00B1\u0083\u0089\u00B6,^z\r\u00AB\u009E\x00\u009Fy(\x12b6,\u00AF.JH\u008A'\u0097\u00D8U\tk\u00BB?tGL\x15\x1D\x07\x1F\u00B9\u00F5\u00FEs~k\u00DF\x13\u00E4\u00FE'\x06&r\u00DC*\x0F\x01\x00\u00F6\u008A\u00E8\x1B\u00B7o|\u00F9\u0087v\u00AE{\u00E75su*\u0081e\f\"\u00D4\u0081\u00C0HLH|@\x05\u00D3\u00E5\u00D3\u0080\u00C4\x13*F@\u00F2\u0092\u008FP\u00BC5dhHI\u00CD\u00E6\u009C\u00B1\u00EE_<\u00B8bE\u0080\u00D0\u00A6\u00E15\x10h\x04s\u00EC\u00C4\x04\u00FC\u00C5\u00FAY \bB\u008A\t]sk\u00CD^\u00FE\u0092\u00D5\u0099\u00DD\u00F7GN\x0E\u0085\u009Dk\u00D4\u0086m(\u00D6\u00BBy\x07\u00B3\u00EE\x14\u00E3n\u00B1v\x02j\u00B6^\x03'\u00D2\u00E8B`\u00C8=2\x06.\x7F\u00F3\u00BA\u00B3S\u00F3\u00D4\u0083ag\u00E5\u00D1\x05\b=\x17\u0098%\u00D0H\u00CB\u00D8\x1C\u00C3\u0080p\u00AD\x05\u00BF\u00D7\x15\u008F\u0090\x1E\t\x04\u00B1\u00B8\u008C\u00DDj\t\u00F0\u00BA=_\x12\u0091\u00F8.hY\u0089]\u00D0]\x17\u00B5y\u009Ex@z\x1B\u00DD\x15\u00DF\u0088\x14T\u00A1\u00DB\u00DE\u00E9aK(\x05\u00BB&\u00C7g\u00A8@\u00B9\u00AF&\u0081\u0089\u00EF\u009F\u00CA\u00A6\u00FB\u00FDFj\x12\u00F6\u00BC\u00A4(\x03\x1E\u00C3\u009E{\u00CBgM%BY\u00A8\u009F\u0097\u00BF9?\u00E8\u0081PaLZ\u00E1\u00D1\u00B0Q\x10(\u00EA\u008D\u00A1\u00F4\x004\u00E0\u00E8\u00EE\x05\u0086\u00AF\u00C7\u00CBo\u00AD2\x00\u00EF\u00F1d\u00C6e\u009B\u009F\u00F9\u00DD7\u00AF\x1Dz\u00C62\u00AC\x7FI\x15\u00D2\u00D2\u00D76d%\u00930\u00C5\x0B8u\n\u00E9\u00C8\u00A5\u00D0X\u00C8l\u00A1\u00B6\u009AE\x00\u00B2\u0097\x1C\x02M\u009F<h>\\\x00\\\u00D04<\u008A\u00F0\"\u00F8}\u00BA\u00E15\u0093\u00C0\x14\u0093L44\u0095\x032^\u00E4dA\u00F1\f&\u00B9f\x13\u00F85(\u00CE\u00E9\u00C0Y*\u00D8\u00E8\u008A\u00F5\u00AEX\u0083`\u00DE\x05kjX\u0082hf\x10fj\u0096tN\u00A1W\u00B3\u00A0D\u00C8E\u00BD\x0E\u00C2\x05\u00BB\u008D\u0088\u009C\u00BD\u0091\u009A\u00E8!\u00F8z\u00C7=\x17Y\u00CD<>-\x1C\u0080\u00D1\u00B3\f\u009A\u00A8\u008C\u00C00\x10Q\u00F3\x10\u00CC30\u00A0\u0091*bp\x014k\u00EFX\u0083\u00FA[\u00F5{h\u00F4P\u0090\u0082>\u00A3\u0087\u00D0\u00E3\u00ED\u009B\x07\u00E4\u00CF\u0095\u00DB\u00E7=\u00E5s\u0080oKk\u00CF\u00E3\x0Eu\u00DF>\u00DD\u00DE\u00E9^\u00CF\x00\x00 \x00IDAT\u009F\u009C\u0084\x00\x18\u00C19\u00A2Q\u0092NrS\u00DD?\u0095\x00\u009C\u009EN+\u00C3\u00F0\u0092\u00F3`E!\u00F0\u00EFR\u00C1\x1C\u00DC\x07-\x1C\u0086\u0092\u00AAF1.\x18\u00CD\u00DBY\x05Y}\u00D2\u00A2Cq\u00F81\u0087\u00DE\u00BA\u00EF}\u00FB\u009F\u0089\u0093\x18'\u00C4C8\u00DE\u00F8Gk\x17\u00BE\u00F4\u00BE\u00CB\u00B5E\u00AE\u009E\u00E3\u0082\u00DA\u00DC\u00F5\u00AF\u0099\u0082P\x06\u00B9P\u00EA\u00DF\u00DC\u0087\u00D1\u00D8\u008DT\x06S%P\u008E\u00C9\u00C53\u00F8\x10'\x0F\x14\u00A1\u00A9\u00834T\u00E3\u00C2\u00F2\u00BD\u00D22N4\u00BA+\x17\x02I\u00DD\u00CC\x0FIK\x00\u00F2\u009E' Vz\x17S\x1C\u0081q \u00B0\u00E8\u008A\u00EDn\u00F5\r\u00CBnU\u008D\x0B5\u008EYt4\u00F2Sv\u00D5\u00B85\u00B6u\u00CF\u00D2h.\u00F6Z\u00F2\u00EF\u00AC'\x00\u00B9\n\u00AC3\x00\u00D8M\u00A9\u00E6\u00F5\u00AD{\u0091:\x7F@\u00A3\u00CEbY\u00AE\u0081\u00CF\u0084\x19\u00CA\u00B1\x1CgT\u00A0\u00C3R\u00CD\u00C6\u00AB\x11_\t\u00C0+(\x15\u00D3\u009F>\u00BD\u0087z\u00AF\u00BC\u00CE\u00E8\x1EU\x7F\u00C6\u00E9\u00F7<V\u00E5I\u00F0\u00F9\u00F5\u00E3\u009D+\u00EA/4\u00CE]\u00AF\u00A9\u00FE\u009Ep\x14\u00FA\u00CA\u00F5i\nu\u00E0\x06uN\u00B9A\u00E0\n\u00D9\u00F5\u00DE#4\u00AD\u00FBW\u00A3\u00B5b`\u00C0PZs\u00FE\x00\u00F5xv\u0096\u00ED\x03\u008B\u00C5\u00C67o\u00BC\u00F4$D\x19\u0094\u00B4\u0093\x1A\u00CF\x1Ev]\u00F9\u00D4q\u00EFk7\x16\u00D6\u009AL\u009Ada\u0091\n\u00A6e\u00CB\u00ECe \u00F9\u00FF\u00F2S3\u00DE6\u00DC\u008B\b\u00CC`JY&\x01\u00A8>\u00ECI\u0098@+0\u0099\u0090\u009E\u009ErA\x11>tz\x14\x14\u00F8\u00D1[\u008AGx\u0080p\x1Dc\u0081\u0095\u00A2\u00E9'/\x18\u0098Z\x06?\u008FY'\u00EFu\u00A8\u0082E\u00B7~\t#\n\u00C1\u0087\u0093\u0096\n\u00CA\u00BD\u0094\u00DA+1\n\u0086z\u00AE\u0094\\\u00E3L+\f\u0092\u0089\u00B0\u0090t\x13-\u00E1W~\u0082\u00E8\u00A4t\u00EB\u00CD\u0085\u00ED\u00FEy\u00F6&pr\u00CE\u0098\u0082be\u00D8\be\u00ECd;?&\tS\u0095p\u0084\x00=\u00B34\u00DA\x15\u009Bf\u008F\u0088\u00C9J\u00D5\x14\u00FA\u009E\x02?!v\x01HR\u0090N\u00AF\u00ADk(\u00CFT\x06\u009A\u008A\u0096\u00C2>\x02\x18]\u00D1)&\n\u00A1\u00D7s\u00B9qQ\u00E5\u00CA\x14\u00F9\u00EE9\u00C7\"\u00E4\\\t{\x10j\u00C24\u00CBj\u00B3\x15\x14\x0F\u00B3*\u008C8\x07\u0087\u0094\x10B\x04\u00CBG._\u00BB\u00FE\u009Fv]y\"\u00B2{\u00BCq\u00D2!\x03\x00|Hu\u00DF\u00BF\u00D8\u00FA\u00EC\u0095\u00EF__^h\x12C:3\u008B\u008E\u008A\u00DEQS\x1C\x13&!\u009B\u0099\u00CA\u00AA~\u00E2\u00A2+\u00A9 \u00C8\x17G\b5L`+\u00D0B\u00CE\u0080\u00A7\u00C7\u00A6\u00F1\u00B3_\u009F\x0B9\x00LR\u008AT\x12\u00AE(\u00A84\u00E2<NJQ`\u0092\x1A\u0092z\x0E\u0086\x1D\u00D5\u009DT\u00EE\u008F\u0088o\x07\u00ED\u00D8\u00A7\rg+\u00B0\u00BB\u009B\u00DB\u00BB\u00A6FL2r\u0092Ze\u00BB\u0083\u008C\x02\u00A35\u00B7Q\u0093\u00CA\u00ACL\u00A1I(@*\u00A8A\u00E1\u00EE\u00AFZ\b\x00\u00C7\t\u00E85\u0095<y\x0BE\u0098\u008A\u00ABy\u00F8@\u0095-\u00F0\u00B0\x04\u00A6\u00BEg0\x07P\u00B4\u00D0\u00AA\u00D5B[\u00BA\u00EC\u00E6\u00C2\u00DB\u00C2,\x06N\u0096\x18^\x13\u00C4\u008A\u00B2\u00EF\u00CE7.Q\u00CF@ u\u00F57\x14\u00C1\x15\u0098\u00D1\x00\u00F0\u00D8\x05Le(E\u00F7?0\x1A\u00C0(\u00DB*\x18\u00C6\fC\u00E0\u00F7He_\u00C3\u0096\u00D6\u0093\u00E2M\u00CC%:a\u008F\u0089A\u0098\u00C6*\x1Eg7U\u0080Q \u00DD\u00975\u00E8bD'*\u00FE\x12\u008E\u00847\u00CB\u00FD\u00F9\u009Bb\u00C49\x0F\u00E0\u00E8}\x0E_\u00B7\u00FB\u00A7v_\"\u0097\r\x07\u008F+\u00A0'0N\u00DAC\x00\u0080G\u0089\x1C|z;\u00FBe\u00FB\u0096|\t9\u0085\x12?p\u0090P\u00ACDzZ\u00BA,\u00DE\u00BB\u0080\u00A3x\x01!\u00FC\u00CD\x18\u008A<\u009E?d{!R\u00C2\x02{\u0088\u008D\u0093\u0080Z;\b!\u0080\u00E5\u00E9\u008F\u00E3\u00DA\u00F7\u009Cp\u00D5\u00F2s\x02\u00B4\u00E8e\u0097aH\x02\u0080~\u00E5.d,&\u00AAu\u00FCV7!\u00A1\u00FC\u0097*\u00D8T\x0B\x03v\u00BA\u00FD^\u00AA7[U\u0096\x1C\x0B\u0096]\u00AD5\u00FB\u00E8n\u00B3\u00B2\x11*i\u00C1i\u00D1\u00AA+l\u00EE\u00BD\u00AFD\rZ\u00C5\\@%\u00D6R\x1Ck\u00B92\u00BC)\u00AAL\u00AD\u00A3{\x11<\u0087*\u00DD\u00FD\u00F4X\u0096\f3\u00E8Eh^\x0B\u008F\u00BBpO\u00C7<0\u00C5rDt\u0093\u008A\u00B5\x1B\u00FD\u009A\u0096\u009D\u00E1TV\u008AF\u00EB\u00FA\u00D1\x1B\u00C3\u008E\x1A%\u00DA\u00CB\u00DE1\u00F6\x11}\u00EC\x16V@'tk^\u00EF\u0092\u00D7L\u00CF\u00C8\u00BF[\u00F2\u009E\u00B0\x12\u00D2\u00C0\u00BD\x0E\u00D5\u00A0k/\u00E1+Oi\u00E9\u00FE\u00A4\u00D5S,\u00B1~\u00CC\u00A7t\u00FDWC\u0086jT2T\u00C0\u00B1\u00A1\b\u0095\u0085{:\u008B\u00D9\x02\u00CB'\u00F6\u0097\u009D\ne@\u00C9;%\u00E3FUy\u00E1\u00E65\u00EF\u00FC\u00DD\u00F5\u00EDK;\u00BCu\u00D3J\u0083\x12\u00B7q\u00A8\u00C2^C\x04\u0083M\u00ECs\u009D\u0084\x0E\u008E\x15\u00F8\u00FF\x02\u0094\u00D1\u00D4\u00AE\u00F9\u00B0\u00FCyF\ff\u00BF\u0089\u00FEWaO\u00B7\u0098\x1A\x17H '\u0081\u00C3\u00E9q\u00CA\u00B1\x1D\u0081\u008E\u0090\u00A5W\u00AF\x00\u0088\u0082\u0096\u00F2\u00B2\u00FB\x04\u00F5W\u00CC;\f\\\u00EC\u0096\u0082\\S{Bk\u00DD\x00\u00C49L\u00B9\u00CC\x14\u0098\u00B9\x15\x0B\u00F2O'\u00F8\u0095)8\x16.\rE1\u0091hD\u008F!\u00AC\u009E\u0093~\u00C8\u00AB\u009F\x14;u?\x16\u0098J\u00B4{\u00ADA\x1ES\u0089\r~\x1D\x0E\u00D4\u00F2\x1A-\u00EF\u00EF\u00C0\u00A4\u00A7kE\u00C5\u0080H\x07k\u009B+\u00CC$Z\u00B99\u00F0l\u008B\u00C4\u00FD\x12\u00A8,\u00DBQ\u00A9\u00C7\u00B6Z\u008A\u009D\u00DC\u00A1\x1E\u008D\x02^{\x1D4\u0094\u00F3\u00D4\u00E7\u0081L\u00DBFS\u0099\u00EE\u00E7.\u00DB\x13\x0F2\x0FF\u00D33\u00E9E\u00E8;J\u00E1\x12&\u0085N\u00D5\u00E2OR\u008D\u0095\u00AE\\\u008F\u00E5\u0099\u0099\u009C\u0084\u0082\u00EE\u00FF6\x1Fy\u00F4]{~\x7F\u00EF\u00DF\u0097\x0Bo}f\u00A1\u008E[\u009Dv\\\x1D\u00E7\u0089\u00E8ol\x1Dy\u00F1\x15\u008B\x1B\u00FF\u00E2\x13k\u00C3:S\u0088\u00B6\u00B0ju\u00FB\x07\x00\u0080h.#\u00AF0\u00D5@g\u00D1\u00A7\u008AoO\x1C\u00A2\n:\u0085\u008E\x05E\u00F9P\u00D9\u0094\u00B4\u0096\u009BN\u00E20\u0086\x07\u00FER\x02W`\x16\u0081\u00AC\u00C3\u00AE\u0088\u00BAz\u0086'\u00AA\u00EE\x06Z\u00B6\u0081<\u0086\x04+5\u00D1`\u009F\u00AC$\u0093\u00992\x00\u00A6\u00EE\u009F`\x1C\x15G\x15X\u00EB\x14\u009C\u00A4\u00DB\u0092\u009CC\x04\u009A\u00CDE+\u00C9\u0088\x1D\u0098\u0082\u00F8\x12\x13S\x03l\x1D\u00CB\u00F7\u00CD\u00D3f\u00DA\u0081\u00A6\rc'o?\u008F\u00CB\u00A0|B\u00BD\u00F5\u00A0\u00D5h\u00C8\u00F9\\\x05VF\u00DD\u00BA\u00BD*\u0092\u0092\u00C2\u00EAy\u0098\x12\n\u009D\u008F\u00D6\u00FF\u00DF\u0095\u00E9L\x04-\u00BB\u00BE\u00E6\u00A8u\u00F00mB\u00C2\u00F2\u00FB\r\u00BAs\u0080\u00A8\x19\x12\u00A47h;\r\u00DE\x7F\u0093\u00D7\u00CC\u0095\u00A78?\u009A\x1B\u0081\u00BC\x07z\u00BD|\u0087I\u00B5S\u009FO\u009C\u0083q\x1C\u00DE;\u00FF^\u00F1\x0624-\u00C6\u00C1o\u0098i\u00E1\u00C8Z\u00F9|V\u0097\u00F7 G\t\u00B0\u00D8?n\u008F\u00DF,/>U\u00CA\x008E!\x03\u00C7s6\u00CE\u00BA\u00E2Y\u00D8\u00FBc\x1BK\x00\u008C\u0093\u00A4\u00F6_\u00B40\u00A2a@\u0093\u00A1\u0084\t\u0099u\u0098b\x06\u00E9a0\u00BE\u0095N\u00A2IZ\u00B5\u00DA\u00BB\u00A0E\u00A6#'9=\x01\u00EE\u0093\x15\u008D^f\u00EA\u00FB\u00D3\u00D2X|\u00D8\u00C0.:A&\x19\x052r-\u0086V\u00E2K\u0099Z\u00A5n\u0084\x17\u00B3\u00C6\x0E\u00A8\u00D1\u00B2\u00F07\u00EC\u00DA\x15\u0082-(\u00B6\u0094\x19\x07kz\u00BADv?\u00A2\x0B;]\u00E6\x1D\u0093,\x00\x01?\x02rK\u00B5\u0086\u00A9\x0B\u0094\u00EDK(\u00A0\u00B1\u009F$J\u00EF\u00F7\u00C9NL\x04\u00F9\u00CC\u00CDv\u00D7\x1D\u00D6\u00CBa\x02\u00DA\u00B9\x10N\u00D7\u009C\u00C8\u00AAN.\u00C2R\u00AF\u0093\u008CG-?]5\u00DC\u00FC\u00E8\u0084\u00E4 \u00AB}\u00EE l\x00\u00AA\x1Aa\u0084e!,|X\u00F6\x1E\u008DX\u00A7\u009D\u00A6\u00B2'\u00E5\u00B2#\u00C0\u00CC\u009A\u00E5\u00B0\u00BF\u00F3\u00D8\u00B9N&\u00AF7\u00DF\u00A7u\u00D4f\x18\"Xj\u0086Y\x15\u00E8\u00A6\u00FD\x02\x10^J0h\x1DxL%\u00E2Y3\x0F\x19\u00B8\u0092Y\u00E0\f~\u00E0\u008E\x11;\u008F\u00DE\u00FA\u00B1\u00FD?\u00BA\u00F7\u008AS$\u00BE6'O\u00E5\u00C1\x00\u00E0\u00AFT\u00E7/\u00D9\u00BE\u00E9\u00FD\u00EF\u009C/\x1E9\n[\u00B4W6\u00E1qx\x04\u00A5\x1E\u00C1:\u00EFL\u00AD;5ih~j\u00D4\x0Et\u00A4\u00A6\u00D5\x00\u00FDP,T\u00D9\u00DE\x7F\u00F7\u0089\x16w\u008D\x1CV\u00D4\u00DF\x1E\u00E9\u00B1(\u00C7u\u00E5a\u0096\u00CD&G\u00E4\u00AF\u00FD\u00FA\u008C\u0080\u00A38\u00A6\u00F6\u00C1'\u00C9j\u00ECh\x16@\u00B1\u00A1\u0082}#\u00B0\u00A1\u0082\r\u00F5>\t\u00DD~7\u00B5pa\u00EE\u00AE\u00EC\u008C.\u00AA[\u00C9\u00F9\u00D2\u009FfO\u00E0\u008B!\u0083\u00A8s\u00F2\u00B9\u00BD\u00BB\u00B2\u00D5\u00CB\u00A0G\x01\x15\fK\u008D\u00EB\u0095\u00B1\u00D4\x0F !\u00DE\x01F\u00B9f\u009F\x06\u0081_\x1B$\u00F8\x06\u00AB\u00F5\x0B\u00B6\x1F\u00C2Z[\x1F\u00C9\fI\u0092\u00DA\u00EC3B\x11\u00E7&\u00DD\x19J\u009EC\u0086\r\u0080fX\u00C1\u00D0G\u0093[ \u009D\u00E0\u00A6i.\u00D6J@\x05k\u00ACr\u00EDy\u00CD\u00E4.0\x1Ce\u00BDC\u00E5j\u00B4\u008Aa\u00F1\u0099\u00FB\u00F3\x1C\u00C8\u00ED\u00E0\u00F5\u00F5\u00E2u\u00A9=\u00D3I\u00DDC\u0084\x12\u00F6\u00BD\u008E9\u00DF'\u009E-\x10\u00F3\x11\u00D2\u00B1y\u00C9\u0091\x0F\u00EF\u00FE\u00CF{\x1E'\u00DF,\u008B\u00BFM\x1EOt\u009C\u00B2\u0090\u0081\u00E3\u00C1\"\u008B\u00FF\u00B2s\u00F4\u00F9W.\x0F\u00BE\u00F7\u00AA9\u00E6!\u00F8\u008A\u00B2b\u0092\u00BB\x7F@\u00FC_\u00948B*\u008Ax\x00\u00F1\u00920\x11\u00EC\x10*d,\t\u008F5k\x05c\u00C5\x02&\u00DDp\u00F8\x12\"\x04IA\x0E\"\x13?sO T\u00952\u008C\u0098^\x13\u00DB{GM<\u00EC\u00DC\u00E1\u00A9\u00F8i\u0098\u00BD\x10\u00FF~\u0084Yu\u0082T\u0083\u00DAa\u0097\x14\u00E2\u009ETa\u008C\u00D3x\u009Bno^O\u00D2\u0099\x19\u00AAD\x15`\u00B9\u00D6\u00DAX$Z\u0096\u00F1Z{y\u00FE\u00AA\x1E\x1AH\u0084\x04\u0093\u00D0\x05\u00D99it\u00E5A\u00A4^a\x169\\\u00D1nX\u00C3\u00E8\u00D7\x16\u00F5\x05\u00BE\x7F,\u00CE\x02\x0F\x05\u0090\u00EF^|Nt\u0098\u00C5\u009E9[\u009FF\u0082\u008A@\u00CA\u00BB\u00E1\u00EF\u00DE\u00F33\u00DE\u00D7\x18\u00F7\u0087l\x13\u00E7.:\u00B3.\u009D\u00DFu*\u00DD\x14V>'\x1A\x18q\u00EF\u008FF\u0088\u0082\u00DFz\u00EEW\rD\u0086\x0B\x1AaB8\u00FF+\u00CA \u00E6\x0E\x14[\u00E7\x1E]\u00C8S\u00DB\u00F3O\u00B52\x00Nq\u00C8\u00C0\u00F1\u00C2\u00B5\u00DD\x1F\u00FA\u0096\u00BE\u00EB\u0095k\u009D \u00A2\u00AD\u00B7\u00D0&\u00DEA\u00F2\x0B\u00AC\u00FD\x1A`@d\n\u00AE\x1C\u00EFg\u00CC\u00BFc\x02\u00BB&\x0E\x16\x19y\u00E3\u00AE$X\x0F_\u00B5\u00BC\u00F6\u00EE8\x02\u00BFg\u0098\u0080|\x11.(\x15\u00C0\"\u00FA|<r\u0093x\u00BC\u0099EG99\u00E9\u0081D\u00BF\u00C3:1F`\x1C\x15[\u00AA\u0081\u00ACGSVE!\t\u00E9\u00D4\x15W\u00C4\x12p\u00C7\u0086\x0E\u00C5\x05F\n\u0082\x03\u00FC6\x1F\u00B9=VHP\u009D\u00C74\u00D7\u00DE\u0090~\u00C5r\u00EC\u0091\u00E9\u00C0\b\u008CK\u00B8\u00EB\u00CF\u0090C3\x1Cpa:\u00F6\u0087\u00CDUK8R\u00CE_\u00FF\u00D6\bA\u009CX5\u0096\u00B0\u0084\u00C6\u00A0\u00FCp\t:\u0086CyO\u00AE\u00A0;\u0082W\u00C1%\u00E1j\x16\u0084Y\x18.\u00CB\u00C6F\u00B0I\u00C6\u00B2\u00D0e\x1Ar\u00E51T\u00F3\u00BE\u00EB\u00B3\x185\u00C3\x12\u00BE\x07\u00E5\x01\x148^F\u0082#\u008C\x15L\x19\u008C\u00B2\u00C0\u00D1\u00C7\x1Cz\u00E5\u00EE\u009F;\u00FB\u00CB\u00EE\u0093x\"\u00E3\u0094\u0087\f\x1C\x1FS\u009D\u00BDt\u00EB\u0096w\u00BF}\u00AD=A\u00A5Y\u00BE\x1A4\u00BC\u00B5\u00A3\x11\u0087c\u00D4\u009A\u00B9\u00DC\u00E8\x17Y\u00B4i\u00F0\u00CF\u00FDAR\u00E3\u00D6\u009A\x04`\u00DA\u00BB +\u00CD\x00\u00C0;\u00DE\u00B8\u00E57\u00FE:{\x05\x00\u00D1\u00F7\u00A0x\x0Bm\u00B2?\u00D2DN\u00BC\x04f)r\x7F\u00D5\u00A95\u00A1\u0092c\u0098\u00A0Tf<?\x04\u00FB:\u008C\u00CA\u00EC\u00A1\u00C3z\u00F7\u00B6j\u00DD\\t\x16\u00FCX\u008B2\rD\x7F6V\x1E\u0082b\u00D6\u00B3\u008AO\u00D4B\u008E\u00D6\u00DDj\u00BA\x0B>_\u00DA\u00FDR\u00A1\u00B6\x0E\u00B4\u00A5\u0086%&\u008D\u0099\u00DC\x03\x16[\u00D1\u009D\u009F\u00A9`\u0090\u0092\u00F9@\u00F2\x02\u0086\u00F2w4x\u0081_3\u00B3\x11\u00B4\u00BA\u009A\u00D9\u0083\b\x1DT\u0093\u009E\f\u00CD,\u008A\u00B6\u00B0\u00A2k\u00A3=K\x16R!0\u00A0\u00F4\u0098\u00E2\u00FF=\u00C9a\u00E8\x02\u0095\u00CC\u00D0\u00D8sS\x10\u009C\x1E\u00BA\u0084\u00EB\x0F\x14O(\u008E\u0085r\u00ACr\u00BE\u008E\u00C825\x05\u00DA\u0092\u00F3/\u0097\u0096\u009B4\u00A8!\u0088\u00DDK\x18\u00E1\u008A\u0085\x18B\u00E5\x1Btt\x1Cy\u00C8\u00E1\u00F7\u00EE\u00F9\u00A5\u00BDO\u0092\u00AF\u0095\u00E5\t\u0088\u00E3\u0097=Ny\u00C8\u00C0\u00F1P\u0091\u00E5\x1B\u00FA\u00E2y\u009FYn~\u00F0\u00CA\u00B9\u009E\u009D\u00CEv\u00A5*#?#xR\u0084\x7F\"\u00EC>y\u00BA:\u00D3\u008B\u009AT\u00B2\x12\u00CE\u00F6\u00F1\u00E3\u00D6*\u00B4\x0E\u0098\u0092\u00D0xA\x14\u00C2\u00EE\u00BF\x03\x04*iC\u00BAp\u0091)p\x173\u00B2\x18n\r\u00A8\fX\u008D\x06\u0094}9\u00C9c;qE\u00E3\u00E7u\u009FX}\u00E2o\u00BA\u00E0\x0Fn\u0099\x07\x17\u00D8@\u00C4{*(\u00F1,\b\u0095^\u0084\t]\u0082\u00DA\x1B\n\u00B1kL\u00DAT\u00AEN\u00CEr\u00E5A\x0B\x16\u009F\u00F93\u00ECJw\u00D5\u00AC\u00B0\x02V\u00B8\u00EA\u00F7\u00DCk\x00\u00D83t\b\f\x07)\u00F8\u00D1\u0097B\\A!\u0089`\u0082L5*\u00A4\u0084,F\u0081\u00B6\x10\u00C2\u00F1\x1Ca\u00F6$[\u00A13\x05\u00D8\u00E9\u00F9i\u00A58O\u00E7\t\u00C3\x01zk\u00DC\u00C7\x04\u00DD\u00F8\x06@n\u00CBP\u0086\u00EB<D\u00F8\u00EA\u00CF\u00BD\u00C7v\u0099\u0091\u00EA\u00E1\x1D\u0092\u0091\u00C8\u00FBp/\u00C4\u00B1\x14z\u008D\u009D\u00CFbr\u009D\u00AE,\x04\u00D8\u00BC`\u00E7p\u00FB\u00B6\u00D9\u00F3n+e\x00\u00DCF!\x03\u00C7\u00F3\u00DA\u00FC\u0093\u00FFP\u00DBw\u00EF]v\u00A86t\u00E7 d\u00C5\u00A2)\x07f\x00\b\u00BCU\u00D2\u0091\u00BDD\u00CD\x17\u00A0\x12\x02\r\u00D0\r/\u00F1\u00BA\x02Q@\u00E2\u009E@-@\u00AA\u00AE\u00BA\u00F6\x16\x02$t\u00F5\u00FD\u00A7\u00E6\u00E4\x03{\u0088\u00FD`\u00EB\x10\x04XG7S\u00F2\u00FA\u0098\u00B9(\u00FB\u00D52\u00EC\u00F0S\u00C1\u00FB\x15`\x14\u00EC\u00A8`\u00A7{\u00D8\u00E0!DE\u00C6\x17=\u00DD\u00EF$\x05\u00C1;9\u00BBP\u00BB\u00EBonrG\u00AC\x7F\u00D03\u00E41\u0097:q\u0096\u008Et\u008F{\u00D9\u0096n\u00EF\u00B2\u00AB\u00AD)\u00E1J\u00C3B\x17\u008D\u00CCE\u00D4\x1Dhn\u00C3\u00DB\f\u00C5\u00A1\u00B9\r3\b\u00B9\u008C\u009CN~/\u00BBb\u00A1\x1A\u0099\u0083\\\r;\u00D7\u00A5X\u00D6c)\u008E\u00C9\u0098L\u00B2\x03\f\u0085\u00F8\u00CC\u00EA\u00F5\u00F6\u00BCft\t\"W\r)\u00F8\x7F\x1A\u0081\u00B1\u00DCk\u00BE\u008FZ\u008B\"\u00E55K\t\x192\x1C\u0089\u00F7\u00E1\u00D3 C\u00CCcA\u00C5\u00ED\u00F5m\u00E0\u00EF-\u00BE\u00FB\u00EC\x1F>\u00EB\u0093\u00A7L@\u008F3n\u00B3\u0090\u00A1\u008E\u00E7\u00EE\x1C~\u00C3o\r\u00F2\x1D\x0BY\x07 \x13\u008A@\u00E4\u00A7)<@\u00B8O\u00B5\u00F5y\x05\x05iU\u00A8\u00E5\u00CD\x05v\u00F7\u00BF\u00A2\u00B6.p\u00E6v\u00B5\u00CCR\u00C4\x04(\x00\u00A2z>\u009CV\u00A9\u00E7\u00DFD\u00E0#\u00DB\u00E0\u0096(\x1A\u009A\x14\u00AB\x1B\u00C0\x10\x15\u0089j\u00B8\u00B3\u00DA}\u00A5g\u00B0\\\u009Ai,\u00C0L\u009EY\u00B15U\u009C=\x02\x1B\n\u00EB\u00AA\u00D4\u00D3\u00BD\u009DuC\u00F4\x07\x18]\u00D9:+%uwp\x0F\u00A3uX\x7FD\u00BA\u00C1c>\u00C7\u00EC`\u00ACAUV\u009F\u00D9m\x14\f\u008E\u00A5p-D\u00D1\u00CC,\u00CC\x15IM\u0086F\x06\u0082\u00FD\x14\u008D\u00ACna\u00C1\u008C\u00E7\u0081\u00B9\u00E4\u00E1\u00C2{\x16imi/7\u00C3\x04\u00C1L\u00D3\u00927d\u0086 i\u00C2\x12\f\u00D4\x19\u00AD(\x18\u008E\u00AC\x10\u008B:\u00C3\u0081\u00E9\u00F9\u00E9\u00C50\u00A5<\u00F4\u009CO\u00B1\u0096Ex\\\u00C9\u00E7\x10d\x17%\u0086\u00B4\u00ADO)\u00CF|\x07@\u0086\x12\u00B5B2B\u00B7\u009E-\u00F3\u00F8\u008Cj\u0088R\u00A9\u00CB\u00A3(\x0E\x7F\u00ED\u00E17\u009E\u00F3\u00EE\u00BD\u00CF;!\u00C1\u00BB\x15\u00E36\x0B\x19\u00EA\u00F8W\u00F3\u00B3^\u00FC\u00D9\u00A3G\x1E\u00FB\u009E\u00F5\u00FE \x1Df\u00E1~\u00CAD \u00FD\x01\x14\u0081\u008A%\u00D1\u00FDe\u00A8\u00C9}\u0080v\u00DD_\u00BC\u00C2\u00E22z\x17&l\x1D\x19&4\x10\u00F5\u00B6\u00EF\u008B;\u00C8\u00F3\x15mL\u00AB\u0096D\u0093\u0095\u00EB\u00F4c\u00F0\u00DA\x18J\u00A4G\u00A0yl^\x7F\u00B7\u00C9l\nM\u008A\u00D2h\x06r\u0096s,\x14\u00D8D\u00C7L\x1B\u0096\u009C\u0098>\u0081:\u00CC\u00FAL\x14O\u00F9;p\nZ\x1E\u008F_\u00B53\u00BE6\u00EB\u00D8 \u00C17\u00E8\u00BCV\u00FE\u00BF\x1C\u0087\u00E4,q%\u00BC\x14\u00AF\u00C3\x00B0\u00FDQ\x068I\u00AF\u008EY\u0099\x16\u008A\x02\t\u00AA\u00C1\u00EEk\u00E6\x19'\u00C0\u00DE\u00E7RS\u00F0\u00C5-.\u00DB\u00A5Y\u00C8 \x11>VRQ5\x0E\x04\x7F\u00C5\u00CF\u00A3\u00FE\u00BE\x18\u00C2\u0091u)\u009D\u00DEN*\u0081t\u00D5\u00B3n\u0085\u0099\u0086\u00C8\u00C8h\u00F1|4\u0085\u009D^%\u00C3\x07Z\u00FFZc\u00D1\x03[r\x03\x03\u00F8j\u00E0\x00$C&\u009B\u00E8@oKl\u00DE\x7F\u00F3\u008A}\u00FFr\u00CF\u008B\u00F1\u00EE[#}'6n\x17\u0085\u00F0u\"\u0087\x7Fe\u00B1s\u00D9\u00F5\u00CB\u00F1\u00CF\u00FEZfg\u008D\u00E4\x19\u00C4\x0BP\u00B0\u00F5\u0098\x14\u00CD\u00C8\u0087\x1E3\u00A6hb0\u0096\u00F4\u0097\x17}\b\b\u00EE\u0085\u00DB\u00E5\x0F\u009F/'\x00I\u00A4\u00A7@\u0081\x02\u0092J\x1A\u00E7\u00D7\x10\n\u00F8$\u0099\u0080\u008BeB&\b\u0084\u00D8?\u00D9\x7F~\x1BJ\u00EFG\u00E2>\u00AA\u00A2\u00A1\x17\u00B4\u00A3\u0082-L\u00EB\u00FE[U\x1C\u00DA]\u00C1(\u00E0\u008D9\x06\u00BF\u009F\u00A5\u00D2\u009B\u0090P6\u00E1\u00C6\x17w\u0094q,\u00C3%\u0082\u00AA|\u008E=\u00C22o\u0094\u0082d\u00E9Q \x15\t\x1A\u00F2s\n.i\u00C2\u00E6r'\u00AD\u0099\x19\u0082\u00DE\u0081\u00D11\u00A0\u008A\x1B\u0090?\x10\u0082\u0085\x14n\nce\x05N\x18\u0081\u00E4\u0086\u00F0\u00F9#\u00BDB\n}\x02\u00C79\x07\u00EA1L\u00A1I\u0080\u00B044\u00AD\\;\u0085\u009Fn\x7F\u00E0AZK\u00E0\u00D3\u009B\u0088g\u00DE\u0099X\u00D7\u00C8@\u0091UY\u00E7\u00BB\u00DD\u00ABb\u00FB\u00DC\u00C5\u0091\u00E5S\x16\u0097\u00C9s\u00E5\u00F0\u00DF,a\u00A7n\u00DC.!\x03\u00C7\x0Fnm?\u00E7u\u00D2\x7F\u00FD\u00C6\u00F9FX\u00B5\u00C0\f\\\u00F8d\u00B4K\u009A(\u0083\u00D5\x1F`j\x195_\\Xh\x07\x07M\u00D8[\x1E\x0FSl\x00e\x7F\u0081B\u00DDm\u00A7\u00F5\bBIy\u00A1q}\u00E58i\u00A1\u008BBb&\u00A2O\u00CF3Qvq\x1F\u00EA\x12F\x0Bh\u00C4\u0099\u00B3\x15\u00D8\u00A5\u008A\u008D\u00D1\u009B\u00AC\u00AA-\f;S\u0089\u00D0 \u00C2\u00841\u00D1\u00FA\u00E6U\u008E\u008D\u00D5t>\u00F9\x06'1\x11\u009C\u00D4n\u00A1E\x00\u008C\u00DA\u00CC\u009A\u0095n@<\u00AE\u00887\u0080\u00D5\u00CC:\b\u009C(\x05\u00ADkrg}\x054\u00DB\u00B1\u00FBs\u008A\u0090E\u0081Ah\u00B1\x11\u00A1C\u00A0\u00FFE\u00A0'\u00D5\u008E\u00E5\u00F8\u00F1\u00B9\u0083y<6\u00CA\u00FE\f%\u00F8^\u00E9\u00FD\u00CDz\u00BE\x076\u009A\u0099Tb\u008Ei,\u009A{}\fE\x18:@\u00E9=tW\x12-\u00CE3x\u0096LF\u0086oTh\x06(G\u0095\u00AAf\u00D8C\x1EK\u0083b\u00B9k\u0081\u009D\u00A7\x1F}\u00EE\u00BE\u00DF<\u00F77n\u00BD\u00D4\u009D\u00D8\u00B8]\x15\x02\x00<\x7F\u00FB\u00C8\u00CF\u00FE:\u00D6\u00BEw\u00C4,d\u0086\x16i\u00E2~\x13\u00A1\x05Bp&\u00CB\u0083\x15aLP\x06\u0091t'\u00BAkB\u00DC\u008A@j\u00E0\x11t\x0B\u00AD-\u0098\u009F\u00D3\u0089\u00FA6\x11<\u00EE\x0F\r\u00EE\u00A9BZ\u0098\u00E3\b|]\u00E3\u0081\x16\u00825\u00FF\u00C7x:\x1E\u009F*\u0089\x02\x1DY2\u00EB\x13\u00DE\u00D6oP[\u00C3A\u00815\u00C7\x00\u00E6H\u00C1\x1F\u00D4\u00D2r\u00C3\b\u00B0\x0BsS\u00EF\u00BAT\u0094&\u00D4R\u008D\u0099\u00A5\u00F0\u00F3,M!\u00D4\u00C5C\u0086\u0091\x1E\u0083F\u00B8bBo1|\u0093d\x1E\u00CE\u00BA\u00E1\n\r\u00885\x17\x06%k1k4\x04H\\\u0081\u00CA\x01S\u00C1W\u00E4\u00CAUA`\u00D7\u00F4\b\u00A2;SG4\u00A6\u00AD\x05R\u00A1H4\u0085\u00BBi.e\x17)Cn\u00E7\u00F3mV\u00B8+\u00C1:,\u00EFp\u00D2]\u00C9\u009F\u00B5\"=\x05\u00D1\x1E\u00CF\u009E\u00F8R%\u0083\u00D5\u00B4%\rY\u00AClU\u0094W\u00CC\u00F96b\u00F3\u00D2\u00A3\u00AF\u00DA\u00FFG{\u00FF\u00DD\t\t\u00D8I\u008E\u00DB%d\u00A8\u00E3%k\u00BB_v\u00DD\u00D1\u00AD\u0087\u00BD}\u00C07J\u00B7\u00D3'\x1BP\x01\u00C6\u00D7!p\u0098\u00B8\u0086\x15\u00B5N\u00E5\x01D6!\x18a\u00C7\u00D2\u0098\u00A3\x1E=\u008E\u00EFB^\u00D2C\u00D9\tI\u00D3\u00F2\u00C3<\x03U\u0098\u00C0\n\u00DD\u00C5\u008C\u00FB'\u00E9\u00A5r\u008DUq\u00F0\u00B0qo\u00DC?\u00E8\u00AA\\\u00C6\u00DB\u008E?*\u00B0\u00A5\u00E6-4\u00A4\x0B-=Kn-\u00A4\x10g\u00C9\u00E9\x04\u008Cb\u00E8P'!3\x05\u00E1\u00CAr\u00E1\x11\u00AF\u00924\u00CCAb\u00DD\u00C4\u00C0\x12\u00EC\u00AA\x02=wq0\u00D7\u00B6\u00E7k`\u00EB{\x03N1q\u00DF\u009B8k\u00D1\u00DFE\u008D\u00FF\x13W\x02\x14\x12\u00E4\u00A9\u00DA\u00E0&R\u00BB\f\u00954\u00D7\u0086\u00ACJ/\x04]3L\u00E0\x1C\u00C1X\u00D6\u008EPs\u00F7!EI\u00F2Zy\u00FF\u00FE\u009B\u0085i\u008A\u00A2\u00DC\u008B\u00B0\x0F\u00DA\u00B28\u00AB\\3\x15}4\u0084\u00F5w\u00D3\u00D4l\x17\u00D9\u0098\x13\u0082\u00BF(\u00B6\x1F\u00BE\u00F3\u008Es^\u00B5\u00E7ex\u00C4\u00DF)R\u00A7t\u00DC\u00EE\n\u00E1Q\"\u00E3\u00DBT\u009F}\u00F3\u00E1\u00CD\u00FF\u00FB\u0081\u00D6.\x01$\x1E\x12 \u00FE\u00F0s\u00A5\u0087hP\u00C2\x17\u00EFnX\u00C4\u00DD\x10D\x1FEmeR\u00C81\x02\x1A\u00AC\u00C1\u00F0,\u00F2\u00E50\u00A5\u00A9\u00F5E\u00FB66\u00D15C\x04\u009E\u00DA5<\u00CA\u00FE\u00D3x\u00B6\u00A4<y\u00FD\x1EVT:sN\u009E\x04\u00BAx\x1D\u008B\u00B1cP\u00C1\\\r\x0FX\u00F85\u00AF\u00F1\u00FE+8V\u00EE1Ro\u009A\u00CDGxL\u00F2\nH\u00A9%\u00B8%\u00CE\u00D8\u00E4\u00C4fZ\u008D(x\u008F\u00DF\u00EA\u009C\x01\u0085\u008A\u00E3\x0B\u00DA-\u00CF\u00E3\n,dA\u00DD\x13So\u00E7\u00EE\u00CF\u00A6Y\u0080\x16B\u00AF\u00AE\x11\u00D8<\u0095\u00D9\x19>\x0B\u0086\n\u00CB\u009Ea\u0081\u00A5\x05{\u009C\u0093\u0095\u00A2y\u00BF%\u00C3\u00E3/\u008D^\b\u00A7E\u00BC\u00BF\u00FA\x1E\u00FC\u00DE\u00C7\u00E2\u00E1q\x15(f\u00B4\u0080\u00F4`b>h\u00C1\x18\x14\u00A1\u009C[7\u00E6\u00A7\u0091\u00E0\u00FC\x19z\u00A6F\u00CBo\x00\u00E8}\u00C4\u00E2+\x0F_\u00B9\u00EFE\u00BB\u009F-\u008F\u00B8u\u00AD\u00D4Of\u00DC\u00EE\n\x01\x00\u009E&r\u00F3\u00AB\x17\u00DB\u00CF8tt\u00FB\u00CF>\u00D66\x0E\u00F0%\u0085\u00E0\u00A2Z\u00F6\u00D2\u0085\u0086\x13$\u00B6\u00D5\u00B0\u00C4\u00B4x\u00B5 )\u00ADr\x11\u00C0\x12f\u00C4q'\x1A_CYh9G\u0086\x19+\u00D6\u00C8\x15\x05_\u00F6$\x05\x19\u00D7\u00AFay\u00C8w\u00AF\x13PBy \t4\x11\x12\t\u0096\u00A3b\u00E1\x13)]\u00DE\u00AC\u00C3\u00E7\u00FA\x0B\u00A1\u00D8\u00FA\u00D4%>\u00E6\x1E\u00A9\u0098\u00A2\x17\u0080\x04:\x1F\x18\u0088\x13\u0093\b4Z\u008D\u0082#\u00EE*\u00DE\u00EC\u00C7\u0098\u008A\u00BD\x03\u00A1\u00F0\nV0bZ\u008A\u00DEt\u00F5=\u00DA\u00E7,~\u00A2\u00D5\u00B6\u00F7;}\u00DEI\u00FCq\u0090\u00D1\u00FF\u008E\u008CMy\u00E7\u00E4<4'-Qqg\u00EF\x0BW\u0088@d<\u0096\u009A \x1FT\x03\u0093H\x0F\u0086\x041;\u0099\u00FAs\x1F\u00E8\u0089N\u00DE\u00A9\u00960\u00C1\u008D\x12\x14u\u00DE\u00F1\u00B8\u00EE\u00A0\u00D8\u00B9\u00A4cy\u00AF\u00AD\u009B\u00E4\u0099\u00B3g\f/^\u00BB\u00F9\u00C4%\u00EB\u00E4\u00C7\x1D\u00A2\x10\x00\u00E0%\u00F3\u00F5O\u00FD\u00A7\u00AD\u009Do=\u00BC\u00B9\u00FC\u00C3\u00AB\u0086\u00D9F\x15\u009E\u00AC\t\u0097I\x18\u00C0\u0087m\u0093\u008FT\u00E3d~U%\u00C1c\x01\u00B0\t\u00E1\u009A=\x15\u0086\u00BB\u00C8\x13\u00C4\u00B9f\b4\u00CEy\u00BC\u00E2$(_6{(\u00F8\u00A9\x03\u0084\u00AC\u0093D&\u00C4\u00A6\u008ADW`r\u00F2\u00D3\u00F3X]\u009B)\x04\u00B5\u00DC\u00FF\u008E\x0Bt\u00E3*K\u00CA\"\u00A8\u00E3\x1C\u0097n/\x05h\u00AC\u00CA\u0082J\u00A0\u00D6kHxC\u00D1\u00ECe4\u00C1\u00E3\u00E3\u00A5@\u00AA\"V\u00C3c\u00A8\u00C7\u00EF\u00CC\u00BF\u00CB\f\u0082\u00F8kc\u00F8Q\u00F1\u00A1d$\u00A6\x17\x12\u0082\x03D\u00E8U?\u00D7r\x1E\x14%BA\u00E5\u00FD\u00A8\x7F\u00CF\u00D0#\u00C2.Me\u00AC\u00D0\u00C8\x14X\u00EC\u00EF}%}\u00DE\r\u00AE4l.\u00E5<\x0BP\u00B1\bx\u00ADUi\u00E5\u00BD\u00B7\be\u00F3\x19\u00D9\u0083H\x0Fa{\u00EF\u00E6\u00D6\u00DAS\u0096\u00DF\u00BA\u00FF\u00E7\u00CF\u00F9\u00D4\u0097#C\u00B7\u00C5\u00B8\u00DDA\u00C5\u00D5\u00F1\u00FD\u0087\u00B6.\u00FB\u00E5\u00A5\u00BC\u00E9\u00A0\u00CCm\u009D\u00D1\u00D1\\\u00C9I\x1F\u0082x\u00F1\u009A\x02\u00DD;Zw\u00A7\u00AD\u0082|\u00A5\u0096`\u00D5\u00AA\x0B\u00DC\u009A\u0085\u00D0\u0090R\u00AA\u00A1\x1CZ)~\u00A1\u00D5;f\u00B9-N\x06W2\u00ADX%\u009D\x1C\u00FF\u00D8\u00BF'\u00BF;\u008E\u00F1(2\u00BB\u0091\u00F1\u00A8\u00AA-\u00CD\u00BE\u00A6\u00C0n\x15\u00CC\u00D5\u0097\x7F\x1B\u00B9X\u008B\u00F74\u00EC$)e1W\x05\u00C2*:.\u008A\u00E0\u00DA\u00B3i\u008A\u00A8\u0081\u0094\u00EA\u00CAbX\u00A6\u0095c91\x17ba[\\\x02\u0085\\\u00C8e\x10\u0099\u00D46\u0088f\u00BBvZ\u00E5\u0086\\\u008A\u008E\u00E0\x1AC\x02\u00DBOJ\u00A9\u00F4\nP\u00E8\u008AAx\u00DD\u00FCL\u00E9A\u00D1\u00DBI\u00F4>j\x19\u00C0X\u00BE\u0086\u008A\x1A\u0099\u008D\n(\u00D6mf|\x0F\u00DD\u00B2;Tj\u00CC\u00D8X&\u00C6\u0095\u00DF\u0098\x00&\x17\u0096\u00D5r\u008D\u00F5>\x04\u0096e\u0081*\x16g\u00ED\u00F4\u00E1\x1F,\u009E}\u00D1\u00E5{.?I\u0091:\u00A9q\u0087+\x04\x00x\u00E1\u0097\u00B6\u00BF\u00FB\u00F2e{\u00F5!\f\u00E9\t\u00C0\u0080.s\u00B3\u00BD\u00E0\u0086BJ\u00A1\u00EC\x00\u00AAu\u00F6mk'\u00A5t\u00913\u00F4\u0098\u0084\x02L\r\u00ADz%\u008AH1\u00D6\u00F4&\u00B1\u0081\x10\u00F4\u00C2YXm\u00B0\x1Ay\u00FD*\u00F4+\u00C7Ze9\u00C66=\u00F3\u00DA4Om\u00EC\u00D8\u00E8\u0082]\x00\u00D6\u00BB`}\u00CC\u0095\u00A2C!t\x16>\u00D5\u00F0\x02\u0099\u0092\x1CY\u0091\u0089\u00F81\u00A5\u0090?P@GE[d\u00A3R\u00AE\u00CD\x10\u00A4$\u0098\u00A0\u00CF\u00D0r\u00B2\u00C3W\u008Bv\u0090\u00CEX\u0089\u00DE\x10\u0096\n\u00C1\u00B7#\x0BQ\u00B4d\x1E\u00E0\x19\x12\u0094^\x04(\b\u00BF\u0094\x15\u00AE\u00B9?\u00AC\x1E\u0080\u00AC\u00C2X\x03\u0092\x1E\x0F\x18\x16\u00A5r\x10?\x16\u0095\n\u00C0\u00E7u,\u0087\u0080d.\u00BE\u00F3\x19\u008B\u00AB\u00D4\u00FB4\u0094U\u00B7\u00C9n\u00E4\u00DFdM\u0082\u00D7\x00\u00DEg^[\u0083\u00A0\u00AF\u00ED\x00\u0097\x1E}\u00C9\u00BD\u00DE\u00B6\u00FF5\u00A7B\u009ENf\u009C\x16\n\x01\x00^t\u00CB\u00E6\u008F\u00BC\u00E1\u00A8\u00FC\u00C7\x1D\u0099\x03hnm]h]\u00E3G\u00E5\x17\x1F\u00B43\u00C9\u00C2\u00E5W$\u00E8X\x05?\u00D2i\u009A\x02\x16\u00F1&\x17T\u00B1s\x19kM#\u00ED\x18 b(!)\u00D7\x04\x18\u0093\u00AD\x1C\u00D7'\u0098)\u00A6\u00964l*\x11fS\u00FC\u00D8t\u008B\u00E9E\u00E4\u00F2]~\x7F\x00\u00D8[AF[\u00EAm\u0097\n6\u00C4R\u0090\u00EBN\u0085\u00AD\u009E\x02\u00C3\x03\u00AB\u0086\u0094\u00E8\u00B3h\u00FC\x02\x13\u009A\u00A1TG\u00D2#hK$\u00FBpt\x0FA\u0081X\x17\x11\u00DE=\u00D9\u0085w\u00A6\u00E6\x11\u0090\x1A\x1C\u00A9G\u0094&&\u00C8\u00CA\u00C7\x14bL<\u0084\u00B0\u00FA\u00FC\u00BC\u00E7\u00B6L\u00F9\u00F2\u00BBJC\u00B6\u00FD\u00F3\\\u00D0\u00E9\u00F1\u00AB+?I7\u00F6\u0082\u00E8\u008F+\u00DE\x06\u0089CE\u00B0M\u00C8\u00ED\u009D5\u008F\u0093\x04l\u009E\u0092\u00FD\x17\u00E3<\u009DJ\u00C8\x19\u009E\u00C8P\u00C9\u00B2\b\x12\u00F7\u00A0\u00C3\b\u00BDt\u00F3G/~\u00C7\u00BEW\u009C\x021:\u00E9q\u0087a\b\u00AB\u00E3\u00C7\u00F7l\u00BC\u00E2\u00D0\u00CE\u00E1}o\u00DEi/YHKk\t\x14a\u00AB(~\u0092\u008D\"\u00C5\u00E4\u00FFW\u00D4\u00CF+O@\u0082\u00F1\x16e\u00BF!\u00F4\x068\x11\u0095\u00AE\u00E0cZ\u008C\u00FC\u00DC\u0080.\x05J\u0096\u0084\n\u00C2\u00BC\u0085\u00CCC\x13S\u00C8\u00D0A\u00F2:QB\u0083h\u00B2\u0099%\u00AFZ\u00D8\u0089\u00B6\x00\n\u00B0\u00ADe;\u00AFu0\u00DE\u008B\x1D18\u00FE%nm^*\u00CC~\u008A\x1D)X\u00E2iO\u00A3\u0082k2\u00F4 N\u00DE\u00E9\x01\u00C4\x01\x1E\x1B\u00FB3dzp\x1E\u008AQ!\u00AE_\u0089\u0098\x11$n\u00AE\u0088#{\x00\u00A0\u00C9\u00B4\u0084=>\u00E7\u00ABG\u0089\u00FF\u0081\u00D8\u00D6\u0096Z\u0093\u00C9<!\x01\u0096\u008B\u00C8\u008E\u00E5\u00DD\x05\u00B0\u00D8\u00ED\u0099\x1A\u00CC'\u0091\u0089`\u00DF\u00C9\u00CA\u0094\u00ED\u00A1\u0098\u00E1\u0098\x05\u0095t-\u00A5\u00F6K\u0098xZ\u00CC\u008C\x15\u00E5\u0094\u00CE\u00A6\u009F[\u00D1\u0087\x1D\u00B4'\u00ED\u00BC\u00FA>\u00AF\u00DF\u00F7\n\\\u00F4\u00E5H\u00C9m?N\x1B\u0085p\u009E\b>\u00AD\u00FA=;7n\u009D\u00F5;\u00DB\u00ED\x05#Z\bRX]\n\u00BC\u00CF\u008C\u0089;\u00DEsB1\u00EE>&\u00B3\u0080\u00F4\x1A\u0098R\u00C4X?\u0087\u0083K\u00C7Q2=\x15\r4\x17\x04\u00A9^G\u00E0\x18\u009A.y\x02\u0096\u00B9\x7F%)\u00D5\u00ED\u00ECo\x07HyM\u00BC\u00EF\x0E`4%\u00B4tr\x14\u00BB.\u008F\u008E\u00EC\t\x10\u0098K\u00F3\u0090c\u00E8Rp\r\x16\x0E\u0089O|\u0086\x04\u00F4V$B\"[\u008CE\x03\u00EB\u00B0\u0094\u00A2\u00ED\u00CF&\u00ABK8\x1E\u00E0\u00BC\u0081P\u00A6\u00C8\u00F6e$\x18(\x15\u0096\u00AA\x0B\u0097\x1Dz\x06\n^\n\x0F\u0095\x11\u0090\u00EF\u008E\u00E0!\u008Fk\u00AE\u00BD)N\u00AB\u00F0\u00B40 C1\r\u0081\x1CF\u00D8\\(\u00C7Su\u00E2TyG\u00CD\u00EB8\u00F8^\u009A\x1B\n\x05\"$\u00B1\x05]\u00EDb\u0082\u00A7\x10\u00FB\u00C3\u00B1$g\"\u00A24^u\u00D0U\x01H\x13`Xb\u00FE\u00D8\u00E5\u00AF\u00DC\u00FB\u00F5\u00FB\u00BEG.:m\x1C\u00F5\u00D3G!\x00\u00C0\u00FDD\u00F4\x13\u00AA/\u00C2\u00F5\u009B\u00B3\u00B7\u00EC\u00AC\u00FD\u00B3Q\u0087\u0089P\u00860u\u009F0\x13eP8\x07\x00&H>\u0090\u0093\u008A\u00E9\u00BD\u00C2O\u00E0\u00F7S \u00CF\u00AD\x050\u00C9\x04hy\u00C1\x14\u0094p\u00FD;\u00A2b1\u00EA\x17\u00A8m\"\u00FCI\u00A6\u00A3\u00E1\x04\u00D3\u00FB`\u00B8`\u00BB\u0099\u00B0NR\u0095]\u00C2\u009A-4\u009B\u0090p1\x17\u00E6\u00BCSY\u00B2\u00CE\u00C3\u00EEw\u00E6\u00A5\u00C49y\x19#'\u0088\u00DB=\u00AC!\u00EE\u00C24\x1A\u00DB\u00A7\u00C5\u00F4us\u00DEDaEGt\r\x10\u00D9\x18\u00EA\u00C8 \u0091\x05W\u0081\u00E8\u00BD\x1D-:L\u00F3=#\u00AD\u00BC\u00BA\u00F0\u00C7\u00F9\x19\u00EA\u00F8{k\u00AE\u00EC\u0098\u008D\u0088\u00E5\u00E74\u0081F\u00B6@\u00B3y\u0090\\\x0BhR\u00AD\u00BB\u00A6W\x11s\x00\u00A9\u00B4\r_\u00EA!\u00D8\u00AD\x18+\u00DE{\x16|\u00D1\u008B\u00F2g\x03x\u0093 {\x18k\u008F\u00DA\u00FE\u00B5{\u00FF\u00CC\u00DE\x17\u00C9W\u00C4\f=-\u00C6i\u00A5\x10\x00\u00E0\u0081\"\u00FDJ\u00D5\x17,\u00AF;\u0082?\u00D8\u00DA\u00F8g\u008B>L,\u00FCd\u00B1\u00CB\u0089\x15\u00972\u00F9\u00D3\u00BD\u009B\u00E0\x0B+J\u00A3\u00BA\u00AA5\u00E7\u009C_f\u0098\u0090\u00C7\u00D1\u00DC\u00AE\u00B8\u00E4\u00D5KY\u00BD\u00B6\u00C95\x04\u00FB\u00D2\u00FB'\x14\u0092R\u00E0\x14\u00A1\u0090|?\u00BA\u00FBE\u00B8G\x05\u00B6\u00DD\x1BI\u00A5S\x14\x1A-\u00BB[|z\x19\u00F4\u0080jX\u00D3\u00BBD\u00F6\u0081\u009E\u0085\u00B8\u00E2\x19\x01_,\u00D6n7\n|\u00F8l@\\6\u009F\u0097\x02\x18]pjHe>\u00BD\u00DD?\u008B\u009CF ,wM\u00E3\u00C5\u00A3.\u009Fu \u0084\u00D1X\u009A\u00C5\u0093\u00A2\x07\u00E6\u00F7\u00DD\u00F8\x1DX\u0081\u00E9\u00D6\u009A\u009EB=n}\x7F\u00F1\u00DE40\x1C\u00E2\x0E\u00E9\u00B1\u009AR\u0089\u00CAN\u00BF\u00F6\u00DE\u00F9\u009D\u008D\x0EWr>UZ\x17,gK\u00CC\x1E{\u00F4\u00D7\u00BE\u00EAU\u00FB^ _\u0093\x1C\u00D0\u00D3e\u009Cv\n\x01\x00.\x11\x19\u00FFJ\u00F5\u009F\u00CF\u00AE9\u00B2|\u00EB\u00D1\u008D\x17,\x1C\u009C\u009B\u00F4\u009E+\x13(x\x0B\u008A\u00A8E\u0088\u00859B\u00D0r\u00FF*\u00A0\f=\x18&\x103\u00E0[\u00D4\u0095s\u00C5Lu+\x1C` \u0090@ \x05\u00CEc\u00D5\u0089\u00F5\u00E7D\u009Bx\f\u0088I\x18\x1Dr\x14e\u00D5\u00E8\u00B4fp\u00D0\u00B3wk\u00FB\u00CD%\u00CB\u0088\u0092s\u009DG\u00A6\x1C\u0087\u0091\x16\u00DA\u00CEC\u00F6\x1D\u00A0\u008E5H\u00C6\u00D8\u008APR\u00B4\u0088\x00\u00A2\u009E\u00BFAc\x01Y*h+k\u00EE\u00FE\x7Fs\u0095\x15F)\x1E;\u0082\u0081G\u00EC\u00C6\x18zL\u00ED\u009A\u008E`\u00980*\u00CA3\u00F6\u00FB\x06\u009FM\u00E8\u00E8t\u00C3Kz\x11\u00C0D\u00A0\u00B9\u009Dm\u00A6\u00A9$\u00CB\\!+\u0096\u00FBV\x12\\cM\f\x10\rZ9\u0087\u00D8\u0099\u00B9\u00BB\u00C2\u0093\u00F2n#3\x05\u0080\u00AB86X\u00EB4\u009Dob\u00FE\u0098\u009D_\u00B9\u00E4g\u00F6\u00BD\u00E8tT\x06\u00C0i\u00AA\x10\x00\u00E0\u00C1\"\u00FDj\u00D5\u00EF\u00DA\u00F5\u0085\u00C3G\u00DErd\u00FD%Gd\x0E\x00\u00C1\u00AD\u009FZQ\u00BE\u00C8\u00ACy\x0F\u00A1\fSC\u00F4\x1C9\u00E1\u00A0E ]1\u0084\u00A0\u00A2\b\t\u00D2}t7\x1E\u00C0D\u0090\u00A3n\u0081\u00D7\u00E4\u00E7\u00B1\u00D3\u0088#\u00D3\u00F99S\u0092\u00AB\u0093{r^*\u00BB:\u00D1\u00FD~\u00F9=\u00BB\x15-YX\x13}(\u00AD\u00E0i\u008Cs\u00E5\u00BD\x18*\u00EF\u00E7Z\u00DA\u00BD\u00F7\u00A2\x18\u00E0\x1EB\\\u0087c\x17\u00BC\u0084\u00C8\x0E\u0080:\u00C3\x1A\u00AA\u00F0\u00B6'\u00E9XI\u00AF\u00AB\u00C3\u0094V,\u00D9\u00A3EX\u0091\u00F7\x1Cy\x7F R\u0085\u00A3k\x03k\u00A3.q\x1Ef\x0EB\u00A1\u00F8>ld\u00CA\u00FD\u00ADn\u0081\u00CA\u009F\u0082n7\u00C0\u00BA\x02\u008A\u00B2({*d\u00D1\x14i\u00DCCxY\x1A\u00A1\x0F\u00FB)\u0088\u00DA\u00C9D\u00E9=\u00D9\u009DvU\u00B4\u00D9\x0Ef\u008F\u00DFy\u00F5%\u00BF\u00B4\u00EF{\u00E4!\u00A7W\u0098P\u00C7i\u00AB\x10\x00\u00E0^\"z\u0093\u00EA\u00BF\u0099}a<\u00F8\u00DB\x07\x17\u00FF\u00F1\x16\u0099E\u00E3O\x00\x13W\u00DE\u00F4mZh s\u00EA=P}M\u00ABW'\u00AD\u00BA\x00(\u00AB\x19\x01\u0086\x06\u00D5\u009B\u00E0\x04\u00A5`\u00AE\u00B6U\u00CB\u00F0\u00A5\u00DC\x043\x1B]b\u00D2\u00F3\u00DCU\x19To$Zz\u00D5T\u00A5\x0B\u00F3\x04XuA\u00D9!\u00FB\u00D0\u00D3}v\x1C\u0089u\x10\u009B\u00DF\b\u00C9G\u00A3zO\u0088\u00D5\u00F5\x19\u00E2Z\x10a\x06\u00E3o\u00ED\x02\x11\u008D\u009A\x06>n\"\u00FBl\u00B5\x1E\u0082\u00C1\u00EF\u00B9m}vq\u00DF\u00E6UPY\x11w\u00A0+\u00CFc\u00D9\u00BB%(g\u0099\x10\b\u00A2U\u00BA\u0096sq\u00DB\u00C6k.\u00F3\u0084\u00CF\u0087\u00E7'\u008E\u00A0\u00E5\u00BC\n\x03!\u00C7\u009E\u00A9\u00C4j \u00B8O\u009C\u00CB\u0081\u00CC\x16\u00D7\u009F|\x0B^\u0094\u00ACw\fO<\u00F4\u00A3\x0F\u00FA\u008D\u00F3^!\x17\u009E>\x00\u00E2\u00F1\u00C6i\u00AD\x10\x00\u00E0\u0080\u00CD\u00B8W<\u00EF\u00BA\u009Box\u00DBM\x1B?\u00F7%]o\x14\u00F0\u009CX\u00EAVS\u00FF\u00F5_]\x00\x00\x17\u00F9IDAT\u00F2\u00ED\u00BAPL\u00B6\u00C3\u00CA\u00FF\u00EB$r!\u00AD-\u00D4\u00E8Y(_.\u00B2\u00ECZ'B\u0094\x05F5\u00AC\b\u00F7\u009F\x16\u008F\x13k%\u00F4\u0099\u00AC\x05A\x0B\u00AF\x1A\u0093p\x12\u00C6\x14e@\u0080t\u00A9\u00B6X,\u00AD\u00F9\x1ASe\u00BE\u00AD5\x01\x15\u00B4\u0091\u00F8@GG\x0BO\u00C0\u00D6\u0082t\u008B\u00D7\u00BD\u00A0\u00CC\x19\x7Fl_\u00AE\u00DD\u00B2\t|<K8\u00F1\u0086R\u00EE\u00D8\u0081\t\u00B1\u0086pwL=\x05*\u00E5P\u00E4R\x14\u009D\x1F\u008A\u00AF\u0083\\\u0083\u00AEi\u00BD\u00B5\u00D3\x13\u00B0\u00FBi\u00E5\x12\u0082e\u00E8!\u00D5*]\x19\u00AE\b\t\u00C4\u00F6\u00E3.\u008Ab\u00BD\x17\u00D9\x1Eo\u00A6\u00C8\x0ERT\u00A2H\u00EF\u0084J\u0084\u00AB\u008A\fPp\u00E1AQ@\u00F6l\u00F6\u008D\u00AF\u00DD\u00FC\u009E\u0087\u00BF\u00ED\u00FC\u00D7\u00E0\u00C2\u00BFs\u00BA\u00DF\u00E1\u00E3\u00F4VW+\u00E3\u00E5Wo^\u00F6\u00EB7\u00EB\u00EB??\u00AEo\u0090\u0087\u00C0:\u00FE\x10p\u00C6\u0084@\u0096'\u00BB\u00B0\x05\u009E\x10\x02\u00CA\u009C\u00B8\x1FC*\u00E2_\u008E\x0B\u00A40\u00AAZ&\u00A1\u00A3\u009C\x13)\u00A8\u00DCF\u008B\u00DB]\u00B6\u00D58\u00AE\u00FA$N\u00A5\u00C1\u00B5\b\x00\u00BF\u00DE\u00BA\u00BCx\u00D9_\u00D8~<\u00BC\n\u008Bk\u00D7\u00BAb\u00AD\x1B\u008B1\u00DA\u00B3w`>&+p\bwW0_$\u00F9\u0086\u00CC\u00CA\u00B6D(\x06\x02\u0092\u00CD;\u008C\n\x043\u00B6\u00F9\x12o\r\u008FB\u00F2Q\u00EF\u00B9\b\u00F6D\u00D0d\x1F\x164?[\u00B2#Rs\x14\u00B2\fG$HIl\x04c\u00C0\u00A0\u00C4\x020\u00B6\u00F2R\u0092\u0094jk4R\u0087\u00E9\u00F2\u00B3\f;\u00FB$:\u0089\u00ABg\u00F8G\u00A5\u00D4\x1C\u0097\u0099\u00B0\x1By\u00AC\u00CEtd\u0092\u00A6\u0082<\u00C5s\u00B9\u00F2k\u00FB\u008El\u009D}\u00E9\u0091\x7F\u00FA\u00F07_x\u0087\u00D2\u0091Od\u00DC\u00A9\x14\x02\x00\u00BC\u00FC\u00DACOz\u00F3\r\u00ED\u00B7?\u00B5\u00D88\u00A0*e\u00E9+Mp/\u00C8A\u009A\u009E\u0082\u00BFD\x19i\u00C9\u00B5\u00E0\x05\u008E\x0BHZy\u0089NCi\u00D9\x05fU\u00A8\\\b\x0B\u00A5\u00AB?U\"\u0093~\x02.\u00E8:\u00F2$\u0098(\rZ\u00E1\u00C0;|RO\u00C3\x04E\u00AC\x10\f\u00B7\u00E6\u00B1\u00F8l\u00AE\u00DD\u00B0\u00E1\na\u00D6\u0081\u008D\u00A2\f\u00B8\x04\x1C\\1\u00CC\u0096\u00C5\u00AA2L\u00F0&+V<\u00C5\u00CF\u00F8l\x053\u00B8K,y>*\u0084\x01N\u00A1\u00F6YE\u0081d5#k!\x06\u0099\njU\b\u00F3\u009E\x7F\u00F3\u00F3`'\u0082M]\u00CD\x06\u00AF\u0095\u00DE\x0EA\u009D\u00EE^c\x11J$\u00EF\u008F\u00D4kT\u0085@\u00D4\u00B3\u00F3<\u00A9$\u00EB\u00B2r\u00F0k\u00E5\u00B3\f\x05\u0081\fw\u00E2Z\x05\x18/<t\u00D3\u00DE\u00A7\u00F6o}\u00F4\u00AF\u009Ds;tB<u\u00E3N\u00A7\x10\x00\u00E0g\x0Fn\u00DD\u00FFM\u009F[\u00BE\u00F5\u00C3G6.Y(\u00EB\x1F\u00B2\u00A6\u009Fn\\6(Y\u0089\x05\u00B5\x14'\x15R\u008E\u00B9\u00B9\u00DE\u00D8c\u00C5\u00BB\b`\u00B1\u00A7'\u00C0\u00C5U4\u0094L9\x17\u00BD\u0082\u0091\x15\u0085\x00S\u00A3\x00\u00F7\u0097\u00B2~\u00C4T\u00A1d\u00E7\u00E9\u009C\u00A0\u00DA\x11\u008D\f\u00ED\u00BC\\\u00ECEC\u00F14\x00\u00BB\u009C\u00BD8/\u00B5\x0E\f\t\u00B8V\u00E1\u00AC\x1B\u00A5\u0099\u00DC~\x02am\u00F4\x02\u00A8\u00D2pE\u00DCCh\x10/^\u00CA8\u0099\x1E\x02=\u0083\u0099c\r& \x12kLL\u00FE\u008F$\x185)\u0082\u00DF\u00FF&\u0085\u00A0Q<%\u00B0f$\x02\x07\x1F{\u00CF\u0085Z:=\x05\x0B\x1F\u00AB\x05\u00E7\u00FD\u00C7\u00FD\u00C2\x00J6X\u0081j\u00D4\u0081\u00E4\u00FA\f\x14|\u008D9c\u00CA\u00AF\x15E5U\b\x02\u00C5p\u00F1\u0091+/x\u0086<\u00E3\u00C1\u00AF9\u00FB\x0E\u00ABZ\u00BC\u00B5\u00E3N\u00A9\x10\x00\u00E0\x7F\x1E\u00D5\u00FD\u00BF\u00FC\u0099\u00AD7\u00BD\u00F3\u00E0\u00F0\u008D\x0B\x1D\u008C_\x00)18B\u00D0B@\u0081@\u00C4\u00A8\x104\u008A\u00A3\x14\u0088\x14\u00D2\u00B4\u00DB\u00D2$\u00CC\u0088\x10\u00C0YkhSP\u0090\u00C5R\u00BE\u009Dj\x0F\u0082O\u00C6\u00AA\u00D9\u00DC\u0094\u00EEzz\x19\u0092\u00D7Lo\u00A5x\x10R\u0094\x18F6YEx:\x02`\u00BD[\u00CD\u00C3Z\x07\u00E6]\u00AC\u00B7\"h\x15]\u00E8\u008B\x17@!\u0081\u00FF\x7F\u00E8~,\u00B6v\x1B\u00CD\u008D\u00B6\u00B6h^; \u00C5\u00A2\x02\u0098Ac}o\x13Pq\u00E1e\x1D\u0083F\u00ABv\u00B6M\u009B\x15\u00E1\u009Fx\bj!\u00C9P>\x1FT\u00DDKH\u00EBl!G\u009B\u00B4Q\u00A7\x07\u0093L\u00C3\u00A9\u00E0\u00D6\u00B6h\u00ACFl\u00E59\u00B3\u00FE\"i\u00CC\u00A6\x10\"\u00E4\x00\\\x1D kF,\x7F\u0082&K\u00AC=\u00EC\u00C8;\x1E\u00F8\u009D\u00EB\u00CF\u00BE\u00E8\u00DF\u00EE\u00BEC\u00FA\x19\u009C\u00EC\u00B8\u00D3*\x04\x00\u00F8\u00A0\u00EA\u00F0\u0093\u009F\u00D9\u00FA\u00E9\u00B7]/\u00DF{x\u00B1\x06@&\u00D6VV\u0084+R\u0088.\u00D0\u0093\u00BC\u00B1\x0B\u00AA\x05\u00F2\u00E4\x17\x14\u00B0/\x14\nR\x00\u00A9H\nEZ\u0099\u00FB\u00EA\x05\x1FP\u00B2#\u00BDwB\u00D7\u0089\u0092\u00A9\u00FC\u00F9\u00D5\u00AEM\x01,v\x05z\x07\u00D7\u0097Hfa\u00D6\u00E5\u00F3x\u00B3nK\u00BB\u00CD}\u0082ot\n\x0F\u00AD\u00AA\u00F5\x10\x14\x05\u00DAR}\u00CD\x066\u00F5\u0090P\b\u00F5Y\x0EK\u00B3\u00FE\x112\u00C0\u00AB\x14Qp\x04\n\u009C\u00A4`\u00B1z\u00B1\u00A9\u00C6\u00DA\x0E\u00EC\u00930\x1F33\u00C1T\u00E4\u00DC\x15j\u0093\x04\u00FF\u00A2h\u008Ba_Q$3L\u00DDx*\x11\n+\u00F7\u009F\x14\x1F\u00D5\u00FFs\u00BB\u00A2d&\u00E9\u00CC\u00AE\u00A1\x042{P\u00CF\u00E5+J\u00ED\u00DA\u00C6\u00FE\u00AF9\u00FC\u00AA'\u00FE\u00E4y/\u0093\u00C7\u00DF\u00FE\u009D\u008EN\u00D5\u00B8S+\x04\u008E\u0097|\u00F6\u00D0s\u00DE\u00FA\u0085\u00D9/\x7Ff{\u00FD,\u00A2\u00DA\x13\u00E6\x1B\u0095\x000\t\x19\u00A2G>\x15\u0087\x7F\x1Fi\u00B0\u00A8<DZ\u00F7\x1A\x1E\u00F0<\u00DC/\u00BC\u0087\u00E2\u00F2W\u0090\x10\x05/\u00F0N\u00C7\u00F6]6x\t\u00E5\u00C5\u00E3D\u00C1\x13\u00CF'\u00A9\u0094\u00E850\u00B5\x19\u00F8\u0083\t\u00F7Z7\u00A5\u0090\u00EBCf'\u00A0\u00A1x\x033\u00F6Sw\u00AF\u0083\u00DFE\u00C8\u00E3\n\u00A3)0\x0F\x0F\u00C1\u009B\u008DR\u00F8\x1D\x17\u0098\u008F\u00A6\x10R\x19\u00F0\u00EF\u00EE\u00DEE\u00BA\u00F2\u00F3\u00DE\u00AD\u00C3\x12\x12p\u009Cw@\x1D\u0094\u009C\x05\u00DF\u00C3Vz\u00E6~\u0083\x16.\u0083\u00DF\x17y\"\x14~\u00AE\u00E3\x00\u00AC(\x04\u00CD{\u0089\u009E\tE\u0099\u00B0d\u00D9\u00C2FSb\x00\u0095\u0096\x1F\u00B3\u00A7\u00A7\u00D2\u00DA\x12\u00B3\u00FD[G.x\u00F2\u00F6w=\u00F6\x7F\u009C\x7F\u00BBuG\u00BE\u00AD\u00C6]B!\x00\u00C0\u00CF}q\u00EBa\u00AF\u00FF\u00D4x\u00F9\u0087\u008F\u00CC\x1F\u00A4:\u009F\u00BA\u00EF\u00A3N\u00AC\u00BDW\n\u0098\u00D0BV\u00B2\x11\x1A\u00827\x11N*\x14\x12\u008E\u00FC'\u0094Fx%\bkO\f \x04\x0B\x00\x17x\u00E5JI\u0093\u00C2\"*\u0084\x10\u00F8\f%\u00C2\x0BXN;N\x13\u00DC4\u00AF\u00A7\u009Brq/\u00A7u\u00C1\u00C6\u0098\u008A\u0081V\u0096\x15\u008D\u00CD=\u0089!\u00C2(S&\u00C3h)=fHH\u00B42\u00CC@\"S\x10\u0099\x04\u00CD\u0086)\u00D9\u00A2}U! \u009A\u009Ed\u00F6@#\u0086\u00E763\u00CF16\u00D5\u00E8\u0093`\u00F8\u0082\u00E4\u00F1\u00AA\x10\u00C3\u00DB\u00A5\u00A3X|5\u00EF\u00AC\u0082\u0082\x15\u00A7\b\u0085\u0080T\n$/\u00F1\u00FA\u00BB+\x0E\u00B6\u0095\x17\u0098\u00A2\u00E3\u00FB!\u00C8\u00B9v\u00FF\u0083W\u00DC\u00F7i\u00F3\u00CB\x1E\u00FA\u00EA=\x7FyJ&\u00F2\x1D<\u00EE2\n\x01\x00\u00FE\u00F7B\u00CF\u00FE\u00D9\u008F\x1D\u00FA\u0085w\u00DD\u00B4\u00F1\x1D\u00B7\u00F4\x19\x02\u00C4\x1B\x11\u0084&3\u00CA\u00DE!\u00A9\x03\u0082\u0096\u008C\u00C6b\u00E1\u0083W\u0080\"p\u00C4\x05P0\n\n\u00AB\x0Bo\u0086\tU\x19\u00F8\x0E\u00C5s@\u00F7\u00B5!\u00A5M\x04\u009CJ,\u00D6i\u00A7\x07SHJ5L\u0088\u00A2\u009E^\u00F7\u00F1\u00FDF`\u00CDC\u0087y7\u00EB\u00CD\u00CC@\b\x14ADNtv\u00FC\u00F1g&.\x18\u00FC{\u0086bM\u0091B^\u00B3\r\x12\x16\x1E\u00C7\u0082\u0089U8\u0091x\x02\u00E3yz\x1B\u0083+-\u00EE\u00DF\u00CAv\u0091*\u00D5\u00F4*\u0080\u00A2\x10x\x1E\x7F\x16\u0091\u00EA<\u00C6C \x00Y\x15\t\x1C\\Ne\u00C4\u00B0\u0088^\x01T\u00D1\u00D6vp\u00EEW\x1F}\u00E3W\u00BFt\u00FF\u008B\x0F|\u00FB\u00ED\u00B3\u0088\u00CA\u00ED1\u00EER\n\u0081\u00E3\u00A5\u009F<\u00FA\u009Do\u00F9<^\u00F3\u00FF\u00B67\u00CE\u00A6\x15\u00AF\u00BD\x01\u00D2\u00B5\u00B6\u00EDW\u00DBj\u00C7\u00DFE\u00A0\u0093c\u00E0\x0F\u008D\u009E\x02)\u00C7E\u00A1P\u0091T\u0097~\u008A\x17\u00F8u\u008C@\u00E0\x1E\u00FD\u00D8\u00F3S\thm\x1D?vpm\u00888\u00D7\u0088HY\u0092\x12\r\u00CF>\u00C8h\u00AE73\x0E\u00F3\u009Em\u00C0\x18O\u00CF\u0096\u009Aq\u00F4\u00D2-\u00F1X\x04\u008A<\x04ZL5\u00F7\u0099\u00D8\u00C1\f+\u00DE\x00hm\u00D5+\u00FC\u009C\x0BQ\x05\u00DB\u00EE|B\u0083\u00E6o\n\u00FD\x04\x13@\u00F9\u00BF&\x1F\u00C1\u00C2\x03{\u00DC5\x14 \u00EE\u00C0\u0090\u00AEr\x1F\b$\u009A\"\u00C8\f\u00CB\u00F1\u00D2\u0088@*\u00B2A\x15\u00DA:\u00DA\u00B9\u009B\u0087\u00EF\u00F1\u00A4\u00ED\u00EF~\u00F2\u00FF<\u00EF\u00BF\u009D\u00E4T=\u00ED\u00C6]R!\x00\u00C0/_\u00B7|\u00C0\u00AF\u00FF\u00F5\u00D6\x1B\u00DE}\u00E3\u00DA\x13F\u009D\u0085\u00B55,\u00C1-\u00A9\u00F3\x0F\u0092\u00FC\u00A3\u0089'\u00ACX\u00F8\u008A\x1B\u00D4\u00B4bd\b\u0082;\u0090\u00DB\u008A+\u0092\x001\u0091\u008A\u0089\x1EB\u00ACVCE\u0080<N*\x1D*\n\u008D\u00EB\u00E65t\x0F-\u00B2\u0083S+\u00DC\x07\u00C3*\u00B8(\u00EC\u009AZ\x1F\u00C6A\x0B\u00B8\u00E6\x18BcX0\u00A6\u00B2`\x11\x0FF\u00CD\u00AEKZ\u00C3\x02c\u00E6\u00B1=Z\b\x0E3\x10\u00DD\u00B1\x05\u0098[\u009F\x19\b\u00EE\u009F\u0096:\x17l\u00F1\x15\u00A9\\\u00A9\u00D6~\u008B\u00E1! \x15\x02\u00F1\x10\u0085\tw\u00AC\u00D0\u00A4\u00C0|\u0094\u0088\u00F7\x1B\u00D2\x13\x10\u00CD>\x05\u00D5[R\u0092\u00AE\u0090\u00A1\x03x\u00CD\x00\x1A\u00B6\u00B0\u00EBA\u00B7\u00BC\u00F7\x01\u00DF6<\u00EF\x11?z\u00DEm\u00BA\n\u00F3\x1D5\u00EE\u00B2\n\x01\x00nP\u009D}\u00DF\u00C7\x0F\u00FF\u00C0;\u00AEn\u00FF\u00E1\u00BA\u00CD\u00B59tf2\u00E7\x02\u00CE\u00BC\u00BE\x1CO\u00A0};\u00A0\u0084\t\u009E^\u00A40\x06\u00BF\u00A0\u00B8\u00AC\u00BD\u0097\u00E3\u00AD\n4\u00A6\x1E@\u00BA\u00FAT2=\u00F0\u0087)x\u00A8\x01v\u00C2\u00BD\u008A\x00,\u00A9\f\bL\u00B2\u00D6\u00A3S!e\x0F\u00C59\x14\u00EBKKCJ\u0097X1\u009A\u00B8\u0082t\u00B16jc\n\u00B4:S\u0091\u00D7\u00CD\\\u00BD\u0088uIb\u00CFDZ\u00FF\u009AV\u00AC`\u00E3\u00CC\u00D3\u0096)\\\u00CE\u008B\u0080L\x04\x1E\u00F0%\u00EBz\x11H\u00CD\u0098\x7F\u00E8\u0092<\x06\u00F7\u00A8\"\u00E4P\u00A3`\u00DBwb\u0085P\u00BA\x12\u00A6(\u008F\u00B9\x12&(\x00\u0099z\"\u00D6\u00EE\f\x10\u008C\u0098\u00ED\u00DBY\u00EC\x7F\u00C2\u00D1W>\u00FDG\x0E\u00FC\u00B8<N\u0096'=9O\u00D3q\u0097V\b\x1C?\u00F5\u00D9\u00A3\u008Fz\u00CBU\u00FD\u00BF~\u00E0\u00C6\u00B5G\u00F6>\u00CF\u00EE\u00C8$\u00F90\u00D5\x17\u00C2\u00AA\u00A8\u008B\u00AE\u00AC2\x11\u0089\x01\u00D4\u00DC\u00FF\u0094\x15\u00A9\u00A1\x10Hf\u008A,Fa\u00D7\u0099\x05\u00F7\u00CF\u0095\x1C\u00FA\u00E45\u00D0\u00C3\u00A0\u00F5'M\u00B9\u0086\x14\u00E4!\x18\u00960\u00CD@\u00ACfI\u0086n\na\u00DE\u00E9\x19 \u0080\u00B6\x16\u00D4e\t\u00E5\x10k\x1BF\x16B-\u0095)m\u0082\x11Tl\u0080L\u00C4\u00CAQ\u00E0\u00B6\u00C1+\u00A8\u00FB\x00\x13\u00AB\u00CE\u00EF\u00AAg@\x16\"\x15B+\u00E7k\u00FE\x1E\u00B2\u0083sV\x1E\u0092;0\u00A10k\u009EG\u00FC\u00B8\x11>\u0094\u0085g\x07\u00D8\x1F3Yb\u00EF\x03\u00B6>|\u00CF\u00A7.\u009F\u00FF\u00A4\u009F?\u00F7C\u00A7|r\u009Ef\u00E3n\u00A1\x10\x00\u00E0}\u00AA\u00F3\u00D7\u00FE\u00F9\u00C1\u00EF\u00FF\u0093k\u00D7\x7F\u00F0\u00AA\u00A3\u00EB\u00EBY\u009C$S\u008F \\w\x14\u00C1C\u00C4\u00E9\u00AB}\x15\x01W\x14\x10T\u00A6a\x05\"Y\u00E84\u00E14\x14A\u0095n=\x04\u00C2\u00D2\x17\u00A1\u00D61\u00AB\x14\u00B9\u00EC\\\x1E\u0087\u00A9KR\u0098\u00CB=\u00B8\u00D0O\u00C0\u00CF\u00AE\u0098\u008F&\u00E8\u00A46\x1BkQ\u00A39h[z7\u0083n\u00EB@\x06\x0E\u00E2\u00CAf\u00EE\u00AE\u00BF\u00C8T!DCUMjr\u0082\u008A\u0096Q \u00A5\u0098.9=\u0089\u00B9[gvB\u00AA\x1EF\u00C4\u00F5<NG\u00F4P\x18\x14Q\u00EE=\u00F8s\u009C\x00\u0091\u00FE\u00FEV\u00BD\u0081\u00BA\u009E$\u0090\u008A\u00878\x03`D\u00AE\u00E1\u00C0\u00C1\u00ED{>Z\x7F\u00EC\u00E9?p\u00EEO\x0E\x7FO\x16'5\x01\u00EF$\u00E3n\u00A3\x108~\u00ED\u00BA#\x0Fz\u00FD\u00C7\x17\u00BF\u00F0\u00DE\u00EBw]\u00BA=\u00CE0\u00F6\x16a\u00C4\u00B4W\x02\x02\u00B4\u00E3:\u0082(\u0082\x01wc\u00ED\u00CF\u00A9e\x060\u00C9XT\x1E\u00C3jQT4Q\u00ED\u00A4!\u0097\x10\u00C3\u00BD\t\u00BA\u00B9q-%L\u00806\u00AF\u00E7\u00C8\u00FDB\u00D9uW\"]\u0081nB>\u0090{\u00D0\u00D3\n\u00CF:0\u008C\u009EI\u00A0@\u008DV\u00EB\x10)Wz\b\u00A3\x1CCH2k\u00EF\u0099\x05\u00CB\u00DB\u0080]\u0099i\u00A5\u00C5\u00D3\u0088\x03$)\u00CB\u0098\u00A6\u00FA\x02\u00F4\u00C3\u00B1\u00C0b\u00F3d\u00B1-\x02+\u00A1\x1C\u00E8m\u00CDz\x12\u0099\u00C0\u00EBQ@\u00A4\u00B6s/\u00FC\x03\x14O\x01E\u00B9\u00A9B\u00E6Gq\u00EEC\u0097\u00EF\u00BA\u00E4i\u00FD\u00C5\u008F\u00F9\u00F1s\u00AF8\x15\u00F3\u00EE\u00CE2\u00EEv\n\x01\x00\u00BE\u00A8*?\u00F1\u00B1#\u00CF\u00FB\u0083\u00BFn?\u00FD\u00F1\u009B\u00D7.\u00EC:\u0084\u0090O*\x1C\u00DD\n\u009B[_\u00C0>n\u00AB@\x10\u00A1h\u00991\r\x07\"\u009E\u00D7\x12>\u0084\u0087a\u008Af\u00B5\u00A4\u009A\u00CDM'\u00A5\u00B9\u00A0\u00C7\u00D1#$\u0091\u00DE&\u00DEDz\x02\u00C80#>\u0097\b#\b\u00C4\u00CD\u00C7$\u00E3\u00CC\u00D8\x15i\x04\x00\u009D(\x04*\x1Cx\x18\x11\u0085L\u00A8\x1EBw\u00901\u0089C\u0095\u00AF\x00\u00FF\u009Bt\u00E5\u008A\x03$\u00BBP\u00A7t\u00E5n\u00DD\u00A1b\u0089w0\u00F4(\u00ACAw\u00EF\u00E8\u00F5\u00A4\u0090'\u00EE@B\u0091\u00F8\u00B5\x05\u00B7\x01\u00F6\x0E\x1B\x18\u00E6t\u00EC\u00BE\u00F7\u00E6u_\u00F1\u00B5\u00CB\u0097=\u00FD\u00D5\u00E7\u00BCA\u00CE\u00E7Y\u00EF>\u00E3n\u00A9\x108\u00FEd[\u00F7\u00FD\u00E2\x07\x0E\u00FF\u00D0\u00BB>/\u00FF\u00FA\u00BA\u00C3\u00BB\u00E7Q\u00D3O\u00CB\u00CAJ\u00CAb\u00E9\x05\u00C8\u00AC\x00$\x1B\u0088v+t\u008A\u00B4\u00A2\x0Ba\u00E57\u00B0Y)\u00AD\u00ADhK\u00A0\u0090\u00FB\u008C+a\t*\u0086\u00D1\u00CB\u00B1\u00E4\u00B8\u00C0c\u00D0\u00B5\u00EB\u00825\x04\x05\u00A9\u00A4\\A\u0091\u0097\u00C0\u00D4]\u00EB\u00C0\u00B0T\u00A0[\u00FD\u00C5|l\x19~\u00F85\u00B6\u009E\u00A1A-}\x1E\\\x00\t0r\u00C5\u00A6Y\u00B0\n\u00CDk\u0098\u00BB\u00CB\x1Fk5(\u00A6\u0098\u0080\u00D7\u00944X\u009AT\\\u00F1\u00D6\u00E5\u00E4#EI\x0F\x04\u00CC\u009A\u00E8\u00E4\u00FC\u00C2\u00CC\x0E\u008F/\u0080h\x0B\u00CA\u00B1\u00A83%\u00A5cc\u00FF\u00F6\u00E2\x1E\x0F\u00DF~\u00ED\u00D7\u00BFp\u00DF\x0F\u00DF\u00FB\u00B9\u00B3\u0083\u00A7|\u00B2\u00DDI\u00C6\u00DDZ!p\u00BC\u00F6\u00FA\u00EDK~\u00EFC;?\u00F3\u009Ekf\u00CF8\u00B2\u00B3\u00EE\x0B\u0082f\u009DCm'\u00C6N\u00C9\u00C7\u00C4\u00F3>q3\u00AD\u0088\u00E47L\u00C2\x07\u00AE\x07\u00E9\u00DD\u0097+n\u00A1\u00F0\u0096f\u00E9Q\x00\b,\u00C1x\x06\r\u00AC\u00C1\u0098tm\u00A6w\u00C0\u009E\u0092\u00A5\u00ACz\u00EAAx\x1B\u00B1\u00D1\u00AC*\x19\u008B\\\u00E8\u00A5-5\u0084kXz\u00B1\u00D7\u0098@j\u00AC\f%iy#\u00DD\b\u00B7\u00F2\u00C5\u00A5O\x0F\u00C1\u00DA\u00A4\u0093Dd\u00C0\u00A1\x06\u00907\x14\u0085@\u00AB\x1E\u0085N\u00C8\u00F0\u0081J \u0084\\\u00A7\u00DBP\u00B9\u00F9z\u00CF\u00B1\u00DA\u0093(\n\u00A5ZC\u00F1\f\u00BB\x0E\u00E3\u00A2\u0087\u00E2\u00AD_\u00FD\u008C\u00E1\u00A5\u008F}\u00C5YW\u009E\u00D2\u0089u'\x1Cg\x14B\x19?\u00F1\u00C9\u00A3\u00DF\u00F0\u00FBW\u00F4\u009F\u00FA\u00E0\u00D5\u00F3\u00C7m\u00F6\x19b\u00E5\u00A4\x11`S\u0093\x00\u00FD\\\u0080k\u00A7\u00E4\f\x0F\u00B2%x\u0084\x0F\u0091\u0095(\u00E1\x00\u00BB\x13\u0091\u00BE\\\u00B3\x18\u00A1h\u00E0a\x02\u00CF;\u00F5X\u00D8\u00BA\u00D8\u00CE\u00A9%\u009C\u00E01\u00BB\x0B\u00B4/Q\u00C7\u00E6.%Dh=\u00B3\x00\u00ACoh]\u00D1\u0096\x12\u00CA\u008C\u0095\u008F\u008D\n\u00C1\u00C3\x03\"\u00F2\x0348\x04\x04\u00FD,#\u0091\x02[C\u0086Ua\u00AE\u00DF\u00D1k \x1FaU!\u0084W\x01\u00842\u00A1sOO`\u0082K\u00F0oq\u008C\x00\x00f[\u00B8\u00F0\u0081[\u00EF\u00BF\u00FFS\u00F1\u00F2o|\u00D5\u00B9\u00FF\u00EB6\u009ARw\u00BAqF!\u00AC\u008Ck\x16*\u00BF\u00F0\u0097\u009B\u00FF\u00E8\u00ED\u009F\u00DA\u00FE\u00E1\u008F\\\u00BB\u00EB!\u00DB}\u00EE\x02\u00ECe\u00D2\u0085\u00B5\x18\u00AE?\x00\u00AE\t\u00D9\u00BADCNV\u00DB\x05\u0093\x10\u00E9Ap)\u00F6\u009A\x05\u00A8\u00A0\u00A3N\u0094H*\u0099)\u0093q\x1A\x02\u00C0\u00C3\x04\u0092\u0089\u00A8p\u00F2s\x01\nP\u00A8T\b\u009AJ\u0080$&\x19\u0093\u00E5\u0088\x0EH,\u00EBV\u00D2\u0088@\x10\u0091\x06e\u0097$\t\u0081\x1C:\u00A2\u00DF\x01\u00BA\u0085\x10s\u0095\u00E8\u00C6\x1E\x0B\u00BA\x02\u0098\u00ABN\u00AD=Jz\u00B1\u00F3\u00F8\x12a\x14\x0B\u00AB*\u00C7@\u0080Ia\x15y\x10\u00E6U\x18(9\u00C76\x0E<x\u00E7\u00E3\u00F7y\u00E2\u00F8C\u00FF\u00F0\u00A7\u00F7\u00FF\x0F\u00D9\x7F\u00F7\u00C3\t\u00FE\u00B6qF!\u00FC\r\u00E3\u00F3\u00AA\u00C3\u00AB>p\u00CB\u00B3\u00DF\u00F3\u00A9\u00F6\x1F>t\u00ED\u00ECA;}\x1D\u00D10\u0095`!\u00A6 \u00A1\u0096\u00DF\u00ABl\u00C6\u00E43\u00ACd$\u00A8\x1C\u008E\u00F1\ntE\u00F10l\u00D1\u00F8;\u00EB+\u00E8\x150\u0084\u00D0\u00C0\x13t\u00B5\u00D4ztO\u00C3\x15\x12\u00AF\u00DFV\u0086\u00F6\u0085Y\u00F9\u00DD\u0098+\x18K\u00C1!fj\u00EC\u00C4\x106a\u0083\x14Zo\u00EF\u009D\x10\x02\u00C9\u00E6\"\u00CE\u0081\u00F0g\u00CC0\x03\u0092\u008D\\(\u00D8\u0095\u00C7P3\x0E\u0099\u0086,m\u00DA\x14\u0096fDvs\u008A\u00F0\u0081\ni\u00D8\u00C4E\u0097l^\u00F1\u0095\u008F\u00D7W~\u00DB\x0F\x1Ex\u0093|\u00D5\u009D\u00B7D\u00F9\u00B6\x1Cg\x14\u00C2\u00DF1nP\x1D~\u00E6\u00CF\x0F}\u00FB\u00FF\u00FE\u0084\u00FE\u00FB\u00BF\u00B8z\u00E3\u0091[\u00E3\x1C\u00D1\u0099\u00B9g#US\x04:\u00C1\x10\b\u0088u-K\u00A6\x05\rZ\x0B~0\r\x13&MO\"\u00A5Y\u00D6J(\u00E7\u00AAaB\x0B\x0ECn\x1B\u00DC\u0088\u00DE}?\u00C9\u00DA\x0E*\u0095\u00D1\u00B6\x1F\u00BA@F5\u00D7\u00DD\u008B\u009B\x06 J\u00A0\u00A1@s%1\u00D7\u00E4#\b\u008C\x16m\u0096=-t\u00F6IP\u00F7\b$\u00B0\u008A\u00E0\x00\u00F8\u00CF\u00BC\n\u00BB \x17\u008E\u0091\u00A2\x10\u0090^\u0085\u00BAWR\u00B9\x0E\x02\t\x16\u00A5\u00A5\x1C;\u00E6\u00F3M\u009C\u00F3\u00A0\u00C5\u0087\u00EF\u00F7\u0084\u009D\u009F\u00F8'?v\u00C1o\u00C9yg\x14\u00C1\u00DF6\u00CE(\u0084/s\u00DC\u00A4*\u00BF\u00F8\u0091[\u00BE\u00E9\u008F>\u008E\u00EF\u00FB\u00F0\u00E7\u00E7\u00DFpp{\u00CD\u00AC\u00EF8K\x10\u00B0\x02\u0089,o\x06\u009CX$\u00D30A1\u00E5\x19\u00D0\u00C3\u0088B\u00A5\u00C2\u0094t\u00A1]]T%HC\x05\x1F\b2\u0093V\u00A6c\u00CF~\x10\u00A3D\u00E5'\u0097\u0088\u00AB\u00EBA\u00A2K\u00B4P\u0093\u00DEm\u00F9s\u00E7h\u0088Z(1\u00C0:1\u0091\u00C8\x13\x1E\u0082\x0B4-4\u0097\u008D\u00E7\u008A\u00CA\u0086\x13h\bpM\x1F2Kaq\u00BEd\u00FA\u0091\u00C7*\n#AE-\u00D8\u0080g\x19\x00\u00B4\x06\u00CCv\x1F\u00C2=/\u00D9\u00FE_\x0F\u00FFz\u00FC\u00E7g\u00BE\u00EC\u0082?\u0090\u008B\u00CE\u0084\x06_\u00CE8\u00A3\x10n\u00C5x\u00DD\u00A7\u00B7\x1Eu\u00F9\u00FB\x0F\u00BF\u00E4\u0093\u00D7\u00AC=\u00FBs7\u00ED\u00DE\x18\u00D1\f\u00D5\x1E%\u0080H\u0096CG\u00DD\u00C3\u0098\x169\u0081\u00C8\u009A\u00A5pE\x10\u00DC\u0085Z\u00E1X>\u00AF\u00A0\u00A2f/\u00C5\x00\x1DGWD\u00DD\u0081E\u00ED\b\u00A6%$\u00AE#\u0088OU\u00C1x3\x16\u00E9ITj\u00DEg1\x14\u00D1h\u0085E\u00F3\x1A\u00A3\u00A3T<V\u00AB\x0ED\x01\u00D3\u00E0\u0084%\u00F6<\u009C\u00D4\x17\u00C0B\x00\u00B3\u00FE2\u00A15\x1B&P\u008B\u00A0\u00A4(\tE\u0093\x0E\u00A0y\u00B8\u00D0\u00B1\u00FF\u0082\u00A3[\u00E7\u00DDw\u00E7MO|\u00D6\u00AEW\u00FF\u00FD\u0097\u00EF\u00BE\u00CBS\u008DO\u00F58\u00A3\x10Nb\u00BC\u00E7\u0090\u009E\u00FF\u00BA?\u00BD\u00E5\u009F\u00FF\u00E5\u00D5x\u00D1\u00C7>7\u00BB\u00DF\u00D6\u00F6\x06 \u00CD\u0090} \u00847]t\u00F5\u00EAD\u0084\x070\u00A9U`\x16\u00C1\u00853j&jj\u00D2\u00C1\u00CB\u00BA\u00D2S\u00E0\x02+!\u0087Q\u0097uJ`\u00A2\u0097r\x1C\u0085@\u009C@\u00C7\u00EE]\u0093\u00DA\x04?\x10\x05\u00DA\u00C8\u00B6\u00E7\x12\x1D\u0096\u00A3\x7F\x01d\n\u00E4)\u00D0\u009A\u0096\u00BE\u008A\x12\u00D6~V\u0094\x070\r5\u00C87\b<\x00\u00EC\u00A7\u00E8\u00CA\u00C23\x17\u00B3\x0EH\x1B1\f\x0B\u00DC\u00E3\u0092\u00FE\u00E9\x0B\u00EEw\u00E4u\u00DF\u00FC\u00C2\u00F3\x7F\u00F5\u00C1\u00CFl7\u00DC>3\u00E0\u00AE7\u00CE(\u0084S0nPm\u00BF\u00FA\u00C1\u00AD\u00A7\u00FC\u00D1G7_p\u00E5\u00B5\u00C3\u00B3\u00AE\u00BEq\u00D7F\u00979d\u00EC\u00D0\u009E\u00E5\u00C8\u00E6\x01$m6\x00H\u00EF\u00F8\u00B3\u00DA\u0082\u00AD\x15\u00C1\r\u008F\u00A2\u0084\t\x13\u009EAaE\u00B2{R\x1B\u00C5\u00D6.\u00EC\x19&P\u00C0'@\u00A8\u00EF\u009B\n\u00C1\x0B\u009DF\u00C9\u00BF\u00BB@tD\x1B\u00CD\u00E1\u00B7\u00B4\u00A2U;\x1A\x01)\x05\u00BAIV5\x0E\u00A2\u0091%\u00A8\u00B5\fMR\u00A1\x10,\u0094\u00A6%4(U\u008D\x00\x04=k%\u009Ab\u00AE#\u00F6\u00DEck\u00EB\u00C2\u008B\u00FB[\x1E\u00F1u\u00F2+\u0097}\u00EF\u00BE?\u0096{\u00B0\u00A6\u00F4\u00CC\u00B8\u00B5\u00E3\u008CB8\u00C5\u00E3\u00A3\u00DB\u00BA\u00FFu\u00EF8\u00F4\u008F?x\u00D5\u00F8\x1D\u009F\u00B9v\u00F6u_\u00B8e]0\x0E%\u00BB\u00E0\u008F\u00FC\x18\x001]y\u00F5X=\x0B\u00A4\u00DC\u009B\x00\u0080\u00B1\u00B4U\u00EB2\u00B5\u00F8\u009E\u0081`wf\u00E9\r\u00DD\u00B9\x13Qe\u00E9\x05KUy\u00C4Z\x15\u00A3\u00A5!\u0089u\u0084\u0097\u00C2f)\u00DD\u00BEg\u00E5!\x15B\u0083Z\u00B3S\u0086\x02\u0092\u00C2L\u009As\u00E5\x05\x10+`\u00B6\x02\u00D2]\t\u00B4l\u00AC\u008A\u00C4 \u00C4=\u0084&\u0082=\u00FB\u008E\u00E8=\u00BEj\u00FC?\u00F7{X{\u00E37}\u00E7\u00DE\u00FF\u00FE\u00C0K\u00E5N\u00D9\u00DD\u00F8t\x1Dg\x14\u00C2m8\u00DEv\u0093^|\u00F9;o\u00BC\u00EC\x13\u009F\u00DB}\u00D9g\u00AE\u00E9\u008F\u00BF\u00E1K\x1B\u00B2h\u0080\u00F6Y\u00A4\x13\u00938d\x19\x00x#\u0092\x1A&\u00E4\u0092\u00F4Z\u00D2\u0089\u0098x\x05LSF\x1B\u00F9\u00B1\u00B4V+\x00c\u00F5\x0Ejed\u00FDa\u00F6\u00A3\u0091\u00E30j\\#y\x0B@\x12\u0092\x06\u00F1\u00DF\x10\u00D4\u00F6\u00EB\u0081/\x1400\u00BD\b\x0F%JS\x12\x02\u008F\u00CC.4\x05\u00B4m\u00E3\u009C\u00FDK\u00BD\u00E7}v\u00DEw\u00AF\u008B\u00FB\u00E5O\u00BB\u00EC\u00C0\u00E5\u008F~\u008E\\u\u00DB\u00BF\u00BD\u00BB\u00E78\u00A3\x10n\u00A7\u00F1\u00A7\u00D7\u00EAW\u00BC\u00FEO\u00BF\u00F8\u00AC+>7|\u00CBu7\u00CC\u009E\u00FC\u00E9\u00EB\u00DB\u00BA\u00F6\r\u00F4>B\u00FAP\\\u00FF$\x16\u00B1\u00D1\u00CA$\u00ADY]\u00FF\x122\u00B0\u0085Z\x1Bk\u00AAq\u00CAy\u0098\u0084!\x05C\u00A0\x17\u0090\u00C5U\u00E4!\u00A4\x12!\u0086 E!4'\x18\u00AD\u00B9R\b\u008C@\x10\x1D\u008BY\u00B7\x10\u009C\x02$.@\u00C6c\u0093\x1E\x15\u0088\u0096\u00A5\u00D8\u00C2E_\u00B1\u00D8>\u00FF\u00C2\u00FE'\x17\u00DF\u00B7\u00FF\u00CE3\u009F\u00B7\u00E7-\u008Fx\u00FA\u00DA\u00E7o\u00CF\u00F7uw\x1Dg\x14\u00C2\x1D0>\u00BE\u00D0\u00B3\x7F\u00F3O7\u009F\u00F2\u00BE\x0Fo~\u00E3\u00F57\u00F7\u00A7^u\u00ED\u00EE\x07\u00DC|x\u008E\u00DEg\u00C1\u0084\u00AC\u009C\u0083\u00EA\x1D`d\x1A\u00D3>\u00EF$*\u00D5\x02&\x0F\x13j\x0B\u00B7\u00C8\x12\x14\u00E5\x10\u00D8\u00C3q?K\u00E5\x12e\u00D1\x1E\u00CAp\u00DA\x18\u00E3P\u00B1\x06\u00F1\u00DA\u0086\x04\n[\u00CFjH\u0092\u0093\u00A2\u00AB\u0092\u00A470\x03 \u00B2\u0085\u00B3\u00F6\u00EE\u00E0\u009E\u00F7\\~\u00F2\u00FC\u00F3\u00C7?|\u00F0c\u00E7\u00EFx\u00CE\u00F3\u00CF\u00F9\u00E3\x03\x0F\u00B9\u00EB4/\u00BD\u00B3\u008C3\n\u00E14\x18\x1F\u00B8Q/~\u00FD\x1F|\u00F1\u00D2+\u00AF\u009A?\u00F9\u00FA\u0083\u00FDI_\u00B8\x01\x0F\u00B8\u00F1Kk\u0082\u00E5\x06\u00FA\u00D8\x01VE\u00BA\u0092hZ\u00D8\u0087U\u00E0'!@\u0082\u008E\u00D19\tn\u00FD\u00C9+\b\u00D0R\u00DD\x13\u00D1LK\u008E\u0088B\u00AB\u00C8Bxf@\u00FD\x070\u00D7\x7F\x069\u0086\x1D\u00C8\u008E\u00CC\x19\x06X\u008ApP\u00C5 K\u009Cs\u00EE\u00B6^x\u00E1\u00E2\u0093\u00FB\x0F,\u00DF}\u00DF\u00FB\x0F\x7F\u00F2-\u00CF=\u00FF]\u008Fy\u00CA\u0099P\u00E0\u008E\x1Eg\x14\u00C2i8>pT\u00CF{\u00F3\u00DB\u008F|\u00CDG>q\u00E4q7\u00DC8\x7F\u00EC\u00E6Qy\u00F4\u00E7\u00AF_\u00BB\u00E8\u00E6\u0083\u0082\u00D6\u00D7c\t\u00B7\u00DE-o?\u00EAh\u00DE\u00C08K*uu\u00F9)\u00C4\u00A55[\u00A65\u00E1\u0095\u0094\x1E^,\u00CB\u00BET$c\u00F6\x16`\u00898\u00E0J\x02\u008A\u00D6\x14M\u009B\t|o\u0096Y\x000H\u00C7\\\x17\u00D8\u00BBg\u00C4\x05\x17m_{\u00F6Y\u00F3\x0F\x1E\u00B8\u00E0\u00E0\x07\x1E\u00F2\u0088s\u00DE\u00FF\u00DC\x7Fz\u00F6\u009F]\u00FCP\u00B9\u00F1\u00F6\x7F\u00BAg\u00C6\u00DF6\u00CE(\u0084;\u00C9\u00F8\u00C4!\u00BD\u00F0w\u00FE\u00E8\u00E6\u0087\u00BF\u00E7\u00A3\u00FA\u00B0\u009B\u00BE\u00D8\x1E|\u00F0\u00E8\u00F8\u00A0qs\u00FD\x01_:\u0084\u008B\u00AE\u00F9\u00E2R\u0096\u00DB\u00EB\u00C0\u00B8\x11K\u00C8G\u00C6\u0080)\u00BD\x0Et_\x1C\u00D6\u00BAC\x19zo\x1C\x05\u008D\x05`\u00C4S\u00A3p\u00CFA<,\x19\u00E0\u00D4kXH\u00A2\u009EZ\x1C\u00A0\x18t\u0089\u00F9p\x14\u00E7\u009E\u00DFt\u00FF\u00DE\u00E1\u00DA=\u00B3\u00ADO\u009E\u00BDG\u00AF\u00D8\x7F`\u00FC\u00ABG>\u00BA\u00FF\u00E5\u00B7?\u00E7\u00A2\u008F\u00DE\u00EF\u00C1r\u00DD\x1D\u00F8\u00F8\u00CE\u008C/s\u009CQ\bw\u00F2q\u00D5\u00A8g\u00BD\u00FFS\u00F8\u00CA\u00F7\u00BD\u00EB\u00E6\u00FB\\\u00FD\u00B9\u00E5\u00C5\u009F\u00BFe\u00F3^\u0087\x0F\u00EE\u00B9\u00C7\u00F6\u0091\u00D9E}G\u00CE_.\x16\x07\x16\u0090s\u0097\u00DB\u00B3\u00BD[Gt\u00D8\u00DC\u009Ec\u00B15\u00C3r{\u0080.\x15:6\u00E8\u00C2\u00B3\x1A\u00EA\x1D\u008A\u00A0\u0098\r#\u00D6\u00D0\u00B1\u00BE\u00B6\u00C4\u00AE\u008D>\u00CEf\u008B[f\u00D0/\x0Em\u00FD\u00A6a\u00BE\u00BCa>\u00C7\u00B5{\u00F6}\u00F1\u009A\u008B\u00F6\u00AE_}\u00EF\u008B\u00CF\u00BA\u00EA\u00D2'\u009F\u00F3\u00D9\u00C7=\x11\u009F\u00B9\u00F0^r\u00E4\u008E~&g\u00C6\u00AD\x1F\u00FF\x1F\x00\u00F3\u00AA\r=e\u0082\u0081\x00\x00\x00\x00IEND\u00AEB`\u0082" ;
this.slash = "/"
var targetFolder = new Folder(Folder.userData.fullName +this.slash+ "Aescripts"+this.slash+"colorPicker");
!targetFolder.exists && targetFolder.create();
this.settingFile = new File(targetFolder.fullName + this.slash + "colorPicker.xml");
if(!this.settingFile.exists){
this.settingFile.open("w");
this.settingFile.write("<setting></setting>");
this.settingFile.close();
}
this.haveSetting = function(name){
this.settingFile.open("r");
var content = this.settingFile.read();
this.settingFile.close();
return content.toString().indexOf("<"+name+">") !=-1;
}
this.getSetting = function(name){
this.settingFile.open("r");
var xml= new XML( this.settingFile.read() );
this.settingFile.close();
return xml[name].toString();
}
this.getSettingAsBool = function(name){
var result = this.getSetting(name);
return result == "true" ? true:false;
}
this.saveSetting = function(name,value){
this.settingFile.open("r");
var xml= new XML( this.settingFile.read() );
this.settingFile.close();
var isOk = true;
try{
xml[name] = value.toString();
}catch(err){
isOk = false;
}
this.settingFile.open("w");
this.settingFile.write(xml);
this.settingFile.close();
return isOk;
}
}
$.global.colorPicker = colorPicker;
return colorPicker;
})();
// ==================== |--------| ====================
// ==================== | base64 | ====================
// ==================== |--------| ====================
/**
* Base64 codec.
* @name Base64
* @namespace
* @see {@link https://github.com/dankogai/js-base64}
* @license BSD-3-Clause http://opensource.org/licenses/BSD-3-Clause
* @category DuESF
* @subcategory Third Party
* @version 2.6.4
* @copyright 2014, Dan Kogai All rights reserved.
*/
var Base64 = (function() {
var version = "2.6.4";
// constants
var b64chars
= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var b64tab = function(bin) {
var t = {};
for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
return t;
}(b64chars);
var fromCharCode = String.fromCharCode;
// encoder stuff
var cb_utob = function(c) {
if (c.length < 2) {
var cc = c.charCodeAt(0);
return cc < 0x80 ? c
: cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6))
+ fromCharCode(0x80 | (cc & 0x3f)))
: (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f))
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
+ fromCharCode(0x80 | ( cc & 0x3f)));
} else {
var cc = 0x10000
+ (c.charCodeAt(0) - 0xD800) * 0x400
+ (c.charCodeAt(1) - 0xDC00);
return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07))
+ fromCharCode(0x80 | ((cc >>> 12) & 0x3f))
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
+ fromCharCode(0x80 | ( cc & 0x3f)));
}
};
var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
var utob = function(u) {
return u.replace(re_utob, cb_utob);
};
var cb_encode = function(ccc) {
var padlen = [0, 2, 1][ccc.length % 3],
ord = ccc.charCodeAt(0) << 16
| ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)
| ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)),
chars = [
b64chars.charAt( ord >>> 18),
b64chars.charAt((ord >>> 12) & 63),
padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),
padlen >= 1 ? '=' : b64chars.charAt(ord & 63)
];
return chars.join('');
};
var btoa = function(b) {
if (b.match(/[^\x00-\xFF]/)) throw new RangeError(
'The string contains invalid characters.'
);
return b.replace(/[\s\S]{1,3}/g, cb_encode);
};
var _encode = function(u) {
return btoa(utob(String(u)));
};
var encode = function(u) {
return _encode(u);
};
var encodeURI = function(u) { return encode(u, true) };
// decoder stuff
var re_btou = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g;
var cb_btou = function(cccc) {
switch(cccc.length) {
case 4:
var cp = ((0x07 & cccc.charCodeAt(0)) << 18)
| ((0x3f & cccc.charCodeAt(1)) << 12)
| ((0x3f & cccc.charCodeAt(2)) << 6)
| (0x3f & cccc.charCodeAt(3)),
offset = cp - 0x10000;
return (fromCharCode((offset >>> 10) + 0xD800)
+ fromCharCode((offset & 0x3FF) + 0xDC00));
case 3:
return fromCharCode(
((0x0f & cccc.charCodeAt(0)) << 12)
| ((0x3f & cccc.charCodeAt(1)) << 6)
| (0x3f & cccc.charCodeAt(2))
);
default:
return fromCharCode(
((0x1f & cccc.charCodeAt(0)) << 6)
| (0x3f & cccc.charCodeAt(1))
);
}
};
var btou = function(b) {
return b.replace(re_btou, cb_btou);
};
var cb_decode = function(cccc) {
var len = cccc.length,
padlen = len % 4,
n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0)
| (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)
| (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0)
| (len > 3 ? b64tab[cccc.charAt(3)] : 0),
chars = [
fromCharCode( n >>> 16),
fromCharCode((n >>> 8) & 0xff),
fromCharCode( n & 0xff)
];
chars.length -= [0, 0, 2, 1][padlen];
return chars.join('');
};
var _atob = function(a){
return a.replace(/\S{1,4}/g, cb_decode);
};
var atob = function(a) {
return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g, ''));
};
var _decode = function(a) { return btou(_atob(a)) };
var _fromURI = function(a) {
return String(a).replace(/[-_]/g, function(m0) {
return m0 == '-' ? '+' : '/'
}).replace(/[^A-Za-z0-9\+\/]/g, '');
};
var decode = function(a){
return _decode(_fromURI(a));
};
// export Base64
return {
VERSION: version,
atob: atob,
btoa: btoa,
fromBase64: decode,
toBase64: encode,
utob: utob,
encode: encode,
encodeURI: encodeURI,
btou: btou,
decode: decode
};
})();
// ==================== |---------| ====================
// ==================== | version | ====================
// ==================== |---------| ====================
/**
* Constructs a new version object
* @class
* @name DuVersion
* @classdesc Informations about the version of an application.<br />
* The version is in the form: Major.Minor.Patch#Build. Example: <code>17.0.0x557</code> or <code>17.0.0-Alpha1</code> or <code>17.4</code><br />
* The only required part is the Major version.
* @param {string} [version='1.0.0'] The version in the form Major.Minor.Patch
* @property {string} fullVersion The complete version name
* @property {string} versionString The Major.Minor part as a string
* @property {float} version The Major.Minor part as a float
* @property {int} major
* @property {int} minor
* @property {int} patch
* @property {int} build The build is -1 if it cannot be parsed as an int, like "alpha".
* @property {string} buildString The build as a string
* @category DuESF
*/
function DuVersion( version )
{
if (!isdef( version )) version = '';
var reV = /^(\d+)\.?(\d*)\.?(\d*)\D?(\S*)/i;
var v = version.match( reV );
if (version == '' || !v)
{
this.fullVersion = '1.0.0';
this.versionString = '1.0';
this.version = 1;
this.major = 1;
this.minor = 0;
this.patch = 0;
this.build = -1;
this.buildString = "";
return;
}
if (!v[ 1 ]) v[ 1 ] = '1';
if (!v[ 2 ]) v[ 2 ] = '0';
if (!v[ 3 ]) v[ 3 ] = '0';
if (!v[ 4 ]) v[ 4 ] = '';
this.fullVersion = version;
this.versionString = v[ 1 ] + '.' + v[ 2 ];
this.version = parseFloat(this.versionString);
if ( isNaN(this.version) ) this.version = parseFloat(v[ 1 ]);
if ( isNaN(this.version) ) this.version = 0;
this.major = parseInt( v[ 1 ] );
if ( isNaN(this.major) ) this.major = 0;
this.minor = parseInt( v[ 2 ] );
if ( isNaN(this.minor) ) this.minor = 0;
this.patch = parseInt( v[ 3 ] );
if ( isNaN(this.patch) ) this.patch = 0;
this.build = parseInt( v[ 4 ] );
if ( isNaN(this.build) ) this.build = -1;
this.buildString = v[ 4 ];
}
/**
* Checks if this version is higher than another.
* @memberof DuVersion
* @param {DuVersion|string} otherVersion The version to compare with.
* @return {bool} true if this version is more recent than otherVersion
*/
DuVersion.prototype.higherThan = function( otherVersion )
{
return DuVersion.compare(this, otherVersion);
}
/**
* Checks if this version is higher than or equals another.
* @memberof DuVersion
* @param {DuVersion|string} otherVersion The version to compare with.
* @return {bool} true if this version is more recent than or the same as otherVersion
*/
DuVersion.prototype.atLeast = function( otherVersion )
{
if ( this.equals(otherVersion) ) return true;
return DuVersion.compare(this, otherVersion);
}
/**
* Checks if this version is the same as another.
* @memberof DuVersion
* @param {DuVersion|string} otherVersion The version to compare with.
* @return {bool} true if this version is the same
*/
DuVersion.prototype.equals = function( otherVersion )
{
if (! (otherVersion instanceof DuVersion) ) otherVersion = new DuVersion( otherVersion );
return this.fullVersion == otherVersion.fullVersion;
}
/**
* Compares two versions of an application
* @static
* @memberof DuVersion
* @param {string|DuVersion} vA - The first version
* @param {string|DuVersion} [vB=DuESF.scriptVersion] - The other version
* @return {boolean} True if vA is more recent than vB (strict, if they're equal it will return false)
*/
DuVersion.compare = function (vA, vB)
{
vB = def(vB, DuESF.scriptVersion);
if (! (vA instanceof DuVersion) ) vA = new DuVersion( vA );
if (! (vB instanceof DuVersion) ) vB = new DuVersion( vB );
//compare major version
if ( vA.major > vB.major ) return true;
if ( vA.major < vB.major ) return false;
//minor
if ( vA.minor > vB.minor ) return true;
if ( vA.minor < vB.minor ) return false;
//patch
if ( vA.patch > vB.patch ) return true;
if ( vA.patch < vB.patch ) return false;
//build
if ( vA.build >= 0 && vB.build >= 0 )
{
if ( vA.build > vB.build ) return true;
if ( vA.build < vB.build ) return false;
}
//build as a string
if ( vA.buildString == vB.buildString ) return false;
if ( vA.buildString == '' && vB.buildString != '') return true;
if ( vA.buildString != '' && vB.buildString == '') return false;
var vABuildString = vA.buildString.toLowerCase();
var vBBuildString = vB.buildString.toLowerCase();
if ( vABuildString.indexOf('rc') >= 0 )
{
if ( vBBuildString.indexOf('rc') < 0 ) return true;
return vABuildString > vBBuildString ;
}
if ( vABuildString.indexOf('gamma') >= 0 )
{
if ( vBBuildString.indexOf('rc') >= 0 ) return false;
if ( vBBuildString.indexOf('gamma') < 0 ) return true;
return vABuildString > vBBuildString ;
}
if ( vABuildString.indexOf('beta') >= 0 )
{
if ( vBBuildString.indexOf('rc') >= 0 ) return false;
if ( vBBuildString.indexOf('gamma') >= 0 ) return false;
if ( vBBuildString.indexOf('beta') < 0 ) return true;
return vABuildString > vBBuildString ;
}
if ( vABuildString.indexOf('alpha') >= 0 )
{
if ( vBBuildString.indexOf('rc') >= 0 ) return false;
if ( vBBuildString.indexOf('gamma') >= 0 ) return false;
if ( vBBuildString.indexOf('beta') >= 0 ) return false;
if ( vBBuildString.indexOf('alpha') < 0 ) return true;
return vABuildString > vBBuildString ;
}
return vABuildString > vBBuildString;
}
/**
* Information got from an update query on a RxVersion server, as returned by {@link DuVersion.checkUpdate DuVersion.checkUpdate()}.
* @typedef {Object} DuVersion~UpdateReply
* @property {Boolean} update Whether this script needs an update.
* @property {string} version The available version.
* @property {string} name The name of the script.
* @property {string} description A description of the version.
* @property {string} downloadURL The link to download the current version.
* @property {string} changelogURL The link to the changelog.
* @property {string} donateURL The link to make a donation.
* @property {string} date The date of the version, in the form "yyyy-MM-dd hh:mm:ss".
* @property {string} message Information about success/errors.
* @property {Boolean} success false if the query failed and the version could not be retrieved.
* @property {Boolean} accepted false if the query was wrong or the server did not recognize it. Should always be true.
/**
* Checks if a new version is available for the current script,<br/>
* using the DuESF.rxVersionURL if it is set.
* @returns {DuVersion~UpdateReply|null} An {@link DuVersion~UpdateReply} or null if the server could not be reached.
*/
DuVersion.checkUpdate = function()
{
if ( DuESF.rxVersionURL == '' ) return null;
var co = new Socket;
co.encoding = 'UTF-8';
// No need for more than 5 seconds for just checking updates.
co.timeout = 5;
var url = DuESF.rxVersionURL.replace('http://','').replace('https://','').split('/');
var baseUrl = url.shift();
var fullUrl = baseUrl + ':80';
if ( co.open( fullUrl ) )
{
// Build request
var os;
if (DuSystem.mac) os = 'mac';
else os = 'win';
var args = {
"getVersion": "",
"name": DuESF.scriptName,
"version": DuESF.scriptVersion.fullVersion,
"os": os,
"osVersion": DuSystem.osVersion,
"host": DuESF.host,
"hostVersion": DuESF.hostVersion.fullVersion,
"languageCode": i18n.getLocale()
}
if (DuESF.isPreRelease) args["prerelease"] = '';
var request = DuURL.buildGET( baseUrl, url, args );
var reply = '';
DuDebug.log( "Version check - Sys Info\r\n" +
"Script: " + DuESF.scriptName + '\r\n' +
"Script Version: " + DuESF.scriptVersion.fullVersion + '\r\n' +
"OS: " + os + '\r\n' +
"OS Version: " + DuSystem.osVersion + '\r\n' +
"Host: " + DuESF.host + '\r\n' +
"Host Version: " + DuESF.hostVersion.fullVersion + '\r\n' +
"Language Code: " + i18n.getLocale() + '\r\n\r\n' +
"Request: " + request
);
if ( co.writeln( request ) ) {
// Wait for the closing } or timeout
// timeout in 2 seconds
var now = new Date().getTime();
var timeout = now + 2000;
while ( (reply == '' || reply.lastIndexOf('}') < reply.indexOf('{') ) && now < timeout)
{
$.sleep(100);
now = new Date().getTime();
reply += co.read();
}
}
co.close();
// Sanitize reply (strip http headers, and parse)
DuDebug.log( "Version check - Reply\r\n" + reply );
reply = reply.substring( reply.indexOf('{'), reply.lastIndexOf('}')+1 );
try {
reply = JSON.parse(reply);
}
catch (e) {
if (DuESF.debug) alert(e);
}
return reply;
}
return null;
}
// ==================== |--------------| ====================
// ==================== | fuzzy-search | ====================
// ==================== |--------------| ====================
/**
* fuzzy-search.jsxinc.<br />
* by {@link http://duduf.com Duduf} and Wouter Rutgers.<br />
* @example
* // Encapsulate everything to avoid global variables!
* (function() {
* #include "fuzzy-search.jsxinc"
*
* var people = [{
* name: {
* firstName: 'Jesse',
* lastName: 'Bowen',
* },
* state: 'Seattle',
* }];
*
* var searcher = new FuzzySearch(people, ['name.firstName', 'state'], {
* caseSensitive: true,
* });
* var result = searcher.search('ess');
* })();
* @class
* @author Nicolas Dufresne and contributors
* @copyright 2022 Nicolas Dufresne, RxLaboratory
* @version 1.0.0
* @license GNU-GPLv3
* @category fuzzy-search.jsxinc
*/
var FuzzySearch = function (haystack, keys, options) {
if (typeof haystack === 'undefined') haystack = [];
if (typeof keys === 'undefined') keys = [];
if (typeof options === 'undefined') options = {};
if (!(keys instanceof Array)) {
options = keys;
keys = [];
}
this.haystack = haystack;
this.keys = keys;
this.options = options;
if (typeof options.caseSensitive === 'undefined') options.caseSensitive = false;
if (typeof options.sort === 'undefined') options.sort = true;
};
// === METHODS === //
/**
* Searches for items in the haystack
* @param {string} query The string to search
* @return {Obbject[]} The matching results. Objects with two attributes: <code>item</code> and <code>score</code>.
*/
FuzzySearch.prototype.search = function (query) {
if (typeof query === 'undefined') query = '';
if (query === '') {
return this.haystack;
}
var results = [];
for (var i = 0, ni = this.haystack.length; i < ni; i++) {
var item = this.haystack[i];
if (this.keys.length === 0) {
var score = FuzzySearch.match(item, query, this.options.caseSensitive);
if (score > 0) {
results.push({
"item": item,
"score": score
});
}
} else {
for (var y = 0, ny = this.keys.length; y < ny; y++) {
var propertyValues = FuzzySearch.getDescendantProperty(item, this.keys[y]);
var found = false;
for (var z = 0, nz = propertyValues.length; z < nz; z++) {
var score = FuzzySearch.match(propertyValues[z], query, this.options.caseSensitive);
if (score > 0) {
found = true;
results.push({
"item": item,
"score": score
});
break;
}
}
if (found) {
break;
}
}
}
}
if (this.options.sort) {
results.sort(function (a, b) {
return a.score - b.score
});
}
return results;
};
// === STATIC === //
/**
* Caculates the score of a string against a query
* @static
* @param {string} item The string to test
* @param {string} query The string to search
* @param {bool} [caseSensitive=false] Whether to check the case or not
* @return {float} The score, a positive value.<br/>
* - <code>0</code>: no match<br/>
* - <code>1</code>: perfect match<br/>
* - <code>>1</code>: the lower the score, the better the match<br/>
*/
FuzzySearch.match = function (item, query, caseSensitive) {
if (typeof caseSensitive === 'undefined') caseSensitive = false;
if (!caseSensitive) {
item = item.toLowerCase();
query = query.toLowerCase();
}
// Exact matches should be first.
if (item === query) {
return 1;
}
// If the query is longher than the item, inverse the two
if (query.length > item.length) {
var t = item;
item = query;
query = t;
}
var indexes = FuzzySearch.nearestIndexesFor(item, query);
if (!indexes) {
return 0;
}
// Matches closest to the start of the string should be first.
var o = indexes[0];
// More indices is better (the best is as many indexes as item letters)
var num = item.length - indexes.length;
// We want to match as many letters as there are in the query too
num += query.length - indexes.length;
// and the closest the indices the better
var close = indexes[indexes.length - 1] - indexes[0] + 1;
// taking into account the number of letters in the query
close /= indexes.length;
return 2 + close + o + num;
}
// Low-level undocumented method
// Gets suboject from a path string like "obj.key1.key2"
FuzzySearch.getDescendantProperty = function (object, path, list) {
if (typeof list === 'undefined') list = [];
var firstSegment;
var remaining;
var dotIndex;
var value;
var index;
var length;
if (path) {
dotIndex = path.indexOf('.');
if (dotIndex === -1) {
firstSegment = path;
} else {
firstSegment = path.slice(0, dotIndex);
remaining = path.slice(dotIndex + 1);
}
value = object[firstSegment];
if (value !== null && typeof value !== 'undefined') {
if (!remaining && (typeof value === 'string' || typeof value === 'number')) {
list.push(value);
} else if (Object.prototype.toString.call(value) === '[object Array]') {
for (index = 0, length = value.length; index < length; index++) {
FuzzySearch.getDescendantProperty(value[index], remaining, list);
}
} else if (remaining) {
FuzzySearch.getDescendantProperty(value, remaining, list);
}
}
} else {
list.push(object);
}
return list;
}
FuzzySearch.nearestIndexesFor = function (item, query) {
var letters = query.split('');
var indexes = [];
var indexesOfFirstLetter = FuzzySearch.indexesOfFirstLetter(item, query);
for (var i = 0, ni = indexesOfFirstLetter.length; i < ni; i++) {
var startingIndex = indexesOfFirstLetter[i];
var index = startingIndex + 1;
indexes[i] = [startingIndex];
for (var j = 1, nj = letters.length; j < nj; j++) {
var letter = letters[j];
index = item.indexOf(letter, index);
if (index === -1) {
indexes[i] = false;
break;
}
indexes[i].push(index);
index++;
}
}
indexes = FuzzySearch.arrayFilter(indexes, function(letterIndexes){ return letterIndexes !== false });
if (!indexes.length) {
return false;
}
return indexes.sort(function(a, b) {
if (a.length === 1) {
return a[0] - b[0];
}
a = a[a.length - 1] - a[0];
b = b[b.length - 1] - b[0];
return a - b;
})[0];
}
FuzzySearch.indexesOfFirstLetter = function (item, query) {
var match = query[0];
var result = FuzzySearch.arrayMap( item.split(''), function(letter, index) {
if (letter !== match) {
return false;
}
return index;
});
return FuzzySearch.arrayFilter( result, function(index) {
return index !== false;
});
}
// Low-level undocumented method
// Reimplements Array.prototype.map, missing in ExtendScript
FuzzySearch.arrayMap = function (arr, callbackFn) {
var result = [];
for(var i = 0, ni = arr.length; i < ni; i++)
{
result.push(
callbackFn( arr[i], i, arr )
);
}
return result;
}
// Low-level undocumented method
// Reimplements Array.prototype.filter, missing in ExtendScript
FuzzySearch.arrayFilter = function (arr, callbackFn) {
var result = [];
for(var i = 0, ni = arr.length; i < ni; i++)
{
if (callbackFn( arr[i], i, arr ))
result.push( arr[i] );
}
return result;
}
// ==================== |--------| ====================
// ==================== | string | ====================
// ==================== |--------| ====================
/**
* JavaScript String related methods
* @namespace
* @category DuESF
*/
var DuString = {};
/**
* Counts the number of occurences of item in string
* @memberof DuString
* @param {string} string - The string where to count
* @param {string} item - the string to search
* @return {int} the number of occurences
*/
DuString.occurences = function(string, item) {
return (string.split(item).length - 1);
}
/**
* Parses the string as a boolean.<br />
* The following strings are falsy:<br />
* 'false', '0', '', 'null', 'undefined', 'NaN'.<br />
* Note that any string consisiting only in any number of 0 will be falsy.
* @memberof DuString
* @param {string} string - The string to parse
* @param {boolean} [caseSensitive=true] - When false, 'FALSE', 'nan', 'UNdefined'... for example will be falsy too.
* @return {boolean} The resulting boolean
*/
DuString.parseBool = function(string, caseSensitive) {
caseSensitive = def(caseSensitive, true);
if (!caseSensitive) string = string.toLowerCase();
var nan = 'NaN';
if (!caseSensitive) nan = 'nan';
// test if string is only zeroes
var reZeroes = /^0+$/g;
if (reZeroes.test(string)) return false;
return string !== 'false' && string !== 'null' && string !== '' && string !== 'undefined' && string !== nan;
}
/**
* Replaces <strong>all</strong> occurences of a substring by another and returns the new string.
* @memberof DuString
* @param {string} string - The original string
* @param {string} find - The substring to replace
* @param {string} replace - The new substring to insert
* @param {boolean} [caseSensitive=true] - Optionnal. Do a case sensitive search of substring.
* @return {string} The new string
*/
DuString.replace = function(string, find, replace, caseSensitive) {
if (caseSensitive == undefined) caseSensitive = true;
var re = new RegExp(DuRegExp.escape(find), caseSensitive ? 'g' : 'gi');
return string.replace(re, replace);
}
/**
* Replaces all occurences of "{#}" in the string by the args.
* @memberof DuString
* @param {string} string - The original string
* @param {string[]|string} args - The arguments
* @return {string} The new string
*/
DuString.args = function(string, args) {
args = def(args, []);
if (typeof args === 'string' || args instanceof String) args = [args];
var str = string;
while (str.indexOf("{#}") !== -1) {
// While there is stuff to format
if (args.length < 1) break;
var arg = args.shift(); // Take the first arg and remove it
str = str.replace("{#}", arg);
}
return str;
}
/**
* Checks if a string ends with a given suffix
* @memberof DuString
* @param {string} str The string to check
* @param {string} suffix The suffix
* @return {bool} Whether the string ends with the given suffix or not
*/
DuString.endsWith = function(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
/**
* Checks if a string starts with a given prefix
* @memberof DuString
* @param {string} str The string to check
* @param {string} suffix The suffix
* @return {bool} Whether the string ends with the given suffix or not
*/
DuString.startsWith = function(str, prefix) {
return str.indexOf(prefix) === 0;
}
/**
* Generates a new unique string (numbered)
* @param {string} newString - The wanted new string
* @param {string[]} stringList - The list of strings where the new one must be generateUnique
* @param {boolean} [increment=true] - true to automatically increment the new name if it already ends with a digit
* @param {boolean} [isFile=false] - when generating name for files, setting this to true will add the increment before the extension
* @return {string} The unique string, with a new number at the end if needed.
*/
DuString.generateUnique = function(newString, stringList, increment) {
increment = def(increment, true);
if (!increment) newString += ' ';
//detect digits
var reg = "( *)(\\d+)([.,]?\\d*)$";
//clean input
var regexClean = new RegExp(reg);
newString = newString.replace(regexClean, "");
//go!
var regex = new RegExp(DuRegExp.escape(newString) + reg);
//The greatest number found
var greatestNumber = 0;
//The number of digits for the number as string
var numDigits = 0;
var spaceString = "";
for (var i = 0; i < stringList.length; i++) {
var currentNumberMatch = stringList[i].match(regex);
if (stringList[i] === newString && greatestNumber == 0) greatestNumber++;
else if (currentNumberMatch !== null) {
//if its a decimal number, keep only the integer part
var numberAsString = currentNumberMatch[2];
//convert to int
var numberAsInt = parseInt(currentNumberMatch[2], 10);
if (isNaN(numberAsInt)) continue;
if (numberAsInt >= greatestNumber) {
greatestNumber = numberAsInt + 1;
spaceString = currentNumberMatch[1];
}
//check if there are zeroes before the number, count the digits
if (numberAsInt.toString().length < numberAsString.length && numDigits < numberAsString.length) numDigits = numberAsString.length;
}
}
//add leading 0 if needed
if (greatestNumber > 0) {
//convert to string with leading zeroes
if (greatestNumber == 1) {
greatestNumber++;
spaceString = " ";
}
newString += spaceString;
greatestNumber = DuNumber.toString(greatestNumber, numDigits);
newString += greatestNumber;
}
if (!increment) newString = newString.substr(0, newString.length - 1);
return newString;
}
/**
* Returns a copy of the string without leading and trailing white spaces.
* @param {string} str - The string to trim
* @return {String} The trimmed string
*/
DuString.trim = function(str) {
if (typeof str === 'undefined') return "";
return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}
/**
* Returns a copy of the string without leading white spaces.
* @param {string} str - The string to trim
* @return {String} The trimmed string
*/
DuString.leftTrim = function(str) {
return str.replace(/^[\s\uFEFF\xA0]+/, '');
}
/**
* Returns a copy of the string without trailing white spaces.
* @param {string} str - The string to trim
* @return {String} The trimmed string
*/
DuString.rightTrim = function(str) {
return str.replace(/[\s\uFEFF\xA0]+$/, '');
}
/**
* Returns a copy of the string without leading and trailing white spaces, and without any new line, leaving only standard spaces.
* @param {string} str - The string to trim
* @return {String} The trimmed string
*/
DuString.fullTrim = function(str) {
var newStr = str.replace(/[\s\uFEFF\xA0]+/g, ' ');
return DuString.trim(newStr);
}
/**
* Returns a copy of the string without trailing white spaces and numbers.
* @param {string} str - The string to trim
* @return {String} The trimmed string
*/
DuString.trimNumbers = function(str) {
return str.replace(/[\s\uFEFF\xA0\d]*$/g, '');
}
/**
* Converts a size in Bytes to a human-readable string with a fitting unit automatically chosen<br />
* Note that the conversion uses 1024 Bytes per kB.
* @param {int} size The size in Bytes
* @return {string} The stringified size
*/
DuString.fromSize = function(size) {
if (size < 1024) return size + " B";
if (size < 1048576) return DuPath.sizeFromBytes(size, "kB") + " kB";
if (size < 1073741824) return DuPath.sizeFromBytes(size, "MB") + " MB";
if (size < 1099511627776) return DuPath.sizeFromBytes(size, "GB") + " GB";
else return DuPath.sizeFromBytes(size, "TB") + " TB";
}
/**
* Generates a camel case text from a snake case or standard one
* @param {string} text The source text
* @return {string} The camelCase version of the text
*/
DuString.toCamelCase = function(text) {
text = text.split(" ");
if (text.length == 0) return "";
text[0] = DuString.unCapitalize(text[0]);
for (var i = 1, n = text.length; i < n; i++) {
text[i] = DuString.capitalize(text[i]);
}
return text.join("");
}
/**
* Sets the first character of the text to be capital case if it's a letter.<br />
* Note that the string is left trimmed first: any leading white space is removed.
* @param {string} text The source text
* @return {string} The new text
*/
DuString.capitalize = function(text) {
var newText = DuString.leftTrim(text);
var firstChar = newText.substring(0, 1);
firstChar = firstChar.toUpperCase();
return firstChar + newText.substring(1);
}
/**
* Sets the first character of the text to be lower case if it's a letter.<br />
* Note that the string is left trimmed first: any leading white space is removed.
* @param {string} text The source text
* @return {string} The new text
*/
DuString.unCapitalize = function(text) {
var newText = DuString.leftTrim(text);
var firstChar = newText.substring(0, 1);
firstChar = firstChar.toLowerCase();
return firstChar + newText.substring(1);
}
/**
* Caculates the score of a string against a query, using Duduf's fuzzy-search.jsxinc
* @param {string} item The string to test
* @param {string} query The string to search
* @param {bool} [caseSensitive=false] Whether to check the case or not
* @return {float} The score, a positive value.<br/>
* - <code>0</code>: no match<br/>
* - <code>1</code>: perfect match<br/>
* - <code>>1</code>: the lower the score, the better the match<br/>
*/
DuString.match = function(str, query, caseSensitive) {
return FuzzySearch.match(str, query, caseSensitive);
}
/**
* Same as JS String.split except that it works with a list of separators too
* @param {string} str The string to split
* @param {string|string[]|DuList.<string>} separators The separator(s)
* @return {string[]} The array of strings.
*/
DuString.split = function(str, separators) {
separators = new DuList(separators);
var lastSep = separators.last();
var arr = str.split(separators.first());
separators.do( function(sep) {
if (separators.current == 0) return;
arr = arr.join(lastSep).split(sep);
});
return arr;
}
/**
* Splits the string into same-length substrings.
* @param {string} str The string to split
* @param {int} subStringLength The length of the substrings
* @return {string[]} The array of strings. The last one may be shorter than <code>subStringLength</code> if the original string length is not a multiple of it.
*/
DuString.chunk = function(str, subStringLength) {
return str.match(new RegExp("([\\s\\S]{1," + subStringLength + "})", "g"));
/*var strArr = [];
for (var i = 0, ni = str.length; i < ni; i) {
// don't split escaped characters
var end = subStringLength;
while ( str[i+end-1] == "\\" ) end++;
strArr.push(str.substr(i, end));
i += end;
}*/
return strArr;
}
/**
* Checks if the string contains one of the substrings
* @param {string} str The string to check
* @param {string|string[]|DuList.<string>} subStrs The substrings to look for
* @return {boolean}
*/
DuString.contains = function(str, subStrs) {
subStrs = new DuList(subStrs);
for (var i = 0, ni = subStrs.length(); i < ni; i++) {
if ( str.indexOf( subStrs.at(i) ) >= 0 ) return true;
}
return false;
}
// ==================== |------| ====================
// ==================== | file | ====================
// ==================== |------| ====================
/**
* File related methods
* @namespace
* @category DuESF
*/
var DuFile = {};
/**
* The list of legit characters for base64 encoding
* @type {string[]}
*/
DuFile.base64Chars = [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"+",
"/",
];
/**
* Checks if the given path exists
* @param {string} [path] - The file path
* @return {boolean} True or false
*/
DuFile.exists = function(path)
{
var file;
if (path instanceof File) file = path;
else file = new File(path);
return file.exists;
}
/**
* Reads the first line of a file and return its content
* @param {File|string} file - The file
* @return {string} The content
*/
DuFile.readFirstLine = function(file)
{
if (jstype(file) == 'string') file = new File(file);
//open and parse file
if (!file.open('r')) return '';
var data = file.readln();
file.close();
return data;
}
/**
* Reads a whole file and return its content
* @param {File|string} file - The file
* @param {string} [encoding='UTF-8'] - The text encoding
* @return {string} The content
*/
DuFile.read = function(file,encoding)
{
if (jstype(file) == 'string') file = new File(file);
encoding = def(encoding, 'UTF-8');
//open and parse file
file.encoding = encoding;
if (!file.open('r')) return '';
var data = file.read();
file.close();
return data;
}
/**
* Writes a text file
* @param {File} file - The file
* @param {string} content - The content to write
* @param {boolean} [append=false] - Appends instead of replacing
* @param {string} [encoding='UTF-8'] - The text encoding
* @return {boolean} true if the file has been correctly written
*/
DuFile.write = function(file, content, append, encoding)
{
if (!(file instanceof File)) file = new File(file);
append = def(append, false);
encoding = def(encoding, 'UTF-8');
file.encoding = encoding;
var mode = 'w';
if (append) mode = 'a';
try
{
if (!file.open(mode)) return false;
file.write(content);
file.close();
return true;
}
catch (e)
{
DuDebug.throwError( "Can't write file" , 'DuFile.write', e);
}
return false;
}
/**
* Writes a line in a text file
* @param {File} file - The file
* @param {string} content - The content to write
* @param {boolean} [append=false] - Appends instead of replacing
* @param {string} [encoding='UTF-8'] - The text encoding
* @return {boolean} true if the file has been correctly written
*/
DuFile.writeln = function(file, content, append, encoding)
{
append = def(append, false);
encoding = def(encoding, 'UTF-8');
//open and parse file
file.encoding = encoding;
var mode = 'w';
if (append) mode = 'a';
try
{
if (!file.open(mode)) return false;
file.writeln(content);
file.close();
return true;
}
catch (e)
{
DuDebug.throwError( "Can't write file" , 'DuFile.write', e);
}
return false;
}
/**
* Parses a JSON file
* @param {File} file - The file
* @return {Object|null} The content or null if the file couldn't be parsed
*/
DuFile.parseJSON = function(file)
{
if (!(file instanceof File)) file = new File(file);
if (!file.exists) return {};
//open and parse file
var json = DuFile.read(file);
if (json == '') return null;
var data = {};
try { data = JSON.parse(json); } catch (e) { if (DuESF.debug) alert(e.description + '\n\nJSON DATA:\n\n' + json); }
return data;
}
/**
* Saves a js object to a JSON file
* @param {Object} obj - The object to save
* @param {File|string} file - The file or URI
* @return {boolean} true if the file has been correctly written
*/
DuFile.saveJSON = function (obj,file)
{
if (!(file instanceof File)) file = new File(file);
var data = JSON.stringify(obj,null,4);
return DuFile.write( file, data );
}
/**
* Parses a CSV file
* @param {File} file - The file
* @param {string} [delimiter=','] - The delimiter used
* @param {string} [textSeparator='"'] - The separator for texts
* @return {string[]|null} The content (a two-dimensionnal Array) or null if the file couldn't be parsed
*/
DuFile.parseCSV = function(file,delimiter,textSeparator)
{
if (!isdef( delimiter )) delimiter = ',';
if (!isdef( textSeparator )) textSeparator = '"';
var reQuoted = new RegExp("(" + textSeparator + ")((?:\\\\\\1|(?:(?!\\1).))*)\\1");
//open and parse file
var data = DuFile.read(file);
if (data == '') return null;
data = data.split('\n');
for (var i = 0, num = data.length; i < num; i++)
{
//get quoted values
var quotedValues = [];
var line = data[i];
var quoted = reQuoted.exec(line);
while (quoted)
{
quotedValues.push(quoted[2]);
line = line.replace(quoted[0],'%%');
quoted = reQuoted.exec(line);
}
data[i] = line.split(delimiter);
for (var j = 0, numJ = data[i].length; j < numJ; j++)
{
data[i][j] = DuString.trim(data[i][j]);
if (data[i][j] == '%%')
{
data[i][j] = quotedValues.shift();
}
}
}
return data;
}
/**
* Encodes a file to a base64 string.
* @param {string|File} file The file or its path
* @return {string} The base64 string
*/
DuFile.toBase64 = function( file )
{
if (!(file instanceof File)) file = new File(file);
if (!file.exists) return "";
file.encoding = "binary";
file.open("r");
var bin = file.read();
file.close();
var ecdStr = Base64.btoa(bin);
return ecdStr;
}
/**
* Checks if the base64 string seems valid
* @param {string} b64 The string to check
* @return {bool}
*/
DuFile.checkBase64 = function ( b64 )
{
var len = b64.length
if (len % 4 > 0) {
return false;
}
// If there's a dot, that's probably a file name
if ( b64.indexOf(".") >= 0 )
return false;
return true;
}
/**
* Writes a file from a base64 string.
* @param {string} b64 The base64 string
* @param {string|File} file The destination file or its path
* @return {File|null} The File object or null if it fails
*/
DuFile.fromBase64 = function( b64, file )
{
if (b64 == "") return null;
if (!DuFile.checkBase64(b64)) return null;
// Convert to bin
var bin = Base64.atob(b64);
// Write to file
if (!(file instanceof File)) file = new File( file );
file.encoding = 'BINARY';
try
{
if (!file.open('w')) return null;
file.write(bin);
file.close();
return file;
}
catch (e) { return null; }
}
/**
* Shows the default save file dialog and returns the file selected by the user.<r />
* If the user ommits the extension, the default extension will be appended (Mac OS fix).
* @param {string} prompt - The prompt text, displayed if the dialog allows a prompt.
* @param {string} [filter=''] - The file type filter (windows only)
* @param {string} [defaultExtension=''] - The default extension
* @return {File|null} The file or null if the user cancels
*/
DuFile.saveDialog = function(prompt, filter, defaultExtension)
{
defaultExtension = def( defaultExtension, '' );
var saveFile = File.saveDialog(prompt, filter);
if (!saveFile) return null;
if (saveFile.name.indexOf('.') < 1 && defaultExtension != '')
{
if (defaultExtension.indexOf('.') != 0) defaultExtension = '.' + defaultExtension;
saveFile = new File(saveFile.parent.absoluteURI + '/' + saveFile.name + defaultExtension);
}
return saveFile;
}
/**
* Gets the number of a frame from an file/image sequence<br />
* The number must be right before the extension.
* @param {string} path - The path of the frame
*/
DuFile.getSequenceNumber = function(path)
{
var string_noExt = path.substring(0,path.lastIndexOf('.'));
var numberi = 0;
var namei = 0;
for(var i = string_noExt.length-1;i>=0;i--)
{
if(isNaN(parseInt(string_noExt.charAt(i))))
{
numberi = i+1;
break;
}
}
number = string_noExt.substring(numberi,string_noExt.length);
return number;
}
/**
* Moves a file to a new location
* @param {string|File} file The file
* @param {string|File} newURI The new URI/Path (including file name)
* @return {File|null} the new file object, or null if it could not be moved
*/
DuFile.move = function(file, newURI) {
if (!(file instanceof File)) file = new File(file);
if (!file.exists) return null;
if (file.copy(newURI)) {
file.remove();
var f = new File(newURI);
if (f.exists) return f;
else return null;
}
return null;
}
// ==================== |--------| ====================
// ==================== | regexp | ====================
// ==================== |--------| ====================
/**
* JavaScript Regular Expression related methods
* @namespace
* @category DuESF
*/
var DuRegExp = {};
/**
* Escape reg exp reserved characters from a string to build a regular expression compatible string
* @memberof DuRegExp
* @param {string} string - The string to escape
* @return {string} The escaped string
*/
DuRegExp.escape = function (string)
{
return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
/**
* The set containing javascript symbols ( +, -, [, etc.). Useful when parsing javascript code.
* @readonly
*/
DuRegExp.javascriptSymbols = "[\\s=!/*\\-+%()[\\]{};:.]";
/**
* The set containing javascript symbols ( +, -, [, etc.) except the dot. Useful when parsing javascript code.
* @readonly
*/
DuRegExp.javascriptSymbolsNoDot = "[\\s=!/*\\-+%()[\\]{};:]";
/**
* The set containing authorized characters for javascript variable. Useful when parsing javascript code.
* @readonly
*/
DuRegExp.javascriptVarChars = "[a-zA-Z0-9_]";
// ==================== |--------| ====================
// ==================== | number | ====================
// ==================== |--------| ====================
/**
* Number related methods
* @namespace
* @category DuESF
*/
var DuNumber = {};
/**
* Converts a number to a string, adding optionnal leading zeroes
* @param {Number} num - The number
* @param {int} numDigits - The number of digits in the string. Adds leading zeroes
* @param {int} [base=10] - The conversion base
* @return {string} The number as a string
*/
DuNumber.toString = function (num, numDigits, base)
{
if (base == undefined) base = 10;
var result = num.toString(base);
while(numDigits > result.length)
{
result = "0" + result ;
}
return result;
}
// ==================== |------| ====================
// ==================== | math | ====================
// ==================== |------| ====================
/**
* Math related methods
* @namespace
* @category DuESF
*/
var DuMath = {};
/**
* Enum for locations.
* @readonly
* @static
* @enum {int}
*/
DuMath.Location = {
TOP: 1,
TOP_RIGHT: 2,
RIGHT: 3,
BOTTOM_RIGHT: 4,
BOTTOM: 5,
BOTTOM_LEFT: 6,
LEFT: 7,
TOP_LEFT: 8,
CENTER: 0
}
/**
* Checks if the given location is on the right side
* @param {DuMath.Location} location The location
* @returns {boolean}
*/
DuMath.isLocationRight = function(location) {
if (location == DuMath.Location.TOP_RIGHT) return true;
if (location == DuMath.Location.RIGHT) return true;
if (location == DuMath.Location.BOTTOM_RIGHT) return true;
return false;
}
/**
* Checks if the given location is on the center on the horizontal axis
* @param {DuMath.Location} location The location
* @returns {boolean}
*/
DuMath.isLocationHCenter = function(location) {
if (location == DuMath.Location.TOP) return true;
if (location == DuMath.Location.CENTER) return true;
if (location == DuMath.Location.BOTTOM) return true;
return false;
}
/**
* Checks if the given location is on the left side
* @param {DuMath.Location} location The location
* @returns {boolean}
*/
DuMath.isLocationLeft = function(location) {
if (location == DuMath.Location.TOP_LEFT) return true;
if (location == DuMath.Location.LEFT) return true;
if (location == DuMath.Location.BOTTOM_LEFT) return true;
return false;
}
/**
* Checks if the given location is on the top side
* @param {DuMath.Location} location The location
* @returns {boolean}
*/
DuMath.isLocationTop = function(location) {
if (location == DuMath.Location.TOP) return true;
if (location == DuMath.Location.TOP_RIGHT) return true;
if (location == DuMath.Location.TOP_LEFT) return true;
return false;
}
/**
* Checks if the given location is on the center on the vertical axis
* @param {DuMath.Location} location The location
* @returns {boolean}
*/
DuMath.isLocationVCenter = function(location) {
if (location == DuMath.Location.LEFT) return true;
if (location == DuMath.Location.CENTER) return true;
if (location == DuMath.Location.RIGHT) return true;
return false;
}
/**
* Checks if the given location is on the bottom side
* @param {DuMath.Location} location The location
* @returns {boolean}
*/
DuMath.isLocationBottom = function(location) {
if (location == DuMath.Location.BOTTOM_RIGHT) return true;
if (location == DuMath.Location.CENTER) return true;
if (location == DuMath.Location.BOTTOM_LEFT) return true;
return false;
}
/**
* Returns the location of the point relative to the origin. Works with 2D values, the first two coordinates.
* Considers the coordinate [0,0] to be the top left corner of the system: positive values are right, bottom
* @param {float[]} point The point to check
* @param {float[]} [origin=[0,0]] The coordinates of the origin
* @return {DuMath.Location} The location
*/
DuMath.relativeLocation = function(point, origin) {
var x = point[0] - origin[0];
var y = point[1] - origin[1];
if (x == 0 && y == 0) return DuMath.Location.CENTER;
if (x < 0 && y < 0) return DuMath.Location.TOP_LEFT;
if (x < 0 && y == 0) return DuMath.Location.LEFT;
if (x < 0 && y > 0) return DuMath.Location.BOTTOM_LEFT;
if (x == 0 && y > 0) return DuMath.Location.BOTTOM;
if (x > 0 && y > 0) return DuMath.Location.BOTTOM_RIGHT;
if (x > 0 && y == 0) return DuMath.Location.RIGHT;
if (x > 0 && y < 0) return DuMath.Location.TOP_RIGHT;
if (x == 0 && y < 0) return DuMath.Location.TOP;
}
/**
* Generates a random integer between minimum and maximum
* @memberof DuMath
* @param {int} [min=0] - The minimum value
* @param {int} [max=1] - The maximum value
* @return {int} The randomly generated integer
*/
DuMath.random = function(min, max) {
if (min == undefined) min = 0;
if (max == undefined) max = 1;
var rng = null;
$.sleep(10);
var date = new Date();
var rng = new Math.seedrandom(date.getTime())();
//rng = Math.random();
if (!rng) return 0;
return rng * (max - min) + min;
}
/**
* Generates a random integer between minimum and maximum.<br/>
* The results are distributed along a gaussian (bell) curve.<br/>
* Note that a few (< 10%) values may be outside of the range. Set the <code>bounded</code> to true to avoid that.
* @memberof DuMath
* @param {int} [min=0] - The minimum value
* @param {int} [max=1] - The maximum value
* @param {Bool} [bounded=false] - When this is false, a few values may be outside the range. Set it to true to make sure all values are between min and max.
* @return {int} The randomly generated integer
*/
DuMath.gaussRandom = function(min, max, bounded) {
min = def(min, 0);
max = def(max, 1);
bounded = def(bounded, false);
var u = 0, v = 0;
while(u === 0) u = DuMath.random(); //Converting [0,1) to (0,1)
while(v === 0) v = DuMath.random();
var num = Math.sqrt( -2.0 * Math.log( u ) ) * Math.cos( 2.0 * Math.PI * v );
num = num / 10.0 + 0.5; // Translate to 0 -> 1
if (bounded && (num > 1 || num < 0)) return DuMath.gaussRandom(min, max, true); // resample between 0 and 1
num *= max - min; // Stretch to fill range
num += min; // offset to min
return num;
}
/**
* Measures the vector length between two points
* @param {int[]} value1 - The first value
* @param {int[]} value2 - The second value
* @return {float} The length
*/
DuMath.length = function(value1, value2) {
if (typeof value1 !== typeof value2) {
return null;
}
if (value1.length > 0) {
var result = 0;
for (var dim = 0; dim < value1.length; dim++) {
result += (value1[dim] - value2[dim]) * (value1[dim] - value2[dim]);
}
result = Math.sqrt(result);
return result;
} else return Math.abs(value1 - value2);
}
/**
* Compares two numbers
* @memberof DuMath
* @param {Number|Number[]} value1 - The first value
* @param {Number|Number[]} value2 - The second value
* @param {int} [floatPrecision=-1] - The precision for (float) number comparison, number of decimals. Set to -1 to not use.
* @return {boolean} true if the two values are equal
*/
DuMath.equals = function(value1, value2, floatPrecision, test) {
floatPrecision = def(floatPrecision, -1);
value1 = new DuList(value1);
value2 = new DuList(value2);
if (value1.length() != value2.length()) return false;
var item1 = value1.next();
while(item1 !== null)
{
var item2 = value2.at(value1.current);
if (jstype(item1) !== jstype(item2)) return false;
if (jstype(item1) === 'array') return DuMath.equals(item1, item2, floatPrecision);
if (jstype(item1) !== 'number') return false;
if (floatPrecision >= 0) {
var mul = Math.pow(10, floatPrecision);
var num1 = Math.round(item1 * mul) / mul;
var num2 = Math.round(item2 * mul) / mul;
if (num1 != num2) return false;
} else if (item1 != item2) return false;
item1 = value1.next();
}
return true;
}
/**
* Calculates the log10 of a number
* @memberof DuMath
* @param {float} w - The number
* @return {float} The result of log10(x)<br />
* i.e. Math.log(x)/Math.LN10
*/
DuMath.log10 = function(x) {
return Math.log(x) / Math.LN10;
}
/**
* Calculates the average value in a list
* @memberof DuMath
* @param {Number|Number[]} values - values
* @return {float} The average value
*/
DuMath.average = function(values) {
if (!(values instanceof Array)) return values;
var sum = 0;
var total = values.length;
if (total == 0) return 0;
for (var i = 0; i < total; i++) {
sum += values[i];
}
return sum / total;
}
/**
* Alias for {@link DuMath.average}.
* @memberof DuMath
* @function
*/
DuMath.mean = DuMath.average;
/**
* Clamps the value
* @memberof DuMath
* @param {Number|Number[]} values - values
* @param {Number} [min=0] - The minimum value
* @param {Number} [max=1] - The maximum value
* @return {Number|Number[]} The clamped values
*/
DuMath.clamp = function(values, min, max) {
if (!isdef( min )) min = 0;
if (!isdef( max )) max = 1;
if (max < min) {
var t = max;
max = min;
min = t;
}
var isArray = true;
var result = [];
if (!(values instanceof Array)) {
isArray = false;
values = [values];
}
for (var i = 0, num = values.length; i < num; i++) {
var v = values[i];
if (v < min) v = min;
if (v > max) v = max;
result.push(v);
}
if (isArray) return result;
else return result[0];
}
/**
* Converts the number from degrees to radians
* @memberof DuMath
* @param {number} value - the value
* @return {float} The value in radians
*/
DuMath.toRadians = function(value) {
return value * Math.PI / 180;
}
/**
* Converts the number from radians to degrees
* @memberof DuMath
* @param {number} value - the value
* @return {float} The value in degrees
*/
DuMath.toDegrees = function(value) {
return value * 180 / Math.PI;
}
/**
* The logistic function (sigmoid)
* @param {Number} value The value
* @param {Number} [midValue=0] The midpoint value, at which the function returns max/2
* @param {Number} [min=0] The minimum return value
* @param {Number} [max=1] The maximum return value
* @param {Number} [rate=1] The logistic growth rate or steepness of the function
* @return {Number} The result in the range [min, max] (excluding min and max)
*/
DuMath.logistic = function(value, midValue, min, max, rate) {
if (!isdef( midValue )) midValue = 0;
if (!isdef( min )) min = 0;
if (!isdef( max )) max = 1;
if (!isdef( rate )) rate = 1;
var exp = -rate * (value - midValue);
var result = 1 / (1 + Math.pow(Math.E, exp));
return result * (max - min) + min;
}
/**
* The inverse logistic function (inverse sigmoid)
* @param {Number} v The variable
* @param {Number} [midValue=0] The midpoint value, at which the function returns max/2 in the original logistic function
* @param {Number} [min=0] The minimum return value of the original logistic function
* @param {Number} [max=1] The maximum return value of the original logistic function
* @param {Number} [rate=1] The logistic growth rate or steepness of the original logistic function
* @return {Number} The result
*/
DuMath.inverseLogistic = function(v, midValue, min, max, rate) {
if (!isdef( midValue )) midValue = 0;
if (!isdef( max )) max = 1;
if (!isdef( min )) min = 0;
if (!isdef( rate )) rate = 1;
if (v == min) return 0;
return midValue - Math.log((max - min) / (v - min) - 1) / rate;
}
/**
* The gaussian function
* @param {Number} value The variable
* @param {Number} [min=0] The minimum return value
* @param {Number} [max=1] The maximum return value
* @param {Number} [center=0] The center of the peak
* @param {Number} [fwhm=1] The full width at half maximum of the curve
* @return {Number} The result
*/
DuMath.gaussian = function(value, min, max, center, fwhm) {
if (!isdef( max )) max = 1;
if (!isdef( min )) min = 0;
if (!isdef( center )) center = 0;
if (!isdef( fwhm )) fwhm = 1;
if (fwhm === 0 && value == center) return max;
else if (fwhm === 0) return 0;
var exp = -4 * Math.LN2;
exp *= Math.pow((value - center), 2);
exp *= 1 / Math.pow(fwhm, 2);
var result = Math.pow(Math.E, exp);
return result * (max - min) + min;
}
/**
* A "reversed" gaussian function, growing faster with low value
* @param {Number} value The variable
* @param {Number} [min=0] The minimum return value
* @param {Number} [max=1] The maximum return value
* @param {Number} [center=0] The center of the peak
* @param {Number} [fwhm=1] The full width at half maximum of the curve
* @return {Number} The result
*/
DuMath.reversedGaussian = function(value, min, max, center, fwhm) {
r = -value - fwhm + 1;
return gaussian(value, min, max, center, r);
}
/**
* The inverse gaussian function
* @param {Number} v The variable
* @param {Number} [min=0] The minimum return value of the corresponding gaussian function
* @param {Number} [max=1] The maximum return value of the corresponding gaussian function
* @param {Number} [center=0] The center of the peak of the corresponding gaussian function
* @param {Number} [fwhm=1] The full width at half maximum of the curve of the corresponding gaussian function
* @return {Number[]} The two possible results, the lower is the first in the list. If both are the same, it is the maximum
*/
DuMath.inverseGaussian = function(v, min, max, center, fwhm) {
if (!isdef( max )) max = 1;
if (!isdef( min )) min = 0;
if (!isdef( center )) center = 0;
if (!isdef( fwhm )) fwhm = 1;
if (v == 1) return [center, center];
if (v === 0) return [center + fwhm / 2, center - fwhm / 2];
if (fwhm === 0) return [center, center];
var result = (v - min) / (max - min);
result = Math.log(result) * Math.pow(fwhm, 2);
result = result / (-4 * Math.LN2);
result = Math.sqrt(result);
return [result + center, -result + center];
}
/**
* The inverse of the reversed gaussian function
* @param {Number} value The variable
* @param {Number} [min=0] The minimum return value of the corresponding gaussian function
* @param {Number} [max=1] The maximum return value of the corresponding gaussian function
* @param {Number} [center=0] The center of the peak of the corresponding gaussian function
* @param {Number} [fwhm=1] The full width at half maximum of the curve of the corresponding gaussian function
* @return {Number[]} The two possible results, the lower is the first in the list. If both are the same, it is the maximum
*/
DuMath.inverseReversedGaussian = function(value, min, max, center, fwhm) {
r = -value - fwhm + 1;
return inverseGaussian(value, min, max, center, r);
}
/**
* The linear function
* @param {Number} value The variable
* @param {Number} [min=0] The minimum input value
* @param {Number} [max=1] The maximum input value
* @param {Number} [targetMin=0] The minimum output value
* @param {Number} [targetMax=1] The maximum output value
* @param {Boolean} [clamp=false] Whether to clamp the output value to the target or not.
* @returns {Number}
*/
DuMath.linear = function(value, min, max, targetMin, targetMax, clamp) {
min = def(min, 0);
max = def(max, 1);
targetMin = def(targetMin, 0);
targetMax = def(targetMax, 1);
clamp = def(clamp, false);
// Handle stupid values
if (min == targetMin && max == targetMax) return value;
if (min == max) return value;
if (targetMin == targetMax) return targetMin;
if (value == min) return targetMin;
if (value == max) return targetMax;
var result = targetMin + (value - min) / (max - min) * (targetMax - targetMin);
if (clamp) result = DuMath.clamp(value, targetMin, targetMax);
return result;
}
/**
* Checks if a point is located inside given bounds
* @param {float[]} point The point
* @param {float[]} bounds The bounds. The number of bounds must be at least twice the number of dimensions, in this order : [a1, a2, ..., x1,x2,y1,y2,z1,z2]
* @return {Boolean} true if the point is inside the bounds
*/
DuMath.isInside = function(point, bounds) {
for (var i = 0, n = point.length; i < n; i++) {
var c = point[i];
var c1 = bounds[i * 2];
var c2 = bounds[i * 2 + 1];
if (c < c1) return false;
if (c > c2) return false;
}
return true;
}
/**
* Checks the sign of a number
* @param {Number} num The number to check
* @returns {int} 1 if num is positive, -1 if negative, 0 in other cases (0, NaN...)
*/
DuMath.sign = function(num) {
if (num < 0) return -1;
if (num > 0) return 1;
return 0;
}
/**
* Gets the bounds of the values
* @param {Number[][]} values A list of values
* @return {float[]} The bounds, for N dimensions: [min1, min2, ..., minN, max1, max2, ..., maxN]
*/
DuMath.bounds = function(values) {
var mins = [];
var maxs = [];
for (var i = 0, n = values.length; i < n; i++) {
var v = values[i];
for (var j = 0, d = v.length; j < d; j++) {
if (typeof(mins[j]) === 'undefined') mins[j] = v[j];
else if (v[j] < mins[j]) mins[j] = v[j];
if (typeof(maxs[j]) === 'undefined') maxs[j] = v[j];
else if (v[j] > maxs[j]) maxs[j] = v[j];
}
}
return mins.concat(maxs);
}
/**
* Checks if the direction changes (if the point is at an extreme value). If the values have multiple dimensions, checks each axis individually.
* @param {Number|Number[]} previousValue The value just before the point.
* @param {Number|Number[]} point The point to check.
* @param {Number|Number[]} nextValue The value just after the point.
* @param {int} [precision=1] The precision for floating point comparisons; number of decimals.
* @return {Boolean}
*/
DuMath.isExtremePoint = function(previousValue, point, nextValue, precision) {
previousValue = new DuList(previousValue);
point = new DuList(point);
nextValue = new DuList(nextValue);
for (var i = 0, n = point.length(); i < n; i++) {
var p = point.at(i);
var pv = previousValue.at(i);
var nx = nextValue.at(i);
if (DuMath.equals(p, pv, 1) && DuMath.equals(p, nx, 1)) continue;
// Get the dif
var pDif = p-pv;
var nDif = p-nx;
// If the sign is the same, this is an extreme
if (pDif >= 0 && nDif >= 0) return true;
if (pDif <= 0 && nDif <= 0) return true;
// If one of the two is 0, this is an extreme
if (DuMath.equals(pDif, 0.0, 1)) return true;
if (DuMath.equals(nDif, 0.0, 1)) return true;
}
return false;
}
/**
* Checks if the point is an inflexion point.
* @param {Number|Number[]} previousVelocity The derivative (speed) of a point just before the point.
* @param {Number|Number[]} pointVelocity The derivative (speed) at the point to check.
* @param {Number|Number[]} nextVelocity The derivative (speed) of a point just after the point.
* @return {Boolean}
*/
DuMath.isInflexionPoint = function(previousVelocity, pointVelocity, nextVelocity) {
previousVelocity = new DuList(previousVelocity);
pointVelocity = new DuList(pointVelocity);
nextVelocity = new DuList(nextVelocity);
for (var i = 0, n = pointVelocity.length(); i < n; i++) {
var p = pointVelocity.at(i);
var pv = previousVelocity.at(i);
var nx = nextVelocity.at(i);
if (p == pv && p == nx) continue;
if ( p >= pv && p >= nx) return true;
if ( p <= pv && p <= nx) return true;
}
return false;
}
/**
* Finds the angle formed by three points
* @param {float[]} anglePoint The point at which to measure the angle
* @param {float[]} oppositePointA One of the opposite points
* @param {float[]} oppositePointB The other opposite point
* @return {float} The angle in radians.
*/
DuMath.angleFromSides = function( anglePoint, oppositePointA, oppositePointB ) {
var sideA = DuMath.length(anglePoint, oppositePointA);
var sideB = DuMath.length(anglePoint, oppositePointB);
var oppositeSide = DuMath.length(oppositePointA, oppositePointB);
var result = sideA*sideA + sideB*sideB - oppositeSide*oppositeSide;
result = result / (2*sideA*sideB);
result = Math.acos(result);
return result;
}
// ==================== |---------------| ====================
// ==================== | interpolation | ====================
// ==================== |---------------| ====================
/**
* Interpolation methods
* @namespace
* @category DuESF
*/
var DuInterpolation = {};
/**
* Linear (extra/inter)polation
* @param {Number} value The variable
* @param {Number} [min=0] The minimum input value
* @param {Number} [max=1] The maximum input value
* @param {Number} [targetMin=0] The minimum output value
* @param {Number} [targetMax=1] The maximum output value
* @param {Boolean} [extrapolate=true] Whether to extrapolate outside the target.
* @returns {Number}
*/
DuInterpolation.linear = function(value, min, max, targetMin, targetMax, extrapolate) {
extrapolate = def(extrapolate, true);
return DuMath.linear(value, min, max, targetMin, targetMax, !extrapolate)
};
/**
* Interpolates a value with a bezier curve.
* @param {number} t The value to interpolate
* @param {number} [tMin=0] The minimum value of the initial range
* @param {number} [tMax=1] The maximum value of the initial range
* @param {number} [value1=0] The minimum value of the interpolated result
* @param {number} [value2=1] The maximum value of the interpolated result
* @param {number[]} [bezierPoints=[0.33,0.0,0.66,1.0]] an Array of 4 coordinates wihtin the [0.0, 1.0] range which describes the Bézier interpolation. The default mimics the native ease() function<br />
* [ outTangentX, outTangentY, inTangentX, inTangentY ]
* @return {number} the value.
*/
DuInterpolation.bezier = function(t, tMin, tMax, value1, value2, bezierPoints) {
tMin = def(tMin, 0);
tMax = def(tMax, 1);
value1 = def(value1, 0);
value2 = def(value2, 1);
bezierPoints = def(bezierPoints, [0.33,0.0,0.66,1.0]);
if (arguments.length !== 5 && arguments.length !== 6) return (value1+value2)/2;
var a = value2 - value1;
var b = tMax - tMin;
if (b == 0) return (value1+value2)/2;
var c = DuMath.clamp((t - tMin) / b, 0, 1);
if (!(bezierPoints instanceof Array) || bezierPoints.length !== 4) bezierPoints = [0.33,0.0,0.66,1];
return a * h(c, bezierPoints) + value1;
function h(f, g) {
var x = 3 * g[0];
var j = 3 * (g[2] - g[0]) - x;
var k = 1 - x - j;
var l = 3 * g[1];
var m = 3 * (g[3] - g[1]) - l;
var n = 1 - l - m;
var d = f;
for (var i = 0; i < 5; i++) {
var z = d * (x + d * (j + d * k)) - f;
if (Math.abs(z) < 1e-3) break;
d -= z / (x + d * (2 * j + 3 * k * d));
}
return d * (l + d * (m + d * n));
}
}
/**
* Interpolates and extrapolates a value with an exponential function.
* @param {number} t The value to interpolate
* @param {number} [tMin=0] The minimum value of the initial range
* @param {number} [tMax=1] The maximum value of the initial range
* @param {number} [value1=0] The minimum value of the interpolated result
* @param {number} [value2=1] The maximum value of the interpolated result
* @param {number} [rate=1] The raising speed in the range [0, inf].
* @return {number} the value.
*/
DuInterpolation.exponential = function(t, tMin, tMax, vMin, vMax, rate)
{
tMin = def(tMin, 0);
tMax = def(tMax, 1);
vMin = def(vMin, 0);
vMax = def(vMax, 1);
rate = def(rate, 1);
if (rate == 0) return DuInterpolation.linear(t, tMin, tMax, vMin, vMax);
// handle tMax < tMin
var rev = false;
if (tMax < tMin) {
var temp = tMax;
tMax = tMin;
tMin = temp;
rev = true;
}
// Offset t to be in the range 0-Max
tMax = ( tMax - tMin ) * rate;
t = ( t - tMin ) * rate;
// Compute the max
var m = Math.exp(tMax);
// Compute current value
t = Math.exp(t);
var result = DuInterpolation.linear(t, 1, m, vMin, vMax);
if (rev) result = DuInterpolation.linear(result, vMin, vMax, vMax, vMin);
return result;
}
/**
* Interpolates a value with a gaussian function.
* @function
* @param {number} t The value to interpolate
* @param {number} [tMin=0] The minimum value of the initial range
* @param {number} [tMax=1] The maximum value of the initial range
* @param {number} [value1=0] The minimum value of the interpolated result
* @param {number} [value2=1] The maximum value of the interpolated result
* @param {number} [rate=0] The raising speed in the range [-1.0, 1.0].
* @return {number} the value.
*/
DuInterpolation.gaussian = function( t, tMin, tMax, value1, value2, rate )
{
tMin = def(tMin, 0);
tMax = def(tMax, 1);
value1 = def(value1, 0);
value2 = def(value2, 1);
rate = def(rate, 0);
// handle tMax < tMin
var rev = false;
if (tMax < tMin) {
var temp = tMax;
tMax = tMin;
tMin = temp;
rev = true;
}
// fix small bump at first value
if (t != tMin)
{
var newValue1 = DuInterpolation.gaussian( tMin, tMin, tMax, value1, value2, rate );
var offset = newValue1 - value1;
value1 = value1 - offset;
}
if (rate < 0) rate = rate*10;
rate = DuInterpolation.linear(t, tMin, tMax, 0.25, rate);
var r = ( 1 - rate );
var fwhm = (tMax-tMin) * r;
var center = tMax;
if (t >= tMax) {
if (rev) return value1;
return value2;
}
if (fwhm === 0 && t == center) {
if (rev) return value1;
return value2;
}
else if (fwhm === 0) {
if (rev) return value2;
return value1;
}
var exp = -4 * Math.LN2;
exp *= Math.pow((t - center),2);
exp *= 1/ Math.pow(fwhm, 2);
var result = Math.pow(Math.E, exp);
result = result * (value2-value1) + value1;
if (rev) result = DuInterpolation.linear( result, value1, value2, value2, value1);
return result;
}
/**
* Interpolates and extrapolates a value with a logarithmic function.
* @function
* @param {number} t The value to interpolate
* @param {number} [tMin=0] The minimum value of the initial range
* @param {number} [tMax=1] The maximum value of the initial range
* @param {number} [value1=0] The minimum value of the interpolated result
* @param {number} [value2=1] The maximum value of the interpolated result
* @param {number} [rate=1] The raising speed in the range [0, inf].
* @return {number} the value.
*/
DuInterpolation.logarithmic = function(t, tMin, tMax, vMin, vMax, rate)
{
tMin = def(tMin, 0);
tMax = def(tMax, 1);
vMin = def(vMin, 0);
vMax = def(vMax, 1);
rate = def(rate, 1);
if (rate == 0) return DuInterpolation.linear(t, tMin, tMax, vMin, vMax);
// handle tMax < tMin
var rev = false;
if (tMax < tMin) {
var temp = tMax;
tMax = tMin;
tMin = temp;
rev = true;
}
// Offset t to be in the range 0-Max
tMax = ( tMax - tMin ) * rate + 1;
t = ( t - tMin ) * rate + 1;
if (t <= 1) {
if (rev) return vMax;
return vMin;
}
// Compute the max
var m = Math.log(tMax);
// Compute current value
var v = Math.log(t);
var result = DuInterpolation.linear(v, 0, m, vMin, vMax);
if (rev) result = DuInterpolation.linear(result, vMin, vMax, vMax, vMin);
return result;
}
/**
* Interpolates and extrapolates a value with a logistic (sigmoid) function.
* @function
* @param {number} t The value to interpolate
* @param {number} [tMin=0] The minimum value of the initial range
* @param {number} [tMax=1] The maximum value of the initial range
* @param {number} [value1=0] The minimum value of the interpolated result
* @param {number} [value2=1] The maximum value of the interpolated result
* @param {number} [rate=1] The raising speed in the range [0, inf].
* @param {number} [tMid] The t value at which the interpolated value should be half way. By default, (tMin+tMax)/2
* @return {number} the value
*/
DuInterpolation.logistic = function( t, tMin, tMax, value1, value2, rate, tMid )
{
tMin = def(tMin, 0);
tMax = def(tMax, 1);
value1 = def(value1, 0);
value2 = def(value2, 1);
rate = def(rate, 1);
tMid = def(tMid, (tMin+tMax)/2);
if (rate == 0) return DuInterpolation.linear(t, tMin, tMax, value1, value2);
t = DuMath.logistic( t, tMid, tMin, tMax, rate);
// Scale to actual min/max
var m = DuMath.logistic( tMin, tMid, tMin, tMax, rate);
var M = DuMath.logistic( tMax, tMid, tMin, tMax, rate);
return DuInterpolation.linear( t, m, M, value1, value2);
}
// ==================== |------| ====================
// ==================== | date | ====================
// ==================== |------| ====================
/**
* Date related methods
* @namespace
* @category DuESF
*/
var DuDate = {};
// low-level undocumented list. Use getMonth and getMonthName
DuDate.MonthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
/**
* Gets the month number from a literal localized name. 0 is january, 11 is december
* @memberof DuDate
* @param {string} string - The month name
* @return {int} The month number
*/
DuDate.getMonth = function (string)
{
string = string.toLowerCase();
for (var i = 0; i < 12; i++)
{
if ( DuDate.MonthNames[i].toLowerCase().indexOf(string) >= 0 ) return i;
}
return 0;
}
/**
* Gets the month name from an index. 0 is january, 11 is december
* @memberof DuDate
* @param {int} string - The month index
* @return {string} The month name
*/
DuDate.getMonthName = function (index)
{
if (index >= 0 && index < 12) return i18n._(DuDate.MonthNames[i]);
return "";
}
/**
* Returns a pretty formatted string representing the date
* @todo format option
* @memberof DuDate
* @param {Date} date - The date
* @return {string} The date
*/
DuDate.toString = function (date)
{
var hour = date.getHours();
var minute = date.getMinutes();
var second = date.getSeconds();
var day = date.getDate();
var month = date.getMonth();
var year = date.getFullYear();
return year + "-" + (month+1) + "-" + day + " " + hour + "-" + minute + "-" + second;
}
// ==================== |--------| ====================
// ==================== | binary | ====================
// ==================== |--------| ====================
/**
* Constructs a new string representation of a binary file
* @class DuBinary
* @classdesc Represents a binary file as a string and a filename.
* @param {string} binAsString - The string representation.
* @param {string} fileName - The name of the original file.
* @param {string} [category=''] - A Category for the file, will be used as a subfolder to extract files. Can have subcategories like "category/subcategory".
* @category DuESF
*/
function DuBinary( binAsString, fileName, category)
{
/**
* The string representation of the binary file.
* @memberof DuBinary
* @type {string}
* @name binAsString
*/
this.binAsString = binAsString;
fileName = DuString.replace(fileName,'%20','_');
/**
* The name of the original file.
* @memberof DuBinary
* @type {string}
* @name fileName
*/
this.fileName = fileName;
if (category === undefined) category = '';
/**
* A Category for the file, will be used as a subfolder to extract files. Can have subcategories like "category/subcategory".
* @memberof DuBinary
* @type {string}
* @name category
*/
this.category = category;
}
/**
* Returns a string representation of the {@link DuBinary} which can be written in a jsxinc file.
* @returns {string} The source.
*/
DuBinary.prototype.toSource = function ()
{
return 'new DuBinary( "' + this.binAsString + '", "' + this.fileName + '", "' + this.category + '" )';
}
/**
* Writes the file.
* @param {string} [outputFileName=DuFolder.duesfData/category/binaryfilename] - The output filename.
* @param {boolean} [onlyAtFirstRun=true] - Does not extract the file if it already exists and this is not the first time this version of the script is being used.<br />
* The file will be extracted only if the script is new or has just been updated (the version changed).
* @return {File|null} The file created, null if the file could not be written.<br />
* If the file is not written, check user permissions, and check if the file and network access preference is checked.
* @example
* //First, include the text representation of the file (Add a # before the include word)
* include executable.exe.jsxinc
* // Now, a variable called `executable` (the original file name without extension) is available, it's an instance of DuBinary.
* var execFile = executable.extract();
* //Now, the file exists in the file system, and execFile is an ExtendScript File object.
* // The `DuBinary.extract()` method extracts the file to the Application Data folder by default.
* execFile.fsName; // C:\users\duduf\appData\Roaming\RxMaboratory\AdobeScripts\DuESF\icon.png (Example on Windows)
* @example
* //You can specify the output file name. (Add a # before the include word)
* include preset.ffx.jsxinc
* var presetFile = preset.extract("C:/test/test_preset.ffx");
* presetFile.fsName; // C:\test\test_preset.exe");
*/
DuBinary.prototype.toFile = function( outputFileName, onlyAtFirstRun )
{
onlyAtFirstRun = def( onlyAtFirstRun, true );
var rootFolder = DuFolder.duesfData.absoluteURI;
outputFileName = def( outputFileName, rootFolder + '/' + this.category + '/' + this.fileName )
var file = new File(outputFileName);
// performance
// do not extract if the file exists and we're not in the first run (or debug mode)
if (file.exists && !DuESF.scriptFirstRun && !DuESF.debug && onlyAtFirstRun)
{
return file;
}
if (!file.exists)
{
var folder = new Folder(file.path);
if (!folder.exists)
{
folder.create();
}
}
DuFile.write( file, this.binAsString, false, 'BINARY');
return file;
}
/**
* Writes the DuBinary to a jsxinc file
* @param {string} outputFilePath - The file path for the output.
* @param {any} [varName=File name without extension] - The name of the variable used to store the javascript object.
* @returns {File} the new jsxinc file.
*/
DuBinary.prototype.toJsxincFile = function( outputFilePath, varName )
{
var fileName = DuPath.getBasename(outputFilePath);
varName = def (varName, fileName.replace(/\..+/,'') );
varName = DuString.replace(varName,'%20','_');
var jsString = 'var ' + varName + ' = ' + this.toSource() + ';\r\n' + varName + ';\r\n';
var outputFile = new File( outputFilePath );
outputFile.encoding = 'UTF8';
if (outputFile.open("w", "TEXT", "????"))
{
outputFile.write(jsString);
outputFile.close();
}
return outputFile;
}
// =============== STATIC =====================
/**
* Creates a {@link DuBinary} object from an existing file
* @static
* @param {File} file - The File to convert
* @param {string} [category] - A Category for the file, will be used as a subfolder to extract files. Can have subcategories like "category/subcategory".
* @return {DuBinary} The {@link DuBinary} object containing the file as a string
*/
DuBinary.fromFile = function ( file, category )
{
var content = '';
file.encoding = "BINARY";
if (file.open("r"))
{
content = file.read().toSource();
content = content.substr(content.indexOf("\"")+1, content.length);
content = content.substr(0, content.lastIndexOf("\""));
file.close();
}
return new DuBinary(content,file.name,category);
}
/**
* Converts a file to a jsxinc file
* @static
* @param {File} file - The binary file to convert
* @param {string} [category] - A Category for the file, will be used as a subfolder to extract files. Can have subcategories like "category/subcategory". Default is the name of the folder containing the file.
* @param {string} [outputFilePath=Same folder, same name with .json extension] - The file name for the output.
* @param {string} [varName=File name without extension] - The name of the variable used to store the javascript object.
* @return {File} The jsxinc file created
*/
DuBinary.toJsxincFile = function(file,category,outputFilePath,varName)
{
category = def (category, file.parent.name );
outputFilePath = def (outputFilePath, file.absoluteURI + '.jsxinc' );
var duBinary = DuBinary.fromFile(file,category);
return duBinary.toJsxincFile( outputFilePath, varName );
}
// ============== DEPRECATED ==================
/**
* Creates a file from a DuBinary object representation.
* @static
* @deprecated
* @param {DuBinary} DuBinary - The DuBinary object containing the string representation.<br />
* This object will be replaced by the File object created.<br />
* If a file object is provided (the file has already been extracted), does nothing.
* @param {string} [outputFileName=DuFolder.duesfData/DuAEF/scriptName/category/binaryfilename] - The output filename.
* @param {boolean} [onlyAtFirstRun=true] - Does not extract the file if it already exists and this is not the first time this version of the script is being used.<br />
* The file will be extracted only if the script is new or has just been updated (the version changed).
* @return {File|null} The file created, null if the file could not be written.<br />
* If the file is not written, check user permissions, and check if the file and network access preference is checked.
* @example
* //First, include the text representation of the file (Add a # before the include word)
* include executable.exe.jsxinc
* // Now, a variable called `executable` (the original file name without extension) is available, you can pass this object to the `DuBinary.toFile()` method to extract it and get an ExtendScript File object representation of it.
* // Note: This object is an instance of a `DuBinary` class, which contains all information and a string representation of the original binary file
* var execFile = DuBinary.toFile(executable);
* //Now, the file exists in the file system, and execFile is an ExtendScript File object.
* // The `DuBinary.toFile()` method extracts the file to the Application Data folder by default.
* execFile.fsName; // C:\users\duduf\appData\Roaming\DuAEF\icon.png (Example on Windows)
* @example
* //You can specify the output file name. (Add a # before the include word)
* include preset.ffx.jsxinc
* var presetFile = DuBinary.toFile(preset,"C:/test/test_preset.ffx");
* presetFile.fsName; // C:\test\test_preset.exe");
* @category DuESF
*/
DuBinary.toFile = function(DuBinary,outputFileName,onlyAtFirstRun)
{
onlyAtFirstRun = def( onlyAtFirstRun, true );
if (!isdef( DuBinary )) return;
if (DuBinary instanceof File) return;
var rootFolder = DuFolder.duesfData.absoluteURI;
if (DuESF.scriptName != '') rootFolder += '/' + DuESF.scriptName;
if (!isdef( outputFileName )) outputFileName = rootFolder + '/' + DuBinary.category + '/' + DuBinary.fileName;
var file = new File(outputFileName);
// performance
// do not extract if the file exists and we're not in the first run (or debug mode)
if (file.exists && !DuESF.scriptFirstRun && !DuESF.debug)
{
return file;
}
if (!file.exists)
{
var folder = new Folder(file.path);
if (!folder.exists)
{
folder.create();
}
}
file.encoding = 'BINARY';
if (file.open('w'))
{
success = file.write(DuBinary.binAsString);
file.close();
}
DuBinary = file;
return file;
}
/**
* Converts a JS file to a binary file
* @deprecated
* @static
* @param {File} jsFile - The JS file to convert
* @param {string} [outputFileName=DuFolder.duesfData/category/binaryfilename] - The output filename.
* @return {File} The binary file created, or null if failed
* @example
* //If you don't want to include the file in the script. (Add a # before the include word)
* var stringFile = new File("C:\test\image.jpg.jsxinc");
* var jpgFile = DuBinary.convertToBinaryFile(stringFile);
* // Warning, this method uses `$.eval()` which is a bad security issue.
* // Do not use this method for anything else than debugging and testing.
*/
DuBinary.convertToBinaryFile = function(jsFile,outputFileName)
{
//TODO parse instead of eval
var obj = $.eval(jsFile);
return this.toFile(obj);
}
// ==================== |---------| ====================
// ==================== | process | ====================
// ==================== |---------| ====================
/**
* Constructs a new DuProcess instance
* @class DuProcess
* @classdesc A class to launch and manage processes, using the system command line.
* @param {string} processPath - The path to the process executable binary
* @param {string[]} [args] - The common arguments used to run the process. You can append arguments each time you run the process with start(args)
* @param {int} [timeout=0] - Waiting timeout after process start, in ms, 0 to avoid waiting, -1 for infinite.
* If the process times out, process will not be killed, the script will just stop waiting.
* @category DuESF
*/
function DuProcess( processPath, args, timeout ) {
processPath = def( processPath, "");
args = def( args, []);
timeout = def( timeout, 0);
/**
* The path to the process executable binary
* @type {string}
* @name processPath
* @memberof DuProcess
* @readonly
*/
this.processPath = processPath;
/**
* The common arguments used to run the process. You can append arguments each time you run the process with start(args)
* @type {any[]}
* @name args
* @memberof DuProcess
*/
this.args = args;
/**
* Waiting timeout after process start, in ms, 0 to avoid waiting, -1 for infinite.<br />
* If the process times out, process will not be killed, the script will just stop waiting. Default: 0
* @type {int}
* @name timeout
* @memberof DuProcess
*/
this.timeout = timeout;
/**
* True if the process path leads to an application package (.app folder containing at least "/Contents/MacOS") on mac. False otherwise.
* @type {Boolean}
* @name isAppPackage
* @memberof DuProcess
* @readonly
*/
this.isAppPackage = DuProcess.isAppPackage( processPath );
/**
* An Array of arguments array.<br />
* Arrays of arguments in the queue will be processed one after each other.<br />
* Update the Array and launch the queue with startQueue()
* @type {string[][]}
* @name queue
* @memberof DuProcess
*/
this.queue = [];
/**
* The latest command which has been run.
* @type {string}
* @name latestCommand
* @memberof DuProcess
* @readonly
*/
this.latestCommand = '';
}
/**
* Starts the process
* @memberof DuProcess
* @param {Array} args - Args to append to {@link DuProcess.args} before starting
* @param {int} [timeout] - Overrides the default timeout.
*/
DuProcess.prototype.start = function ( args, timeout ) {
timeout = def(timeout, this.timeout);
var cmd = this.buildCmd( args );
this.startCmd( cmd );
this.latestCommand = cmd;
if (timeout > 100) this.waitForFinished( timeout );
}
/**
* Starts the queue
* @memberof DuProcess
* @param {int} [timeout] - Overrides the default timeout.
*/
DuProcess.prototype.startQueue = function ( timeout ) {
if ( timeout === undefined ) timeout = this.timeout;
var cmd = this.buildQueueCmd();
this.startCmd( cmd );
this.latestCommand = cmd;
this.queue = [];
this.waitForFinished( timeout );
}
/**
* Waits for the process to finish
* @memberof DuProcess
* @param {int} [timeout] - Overrides the default timeout.
*/
DuProcess.prototype.waitForFinished = function ( timeout ) {
if ( timeout === undefined ) timeout = this.timeout;
//wait
var wait = true;
var currentDate = new Date();
var timeout = currentDate.getTime() + timeout;
var checkTime = new Date();
var checkCmd = '';
var processName = new File( this.processPath ).name;
if ( DuSystem.mac ) checkCmd = 'ps -A | grep "' + processName + '"';
if ( DuSystem.win ) checkCmd = 'tasklist /FO TABLE /NH /FI "Imagename eq ' + processName + '"';
//wait
while ( wait ) {
var test = system.callSystem( checkCmd );
if ( test.indexOf( processName ) < 0 ) break;
$.sleep( 100 );
checkTime = new Date();
if ( this.timeout >= 0 ) {
wait = checkTime.getTime() < timeout;
}
}
}
/**
* Builds and returns the command line
* @memberof DuProcess
* @param {string[]} [args] - Args to append to DuProcess.args before starting
* @return {string} The command
*/
DuProcess.prototype.buildCmd = function ( args ) {
if ( args === undefined ) args = [];
//build command
//make sure to use fsName
if ( this.processPath.indexOf( '/' ) >= 0 || this.processPath.indexOf( '\\' ) >= 0 ) {
var processFile = new File( this.processPath );
this.processPath = processFile.fsName;
if ( this.processPath.indexOf( ' ' ) > 0 ) {
this.processPath = '"' + this.processPath + '"';
}
}
var cmd = "";
// if it's an app package, prepend "open -n " and append " --args"
if (this.isAppPackage)
{
cmd = "open -n " + this.processPath;
if (args.length > 0 || this.args.length > 0)
{
cmd += " --args";
}
}
else
{
cmd = this.processPath;
}
//args
for ( var a = 0; a < this.args.length; a++ ) {
var arg = this.args[ a ];
if (jstype(arg) != 'string') arg = arg.toString();
if ( arg.indexOf( ' ' ) > 0 ) {
arg = '"' + arg + '"';
}
cmd += ' ' + arg;
}
for ( var b = 0; b < args.length; b++ ) {
var arg = args[ b ];
if (jstype(arg) != 'string') arg = arg.toString();
if ( arg.indexOf( ' ' ) > 0 ) {
arg = '"' + arg + '"';
}
cmd += ' ' + arg;
}
return cmd;
}
/**
* Builds and returns the command line to launch the current queue
* @memberof DuProcess
* @return {string} The command
*/
DuProcess.prototype.buildQueueCmd = function () {
var cmd = '';
if ( this.queue.length == 0 ) {
return this.buildCmd();
}
for ( var i = 0; i < this.queue.length; i++ ) {
if ( i > 0 ) {
if ( DuSystem.mac ) cmd += ' && ';
if ( DuSystem.win ) cmd += '\r\n';
}
cmd += this.buildCmd( this.queue[ i ] );
}
return cmd;
}
/**
* Starts a command (in another thread).
* @memberof DuProcess
* @param {string} cmd - The command to start
*/
DuProcess.prototype.startCmd = function ( cmd ) {
//Start
if ( DuSystem.mac ) {
//add ' &' at the end to start detached
cmd += ' &';
system.callSystem( cmd );
}
if ( DuSystem.win ) {
//create batch file
var batName = 'DuProcess_' + new Date().getTime() + '.bat'
var bat = new File( DuFolder.duesfData.absoluteURI + '/' + batName );
bat.encoding = "CP437";
cmd += '\ndel "' + DuFolder.duesfData.fsName + '\\' + batName + '"';
bat.open( 'w' );
bat.write( cmd );
bat.close();
bat.execute();
}
}
// ================ STATIC =====================
/**
* Runs a command with some arguments.
* @static
* @param {string|File} process - The process or a path to the process.
* @param {string[]} [args=[]] - The arguments to pass to the command.
* @param {bool} [detached=false] - The script won't wait for the command to finish.
* @return {string} The output from the command.
*/
DuProcess.run = function( process, args, detached ) {
detached = def(detached, false);
var p;
if (process instanceof File) p = new DuProcess(process.absoluteURI, args);
else p = new DuProcess(process, args);
var output = "";
if (detached) p.start();
else {
var cmd = p.buildCmd();
output = system.callSystem( cmd );
}
return output;
}
/**
* Checks if a given path leads to an Application package on Mac, i.e. it's a path ending with <code>.app</code> containing at least <code>Contents/MacOS/</code>.
* @static
* @param {string|Folder|File} path - The path to the file or folder to check.
* @return {bool} true if path is an application package.
*/
DuProcess.isAppPackage = function (path) {
if (DuSystem.win) return false;
if (path instanceof File || path instanceof Folder) path = path.absoluteURI;
if (!DuString.endsWith( path, ".app")) return false;
var testFolder = new Folder(path + "/Contents/MacOS/");
return testFolder.exists;
}
/**
* Constructs a queue of different processes
* @class DuProcessQueue
* @classdesc A class to launch several processes
* @param {DuProcess[]} [processes] - The DuProcess Array
* @category DuESF
*/
function DuProcessQueue( processes ) {
/**
* The DuProcess list
*@name processes
* @type {DuProcess[]}
* @memberof DuProcessQueue
*/
this.processes = def (processes, []);
}
/**
* Starts the processes
* @memberof DuProcessQueue
*/
DuProcessQueue.prototype.start = function () {
if ( !this.processes.length ) return;
var cmd = '';
for ( var i = 0; i < this.processes.length; i++ ) {
var p = this.processes[ i ];
if ( i > 0 ) {
if ( DuSystem.mac ) cmd += ' && ';
if ( DuSystem.win ) cmd += '\r\n';
}
cmd += p.buildQueueCmd();
p.queue = [];
}
this.processes[ 0 ].startCmd( cmd );
}
// ==================== |-----| ====================
// ==================== | xml | ====================
// ==================== |-----| ====================
/**
* XML tools
* @namespace
* @category DuESF
*/
var DuXML = {};
/**
* Parses the value of the XML object.<br />
* If this object length is not 1, an Array is returned
* @memberof DuXML
* @param {XML} xml - The xml to parse
* @param {string} [type='string'] - The type, one of 'string', 'bool', 'int', 'float' or 'date'.
* @return {any[]|any} The value or an Array of values
*/
DuXML.getValue = function (xml,type)
{
type = def(type, 'string');
var value;
if(xml.length() > 1)
{
value = [];
for (var i = 0 ; i < xml.length() ; i++)
{
var val = DuXML.getValue(xml[i],type);
value.push(val);
}
}
else
{
var xmlString = xml.toString();
if (type.toLowerCase() == 'float')
{
value = parseFloat(xmlString);
}
else if (type.toLowerCase() == 'int' || type.toLowerCase() == 'integer')
{
value = parseInt(xmlString);
}
else if (type.toLowerCase() == 'boolean' || type.toLowerCase() == 'bool')
{
value = DuString.parseBool(xmlString);
}
else if (type.toLowerCase() == 'date')
{
var reDate = /(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\d{1,2}) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4}) (\d\d):(\d\d):(\d\d)/gim
var matches = reDate.exec(xmlString);
var year = parseInt(matches[4]);
var month = DuDate.getMonth(matches[3]);
var day = parseInt(matches[2]);
var hours = parseInt(matches[5]);
var min = parseInt(matches[6]);
var sec = parseInt(matches[7]);
value = new Date(year, month, day, hours, min, sec);
}
else
{
value = xmlString;
}
}
return value;
}
// ==================== |-----| ====================
// ==================== | xmp | ====================
// ==================== |-----| ====================
/**
* XMP tools
* @namespace
* @category DuESF
*/
var DuXMP = {};
DuXMP.initialized = false;
/**
* Loads the XMP library if it has not been loaded yet. There is no need to call this function as it's called automatically by DuESF methods if needed.<br />
* Call it once if you plan to use XMP without the methods in DuESF.<br />
* Note that the XMP library is added statically as <code>ExternalObject.AdobeXMPScript</code>.
*/
DuXMP.init = function ()
{
if (DuXMP.initialized) return;
// load the library
ExternalObject.AdobeXMPScript = def ( ExternalObject.AdobeXMPScript, new ExternalObject( "lib:AdobeXMPScript") );
DuXMP.initialized = true;
}
// ==================== |-----| ====================
// ==================== | zip | ====================
// ==================== |-----| ====================
/**
* Zip methods. This lib needs the 7zip command line tool <code>7za.exe</code> to be located next to the script on Windows.
* @namespace
* @category DuESF
*/
var DuZip = {};
if ( DuSystem.win ) DuZip.processArgs = ['a', '-mx9'];
else DuZip.processArgs = ['-r', '-X'];
/**
* The path to 7za.exe or 7z.exe (windows only, mac uses the internal command 'zip')
* @memberof DuZip
* @type {string}
*/
DuZip.processPath = '';
if ( DuSystem.win ) DuZip.processPath = DuESF.file.parent.absoluteURI + '/7za.exe';
else DuZip.processPath = 'zip';
/**
* Compresses the content of a folder
* @memberof DuZip
* @method
* @param {Folder} folder The folder to zip
* @param {string} [archiveName=folder.name + ".zip"] The archive name
* @param {boolean} [wipeFolder=false] Set to true to remove the original folder as soon as the zip is ready
* @return {File} The zip file
*/
DuZip.compressFolderContent = function (folder, archiveName, wipeFolder) {
wipeFolder = def (wipeFolder, true);
archiveName = def(archiveName, DuPath.getName(folder) + ".zip");
if (archiveName == '') archiveName = folder.name + ".zip";
var folderPath = folder.fsName;
if (DuSystem.win) folderPath += "\\*";
var args = DuZip.processArgs.concat( [folder.parent.fsName + "/" + archiveName, folderPath ] );
if ( DuSystem.win && !new File(DuZip.processPath).exists ) {
alert("7zip not found. Cannot zip folder: " + folder.fsName);
return null;
}
var processes = [ new DuProcess(DuZip.processPath, args) ];
if (wipeFolder) {
var wipeProcessPath = '';
var wipeProcessArgs = [];
if (DuSystem.win) {
wipeProcessPath = 'rmdir';
wipeProcessArgs = [ '/Q', '/S', folder.fsName];
}
else {
wipeProcessPath = 'rmdir';
//TODO test on mac. For safety, set to interactive mode. change the flags to '-rf' when tests are run succesfully
wipeProcessArgs = [ '-ir', folder.fsName];
}
processes.push( new DuProcess( wipeProcessPath, wipeProcessArgs ) );
}
var processQueue = new DuProcessQueue(processes);
processQueue.start();
return new File( folder.parent.fsName + "/" + archiveName );
}
// ==================== |-----| ====================
// ==================== | oca | ====================
// ==================== |-----| ====================
/**
* @class
* @name DuOCADocument
* @classdesc An Open Cel Animation document<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuOCA.load} to create an OCA object.
* @property {string} ocaVersion - The version of OCA used to export this document
* @property {string} name - The name of this project
* @property {int} width - The width, in pixels
* @property {int} height - The height, in pixels
* @property {int} frameCount - The duration, in frames
* @property {float} frameRate - The frame rate, in frames per second
* @property {float} pixelAspect - The pixel aspect ratio
* @property {OCALayer[]} layers - The layers
* @property {int} startTime - The frame number at which the animation starts
* @property {int} endTime - The frame number at which the animation ends
* @property {DuOCA.colorDepths} colorDepth - Bits per channel used in the document
* @property {float[]} backgroundColor - The background color
* @property {string} originApp - The application name from which the document was exported.
* @property {string} originAppVersion - The version of the origin application.
* @property {Folder} folder - The folder containing the oca files.
* @property {string} path - The URI to the folder containing the oca files.
* @category DuOCA
*/
/**
* @class
* @name DuOCALayer
* @classdesc An OCA Layer<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuOCA.load} to create an OCA object containing the layers.
* @property {string} name - The layer name
* @property {OCAFrame[]} frames - The keyframes of the animation for this layer.
* @property {OCALayer[]} childLayers - The child layers if this layer is a group.
* @property {DuOCA.LayerType} type - The type of the layer. See the Layer Types section below
* @property {string} fileType - The type of the files used for the frames. The file extension, without the initial dot.
* @property {DuOCA.BlendingModes} blendingMode - The blending mode of the layer
* @property {bool} inheritAlpha - The inherit alpaha option (preserve transparency)
* @property {bool} animated - Whether this layer is a single frame or not.
* @property {int[]} position - The coordinates of the center of the layer, in pixels [X,Y] in the document coordinates.
* @property {int} width - The width, in pixels.
* @property {int} height - The height, in pixels.
* @property {int} label - A label for the layer.
* @property {float} opacity - The opacity, in the range 0.0-1.0
* @property {bool} visible - True if the layer is visible
* @property {bool} reference - Whether the layer is a guide or reference, and should not be rendered.
* @property {bool} passThrough - Whether the layer is in pass through mode. Only for grouplayer.
* @category DuOCA
*/
/**
* @class
* @name DuOCAFrame
* @classdesc An OCA Frame<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuOCA.load} to create an OCA object containing the layers.
* @property {string} name - The layer name
* @property {string} fileName - The path and name of the file of the frame. It is the absolute path from the root of the OCA folder.
* @property {int} frameNumber - The frame in the document at which the frames starts to be visible
* @property {float} opacity - The opacity, in the range 0.0-1.0
* @property {int[]} position - The coordinates of the center of the layer, in pixels [X,Y] in the document coordinates.
* @property {int} width - The width, in pixels.
* @property {int} height - The height, in pixels.
* @property {int} duration - The duration of the frame, in frames.
* @category DuOCA
*/
/**
* Open Cel Animation interchange tools
* @namespace
* @category DuOCA
*/
var DuOCA = {};
/**
* The different layer types
* @readonly
* @enum {string}
*/
DuOCA.LayerType = {
PAINT: 'paintlayer',
VECTOR: 'vectorlayer',
GROUP: 'grouplayer'
}
/**
* Loads an OCA document from a json file
* @param {File|string} file - The .json file or its path
* @return {DuOCADocument|null} The OCA document or null if the file could not be parsed or opened
*/
DuOCA.load = function ( file )
{
if (!DuDebug.checkVar( file, 'file', undefined, 'DuOCA.load(file)') !== true) return null;
if ( typeof file === 'string' ) file = new File( file );
if (!DuDebug.checkVar( file, 'file', 'File', 'DuOCA.load(file)') !== true) return null;
if ( !file.exists ) throw "DuOCA.load(file): file does not exist.";
var data = DuFile.parseJSON( file );
//add containing folder
data.folder = file.parent;
data.path = data.folder.absoluteURI;
return data;
}
// ==================== |-----| ====================
// ==================== | url | ====================
// ==================== |-----| ====================
/**
* Methods to manage URLS
* @namespace
* @category DuESF
*/
var DuURL = {};
/**
* The list of escaped characters in URLs
*/
DuURL.EscapedChars = {
"%": "%25",
" ": "%20",
"#": "%23",
"$": "%24",
"&": "%26",
"@": "%40",
"`": "%60",
"/": "%2F",
":": "%3A",
":": "%3B",
"<": "%3C",
"=": "%3D",
">": "%3E",
"?": "%3F",
"[": "%5B",
"\\": "%5C",
"]": "%5D",
"^": "%5E",
"{": "%7B",
"|": "%7C",
"}": "%7D",
"~": "%7E",
"\"": "%22",
"‘": "%27",
"+": "%2B",
",": "%2C"
};
/**
* Escapes common characters from a string to be included in a GET request URL.
* @param {string} str The string to escape.
* @returns {string} The escaped string.
*/
DuURL.escape = function( str )
{
for( ch in DuURL.EscapedChars )
{
str = DuString.replace(str, ch, DuURL.EscapedChars[ch] );
}
return str;
}
/**
* Builds a query, made of "key=value" pairs.
* @param {Object} arr An associative array with key/value pairs. Values will be escaped.
* @returns {string} The query, with the leading ?.
*/
DuURL.buildRequest = function( arr )
{
var q = '?';
var first = true;
for( key in arr )
{
if (!arr.hasOwnProperty(key)) continue;
if (!first) q += '&';
first = false;
q += key;
if (arr[key] != '') q += "=" + DuURL.escape( arr[key] );
}
return q;
}
/**
* Builds a HTTP GET request. The user-agent is named after DuESF.scriptName.
* @param {string} host The host, without port, without http part; for example: "duduf.com" or "version.rxlab.io"
* @param {string[]} [subfolders] The subfolders.
* @param {Object} [argsArray] An associative array with key/value pairs. Values will be escaped.
* @param {string} [httpVersion="1.1"] An associative array with key/value pairs. Values will be escaped.
* @returns {string} The query ready to be posted with a socket
*/
DuURL.buildGET = function( host, subfolders, argsArray, httpVersion )
{
subfolders = def(subfolders, []);
httpVersion = def(httpVersion, "1.1");
var request = 'GET ';
// add subfolders
for (var i = 0, n = subfolders.length; i < n; i++)
{
request += '/' + subfolders[i];
}
request += '/';
// query
if (isdef( argsArray ))
{
request += DuURL.buildRequest( argsArray );
}
request += ' HTTP/' + httpVersion + '\n';
// Build user agent
var userAgent = 'User-Agent: ' +
DuESF.scriptName +
'/' + DuESF.scriptVersion.fullVersion +
' (' + DuString.trim($.os) + ') AE/' +
DuESF.hostVersion.version;
request += userAgent + '\nHost: ' + host + '\n';
return request;
}
// ==================== |------| ====================
// ==================== | core | ====================
// ==================== |------| ====================
/**
* Methods related to ScriptUI
* @namespace
* @category DuScriptUI
*/
var DuScriptUI = {}
// ==================== |---------| ====================
// ==================== | strings | ====================
// ==================== |---------| ====================
/**
* The list of strings used by the UI.
* @enum {string}
*/
DuScriptUI.String = {
ABOVE: "Above",
ABOVE_SHORT: "Ab",
ADD_ITEM_OR_CAT: "Add new item or category",
AE_BLUE: "After Effects Blue",
AE_BLUE_TIP: "The After Effects highlighting blue",
AE_ORANGE: "After Effects Orange (CS6)",
AE_ORANGE_TIP: "The After Effects highlighting orange from good ol'CS6",
APPLY: "Apply",
APPLY_ALL: "Apply all",
APPLY_SETTINGS: "Apply changes to the settings.",
APPLY_SETTINGS_ALERT: "You may need to restart the script for all changes to take effect.",
ARM: "Arm",
ARTHROPOD: "Arthropod",
AUDIO: "Audio",
AXIS: "Axis",
BACK_PREVIOUS: "Back",
BACK_LOCATION: "Back",
BACK_LOCATION_SHORT: "Bk",
BIRD: "Bird",
BODY: "Body",
BONE: "Bone",
BUG_REPORT: "Bug report",
BUG_REPORT_TIP: "Bug report\nFeature request\n\nTell us what's wrong or request a new feature.",
CAM: "Cam",
CAMERA: "Camera",
CANCEL: "Cancel",
CALF: "Calf",
CATEGORY: "Category",
CHARACTER: "Character",
CHANNEL: "Channel",
CHECK_UPDATE: "Check for updates",
CIRCLE: "Circle",
CLAWS: "Claws",
CLEAR_LIB_TIP: "Clears the current search and category",
CLOSE: "Close",
COLOR: "Color",
COLOR_HIGHLIGHT_TIP: "Set the highlight color.",
COPY: "Copy",
CREATE: "Create",
CUSTOM: "Custom",
CUSTOM_COLOR_TIP: "Select a custom color.",
CLOSE_SETTINGS: "Close settings",
DEFAULT: "Default",
DEV_MODE: "Dev and Debug mode",
DEV_MODE_TIP: "Use at your own risk!",
DIGITIGRADE: "Digitigrade",
EAR: "Ear",
EDIT: "Edit",
EDIT_ITEM_OR_CAT: "Edit selected item or category",
EDIT_SETTINGS: "Edit settings",
EFFECTS: "Effects",
EXPERT: "Expert",
EXPERT_UI_TIP: "The smallest UI, for expert users.",
EYE: "Eye",
EYES: "Eyes",
EYEBROW: "Eyebrow",
FEATHER: "Feather",
FEATHERS: "Feathers",
FEATURE_REQUEST: "Feature request",
FEATURE_REQUEST_TIP: "Feature request\nRequest something new.",
FILE: "File",
FIN: "Fin",
FINGERS: "Fingers",
FISH: "Fish",
FISHBONE: "Fishbone",
FISHBONES: "Fishbones",
FISH_SPINE: "Fish spine",
FOOT: "Foot",
FOREARM: "Forearm",
FRAME: "Frame",
FRAMES: "Frames",
FRONT: "Front",
FRONT_LEG: "Front leg",
FRONT_SHORT: "Fr",
FUNDING_BAR_TIP: "Thank you for your donations!",
FUNDING_BAR_TIP_DETAILS: "This month, the {#} fund is ${#}.\nThat's {#}% of our monthly goal ( ${#} )\n\nClick on this button to join the development fund!",
GROUPS: "Groups",
HAIR: "Hair",
HAND: "Hand",
HEAD: "Head",
HEEL: "Heel",
HELP: "Help",
HELP_TIP: "Get help.",
HIPS: "Hips",
HOMINOID: "Hominoid",
HOOF: "Hoof",
INVERT: "Invert",
INVERTED: "Inverted",
ISOLATE: "Isolate",
ITEM: "Item",
LANGUAGE_TIP: "Set the language of the interface.",
LAYER: "Layer",
LAYERS: "Layers",
LEFT: "Left",
LEFT_SHORT: "L",
LEG: "Leg",
LIMB: "Limb",
LOCATION: "Location",
LOCATION_SHORT: "Loc",
MAGIC: "Magic is happening",
MAIN: "Main",
MASKS: "Masks",
MAXIMUM: "Maximum",
MIDDLE: "Middle",
MIDDLE_SHORT: "Md",
MINIMUM: "Minimum",
MORE_OPTIONS_TIP: "[Shift]: More options...",
MOUTH: "Mouth",
NAME: "Name",
NECK: "Neck",
NEXT: "Next",
NONE: "None",
NORMAL_MODE: "Normal mode",
NOSE: "Nose",
NULL_OBJECT: "Null",
OK: "OK",
OPACITY: "Opacity",
OPEN: "Open",
OPEN_FOLDER: "Open folder",
ORIGINAL: "Original",
PATH: "Path",
PAW: "Paw",
PENIS: "Penis",
PINCER: "Pincer",
PIN_TIP: "Keeps this panel open",
PLANTIGRADE: "Plantigrade",
PREVIOUS: "Previous",
POLYGON: "Polygon",
POSITION: "Position",
PROPERTIES: "Properties",
RANDOM_TIP: "Set a random value.",
RECTANGLE: "Rectangle",
REFRESH_LIB: "Refresh library",
REMOVE: "Remove",
REMOVE_ITEM_OR_CAT: "Remove selected item or category",
RESET_SETTINGS: "Reset the settings to their default values.",
RIGHT: "Right",
RIGHT_SHORT: "R",
ROOKIE: "Rookie",
ROOKIE_UI_TIP: "The easiest-to-use mode, but also the biggest UI.",
ROTATION: "Rotation",
ROUNDED_RECTANGLE: "Rounded rectangle",
RUN_EXECUTE: "Run",
RX_PURPLE: "RxLab Purple",
RX_PURPLE_TIP: "The RxLaboratory Purple",
RX_RED: "Rainbox Red",
RX_RED_TIP: "The Rainbox Productions Red",
SAVE_AS: "Save as",
SCALE: "Scale",
SCRIPTING: "Scripting",
SELECT_LAYERS: "Select layers",
SELECT_GROUPS: "Select groups",
SET_QUALITY: "Set quality",
SETTINGS: "Settings",
SETTINGS_FILE: "Settings file",
SETTINGS_FILE_TIP: "Set the location of the settings file.",
SHOULDER: "Shoulder",
SHOULDERS: "Shoulders",
SHOULDERS_AND_NECK: "Shoulders & neck",
SIDE: "Side",
SIZE: "Size",
SNAKE: "Snake",
SPINE: "Spine",
STANDARD: "Standard",
STANDARD_UI_TIP: "The standard not-too-big UI.",
START_TYPING_CMD: "Start typing something...",
TAIL: "Tail",
TAIL_SHORT: "Tl",
TARGET: "Target",
TEXT: "Text",
TEXTURE: "Texture",
THIGH: "Thigh",
TIMELINE: "Timeline",
TIP_BONE: "Tip",
TRANSFORM: "Transform",
TOES: "Toes",
TORSO: "Torso",
TYPE: "Type",
UI_MODE_TIP: "Select the UI mode.",
UNDER: "Under",
UNDER_SHORT: "Un",
UNGULATE: "Ungulate",
VERTEBRAE: "Vertebrae",
VULVA: "Vulva",
WING: "Wing",
WORKING: "Working...",
X_POSITION: "X Position",
Y_POSITION: "Y Position"
}
// OS Specific strings
if (DuSystem.win) {
DuScriptUI.String.CTRL_KEY = "Ctrl";
DuScriptUI.String.SHIFT_KEY = "Shift";
DuScriptUI.String.ALT_KEY = "Alt";
}
else {
DuScriptUI.String.CTRL_KEY = "Cmd";
DuScriptUI.String.SHIFT_KEY = "Shift";
DuScriptUI.String.ALT_KEY = "Alt";
}
DuScriptUI.String.CTRL_KEY_TIP = "[" + DuScriptUI.String.CTRL_KEY + "]";
DuScriptUI.String.SHIFT_KEY_TIP = "[" + DuScriptUI.String.SHIFT_KEY + "]";
DuScriptUI.String.ALT_KEY_TIP = "[" + DuScriptUI.String.ALT_KEY + "]";
// ==================== |-------| ====================
// ==================== | icons | ====================
// ==================== |-------| ====================
// very small 18px
// ==================== |------------| ====================
// ==================== | w8_ae_blue | ====================
// ==================== |------------| ====================
var w8_ae_blue = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\b\x00\x00\x00\b\b\x06\x00\x00\x00\u00C4\x0F\u00BE\u008B\x00\x00\x00\tpHYs\x00\x00\x03\u00B0\x00\x00\x03\u00B0\x01'\u00C4\x0F\u00AD\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\x16IDAT\x18\u0095c\u00D4\u00EDy\u00FD\u009F\x01\x0F`\u00C2'9|\x14\x00\x00\u00B9|\x02\u00B3i>m(\x00\x00\x00\x00IEND\u00AEB`\u0082", "w8_ae_blue.png", "w8" );
w8_ae_blue;
// ==================== |--------------| ====================
// ==================== | w8_rx_purple | ====================
// ==================== |--------------| ====================
var w8_rx_purple = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\b\x00\x00\x00\b\b\x06\x00\x00\x00\u00C4\x0F\u00BE\u008B\x00\x00\x00\tpHYs\x00\x00\x03\u00B0\x00\x00\x03\u00B0\x01'\u00C4\x0F\u00AD\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\x16IDAT\x18\u0095c\\\u00AAv\u00E4?\x03\x1E\u00C0\u0084Or\u00F8(\x00\x00\u00A4\u009D\x02\u009E=\u008C7\u00F7\x00\x00\x00\x00IEND\u00AEB`\u0082", "w8_rx_purple.png", "w8" );
w8_rx_purple;
// ==================== |-----------| ====================
// ==================== | w8_rx_red | ====================
// ==================== |-----------| ====================
var w8_rx_red = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\b\x00\x00\x00\b\b\x06\x00\x00\x00\u00C4\x0F\u00BE\u008B\x00\x00\x00\tpHYs\x00\x00\x03\u00B0\x00\x00\x03\u00B0\x01'\u00C4\x0F\u00AD\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\x16IDAT\x18\u0095c|#!\u00F1\u009F\x01\x0F`\u00C2'9|\x14\x00\x00/\u00DE\x02+f\u00E7\x16^\x00\x00\x00\x00IEND\u00AEB`\u0082", "w8_rx_red.png", "w8" );
w8_rx_red;
// ==================== |--------------| ====================
// ==================== | w8_ae_orange | ====================
// ==================== |--------------| ====================
var w8_ae_orange = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\b\x00\x00\x00\b\b\x06\x00\x00\x00\u00C4\x0F\u00BE\u008B\x00\x00\x00\tpHYs\x00\x00\x03\u00B0\x00\x00\x03\u00B0\x01'\u00C4\x0F\u00AD\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\x16IDAT\x18\u0095c<>\u0089\u00E1?\x03\x1E\u00C0\u0084Or\u00F8(\x00\x00n?\x02h(Q\u00FBI\x00\x00\x00\x00IEND\u00AEB`\u0082", "w8ae_orange.png", "w8" );
w8_ae_orange;
// small 12px
// ==================== |---------------| ====================
// ==================== | w12_bugreport | ====================
// ==================== |---------------| ====================
var w12_bugreport = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x014IDAT(\u0091u\u008F\u00BF.\x04a\x14\u00C5\x7Fwf\u00D6D\u0096B\x14\u00A2C\u00B3\x14\x12\u00AF@#\u0091\u00EC\u00CEWH\u0088^\u00A2Rl/\"\u00E8E\u0094z2b\u00B3_au\u00E2\r\x04\r\u008DG\x10\u009D,3s\x1462&\u00D9\u00D3\u009D\u009C{\u00CF\x1F\u00A3\x02\u00EF}\x1Bh\x03\u00A1\u00A43\u00E7\u00DCqY\u00B72\u00E9v\u00BBkfv\u00F3\u00EF\u00C0l\u00A3\u00D5j\u00A5\x7F\u00BC\u00D3\u00E9LFQt\u009Ae\u00D9Q\x18\u0086\x0B\u00C0j\u00E5\u00E1\u00DE\u00CC\x1E$\u00EDeY\u00B6\x1B\x01HZ\x0E\u00C3p\u00C5\u00CCv\u008A\u00A2\u00B8\u00AA<\u00D4\u008B\u00A2\u00B8\u00FBW\u00C9{\u00DF\x00\x1E\u0081\u00B8\u00BAi\u0080~\x10\x04K\u00CDf\u00F3\u00E5o\u0083\u00F7\u00FE\x1D\u0098\x18\u00B8\u009EK\x12\u00B0=\u0090?\u0092$\u00F9\u00D5\u00BC\u00F7O\u00C0b\u00D9NR\u00DB\u00CC\n\u00E0\u00A4\u0092\u00F4\x1C\x01\u0087f6'i\x1F\x18\x1DR\u00E9\u00D3\u00CC\x0E$\u00BD\u0095+\u00BD\x00\u008D!\t\u00AFI\u0092\u00CC\x03\x04\x00i\u009A\u008E\x01_C\u00DC\u0091\u00F4\u00ED\u00BD\x1F\x07\b\u00D24\x1D\u0089\u00E3\u00F8\x16\u0098\u0097t\r\u00E4\u00A5\u00DB\\\u00D2\u00B5\u00995\u0080\u00DB^\u00AF\x17\x07\u00F5z}\x1A\u0098\u0095\u00B4\u00E5\u009C[\u00CF\u00F3|\u00AAV\u00AB]\x00\u0097y\u009EO9\u00E7\u00D6\u0081M`\u00A6\u00DF\u00EFO\u00FF\x00\x1F\u00ED}\u0096\u00BB}\u00F8+\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_bugreport.png", "w12" );
w12_bugreport;
// ==================== |-------------| ====================
// ==================== | w12_feature | ====================
// ==================== |-------------| ====================
var w12_feature = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00A3IDAT(\u0091\u009D\u00CF!\x0E\u00C2P\f\u00C6\u00F1\x7F\u00E1eO\u00A0`H\x12N\u00C1%0s\u00CFq\x02\x04\x1E\u0081\u00E7\x18$\u00A8\u00C9\x19n\u0081\u00E6\x0E\u00C8a&h1\u0088\u0097B\u00B6@U\u00D3\u00AF\u00BF\u00A4\x15\\5M\u00B31\u00B3\x13 \u00C0\u00A5\u00AA\u00AAu\u009E\u008F<P\u00D5\u00E9{\x19\x11)}\u00FE\x01\u00806\u00C3\u00AD\x0F{\u0081\u00EB\u00BF\x03\x11y\u00FC\x04\u00F2%\u0087\u00FF;)\u00F8A\u00D7u\u00B7\x18\u00E3\u00C1\u00CC&!\u0084\u00F3 \x00\n\u00A0\x04\x16fV\f\u0082\x18\u00E3\x1E\u00D8\u0089\b\u00AA\u00BA\x02\u0096C?\u00CC\u00B3~V\u00D7\u00F5\u00B8\x17\u00A8\u00EA\x11\u00B8\x02w3\u00DB\u00A6\u0094\u009Ey\u00FE\x02\u00AA34\x1FQ\u00E27e\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_feature.png", "w12" );
w12_feature;
// ==================== |----------| ====================
// ==================== | w12_help | ====================
// ==================== |----------| ====================
var w12_help = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00FDIDAT(\u0091\u008D\u00901J\x04A\x10E_\u00D5\u0088\u00A7\u00D0\u00CCx\x05\u00CD60\u00DAH\x19\u00BA\u00C1\u008B\u0098\u0098\x18\x18\u00AC\u00A8\u00A0\u0089\u0081\x18\u0089`b:\u00DDtf&\u0088\u00D9b\u00B0\x17P\u00CC\x14\x13\u00E3\u00A1\u00CB\u00C0\u0099e\x1CW\u00F4GU\u00FD\u00DF\u00A7\u00BAJ\u00E8\u00A8\u00AA\u00AAuU\u00DD\x07\u0086\u00C0\"\u00F0\u00A8\u00AAGeY\u00DE\u00B6\u008C\u00B4E\baSD\u00AA\x06\u00ECk\u00CF9w\f\u00A0\u00ED\u008B\u00AA\u008E\x1B\u00F8CD\x0EEd\f\u00BC7\u00F6A\ba\x19`\u00A1\r\x14E\u00B1]\u00D7\u00F5\u00A9\u0099\u009D9\u00E7\x1E\u009A\u00A9\u00AF\"r\x0E\x14\u00AA\u00BA\x06\u00BC\u00CC\u00BE\u00D4W\ba(\"\u00D7\u00C0\n\u0080\u0099\r\u00BC\u00F7S\u009D\x07\u00C7\x18OD\u00E4\u00BE\x03_x\u00EF\u00A7\u00DF\u0096\u00EE\u00C0\x1B\u00C0]\u00D3\u00BE\u0099\u00D9\u008E\u00F7\u00FEf\u00B6k?`fK\u009Dv\u00B7\x0B\u00FF\u00AA\u0094\u00D2j\u008Cq0\u00CF\u00FB1!\u00A5\u00E4s\u00CE\x13`\x12c\u00DC\u00FA3\u0090s\x1E\x01\x05_'\x1F\u00FD'pif\u00CF\u00C0\u0093\u0099]\u00F5\u00FDO3\u0095[g\u00AAB\u00A3I\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_help.png", "w12" );
w12_help;
// ==================== |--------------| ====================
// ==================== | w12_settings | ====================
// ==================== |--------------| ====================
var w12_settings = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\x03IDAT(\u0091}\u00D0\u00A1N\u00C4@\x10\x06\u00E0\u00FFo\u00AFl5\u0086\u00A7@\u00B0\x1E\u0085C5}\x03Np\u0084\u0080E\u00AF\u00C6@\u00E0\x02\u0082#\x01[\u00D5\u00B4\u00C1\u009C\u00E4\x01:\u00EB\u00F0\b\x14\u00C1P\x10\\\u00E8`z\u00E4\u00B8k\x19\u00B7;\u00DF\u00CC\u009F\f\u00D1Sy\u009Eo\u0093<\x05\u00B0\u00A5\u00AA'I\u0092\\\x00\u00C0\u00A0\x07o\u0092\u009C\x02\u0088\x01\u00DC{\u00EF\u00C7\u00F3^\u00E7\x00\u00C9\u00A3\x16\u00BF\u0088\u00C8\u00D09\u00D7dY\x16\u00C6q|\u00CC\u00AE\u0081\u00A2(\x1ETu\x17\u00C0w\x14E\u00EBu]\x7F\x18c\u00EE\x00$+\t\u00CE\u00B9@U\u00D7\u00DAg8\u009B\u00CD\u00DE\u008C1\u00AF\x006\x00<r\x19[k'\u00AA\u00BA\u00D7\x11\u00FCIr\u0087\u00FFaU\u009D\u0092\u00FCR\u00D5\u00E7\u00A6i\u00C6i\u009A>\r\u00DA\x06\u00CB\u00B2\u00BCZ\u00DA|\u00EB\u00BD\u00DFw\u00CE5\x7F\x0E\u00D2\u00E2kU\x1D-b\x11Y\u00C1\x00\x10Zk/\x01\x1C.\u00FC\u00DD\u0088\u00C8\u00A8\x0B\x03@\x00`\u00F8\x1BGND\u00E4\u00A0\x0F\x03@\u00A0\u00AA\u00E7\x00\u00DEI\u009EUU\u00D5\u00BBy^?WT\x7F\u00BAa\u009Ae\u0093\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_settings.png", "w12" );
w12_settings;
// ==================== |----------| ====================
// ==================== | w12_back | ====================
// ==================== |----------| ====================
var w12_back = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u008EIDAT(\u0091\u008D\u00CF\u00B1\x11\u00C20\f\u0085\u00E1\x1F\u00D5\u00CC@K\u00C1\x0E)\u00E8\u008D\\\u00B2\x1D3\u00C8w9\u008A\f\u00C0\x00t\u00D4\u00EC@'Q\u00A5\b\u00B1\x1DTJ\u00EF\u00BB{\u00DA\u00D1\u0099R\u00CA9\"n\"rM)=\x00\u00A4\x156\u00B3!\"\f8D\u00C4q\u00DEW\u0081\u0099\r\u00C0\b\u00EC\x7Fo+\u00D0\x0B\u00AF\u00C0Vx\x01J)'\u00E0\u00DE\x0B/\u0080\u00BBK\u00ADb\x13\u00E4\u009C\u009F\u00C0\x05\u00F8\u00FC\x05\x00Tu\x02\u00B4\u0087V\x15\u00B6P\u00B5s\x0F5\u009FT\u00D5ID2\u00F0v\u00F7\u00D7\u00BC\u00FF\x02}\u009C3\x19\u00CA?SM\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_back.png", "w12" );
w12_back;
// ==================== |-----------| ====================
// ==================== | w12_close | ====================
// ==================== |-----------| ====================
var w12_close = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\x1FIDAT(\u0091m\u0090\u00BDJ\x03Q\x10F\u00CFL,\u008C(\u0088\u00CF\u00E0\x13h)h!b\x11w\u00EF\u00DE\u00C6F\x10R\u00E9KI*\u00EBp\u00B3\x06\u00C5\b\x11\u00B4\x12|\u0082\u00D8Z\u008B\u009A\x1F\"\u00EC\x1D\u009BMX\u00C2~\u00E5\u0099o\x0E\u00CC\b@\b\u00E1\x04\u00E8\x00m\u00E7\u00DC\u0080Jz\u00BD\u00DE\u0091\u0099\u00DD\u008A\u00C8u\u009A\u00A6wR\u0096\x03\u00D0\x04ff\u0096fY\u00F6T\u008A\x0E\u0080{`\x0B\u00F8\x13\u0091s\t!\u008C\u0080\u00DD\u008At*\"\u0089\u0099\u00CD\u0081\x07`\u00B32\u00FBTU\u00BD\x04~*p\u00C3\u00CC\u00F2\u00D2\\-OD\u00E4B\x00\u00BA\u00DD\u00EE~\u00A3\u00D1x4\u00B3\x1D\u00EA3\x01\x12\u00E7\u00DCP\x16$\u00CF\u00F3=3\x1B\u00D4,Mb\u008Cg\u00DE\u00FBg\x00]\u00D0\u00A2(\u00D6\u00CDl\u00AD\u00C6.\"\u00B2\u00E4R\u00F3\u008D\u00BA,\u00BF'!\u0084C\u00A0\u00BFr\u00E0/P\x00\u00DB\u00D5%\u00C0)p\u00B3R\x1E\u009BY+\u00C6x\n|Wx\x13\u00E8\u00A8\u00AA\u00B6\u0081q\t\u00A7@\u009Ae\u00D9\u008B\u00F7\u00FE-\u00C6x\f|\u0095\u00B39p\u00A5I\u0092\u00BC\u00C6\x18\x13\u00E0#\u00C6\u00D8r\u00CE\r\x17J\u00EF\u00FD\u00BB\u00AA\u00B6\u0080\u0091\u00AA\u00A6\u00CE\u00B9\u00FE?q\u00EB}\u00EF\u00EA$\u00F9\u00F7\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_close.png", "w12" );
w12_close;
// ==================== |---------| ====================
// ==================== | w12_pin | ====================
// ==================== |---------| ====================
var w12_pin = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\x13IDAT(\u0091\u0085\u008F\u00B1J\u00C3p\x18\u00C4\u00EF\u00FE\t\u0084\u00D2\u0087pt\x17\u00E9C\u0088$iT\u00DC\x1C\u0082\x0E...\u00F5\r\u009C\x15\u009D\\\x14AZ\u0084|)\x18\b\"]\u00DC\x1C\u00FA\x04\u00BE\u0080\u008E.M\u009A\u00E4s\n\u00A66\u00D6\u009B>\u00EE\u00FB\x1D\u00DC\x11+\x14\u00C7\u00F1\u00BA\u00AA\u00A6\u00C6\u00983\u00D7u\x1F\x00\u00C0\u00FC\x05O&\x13\u009B\u00E4\x1D\u00C95U\u00BD\x17\u0091C\x00\u00E0o0M\u00D3n\u0096e\u009B\u00AA\x1A\x028h\u00BC\u0094dh7a\x11\u00B9\u0098\u00CDf\u00C7\x00\x16\u00FCZUUu\x17*Y\u00965\x00\u00F0\u00D2\u00C2~\u0091\u00DC\u00F3}\u00FFj\u00A9R\u0092$\u00CE|>\x1F\x02\u00F0\x1A\u00F6\u00B6\u00E7yO@\u00CB\u00E8<\u00CF\u00B7H\u009E\x03\u00B8\u00AD\u00BD\u00B2,\u00DF\u00EA{! \"\u00BB$\u0087\x006\u00A6\u00D3iH\u00F2\x06\u00C0g\x10\x04\x1FK%\u00C7\u00E3\u00F1N\x1C\u00C7\u00B9\u0088\u009C\u00D4\u009E\u00AARD\u00F6\u009B\x1C\x01 \u008A\u00A2\u009E1\u00E6\u0095\u00E4\u00A9\u00EB\u00BA\u0097-\u00A3\x7F\x02\u00A3\u00D1\u00C8r\x1C\u00E7YU\u00AF}\u00DF\x7F\\\x05\x03\u0080\u00DD\u00E9tzEQ\x1C\u00F5\u00FB\u00FD\u00F7\u00FF`\x00\u00F8\x06Nlb'\u00CF\u00F9\u00A5W\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_pin.png", "w12" );
w12_pin;
// ==================== |------------| ====================
// ==================== | w12_pinned | ====================
// ==================== |------------| ====================
var w12_pinned = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00ADIDAT(\u0091\u008D\u0091\u00BB\r\u00C20\x10\u0086\u00BF\u00B3\u008C\x14\tQd$:\u00AA\u00B8d\x04\u00F6\u00A1\u008A\x18\u0080\u0082\n\u009B\x19\x18\x02\u0089\x1D(\u00A0r\u0084\u008F&AN\x14K\u00F9\u00AB\u00D3\u00FF\u00B8\u0087N\u00C8\u00E0\u00BD\u00DF\x017\u00C0\u00F6T\x07l\u009Ds\u00F7\u00C1c\u00F2\u0080\u0088Hf\x06X\u00A9\u00AA\u00E6\u009EQ\u00C0\x18\u00F3`\u0082\u0094\u00D2\u00B3\x18\u00881~\u0080\u00BCc\u00AA\u00AA\u00AA<\u00C1Z{\x04$\u00D7c\u008C\u00A7\u00D1\u00DAC\x11Bp\u00AAz\u009D\u00AE\x04 \"\u00FB\u00A6i.0>\u00B0\x16\u0091\u00C3\\\x00\u00F8\x16\u00F8\u00FF\u00B46\u0084\u00D0\u00CEiv\u008ET\u00D5u\u00A9\u0099)\t%H\u00FF\u00DD\u00CDB\u00FF\u00DB\x02g\u00A0^\x18x\u00FD\x00x\u00860\u00D4\r`O\u00AE\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_pinned.png", "w12" );
w12_pinned;
// ==================== |-------------| ====================
// ==================== | w12_options | ====================
// ==================== |-------------| ====================
var w12_options = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01$IDAT(\u0091\u0095\u00901J\x03a\x10\u0085\u00BFI~V#\u00EC\x11\u00B2v\u0082\u00C6\x03X(\b\x166j\u00B7\u00D1raC\u00BA\x14+\x1E`\x11\u00BCBX\u00C8O\u0090m\u00B7\x12\u00B5\u0091\u00E0\rl\u008D\u00E0\r\x04\u008B\x10\x02\x1A\u0092\u008C\u00CD&dc\u0095W\r\u00F3\u0086\u0099o\x1E\u00AC)Ym\x04A\u00B0i\u008C\u00B9\x06\u00CA\u00E3\u00F1\u00B8\u009D\u00A6\u00E9\u00F7\u00B2_\u009A\x17\u00ADVk\u00C3\u00F7\u00FD\u00B21\u00A6\x06\u00DC\x01\u00B7\u008E\u00E3\x1C\x02\u00D2l6\u00B7\u00FE]\b\u00C3\u00F0ADv\u0080m\u00A0\u0092\u00B7'\u00C0\x07\u00F03\x18\f\x0E\u00B2,\u009B\x1A\u0080F\u00A3\u00B1\x0B\\,\u00B6\u0088<\u00AB\u00EA\b\u00F0\u0081}\x00\u00D7uO\u0080\u0097\x12\u0080\u00B5\u00B6\u009Fc\x00\u00F4:\u009D\u00CE\u0099\u00B5\u00F6\x12H\x01T\u00F5\u00DE\u00F3\u00BC^\u00E1\x07\x11\u0099\u00AE\x06\u00A0\u00AA\u009A{\x1A\u00C7\u00B1.~\u00C8\u0091\u00DE\u0097f\x1FUu$\"\u00F5\u00F9\u00D2\u00D9lv\u00DA\u00EDv\x0BHO\u00AA\u00FA\t\u00FC\x02\u00E7\"r\x05(\u00D0\x07\u00DE<\u00CF{- \u00B9\u00AE[\u00AFV\u00AB{\"r\u00B4t\u00A9n\u00AD\u00AD9\u008Es\x1C\u00C7\u00F1\u00A4\x10\u00EB\\Q\x14U\u0086\u00C3\u00E1\rP6\u00C6\u00B4\u0093$\u00F9Z\u009DYK\x7F\x10\u00FB^\x18\u0080\u00E9g\u009B\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_options.png", "w12" );
w12_options;
// ==================== |-----------| ====================
// ==================== | w12_check | ====================
// ==================== |-----------| ====================
var w12_check = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00BFIDAT(\u0091\u0095\u0090\u00C1I\u00C4P\x14E\u00CF\u009D7=\x04+\x18lA{\u00C8\u00C7 n\x06\x14\x04;\x18,\u00C2\x16\x04w\u00A2\u00FCExI\x03)c\n\u00B0\u008D|\u009E\u009B0d\x06\x1D\u00E3]\u009F\u00F3\u00B8\u00F7\u00C1?\u00A3%P\u00DB\u00B6\u0097f\u00F6\x1A\x11\u00CF\x7F\n\x13<\x00\x15\u00F0uV\u00E8\u00BAn\x03\f\u00C0\x05P\"\u00E2~\u00ED\u00EEW\u0092^J)OM\u00D3\u00EC\u00E7\u0097'\u00B8\x02FI\u00DB\u0094R^I\u00FA\x04\u00AE\u00CDl\u0098\u00A0\u00D3\x1A\u00A3\u00A4m]\u00D7\x19`%i\x07\u008C@ef\u0083\u00BB\u00DF\u00FC\x06\x1F\u00BE\u00E4\u00EE\u00B7\u0092>\u0080\u00F5lB\x01\x1ERJ\u00EF\u00F3]\u0087\u00D1'\u00D2\u008F\u00F0\u0091\x00\u00D0\u00F7\u00FD]D\u00BCIz\u009C\u00D78\u009B\u009C\u00B3-\x02\u0097\u00E6\x1B\u0085\u0087H\x14\u00BC\u0092~\u00E6\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_check.png", "w12" );
w12_check;
// ==================== |-----------| ====================
// ==================== | w12_reset | ====================
// ==================== |-----------| ====================
var w12_reset = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01'IDAT(\u0091\u008D\u008F\u00BFK\x02a\x1C\u00C6?\u00EF\u00F7\u00DE\u00C3\u00A6\x02\x17q8j/\u0088\u00A0\u00BD$\u00A8\x10IIp\u00A9\u00A1\u00C9\u00EB\x0F\u00E8\u00AF\u00A9\u00B5\u00C9I=r=\u00B21P\x1A\x1CZj\u0089\u009B\u009A\\\u00C2|\u00BBoCG\u0098P\u00F6\u00D9\x1Ex~\u00F0\u0098(\u008A\u00F6\u0092$\u00E9\x07A \u00CE\u00B9\u00A6\u00AA\u009E\x00\x1B\x00\u00C6\u0098\x11pm\u00AD\u00BD,\u0097\u00CB\x13\x00\u00D3\u00E9t\x1Cp\x03\x04\u00C0\x16_|\x00)\u00E0g\u00FA\u00C19W\u00A9\u00D7\u00EB/\x02\bp\u0094\u0099G\"r \"\u00CB\u00C3\u00E1p\u00C9\x18\u00B3\x0B\u00DC\x03\u009B\u00D6\u00DA\u00A8\u00D7\u00EB\u00E5,?yL\u0092$\x0E\u00C3p\u009A\u00E9\u00DBV\u00AB\u00B5\u0093\u00CB\u00E5\u00FA\u00C0\u00B6s\u00AE)\u00C0\u00EBL\u00E0\u00B8P(\x1C\u00CE64\x1A\u008D7c\u00CCE&O\u00C5\u00F7\u00FD\u00C0\u00F3\u00BC\u00BC\u00E7yy\u00DF\u00F7Wj\u00B5Z4\u00B7\u00CA`0\u00B8\x03\u00DEUu\u00DDf\u00EF'\u00F3\u00A6Y\u008A\u00C5\u00A2\x07\x18@\u00E6?\u00FC\u008A\u0088T\u00D24\u00DD7\x00\u00DDn\u00F7JU\u00D7\x16d\u009E\u00AA\u00D5jh\u00E38\u00B6\u00E3\u00F1\u00F8\fX\u00B46U\u00D5s[*\u0095\\\u00BB\u00DD\u00AE\u0088\u00C8\u00EA_nU}6\u00C6\u00E8\x7F/|\u00F3\t'\u00ADc\x01\u008D\u00A7\u00A0@\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_reset.png", "w12" );
w12_reset;
// ==================== |------------| ====================
// ==================== | w12_update | ====================
// ==================== |------------| ====================
var w12_update = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01%IDAT(\u0091\u0095\u0091\u00B1J\x03q\f\u0087\u00BF\u00C4\u0083StP\u00D0gp\u00AE\u008B\u00F8\x02.r-t8p\x16\u009F@\u00C1\"\u008AU'\x17Wuu\x11\u00EAP\u008E\x1B\u00C5]\u0097>\u0082\x0F\u00A0\u008B\u00A2\x16\u00AE\\\u00FEq\u00B9+\u00A5\u00BA\u00F8\u009B\u0092\x1F\u00F9\u0092\u0090\u00C0?%\u00D3F\u009E\u00E7\u00CB!\u0084\x060gfO\u00EDv\u00FB\u00F5O\u00A0\u00DB\u00EDj\u00A3\u00D18\x05\u00F6\u0081\u00D9\u00CA\x1E\u0089\u00C8\u00A5\u00BB\u00AF\u00A8\u00EA]\u0092$\u008FQ\rT\u00C5\u0087\u00C0\u0099\u0088\u00DC\u00BA\u00FB\f\u00B0\u00EB\u00EE\x1D\x003{\x00\u0088\x00\u00FA\u00FD\u00FEb\u00D5\u00F9\u00BC\u00D5j\u009D\u00D6M\u00B2,[\u00AAcU\u00F51\x10E\u00D1|\b\u00E1\u00A4(\u008A\u009B\u00C9}U\u00F5\u00DA\u00CCzU\u00FA\f\u00A0\x00eY~\u00BB\u00FBK\x1C\u00C7\u009By\u009Eo\u00D4@\u0092$\u0083\u00D1h4\x10\u0091\u00B5\u00B2,\x17\u00C6\u0080\u00AAn\u0089\u00C8=\u00D03\u00B3\u00ED\u00C9)q\x1Cw\u0080c3\u00FB\x1C\u00AF4u\u00DEa\u0096e\u00AB\u00AA\x1A\u00CCl\x07\u00D8w\u00F7\u00C34M\u00BF\u00C6\u0080\u00BB\u00BF\u0089\u00C8\x15\u00F0.\"{\u00C0A\b\x01\x11\x19\u008A\u00C8Q\u00B3\u00D9\u00BC\u00F8\u00F5\u0087Z\u00D5\u00E3\u00D6\u0081aQ\x14\u00834M?\u00A6k\u00FE\u00A5\x1FC\u00C1x\u00DC\u00FA\u0087\r\u0095\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_update.png", "w12" );
w12_update;
// ==================== |-----------------| ====================
// ==================== | w12_box_checked | ====================
// ==================== |-----------------| ====================
var w12_box_checked = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00C7IDAT(\u0091\u008D\u00D0=J\u00C4`\x10\u00C6\u00F1\u00FF\u00F3\"\u0093\u00C2\u00EC\u00A6[O`#X\u00D8Z\u00D8\t\u00DB[\u00E4\x00\u00B9\u0084\u00E7\u00F0\"j\u00E7G\u00AD7\u00B0\x10\u00C1\x03\u00D8\u00C94I0\x19\u00AB\u0095\x10^\u00DC<\u00F5o\u0098yFM\u00D3\u00BCI:aA$}\u00A4\u00A5\x18 \"\u008E\u00D3R\u00BC\u00CB\u00DE\x01Iw\u00C0\x15\u00D0\x01\x1C\u00EC\u00F1\u00B7\u0092\u00EA\u00AE\u00EB\u00D6f\u00F6\rlv\x1BB\u00D25\u00F0:\u00C5)\u00A5\u00BAm\u00DB\u00B5\u0099=\x03\u009B\u00E9I_eY\u00DE\u008C\u00E3\u00B8\x05^2\u00F8l\u00DE\u00E1\u00C8\u00DD\u00EF\u00AB\u00AA\u00FA1\u00B3mJ\u00A9\x1E\u0086a5\u00C7\u00F3\u00D2\u0097\u00EE\u00FE\u00E0\u00EE\u00D1\u00F7\u00FD\u00A1\u00A4\u00C79\u00CE\u0095\u00BE(\u008A\u00E2\tX\x01\u00A7\u00B9/\u00E4\u00BEt\u009E\u0083\x7F'E\u00C4\u00FB\x7F`\u009A\u0088\u00F8\u00FC\x05\u0080\u00F3=\u00A5\u0097\u009B\u00CD\u00FD\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_box_checked.png", "w12" );
w12_box_checked;
// ==================== |-------------------| ====================
// ==================== | w12_box_unchecked | ====================
// ==================== |-------------------| ====================
var w12_box_unchecked = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00lIDAT(\u0091\u00ED\u0091\u00B1\r\u00C2@\x10\x04g\u00AD\u00EF\u00C7\u00D4CBr\u00DF\u0087\u0089)\u00E1%RK\u00D0\x0291\u00CD8\u00BCuB\u0086tr\x01L<#\u00AD\u00B4\u008A\u0088;p\x01&j\x12x6\u00E0\f,\u0092\u00DE\u00A5\u009Dy\u0092tk\u00C0$\u00E93\u00C6xUAD$\x07f\u00FC\u00F0\x0F\u008E\u00D0\u0080\u00B4=\u00F7\u00DE\u00B7J\u00FC\x1EG\u00B3\u00BDJ\u00BA\u00DAV\x15H2\u00F0\u00D8\x01\u0082\x05!+\x1F\u00A2 ^\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_box_unchecked.png", "w12" );
w12_box_unchecked;
// ==================== |------------| ====================
// ==================== | w12_folder | ====================
// ==================== |------------| ====================
var w12_folder = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00C7IDAT(\u0091\u00AD\u008E=j\x02a\x14E\u00CF{3\u0089\u00B8\u0094@z\u00DBt\u0081i\u0086Y\u0080k\u00B0s\x05)\u0092\u00DA=\x04\u00AC\u0086\u00D9AH\x11\x10\\@H\u00A7 \u00D66\u00C2\b\u00EF\u00BB)t\u0082\b\u0093\x1F\u00C8i\x1E\\\u00CE}\\\u00F8#V\u00D7\u00F5]\u0096e\u00A3.\u0088\u0088EUU/\u00BD\u0085\u00A6i\u00F6\u00C0\u00F0,;\u0098\u00D9\u00D8\u00DD\u0097]\u00E0\u00EE\u009B\u00A2(Z\u0080\u00FCB\x06\u00B8\u00964\u008F\u0088\u00AF \"\u00DE\u0081\u009B\u00AE\u00D0\u0091\u0080\u00F5\u00E9^ru>I\u00C0\u009B\u0099=H\u00BA53\u00EF\u00DB\u009FR\u00DA\u00E6\u00C0\u00FE$\u00D7\u00C0@R\u009F\u008F\u0099-\x1Cx\u00944\x01\x06\u00BD\u00E6\x11\x01S\u0097\u00F4\x01\u00DC\u00FF \x03<\u0097e\u00F9\u009A\u00BB\u00FB*\u00A5\u00F4\u00F4\u009Dif\u00BB\u00B6mg\u00BFx\u00FA\x0F|\x026hF\u00C5\u00D3$\u00AB\u00BF\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_folder.png", "w12" );
w12_folder;
// ==================== |----------| ====================
// ==================== | w12_file | ====================
// ==================== |----------| ====================
var w12_file = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u0085IDAT(\u0091cd```\u00D8\u00B0aC5##c\x0B\x03*\u00D8\u00C2\u00C0\u00C0p\u0081\u0091\u0091\u00F1\u009A\u009F\u009F\u00DFr\u0098 \x13\u0094\x16g\u00C0\x02\u00FE\u00FF\u00FF\u00CF\u00F1\u00FF\u00FF\u00FF\u00C5\x1B7n\u008CA\u00D7\u0080\x0F0300,\u00D8\u00B4i\u0093\x13!\rj\u00CC\u00CC\u00CC\u00EB\u00FE\u00FD\u00FBg\u00F2\u00EF\u00DF?sff\u00E6k\x045\u00FC\u00FB\u00F7\u00EF\x18\x13\x13\u00D3\x19&&\u00A63\u00BF\x7F\u00FFN \u00D6I(`\u00E4j`dd\u00FCHH!##\u00E3\x07\x06\x06\x06\x06\x000a&\x1B\u00F7\x01\x10\u00DE\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_file.png", "w12" );
w12_file;
// ==================== |----------| ====================
// ==================== | w12_next | ====================
// ==================== |----------| ====================
var w12_next = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u0084IDAT(\u0091\u008D\u00CD\u00B1\r\u00C20\x10F\u00E1w'*\u0086\u00A0\u00CD\x18(\x0387\x01\u00CB\u00B1\u00C1\x19\u00A4T4,\u0081\u00E8\u0099!\u009De\x1A\u0082\"\u0094\x0B\u00FE\u00AB\u0093\u00F5>Y\u00F8,\u00E7|\u00AC\u00B5\u009E\u0081\u0093\u0099\u00DD\t\u00A6\u008B\u00BB\x03\x0E\u00C0\u00E8\u00EE}\x0B\u0098\u00B7\x07\u00AE\x11Z\x03\u009B(\x02!\u00DA\x023\u00BA\u00B8{\u00D7\n\x00\u00A4\u0094\u00F2\u00EDv\x7F\u00E2IU\x073{\u00B4\u00FC0\u00A9\u00EA\u0090R\u00BA-\x1F#\u00B0\x1AG \u008C\x7F\u00C1\x13x\u0089H\u008Ab\u00807\x0E\u00C1*\x1A<\x1B\u00A2l\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_next.png", "w12" );
w12_next;
// ==================== |----------| ====================
// ==================== | w12_goto | ====================
// ==================== |----------| ====================
var w12_goto = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00FBIDAT(\u0091\u0085\u00D0\u00BDJ\u00C4@\x10\u00C0\u00F1\u00FF,\t\"\u00EA\u00B5\u00BE\u0089\"\u0082 6\u00DEB\u00B2y\x0F{\x1B\x11\u00E1\u00B0\u00D1\u00A7\u00F0\x116'\x06Si\u00E3\u0081\x0F\u00E0;\u0098\u00F6\x0EdI\u008A\u00B1\u00D9H\u00A2\u00C79\u00E5\u00CEov>\u0084\rQ\u0096\u00E5\x1E\u00D0\x00\u00DB\u00F1\u00E9S\u00CA\u00B2<\x15\u0091\u0083!T\u00D5\x07\u00E7\\\x13\u008B.\u0081\u00BB>\u0095\x00^U'c\u00AF\u00AF@SU\u00D5\u00A4m\u00DBBD~\u0092\t\u00B0+\"\u00B3<\u00CFo\u0086]\"~\x16\u0091#\u00E0\r\u0098\x03\u00D7f\u00DD\u00ECk\u00F0\u00D49wo\u008C\u00D9\u00FFSP\u00D7\u00F5N\u00D7u\u008F\x11/\"^\x01dY\u00F6\u0095\u00FC\u00C6!\u0084\n8\x01\x16i\u009AN\u00AD\u00B5\u00AB\u00A1\x19u\b!<\u00F5\x188\u00B7\u00D6.\x01\u00BC\u00F7W\u00DE\u00FB\u00C3~\u00E9\u00E1y>\u008C1\u00AA\u00AAy?\x06\u0080\u0088\u00DC\x02)\u00F0>*(\u008A\u00E2b\u00DD\x11\u0086\u0091\u00C4\u009F\u00CF\u00BC\u00F7[\u00FFX\x05HTu.\"ND\u008E7\u00E0%\u00F0\x02\u00F0\rs\u0080j\x1B\u00F2\t\u0097\u00B9\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_goto.png", "w12" );
w12_goto;
// ==================== |-----------------| ====================
// ==================== | w12_eye_dropper | ====================
// ==================== |-----------------| ====================
var w12_eye_dropper = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00F2IDAT(\u0091\u008D\u0090\u00ADN\u00C4P\x10\u0085\u00BFsoy\x03\u0092\u00CA\u00D5\x18\x1Cj\u00DF\u0080\u00A4\u00A1\x04r\u00E5&<\x03\u008A\x00\x02\x12\x16\x03XR\u0087\x02\u00D7\u00E4\u00D6\u00E01($\u008A\u0084\x04\x0B\t\u00BA\x15\u00DD\u00C1\u00B0\u00A4[\u00CA\u00CF\u0091\u00E7|g23\u00E2\x17\x15E\u00B1\u0094\u00A6\u00E9\u00B9\u00A4\t\u00F0\u00EC\u009C\u00DB\u00D1\x1F\u00F0\u008D\u00A4\u00AD\u00B9'\u00E9\u00E1[!\u00C6\u00B8jf\u0095\u00A4W`\u00AD\x17\u00BF%\x03\u00C3/%\u008D\u0080\u00D1@v\u00E5\u00FA\u008E\u00A4]I\u00EF\x03\u00FEu\u00D34{\x0B\u0085\x18\u00E3\u00B1\u0099\u009D\u0099\u00D9f\u00B7df\u00F7u]OB\b\u00AD\u00EB\u00C2\u00C0!0\x06\u00A6\u00DD\u0092\u00A4\u00E5\x10B\x0B\u00E0\x00\u00AA\u00AA:\u00FA\u0084\u00E7\x1A\x03\u00D3\u00D9l\u00B6\x0E\u00DC\u00B5m\u00BB\u00F1\u00B5ZY\u0096+\u00DE\u00FBG`\u00E1cfv\u0090\u00E7\u00F9I\u00FF\x16')\u00FD/\f\u00908\u00E7\u00B6\u00BB,\u00B0\u009F\u00E7\u00F9\u00E9\x10\f\u0090\x00)paf\u00B7\u00DE\u00FB\u0097,\u00CB\u009E~\u0082\x01>\x00G\u00B9W6\x14\u00F7K\u0085\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_eye_dropper.png", "w12" );
w12_eye_dropper;
// ==================== |----------| ====================
// ==================== | w12_menu | ====================
// ==================== |----------| ====================
var w12_menu = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u0082IDAT(\u0091\u0095\u008E=\n\x021\x10\u0085g&\x13E\b\u00AC\u008D6{3\u00DB\x04\x0Fce\u00E3\u00E5\u00BC\u0084\b\x11v\\\u00C8\u00B3\x14\u00BB\u00E7W\x7F\u00EFG\u00E4O\u00B4\u00D6z3\u00B3\x03#\x03x\u00BA\u00AA\u009E\x00\u00EC\u00C9\u0081\u00C5\u00CDlN)m\x19\u00BB\u00F7\u00FEv\x11\u0099\"b\u00C7\x04r\u00CE\u008B\u008F1\u00EEfV\u00C8K\u00AB\x03\u00B8\u00AA\u00EA\u0091\u00B1\x01<\u00C8\u00E2/\u00DAZ\u00BB\u0088\u00C8D\u00C9\u00AA/\x17\u00913\x1B\x00\x10\x1E\x11s)eC.\u00AC\u008C\u00F7\u00C3\x07<\u00B8*\"Z\u009E\x14h\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_menu.png", "w12" );
w12_menu;
// ==================== |-----------| ====================
// ==================== | w12_heart | ====================
// ==================== |-----------| ====================
var w12_heart = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x00X\x00\x00\x00X\x01x\u00DAKb\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00FFIDAT(\u0091\u0095\u008F\u00BDJ\u00C3`\x14\u0086\u009Fs\x1AH\u0083\u008BtU';z\rR\x05\x07M\u00BF!K\x06\u00F1N\u00EA\x15\u0088\u00B7\u00A1\u00C3\u00B7\u00C4\x10\x10\x1C{\x0F\x16\x05\x11\u00D4\u00E2\u00AE $\u0085$\u009FK\x02\u00A5\u00C6\u00BFgzy\u00DF\u00E7\fGhH\u00D3t\u00A8\u00AAky\u009E\u00DF\u00C6q\\\x01Xk{A\x10\u00EC\u0094e\u00F9\x11E\u00D1#\u0080$I\u00B2\u00AE\u00AA\u0097\u00C0as;\x07N\u009A|\x01l\x02\u0088\u00C8uQ\x14\u00C7\u009E\u00AA\u009E/\u00C9\x00[\u00C0M\u0093\u00FBm\u00E9\u009C;\u00F2}\u00FF\u00CC\x03\u00C6|\u00A5\u00DF\u00D1\x01\u008C\x15\u00A8\u00BF\x19\u00BB\u00A8\u00D59\u0097\u00FC\u00E3\u00E0J\u009Ds\x13`\u00F6\x07y\u00B6X,N5\u008A\u00A2\u00B7\u00AA\u00AA\u00F6E\u00E4\u00EE\x07\u00F9\u00A1,\u00CB\u00838\u008E\u00DF\u00A5m\u00B2,\u00DB\u00A8\u00EBz\nl\u00AF\u00C8/\u009E\u00E7\u00ED\u0086a\u00F8\f\u00A0mk\u008Cyu\u00CE\u00ED\x01OK\u00F2\\DF\u00AD\f \u00AC\u0090\u00A6\u00E9\x10\u0098\x02=U\x1D\x19c\u00EE\x7F\u00FD\u00CEZ;\u00B0\u00D6\x0E\u00BA\u00B6Ob\u00A5U3X1\x7Fs\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_heart.png", "w12" );
w12_heart;
// ==================== |----------| ====================
// ==================== | w12_more | ====================
// ==================== |----------| ====================
var w12_more = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u009CIDAT(\u0091\u009D\u00901\n\u00C2@\x10E\u00DF,\u008B\u00A2\u00C4X\u00A8E\u00AEf\u009D`i\u00EF\x15,,-\u0092\u00A3\u00D9\x0BJ\u00BA\x10X!\u00FBm\u0083D\x12}\u00E5|\u00DE\u00CCg\u00E0Glh\u0098\u00E7\u00F9\u00D5\u00CC6\u0092\u00EEUU\x1D\u00FB\u0099\x1F\u00DCb\u00B6\u0097\u0094\x02\x0F`\u0092\u0090y\u00EF\u00E7]\u00D7\u0085\u00CFlP\x00\u00D6!\u0084E\u008C\u00B1\x05\u00DAQ!\u00C6xs\u00CE-\u009Ds5\u00B0\u009Dr\u00E1\x02\u00EC\u0080\u00FB\u0097|:\u0083o-\u008A\u00E2,)5\u00B3\u00BA,\u00CB\u00D3h%I\x07`%\u00E9\t\u008C\x0B!\u0084,I\u0092Y\u00D34\u00AF\u00FF\u008A\u00F7x\x03\r\x1D67@\u00C0\x1B6\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_more.png", "w12" );
w12_more;
// ==================== |------------| ====================
// ==================== | w12_search | ====================
// ==================== |------------| ====================
var w12_search = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01)IDAT(\u0091\u0085\u0091\u00AFO\x03A\x10\u0085\u00BF\u00B79(\x02\x12\x12\x1C(D%\u00E0\u00EA1\b\u0092\u00F6*\u0090\x15\u0090\x00\u00A6\u00E1\x1F\u00A8&X\x1C\tI% 0\u00DC\u00B5\u0086\u0084\x10\x04\x02P5848~\x04\u00D4]\u00B8\x1D\u00CC^\u00D3\u009C\u00E1\u0099\u009D\u00CC\u00BE/\u00F3vV\x04\u00A5i\u00DA\u0091t`f+@\x0E\u00DC\u009B\u00D9a\u00BB\u00DD~bB\n\u00E6S`?\u00F4\u00BE\u0081\x19`\x1A\u00F8\x05v\u00E28>+\x01\u0097\u00A6i'\u0098\u00DF\u0080\u008DV\u00AB5\u009Fe\u00D9\u009C\u00A4.`@\x7F0\x18,\u0097@\x04tC\u00BD\x1D\u00C7\u00F1M\u00A8s\u00E0$I\u0092%I=3\u00DB\x03z\x00\x0EX\x05~F\u00A3\u00D1-\x15I\u00BA\n\u00E7\u00DA8\x12\u0090\x01\u00B5F\u00A31U\x05\u00BC\u00F7\u00B3\x00f\u00F69\t\u00DC\x03\u00D3y\u009E\u00EFV\x01\u00E7\\\x19\u00F7q\u00FC\x063;\u0092\u00B4)\u00E98I\u0092EIW\u00DE\u00FB\u00D9`\u00DE\u0092\u00F4\u00E1\u009C;\u00AF\u00AE\u00B5\x03\u00F4\u0081Ze\u00C8;\u00B0\x00\u00BC8\u00E7\u00D6\u009B\u00CD\u00E6\u00AB\u00CA\u009B\u00B0\u00BA\u00FD\u00F0q_f\u00F6\x10E\u00D1EQ\x14O@\u00BD\u0084\u00C4?\x1A\x0E\u0087K\u00DE\u00FB;\u00A0nf\u00CF\u00FF\x02%T\x14\u00C5\u00B5\u00A4\u00CB?E\u00CCr9\u00B0B\u00A2h\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_search.png", "w12" );
w12_search;
// ==================== |------------| ====================
// ==================== | w12_parent | ====================
// ==================== |------------| ====================
var w12_parent = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00EEIDAT(\u0091\u0095\u008F1K\u00C4@\x10\u0085\u00DF\u00CC.h\u0095\u00E2\x14\x14[AP\u00AE;\x0BK\u0083}6\x04l\u00FD\t\u0082\u00BD\u00BD\u00BD\u00FE\x04\u00E1\u00AE\u00DB\x10\x16b/\u0096V\"\u00F8\x0F,\x0E\x04+\u00C1\u00C4\u00CC\u00D8\u009C\u00CD\u00BAA\u009Cjx\u00EF}\u00F3\x18\u00E0\u009FC\u00DE\u00FB}c\u00CCt\x18\u0086\u00E7\u00AA\u00AA^\u00C6\u0082M\u00D3\u00EC0\u00F3\u00B6\u00B5\u00D6>\u00A8\u00EA\u00C4\x18\u00F3\x06`3\x0E\u00B6m\u00BB\u00D6u\u00DD\x05\u0080K\x11Q\u00AB\u00AA\u0093\u0095\u00B7\x11\u0087C\b'}\u00DF_\x13\u00D1\u00C1\u008FfS\u00F5u]\u00EF2\u00F3\u0095\u0088\u009C\u00C6\u00DE/ \u0084P\u008A\u00C8\x02\u00C0z\u00EA\x18\u00C7\u0082\u00AA~\x02\x18R\u00E1$P\x14\u00C5\x1D\u0080=U\u00BD\x05\u00A0\x7F\x02\x00\u00E0\u009C{-\u00CB\u00F2\f\u00C01\u0080\u00A7\x18X\u00AE\u00F6e\u00C4\u00C19w\u009Fe\u00D9\u008C\u0088\u00CE\x01\u00BC\x03\u00F8\u00B0Dt\u00A4\u00AA\u0087D\u00F4\u0098j\u00CB\u00F3\u00FC\x0B\u00C0\u008D\u00F7~\u00CE\u00CC[c\u00BF\u008D\u00CE7Q\x7FM\u00B8\u00BA\x06dZ\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_parent.png", "w12" );
w12_parent;
// ==================== |-------------------| ====================
// ==================== | w12_folder_closed | ====================
// ==================== |-------------------| ====================
var w12_folder_closed = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u0080IDAT(\u0091c` \x110n\u00DA\u00B4)\u00F6\u00FF\u00FF\u00FF>\f\f\f\u008C\f\f\f\f\u008C\u008C\u008C\u00FF\u00FE\u00FC\u00F9S\x17\x14\x14t\x0B\u00AB\u0086\u008D\x1B7~b``\u00E0E\x13\x7F\u00C6\u00C8\u00C8\x18\u00CC\u00C4\u00C4\u00F4\nY\u00F0\u00DB\u00B7o\u00CFY\x18\x18\x18X\u00B1\x18$\u00F5\u00FF\u00FF\u00FF\u00E3\x7F\u00FF\u00FEE\x11dgg\u00BF\u00CCD\u00A2\x17tI\u00D5\u00C00H5|&A\u00FDg\u0096\u00FF\u00FF\u00FF\x07011\u00D9\x12\u00A3\u00FA\u00DF\u00BF\x7F\u00C7Hu\x11\x03\x00\u00B3\u00AD&C\u00C5\u00F7F\u009C\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_folder_closed.png", "icons" );
w12_folder_closed;
// ==================== |---------| ====================
// ==================== | w12_run | ====================
// ==================== |---------| ====================
var w12_run = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00E4IDAT(\u0091\u008D\u008E=N\u00C3@\x10\u0085\u00DF\u00B3\f\u00AEpmQCI\x05\x17p\u008F<\u00B2\u00DCs\x04\u00A4H \u00AE\u0080\u0094\u008A#\u00A4\u00C4\u00D5\u00EE:tH\u00A4JK\u0087\u00F8I\u0083D\u00E1\x13 \u0090\u00D9\u009DT\u0091,Hb^\u00F9\u00CD|\u00F3\u0086u]\u00EF&I\u00F2H\u0092\u00AAz)\"w\u00D8\x12Zk\x0F\x00\u00BC\u00F5\u00D8\x14\u00C0HD\x16\u00EB\u0084(\u008A\"\u00FEb\u00A7\x00\u009E\u009Cs7\u00D6\u00DA\u00BD?\u00C2\u0086\u00E6\x1DU=\x07\u00F0l\u008C9SU\x0E\t\u00AB\u00EC\u0093\u009C8\u00E7\x1E\u008C1G\x00\u00C0\u00A6i\x0EC\b\u00AF\x03\"\x00|\u0093<\x1Ej\u00E8\u00C7\u00AB\u00EA\u00CF\x7F\u0085{\u00EF\u00FD\u0089\u0088\u00BC\u00C4\x03\u008B\x0B\u0092\u00A3\u00A2(\u00A6+\u00B0I\u00F8\x040N\u00D3\u00F4:\u00CF\u00F3\u00AF\u00FE \x0E!h\x1F\u0090\u00BC\u00ED\u00BA\u00EE\u00A2\u00AA\u00AA\u008Fu\u0097\u00E2\u00B6m\u00DF\u00B3,\u009B\u0093\u008C\u00BD\u00F7WeY\u00CE\u00B6\u00FD\u00B8\x04\u00AD\u0088P\bP\u0083\u00FB\u00B9\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_run.png", "icons" );
w12_run;
// ==================== |----------| ====================
// ==================== | w12_edit | ====================
// ==================== |----------| ====================
var w12_edit = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00BEIDAT(\u0091\u0085\u00CE\u00B1M\u00C4P\x10\u0084\u00E1\x7F\u00DE\x11\u00D1\u00C3\u00C5G\x11\u00D4\u00E0]\x11Q\x00\t\x05\u0080\u0091\b8BKdD\b\n B\u00B6\u009B\u00B8\u0094\u0094\x02\u0088\u00A0\u0087!1\u0092\x05\u00B6\u00DEd;\u00FA\u00B4\u00BB\u00A2\u0092\u00BE\u00EFo%]\u00D9n3\u00F3I\x15|!\u00E9y\x1Am\u00FBr\u00B3\u0086\u0087a\u00B8\x06\u00DE\u0081\x13I[@\u0092N\u00CB\n\u00DE\x03\u009D\u00A47\u00A0\u00B5}\u0098N<\u00FC{i\u00C2w\u00B3\u00EA\u00DB\u00F6\x19\u00B0\u00CB\u00CC\u0097M\x05\x03\x1C\u0097R>\"\u00E2\x11\u00A0T0\u00B6\u00BB\u00A6i\u00BA\u00DFY5\u009C\u00997\u00F3\u00AE\u008C\u00E3\u00D8.a\u00E0\u00FE/\x06(\u00B6\u00CF\u00976G\u00C4~a\t\x05\u00D8\u00D6\u00DE\u0098\u00E7\b0\u00F0\x05|Jz\u008D\u0088n\r\x03\u00FC\x00\u00E1\u00B1T\u00C0\u00EC\u008A\u0080\u00CF\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_edit.png", "icons" );
w12_edit;
// ==================== |---------| ====================
// ==================== | w12_add | ====================
// ==================== |---------| ====================
var w12_add = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00B8IDAT(\u0091\u00BD\u00901\n\u00C2P\x10Dg\u00B2\u00BFW\u00BC\u0080\u0085DKA\x1B/`\x15\u0084\u00DC\u00C6\x03Xy\u009B\x1F\u00D3z\x00A\u00B0\u0096\x14\u0082\u0095\u0095'\b\x7F\u00D7\u00C6h\u00F8\"\u0092\u00C6\u00ADv\u00F6\u00ED\f\u00CB\x12Q\x15E1\x01p \u00A9\"2\u00CB\u00B2\u00EC\u00DA\u00E6Il0\u00B31\u0080\u00BE\u0099\r\u00EA\u00BANc\u00FEa\u00F8U\u009D\r\u00AE,\u00CB4\u00840l\x06$\u00A7\u00AD~\u00E6\u00BD\x7F/;wq\u00AAz$\u00D9\u00FB\x12\u00B8%\u00F9\x12!\u0084[\x02@;\\d\u008E\u00E4\\UG\u00AD3\x16\x006O\u00B96\u00B3S\u00C3D\u00A4b\x1C\u00E1\u00BD_\u0091\u00DC\x01\u0080\u0099-\u00F3<\u00DF\u00B7\u00F9\x1F\u00DE\x1A\x0FD\u00E4\u00AC\u00AAw\x00AD\u00AA\u0098?\x00\rZ:Hg\x16\u008E\u00A1\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_add.png", "icons" );
w12_add;
// ==================== |------------| ====================
// ==================== | w12_remove | ====================
// ==================== |------------| ====================
var w12_remove = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x003IDAT(\u0091c`\x18\u00FA\u0080q\u00E3\u00C6\u008D\u00A9\f\f\f&D\u00AA?\u00CF\u00C2\u00C0\u00C00\u0099\u0081\u0081\u0081\u009DH\r\x7F\u0099\x18\x18\x18\u008E\u0092\u00E0\u00A2c$\u00A8\x1D>\x00\x00\u00BE\u00ED\x06\u00A5?G4\x19\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_remove.png", "icons" );
w12_remove;
// ==================== |----------| ====================
// ==================== | w12_sort | ====================
// ==================== |----------| ====================
var w12_sort = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01 IDAT(\u0091\u008D\u00911K\x03A\x10\u0085\u00BF\x1D.\u00B7Uj#\x16\u0081\u0090NR)Z\u00C76\u00C5\\e\u00A7?\u00C0\u00D2\x1F\x10\u00ACLeaei\x7F\u0082aO\u00EC\x04K\u00AB`\x17\x10\u00C1\u00C6\u00C6\u00C6.\x04C\u0096;\x0B\u00F7`9\x14|\u00F0`gfg\u00E6\u00F1\u00C6\x00\u00E4y\u009E\u00A6i\u00DA\u00CF\u00B2l\x0E0\u009DN\u00F7D\u00A4\u00CB\x0F\u0096\u00AAz\x1F\u00DE\b\u0080\u00B5\u00F6\u00CA\x18\u00F3T\x14\u00C56\u0080\u0088L\u0080<\u00B0 \u0082q\u00CE\u009D\x02\x17!~\x13\u0091}\u00C0z\u00EF;\u00A1y\u00A1\u00AA/\u00F1\u0086>0\x0F\u00F1\u00AC,\u00CB\u0081\u00F7~KDz\u00C0g\u00FC\x19@T\u00F5\x04\u00B8\x01P\u00D5CU}\u00AC%\u0089\u00C8\x11\r$\u00CDD\u0090q\u00EC\u00BD\u00EFXk_\u00FF\u00D5\x10$u\u00D7\u00EBu\u00CF9\x17\u0097\u0096\x7Fm\u0098\x00\x07\u00BF\u0094J\u00E3\u009C\u00BB\x03v\u0080Mc\u00CC\f83\u00C6<\u00D7.5\x06-\x04\u00B8\x066\x00\u00AA\u00AA\u00F2\u00EDv\u00FB\u00A1v)&\u00C11Q\u00D5\u00DB\u00AA\u00AA\u00C6\u00C0{\u0092$\u00D9p8\u00FCj\x1C.'rL\x00T\u00F5|\u00B5Z\rF\u00A3\u00D1G\u00EDRY\u0096\u00BB1[\u00AD\u00D6%\u00C07\u0093eo,\u00FFz&S\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_sort.png", "icons" );
w12_sort;
// ==================== |-------------| ====================
// ==================== | w12_sort_up | ====================
// ==================== |-------------| ====================
var w12_sort_up = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00F5IDAT(\u0091\u008D\u0090\u00B1J\x041\x14E\u00CF<w&\u00BF\u00B0`\u00B1\u00B0l'Zjo#\u008CEf>@?\u00C2\x0FPa\u008B\u00AD,\u00AC\u00FC\u00874\u00C3\u009B\u00DE\u00DABl\u00B7Y\x10\u00BFA\x16\u009B\u00C4`\u0093\u00C08\u00E8\u00EA\u00ADr_r\u00B9\u00E7\u00A5\x00p\u00CEUUU-\u009A\u00A6Y\x03t]w,\"\u00B3\x18\u00E3s\u00DB\u00B6o\f$\x00\u00C6\u0098\u0087\u00A2(\u009E\u00FA\u00BE?\x00\x10\u0091\x15\u00E0D\u00E4\u0082\u0091\nU\u00BD\x02\u00EE\u0092\x7F\x15\u0091\x13\u00C0\u0084\x10\u00A6\u00C6\u0098M]\u00D7\u00EF\u00E3\u0086\x05\u00B0N\u00FE%\u00C6x\x18B\u00D8\x17\u0091\u00B9\u00F7\u00FELU\u00CF\u00BF5\x00\u00A8\u00EA-pc\u00AD\u00CD\u00FE\x118Mo\u00A2\u00B5v/\x07&c\u00C6\u00B4\u00C3e\ba\u009A\u00CE\u00DB\u00E1\u00DD\u008F\u0081\u00844\u00CB^U\u008F\u00F2\u008F\u00FD\u00D6\u00B0\x1A \u00E5\u00D95\u00B0\u00FC\x13)\u00CB\x18\u00B3\u00F97\x12\u0080\u00F7~\u00AE\u00AA\x1F9\u00F0\t\u00C4]HIq\x02P\u0096\u00E5\u00BD\u00F7\u00DE\u00EDBJ\u00F3\u00ED\x17F{]\u009A\u00992\u00D4f\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_sort_up.png", "icons" );
w12_sort_up;
// ==================== |---------------| ====================
// ==================== | w12_sort_down | ====================
// ==================== |---------------| ====================
var w12_sort_down = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00EFIDAT(\u0091\u008D\u00D0\u00B1J\u00C4@\x10\u0080\u00E1\x7F\u00C7\r\u00A9R+X\u0088\u0096\u0082\u0095b\x1F\u00DB\x14\u00E3\x0Bx\u008Fr\x1C\bZ\u00DF\x13\u00D8[\x18\u00B2b\u00E7\x1B\u00E8\x0B\u0088\u008D`ccw\x1C\x06\u0096\x1D\u009B\u00DC\u00E1%9p\u00ABe\u00D8\x7F\u00F9\x18\x07\x10B84\u00B3cU}\x02\u00A8\u00EB\u00FA\\D\x0E\x18\u009E\u00A5\x070\u00B3\t0\x03\x1C\u0080\u0088\u00DC\x02\x17#A\u00F2\u00DD\u00C5\u00FD\u009D\u008A\u00C8$\u00C6\u00B8\u00D7\x7F-\"\x0B\u00DF\x1F\x02\u00C4\x18\u00F7\u00FB\u00A4\u0094\u00D2\u008B\u00AA~\u008C\x06c$\x11\u0099\x02\u00D7\u00DB\u0082\x01)\u00CF\u00F3w\u0080\u00FF\u0090\u0096\u00AB\u00EDm\rz\u00A4\x04\u00EC\u00AC\u0083\u00A6i\x1E\u0081S\u0080\x10\u00C2+0s\u00CE\u00ADI\"\u00B2\u00D8\u00F8\f\u00B8\x03v\x01\u00CC,\x16E\u00F1\u00DC\u0091\u008E\u0080oU}\u00DB\bT\u00F5\u00C1\u00CC\u00A6\u00C0\u00A7\u00F7\u00FE\u00B2,\u00CB\u009F\u008Et/\"W\x03.\u0080\u00AA\u00DE\u00B4m{RU\u00D5\u00D7jK)\u00A5\u00B3,\u00CB\u00E6\u00FD\u00E0\x17\u00D8\u00F2U\u00A0\u00FB \u00A6,\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_sort_down.png", "icons" );
w12_sort_down;
// ==================== |--------------| ====================
// ==================== | w12_language | ====================
// ==================== |--------------| ====================
var w12_language = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\f\x00\x00\x00\f\b\x06\x00\x00\x00Vu\\\u00E7\x00\x00\x00\tpHYs\x00\x00\x05\u0089\x00\x00\x05\u0089\x01mh\u009D\u00FA\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\x15IDAT(\u0091\u0085\u008F\u00B1J\x03Q\x10E\u00CF{/K\u00D6\u00CE\u00CE\u0098\"\u0085\x16\u00C1*B\n\u00B1\u00D5B\u0090\u00C5\u0097BX\u00FD\x00!\u00B5\u0085?\u00E0\x1F\u0088M\u009A\u00D8\u00AF\u0090e\u0093\u00D4\u00FA\x05AD,\x15;;c\u00BB\u00C5\u00E6\u00DA\x18\u00D0\u0098\u00C4S\r3\x1C\u00EE\\\u00C37\u00BD^o\u00CB9\u00F7\x00\u00DCy\u00EF\x0FY\u0080\u009D\x0E\u00A5R\u00E9\x04\x18\x03\x07Y\u0096U\u00FF\x15$\u00C5\u0092\u00BA\u00C0X\u00D2\u00F1R!M\u00D3m\u00A0n\u00AD\u00951\u00E6\u00CD\x18\x13/\x15\u009Cs1\u0090K:\u0095\u00B4\x0E\u00EC\x0E\x06\u0083\u00DABA\u00D2\x11p\u00EB\u00BD\u00DF\u00B4\u00D66\u0080BR\u0094$\u00C9J\u00A7\u00D3\t\u00E6u\u00D8\x0B\u00C3\u00B0\r0\x1A\u008D>$yI\u008F\u00E5r\u00F9\u00BER\u00A9\\\u00A7i\u00DA\u00CC\u00B2\u00AC\x0E`f#\u0087\u00C3\u00E1FQ\x14/s\u00BEy\u00F7\u00DEW\u00ED\u00EC6\u008A\u00A2\u00D7<\u00CFW'\u0093\u00C9\u008E\u00B5\u00B6\t\\\x02\u009FA\x104\u00E6&L\u00E9\u00F7\u00FB]I\u00FB\u00C0\u009A\u00A4\u008BV\u00ABu\u00F5\u00B3\u00C3\x1F$=\x035\x00c\u00CC\u00D3l\u00E9_$I\u00E2\u00803I\u00E7\u00C0\r\u00D0\u009E\u00DE\u00BE\x00Mvj\x14\"V\u0098\u00CA\x00\x00\x00\x00IEND\u00AEB`\u0082", "w12_language.png", "icons" );
w12_language;
// normal 16px
// ==================== |--------------| ====================
// ==================== | w16_language | ====================
// ==================== |--------------| ====================
var w16_language = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\x11IDAT8\u008D\u00ED\u0090\u00BDJ\u00C4`\x10E\u00CF|\u009B\u00FD@\u008B\u00B5[{\u00D1\u00DA\x05\u00C1\u009F\u00CAJ\x0BKA\u00EC,\x04\x1FA\u00D6\u00C6G\u00B0\x12}\x02m\u0082\u00A0\u00C1\u00CA\x07\x10\u00844vZ\u00BA,\"VQ\x0BCB\u00C8\u00B5\u0089KX\u00A2/\u00A0S\u00DE;s\u00EEe\u00E0\x7F,\u008A\u00A2W\u00A0\u00FB\u0083\x7F\x02L\x00\u00BBM\u00A6\u00A4\u0081\u00AB\x1D\x7F\x02G\u00C0\u00F0\u0097\u00C0\x14\u00B8\x18\u00A5\u009Bu]\u00CD\u00BC\u00CC\u00B2\u00EC\x108n8,\u00CDl\u00C39\u00B7\u009Ee\u00D9\u008E\u00A45\u00A0\x0FP\x07\u0084\u00DE\u00FB\u00BD \bB@c\x00'\u00A9/i\u00CB{\x7F\u009A\u00E7\u00F9-\u00B0Y\x07\u00BC\u00B5\u00DB\u00ED\x1B\u00E7\u00DCs\x1C\u00C7CIw\r-V\u00CB\u00B2\u00BC6\u00B3\x03\u00EF\u00FD>\u00B0\x04\x10T\u00CF\u00B8*\u008AbV\u00D2v\u00AF\u00D7K\u009Ds\u00A1\u00A4\u00951\u00C0{\u00AB\u00D5\x1AH\u009A\x04\x1E\u00BE\u00C5\u00A0z\u00C6\u00B2\u00A4s`\x1EX\u0094\u00F4\u00D2\u00D0`J\u00D2\u0099\u00A4\x053\u00BB\u00974\x02<\x02\u00D3\u00D5Rbf\x1D\u00A0\x03$\u0092R3\x13\u0090TM\u00E7\u0080\x0FI3\u0095\u00F6\u00D4\x10\u00F4\u00E7\u00E6\x0B\u009C7p\u0085\x04}\f\u00FE\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_language.png", "w16" );
w16_language;
// ==================== |-------------------| ====================
// ==================== | w16_settings_file | ====================
// ==================== |-------------------| ====================
var w16_settings_file = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01JIDAT8\u008D\u0095\u0092=K\x03A\x10\u0086\u009F9\x0E\x16s\u0095]\u00B4\x10,\u00B4\u00B5\u00B1\x16R\tB\u00B2]\u008AT6\"VZ\u00F8'l\x15A\u00FC\t\u0081\u00C0\r1\u00B6~T\u00B6\u0096b\nA\u00A2\u0085\u0082\u0090\"p\u00C7\u0085\u00B5HN.\u00F1\u00F2\u00F5V\u00B3\u00B3\u00EF<\u00CC\f#\x00\u00F5z}\u00C9\x18\u00D3\x06V\u00F9\u00AF\u009F(\u008A\u00D6\u008D1\x0F\u00C0\u009E\u00B5\u00F6#\u00FB\u00E9\x01\x18c\u0096'\x14g\u00B5\x05\u00DC\u00AB\u00EA\u0088\u00CF\u009BQ4\u00AE\r\u00E0.\x0BY\x14\x00\u00B0\t\u0084\u00E9\u00C3\u009F`ze0R\x00\x04\u00C6\u0098\u00C0Z+yF\x01\x18\u00B6\u00D4\x19\u00E6\x12\x11\u00D9\x15\u0091\u00A7~\u00BF\x7F)\"\u00FB9u=km\u00907\u00C23\u00E0;\u00E7\u009A\u00C0Z\x1C\u00C7\u0087\u00C0\u00F7\u00B4yF\x00\u00CE\u00B9\x1Ap\u00E5\u009C\u00FBr\u00CEE\u00D5j5\x06\u00DE\u00A6\x01\u00C6wP\u00B2\u00D6\x1E\u008D\u00E5\x1E\u0081\u00ED\u00B9:\x10\u00913U=h\u00B5Z\u00C5F\u00A3\u00B1\x02P\u00A9TN\u009Ds\x17s\x01\x18l\u00FD:I\u0092O\u00DF\u00F7\u00DB\u00AAZ\x12\x11g\u00AD=\x16\u0091\u00F3y\x00Y\x15\u0080\u009B\x14R.\u0097O\u00F2 \u00B3\x0E\u00A9\x004Uu'\u0085\x00/Y\u00C3\u00A4C\u00CA*\x00nU\u00B5\x16\u0086a\u00C7\u00F3\u00BC\u00E2\u00A2\u0080\x14\u00A2\u009E\u00F7\u00D7p/\r<\u0080(\u008A\u00BA@wN\x18\u00C0{\x1A\u00FC\x02\x17\x16i\u0080-\u00A9W\u00C4\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_settings_file.png", "w16" );
w16_settings_file;
// ==================== |----------| ====================
// ==================== | w16_user | ====================
// ==================== |----------| ====================
var w16_user = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\x1BIDAT8\u008D\u009D\u0092?K\u00C3@\x18\u0087\u009F\u00F7\u0092\u008F\u00D0\x16\u00C1Epw\u00D5\u008E.\u008A\u00C14$\u009FE\x10\x1CT\x1C\u00A4\u00F4\u00C3\x044\u0090A\x14\x1C\u00DD\u00C4\u00D5\u00A9\u008B\u00A0\x16\x04\u0087\x0E\u00CD\u009F;\x07\u00DB\x1A\u00D2\u00D4\u00B4\u00F9\u00C1-\u00EF{\u00CFs\u00C7\u00DD+T$\u008A\u00A2\u0096\u00D6\u00BA\x0F\x1C\x02\"\"w\u00B6m\u009F:\u008E3*\u00EF\u0095r!\u008E\u00E3v\u009A\u00A6\u00CF\u00C0f\u00A9\u00F5\u00AE\u00B5\u00EE\u00FA\u00BE?,\x16UY\u0090$\u00C9\u00A0\x02\x06\u00D8PJ]\u0097\u008B\x0B\x02\x119\u00AA\u0080g9\u00A8\x15\x00\u00D6?\u0082\u0085^\u0095\u00E0~\x19m\u008Cy\u00A8\x15(\u00A5ND\u00E4\u00AB\u0082\u00FF\u00D6Z\u009F\u00D5\n\\\u00D7}\u00CB\u00B2\u00AC\x0B\u00DC\x00\u00E3\u00E9\u00BAUJ\u00ED\x05A\u00F0\u00BA\u00ECv\u008D3\u009F\u00830\f;\u00B6m_\x19cz@\u00C7\x18\u00F3(\"/\u0080\x01v\u0080}`\x04D\u00C0\u00B9\u00E7y\x1Fs\u00C1t\u00F2\u009E\u0080\u00ED\x15\x0F\x1E\u00E6y\u00BE\x1B\x04\u00C1\u00A7\x02\u00D0Z_\u00AC\x01\x03lY\u0096u\t\x7F\u008Fx\u00BC\x06<K\u00AF(h5\x10\u00B4\u008B\u0082I\x03\u00C1\u00A4(\x18\u00F0\u00FB\u00DF\u00ABf,\"}\u0080\x1Fd3T}\u0091\u009Et \x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_user.png", "w16" );
w16_user;
// ==================== |---------| ====================
// ==================== | w16_bug | ====================
// ==================== |---------| ====================
var w16_bug = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\u009BIDAT8\u008D\u008D\u0092\u00B1\u008B\x13Q\x10\u00C6\x7F\u00F3\u00DE.\u00C6\u00E6\u00AC\x14\u00AC\x15,$J\"\u00D8\u00E8\u00A1x\u00DD\u00EE\u00C5\u0084-\u00F2Oha#X\u00A6\u00F4/\u00D0\u00BF\"\u0081\u0087\u00F2\x04\u00D1\u00A0\u00DC]\u00A5\u008D\u00DC\x156\u0087\u00D8\x1D\u008A\u00CD\u0081hn_\u00DE\u00D8\u00E4\u0096\u00B0\u00EC\u00E1~\u00D503\u00DF\u00F7\u00CD\f#4\u00C0{\u00BFQ\u0096\u00E5s\u00E0!`E\u00E4M\b\u00E1iQ\x14\u00BF\u00EA\u00BDROL&\x13\u00D3\u00EB\u00F5\u00E6\u00C0\u00BDZ\u00E9\u00CBb\u00B1\u00B8=\x1E\u008FO\u00D6\u0093\u00A6.\u00D0\u00EF\u00F7\u00EF7\u0090\x01nv:\u009DA\u00E3\x04\u00CE\u00B9\x1D\x119\x1FB\u00C8\u00D24\u00BD\u00B2\\.\u0087M\u00AB\x19c\u00DE\u0086\x10\x0E\u0092$\u00F1\u00AA\u00FAg8\x1Cn&\u00AB\u00DA9U\u00BDe\u00AD\u009D\u00C7\x18\u00B7F\u00A3\u00D1\u00B3&\u0081\u00D9lv\u00C9Z;W\u00D5\u00EB\u00C0g\u0080\x04 M\u00D3\u00BC,\u00CB\u00F7@\x178r\u00CE5\u00F1\u00D7\u00F15I\u0092\x01\u00ACn\u0090e\u00D9O\x11y\u00F2?\u00D6\u00DA*\u008F\u00F3<?\u00AA\x04VX\u00D6\x1BU5\x17\u0091\u00AC\u009E\x0F!T\u00BD\u00E2\u009C\u00DB\x01\u00EE\u009Ea\u00F6@D\u00A2\u00AA~8\u00A3\u00BEgD$\u00B4\x1D\u00BD\x01\u00A1z$\u00E7\u00DC\x1D`\u00B7\u00CD\x04\u00C6\u0098\u00CD\u00C1`\u00B0\x0Bk7\b!|\x07\u00B4\u00A5\u00F3\u00B7J\u00EC4\x10\u0091\x13\u00E0\u00B8\x05\u00F98\u00C6X\u00ADm\x00\u00BC\u00F7\x17\u00AD\u00B5\u00EF\u0080\x0B-\x046\u0080\u008F\u00D3\u00E9\u00F4r%P\u0096\u00A5\x07\u00BA\u00AAz\u00A0\u00AA7T\u00F5\x05p(\"1\u00C6\x18\u0081C\u00E0\u00A5\u00B5\u00B6\x0B\u00EC\x03\u00D7\u00D24}\x05\u00ABO\x04~\x03\u009Fb\u008C\u00DBEQ\u00FC\x00\x1E\u00D5\\\u00AF\u009E\x06\u00DE\u00FB\u00AD\x10\u00C2kU\u00FD\x0B\u00F0\x0Fk\u009B\u00A0\x10\u009B\u00CE\u00DB\u00A2\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_bug.png", "w16" );
w16_bug;
// ==================== |------------| ====================
// ==================== | w16_rookie | ====================
// ==================== |------------| ====================
var w16_rookie = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x00\u00F5IDAT8\u008D\u00DD\u00921N\x031\x10E\u00FF\u008C\u00B1\u00BD\x12\x1D\x14\u00D4Pp\x05n@\u00B4\u00ABu\u00EB\u00823\u00E4N\u00D0\u0081\\lOI.\u00C0\x01\u0090(\"%\r\x01\u00CA\u0095!\u008CiR$Y\u009Bl\u00CD/\u00FF\u00BC\u00F9\x1A\u00CD\f\u00A1\u00A0\u00AE\u00EBN\u0095R\u00CF\x0043_9\u00E7\u00E69\u008EK\x01UU1\x11\x1D\x03\u00B0\x00~J\\1\u00A0\u00AE\u00EB\u0095\u0088\u00CCRJOm\u00DB.K\x1C\u00E5\u00CC\x10\u00C2\u00891\u00E6\u009E\u0088\u00AE7\u00D6c\u008C\u00F1\u00C6{\u00FF1j\x02c\u00CCt\u00AB\x19\x00&\u00C6\u0098i\u008E=*L\u00F6\x0E\u00E03\u00E3\u008D\x0B\x10\u0091\x07\u00AD\u00F5BD4\x000\u00F3w\u00DF\u00F7\u00B3\x1C;\u00D8A\bAYk_\x00\u009C\u00EF\u0095^c\u008C\u0097\u00DE\u00FB\u009D\u008B\fv`\u00AD=\u00CB4\x03\u00C0\u00C5\u00A6\u00B6\u00A3A\x003g/S\u00AA\x15\u00FF`\u00AC\u00FEc\u0080R\u00EA\u00EB\x0F>\x1E\fh\u009A\u00E6-\u00A5t\x07`\u00BDe\u00AF\u0089\u00E8\u00D69\u00B7\u00DA\u00E7\x7F\x01\u008D\u00CA@c^Wf\u00EE\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_rookie.png", "w16" );
w16_rookie;
// ==================== |--------------| ====================
// ==================== | w16_standard | ====================
// ==================== |--------------| ====================
var w16_standard = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\x14IDAT8\u008D\u00AD\u0090\u00B1J\u00C4P\x10E\u00EF\r/E\x14\x11\u00EB\u00E0G\u00F8\x07j\u00A3\u00A4H\u00D2+X\u00A6\x10\x11\u00B5\u00B1L-\b\x16\"\x04\u00ACm\u0084%oI\u00E1\x0Fh\x15\u00F4\x03,l\u0084\u00D4\u00E2\u0082\x04\u0097\\\x1Bw\u00D9,\u00C8n\u00D4\u00A9f\x06\u00CE\u0099\u00CB\x00\x7F,\u008E\u009A,\u00CB\\\u00DF\u00F7Wg\x01M\u00D3\u00A8\u00AA\u00AA\u00D7$I>[\x02k\u00ED9\u0080\u00E39\x0F\u00D7\x00\u00AE]\u00D7=1\u00E3(\u00E4\u00A2\u00A4Y\u00A0$\u009D\x01X#\u00B9?\x1C\x0E\u00DF\u00CD,bZ\x10\u00C7\u00F1\u00A9$Zk\u00EF\x01\u00EC8\x1D\x05\u00A3\u00B4\"\u00F9\x01`\u00B9k\x02\u00E6y\u00BE\u00E58\u00CE\u00BA\u00A4M\u00927\u009D\x05$\u00EF\u00BE\x7F\u00F5@\u00F2p,\u00904\u0098C0\x00\u00B0\x07\u00E0%\u008A\u00A2'\x00\u0098\x14\u00F4H\x1E\x01\u00F8\u00F1/$o\u00C30\u00EC\u00B5v\u0093\u0083\u00B5v\x03\u00C0.\u0080\u00A5)V$\x1F\u008D1\x17A\x10\u00D4-A\u00BF\u00DF?\x00\u00B00G\u00FC\u00B6QR]\u00D7WFR\x00`\u00BB\u00AB\x00\u00C0\u00B3\u00E7y\u0097\x04\u0080\u00A2(V\u00BA\u00D2eY\u00BE\u00A5i\u00DA\u00FC\u00E2\u00F0?\u00D7\x17=\x05_\u00E7\u00F7_.`\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_standard.png", "w16" );
w16_standard;
// ==================== |------------| ====================
// ==================== | w16_expert | ====================
// ==================== |------------| ====================
var w16_expert = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07`\x00\x00\x07`\x01G\u0084ZR\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\x00IDAT8\u008D\u00C5\u0092\u00B1J\u00C4@\x14E\u00CF\x0B\u008B\u0084\u00FD\b;;k\u00F1\x03,l\x02CZ+\x0B+\x0B+E\u00D1\u00D2Fp\u00F1\x07\u00FC\x02\u00AB$*\u00C8\u00B6\u0082\u00B0`)h\u00A1\u009D\u009D\u00F5J\u009A\u00CC\u00B5I`v@6\u00B1\u00F1V\u00EF\u00DD\u00CB=\u00BC\u0081\u00B1\u00AA\u00AAf\u00926X\u00AEoI\u00DB\u00CE\u00B9\u00C7\u00D0Lz\u0096\x01\u00C6f\u00B6\x1E\u009BV\u0096\u00A5z\x02\x00\u00DE\u0080O\u00E0+I\u0092\u009B,\u00CB\u008A\x05\u0080\u00A4\u00A9\u0099%\u00C0\x160\x07\u00AE\u0081\u0083\u00DFhf\u00B6\u009FD\u00C6-p\u00D7\u00AE\u00EFi\u009A\u009E\u0086\u00B9\u00A4\u00A9\u00A4\x0B\u00E0\x19\u00C0{\x7F\u00B4\x00X&3+\u009Cs\u00C7\u00C0\u00A4\u00DDW\x07\x01\u0082K\u00BAg\u00DB\u009F\x00\u00A1\u00FE\x1D\u00A0\u00D1\u00C0\u00C2JQ\x14\u00BBfv\u00D2\u00EE\x1FC\x01\u0097f\u00D6u\u00BC\u00A4\u00B3\x18\u00B0\x03X;\u00AF\u00D5u}\x15\u00E5#@\u00C0\u0083\u00A4s\u00E7\u00DCS\f\u00D8\f\u00E61\u00B0\x17\u00E5\u00F7M\u00D3\x1C\u00E6y\u00FE\u00DA\x19C?R\x15\u0096;\u00C0\u00ACg\x7F\u00EE\u00BD\x7F\u0089\u00CD\x1FA\x0EY\u0090\u00AEi\u0085\u009B\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_expert.png", "w16" );
w16_expert;
// ==================== |-----------------| ====================
// ==================== | w16_placeholder | ====================
// ==================== |-----------------| ====================
var w16_placeholder = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x017IDAT8\u008D\u0095\u0093AN\x021\x14\u0086\u00BF\u00D7\u00E2\x19p\b\t;0\u009C\u00C0\u009D\u008A\x1E\u00C0\x13hf`H\u00E0B\u00930q\u00B8\u0088\u00A2\u00EE<\u0081\x11v$\x06\u00C2!\u00A6u\u00D3!\u00A5 \u0084\u00B7j\u00DA\u00FE_\u00FF\u00FF\u00BDT\u00FA\u00FD\u00FE\u00A3\u0088d\u00C0%\u00E7\u00D5\x1A\x18\u00D5\x02\u00F1/\u00B09!\u00AC\x03M \x02\u00B2Z\u00F0\u00F2EY\u0096O\u00D3\u00E9\u00F4\u00FB\u00902M\u00D3\u00B6\u00B5\u00F6\u00DD\u00DB\u008A\u0094[,\u009D\u00A5\u00BA\u00D6\u00FA5I\u0092N(\x1E\x0E\u0087W\u00D6\u00DA\x0F\u00A0\u00E1\u00EE.\x01*\u00C0ZDn\u0081\x15\x10)\u00A5>\u00E38\u00EE\u00FA/\x1Bc\u00DE\u009C\u00EDMY\u0096\x0F\x0E\u00B2\x05\u0090\u00E7\u00F9BD\u00EE\x1C\u00A4\u00AE\u00B5\u009E\u00C5q\u00DC\u00F5l7\u009C\u00B8\u00E7G\u00DC\x02*\u00881\u00A6\u00E7\u00C7\u00F1m\x1Bcn\u00C2\u00FE\u00EC\x00\x00\u008A\u00A2\u0098\u00FBq|\u00DBEQ\u00CC\u00C3\u00FB{\u0080sk\x0F\u0090$I'\u00E8\u00F6\u00D1\u00E9\u00EC\x00\u00D24m+\u00A5f\u00BE\u00EDc\u00D3\x01\u0090\u00C1``\u0081/\x11y\u00FE\u00AF\u00DB\u0087&\u00A1\u00B5~\x01\u00AE+\x07\u00D1\u00B1n\u00E7y\u00BEPJ\u00DD\u00FBq\u009C\u00CBm\u0084\x16'\u00BA=\u0099L~\u0082\u00E9\u00B4\u00AA\b\u00D5\x06\u009C\u00F7\u0099\x00V5`\x04d\x0E\u00D2\u00F4\x0EO\u00D5\x1A\x18\u00FF\x01 \x03\u00A8.\u00A9X\u00C8\t\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_placeholder.png", "w16" );
w16_placeholder;
// ==================== |--------------| ====================
// ==================== | w16_download | ====================
// ==================== |--------------| ====================
var w16_download = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\x0FIDAT8\u008D\u00DD\u0092=N\u00C30\x18@\u009FqEB\x15%\x0B\u00E2\x0E\u0094\x03\u00D0\u00D5\u00EA\x16\t\u0085\b)\x03;\u00A7\u00E2\x06\x11K\x12\x162\u00B1 \u00B8\x00\u00D9\u00B8\x00b.5\u0092m\u0096\x04\x15\u0093\u00A2\u00B0\u00F2-\u00DF\u008F\u00BE\u00F7lK\x16\u00EC\u0088\u00AA\u00AA\u009E\u0081E\u00DFvY\u0096\u009D\u008C\u00ED\u00ED\u00ED\x12l\u00C1~=Y0)\u00FE\u0081@\fE\u00D34+km;Aj\u00AD\u00B5\u00AB<\u00CF\u00EF\u00BF\u00DD \u008A\u00A2\x07\u00A0\u009Dph\x1B\x04\u00C1\u00E3\u00D0|\t\u0094R\x1B\u00ADu\x06\u00DC\u00FE\x02\u00DF\u00C5q\u009C\u00A7i\u00AA\x7F\b\x00\u008A\u00A2\u00F8\u00D0Z_\b!\x1A\u009Ft\u00CE\u00B5q\x1C\u009F+\u00A56\u00DBs\u00E1/\x02\u0094e\u00B9\x1F\u0086\u00E1\u008Ds\u00EEl\u0080\u0093$\u00C9|\x18@\u00D4u\u00DD9\u00E7\u008E\u00BD\u00F9\u00931&\u0093R^\x03\x18c\u00AE\u00A4\u0094\x15\u00B0\u00F4\u00F6\u00BA\u00D9\b\f\u00B0\u0094R\u00D6R\u00CA\u00CB\u00BE\x1F\u0083\x01\x16\u00B3\u00B1'\u00F4qj\u008Cy\u00F1f\u00EF\u00C0\u00C1V\u00FE\u00D3GZk\u00AD\u008F\u0080\u00B7>\u00AF\x07A7Q0\x0F\u0082\u00E0\x158\u00EC\u00F3\x1C\u00E8>\x01\u00D4u]l6\u00FC\u00EA\u00B8\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_download.png", "w16" );
w16_download;
// ==================== |----------| ====================
// ==================== | w16_list | ====================
// ==================== |----------| ====================
var w16_list = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01NIDAT8\u008D\u00AD\u0092\u00BDJ\x03Q\x10\u0085\u00BF\u00B9\u00DC]\x05+\u00FF@l\x04Q\x1B\u00F5\x01\x04kAp\t\u00C1*h\u0099G\b\u00A6\u00B1\x13\u00C1\u00C62/`%\u00C1\u00E2&&El\u00D2\u00D9\u00F8\x12\u0082\u0088X\x05\u0092&\u00D9\u00DD\u00BBc\u0093\u0088V\u00BB\u00A0\u00A7\x1A\u00CEp\u00E6\u009C\x19\x06\u00FE\b\u00E9t:{\u00DE\u00FBk`\u009CeY\u00BD\\.\u00BFv\u00BB\u00DD\u008D8\u008EW\u00F2\u00C4A\x10\f\u00AC\u00F7\u00FE\x1E\u00D8\x050\u00C6,\x02Gi\u009A>\x1Bc\u00D6\u00F3\x06x\u00EF\u00C7\x06X\u00FB\u00C1\u00CD\u00EA\u00B9\u0082\x1B\u00CC[\x11\u00A9\u00ABj\x03H\u0080K\x00\x11\u00A9gY\u00B6\u009D\u00A76\u00C6\u00BC\x03\u00D0l6\u0097z\u00BD\u00DEBA\u00D7_\x10\u00E7\\UD\x1A@b\u008C9\u008F\u00A2\u00C89\u00E7\u00AA\u00C0V\u0091\x04VDn\u0080\x10\b\u00BD\u00F7W\u0080\u009Br\u00CBy\x03T\x15\u00A3\u00AA\x1F\u00DFqDf\u00F5\u00A4\u00E0\x06ck\u00AD\u00AD\u00CC\u00FE\u00C0Z{1\u008Dv\u0090\u00A6\u00E9j\u009E:\b\u0082\u0081\u00F4\u00FB};\x1A\u008DJ\u00C0$\u008A\u00A2\u00AE\u0088hAw\x00\u00ECp8\u00BC\x03*\x00\u00EDv\u00FB\x16\u00A8\u00B5Z-\x07\u00EC\x17\u00D0\u00BFY\u00E0\u00E4\x07\x11\x015\u00E0\u0090\x02G\x046\u00AD\u00AA>\u0089\u00C8)\u0080\u00AA\u00F6\u00A6\u008D\x17`'O\u00AD\u00AA\u009F6\f\u00C3\u00B3$I\x1EUu\x1C\u00C7\u00F1\x03@\u00A9T:.\u00E0\u00FE?\u00F8\x023\u009D}\u00CA\u0097\u009C\u00BE\u00A0\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_list.png", "w16" );
w16_list;
// ==================== |-------------------| ====================
// ==================== | w16_language_file | ====================
// ==================== |-------------------| ====================
var w16_language_file = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\u00AFIDAT8\u008D\u008D\u0093\u00BF\u008B\x13A\x1C\u00C5\u00DF\u00F7\u009B%\u00B9\u00B5H\x1A\x11\u009B\x13\x7F\x1D\u008A\u008D\u00E4\u00AE\x11\u00F4\bil\u00CE\u00CCv\u00F1/\u00B0\u00B0VKq\u00AD\u00AE\x10\x15\x1B\u00FD\x03\u00EC<%\u00CC\u00AE\u008D\u00A0\x06\x0B\x11\u00AC-\x14\u00C1\x1F\u00E0\u00C1q)\u0084\u0080\u00C9\u00C6]\u00E6Y\x18a]\u00C7\u00E8\u00EBf\u00DE\u00F7}f\u00DE\u00C0\b\x00\u00C4q\u00AC\u00EDv\u00FB-\u0080\x15xd\u008C\u00D1$I\u00DE\u0088\u00C89c\u00CC\u00C7\u00B2\u00A7\x00\u00D0\u00E9t\u00EA\x7F\x0B\u0097t\u008C\u00E4\u008B\u00C1`p\u00E4\x0F@E\u00DF\x00\u00DC\x04\u00F0\u00D4\u00E3-\u00AB\u00EA\u00F3$I\x0E-\x02<4\u00C6\\q\u00CE]\x00@\u008F\x7F\u0080\u00E4\u00938\u008E\x15\x00\x04\x00\u0086\u00C3\u00E1\u00D2x<\u009E\x02\x00\u00C9\rUUc\u00CCck\u00ED+\x00\u00A7\x00\x1C\u008F\u00A2\u00E8\u009D\u00AFW\x15\u00F0u6\u009B\u00EDo4\x1A\u00F7I^W\u00D5\u00B3$o\u00FB\u0082\u00CDf3\u00ECv\u00BB\u00D9o\x15D\u00E4Q\u00BF\u00DF\u00FF^\x14\u00C5\u00A50\f?\u008B\u00C8\x16\x00\u00E7\x03x\u00DF\u0080\u00E4\u00834MW\u0083 \u00B85\u009DN\u00AF\u00F6z\u00BDm\x00/\x17\x01\u0082\n`\u00CD9w\x06\u00C0\u0086\u0088\u00E4\u00D6\u00DA/\u00F3C\u00EE\x00X\x05\u00B0\u00BE\x10 \"\u009B\u00A5\u00E5.\u00C9\u00F7\x00\u00E2z\u00BD\u00FE:\u00CF\u00F3}$\x0F\u008A\u00C8\n\u0080\u00BB\u00DE\n\x155\x00\u00B4Uu\u00AD(\u008A\u008B\x00NDQ\u00F4\u008C\u00E4\u00E1\u00F2\u00D0\"\u00C0^\x11\u00D9$\u0099\u0091\fk\u00B5\u00DA\u00874M\u008D\u0088\\\u00FE_\u00C0\u00AFZ'\u00A3(\u008A'\u0093\u00C9'\u0092\u00F7\u00AA\u00FE\u00BF\x00\u0099s\u00EE\u009A\u00B5\u00F6h\u00AB\u00D5\u00A2\u0088\u00C4\u00D5\u0081\u00C0\x13*kIUo\u0090\u00ECeY\u00B6\r`\u00D7{\u0083\u00D1h\u0094\x03\u00D8\u00F1\x11H\u009E\x07\u00B0\x07?\x7F\u00EB\u00E9\u00F9\u00F6\u00CE<\u0083\x1F\r\u0084\u00AE\u00AD\u00A7\u008B\x19\u00DD\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_language_file.png", "w16" );
w16_language_file;
// ==================== |------------| ====================
// ==================== | w16_random | ====================
// ==================== |------------| ====================
var w16_random = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01\u00B1IDAT8\u008D\u00BD\u0093?kTA\x14\u00C5\x7Fwf]\u00FF\u00B4\",\u00C1\u00D6\u00C6\u00C6\"_ B\u008A\u0080\u00FB\u00DE3\u00AC\u00BER,\u00D4-$\x16b\u0093\u00A0\u008DH\u00CA\u00A06\u00B2\u008D\u00A0M\u00C2\x12x\u00CC[XP\u00B0\u00B4\x10R\u0089\x16\u00B6\"\u00FA\x05B\u00C2\u00DBq\u00E6Z\u00E4\x05w\u0097]D\x0BOw\u00EF\u009C{\u00EF\u0099;g\u00E0\x7F\u00A3,\u00CB;\u00CE\u00B9\u00F5\u00E3\u00D8\u00FCC\u008F\u0093\u00C0\u00A6s\u00EE\x11\u00808\u00E7*\u00A0\t\x1C\x02\x1FE\u00E4Y\u009A\u00A6\u00DB\x00\u00C3\u00E1\u00F0\u009C\u00F7\u00FE1\u0090\x00\x0B\u0080L5\u00DB\x10\u00E7\u009C\u00CE\u0098\u00F2\u00C4Z\u00BB\x15B\u00F8\x00\\\u0098'EU\u00DF6b\u008C\u0097E\u00A4\x01\u009C\x15\u0091[\u00C0\u00B2\u0088\\\n!<\u00AF\u008B\x0F\u0081M`OU\x7F\u008A\u00C8U\u00E0\u00AE\u00AA\u00BE\u00B3\u00D6\u00AENH\u00EA\u00F5z'Z\u00AD\u00D6{\u00E0%\u00F0\u00A2N\u00DF\u00CC\u00B2\u00EC\u00D51\u00A7,\u00CB\u00B5\x18cf\u00ADM\u0093$9\u0098Xb\u00B7\u00DB\u00F5\u00C6\u0098UU]\u00AES\u009F\u00D34}=\u00CE\u00F1\u00DE\u00EF\u008EF\u00A3$I\u0092\x03\u0080\u00C6\u00F4\u00BDb\u008C\u00A7k\u0099\u00A8\u00EAS\x11\u0099\u00D8Q\u00A7\u00D3\u00F91\x1E\u00CFz\u00C6\u0087\u0080\x05\u00BE\x01\x0BEQ,\u008E\x1F:\u00E7n\u00CF\u00F4\u00C1`08\u00E3\u009C\u00BB\x0F\u00DC\u00A8\u00A7o\u0089\u00C8=c\u00CCvQ\x14\u008B\u00AA*\x00\"r\u008A#\x1F\u00AC\u00C3\u00A4\x0F\u00C6\u00F1\u00C6Z\u00FB \u0084\u00B0\u00C7\u0091q\u00E6a\u00C3L\x15G`\x07\u00B8\u00DEn\u00B7?\u00A9j\x1B\u00F8:\u00AFZU\u0097\u00A4(\u008A%\x11i\u0088H\x00\u00BEdY\u00F6}\u009C\u00D4\u00EF\u00F7m\u00B3\u00D9\u00BCh\u008C9\x0FXU]\u00E1\u00B7\x0F\u00D2ik\u00FE\x11eY\u00AE\u00A9\u00EA\u00B5\u00AA\u00AA\u00AE\u00E4y\u00BE\u00FF\u00D7\u009F\u00C9{\u00BF[U\u00D5J\u009E\u00E7\u00FB\x00\u00BF\x00\u0081\u00B4\u00AF\u008D(\u008C\x1F\u00E1\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_random.png", "w16" );
w16_random;
// ==================== |-----------------| ====================
// ==================== | w16_eye_dropper | ====================
// ==================== |-----------------| ====================
var w16_eye_dropper = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\u00F3\u00FFa\x00\x00\x00\tpHYs\x00\x00\x07a\x00\x00\x07a\x01\u0095\u00C3\u00B8\u00B6\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x01[IDAT8\u008D\u0095\u0091/L\u00C3@\x14\u00C6\u00BF\u00F7h:\u00E4\x04\x02\u008D%\u0081\u00D4\x132\x10\u00A0\u00BAsud\u00B8\u008AI23B2\u00C6\x02\t\x02\u008B \x19\n\x1C\u00A2\u0095\u0090\u00B0`\x118\u00D4\fS+\tf\u008A\\X\u00FA0W\u00D2\u0094]\x19\u009F\u00B9{\x7F~\u00DF\u00BB?\u008495\x18\f\x16'\u0093\u00C9\t\x11\u00ED\x01H\u0089\u00A8?\x1E\u008F\u008F\u00E9\x1FpDD;\u00F9<\x11u\u00AD\x06\u009DN\u0087=\u00CF\u00DB\u00D0Z\u00BF\u00B8\u00AE{W\u0084\x01@DFN\t\u00DC\x07\u00D0\u00A8T*\x1F\x00\u0096l\u0083xV\u00D2\u00F3\u00BC}\x00\r\x13Za\"\u00BA\u009Ei \"\x0F\"2\u00B2\u0081FQ\u0092$\u00A73\r\u0098y\u009D\u0099\u00B7JL\"\u00ADu\x10\u0086\u00E1\u00D7B\u00B1\x12\u00C7qOD.\x018\u00CC\x1C\x02P\x00\u00AA\u00B9\u0096Dk\u00BD\x19\x04\u0081\x06\no`\u00E0C\x136E\u00E4\u0080\u0088\u00B6\x01\u00BC\u00E5\u00DA\u0096]\u00D7]\u00CB\u0082\u009F_\u0088\u00A2\u00A8\u009B\u008335E\u00C4u\x1C\u00A76\u009DN\x1F\x01\u00AC\x00\u00B8RJ=g\rd\u00E0\x16\u0080s\u00CB}\u00C1\u00CC\u00BB\x00^\u00D34\u00AD\u00D5\u00EB\u00F5[\"\u0092\u00E2\t\u00DA6\x18@\u00DB\u00F7\u00FD{\u00B3\u00BF\u00F9en\u00D6j\u00B1`t\u00A4\u0094:+1\x07\u00C7q\u00BCj\u009B\u00AC\u0094\u00EA\u0095\u00C1\x00\u00C0i\u009A^\x14r\u00EFD\u00D4\u00FCkr&\u0087\u0099\u009FD\u00E4\u0093\u0099[\u00BE\u00EF\x0F\u00E7\u0081\u00F2\u00FA\x06n\n\u0081\u00C01\x12\x16f\x00\x00\x00\x00IEND\u00AEB`\u0082", "w16_eye_dropper.png", "w16" );
w16_eye_dropper;
// huge
// ==================== |---------------| ====================
// ==================== | w320_progress | ====================
// ==================== |---------------| ====================
var w320_progress = new DuBinary( "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x01@\x00\x00\x00\f\b\x06\x00\x00\x00\x13}d\t\x00\x00\x00\tpHYs\x00\x00\x0E\u00C3\x00\x00\x0E\u00C3\x01\u00C7o\u00A8d\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\u009B\u00EE<\x1A\x00\x00\x04GIDATh\u0081\u00ED\u00DBQ\u0088TU\x1C\u00C7\u00F1\u00EF\u00FF\u00EC\u00E4\u00D8\u00AEhnV\u0096\x05E\x05\x15FB\x11\u0088\u00A2\u00AD\u00B0\u00A0ms\u00CFL\x1B\u00EB\u00FAf`ld&\x04B\u008B\u00F8\x12eBH\u00E1\u0083\u0094`\x10&\u00C9\u00E2\u00CC\u00EC\u00DD\u00BB\u00AEm`Z\b\x06>\b\u00F5\u0092\u00A4$\x158[`\u00B9-8\u00B1s\u00CF\u00BF\u0087\u0099\u0085\u00C9v\u00B7\u00BD\u00A3/M\u00E7\u00F3t\u00EF\u00B9g~\u00FC\u00EF\u00CB\u009Fs\u00E7\u009E+4 \u008A\u00A2%\x0B\x16,\u0098\u0098\u0098\u00980\u00CE\u00B9\u00BD\u00C6\u0098\u00A3\u0099L\u00E6D#YCCC\u009B\u00CB\u00E5r\u00B1\u00A7\u00A7\u00E7*\u00C0\u00E8\u00E8h[\u00B9\\\u00CE\x03-\u00C0\u00F3\u00D6\u00DA?\x1A\u00C9\u00F5<\u00AF9\u00E4\u00F3\u00F9\u00BB\u00BB\u00BB\u00BB/\x03\u0084a\u00F8\u00A9s\u00EE\u00C3\\.w\u00EAfdK\u00D2\x1F\f\f\f\u00DC\u009AN\u00A7\u00F7Zk_\u00A9\x15\u00F4\x10pZDN\u00C5q\u00FCr.\u0097\u00FB}.9\u00AA*a\x18n\x12\u0091\u00F7\u00813\u00CE\u00B9\u00D7[[[K\u00E5ry\x04x\x18p\u00C0%`\u0083o\u0082\u009E\u00F7\u00FFt\u00EC\u00D8\u00B1\u00A5\u0095J\u00E5\x02\u00B0\x02x\x00\u00F8\\D\u00F6\x05A\u00B0\u00BD\u00D6Cz\u008C1\u00F7\u00D7\u00A6\x7F\x13\x04\u00C1\u00F1$\u00F9&iA\u00E9t\u00BA_U\u00BF\u009F:\u00B7\u00D6^\x10\u0091\u00B7Uu\u00A31\u00E6d\u00A1P\u00B8}.9\"\u00A2\"\u00F23pMD~I\u00A5R\u0093S\u00CD\u00CF\x18\u00B3.\u008E\u00E3uTozo\u00D2\x1A=\u00CFk\x0Eq\x1Cw\x02m\u00C0A\u00A0\x02\u008C\u00A9\u00EA\u008Ba\x18\u00DE\x13EQ\u00BF\u0088\x1CQ\u00D5=\u00AA\u00BA'\u008E\u00E3kI\u00F3\x137@\u00E0\u00A5\u0096\u0096\u0096\u00B3\u00F5\x03\u00CE\u00B9\u00B6\u00DA\u00E1\n\u00E0\u00CE\u00B9\x06Yk\u00BF\x12\u0091\u00AD\"\u00D2\u00EF\u009C;\x04\u00AC1\u00C6\u00F4d2\u0099\u00EFZZZ\u00FA\u0080y\"2\u00D4@\u008D\u009E\u00E75\x01\u00E7\u00DC\u00B2\u00DA\u00E1\x1A\u00E0M\u00A0\x1B\u00F8@U\u009Fu\u00CE\u00AD\x03~\x02\u008E\x034\u00F2X\u009C\u00A8\x01\u0086ax\x17\u00B0\x14\x18\u00AB\x1B[/\"\u00BBk\u00A7\u009AJ\u00A5\x0E\u0085a\u00F8\u00E5\\W\u0082\u00E9t\u00FA\u00A4s\u00EE(\u00D5\x1B\u00C49\u00B7?\f\u00C3\u00FD\u00C06\u00A07\b\u0082\u00E1$5z\u009E\u00D7<\u008C1W\u00EANW\x03_\u00A8\u00EA\x1D@,\"K\u0080\u00FB\u0080\r\r\u00E7'\u0099\x1C\u00C7\u00B1\x02\u00A8\u00EAc\x00\u0083\u0083\u0083[Dd\u00A7\u00AA~\"\"+\u0081\u00D7T\u00F5I\u00AA+\u00B9\x1D\u00FF\u0096\x17EQk\u00B9\\\u008E\u0080\u00B5u\u00C3\u008F\x03[Tu\u00A3\u00B5\u00B6\u0090\u00A4>\u00CF\u00F3\u009AK\x1C\u00C7'\x00\u00AD\x1B\u009A'\"\u009BE\u00E4#\u00E0\u0089\x1B\u00CDO\u00D4\x00s\u00B9\u00DC\u00AF\u00C0%U\u00DD\x1EE\u00D1\u00D3\"r@U\u0097\x03\u00EF\x06A\u00F05\u00F0[\u00AD\u00D8\u00C2\u00D8\u00D8\u00D8\u00AE\u00D9\u00B2FGG\u00DB\u009Cs#@\u00C7u\u0097&\u0081\u00DEE\u008B\x16%\u00FA3\u00D3\u00F3\u00BC\u00E6\u0093\u00CB\u00E5.\x02\u00C59L\u00D5\x7F\u009F\u00F2O\u0089\x1A\u00A0\u0088(\u00F01\u00D5\u00A6\u00F5\x02\u00D5g\u00F2\u00CEl6\u00FB-\u0080\u00B5\u00F6\u00B0\u00AA>W*\u0095z\u00FB\u00FA\u00FA&g\u00CA\u0099a\u00E5\x0709\u00B5\u00F2\x1B\x1F\x1F\x7F\u00A3X,>\u0093\u00A4>\u00CF\u00F3\u009AO*\u0095\u00DAJuG\u00C8l.7\u0092\u009D\u00F8%H*\u0095\u00CA\x03\u00E7\u009Ds;\u0080\u00A7T\u00F5\u00CF\u00FA\u00EB\u00D9lvd\u00B6\u00E67000o\u00B6\u0095_6\u009B-\u0086a\u00B8\x0B\u00D8i\u008C)\u0086a\u00B8>i\u008D\u009E\u00E75\u008F\u00AE\u00AE\u00AE\x12\u00D0I\u00DD\u00BB\u0087i\x1Ch$;\u0095d\u00F2\u00E0\u00E0`_\u00A5R\u00D9'\"[\u009CsVD\u00BAE\u00A4+\f\u00C3\u008B\u00C0\x0F\u00B5i\x07\u00AD\u00B5Gf\u00CA\u0098?\x7F\u00FE2U]u\u00DD\u00F0$\u00D0;\u00F5\u009F\u00DF\u00C2\u0085\x0B\u00DF\x19\x1F\x1F_.\"\u00A7\u0083 \u00F8,I\u008D\u009E\u00E75\u0087\u0091\u0091\u0091\u0085\u0095J\u00E5U\u00E7\u00DCZ`\x15\u00D5\u00ED0\u00D3\x19(\u0095J\u00BBg\u00B86\u00AB9o\u0084.\x16\u008B\u00B7\x19c\u00CES\u00FDB\u00A3\u00D3Z{\u00AEP(<j\u008C\u00B1\"\u00B2RDV\u00ABj;0\u00E6\u009C{d\u00B6\r\u00D1CCC=\u00AAz\u0098j\x03\u00FE[\u00F3\u009B\u0092\u00CF\u00E7\u00EFmoo/uttT\x1A\u00B91\u00CF\u00F3\u00FE\u00DB\u0086\u0087\u0087\x17\u00C7q|\x16x\u00B0n\u00D8\x01\x07U\u00F5\u008A\u0088\\u\u00CE\u009D\u00B9\u0091\u00AFB\x12}\t2<<\u00BC<\u008E\u00E3[\u00AC\u00B5\u00E7\u00A6+\u00B6R\u00A9\u00BC%\"+\u008C1\u009B2\u0099\u00CC\u008F\u00B3e\u00D5\u009A\u00E0{\u00C06\u00FF\u00B6\u00D7\u00F3\u00BC\u00E9DQ\u00B4\u00C49\u00D7\x0Fd\u0080\u00C5@\u00DF\u00CD\u00EC\x17\x7F\x01\u00D0\u00BB\u00C9\u0082\u0095bR\u00F1\x00\x00\x00\x00IEND\u00AEB`\u0082", "w320_progress.png", "w320" );
w320_progress;
/**
* The list of available icons.
* @enum {DuBinary}
* @static
* @memberof DuScriptUI
*/
DuScriptUI.Icon = {
ADD: w12_add,
AE_BLUE: w8_ae_blue,
AE_ORANGE: w8_ae_orange,
BACK: w12_back,
BOX_CHECKED: w12_box_checked,
BOX_UNCHECKED: w12_box_unchecked,
BUG: w16_bug,
BUG_REPORT: w12_bugreport,
CHECK: w12_check,
CLOSE: w12_close,
DOWNLOAD: w16_download,
EDIT: w12_edit,
EXPERT: w16_expert,
EYE_DROPPER: w12_eye_dropper,
EYE_DROPPER_BIG: w16_eye_dropper,
FEATURE: w12_feature,
FILE: w12_file,
FOLDER: w12_folder,
FOLDER_CLOSED: w12_folder_closed,
GO_TO: w12_goto,
HEART: w12_heart,
HELP: w12_help,
LANGUAGE: w16_language,
LANGUAGE_FILE: w16_language_file,
LANGUAGE_SMALL: w12_language,
LIST: w16_list,
MENU: w12_menu,
MORE: w12_more,
NEXT: w12_next,
OPTIONS: w12_options,
PARENT: w12_parent,
PIN: w12_pin,
PINNED: w12_pinned,
PLACEHOLDER: w16_placeholder,
PROGRESS: w320_progress,
RAINBOX_RED: w8_rx_red,
RANDOM: w16_random,
REMOVE: w12_remove,
RESET: w12_reset,
ROOKIE: w16_rookie,
RUN: w12_run,
RX_PURPLE: w8_rx_purple,
SETTINGS: w12_settings,
SETTINGS_FILE: w16_settings_file,
STANDARD: w16_standard,
UPDATE: w12_update,
USER: w16_user,
SEARCH: w12_search,
SORT: w12_sort,
SORT_UP: w12_sort_up,
SORT_DOWN: w12_sort_down,
};
// ==================== |----------| ====================
// ==================== | titleBar | ====================
// ==================== |----------| ====================
/**
* @class
* @name DuTitleBar
* @classdesc For use with {@link DuScriptUI}.<br />
* A title bar.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.titleBar} to create title bar.<br />
* The itle bar inherits the <code>Group</code> object from ScriptUI and has all of its properties and methods.
* @property {Boolean} pinned True when the title bar has been pinned and the corresponding panel should not be hidden.
* @property {DuTitleBar~onClose} onClose - The function to execute when the close button is clicked
* @property {DuTitleBar~onPin} onPin - The function to execute when the pin button is clicked
* @category DuScriptUI
*/
/**
* The function to execute when the close button is clicked
* @callback DuTitleBar~onClose
* @memberof DuTitleBar
*/
/**
* The function to execute when the pin button is clicked
* @callback DuTitleBar~onPin
* @param {Boolean} pinned Wether the bar has been pin or not
* @memberof DuTitleBar
*/
/**
* Creates a titlebar
* @param {Panel|Window|Group} container The ScriptUI Object which will contain and display the titlebar.
* @param {string} [title=""] The title.
* @param {Boolean} [closeButton=true] Wether to add a close button
* @param {Boolean} [pinButton=true] Wether to add a pin button
* @returns {DuTitleBar} The titlebar
*/
DuScriptUI.titleBar = function( container, title, closeButton, pinButton ) {
// Defaults
title = def(title, "");
closeButton = def(closeButton, true);
pinButton = def(pinButton, true);
// Creates the title bar
var titleBar = container.add( 'group' );
titleBar.margins = 0;
titleBar.spacing = 0;
titleBar.orientation = 'row';
titleBar.alignment = ['fill','top'];
DuScriptUI.setBackgroundColor( titleBar, DuColor.Color.DARK_GREY );
titleBar.onClose = function() {};
titleBar.onPin = function(p) {};
// Pin button
if(pinButton) {
titleBar.pinButton = DuScriptUI.checkBox(titleBar, '', w12_pin, i18n._("Keep this panel open"), '', w12_pinned);
titleBar.pinButton.alignment = [ 'left', 'center' ];
titleBar.pinButton.onClick = function() {
titleBar.pinned = titleBar.pinButton.checked;
titleBar.onPin(titleBar.pinned);
};
}
// Title
if (title != "") {
titleBar.titleLabel = titleBar.add('statictext', undefined, title);
titleBar.titleLabel.alignment = [ 'center', 'center' ];
}
// Close
if (closeButton) {
titleBar.closeButton = DuScriptUI.button(titleBar, '', w12_close, 'Close');
titleBar.closeButton.alignment = [ 'right', 'center' ];
titleBar.closeButton.onClick = function() {
titleBar.onClose();
};
}
return titleBar;
}
// ==================== |-------| ====================
// ==================== | panel | ====================
// ==================== |-------| ====================
/**
* @class
* @name DuPanel
* @classdesc For use with {@link DuScriptUI}.<br />
* A Panel or Window.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.mainPanel} to create a Panel.<br />
* The DuPanel inherits the Panel or Window object from ScriptUI and has all of its properties and methods.
* @category DuScriptUI
*/
/**
* The ScriptUI group where the new controls must be added. Do not add any control directly inside the DuPanel.
* @name content
* @type {Group}
* @memberof DuPanel.prototype
*/
/**
* A function to reload a script in this panel.
* @method refreshUi
* @param {File} file - The script to reload.
* @return {boolean} true on success, false otherwise.
* @memberof DuPanel.prototype
*/
/**
* Creates the main panel for a script
* @param {Panel|null} container - The container ('this' in the root of the calling script), either a Panel (when launched from the 'Window' menu) or null (when launched from 'file/scripts/run...')
* @param {string} [scriptName=DuESF.scriptName] - A name for this UI
* @param {string[]} [contentAlignment=DuScriptUI.defaultColumnAlignment] - The alignment of the content in the panel
* @param {string} [borderless=false] - When true, creates a borderless window if container is not a panel
* @return {DuPanel} The panel created, either a ScriptUI Panel or a ScriptUI Window.
* @example
* var ui = DuScriptUI.mainPanel(this,"Test Script");
* var refreshButton = ui.content.add('button',undefined,"Refresh");
* refreshButton.onClick = function() { ui.refreshUI( new File($.fileName) ); }; //reloads the current script
* DuScriptUI.showUI(ui);
*/
DuScriptUI.mainPanel = function( container, scriptName, contentAlignment, bless )
{
scriptName = def(scriptName, DuESF.scriptName, false);
contentAlignment = def(contentAlignment, DuScriptUI.defaultColumnAlignment, false);
bless = def (bless, false);
var myPal = null;
var winType = 'palette';
if (DuESF.host == DuESF.HostApplication.PHOTOSHOP)
{
bless = false;
winType = 'dialog';
}
container instanceof Panel ? myPal = container : myPal = new Window( winType, scriptName, undefined,
{
resizeable: true,
borderless: bless
} );
if ( myPal == null ) throw "Failed to create User Interface.";
myPal.margins = 0;
myPal.spacing = 0;
//create margins to dim buttons
var topMargin = myPal.add( 'button' );
topMargin.onDraw = function() {};
topMargin.minimumSize.height = DuScriptUI.defaultMargins;
topMargin.alignment = [ 'fill', 'top' ];
topMargin.maximumSize.height = topMargin.minimumSize.height = topMargin.preferredSize.height = DuScriptUI.defaultMargins;
var mainRow = myPal.add( 'group' );
mainRow.alignment = contentAlignment;
mainRow.alignChildren = contentAlignment;
mainRow.margins = 0;
mainRow.spacing = 0;
var leftMargin = mainRow.add( 'button' );
leftMargin.onDraw = function() {};
leftMargin.maximumSize.width = leftMargin.minimumSize.width = leftMargin.preferredSize.width = DuScriptUI.defaultMargins;
leftMargin.alignment = [ 'left', 'fill' ];
myPal.content = mainRow.add( 'group' );
myPal.content.margins = 0;
myPal.content.spacing = DuScriptUI.defaultSpacing;
myPal.content.alignChildren = DuScriptUI.defaultColumnAlignment;
myPal.content.orientation = "column";
myPal.content.alignment = ['fill', 'fill'];
var rightMargin = mainRow.add( 'button' );
rightMargin.onDraw = function() {};
rightMargin.maximumSize.width = rightMargin.minimumSize.width = rightMargin.preferredSize.width = DuScriptUI.defaultMargins;
rightMargin.alignment = [ 'right', 'fill' ];
var bottomMargin = myPal.add( 'button' );
bottomMargin.onDraw = function() {};
bottomMargin.maximumSize.height = bottomMargin.minimumSize.height = bottomMargin.preferredSize.height = DuScriptUI.defaultMargins;
bottomMargin.alignment = [ 'fill', 'bottom' ];
myPal.addEventListener( "mouseover", DuScriptUI.dimControls );
topMargin.addEventListener( "mouseover", DuScriptUI.dimControls );
leftMargin.addEventListener( "mouseover", DuScriptUI.dimControls );
rightMargin.addEventListener( "mouseover", DuScriptUI.dimControls );
bottomMargin.addEventListener( "mouseover", DuScriptUI.dimControls );
//*/
return myPal;
}
/**
* @class
* @name DuPopup
* @classdesc For use with {@link DuScriptUI}.<br />
* A borderless popup, to be tied to a ui control.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.popUp} to create a Popup.<br />
* The DuPopup inherits the Window object from ScriptUI and has all of its properties and methods.
* @property {Group} content - The ScriptUI group where the new controls must be added. Do not add any control directly inside the DuPopup.
* @property {boolean} pinned - true if this popup is "pinned", which means it won't hide when the user clicks outside of the window.
* @property {DuPopup~build} build - You can use this callback to add a function which builds the UI of the popup, it will be called on first display.
* @category DuScriptUI
*/
/**
* You can use this callback to add a function which builds the UI of the popup, it will be called on first display.<br />
* This allows a faster startup of your script by delaying the creation of the tabs which are hidden at startup.<br />
* You can add controls in the <code>this.content</code> object.
* @callback DuPopup~build
* @memberof DuPopup
*/
/**
* Ties the popup to a ui control. The popup will be shown just above the control when it is clicked.<br />
* The control must have an addEventListener method.
* @method
* @memberof DuPopup
* @name tieTo
* @param {ScriptUI} [control] - The control
* @param {Boolean} [onShift=false] - If set to true, the popup is tied on Shift + Click only
* @param {boolean} [alwaysBlock=false] If true, the popup will never be automatically shown. Call show() to show it.
*/
/**
* Pins the popup (it won't be hidden anymore when deactivated).
* @method
* @memberof DuPopup
* @name pin
* @param {boolean} [pinned=true] - true to pin the popup, false to un-pin it.
*/
/**
* Hides the popup.
* @method
* @memberof DuPopup
* @name hidePopup
*/
/**
* Hides the popup (alias for {@link DuPopup.hidePopup}).
* @alias DuPopup.hidePopup
* @method
* @memberof DuPopup
* @name cancel
*/
/**
* Sets this parameter to <code>true</code> to prevent the next show of the popup.<br />
* This will prevent it from showing once (and only once).
* @type {Boolean}
* @memberof DuPopup
* @name block
* @default false
*/
/**
* Creates a borderless popup
* @param {string} title The title of the popup
* @param {string[]} [alignment=[ "fill", "top" ]] The alignement of this window
* @param {boolean} [modal=false] Set the popup to a modal dialog
* @return {DuPopup} The popup, a ScriptUI Window which is borderless, with a 'tieTo(control)' method.
* @example
* var popup = DuScriptUI.popUp( );
* var popupButton = DuScriptUI.button( myUI, "My Buttton for the popup" );
* popup.tieTo( popupButton ); // will show the popup when the button is clicked, just above it.
*/
DuScriptUI.popUp = function( title, alignment, modal )
{
modal = def(modal, false);
alignment = def( alignment, [ "fill", "top" ] );
title = def (title, "");
var type = 'palette';
if (modal) type = 'dialog';
var popup = new Window( type, title, undefined,
{
resizeable: false,
borderless: true
} );
DuScriptUI.setBackgroundColor(popup, DuColor.Color.OBSIDIAN);
popup.margins = 0;
popup.spacing = 0;
popup.pinned = false;
//add stroke around
function drawBorder ( drawState ) {
/*var g = this.graphics;
var c = DuColor.Color.DARK_GREY;
var pen = g.newPen( g.PenType.SOLID_COLOR, c, 1 );
g.newPath();
if (this.side == 0) {
g.moveTo (0, 0);
g.lineTo (this.size.width, 0);
}
else if (this.side == 2) {
g.moveTo (this.size.width, 0);
g.lineTo (this.size.width, this.size.height);
}
else if (this.side == 1) {
g.moveTo (0, 0);
g.lineTo (0, this.size.height);
}
else if (this.side == 3) {
g.moveTo (0, this.size.height);
g.lineTo (this.size.width, this.size.height);
}
g.strokePath( pen );*/
};
//create margins to dim buttons
var topMargin = popup.add( 'button' );
topMargin.onDraw = drawBorder;
topMargin.side = 0;
topMargin.minimumSize.height = DuScriptUI.defaultMargins;
topMargin.alignment = [ 'fill', 'top' ];
topMargin.maximumSize.height = topMargin.minimumSize.height = topMargin.preferredSize.height = 1;
var mainRow = popup.add( 'group' );
mainRow.alignment = alignment;
mainRow.alignChildren = alignment;
mainRow.margins = 0;
mainRow.spacing = 0;
var leftMargin = mainRow.add( 'button' );
leftMargin.onDraw = drawBorder;
leftMargin.side = 1;
leftMargin.maximumSize.width = leftMargin.minimumSize.width = leftMargin.preferredSize.width = 1;
leftMargin.alignment = [ 'left', 'fill' ];
var mainColumn = mainRow.add( 'group' );
mainColumn.orientation = 'column';
mainColumn.spacing = 0;
mainColumn.margins = 0;
mainColumn.alignment = ['fill','fill'];
var titleBar = DuScriptUI.titleBar(
mainColumn,
title
);
if (modal) DuScriptUI.setTextColor(titleBar.titleLabel, DuColor.Color.APP_HIGHLIGHT_COLOR)
popup.content = mainColumn.add( 'group' );
popup.content.margins = 0;
popup.content.spacing = 0;
popup.content.alignChildren = alignment;
popup.content.orientation = "column";
var rightMargin = mainRow.add( 'button' );
rightMargin.onDraw = drawBorder;
rightMargin.side = 2;
rightMargin.maximumSize.width = rightMargin.minimumSize.width = rightMargin.preferredSize.width = 1;
rightMargin.alignment = [ 'right', 'fill' ];
var bottomMargin = popup.add( 'button' );
bottomMargin.onDraw = drawBorder;
bottomMargin.side = 3;
bottomMargin.maximumSize.height = bottomMargin.minimumSize.height = bottomMargin.preferredSize.height = 1;
bottomMargin.alignment = [ 'fill', 'bottom' ];
popup.block = false;
popup.build = function( contentGroup ) {};
popup.built = false;
popup.tiedAlwaysBlock = false;
popup.tieTo = function (control, onShift, alwaysBlock)
{
onShift = def(onShift, false);
alwaysBlock = def(alwaysBlock, false);
popup.tiedAlwaysBlock = alwaysBlock;
control.tiedPopups = def(control.tiedPopups, []);
control.tiedOnShift =def(control.tiedOnShift, []);
control.tiedPopups.push(popup);
control.tiedOnShift.push(onShift);
if (control.tiedPopups.length == 1)
{
control.addEventListener( 'click', function(e) {
if(e.view.parent != control) return;
for (var i = 0; i < control.tiedPopups.length; i++)
{
var ppup = control.tiedPopups[i];
if ( !e.shiftKey && control.tiedOnShift[i] ) return;
if (ppup.block)
{
ppup.block = false;
return;
}
if ( !ppup.built )
{
ppup.build( );
ppup.built = true;
}
var x = e.screenX - e.clientX;
var y = e.screenY - e.clientY;
//we need a layout and resize to get the window frame size
DuScriptUI.layout( ppup );
var location = [x,y];
location = DuScriptUI.moveInsideScreen( [x, y], ppup.frameSize );
ppup.location = location;
if (!ppup.pinned) DuScriptUI.eventFunctionsPaused = true;
if (!ppup.tiedAlwaysBlock) ppup.show();
}
}, true);
}
}
popup.pin = function(p)
{
p = def (p, true);
popup.pinned = p;
titleBar.pinButton.setChecked(p);
}
var movingTitle = false;
var movingPopup = false;
var startMouseLocation = [0,0];
var startPopupLocation = [0,0];
function enterTitleMoveState(e)
{
movingTitle = true;
startMouseLocation = [e.screenX, e.screenY];
startPopupLocation = [popup.location[0], popup.location[1]];
}
function enterPopupMoveState(e)
{
movingPopup = true;
startMouseLocation = [e.screenX, e.screenY];
startPopupLocation = [popup.location[0], popup.location[1]];
}
function movePopup(e)
{
if (!movingPopup) return;
if (movingTitle) return;
var x = e.screenX - startMouseLocation[0];
var y = e.screenY - startMouseLocation[1];
popup.location = [ x, y ] + startPopupLocation;
}
function moveTitle(e)
{
if (!movingTitle) return;
var x = e.screenX - startMouseLocation[0];
var y = e.screenY - startMouseLocation[1];
popup.location = [ x, y ] + startPopupLocation;
}
function endMoveState(e)
{
movingTitle = false;
movingPopup = false;
}
function esc(e)
{
if ( e.keyName == "Escape") { hidePopup(); }
}
function hidePopup()
{
popup.hide();
DuScriptUI.eventFunctionsPaused = false;
}
popup.cancel = popup.hidePopup = hidePopup;
popup.onDeactivate = function () { if (!popup.pinned) hidePopup(); };
titleBar.onClose = hidePopup;
titleBar.onPin = function (pinned) { popup.pin(pinned); };
popup.addEventListener( "mouseover", DuScriptUI.dimControls );
popup.addEventListener( "keydown", esc );
topMargin.addEventListener( "mouseover", DuScriptUI.dimControls );
leftMargin.addEventListener( "mouseover", DuScriptUI.dimControls );
rightMargin.addEventListener( "mouseover", DuScriptUI.dimControls );
bottomMargin.addEventListener( "mouseover", DuScriptUI.dimControls );
titleBar.addEventListener( "mouseover", DuScriptUI.dimControls );
titleBar.addEventListener( "mousedown", enterPopupMoveState );
titleBar.addEventListener( "mousemove", movePopup);
titleBar.addEventListener( "mouseup", endMoveState );
titleBar.addEventListener( "mouseout", endMoveState );
titleBar.titleLabel.addEventListener( "mousedown", enterTitleMoveState );
titleBar.titleLabel.addEventListener( "mousemove", moveTitle );
titleBar.titleLabel.addEventListener( "mouseup", endMoveState );
titleBar.titleLabel.addEventListener( "mouseout", endMoveState );
if (modal) popup.pin();
return popup;
}
/**
* Resizes and shows the main panel of a script
* @param {Panel|Window} ui - The UI created by Duik.ui.createUI
* @param {boolean} [enterRunTime=false] - Set to true to automatically set DuESF to runtime state<br />
* Set this to false if the ui shown is not the actual main panel of the script and it is shown before the main panel has been loaded.
*/
DuScriptUI.showUI = function( ui, enterRunTime )
{
DuScriptUI.updateFundingInfo();
enterRunTime = def( enterRunTime, true);
DuDebug.log( "DuScriptUI.showUI" );
DuDebug.log( "DuScriptUI.showUI: Defining layout & resize" );
DuScriptUI.layout( ui, true );
DuDebug.log( "DuScriptUI.showUI: Layout OK" );
ui.onResizing = ui.onResize = function()
{
try
{
ui.layout.resize();
}
catch ( e )
{}
}
DuDebug.log( "DuScriptUI.showUI: UI Ready" );
// If it's a Window, it needs to be shown
if ( ui instanceof Window )
{
DuDebug.log( "DuScriptUI.showUI: Showing window" );
//ui.center();
ui.show();
DuDebug.log( "DuScriptUI.showUI: Window visible." );
}
DuDebug.log( "DuScriptUI.showUI: UI shown" );
DuScriptUI.uiShown = true;
if (enterRunTime) DuESF.enterRunTime();
DuDebug.log( "DuScriptUI.showUI: Runtime!" );
// Build tabs
for (var i = 0, n = DuScriptUI.allTabs.length; i < n; i++)
{
var tab = DuScriptUI.allTabs[i];
if ( !tab.activated ) continue;
tab.duBuild();
tab.visible = true;
tab.tabActivated();
}//*/
DuDebug.log( "DuScriptUI.showUI: Tabs built." );
}
/**
* @class
* @name DuScriptPanel
* @extends DuPanel
* @classdesc For use with {@link DuScriptUI}.<br />
* A Panel or Window.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.scriptPanel} to create a Panel.<br />
* @property {Group} settingsGroup - The ScriptUI group where to add the UI for the settings of the script.
* @property {Group} mainGroup - The ScriptUI group where to add the main UI of the script.
* @property {DuScriptPanel~onApplySettings} onApplySettings Called when the apply settings button is clicked.
* @property {DuScriptPanel~onResetSettings} onResetSettings Called when the reset (default) settings button is clicked.
* @category DuScriptUI
*/
/**
* The function to execute when the apply settings button is clicked.
* @callback DuScriptPanel~onApplySettings
* @memberof DuScriptPanel
*/
/**
* The function to execute when the reset settings button is clicked.
* @callback DuScriptPanel~onResetSettings
* @memberof DuScriptPanel
*/
/**
* Adds some settings common to all scripts (the file, highlight color, languages...)
* @method addCommonSettings
* @memberof DuScriptPanel.prototype
*/
/**
* Creates the main panel of a script
* @param {Panel|null} container - The container ('this' in the root of the calling script), either a Panel (when launched from the 'Window' menu) or null (when launched from 'file/scripts/run...')
* @param {bool} [addSettingsButton=true] - Whether to create a button to open the settings or not
* @param {bool} [addHelpButton=false] - Whether to create a button to open the help panel or not
* @param {File} [scriptFile] - The main script file, needed for the refresh button in debug mode
* @return {DuScriptPanel} The panel created, either a ScriptUI Panel or a ScriptUI Window.
* @example
* var ui = DuScriptUI.mainPanel(this,"Test Script");
* var refreshButton = ui.content.add('button',undefined,"Refresh");
* refreshButton.onClick = function() { ui.refreshUI( new File($.fileName) ); }; //reloads the current script
* DuScriptUI.showUI(ui);
*/
DuScriptUI.scriptPanel = function( container, addSettingsButton, addHelpButton, scriptFile )
{
function testGroup( group )
{
group.add('statictext', undefined, "test");
DuScriptUI.setBackgroundColor( group, DuColor.Color.BLACK );
}
addSettingsButton = def(addSettingsButton, true);
addHelpButton = def(addHelpButton, false);
var panel = DuScriptUI.mainPanel( container, undefined, ['fill','fill'] );
panel.content.orientation = 'stack';
panel.content.alignment = ['fill','fill'];
var contentsMainGroup = DuScriptUI.group(panel.content, 'column');
contentsMainGroup.alignment = ['fill', 'fill'];
contentsMainGroup.margins = 0;
var contentsMainStack = DuScriptUI.group(contentsMainGroup, 'column');
contentsMainStack.orientation = 'stack';
contentsMainStack.alignment = ['fill', 'fill'];
panel.mainGroup = DuScriptUI.group(contentsMainStack, 'column');
panel.mainGroup.alignment = ['fill', 'fill'];
//a margin at the bottom (hidden by the bottom bar buttons)
var mainGroupMarginBottom = DuScriptUI.group(contentsMainGroup, 'column');
mainGroupMarginBottom.alignment= ['fill', 'bottom'];
//mainGroupMarginBottom.minimumSize = [0, 16];
// == SETTINGS PANEL
var rootSettingsGroup = DuScriptUI.group(contentsMainStack, 'column');
rootSettingsGroup.alignment = ['fill', 'fill'];
rootSettingsGroup.visible = false;
panel.settingsGroup = DuScriptUI.group(rootSettingsGroup, 'column');
panel.settingsGroup.alignment = ['fill', 'top'];
var settingsTitle = DuScriptUI.titleBar(
panel.settingsGroup,
i18n._( "%1 Settings", DuESF.scriptName),
true,
false
);
panel.hasCommonSettings = false;
panel.addCommonSettings = function()
{
DuDebug.log("ScriptUI: adding settings to main panel.");
if (panel.hasCommonSettings) return;
panel.hasCommonSettings = true;
var debug = DuESF.scriptSettings.get("common/debug", false);
// Add default settings
// language
// Prepare languages, count how many there are, etc
var languages = i18n.getAvailableLanguages();
var languageGroup = DuScriptUI.group(panel.settingsGroup, 'row');
if (languages.count >= 1)
{
panel.settingsGroup.languageSelector = DuScriptUI.selector(
languageGroup,
i18n._("Set the language of the interface.")
)
for (var locale in languages)
{
if (locale == "count") continue;
DuDebug.log("ScriptUI: Found locale: " + locale);
panel.settingsGroup.languageSelector.addButton( {
text: languages[locale],
image: w16_language,
data: locale
});
}
panel.settingsGroup.languageSelector.setCurrentIndex(0);
}
DuDebug.log("ScriptUI: language settings ready.");
// settings file
panel.settingsGroup.fileButton = DuScriptUI.fileSelector (
panel.settingsGroup,
i18n._("Settings file") + '...',
true,
i18n._("Set the location of the settings file."),
w16_settings_file,
'save',
"JSON: *.json, All Files: *.*",
'column'
);
DuDebug.log("ScriptUI: settings file ready.");
// color
panel.settingsGroup.colorSelector = DuScriptUI.selector(
panel.settingsGroup,
i18n._("Set the highlight color.")
);
panel.settingsGroup.colorSelector.addButton(
i18n._("After Effects Blue"),
w8_ae_blue,
i18n._("The After Effects highlighting blue")
);
panel.settingsGroup.colorSelector.addButton(
i18n._("RxLab Purple"),
w8_rx_purple,
i18n._("The RxLaboratory Purple")
);
panel.settingsGroup.colorSelector.addButton(
i18n._("Rainbox Red"),
w8_rx_red,
i18n._("The Rainbox Productions Red")
);
panel.settingsGroup.colorSelector.addButton(
i18n._("After Effects Orange (CS6)"),
w8_ae_orange,
i18n._("The After Effects highlighting orange from good ol'CS6")
);
panel.settingsGroup.colorSelector.addButton(
i18n._("Custom..."),
undefined,
i18n._("Select a custom color.")
);
panel.settingsGroup.colorSelector.setCurrentIndex(0);
DuDebug.log("ScriptUI: color ready.");
// ui mode
panel.settingsGroup.uiModeSelector = DuScriptUI.selector(
panel.settingsGroup,
i18n._("Select the UI mode.")
)
panel.settingsGroup.uiModeSelector.addButton(
i18n._("Rookie"),
w16_rookie,
i18n._("The easiest-to-use mode, but also the biggest UI.")
);
panel.settingsGroup.uiModeSelector.addButton(
i18n._("Standard"),
w16_standard,
i18n._("The standard not-too-big UI.")
);
panel.settingsGroup.uiModeSelector.addButton(
i18n._("Expert"),
w16_expert,
i18n._("The smallest UI, for expert users.")
);
panel.settingsGroup.uiModeSelector.setCurrentIndex(0);
DuDebug.log("ScriptUI: ui mode ready.");
// dev & debug
panel.settingsGroup.debugButton = DuScriptUI.checkBox (
panel.settingsGroup,
i18n._("Normal mode"),
w16_user,
i18n._("Use at your own risk!"),
i18n._("Dev and Debug mode"),
w16_bug
);
DuDebug.log("ScriptUI: Dev and debug mode ready.");
panel.settingsGroup.fileButton.onChange = function()
{
var file = panel.settingsGroup.fileButton.getFile();
if (file == null) return;
DuESF.scriptSettings.setFile(file);
panel.settingsGroup.reset();
}
panel.settingsGroup.colorSelector.onChange = function()
{
var customColor = new DuColor( DuESF.scriptSettings.get("common/highlightColor", DuColor.Color.APP_HIGHLIGHT_COLOR) );
var colorIndex = panel.settingsGroup.colorSelector.index;
if (colorIndex == panel.settingsGroup.colorSelector.items.length -1 )
{
var newColor = new DuColor( colorPicker( customColor.floatRGB() ) );
// If white, the user has canceled
if ( newColor.equals( DuColor.Color.WHITE ) )
{
panel.settingsGroup.colorSelector.setCurrentIndex(0);
return;
}
// Store the color for the valid button
panel.settingsGroup.colorSelector.customColor = newColor;
}
}
DuDebug.log("ScriptUI: events ready.");
// Load settings
panel.settingsGroup.reset = function()
{
var highlightSelection = DuESF.scriptSettings.get("common/highlightSelection", 0);
var currentLanguageName = DuESF.scriptSettings.get("common/currentLanguageName", 'Esperanto');
var debug = DuESF.scriptSettings.get("common/debug", false);
var uiMode = DuESF.scriptSettings.get("common/uiMode", 0);
var customColor = new DuColor( DuESF.scriptSettings.get("common/highlightColor", DuColor.Color.APP_HIGHLIGHT_COLOR) );
// Settings File
panel.settingsGroup.fileButton.setPath( DuESF.scriptSettings.file.absoluteURI );
// Ui mode
panel.settingsGroup.uiModeSelector.setCurrentIndex( uiMode );
// Color
panel.settingsGroup.colorSelector.setCurrentIndex( highlightSelection, true );
// Set custom
panel.settingsGroup.colorSelector.customColor = customColor;
// Language
if (panel.settingsGroup.languageSelector)
for (var i = 0 , num = panel.settingsGroup.languageSelector.items.length; i < num; i++)
{
if (panel.settingsGroup.languageSelector.items[i][0] == currentLanguageName)
{
panel.settingsGroup.languageSelector.setCurrentIndex(i);
break;
}
}
// Debug mode
panel.settingsGroup.debugButton.setChecked( debug );
panel.onResetSettings();
}
DuDebug.log("ScriptUI: calling reset method...");
panel.settingsGroup.reset();
DuDebug.log("ScriptUI: settings panel ready!");
}
DuScriptUI.separator( rootSettingsGroup );
panel.settingsGroup.updateButton = DuScriptUI.button(
rootSettingsGroup,
i18n._("Check for updates"),
DuScriptUI.Icon.DOWNLOAD,
i18n._("Check for updates"),
false,
'row',
'center'
)
DuScriptUI.separator( rootSettingsGroup );
var validGroup = DuScriptUI.group( rootSettingsGroup, 'row');
panel.settingsGroup.resetButton = DuScriptUI.button(
validGroup,
i18n._("Default"),
w12_reset,
i18n._("Reset the settings to their default values."),
false,
'row',
'center'
);
panel.settingsGroup.validButton = DuScriptUI.button(
validGroup,
i18n._("Apply"),
w12_check,
i18n._("Apply settings."),
false,
'row',
'center'
);
panel.onApplySettings = function() {};
panel.onResetSettings = function() {};
panel.settingsGroup.validButton.onClick = function()
{
if (!panel.hasCommonSettings)
{
panel.onApplySettings();
return;
}
// Update and Save settings
// UI Mode
DuESF.scriptSettings.set("common/uiMode", panel.settingsGroup.uiModeSelector.index);
// Color
var colorIndex = panel.settingsGroup.colorSelector.index;
DuESF.scriptSettings.set("common/highlightSelection", colorIndex);
// Custom
if (colorIndex == panel.settingsGroup.colorSelector.items.length -1 )
{
// Save and set
var color = panel.settingsGroup.colorSelector.customColor;
DuESF.scriptSettings.set("common/highlightColor", color.floatRGBA());
DuColor.Color.APP_HIGHLIGHT_COLOR = color;
}
else if (colorIndex == 0)
{
DuESF.scriptSettings.set("common/highlightColor", DuColor.Color.AFTER_EFFECTS_BLUE);
DuColor.Color.APP_HIGHLIGHT_COLOR = DuColor.Color.AFTER_EFFECTS_BLUE;
}
else if (colorIndex == 1)
{
DuESF.scriptSettings.set("common/highlightColor", DuColor.Color.RX_PURPLE);
DuColor.Color.APP_HIGHLIGHT_COLOR = DuColor.Color.RX_PURPLE;
}
else if (colorIndex == 2)
{
DuESF.scriptSettings.set("common/highlightColor", DuColor.Color.RAINBOX_RED);
DuColor.Color.APP_HIGHLIGHT_COLOR = DuColor.Color.RAINBOX_RED;
}
else if (colorIndex == 3)
{
DuESF.scriptSettings.set("common/highlightColor", DuColor.Color.AE_ORANGE);
DuColor.Color.APP_HIGHLIGHT_COLOR = DuColor.Color.AE_ORANGE;
}
// Language
if (panel.settingsGroup.languageSelector)
{
DuESF.scriptSettings.set("common/language", panel.settingsGroup.languageSelector.currentData );
DuESF.scriptSettings.set("common/currentLanguageName", panel.settingsGroup.languageSelector.text);
}
// Dev Mode
DuESF.scriptSettings.set("common/debug", panel.settingsGroup.debugButton.value);
DuESF.debug = panel.settingsGroup.debugButton.value;
DuESF.scriptSettings.save();
panel.mainGroup.visible = true;
rootSettingsGroup.visible = false;
panel.onApplySettings();
// Restart script
var ok = DuScriptUI.refreshPanel( panel, scriptFile );
if (!ok) alert(i18n._("You may need to restart the script for all changes to take effect."));
}
panel.settingsGroup.resetButton.onClick = function()
{
DuESF.settings.reset();
DuESF.scriptSettings.reset();
panel.settingsGroup.reset();
}
panel.settingsGroup.updateButton.onClick = function()
{
DuScriptUI.checkUpdate(undefined, undefined, true);
}
// == Bottom Line
var bottomUberGroup = mainGroupMarginBottom;
// TODO Test in Ae
/*var bottomUberGroup = DuScriptUI.group(panel.content, 'column');
bottomUberGroup.alignment = ['fill', 'bottom'];
bottomUberGroup.margins = 0;
bottomUberGroup.spacing = 0;*/
// Add a progress bar
panel.progressBar = new DuProgressBar("", bottomUberGroup);
// Draw a thin line like other panels.
//var bottomLine = DuScriptUI.separator( bottomUberGroup );
if ( DuESF.rxVersionURL != '' ) {
DuScriptUI.fundingBar = bottomUberGroup.add( 'progressbar' );
DuScriptUI.fundingBar.maximumSize = [32000, 2];
DuScriptUI.fundingBar.alignment = ['fill', 'top'];
DuScriptUI.fundingBar.helpTip = i18n._("Thank you for your donations!");
DuScriptUI.updateFundingInfo();
}
panel.bottomGroup = DuScriptUI.group(bottomUberGroup, 'row');
panel.bottomGroup.margins = 0;
if (DuESF.bugReportURL != '' || addSettingsButton || addHelpButton || DuESF.translateURL != '')
{
panel.moreButton = DuScriptUI.multiButton( panel.bottomGroup,
{
text: "",
helpTip: i18n._("Help and options")
}
);
panel.moreButton.alignment = ['left', 'center'];
if (DuESF.bugReportURL != '')
{
var bugButton = DuScriptUI.addBugButton( panel.moreButton );
bugButton.alignment = ['left', 'fill'];
}
if (addSettingsButton)
{
var settingsButton = panel.moreButton.addButton(
i18n._("Edit settings"),
w12_settings,
i18n._("Edit settings")
);
settingsButton.alignment = ['left', 'fill'];
settingsButton.onClick = function()
{
if (!rootSettingsGroup.visible)
{
if(panel.hasCommonSettings) panel.settingsGroup.reset();
panel.mainGroup.visible = false;
rootSettingsGroup.visible = true;
}
else
{
panel.mainGroup.visible = true;
rootSettingsGroup.visible = false;
}
}
settingsTitle.onClose = function()
{
if( panel.hasCommonSettings ) panel.settingsGroup.reset();
panel.mainGroup.visible = true;
rootSettingsGroup.visible = false;
}
}
if (DuESF.translateURL != '')
{
var translateButton = DuScriptUI.addTranslateButton( panel.moreButton );
translateButton.alignment = ['left', 'fill'];
}
if (addHelpButton)
{
var helpButton = DuScriptUI.addHelpButton( panel.moreButton, false );
helpButton.alignment = ['left', 'fill'];
}
}
var donateButton = DuScriptUI.addDonateButton( panel.bottomGroup, true);
donateButton.alignment = ['left', 'fill'];
var versionButton = DuScriptUI.versionButton(
panel.bottomGroup
);
versionButton.alignment = ['right', 'bottom'];
//Refresh button
if ( DuESF.debug && (scriptFile instanceof File) && scriptFile.exists )
{
var refreshButton = panel.bottomGroup.add( 'button', undefined, 'R' );
refreshButton.alignment = [ 'right', 'bottom' ];
refreshButton.maximumSize = [ 20, 20 ];
refreshButton.onClick = function()
{
DuScriptUI.refreshPanel( panel, scriptFile );
};
}
return panel;
}
DuScriptUI.refreshPanel = function( panel, scriptFile )
{
return DuScriptUI.refreshWindow( panel, scriptFile );
}
DuScriptUI.refreshWindow = function ( win, scriptFile )
{
// If Panel is a window, close, and run script again
if( win instanceof Window && scriptFile.exists)
{
win.close();
$.evalFile( scriptFile );
return true;
}
return false;
}
/**
* Creates a popup to ask for a simple string
* @param {string} title The title of the popup
* @param {string} defaultString The placeholder for the edit text
* @returns {DuPopup} The popup, with an <code>onAccept( str )</code> callback.
*/
DuScriptUI.stringPrompt = function ( title, defaultString )
{
defaultString = def(defaultString, '');
var nameEditor = DuScriptUI.popUp( title );
nameEditor.content.alignment = ['fill','top'];
nameEditor.editText = DuScriptUI.editText(
nameEditor.content,
'',
'',
'',
defaultString
)
nameEditor.editText.alignment = ['fill','top'];
var nameButtons = DuScriptUI.group( nameEditor.content );
nameButtons.alignment = ['fill','top'];
var nameOKButton = DuScriptUI.button(
nameButtons,
i18n._("OK"),
DuScriptUI.Icon.CHECK,
title,
false,
'row',
'center'
)
nameEditor.previousString = '';
nameEditor.onAccept = function( str ){};
nameEditor.accept = function() {
nameEditor.onAccept( nameEditor.editText.text );
nameEditor.hidePopup();
return nameEditor.editText.text;
}
nameEditor.setText = function (text) {
nameEditor.previousString = text;
nameEditor.editText.setText(text);
}
nameEditor.edit = function() {
nameEditor.editText.clicked();
}
nameOKButton.onClick = nameEditor.accept;
nameEditor.addEventListener('enterkey', nameEditor.accept );
return nameEditor;
}
/**
* Finds the window containing this ScriptUI Control
* @param {Control} scriptUIControl The ScriptUI Control
* @return {Window} The containing ScriptUI Window
*/
DuScriptUI.parentWindow = function ( scriptUIControl )
{
var p = scriptUIControl;
while(p.parent) p = p.parent;
return p;
}
// ==================== |--------| ====================
// ==================== | button | ====================
// ==================== |--------| ====================
/**
* @class
* @name DuButton
* @classdesc For use with {@link DuScriptUI}.<br />
* An Image Button.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.button} to create an Image Button.<br />
* The Image Button inherits the <code>Group</code> object from ScriptUI and has all of its properties and methods.
* @property {string} helpLink - A URL to a help page, shown on shift click on the button.
* @property {Image} image - The scriptui object representing the image.
* @property {StaticText} label - The label
* @property {DuPopup} optionsPopup - A popup for .
* @property {Group} optionsPanel - The ScriptUI Group where to add the options (child of the popup).
* @property {DuButton~onClick} onClick - The function to execute when the button is clicked
* @property {DuButton~onAltClick} onAltClick - The function to execute when the button is Alt + clicked
* @property {DuButton~onCtrlClick} onCtrlClick - The function to execute when the button is Ctrl + clicked
* @property {DuButton~onCtrlAltClick} onCtrlAltClick - The function to execute when the button is Ctrl + Alt + clicked
* @property {DuButton~onOptions} onOptions - The function to execute when the options are called (Shift+CLick or click on the options button)
* @category DuScriptUI
*/
/**
* The function to execute when the button is clicked.
* @callback DuButton~onClick
* @memberof DuButton
*/
/**
* The function to execute when the button is Alt + clicked.
* @callback DuButton~onAltClick
* @memberof DuButton
*/
/**
* The function to execute when the button is Ctrl + clicked.
* @callback DuButton~onCtrlClick
* @memberof DuButton
*/
/**
* The function to execute when the button is Ctrl + Alt + clicked.
* @callback DuButton~onCtrlAltClick
* @memberof DuButton
*/
/**
* The function to execute when the options are called (Shift+CLick or click on the options button).<br/>
* Use the <code>showUI</code> parameter to build the options when the button is clicked without showing any UI.
* @callback DuButton~onOptions
* @param {Boolean} showUI This is set to false when the button is clicked: use this parameter to build the options without showing any UI when needed on all button clicks.
* @memberof DuButton
*/
/**
* Changes the background color of the button.
* @method
* @memberof DuButton
* @name setBackgroundColor
* @param {DuColor} color - the color.
*/
/**
* Changes the text color of the button.
* @method
* @memberof DuButton
* @name setTextColor
* @param {DuColor} color - the color.
*/
/**
* Changes the image of the button.
* @method
* @memberof DuButton
* @name setImage
* @param {File|DuBInary} image - the image. Changing the image does not work with PNG as strings, a File must be passed.
*/
/**
* Changes the helptip of the button.
* @method
* @memberof DuButton
* @name setHelpTip
* @param {string} helptip - the helptip.
*/
/**
* Changes the text of the button.
* @method
* @memberof DuButton
* @name setText
* @param {string} text - the text.
*/
/**
* Creates a button with an optionnal icon. Must have at least an icon or a text, or both.
* @param {Panel|Window|Group} container - The ScriptUI Object which will contain and display the button.
* @param {string} [text] - The label of the button. Default: empty string
* @param {string|DuBInary} [image] - The path to the icon (or a PNG as a string representation). Default: empty string
* @param {string} [helpTip] - The helptip. Default: empty string
* @param {Boolean} [addOptionsPanel=false] - Adds a panel for options and a button to access it.
* @param {Boolean} [orientation='row'] - The orientation of the button (icon, text, options button). Default will be changed to 'column' if there's no text.
* @param {Boolean} [alignment='left'] - The alignment of the button content ('center', 'right' or 'left' for 'row', 'top' 'bottom', 'center' for column).
* @param {Boolean} [localize=true] - Set to false if the text must not be translated.
* @param {Boolean} [ignoreUIMode=false] - Will show texte even if the ui mode is set to > 1 in the script settings
* @param {Boolean} [optionsWithoutButton=false] - Don't add an "ok" button to the options popup
* @param {string} [optionsButtonText] - Change the displayed text of the bottom button of the options
* @param {Boolean} [optionsWithoutPanel=false] - Don't create the options popup panel (use <code>DuButton.onOptions</code> to add your own callback when the options are requested)
* @return {DuButton} The image button created.
*/
DuScriptUI.button = function(container, text, image, helpTip, addOptionsPanel, orientation, alignment, ignoreUIMode, optionsWithoutButton, optionsButtonText, optionsWithoutPanel) {
if (!isdef( container )) {
throw 'DuScriptUI: DuScriptUI.button(container, text, image, helpTip, imageOver):\nMissing argument: container.';
}
if (jstype(container) != 'group' && jstype(container) != 'panel' && jstype(container) != 'window') {
throw 'DuScriptUI: DuScriptUI.button(container, text, image, helpTip):\nTypeError.\n\ncontainer has to be a Group/Panel/Window.\n\ncontainer is ' + jstype(container) + ' with value ' + container.toString();
}
var options = {};
text = def(text, '');
if (jstype(text) != 'string') options = text;
options.text = def(options.text, text);
image = def(image, '');
helpTip = def(helpTip, '');
addOptionsPanel = def(addOptionsPanel, false);
orientation = def(orientation, 'row');
localize = def(localize, true);
ignoreUIMode = def(ignoreUIMode, false);
optionsWithoutButton = def(optionsWithoutButton, false);
optionsWithoutPanel = def(optionsWithoutPanel, false);
optionsButtonText = def(optionsButtonText, options.text);
options.image = def(options.image, image);
options.helpTip = def(options.helpTip, helpTip);
options.options = def(options.options, addOptionsPanel);
options.orientation = def(options.orientation, orientation);
options.ignoreUIMode = def(options.ignoreUIMode, ignoreUIMode);
options.optionsWithoutButton = def(options.optionsWithoutButton, optionsWithoutButton);
options.optionsButtonText = def(options.optionsButtonText, optionsButtonText);
options.optionsWithoutPanel = def(options.optionsWithoutPanel, optionsWithoutPanel);
options.small = def(options.small, false);
var uiMode = DuESF.scriptSettings.get("common/uiMode", 0);
if (uiMode >= 2 && options.image != '' && !options.ignoreUIMode) options.text = '';
if (!isdef( alignment )) {
if (options.orientation == 'row' && options.text != '') alignment = 'left';
else if (options.orientation == 'row') alignment = 'center';
else alignment = 'top';
}
options.alignment = def(options.alignment, alignment);
if (options.options) {
if (options.helpTip != '') options.helpTip += '\n\n';
options.helpTip += i18n._("[Shift]: More options...");
}
if (options.image == '' && options.text == '') options.text = 'button';
var duButton = container.add('group');
duButton.orientation = options.orientation;
duButton.alignment = ['fill', 'top'];
duButton.spacing = 0;
duButton.bgColor = DuColor.Color.TRANSPARENT;
if (DuESF.host == DuESF.HostApplication.PHOTOSHOP) duButton.bgColor = DuColor.Color.APP_BACKGROUND_COLOR;
duButton.textColor = DuColor.Color.APP_TEXT_COLOR;
duButton.onClick = function() {};
duButton.onAltClick = function() {};
duButton.onCtrlAltClick = function() {};
duButton.onCtrlClick = function() {};
duButton.onShiftClick = function() {};
duButton.onOptions = function() {};
// Location of the latest event
duButton.screenX = 0;
duButton.screenY = 0;
// Options panel
if (options.options) {
if (!options.optionsWithoutPanel) {
// Create panel
var optionsPanel = DuScriptUI.popUp(options.text == '' ? 'Options' : options.text);
duButton.optionsPopup = optionsPanel;
duButton.optionsPanel = DuScriptUI.group(optionsPanel.content, 'column');
if (!options.optionsWithoutButton) {
DuScriptUI.separator(optionsPanel.content);
duButton.optionsButton = DuScriptUI.button( optionsPanel.content, {
text: options.optionsButtonText,
image: w12_check,
helpTip: options.helpTip
});
duButton.optionsButton.onClick = function() {
duButton.clicked();
};
duButton.optionsButton.onAltClick = function() {
duButton.altClicked();
};
duButton.optionsButton.onCtrlClick = function() {
duButton.ctrlClicked();
};
duButton.optionsButton.onCtrlAltClick = function() {
duButton.ctrlAltClicked();
};
}
optionsPanel.tieTo(duButton, true);
}
// Add button
if (options.text != '' && options.orientation != 'column') {
var optionsButton = DuScriptUI.button(
duButton,
'',
w12_options,
options.text + '\nOptions'
)
optionsButton.alignment = ['left', 'center'];
if (!options.optionsWithoutPanel) optionsPanel.tieTo(optionsButton);
optionsButton.onClick = function () { duButton.onOptions(true); };
}
}
var mainGroup = DuScriptUI.group(duButton, options.orientation);
mainGroup.alignment = ['fill', 'fill'];
mainGroup.margins = 0;
//Add fillers to be able to click anywhere on the button
if (options.text != '' && options.orientation == 'row') {
duButton.fillerL = mainGroup.add('statictext', undefined, " ");
duButton.fillerL.alignment = ['left', 'fill'];
if (options.alignment == 'left' && !options.options && !options.small) duButton.fillerL.size = [18, -1];
else if (options.alignment == 'left') duButton.fillerL.size = [2, -1];
}
// Add options.image
if (options.image != '') {
duButton.imageGroup = mainGroup.add('group');
duButton.imageGroup.margins = 0;
duButton.imageGroup.spacing = 0;
duButton.imageGroup.alignment = ['fill', 'fill'];
var subgroup = duButton.imageGroup.add('group');
subgroup.margins = 2;
subgroup.spacing = 0;
subgroup.alignment = ['fill', 'fill'];
if (options.image instanceof DuBinary) options.image = options.image.binAsString;
duButton.image = subgroup.add('image', undefined, options.image);
if (options.orientation == 'row') duButton.imageGroup.alignment = [options.alignment, 'center'];
else duButton.imageGroup.alignment = ['center', options.alignment];
// Bigger buttons on photoshop
if (DuESF.host == DuESF.HostApplication.PHOTOSHOP) duButton.imageGroup.margins = 3;
duButton.image.helpTip = options.helpTip;
}
// Another filler
if (options.text != '' && options.image != '' && options.orientation == 'row') {
duButton.fillerM = mainGroup.add('statictext', undefined, " ");
duButton.fillerM.alignment = ['left', 'fill'];
if (options.small) duButton.fillerM.size = [2, -1];
else duButton.fillerM.size = [8, -1];
}
// Add text
if (options.text != '') {
duButton.label = mainGroup.add('statictext', undefined, options.text);
duButton.label.helpTip = options.helpTip;
if (options.orientation == 'row' && !options.small) duButton.label.alignment = [options.alignment, 'center'];
else if (options.orientation == 'row') duButton.label.alignment = [options.alignment, 'bottom'];
else duButton.label.alignment = ['center', options.alignment];
}
// Another filler
if (options.text != '' && options.orientation == 'row') {
duButton.fillerR = mainGroup.add('statictext', undefined, " ");
duButton.fillerR.alignment = ['fill', 'fill'];
if (options.alignment == 'right') duButton.fillerR.size = [10, -1];
}
//events
duButton.clicked = function() {
if (options.options) {
if (!options.optionsWithoutPanel && !duButton.optionsPopup.built) {
duButton.optionsPopup.build();
duButton.optionsPopup.built = true;
}
duButton.onOptions(false);
}
DuDebug.safeRun(duButton.onClick);
}
duButton.shiftClicked = function() {
if (options.options) {
if (!options.optionsWithoutPanel && !duButton.optionsPopup.built) {
duButton.optionsPopup.build();
duButton.optionsPopup.built = true;
}
duButton.onOptions(true);
}
DuDebug.safeRun(duButton.onShiftClick);
}
duButton.altClicked = function() {
if (options.options) {
if (!options.optionsWithoutPanel && !duButton.optionsPopup.built) {
duButton.optionsPopup.build();
duButton.optionsPopup.built = true;
}
duButton.onOptions(false);
}
DuDebug.safeRun(duButton.onAltClick);
}
duButton.ctrlAltClicked = function() {
if (options.options) {
if (!options.optionsWithoutPanel && !duButton.optionsPopup.built) {
duButton.optionsPopup.build();
duButton.optionsPopup.built = true;
}
duButton.onOptions(false);
}
DuDebug.safeRun(duButton.onCtrlAltClick);
}
duButton.ctrlClicked = function() {
if (options.options) {
if (!options.optionsWithoutPanel && !duButton.optionsPopup.built) {
duButton.optionsPopup.build();
duButton.optionsPopup.built = true;
}
duButton.onOptions(false);
}
DuDebug.safeRun(duButton.onCtrlClick);
}
duButton.highlight = function(e) {
e.stopPropagation();
DuScriptUI.dimControls();
if (duButton.label && !options.small) {
DuScriptUI.setTextColor(duButton.label, DuColor.Color.APP_HIGHLIGHT_COLOR);
}
else {
DuScriptUI.setBackgroundColor(mainGroup, DuColor.Color.APP_HIGHLIGHT_COLOR.darker());
}
DuScriptUI.highlightedControls.push(duButton);
}
duButton.dim = function() {
DuScriptUI.setBackgroundColor(mainGroup, duButton.bgColor);
if (duButton.label) DuScriptUI.setTextColor(duButton.label, duButton.textColor);
}
duButton.setBackgroundColor = function(color) {
duButton.bgColor = color;
duButton.dim();
}
duButton.setTextColor = function(color) {
duButton.textColor = color;
duButton.dim();
}
duButton.setImage = function(image) {
if (options.image == '') return;
if (image instanceof DuBinary) image = image.toFile();
duButton.image.image = image;
}
duButton.setHelpTip = function(helpTip) {
if (options.image != '') duButton.image.helpTip = helpTip;
if (options.text != '') duButton.label.helpTip = helpTip;
}
duButton.setText = function(text) {
if (options.text != '') duButton.label.text = text;
}
//add events
function clickEvent(e) {
// On After Effects, event is triggered multiple times...
if (e.view.parent != mainGroup && DuESF.host == DuESF.HostApplication.AFTER_EFFECTS) return;
duButton.screenX = e.screenX - e.clientX;
duButton.screenY = e.screenY - e.clientY;
if (e.shiftKey) duButton.shiftClicked();
else if (e.altKey && e.ctrlKey) duButton.ctrlAltClicked();
else if (e.altKey) duButton.altClicked();
else if (e.ctrlKey) duButton.ctrlClicked();
else duButton.clicked();
}
mainGroup.addEventListener('click', clickEvent, true);
mainGroup.addEventListener("mouseover", duButton.highlight);
duButton.dim();
return duButton;
}
/**
* @class
* @name DuSmallButton
* @classdesc For use with {@link DuScriptUI}.<br />
* An Small Button.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.smallbutton} to create a Small Button.<br />
* The Small Button inherits the Group object from ScriptUI and has all of its properties and methods.
* @property {string} helpLink - A URL to a help page, shown on shift click on the button.
* @property {StaticText} label - The label
* @property {any} value - A user value stored in the button, which is passed to the onClick method
* @property {DuSmallButton~onClick} onClick - The function to execute when the button is clicked
* @category DuScriptUI
*/
/**
* The function to execute when the button is clicked.<br />
* The implementation of this function can take one parameter which is the used defined value of the button.
* @callback DuSmallButton~onClick
* @memberof DuSmallButton
* @param {any} value - The used defined value of the button
*/
/**
* Creates a small button.
* @param {Panel|Window|Group} container - The ScriptUI Object which will contain and display the button.
* @param {string} text - The label of the button. Default: empty string
* @param {string} [helpTip] - The helptip. Default: empty string
* @param {any} [value] - A user value stored in the button, which is passed to the onClick method
* @return {DuButton} The image button created.
*/
DuScriptUI.smallbutton = function(container, text, helpTip, value) {
if (!isdef( helpTip )) helpTip = '';
var smallButton = container.add('group');
smallButton.orientation = 'row';
smallButton.margins = 0;
smallButton.alignment = ['fill', 'top'];
smallButton.alignChildren = ['fill', 'fill'];
smallButton.spacing = 0;
smallButton.onClick = function( v ) {};
smallButton.onAltClick = smallButton.onClick;
smallButton.onCtrlAltClick = smallButton.onClick;
smallButton.onCtrlClick = smallButton.onClick;
smallButton.label = smallButton.add('statictext', undefined, text);
smallButton.label.helpTip = helpTip;
smallButton.label.justify = 'center';
smallButton.value = value;
smallButton.helpLink = '';
//events
smallButton.clicked = function() {
smallButton.onClick( smallButton.value );
}
smallButton.shiftClicked = function() {
if (duButton.helpLink != '') {
DuSystem.openURL(smallButton.helpLink);
}
}
smallButton.altClicked = function() {
smallButton.onAltClick( smallButton.value );
}
smallButton.ctrlAltClicked = function() {
smallButton.onCtrlAltClick( smallButton.value );
}
smallButton.ctrlClicked = function() {
smallButton.onCtrlClick( smallButton.value );
}
smallButton.highlight = function(e) {
e.stopPropagation();
DuScriptUI.dimControls();
if (smallButton.label) DuScriptUI.setTextColor(smallButton.label, DuColor.Color.VERY_DARK_GREY);
DuScriptUI.setBackgroundColor(smallButton, DuColor.Color.LIGHT_GREY);
DuScriptUI.highlightedControls.push(smallButton);
}
smallButton.dim = function() {
if (smallButton.label) DuScriptUI.setTextColor(smallButton.label, DuColor.Color.APP_TEXT_COLOR);
DuScriptUI.setBackgroundColor(smallButton, DuColor.Color.TRANSPARENT);
}
//add events
smallButton.addEventListener('click', function(e) {
if (e.shiftKey) smallButton.shiftClicked();
else if (e.altKey && e.ctrlKey) smallButton.ctrlAltClicked();
else if (e.altKey) smallButton.altClicked();
else if (e.ctrlKey) smallButton.ctrlClicked();
else smallButton.clicked();
}, true);
smallButton.addEventListener("mouseover", smallButton.highlight);
if (smallButton.label) smallButton.label.addEventListener("mouseover", smallButton.highlight);
return smallButton;
}
// ==================== |------| ====================
// ==================== | help | ====================
// ==================== |------| ====================
/**
* Creates a button displaying the version of the script and redirecting to the about url.
* @param {Panel|Window|Group} container - The ScriptUI Object which will contain and display the button.
* @param {string|DuBinary} [image=DuESF.scriptIcon] - The path to the icon or a png binstring. Default: empty string
* @return {DuButton} The version button created.
*/
DuScriptUI.versionButton = function( container, image )
{
image = def(image, DuESF.scriptIcon);
var button = DuScriptUI.button(
container,
'v' + DuESF.scriptVersion.fullVersion,
image,
DuESF.companyName + '\n' + DuESF.scriptName + ' v' + DuESF.scriptVersion.fullVersion,
false,
undefined,
undefined,
false
)
//button.bgColor = DuColor.Color.TRANSPARENT;
button.dim();
if (DuESF.aboutURL != '') button.onClick = function() {
DuSystem.openURL(DuESF.aboutURL);
};
return button;
}
/**
* Creates a button opening the bug report url.
* @param {Panel|Window|Group} container - The ScriptUI Object which will contain and display the button.
* @param {bool} [showLabel=false] - When true, the button includes a "Bug Report" label.
* @return {DuButton} The bug button created.
*/
DuScriptUI.addBugButton = function( container, showLabel )
{
showLabel = def(showLabel, false);
var label = i18n._("Bug Report or Feature Request");
var helpTip = i18n._("Bug report\nFeature request\n\nTell us what's wrong or request a new feature.");
var button;
if (container.addButton)
{
button = container.addButton(
label,
w12_bugreport,
helpTip
);
}
else
{
button = DuScriptUI.button(
container,
showLabel ? label : "",
w12_bugreport,
helpTip
);
}
button.onClick = function ()
{
DuSystem.openURL( DuESF.bugReportURL );
}
button.onAltClick = function ()
{
DuSystem.openURL( 'http://bat-ultra-bunny.tumblr.com' );
}
return button;
}
/**
* Creates a button opening the help panel.
* @param {Panel|Window|Group} container - The ScriptUI Object which will contain and display the button.
* @param {bool} [showLabel=false] - When true, the button includes a "Help" label.
* @return {DuButton} The help button created.
*/
DuScriptUI.addHelpButton = function( container, showLabel )
{
showLabel = def(showLabel, false);
var label = i18n._("Help");
var helpTip = i18n._("Get help.");
var button;
if (container.addButton)
{
button = container.addButton(
label,
w12_help,
helpTip
);
}
else
{
button = DuScriptUI.button(
container,
showLabel ? label : "",
w12_help,
helpTip
);
}
button.onClick = function ()
{
DuSystem.openURL( DuESF.docURL );
}
button.onAltClick = function ()
{
DuSystem.openURL( 'http://bat-ultra-bunny.tumblr.com' );
}
return button;
}
/**
* Creates a button opening the help panel.
* @param {Panel|Window|Group} container - The ScriptUI Object which will contain and display the button.
* @param {bool} [showLabel=false] - When true, the button includes a "Help" label.
* @return {DuButton} The help button created.
*/
DuScriptUI.addTranslateButton = function( container, showLabel )
{
showLabel = def(showLabel, false);
var label = i18n._("Translate %1", DuESF.scriptName);
var helpTip = i18n._("Help translating %1 to make it available to more people.", DuESF.scriptName);
var button;
if (container.addButton)
{
button = container.addButton(
label,
w12_language,
helpTip
);
}
else
{
button = DuScriptUI.button(
container,
showLabel ? label : "",
w12_language,
helpTip
);
}
button.onClick = function ()
{
DuSystem.openURL( DuESF.translateURL );
}
button.onAltClick = function ()
{
DuSystem.openURL( 'http://bat-ultra-bunny.tumblr.com' );
}
return button;
}
/**
* Creates a button opening the link for like/follow/donation
* @param {Panel|Window|Group} container - The ScriptUI Object which will contain and display the button.
* @param {bool} [showLabel=false] - When true, the button includes a <code>"I ♥ " + DuESF.scriptName</code> label.
* @return {DuButton} The like button created.
*/
DuScriptUI.addDonateButton = function ( container, showLabel )
{
showLabel = def(showLabel, false);
var defaultLabel = "I \u2665 " + DuESF.scriptName;
var button = DuScriptUI.button(
container,
{
text: showLabel ? defaultLabel : "",
image: w12_heart,
helpTip: defaultLabel,
small: true
}
);
DuScriptUI.fundingLabel = button.label;
button.onClick = function ()
{
DuSystem.openURL( DuESF.donateURL );
}
button.onAltClick = function ()
{
DuSystem.openURL( 'http://patreon.com/duduf' );
}
DuScriptUI.updateFundingInfo();
return button;
}
/**
* Displays a prompt to select the language of the script.<br />
* Won't do nothing if the script already has a language set in the settings.<br />
* Use this method before launching the script.
* @param {function} callback - The function to execute when the user has chosen the language.<br />
* This function should be the one which loads the script.
* @param {Panel|Window} [ui] - A container to display the UI. A modal Dialog is created if omitted
*/
DuScriptUI.askLanguage = function ( callback, ui )
{
if (! (ui instanceof Panel || ui instanceof Window) )
{
ui = new Window('dialog', "Language")
}
var currentLanguageName = DuESF.scriptSettings.get("common/currentLanguageName", '');
var currentLocale = DuESF.scriptSettings.get("common/language", '');
var languages = i18n.getAvailableLanguages();
if (currentLocale != '' || languages.count == 0)
{
callback()
return;
}
var ui_languageGroup = DuScriptUI.group( ui, 'column' );
ui_languageGroup.add('statictext', undefined, "EO: Selektu la lingvo kiu vi volas uzi per tio softvaro.", { multiline: true } );
ui_languageGroup.add('statictext', undefined, "EN: Select the language you wish to use with this software.", { multiline: true } );
var currentIndex = 0;
var languageSelector = DuScriptUI.selector( ui_languageGroup, "Set the language of the interface." );
var i = 0;
for (var locale in languages)
{
if (locale == "count") continue;
languageSelector.addButton( {
text: languages[locale],
image: w16_language,
data: locale
});
if (locale == 'eo') currentIndex = i;
i++;
}
languageSelector.setCurrentIndex( currentIndex );
var okButton = DuScriptUI.button( ui_languageGroup, {
text: "Apply",
image: w12_check,
helpTip: "Apply changes to the settings.",
alignment: 'center'
});
okButton.onClick = function()
{
// Save settings
DuESF.scriptSettings.set("common/language", languageSelector.currentData );
DuESF.scriptSettings.set("common/currentLanguageName", languageSelector.text);
i18n.setLocale( languageSelector.currentData, true );
DuESF.scriptSettings.save();
// Delete
ui.remove( ui_languageGroup );
callback();
}
DuScriptUI.showUI( ui, false );
DuScriptUI.uiShown = false;
}
/**
* Checks if the script can be updated
* @param {function} [callback] - The function to execute when the user has clicked on the "dismiss" button.
* @param {Panel|Window} [ui] - A container to display the UI. A modal Dialog is created if omitted
* @param {Boolean} [showAlert] Whether to show an alert if the check failed or if the version is up-to-date.
*/
DuScriptUI.checkUpdate = function ( callback, ui, showAlert )
{
var noCallback = false;
if (!isdef( callback ))
{
callback = function(){};
var noCallback = true;
}
showAlert = def(showAlert, false);
//if (DuESF.debug) showAlert = true;
//Just once a day
var latestUpdateCheck = DuESF.scriptSettings.get("common/latestUpdateCheck", 0);
var now = Date.now();
// 24h
if (now - latestUpdateCheck < 86400000 && !showAlert && !DuESF.debug) {
// Just update funding info
DuScriptUI.updateFundingInfo();
return callback();
}
DuESF.scriptSettings.set("common/latestUpdateCheck", now);
DuESF.scriptSettings.save();
if ( DuESF.rxVersionURL == '' ) return callback();
// Get the version & info from the server
var update = DuVersion.checkUpdate();
// Compare
if (!update)
{
if (noCallback) return;
return callback();
}
if (!update.success)
{
if (showAlert) alert("Sorry, something failed...\n\n" + update.message);
if (noCallback) return;
return callback();
}
// Update funding info
if (update.monthlyFund && update.fundingGoal) {
DuScriptUI.updateFundingInfo(update.monthlyFund, update.fundingGoal);
}
if (!update.update)
{
if (showAlert) alert("This version is up-to-date, congratulations!");
if (noCallback) return;
return callback();
}
// Show window if update
var reuseUI = true;
if (! (ui instanceof Panel || ui instanceof Window) )
{
reuseUI = false;
ui = new Window('dialog',i18n._("New version"),undefined,
{
resizeable: true
} );
}
var ui_updateGroup = DuScriptUI.group( ui, 'column' );
ui_updateGroup.alignment = ['fill', 'fill'];
var titleGroup = DuScriptUI.group( ui_updateGroup );
titleGroup.alignment = ['fill', 'top'];
DuScriptUI.setBackgroundColor( titleGroup, DuColor.Color.DARK_GREY );
DuScriptUI.staticText(
titleGroup,
DuString.args( "New {#}!", [ DuESF.scriptName ] ),
DuColor.Color.APP_TEXT_COLOR.lighter(130)
).alignment = ['center','top'];
var newVersionGroup = DuScriptUI.group( ui_updateGroup );
newVersionGroup.alignment = ['fill', 'top'];
newVersionGroup.margins = 2;
DuScriptUI.staticText(
newVersionGroup,
"New version" + ": "
);
DuScriptUI.staticText(
newVersionGroup,
update.version,
DuColor.Color.APP_TEXT_COLOR.lighter(115)
);
var descriptionGroup = DuScriptUI.group( ui_updateGroup );
descriptionGroup.margins = 2;
descriptionGroup.alignment = ['fill','fill'];
DuScriptUI.setBackgroundColor( descriptionGroup, DuColor.Color.OBSIDIAN );
var descriptionText = descriptionGroup.add("edittext", undefined, update.description, {multiline:true});
descriptionText.alignment = ['fill','fill'];
descriptionText.minimumSize = [-1,100];
DuScriptUI.setBackgroundColor( descriptionText, DuColor.Color.OBSIDIAN );
DuScriptUI.staticText(
ui_updateGroup,
"Current version: " + DuESF.scriptVersion.fullVersion,
DuColor.Color.APP_TEXT_COLOR.darker(160)
).alignment = ['fill', 'bottom'];
if (update.downloadURL)
{
var downloadButton = DuScriptUI.button(
ui_updateGroup,
"Download",
DuScriptUI.Icon.DOWNLOAD,
"Download the new version now."
);
downloadButton.alignment = ['fill','bottom'];
downloadButton.onClick = function() {
DuSystem.openURL( update.downloadURL );
};
}
if (update.changelogURL)
{
var changelogButton = DuScriptUI.button(
ui_updateGroup,
"Changelog",
DuScriptUI.Icon.LIST,
"Read all the details about what's changed."
);
changelogButton.alignment = ['fill','bottom'];
changelogButton.onClick = function() {
DuSystem.openURL( update.changelogURL );
};
}
if (update.donateURL)
{
var donateButton = DuScriptUI.button(
ui_updateGroup,
"I \u2665 " + DuESF.scriptName,
DuScriptUI.Icon.HEART,
"We need you!\nMake a donation to help us continue to release and update free software."
);
donateButton.alignment = ['fill','bottom'];
donateButton.onClick = function()
{
DuSystem.openURL( update.donateURL );
};
donateButton.onAltClick = function ()
{
DuSystem.openURL( 'http://patreon.com/duduf' );
}
}
var validButton = DuScriptUI.button(
ui_updateGroup,
noCallback ? "Close" : DuString.args("Update later"),
noCallback ? DuScriptUI.Icon.CLOSE : DuScriptUI.Icon.NEXT,
noCallback ? "Close" : DuString.args("Continue to {#}", [DuESF.scriptName])
);
validButton.onClick = function()
{
// Delete or hide ui
if (reuseUI) ui.remove( ui_updateGroup );
else ui.close();
if (!noCallback) callback();
}
DuScriptUI.showUI( ui, false );
DuScriptUI.uiShown = false;
}
DuScriptUI.updateFundingInfo = function (fund, goal) {
fund = def(fund, DuESF.scriptSettings.get("funding/monthlyFund", 0));
goal = def(goal, DuESF.scriptSettings.get("funding/fundingGoal", 4000));
if (fund) {
fund = parseFloat(fund);
DuESF.scriptSettings.set("funding/monthlyFund", fund);
}
if (goal) {
goal = parseFloat(goal);
DuESF.scriptSettings.set("funding/fundingGoal", goal);
}
DuESF.scriptSettings.save();
if (!fund || !goal && DuScriptUI.fundingBar) {
DuScriptUI.fundingBar.value = 0;
DuScriptUI.fundingBar.helpTip = i18n._("Thank you for your donations!");
return;
}
var ratio = Math.round( fund / goal * 100);
var helpTip = i18n._("Thank you for your donations!") + '\n\n' +
i18n._( "This month, the %1 fund is $%2.\nThat's %3% of our monthly goal ( $%4 )\n\nClick on this button to join the development fund!", DuESF.companyName, fund, ratio, goal );
if (DuScriptUI.fundingBar) {
DuScriptUI.fundingBar.value = ratio;
fund = Math.round( fund );
DuScriptUI.fundingBar.helpTip = helpTip
}
if (DuScriptUI.fundingLabel) {
DuScriptUI.fundingLabel.helpTip = helpTip;
DuScriptUI.fundingLabel.text = ratio + '%';
}
}
// ==================== |-------| ====================
// ==================== | style | ====================
// ==================== |-------| ====================
/**
* Resets the layout of the whole ui containing a scriptUI item.
* @param {ScriptUI} item - The ScriptUI element which needs to be resized
* @param {Boolean} [force] - Needs to be true if you need to layout before <code>DuESF.state</code> is <code>DuESF.State.RUNTIME</code>.<br />
* That should be the case only for the main UI; Note that you should not need it anyway, {@link DuScriptUI.showUI} does that for you.
*/
DuScriptUI.layout = function( item, force )
{
force = def( force, false );
if (DuESF.state != DuESF.State.RUNTIME && !force) return;
item.layout.layout( true );
item.layout.resize();
}
/**
* Changes the color of the text of a ScriptUI Object
* @param {ScriptUI} text - The ScriptUI Object
* @param {DuColor} color - The new color
* @param {Boolean} [adjusted=false] - lightens the color if the brightness setting of Ae is not set on the darkest one
*/
DuScriptUI.setTextColor = function( text, color, adjusted )
{
if ( !isdef( text ) ) throw "You must provide a ScriptUI Control to change the color of the text";
adjusted = def( adjusted, false );
var g = text.graphics;
var c;
if (adjusted) c = color.adjusted();
else c = color;
var textPen = g.newPen( g.PenType.SOLID_COLOR, c.floatRGBA(true), 1 );
g.foregroundColor = textPen;
}
/**
* Changes the color of the background of a ScriptUI Object
* @param {ScriptUI} uiItem - The ScriptUI Object
* @param {Array} color - The new color [R,V,B,A] Array
* @param {Boolean} [adjusted=true] - lightens the color if the brightness setting of Ae is not set on the darkest one
*/
DuScriptUI.setBackgroundColor = function( uiItem, color, adjusted )
{
if ( !isdef( uiItem ) ) throw "You must provide a ScriptUI Control to change the color of the background";
adjusted = def( adjusted, true );
var g = uiItem.graphics;
var c;
if (adjusted) c = color.adjusted();
else c = color;
var brush = g.newBrush( g.BrushType.SOLID_COLOR, c.floatRGBA(true) );
g.backgroundColor = brush;
}
// ==================== |--------| ====================
// ==================== | layout | ====================
// ==================== |--------| ====================
/**
* Adds a group in a container, using DuScriptUI default alignments, and DuScriptUI.defaultSpacing. Margins are set to 0.
* @param {Panel|Window|Group} container Where to create the group
* @param {string} [orientation] The orientation to use. One of "column", "row" or "stack". By default, "column" if added in a row, "row" if added in a column.
* @return {Group} The group created
*/
DuScriptUI.group = function( container, orientation )
{
var group = container.add( "group" );
group.spacing = 0;
group.margins = 0;
if ( orientation !== undefined ) group.orientation = orientation;
if ( group.orientation === "row" )
{
group.alignChildren = DuScriptUI.defaultRowAlignment;
}
else if ( group.orientation === "column" )
{
group.alignChildren = DuScriptUI.defaultColumnAlignment;
}
else
{
group.alignChildren = DuScriptUI.defaultStackAlignment;
}
group.addEventListener( "mouseover", DuScriptUI.dimControls );
return group;
}
/**
* @class
* @name DuSeparator
* @classdesc For use with {@link DuScriptUI}.<br />
* A DuSeparator.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.separator} to create a DuSeparator.<br />
* The DuSeparator inherits the Group object from ScriptUI and has all of its properties and methods.
* @property {StaticText|CheckBox} label - the label
* @property {boolean} checkable - Read-Only | true if a checkbox is displayed
* @category DuScriptUI
*/
/**
* Adds separator with an optionnal name in the group
* @memberof DuScriptUI
* @param {Panel|Window|Group} container - Where to create the separator
* @param {string} [name] - The name displayed
* @param {boolean} [checkable=false] - When true, adds a checkbox to the separator
* @param {boolean} [drawLine=true] - Draws a line when there is no name. When false, the separator is an empty space
* @param {boolean} [translatable=true] - If false, the name won't be translated
* @return {DuSeparator} The separator
*/
DuScriptUI.separator = function( container, name, checkable, drawLine )
{
name = def(name, '' );
checkable = def( checkable, false );
drawLine = def( drawLine, true );
var separator = DuScriptUI.group( container );
separator.margins = 0;
separator.checkable = checkable;
separator.label = null;
if ( name != '' || checkable )
{
if ( checkable ) separator.label = separator.add( 'checkbox', undefined, name );
else separator.label = separator.add( 'statictext', undefined, name );
separator.label.alignment = [ 'center', 'bottom' ];
if ( drawLine ) DuScriptUI.setBackgroundColor( separator, DuColor.Color.DARK_GREY );
}
else if ( drawLine )
{
DuScriptUI.setBackgroundColor( separator, DuColor.Color.ABYSS_GREY );
var size = 1;
if (DuESF.host == DuESF.HostApplication.AFTER_EFFECTS) size = 2;
if (container.orientation == 'row')
{
separator.alignment = ['left', 'fill'];
separator.minimumSize = [size,-1];
}
else
{
separator.alignment = [ 'fill', 'top' ];
separator.minimumSize = [-1,size];
}
}
separator.addEventListener( "mouseover", DuScriptUI.dimControls );
return separator;
}
// ==================== |----------| ====================
// ==================== | checkbox | ====================
// ==================== |----------| ====================
/**
* @class
* @name DuCheckBox
* @classdesc For use with {@link DuScriptUI}.<br />
* An Image Checkbox.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.checkBox} to create an Image Checkbox.<br />
* The Image Checkbox inherits the Group object from ScriptUI and has all of its properties and methods.
* @property {Image} image The scriptui object representing the image, if any
* @property {StaticText} label The label, if any
* @property {string} text The current text
* @property {string} textChecked The text to display when the checkbox is checked
* @property {string} defaultText The text to display when the checkbox is unchecked
* @property {boolean} checked The checked state of the button
* @property {DuColor} textColor The color of the text of the label
* @property {DuCheckBox~onClick} onClick The function to execute when the button is clicked
* @category DuScriptUI
*/
/**
* The function to execute when the button is clicked.
* @callback DuCheckBox~onClick
* @memberof DuCheckBox
*/
/**
* Sets the checked state of the button
* @method
* @memberof DuCheckBox
* @name setChecked
* @param {boolean} [checked=true] - The state
*/
/**
* Creates a checkbox with an optionnal icon. Must have at least an icon or a text, or both.
* @param {Panel|Window|Group} container The ScriptUI Object which will contain and display the button.
* @param {string} text The label of the button. Default: empty string
* @param {string|DuBinary} [image] The path to the icon. Default: empty string
* @param {string} [helpTip=''] The helptip. Default: empty string
* @param {string} [textChecked=text] The label of the button displayed when it is checked.
* @param {string|DuBinary} [imageChecked] The image to show when it is checked
* @param {string} [orientation='row'] The orientation
* @return {DuCheckBox} The checkbox created.
*/
DuScriptUI.checkBox = function( container, text, image, helpTip, textChecked, imageChecked, orientation, isTab )
{
var options = {};
text = def(text, '');
if (jstype(text) != 'string') options = text;
options.text = def(options.text, text);
isTab = def(isTab, false);
options.isTab = def(options.isTab, isTab);
orientation = def(orientation, 'row');
options.orientation = def(options.orientation, orientation);
textChecked = def(textChecked, options.text);
options.textChecked = def(options.textChecked, textChecked);
image = def(image, '');
options.image = def(options.image, image);
imageChecked = def(imageChecked, options.image);
options.imageChecked = def(options.imageChecked, imageChecked);
helpTip = def(helpTip, '');
options.helpTip = def(options.helpTip, helpTip);
if ( !container ) return null;
if (options.isTab || options.orientation == 'column')
{
var commonSettings = def( DuESF.scriptSettings.data.common, {} );
var uiMode = def(commonSettings.uiMode, 0);
if (uiMode >= 2 && options.image != '') text = '';
}
//create ui
var duCheckBox = container.add( 'group' );
duCheckBox.orientation = 'column';
if (options.isTab || options.orientation == 'column') duCheckBox.alignment = ['left', 'fill'];
else duCheckBox.alignment = [ 'fill', 'top' ];
if (options.isTab || options.text == '' || options.orientation == 'column' ) duCheckBox.hasIcon = false;
else duCheckBox.hasIcon = true;
duCheckBox.hasImage = options.image != '';
duCheckBox.hasLabel = options.text != '';
duCheckBox.imageOnly = options.image == options.imageChecked && options.text == '';
//if no options.image and no options.text, at least the icon
if ( options.image == '' && options.text == '' ) duCheckBox.hasIcon = true;
// attributes
duCheckBox.onClick = function() {};
duCheckBox.onAltClick = function() {};
duCheckBox.checked = duCheckBox.value = false;
duCheckBox.textChecked = options.textChecked;
duCheckBox.defaultText = options.text;
duCheckBox.text = options.text;
duCheckBox.helpLink = '';
duCheckBox.textColor = DuColor.Color.APP_TEXT_COLOR;
duCheckBox.mainGroup = DuScriptUI.group(duCheckBox, 'column');
duCheckBox.mainGroup.margins = 1;
duCheckBox.mainGroup.alignment = ['fill', 'fill'];
duCheckBox.buttonGroup = DuScriptUI.group(duCheckBox.mainGroup, options.orientation);
duCheckBox.buttonGroup.alignment = ['fill', 'top'];
//the checkbox icon & options.image group
duCheckBox.imageGroup = duCheckBox.buttonGroup.add('group');
duCheckBox.imageGroup.margins = 0;
duCheckBox.imageGroup.spacing = 4;
if ( options.text == '' || options.orientation == 'column' ) duCheckBox.imageGroup.alignment = [ 'center', 'center' ];
else duCheckBox.imageGroup.alignment = [ 'left', 'center' ];
if (duCheckBox.hasIcon)
{
var subgroup = duCheckBox.imageGroup.add('group');
subgroup.orientation = 'stacked';
subgroup.margins = 0;
subgroup.spacing = 0;
subgroup.size = [12,12];
duCheckBox.checkboxIcon = subgroup.add( 'image', undefined, DuScriptUI.Icon.BOX_UNCHECKED.binAsString );
duCheckBox.checkboxIcon.helpTip = options.helpTip;
duCheckBox.checkboxIconChecked = subgroup.add( 'image', undefined, DuScriptUI.Icon.BOX_CHECKED.binAsString );
duCheckBox.checkboxIconChecked.helpTip = options.helpTip;
duCheckBox.checkboxIconChecked.visible = false;
}
//checkbox icon
if ( duCheckBox.hasImage )
{
var subgroup = duCheckBox.imageGroup.add('group');
subgroup.orientation = 'stacked';
subgroup.margins = 2;
subgroup.spacing = 0;
subgroup.minimumSize = [20,20];
if ( options.image instanceof DuBinary ) options.image = options.image.binAsString;
if ( options.imageChecked instanceof DuBinary ) options.imageChecked = options.imageChecked.binAsString;
if (options.imageChecked != '')
{
duCheckBox.imageChecked = subgroup.add( 'image', undefined, options.imageChecked );
duCheckBox.imageChecked.helpTip = options.helpTip;
duCheckBox.imageChecked.visible = false;
duCheckBox.imageChecked.alignment = ['center', 'center'];
}
duCheckBox.image = subgroup.add( 'image', undefined, options.image);
duCheckBox.image.helpTip = options.helpTip;
duCheckBox.image.alignment = ['center', 'center'];
}
//Add fillers to be able to click anywhere on the button
if ( duCheckBox.hasLabel && !options.isTab && options.orientation != 'column')
{
duCheckBox.fillerM = duCheckBox.buttonGroup.add( 'statictext', undefined, " " );
duCheckBox.fillerM.alignment = [ 'left', 'fill' ];
duCheckBox.fillerM.size = [ 7, -1];
}
// label
if ( duCheckBox.hasLabel )
{
var labelGroup = duCheckBox.buttonGroup.add('group');
labelGroup.orientation = 'stacked';
labelGroup.margins = 0;
if (options.orientation == 'column') labelGroup.alignment = [ 'center', 'top' ];
else labelGroup.alignment = [ 'left', 'fill' ];
duCheckBox.label = labelGroup.add( 'statictext', undefined, options.text );
duCheckBox.label.helpTip = options.helpTip;
if (options.orientation == 'column') duCheckBox.label.alignment = [ 'center', 'top' ];
else duCheckBox.label.alignment = [ 'left', 'fill' ];
DuScriptUI.setTextColor( duCheckBox.label, DuColor.Color.APP_TEXT_COLOR );
duCheckBox.labelChecked = labelGroup.add( 'statictext', undefined, options.textChecked );
duCheckBox.labelChecked.helpTip = options.helpTip;
duCheckBox.labelChecked.visible = false;
if (options.orientation == 'column') duCheckBox.labelChecked.alignment = [ 'center', 'top' ];
else duCheckBox.labelChecked.alignment = [ 'left', 'fill' ];
DuScriptUI.setTextColor( duCheckBox.labelChecked, DuColor.Color.APP_HIGHLIGHT_COLOR );
}
//Add fillers to be able to click anywhere on the button
if ( duCheckBox.hasLabel && !options.isTab && options.orientation != 'column' )
{
duCheckBox.fillerR = duCheckBox.buttonGroup.add( 'statictext', undefined, "" );
duCheckBox.fillerR.alignment = [ 'fill', 'fill' ];
}
// outline
if ( options.isTab )
{
duCheckBox.outLine = duCheckBox.mainGroup.add( 'group' );
duCheckBox.outLine.margins = 0;
duCheckBox.outLine.minimumSize.height = 2;
duCheckBox.outLine.alignment = [ 'fill', 'bottom' ];
}
duCheckBox.setChecked = function( c )
{
c = def(c, true);
duCheckBox.checked = duCheckBox.value = c;
if ( c )
{
if (options.isTab) DuScriptUI.setBackgroundColor( duCheckBox.outLine, DuColor.Color.APP_TEXT_COLOR );
else
{
//icon
if ( duCheckBox.hasIcon )
{
duCheckBox.checkboxIconChecked.visible = true;
duCheckBox.checkboxIcon.visible = false;
}
//image
if ( duCheckBox.hasImage )
{
duCheckBox.imageChecked.visible = true;
duCheckBox.image.visible = false;
if (duCheckBox.imageOnly)
{
DuScriptUI.setBackgroundColor( duCheckBox.imageGroup, DuColor.Color.APP_HIGHLIGHT_COLOR.darker() );
}
}
//label
if ( duCheckBox.hasLabel )
{
duCheckBox.label.visible = false;
duCheckBox.labelChecked.visible = true;
duCheckBox.text = duCheckBox.textChecked;
DuScriptUI.setTextColor( duCheckBox.label, DuColor.Color.APP_HIGHLIGHT_COLOR );
}
}
}
else
{
if (options.isTab) DuScriptUI.setBackgroundColor( duCheckBox.outLine, DuColor.Color.TRANSPARENT );
else
{
//icon
if ( duCheckBox.hasIcon )
{
duCheckBox.checkboxIconChecked.visible = false;
duCheckBox.checkboxIcon.visible = true;
}
//image
if ( duCheckBox.hasImage )
{
duCheckBox.imageChecked.visible = false;
duCheckBox.image.visible = true;
if (!duCheckBox.hasIcon && !duCheckBox.hasLabel) DuScriptUI.setBackgroundColor( duCheckBox.imageGroup, DuColor.Color.TRANSPARENT );
}
//label
if ( duCheckBox.hasLabel )
{
duCheckBox.label.visible = true;
duCheckBox.labelChecked.visible = false;
duCheckBox.text = duCheckBox.defaultText;
DuScriptUI.setTextColor( duCheckBox.label, DuColor.Color.APP_TEXT_COLOR );
}
}
}
}
duCheckBox.clicked = function( e )
{
e.stopPropagation();
if ( e.shiftKey )
{
if ( duCheckBox.helpLink != '' )
{
DuSystem.openURL( duCheckBox.helpLink );
}
return;
}
duCheckBox.setChecked( !duCheckBox.checked );
DuDebug.safeRun(duCheckBox.onClick);
}
duCheckBox.altClicked = function()
{
DuDebug.safeRun(duCheckBox.onAltClick);
}
duCheckBox.highlight = function( e )
{
e.stopPropagation();
DuScriptUI.dimControls();
if ( duCheckBox.checked )
{
if (duCheckBox.hasLabel && !options.isTab) DuScriptUI.setTextColor( duCheckBox.labelChecked, DuColor.Color.APP_HIGHLIGHT_COLOR.lighter(150) );
if (!duCheckBox.hasLabel && !options.isTab) DuScriptUI.setBackgroundColor( duCheckBox.buttonGroup, DuColor.Color.APP_HIGHLIGHT_COLOR );
// image
if (!options.isTab && duCheckBox.imageOnly) DuScriptUI.setBackgroundColor( duCheckBox.imageGroup, DuColor.Color.APP_HIGHLIGHT_COLOR );
//outline
if ( options.isTab )
{
DuScriptUI.setBackgroundColor( duCheckBox.outLine, DuColor.Color.APP_HIGHLIGHT_COLOR );
if (duCheckBox.hasLabel) DuScriptUI.setTextColor( duCheckBox.label, DuColor.Color.APP_HIGHLIGHT_COLOR );
}
}
else
{
if (duCheckBox.hasLabel && !options.isTab) DuScriptUI.setTextColor( duCheckBox.label, DuColor.Color.APP_HIGHLIGHT_COLOR );
if (!duCheckBox.hasLabel && !options.isTab) DuScriptUI.setBackgroundColor( duCheckBox.buttonGroup, DuColor.Color.APP_HIGHLIGHT_COLOR.darker() );
// image
if (!options.isTab && duCheckBox.imageOnly) DuScriptUI.setBackgroundColor( duCheckBox.imageGroup, DuColor.Color.APP_HIGHLIGHT_COLOR.darker() );
//outline
if (options.isTab )
{
DuScriptUI.setBackgroundColor( duCheckBox.outLine, DuColor.Color.APP_HIGHLIGHT_COLOR );
if (duCheckBox.hasLabel) DuScriptUI.setTextColor( duCheckBox.label, DuColor.Color.APP_HIGHLIGHT_COLOR );
}
}
DuScriptUI.highlightedControls.push( duCheckBox );
}
duCheckBox.dim = function( e )
{
if ( duCheckBox.checked )
{
if (duCheckBox.hasLabel && !options.isTab) DuScriptUI.setTextColor( duCheckBox.labelChecked, DuColor.Color.APP_HIGHLIGHT_COLOR );
if (!duCheckBox.hasLabel && !options.isTab) DuScriptUI.setBackgroundColor( duCheckBox.buttonGroup, DuColor.Color.TRANSPARENT );
// image
if (!options.isTab && duCheckBox.imageOnly) DuScriptUI.setBackgroundColor( duCheckBox.imageGroup, DuColor.Color.APP_HIGHLIGHT_COLOR.darker() );
//outline
if ( options.isTab )
{
DuScriptUI.setBackgroundColor( duCheckBox.outLine, DuColor.Color.APP_TEXT_COLOR );
if (duCheckBox.hasLabel) DuScriptUI.setTextColor( duCheckBox.label, duCheckBox.textColor );
}
}
else
{
if (duCheckBox.hasLabel && !options.isTab) DuScriptUI.setTextColor( duCheckBox.label, duCheckBox.textColor );
if (!duCheckBox.hasLabel && !options.isTab) DuScriptUI.setBackgroundColor( duCheckBox.buttonGroup, DuColor.Color.TRANSPARENT );
// image
if (!options.isTab && duCheckBox.imageOnly) DuScriptUI.setBackgroundColor( duCheckBox.imageGroup, DuColor.Color.TRANSPARENT );
//outline
if ( options.isTab )
{
DuScriptUI.setBackgroundColor( duCheckBox.outLine, DuColor.Color.TRANSPARENT );
if (duCheckBox.hasLabel) DuScriptUI.setTextColor( duCheckBox.label, duCheckBox.textColor );
}
}
}
duCheckBox.addEventListener( "click", function(e)
{
if(e.view.parent != duCheckBox) return;
if ( e.altKey ) duCheckBox.altClicked();
else duCheckBox.clicked(e);
}, true );
duCheckBox.addEventListener( "mouseover", duCheckBox.highlight );
duCheckBox.buttonGroup.addEventListener( "mouseover", duCheckBox.highlight );
duCheckBox.mainGroup.addEventListener( "mouseover", duCheckBox.highlight );
if(duCheckBox.label) duCheckBox.label.addEventListener( "mouseover", duCheckBox.highlight );
if(duCheckBox.checkBox) duCheckBox.imageChecked.addEventListener( "mouseover", duCheckBox.highlight );
if(duCheckBox.image) duCheckBox.image.addEventListener( "mouseover", duCheckBox.highlight );
duCheckBox.dim();
return duCheckBox;
}
// ==================== |--------| ====================
// ==================== | screen | ====================
// ==================== |--------| ====================
/**
* Moves the coordinates so that the size completely fits inside an existing screen
* @param {int[]} location The coordinates [top, left]
* @param {Dimension} size The rectangle size, an object which has two properties: width and height
* @return {int[]} the new location
*/
DuScriptUI.moveInsideScreen = function (location, size)
{
var screen = DuScriptUI.getClosestScreen(location);
var result = location;
var left = location[0];
var right = location[0] + size.width;
var top = location[1];
var bottom = location[1] + size.height;
// if outside on the left
if (screen.left > location[0]) result[0] += ( screen.left - left );
if (screen.right < right) result[0] += ( screen.right - right );
if (screen.top > top) result[1] += ( screen.top - top );
if (screen.bottom < bottom) result[1] += ( screen.bottom - bottom );
return result;
}
/**
* Centers the coordinates in their screen
* @param {int[]} location The coordinates [top, left]
* @param {Dimension} size The rectangle size, an object which has two properties: width and height
* @return {int[]} the new location
*/
DuScriptUI.centerInScreen = function (location, size)
{
var screen = DuScriptUI.getClosestScreen(location);
if (!screen) return location;
var x = (screen.left + screen.right) / 2;
var y = (screen.top + screen.bottom) / 2;
// offset
x -= size[0] / 2;
y -= size[1] / 2;
return [x, y];
}
/**
* Gets the corners of the screen the closest to (or containing) the location
* @param {int[]} location The coordinates
* @return {Object} The screen object with a top, left, right and bottom property.
*/
DuScriptUI.getClosestScreen = function(location)
{
var screen = null;
var distance = 999999999;
for (var i = 0, n = $.screens.length; i < n; i++)
{
var s = $.screens[i];
// check if the location is inside the screen
if (location[0] > s.left && location[0] < s.right && location[1] > s.top && location[1] < s.bottom)
{
return s;
}
//get the center
var c = [ (s.left + s.right)/2, (s.top + s.bottom) / 2];
//get the distance
var d = DuMath.length(c, location);
if (d < distance)
{
screen = s;
distance = d;
}
}
return screen;
}
// ==================== |----------| ====================
// ==================== | selector | ====================
// ==================== |----------| ====================
/**
* @class
* @name DuSelector
* @classdesc For use with {@link DuScriptUI}.<br />
* A drop down selector.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.selector} to create a Selector.<br />
* The Selector inherits the Group object from ScriptUI and has all of its properties and methods.
* @property {string} image - The path to the current image (or a PNG as a string representation)
* @property {Image} icon - The Image currently displayed
* @property {StaticText} label - The current text
* @property {string[][]} items - The buttons, each one is an array with [text, image, helptip]
* @property {int} index - The current index
* @property {string} text - The current text
* @property {*} currentData - The current data
* @property {Selector~onChange} onChange - The function to execute when the index changes.<br />
* You can set your own function here, which must take no argument.<br />
* The method is called after the index has changed.
* @property {Selector~onRefresh} onRefresh - The function to execute to refresh the content.<br />
* You can set your own function here, which must take no argument.
* @category DuScriptUI
*/
/**
* The function to execute when the index changes.<br />
* The method is called after the index has changed.
* @callback Selector~onChange
* @memberof DuSelector
*/
/**
* The function to execute to refresh the content.
* @callback Selector~onRefresh
* @memberof DuSelector
*/
/**
* Adds a new button to the selector
* @method
* @memberof DuSelector
* @name addButton
* @param {string} [text] - The text displayed by the button
* @param {string} [image] - The icon, either a path to the file or a PNG represented as a string.
* @param {string} [helpTip] - The help tip for the entry
* @param {*} [data] - Some data to associate with the button.
*/
/**
* Removes all buttons from the selector. This is the same as {@link Selector.clear}.
* @method
* @memberof DuSelector
* @name removeAll
*/
/**
* Removes all buttons from the selector. This is the same as {@link Selector.removeAll}.
* @method
* @memberof DuSelector
* @name clear
*/
/**
* Changes the selection and the current index of the selector
* @method
* @memberof DuSelector
* @name setCurrentIndex
* @param {int} index - The new index
*/
/**
* Changes the selection and the current index of the selector, using the text of the selection
* @method
* @memberof DuSelector
* @name setCurrentText
* @param {string} text - The text to select
* @param {Boolean} [quiet=false] When true, the onChange() callback will not be triggered.
*/
/**
* Creates a drop down selector, using image buttons
* @param {Window|Panel|Group} container - The ScriptUI Object which will contain and display the selector.
* @param {string} [helpTip] - The help tip to show on the selector
* @return {DuSelector} - The selector
*/
DuScriptUI.selector = function( container, helpTip, iconOnly )
{
helpTip = def(helpTip, '');
iconOnly = def(iconOnly, false);
//create main group
var selector = container.add( 'group' );
selector.orientation = 'row';
selector.spacing = 0;
selector.alignment = [ 'fill', 'top' ];
selector.alignChildren = [ 'center', 'center' ];
selector.freeze = false;
selector.mainGroup = selector.add( 'group' );
selector.mainGroup.orientation = 'row';
if (!iconOnly) selector.mainGroup.margins = 1;
selector.mainGroup.spacing = 4;
selector.mainGroup.alignment = [ 'fill', 'fill' ];
selector.mainGroup.alignChildren = [ 'center', 'center' ];
selector.helpTip = helpTip;
DuScriptUI.setBackgroundColor( selector.mainGroup, DuColor.Color.OBSIDIAN );
//the menu button
if (!iconOnly) {
selector.menuButton = selector.mainGroup.add( 'image', undefined, DuScriptUI.Icon.MENU.binAsString );
selector.menuButton.alignment = [ 'left', 'center' ];
selector.menuButton.helpTip = helpTip;
}
//the image
var iconGroup = selector.mainGroup.add('group');
if (!iconOnly) {
iconGroup.minimumSize = [20,20];
iconGroup.margins = 2;
}
else {
iconGroup.minimumSize = [16,16];
}
iconGroup.alignment = ['left', 'fill'];
selector.icon = iconGroup.add( 'image', undefined, DuScriptUI.Icon.PLACEHOLDER );
selector.icon.alignment = [ 'center', 'center' ];
selector.icon.helpTip = helpTip;
if (!iconOnly) {
//add fillers to be able to click anywhere on the button
selector.fillerM = selector.mainGroup.add( 'statictext', undefined, " " );
selector.fillerM.alignment = [ 'left', 'fill' ];
selector.fillerM.size = [ 0, -1];
//the text
selector.label = selector.mainGroup.add( 'statictext', undefined, '' );
selector.label.helpTip = '';
selector.label.alignment = [ 'fill', 'center' ];
selector.label.helpTip = helpTip;
//add fillers to be able to click anywhere on the button
selector.fillerR = selector.mainGroup.add( 'statictext', undefined, " " );
selector.fillerR.alignment = [ 'fill', 'fill' ];
}
//create popup
selector.popup = new Window( 'palette', '', undefined,
{
borderless: true
} );
selector.popup.margins = 2;
selector.popup.spacing = 0;
selector.popup.closeButton = DuScriptUI.button( selector.popup, i18n._("Cancel") );
selector.popup.closeButton.onClick = selector.popup.onDeactivate = function()
{
selector.popup.hide();
DuScriptUI.eventFunctionsPaused = false;
};
selector.popup.buttons = selector.popup.add( 'group' );
selector.popup.buttons.orientation = 'column';
selector.popup.buttons.margins = 0;
selector.popup.buttons.spacing = 0;
selector.popup.hide();
//add default button
selector.items = [];
selector.index = -1;
selector.currentData = null;
selector.addButton = function( text, image, helpTip, data )
{
selector.popup.hide();
var options = {};
text = def(text, '-');
if (jstype(text) != 'string') options = text;
options.text = def(options.text, text);
image = def(image, '');
options.image = def(options.image, image);
helpTip = def(helpTip, '');
options.helpTip = def(options.helpTip, helpTip);
data = def(data, null);
options.data = def(options.data, data);
if (!iconOnly) {
var size = text.length * 7;
if ( selector.label.minimumSize.width < size ) selector.label.minimumSize.width = size;
}
var index = selector.items.length;
selector.items.push( [ options.text, options.image, options.helpTip, options.data ] );
//add to popup
options.localize = false;
options.ignoreUIMode = true;
var button = DuScriptUI.button( selector.popup.buttons, options );
button.bgColor = DuColor.Color.OBSIDIAN;
button.dim();
button.onClick = function()
{
selector.setCurrentIndex( index );
selector.popup.hide();
};
selector.popup.layout.layout(true);
selector.popup.update();
}
selector.removeAll = function()
{
selector.popup.hide();
selector.items = [];
selector.setCurrentIndex( 0 );
for ( var i = selector.popup.buttons.children.length - 1; i >= 0; i-- )
{
selector.popup.buttons.remove( selector.popup.buttons.children[ i ] );
}
selector.popup.layout.layout(true);
selector.popup.update();
}
selector.clear = selector.removeAll;
selector.setCurrentIndex = function( index, quiet )
{
if ( index < 0 ) return;
if ( index >= selector.items.length ) return;
var item = selector.items[ index ];
if ( !item ) return;
var image = item[ 1 ];
if ( image != '' )
{
if (image instanceof DuBinary) image = image.toFile();
selector.icon.show();
selector.icon.image = image;
}
else if (!iconOnly)
{
selector.icon.hide();
}
if (!iconOnly) selector.label.text = item[ 0 ];
selector.index = index;
selector.text = item[ 0 ];
selector.currentData = item[3];
if (!quiet && !selector.freeze) selector.onChange();
}
selector.setCurrentText = function( text )
{
for (var i = 0, n = selector.items.length; i < n; i++)
{
if (selector.items[i][0] == text)
{
selector.setCurrentIndex(i);
break;
}
}
}
selector.setCurrentData = function( data )
{
selector.setCurrentIndex(0);
for (var i = 0, n = selector.items.length; i < n; i++)
{
if (selector.items[i][3] == data)
{
selector.setCurrentIndex(i);
break;
}
}
}
selector.clicked = function( e )
{
selector.onRefresh();
var popup = false;
if (!iconOnly) if ( e.target === selector.menuButton ) popup = true;
if ( e.altKey ) popup = true;
if ( popup )
{
var x = e.screenX - e.clientX;
var y = e.screenY - e.clientY;
selector.popup.location = [ x, y ];
selector.popup.show();
DuScriptUI.eventFunctionsPaused = true;
}
else
{
var index = selector.index;
if ( e.ctrlKey ) index = -1;
if ( index == selector.items.length - 1 ) index = 0;
else index++;
selector.setCurrentIndex( index );
DuScriptUI.eventFunctionsPaused = false;
}
}
//mouse over
selector.highlight = function( e )
{
e.stopPropagation();
DuScriptUI.dimControls();
DuScriptUI.setBackgroundColor( selector, DuColor.Color.APP_HIGHLIGHT_COLOR.darker() );
DuScriptUI.highlightedControls.push( selector );
}
selector.dim = function( e )
{
DuScriptUI.setBackgroundColor( selector, DuColor.Color.TRANSPARENT );
//if (!iconOnly) DuScriptUI.setTextColor( selector.label, DuColor.Color.APP_TEXT_COLOR );
}
selector.mainGroup.addEventListener( "mouseover", selector.highlight );
selector.mainGroup.addEventListener( "click", selector.clicked, true );
selector.onChange = function() {};
selector.onRefresh = function (){};
selector.dim();
return selector;
}
// ==================== |------| ====================
// ==================== | text | ====================
// ==================== |------| ====================
/**
* @class
* @name DuEditText
* @classdesc For use with {@link DuScriptUI}.<br />
* A Nice EditText.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.editText} to create a Nice EditText.<br />
* The Nice EditText inherits the Group object from ScriptUI and has all of its properties and methods.
* @property {string} text - Read-Only | The text displayed
* @property {DuColor} textColor - The color of the text
* @property {DuEditText~onActivate} onActivate - Function to execute when activated
* @property {DuEditText~onDeactivate} onDeactivate - Function to execute when deactivated
* @property {DuEditText~onChange} onChange - Function to execute when text changed
* @property {DuEditText~onChange} onEnterPressed - Function to execute when enter key is pressed whil in edit mode
* @category DuScriptUI
*/
/**
* The function to execute when the text is changed.
* @callback DuEditText~onChange
* @memberof DuEditText
*/
/**
* Function to execute when enter key is pressed whil in edit mode.
* @callback DuEditText~onEnterPressed
* @memberof DuEditText
*/
/**
* The function to execute when the box is activated.
* @callback DuEditText~onActivate
* @memberof DuEditText
*/
/**
* The function to execute when the box is deactivated.
* @callback DuEditText~onDeactivate
* @memberof DuEditText
*/
/**
* Changes the text
* @method
* @memberof DuEditText
* @name setText
* @param {string} text - The new text
*/
/**
* Changes the prefix
* @method
* @memberof DuEditText
* @name setPrefix
* @param {string} prefix - The new prefix
*/
/**
* Changes the suffix
* @method
* @memberof DuEditText
* @name setSuffix
* @param {string} suffix - The new suffix
*/
/**
* Changes the placeholder
* @method
* @memberof DuEditText
* @name setPlaceholder
* @param {string} placeholder - The placeholder text
*/
/**
* Creates a nice edittext where the edit text is shown only on click.
* @param {Window|Panel|Group} container - The ScriptUI Object which will contain and display the nice edit text.
* @param {string} text - The initial text in the edit.
* @param {string} [prefix=""] - A text prefix to display.
* @param {string} [suffix=""] - A text suffix to display.
* @param {string} [placeHolder=""] - A place holder default text.
* @param {string} [helpTip=""] The helpTip of this control
* @param {bool} [localize=true] Whether to translate the texts of this control
* @return {DuEditText} The custom Group containing the edit text.
*/
DuScriptUI.editText = function( container, text, prefix, suffix, placeHolder, helpTip )
{
var options = {};
text = def(text, '');
if (jstype(text) != 'string') options = text;
prefix = def(prefix, '');
suffix = def(suffix, '');
placeHolder = def(placeHolder,'');
helpTip = def(helpTip, '');
options.text = def(options.text, text);
options.suffix = def(options.suffix, suffix);
options.prefix = def(options.prefix, prefix);
options.placeHolder = def(options.placeHolder, placeHolder);
options.helpTip = def(options.text, helpTip);
var niceEditText = container.add( 'group' );
niceEditText.orientation = 'stack';
niceEditText.margins = 0;
niceEditText.alignChildren = [ 'fill', 'fill' ];
niceEditText.placeHolder = options.placeHolder;
var staticText = options.text;
if ( options.text == "" && options.placeHolder != "" ) staticText = options.placeHolder;
niceEditText.static = niceEditText.add( 'statictext', undefined, options.prefix + staticText + options.suffix );
niceEditText.static.alignment = [ 'fill', 'center' ];
niceEditText.static.helpTip = options.helpTip;
if ( options.text == "" && options.placeHolder != "" ) DuScriptUI.setTextColor( niceEditText.static, DuColor.Color.APP_HIGHLIGHT_COLOR.darker(150) );
else DuScriptUI.setTextColor( niceEditText.static, DuColor.Color.APP_HIGHLIGHT_COLOR );
niceEditText.edit = niceEditText.add( 'edittext', undefined, options.text );
niceEditText.edit.visible = false;
niceEditText.edit.helpTip = options.helpTip;
niceEditText.edit.alignment = ['fill', 'center'];
niceEditText.prefix = options.prefix;
niceEditText.suffix = options.suffix;
niceEditText.textColor = DuColor.Color.APP_HIGHLIGHT_COLOR;
niceEditText.freeze = false;
niceEditText.text = options.text;
niceEditText.tempText = '';
niceEditText.previousText = options.text;
niceEditText.editing = false;
niceEditText.onActivate = function() {};
niceEditText.onDeactivate = function() {};
niceEditText.onChange = function() {};
niceEditText.onChanging = function() {};
niceEditText.onEnterPressed = function() {};
niceEditText.clicked = function()
{
if ( niceEditText.editing ) return;
else
{
niceEditText.static.visible = false;
niceEditText.edit.visible = true;
niceEditText.editing = true;
niceEditText.edit.active = true;
}
}
niceEditText.changed = function()
{
if ( niceEditText.text == niceEditText.edit.text )
var freeze = niceEditText.freeze;
niceEditText.freeze = true;
niceEditText.editing = false;
var staticText = '';
if ( niceEditText.edit.text == '' && niceEditText.placeHolder != '' )
{
DuScriptUI.setTextColor( niceEditText.static, niceEditText.textColor.darker(200) );
staticText = niceEditText.placeHolder;
}
else
{
if ( niceEditText.placeHolder != '' ) DuScriptUI.setTextColor( niceEditText.static, niceEditText.textColor );
staticText = niceEditText.edit.text;
}
niceEditText.static.text = niceEditText.prefix + staticText + niceEditText.suffix;
niceEditText.previousText = niceEditText.text;
niceEditText.text = niceEditText.edit.text;
niceEditText.edit.visible = false;
niceEditText.static.visible = true;
niceEditText.edit.active = false;
niceEditText.freeze = freeze;
if (!niceEditText.freeze) niceEditText.onChange();
}
niceEditText.changing = function()
{
var freeze = niceEditText.freeze;
niceEditText.freeze = true;
niceEditText.tempText = niceEditText.edit.text;
niceEditText.freeze = freeze;
// workaround for ae not sending the event
if (!niceEditText.freeze) niceEditText.onChanging();
}
niceEditText.enterPressed = function()
{
if (!niceEditText.freeze) niceEditText.onEnterPressed();
}
niceEditText.setText = function( text )
{
var freeze = niceEditText.freeze;
niceEditText.freeze = true;
niceEditText.previousText = niceEditText.text;
if (text == '')
{
niceEditText.edit.text = niceEditText.text = text;
niceEditText.static.text = niceEditText.prefix + niceEditText.placeHolder + niceEditText.suffix;
}
else
{
niceEditText.edit.text = niceEditText.text = text;
niceEditText.static.text = niceEditText.prefix + text + niceEditText.suffix;
}
niceEditText.freeze = freeze;
}
niceEditText.setPrefix = function( prefix )
{
niceEditText.prefix = prefix;
niceEditText.setText(niceEditText.text);
}
niceEditText.setSuffix = function( suffix )
{
niceEditText.suffix = suffix;
niceEditText.setText(niceEditText.text);
}
niceEditText.setPlaceholder = function( ph )
{
var freeze = niceEditText.freeze;
niceEditText.freeze = true;
niceEditText.placeHolder = ph;
var text = niceEditText.text;
if (text == '')
{
niceEditText.edit.text = niceEditText.text = text;
niceEditText.static.text = niceEditText.prefix + niceEditText.placeHolder + niceEditText.suffix;
}
else
{
niceEditText.edit.text = niceEditText.text = text;
niceEditText.static.text = niceEditText.prefix + text + niceEditText.suffix;
}
niceEditText.freeze = freeze;
}
niceEditText.edit.onActivate = function()
{
if (!niceEditText.freeze) niceEditText.onActivate();
}
niceEditText.edit.onDeactivate = function()
{
if (!niceEditText.freeze) niceEditText.changed();
if (!niceEditText.freeze) niceEditText.onDeactivate();
}
niceEditText.edit.onChange = niceEditText.changed;
niceEditText.edit.onChanging = niceEditText.changing;
niceEditText.addEventListener( "mousedown", niceEditText.clicked, true );
niceEditText.addEventListener( "mouseover", DuScriptUI.dimControls );
niceEditText.edit.addEventListener('keydown', function(e){ if (e.keyName == 'Enter' && !niceEditText.freeze) niceEditText.enterPressed(); } );
niceEditText.helpTip = options.helpTip;
return niceEditText;
}
/**
* Creates a statictext (with an optionnal color).
* @param {Window|Panel|Group} container - The ScriptUI Object which will contain and display the nice edit text.
* @param {string} text - The initial text in the edit.
* @param {DuColor} [color] - The color of the text. By default, uses a slightly darker text than the Host App text color
* @param {bool} [localize=true] - Set this to false to never translate this text.
* @param {bool} [multiLine=false] - Set this to true to add a multiline text. Auto detected by default if the text contains the newline character.
* @return {StaticText} The ScriptUI StaticText created.
*/
DuScriptUI.staticText = function( container, text, color, multiLine )
{
var options = {};
text = def(text, '');
if (jstype(text) != 'string') options = text;
options.text = def(options.text, text);
multiLine = def(multiLine, options.text.indexOf("\n") >= 0 )
color = def(color, DuColor.Color.APP_TEXT_COLOR.darker(130) );
options.color = def(options.color, color);
options.multiLine = def(options.multiLine, multiLine);
var t = container.add( 'statictext', undefined, options.text,
{
multiline: options.multiLine
} );
DuScriptUI.setTextColor( t, options.color );
t.setText = function (txt) { t.text = txt; };
return t;
}
// ==================== |--------------| ====================
// ==================== | fileSelector | ====================
// ==================== |--------------| ====================
/**
* @class
* @name DuFolderSelector
* @classdesc For use with {@link DuScriptUI}.<br />
* A "Browse" button for folders only, with an optional text field for the path.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.folderSelector} to create a selector.<br />
* The DuFolderSelector inherits the Group object from ScriptUI and has all of its properties and methods.
* @property {string} path - The folder path, initialized with an empty string. Note that this may not point to an existing folder if the user entered an incorrect path in the text field.<br />
* To be sure to get an existing folder, you can use the getFolder() method.
* @property {DuEditText|null} editText - The text field, if any.
* @property {DuButton} button - The "Browse" button.
* @category DuScriptUI
*/
/**
* Gets the selected folder.
* @method
* @memberof DuFolderSelector
* @name getFolder
* @return {Folder|null} The new Folder, if any. null if the Folder does not exist or the user has input an incorrect path.
*/
/**
* Creates a folder selector with a field for the path and a browse button<br />
* @param {Window|Panel|Group} container - The ScriptUI Object which will contain and display the panel.
* @param {string} [text="Browse..."] - The text to display on the button.
* @param {boolean} [textField=true] - Whether to show the text field for the path.
* @param {string} [helpTip=''] - The helptip for this control.
* @param {string} [orientation='row'] - The orientation of the control (button+edittext).
* @return {DuFolderSelector} The control.
*/
DuScriptUI.folderSelector = function ( container, text, textField, helpTip, orientation )
{
text = def(text, "Browse...");
textField = def(textField, true);
helpTip = def(helpTip, '');
orientation = def(orientation, 'row');
if (DuESF.scriptSettings.get("common/uiMode", 0) >= 1) orientation = 'row';
var folderSelector = container.add('group');
folderSelector.orientation = orientation;
folderSelector.margins = 0;
folderSelector.spacing = 0;
folderSelector.path = '';
folderSelector.button = DuScriptUI.button(
folderSelector,
text,
w12_folder,
helpTip
);
if (orientation == 'row') folderSelector.button.alignment = ['left','fill'];
folderSelector.editText = null;
if (textField) {
folderSelector.editText = DuScriptUI.editText(
folderSelector,
'',
'',
'',
i18n._p("file system", "Path..."), /// TRANSLATORS: path for a file or a folder
helpTip
);
folderSelector.editText.alignment = ['fill','center'];
folderSelector.editText.onChange = function () {
folderSelector.path = folderSelector.editText.text;
}
}
folderSelector.button.onClick = function () {
var folder = Folder.selectDialog ("Select Folder");
if (!folder) return;
folderSelector.editText.setText(folder.fsName);
folderSelector.path = folder.absoluteURI;
}
folderSelector.getFolder = function () {
var folder = new Folder(folderSelector.path);
if (folder.exists) return folder;
else return null;
}
return folderSelector;
}
/**
* @class
* @name DuFileSelector
* @classdesc For use with {@link DuScriptUI}.<br />
* A "Browse" button for files only, with an optional text field for the path.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.fileSelector} to create a selector.<br />
* The DuFileSelector inherits the Group object from ScriptUI and has all of its properties and methods.
* @property {string} path - The file path, initialized with an empty string. Note that this may not point to an existing folder if the user entered an incorrect path in the text field.<br />
* To be sure to get an existing file, you can use the getFile() method.
* @property {DuEditText|null} editText - The text field, if any.
* @property {DuButton} button - The "Browse" button.
* @property {DuFileSelector~onChange} onChange A function called when the file has changed.
* @category DuScriptUI
*/
/**
* Called when the file has changed.
* @callback onChange
* @memberof DuFileSelector
*/
/**
* Gets the selected file.
* @method
* @memberof DuFileSelector
* @name getFile
* @return {File|null} The new File, if any. null if the File does not exist or the user has input an incorrect path.
*/
/**
* Creates a file selector with a field for the path and a browse button.
* @param {Window|Panel|Group} container - The ScriptUI Object which will contain and display the panel.
* @param {string} [text="Browse..."] - The text to display on the button.
* @param {boolean} [textField=true] - Whether to show the text field for the path.
* @param {string} [helpTip=''] - The helptip for this control.
* @param {string|DuBinary} [image] - The image to use as an icon; a "file" icon by default.
* @param {string} [mode='open'] - The open mode, either 'open' or 'save'.
* @param {string} [filters] - The file type filters.
* @param {string} [orientation='row'] - The orientation of the control (button+edittext).
* @return {DuFileSelector} The control.
*/
DuScriptUI.fileSelector = function ( container, text, textField, helpTip, image, mode, filters, orientation )
{
text = def(text, "Browse...");
textField = def(textField, true);
helpTip = def(helpTip, '');
mode = def(mode, 'open');
image = def(image, w12_file);
filters = def(filters, "All files: *.*");
orientation = def(orientation, uiMode >= 1 ? 'row' : 'column');
var uiMode = DuESF.scriptSettings.get("common/uiMode", 0);
var fileSelector = container.add('group');
fileSelector.orientation = orientation;
fileSelector.margins = 0;
fileSelector.spacing = 0;
fileSelector.path = '';
fileSelector.button = DuScriptUI.button(
fileSelector,
text,
image,
helpTip
);
if (orientation == 'row') fileSelector.button.alignment = ['left','fill'];
fileSelector.editText = null;
if (textField) {
fileSelector.editText = DuScriptUI.editText(
fileSelector,
'',
'',
'',
i18n._p("file system", "Path..."), /// TRANSLATORS: path for a file or a folder
helpTip
);
fileSelector.editText.alignment = ['fill','center'];
fileSelector.editText.onChange = function () {
fileSelector.path = fileSelector.editText.text;
fileSelector.onChange();
}
}
fileSelector.onChange = function() {};
fileSelector.button.onClick = function () {
var file = new File(fileSelector.path);
if (file.exists)
{
if (mode == 'open') file = file.openDlg("Select File", filters);
else file = file.saveDlg ("Save File", filters);
}
else
{
if (mode == 'open') file = File.openDialog("Select File",filters);
else file = File.saveDialog ("Save File", filters);
}
if (!file) return;
if (mode == 'open' && !file.exists)
{
if (fileSelector.editText) fileSelector.editText.setText("");
fileSelector.path = file.absoluteURI;
}
else
{
if (fileSelector.editText) fileSelector.editText.setText(file.fsName);
fileSelector.path = file.absoluteURI;
}
fileSelector.onChange();
}
fileSelector.getFile = function () {
var file = new File(fileSelector.path);
if (!file.exists && mode == 'open') return null;
else return file;
}
fileSelector.setPath = function(path)
{
var file = new File(path);
fileSelector.path = file.absoluteURI;
if ((mode == 'open' && file.exists) || mode == 'save')
{
fileSelector.editText.setText( file.fsName );
}
else
{
fileSelector.editText.setText( "" );
}
}
fileSelector.setPlaceholder = function(text)
{
fileSelector.editText.setPlaceholder(text);
}
return fileSelector;
}
// ==================== |--------| ====================
// ==================== | slider | ====================
// ==================== |--------| ====================
/**
* @class
* @name DuSlider
* @classdesc For use with {@link DuScriptUI}.<br />
* A Nice Slider.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.slider} to create a Nice Slider.<br />
* The Nice Slider inherits the Group object from ScriptUI and has all of its properties and methods.
* @property {int} value - Read-Only | The current value
* @property {DuSlider~onChanging} onChanging - Function to execute when changing
* @property {DuSlider~onChange} onChange - Function to execute when changed
* @category DuScriptUI
*/
/**
* Function to execute when changing
* @callback DuSlider~onChanging
* @memberof DuSlider
*/
/**
* Function to execute when changed
* @callback DuSlider~onChange
* @memberof DuSlider
*/
/**
* Changes the value
* @method
* @memberof DuSlider
* @name setValue
* @param {int} value - The new value
*/
/**
* Creates a slider.
* @param {Window|Panel|Group} container - The ScriptUI Object which will contain and display the nice edit text.
* @param {int} [defaultValue=0] - The initial value.
* @param {int} [min=0] - The minimal value.
* @param {int} [max=100] - The maximal value.
* @param {string} [orientation='column'] - Either 'row' or 'column'
* @param {boolean} [invertedAppearance] - Revert the slider with max value on the left
* @param {string} [prefix] - A text prefix to display.
* @param {string} [suffix] - A text suffix to display.
* @param {string} [textAlignment='center'] - The alignment of the text.<br />
* One of 'left', 'center', 'right' for column orientation,<br />
* And 'top', 'center', 'bottom' for row orientation.
* @param {int[]} [valueButtons=[]] - A list of predefined values to add as buttons.
* @return {DuSlider} The custom Group containing the slider.
* @todo implement helpTip
*/
DuScriptUI.slider = function( container, defaultValue, min, max, orientation, invertedAppearance, prefix, suffix, textAlignment, valueButtons )
{
prefix = def(prefix, '');
suffix = def(suffix, '');
min = def( min, 0);
max = def( max, 100);
defaultValue = def(defaultValue, 0);
textAlignment = def(textAlignment, 'center');
valueButtons = def(valueButtons, []);
orientation = def(orientation, 'column');
if ( valueButtons.length > 0 && textAlignment == 'center' )
{
if ( orientation == 'column' ) textAlignment = 'right';
else textAlignment = 'bottom';
}
var niceSlider = container.add( 'group' );
niceSlider.orientation = orientation;
niceSlider.spacing = 0;
niceSlider.margins = 0;
niceSlider.alignment = [ 'fill', 'top' ];
niceSlider.alignChildren = [ 'fill', 'fill' ];
niceSlider.value = defaultValue;
niceSlider.invertedAppearance = invertedAppearance;
var sliderValue = defaultValue;
if ( niceSlider.invertedAppearance ) sliderValue = max - defaultValue + min;
niceSlider.slider = niceSlider.add( 'slider', undefined, sliderValue, min, max );
niceSlider.editGroup = DuScriptUI.group( niceSlider );
niceSlider.editGroup.spacing = 0;
niceSlider.editGroup.margins = 0;
//a function to add buttons before or after the edittext, depending on the textAlignment
niceSlider.buttons = [];
function addButtons ()
{
//add
for ( var i = 0, num = valueButtons.length; i < num; i++ )
{
niceSlider.buttons[i] = DuScriptUI.smallbutton(
niceSlider.editGroup,
valueButtons[ i ].toString(),
"",
valueButtons[ i ]
);
niceSlider.buttons[i].alignment = [ 'fill', 'fill' ];
niceSlider.buttons[i].onClick = function( val )
{
niceSlider.setValue( val );
niceSlider.onChange();
}
}
}
if ( textAlignment == 'right' || textAlignment == 'bottom' ) addButtons();
niceSlider.edit = DuScriptUI.editText( niceSlider.editGroup, defaultValue.toString(), prefix + ': ', suffix + ' ', '', '', false );
var numChars = max.toString().length;
if ( min.toString().length > max.toString().length ) numChars = min.toString().length;
niceSlider.edit.edit.characters = numChars + prefix.length + suffix.length - 1;
if ( orientation == 'row' )
{
niceSlider.editGroup.alignment = [ 'right', 'fill'];
niceSlider.edit.alignment = [ 'center', textAlignment ];
niceSlider.edit.static.justify = 'center';
}
else
{
niceSlider.editGroup.alignment = [ 'fill', 'bottom'];
niceSlider.edit.alignment = [ textAlignment, 'center' ];
niceSlider.edit.static.justify = textAlignment;
}
if ( textAlignment == 'left' || textAlignment == 'top' ) addButtons();
niceSlider.onChanging = function() {};
niceSlider.onChange = function() {};
niceSlider.setValue = function( val )
{
niceSlider.value = val;
niceSlider.edit.setText( niceSlider.value );
if ( niceSlider.invertedAppearance ) niceSlider.slider.value = niceSlider.slider.maxvalue - val + niceSlider.slider.minvalue;
else niceSlider.slider.value = val;
}
//events
niceSlider.slider.onChanging = function()
{
if ( niceSlider.invertedAppearance ) niceSlider.value = Math.round( niceSlider.slider.maxvalue - niceSlider.slider.value + niceSlider.slider.minvalue );
else niceSlider.value = Math.round( niceSlider.slider.value );
niceSlider.edit.setText( niceSlider.value );
niceSlider.onChanging();
}
niceSlider.slider.onChange = function()
{
niceSlider.onChange();
}
niceSlider.edit.onChanging = function()
{
var val = parseInt( niceSlider.edit.text );
if ( isNaN( val ) ) return;
niceSlider.value = val;
if ( niceSlider.invertedAppearance ) niceSlider.slider.value = niceSlider.slider.maxvalue - val + niceSlider.slider.minvalue;
else niceSlider.slider.value = val;
niceSlider.onChanging();
}
niceSlider.edit.onChange = function()
{
var val = parseInt( niceSlider.edit.text );
if ( isNaN( val ) ) return;
if ( val < niceSlider.slider.minvalue ) val = niceSlider.slider.minvalue;
if ( val > niceSlider.slider.maxvalue ) val = niceSlider.slider.maxvalue;
niceSlider.edit.setText( val );
niceSlider.value = val;
if ( niceSlider.invertedAppearance ) niceSlider.slider.value = niceSlider.slider.maxvalue - val + niceSlider.slider.minvalue;
else niceSlider.slider.value = val;
niceSlider.onChange();
}
niceSlider.addEventListener( "mouseover", DuScriptUI.dimControls );
return niceSlider;
}
// ==================== |------| ====================
// ==================== | form | ====================
// ==================== |------| ====================
/**
* @class
* @name DuForm
* @classdesc For use with {@link DuScriptUI}.<br />
* A Form.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.form} to create a Form.<br />
* The DuForm inherits the Group object from ScriptUI and has all of its properties and methods.
* @property {Group} labels - The left vertical group
* @property {Group} buttons - The right vertical group
* @category DuScriptUI
*/
/**
* Adds a field to the form
* example: form.addField('Composition:','dropdownlist',['Composition1','Composition2'],'Select a composition')
* @method
* @memberof DuForm
* @name addField
* @param {string} label - The label text.
* @param {string} type - The type of ScriptUI object to add (like 'button','edittext', etc.).
* @param {object} [value] - The default value or content of the field added, depends on the type.
* @param {string} [helpTip] - The helpTip of the form control.
* @return {ScriptUI[]} An array with at 0 the StaticText label, and at 1 the ScriptUI object of the type type, added to the form
*/
/**
* Creates a layout to add forms to a UI, using ScriptUI groups.<br />
* You can easily add controls/fields to this form using DuScriptUI.addField
* @param {Window|Panel|Group} container - The ScriptUI Object which will contain and display the form.
* @return {DuForm} The custom Group containing the form.
*/
DuScriptUI.form = function( container )
{
var mainGroup = container.add( 'group' );
mainGroup.orientation = 'row';
mainGroup.margins = 0;
mainGroup.spacing = DuScriptUI.defaultSpacing;
var labelsGroup = mainGroup.add( 'group' );
labelsGroup.alignment = [ 'left', 'top' ];
labelsGroup.orientation = 'column';
labelsGroup.alignChildren = [ 'left', 'bottom' ];
labelsGroup.spacing = DuScriptUI.defaultSpacing;
var buttonsGroup = mainGroup.add( 'group' );
buttonsGroup.alignment = [ 'fill', 'top' ];
buttonsGroup.orientation = 'column';
buttonsGroup.alignChildren = [ 'fill', 'fill' ];
buttonsGroup.spacing = DuScriptUI.defaultSpacing;
mainGroup.labels = labelsGroup;
mainGroup.buttons = buttonsGroup;
mainGroup.addEventListener( "mouseover", DuScriptUI.dimControls );
mainGroup.addField = function( label, type, value, helpTip )
{
helpTip = def (helpTip, '');
var control = null;
var height = 20;
if ( type == "DuButton" )
{
control = DuScriptUI.button( mainGroup.buttons, '', value[ 0 ], helpTip );
height = control.image.preferredSize[ 1 ];
}
else
{
control = mainGroup.buttons.add( type, undefined, value );
control.helpTip = helpTip;
height = control.preferredSize[ 1 ];
}
var l = mainGroup.labels.add( 'statictext', undefined, label );
l.helpTip = helpTip;
l.minimumSize.height = l.maximumSize.height = height;
return [ l, control ];
}
return mainGroup;
}
// ==================== |----------| ====================
// ==================== | tabPanel | ====================
// ==================== |----------| ====================
/**
* @class
* @name DuTabPanel
* @classdesc For use with {@link DuScriptUI}.<br />
* A Panel with tabs.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.tabPanel} to create a picker.<br />
* The DuTabPanel inherits the Group object from ScriptUI and has all of its properties and methods.
* @property {DuButton[]} buttons - The buttons for the tabs. Not that an "index" property is added to the button, containing the DuTab index.
* @property {DuTab[]} tabs - The tabs.
* @property {int} index - The currently visible tab.
* @property {Group} buttonsGroup - The ScriptUI Group containing the buttons
* @property {Group} mainGroup - The ScriptUI Group containing the tabs
* @property {string} scriptUIPanel - The file name ("script.jsx") of a scriptUI Panel. Alt+Click on this tab will open/close this panel.
* @property {DuTabPanel~onChange} onChange - Called when the index changes.
* @category DuScriptUI
*/
/**
* Adds a new empty DuTab in the DuTabPanel.
* @method
* @memberof DuTabPanel
* @name addTab
* @param {string} [text=''] - The label of the button.
* @param {string} [image=''] - The path to the icon.
* @param {string} [helpTip=''] - The helptip.
* @param {bool} [translatable=true] - Set to false to not translate this tab text and helptip
* @return {DuTab} The new DuTab.
*/
/**
* Sets the current visible tab.
* @method
* @memberof DuTabPanel
* @name setCurrentIndex
* @param {int} [index=0] - The index of the tab to show.
*/
/**
* Called when the index changes.
* @callback DuTabPanel~onChange
* @memberof DuTabPanel
*/
/**
* @class
* @name DuTab
* @classdesc For use with {@link DuScriptUI}.<br />
* A DuTab inside a {@link DuTabPanel}.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuTabPanel.add} to create a new DuTab.
* @property {int} index - The index of this tab.
* @property {DuButton} button - The button associated with this tab.
* @property {DuTab~tabActivated} onActivate - The function to execute when the tab is displayed.
* @property {DuTab~tabDeActivated} onActivate - The function to execute when the tab is hidden.
* @property {DuTab~build} build - You can use this callback to add a function which builds the UI of the tab, it will be called on first display.
* @category DuScriptUI
*/
/**
* The function to execute when the tab is about to be displayed.
* @callback DuTab~tabActivated
* @memberof DuTab
*/
/**
* The function to execute when the tab is hidden.
* @callback DuTab~tabDeActivated
* @memberof DuTab
*/
/**
* You can use this callback to add a function which builds the UI of the tab, it will be called on first display.<br />
* This allows a faster startup of your script by delaying the creation of the tabs which are hidden at startup.<br />
* You can add controls in the <code>this</code> object.
* @callback DuTab~build
* @memberof DuTab
*/
/**
* Creates a panel with tabs
* @param {Window|Panel|Group} container - The ScriptUI Object which will contain and display the panel.
* @param {string} tabOrientation - The orientation to use for tab buttons.
* @return {DuTabPanel} The panel.
*/
DuScriptUI.tabPanel = function( container, tabOrientation )
{
tabOrientation = def(tabOrientation, 'row');
var panel = DuScriptUI.group( container, 'column' );
panel.tabOrientation = tabOrientation;
panel.alignment = [ 'fill', 'fill' ];
panel.buttons = [];
panel.tabs = [];
panel.tabGroups = [];
panel.showTitle = true;
panel.onChange = function() {};
//adds a group for the buttons
panel.buttonsGroup = DuScriptUI.group( panel, 'row' );
//adds a group for the tabs
panel.mainGroup = DuScriptUI.group( panel, 'stack' );
panel.mainGroup.alignment = [ 'fill', 'fill' ];
panel.addTab = function( text, image, helpTip, translatable )
{
text = def( text, '');
image = def (image, '');
translatable = def (translatable, true);
var uiMode = DuESF.scriptSettings.get("common/uiMode", 0);
if (uiMode >= 2 && image != '') text = '';
// If this button and the previous one have a label, add a spacer
if (panel.buttons.length > 0)
{
var prevButton = panel.buttons[panel.buttons.length-1];
if (prevButton.label) if (prevButton.label.text != '' && text != '')
{
var spacer = panel.buttonsGroup.add('group');
spacer.spacing = 0;
spacer.margins = 0;
spacer.minimumSize = [5,-1];
}
}
//adds the new button
var button = DuScriptUI.checkBox( panel.buttonsGroup, text, image, helpTip, undefined, undefined, panel.tabOrientation, true );
button.index = panel.tabs.length;
panel.buttons.push( button );
//adds the new tab
var tab = DuScriptUI.group( panel.mainGroup, 'column' );
tab.alignment = [ 'fill', 'fill' ];
tab.visible = false;
tab.activated = false;
//the onActivate callBack
tab.tabActivated = function() {};
tab.tabDeActivated = function() {};
//the build callBack
tab.build = function( theTab ) {};
tab.duBuild = function() {
if ( tab.built ) return;
DuDebug.log( "TabPanel - Building Tab: " + tab.name + ' ' + tab.index );
tab.build( tab );
//resize everything
DuDebug.log( "TabPanel - Tab Built, Layout..." );
DuScriptUI.layout( tab );
DuDebug.log( "TabPanel - Tab ready." );
tab.built = true;
}
tab.built = false;
tab.scriptUIPanel = "";
tab.button = button;
//add to the list
tab.index = panel.tabs.length;
tab.name = text;
panel.tabs.push( tab );
DuScriptUI.allTabs.push( tab );
button.onClick = function()
{
panel.setCurrentIndex( button.index );
}
return tab;
}
panel.setCurrentIndex = function( index )
{
index = def( index, panel.index );
var numTabs = panel.tabs.length;
if ( numTabs == 0 ) return;
//hide all tabs
for ( var i = 0; i < numTabs; i++ )
{
var tab = panel.tabs[ i ];
tab.activated = i == index;
// Build only AFTER main UI has been shown, this improves performance
// DuScriptUI.showUI will build visible tabs
if ( tab.activated && DuScriptUI.uiShown )
{
tab.duBuild();
tab.visible = true;
tab.tabActivated();
}
if ( !tab.activated )
{
tab.visible = false;
tab.tabDeActivated();
}
panel.buttons[ i ].setChecked( tab.activated );
}
if ( index >= 0 && index < numTabs ) panel.index = index;
else panel.index = -1;
panel.onChange();
}
return panel;
}
/**
* Builds all the tabs from all the tab panels
*/
DuScriptUI.buildAllTabs = function()
{
for ( var i = 0, num = DuScriptUI.allTabs.length; i < num; i++ )
{
var tab = DuScriptUI.allTabs[ i ];
if ( !tab.built )
{
tab.build( tab );
//resize everything
DuScriptUI.layout( tab );
tab.built = true;
}
}
//check if there's still unbuilt tabs which may have been added
var redo = false;
for ( var i = DuScriptUI.allTabs.length - 1; i >= 0; i-- )
{
var tab = DuScriptUI.allTabs[ i ];
if ( !tab.built )
{
redo = true;
break;
}
}
if ( redo ) DuScriptUI.buildAllTabs();
}
// ==================== |---------------| ====================
// ==================== | colorSelector | ====================
// ==================== |---------------| ====================
/**
* @class
* @name DuColorSelector
* @classdesc For use with {@link DuScriptUI}.<br />
* A color selector, with an edittext and a random button.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.colorSelector} to create a Selector.<br />
* The Selector inherits the Group object from ScriptUI and has all of its properties and methods.
* @category DuScriptUI
*/
/**
* The current color
* @type {DuColor}
* @memberof DuColorSelector
* @name color
*/
/**
* Sets the current color
* @method
* @name setColor
* @param {DuColor} color The new color
* @memberof DuColorSelector
*/
/**
* Method called when the has been changed
* @type {DuColorSelector~onChange}
* @name onChange
* @memberof DuColorSelector
*/
/**
* The function to execute when the color is changed.
* @callback DuColorSelector~onChange
* @memberof DuColorSelector
*/
/**
* Creates a new color selector and adds it to the container
* @param {Window|Panel|Group} container
* @param {string} [helpTip] - The help tip to show on the selector
* @returns {DuColorSelector}
*/
DuScriptUI.colorSelector = function ( container, helpTip )
{
helpTip = def(helpTip, '');
var colorSelector = container.add('group');
colorSelector.orientation = 'row';
colorSelector.margins = 0;
colorSelector.spacing = 0;
colorSelector.alignment = ['fill', 'top'];
colorSelector.color = DuColor.Color.RX_PURPLE;
var label = DuScriptUI.staticText( colorSelector, "Color:", DuColor.Color.RX_PURPLE );
label.helpTip = helpTip;
label.alignment = ['left','center'];
var editText = DuScriptUI.editText( colorSelector, "A526C4", '#', '', '000000', helpTip );
editText.alignment = ['fill', 'fill'];
var randomButton = DuScriptUI.button( colorSelector, '', DuScriptUI.Icon.RANDOM, i18n._("Set a random value.") );
randomButton.alignment = ['right', 'center'];
colorSelector.onChange = function() {};
function labelClicked()
{
var color = new DuColor( colorPicker(editText.text) );
editText.setText( color.hex() );
colorChanged();
}
function randomClicked()
{
var color = DuColor.random();
editText.setText( color.hex() );
colorChanged();
}
function colorChanged()
{
var color = DuColor.fromHex( editText.text );
colorSelector.color = color;
DuScriptUI.setTextColor( label, color );
colorSelector.onChange();
}
colorSelector.setColor = function( color )
{
editText.setText( color.hex() );
colorChanged();
}
label.addEventListener("mousedown",labelClicked,false);
randomButton.onClick = randomClicked;
editText.onChange = colorChanged;
return colorSelector;
}
// ==================== |-------------| ====================
// ==================== | multiButton | ====================
// ==================== |-------------| ====================
/**
* @class
* @name DuMultiButton
* @classdesc For use with {@link DuScriptUI}.<br />
* A multi button popup.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.multiButton} to create a DuMultiButton.<br />
* The DuMultiButton inherits the Group object from ScriptUI and has all of its properties and methods.
* @property {StaticText} label - The current text
* @property {DuButton[]} buttons - The buttons
* @property {string} text - The current text
* @property {DuMultiButton~build} build - You can use this callback to add a function which builds the UI of the popup, it will be called on first display.
* @category DuScriptUI
*/
/**
* You can use this callback to add a function which builds the content of the popup, it will be called on first display.<br />
* This allows a faster startup of your script by delaying the creation of the tabs which are hidden at startup.<br />
* You can use <code>this.addButton</code> to add buttons in the popup.
* @callback DuMultiButton~build
* @memberof DuMultiButton
*/
/**
* Creates a multi button popup
* @param {Window|Panel|Group} container - The ScriptUI Object which will contain and display the multi button.
* @param {string} [text] - The text.
* @param {string|DuBInary} [image] - The path to the icon (or a PNG as a string representation). Default: empty string
* @param {string} [helpTip] - The help tip to show on the multi button
* @param {Boolean} [ignoreUIMode=false] - Will show texte even if the ui mode is set to > 1 in the script settings
* @return {DuSelector} - The multiButton
*/
DuScriptUI.multiButton = function( container, text, image, helpTip, ignoreUIMode )
{
var options = {};
text = def(text, '');
if (jstype(text) != 'string') options = text;
options.text = def(options.text, text);
if (options.text != '') options.text = options.text + '...';
helpTip = def(helpTip, '');
image = def(image, '');
ignoreUIMode = def(ignoreUIMode, false);
options.image = def(options.image, image);
options.helpTip = def(options.helpTip, helpTip);
options.ignoreUIMode = def(options.ignoreUIMode, ignoreUIMode);
var uiMode = DuESF.scriptSettings.get("common/uiMode", 0);
if (uiMode >= 2 && image != '' && !options.ignoreUIMode) options.text = '';
var multiButton = container.add( 'group' );
multiButton.orientation = 'row';
multiButton.spacing = 0;
multiButton.alignment = [ 'fill', 'top' ];
multiButton.alignChildren = [ 'center', 'center' ];
multiButton.mainGroup = multiButton.add( 'group' );
multiButton.mainGroup.orientation = 'row';
if (text != '') {
multiButton.mainGroup.margins = 1;
multiButton.mainGroup.spacing = 4;
}
multiButton.mainGroup.alignment = [ 'fill', 'fill' ];
multiButton.mainGroup.alignChildren = [ 'center', 'center' ];
multiButton.helpTip = helpTip;
//the menu button
if ( options.text != '' || (options.text == "" && options.image == ""))
{
multiButton.menuButton = multiButton.mainGroup.add( 'image', undefined, DuScriptUI.Icon.MORE.binAsString );
multiButton.menuButton.alignment = [ 'left', 'center' ];
multiButton.menuButton.helpTip = options.helpTip;
}
//the image
if (options.image != '')
{
var iconGroup = multiButton.mainGroup.add('group');
if ( options.text == '' ) {
iconGroup.minimumSize = [16,16];
iconGroup.alignment = ['center', 'fill'];
}
else {
iconGroup.minimumSize = [20,20];
iconGroup.margins = 2;
iconGroup.alignment = ['left', 'fill'];
}
if (options.image instanceof DuBinary) options.image = options.image.binAsString;
multiButton.icon = iconGroup.add( 'image', undefined, options.image );
multiButton.icon.alignment = [ 'center', 'center' ];
multiButton.icon.helpTip = options.helpTip;
}
if ( options.text != '')
{
//add fillers to be able to click anywhere on the button
multiButton.fillerM = multiButton.mainGroup.add( 'statictext', undefined, " " );
multiButton.fillerM.alignment = [ 'left', 'fill' ];
multiButton.fillerM.size = [ 0, -1];
//the text
multiButton.label = multiButton.mainGroup.add( 'statictext', undefined, options.text );
multiButton.label.helpTip = '';
multiButton.label.alignment = [ 'fill', 'center' ];
multiButton.label.helpTip = options.helpTip;
//add fillers to be able to click anywhere on the button
multiButton.fillerR = multiButton.mainGroup.add( 'statictext', undefined, " " );
multiButton.fillerR.alignment = [ 'fill', 'fill' ];
}
//create popup
multiButton.popup = new Window( 'palette', '', undefined,
{
borderless: true
} );
multiButton.popup.margins = 2;
multiButton.popup.spacing = 0;
multiButton.popup.closeButton = DuScriptUI.button( multiButton.popup, i18n._("Cancel") );
multiButton.popup.closeButton.onClick = multiButton.popup.onDeactivate = function()
{
multiButton.popup.hide();
DuScriptUI.eventFunctionsPaused = false;
};
multiButton.popup.buttons = multiButton.popup.add( 'group' );
multiButton.popup.buttons.orientation = 'column';
multiButton.popup.buttons.margins = 0;
multiButton.popup.buttons.spacing = 0;
multiButton.popup.hide();
multiButton.build = function() {};
multiButton.built = false;
//add default button
multiButton.buttons = [];
multiButton.addButton = function( text, image, helpTip, addOptionsPanel, optionsWithoutButton, optionsButtonText )
{
multiButton.popup.hide();
text = def(text, '-');
image = def(image, '');
helpTip = def(helpTip, '');
if (multiButton.label)
{
var size = text.length * 7;
if ( multiButton.label.minimumSize.width < size ) multiButton.label.minimumSize.width = size;
}
//add to popup
var button = DuScriptUI.button( multiButton.popup.buttons,
{
text: text,
image: image,
helpTip: helpTip,
options: addOptionsPanel,
ignoreUIMode: true,
optionsWithoutButton: optionsWithoutButton,
optionsButtonText: optionsButtonText
} );
multiButton.buttons.push( button );
button.bgColor = DuColor.Color.OBSIDIAN;
button.dim();
multiButton.popup.layout.layout(true);
multiButton.popup.update();
return button;
}
multiButton.clicked = function( e )
{
if (!multiButton.built)
{
multiButton.build();
multiButton.built = true;
}
var x = e.screenX - e.clientX;
var y = e.screenY - e.clientY;
multiButton.popup.location = [ x, y ];
multiButton.popup.show();
DuScriptUI.eventFunctionsPaused = true;
}
//mouse over
multiButton.highlight = function( e )
{
e.stopPropagation();
DuScriptUI.dimControls();
if (multiButton.label)
{
DuScriptUI.setTextColor( multiButton.label, DuColor.Color.APP_HIGHLIGHT_COLOR );
}
else
{
DuScriptUI.setBackgroundColor( multiButton, DuColor.Color.APP_HIGHLIGHT_COLOR.darker() );
}
DuScriptUI.highlightedControls.push( multiButton );
}
multiButton.dim = function( e )
{
if (multiButton.label) DuScriptUI.setTextColor( multiButton.label, DuColor.Color.APP_TEXT_COLOR );
DuScriptUI.setBackgroundColor( multiButton, DuColor.Color.TRANSPARENT );
}
multiButton.mainGroup.addEventListener( "mouseover", multiButton.highlight );
multiButton.mainGroup.addEventListener( "click", multiButton.clicked, true );
multiButton.dim();
return multiButton;
}
// ==================== |-------------| ====================
// ==================== | progressbar | ====================
// ==================== |-------------| ====================
/**
* Constructs a progress bar popup
* @class DuProgressBar
* @classdesc A simple progress bar.<br />
* This was inspired by {@link https://github.com/indiscripts/extendscript/blob/master/scriptui/ProgressBar.jsx the progress bar}
* by {@link https://www.indiscripts.com/ Marc Autret / IndiScripts}.
* @param {string} [title="Magic is happening"] The title of the progress bar
* @param {Group} [container] A ScriptUI Group to add the progress bar. If not provided, the bar will be added in a new window popup
* @category DuScriptUI
*/
DuProgressBar = function( title, container )
{
title = def(title, i18n._("Magic is happening"));
if (title == "") title = i18n._("Magic is happening");
this.container = def(container, null );
this.isPopup = !this.container;
if (!this.isPopup) this.window = DuScriptUI.parentWindow(this.container);
else this.window = null;
this.built = false;
this.coordinates = null;
// Create ui
this.__setupUI = function()
{
if (this.built) return;
if (this.isPopup)
{
this.window = new Window('palette', '', [0,0,360,86]);
this.container = this.window;
}
this.rootGroup = this.container.add('group');
this.rootGroup.margins = 0;
this.rootGroup.orientation = 'column';
this.stage = this.rootGroup.add( 'statictext', this.isPopup ? { x:20, y:15, width:320, height:16 } : undefined, i18n._("Working"));
this.barGroup = this.rootGroup.add('group', this.isPopup ? { x:20, y:37, width:320, height:12 } : undefined);
this.barGroup.orientation = 'stacked';
this.barGroup.margins = 0;
this.bar = this.barGroup.add( 'progressbar', this.isPopup ? { x:0, y:0, width:320, height:12 } : undefined, 0, 0);
this.image = this.barGroup.add( 'image', this.isPopup ? { x:0, y:0, width:320, height:12 } : undefined, DuScriptUI.Icon.PROGRESS.binAsString );
this.label = this.rootGroup.add( 'statictext', this.isPopup ? { x:20, y:51, width:320, height:16 } : undefined);
this.message = title + ' %1';
//if (this.isPopup) this.window.center();
DuScriptUI.layout( this.container );
this.built = true;
}
// Destroy ui
this.__delete = function()
{
if (!this.built) return;
// Remove controls
this.rootGroup.remove( this.label );
this.barGroup.remove( this.bar );
this.barGroup.remove( this.image );
this.rootGroup.remove( this.barGroup );
this.rootGroup.remove( this.stage );
this.container.remove( this.rootGroup );
// Delete to (try to) free memory
delete this.label;
delete this.image;
delete this.bar;
delete this.barGroup;
delete this.stage;
delete this.rootGroup;
if (this.isPopup)
{
delete this.container;
delete this.window;
}
this.built = false;
// Garbage collection
$.gc();
}
// A private method to update with the message and the value
this.__ = function()
{
var ok = this.bar.maxvalue != 0;
if (ok) this.label.text = DuString.replace(this.message, '%1', this.bar.value + ' / ' + this.bar.maxvalue);
else this.label.text = i18n._("Magic is happening") + '!';
this.bar.visible = ok;
this.image.visible = !ok;
if (this.window.update) this.window.update();
};
}
/**
* Changes the text of the label
* @param {string} message The text
*/
DuProgressBar.prototype.msg = function( message )
{
this.__setupUI();
if(isdef( message )) this.message = message;
this.__();
}
/**
* Changes the text of the current stage
* @param {string} message The text
*/
DuProgressBar.prototype.stg = function( stage )
{
this.__setupUI();
this.stage.text = stage;
this.msg();
}
/**
* Shows the progress bar and updates the value and text
* @param {string} [message=''] The text
* @param {int[]} [eventCoordinates] - Provide the screen coordinates to center the progress bar on the corresponding screen.
*/
DuProgressBar.prototype.show = function( message, eventCoordinates )
{
eventCoordinates = def(eventCoordinates, null);
this.__setupUI();
this.rootGroup.show();
this.msg(message);
if (this.isPopup) {
if (eventCoordinates) this.coordinates = DuScriptUI.centerInScreen( eventCoordinates, this.window.frameSize );
if (this.coordinates) this.window.location = this.coordinates;
else this.window.center();
this.window.show();
}
}
/**
* Hides and resets the progress bar to 0 and default texts
*/
DuProgressBar.prototype.reset = function( )
{
this.hide();
this.close();
this.__delete();
}
/**
* Updates and increments the progress bar
* @param {int} [value] The new value. if omitted, the bar is just incremented by 1
* @param {string} [message] A new label
*/
DuProgressBar.prototype.hit = function( value, message )
{
this.__setupUI();
value = def(value, 1);
this.bar.value += value;
this.msg(message);
}
/**
* Sets the maximum value
* @param {int} maxValue The new maximum value
* @param {Boolean} [onlyIfZero=true] Set to false to change the max value even if it was not 0 before
*/
DuProgressBar.prototype.setMax = function( maxValue, onlyIfZero )
{
this.__setupUI();
onlyIfZero = def(onlyIfZero, true);
if (onlyIfZero && this.bar.maxvalue != 0) return;
this.bar.maxvalue = maxValue;
this.msg();
}
/**
* Increments the maximum value
* @param {int} [maxValue=1] The value to add to the maximum
*/
DuProgressBar.prototype.addMax = function( maxValue )
{
this.__setupUI();
maxValue = def(maxValue, 1);
this.setMax(this.bar.maxvalue + maxValue, false);
}
/**
* Hides the progress bar
*/
DuProgressBar.prototype.hide = function( )
{
if (!this.built) return;
this.rootGroup.hide();
this.__delete();
if (!this.isPopup) DuScriptUI.layout( this.container );
}
/**
* Closes the progress bar
*/
DuProgressBar.prototype.close = function( )
{
if (!this.built) return;
if (this.isPopup) this.window.close();
this.hide();
}
DuProgressBar.init = function()
{
/**
* Show this progress bar before long operations with {@link DuProgressBar.show} and DuESF will update it.
* @type {DuProgressBar}
*/
DuScriptUI.progressBar = new DuProgressBar();
}
DuESF.initMethods.push( DuProgressBar.init );
// ==================== |---------| ====================
// ==================== | toolbar | ====================
// ==================== |---------| ====================
/**
* Creates a toolbar with a lighter background
* @param {Panel|Window|Group} container The ScriptUI Object which will contain and display the toolbar.
* @param {int} [numCols] The number of columns to use when adding the buttons.
* @returns {Group} The toolbar, a ScriptUI Group
*/
DuScriptUI.toolBar = function( container, numCols )
{
var uiMode = DuESF.scriptSettings.get("common/uiMode", 0);
if (!isdef( numCols ))
{
if (uiMode == 0) numCols = 3;
else numCols = 6;
}
var toolsGroup = DuScriptUI.group( container, 'row' );
toolsGroup.spacing = 3;
toolsGroup.margins = 3;
DuScriptUI.setBackgroundColor(toolsGroup , DuColor.Color.DARK_GREY );
toolsGroup.columns = [];
toolsGroup.spacing = 3;
if (uiMode == 0) toolsGroup.alignChildren = ['fill', 'top'];
else toolsGroup.alignChildren = ['center', 'top'];
for (var i = 0; i < numCols; i++)
{
toolsGroup.columns.push( DuScriptUI.group(toolsGroup, 'column') );
}
toolsGroup.currentCol = 0;
toolsGroup.addButton = function (text, icon, helpTip, addOptions, optionsWithoutPanel)
{
var options = {};
text = def(text, '');
if (jstype(text) != 'string') options = text;
options.text = def(options.text, text);
helpTip = def(helpTip, "");
addOptions = def(addOptions, false);
optionsWithoutPanel = def(optionsWithoutPanel, false);
options.helpTip = def(options.helpTip, helpTip);
options.options = def(options.options, addOptions);
options.optionsWithoutPanel = def(options.optionsWithoutPanel, optionsWithoutPanel);
options.image = def(options.image, icon);
options.localize = false;
options.orientation = uiMode == 0 ? 'column' : undefined;
options.helpTip = uiMode == 0 ? helpTip : options.text + "\n\n" + helpTip;
options.text = uiMode == 0 ? options.text : '';
var button = DuScriptUI.button ( toolsGroup.columns[toolsGroup.currentCol], options);
button.alignment = uiMode == 0 ? ['fill', 'top'] : ['center', 'top'];
toolsGroup.currentCol = (toolsGroup.currentCol + 1) % numCols;
return button;
}
return toolsGroup;
}
// ==================== |---------| ====================
// ==================== | library | ====================
// ==================== |---------| ====================
/**
* @class
* @name DuLibrary
* @classdesc For use with {@link DuScriptUI}.<br />
* A complete library interface.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.library} to create a Library.<br />
* The Library inherits the <code>Group</code> object from ScriptUI and has all of its properties and methods.
* @property {ListBox} list The Listbox with the current items for the library.
* Items are extended with new properties, see {@link DuListItem} for more details.
* @property {DuLibraryItem} library The associated library data. Use setLibrary to change it.
* @property {DuLibrary~onRun} onRun The function used to run the selected item.
* @property {DuLibrary~onAltRun} onAltRun The function used to run the selected item with alt click.
* @property {DuLibrary~onCtrlRun} onCtrlRun The function used to run the selected item with ctrl click.
* @property {DuLibrary~onCtrlAltRun} onCtrlAltRun The function used to run the selected item with ctrl alt click.
* @property {DuLibrary~onEditData} onEditData The function to execute to edit data.
* @property {DuLibrary~onFolderOpened} onFolderOpened The function to open a containing folder
* @property {DuLibrary~onFolderEdited} onFolderEdited The function to edit the folder
* @property {DuLibrary~onAddItem} onAddItem The function to execute when adding a new item.
* @property {DuLibrary~onEditItem} onEditItem The function to execute when editing an item.
* @property {DuLibrary~onRemoveItem} onRemoveItem The function to execute when removing an item.
* @property {DuLibrary~onRefresh} onRefresh The function to execute to refresh the library.
* @property {DuLibrary~setLibrary} setLibrary Sets a new library. May be called from onRefresh to replace the lib by a new one.
* @property {DuLibrary~setLibrary} runItem Runs the selected item. Tied to the apply/run button by default.
* @extends {Group}
* @category DuScriptUI
*/
/**
* The function to execute to refresh the library.
* @callback DuLibrary~onRefresh
* @param {DuLibraryItem} category The category to refresh.
* @memberof DuLibrary
*/
/**
* Sets a new library. May be called from onRefresh to replace the lib by a new one.
* @callback DuLibrary~setLibrary
* @param {DuLibraryItem} newLib The new library.
* @memberof DuLibrary
*/
/**
* The function used to run the selected item.
* @callback DuLibrary~onRun
* @param {DuListItem} item The item to run/apply.
* @memberof DuLibrary
*/
/**
* The function used to run the selected item.
* @callback DuLibrary~onAltRun
* @param {DuListItem} item The item to run/apply.
* @memberof DuLibrary
*/
/**
* The function used to run the selected item.
* @callback DuLibrary~onCtrlRun
* @param {DuListItem} item The item to run/apply.
* @memberof DuLibrary
*/
/**
* The function used to run the selected item.
* @callback DuLibrary~onCtrlAltRun
* @param {DuListItem} item The item to run/apply.
* @memberof DuLibrary
*/
/**
* The function used to edit an item data.
* @callback DuLibrary~onEditData
* @param {DuListItem} item The item to edit.
* @memberof DuLibrary
*/
/**
* The function to open a containing folder.
* @callback DuLibrary~onFolderOpened
* @param {DuListItem} item The item to edit.
* @param {Object} category The current category.
* @memberof DuLibrary
*/
/**
* The function to edit the folder.
* @callback DuLibrary~onFolderEdited
* @param {DuListItem} item The item to edit.
* @param {Object} category The current category.
* @memberof DuLibrary
*/
/**
* The function to execute when adding a new item.
* @callback DuLibrary~onAddItem
* @param {Object} category The current category.
* @memberof DuLibrary
*/
/**
* The function to execute when editing an item.
* @callback DuLibrary~onAddItem
* @param {DuListItem} item The item to edit.
* @param {Object} category The current category.
* @memberof DuLibrary
*/
/**
* The function to execute when removing an item.
* @callback DuLibrary~onRemoveItem
* @param {DuListItem} item The item to remove.
* @param {Object} category The current category.
* @memberof DuLibrary
*/
/**
* @class
* @name DuListItem
* @classdesc For use with {@link DuScriptUI}.<br />
* An item in a custom listbox used with {@link DuLibrary}.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.library} to create a Library.<br />
* The Library has a <code>list</code> property which is a ScriptUI <code>ListBox</code> which contains these DuListItem.<br />
* DuListItem inherits the <code>Item</code> object from ScriptUI and has all of its properties and methods.
* @property {*} data The data associated with the item, depends on the library object passed to the DuLibrary.
* @property {string} libType The type of item, one of ['item', 'category', 'parent']
* @property {bool} editableData Wether this item data can be edited by the user.
* @property {bool} editableItem Whether this item can be edited by the user.
* @category DuScriptUI
*/
/**
* @class
* @name DuLibraryItem
* @classdesc For use with {@link DuLibrary}.<br />
* An item in a library.
* @hideconstructor
* @property {*} data The data associated with the item.
* @property {string} libType The type of item, one of ['item', 'category']
* @property {bool} editableData Wether this item data can be edited by the user.
* @property {bool} editableItem Whether this item can be edited by the user.
* @property {string} icon Either the path to an image or a png representation as a string.
* @category DuScriptUI
*/
/**
* Adds a new {@link DuLibrary} to the container.
* @param {Panel|Window|Group} container The ScriptUI Object which will contain and display the library.
* @param {DuLibraryItem} library A library object. Key/value pairs, values being {@link DuLibraryItem} objects, keys being their display name.
* @param {object} [options] Options and other values.
*/
DuScriptUI.library = function( container, library, options ) {
options = def(options, {});
options.runButton = def(options.runButton, true);
options.editDataButton = def(options.editDataButton, false);
options.folderButton = def(options.folderButton, true);
options.canEditFolder = def(options.canEditFolder, false);
options.editListButtons = def(options.editListButtons, true);
options.refreshInterval = def(options.refreshInterval, -1);
options.defaultItemIcon = def(options.defaultItemIcon, '');
options.itemName = def(options.itemName, i18n._p("project", "Item")); /// TRANSLATORS: an item in the After Effects project
options.runHelpTip = def(options.runHelpTip, i18n._p("file", "Run")); /// TRANSLATORS: as in "run script"
options.folderHelpTip = def(options.folderHelpTip, i18n._("Open folder"));
options.addItemHelpTip = def(options.addItemHelpTip, i18n._("Add item or category"));
options.editItemHelpTip = def(options.editItemHelpTip, i18n._("Edit item or category"));
options.removeItemHelpTip = def(options.removeItemHelpTip, i18n._("Remove item or category"));
options.editDataHelpTip = def(options.editDataHelpTip, '');
options.sortButton = def(options.sortButton, true);
options.refreshButton = def(options.refreshButton, false);
// The main group
var duLibrary = container.add('group');
duLibrary.orientation = 'column';
duLibrary.alignment = ['fill', 'fill'];
duLibrary.margins = 0;
duLibrary.spacing = 0;
// And its lib
duLibrary.library = library;
var runIcon = '';
if (options.runButton) runIcon = DuScriptUI.Icon.RUN;
else runIcon = DuScriptUI.Icon.CHECK;
// UTILS
duLibrary.currentCategory = duLibrary.library;
var currentCommand = [];
duLibrary.sortMode = 'up';
function setCategory( cat ) {
duLibrary.onRefresh( cat );
duLibrary.currentCategory = cat;
// Update the sort button
duLibrary.srtButton.freeze = true;
if (duLibrary.sortMode == 'none') duLibrary.srtButton.setCurrentIndex(0);
else if (duLibrary.sortMode == 'down') duLibrary.srtButton.setCurrentIndex(2);
else duLibrary.srtButton.setCurrentIndex(1);
duLibrary.srtButton.freeze = false;
if (duLibrary.sortMode != 'none') cat = DuJSObj.sort(cat, false, function( a, b ) {
var aType = cat[a].libType;
var bType = cat[b].libType;
var r = 1;
if (duLibrary.sortMode == 'down') r = -1;
// Categories go first no matter what
if (aType == 'category' && bType == 'item') return -r;
else if (aType == 'item' && bType == 'category') return r;
// Alphabetical
if (a < b) return -r;
if (a > b) return r;
return 0;
});
libList.removeAll();
if (duLibrary.currentCategory != duLibrary.library) {
var item = libList.add('item', '..');
item.image = DuScriptUI.Icon.PARENT.binAsString;
item.libType = 'parent';
item.data = '';
item.editableItem = false;
item.editableData = false;
}
for (i in cat) {
if (!cat.hasOwnProperty(i)) continue;
// ignore lib properties
if (i == 'libType') continue;
if (i == 'icon') continue;
if (i == 'data') continue;
if (i == 'editableData') continue;
if (i == 'editableItem') continue;
var val = cat[i];
// ignore unknown types
if (typeof val.libType === 'undefined') continue;
if (val.libType == 'item') {
val.icon = def(val.icon, options.defaultItemIcon);
if (val.icon == '') val.icon = options.defaultItemIcon;
val.data = def (val.data, '');
val.editableData = def (val.editableData, false);
val.editableItem = def (val.editableItem, false);
var item = libList.add('item', i);
item.libType = 'item';
item.data = val.data;
item.editableItem = val.editableItem;
item.editableData = val.editableData;
try { item.image = val.icon; }
catch(e) {}
}
else {
val.icon = def(val.icon, DuScriptUI.Icon.FOLDER_CLOSED.binAsString);
val.data = def (val.data, '');
val.editableData = false;
val.editableItem = def (val.editableItem, false);
var item = libList.add('item', i);
item.libType = 'category';
item.data = val.data;
item.editableItem = val.editableItem;
item.editableData = val.editableData;
try { item.image = val.icon; }
catch(e) {}
}
}
libList.onChange();
}
function updateCategory() {
var cat = duLibrary.library;
duLibrary.onRefresh( cat );
for (var i = 0; i < currentCommand.length; i++) {
if (!cat[currentCommand[i]]) break;
cat = cat[currentCommand[i]];
if (cat) duLibrary.onRefresh( cat );
}
setCategory(cat);
}
function updateBreadCrumbs() {
tipLabel.text = currentCommand.join(' > ');
}
function parseCmd() {
var currentCmd = cmdEdit.tempText;
if ( DuString.endsWith(currentCmd, ' >')) return;
if ( DuString.fullTrim(currentCmd) == '') {
updateCategory();
return;
}
var newBlock = DuString.endsWith(currentCmd, ' ');
if (!newBlock) {
search = currentCmd.toLowerCase();
// Reset list
setCategory(duLibrary.currentCategory);
for (var i = libList.items.length -1; i >= 0; i--) {
var item = libList.items[i];
if ( // unsuccessful search
item.text.toLowerCase().indexOf(search) < 0
)
{
libList.remove(i);
}
}
return;
}
if (libList.items.length > 0 && currentCmd.length > 0) {
var item = libList.items[0];
if (item.libType == 'parent') {
currentCommand.pop();
updateBreadCrumbs();
updateCategory();
cmdEdit.setText('');
}
else if (item.libType == 'category') {
currentCommand.push(item.text);
updateBreadCrumbs();
updateCategory();
cmdEdit.setText('');
}
/*else if (item.libType == 'item') {
cmdEdit.setText(item.text + ' >');
updateCategory();
}*/
}
};
function run() {
parseCmd();
cmdEdit.setText(cmdEdit.text + ' ');
parseCmd();
if (libList.items.length < 0) return;
var item = libList.items[0];
if (item.libType == 'item') {
duLibrary.onRun(item);
// Clears the edit
cmdEdit.setText('');
parseCmd();
}
}
duLibrary.runItem = function ( modifier ) {
var item = libList.selection;
if (!item) return;
modifier = def(modifier, '');
if (item.libType == 'parent') {
currentCommand.pop();
updateBreadCrumbs();
updateCategory();
cmdEdit.setText('');
return;
}
if (item.libType == 'category') {
currentCommand.push(item.text);
updateBreadCrumbs();
updateCategory();
cmdEdit.setText('');
return;
}
if (modifier == 'alt') duLibrary.onAltRun(item);
else if (modifier == 'ctrl') duLibrary.onCtrlRun(item);
else if (modifier == 'ctrlAlt') duLibrary.onCtrlAltRun(item);
else duLibrary.onRun(item);
// Clears the edit
cmdEdit.setText('');
parseCmd();
};
duLibrary.clear = function () {
var currentCmd = cmdEdit.text;
// Empty
if ( DuString.fullTrim(currentCmd) == '') {
currentCommand = [];
updateBreadCrumbs();
updateCategory();
}
cmdEdit.setText('');
parseCmd();
};
duLibrary.refresh = function () {
// Only if visible
if (!duLibrary.visible) return;
//duLibrary.onRefresh(duLibrary.currentCategory);
setCategory(duLibrary.currentCategory);
updateBreadCrumbs();
};
duLibrary.sort = function(mode) {
duLibrary.sortMode = mode;
duLibrary.onRefresh(duLibrary.currentCategory);
setCategory(duLibrary.currentCategory);
updateBreadCrumbs();
};
// CREATE UI
var cmdGroup = DuScriptUI.group(duLibrary, 'row');
cmdGroup.spacing = 3;
cmdGroup.alignment = ['fill', 'top'];
var clearButton = DuScriptUI.button(
cmdGroup,
'',
DuScriptUI.Icon.CLOSE,
i18n._("Remove all")
);
clearButton.alignment = ['left', 'fill'];
clearButton.onClick = duLibrary.clear;
var cmdEdit = DuScriptUI.editText(
cmdGroup,
'',
'',
'',
i18n._("Start typing...") /// TRANSLATORS: start typing a command in a command line interface
);
cmdEdit.alignment = ['fill', 'fill'];
cmdEdit.onChanging = parseCmd;
cmdEdit.onChange = parseCmd;
cmdEdit.onEnterPressed = run;
var tipGroup = DuScriptUI.group( duLibrary, 'row');
tipGroup.alignment = ['fill', 'top'];
var tipLabel = DuScriptUI.staticText(
tipGroup,
''
);
tipLabel.alignment = ['fill', 'fill'];
var listGroup = DuScriptUI.group( duLibrary, 'row');
listGroup.alignment = ['fill', 'fill'];
var libList = listGroup.add('listbox');
libList.alignment = ['fill', 'fill'];
libList.onDoubleClick = duLibrary.runItem;
libList.onChange = function() {
var item = libList.selection;
if (options.editDataButton) {
if (!item) duLibrary.editDataButton.visible = false;
else duLibrary.editDataButton.visible = item.editableData;
}
if (options.editListButtons) {
if (!item) {
duLibrary.editItemButton.visible = false;
removeItemButton.visible = false;
}
else {
duLibrary.editItemButton.visible = item.editableItem;
removeItemButton.visible = duLibrary.currentCategory.editableItem && item.editableItem;
}
addItemButton.visible = duLibrary.currentCategory.editableItem;
}
};
var listButtonsGroup = DuScriptUI.group( listGroup, 'column');
listButtonsGroup.alignment = ['right', 'fill'];
duLibrary.runButton = DuScriptUI.button(
listButtonsGroup,
'',
runIcon,
options.runHelpTip
);
duLibrary.runButton.alignment = ['right','top'];
duLibrary.runButton.onClick = duLibrary.runItem;
duLibrary.runButton.onAltClick = function() { duLibrary.runItem('alt') };
duLibrary.runButton.onCtrlClick = function() { duLibrary.runItem('ctrl') };
duLibrary.runButton.onCtrlAltClick = function() { duLibrary.runItem('ctrlAlt') };
if (options.editDataButton) {
duLibrary.editDataButton = DuScriptUI.button(
listButtonsGroup,
'',
DuScriptUI.Icon.EDIT,
options.editDataHelpTip
);
duLibrary.editDataButton.alignment = ['right','top'];
duLibrary.editDataButton.onClick = function() {
var selection = libList.selection;
if (!selection) return;
if (!selection.editableData) return;
duLibrary.onEditData( selection );
};
duLibrary.editDataButton.onAltClick = function() {
var selection = libList.selection;
if (!selection) return;
if (!selection.editableData) return;
duLibrary.onAltEditData( selection );
};
}
if (options.folderButton) {
var fldrButton = DuScriptUI.button(
listButtonsGroup,
'',
DuScriptUI.Icon.FOLDER_CLOSED,
options.folderHelpTip
);
fldrButton.alignment = ['right','top'];
fldrButton.onClick = function() {
var selection = libList.selection;
duLibrary.onFolderOpened(selection, duLibrary.currentCategory);
};
if (options.canEditFolder) {
fldrButton.onAltClick = function() {
var selection = libList.selection;
duLibrary.onFolderEdited(selection, duLibrary.currentCategory);
};
}
}
if (options.sortButton) {
duLibrary.srtButton = DuScriptUI.selector( listButtonsGroup, '', true );
duLibrary.srtButton.alignment = ['right','top'];
duLibrary.srtButton.addButton(
'',
DuScriptUI.Icon.SORT
);
duLibrary.srtButton.addButton(
'',
DuScriptUI.Icon.SORT_UP
);
duLibrary.srtButton.addButton(
'',
DuScriptUI.Icon.SORT_DOWN
);
duLibrary.srtButton.setCurrentIndex(1);
duLibrary.srtButton.onChange = function() {
if (duLibrary.srtButton.index == 0) duLibrary.sort('none');
else if (duLibrary.srtButton.index == 1) duLibrary.sort('up');
else if (duLibrary.srtButton.index == 2) duLibrary.sort('down');
}
}
if (options.refreshButton) {
var rfrshButton = DuScriptUI.button(
listButtonsGroup,
'',
DuScriptUI.Icon.UPDATE,
i18n._("Refresh") /// TRANSLATORS: refresh a list
);
rfrshButton.alignment = ['right','top'];
rfrshButton.onClick = duLibrary.refresh;
}
if (options.editListButtons) {
var addItemButton = DuScriptUI.multiButton(
listButtonsGroup,
'',
DuScriptUI.Icon.ADD,
options.addItemHelpTip
);
addItemButton.alignment = ['right','bottom'];
duLibrary.addItemButton = addItemButton.addButton(
options.itemName
);
duLibrary.addCategoryButton = addItemButton.addButton(
i18n._("Category")
);
duLibrary.addItemButton.onClick = function() {
duLibrary.onAddItem(duLibrary.currentCategory);
};
duLibrary.addCategoryButton.onClick = function() {
duLibrary.onAddCategory(duLibrary.currentCategory);
}
duLibrary.editItemButton = DuScriptUI.button(
listButtonsGroup,
'',
DuScriptUI.Icon.EDIT,
options.editItemHelpTip
);
duLibrary.editItemButton.alignment = ['right','bottom'];
duLibrary.editItemButton.onClick = function() {
var selection = libList.selection;
if (!selection) return;
if (!selection.editableItem) return;
duLibrary.onEditItem(selection, duLibrary.currentCategory);
};
var removeItemButton = DuScriptUI.button(
listButtonsGroup,
'',
DuScriptUI.Icon.REMOVE,
options.removeItemHelpTip
);
removeItemButton.alignment = ['right','bottom'];
removeItemButton.onClick = function() {
var selection = libList.selection;
if (!selection) return;
if (selection.libType == 'parent') return;
// Add confirm
duLibrary.onRemoveItem(selection, duLibrary.currentCategory);
};
}
duLibrary.onRun = function() {};
duLibrary.onAltRun = function() {};
duLibrary.onCtrlRun = function() {};
duLibrary.onCtrlAltRun = function() {};
duLibrary.onEditData = function() {};
duLibrary.onAltEditData = function() {};
duLibrary.onFolderOpened = function() {};
duLibrary.onFolderEdited = function() {};
duLibrary.onAddItem = function() {};
duLibrary.onAddCategory = function() {};
duLibrary.onEditItem = function() {};
duLibrary.onRemoveItem = function() {};
duLibrary.onRefresh = function() {};
duLibrary.setLibrary = function (newLib) {
duLibrary.library = newLib;
updateCategory();
}
duLibrary.list = libList;
// Init
setCategory(duLibrary.library);
// Refresh
if (options.refreshInterval > 0) DuScriptUI.addEvent(duLibrary.refresh, options.refreshInterval);
return duLibrary;
}
// ==================== |--------------| ====================
// ==================== | settingField | ====================
// ==================== |--------------| ====================
/**
* @class
* @name DuSettingField
* @classdesc For use with {@link DuScriptUI}.<br />
* An Setting field, which can be enabled or disabled.<br />
* This is not a real class, and cannot be instanciated.<br />
* Use {@link DuScriptUI.settingField} to create setting field.<br />
* The DuSettingField inherits the <code>Group</code> object from ScriptUI and has all of its properties and methods.
* @property {DuSettingField~onClick} onClick - The function to execute when the checkbox is clicked
* @category DuScriptUI
*/
/**
* The function to execute when the checkbox is clicked.
* @callback DuSettingField~onClick
* @memberof DuSettingField
*/
/**
* Creates a {@link DuSettingField} which can be enabled or disabled
* @param {Panel|Window|Group} container - The ScriptUI Object which will contain and display the setting.
* @param {string} [text] - The label of the setting
* @param {int} [minimumLabelWidth] - The minmimum width of the label
* @return {DuSettingField} The setting created.
*/
DuScriptUI.settingField = function(container, text, minimumLabelWidth ) {
minimumLabelWidth = def(minimumLabelWidth, -1);
var settingGroup = DuScriptUI.group(container, 'row');
settingGroup.margin = 0;
settingGroup.spacing = 3;
settingGroup.alignment = ['fill', 'top'];
var checkbox = DuScriptUI.checkBox( settingGroup, {
text: text + ':',
localize: false
});
checkbox.alignment = ['left', 'center'];
checkbox.minimumSize = [minimumLabelWidth, -1];
settingGroup = DuScriptUI.group(settingGroup, 'row');
settingGroup.spacing = 0;
settingGroup.margin = 0;
settingGroup.alignment = ['fill', 'fill'];
settingGroup.checked = settingGroup.enabled = false;
settingGroup.onClick = function() {};
settingGroup.setChecked = function (c) {
checkbox.setChecked(c);
settingGroup.enabled = c;
};
checkbox.onClick = function() {
settingGroup.checked = checkbox.value;
settingGroup.enabled = checkbox.value;
settingGroup.onClick();
};
return settingGroup;
};
//low-level list of functions to be run when the user interacts with the UI
DuScriptUI.eventFunctions = [];
/**
* Adds a function to be run periodically, which will be connected to several UI events, fired when the user interacts with the UI.
* @param {function} func The function to connect to the UI
* @param {int} [timeOut=3000] A timeOut in milliseconds which prevents the function to be run too often
* @return {int} a unique identifier to be used to remove the function later, with {@link DuScriptUI.removeEvent}.
*/
DuScriptUI.addEvent = function (func, timeOut)
{
timeOut = def(timeOut, 3000);
func.id = new Date().getTime();
func.timeOut = timeOut;
func.lastFire = func.id;
DuScriptUI.eventFunctions.push(func);
return func.id;
}
/**
* Removes a function previously added with {@link DuScriptUI.addEvent}.
* @param {int} id The id of the function
*/
DuScriptUI.removeEvent = function (id)
{
for (var i = 0, n = DuScriptUI.eventFunctions.length; i < n; i++)
{
var f = DuScriptUI.eventFunctions[i];
if (f.id == id)
{
DuScriptUI.eventFunctions.splice(i,1);
break;
}
}
}
/**
* The default alignment of children of containers with "column" orientation
* @type {String[]}
* @default ["fill","top"]
*/
DuScriptUI.defaultColumnAlignment = [ "fill", "top" ];
/**
* The default alignment of children of containers with "row" orientation
* @type {String[]}
* @default ["left","center"]
*/
DuScriptUI.defaultRowAlignment = [ "left", "center" ];
/**
* The default alignment of children of containers with "stack" orientation
* @type {String[]}
* @default ["fill","top"]
*/
DuScriptUI.defaultStackAlignment = [ "fill", "fill" ];
/**
* The default spacing of containers
* @type {int}
* @default 2
*/
DuScriptUI.defaultSpacing = 2;
/**
* The default margins of containers
* @type {int}
* @default 2
*/
DuScriptUI.defaultMargins = 2;
/**
* A bar used to show current funding status.
* @type {ProgressBar}
*/
DuScriptUI.fundingBar = null;
/**
* A label used to show current funding status.
* @type {StaticText}
*/
DuScriptUI.fundingLabel = null;
//low-level undocumented list: keeps the currently highlighted controls
DuScriptUI.highlightedControls = [];
//low-level undocumented list: keeps all the tabs, used in the buildAllTabs method
DuScriptUI.allTabs = [];
//low-level undocumented flag: set to true when showUI has been called. Before this, there's no need to set layout for children, this will improve performance greatly.
DuScriptUI.uiShown = false;
//low-level undocumented flag: pause events firing (used with popups)
DuScriptUI.eventFunctionsPaused = false;
//low-level undocumented function: dims all currently highlighted controls
//Also runs all functions in DuScriptUI.eventFunctions
DuScriptUI.dimControls = function( )
{
if (!DuScriptUI.eventFunctionsPaused)
{
var now = new Date().getTime();
for (var i = 0, n = DuScriptUI.eventFunctions.length; i < n; i++)
{
var f = DuScriptUI.eventFunctions[i];
if (now - f.lastFire < f.timeOut) continue;
f();
f.lastFire = now;
}
}
if ( DuScriptUI.highlightedControls.length == 0 ) return;
for ( var i = DuScriptUI.highlightedControls.length - 1; i >= 0; i-- )
{
try
{
DuScriptUI.highlightedControls[ i ].dim();
}
catch ( e )
{};
DuScriptUI.highlightedControls.pop();
}
}
// ==================== |-----| ====================
// ==================== | oco | ====================
// ==================== |-----| ====================
/**
* Methods to load and create OCO (Open Cut Out) Documents and armatures
* @namespace
* @category OCO
*/
var OCO = {};
/**
* Types of OCO armatures
* @enum {string}
*/
OCO.Type = {
/**
* A meta-rig, used to automatically create a default armature to be used with an auto-rig.
*/
META: 'meta',
/**
* An actual (rigged) character.
*/
CHARACTER: 'character'
}
/**
* Predefined limbs
* @enum {string}
*/
OCO.Limb = {
SPINE: 'spine',
ARM: 'arm',
LEG: 'leg',
TAIL: 'tail',
WING: 'wing',
HAIR: 'hair',
SNAKE_SPINE: 'snakeSpine',
FISH_SPINE: 'fishSpine',
FIN: 'fin',
CUSTOM: 'custom'
}
/**
* Some bone identifierss
* @enum {string}
*/
OCO.Bone = {
CUSTOM: 'custom',
TIP: 'tip',
CLAVICLE: 'clavicle',
HUMERUS: 'humerus',
RADIUS: 'radius',
CARPUS: 'carpus',
FINGER: 'finger',
HEEL: 'heel',
FEMUR: 'femur',
TIBIA: 'tibia',
TARSUS: 'tarsus',
TOE: 'toe',
HIPS: 'hips',
SPINE: 'spine',
TORSO: 'torso',
NECK: 'neck',
SKULL_TIP: 'skullTip',
SKULL: 'skull',
TAIL: 'tail',
TAIL_ROOT: 'tail1',
TAIL_MID: 'tail2',
TAIL_END: 'tail3',
FEATHER: 'feather',
SNAKE_SPINE_ROOT: 'snakeSpine1',
SNAKE_SPINE_MID: 'snakeSpine2',
SNAKE_SPINE_END: 'snakeSpine3',
FISH_SPINE_ROOT: 'fishSpine1',
FISH_SPINE_MID: 'fishSpine2',
FISH_SPINE_END: 'fishSpine3',
HAIR: 'hair',
HAIR_ROOT: 'hair1',
HAIR_MID: 'hair2',
HAIR_END: 'hair3',
FIN: 'fin',
FIN_FISHBONE: 'finBone'
}
/**
* Types of limbs
* @enum {string}
*/
OCO.LimbType = {
HOMINOID: 'hominoid',
PLANTIGRADE: 'plantigrade',
DIGITIGRADE: 'digitigrade',
UNGULATE: 'ungulate',
ARTHROPOD: 'arthropod',
CUSTOM: 'custom'
}
/**
* Sides for the limbs Use these with {@link OCO.Location} to differenciate similar limbs,<br />
* for example, a leg can be Front-Right, Front-Left, Back-Right, Back-Left, etc.
* @enum {string}
*/
OCO.Side = {
LEFT: "L",
RIGHT: "R",
NONE: ''
}
/**
* Locations for the limbs. Use these with {@link OCO.Side} to differenciate similar limbs,<br />
* for example, a leg can be Front-Right, Front-Left, Back-Right, Back-Left, etc.
* @enum {string}
*/
OCO.Location = {
FRONT: "Fr",
BACK: "Bk",
TAIL: "Tl",
MIDDLE: "Md",
ABOVE: "Ab",
UNDER: "Un",
NONE: ''
}
/**
* View axis for limbs.
* @enum {int}
*/
OCO.View = {
FRONT: 0,
LEFT: 1,
RIGHT: 2,
BACK: 3,
TOP: 4,
BOTTOM: 5
}
/**
* How images are encoded in the OCO file
* @enum {string}
*/
OCO.ImageEncoding = {
PNG_BASE64: 'PNG/BASE64',
RELATIVE_PATH: 'PATH',
ABSOLUTE_PATH: 'ABS_PATH'
}
/**
* The path where the OCO Library is stored.
* This is in the User Documents/OCO/OCO Library if the script has write access in this folder.<br/>
* In rare cases on MacOS we can't write files in the documents folder. In this case the default library path in a temp folder.<br/>
* You can change this path to a custom path before using the OCO API.
* @type {string}
*/
OCO.libraryPath = "";
// === PRIVATE ===
// Gets a value or a default value if it is undefined
OCO._d = function( val, defaultVal )
{
return typeof val !== 'undefined' ? val : defaultVal;
}
OCO._escapeRegEx = function (string)
{
return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
// Generates a unique string
OCO._uniqueString = function( newString, stringList, increment)
{
increment = def(increment, true);
if (!increment) newString += ' ';
//detect digits
var reg = "( *)(\\d+)([.,]?\\d*)$";
//clean input
var regexClean = new RegExp(reg);
newString = newString.replace(regexClean, "");
//go!
var regex = new RegExp(OCO._escapeRegEx(newString) + reg);
//The greatest number found
var greatestNumber = 0;
//The number of digits for the number as string
var numDigits = 0;
var spaceString = "";
for (var i = 0; i < stringList.length; i++) {
var currentNumberMatch = stringList[i].match(regex);
if (stringList[i] === newString && greatestNumber == 0) greatestNumber++;
else if (currentNumberMatch !== null) {
//if its a decimal number, keep only the integer part
var numberAsString = currentNumberMatch[2];
//convert to int
var numberAsInt = parseInt(currentNumberMatch[2], 10);
if (isNaN(numberAsInt)) continue;
if (numberAsInt >= greatestNumber) {
greatestNumber = numberAsInt + 1;
spaceString = currentNumberMatch[1];
}
//check if there are zeroes before the number, count the digits
if (numberAsInt.toString().length < numberAsString.length && numDigits < numberAsString.length) numDigits = numberAsString.length;
}
}
//add leading 0 if needed
if (greatestNumber > 0) {
//convert to string with leading zeroes
if (greatestNumber == 1) {
greatestNumber++;
spaceString = " ";
}
newString += spaceString;
greatestNumber = OCO._numberToString(greatestNumber, numDigits);
newString += greatestNumber;
}
if (!increment) newString = newString.substr(0, newString.length - 1);
return newString;
}
// Converts a number to a string, with leading zeroes
OCO._numberToString = function (num, numDigits, base)
{
if (base == undefined) base = 10;
var result = num.toString(base);
while(numDigits > result.length)
{
result = "0" + result ;
}
return result;
}
// Interpolates a value
OCO._linearInterpolation = function(value, min, max, targetMin, targetMax, clamp) {
min = def(min, 0);
max = def(max, 1);
targetMin = def(targetMin, 0);
targetMax = def(targetMax, 1);
clamp = def(clamp, false);
// Handle stupid values
if (min == targetMin && max == targetMax) return value;
if (min == max) return value;
if (targetMin == targetMax) return targetMin;
if (value == min) return targetMin;
if (value == max) return targetMax;
var result = targetMin + (value - min) / (max - min) * (targetMax - targetMin);
if (clamp) result = OCO._clamp(value, targetMin, targetMax);
return result;
}
// Clamps the value
OCO._clamp = function(values, min, max) {
if (!isdef( min )) min = 0;
if (!isdef( max )) max = 1;
if (max < min) {
var t = max;
max = min;
min = t;
}
var isArray = true;
var result = [];
if (!(values instanceof Array)) {
isArray = false;
values = [values];
}
for (var i = 0, num = values.length; i < num; i++) {
var v = values[i];
if (v < min) v = min;
if (v > max) v = max;
result.push(v);
}
if (isArray) return result;
else return result[0];
}
// Checks if the script can write files in the folder
OCO._canWrite = function( folder ) {
if ( !(folder instanceof Folder) ) folder = new Folder(folder);
if ( !folder.exists ) return false;
var content = 'test-write-access';
var f = new File(folder.absoluteURI + '/' + content);
if (f.open('w')) {
try {
f.write(content);
f.close();
f.remove();
return true;
}
catch(e) { return false };
} else return false;
}
// Encodes a file as a base64 string
OCO._base64 = function( file ) {
if (!(file instanceof File)) file = new File(file);
if (!file.exists) return "";
file.encoding = "binary";
file.open("r");
var bin = file.read();
file.close();
var ecdStr = Base64.btoa(bin);
return ecdStr;
}
// Checks if this is a valid base64 encoding
OCO._checkBase64 = function( b64 ) {
var len = b64.length;
if (len % 4 > 0) {
return false;
}
// If there's a dot, that's probably a file name
if ( b64.indexOf(".") >= 0 )
return false;
return true;
}
// Decodes base64 content to a file; returns the File object or null
OCO._fromBase64 = function( b64, file )
{
if (b64 == "") return null;
if (!DuFile.checkBase64(b64)) return null;
// Convert to bin
var bin = Base64.atob(b64);
// Write to file
if (!(file instanceof File)) file = new File( file );
file.encoding = 'BINARY';
try
{
if (!file.open('w')) return null;
file.write(bin);
file.close();
return file;
}
catch (e) { return null; }
}
/**
* Gets the boundaries of an armature (a chain of bones)
* @param {OCOBone[]} armature The chain of bones
* @returns {float[]} [left, top, right, bottom]
*/
OCO.getBounds = function ( armature )
{
var left = 9999;
var right = -9999;
var top = 9999;
var bottom = -9999;
for (var i = 0, n = armature.length; i < n; i++)
{
var b = armature[i];
// Check current bone
if (b.x < left) left = b.x;
if (b.x > right) right = b.x;
if (b.y < bottom) bottom = b.y;
if (b.y > top) top = b.y;
// Check children
var childBounds = OCO.getBounds( b.children );
if (childBounds[0] < left) left = childBounds[0];
if (childBounds[1] < top) top = childBounds[1];
if (childBounds[2] < right) right = childBounds[2];
if (childBounds[3] < bottom) bottom = childBounds[3];
// Check other limbs
for ( var j = 0, nL = b.limbs.length; j < nL; j++ )
{
var limbBounds = b.limbs[i].bounds();
if (limbBounds[0] < left) left = limbBounds[0];
if (limbBounds[1] < top) top = limbBounds[1];
if (limbBounds[2] < right) right = limbBounds[2];
if (limbBounds[3] < bottom) bottom = limbBounds[3];
}
}
return [left, top, right, bottom];
}
// Makes a path relative to a file
OCO._relativePath = function ( path, destinationFile )
{
if (!(destinationFile instanceof File)) destinationFile = new File(destinationFile);
var sourceFile = new File( path );
var sourceFolder = sourceFile.parent;
var destFolder = destinationFile.parent;
var destPath = destFolder.absoluteURI;
var sourcePath = sourceFolder.absoluteURI;
var count = 0;
while (sourcePath.indexOf(destPath) != 0)
{
// Up one folder and check again
count++;
destFolder = destFolder.parent;
if (!destFolder) destPath = "";
else destPath = destFolder.absoluteURI;
}
// Add ups
var relativePath = "";
for (var i = 0; i < count; i++)
{
relativePath += '../';
}
if (destPath.length > 0)
relativePath += sourcePath.substr( destPath.length + 1 ) + '/';
else
relativePath += sourcePath + '/';
relativePath += sourceFile.name;
return relativePath;
}
// Makes a path absolute
OCO._absolutePath = function ( path, relativeTo )
{
if (!(relativeTo instanceof File)) relativeTo = new File(relativeTo);
var absFolder = relativeTo.parent;
while (path.indexOf("../") == 0)
{
path = path.substr(3);
absFolder = absFolder.parent;
}
var absPath = absFolder.absoluteURI + '/' + path;
return absPath;
}
OCO._parseJsonFile = function (file )
{
if (!(file instanceof File)) file = new File(file);
file.encoding = 'UTF-8';
if (!file.open('r')) return null;
var json = file.read();
file.close();
if (json == '') return null;
var data = {};
try { data = JSON.parse(json); } catch (e) { return null; }
return data;
}
/**
* Measures the vector length between two points
* @param {int[]} value1 - The first value
* @param {int[]} value2 - The second value
* @return {float} The length
*/
OCO._length = function(value1, value2) {
if (typeof value1 !== typeof value2) {
return null;
}
if (value1.length > 0) {
var result = 0;
for (var dim = 0; dim < value1.length; dim++) {
result += (value1[dim] - value2[dim]) * (value1[dim] - value2[dim]);
}
result = Math.sqrt(result);
return result;
} else return Math.abs(value1 - value2);
}
// Clamp values between min and max
OCO._clamp = function(values, min, max) {
min = OCO._d(min, 0);
max = OCO._d(max, 1);
if (max < min) {
var t = max;
max = min;
min = t;
}
var isArray = true;
var result = [];
if (!(values instanceof Array)) {
isArray = false;
values = [values];
}
for (var i = 0, num = values.length; i < num; i++) {
var v = values[i];
if (v < min) v = min;
if (v > max) v = max;
result.push(v);
}
if (isArray) return result;
else return result[0];
}
// Linear interpolation
OCO._linear = function(value, min, max, targetMin, targetMax, clamp) {
min = OCO._d(min, 0);
max = OCO._d(max, 1);
targetMin = OCO._d(targetMin, 0);
targetMax = OCO._d(targetMax, 1);
clamp = OCO._d(clamp, false);
// Handle stupid values
if (min == targetMin && max == targetMax) return value;
if (min == max) return value;
if (targetMin == targetMax) return targetMin;
if (value == min) return targetMin;
if (value == max) return targetMax;
var result = targetMin + (value - min) / (max - min) * (targetMax - targetMin);
if (clamp) result = DuMath.clamp(value, targetMin, targetMax);
return result;
}
// === INIT ===
if ( OCO._canWrite( Folder.myDocuments ) ) OCO.libraryPath = Folder.myDocuments.absoluteURI + "/OCO/OCO Library";
else OCO.libraryPath = Folder.temp.absoluteURI + "/OCO/OCO Library";
/**
* Creates a new OCO Document.
* @class
* @classdesc An Open Cut-Out character or meta-rig document.
* @param {string} name The name of the character or the meta rig
* @category OCO
*/
function OCODoc(name)
{
/**
* The type of this OCO Doc
* @name type
* @memberof OCODoc
* @type {string}
*/
this.type = OCO.Type.META;
/**
* The name of this OCO Doc
* @name name
* @memberof OCODoc
* @type {string}
*/
this.name = name;
/**
* The height of the character. Should always be 1!
* @name height
* @memberof OCODoc
* @type {float}
*/
this.height = 185.0;
/**
* The width of the character.
* @name width
* @memberof OCODoc
* @type {float}
*/
this.width = 60.0,
/**
* The coordinates of the center of mass of the character. [X, Y].
* @name centerOfMass
* @memberof OCODoc
* @type {float[]}
*/
this.centerOfMass = [0.0, 107.0];
/**
* The limbs/ Armatures
* @name limbs
* @memberof OCOLimb
* @type {OCOLimb[]}
*/
this.limbs = [];
/**
* The resolution, in pixels, of the document
* @type {int[]}
*/
this.resolution = [1920,1080];
/**
* The world origin in pixels in the document
* @type {float[]}
*/
this.world = [960,980];
/**
* The definition
* @type {float}
*/
this.pixelsPerCm = 4.22;
/**
* An icon or thumbnail path
* @type {string}
*/
this.icon = ""
/**
* How images should be encoded when exporting the doc to a file/folder
* @type {OCO.ImageEncoding}
*/
this.imageEncoding = OCO.ImageEncoding.PATH;
// Private: Converts a pixel coordinate relative to the world to a coordinate relative to the top left corner
this.fromWorld = function( point )
{
var x = point[0] + this.world[0];
var y = this.world[1] - point[1];
return [x,y];
}
// Private: Converts a pixel coordinate relative to the top left corner to a coordinate relative to the world
this.toWorld = function( point )
{
var x = point[0] - this.world[0];
var y = this.world[1] - point[1];
return [x, y];
}
}
/**
* Finds the spine
* @return {OCOLimb|null} The spine.
*/
OCODoc.prototype.getSpine = function()
{
for (var i = 0, ni = this.limbs.length; i < ni; i++)
{
var limb = this.limbs[i];
if (limb.limb == OCO.Limb.SPINE || limb.limb == OCO.Limb.FISH_SPINE)
return limb;
}
return null;
}
/**
* Counts the total number of bones in this doc
* @return {int}
*/
OCODoc.prototype.numBones = function ()
{
var count = 0;
for (var i = 0, ni = this.limbs.length; i < ni; i++ )
{
count += this.limbs[i].numBones();
}
return count;
}
/**
* Counts the total number of limbs in this doc
* @return {int}
*/
OCODoc.prototype.numLimbs = function ()
{
var count = 0;
for (var i = 0, ni = this.limbs.length; i < ni; i++ )
{
count += this.limbs[i].numLimbs();
}
return count;
}
/**
* Gets the boundaries of the doc
* @returns {float[]} [left, top, right, bottom]
*/
OCODoc.prototype.bounds = function ()
{
var left = 9999;
var right = -9999;
var top = 9999;
var bottom = -9999;
for (var i = 0, n = this.limbs.length; i < n; i++)
{
var limbBounds = this.limbs[i].bounds();
if (limbBounds[0] < left) left = limbBounds[0];
if (limbBounds[1] < top) top = limbBounds[1];
if (limbBounds[2] < right) right = limbBounds[2];
if (limbBounds[3] < bottom) bottom = limbBounds[3];
}
return [left, top, right, bottom];
}
/**
* Updates the width and height of the character, according to the content.<br />
* This method should be called each time a limb/bone is added/removed/edited and the bounds may change.
*/
OCODoc.prototype.updateSize = function()
{
// Update doc width & height
var bounds = this.bounds();
// right - left
this.width = this.bounds[2] - this.bounds[0];
// top - bottom
this.height = this.bounds[1] - this.bounds[3];
}
/**
* Creates a new limb and adds it to the doc
* @param {OCO.Limb} [limb=OCO.Limb.CUSTOM] A Predefined limb
* @param {OCO.Side} [side=OCO.Side.NONE] - The side of the limb
* @param {OCO.Location} [location=OCO.Location.NONE] - The location of the limb
* @param {OCO.LimbType} [type=OCO.LimbType.CUSTOM] - The type of the limb
* @return {OCOLimb} The new limb
*/
OCODoc.prototype.newLimb = function( limb, side, location, type )
{
var l = new OCOLimb( limb, side, location, type );
this.limbs.push(l);
return l;
}
/**
* Creates a new arm.
* @param {OCO.LimbType} [type=OCO.LimbType.HOMINOID] The type of limb
* @param {OCO.Side} [side=OCO.Side.LEFT] The side
* @param {Boolean} [shoulder=false] Whether to create a shoulder
* @param {Boolean} [arm=true] Whether to create an arm / humerus
* @param {Boolean} [forearm=true] Whether to create a forearm
* @param {Boolean} [hand=true] Whether to create a hand
* @param {Boolean} [claws=false] Whether to add claws
* @param {float[]} [position] The position of the first bone of the arm.<br />
* If omitted, computed automatically according to the current character in the doc.
* @param {OCO.Location} [location=OCO.Location.FRONT] The location of the limb
* @param {OCO.View} [view] The view
* @returns {OCOLimb} The arm
*/
OCODoc.prototype.newArm = function( type, side, shoulder, arm, forearm, hand, claws, position, location, view )
{
side = OCO._d(side, OCO.Side.LEFT);
type = OCO._d(type, OCO.LimbType.HOMINOID);
location = OCO._d(location, OCO.Location.FRONT);
shoulder = OCO._d( shoulder, false );
arm = OCO._d( arm, true );
forearm = OCO._d( forearm, true );
hand = OCO._d( hand, true );
claws = OCO._d( claws, false );
// Unit
var u = this.height;
// type
var hum = type == OCO.LimbType.HOMINOID;
var hum = type == OCO.LimbType.PLANTIGRADE && !claws;
var plan = type == OCO.LimbType.PLANTIGRADE && claws;
var dig = type == OCO.LimbType.DIGITIGRADE;
var ung = type == OCO.LimbType.UNGULATE;
var artF = type == OCO.LimbType.ARTHROPOD && location != OCO.Location.MIDDLE && location != OCO.Location.BACK;
var artM = type == OCO.LimbType.ARTHROPOD && location == OCO.Location.MIDDLE;
var artB = type == OCO.LimbType.ARTHROPOD && location == OCO.Location.BACK;
if ( !hum && !plan && !dig && !ung && !artF && !artM && !artB ) hum = true;
// View
if (typeof view === 'undefined')
{
if (type == OCO.LimbType.PLANTIGRADE && !claws) view = OCO.View.FRONT;
else view = OCO.View.RIGHT;
}
// count how many bones we need
var num = 1;
if ( shoulder ) num++;
if ( arm ) num++;
if ( forearm ) num++;
if ( hand ) num++;
if ( claws ) num++;
if (num == 1) return null;
// Create limb
var limb = this.newLimb( OCO.Limb.ARM, side, location, type );
var b = limb.newArmature( i18n._("Arm"), num );
// side
var s = 1;
if ( side == OCO.Side.RIGHT && view == OCO.View.FRONT ) s = -1;
else if ( side == OCO.Side.LEFT && view == OCO.View.BACK ) s = -1;
else if ( view == OCO.View.LEFT ) s = -1;
// shoulder position
var x, y;
var ox = 0;
var oy = 0;
if (hum)
{
x = s * u * .02;
y = u * .78;
}
else if (plan)
{
x = s * u * .40;
y = u * .92;
}
else if (dig)
{
x = s * u * .45;
y = u * .86;
}
else if (ung)
{
x = s * u * .33;
y = u * .80;
}
else if (artF)
{
x = s * u * .33;
y = u * .65;
}
else if (artM)
{
x = s * u * .09;
y = u * .50;
}
else if (artB)
{
x = s * u * -.03;
y = u * .48;
}
// offset
if (typeof position !== 'undefined' )
{
ox = position[0] - x;
oy = position[1] - y;
}
// for each part, set the location
if ( shoulder )
{
b.name = i18n._("Shoulder");
b.type = OCO.Bone.CLAVICLE;
b.x = x + ox;
b.y = y + oy;
if (b.children.length > 0) b = b.children[0];
}
if ( arm )
{
b.name = i18n._("Arm");
b.type = OCO.Bone.HUMERUS;
if (hum)
{
b.x = s * u * .12 + ox;
b.y = u * .77 + oy;
}
else if (plan)
{
b.x = s * u * .48 + ox;
b.y = u*.74 + oy;
}
else if (dig)
{
b.x = s * u * .45 + ox;
b.y = u*.66 + oy;
}
else if (ung)
{
b.x = s * u * .35 + ox;
b.y = u*.63 + oy;
}
else if (artF)
{
b.x = s * u * .24 + ox;
b.y = u*.45 + oy;
}
else if (artM)
{
b.x = s * u * -.01 + ox;
b.y = u*.44 + oy;
}
else if (artB)
{
b.x = s * u * -.17 + ox;
b.y = u*.43 + oy;
}
if (b.children.length > 0) b = b.children[0];
}
if ( forearm )
{
b.name = i18n._("Forearm");
b.type = OCO.Bone.RADIUS;
if (hum)
{
b.x = s * u * .15 + ox;
b.y = u *.65 + oy;
}
else if (plan)
{
b.x = s * u * .36 + ox;
b.y = u*.44 + oy;
}
else if (dig)
{
b.x = s * u * .21 + ox;
b.y = u*.45 + oy;
}
else if (ung)
{
b.x = s * u * .24 + ox;
b.y = u*.53 + oy;
}
else if (artF)
{
b.x = s * u * .55 + ox;
b.y = u*.80 + oy;
}
else if (artM)
{
b.x = s * u * -.08 + ox;
b.y = u*.82 + oy;
}
else if (artB)
{
b.x = s * u * -.44 + ox;
b.y = u*.92 + oy;
}
if (b.children.length > 0) b = b.children[0];
}
if ( hand )
{
b.name = i18n._("Hand");
b.type = OCO.Bone.CARPUS;
if (hum)
{
b.x = s * u * .12 + ox;
b.y = u * .51 + oy;
}
else if (plan)
{
b.x = s * u * .38 + ox;
b.y = u*.05 + oy;
}
else if (dig)
{
b.x = s * u * .45 + ox;
b.y = u*.14 + oy;
}
else if (ung)
{
b.x = s * u * .19 + ox;
b.y = u*.31 + oy;
}
else if (artF)
{
b.x = s * u * .77 + ox;
b.y = u*.28 + oy;
}
else if (artM)
{
b.x = s * u * -.17 + ox;
b.y = u*.42 + oy;
}
else if (artB)
{
b.x = s * u * -.77 + ox;
b.y = u*.47 + oy;
}
if (b.children.length > 0) b = b.children[0];
}
if ( claws )
{
b.name = i18n._("Claws");
b.type = OCO.Bone.FINGER;
if (plan)
{
b.x = s * u * .455 + ox;
b.y = u*.035 + oy;
}
else if (dig)
{
b.x = s * u * .49 + ox;
b.y = u*.03 + oy;
}
else if (ung)
{
b.x = s * u * .19 + ox;
b.y = u*.14 + oy;
}
else if (artF)
{
b.x = s * u * .86 + ox;
b.y = u*.07 + oy;
}
else if (artM)
{
b.x = s * u * -.25 + ox;
b.y = u*.07 + oy;
}
else if (artB)
{
b.x = s * u * -.98 + ox;
b.y = u*.07 + oy;
}
if (b.children.length > 0) b = b.children[0];
}
// Tip
b.name = i18n._("Arm") +
'_' + i18n._("Tip");
b.type = OCO.Bone.TIP;
if (hum)
{
b.x = s * u * .08 + ox;
b.y = u * .42 + oy;
}
else if (plan)
{
b.x = s * u * .585 + ox;
b.y = 0 + oy;
}
else if (dig)
{
b.x = s * u * .59 + ox;
b.y = u*.03 + oy;
}
else if (ung)
{
b.x = s * u * .21 + ox;
b.y = oy;
}
else if (artF)
{
b.x = s * u * .93 + ox;
b.y = oy;
}
else if (artM)
{
b.x = s * u * -.31 + ox;
b.y = oy;
}
else if (artB)
{
b.x = s * u * -1.06 + ox;
b.y = oy;
}
//Heel
if (plan)
{
var heelBone = new OCOBone( i18n._("Heel") );
heelBone.type = OCO.Bone.HEEL;
heelBone.x = s * u * .38 + ox;
heelBone.y = 0 + oy;
b.children.push(heelBone);
}
return limb;
}
/**
* Creates a new leg.
* @param {OCO.LimbType} [type=OCO.LimbType.HOMINOID] The type of limb
* @param {OCO.Side} [side=OCO.Side.LEFT] The side
* @param {Boolean} [thigh=true] Whether to create a thigh
* @param {Boolean} [calf=true] Whether to create a calf
* @param {Boolean} [foot=true] Whether to create a foot
* @param {Boolean} [claws=false] Whether to add claws
* @param {float[]} [position] The position of the first bone of the arm.<br />
* If omitted, computed automatically according to the current character in the doc.
* @param {OCO.Location} [location=OCO.Location.BACK] The location of the limb
* @param {OCO.View} [view] The view
* @returns {OCOLimb} The leg
*/
OCODoc.prototype.newLeg = function( type, side, thigh, calf, foot, claws, position, location, view )
{
side = OCO._d(side, OCO.Side.LEFT);
type = OCO._d(type, OCO.LimbType.HOMINOID);
location = OCO._d(location, OCO.Location.BACK);
thigh = OCO._d( thigh, true );
calf = OCO._d( calf, true );
foot = OCO._d( foot, true );
claws = OCO._d( claws, false );
// Unit
var u = this.height;
// type
var plan = type == OCO.LimbType.PLANTIGRADE;
var dig = type == OCO.LimbType.DIGITIGRADE;
var ung = type == OCO.LimbType.UNGULATE;
var hum = type == OCO.LimbType.HOMINOID;
// View
if (typeof view === 'undefined')
{
if (type == OCO.LimbType.PLANTIGRADE) view = OCO.View.FRONT;
else view = OCO.View.RIGHT;
}
// count how many bones we need
var num = 1;
if ( thigh ) num++;
if ( calf ) num++;
if ( foot ) num++;
if ( claws ) num++;
if (num == 1) return null;
// Create limb
var limb = this.newLimb( OCO.Limb.LEG, side, location, type );
var b = limb.newArmature( i18n._("Leg"), num );
// side
var s = 1;
if ( side == OCO.Side.RIGHT && view == OCO.View.FRONT ) s = -1;
else if ( side == OCO.Side.LEFT && view == OCO.View.BACK ) s = -1;
else if ( view == OCO.View.LEFT ) s = -1;
// thigh position
var x, y;
var ox = 0;
var oy = 0;
if (plan || hum)
{
x = s * u * .04;
y = u * .53;
}
else if (dig)
{
x = s * u * -.45;
y = u * .73;
}
else if (ung)
{
x = s * u * -.26;
y = u * .69;
}
// offset
if (typeof position !== 'undefined' )
{
ox = position[0] - x;
oy = position[1] - y;
}
// for each part, set the location
if ( thigh )
{
b.name = i18n._("Thigh");
b.type = OCO.Bone.FEMUR;
b.x = x + ox;
b.y = y + oy;
if (b.children.length > 0) b = b.children[0];
}
if ( calf )
{
b.name = i18n._("Calf");
b.type = OCO.Bone.TIBIA;
if (plan || hum)
{
b.x = s * u * .05 + ox;
b.y = u*.28 + oy;
}
else if (dig)
{
b.x = s * u * -.16 + ox;
b.y = u*.54 + oy;
}
else if (ung)
{
b.x = s * u * -.2 + ox;
b.y = u*.52 + oy;
}
if (b.children.length > 0) b = b.children[0];
}
if ( foot )
{
b.name = i18n._("Foot");
b.type = OCO.Bone.TARSUS;
if (plan || hum)
{
b.x = s * u * .036 + ox;
b.y = u*.027 + oy;
}
else if (dig)
{
b.x = s * u * -.5 + ox;
b.y = u*.24 + oy;
}
else if (ung)
{
b.x = s * u * -.38 + ox;
b.y = u*.33 + oy;
}
if (b.children.length > 0) b = b.children[0];
}
if ( claws )
{
b.name = i18n._("Claws");
if (hum) b.name = i18n._("Toes");
b.type = OCO.Bone.TOE;
if (plan || hum)
{
b.x = s * u * .10 + ox;
b.y = u*.0125 + oy;
}
else if (dig)
{
b.x = s * u * -.37 + ox;
b.y = u*.03 + oy;
}
else if (ung)
{
b.name = i18n._("Hoof");
b.x = s * u * -.37 + ox;
b.y = u*.08 + oy;
}
if (b.children.length > 0) b = b.children[0];
}
// Tip
b.name = i18n._("Leg") +
'_' + i18n._("Tip");
b.type = OCO.Bone.TIP;
if (plan || hum)
{
b.x = s * u * .145 + ox;
b.y = oy;
}
else if (dig)
{
b.x = s * u * -.25 + ox;
b.y = oy;
}
else if (ung)
{
b.x = s * u * -.34 + ox;
b.y = oy;
}
//Heel
if ((plan || hum) && claws)
{
var heelBone = new OCOBone( i18n._("Heel") );
heelBone.type = OCO.Bone.HEEL;
heelBone.x = s * u * .01 + ox;
heelBone.y = oy;
b.children.push(heelBone);
}
return limb;
}
/**
* Creates a new spine.
* @param {Boolean} [head=true] Whether to create a head
* @param {int} [neck=1] Number of neck bones
* @param {int} [spine=2] Number of spine bones
* @param {Boolean} [hips=true] Whether to create hips
* @param {float[]} [position] The position of the first bone of the arm.<br />
* If omitted, computed automatically according to the current character in the doc.
* @param {OCO.View} [view] The view
* @returns {OCOLimb} The spine
*/
OCODoc.prototype.newSpine = function( head, neck, spine, hips, position )
{
head = OCO._d( head, true );
neck = OCO._d( neck, 1 );
spine = OCO._d( spine, 2 );
hips = OCO._d( hips, false );
// Unit
var u = this.height;
// count how many bones we need
var num = 1;
if ( head ) num++;
num += neck;
num += spine;
if ( hips ) num++;
if (num == 1) return null;
// Create limb
var limb = this.newLimb( OCO.Limb.SPINE, OCO.Side.NONE, OCO.Location.NONE );
var b = limb.newArmature( i18n._("Spine"), num );
// hips position
var ox = 0;
var oy = 0;
var x = 0;
var y = u * .53;
// offset
if (typeof position !== 'undefined' )
{
ox = position[0] - x;
oy = position[1] - y;
}
// for each part, set the location
if ( hips )
{
b.name = i18n._("Hips");
b.type = OCO.Bone.HIPS;
b.x = x + ox;
b.y = y + oy;
if (b.children.length > 0) b = b.children[0];
}
if (spine > 1)
{
var totalSpineLength = u*.27;
var torsoLength = totalSpineLength*.5;
var spineLength = (totalSpineLength - torsoLength) / (spine-1);
var spineStart = u*.56;
for (var i = 0; i < spine-1; i++)
{
b.name = i18n._("Spine");
b.type = OCO.Bone.SPINE;
b.y = spineStart + i*spineLength + oy;
b.x = ox;
if (b.children.length > 0) b = b.children[0];
}
// Torso
b.name = i18n._("Torso");
b.type = OCO.Bone.TORSO;
b.y = spineStart + (spine-1)*spineLength + oy;
b.x = ox;
if (b.children.length > 0) b = b.children[0];
}
else if (spine == 1)
{
// Torso
b.name = i18n._("Torso");
b.type = OCO.Bone.TORSO;
b.x = ox;
b.y = u*.56 + oy;
if (b.children.length > 0) b = b.children[0];
}
if (neck > 0)
{
var neckLength = u*.06 / neck;
var neckStart = u*.83;
for (var i = 0; i < neck; i++)
{
b.name = i18n._("Neck");
b.type = OCO.Bone.NECK;
b.y = neckStart + i*neckLength + oy;
b.x = ox;
if (b.children.length > 0) b = b.children[0];
}
}
if ( head )
{
b.name = i18n._("Head");
b.type = OCO.Bone.SKULL;
b.x = ox;
b.y = u*.89 + oy;
if (b.children.length > 0) b = b.children[0];
}
// Tip
b.name = i18n._("Spine") +
'_' + i18n._("Tip");
b.type = OCO.Bone.TIP;
b.x = ox;
b.y = u + oy;
return limb;
}
/**
* Creates a new tail.
* @param {int} [numBones=3] Number of tail bones
* @returns {OCOLimb} The spine
*/
OCODoc.prototype.newTail = function( numBones )
{
numBones = OCO._d( numBones, 3 );
// Unit
var u = this.height;
// count how many bones we need
var num = numBones+1;
if (num == 1) return null;
// Create limb
var limb = this.newLimb( OCO.Limb.TAIL, OCO.Side.NONE, OCO.Location.NONE );
var b = limb.newArmature( i18n._("Tail"), num );
// for each part, adjust name and type
for (var i = 0, n = num-1; i < n; i++)
{
b.name = i18n._("Tail");
if ( i <= (n-1) / 3) b.type = OCO.Bone.TAIL_ROOT;
else if ( i <= (n-1)*2 / 3) b.type = OCO.Bone.TAIL_MID;
else b.type = OCO.Bone.TAIL_END;
if (b.children.length > 0) b = b.children[0];
}
b.name = i18n._("Tail") +
'_' + i18n._("Tip");
b.type = OCO.Bone.TIP;
return limb;
}
/**
* Creates a new hair strand.
* @param {int} [numBones=3] Number of hair bones
* @returns {OCOLimb} The hair
*/
OCODoc.prototype.newHairStrand = function( numBones )
{
numBones = OCO._d( numBones, 3 );
// Unit
var u = this.height;
// count how many bones we need
var num = numBones+1;
if (num == 1) return null;
// Create limb
var limb = this.newLimb( OCO.Limb.HAIR, OCO.Side.NONE, OCO.Location.NONE );
var b = limb.newArmature( i18n._("Hair"), num );
var l = 100 / numBones;
// for each part, adjust name and type
for (var i = 0, n = num-1; i < n; i++)
{
b.name = i18n._("Hair");
if ( i <= (n-1) / 3) b.type = OCO.Bone.HAIR_ROOT;
else if ( i <= (n-1)*2 / 3) b.type = OCO.Bone.HAIR_MID;
else b.type = OCO.Bone.HAIR_END;
b.x = 0;
b.y = 150 - i*l;
if (b.children.length > 0) b = b.children[0];
}
b.name = i18n._("Hair") +
'_' + i18n._("Tip");
b.type = OCO.Bone.TIP;
b.x = 0;
b.y = 50;
return limb;
}
/**
* Creates a new wing.
* @param {OCO.Side} [side=OCO.Side.LEFT] The side
* @param {Boolean} [arm=true] Whether to create an arm / humerus
* @param {Boolean} [forearm=true] Whether to create a forearm
* @param {Boolean} [hand=true] Whether to create a hand
* @param {int} [feathers=5] Number of feathers
* @param {float[]} [position] The position of the first bone of the arm.<br />
* If omitted, computed automatically according to the current character in the doc.
* @param {OCO.View} [view=OCO.View.TOP] The view
* @returns {OCOLimb} The wing
*/
OCODoc.prototype.newWing = function( side, arm, forearm, hand, feathers, position, view )
{
side = OCO._d(side, OCO.Side.LEFT);
view = OCO._d(view, OCO.View.TOP);
arm = OCO._d( arm, true );
forearm = OCO._d( forearm, true );
hand = OCO._d( hand, true );
feathers = OCO._d( feathers, 5 );
// Unit
var u = this.height;
// count how many bones we need
var num = 1;
if (arm) num++;
if (forearm) num++;
if (hand) num++;
if (num == 1) return null;
// Create limb
var limb = this.newLimb( OCO.Limb.WING, side, OCO.Location.NONE );
var b = limb.newArmature( i18n._("Wing"), num );
// side
var s = 1;
if ( side == OCO.Side.RIGHT && (view == OCO.View.TOP || view == OCO.View.BACK) ) s = -1;
else if ( side == OCO.Side.LEFT && (view == OCO.View.BOTTOM || view == OCO.View.FRONT) ) s = -1;
else if ( view == OCO.View.LEFT ) s = -1;
// arm position
var x, y;
if ( view == OCO.View.TOP || view == OCO.View.BOTTOM )
{
x = s * u * -.12;
y = u * .64;
}
else return limb; // not supported yet
var ox = 0;
var oy = 0;
// offset
if (typeof position !== 'undefined' )
{
ox = position[0] - x;
oy = position[1] - y;
}
// We need to keep the parents of the feathers
var parent1, parent2;
// for each part, set the location
if ( arm )
{
b.name = i18n._p("Anatomy", "Arm");
b.type = OCO.Bone.HUMERUS;
b.x = x + ox;
b.y = y + oy;
if (!forearm && !hand)
{
parent1 = b;
parent2 = b;
}
if (b.children.length > 0) b = b.children[0];
}
if (forearm)
{
b.name = i18n._p("Anatomy", "Forearm");
b.type = OCO.Bone.RADIUS;
b.x = s * u * -.22 + ox;
b.y = u * .53 + oy;
parent1 = b;
if (!hand) parent2 = b;
if (b.children.length > 0) b = b.children[0];
}
if (hand)
{
b.name = i18n._p("Anatomy", "Hand");
b.type = OCO.Bone.CARPUS;
b.x = s * u * -.37 + ox;
b.y = u * .64 + oy;
parent2 = b;
if (!forearm) parent1 = b;
if (b.children.length > 0) b = b.children[0];
}
// Tip
b.name = i18n._p("Anatomy", "Wing") +
'_' + i18n._p("Anatomy", "Tip");
b.type = OCO.Bone.TIP;
b.x = s * u * -.53 + ox;
b.y = u * .66 + oy;
// Add Feathers
if (feathers == 0) return limb;
function createFeather( xr, yr, xt, yt, p )
{
var boneName = OCO._uniqueString( i18n._p("Anatomy", "Feather"), names);
b = new OCOBone( boneName );
b.type = OCO.Bone.FEATHER;
b.x = s * u * xr + ox;
b.y = u * yr + oy;
// tip
var boneName = OCO._uniqueString( i18n._p("Anatomy", "Feather") +
'_' + i18n._("Tip"), names);
var bt = new OCOBone( boneName );
bt.type = OCO.Bone.TIP;
bt.x = s * u * xt + ox;
bt.y = u * yt + oy;
bt.attached = true;
b.children.push(bt);
p.children.push(b);
}
var names = [];
// Single one, add in the middle
if (feathers == 1)
{
createFeather( -.38, .63, -.56, .28, parent1 );
return limb;
}
for (var i = 0, n = feathers; i < n; i++)
{
if ( i < n/2)
{
var x = OCO._linearInterpolation( i, 0, n/2, -.54, -.38);
var y = OCO._linearInterpolation( i, 0, n/2, .66, .63);
var xt = OCO._linearInterpolation( i, 0, n/2, -.96, -.56);
var yt = OCO._linearInterpolation( i, 0, n/2, .62, .28);
createFeather( x, y, xt, yt, parent2 );
}
else
{
var x = OCO._linearInterpolation( i, n/2, n-1, -.38, -.22);
var y = OCO._linearInterpolation( i, n/2, n-1, .63, .51);
var xt = OCO._linearInterpolation( i, n/2, n-1, -.56, -.08);
var yt = OCO._linearInterpolation( i, n/2, n-1, .28, .39);
createFeather( x, y, xt, yt, parent1 );
}
}
return limb;
}
/**
* Creates a new snake spine.
* @param {Boolean} [head=true] Whether to create a head
* @param {int} [spine=5] Number of spine bones
* @param {float[]} [position] The position of the first bone of the arm.<br />
* If omitted, computed automatically according to the current character in the doc.
* @returns {OCOLimb} The snake spine
*/
OCODoc.prototype.newSnakeSpine = function( head, spine, position )
{
head = OCO._d( head, true );
spine = OCO._d( spine, 5 );
// Unit
var u = this.height;
// count how many bones we need
var num = spine+1;
if (num == 1) return null;
// Create limb
var limb = this.newLimb( OCO.Limb.SNAKE_SPINE, OCO.Side.NONE, OCO.Location.NONE );
var b = limb.newArmature( i18n._p("Anatomy", "Spine"), num );
// hips position
var ox = 0;
var oy = 0;
var x = u *.75;
var y = u * .5;
// offset
if (typeof position !== 'undefined' )
{
ox = position[0] - x;
oy = position[1] - y;
}
// for each part, set the location
var spineLength = u*1.5 / spine;
for (var i = 0; i < spine; i++)
{
b.name = i18n._p("Anatomy", "Spine");
if ( i <= (spine-1) / 3) b.type = OCO.Bone.SNAKE_SPINE_ROOT;
else if ( i <= (spine-1)*2 / 3) b.type = OCO.Bone.SNAKE_SPINE_MID;
else b.type = OCO.Bone.SNAKE_SPINE_END;
b.y = y + oy;
b.x = x - spineLength*i - ox;
if (b.children.length > 0) b = b.children[0];
}
// Tip
b.name = i18n._p("Anatomy", "Spine") +
'_' + i18n._p("Anatomy", "Tip");
b.type = OCO.Bone.TIP;
b.x = -u *.75;
b.y = y + oy;
// Head
if (head)
{
var b = new OCOBone( i18n._("Head") );
b.type = OCO.Bone.SKULL;
b.x = x;
b.y = y;
var s = limb.armature.pop();
limb.armature.push(b);
b.children.push(s);
var bt = new OCOBone( i18n._p("Anatomy", "Head") +
'_' + i18n._p("Anatomy", "Tip") );
bt.type = OCO.Bone.SKULL_TIP;
bt.x = x + u*.2;
bt.y = y;
bt.attached = true;
b.children.push(bt);
}
return limb;
}
/**
* Creates a new fish spine.
* @param {Boolean} [head=true] Whether to create a head
* @param {int} [spine=3] Number of spine bones
* @param {float[]} [position] The position of the first bone of the spine.<br />
* If omitted, computed automatically according to the current character in the doc.
* @returns {OCOLimb} The fish spine
*/
OCODoc.prototype.newFishSpine = function( head, spine, position )
{
head = OCO._d( head, true );
spine = OCO._d( spine, 3 );
// Unit
var u = this.height;
// count how many bones we need
var num = spine+1;
if (num == 1) return null;
// Create limb
var limb = this.newLimb( OCO.Limb.FISH_SPINE, OCO.Side.NONE, OCO.Location.NONE );
var b = limb.newArmature( i18n._p("Anatomy", "Spine"), num );
// hips position
var ox = 0;
var oy = 0;
var x = u *.75;
var y = u * .5;
// offset
if (typeof position !== 'undefined')
{
ox = position[0] - x;
oy = position[1] - y;
}
// for each part, set the location
var spineLength = u / spine;
for (var i = 0; i < spine; i++)
{
b.name = i18n._p("Anatomy", "Spine");
if ( i <= (spine-1) / 3) b.type = OCO.Bone.FISH_SPINE_ROOT;
else if ( i <= (spine-1)*2 / 3) b.type = OCO.Bone.FISH_SPINE_MID;
else b.type = OCO.Bone.FISH_SPINE_END;
b.y = y + oy;
b.x = x - spineLength*i - ox;
if (b.children.length > 0) b = b.children[0];
}
// Tip
b.name = i18n._p("Anatomy", "Spine") +
'_' + i18n._p("Anatomy", "Tip");
b.type = OCO.Bone.TIP;
b.x = -u *.25;
b.y = y + oy;
// Head
if (head)
{
var b = new OCOBone( i18n._p("Anatomy", "Head") );
b.type = OCO.Bone.SKULL;
b.x = x;
b.y = y;
var s = limb.armature.pop();
limb.armature.push(b);
b.children.push(s);
var bt = new OCOBone( i18n._p("Anatomy", "Head") +
'_' + i18n._p("Anatomy", "Tip") );
bt.type = OCO.Bone.SKULL_TIP;
bt.x = x + u*.3;
bt.y = y;
bt.attached = true;
b.children.push(bt);
}
return limb;
}
/**
* Creates a new fin.
* @param {OCO.Side} [side=OCO.Side.LEFT] The side
* @param {int} [fishbones=5] Number of feathers
* @param {float[]} [position] The position of the first bone of the arm.<br />
* If omitted, computed automatically according to the current character in the doc.
* @param {OCO.View} [view=OCO.View.RIGHT] The view
* @returns {OCOLimb} The fin
*/
OCODoc.prototype.newFin = function( side, fishbones, view, position )
{
side = OCO._d(side, OCO.Side.LEFT);
view = OCO._d(view, OCO.View.RIGHT);
fishbones = OCO._d( fishbones, 5 );
// Unit
var u = this.height;
// Create limb
var limb = this.newLimb( OCO.Limb.FIN, side, OCO.Location.NONE );
// side
var s = 1;
if ( side == OCO.Side.RIGHT && (view == OCO.View.TOP || view == OCO.View.BACK) ) s = -1;
else if ( side == OCO.Side.LEFT && (view == OCO.View.BOTTOM || view == OCO.View.FRONT) ) s = -1;
else if ( view == OCO.View.LEFT ) s = -1;
// arm position
var x = s * u * -.17;
var y = u * .47;
var ox = 0;
var oy = 0;
// offset
if ( typeof position !== 'undefined' )
{
ox = position[0] - x;
oy = position[1] - y;
}
// Create root
var rootBone = new OCOBone( i18n._p("Anatomy", "Fin") );
rootBone.type = OCO.Bone.FIN;
rootBone.x = x + ox;
rootBone.y = y + oy;
limb.armature.push(rootBone);
// Tip
var bt = new OCOBone( i18n._p("Anatomy", "Fin") +
'_' + i18n._p("Anatomy", "Tip") );
bt.type = OCO.Bone.TIP;
bt.x = s * u * -.24 + ox;
bt.y = u * .62 + oy;
bt.attached = true;
rootBone.children.push(bt);
// Add FishBones
if (fishbones == 0) return limb;
var names = [];
function createFishbone( xr, yr, xt, yt )
{
var boneName = OCO._uniqueString( i18n._p("Anatomy", "Fishbone"), names);
b = new OCOBone( boneName );
b.type = OCO.Bone.FIN_FISHBONE;
b.x = s * u * xr + ox;
b.y = u * yr + oy;
// tip
var boneName = OCO._uniqueString( i18n._p("Anatomy", "Fishbone") +
'_' + i18n._p("Anatomy", "Tip"), names);
var bt = new OCOBone( boneName );
bt.type = OCO.Bone.TIP;
bt.x = s * u * xt + ox;
bt.y = u * yt + oy;
bt.attached = true;
b.children.push(bt);
rootBone.children.push(b);
}
// Single one, add in the middle
if (fishbones == 1)
{
createFishbone( -.22, .43, -.77, .60 );
return limb;
}
for (var i = 0, n = fishbones; i < n; i++)
{
var x = OCO._linearInterpolation( i, 0, n-1, -.24, -.15);
var y = OCO._linearInterpolation( i, 0, n-1, .63, .41);
var xt = OCO._linearInterpolation( i, 0, n-1, -.82, -.17);
var yt = OCO._linearInterpolation( i, 0, n-1, .60, .29);
createFishbone( x, y, xt, yt );
}
return limb;
}
/**
* Converts a doc coordinate/value in centimeters to pixel coordinates.<br/>
* For multidimensionnal values (coordinates), the origin is adjusted from doc to image.
* @param {float[]} point The coordinate to convert
* @returns {int[]} The coordinates in pixels
*/
OCODoc.prototype.toPixels = function( point )
{
if (point.length)
{
// First, convert to pixels
var x = point[0]*this.pixelsPerCm;
var y = point[1]*this.pixelsPerCm;
// We're relative to the world, adjust
return this.fromWorld( [x, y] );
}
return point * this.pixelsPerCm;
}
/**
* Converts coordinates in pixels in the world to centimeters coordinates in the doc<br/>
* For multidimensionnal values (coordinates), the origin is adjusted image to doc.
* @param {float[]|float} point The coordinate to convert
* @returns {float[]|float} The coordinates in centimeters relative to the doc
*/
OCODoc.prototype.fromPixels = function( point )
{
if (point.length)
{
// We need to be relative to the world
point = this.toWorld(point);
var x = point[0];
var y = point[1];
// Convert to cm
x /= this.pixelsPerCm;
// Y coordinate is at bottom in OCO, top in 2D docs
y /= this.pixelsPerCm;
return [x, y];
}
return point / this.pixelsPerCm;
}
/**
* Creates a js object containing this document data.<br/>
* This object could then be exported to JSON for example.
* @return {Object} the JS Object
*/
OCODoc.prototype.toObject = function()
{
var data = {
'type': this.type,
'name': this.name,
'height': this.height,
'width': this.width,
'centerOfMass': this.centerOfMass,
'limbs': [],
'resolution': this.resolution,
'world': this.world,
'pixelsPerCm': this.pixelsPerCm,
'icon': this.icon,
'imageEncoding': this.imageEncoding
}
for (var i = 0, n = this.limbs.length; i < n; i++)
{
var limbData = this.limbs[i].toObject();
data['limbs'].push(limbData);
}
return data;
}
/**
* Creates a JSON string representing this document
* @param {OCO.ImageEncoding} [imageEncoding=OCO.ImageEncoding.PATH] How to encode images in the OCO File
* @param {File} [destinationFile] If imageEncoding is `OCO.ImageEncoding.PATH`, you must provide the OCO file to make the paths relative to it.
* @return {string} the JSON document
*/
OCODoc.prototype.toJson = function( destinationFile )
{
var data = this.toObject();
if (data.imageEncoding == OCO.ImageEncoding.PNG_BASE64) {
data.icon = OCO._base64( data.icon );
}
else if (data.imageEncoding == OCO.ImageEncoding.PATH) {
data.icon = OCO._relativePath( data.icon, destinationFile );
}
else if (data.imageEncoding == OCO.ImageEncoding.ABS_PATH) {
var iconFile = new File( data.icon );
data.icon = iconFile.absoluteURI();
}
return JSON.stringify(data, null, 4);
}
/**
* Exports the current document to an oco file
* @param {File|string} file The file.
* @return {File} the file.
*/
OCODoc.prototype.toFile = function ( file )
{
if (!(file instanceof File)) file = new File(file);
file.encoding = 'UTF-8';
var data = this.toJson( );
if (!file.open('w')) return file;
file.write(data);
file.close();
return file;
}
/**
* Gets all the bones sorted by z index
* @return {OCOBone[]} The list of bones
*/
OCODoc.prototype.getBones = function ()
{
var allBones = [];
for (var i = 0, ni = this.limbs.length; i < ni; i++)
{
allBones = allBones.concat(this.limbs[i].getBones());
}
allBones.sort( OCOBone._zSorter );
return allBones;
}
/**
* Normalizes the Z indices of all bones so they're positive (including 0) and continuous integers.
* @param {int} [offset=0] An offset/start number
* @return {int} The highest index
*/
OCODoc.prototype.normalizeZIndices = function( offset )
{
offset = OCO._d(offset, 0);
var numBones = this.numBones();
// List all bones
var bs = this.getBones();
// Update indices
for(var i = 0, ni = bs.length; i < ni; i++)
{
bs[i].zIndex = offset + i;
}
}
/**
* Finds the maximum and minimum Z index from all bones
* @return {int[]} The [min, max] Z indices
*/
OCODoc.zBounds = function()
{
var maxZ = Number.NEGATIVE_INFINITY;
var minZ = Number.POSITIVE_INFINITY;
for (var i = 0, ni = this.limbs.length; i < ni; i++)
{
var zb = this.limbs[i].zBounds();
if (zb[0] < minZ) minZ = zb[0];
if (zb[1] > maxZ) maxZ = zb[1];
}
return [minZ, maxZ];
}
/**
* Extracts the icon from the OCO file
* @param {string|File} file The OCO file
* @param {string|File} [destination] The destination file if the file is included in the OCO file. Next to the OCO file by default.
* @return {File} The icon file, or null if there was no icon/the file could not be written.
*/
OCODoc.extractIcon = function( file, destination )
{
if (!(file instanceof File)) file = new File(file);
// Check if there's an icon to extract
var data = OCO._parseJsonFile( file );
if (data.icon == "") return null;
if (data.imageEncoding == OCO.ImageEncoding.ABS_PATH) return new File(data.icon);
if (data.imageEncoding == OCO.ImageEncoding.PATH)
{
destination = OCO._absolutePath( data.icon, file);
return new File(destination);
}
if (data.icon != "" && data.imageEncoding == OCO.ImageEncoding.PNG_BASE64)
{
destination = OCO._d(destination, file.parent.absoluteURI + "/" + file.displayName.replace(".oco", ".png"));
return OCO._fromBase64(data.icon, destination);
}
return null;
}
/**
* Creates a new doc by reading a file
* @param {File|string} file The file
* @return {OCODoc|null} The document or null if the file couldn't be read or parsed
*/
OCODoc.fromFile = function ( file )
{
var data = OCO._parseJsonFile( file );
var doc = new OCODoc( OCO._d( data.name, "Character" ) );
doc.type = OCO._d( data.type, OCO.Type.META );
doc.height = OCO._d( data.height, 185 );
doc.width = OCO._d( data.width, 60 );
doc.centerOfMass = OCO._d( data.centerOfMass, [0.0, 107.0] );
doc.resolution = OCO._d( data.resolution, [1920, 1080] );
doc.world = OCO._d( data.world, [960, 980] );
doc.pixelsPerCm = OCO._d( data.pixelsPerCm, 4.22 );
doc.icon = OCO._d( data.icon, "" );
doc.imageEncoding = OCO._d( data.imageEncoding, OCO.ImageEncoding.PATH );
// Check Data
if (doc.resolution[0] <= 0 || doc.resolution[0] > 30000)
throw "Invalid resolution; the width must be in the range [0, 30000]";
if (doc.resolution[1] <= 0 || doc.resolution[1] > 30000)
throw "Invalid resolution; the height must be in the range [0, 30000]";
// Parse limbs
data.limbs = OCO._d( data.limbs, [] );
for (var i = 0, ni = data.limbs.length; i < ni; i++)
{
doc.limbs.push( OCOLimb.fromObject(data.limbs[i]) );
}
return doc;
}
/**
* Creates a new OCO Limb.
* @class
* @classdesc A limb contained in an OCO Doc.
* @param {OCO.Limb} [limb=OCO.Limb.CUSTOM] A Predefined limb
* @param {OCO.Side} [side=OCO.Side.NONE] - The side of the limb
* @param {OCO.Location} [location=OCO.Location.NONE] - The location of the limb
* @param {OCO.LimbType} [type=OCO.LimbType.CUSTOM] - The type of the limb
* @category OCO
*/
function OCOLimb(limb, side, location, type )
{
/**
* The predefined limb
* @name limb
* @memberof OCOLimb
* @type {OCO.Limb}
*/
this.limb = OCO._d(limb, OCO.Limb.CUSTOM);
/**
* The type of the limb
* @name type
* @memberof OCOLimb
* @type {OCO.LimbType}
*/
this.type = OCO._d(type, OCO.LimbType.CUSTOM);
/**
* The sided of the limb
* @name side
* @memberof OCOLimb
* @type {OCO.Side}
*/
this.side = OCO._d( side, OCO.Side.NONE);
/**
* The location of the limb
* @name location
* @memberof OCOLimb
* @type { OCO.Location}
*/
this.location = OCO._d( location, OCO.Location.NONE);
/**
* The bones of the limb
* @name location
* @memberof OCOLimb
* @type {OCOBone[]}
*/
this.armature = [];
}
/**
* Counts the total number of bones in this limb
* @return {int}
*/
OCOLimb.prototype.numBones = function ()
{
var count = 0;
for (var i = 0, ni = this.armature.length; i < ni; i++ )
{
count += this.armature[i].numBones();
}
return count;
}
/**
* Counts the total number of limbs in this limb
* @return {int}
*/
OCOLimb.prototype.numLimbs = function ()
{
var count = 1; // myself
for (var i = 0, ni = this.armature.length; i < ni; i++ )
{
count += this.armature[i].numLimbs();
}
return count;
}
/**
* Gets the boundaries of the limb
* @returns {float[]} [left, top, right, bottom]
*/
OCOLimb.prototype.bounds = function( )
{
return OCO.getBounds( this.armature );
}
/**
* Creates a new chain of bones and adds it to the limb.
* @param {string} name The name of the bones (will increment if needed)
* @param {int} [num=2] The number of bones in the chain
* @param {float} [length=100.0] The length in centimeters
* @return {OCOBone} The root bone.
*/
OCOLimb.prototype.newArmature = function( name, num, length )
{
num = OCO._d(num, 2);
length = OCO._d(length, 100.0);
var boneLength = length / (num-1);
var x = -length / 2;
var b = null;
var root = null;
var names = [];
for (var i = 0; i < num; i++)
{
var boneName = OCO._uniqueString(name, names);
var bone = new OCOBone( boneName );
names.push(boneName);
bone.y = 100;
bone.x = x;
bone.zIndex = length - i;
if (b != null)
{
b.children.push( bone );
bone.attached = true;
}
else
{
this.armature.push( bone );
root = bone;
}
x += boneLength;
b = bone;
}
return root;
}
/**
* Creates a js object containing this limb data.<br/>
* This object could then be exported to JSON for example.
* @return {Object} The JS Object
*/
OCOLimb.prototype.toObject = function()
{
var data = {
'limb': this.limb,
'type': this.type,
'side': this.side,
'location': this.location,
'armature': []
}
for (var i = 0, n = this.armature.length; i < n; i++)
{
var boneData = this.armature[i].toObject();
data['armature'].push(boneData);
}
return data;
}
/**
* Normalizes the Z indices of all bones so they're positive (including 0) and continuous integers
* @param {int} [offset=0] An offset/start number
* @return {int} The highest index
*/
OCOLimb.prototype.normalizeZIndices = function( offset )
{
offset = OCO._d(offset, 0);
var numBones = this.numBones();
// List all bones
var bs = this.getBones();
// Update indices
for(var i = 0, ni = bs.length; i < ni; i++)
{
bs[i].zIndex = offset + i;
}
}
/**
* Gets all the bones sorted by z index
* @return {OCOBone[]} The list of bones
*/
OCOLimb.prototype.getBones = function ()
{
var allBones = [];
for (var i = 0, ni = this.armature.length; i < ni; i++)
{
allBones = allBones.concat(this.armature[i].getBones());
}
allBones.sort( OCOBone._zSorter );
return allBones;
}
/**
* Finds the maximum and minimum Z index from all bones
* @return {int[]} The [min, max] Z indices
*/
OCOLimb.prototype.zBounds = function()
{
var maxZ = Number.NEGATIVE_INFINITY;
var minZ = Number.POSITIVE_INFINITY;
for (var i = 0, ni = this.armature.length; i < ni; i++)
{
var zb = this.armature[i].zBounds();
if (zb[0] < minZ) minZ = zb[0];
if (zb[1] > maxZ) maxZ = zb[1];
}
return [minZ, maxZ];
}
/**
* Creates a limb from a js object.
* @param {Object} data The js object representing the limb
* @return {OCOLimb} The new limb
*/
OCOLimb.fromObject = function( data )
{
var limb = OCO._d( data.limb, OCO.Limb.CUSTOM);
var side = OCO._d( data.side, OCO.Side.NONE);
var location = OCO._d( data.location, OCO.Location.NONE);
var type = OCO._d( data.type, OCO.LimbType.CUSTOM);
var l = new OCOLimb( limb, side, location, type);
// Parse armature
data.armature = OCO._d( data.armature, [] );
for (var i = 0, ni = data.armature.length; i < ni; i++)
{
l.armature.push( OCOBone.fromObject( data.armature[i] ) );
}
return l;
}
/**
* Creates a new OCO Bone.
* @class
* @classdesc A bone contained in an OCO Limb.
* @param {string} name The name
* @category OCO
*/
function OCOBone(name)
{
/**
* The name of the bone
* @name name
* @memberof OCOBone
* @type {string}
*/
this.name = name;
/**
* The x coordinate of the bone
* @name x
* @memberof OCOBone
* @type {float}
*/
this.x = 0.0;
/**
* The y coordinate of the bone
* @name y
* @memberof OCOBone
* @type {float}
*/
this.y = 0.0;
/**
* An arbitrary Z-index. Higher is under, lower is above
* @name zIndex
* @memberof OCOBone
* @type {int}
*/
this.zIndex = 0;
/**
* true if this bone is attached to its parent.
* @name attached
* @memberof OCOBone
* @type {Boolean}
*/
this.attached = false;
/**
* The child bones.
* @name children
* @memberof OCOBone
* @type {OCOBone[]}
*/
this.children = [];
/**
* The child limbs.
* @name limbs
* @memberof OCOBone
* @type {OCOLimb[]}
*/
this.limbs = [];
/**
* The type of bone.
* @name type
* @memberof OCOBone
* @type {OCO.Bone}
*/
this.type = OCO.Bone.CUSTOM;
/**
* The envelop of the bone,<br/>
* In a meta rig, this is a silhouette which will contain the design,
* and can be used to help locate the joint, link the design to the bone, etc.<br/>
* In a rigged character, this should be a simple silhouette close to the artwork silhouette.
*/
this.envelop = {};
/**
* The width of the envelop, around the joint
* @type {float}
*/
this.envelop.width = -1.0;
/**
* The offset of the envelop, in the local X-axis<br/>
* 0.0 is centered around the joint,<br/>
* positive values are on the right of the joint when the bone is oriented towards the ground,<br/>
* negative values are on the left of the joint when the bone is oriented towards the ground.
* @type {float}
*/
this.envelop.offset = 0.0;
}
/**
* The length of the bone (this distance with its first child)
* @return {int} The length
*/
OCOBone.prototype.length = function ()
{
if (this.children.length == 0) return 0;
var child = this.children[0];
return OCO._length([this.x, this.y], [child.x, child.y]);
}
/**
* Creates a new limb and adds it to the bone
* @param {OCO.Limb} [limb=OCO.Limb.CUSTOM] A Predefined limb
* @param {OCO.Side} [side=OCO.Side.NONE] - The side of the limb
* @param {OCO.Location} [location=OCO.Location.NONE] - The location of the limb
* @param {OCO.LimbType} [type=OCO.LimbType.CUSTOM] - The type of the limb
* @return {OCOLimb} The new limb
*/
OCOBone.prototype.newLimb = function( limb, side, location, type )
{
var l = new OCOLimb( limb, side, location, type );
this.limbs.push(l);
return l;
}
/**
* Counts the total number of child bones
* @return {int}
*/
OCOBone.prototype.numBones = function ()
{
var count = 1;
for (var i = 0, ni = this.children.length; i < ni; i++ )
{
count += this.children[i].numBones();
}
for (var i = 0, ni = this.limbs.length; i < ni; i++ )
{
count += this.limbs[i].numBones();
}
return count;
}
/**
* Counts the total number of child limbs
* @return {int}
*/
OCOBone.prototype.numLimbs = function ()
{
var count = 0;
for (var i = 0, ni = this.children.length; i < ni; i++ )
{
count += this.children[i].numLimbs();
}
count += this.limbs.length;
return count;
}
/**
* Translates the bone by [x, y] pixels
* @param {int} [x=0] The horizontal offset
* @param {int} [y=0] The vertical offset
* @param {bool} [translateChildren=true] If false, the children stay at their current location
*/
OCOBone.prototype.translate = function( x, y, translateChildren ) {
translateChildren = OCO._d(translateChildren, true);
x = OCO._d(x, 0);
y = OCO._d(y, 0);
this.x += x;
this.y += y;
if (translateChildren) {
for (var i = 0, n = this.children.length; i < n; i++) {
this.children[i].translate(x, y);
}
}
}
/**
* Translates the bone to the new coordinates
* @param {int} [x] The new X value. If omitted, moves the layer vertically
* @param {int} [y] The new Y value. If omitted, moves the layer horizontally
* @param {bool} [translateChildren=true] If false, the children stay at their current location
*/
OCOBone.prototype.translateTo = function( x, y, translateChildren ) {
translateChildren = OCO._d(translateChildren, true);
x = OCO._d(x, this.x);
y = OCO._d(y, this.y);
var offsetX = x - this.x;
var offsetY = y - this.y;
this.x = x;
this.y = y;
if (translateChildren) {
for (var i = 0, n = this.children.length; i < n; i++) {
this.children[i].translate(offsetX, offsetY);
}
}
}
/**
* Creates a js object containing this bone data.<br/>
* This object could then be exported to JSON for example.
* @return {Object} the JS Object
*/
OCOBone.prototype.toObject = function()
{
var data = {
'name': this.name,
'x': this.x,
'y': this.y,
'zIndex': this.zIndex,
'attached': this.attached,
'children': [],
'limbs': [],
'type': this.type
}
data.envelop = {};
data.envelop.width = this.envelop.width;
data.envelop.offset = this.envelop.offset;
for (var i = 0, n = this.children.length; i < n; i++)
{
var boneData = this.children[i].toObject();
data['children'].push(boneData);
}
for (var i = 0, n = this.limbs.length; i < n; i++)
{
var limbData = this.limbs[i].toObject();
data['limbs'].push(limbData);
}
return data;
}
/**
* Normalizes the Z indices of all bones so they're positive (including 0) and continuous integers
* @param {int} [offset=0] An offset/start number
* @return {int} The highest index
*/
OCOBone.prototype.normalizeZIndices = function( offset )
{
offset = OCO._d(offset, 0);
var numBones = this.numBones();
// List all bones
var bs = this.getBones();
// Update indices
for(var i = 0, ni = bs.length; i < ni; i++)
{
bs[i].zIndex = offset + i;
}
}
/**
* Gets all the bones sorted by z index
* @return {OCOBone[]} The list of bones
*/
OCOBone.prototype.getBones = function ()
{
var allBones = [];
for (var i = 0, ni = this.limbs.length; i < ni; i++)
{
allBones = allBones.concat(this.limbs[i].getBones());
}
for (var i = 0, ni = this.children.length; i < ni; i++)
{
allBones = allBones.concat(this.children[i].getBones());
}
allBones.push(this);
allBones.sort( OCOBone._zSorter );
return allBones;
}
/**
* Finds the maximum and minimum Z index from all bones
* @return {int[]} The [min, max] Z indices
*/
OCOBone.zBounds = function()
{
var maxZ = Number.NEGATIVE_INFINITY;
var minZ = Number.POSITIVE_INFINITY;
for (var i = 0, ni = this.limbs.length; i < ni; i++)
{
var zb = this.limbs[i].zBounds();
if (zb[0] < minZ) minZ = zb[0];
if (zb[1] > maxZ) maxZ = zb[1];
}
for (var i = 0, ni = this.children.length; i < ni; i++)
{
var zb = this.children[i].zBounds();
if (zb[0] < minZ) minZ = zb[0];
if (zb[1] > maxZ) maxZ = zb[1];
}
if (this.zIndex < minZ) minZ = this.zIndex;
if (this.zIndex > maxZ) maxZ = this.zIndex;
return [minZ, maxZ];
}
/**
* Creates a bone from a js object.
* @param {Object} data The js object representing the bone
* @return {OCOBone} The new bone
*/
OCOBone.fromObject = function( data )
{
var bone = new OCOBone( OCO._d(data.name, "Bone"));
bone.x = OCO._d(data.x, 0.0);
bone.y = OCO._d(data.y, 0.0);
bone.zIndex = OCO._d(data.zIndex, 0);
bone.attached = OCO._d(data.attached, false);
bone.type = OCO._d(data.type, OCO.Bone.CUSTOM);
data.envelop = OCO._d(data.envelop, {});
bone.envelop.width = OCO._d(data.envelop.width, bone.envelop.width);
bone.envelop.offset = OCO._d(data.envelop.offset, bone.envelop.offset);
// Parse children
data.children = OCO._d(data.children, []);
for( var i = 0, ni = data.children.length; i < ni; i++)
{
bone.children.push( OCOBone.fromObject(data.children[i]) );
}
// Parse limbs
data.limbs = OCO._d(data.limbs, []);
for (var i = 0, ni = data.limbs.length; i < ni; i++)
{
bone.limbs.push( OCOLimb.fromObject(data.limbs[i]) );
}
return bone;
}
// Sorter for bones according to the z index
OCOBone._zSorter = function (ba, bb)
{
return ba.zIndex - bb.zIndex;
}
//*/
Source