Фреймворк для интерактивной графики в документах LaTeX

Фреймворк для интерактивной графики в документах LaTeX

Я написал прототип пакета LaTeX и связанную с ним структуру просмотра, чтобы документы LaTeX можно было просматривать в среде, которая допускает взаимодействие с фигурами. Фигура может иметь точки, которые можно перетаскивать, принимать числовые вводы с клавиатуры, использовать различные виджеты графического интерфейса и т. д. Цель состоит в том, чтобы LaTeX создавал PDF, как это обычно происходит, но позволял бы загружать PDF вместе с некоторыми дополнительными файлами в специальный просмотрщик, который заменяет все статические фигуры PDF на интерактивные фигуры. Видимо, две версии, статическая и интерактивная, должны быть идентичны во всех отношениях, кроме интерактивности или статичности. У меня есть доказательство концепции, использующее веб-браузер в качестве платформы для просмотра и взаимодействия. Также есть менее разработанная версия для Java, но она кажется менее полезной, поэтому от нее отказались.

Ниже показана часть фреймворка LaTeX, и она работает, но осталась одна проблема. Как обсуждалось вУстановка высоты tikzpicture с помощью latex3, площадь фигур нестабильна. Приведенный там ответ работает для данного MWE, но не в полном контексте.

Подводя итог, intfigсреда принимает имя файла (без суффикса .tikz) и высоту, которую должна иметь фигура. Среда ищет указанный файл .tikz. Если файл существует, он загружается; если его нет, то отображается поле с сообщением «недоступно». Если файл не существует и не загружен, то фигура имеет одну высоту; если файл доступен, то фигура кажется на одну строку текста выше, чем ожидалось, как будто за \parфигурой следует дополнительная.

Я надеюсь на конкретную помощь по проблеме размещения рисунков и общие предложения по улучшению, поскольку я далек от эксперта по LaTeX3.

В приведенном ниже коде есть несколько дополнительных замечаний о работе системы.

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

решение1

\labelкоманды whatsits и могут влиять на интервалы. Например, простой вариант вашей проблемы

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

введите описание изображения здесь

Если вы поставите a \parперед the, \zsaveposвы гарантируете, что оба случая будут вести себя одинаково. Альтернатива — убедиться, что нет нежелательных пробелов.

Связанный контент