/*
 * Resizer Lite v0.1.4a (19 November 2004)
 * Copyright 2004 digitalMSX, Australia. by Leon Dang.
 *   For Licensing details, see bottom of script. Basically you are
 *   free to use this script as you like, whether commercially or
 *   private, so long as you retain copyright acknowledgements.
 *
 * TODO:
 *  - use mask instead of actually cropping the image layer
 *    in case the person wants no cropping after resize at all.
 *  - position text in vertical plane correctly.
 *  - include graphic signature elements
 */

/*
 * DO NOT MODIFY ANYTHING WITHIN THIS SECTION *****
 *
 * Constants
 */

 // leave this for debugging purposes
$.level = 1;

 // ruler units
var PIXELS = 0, pixels = 0;
var INCHES = 1, inches = 1;
var CM   = 2, cm = 2;

 // orientation options
var ORIG = 0, orig = 0;
var LANDSCAPE = 1, landscape = 1;
var PORTRAIT = 2, portrait = 2;

 // text positions
var TOP_EDGE = 0x00;
var BOTTOM_EDGE = 0x10;
var LEFT_EDGE = 0x20;
var RIGHT_EDGE = 0x40;

var LEFT_CORNER = 0x00;
var CENTER = 0x01;
var RIGHT_CORNER = 0x02;

var VERTICAL_1 = 0x04; // bottom to top direction
var VERTICAL_2 = 0x08; // top to bottom direction

var TEXT_ALIGN_BORDER = 0x100;

/*
 * MODIFY THIS SECTION ONLY ***********************
 *
 * Change the following parameters to what you want.
 * Only change the values after the "=" sign.
 *   - units can be PIXELS, CM, or INCHES
 *   - long_side and short_side define the dimensions
 *      of your output image. If long_side is 0, then
 *      the image is resized in proportion to the
 *      short_side, and the image is not cropped.
 *      The converse applies to the short_side.
 *   - dpi = resolution. If you don't want it changed
 *      then set it to 0
 *   - orientation can be ORIG, LANDSCAPE or PORTRAIT
 *   - border if set to 0 means no matting border
 *      layer created.
 *   - dropshadow = moves the resized image to the
 *      top left corner so that a dropshadow layer
 *      style can be applied to the layer, for thumbnail
 *      purposes.
 */

var units       = INCHES;
var long_side   = 7;
var short_side  = 5;
var dpi         = 250;
var orientation = ORIG;
var border      = 0.25;
var dropshadow  = 0;

/* Text options:
 *
 * %f - filename
 * %F - filename with extension
 * %d - date in YYYY/mm/dd format
 * %D - date in dd/mm/YYYY format
 * %h - time in hh:mm AM/PM, 12 hour format
 * %H - time in HH:MM, 24 hour format
 * %i - ISO
 * %a - aperture (f/stop)
 * %s - speed (1/n seconds)
 * %l - lens focal length (in mm)
 * %b - camera Make
 * %c - camera Model
 *
 * NOTE: Photoshop only allows for 7bit ASCII to be typed into
 *       a variable's string. To include special symbols such
 *       as Copyright (C), you can use one of:
 *         \251   (octal)
 *         \xA9   (hex)
 *         \u00A9 (unicode hex)
 *
 *       For other symbols, use Character Map to show the
 *       Hexadecimal numeric value of that character and enter it
 *       for the string as in the Copyright example above.
 *
 * Others:
 *   \n - new line character [Enter]
 *
 * text size is specified in Points
 *
 * *** If you do not want text, simply set text_fields to ""
 *         ie: var text_fields = "";
 */

var text_fields = new Array (
	new Array(
		"\xA9 Copyright - text field 1",  // text
		"Arial",                          // font
		6,                                // size
		TOP_EDGE | LEFT_CORNER | TEXT_ALIGN_BORDER // align
	),
	new Array(
		"%D - %h - text field 2",         // text
		"Arial",                          // font
		4,                                // size
		BOTTOM_EDGE | LEFT_CORNER | TEXT_ALIGN_BORDER // align
	),
	new Array(
		"%f - text field 3",              // text
		"Arial",                          // font
		4,                                // size
		BOTTOM_EDGE | RIGHT_CORNER | TEXT_ALIGN_BORDER // align
	),
	new Array(
		"ISO %i - vertical field 1.1",    // text
		"Arial",                          // font
		4,                                // size
		TOP_EDGE | LEFT_EDGE | VERTICAL_1 | TEXT_ALIGN_BORDER // align
	),
	new Array(
		"f/%a - vertical field 1.2",      // text
		"Arial",                          // font
		4,                                // size
		CENTER | LEFT_EDGE | VERTICAL_2 | TEXT_ALIGN_BORDER // align
	),
	new Array(
		"%s s - vertical field 1.3",      // text
		"Arial",                          // font
		4,                                // size
		BOTTOM_EDGE | LEFT_EDGE | VERTICAL_1 | TEXT_ALIGN_BORDER // align
	),
	new Array(
		"f/%a %ss - vertical field 2.1",  // text
		"Arial",                          // font
		4,                                // size
		TOP_EDGE | RIGHT_EDGE | VERTICAL_2 | TEXT_ALIGN_BORDER // align
	),
	new Array(
		"%ss - vertical field 2.2",       // text
		"Arial",                          // font
		4,                                // size
		CENTER | RIGHT_EDGE | VERTICAL_1 | TEXT_ALIGN_BORDER // align
	),
	new Array(
		"f/%a - vertical field 2.3",      // text
		"Arial",                          // font
		4,                                // size
		BOTTOM_EDGE | RIGHT_EDGE | VERTICAL_2 | TEXT_ALIGN_BORDER // align
	)

    );

var proof_text = "";
var proof_font = "Arial";
var proof_text_size = 60;
var proof_text_rotate = 10; /* degrees of rotation, counter-clockwise */
var proof_text_opacity = 50.0; /* Opacity of proof text layer (0-100) */

/*
 * DO NOT MOFIY ANYTHING BELOW ********************
 *
 * The work
 */

var EDGE_MASK = 0xf0;
var JUST_MASK = 0x0f;

try {
	Main();
} catch (e) {
	alert(e);
}

/*
 * Functions for Adobe's XMP metadata for TIFF/RAW EXIF information.
 */
function ExifSearch(exifarray, key)
{
	var i;

	for (i = 0; i < exifarray.length; i++) {
		if (exifarray[i][0] == key) {
			return (exifarray[i][1]);
		}
	}
	return "";
}

function ExtractXMLValue(str)
{
	var i;
	var obtaining = 0;
	var txt = "";

	for (i = 0; i < str.length; i++) {
		if ((str.charAt(i) != ' ') &&
		    (str.charAt(i) != '\n') &&
		    (str.charAt(i) != '\r') &&
		    (str.charAt(i) != '\t'))
			txt += str.charAt(i);
	}

	str = txt;
	txt = "";
	i = 0;
	while (i < str.length) {
		if (str.charAt(i) == '<') {
			if (obtaining)
				break;
			while ((i < str.length) &&
				(str.charAt(i) != '>')) {
				i++;
			}
		} else {
			txt += str.charAt(i);
			obtaining = 1;
		}
		i++;
	}
	return txt;
}

function SearchXML(xml, key)
{
	if (xml == undefined) {
		return "";
	}

	var s = "exif:" + key;
	var end = "</exif:" + key;
	var i, j;
	var newstring = "";
	i = xml.indexOf(s);
	if (i > 0) {
		newstring = xml.substring(i, xml.length);
		i = newstring.indexOf(">") + 1;
		newstring = newstring.substring(i, newstring.length);
		j = newstring.indexOf(end);
		newstring = newstring.substring(0, j);
		newstring = ExtractXMLValue(newstring);
	}
	return newstring;
}

/*
 * Scan through EXIF for the key and return its value if we find it
 */
function ExifSearch(exifarray, key)
{
	var i;

	for (i = 0; i < exifarray.length; i++) {
		if (exifarray[i][0] == key) {
			return (exifarray[i][1]);
		}
	}
	return "";
}

function GetDateTimeOriginal(docinfo, xml)
{
	var tmpstring = "";

	if ((docinfo != undefined) && (docinfo.exif != undefined)) {
		tmpstring = ExifSearch(docinfo.exif, "Date Time Original");
	}
	if (tmpstring == "") {
		if (xml != undefined) {
			tmpstring = SearchXML(xml, "DateTimeOriginal");
		}
		if (tmpstring != "") {
			var re = new RegExp ('T', 'gi');
			tmpstring = tmpstring.replace(re, ' ');
			re = new RegExp('Z', 'gi');
			tmpstring = tmpstring.replace(re, '');
			re = new RegExp('-', 'gi');
			tmpstring = tmpstring.replace(re, ':');
		}
	}
	return tmpstring;
}

/*
 * Parse the text string, and reproduce any EXIF information
 */
function ParseText(doc, input)
{
// XXX see a use for regex to break up the fields
	var output = "";
	var tmpstring = "";
	var i, j, c;
	var dinfo = doc.info;
	var xml;
	var returnval = new Array(2);
	var lines = 1;

	if (doc.xmpMetadata != undefined) {
		xml = doc.xmpMetadata.rawData.toString();
	}

	i = 0;
	while (i < input.length) {
		if (input.charAt(i) == '%') {
			// an escape character 
			i++;
			c = input.charAt(i);

			switch (c) {
			case 'f':
				j = doc.name.lastIndexOf(".");
				output += doc.name.substring(0, j);
				break;
			case 'F':
				output += doc.name;
				break;
			case 'd':
				tmpstring = GetDateTimeOriginal(dinfo, xml);
				if (tmpstring != "") {
					// reformat the date 
					j = tmpstring.indexOf(" ");
					tmpstring = tmpstring.substring(0, j);
					for (j = 0; j < tmpstring.length; j++) {
						if (tmpstring.charAt(j) == ':')
							output += "/";
						else
							output += tmpstring.charAt(j);
					}
				}
				break;
			case 'D':
				tmpstring = GetDateTimeOriginal(dinfo, xml);
				if (tmpstring != "") {
					// reformat the date
					var y, m, d;
					j = tmpstring.indexOf(":");
					y = tmpstring.substring(0, j);
					tmpstring = tmpstring.substring(j+1, tmpstring.length);
					j = tmpstring.indexOf(":");
					m = tmpstring.substring(0, j);
					tmpstring = tmpstring.substring(j+1, tmpstring.length);
					j = tmpstring.indexOf(" ");
					d = tmpstring.substring(0, j);
					output += d + "/" + m + "/" + y;
				}
				break;
			case 'h':
			case 'H':
				tmpstring = GetDateTimeOriginal(dinfo, xml);
				if (tmpstring != "") {
					// reformat the time
					j = tmpstring.indexOf(" ");
					tmpstring = tmpstring.substring(j+1, tmpstring.length);
					j = tmpstring.lastIndexOf(":");
					tmpstring = tmpstring.substring(0, j);

					if (c == 'h') {
						var h, m;
						j = tmpstring.indexOf(":");
						h = parseInt(tmpstring.substring(0, j));
						m = tmpstring.substring(j+1, tmpstring.length);
						if (h > 12) {
							h -= 12;
							if (h < 10)
								output += "0";
							output += h + ":" + m + " PM";
						} else {
							if (h < 10)
								output += "0";
							output += h + ":" + m + " AM";
						}
					} else {
						output += tmpstring;
					}
				}
				break;
			case 'i':
				if ((dinfo != undefined) && (dinfo.exif != undefined)) {
					tmpstring = ExifSearch(dinfo.exif, "ISO Speed Ratings");
					if (tmpstring != "") {
						output += tmpstring;
					}
				}
				if (tmpstring == "") {
					tmpstring = SearchXML(xml, "ISOSpeedRatings");
					if (tmpstring != "") {
						output += tmpstring;
					}
				}
				break;
			case 'a':
				if ((dinfo != undefined) && (dinfo.exif != undefined)) {
					tmpstring = ExifSearch(dinfo.exif, "Aperture Value");
					if (tmpstring != "") {
						output += tmpstring.substring(2, tmpstring.length);
					}
				}
				if (tmpstring == "") {
					tmpstring = SearchXML(xml, "FNumber");
					if (tmpstring != "") {
						tmpstring = eval(tmpstring).toString();
						output += tmpstring;
					}
				}
				break;
			case 's':
				if ((dinfo != undefined) && (dinfo.exif != undefined)) {
					tmpstring = ExifSearch(dinfo.exif, "Shutter Speed");
					if (tmpstring != "") {
						output += tmpstring.substring(0,
							tmpstring.indexOf(" "));
					}
				}
				if (tmpstring == "") {
					tmpstring = SearchXML(xml, "ExposureTime");
					output += tmpstring;
				}
				break;
			case 'l':
				if ((dinfo != undefined) && (dinfo.exif != undefined)) {
					tmpstring = ExifSearch(dinfo.exif, "Focal Length");
					if (tmpstring != "") {
						output += tmpstring.substring(0, tmpstring.indexOf(" "));
					}
				}
				if (tmpstring == "") {
					tmpstring = SearchXML(xml, "FocalLength");
					tmpstring = eval(tmpstring).toString();
					output += tmpstring;
				}
				break;
			case 'b':
				if ((dinfo != undefined) && (dinfo.exif != undefined)) {
					tmpstring = ExifSearch(dinfo.exif, "Make");
					if (tmpstring != "") {
						output += tmpstring;
					}
				}
				if (tmpstring == "") {
					tmpstring = SearchXML(xml, "Make");
					output += tmpstring;
				}
				break;
			case 'c':
				if ((dinfo != undefined) && (dinfo.exif != undefined)) {
					tmpstring = ExifSearch(dinfo.exif, "Model");
					if (tmpstring != "") {
						output += tmpstring;
					}
				}
				if (tmpstring == "") {
					tmpstring = SearchXML(xml, "Model");
					output += tmpstring;
				}
				break;
			case '%':
				output += '%';
				break;
			// ignore any other escaped character
			}
		} else {
			if (input.charAt(i) == '\n') {
				output += '\r';
				lines++;
			} else {
				output += input.charAt(i);
			}
		}
		i++;
	}

	returnval[0] = output;
	returnval[1] = lines;
	return (returnval);
}

function WriteText(doc, txt, txtsize, txtfont, txtalign, layername)
{
	var x = 0, y = 0;
	var lineheight;
	var linewidth;

	var textcolor = new SolidColor;
	textcolor.rgb.red = 0;
	textcolor.rgb.green = 0;
	textcolor.rgb.blue = 0;

	var artlayer = doc.artLayers.add();
	artlayer.kind = LayerKind.TEXT;

	artlayer.name = layername;

	var txtref = artlayer.textItem;

	// This will automatically convert to PIXEL units for us
	txtref.size = txtsize;
	txtref.contents = txt;
	txtref.color = textcolor;
	txtref.font = txtfont;
	txtref.kind = TextType.PARAGRAPHTEXT;

	lineheight = txtref.height.value;
	var vertical = false;

	if (txtalign & VERTICAL_1) {
		// Top of text is left, bottom faces right
		artlayer.rotate(-90);
		vertical = true;
	} else if (txtalign & VERTICAL_2) {
		// Top of text is right, bottom faces left
		artlayer.rotate(90);
		vertical = true;
	}

	if (vertical) {
		//
		// For some reason Photoshop artLayer.translate only
		// recognises original values with their Unit object,
		// ie we don't extract the .value element of the
		// bounds so that we keep it in its original Unit object
		// state. If we don't use the Unit object, then
		// the numbers get corrupted... majorly.
		//
		var bounds = artlayer.bounds;
		var x1, x2, y1, y2;
		var gap = border;

		x1 = bounds[0];
		x2 = bounds[2];
		y1 = bounds[1];
		y2 = bounds[3];
		linewidth = x2 - x1;
		lineheight = y2 - y1;

		if ((border == 0) || (txtalign & TEXT_ALIGN_BORDER == 0)) {
			gap = 3;
		}

		if (txtalign & CENTER) {
			y = (doc.height - lineheight)/2 - y1;
		} else if (txtalign & BOTTOM_EDGE) {
			y = doc.height - gap - lineheight - y1;
		} else {
			y = -y1 + gap;
		}

		if (gap > linewidth.value) {
			gap = (gap - linewidth.value)/2;
		}
		if (txtalign & RIGHT_EDGE) {
			x = doc.width - linewidth - gap - x1;
		} else {

			x = -x1 + gap;
		}

		artlayer.translate(x, y);
	} else {
		//
		// XXX ToDo: use artLayer.translate so that the
		//           code is common to that of the above.
		//
		if ((txtalign & EDGE_MASK) == BOTTOM_EDGE) {
			if (border > 0) {
				if (txtref.height > border) {
					y = doc.height.value - lineheight - 3;
				} else {
					y = doc.height.value - border + (border - lineheight)/2;
				}
			} else {
				y = doc.height - lineheight - 3;
			}
		} else if ((txtalign & EDGE_MASK) == TOP_EDGE) {
			if (border > 0) {
				if (lineheight > border) {
					y = lineheight + 3;
				} else {
					y = (border - lineheight*3/4)/ 2;
				}
			} else {
				y = lineheight + 3;
			}
		}

		if ((txtalign & JUST_MASK) == LEFT_CORNER) {
			txtref.justification = Justification.LEFT;
			if ((border > 0) && (txtalign & TEXT_ALIGN_BORDER)) {
				x = border;
			} else {
				x = 3;
			}
		} else if ((txtalign & JUST_MASK) == CENTER) {
			txtref.justification = Justification.CENTER;
			x = (doc.width.value - txtref.width.value)/2;
		} else {
			// right
			txtref.justification = Justification.RIGHT;
			if ((border > 0) && (txtalign & TEXT_ALIGN_BORDER)) {
				x = doc.width.value - border - txtref.width.value;
			} else {
				x = doc.width.value - 3 - txtref.width.value;
			}
		}

		txtref.position = Array(x, y);
	}
}

function DrawText(doc)
{
	var runits_orig, x1, y1;
	runits_orig = app.preferences.rulerUnits;
	app.preferences.rulerUnits = Units.PIXELS;

	var tunits_orig = app.preferences.typeUnits;
	app.preferences.typeUnits = TypeUnits.PIXELS;

	if (text_fields != "") {
		var ntxtfields = text_fields.length;
		var t, txt;

		for (x = 0; x < ntxtfields; x++) {
			t = text_fields[x];
			if (t[0] != "") {
				txt = ParseText(doc, t[0])[0];
				WriteText(doc, txt, t[2], t[1], t[3], "TextLayer"+x);
			}
		}
	}

	if (proof_text != "") {
		var artlayer3 = doc.artLayers.add();
		artlayer3.kind = LayerKind.TEXT;
		artlayer3.name = "ProofTextLayer";

		var txt3ref = artlayer3.textItem;

		var textcolor = new SolidColor;
		textcolor.rgb.red = 150;
		textcolor.rgb.green = 0;
		textcolor.rgb.blue = 50;

		txt3ref.size = proof_text_size;
		txt3ref.contents = proof_text;
		txt3ref.color = textcolor;
		txt3ref.font = proof_font;
		txt3ref.justification = Justification.CENTER;

		x1 = doc.width/2;
		y1 = doc.height/2 + txt3ref.size/4;
		txt3ref.position = Array(x1, y1);

		if (proof_text_rotate != 0) {
			artlayer3.rotate(proof_text_rotate * (-1));
		}
		artlayer3.opacity = proof_text_opacity;
	}

	app.preferences.rulerUnits = runits_orig;
	app.preferences.typeUnits = tunits_orig;
}


function Main()
{
	var doc = app.activeDocument;
	var width = doc.width.value;
	var height = doc.height.value;
	var proportion1, proportion2;
	var newx, newy;
	var crop = false;

	// backup store
	var orig_ruler_units = app.preferences.rulerUnits;
	var orig_type_units = app.preferences.typeUnits;
	var tmp;

	if (units == INCHES) {
		app.preferences.rulerUnits = Units.INCHES;
	} else if (units == CM) {
		app.preferences.rulerUnits = Units.CM;
	} else {
		app.preferences.rulerUnits = Units.PIXELS;
	}

	app.preferences.typeUnits = TypeUnits.POINTS;

	if (dpi == 0) {
		dpi = doc.resolution;
	}

	if (long_side == 0) {
		// fit to short side - no croppying 
		if (width > height) {
			proportion1 = short_side / height;
		} else {
			proportion1 = short_side / width;
		}
	} else if (short_side == 0) {
		// fit to long side - no cropping
		if (width > height) {
			proportion1 = long_side / width;
		} else {
			proportion1 = long_side / height;
		}
	} else if (long_side < short_side) {
		var tmp = long_side;
		long_side = short_side;
		short_side = tmp;
		crop = true;
	} else {
		crop = true;
	}

	if (crop) {
		if (width > height) {
			proportion1 = long_side / width;
			proportion2 = short_side / height;
		} else {
			proportion1 = short_side / width;
			proportion2 = long_side / height;
		}

		if (proportion1 >= proportion2) {
			newx = proportion1 * width;
			newy = proportion1 * height;
		} else {
			newx = proportion2 * width;
			newy = proportion2 * height;
		}
	} else {
		newx = proportion1 * width;
		newy = proportion1 * height;
	}

	// Use the new Photoshop Bicubic resize algorithms
	// depending on whether we inlarge or reduce
	if (newx > width) {
		doc.resizeImage(newx, newy, dpi,
		    ResampleMethod.BICUBIC);
	} else {
		doc.resizeImage(newx, newy, dpi,
		    ResampleMethod.BICUBIC);
	}

	// now crop the dimensions
	if (crop) {
		if (newx > newy) {
			doc.resizeCanvas(long_side, short_side,
			    AnchorPosition.MIDDLECENTER);
		} else {
			doc.resizeCanvas(short_side, long_side,
			    AnchorPosition.MIDDLECENTER);
		}
	}

	// resize and crop to accommodate matting border
	if (border > 0 || dropshadow > 0) {
		if (units != PIXELS) {
			// set to pixels
			if (units == INCHES) {
				border = border * doc.resolution;
			} else if (units == CM) {
				border = border / 2.54 * doc.resolution;
			}
		}

		app.preferences.rulerUnits = Units.PIXELS;

		var bottom_layer = doc.activeLayer;
		var top_layer = doc.activeLayer.duplicate();
		top_layer.name = "ResizedLayer1";

		/* Clear the bottom layer */
		var fillcolor = new SolidColor();
		fillcolor.rgb.red = 255;
		fillcolor.rgb.green = 255;
		fillcolor.rgb.blue = 255;

		doc.activeLayer = bottom_layer;
		doc.selection.selectAll();
		doc.selection.fill(fillcolor, ColorBlendMode.NORMAL);

		/* Resize and crop the top layer */
		width = doc.width.value;
		height = doc.height.value;

		/* When doing a resize, we have to crop the shorter side */
		var bvar;
		if (border > 0)
			bvar = border;
		else
			bvar = dropshadow;

		if (width < height) {
			proportion1 = (height - (bvar*2)) / height * 100;
		} else {
			proportion1 = (width - (bvar*2)) / width * 100;
		}

		doc.activeLayer = top_layer;
		if (dropshadow == 0) {
			/* Only want a border */
			top_layer.resize(proportion1, proportion1,
			    AnchorPosition.MIDDLECENTER);

			var selRegion = Array(
				Array(border, border),
				Array(width-border, border),
				Array(width-border, height-border),
				Array(border, height-border)
				);

			doc.selection.select(selRegion, SelectionType.REPLACE);
			doc.selection.invert();
			doc.selection.clear();
			doc.selection.deselect();
		} else {
			doc.resizeCanvas(width+dropshadow, height+dropshadow,
			    AnchorPosition.TOPLEFT);
		}

	}

	// Add text info
	if ((text_fields != "") || (proof_text != "")) {
		DrawText(doc);
	}

	// rotate to orientation desired
	if (orientation == LANDSCAPE) {
		if (width < height) {
			doc.rotateCanvas(90.0);
		}
	} else if (orientation == PORTRAIT) {
		if (width > height) {
			doc.rotateCanvas(90.0);
		}
	}

	// clean-up
	app.preferences.rulerUnits = orig_ruler_units;
	app.preferences.typeUnits = orig_type_units;
}

// Done
//
// -----------------------------------------------------------------------
// Copyright 2004 digitalMSX. All rights reserved.
//
// Redistribution and use in source and binary forms, with or
// without modification, are permitted provided that the following
// conditions are met:
//
// Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in
// the documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY digitalMSX ``AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL digitalMSX OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
