LaTeX 문서의 대화형 그래픽을 위한 프레임워크

LaTeX 문서의 대화형 그래픽을 위한 프레임워크

나는 LaTeX 문서를 Figure와 상호 작용할 수 있는 환경에서 볼 수 있도록 프로토타입 LaTeX 패키지와 관련 보기 프레임워크를 작성했습니다. 그림에는 드래그할 수 있는 점이 있고, 키보드에서 숫자 입력을 허용하고, 다양한 GUI 위젯을 사용하는 등의 작업이 있을 수 있습니다. LaTeX의 의도는 일반적으로 PDF를 생성하는 것이지만 일부 추가 항목과 함께 PDF를 허용하는 것입니다. PDF의 모든 정적 그림을 대화형 그림으로 바꾸는 특수 뷰어에 로드됩니다. 시각적으로 정적 및 대화형의 두 버전은 대화형 또는 정적을 제외한 모든 면에서 동일합니다. 나는 보기와 상호작용을 위한 플랫폼으로 웹 브라우저를 사용하는 개념 증명을 가지고 있습니다. Java용으로 덜 개발된 버전도 있지만 유용성이 떨어지는 것 같아서 폐기되었습니다.

프레임워크의 LaTeX 끝은 아래에 표시되어 있으며 작동하지만 한 가지 문제가 남아 있습니다. 에서 논의한 바와 같이latex3를 사용하여 tikzpicture의 높이 설정하기, 그림의 면적이 안정적이지 않습니다. 제공된 답변은 주어진 MWE에 적용되지만 전체 맥락에서는 적용되지 않습니다.

요약하자면, intfig환경은 파일 이름(.tikz 접미사 제외)과 그림에 있어야 하는 높이를 사용합니다. 환경은 주어진 .tikz 파일을 찾습니다. 파일이 존재하면 로드됩니다. 존재하지 않는 경우 "사용할 수 없음" 메시지와 함께 상자가 표시됩니다. 파일이 존재하지 않고 로드되지 않은 경우 Figure의 높이는 하나입니다. 파일을 사용할 수 있는 경우 \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명령은 그대로이며 간격에 영향을 줄 수 있습니다. 예를 들어 문제의 간단한 변형은 다음과 같습니다.

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

여기에 이미지 설명을 입력하세요

\par앞에 를 넣으면 \zsavepos두 경우 모두 동일하게 작동합니다. 대안은 원하지 않는 공간이 없는지 확인하는 것입니다.

관련 정보