Marco para gráficos interactivos en documentos LaTeX

Marco para gráficos interactivos en documentos LaTeX

Escribí un prototipo de paquete LaTeX y un marco de visualización asociado para permitir que los documentos LaTeX se vean en un entorno que permita la interacción con figuras. La figura puede tener puntos que se pueden arrastrar, aceptar entradas numéricas desde el teclado, hacer uso de varios widgets GUI, etc. La intención es que LaTeX produzca un PDF como lo hace normalmente, pero permita el PDF, junto con algunos archivos adicionales. archivos, que se cargarán en un visor especial que reemplaza todas las figuras estáticas del PDF con figuras interactivas. Visiblemente, las dos versiones, estática e interactiva, deben ser idénticas en todos los aspectos excepto en ser interactivas o estáticas. Tengo una prueba de concepto que utiliza un navegador web como plataforma para visualización e interacción. También hay una versión menos desarrollada para Java, pero parece menos útil, por lo que fue abandonada.

El extremo LaTeX del marco se muestra a continuación y funciona, aunque queda un problema. Como se discutió enConfigurar la altura de una imagen tikz usando latex3, el área de las figuras no es estable. La respuesta proporcionada allí funciona para el MWE dado, pero no en el contexto completo.

En resumen, el intfigentorno toma un nombre de archivo (sin sufijo .tikz) y la altura que debe tener la figura. El entorno busca el archivo .tikz dado. Si el archivo existe, se carga; si no existe, se muestra un cuadro con un mensaje "no disponible". Si el archivo no existe y no está cargado entonces la figura tiene una altura; si el archivo está disponible, entonces la figura parece tener una línea de texto más larga de lo esperado, como si hubiera una línea adicional \pardetrás de la figura.

Espero ayuda específica sobre el problema de la colocación de las figuras y sugerencias generales para mejorar, ya que estoy lejos de ser un experto en LaTeX3.

Hay algunos comentarios adicionales sobre cómo funciona el sistema en el código siguiente.

\documentclass[10pt]{article}

% To precisely locate figures within a document.
\usepackage{zref-savepos}
\usepackage{zref-thepage}
\usepackage{zref-abspage}
\usepackage{zref-user}
\usepackage{zref-pagelayout}

% To treat environment verbatim.
\usepackage{xsim}

\ExplSyntaxOn

% The inner and outer margin widths. There should be a way to
% determine these programmatically instead of having the user provide
% their best estimate, but I don't know how.
\dim_new:N \l_intfig_innermargin_dim
\dim_new:N \l_intfig_outermargin_dim

\dim_set:Nn \l_intfig_innermargin_dim { 0 pt }
\dim_set:Nn \l_intfig_outermargin_dim { 0 pt }

\NewDocumentCommand\SetInnerMargin { m }
{
  \dim_set:Nn \l_intfig_innermargin_dim { #1 }
}

\NewDocumentCommand\SetOuterMargin { m }
{
  \dim_set:Nn \l_intfig_outermargin_dim { #1 }
}

% The figures file is open throughout the run.
\iow_new:N \g_figurefile_iow
\iow_open:Nn \g_figurefile_iow { figures.aux }

% Name for the figure, as given by the user.
\str_new:N \l_intfig_figname_str

% Manditory height given by the user.
\dim_new:N \l_intfig_height_dim

% Optional height given by the user (if the interactive height differs.
\dim_new:N \l_intfig_interactiveHeight_dim

% Scratch values used for calculation of figure position.
\tl_new:N \l_intfig_tempa_tl
\tl_new:N \l_intfig_tempb_tl
\tl_new:N \l_intfig_tempc_tl

% And several booleans.
\bool_new:N \l_intfig_hasintht_bool
\bool_new:N \l_intfig_external_bool
\bool_new:N \l_intfig_nostatic_bool
\bool_new:N \l_intfig_done_bool

% This is for the figures file: the position of the figure on the page.
\dim_new:N \l_intfig_pagepos_dim

% I don't really understand the whole concept of these "variants."
\cs_generate_variant:Nn \file_if_exist:nTF {V}
\cs_generate_variant:Nn \file_input:n {V}

\NewDocumentEnvironment{intfig} { > { \SplitArgument { 1 } { , } } m!o }
{
  % Not sure if this is needed. Are there any local variables at all?
  \group_begin:

  % Parse manditory arguments.
  \intfig_manargs #1

  % The optional arguments.
  \bool_set_false:N \l_intfig_hasintht_bool
  \bool_set_false:N \l_intfig_external_bool
  \bool_set_false:N \l_intfig_nostatic_bool
  \bool_set_false:N \l_intfig_done_bool
  
  \tl_if_blank:nTF { #2 } {} {
    \clist_set:Nn \l_intfig_optclist_clist { #2 }
    \intfig_optargs { } 
  }

  % Prepare the data for the figures file.
  
  % The page number, obtained from a previous run.
  \str_set:Nx \l_intfig_filespec_str { 
    \use:c { zref@extractdefault } { \l_intfig_figname_str -pageno} { abspage } { 0 }
  }

  % The inner and outer margins.
  \str_put_right:Nx \l_intfig_filespec_str { ~ \dim_to_decimal_in_bp:n \l_intfig_innermargin_dim }
  \str_put_right:Nx \l_intfig_filespec_str { ~ \dim_to_decimal_in_bp:n \l_intfig_outermargin_dim }

  % And the text width.
  \str_put_right:Nx \l_intfig_filespec_str { ~ \dim_to_decimal_in_bp:n \textwidth }

  % Calculation of figure location, taking into account the
  % possibility that the figure has been bumped to the next page.
  % Take the location of the bottom edge and subract the figure height.
  \tl_set:Nn \l_intfig_tempa_tl { \zposy { \l_intfig_figname_str } }
  % Convert the figure height to sp.
  \tl_set:Nn \l_intfig_tempb_tl { \dim_to_decimal_in_sp:n{ \l_intfig_height_dim } }
  % Add 
  \tl_set:Nn \l_intfig_tempc_tl { \int_eval:n { \l_intfig_tempa_tl + \l_intfig_tempb_tl } }
  % Convert to a dim
  \dim_set:Nn \l_intfig_pagepos_dim { \l_intfig_tempc_tl sp }
  % And (whew!) write it out, in bp.
  \str_put_right:Nx \l_intfig_filespec_str { ~ \dim_to_decimal_in_bp:n \l_intfig_pagepos_dim }

  % The latex height of the figure (in bp)
  \str_put_right:Nx \l_intfig_filespec_str { ~ \dim_to_decimal_in_bp:n \l_intfig_height_dim }

  % The interactive height, which may be the latex height repeated.
  \bool_if:NTF \l_intfig_hasintht_bool
  { \str_put_right:Nx \l_intfig_filespec_str { ~ \dim_to_decimal_in_bp:n \l_intfig_interactiveHeight_dim }
  } % else
  {
    \str_put_right:Nx \l_intfig_filespec_str { ~ \dim_to_decimal_in_bp:n \l_intfig_height_dim }
  }

  % The figure name.
  \str_put_right:Nx \l_intfig_filespec_str { ~ \l_intfig_figname_str }

  % And a boolean
  \bool_if:NTF \l_intfig_done_bool
  {
    \str_put_right:Nx \l_intfig_filespec_str { ~ true }
  }
  {
    \str_put_right:Nx \l_intfig_filespec_str { ~ false }
  }

  % String is ready. Write out the line.
  \iow_now:Nx \g_figurefile_iow { \l_intfig_filespec_str }
  
  % Based on the optional arguments, write the body of the environment to a
  % file, and/or read a tikz file to replace the current body (which may  be
  % blank). Set two booleans to indicate whether to do these two things.
  \bool_set_false:N \l_intfig_writebody_bool
  \bool_set_false:N \l_intfig_readtikz_bool

  \bool_if:NTF \l_intfig_done_bool
  {
    \bool_if:NTF \l_intfig_nostatic_bool {} {
      \bool_set_true:N \l_intfig_readtikz_bool
    }
  }
  {
    \bool_if:NTF \l_intfig_external_bool
    {
      \bool_if:NTF \l_intfig_nostatic_bool {}
        { \bool_set_true:N \l_intfig_readtikz_bool }
    }
    {
      \bool_set_true:N \l_intfig_writebody_bool
      \bool_if:NTF \l_intfig_nostatic_bool  { }
      {
        \bool_set_true:N \l_intfig_readtikz_bool
      }
    }
  }

  % The output file name needs an extra ".fjs" on the end.
  % Apparently, on Windows, you can't use a suffix for executable
  % types. No .js, no .py, .pl, etc.
  \str_set_eq:NN \l_intfig_jinfile_str \l_intfig_figname_str
  \str_put_right:Nn \l_intfig_jinfile_str { .fjs }
  
  % Whether to add an additional EOL to the output file depends on
  % whether there were any optional arguments. This is mysterious to me.
  \IfValueTF {#2}
    { \xsim_file_write_start:nn { \c_true_bool } }
    { \xsim_file_write_start:nn { \c_false_bool } }
    { \l_intfig_jinfile_str }
}{
  % Post environment commands.

  % Stop writing the body since it's done.
  \xsim_file_write_stop:

  % And replace the current body with an external tikz file.
  \str_set_eq:NN \l_intfig_tikzfile_str \l_intfig_figname_str
  \str_put_right:Nn \l_intfig_tikzfile_str { .tikz }
  
  \file_if_exist:VTF \l_intfig_tikzfile_str {
    % Load the tikz file here.
    \file_input:V \l_intfig_tikzfile_str
  } 
  {
    % No tikz file exists. Display a big empty box.
    \begin{tikzpicture}
      \useasboundingbox (0pt,0pt) rectangle (\textwidth,\l_intfig_height_dim);
      \draw[dashed] (0pt,0pt) rectangle ( \textwidth,\l_intfig_height_dim);
      \node at (\textwidth / 2,\l_intfig_height_dim / 2) {The\ drawing\ is\ not\ available\ to\ load.};
      \draw (5pt,5pt) rectangle ( \textwidth - 5,\l_intfig_height_dim - 5);
    \end{tikzpicture}
  }
  
  % Note values for the next run.
  \zsaveposy { \l_intfig_figname_str }
  \zlabel{ \l_intfig_figname_str -pageno}
  
  \group_end:  
}

% Parser for manditory arguments.
\NewDocumentCommand{\intfig_manargs}{ m m }
{
  \str_set:Nn \l_intfig_figname_str { #1 }
  \dim_set:Nn \l_intfig_height_dim { #2 }
}

% Parser for optional arguments.
\NewDocumentCommand{\intfig_optargs} { }
{
  \seq_set_from_clist:NN \l_intfig_optseq_seq { \l_intfig_optclist_clist }
  \seq_get_left:NN \l_intfig_optseq_seq \l_intfig_firstvalue_tl
  \intfig_if_length:VTF \l_intfig_firstvalue_tl 
  {
    \dim_set:Nn \l_intfig_interactiveHeight_dim \l_intfig_firstvalue_tl
    \bool_set_true:N \l_intfig_hasintht_bool
  }
  {
    % If it's not a dimension, then ignore it.
  }

  % Check whether each possible boolean flag has been set.
  \clist_if_in:NnTF \l_intfig_optclist_clist { external } 
  {
    \bool_set_true:N \l_intfig_external_bool
  }{}
  \clist_if_in:NnTF \l_intfig_optclist_clist { nostatic } 
  {
    \bool_set_true:N \l_intfig_nostatic_bool
  }{}
  \clist_if_in:NnTF \l_intfig_optclist_clist { done } 
  {
    \bool_set_true:N \l_intfig_done_bool
  }{}
}

% Regex to determine whether an input is a dimension. I can't say that
% I really understand what's going on here, other than a bit of copy
% and paste and help from stack overflow.
\prg_new_protected_conditional:Nnn \intfig_if_length:n { T, F, TF }
 {
  \regex_match:nnTF
  % Note that I only allow positive values.
   { \A [+]? ((\d+(\.\d*)?)|(\.\d+)) \s* (pt|pc|in|bp|cm|mm|dd|cc|sp|ex|em) \Z} 
   { #1 } % test string
   { \prg_return_true: }
   { \prg_return_false: }
 }
 
\prg_generate_conditional_variant:Nnn \intfig_if_length:n { V } { T, F, TF }

\ExplSyntaxOff


\usepackage{tikz}

\begin{document}


\begin{intfig}{bezier,200bp}
\\ Code for an external framework goes here.
\\ This is saved to bezier.fjs to be loaded by the framework.
\\ It could be JavaScript or Java (or whatever the framework is written to expect).
\end{intfig}

Here, {\tt bezier} is the ``name'' of the figure, and {\tt 200bp} is
the figure's height. The {\tt intfig} environment writes the body of
the environment out to a file ({\tt bezier.fjs} in this case). Then it
looks for a file, {\tt bezier.tikz}. If that file exists, it is
inserted; if it doesn't exist, then a ``not available'' message
appears in a figure of the given height.

In addition to swapping the body, {\tt intfig} writes information
about the page layout to {\tt figures.aux}: the page on which the
figure appears, together with its position on the page, the margin
sizes, text width, vertical location of the figure on the page, figure
height, figure name, and certain boolean values.

The interactive viewer might produce the following
as {\tt bezier.tikz}. If this file exists, it will be loaded as the
body of the {\tt intfig} above.

\begin{verbatim}
\begin{tikzpicture}[yscale=-1]
\useasboundingbox (0bp,0bp) rectangle (343.71109bp,200bp);
\draw[line width=4bp] (63.56039999999999bp, 27.39158999999995bp) -- (23.560399999999987bp, 57.39158999999995bp) -- (96.56039999999999bp, 121.39158999999995bp) -- (199.5604bp, 79.39158999999995bp) ;
\draw[line width=4bp] (63.56039999999999bp, 27.39158999999995bp) .. controls (23.560399999999987bp, 57.39158999999995bp) and (96.56039999999999bp, 121.39158999999995bp) .. (199.5604bp, 79.39158999999995bp);
\fill (63.56039999999999bp,27.39158999999995bp) ellipse [x radius=3bp,y radius =3bp];
\fill (23.560399999999987bp,57.39158999999995bp) ellipse [x radius=3bp,y radius =3bp];
\fill (96.56039999999999bp,121.39158999999995bp) ellipse [x radius=3bp,y radius =3bp];
\fill (199.5604bp,79.39158999999995bp) ellipse [x radius=3bp,y radius =3bp];
\end{tikzpicture}
\end{verbatim}

\end{document}

Respuesta1

\labelLos comandos son lo que importa y pueden afectar el espaciado. Por ejemplo, una variante simple de su problema es

\documentclass{article}
\usepackage{zref-savepos}
\begin{document}
\rule{\textwidth}{1pt} \zsavepos{A}% space allows line break

text

\rule{\textwidth}{1pt}\zsavepos{B}

text

\end{document}

ingrese la descripción de la imagen aquí

Si pones a \parantes de \zsaveposte aseguras de que ambos casos se comporten de forma idéntica. La alternativa es asegurarse de que no queden espacios no deseados.

información relacionada