% \iffalse meta-comment % % Copyright (c) 2018-2022 David Purton % % This work may be distributed and/or modified under the conditions of % the LaTeX Project Public License, either version 1.3c of this license % or (at your option) any later version. The latest version of this % license is in % http://www.latex-project.org/lppl.txt % and version 1.3c or later is part of all distributions of LaTeX % version 2005/12/01 or later. % %<*driver> \documentclass[a4paper]{l3doc} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \changes{v1.0}{2018/05/08}{First public release} % \changes{v1.1}{2019/11/03}{Fix deprecated macros} % \changes{v1.2}{2020/10/29}{Update to use \pkg{l3hooks}} % \changes{v1.2a}{2021/02/25}{Ensure backend loaded before counting pages} % \changes{v1.2b}{2021/11/01}{Protect unexpandable macros} % \changes{v1.3}{2022/08/27}{Allow PDF file name to be passed in as macro} % % \title{The \pkg{pdfoverlay} package} % \author{David Purton\thanks{Email: \url{dcpurton@marshwiggle.net}}} % \date{2022/08/27 v1.3} % % \maketitle % % \begin{abstract} % This \pkg{pdfoverlay} package provides a simple interface to overlay text % on to an existing PDF. The text to be overlaid is typeset and positioned % normally as you would any other \LaTeX{} document. Some or all of the % pages of the PDF can be included and not all pages of the PDF need have % overlaid text. It's also possible to include text between pages of the % PDF. % \end{abstract} % % \tableofcontents % % \begin{documentation} % % \section{Introduction} % % It's often desirable to take an existing PDF and easily add annotations or % text overlaying the PDF. This might arise if you wish to add comments to a % PDF, fill in a PDF form, or add text to a PDF where space has been left for % notes. % % This package provides a simple interface to do this without having to resort % to inserting one page at a time. Some or all of the pages of the PDF can be % included and not all pages of the PDF need have overlaid text. It's also % possible to include text between pages of the PDF. % % Another advantage of this package is that the overlaid text can be set as % normal flowing from one page to another or with manual page breaks if you % wish. It's also possible to use any standard method to position text at % arbitrary places on a given page. % % \section{Bug Reports and Feature Requests} % % Bug reports and feature requests can be made at the \pkg{pdfoverlay} package % GitHub repository. See \url{https://github.com/dcpurton/pdfoverlay}. % % \section{Documentation} % % \subsection{Basic Usage} % % At it's simplest, the only thing required is to specify the PDF to overlay % with text and then begin typing your document. The pages of the PDF will be % inserted as you go automatically. If your text takes up more pages than the % PDF then any remaining text is inserted on blank pages. % \begin{verbatim} % \documentclass{article} % \usepackage{pdfoverlay} % \pdfoverlaySetPDF{filename.pdf} % \begin{document} % ... % \end{document} % \end{verbatim} % The PDF will be centered on and scaled to fit the document page size while % keeping a fixed aspect ratio. % % \subsection{Main Interface} % % \begin{function}{\pdfoverlaySetPDF, \pdfoverlay_set_pdf:n} % \begin{syntax} % \cs{pdfoverlaySetPDF} \Arg{PDF filename} % \end{syntax} % Specify the \meta{PDF filename} to overlay text on to. This function must % be called first. It can be used either in the preamble or at any point in % the document body. It is possible to call this more than once to include % multiple PDF files. If the specified \meta{PDF filename} cannot be found an % error is generated. % \end{function} % % \begin{function}{\pdfoverlaySetGraphicsOptions, % \pdfoverlay_set_graphics_options:n} % \begin{syntax} % \cs{pdfoverlaySetGraphicsOptions} \Arg{Options} % \end{syntax} % Set the \meta{Options} to be passed to \cs{includegraphics} when each page % of the PDF is output. See the \pkg{graphicx} package documentation for % valid options. % % \textbf{Note} that it is not necessary to specify the \texttt{page} option % as this is appended automatically in the right format. The default % options are: \texttt{keepaspectratio, width=}\cs{paperwidth}\texttt{, % height=}\cs{paperheight}. % \end{function} % % \begin{function}{\pdfoverlayIncludeToPage, \pdfoverlay_include_to_page:n} % \begin{syntax} % \cs{pdfoverlayIncludeToPage} \Arg{page number} % \end{syntax} % Output all pages in the PDF file up until the specified \meta{page number}. % If the specified \meta{page number} does not evaluate to an integer or does % not exist in the PDF an error is generated. % \end{function} % % \begin{function}{\pdfoverlayIncludeToLastPage, \pdfoverlay_include_to_last_page:} % \begin{syntax} % \cs{pdfoverlayIncludeToLastPage} % \end{syntax} % Output all remaining pages in the PDF file. % \end{function} % % \begin{function}{\pdfoverlaySkipToPage, \pdfoverlay_skip_to_page:n} % \begin{syntax} % \cs{pdfoverlaySkipToPage} \Arg{page number} % \end{syntax} % Skip to the specified \meta{page number}. This can be before or after the % current page in the PDF file. If the specified \meta{page number} does not % evaluate to an integer or does not exist in the PDF an error is generated. % \end{function} % % \begin{function}{\pdfoverlayPauseOutput, \pdfoverlay_pause_output:} % \begin{syntax} % \cs{pdfoverlayPauseOutput} % \end{syntax} % Pause outputting pages from the PDF file. Any subsequent text will by % output on blank pages. % \end{function} % % \begin{function}{\pdfoverlayResumeOutput, \pdfoverlay_resume_output:} % \begin{syntax} % \cs{pdfoverlayResumeOutput} % \end{syntax} % Resume outputting pages from the PDF file. % \end{function} % % \end{documentation} % % \begin{implementation} % % \section{Implementation} % % \begin{macrocode} %<*package> %<@@=pdfoverlay> % \end{macrocode} % % \begin{macrocode} \NeedsTeXFormat{LaTeX2e}[2020-10-01] \ProvidesExplPackage{pdfoverlay}{2022/08/27}{1.3} {Overlay text on an existing PDF document (DCP)} % \end{macrocode} % % \noindent The \pkg{graphicx} package is required. % % \begin{macrocode} \RequirePackage{graphicx} % \end{macrocode} % % \noindent Call \cs{@@_output_pdf_page:} on every page. % \begin{macrocode} \hook_gput_code:nnn { shipout/background } { pdfoverlay } { \@@_output_pdf_page: } % \end{macrocode} % % \noindent Add an empty \cs{hbox:n} to the end of the document if an action % is pending. This is required to ensure that the last requested page from the % PDF file is output even if there isn't any other content on the page. % \begin{macrocode} \hook_gput_code:nnn { enddocument } { pdfoverlay } { \bool_if:NT \g_@@_action_pending_bool { \hbox:n { } } } % \end{macrocode} % % \subsection{Messages} % % Error message when specified PDF file cannot be found. \\ % |#|1: PDF file name % \begin{macrocode} \msg_new:nnnn { pdfoverlay } { file-not-found } { PDF~file~`#1'~not~found. } { Unable~to~find~the~file~`#1'. \\ Check~that~the~file~exists~and~you~have~spelt~it~correctly. } % \end{macrocode} % % \noindent Error message when no PDF file has been set. % \begin{macrocode} \msg_new:nnnn { pdfoverlay } { file-not-set } { PDF~file~not~set. } { You~have~not~specified~a~PDF~file. \\ Set~a~PDF~file~using~\pdfoverlaySetPDF. } % \end{macrocode} % % \noindent Error message when the requested page number cannot be found in the % PDF file. \\ % |#1|: PDF file name \\ % |#2|: Requested page number \\ % |#3|: Total number of pages in PDF % \begin{macrocode} \msg_new:nnnn { pdfoverlay } { page-not-found } { Page~not~found~in~PDF. } { PDF~file~`#1'~does~not~contain~page~#2. \\ Specify~a~page~between~1~and~#3. } % \end{macrocode} % % \noindent Error message when the requested page number to include to is less % than the current page number being output from the PDF file. \\ % |#1|: Requested page number \\ % |#2|: Current page number in PDF \\ % |#3|: Total number of pages in PDF % \begin{macrocode} \msg_new:nnnn { pdfoverlay } { page-too-low } { Requested~page~less~than~current~page~in~PDF. } { You~have~requested~to~include~to~page~#1, \\ but~the~current~page~is~already~at~page~#2. \\ Specify~a~page~between~#2~and~#3. } % \end{macrocode} % % \noindent Error message when running in DVI mode. The \pkg{pdfoverlay} % package does not support DVI mode for either \texttt{pdflatex} or % \texttt{lualatex}. % \begin{macrocode} \msg_new:nnnn { pdfoverlay } { dvi-mode } { DVI~mode~not~supported. } { DVI~mode~of~#1~is~not~supported. \\ You~must~use~PDF~mode. } % \end{macrocode} % % \noindent Error message for unsupported engine. Only \texttt{pdftex}, % \texttt{luatex}, and \texttt{xetex} are supported. % \begin{macrocode} \msg_new:nnnn { pdfoverlay } { unsupported-engine } { #1~not~supported. } { The~#1~engine~is~not~supported. \\ Use~one~of~pdftex,~luatex,~or~xetex. } % \end{macrocode} % % % \subsection{Private Variables and Helper Functions} % % \begin{macro}{\g_@@_pdf_file_name_tl} % Store the PDF file name. % \begin{macrocode} \tl_new:N \g_@@_pdf_file_name_tl % \end{macrocode} % \end{macro} % % \begin{macro}{\g_@@_page_count_int} % Store the total number of pages in the PDF file. % \begin{macrocode} \int_new:N \g_@@_page_count_int % \end{macrocode} % \end{macro} % % \begin{macro}{\g_@@_page_int} % Keep track of the current page in the PDF file. % \begin{macrocode} \int_new:N \g_@@_page_int % \end{macrocode} % \end{macro} % % \begin{macro}{\g_@@_output_active_bool} % Flag to store whether to output pages from the PDF file or not. % \begin{macrocode} \bool_new:N \g_@@_output_active_bool % \end{macrocode} % \end{macro} % % \begin{macro}{\g_@@_action_pending_bool} % Flag to store whether an action (\cs{pdfoverlay_include_to_page:n} or % \cs{pdfoverlay_set_page:n}) is pending. If an action is pending, these % functions will start a new page. % \begin{macrocode} \bool_new:N \g_@@_action_pending_bool % \end{macrocode} % \end{macro} % % \begin{macro}{\g_@@_graphics_options_clist} % Hold the options passed to \cs{includegraphics} when including a page from % the PDF. The default options scale the PDF page to fit the document page % while keeping the original PDF page aspect ratio. % \begin{macrocode} \clist_new:N \g_@@_graphics_options_clist \clist_set:Nn \g_@@_graphics_options_clist { keepaspectratio , width = \paperwidth , height = \paperheight , page = \int_use:N \g_@@_page_int } % \end{macrocode} % \end{macro} % % \begin{macro}{\g_@@_pdf_page_coffin} % The current PDF page is set in this coffin which can then be positioned on % the page. % \begin{macrocode} \coffin_new:N \g_@@_pdf_page_coffin % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_output_pdf_page:} % Main function to output the current page of the PDF file. This is called % before every page is shipped using the \texttt{shipout/background} hook. % \begin{macrocode} \cs_new_protected:Nn \@@_output_pdf_page: { % \end{macrocode} % Check if we are currently outputting pages, a PDF file has been set, and % the requested page exists in the PDF file. % \begin{macrocode} \bool_lazy_all:nT { { \bool_if_p:N \g_@@_output_active_bool } { \bool_not_p:n { \tl_if_empty_p:N \g_@@_pdf_file_name_tl } } { \int_compare_p:n { \c_zero_int <= \g_@@_page_int < \g_@@_page_count_int } } } { % \end{macrocode} % Increment the current page counter. % \begin{macrocode} \int_gincr:N \g_@@_page_int % \end{macrocode} % Place requested page from the PDF file in the background of the % document page with the specified format. % \begin{macrocode} \@@_format_pdf_page: \@@_place_pdf_page: % \end{macrocode} % Set action pending flag to false. % \begin{macrocode} \bool_gset_false:N \g_@@_action_pending_bool } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_format_pdf_page:} % Format the PDF page with options from \cs{g_@@_graphics_options_clist} and % place it in \cs{g_@@_pdf_page_coffin}. % \begin{macrocode} \cs_new_protected:Nn \@@_format_pdf_page: { \hcoffin_gset:Nn \g_@@_pdf_page_coffin { \use:x { \exp_not:N \includegraphics [ \clist_use:Nn \g_@@_graphics_options_clist { , } ] { \g_@@_pdf_file_name_tl } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_place_pdf_page:} % Place the PDF page in the centre of the document page. % % Although marked as private, this function could be redefined to position % the PDF page at a different location on the document page. % % This macro is called from within the \texttt{shipout\slash background} % hook, which adds a picture environment into the background of the page % with the (0, 0) coordinate in the top-left corner using a \cs{unitlength} % of 1pt. It should therefore only receive \cs{put} commands or other % commands suitable in a picture environment and the vertical coordinate % values would normally be negative. % % The PDF page to be placed is available as the coffin % \cs{g_@@_pdf_page_coffin}. % \begin{macrocode} \cs_new_protected:Nn \@@_place_pdf_page: { \put ( 0.5 \paperwidth, -0.5 \paperheight ) { \coffin_typeset:Nnnnn \g_@@_pdf_page_coffin { hc } { vc } { 0pt } { 0pt } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_count_pdf_pages:} % Count the number of pages in the current \cs{g_@@_pdf_file_name_tl} and % store the result in \cs{g_@@_pdf_page_count_int}. If no PDF has been set % with \cs{pdfoverlay_set_pdf} then \cs{g_@@_pdf_page_count_int} is set to % zero. % \begin{macrocode} \cs_new_protected:Nn \@@_count_pdf_pages: { \int_gzero:N \g_@@_page_count_int \tl_if_empty:NTF \g_@@_pdf_file_name_tl { \msg_error:nn { pdfoverlay } { file-not-set } } { \sys_if_engine_xetex:TF { \int_gset:Nn \g_@@_page_count_int { \XeTeXpdfpagecount " \g_@@_pdf_file_name_tl " } } { \str_if_exist:NF \c_sys_backend_str { \sys_load_backend:n { } } \sys_if_output_pdf:TF { \sys_if_engine_pdftex:TF { \pdfximage { \g_@@_pdf_file_name_tl } \int_gset_eq:NN \g_@@_page_count_int \pdflastximagepages } { \sys_if_engine_luatex:TF { \saveimageresource { \g_@@_pdf_file_name_tl } \int_gset_eq:NN \g_@@_page_count_int \lastsavedimageresourcepages } { \msg_error:nnx { pdfoverlay } { unsupported-engine } { \c_sys_engine_str } } } } { \msg_error:nnx { pdfoverlay } { dvi-mode } { \c_sys_engine_str } } } } } % \end{macrocode} % \end{macro} % % \begin{macro}[pTF]{\@@_check_page:n} % Check if a PDF file has been set and the requested page is valid. If not, % generate suitable error messages. % \begin{macrocode} \prg_new_conditional:Nnn \@@_if_page_exists:n { p, T, F, TF } { \tl_if_empty:NTF \g_@@_pdf_file_name_tl { \msg_error:nn { pdfoverlay } { file-not-set } \prg_return_false: } { \int_compare:nTF { \c_one_int <= #1 <= \g_@@_page_count_int } { \prg_return_true: } { \msg_error:nnxxx { pdfoverlay } { page-not-found } { \g_@@_pdf_file_name_tl } { \int_eval:n { #1 } } { \int_use:N \g_@@_page_count_int } \prg_return_false: } } } % \end{macrocode} % \end{macro} % % % \subsection{Public Expl3 Functions} % % \begin{macro}{\pdfoverlay_set_pdf:n} % Specify a PDF file to overlay text on to. % \begin{macrocode} \cs_new_protected:Nn \pdfoverlay_set_pdf:n { % \end{macrocode} % Test if the file exists and generate a suitable error if it doesn't. % \begin{macrocode} \file_if_exist:nTF { #1 } { \tl_gset:Nn \g_@@_pdf_file_name_tl { #1 } % \end{macrocode} % Find the number of pages in the PDF file. % \begin{macrocode} \@@_count_pdf_pages: % \end{macrocode} % Initialise variables. % \begin{macrocode} \int_gzero:N \g_@@_page_int \bool_gset_true:N \g_@@_output_active_bool \bool_gset_false:N \g_@@_action_pending_bool } { \msg_error:nnx { pdfoverlay } { file-not-found } { #1 } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\pdfoverlay_set_graphics_options:n} % Set the options as a comma separated list to be passed to % \cs{includegraphics} when outputting each page from the PDF file. The % \texttt{page} option is appended automatically in the % correct format. % \begin{macrocode} \cs_new_protected:Nn \pdfoverlay_set_graphics_options:n { \clist_gset:Nn \g_@@_graphics_options_clist { #1 } \clist_gput_right:Nn \g_@@_graphics_options_clist { page = \int_use:N \g_@@_page_int } } % \end{macrocode} % \end{macro} % % \begin{macro}{\pdfoverlay_include_to_page:n} % Output all pages in the PDF file up until the specified page. Since the % actual PDF page is inserted by \cs{__pdfoverlay_output_pdf_page:} using the % \texttt{shipout/background} hook, we just insert the required number of % blank pages into the document. % \begin{macrocode} \cs_new_protected:Nn \pdfoverlay_include_to_page:n { % \end{macrocode} % Check if a PDF file is set and the requested page exists. % \begin{macrocode} \@@_if_page_exists:nT { #1 } { % \end{macrocode} % Check if the requested page is greater than the current page. % \begin{macrocode} \int_compare:nTF { #1 >= \g_@@_page_int + 1 } { % \end{macrocode} % Check if an action is pending and start a new page if so. % \begin{macrocode} \bool_lazy_all:nT { { \bool_if_p:n { \g_@@_action_pending_bool } } { \int_compare_p:n { \g_@@_page_int < \g_@@_page_count_int - 1 } } { \int_compare_p:n { #1 != \g_@@_page_int + 1 } } } { \hbox:n { } \clearpage } % \end{macrocode} % Loop until the specified page, inserting blank pages. % \begin{macrocode} \int_while_do:nNnn { \g_@@_page_int } < { #1 - 1 } { \hbox:n { } \clearpage } % \end{macrocode} % Set the action pending flag, so final page is output even if no text is % overlaid on it. % \begin{macrocode} \bool_gset_true:N \g_@@_action_pending_bool } { \msg_error:nnxxx { pdfoverlay } { page-too-low } { \int_eval:n { #1 } } { \int_eval:n { \g_@@_page_int + 1 } } { \int_use:N \g_@@_page_count_int } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\pdfoverlay_include_to_last_page:} % Output all remaining pages in the PDF file. % \begin{macrocode} \cs_new_protected:Nn \pdfoverlay_include_to_last_page: { \pdfoverlay_include_to_page:n { \g_@@_page_count_int } } % \end{macrocode} % \end{macro} % % \begin{macro}{\pdfoverlay_skip_to_page:n} % Skip to the specified page number in the PDF file. % \begin{macrocode} \cs_new_protected:Nn \pdfoverlay_skip_to_page:n { \@@_if_page_exists:nT { #1 } { % \end{macrocode} % Check if an action is pending and start a new page if so. % \begin{macrocode} \bool_if:nT { \g_@@_action_pending_bool } { \hbox:n { } \clearpage } % \end{macrocode} % Set the page number for the next page to be output. % \begin{macrocode} \int_gset:Nn \g_@@_page_int { #1 - 1 } % \end{macrocode} % Set the action pending flag, so final page is output even if no text is % overlaid on it. % \begin{macrocode} \bool_gset_true:N \g_@@_action_pending_bool } } % \end{macrocode} % \end{macro} % % \begin{macro}{\pdfoverlay_pause_output:} % Pause outputting pages from the PDF file. % \begin{macrocode} \cs_new_protected:Nn \pdfoverlay_pause_output: { \bool_gset_false:N \g_@@_output_active_bool } % \end{macrocode} % \end{macro} % % \begin{macro}{\pdfoverlay_resume_output:} % Resume outputting pages from the PDF file. % \begin{macrocode} \cs_new_protected:Nn \pdfoverlay_resume_output: { \bool_gset_true:N \g_@@_output_active_bool } % \end{macrocode} % \end{macro} % % \subsection{Public \LaTeX{} Interface} % % \begin{macro}{\pdfoverlaySetPDF} % Specify a PDF file to overlay text on to. % \begin{macrocode} \NewDocumentCommand \pdfoverlaySetPDF { m } { \pdfoverlay_set_pdf:n { #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\pdfoverlaySetGraphicsOptions} % Set the options as a comma separated list to be passed to % \cs{includegraphics} when outputting each page from the PDF file. The % \texttt{page} option is appended automatically in the right format. % \begin{macrocode} \NewDocumentCommand \pdfoverlaySetGraphicsOptions { m } { \pdfoverlay_set_graphics_options:n { #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\pdfoverlayIncludeToPage} % Output all pages in the PDF file up until the specified page. % \begin{macrocode} \NewDocumentCommand \pdfoverlayIncludeToPage { m } { \pdfoverlay_include_to_page:n { #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\pdfoverlayIncludeToLastPage} % Output all remaining pages in the PDF file. % \begin{macrocode} \NewDocumentCommand \pdfoverlayIncludeToLastPage { } { \pdfoverlay_include_to_last_page: } % \end{macrocode} % \end{macro} % % \begin{macro}{\pdfoverlaySkipToPage} % Skip ahead to the specified page number in the PDF file. % \begin{macrocode} \NewDocumentCommand \pdfoverlaySkipToPage { m } { \pdfoverlay_skip_to_page:n { #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\pdfoverlayPauseOutput} % Pause outputting pages from the PDF file. % \begin{macrocode} \NewDocumentCommand \pdfoverlayPauseOutput { } { \pdfoverlay_pause_output: } % \end{macrocode} % \end{macro} % % \begin{macro}{\pdfoverlayResumeOutput} % Resume outputting pages from the PDF file. % \begin{macrocode} \NewDocumentCommand \pdfoverlayResumeOutput { } { \pdfoverlay_resume_output: } % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintChanges % % \PrintIndex