通过调整字体大小使文本适合给定的框

通过调整字体大小使文本适合给定的框

我想将文本(可能是几段)放入给定大小的框中。这应该通过调整所含文本的字体大小来实现。

澄清编辑:给定的尺寸是最大限度结果应占据的空间。无需完全填满框,但在任何情况下都不应超出尺寸。

就像是

\fitbox{<width>}{<height}{Some text to be squeezed into a box \par With paragraphs}

或者

\begin{fitbox}{<width>}{<height>}
Some text to be squeezed into a box

With paragraphs
\end{fitbox}

这里有许多问题在标题中提出相同的问题,但仅针对限制宽度或者高度。我想同时限制两者。另外,我希望文本只在字体大小上进行调整,而不是在一个方向上不成比例地缩放。

@TH 通过在页面上放置文本解决了另一个问题。不幸的是,我的 TeX 知识太有限,无法将其适应框。请参见此处:在受限区域中调整文本并使其居中(两者!)对于页面适配,他的解决方案对我来说看起来很棒。也许这是一个起点。


编辑:比较 Werner 和 Martin 的解决方案

我使用了 TeXLive 2011 中的 XeLaTeX 来比较 Werner 和 Martin 的以下解决方案。字体是 TrueType Times New Roman 矢量字体。esfitbox包含在 a 中framebox以帮助比较。

案例 1 是一个 2cm x 2cm 的盒子。案例 2 是一个 8cm x 2cm 的盒子中的相同文本。

Fitbox 比较

两个箱子都可以水平堆叠在一起,并且只占用它们应有的空间。然而,在案例 1 中,采用 Martin 方法的箱子太高,而 Werner 的箱子正好适合内容。


编辑 2:更多的测试表明:这两条建议都可能失败

经过更多测试后,下图所示的案例不符合 Werner 和 Martin 的建议。这意味着问题仍然存在。

当前的方法因水平盒过满而失败

正如 Werner 在他的回答的评论中指出的那样,原因是 TeX 无法对模式进行连字符处理。同时,它认为将其放入框中并超出边缘是可以接受的。

经过一番研究,我相信算法在每一行之后都必须检查\badness,如果超过 10000,则进一步减小字体大小。至少Paweł Jackowski 的 TeX 珍珠指向那个方向。

有没有人有洞察力和经验将这些作品和 Werner 和 Martin 的出色作品结合在一起,形成一个可以工作并且真正留在给定盒子里的东西?


编辑 3:另一个可能有助于解决这个问题的想法

另一种可能有帮助的方法是测量段落最长单词(或框)的宽度,假设它是. 对应的字体大小为F。用户给出的最大期望盒子宽度是最大心率

然后,字体大小的上限可以通过以下方式计算:

最大速度=F*最大心率/

当然,这是假设一切都按比例进行,考虑到单词和字符之间的间距,这可能并不完全正确。但它应该足以让文本保持fitbox水平。

由于这只能缩小字体大小(这是上限),因此之前满足的垂直条件之后仍然会满足。这意味着,如果需要,可以在 @Werner 的建议之后应用此方法作为检查和调整。

有谁有 TeX 经验来坚持这一点吗?

答案1

使用建议在受限区域中调整文本并使其居中(两者!), 和...一起马丁的回答使用environ包裹,下面提供环境

\begin{fitbox}{<width>}{<height>}
  <stuff>
\end{fitbox}

<stuff>使用二进制搜索的形式进行排版,以在给定高度内适应文本,同时在由 设置的<height>固定宽度约束下。这是为了保持字体和行距 ( ) 的比例缩放。<width>minipage\baselineskip

\documentclass{article}
\usepackage{lmodern}
\usepackage{environ}% http://ctan.org/pkg/environ
\usepackage{lipsum}% http://ctan.org/pkg/lipsum
\newdimen\fontdim
\newdimen\upperfontdim
\newdimen\lowerfontdim
\newif\ifmoreiterations
\fontdim12pt

\makeatletter
\NewEnviron{fitbox}[2]{% \begin{fitbox}{<width>}{<height>} stuff \end{fitbox}
  \def\buildbox{%
    \setbox0\vbox{\hbox{\minipage{#1}%
      \fontsize{\fontdim}{1.2\fontdim}%
      \selectfont%
      \stuff%
    \endminipage}}%
    \dimen@\ht0
    \advance\dimen@\dp0
  }
  \def\stuff{\BODY}% Store environment body
  \buildbox
  % Compute upper and lower bounds
  \ifdim\dimen@>#2
    \loop
      \fontdim.5\fontdim % Reduce font size by half
      \buildbox
    \ifdim\dimen@>#2 \repeat
    \lowerfontdim\fontdim
    \upperfontdim2\fontdim
    \fontdim1.5\fontdim
  \else
    \loop
      \fontdim2\fontdim % Double font size
      \buildbox
    \ifdim\dimen@<#2 \repeat
    \upperfontdim\fontdim
    \lowerfontdim.5\fontdim
    \fontdim.75\fontdim
  \fi
  % Now try to find the optimum size
  \loop
    %\message{Bounds: \the\lowerfontdim\space
    %         \the\fontdim\space \the\upperfontdim^^J}
    \buildbox
    \ifdim\dimen@>#2
      \moreiterationstrue
      \upperfontdim\fontdim
      \advance\fontdim\lowerfontdim
      \fontdim.5\fontdim
    \else
      \advance\dimen@-#2
      \ifdim\dimen@<10pt
        \lowerfontdim\fontdim
        \advance\fontdim\upperfontdim
        \fontdim.5\fontdim
        \dimen@\upperfontdim
        \advance\dimen@-\lowerfontdim
        \ifdim\dimen@<.2pt
          \moreiterationsfalse
        \else
          \moreiterationstrue
        \fi
      \else
        \moreiterationsfalse
      \fi
    \fi
  \ifmoreiterations \repeat
  \box0% Typeset content
}
\makeatother
\begin{document}
\lipsum[1]
\begin{fitbox}{.5\textwidth}{0.5\textheight}
  \lipsum[1-2]
\end{fitbox}
\lipsum[2]

\clearpage

\lipsum[1]
\begin{fitbox}{300pt}{300pt}
  \lipsum[1-2]
\end{fitbox}
\lipsum[2]
\end{document}​

下图中,两页排版为,每页以 开始\lipsum[1],以 结束,\lipsum[2]以提供一些参考框架。左侧页面的fitbox尺寸为 ,.5\textwidth x .5\textwidth而右侧页面的尺寸为300pt x 300pt(正方形)。

固定宽度和高度的盒子

有趣的是,我在 TeXLive 2011 下编译它时遇到了问题。虽然使用在线 LaTeX 编译器编译它没有问题ScribTeX,运行 TeXLive 2009。我不知道这背后的原因是什么……已通过将 替换为 来解决这个\protected@edef\stuff{\BODY}问题\def\stuff{\BODY}原始代码之所以使用这种形式,是因为它提供了两个宏 - 一个用于解析内容(称为\fillthepage{<stuff>}),另一个用于更新内容的调整大小版本(称为\buildbox)。我假设编码结构需要这样做。但是,由于所有内容都包含在上面的单个environment中fitbox,因此不再需要这样做。

答案2

我找到了一种解决问题的方法 - 几乎。它建立在@Werner 概述的方法之上。本质上,它增加了一个对badness框的测试,作为文本超出边缘的度量。该测试是一个递归,不幸的是,它只适用于环境中文本的最后一段。因此仍然需要进行一些改进。

\documentclass{article}
\usepackage{xltxtra}
\usepackage{fontspec}
\setmainfont[Mapping=tex-text]{Times New Roman}

\sloppypar

\usepackage{environ}% http://ctan.org/pkg/environ
\newdimen\fontdim
\newdimen\upperfontdim
\newdimen\lowerfontdim
\newif\ifmoreiterations
\fontdim12pt

\newbox\trialbox
\newbox\linebox
\global\newcount\maxbad
\newcount\linebad
\newcount\currenthbadness


\makeatletter
\NewEnviron{fitbox}[2]{% \begin{fitbox}{<width>}{<height>} stuff \end{fitbox}
    % Store environment body
    \def\stuff{%
        \BODY%
    }%
    % prepare badness box
    \def\badnessbox{%
        \global\maxbad=0\relax%
        \currenthbadness=\hbadness% save old \hbadness
        \hbadness=10000000\relax% make sure, TeX reports overfull boxes
        \message{Starting measureline recursion with width #1^^J}%
        \setbox\trialbox=\vbox{%
            \hsize#1\relax%
            \fontsize{\fontdim}{1.2\fontdim}%
            \selectfont%
            \stuff\par%
            \measurelines% start recursion
        }%
%       \noindent\usebox\trialbox\par
        \hbadness=\currenthbadness% restore old \hbadness
    }
    % prepare recursion to measure line badness
    \def\measurelines{%
        \message{Iteration of measurelines^^J}%
        \begingroup%
            \setbox\linebox=\lastbox% get the last line
            \setbox0=\hbox to \hsize{\unhcopy\linebox}% put the last line into box0 to provoke badness calculation
            \linebad=\the\badness\relax% \badness now reflects the last typeset box, i.e. box0
            \message{Badness: \the\badness\space\the\linebad\space with max \the\maxbad\space at Fontsize: \the\fontdim\space^^J}%
            \ifnum\linebad>\maxbad% store the maximum badness
                \global\maxbad=\linebad% Uncomment this line to ignore overfull hboxes!
            \fi%
            \ifvoid% end of recursion
                \linebox%
            \else%
                \unskip\unpenalty\measurelines% do the recursion
                \ifhmode%
                    \newline%
                \fi%
                \noindent\box\linebox% do the output
            \fi%
        \endgroup%
    }%
    % Prepare measurement box
    \def\buildbox{%
        \badnessbox% measure badness
        \setbox0\vbox{% measure height
            \hbox{%
                \fontsize{\fontdim}{1.2\fontdim}%
                \selectfont%
                \minipage{#1}%
                    \vbox{%                     
                        \stuff\par%
                    }%
                \endminipage%
            }%
        }%
        \message{Measured badness: \the\maxbad\space at Fontsize: \the\fontdim\space^^J}%
        \dimen@\ht0
        \advance\dimen@\dp0
        \message{Measured box height: \the\dimen@\space^^J}%
    }%
    \def\shrinkheight{%
        \loop
            \fontdim.5\fontdim % Reduce font size by half
            \buildbox
            \message{Shrinking, new box height: \the\dimen@\space at Fontsize: \the\fontdim\space^^J}%
        \ifdim\dimen@>#2 \repeat
        \lowerfontdim\fontdim
        \upperfontdim2\fontdim
        \fontdim1.5\fontdim
    }%
    \def\shrinkwidth{%
        \loop
            \fontdim.5\fontdim % Reduce font size by half
            \buildbox
        \ifnum\maxbad>10000 \repeat
        \lowerfontdim\fontdim
        \upperfontdim2\fontdim
        \fontdim1.5\fontdim
    }%
    \def\growheight{%
        \loop
            \fontdim2\fontdim % Double font size
            \buildbox
            \message{Growing, new box height: \the\dimen@\space at Fontsize: \the\fontdim\space^^J}%
        \ifdim\dimen@<#2 \repeat
        \upperfontdim\fontdim
        \lowerfontdim.5\fontdim
        \fontdim.75\fontdim
    }%
    \buildbox
    % Compute upper and lower bounds
    \ifdim\dimen@>#2
        \message{Need to shrink box height: \the\dimen@\space^^J}%
        \shrinkheight
    \else
        \message{Need to grow box height: \the\dimen@\space to target: #2^^J}%
        \growheight
    \fi
    \message{Max font: \the\upperfontdim\space^^J}%
    \message{Min font: \the\lowerfontdim\space^^J}%
    % Potentially further reduce bounds for overfull box
    \ifnum\maxbad>10000
        \shrinkwidth
    \fi 
    \message{Max font adjusted: \the\upperfontdim\space^^J}%
    \message{Min font adjusted: \the\lowerfontdim\space^^J}%
    % Now try to find the optimum height and width
    \loop
        \buildbox
        \message{Height: \the\dimen@\space^^J}%
        \ifdim\dimen@>#2
            \moreiterationstrue
            \upperfontdim\fontdim
            \advance\fontdim\lowerfontdim
            \fontdim.5\fontdim
        \else
            \ifnum\maxbad>10000
                \moreiterationstrue
                \upperfontdim\fontdim
                \advance\fontdim\lowerfontdim
                \fontdim.5\fontdim
            \else
                \advance\dimen@-#2
                \ifdim\dimen@<10pt
                    \lowerfontdim\fontdim
                    \advance\fontdim\upperfontdim
                    \fontdim.5\fontdim
                    \dimen@\upperfontdim
                    \advance\dimen@-\lowerfontdim
                    \ifdim\dimen@<.2pt
                        \moreiterationsfalse
                    \else
                        \moreiterationstrue
                    \fi
                \else
                    \moreiterationsfalse
                \fi
            \fi
        \fi
    \ifmoreiterations \repeat
    \message{Selected font: \the\fontdim\space^^J}%
    \vbox to #2{\box0\hbox{}}% Typeset content
}%
\makeatother

\usepackage{parskip}

\setlength\fboxsep{0pt}
\begin{document}

(2cm x 2cm):

\fbox{%
\begin{fitbox}{2cm}{2cm}%
xx
\end{fitbox}
}%
\fbox{\vbox to 2cm{\hbox to 2cm{}}}%
\fbox{\vbox to 2cm{\hbox to 2cm{}}}%
\fbox{\vbox to 2cm{\hbox to 2cm{}}}%

(8cm x 2cm):

\fbox{%
\begin{fitbox}{8cm}{2cm}
xxxxxxxxxx
\end{fitbox}
}%

(8cm x 1cm):

\fbox{%
\begin{fitbox}{8cm}{1cm}
This box should be 8x1 cm and actually, that's exactly the size it has.
\end{fitbox}
}
\end{document}​

这将产生以下输出:

Fitbox 插图

对于其他答案和问题中讨论的情况,文本不再超出边缘。相反,字体缩小,并且部分框保持空白。

答案3

这是我的想法:使用包捕获内容,将其放置在使用给定宽度的environ框中。然后测量请求的高度与框的总高度之间的比率,并使用和其宏以此大小再次排版内容。这取决于可缩放字体的使用。对于某些输入,它对我来说不起作用,因为似乎存在缩放限制。miniboxrelsize\relscale

\documentclass{article}

\usepackage[T1]{fontenc}
\usepackage{lmodern}

\usepackage{graphicx}
\usepackage{environ}
\usepackage{relsize}

\makeatletter
\NewEnviron{fitbox}[2]{%
    \minipage{#1}%
        \sbox0{\minipage{#1}\strut\BODY\strut\endminipage}%
        \Gscale@div\factor{#2}{\dimexpr\ht0+\dp0\relax}%
        \relscale{\factor}%
        \BODY
    \endminipage
}
\makeatother

\usepackage{lipsum}

\begin{document}

\begin{fitbox}{300pt}{300pt}
    \lipsum[1-2]
\end{fitbox}

\end{document}

答案4

在我看来,最稳定的方式是直接声明调整后的字体大小/基线高度对。由于两者必须保持成比例,因此您可以将两者按相同的因子缩小,该因子大约是测量高度和目标高度的商的平方根。(下面的完整示例平滑了这个因子以获得更高的精度。)

幸运的是,该graphicx包提供了\scalebox命令,因此可以将计算出的因子直接输入到该命令以获得正确的结果。

\documentclass{article}
\usepackage{lipsum}
\renewcommand{\rmdefault}{ppl}
\usepackage[T1]{fontenc}
\usepackage{microtype}
\usepackage{fp,graphicx}
  \setlength\unitlength{1cm}

\makeatletter

\def\accur@cy{0.999}

\newcommand{\fitbox}[3][\textwidth]{%
  \@tempdima#2
  \edef\@wd{\strip@pt\dimexpr#1\relax}
  \def\r@tio{1}
  \@temptokena={\scalebox{\r@tio}{\parbox{\@wd pt}{{#3}}}}
  \setbox0=\vbox{\the\@temptokena}
  \@tempdimb=\dimexpr\ht0+\dp0\relax
  \FPdiv\r@tio{\strip@pt\@tempdima}{\strip@pt\@tempdimb}
  \FProot\r@tio{\r@tio}{2}
  \FPdiv\@wd{\@wd}{\r@tio}
  \fitbox@adjust
  \setbox0=\vbox{\the\@temptokena}
  \box0
}
\newcommand{\fitbox@adjust}{%
  \@tempcnta\z@
  \def\rel@rror@rec{0}
  \fitbox@adjust@
}
\newcommand{\fitbox@adjust@}{%
  \advance\@tempcnta by 1
  \ifnum\@tempcnta<10
   \FPiflt\rel@rror@rec\accur@cy
    \setbox0=\vbox{\the\@temptokena}
    \@tempdimb=\dimexpr\ht0+\dp0\relax
    \FPdiv\rel@rror@rec{\strip@pt\@tempdimb}{\strip@pt\@tempdima}
    \FPdiv\r@tio{\r@tio}{\rel@rror@rec}
    \FPmul\@wd{\@wd}{\rel@rror@rec}
    \fitbox@adjust@
   \fi
  \fi
}

\newcommand{\fitboxdemo}[1]{%
  \framebox(5,#1)[r]{%
    \parbox{5cm}{%
      \fitbox[5cm]{#1cm}{%
        Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
        sed diam voluptua.
        At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
        no sea takimata sanctus est Lorem ipsum dolor sit amet.
      }%
    }%
  }%
  \vskip1em
}

\makeatother

\begin{document}
%\fitbox[8cm]{1cm}{XXXXXXXXXX}
\fitboxdemo{1}
\fitboxdemo{2}
\fitboxdemo{3}
\fitboxdemo{4}
\fitboxdemo{5}
\end{document}

输出

相关内容