<!-- hide JavaScript from old browsers

// JavaScript global Variables
// -------------------------------------------------------
// Configuration Defaults
// -------------------------------------------------------
var imageFormat = "35 mm";                 // Standard Image Plane Size
var aperture = 8;                          // Aperture (f/stop)
var focusPoint = 6;                        // Subject:object (focus) distance
var focusIsObject = false;                 // using object or subject distance
var distScale = "Feet";                    // Distance Scaling
var fStopIncr = "full";                    // full, half, or third stop steps
var calcCoC = false;                       // Calculate CoC based on image format
var aovBasis = "d";                        // AoV based on: width, height, or diagonal (w,h,d)
var aovObject = false;                     // AoV based on: object or image plane
var useEffStop = true;                     // Use effective aperture for DoF and diffraction
var megaPixels = 0;                        // Digital megapixels (millions)
var photoSiteSize = 0;                     // Digital photo-site size (um)
var dofFormula = "W";                      // M=DofMaster, K=NormanKoren, W=Wiki_pupil, V=PaulvanWalree_pupil
var xtraDiag = false;                      // show extra diagnostic information
var warnRR = 0.5;                          // RR: aubject approaching macro distances (warning)
var alertRR = 1.0;                         // RR: aubject exceeds macro distance (alert)
var maxRR = 8.0;                           // RR: max accepted
var pupilRatio = 1.0;                      // exit/entrance pupil ratio
var useMscale = true;                      // use M or RR for scaling EF, Airy Disk, ...
var usePortrait = true;                    // use portrait or landscape orientation
var formatDefault = "p";                   // default lens and subject distance: p, n, or RR
//-------------------------------------------------------------------------------------
// Image Format defaults    name,      CoC,  width,   height, lens (normal perspective)
//-------------------------------------------------------------------------------------
var imageFormatList = [["custom",      0.030, 36.0,    24.0,    50],
                       ["P&S 1/2.3\"", 0.006,  6.13,    4.60,   10], // Nikon CoolPix S610 10MP @3648x2736
                       ["P&S 1/1.8\"", 0.006,  7.18,    5.32,   10], // Canon PowerShot G6?
                       ["P&S 1/1.7\"", 0.006,  7.40,    5.55,   10], // Nikon CoolPix S710 14.5MP @4352x3264
                       ["P&S 4/3 \"",  0.015, 18.0,    13.5,    25], // Olympus E-400 10mp 14-45mm?
                       ["Canon 1.6x",  0.019, 22.5,    15.0,    30], // 22.2x14.8 - 22x15 - 22.5x15.0?
                       ["Nikon 1.5x",  0.020, 23.6,    15.8,    35], // 23.6-7x15.5-8?
                       ["Canon 1.3x",  0.023, 27.0,    18.0,    35], // 28.7x19.1 - 27x18?
                       ["APS",         0.025, 30.2,    16.7,    40],
                       ["35 mm",       0.030, 36.0,    24.0,    50],
                       ["4x4 cm",      0.041, 40.0,    40.0,    60],
                       ["4.5x6 cm",    0.052, 60.0,    45.0,    70], // 56x42?
                       ["6x6 cm",      0.056, 60.0,    60.0,    80], // 56x56?
                       ["6x7 cm",      0.060, 70.0,    60.0,    90], // 56x67 : 56×69?
                       ["6x9 cm",      0.070, 90.0,    60.0,   120],
                       ["4x5 in",      0.100, 25.4*5,  25.4*4, 200],
                       ["5x7 in",      0.150, 25.4*7,  25.4*5, 250],
                       ["8x10 in",     0.200, 25.4*10, 25.4*8, 400]];
var degSymbol = "\u00B0";            // degree symbol
var crSymbol = "\u00A9";             // (c) symbol
var infSymbol = "\u221E";            // infinity symbol
var alertGiven = false;              // show macro distance alert only once
var dlg;                             // Options() object;

// -------------------------------------------------------
// Options()
// Constructor: set defaults from global defaults
// -------------------------------------------------------
function Options() {
    var testNum;
    var i;

    this.distScale = distScale;                 // Distance Scaling
    this.Fl = 50;                               // focal length (mm)
    this.N = aperture;                          // aperture: f/stop
    this.Ne;                                    // effective aperture
    this.useEffStop = useEffStop;               // use effective aperture
    this.useMscale = useMscale;                 // use M or RR for Ne calculations
    this.fStopIncr = fStopIncr;                 // selectable: full, half, or third stops
    this.Sd = this.convertDistance(focusPoint); // Subject (focus) distance from image plane (mm)
    this.Od;                                    // Object (focus) distance from focal plane (mm)
    this.Id;                                    // Image plane distance to lens focal plane (mm)
    this.focusIsObject = focusIsObject;         // Menu: object or subject distance
    this.coc = 0;                               // CoC (mm)
    this.cocDp = 3;                             // CoC decimal precision
    this.calcCoC = calcCoC;                     // Calculate CoC based on image format
    this.megaPixels = megaPixels;               // Digital megapixels (millions)
    this.photoSiteSize = photoSiteSize;         // Digital photo-site size (um)
    this.imageFormat = imageFormat;             // Image format string
    this.imageFormatIdx;                        // Image format index
    this.formatDefault = formatDefault;         // default lens and subject distance: p, n, or RR
    this.imageWidth;                            // Image width (mm)
    this.imageHeight;                           // Image height (mm)
    this.aovBasis = aovBasis;                   // AoV basis (w,h,d)
    this.aovObject = aovObject;                 // AoV based on: object or image plane
    this.usePortrait = usePortrait;             // use portrait or landscape orientation
    this.dofFormula = dofFormula;               // Select desired DoF formula
    if (this.focusIsObject)                     // Hyperfocal Distance plus focal lenght
        this.includeHyperFL = false;
    else
        this.includeHyperFL = true;
    this.fStopList;                             // Selectable f/stop list (precise for calculations)
    this.fStopSelect;                           // Selectable f/stop list (rounded for selection)
    this.P = pupilRatio;                        // exit/entrance pupil ratio
    this.airyDisk;                              // Airy Disk
    this.RR;                                    // perpendicular magnification         image:subject size ratio
    this.M1;                                    // lateral magnification (Id/Od):      image:object distance ratio
    this.M2;                                    // lateral magnification (Fl/(Od-Fl)): focal:object distance ratio
    this.M3;                                    // lateral magnification (Id-Fl)/Fl    image:focal distance ratio
    this.Hd;                                    // Hyperfocal Distance
    this.frontDoF;                              // DoF Distance in front of subject
    this.backDoF;                               // DoF Distance behind subject
    this.nearLimit;                             // Focus Distance limit in front of subject
    this.farLimit;                              // Focus Distance limit behind subject
    this.dofTotal;                              // for text menu displays only
    this.dofBefore;                             // for text menu displays only
    this.dofAfter;                              // for text menu displays only
    this.aov;                                   // angle of view (degrees)
    this.fov;                                   // field of view (distance units)
    this.miscTxtBxText = "";                    // misc message string
    //-----------------------------
    // populate the list of f/stops
    //-----------------------------
    this.refreshFstopList();
    //-----------------------------------
    // populate the list of image formats
    //-----------------------------------
    document.CameraData.imageFormatLstBx.options.length = 0;
    for (i=0; i<imageFormatList.length; i++) {
        document.CameraData.imageFormatLstBx.options[i] = 
            new Option(imageFormatList[i][0], i, false, false);
    }
    //------------------------------
    // find the default image format
    //------------------------------
    this.imageFormatIdx = 0;
    for (i=0; i<imageFormatList.length; i++) {
        if (this.imageFormat == imageFormatList[i][0]) {
            this.imageFormatIdx = i;
            break;
        }
    }
    document.CameraData.imageFormatLstBx.options[this.imageFormatIdx].selected = true;
    this.coc = imageFormatList[this.imageFormatIdx][1];         // standard CoC
    this.imageWidth = imageFormatList[this.imageFormatIdx][2];  // image width
    this.imageHeight = imageFormatList[this.imageFormatIdx][3]; // image height
    this.Fl = imageFormatList[this.imageFormatIdx][4];          // normal perspective lens
    if (this.usePortrait) {
        for (i=0; i<imageFormatList.length; i++) {
            testNum = imageFormatList[i][2];
            imageFormatList[i][2] = imageFormatList[i][3];
            imageFormatList[i][3] = testNum;
        }
        testNum = this.imageWidth;
        this.imageWidth = this.imageHeight;
        this.imageHeight = testNum;
    }
    if(this.calcCoC)
        document.CoC.calcCocCb.checked = true;
    else
        document.CoC.calcCocCb.checked = false;
    this.refreshPanels();
    return;
}

// -------------------------------------------------------
// Options.tryUpdateMenu()
// -------------------------------------------------------
Options.prototype["tryUpdateMenu"] = function(caller) {
    var ex, lineMsg;
    var codeMsg = "";
    var lineMsg = "";

    try {
        this.updateMenu(caller);
    } catch(ex) {
        if (ex.number != undefined)
            codeMsg = " errCode[" + ex.number + "]";
        if (ex.line != undefined)
            lineMsg = " line[" + ex.line + "]";
        alert("updateMenu(" + caller + ")" + codeMsg + lineMsg + " Exception:" +
              "\n" + ex, "Menu Exception!");
        if (this.menuActive)
            this.menu.close(0); // there is no point in re-throwing the exception
    }
    return;
}

// -------------------------------------------------------
// Options.updateMenu()
// -------------------------------------------------------
Options.prototype["updateMenu"] = function(caller) {
    var selectItem, testNum, msgStr;
    var Id, Od, Sd, RR, M;
    var i;

    switch(caller) {
        case "lensFlTxtBx":
            testNum = Number(document.CameraData.lensFlTxtBx.value);
            if (isNaN(testNum) || testNum < 1.0) {
                alert("updateMenu() Lens Focal Length=" + testNum + " must be a valid positive number >=1");
            } else {
                this.Fl = testNum;
            }
            break;
        case "apertureTxtBx":
            testNum = Number(document.CameraData.apertureTxtBx.value);
            if (isNaN(testNum) || testNum < 1.0) {
                alert("updateMenu() Aperture=" + testNum + " must be a valid positive number >=1");
            } else {
                this.N = testNum;
                /// this.menu.stopListLstBx.items[0].selected = true;
                document.CameraData.stopListLstBx.options[0].selected = true;
            }
            break;
        case "subjectDistanceTxtBx":
            if (document.CameraData.subjectDistanceTxtBx.value.toLowerCase() == "infinity" ||
                document.CameraData.subjectDistanceTxtBx.value.toLowerCase() == "inf" ||
                document.CameraData.subjectDistanceTxtBx.value.toLowerCase() == "i")
                testNum = Number.POSITIVE_INFINITY;
            else
                testNum = Number(document.CameraData.subjectDistanceTxtBx.value);
            if (isNaN(testNum) || testNum < 0.0) {
                alert("updateMenu() Focus Distance=" + testNum +
                      " must be a valid positive number or \"infinity\"");
                break;
            }
            testNum = this.convertDistance(testNum);
            Sd = testNum;
            if (Sd >= (4.0)*this.Fl) {
                this.Sd = Sd;
            } else {
                this.Sd = 4.0*this.Fl;
                alert("updateMenu() Subject distance below minimum focus, changed to macro 1:1");
            }
            this.focusIsObject = false;
            break;
        case "objectDistanceTxtBx":
            if (document.CameraData.objectDistanceTxtBx.value.toLowerCase() == "infinity" ||
                document.CameraData.objectDistanceTxtBx.value.toLowerCase() == "inf" ||
                document.CameraData.objectDistanceTxtBx.value.toLowerCase() == "i")
                testNum = Number.POSITIVE_INFINITY;
            else
                testNum = Number(document.CameraData.objectDistanceTxtBx.value);
            if (isNaN(testNum) || testNum < 0.0) {
                alert("updateMenu() Object Distance=" + testNum +
                      " must be a valid positive number or \"infinity\"");
                break;
            }
            testNum = this.convertDistance(testNum);
            Od = testNum;
            if (Od >= (2.0)*this.Fl) {
                this.Od = Od;
            } else {
                this.Od = 2.0*this.Fl;
                alert("updateMenu() Object distance below minimum focus, changed to macro 1:1");
            }
            this.focusIsObject = true;
            break;
        case "fStopRb_FULL":
            this.fStopIncr = "full";
            this.refreshFstopList();
            break;
        case "fStopRb_HALF":
            this.fStopIncr = "half";
            this.refreshFstopList();
            break;
        case "fStopRb_THIRD":
            this.fStopIncr = "third";
            this.refreshFstopList();
            break;
        case "stopListLstBx":
            selectItem = document.CameraData.stopListLstBx.value;
            if (selectItem > 0)
                this.N = this.fStopList[selectItem];
            break;
        case "rrTxtBx":
            testNum = Number(document.CameraData.rrTxtBx.value);
            if (isNaN(testNum) || testNum < 0.0 || testNum > maxRR) {
                alert("updateMenu() Reproduction Ratio=" + testNum + " must be between 0 and " + maxRR);
            } else if (testNum > 1.0){ // > 1:1 ?
                this.focusIsObject = true;
                this.distScale = "Millimeters";
                /// this.menu.distMmRb.value = true;
                RR = 1.0/testNum;      // RR reciprocal
                Sd = (4.0/RR)*this.Fl; // subject distance
                Id = this.findOpticalFit(Sd);
                this.Od = Id;          // swap Id:Od
            } else {
                this.focusIsObject = false;
                this.RR = testNum;
                this.Sd = (4.0/this.RR)*this.Fl;
                /// if (this.focusIsObject)
                    this.Od = this.Sd - this.findOpticalFit(this.Sd);
            }
            break;
        case "pupilRatioTxtBx":
            testNum = Number(document.CameraData.pupilRatioTxtBx.value);
            if (isNaN(testNum) || testNum < 0.0) {
                alert("updateMenu() Pupil Ratio=" + testNum + " must be a valid positive number");
            } else {
                this.P = testNum;
            }
            break;
        case "imageFormatLstBx":
            this.imageFormatIdx = document.CameraData.imageFormatLstBx.value;
            this.coc = imageFormatList[this.imageFormatIdx][1];     // photo standard CoC
            this.imageWidth = imageFormatList[this.imageFormatIdx][2];  // image width
            this.imageHeight = imageFormatList[this.imageFormatIdx][3]; // image height
            this.Fl = imageFormatList[this.imageFormatIdx][4];      // normal perspective lens
            this.photoSiteSize = 0;
            this.megaPixels = 0;
            alertGiven = false;
            break;
        case "imageWidthTxtBx":
            testNum = Number(document.CameraData.imageWidthTxtBx.value);
            if (isNaN(testNum) || testNum < 0.0) {
                alert("updateMenu() Image width=" + testNum + " must be a valid positive number (mm)");
            } else {
                this.imageWidth = testNum;
                if (this.megaPixels > 1.0) {
                    this.photoSiteSize = Math.sqrt(this.imageWidth * this.imageHeight); // mm2
                    this.photoSiteSize /= Math.sqrt(this.megaPixels*1000*1000) / 1000;  // um
                    this.photoSiteSize = Math.round(this.photoSiteSize*1000)/1000;
                }
            }
            break;
        case "imageHeightTxtBx":
            testNum = Number(document.CameraData.imageHeightTxtBx.value);
            if (isNaN(testNum) || testNum < 0.0) {
                alert("updateMenu() Image height=" + testNum + " must be a valid positive number (mm)");
            } else {
                this.imageHeight = testNum;
                if (this.megaPixels > 1.0) {
                    this.photoSiteSize = Math.sqrt(this.imageWidth * this.imageHeight); // mm2
                    this.photoSiteSize /= Math.sqrt(this.megaPixels*1000*1000) / 1000;  // um
                    this.photoSiteSize = Math.round(this.photoSiteSize*1000)/1000;
                }
            }
            break;
        case "megaPixelTxtBx":
            testNum = Number(document.CameraData.megaPixelTxtBx.value);
            if (testNum == "" || testNum == " ") break;
            if (isNaN(testNum) || testNum < 1.0) {
                alert("updateMenu() Megapixels=" + testNum + " must be a number greater than one (mp)");
            } else {
                this.megaPixels = testNum;
                this.photoSiteSize = Math.sqrt(this.imageWidth * this.imageHeight); // mm2
                this.photoSiteSize /= Math.sqrt(this.megaPixels*1000*1000) / 1000;  // um
                this.photoSiteSize = Math.round(this.photoSiteSize*1000)/1000;
            }
            break;
        case "siteSizeTxtBx":
            testNum = Number(document.CameraData.siteSizeTxtBx.value);
            if (isNaN(testNum) || testNum < 0.0) {
                alert("updateMenu() Photo-site size=" + testNum + " must be a valid positive number (um)");
            } else {
                this.photoSiteSize = testNum;
            }
            break;
        case "cocTxtBx":
            testNum = Number(document.CoC.cocTxtBx.value);
            if (isNaN(testNum) || testNum < 0.0) {
                alert("updateMenu() CoC=" + testNum + " must be a valid positive number (mm)");
            } else {
                this.coc = testNum;
            }
            break;
        case "calcCocCb":
            if (document.CoC.calcCocCb.checked) {
                this.cocDp = 5;
                this.calcCoC = true;
            } else {
                this.cocDp = 3;
                this.calcCoC = false;
                this.coc = imageFormatList[this.imageFormatIdx][1];
            }
            break;
        case "useHdFlCb":
            if (document.Hyperfocal.useHdFlCb.checked)
                this.includeHyperFL = true;
            else
                this.includeHyperFL = false;
            break;
        case "distRb_MI":
            this.distScale = "Miles";
            break;
        case "distRb_M":
            this.distScale = "Meters";
            break;
        case "distRb_Y":
            this.distScale = "Yards";
            break;
        case "distRb_FT":
            this.distScale = "Feet";
            break;
        case "distRb_IN":
            this.distScale = "Inches";
            break;
        case "distRb_CM":
            this.distScale = "Centimeters";
            break;
        case "distRb_MM":
            this.distScale = "Millimeters";
            break;
        case "aovBaseRb_W":
            this.aovBasis = "w";
            break;
        case "aovBaseRb_H":
            this.aovBasis = "h";
            break;
        case "aovBaseRb_D":
            this.aovBasis = "d";
            break;
        case "orientRb_P":
            if (this.usePortrait) break;
            for (i=0; i<imageFormatList.length; i++) {
                testNum = imageFormatList[i][2];
                imageFormatList[i][2] = imageFormatList[i][3];
                imageFormatList[i][3] = testNum;
            }
            testNum = this.imageWidth;
            this.imageWidth = this.imageHeight;
            this.imageHeight = testNum;
            this.usePortrait = true;
            break;
        case "orientRb_L":
            if (!this.usePortrait) break;
            for (i=0; i<imageFormatList.length; i++) {
                testNum = imageFormatList[i][2];
                imageFormatList[i][2] = imageFormatList[i][3];
                imageFormatList[i][3] = testNum;
            }
            testNum = this.imageWidth;
            this.imageWidth = this.imageHeight;
            this.imageHeight = testNum;
            this.usePortrait = false;
            break;
        case "imageDistanceTxtBx":
            alert("updateMenu() Image distance is not a valid input field");
            break;
        case "airyDiskTxtBx":
            alert("updateMenu() Airy Disk size is not a valid input field");
            break;
        case "miscTxtBx":
            alert("updateMenu() Miscellaneous calculations is not a valid input field");
            break;
        case "hyperDistanceTxtBx":
            alert("updateMenu() Hyperfocal Distance is not a valid input field");
            break;
        case "nearHyperDistanceTxtBx":
            alert("updateMenu() Near Hyperfocal Distance is not a valid input field");
            break;
        case "beforeSubjectDistanceTxtBx":
            alert("updateMenu() Before subject DoF is not a valid input field");
            break;
        case "afterSubjectDistanceTxtBx":
            alert("updateMenu() After subject DoF is not a valid input field");
            break;
        case "totalDoFDistanceTxtBx":
            alert("updateMenu() Total DoF Range is not a valid input field");
            break;
        case "nearDoFDistanceTxtBx":
            alert("updateMenu() Near DoF Distance is not a valid input field");
            break;
        case "farDoFDistanceTxtBx":
            alert("updateMenu() Far DoF Distance is not a valid input field");
            break;
        default:
            alert("updateMenu() Invalid caller=" + caller);
            break;
    }
    this.refreshPanels();
    return;
}

// -------------------------------------------------------
// Options.showDistance()
// all base distance calculations are in millimeters
// -------------------------------------------------------
Options.prototype["showDistance"] = function(distance) {
    var distanceUnits = distance;

    switch(this.distScale) {
        case "Miles":
            distanceUnits /= 1000.0;
            distanceUnits *= 3.28083989501312;
            distanceUnits /= 5280;
            break;
        case "Meters":
            distanceUnits /= 1000.0;
            break;
        case "Yards":
            distanceUnits /= 1000.0;
            distanceUnits *= 3.28083989501312;
            distanceUnits /= 3.0;
            break;
        case "Feet":
            distanceUnits /= 1000.0;
            distanceUnits *= 3.28083989501312;
            break;
        case "Inches":
            distanceUnits /= 1000.0;
            distanceUnits *= 3.28083989501312;
            distanceUnits *= 12.0;
            break;
        case "Centimeters":
            distanceUnits /= 10.0;
            break;
        case "Millimeters":
            break;
        default:
            throw("showDistance() invalid scale=" + this.distScale);
            break;
    }
    distanceUnits = Math.round(distanceUnits*1000)/1000;
    return(distanceUnits);
}

// -------------------------------------------------------
// Options.convertDistance()
// all base distance calculations are in millimeters
// -------------------------------------------------------
Options.prototype["convertDistance"] = function(distance) {
    var distanceUnits = distance;

    switch(this.distScale) {
        case "Miles":
            distanceUnits *= 1000.0;
            distanceUnits /= 3.28083989501312;
            distanceUnits *= 5280;
            break;
        case "Meters":
            distanceUnits *= 1000.0;
            break;
        case "Yards":
            distanceUnits *= 1000.0;
            distanceUnits /= 3.28083989501312;
            distanceUnits *= 3.0;
            break;
        case "Feet":
            distanceUnits *= 1000.0;
            distanceUnits /= 3.28083989501312;
            break;
        case "Inches":
            distanceUnits *= 1000.0;
            distanceUnits /= 3.28083989501312;
            distanceUnits /= 12.0;
            break;
        case "Centimeters":
            distanceUnits *= 10.0;
            break;
        case "Millimeters":
            break;
        default:
            throw("convertDistance() invalid scale=" + this.distScale);
            break;
    }
    return(distanceUnits);
}

// -------------------------------------------------------
// Options.refreshPanels()
// -------------------------------------------------------
Options.prototype["refreshPanels"] = function() {
    var msgStr;

    this.calculateAll(); // calculate all DoF metrics
    document.CameraData.lensFlTxtBx.value = this.Fl;
    document.CameraData.apertureTxtBx.value = Math.round(this.N*1000)/1000;
    document.CameraData.subjectDistanceTxtBx.value = this.showDistance(this.Sd);
    document.CameraData.objectDistanceTxtBx.value = this.showDistance(this.Od);
    document.CameraData.imageDistanceTxtBx.value = this.showDistance(this.Id);
    document.CameraData.rrTxtBx.value = padDecimalStr(3, this.RR);
    document.CameraData.pupilRatioTxtBx.value = this.P;
    document.CameraData.imageWidthTxtBx.value = this.imageWidth;
    document.CameraData.imageHeightTxtBx.value = this.imageHeight;
    document.CameraData.megaPixelTxtBx.value = this.megaPixels;
    document.CameraData.siteSizeTxtBx.value = this.photoSiteSize;
    document.CoC.cocTxtBx.value = padDecimalStr(this.cocDp, this.coc);
    document.CoC.airyDiskTxtBx.value = this.airyDisk;
    document.CoC.miscTxtBx.value = this.miscTxtBxText;
    document.Hyperfocal.hyperDistanceTxtBx.value = this.showDistance(this.Hd);
    document.Hyperfocal.nearHyperDistanceTxtBx.value = this.showDistance(this.Hd / 2);
    document.DoF.beforeSubjectDistanceTxtBx.value = this.dofBefore;
    document.DoF.afterSubjectDistanceTxtBx.value = this.dofAfter;
    document.DoF.totalDoFDistanceTxtBx.value = this.dofTotal;
    document.DoF.nearDoFDistanceTxtBx.value = this.showDistance(this.nearLimit);
    document.DoF.farDoFDistanceTxtBx.value = this.showDistance(this.farLimit);
    switch(this.distScale) {
        case "Miles":
            document.Distance.distRb[0].checked = true; break;
        case "Meters":
            document.Distance.distRb[1].checked = true; break;
        case "Yards":
            document.Distance.distRb[2].checked = true; break;
        case "Feet":
            document.Distance.distRb[3].checked = true; break;
        case "Inches":
            document.Distance.distRb[4].checked = true; break;
        case "Centimeters":
            document.Distance.distRb[5].checked = true; break;
        case "Millimeters":
        default:
            document.Distance.distRb[6].checked = true; break;
    }
    switch(this.aovBasis) {
        case "w":
            document.Distance.aovBaseRb[0].checked = true; break;
        case "h":
            document.Distance.aovBaseRb[1].checked = true; break;
        case "d":
        default:
            document.Distance.aovBaseRb[2].checked = true; break;
    }
    if (this.usePortrait)
        document.Distance.orientRb[0].checked = true;
    else
        document.Distance.orientRb[1].checked = true;
    return;
}

// -------------------------------------------------------
// Options.calculateAll()
// -------------------------------------------------------
Options.prototype["calculateAll"] = function() {
    var msgStr;

    //--------------
    // caclulate CoC
    //--------------
    if (this.calcCoC)
        this.coc = this.calculateCoC();
    //------------------------------
    // find the image plane distance
    //------------------------------
    if (this.focusIsObject) {
        this.Id = this.findOpticPlanes(this.Od);
        this.Sd = this.Od + this.Id;
    } else {
        this.Id = this.findOpticPlanes(this.Sd);
        this.Od = this.Sd - this.Id;
    }
    //----------------------------------------------------------------
    // lateral magnification ratios (equal when distances are correct)
    //----------------------------------------------------------------
    this.M1 = this.Id/this.Od;           // magnification image:object ratio
    this.M2 = this.Fl/(this.Od-this.Fl); // magnification focal:object ratio
    this.M3 = (this.Id-this.Fl)/this.Fl; // magnification image:focal ratio
    //-------------------------------------------------------
    // reproduction ratio, effective aperture, airy disk, ...
    //-------------------------------------------------------
    this.RR = this.calculateRR();
    if (this.useMscale) this.Ne = this.N*(1+this.M1);
    else                this.Ne = this.N*(1+this.RR);
    this.airyDisk = this.calculateAiryDisk();
    //----------------------------------------------
    // calculate new DoF metrics
    // Ne affects near & far DoF, but not Hyperfocal
    //----------------------------------------------
    this.calculateDofHyper();
    this.calculateDofFront_WikiPupil();
    this.calculateDofBack_WikiPupil();
    //------------------------
    // for the menu text boxes
    //------------------------
    this.dofTotal = this.showDistance(this.frontDoF + this.backDoF);
    this.dofBefore = this.showDistance(this.frontDoF);
    this.dofAfter = this.showDistance(this.backDoF);
    if (this.Sd == Number.POSITIVE_INFINITY) {
        this.nearLimit = this.Hd;
        this.farLimit = Number.POSITIVE_INFINITY;
        this.dofTotal = "";
        this.dofBefore = "";
        this.dofAfter = "";
    }
    if (Math.round(this.dofTotal*1000)/1000 > 0.0
        && this.dofTotal < Number.POSITIVE_INFINITY) {
        this.dofBefore += "  " + Math.round(this.frontDoF/(this.frontDoF+this.backDoF)*100) +"%";
        this.dofAfter += "  " + Math.round(this.backDoF/(this.frontDoF+this.backDoF)*100) +"%";
    }
    //-------------------------------
    // show RR, EF, AoV, FoV, etc.
    //-------------------------------
    msgStr  = "EF=" + Math.round(this.Ne*100)/100;
    msgStr += ", M=" + Math.round(this.M1*1000)/1000;
    if (xtraDiag) {
        msgStr += ", D=" + Math.round((this.Fl/this.N)*100)/100; // aperture diameter
        if (Math.round(this.M1*1000)/1000 != Math.round(this.M2*1000)/1000)
            msgStr += ", M2=" + Math.round(this.M2*1000)/1000;
        if (Math.round(this.M1*1000)/1000 != Math.round(this.M3*1000)/1000)
            msgStr += ", M3=" + Math.round(this.M3*1000)/1000;
    }
    if (this.imageWidth > 0 && this.imageHeight > 0) {
        this.aov = this.calculateAOV();
        this.fov = this.calculateFOV();
        if (msgStr != "")
            msgStr += ", ";
        msgStr += "AoV=" + Math.round(this.aov*10)/10 + degSymbol;
        if (this.Sd > 0 && this.Sd != Number.POSITIVE_INFINITY) {
            msgStr += ", FoV=" + this.showDistance(this.fov);
            switch(this.distScale) {
                case "Miles":       msgStr += " mi"; break;
                case "Meters":      msgStr += " m";  break;
                case "Yards":       msgStr += " yd"; break;
                case "Feet":        msgStr += " ft"; break;
                case "Inches":      msgStr += " in"; break;
                case "Centimeters": msgStr += " cm"; break;
                default:            msgStr += " mm"; break;
            }
        }
    } else {
        this.aov = 0;
        this.fov = 0;
    }
    if (Math.round(this.RR*10000)/10000 > 1.0)
       msgStr += ", < macro distance!";
    if (this.photoSiteSize > 0 && (2*this.photoSiteSize)/1000.0 < this.airyDisk)
       msgStr += ", sensor diffraction!";
    if (this.airyDisk > this.coc)
       msgStr += ", airy disk!";
    this.miscTxtBxText = msgStr;
    return;
}

// -------------------------------------------------------
// Options.refreshFstopList()
// -------------------------------------------------------
Options.prototype["refreshFstopList"] = function() {
    var Idx;
    var i;

    this.fStopList = buildFstopTable(this.fStopIncr);
    this.fStopSelect = new Array(); // Selectable f/stop list (rounded)
    for (i=0; i<this.fStopList.length; i++)
        this.fStopSelect[this.fStopSelect.length] = Math.round(this.fStopList[i]*10)/10;
    this.fStopSelect[0] = "custom";
    //-----------------------------
    // populate the list of f/stops
    //-----------------------------
    document.CameraData.stopListLstBx.options.length = 0;
    for (i=0; i<this.fStopSelect.length; i++) {
        document.CameraData.stopListLstBx.options[i] = 
            new Option(this.fStopSelect[i], i, false, false);
    }
    //------------------------
    // find the current f/stop
    //------------------------
    Idx = 0;
    for (i=0; i<this.fStopList.length; i++) {
        if (Math.round(this.N*1000)/1000 == Math.round(this.fStopList[i]*1000)/1000) {
            Idx = i;
            break;
        }
    }
    document.CameraData.stopListLstBx.options[Idx].selected = true;
    switch(this.fStopIncr) {
        case "full":
            document.CameraData.fStopRb[0].checked = true;
            break;
        case "half":
            document.CameraData.fStopRb[1].checked = true;
            break;
        case "thirds":
        default:
            document.CameraData.fStopRb[2].checked = true;
            break;
    }
    return;
}

// ----------------------------------------------
// Options.findOpticPlanes()
// ----------------------------------------------
Options.prototype["findOpticPlanes"] = function(distance) {
    var f = this.Fl;     // focal length
    var Od;              // object distance
    var Sd;              // subject distance
    var Id;              // image distance
    var M;               // image distance ratio
    var dP = 1000000;    // decimal precision
    var RR, x, error;
    var msgStr;

    if (this.focusIsObject) {
        Od = distance;      // object distance
        M = f/(Od-f);
        Id = (1+M) * f;
        Sd = Od + Id;
    } else {
        Sd = distance;      // subject distance
        Id = this.findOpticalFit(Sd);
        Od = Sd - Id;
        M = Id/Od;
    }
    //-------------------------
    // test 1/Od + 1/Id = 1/f !
    //-------------------------
    x = (1/Id) + (1/Od);
    error = Math.round((x-(1/f))*dP)/dP;
    RR = (4.0*f) / Sd;
    if (1==1 && error != 0.0) {
        msgStr  = "ERROR!(" + error + ")";
        msgStr += ", M(" + Math.round(M*dP)/dP + ")";
        if (!this.focusIsObject) msgStr += ", RR(" + Math.round(RR*1000)/1000 + ")";
        msgStr += "\n1/Id(" + Math.round((1/Id)*dP)/dP + ") ";
        msgStr += " + 1/Od(" + Math.round((1/(Od))*dP)/dP + ")";
        msgStr += ((1/(Id)) + (1/Od) == (1/f) ? "==":"!=");
        msgStr += " 1/f("+ Math.round((1/f)*dP)/dP + ")";
        msgStr += "\nId(" + Math.round((Id)*dP)/dP + ")";
        msgStr += ", Od(" + Math.round(((Od))*dP)/dP + ")";
        msgStr += ", Sd(" + Math.round((Sd)*dP)/dP + ")";
        alert("findOpticPlanes() " + msgStr);
    }
    return(Id);
}

// ----------------------------------------------------------
// Options.findOpticalFit()
// Find the best image/object fit at current subject distance
// ----------------------------------------------------------
Options.prototype["findOpticalFit"] = function(subjectDistance) {
    var f;               // focal length
    var sd;              // subject distance
    var id;              // image distance
    var rr;              // reproduction ratio
    var m;               // lateral magnification
    var x;               // focal offset

    //------------------------------
    // ignore 1:1 and infinity focus
    //------------------------------
    f = this.Fl;
    sd = subjectDistance;
    rr = 4*f/sd;
    id = (1+rr)*f;
    if (rr == 0 || rr == 1)
        return(id); // done
    //------------------------------
    // find m and image distance
    //------------------------------
    x = (sd/f) - 2;
    m = (x + Math.sqrt(Math.pow(x,2)-4)) / 2;
    if (rr < 1) m = 1/m;
    id = (1+m)*f;
    return(id);
}

// ------------------------------------------------
// Options.calculateRR()
// infinity (1:0) and macro (1:1) subject distances
// ------------------------------------------------
Options.prototype["calculateRR"] = function() {
    var RR = 0;

    if (this.Sd > 0)
        RR = (4.0*this.Fl) / this.Sd;
    if (this.M1 > 1.0) // < 1:1
        RR = 1.0/RR;   // reciprocal
    if (RR > alertRR) {
        if (!alertGiven) {
            alert("calculateRR() Reproduction Ratio=1:" + Math.round(RR*1000)/1000 +
                  "\nUsing only object distance for input!");
            alertGiven = true;
        }
    } else {
        alertGiven = false;
    }
    return(RR);
}

// -------------------------------------------------------
// Options.calculateAOV()
// Angle of View (degrees)
// -------------------------------------------------------
Options.prototype["calculateAOV"] = function() {
    var AOV;
    var a, b;

    switch (this.aovBasis) {
        case "w": b = this.imageWidth;
            break;
        case "h": b = this.imageHeight;
            break;
        case "d": b = Math.sqrt(Math.pow(this.imageWidth,2) + Math.pow(this.imageHeight,2));
            break;
        default:
            throw("calculateAOV() Invalid aovBasis=" + this.aovBasis);
            break;
    }
    b /= 2.0;                     // image size
    a = this.Id;                  // image distance
    if (this.aovObject && this.RR != 0) {
        b *= (1/this.RR);         // object size
        a = this.Od;              // object distance
    }
    AOV  = Math.atan2(b, a);      // radians
    AOV *= (2*180.0) / Math.PI;   // 2xdegrees
    return(AOV);
}

// -------------------------------------------------------
// Options.calculateFOV()
// Field of View (mm)
// -------------------------------------------------------
Options.prototype["calculateFOV"] = function() {
    var FOV;
    var d;
    var m;

    switch (this.aovBasis) {
        case "w": d = this.imageWidth;
            break;
        case "h": d = this.imageHeight;
            break;
        case "d": d = Math.sqrt(Math.pow(this.imageWidth,2) + Math.pow(this.imageHeight,2));
            break;
        default:
            throw("calculateFOV() Invalid aovBasis=" + this.aovBasis);
            break;
    }
    m = (1/this.RR);
    if (this.Sd == Number.POSITIVE_INFINITY || this.Sd == 0)
        FOV = Number.POSITIVE_INFINITY;
    else
        FOV = d * m;
    return(FOV);
}

// -------------------------------------------------------
// Options.calculateCoC()
// Circle of Confusion (mm)
// Based on 8x10 print and 5 lp/mm viewed at 25 cm
// -------------------------------------------------------
Options.prototype["calculateCoC"] = function() {
    var imageSize, size8x10;
    var M, COC;

    if (this.imageWidth == 0 || this.imageHeight == 0)
        return(this.coc);
    imageSize = Math.sqrt(Math.pow(this.imageWidth,2) + Math.pow(this.imageHeight,2));
    size8x10 = Math.sqrt(Math.pow(25.4*10,2) + Math.pow(25.4*8,2));
    M = size8x10 / imageSize;
    COC = (1/5) / M;
    return(COC);
}

// ----------------------------------------------------------------------
// Options.calculateAiryDisk()
// wl: blue@420nm, green@550nm, red@680nm
// use EF or N*(1+m) for aperture
//        or N*(1+(m/p))
// http://www.cambridgeincolour.com/tutorials/diffraction-photography.htm
// ----------------------------------------------------------------------
Options.prototype["calculateAiryDisk"] = function() {
    var fStop, AD;
    var dZ = 2.43932;  // diameter of middle dark zone after the first diffraction ring
    var wL = 0.000550; // defacto standard wavelength (green @ 550 nm)

    if (this.useEffStop) fStop = this.Ne;
    else                 fStop = this.N;
    AD = dZ * wL * fStop; // Rayleigh limit
    AD = Math.round(AD*10000)/10000;
    return(AD);
}

// ----------------------------------------------
// Options.calculateDofHyper()
// HyperFocal Distance
// note: pupil magnification & effective aperture
//       are never relavent in HD calculations
// http://en.wikipedia.org/wiki/Depth_of_field
// ----------------------------------------------
Options.prototype["calculateDofHyper"] = function() {
    var HD;

    HD = Math.pow(this.Fl,2) / (this.N*this.coc);
    if (this.includeHyperFL)
        HD += this.Fl;
    this.Hd = HD;
    return;
}

// -------------------------------------------------------
// Options.calculateDofFront_WikiPupil()
// DoF in front of the subject (focus) distance
// subject distance: from focal plane!
// M & P are addressed in this formula
// http://en.wikipedia.org/wiki/Depth_of_field
// -------------------------------------------------------
Options.prototype["calculateDofFront_WikiPupil"] = function() {
    var DN;
    var f = this.Fl;   // focal length
    var s = this.Od;   // object to focal plane
    var N = this.N;    // aperture f/stop
    var c = this.coc;  // CoC
    var P = this.P;    // exit/entrance pupil ratio
    var m;             // image magnification

    //-----------------------------
    // calculate the near DoF limit
    //-----------------------------
    m = f / (s-f);     // wiki definition of "m"
    DN = N*c*(1+(m/P));
    DN /= Math.pow(m,2)*(1+((N*c)/(f*m)));
    this.frontDoF = DN;
    //--------------------------------------------
    // near limit based on subject:object distance
    //--------------------------------------------
    if (this.focusIsObject)
        this.nearLimit = this.Od - this.frontDoF;
    else
        this.nearLimit = this.Sd - this.frontDoF;
    return;
}

// -------------------------------------------------------
// Options.calculateDofBack_WikiPupil()
// DoF behind the subject (focus) distance
// subject distance: from focal plane!
// M & P are addressed in this base formula
// http://en.wikipedia.org/wiki/Depth_of_field
// -------------------------------------------------------
Options.prototype["calculateDofBack_WikiPupil"] = function() {
    var DF;
    var f = this.Fl;   // focal length
    var s = this.Od;   // object to focal plane
    var N = this.N;    // aperture f/stop
    var c = this.coc;  // CoC
    var P = this.P;    // exit/entrance pupil ratio
    var m;             // image magnification

    //-----------------------------
    // calculate the near DoF limit
    //-----------------------------
    m = f / (s-f);     // wiki definition of "m"
    DF = N*c*(1+(m/P));
    DF /= Math.pow(m,2)*(1-((N*c)/(f*m)));
    this.backDoF = DF;
    //-------------------------------------------
    // far limit based on subject:object distance
    //-------------------------------------------
    if (this.backDoF < 0) {
        this.backDoF = this.farLimit = Number.POSITIVE_INFINITY;
        return;
    }
    if (this.focusIsObject)
        this.farLimit = this.Od + this.backDoF;
    else
        this.farLimit = this.Sd + this.backDoF;
    return;
}

// =========================================
// Static Functions
// =========================================

// -----------------------------------------
// initMe()  On Load
// -----------------------------------------
function initMe() {
    dlg = new Options();
    return;
}

// -----------------------------------------
// showtip() show tool tips
// -----------------------------------------
function showtip(tip) {
    document.ToolTips.tip.value=tip;
    return;
}

// -----------------------------------------
// buildFstopTable()
// -----------------------------------------
function buildFstopTable(stops) {
    var fTable = new Array();
    var incr;
    var i;

    if (stops == "third")     incr = 1/3;
    else if (stops == "half") incr = 1/2;
    else                      incr = 1.0;
    fTable[0] = 0;
    for (i=1; i<128.1; i*=Math.sqrt(Math.pow(2, incr)))
        fTable[fTable.length] = i;
    return(fTable);
}

// ---------------------------------------------
// padDecimalStr()
// pads number to decimal string at fixed length
// right justified for tabular display
// ---------------------------------------------
function padDecimalStr(decimalPlaces, num) {
    var i, j, dP;
    var newStr;

    // ----------
    // initialize
    // ----------
    if (isNaN(num))
        throw("padDecimalStr() NaN [" + num + "]");
    dP = Math.pow(10, decimalPlaces);           // Decimal precision Base 10
    newStr = new String(Math.round(num*dP)/dP); // round input
    // ---------------------------
    // pad decimal places to right
    // ---------------------------
    if (decimalPlaces > 0) {
        if (newStr.indexOf(".") < 0)   // is it already decimal precision?
            newStr += ".0";            // no, add the first decimal
        if (decimalPlaces > 1) {       // do we need more decimal positions?
            j = (decimalPlaces + 1) - (newStr.length - newStr.indexOf("."));
            for (i=0; i<j; i++)
                newStr += "0";
        }
    }
    return("" + newStr);
}

// end of: hide JavaScript from old browsers -->
