function exportfig(varargin)
%EXPORTFIG  Export a figure to Encapsulated Postscript.
%   EXPORTFIG(H, FILENAME) writes the figure H to FILENAME.  H is
%   a figure handle and FILENAME is a string that specifies the
%   name of the output file.
%
%   EXPORTFIG(...,PARAM1,VAL1,PARAM2,VAL2,...) specifies
%   parameters that control various characteristics of the output
%   file.
%
%   Format Paramter:
%     'Format'  one of the strings 'eps','eps2','jpeg','png'
%          specifies the output format. Defaults to 'eps'.
%
%   Size Parameters:
%     'Width'   a positive scalar
%          specifies the width in the figure's PaperUnits
%     'Height'  a positive scalar
%          specifies the height in the figure's PaperUnits
%
%     Specifying only one dimension sets the other dimension
%     so that the exported aspect ratio is the same as the
%     figure's current aspect ratio. 
%     If neither dimension is specified the size defaults to 
%     the width and height from the figure's PaperPosition. 
%           
%   Rendering Parameters:
%     'Color'     one of the strings 'bw', 'gray', 'cmyk'
%         'bw'    specifies that lines and text are exported in
%                 black and all other objects in grayscale
%         'gray'  specifies that all objects are exported in grayscale
%         'cmyk'  specifies that all objects are exported in color
%                 using the CMYK color space
%     'Renderer'  one of the strings 'painters', 'zbuffer', 'opengl'
%         specifies the renderer to use
%     'Resolution'   a positive scalar
%         specifies the resolution in dots-per-inch.
%     
%     The default color setting is 'bw'.
%
%   Font Parameters:
%     'FontMode'     one of the strings 'scaled', 'fixed'
%     'FontSize'     a positive scalar
%          in 'scaled' mode multiplies with the font size of each
%          text object to obtain the exported font size
%          in 'fixed' mode specifies the font size of all text
%          objects in points
%     'FontEncoding' one of the strings 'latin1', 'adobe'
%          specifies the character encoding of the font
%
%     If FontMode is 'scaled' but FontSize is not specified then a
%     scaling factor is computed from the ratio of the size of the
%     exported figure to the size of the actual figure. The minimum
%     font size allowed after scaling is 5 points.
%     If FontMode is 'fixed' but FontSize is not specified then the
%     exported font sizes of all text objects is 7 points.
%
%     The default 'FontMode' setting is 'scaled'.
%
%   Line Width Parameters:
%     'LineMode'     one of the strings 'scaled', 'fixed'
%     'LineWidth'    a positive scalar
%          the semantics of LineMode and LineWidth are exactly the
%          same as FontMode and FontSize, except that they apply
%          to line widths instead of font sizes. The minumum line
%          width allowed after scaling is 0.5 points.
%          If LineMode is 'fixed' but LineWidth is not specified 
%          then the exported line width of all line objects is 1
%          point. 
%
%   Examples:
%     exportfig(gcf,'fig1.eps','height',3);
%       Exports the current figure to the file named 'fig1.eps' with
%       a height of 3 inches (assuming the figure's PaperUnits is 
%       inches) and an aspect ratio the same as the figure's aspect
%       ratio on screen.
%
%     exportfig(gcf, 'fig2.eps', 'FontMode', 'fixed',...
%                'FontSize', 10, 'color', 'cmyk' );
%       Exports the current figure to 'fig2.eps' in color with all
%       text in 10 point fonts. The size of the exported figure is
%       the figure's PaperPostion width and height.


if (nargin < 2)
  error('Too few input arguments');
end

% exportfig(H, filename, ...)
H = varargin{1};
filename = varargin{2};
paramPairs = varargin(3:end);

% Do some validity checking on param-value pairs
if (rem(length(paramPairs),2) ~= 0)
  error(['Invalid input syntax. Optional parameters and values' ...
		 ' must be in pairs.']);
end

format = 'eps';
width = -1;
height = -1;
color = 'bw';
fontsize = -1;
fontmode='scaled';
linewidth = -1;
linemode=[];
fontencoding = 'latin1';
renderer = [];
resolution = [];

% Process param-value pairs
args = {};
for k = 1:2:length(paramPairs)
  param = lower(paramPairs{k});
  if (~ischar(param))
	error('Optional parameter names must be strings');
  end
  value = paramPairs{k+1};
  
  switch (param)
   case 'format'
    format = value;
    if (~strcmp(format,{'eps','eps2','jpeg','png'}))
      error('Format must be ''eps'', ''eps2'', ''jpeg'' or ''png''.');
    end
   case 'width'
	width = value;
	if(~LocalIsPositiveScalar(width))
	  error('Width must be a numeric scalar > 0');
	end
   case 'height'
	height = value;
	if(~LocalIsPositiveScalar(height))
	  error('Height must be a numeric scalar > 0');
	end
   case 'color'
	color = lower(value);
	if (~strcmp(color,{'bw','gray','cmyk'}))
	  error('Color must be ''bw'', ''gray'' or ''cmyk''.');
	end
   case 'fontmode'
	fontmode = lower(value);
	if (~strcmp(fontmode,{'scaled','fixed'}))
	  error('FontMode must be ''scaled'' or ''fixed''.');
	end
   case 'fontsize'
	fontsize = value;
	if(~LocalIsPositiveScalar(fontsize))
	  error('FontSize must be a numeric scalar > 0');
	end
   case 'fontencoding'
	fontencoding = lower(value);
	if (~strcmp(fontencoding,{'latin1','adobe'}))
	  error('FontEncoding must be ''latin1'' or ''adobe''.');
	end
   case 'linemode'
	linemode = lower(value);
	if (~strcmp(linemode,{'scaled','fixed'}))
	  error('LineMode must be ''scaled'' or ''fixed''.');
	end
   case 'linewidth'
	linewidth = value;
	if(~LocalIsPositiveScalar(linewidth))
	  error('LineWidth must be a numeric scalar > 0');
	end
   case 'renderer'
	renderer = lower(value);
	if (~strcmp(renderer,{'painters','zbuffer','opengl'}))
	  error('Renderer must be ''painters'', ''zbuffer'' or ''opengl''.');
	end
   case 'resolution'
	resolution = value;
	if ~(isnumeric(value) & (prod(size(value)) == 1) & (value >= 0));
	  error('Resolution must be a numeric scalar >= 0');
	end
   otherwise
	error(['Unrecognized option ' param '.']);
  end
end

allLines  = findall(H, 'type', 'line');
allText   = findall(H, 'type', 'text');
allAxes   = findall(H, 'type', 'axes');
allImages = findall(H, 'type', 'image');
allLights = findall(H, 'type', 'light');
allPatch  = findall(H, 'type', 'patch');
allSurf   = findall(H, 'type', 'surface');
allRect   = findall(H, 'type', 'rectangle');
allFont   = [allText; allAxes];
allColor  = [allLines; allText; allAxes; allLights];
allMarker = [allLines; allPatch; allSurf];
allEdge   = [allPatch; allSurf];
allCData  = [allImages; allPatch; allSurf];

old.objs = {};
old.prop = {};
old.values = {};

% Process size parameters
paperPos = get(H, 'PaperPosition');
old = LocalPushOldData(old, H, 'PaperPosition', paperPos);
figureUnits = get(H, 'Units');
set(H, 'Units', get(H,'PaperUnits'));
figurePos = get(H, 'Position');
aspectRatio = figurePos(3)/figurePos(4);
set(H, 'Units', figureUnits);
if (width == -1) & (height == -1)
  width = paperPos(3);
  height = paperPos(4);
elseif (width == -1)
  width = height * aspectRatio;
elseif (height == -1)
  height = width / aspectRatio;
end
set(H, 'PaperPosition', [0 0 width height]);
paperPosMode = get(H, 'PaperPositionMode');
old = LocalPushOldData(old, H, 'PaperPositionMode', paperPosMode);
set(H, 'PaperPositionMode', 'manual');

% Process rendering parameters
switch (color)
 case {'bw', 'gray'}
  if ~strcmp(color,'bw') & strncmp(format,'eps',3)
    format = [format 'c'];
  end
  args = {args{:}, ['-d' format]};

  %compute and set gray colormap
  oldcmap = get(H,'Colormap');
  newgrays = 0.30*oldcmap(:,1) + 0.59*oldcmap(:,2) + 0.11*oldcmap(:,3);
  newcmap = [newgrays newgrays newgrays];
  old = LocalPushOldData(old, H, 'Colormap', oldcmap);
  set(H, 'Colormap', newcmap);

  %compute and set ColorSpec and CData properties
  old = LocalUpdateColors(allColor, 'color', old);
  old = LocalUpdateColors(allAxes, 'xcolor', old);
  old = LocalUpdateColors(allAxes, 'ycolor', old);
  old = LocalUpdateColors(allAxes, 'zcolor', old);
  old = LocalUpdateColors(allMarker, 'MarkerEdgeColor', old);
  old = LocalUpdateColors(allMarker, 'MarkerFaceColor', old);
  old = LocalUpdateColors(allEdge, 'EdgeColor', old);
  old = LocalUpdateColors(allEdge, 'FaceColor', old);
  old = LocalUpdateColors(allCData, 'CData', old);
 
 case 'cmyk'
  if strncmp(format,'eps',3)
    format = [format 'c'];
    args = {args{:}, ['-d' format], '-cmyk'};
  else
    args = {args{:}, ['-d' format]};
  end
 otherwise
  error('Invalid Color parameter');
end
if (~isempty(renderer))
  args = {args{:}, ['-' renderer]};
end
if (~isempty(resolution))
  args = {args{:}, ['-r' int2str(resolution)]};
end

% Process font parameters
if (~isempty(fontmode))
  oldfonts = LocalGetAsCell(allFont,'FontSize');
  switch (fontmode)
   case 'fixed'
    oldfontunits = LocalGetAsCell(allFont,'FontUnits');
    old = LocalPushOldData(old, allFont, {'FontUnits'}, oldfontunits);
    set(allFont,'FontUnits','points');
    if (fontsize == -1)
      set(allFont,'FontSize',7);
    else
      set(allFont,'FontSize',fontsize);
    end
   case 'scaled'
    if (fontsize == -1)
      wscale = width/figurePos(3);
      hscale = height/figurePos(4);
      scale = min(wscale, hscale);
    else
      scale = fontsize;
    end
    newfonts = LocalScale(oldfonts,scale,5);
    set(allFont,{'FontSize'},newfonts);
   otherwise
    error('Invalid FontMode parameter');
  end
  % make sure we push the size after the units
  old = LocalPushOldData(old, allFont, {'FontSize'}, oldfonts);
end
if strcmp(fontencoding,'adobe') & strncmp(format,'eps',3)
  args = {args{:}, '-adobecset'};
end

% Process linewidth parameters
if (~isempty(linemode))
  oldlines = LocalGetAsCell(allLines,'LineWidth');
  old = LocalPushOldData(old, allLines, {'LineWidth'}, oldlines);
  switch (linemode)
   case 'fixed'
    if (linewidth == -1)
      set(allLines,'LineWidth',1);
    else
      set(allLines,'LineWidth',linewidth);
    end
   case 'scaled'
    if (linewidth == -1)
      wscale = width/figurePos(3);
      hscale = height/figurePos(4);
      scale = min(wscale, hscale);
    else
      scale = linewidth;
    end
    newlines = LocalScale(oldlines, scale, 0.5);
    set(allLines,{'LineWidth'},newlines);
   otherwise
    error('Invalid LineMode parameter');
  end
end

% Export
print(H, filename, args{:});

% Restore figure settings
for n=1:length(old.objs)
    set(old.objs{n}, old.prop{n}, old.values{n});
end


%
%  Local Functions
%

function [outData] = LocalPushOldData(inData, objs, prop, values)
outData.objs = {inData.objs{:}, objs};
outData.prop = {inData.prop{:}, prop};
outData.values = {inData.values{:}, values};

function [cellArray] = LocalGetAsCell(fig,prop);
cellArray = get(fig,prop);
if (~isempty(cellArray)) & (~iscell(cellArray))
  cellArray = {cellArray};
end

function [newArray] = LocalScale(inArray, scale, minValue)
n = length(inArray);
newArray = cell(n,1);
for k=1:n
  newArray{k} = max(minValue,scale*inArray{k}(1));
end

function [newArray] = LocalMapToGray(inArray);
n = length(inArray);
newArray = cell(n,1);
for k=1:n
  color = inArray{k};
  if (~isempty(color))
	if ischar(color)
	  switch color(1)
	   case 'y'
		color = [1 1 0];
	   case 'm'
		color = [1 0 1];
	   case 'c'
		color = [0 1 1];
	   case 'r'
		color = [1 0 0];
	   case 'g'
		color = [0 1 0];
	   case 'b'
		color = [0 0 1];
	   case 'w'
		color = [1 1 1];
	   case 'k'
		color = [0 0 0];
	   otherwise
		newArray{k} = color;
	  end
	end
	if ~ischar(color)
	  color = 0.30*color(1) + 0.59*color(2) + 0.11*color(3);
	end
  end
  if isempty(color) | ischar(color)
    newArray{k} = color;
  else
    newArray{k} = [color color color];
  end
end

function [newArray] = LocalMapCData(inArray);
n = length(inArray);
newArray = cell(n,1);
for k=1:n
  color = inArray{k};
  if (ndims(color) == 3)
    gray = 0.30*color(:,:,1) + 0.59*color(:,:,2) + 0.11*color(:,:,3);
    color(:,:,1) = gray;
    color(:,:,2) = gray;
    color(:,:,3) = gray;
  end
  newArray{k} = color;
end

function [outData] = LocalUpdateColors(inArray, prop, inData)
value = LocalGetAsCell(inArray,prop);
outData.objs = {inData.objs{:}, inArray};
outData.prop = {inData.prop{:}, {prop}};
outData.values = {inData.values{:}, value};
if (~isempty(value))
  if strcmp(prop,'CData') 
    value = LocalMapCData(value);
  else
    value = LocalMapToGray(value);
  end
  set(inArray,{prop},value);
end

function [bool] = LocalIsPositiveScalar(value)
bool = isnumeric(value) & ...
       prod(size(value)) == 1 & ...
       value > 0;
