Resume Using LaTeX and PHP
Beeing actively searching for a new Job, I needed to distribute muy resume in an easy way.
Keeping my resume up to date proved to be a hassle, and formatting in Microsoft Word was never as good as I wished. Moreover, any simple update caused the formatting to span multiple pages, or simply look bad.
This is when I finally turned to LaTeX to format and distribute my wiki.
The goal is to have a nice looking resume that can :
For this, I wrote and formatted my resume in LaTeX (see below or the latex source code), including some basic time-counting functions and translations.
Now, when I want to distribute it, I have to compile the latex to re-calculate everything…
Informatitians are lazy people, so instead of compiling it by hand every time I wanted to send it out, I wrote some php to compile the resume on the spot each time someone wants to download it.
The resume is now accessible for demo at the following address :
Of course, 2 other urls will generate the resume including my private contact informations, but I will not disclose these urls on this public wiki…
You will find below the LaTeX source code as well as the current php script to call the latex compiler.
I am in the process of adding the following functionalities :
Here is the LaTeX code used to compile my resume :
- resume.tex
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DEFINE THE LANGUAGE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% You might need to remove this part to generate it using PHP later on.
\newif\ifenglish
%\englishtrue % Show English text only
\englishfalse % will show French text only
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DEFINE THE VISIBILITY %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% if you want to display your private informations, comment the following line.
\def\ispublic{1}
% you might need to comment it out if you want to handle this with PHP later on.
\documentclass[11pt,a4paper]{article}
\title{Curriculum vit\ae}
\author{Etienne Molinier}
\date{Novembre 2015}
%% Standard packages
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage[utf8]{inputenc}
\usepackage{textcomp}
\usepackage[english]{babel}
\usepackage[bindingoffset=1cm,centering,includeheadfoot,margin=1cm]{geometry}
\usepackage{ifthen}
%\usepackage{blindtext}
%\usepackage{txfonts}
% Change spacing of dections and subsections
\usepackage{titlesec}
%\titlespacing{command}{left spacing}{before spacing}{after spacing}[right]
\titlespacing\section{0pt}{12pt plus 4pt minus 4pt}{5pt plus 2pt minus 3pt}
\titlespacing\subsection{10pt}{10pt plus 2pt minus 4pt}{0pt plus 2pt minus 2pt}
% Change the page margins
\addtolength{\oddsidemargin}{-.3in}
\addtolength{\evensidemargin}{-.3in}
\addtolength{\textwidth}{0.0in}
\addtolength{\topmargin}{-.4in}
\addtolength{\textheight}{0.9in}
%\setlength{\textheight}{9.5in} % increase text height to fit on 1-page
%%%%%%%%%%%%%%%%%%%%%%%%%%% DEFINE YOUR ADDRESS HERE %%%%%%%%%%%%%%%%%%%%%%%%%%%
% Set coordinates depending on public or not
% You can run this command to obtain the public version :
% pdflatex "\def\ispublic{1} \input\{resume.tex}"
\ifdefined\ispublic
\def \email {webmaster@emolinier.com}
\def \address {Region Rh\^{o}ne-Alpes Auvergne}
\def \telephone {http://wiki.emolinier.com/etienne\textunderscore molinier}
\else
\def \email {here goes your private @email}
\def \address {here goes your private postal address}
\def \telephone {Tel : here goes your private phone number}
\fi
%%%%%%%%%%%%%%%%%%%%%%%%% CALCULATE THE AGE OF THE PERSON %%%%%%%%%%%%%%%%%%%%%%%%%
\newcounter{MyAge}
\setcounter{MyAge}{\the\year}
\addtocounter{MyAge}{-PutYourYearOfBirthHere}
\ifthenelse{\the\month<PutYourMonthOfBirthHere}{\addtocounter{MyAge}{-1}}{}
\ifthenelse{\the\month=PutYourMonthOfBirthHere}{
\ifthenelse{\the\day < PutYourDayOfBirthHere}{\addtocounter{MyAge}{-1}}{}
}{}
%%%%%%%%%%%%%%%%%%%%%%% CALCULATE THE TOTAL WORK EXPERIENCE %%%%%%%%%%%%%%%%%%%%%%%
\def \WorkStartYear {2010}
\def \WorkStartMonth {10}
\def \WorkStartDay {01}
\newcounter{WorkDurationYears}
\setcounter{WorkDurationYears}{\the\year}
\addtocounter{WorkDurationYears}{-\WorkStartYear}
\ifthenelse{\the\month<\WorkStartMonth}{\addtocounter{WorkDurationYears}{-1}}{}
\ifthenelse{\the\month=\WorkStartMonth}{
\ifthenelse{\the\day < 01}{\addtocounter{WorkDurationYears}{-1}}{}
}{}
%%%%%%%%%%%%%%%%%%% CALCULATE THE DURATION OF CURRENT EMPLOYMENT %%%%%%%%%%%%%%%%%%%
\def \JobStartYear {2013}
\def \JobStartMonth {01}
\def \JobStartDay {02}
\newcounter{JobDurationYears}
\newcounter{JobDurationMonths}
\setcounter{JobDurationYears}{\the\year}
\setcounter{JobDurationMonths}{1}
\addtocounter{JobDurationYears}{-\JobStartYear}
\ifthenelse{\JobStartMonth<\the\month}{
\addtocounter{JobDurationYears}{0}
\addtocounter{JobDurationMonths}{\the\month}
\addtocounter{JobDurationMonths}{-\JobStartMonth}
}{
\ifthenelse{\the\month<\JobStartMonth}{
\addtocounter{JobDurationYears}{-1}
\addtocounter{JobDurationMonths}{11}
\addtocounter{JobDurationMonths}{\the\month}
\addtocounter{JobDurationMonths}{-\JobStartMonth}
}{}
}
%%%%%%%%%%%% CALCULATE THE DURATION OF PROJECT MANAGEMENT ROLE EXPERIENCE %%%%%%%%%%%%
\def \PMstartYear {2011}
\def \PMstartMonth {03}
\def \PMstartDay {28}
\newcounter{PMdurationYears}
\newcounter{PMdurationMonths}
\setcounter{PMdurationYears}{\the\year}
\setcounter{PMdurationMonths}{0}
\addtocounter{PMdurationYears}{-\PMstartYear}
\ifthenelse{\PMstartMonth<\the\month}{
\addtocounter{PMdurationYears}{0}
\addtocounter{PMdurationMonths}{\the\month}
\addtocounter{PMdurationMonths}{-\PMstartMonth}
}{
\ifthenelse{\the\month<\PMstartMonth}{
\addtocounter{PMdurationYears}{-1}
\addtocounter{PMdurationMonths}{12}
\addtocounter{PMdurationMonths}{\the\month}
\addtocounter{PMdurationMonths}{-\PMstartMonth}
}{}
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%% PAGE SETTINGS & SHORTCUTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\@tablebox#1{\begin{tabular}[t]{@{}l@{\extracolsep{\fill}}}#1\end{tabular}}
\pagenumbering{gobble}
\def\agrave {\`{a}}
\def\egrave {\`{a}}
\def\eaigu {\'{e}}
%%%%%%%%%%%%%%%%%%%%%%%%%% STRUCTURE OF A WORK EXPERIENCE %%%%%%%%%%%%%%%%%%%%%%%%%%
% It has 6 parameters that are ALL MANDATORY except the Client :
% 1 - Year of the experience (start)
% 2 - Duration of the experience
% 3 - Employer (company)
% 4 - Client (CAN BE EMPTY, useful when emplyer is providing services)
% 5 - Role during employment
% 6 - Location
\newenvironment{workexperience}[6]{%
\def\client{#5}\ifx\client\empty
\ifenglish
\subsection{#1 - \textit{#2} \textsf : \textbf #3\textsf , \textbf #4 \textsf #5 \textsf in #6}
\else
\subsection{#1 - \textit{#2} \textsf : \textbf #3\textsf , \textbf #4 \textsf \agrave\ #6}
\fi
\else
\ifenglish
\subsection{#1 - \textit{#2} \textsf : \textbf #3\textsf , \textbf #4 \textsf for \textbf #5 \textsf in #6}
\else
\subsection{#1 - \textit{#2} \textsf : \textbf #3\textsf , \textbf #4 \textsf pour \textbf #5 \textsf \agrave\ #6}
\fi
\fi
\hfill\begin{minipage}{\dimexpr\textwidth-10mm}
}{
\end{minipage}
}
%%%%%%%%%%%%%%%%%%%%%%%%% STARt OF THE DOCUMENT %%%%%%%%%%%%%%%%%%%%%%%%%
\begin{document}
\setcounter{secnumdepth}{0}% Disable Section numbering
%%%% Title and address %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\leavevmode\hbox to \textwidth{
\@tablebox{
\Huge{Etienne Molinier}\\
\email
}
\hfill
\@tablebox{
\small\theMyAge{} ans, Nationalit\'{e} Fran\c caise \\ \vspace{-1mm}
\small\address \\ \vspace{-1mm}
\small\telephone \\ \vspace{-1mm}
}
}\par \vspace{-3mm}
\noindent\rule{19cm}{0.4pt} % draw a horizontal line
\vspace{-8mm} % reduce vertical space to minimum
\section{\ifenglish EDUCATION \else FORMATION \fi}
\begin{tabular}{r l}
2010 & \ifenglish \textbf{Engineering degree in computer science at INSA}
\else Ing\'{e}nieur \textbf{INSA} Sp\'ecialit\'e Informatique \fi\\
2010 & \ifenglish One semester at \textbf{Dresden's University} (Germany)
\else Un semestre \`{a} l'\textbf{Universit\'{e} de Dresde} (Allemagne) \fi\\
2005-2007 & \ifenglish \textbf{Prep-school} at INSA in Lyon (France)
\else \textbf{Classe pr\'{e}paratoire} \`{a} l'INSA de Lyon \fi\\
2005 & \ifenglish \textbf{Baccalaureate} in Science \textbf{with distinction} (Bien) in Clermont-F$^d$
\else \textbf{Baccalaur\'{e}at Scientifique} mention \textbf{Bien} \`{a} Clermont-Ferrand \fi\\
2003 & \ifenglish \textbf{6 month} partner exchange in \textbf{Berlin} with Voltaire Program
\else \textbf{Echange de 6 mois \`{a} Berlin} avec le programme Voltaire \fi\\
\end{tabular}
\vspace{-0mm}
\section{\ifenglish FOREIGN LANGUAGES \else LANGUES \fi} \vspace{-0mm}
\hspace{7.6mm}
\begin{tabular}{l l}
\textbf{\ifenglish German \else Allemand \fi} & \ifenglish Fluent \else Bilingue \fi\\
\textbf{\ifenglish English \else Anglais \fi} & \ifenglish Fluent \else Bilingue \fi\\
\end{tabular}
\hspace{0.5in}
\begin{tabular}{l l}
\textbf{\ifenglish French \else Fran\c cais \fi} & \textbf{\ifenglish Mother tongue \else langue maternelle\fi}\\
\ifenglish Spanish \else Espagnol \fi & \ifenglish Beginner \else D\'{e}butant \fi\\
\end{tabular}
\section{
\ifenglish BUSINESS EXPERIENCE\else EXP\'{E}RIENCE PROFESSIONNELLE\ \fi
\ifenglish
\textsf : \theWorkDurationYears \ years of experience \footnotesize including \thePMdurationYears \ years and \thePMdurationMonths \ moths in Project Management
\else
\textsf \theWorkDurationYears \ ans d'exp\'{e}rience \footnotesize dont \thePMdurationYears \ ans et \thePMdurationMonths \ mois en gestion de projet
\fi
}
\begin{workexperience}{2013}
{
\ifenglish
\theJobDurationYears \ years and \theJobDurationMonths \ months
\else
\theJobDurationYears \ ans et \theJobDurationMonths \ mois
\fi
}
{Wipro}{Project Manager}{Michelin}{Clermont-F$^d$}
Project Manager en charge de plusieurs projets, gestion d'une \'{e}quipe de 4 personnes. Planification, gestion des contributeurs, methodologie cycle en V et agile selon les projets. Participation \`{a} la mise en \oe uvre de la roadmap business sur un programme de construction \& deploiement d'un SI pour la qualite. \\
\end{workexperience}
\begin{workexperience}{2011}
{\ifenglish 1 year and 9 months \else 1 ans et 9 mois\fi}
{Capgemini}{Project Manager}{Michelin}{Clermont-F$^d$}
Project Manager sur le projet "Outil Atterrissage" dans l'entit\'{e} DGSI/BS/GS de Michelin. Planification, gestion des contributeurs (GIC, IBM, ...), m\'{e}thodologie SQAforSI, analyse fonctionnelle, management d'un d\'{e}veloppeur, formation des utilisateurs europ\'{e}ens et nord-am\'{e}ricains.
\end{workexperience}
\begin{workexperience}{2011}
{\ifenglish 2 months \else 2 mois\fi}
{Capgemini}{Consultant}{Lyonnaise des Eaux}{Paris}
Contact direct avec le client, conseils technique et s\'{e}curit\'{e}, chiffrage. Conception et r\'{e}alisation d'un module pour le CMS PHP Symfony : param\'{e}trage automatique de fonctionnalit\'{e}s avanc\'{e}es sur le serveur web Apache.
\end{workexperience}
\begin{workexperience}{2010}
{\ifenglish 4 months \else 4 mois\fi}
{Capgemini}{Consultant}{}{Lyon}
Diverses missions : choix de prestataire pour application de covoiturage, propositions commerciales, preuves de concept, \'{e}volutions dans le cadre d'une TMA.
\end{workexperience}
\ifenglish \def\QAassistantRole{Quality Asistant} \else \def\QAassistantRole{Assistant Qualit\'{e}}\fi
\begin{workexperience}{2010}
{\ifenglish 6 months \else 6 mois\fi}{eDarling}{\QAassistantRole}{}{Berlin}
D\'{e}veloppement du d\'{e}partement qualit\'{e} : mise en place de la m\'{e}thodologie et des processus pour garantir la qualit\'{e} sur le long terme tout en retirant des b\'{e}n\'{e}fices imm\'{e}diats de cet investissement.
Gestion du cycle de vie des bugs, des releases, processus de gestions des incidents.
\end{workexperience}
\ifenglish \def\DeveloperRole{Developer} \else \def\DeveloperRole{D\'{e}veloppeur}\fi
\begin{workexperience}{2009}
{\ifenglish 4 months \else 4 mois\fi}{Novius}{\DeveloperRole}{}{Lyon}
Analyse fonctionnelle, conception et r\'{e}alisation d'un module Vid\'{e}o (Youtube, Vimeo, DailyMotion, ...) pour le CMS PubliNova en PHP sp\'{e}cifique.
\end{workexperience}
\section{\ifenglish IT Skills \else COMP\'{E}TENCES INFORMATIQUE\fi}
\begin{tabular}{l l}
%\ifenglish anglais \else francais \fi
\ifenglish Planning \else Planification \fi & Clarity \& OpenWorkbench, Capacity Planning (Clarity \& Excel)\\
OS & Windows \& Windows Server, UNIX/\textbf{Linux} (Debian-Ubuntu/Red-Hat), VxWorks.\\
\ifenglish Languages \else Langages \fi & \textbf{C/C++, JAVA, SQL, XML, PHP} , Shell, Assembleur, Pascal, Oracle, Delphi\\
\ifenglish Networks \else R\'{e}seaux \fi & Bases Techniques pour les R\'{e}seaux, \textbf{Architectures} R\'{e}seaux, Internet, ISO, R\'{e}seaux Locaux\\
\ifenglish
%nothing
\else
\multicolumn{2}{l}{M\'{e}thodes de Conception : \textbf{MERISE}, Approche Objet (\textbf{UML})}\\
Outils & MATLAB, Maple, ScyLab, Knime, SVN, git\\
\fi
\ifenglish Others \else Divers \fi & \ifenglish Video-Protection over IP, JIRA \& Confluence. \else Mise en place de SI, Video-Surveillance, JIRA \& Confluence. \fi\\
\end{tabular}
\end{document}
At first, I used a remote API to have the LaTeX sources compiled by a third party (this saved me some time first).
Then, I was growing more and more uneasy to use a third party service (what about downtimes, confidentiality, …).
So I moved on and installed a LaTeX compiler on my NAS and wrote the php code to compile my files locally.
You first need to install LaTeX on the webserver, if like me you have an exotic architecture for your NAS hardware, you can follow this guide : Install LaTeX on a Synology NAS
Once LaTeX is installed, here is the php file I used to call the compiler on-demand:
- index.php
<?php
// Default options :
$lang="fr";
$public=true;
$outputname="CV-MOLINIER_Etienne";
$outputsuffix="";
$outputextension=".pdf";
$texcompiler = "/opt/usr/local/texlive/2015/bin/armel-linux/pdflatex";
$outputfolder = "/here/goes/the/destination/folder/to/store/temp/files/and/compiled/pdf";
// Parse options provided by client
if ( array_key_exists("printcontact",$_GET) && $_GET["printcontact"]=="true") {
$public=false;
} else {
$outputsuffix="_public";
}
if ( array_key_exists("lang",$_GET) && $_GET["lang"]=="en") {
$lang="en";
$outputname="Resume-MOLINIER_Etienne";
}
if ( array_key_exists("lang",$_GET) && $_GET["lang"]=="fr") {
$lang="fr";
$outputname="CV-MOLINIER_Etienne";
}
// load the latex code
$tex=file_get_contents(dirname(__FILE__)."/resume.tex");
// check if we are allowed to print private informations
if ($public) {
// add the latex variable to display only public informations
$tex="\def\ispublic{1}\n".$tex;
}
if ($lang=="en") {
$tex="\\newif\\ifenglish\n\\englishtrue\n".$tex;
} else {
$tex="\\newif\\ifenglish\n\\englishfalse\n".$tex;
}
// write the latex code to a temp file :
//$tmpfile = tempnam("/tmp", "resume-latex");
$tmpfile = "$outputfolder/$outputname$outputsuffix.tex";
file_put_contents($tmpfile,$tex);
// compile the latex
$result = shell_exec("$texcompiler -output-directory $outputfolder --interaction batchmode $tmpfile");
//return the file.
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"" . ($outputname.$outputsuffix.$outputextension) . "\"");
readfile("$outputfolder/$outputname$outputsuffix$outputextension"); // do the double-download-dance (dirty but worky)
//echo $result;
?>
The Idea is to call a remote API to compile the latex file on the spot.
Also, we add a few settings to the latex file.
- resume.php
<?php
// Method: POST, PUT, GET etc
// Data: array("param" => "value") ==> index.php?param=value
function CallAPI($method, $url, $data = false){
$curl = curl_init();
switch ($method){
case "POST":
curl_setopt($curl, CURLOPT_POST, 1);
if ($data)
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
break;
case "PUT":
curl_setopt($curl, CURLOPT_PUT, 1);
break;
default:
if ($data)
$url = sprintf("%s?%s", $url, http_build_query($data));
}
// Optional Authentication:
//curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
//curl_setopt($curl, CURLOPT_USERPWD, "username:password");
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($curl);
curl_close($curl);
return $result;
}
// Default options :
$lang="fr";
$public=true;
$outputname="CV-MOLINIER_Etienne";
$outputsuffix="";
$outputextension=".pdf";
// Parse options provided by client
if ( array_key_exists("privateinformationoutput",$_GET) && $_GET["privateinformationoutput"]=="true") {
$public=false;
} else {
$outputsuffix="_public";
}
if ( array_key_exists("lang",$_GET) && $_GET["lang"]=="en") {
$lang="en";
$outputname="Resume MOLINIER Etienne";
}
if ( array_key_exists("lang",$_GET) && $_GET["lang"]=="fr") {
$lang="fr";
$outputname="CV MOLINIER Etienne";
}
// load the latex code
$tex=file_get_contents("resume.tex");
// check if we are allowed to print private informations
if ($public) {
// add the latex variable to display only public informations
$tex="\def\ispublic{1}\n".$tex;
}
if ($lang=="en") {
$tex="\\newif\ifenglish\n\\englishtrue".$tex;
} else {
$tex="\\newif\ifenglish\n\\englishfalse".$tex;
}
// prepare the data for the query
$data =array(
"pole"=>$tex,
"pdf"=>"PDF",
"preklad"=>"latex",
"pruchod"=>"1",
".cgifields"=>"komprim"
);
// and call the API
$result = CallApi("POST","https://tex.mendelu.cz/en/",$data);
//return the file.
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"" . ($outputname.$outputsuffix.$outputextension) . "\"");
//readfile($file_url); // do the double-download-dance (dirty but worky)
echo $result;
?>