Code covered by the BSD License  

Highlights from
Web-browser viewable GUI animations of .PNGs, .GIFs, etc.

image thumbnail
from Web-browser viewable GUI animations of .PNGs, .GIFs, etc. by Kevin Bartlett
Creates javascript/html GUI-driven animation of still images (GIFs, JPEGs, etc.).

jsanim(fileList,varargin)
function [htmlCell,varargout] = jsanim(fileList,varargin)
%
% jsanim.m--Creates a javascript/html GUI-driven animation of a collection
% of still images (.png, .jpg, etc.) viewable in any web browser.
%
% HTMLCELL = JSANIM(FILELIST), where FILELIST is a cell array of filenames
% of image files, returns a cell array of strings containing lines of
% html/javascript code. The contents of HTMLCELL can be written to a .html
% file for opening in a browser. The filenames in FILELIST can contain
% relative or absolute paths; they can also be URLs pointing to image files
% on the Web.
%
% [HTMLCELL,SCRIPTCELL,BODYDECL,BODYCELL] = JSANIM(FILELIST) also returns
% important components of the standalone html code in individual variables.
% This is intended to make it easier to adapt the code generated by jsanim
% for inclusion in existing web pages.
%
% ... = JSANIM(FILELIST,'OUTFILE',OUTFILENAME) writes the html/javascript
% code generated by jsanim.m to the specified file. An error is thrown if
% the specified output file already exists.
%
% ... = JSANIM(...,'FORCEOVERWRITE',true) will not fail if the specified
% output file is found to already exist.
%
% ... = JSANIM(...,'FIRSTIMAGEISBACKGROUND',true) creates html/javascript
% code that treats the first file in fileList as a background image, rather
% than the first frame of the animation. This is useful for reducing total
% file sizes when you have a complex but unchanging background image like a
% map. Use Oliver Woodford's export_fig to make transparent images to
% overlay the fixed background image. Caveat: sometimes a transparent,
% foreground-only image output by export_fig is *bigger* than an image
% output by Matlab's print command containing both background *and*
% foreground, so check for this before committing to using the
% 'FIRSTIMAGEISBACKGROUND' option.
% 
% ... = JSANIM(...,'IMGWIDTH',IMGWIDTH,'IMGHEIGHT',IMGHEIGHT) specifies the
% width and height (in pixels) of the images to be displayed. Specified
% height and width can be larger or smaller than the original images, but
% the ratio between the two should be preserved, or the images will appear
% distorted when viewed in a browser. If imgWidth and imgHeight are not
% specified, the original image dimensions will be used.
%
% ... = JSANIM(...,'IMGLEFT',IMGLEFT,'IMGTOP',IMGTOP) specifies the
% location (in pixels) where the upper-left hand corner of the animated
% images will appear. These can be adjusted to taste; adjusting the value
% of imgTop in particular is useful when there will be additional html code
% inserted above the images in the final webpage.
%
% ... = JSANIM(...,'BUTTONTOP',BUTTONTOP) specifies the location (in pixels
% from the top of the page) of the start/stop button and other controls. If
% not specified, then the value of buttonTop is calculated from imgTop,
% imgHeight and aboveButtonPadding (see below); this can result in
% undesirable placement of the controls if imgHeight has been left
% unspecified as well.
%
% ... = JSANIM(...,'IMGBORDER',IMGBORDER), where IMGBORDER is an integer
% number of pixels, places a border around the animated images. Default is
% a 1-pixel border.
% 
% ... = JSANIM(...,'TITLESTR',TITLESTR) places the specified string into
% a <TITLE> field in the html code.
% 
% ... = JSANIM(...,'FRAMEDELAYS',FRAMEDELAYS) sets the value associated
% with each of the elements of the frame-rate drop-down list to the
% corresponding element of the vector FRAMEDELAYS. Each value is a time
% interval in seconds between frames, so this controls the speed of the
% animation. If not specified, a default set of frame delays is used. If
% FRAMEDELAYS is changed from its default values, then values of the
% FRAMEDELAY (the initially-selected value of frame delay) and
% FRAMEDELAYSTRS (the cell array of strings labelling the drop-down
% elements) parameters will probably also need to be specified to match.
%
% ... = JSANIM(...,'LOOPPAUSES',LOOPPAUSES) sets the values for the
% loop-pause drop-down list. A loop pause of zero means no looping over the
% animation; a loop pause of 2 means a pause equivalent to two frame delays
% between each loop of the animation, and so on. As with the FRAMEDELAYS
% parameter, specifying a non-default value for LOOPPAUSES will probably
% require specifying values for the LOOPPAUSE and LOOPPAUSESTRS parameters
% to match.
%
% ... = JSANIM(...,'SHOWSTARTSTOPCTRL',FALSE) causes the animation
% start/stop button to be invisible. In this case, the animation will run
% immediately when the html page is loaded in a browser.
% 
% ... = JSANIM(...,'SHOWFRAMEDELAYCTRL',FALSE) hides the frame-rate
% drop-down list, making the initially-selected frame-delay value
% unchangeable.
%
% ... = JSANIM(...,'SHOWLOOPCTRL',FALSE) similarly hides the loop-control
% drop-down list. 
%
% ... = JSANIM(...,'SHOWCOUNTERTXT',FALSE) hides the frame-counter text.
%
% The remaining parameters are strictly for adjusting the appearance of the
% generated web page, and can usually be ignored: containerWidth,
% belowButtonPadding, aboveButtonPadding, startButtonWidth and buttonHeight
% are in units of pixels; counterLeftPaddingPts and buttonFontSizePts are
% in units of points.
%
% Examples using image files included with jsanim.m on FileExchange:
%
%   fileList = {'m01.png' 'm02.png' 'm03.png' 'm04.png' 'm05.png' ...
%               'm06.png' 'm07.png' 'm08.png' 'm09.png' 'm10.png'};
%   jsanim(fileList,'outfile','greedy.html','imgHeight',560);
%
%   fileList = {'map.png' 't01.png' 't02.png' 't03.png' 't04.png' ...
%               't05.png' 't06.png' 't07.png' 't08.png' 't09.png' ...
%               't10.png'};
%   jsanim(fileList,'outfile','thrifty.html','firstImageIsBackground',true,'imgHeight',560);
%
%   "thrifty.html" looks identical to "greedy.html" when viewed in a
%   browser, but because it re-uses a single background image, it consumes
%   about 1/9 the amount of memory (275 KB rather than 2.4 MB).
%
% Example making own image files:
%   figure; Z = peaks; fileList = {};
%   for j = 1:20
%     surf(sin(2*pi*j/20)*Z,Z);
%     set(gca,'zlim',[-8.1 8.1]);
%     fileName = sprintf('p%02d.png',j);
%     fileList{j} = fileName;
%     print(gcf,'-r0','-dpng',fileName);
%   end
%   jsanim(fileList,'outfile','peaks.html','imgHeight',400,'imgTop',100);
%
% Troubleshooting hints:
%   (1) If you are getting "broken image" icons in your Web browser, rather
%   than the desired images, you may have used absolute pathnames in your
%   fileList cell array, which won't work if you copy your animation to
%   another location (look in the html code to confirm this). Use relative
%   pathnames or unqualified image filenames to avoid this problem.
%
%  (2) If your GUI controls are appearing in the middle of your images,
%  rather than below them, try specifying a value for the buttonTop and/or
%  imageHeight parameters. Your file list is just a list of names; jsanim.m
%  knows nothing about the dimensions of the images unless told by the
%  user.  

% Developed in Matlab 7.12.0.635 (R2011a) on GLNX86
% for the VENUS project (http://venus.uvic.ca/).
% Kevin Bartlett (kpb@uvic.ca), 2012-03-13 15:21
%-------------------------------------------------------------------------

defaultFrameDelayStrs = {'0.1-second frame delay'
    '0.25-second frame delay'
    '0.5-second frame delay'
    '1-second frame delay'
    '2-second frame delay'
    '3-second frame delay'
};

defaultLoopPauseStrs = {'No looping'
    '1-frame loop pause'
    '2-frame loop pause'
    '3-frame loop pause'
    '4-frame loop pause'
    '5-frame loop pause'
    '6-frame loop pause'
    '7-frame loop pause'
    '8-frame loop pause'
    '9-frame loop pause'
    '10-frame loop pause'
    '15-frame loop pause'
    '20-frame loop pause'};

% Handle input arguments.
p = inputParser;
p.FunctionName = mfilename;
p.addRequired('fileList', @iscell);
p.addParamValue('firstImageIsBackground', false, @(x) islogical(x) || ismember(x,[1 0]));
p.addParamValue('showStartStopCtrl', true, @(x) islogical(x) || ismember(x,[1 0]));
p.addParamValue('showFrameDelayCtrl', true, @(x) islogical(x) || ismember(x,[1 0]));
p.addParamValue('showLoopCtrl', true, @(x) islogical(x) || ismember(x,[1 0]));
p.addParamValue('showCounterTxt', true, @(x) islogical(x) || ismember(x,[1 0]));
p.addParamValue('frameDelays', [.1 .25 .5 1 2 3],  @(x) isnumeric(x) && x>0);
p.addParamValue('frameDelay', .25,  @(x) isnumeric(x) && x>0);
p.addParamValue('loopPauses', [0:10 15 20], @(x) isnumeric(x) && x>=0 && mod(x,1)==0);
p.addParamValue('loopPause', 2, @(x) isnumeric(x) && x>=0 && mod(x,1)==0);
p.addParamValue('forceOverwrite', false, @(x) islogical(x) || ismember(x,[1 0]));
p.addParamValue('outFile', '', @ischar);
p.addParamValue('counterLeftPaddingPts', 2, @(x) isnumeric(x) && x>=0 && mod(x,1)==0);
p.addParamValue('loopPauseStrs', defaultLoopPauseStrs, @iscell);
p.addParamValue('frameDelayStrs', defaultFrameDelayStrs, @iscell);
p.addParamValue('startButtonWidth', 100, @(x) isnumeric(x) && x>0 && mod(x,1)==0); % To prevent dynamic resizing when changing from "Start" to "Stop"
p.addParamValue('containerWidth', 1000, @(x) isnumeric(x) && x>0 && mod(x,1)==0);
p.addParamValue('titleStr', 'jsanim', @ischar);
p.addParamValue('imgLeft', 100, @(x) isnumeric(x) && x>=0 && mod(x,1)==0);
p.addParamValue('imgTop', 0, @(x) isnumeric(x) && x>=0 && mod(x,1)==0);
p.addParamValue('imgWidth',[],@(x) isnumeric(x) && x>0 && mod(x,1)==0);
p.addParamValue('imgHeight',[],@(x) isnumeric(x) && x>0 && mod(x,1)==0);
p.addParamValue('buttonFontSizePts', 12, @(x) isnumeric(x) && x>0 && mod(x,1)==0);
p.addParamValue('buttonHeight', 40, @(x) isnumeric(x) && x>0 && mod(x,1)==0);
p.addParamValue('buttonTop', [], @(x) isnumeric(x) && x>0 && mod(x,1)==0);
p.addParamValue('belowButtonPadding', 12, @(x) isnumeric(x) && x>=0 && mod(x,1)==0);
p.addParamValue('aboveButtonPadding', 12, @(x) isnumeric(x) && x>=0 && mod(x,1)==0);
p.addParamValue('imgBorder', 1, @(x) isnumeric(x) && x>=0 && mod(x,1)==0);

try
    p.parse(fileList, varargin{:});
catch me
    disp([mfilename '.m--Parsing of input arguments failed; check argument names and values. Error message from inputParser follows:']);
    rethrow(me);
end

firstImageIsBackground = p.Results.firstImageIsBackground;
showStartStopCtrl = p.Results.showStartStopCtrl;
showFrameDelayCtrl = p.Results.showFrameDelayCtrl;
showLoopCtrl = p.Results.showLoopCtrl;
showCounterTxt = p.Results.showCounterTxt;
frameDelays = p.Results.frameDelays;
frameDelay = p.Results.frameDelay;
loopPauses = p.Results.loopPauses;
loopPause = p.Results.loopPause;
forceOverwrite = p.Results.forceOverwrite;
outFile = p.Results.outFile;
counterLeftPaddingPts = p.Results.counterLeftPaddingPts;
loopPauseStrs = p.Results.loopPauseStrs;
frameDelayStrs = p.Results.frameDelayStrs;
startButtonWidth = p.Results.startButtonWidth;
containerWidth = p.Results.containerWidth;
titleStr = p.Results.titleStr;
imgLeft = p.Results.imgLeft;
imgTop = p.Results.imgTop;
imgWidth = p.Results.imgWidth;
imgHeight = p.Results.imgHeight;
buttonTop = p.Results.buttonTop;
buttonFontSizePts = p.Results.buttonFontSizePts;
buttonHeight = p.Results.buttonHeight;
belowButtonPadding = p.Results.belowButtonPadding;
aboveButtonPadding = p.Results.aboveButtonPadding;
imgBorder = p.Results.imgBorder;

% If top position of button not specified, calculate it from the position
% and size of the image.
if isempty(buttonTop)
    buttonTop = imgTop+imgHeight;
    if isempty(buttonTop)
       % Either or both of imgTop and imgHeight are empty, so need to use a
       % default value for buttonTop instead of calculating it.
       buttonTop = 500;
    end
end

if ~ismember(frameDelay,frameDelays) | ~ismember(loopPause,loopPauses)
    error([mfilename '.m--Selected values of frameDelay and loopPause must be members of frameDelays and loopPauses, respectively.']);
end

% Convert Booleans into numerical 1s and 0s for easier inclusion in
% javascript.
if firstImageIsBackground
    firstImageIsBackground = 1;
else
    firstImageIsBackground = 0;
end

if showStartStopCtrl
    showStartStopCtrl = 1;
else
    showStartStopCtrl = 0;
end

if showFrameDelayCtrl
    showFrameDelayCtrl = 1;
else
    showFrameDelayCtrl = 0;
end

if showLoopCtrl
    showLoopCtrl = 1;
else
    showLoopCtrl = 0;
end

if showCounterTxt
    showCounterTxt = 1;
else
    showCounterTxt = 0;
end

bodyDecl = '<body onLoad="imageLoad();">';

numImages = length(fileList);

% Assemble the cell containing the javascript.
scriptCell_1 = {'<script LANGUAGE="JavaScript" type="text/javascript">'
    ['<!-- Javascript generated automatically in Matlab by jsanim.m, ' datestr(now,31) ' -->']
    'var currImageNum = null;'
    'var numFrames = null;'
    ['var numImages = ' sprintf('%d',numImages) ';']
    'var iImage = 0;'
    ['var firstImageIsBackground = ' sprintf('%d',firstImageIsBackground) ';']
    'var firstFrameImageNum = null;'
    'var timeout = null;'
    'var loopPauseMs = 0;'
    ['var loopPauseInt = ' sprintf('%d',loopPause) ';']
    'var frameOffset = null;'
    'var currFrameNum = 1;'
    ['var frameDelayMs = ' sprintf('%d',frameDelay*1000) ';']
    ['var showStartStopCtrl = ' sprintf('%d',showStartStopCtrl) ';']
    ['var showFrameDelayCtrl = ' sprintf('%d',showFrameDelayCtrl) ';']
    ['var showLoopCtrl = ' sprintf('%d',showLoopCtrl) ';']
    ['var showCounterTxt = ' sprintf('%d',showCounterTxt) ';']
    'if (firstImageIsBackground==1) {'
    '  // The first (zeroth) image is to be used as a background, so the'
    '  // image for the first frame is image 1.'
    '  firstFrameImageNum = 1;'
    '  numFrames = numImages - 1;'
    '  frameOffset = 0;'
    '}'
    'else {'
    '  // The first image is a frame like all the other images, so the '
    '  // image for the first frame is the zeroth image.'
    '  firstFrameImageNum = 0;'
    '  numFrames = numImages;'
    '  frameOffset = 1;'
    '}'
    ''
    'currImageNum = firstFrameImageNum;'
    ''
    'function imageLoad() {'
    };

scriptCell_2 = cell(numImages,1);
for iFile = 1:numImages
    thisFileName = fileList{iFile};
    scriptCell_2{iFile} = sprintf('  document.images[%d].src = "%s";',iFile-1,thisFileName);
end

scriptCell_3 = {
    ''
    '  // Make only the first frame and the background (if it exists)'
    '  // visible.'
    '  for(iImage = 0; iImage < numImages; iImage++) {'
    '    if (iImage <= firstFrameImageNum){'
    '      document.getElementById("frame" + iImage).style.visibility = "visible";'
    '    }'
    '    else {'
    '      document.getElementById("frame" + iImage).style.visibility = "hidden";'
    '    }'
    '  }'
    '  if (showCounterTxt == 1) document.getElementById("counterText").innerHTML = currFrameNum + " of " + numFrames;'
    '  if (showFrameDelayCtrl == 0) document.animForm.frameRateCtrl.style.visibility = "hidden";'
    '  if (showLoopCtrl == 0) document.animForm.loopCtrl.style.visibility = "hidden";'
    '  if (showStartStopCtrl == 0) {'
    '    document.animForm.startStopButton.style.visibility = "hidden";'
    '    next();'
    '  }'
    '}'
    ''
    'function next() {'
    '  currImageNum = currImageNum + 1;'
    '  refreshView();'
    ''
    '  if (currImageNum<numImages-1){'
    '    // Not last image. Go to next image after selected frame delay.'
    '    if (showFrameDelayCtrl == 1) {'
    '      ind = document.animForm.frameRateCtrl.selectedIndex;'
    '      frameDelayMs = document.animForm.frameRateCtrl.options[ind].value;'
    '    }'
    '    timeout = setTimeout("next()", frameDelayMs);'
    '  }'
    '  else {'
    '    // Last image reached. Behaviour depends on loop control setting.'
    '    if (showLoopCtrl == 1) {'
    '      ind = document.animForm.loopCtrl.selectedIndex;'
    '      loopPauseInt = document.animForm.loopCtrl.options[ind].value;'
    '    }'
    '    currImageNum=firstFrameImageNum - 1;'
    '    if (loopPauseInt==0){'
    '      // Zero loop pause means no looping.'
    '      document.animForm.startStopButton.value = "Start";'
    '    }'
    '    else {'
    '      loopPauseMs = loopPauseInt*frameDelayMs;'
    '      timeout = setTimeout("next()", loopPauseMs);  '
    '    }'
    '  }'
    ''
    '} // end next()'
    ''
    'function refreshView() {'
    '  // Cycle through images (all except the background), setting invisible'
    '  // if not the current frame.'
    '  for(iImage = firstFrameImageNum; iImage < numImages; iImage++) {'
    '    if (iImage == currImageNum){'
    '      document.getElementById("frame" + iImage).style.visibility = "visible";'
    '    }'
    '    else {'
    '      document.getElementById("frame" + iImage).style.visibility = "hidden";'
    '    }'
    '  }'
    '  currFrameNum = currImageNum + frameOffset;'
    '  if (showCounterTxt == 1) document.getElementById("counterText").innerHTML = currFrameNum + " of " + numFrames;'
    '}'
    ''
    'function startStopButtonCheck() {'
    '    if(document.animForm.startStopButton.value == "Start") {'
    '        document.animForm.startStopButton.value = "Stop";'
    '        next();'
    '    } else {'
    '        document.animForm.startStopButton.value = "Start";'
    '        clearTimeout(timeout);'
    '        timeout = null;'
    '    }'
    '}'
    ''
    'function loopCtrlCheck() {'
    '  clearTimeout(timeout);'
    '  timeout = null;'
    '  // Immediately resume running if already running. New pause'
    '  // value will take effect after this iteration.'
    '  if(document.animForm.startStopButton.value == "Stop") {'
    '    if (showFrameDelayCtrl == 1) {'
    '      ind = document.animForm.frameRateCtrl.selectedIndex;'
    '      frameDelayMs = document.animForm.frameRateCtrl.options[ind].value;'
    '    }'
    '    timeout = setTimeout("next()", frameDelayMs);'
    '  }'
    '}'
    ''
    'function frameRateCheck() {'
    '  clearTimeout(timeout);'
    '  timeout = null;'
    ''
    '  if(showStartStopCtrl == 0 || document.animForm.startStopButton.value == "Stop") {'
    '    if (showFrameDelayCtrl == 1) {'
    '      ind = document.animForm.frameRateCtrl.selectedIndex;'
    '      frameDelayMs = document.animForm.frameRateCtrl.options[ind].value;'
    '    }'
    '    timeout = setTimeout("next()", frameDelayMs);'
    '  }'
    '}'
    ''
    '</script>'    
    };

scriptCell = cat(1,scriptCell_1,scriptCell_2,scriptCell_3);

% Assemble the cell containing the code that will appear in the body of the
% html. 
bodyCell_1 = {
    ['<!-- HTML code generated automatically in Matlab by jsanim.m, ' datestr(now,31) ' -->']
    ['<div ID="container" STYLE="width:' sprintf('%d',containerWidth) '">']};

if firstImageIsBackground
    visImages = [1 2];
else
    visImages = 1;
end 

bodyCell_2 = cell(numImages,1);
for iFile = 1:numImages
    thisFileName = fileList{iFile};
    if ismember(iFile,visImages)
        visStr = 'visible';
    else
        visStr = 'hidden';
    end
    
    % Only include height and width specifications for images if specified
    % by user.
    if isempty(imgHeight)
       heightStr = '';
    else
        heightStr = sprintf(' height=%d ',imgHeight);
    end
    
    if isempty(imgWidth)
        widthStr = '';
    else
        widthStr = sprintf(' width=%d ',imgWidth);
    end
    %bodyCell_2{iFile} = ['<div ID="frame' sprintf('%d',iFile-1) '" STYLE="position:absolute; left:' sprintf('%d',imgLeft) '; top:' sprintf('%d',imgTop) '; visibility:' sprintf('%s',visStr) '"><img src="' thisFileName '" width=' sprintf('%d',imgWidth) ' height=' sprintf('%d',imgHeight) ' alt="" border="' sprintf('%d',imgBorder) '"></div>'];
    bodyCell_2{iFile} = ['<div ID="frame' sprintf('%d',iFile-1) '" STYLE="position:absolute; left:' sprintf('%d',imgLeft) '; top:' sprintf('%d',imgTop) '; visibility:' sprintf('%s',visStr) '"><img src="' thisFileName '"' widthStr heightStr ' alt="" border="' sprintf('%d',imgBorder) '"></div>'];
end

bodyCell_3 = {
    ['<div ID="controls" STYLE="position:absolute; left:' sprintf('%d',imgLeft) '; top:' sprintf('%d',buttonTop+aboveButtonPadding) '">']
    '  <FORM NAME="animForm">'
    ['    <INPUT TYPE="button" VALUE="Start" NAME="startStopButton" onClick=startStopButtonCheck() STYLE="height:' sprintf('%d',buttonHeight) 'px; width:' sprintf('%d',startButtonWidth) 'px; vertical-align:top; font-size:' sprintf('%d',buttonFontSizePts) 'pt;">']
    ['    <SELECT NAME="frameRateCtrl" onClick=frameRateCheck() STYLE="height:' sprintf('%d',buttonHeight) 'px; vertical-align:top; font-size:' sprintf('%d',buttonFontSizePts) 'pt;">']
    };
    
bodyCell_4 = cell(length(frameDelays),1);
frameDelaysMS = round(frameDelays*1000);
for iDelay = 1:length(frameDelays)
    thisDelayMS = frameDelaysMS(iDelay);
    thisDelayStr = frameDelayStrs{iDelay};
    if thisDelayMS == frameDelay
        bodyCell_4{iDelay} = sprintf('      <OPTION VALUE=%d SELECTED>%s',thisDelayMS,thisDelayStr);
    else
        bodyCell_4{iDelay} = sprintf('      <OPTION VALUE=%d>%s',thisDelayMS,thisDelayStr);
    end
end

bodyCell_5 = {'    </SELECT>'
    ['    <SELECT NAME="loopCtrl" onClick=loopCtrlCheck() STYLE="height:' sprintf('%d',buttonHeight) 'px; vertical-align:top; font-size:' sprintf('%d',buttonFontSizePts) 'pt;">'];};

bodyCell_6 = cell(length(loopPauses),1);
for iPause = 1:length(loopPauses)
    thisPause = loopPauses(iPause);
    thisPauseStr = loopPauseStrs{iPause};
    if thisPause == loopPause
        bodyCell_6{iPause} = sprintf('      <OPTION VALUE=%d SELECTED>%s',thisPause,thisPauseStr);
    else
        bodyCell_6{iPause} = sprintf('      <OPTION VALUE=%d>%s',thisPause,thisPauseStr);
    end
end

bodyCell_7 = {'    </SELECT>'
    ['    <b id="counterText" STYLE="padding-left:' sprintf('%d',counterLeftPaddingPts) 'pt"></b>']
    '  </FORM>'
    '</div>'
    ''
    ['<div ID="reset-to-static-position" STYLE="position:static; margin-top:' sprintf('%d',buttonTop+buttonHeight+aboveButtonPadding+belowButtonPadding) 'px"></div>']
    ''};

bodyCell = cat(1,bodyCell_1,bodyCell_2,bodyCell_3,bodyCell_4,bodyCell_5,bodyCell_6,bodyCell_7);

% Assemble the stand-alone html from its constituent parts.
htmlCell_1 = {'<html>'
    '<head>'
    ['<title>' titleStr '</title>']
    ''};

htmlCell = cat(1,htmlCell_1,scriptCell,{'</head>'},{''},{bodyDecl},{''},bodyCell,{''},'</body>','</html>');

% Output to file, if requested.
if ~isempty(outFile)
    if exist(outFile,'file')
       if ~forceOverwrite 
          error([mfilename '.m--Output file already exists. Delete and try again or use the ''forceOverwrite'' option.']); 
       end
    end
    
    fid = fopen(outFile,'wt');
    assert(fid>1,[mfilename '.m--Failed to open output file.']);
    
    for iLine = 1:length(htmlCell)
       fprintf(fid,'%s\n',htmlCell{iLine}); 
    end
    
    fclose(fid);
end

if nargout > 1
    varargout{1} = scriptCell;
end

if nargout > 2
    varargout{2} = bodyDecl;
end

if nargout > 2
    varargout{3} = bodyCell;
end

Contact us