Framework für interaktive Grafiken in LaTeX-Dokumenten

Framework für interaktive Grafiken in LaTeX-Dokumenten

Ich habe einen Prototyp eines LaTeX-Pakets und ein zugehöriges Anzeigeframework geschrieben, um die Anzeige von LaTeX-Dokumenten in einer Umgebung zu ermöglichen, die die Interaktion mit Abbildungen ermöglicht. Die Abbildung könnte Punkte haben, die verschoben werden können, numerische Eingaben über die Tastatur akzeptieren, verschiedene GUI-Widgets verwenden usw. Die Absicht ist, dass LaTeX wie üblich ein PDF erstellt, das PDF aber zusammen mit einigen zusätzlichen Dateien in einen speziellen Viewer geladen werden kann, der alle statischen Abbildungen des PDF durch interaktive Abbildungen ersetzt. Sichtbar sollen die beiden Versionen, statisch und interaktiv, in jeder Hinsicht identisch sein, außer ob sie interaktiv oder statisch sind. Ich habe einen Proof of Concept, bei dem ein Webbrowser als Plattform für die Anzeige und Interaktion verwendet wird. Es gibt auch eine weniger entwickelte Version für Java, die jedoch weniger nützlich erscheint und daher verworfen wurde.

Das LaTeX-Ende des Frameworks ist unten dargestellt und funktioniert, allerdings mit einem verbleibenden Problem. Wie inFestlegen der Höhe eines Tikzbilds mit Latex3, der Flächeninhalt der Figuren ist nicht stabil. Die dort gegebene Antwort funktioniert für das angegebene MWE, aber nicht im vollständigen Kontext.

Um es noch einmal zusammenzufassen: Die intfigUmgebung nimmt einen Dateinamen (ohne die Endung .tikz) und die Höhe an, die die Figur haben muss. Die Umgebung sucht nach der angegebenen .tikz-Datei. Wenn die Datei vorhanden ist, wird sie geladen. Wenn sie nicht vorhanden ist, wird ein Feld mit der Meldung „nicht verfügbar“ angezeigt. Wenn die Datei nicht vorhanden ist und nicht geladen wird, hat die Figur eine Höhe. Wenn die Datei verfügbar ist, scheint die Figur eine Textzeile höher als erwartet zu sein, als ob \parder Figur eine zusätzliche Zeile folgt.

Ich hoffe auf konkrete Hilfe zum Problem der Abbildungsplatzierung und allgemeine Verbesserungsvorschläge, da ich bei weitem kein LaTeX3-Experte bin.

Im folgenden Code finden Sie einige zusätzliche Anmerkungen zur Funktionsweise des Systems.

\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}

Antwort1

\labelBefehle sind Dingsbums und können den Abstand beeinflussen. Eine einfache Variante Ihres Problems ist beispielsweise

\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}

Bildbeschreibung hier eingeben

Wenn Sie \parvor das ein setzen \zsavepos, stellen Sie sicher, dass sich beide Fälle identisch verhalten. Die Alternative besteht darin, sicherzustellen, dass keine unerwünschten Leerzeichen vorhanden sind.

verwandte Informationen