Skip to content

Instantly share code, notes, and snippets.

@diegoferigo
Last active April 30, 2020 07:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save diegoferigo/1c0a892e9f952be757e080e116e6cd6f to your computer and use it in GitHub Desktop.
Save diegoferigo/1c0a892e9f952be757e080e116e6cd6f to your computer and use it in GitHub Desktop.
Plot2Latex for Linux

This document explains how to get Plot2Latex working in a Linux environment (tested on Ubuntu 16.04).

Install dependencies

sudo apt install -y \
    inkscape \
    texlive-latex-base \
    libtiff5

And of course Matlab, in my case 2017b.

libtiff workaround

With this Matlab version I had a problem with libtiff. Matlab comes with its own version of the library, and it is not compatible with other libraries loaded by Inkscape.

You can quickly resolve it by launching matlab with:

$ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libtiff.so.5 matlab

Be aware that if you have a Matlab alias in your .bashrc already specifying a LD_PRELOAD variable, the one you pass from the command line gets overridden.

libstdc++ workaround

Other combinations of Matlab versions and OS could produce the following error:

/usr/bin/inkscape: /usr/local/MATLAB/R2020a/sys/os/glnxa64/libstdc++.so.6: version `GLIBCXX_3.4.26' not found (required by /usr/bin/../lib/inkscape/libinkscape_base.so)
/usr/bin/inkscape: /usr/local/MATLAB/R2020a/sys/os/glnxa64/libstdc++.so.6: version `GLIBCXX_3.4.26' not found (required by /lib/x86_64-linux-gnu/libcdr-0.1.so.1)

This is a longstanding problem since Matlab ships its own vendored version of the library that, depending on your Linux distribution, could be either too new or too old wrt the OS's. In this case, you can fix the problem launching Matlab as follows:

LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libstdc++.so.6" matlab

Combine workaround

If you have both the errors above, you can combine the two workarounds specifying the two preloaded libraries separated with a space:

LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libtiff.so.5 /usr/lib/x86_64-linux-gnu/libstdc++.so.6" matlab

Execute the script

The script works probably well on Windows, but on Ubuntu I faced a small error about folder separators. You can find in this gist a fixed version of the script. Override the original matlab script and follow the script instructions.

function Plot2LaTeX( h, filename, options )
%PLOT2LATEX saves matlab figure as a pdf file in vector format for
%inclusion into LaTeX. Requires free and open-source vector graphics
%editor Inkscape.
%
% PLOT2LATEX(h,filename) saves figure with handle h to a file specified by
% filename, without extention. Filename can contain a relative location
% (e.g. 'images\title') to save the figure to different location.
%
% PLOT2LATEX(h,filename, options) saves figure with specified options.
% The y-offset of all text can be modified using options.yCorrFactor.
% The default is options.yCorrFactor = 0.8. The units are px. With
% options.Renderer the renderer of the figure can be specified:
% ('opengl', 'painters').
%
% PLOT2LATEX requires a installation of Inkscape. The program's
% location has to be 'hard coded' into this matlab file if it differs
% from 'c:\Program Files (x86)\Inkscape\Inkscape.exe'. Please specify
% your inscape file location by modifying DIR_INKSC variable on the
% first line of the actual code.
%
% PLOT2LATEX saves the figures to .svg format. It invokes Inkscape to
% save the svg to a .pdf and .pdf_tex file to be incorporated into LaTeX
% document using \begin{figure} \input{image.pdf_tex} \end{figure}.
% More information on the svg to pdf conversion can be found here:
% ftp://ftp.fu-berlin.de/tex/CTAN/info/svg-inkscape/InkscapePDFLaTeX.pdf
%
% PLOT2LATEX produces three files: .svg, .pdf, .pfd_tex. The .svg-file
% contains vector image. The .pdf-file contains figure without the text.
% The .pdf_tex-file contains the text including locations and other
% type setting.
%
% The produced .svg file can be manually modified in Inkscape and
% included into the .tex file using the using the built-in "save to pdf"
% functionality of Inkscape.
%
% PLOT2LATEX saves the figure to a svg and pdf file with the
% approximately the same width and height. Specify the Font size and size
% within Matlab for correct conversion.
%
% Workflow
% - Matlab renames all strings of the figure to labels. The strings are
% stored to be used later. To prevent a change in texbox size, labels are
% padded to match the size of the texbox.
% - Matlab saves the figure with labels to a svg file.
% - Matlab opens the svg file and restores the labels wiht the original
% string
% - Matlab invokes Inkscape to save the svg file to a pdf + pdf_tex file.
% - The pdf_tex is to be included into LaTeX.
%
% Features:
% - Complex figures such as plotyy, logarithmic scales.
% - It parses LaTeX code, even if it is not supported by Matlab LaTeX.
% - Supports real transparency.
% - SVG is a better supported, maintained and editable format than eps
% - SVG allows simple manual modification into Inkscape.
%
% Limitation:
% - Text resize is still done in PLOT2LATEX. The LaTeX fonts in matlab do
% not correspond completely with the LaTeX font size.
% - Legend size is not always correct, use \hspace or \vspace in matlab
% legend to achieve a nicer fit. Requires some iterations.
% - Rotating 3D images using toolbar does not work, using view([]) works.
% - Text boxes wiht LaTeX code which is not interpretable by matlab
% results in too long text boxes.
% - Very large figures sometimes result in very large waiting times.
% - Older versions than matlab 2014b are not supported.
% - PLOT2LATEX currently does not work with titles consisting of multiple
% lines.
% - PLOT2LATEX does not work with annotation textbox objects.
% - PLOT2LATEX does not suport colored text.
%
% Trouble shooting
% - For Unix users: use the installation folder such as:
% '/Applications/Inkscape.app/Contents/Resources/script ' as location.
% - For Unix users: For some users the bash profiles do not allow to call
% Inkscape in Matlab via bash. Therefore change the bash profile in Matlab
% to something similar as setenv('DYLD_LIBRARY_PATH','/usr/local/bin/').
% The bash profile location can be found by using '/usr/bin/env bash'
% To do:
% - Restore Interpreter instead of putting it to LaTeX
% - Annotation textbox objects
% - Allow multiple line text
% - Use findall(h,'-property','String')
% - Speed up code by smarter string replacent of SVG file
% - Resize of legend box using: [h,icons,plots,str] = legend(); (not so simple)
% - PLOT2LATEX does not suport colored text. (Matlab limitation in saving to sgv)
% - Size difference .svg and .fig if specifying units other than px.
% (Matlab limitation?)
% Version: 1.2
% Autor: J.J. de Jong, K.G.P. Folkersma
% Date: 20/04/2016
% Contact: j.j.dejong@utwente.nl
% Change log
% v 1.1 - 02/09/2015 (not released)
% - Made compatible for Unix systems
% - Added a waitbar
% - Corrected the help file
% v 1.2 - 20/04/2016
% - Fixed file names with spaces in the name. (Not adviced to use in latex though)
% - Escape special characters in XML (<,>,',",&) -> (&lt;,&gt;,&apos;,&quot;,&amp;)
%% Config and checks
% Specify location of your inkscape installation
DIR_INKSC = '/usr/bin/inkscape';
% initize waitbar
nStep = 5; Step = 0;
hWaitBar = waitbar(Step/nStep,'Initializing');
%test if installation is correct
if ~exist(DIR_INKSC,'file')
error([DIR_INKSC, ' cannot be found, check installation location'])
end
if verLessThan('matlab', '8.4.0.')
error('Older versions than Matlab 2014b are not supported')
end
if ~strcmp(h.Type,'figure')
error('h object is not a figure')
end
%% process options, first set default
yCorrFactor = 0.8; % default
% do not set default renderer
if nargin > 2
if isfield(options,'yCorrFactor')
yCorrFactor = options.yCorrFactor; % y offset of the text in px
end
if isfield(options,'Renderer') %WARNING: large size figures can become very large
h.Renderer = options.Renderer; % set render
end
end
%% Find all objects with text
TexObj = findall(h,'Type','Text'); % normal text, titels, x y z labels
LegObj = findall(h,'Type','Legend'); % legend objects
AxeObj = findall(h,'Type','Axes'); % axes containing x y z ticklabel
ColObj = findall(h,'Type','Colorbar'); % containg color bar tick
PosAnchSVG = {'start','middle','end'};
PosAligmentSVG = {'start','center','end'};
PosAligmentMAT = {'left','center','right'};
n_Axe = length(LegObj);
for i = 1:n_Axe % scale text omit in next version
LegPos(i,:) = LegObj(i).Position;
end
ChangeInterpreter(h,'Latex')
h.PaperPositionMode = 'auto'; % Keep current size
%% Replace text with a label
Step = Step + 1;
waitbar(Step/nStep,hWaitBar,'Replacing text with labels');
iLabel = 0; % generate label iterator
n_TexObj = length(TexObj);
for i = 1:n_TexObj % do for text, titles and axes labels
iLabel = iLabel + 1;
% find text string
Labels(iLabel).TrueText = TexObj(i).String;
% find text aligment
Labels(iLabel).Alignment = PosAligmentSVG(...
find(ismember(...
PosAligmentMAT,...
TexObj(i).HorizontalAlignment)));
% find achor aligment svg uses this
Labels(iLabel).Anchor = PosAnchSVG(...
find(ismember(...
PosAligmentMAT,...
TexObj(i).HorizontalAlignment)));
% generate label
Labels(iLabel).LabelText = LabelText(iLabel);
%find text posiont
Labels(iLabel).Position = TexObj(i).Position;
% replace string with label
TexObj(i).String = LabelText(iLabel);
end
% do similar for legend objects
n_LegObj = length(LegObj);
iLegEntry = 0;
for i = 1:n_LegObj
n_Str = length(LegObj(i).String);
iLegEntry = iLegEntry + 1;
iLabel = iLabel + 1;
Labels(iLabel).TrueText = LegObj(i).String{1};
Labels(iLabel).Alignment = PosAligmentSVG(1); % legends are always left aligned
Labels(iLabel).Anchor = PosAnchSVG(1);
% generate legend label padded with dots to fill text box
LegObj(i).String{1} = LegText(iLegEntry);
while LegPos(i,3) >= LegObj(i).Position(3) % first label of legend should match box size
LegObj(i).String{1} = [LegObj(i).String{1},'.'];
end
LegObj(i).String{1} = LegObj(i).String{1}(1:end-1);
Labels(iLabel).LabelText = LegObj(i).String{1}; % write as label
for j = 2:n_Str % do short as possible label for other entries
iLegEntry = iLegEntry + 1;
iLabel = iLabel + 1;
Labels(iLabel).TrueText = LegObj(i).String{j};
Labels(iLabel).Alignment = PosAligmentSVG(1);
Labels(iLabel).Anchor = PosAnchSVG(1);
Labels(iLabel).LabelText = LegText(iLegEntry);
LegObj(i).String{j} = LegText(iLegEntry);
end
end
% do similar for axes objects, XTick, YTick, ZTick
n_AxeObj = length(AxeObj);
for i = 1:n_AxeObj
n_Str = length(AxeObj(i).XTickLabel);
for j = 1:n_Str
iLabel = iLabel + 1;
Labels(iLabel).TrueText = AxeObj(i).XTickLabel{j};
Labels(iLabel).Alignment = PosAligmentSVG(2);
Labels(iLabel).Anchor = PosAnchSVG(2);
Labels(iLabel).LabelText = LabelText(iLabel);
AxeObj(i).XTickLabel{j} = LabelText(iLabel);
end
isRightAx = strcmp(AxeObj(i).YAxisLocation,'right'); % exeption for yy-plot
n_Str = length(AxeObj(i).YTickLabel);
for j = 1:n_Str
iLabel = iLabel + 1;
Labels(iLabel).TrueText = AxeObj(i).YTickLabel{j};
if isRightAx % exeption for yy-plot, aligment is left for the right axis
Labels(iLabel).Alignment = PosAligmentSVG(1);
Labels(iLabel).Anchor = PosAnchSVG(1);
else % normal y labels are right aligned
Labels(iLabel).Alignment = PosAligmentSVG(3);
Labels(iLabel).Anchor = PosAnchSVG(3);
end
Labels(iLabel).LabelText = LabelText(iLabel);
AxeObj(i).YTickLabel{j} = LabelText(iLabel);
end
n_Str = length(AxeObj(i).ZTickLabel);
for j = 1:n_Str
iLabel = iLabel + 1;
Labels(iLabel).TrueText = AxeObj(i).ZTickLabel{j};
Labels(iLabel).Alignment = PosAligmentSVG(3);
Labels(iLabel).Anchor = PosAnchSVG(3);
Labels(iLabel).LabelText = LabelText(iLabel);
AxeObj(i).ZTickLabel{j} = LabelText(iLabel);
end
end
% do similar for color bar objects
n_ColObj = length(ColObj);
for i = 1:n_ColObj
isAxIn = strcmp(ColObj(i).AxisLocation,'in'); % find internal external text location
isAxEast = strcmp(ColObj(i).Location,'east'); % find location
isRightAx = isAxIn ~= isAxEast;
n_Str = length(ColObj(i).TickLabels);
for j = 1:n_Str
iLabel = iLabel + 1;
Labels(iLabel).TrueText = ColObj(i).TickLabels{j};
if isRightAx % if text is right aligned
Labels(iLabel).Alignment = PosAligmentSVG(1);
Labels(iLabel).Anchor = PosAnchSVG(1);
else % if text is left aligned
Labels(iLabel).Alignment = PosAligmentSVG(3);
Labels(iLabel).Anchor = PosAnchSVG(3);
end
Labels(iLabel).LabelText = LabelText(iLabel);
ColObj(i).TickLabels{j} = LabelText(iLabel);
end
end
nLabel = iLabel;
% set text interpreter to plain text
ChangeInterpreter(h,'none');
%% Save to fig and SVG
Step = Step + 1;
waitbar(Step/nStep,hWaitBar,'Saving figure to .svg file');
% savefig(h,[filename,'_temp']); % to see the intermediate situation
saveas(h,filename,'svg'); % export to svg
%% Modify SVG file to replace labels with original text
Step = Step + 1;
waitbar(Step/nStep,hWaitBar,'Restoring text in .svg file');
for iLabel = 1:nLabel
Labels(iLabel).XMLText = EscapeXML(Labels(iLabel).TrueText);
end
fin = fopen([filename,'.svg']); % open svg file
fout = fopen([filename,'_temp.svg'],'w'); % make a temp file for modification
StrLine_new = fgetl(fin);%skip first line
iLine = 1; % Line number
nFoundLabel = 0; % Counter of number of found labels
while ~feof(fin)
StrPref = StrLine_new; % process new line
iLine = iLine + 1;
StrLine_old = fgetl(fin);
FoundLabelText = regexp(StrLine_old,'>\S*</text','match'); %try to find label
StrLine_new = StrLine_old;
if ~isempty(FoundLabelText)
nFoundLabel = nFoundLabel + 1;
iLabel = find(ismember(...
{Labels.LabelText},...
FoundLabelText{1}(2:end-6))); % find label number
% Append text alignment in prevous line
StrPrefTemp = [StrPref(1:end-1),...
'text-align:', Labels(iLabel).Alignment{1},...
';text-anchor:', Labels(iLabel).Anchor{1}, '"'];
% correct x - position offset
StrPrefTemp = regexprep(StrPrefTemp,'x="\S*"','x="0"');
% correct y - position offset, does not work correctly
[startIndex,endIndex] = regexp(StrPrefTemp,'y="\S*"');
yOffset = str2double(StrPrefTemp((startIndex+3):(endIndex-1)));
StrPrefTemp = regexprep(...
StrPrefTemp,...
'y="\S*"',...
['y="', num2str(yOffset*yCorrFactor), '"']);
% Replace label with original string
StrCurrTemp = strrep(StrLine_old, ...
FoundLabelText,...
['>',Labels(iLabel).XMLText,'</text']);
StrLine_new = StrCurrTemp{:};
StrPref = StrPrefTemp;
end
fprintf(fout,'%s\n',StrPref);
end
fprintf(fout,'%s\n',StrLine_new);
fclose(fin);
fclose(fout);
movefile([filename,'_temp.svg'],[filename,'.svg'])
%% Invoke Inkscape to generate PDF + LaTeX
Step = Step + 1;
waitbar(Step/nStep,hWaitBar,'Saving .svg to .pdf file');
DIR_FIG = [pwd,'/'];
[status,cmdout] = system(['"', DIR_INKSC, '"',...
' "', DIR_FIG, filename,'.svg"', ...
' ','--export-pdf',...
' "', DIR_FIG, filename,'.pdf"',...
' ','--export-latex',...
' ','-export-area-drawing']);
% test if a .pdf and .pdf_tex file exist
if exist([filename,'.pdf'],'file')~= 2 || exist([filename,'.pdf_tex'],'file')~= 2
cmdout
warning('No .pdf or .pdf_tex file produced, please check your Inkscape installation and specify installation directory correctly.')
end
%% Restore figure in matlab
% for nicety replace labels with the original text
Step = Step + 1;
waitbar(Step/nStep,hWaitBar,'Restoring Matlab figure');
iLabel = 0;
n_TexObj = length(TexObj);
for i = 1:n_TexObj %
iLabel = iLabel + 1;
TexObj(i).String = Labels(iLabel).TrueText;
end
n_LegObj = length(LegObj);
for i = 1:n_LegObj
n_Str = length(LegObj(i).String);
for j = 1:n_Str
iLabel = iLabel + 1;
LegObj(i).String{j} = Labels(iLabel).TrueText;
end
end
n_AxeObj = length(AxeObj);
for i = 1:n_AxeObj
n_Str = length(AxeObj(i).XTickLabel);
for j = 1:n_Str
iLabel = iLabel + 1;
AxeObj(i).XTickLabel{j} = Labels(iLabel).TrueText;
end
n_Str = length(AxeObj(i).YTickLabel);
for j = 1:n_Str
iLabel = iLabel + 1;
AxeObj(i).YTickLabel{j} = Labels(iLabel).TrueText;
end
n_Str = length(AxeObj(i).ZTickLabel);
for j = 1:n_Str
iLabel = iLabel + 1;
AxeObj(i).ZTickLabel{j} = Labels(iLabel).TrueText;
end
end
n_AxeObj = length(ColObj);
for i = 1:n_AxeObj
n_Str = length(ColObj(i).TickLabels);
for j = 1:n_Str
iLabel = iLabel + 1;
ColObj(i).TickLabels{j} = Labels(iLabel).TrueText;
end
end
% restore interpreter
ChangeInterpreter(gcf,'Latex')
close(hWaitBar);
end
function Str = LabelText(iLabel)
% LABELTEXT generates labels based on label number
Str = 'X000';
idStr = num2str(iLabel);
nStr = length(idStr);
Str(end - nStr + 1 : end ) = idStr;
end
function Str = LegText(iLedEntry)
% LEGTEXT generates legend labels based on legend entry number
Str = num2str(iLedEntry);
end
function ChangeInterpreter(h,Interpreter)
% CHANGEINTERPRETER puts interpeters in figure h to Interpreter
TexObj = findall(h,'Type','Text');
LegObj = findall(h,'Type','Legend');
AxeObj = findall(h,'Type','Axes');
ColObj = findall(h,'Type','Colorbar');
Obj = [TexObj;LegObj]; % Tex and Legend opbjects can be treated similar
n_Obj = length(Obj);
for i = 1:n_Obj
Obj(i).Interpreter = Interpreter;
end
Obj = [AxeObj;ColObj]; % Axes and colorbar opbjects can be treated similar
n_Obj = length(Obj);
for i = 1:n_Obj
Obj(i).TickLabelInterpreter = Interpreter;
end
end
function strXML = EscapeXML(str)
% ESCAPEXML repaces special characters(<,>,',",&) -> (&lt;,&gt;,&apos;,&quot;,&amp;)
escChar = {'&','<','>','''','"'};
repChar = {'&amp;','&lt;','&gt;','&apos;','&quot;'};
strXML = regexprep(str,escChar,repChar);
end
@AJJLagerweij
Copy link

Dear Diego,

I want to thank you for your contribution by enabling Linux users to generate proper .svg and integrating those in latex.
On my PC the .svg is created properly but it is not converted to .pdf_tex and .pdf.
I do get the following warning:

cmdout =
     '/usr/bin/inkscape: /usr/local/MATLAB/R2020a/sys/os/glnxa64/libstdc++.so.6: version `GLIBCXX_3.4.26' not found (required by /usr/bin/../lib/inkscape/libinkscape_base.so)
     /usr/bin/inkscape: /usr/local/MATLAB/R2020a/sys/os/glnxa64/libstdc++.so.6: version `GLIBCXX_3.4.26' not found (required by /lib/x86_64-linux-gnu/libcdr-0.1.so.1)'

Warning: No .pdf or .pdf_tex file produced, please check your Inkscape installation and specify installation directory correctly.
> In Plot2LaTeX (line 371)
  In Turbulent_Jet_Statistics (line 46)
>> 

I' m using Ubuntu 20.04 and MatLab 2020a.
This error can be resolved by launching matlab in with the following settings:

LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libtiff.so.5 /usr/lib/x86_64-linux-gnu/libstdc++.so.6" matlab

Could you add this to the explanation on your github page?
Kind Regards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment