LaTeX 文件中的互動式圖形框架

LaTeX 文件中的互動式圖形框架

我編寫了一個原型 LaTeX 套件和相關的檢視框架,以允許在允許與圖形互動的環境中查看 LaTeX 文件。該圖形可能有可以拖曳的點、接受來自鍵盤的數位輸入、使用各種 GUI 小部件等。到特殊檢視器中,用互動式圖形取代PDF 的所有靜態圖形。顯然,靜態和互動式這兩個版本除了互動式或靜態之外,在所有方面都是相同的。我使用網頁瀏覽器作為檢視和互動的平台進行了概念驗證。 Java 還有一個開發程度較低的版本,但似乎不太有用,所以被放棄了。

框架的 LaTeX 端如下所示,它可以工作,但還有一個問題。如中所討論的使用 Latex3 設定 tikzpicture 的高度,圖形的面積不穩定。那裡提供的答案適用於給定的 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指令是什麼,可以影響間距。例如你的問題的一個簡單變體是

\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在 之前放置 a,則\zsavepos可以確保兩種情況的行為相同。另一種方法是確保沒有不需要的空間。

相關內容