需要一种方法来缩放字体以适合矩形

发布于 2024-07-19 08:37:11 字数 744 浏览 6 评论 0原文

我刚刚编写了一些代码来缩放字体以适合矩形(的长度)。 它从 18 宽度开始并向下迭代,直到适合为止。

这看起来效率非常低,但我找不到一种非循环的方法来做到这一点。 这条线用于可缩放的游戏网格中的标签,因此我看不到解决方案(包裹、切断和延伸超过矩形都是不可接受的)。

它实际上非常快,我正在对数百个矩形执行此操作,它的速度足以减慢触摸速度。

如果没有人想出更好的办法,我会从表中加载起始猜测(这样它比 18 更接近)并使用它——除了滞后之外,它效果很好。

public Font scaleFont(String text, Rectangle rect, Graphics g, Font pFont) {
    float nextTry=18.0f;
    Font font=pFont;

    while(x > 4) {                             
            font=g.getFont().deriveFont(nextTry);
            FontMetrics fm=g.getFontMetrics(font);
            int width=fm.stringWidth(text);
            if(width <= rect.width)
                return font;
            nextTry*=.9;            
    }
    return font;
}

I just wrote some code to scale a font to fit within (the length of) a rectangle. It starts at 18 width and iterates down until it fits.

This seems horribly inefficient, but I can't find a non-looping way to do it.
This line is for labels in a game grid that scales, so I can't see a work-around solution (wrapping, cutting off and extending past the rectangle are all unacceptable).

It's actually pretty quick, I'm doing this for hundreds of rectangles and it's fast enough to just slow it down a touch.

If nobody comes up with anything better, I'll just load the starting guess from a table (so that it's much closer than 18) and use this--except for the lag it works great.

public Font scaleFont(String text, Rectangle rect, Graphics g, Font pFont) {
    float nextTry=18.0f;
    Font font=pFont;

    while(x > 4) {                             
            font=g.getFont().deriveFont(nextTry);
            FontMetrics fm=g.getFontMetrics(font);
            int width=fm.stringWidth(text);
            if(width <= rect.width)
                return font;
            nextTry*=.9;            
    }
    return font;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

季末如歌 2024-07-26 08:37:11

半伪代码:

public Font scaleFont(
    String text, Rectangle rect, Graphics g, Font font) {
    float fontSize = 20.0f;

    font = g.getFont().deriveFont(fontSize);
    int width = g.getFontMetrics(font).stringWidth(text);
    fontSize = (rect.width / width ) * fontSize;
    return g.getFont().deriveFont(fontSize);
}

迭代的推导:

/**
 * Adjusts the given {@link Font}/{@link String} size such that it fits
 * within the bounds of the given {@link Rectangle}.
 *
 * @param label    Contains the text and font to scale.
 * @param dst      The bounds for fitting the string.
 * @param graphics The context for rendering the string.
 * @return A new {@link Font} instance that is guaranteed to write the given
 * string within the bounds of the given {@link Rectangle}.
 */
public Font scaleFont(
    final JLabel label, final Rectangle dst, final Graphics graphics ) {
  assert label != null;
  assert dst != null;
  assert graphics != null;

  final var font = label.getFont();
  final var text = label.getText();

  final var frc = ((Graphics2D) graphics).getFontRenderContext();

  final var dstWidthPx = dst.getWidth();
  final var dstHeightPx = dst.getHeight();

  var minSizePt = 1f;
  var maxSizePt = 1000f;
  var scaledFont = font;
  float scaledPt = scaledFont.getSize();

  while( maxSizePt - minSizePt > 1f ) {
    scaledFont = scaledFont.deriveFont( scaledPt );

    final var layout = new TextLayout( text, scaledFont, frc );
    final var fontWidthPx = layout.getVisibleAdvance();

    final var metrics = scaledFont.getLineMetrics( text, frc );
    final var fontHeightPx = metrics.getHeight();

    if( (fontWidthPx > dstWidthPx) || (fontHeightPx > dstHeightPx) ) {
      maxSizePt = scaledPt;
    }
    else {
      minSizePt = scaledPt;
    }

    scaledPt = (minSizePt + maxSizePt) / 2;
  }

  return scaledFont.deriveFont( (float) Math.floor( scaledPt ) );
}

假设您想要向具有矩形边界 r 的组件添加标签,以便标签完全填充组件的区域。 人们可以这样写:

final Font DEFAULT_FONT = new Font( "DejaVu Sans", BOLD, 12 );
final Color COLOUR_LABEL = new Color( 33, 33, 33 );

// TODO: Return a valid container component instance.
final var r = getComponent().getBounds();
final var graphics = getComponent().getGraphics();

final int width = (int) r.getWidth();
final int height = (int) r.getHeight();

final var label = new JLabel( text );
label.setFont( DEFAULT_FONT );
label.setSize( width, height );
label.setForeground( COLOUR_LABEL );

final var scaledFont = scaleFont( label, r, graphics );
label.setFont( scaledFont );

Semi-pseudo code:

public Font scaleFont(
    String text, Rectangle rect, Graphics g, Font font) {
    float fontSize = 20.0f;

    font = g.getFont().deriveFont(fontSize);
    int width = g.getFontMetrics(font).stringWidth(text);
    fontSize = (rect.width / width ) * fontSize;
    return g.getFont().deriveFont(fontSize);
}

A derivation that iterates:

/**
 * Adjusts the given {@link Font}/{@link String} size such that it fits
 * within the bounds of the given {@link Rectangle}.
 *
 * @param label    Contains the text and font to scale.
 * @param dst      The bounds for fitting the string.
 * @param graphics The context for rendering the string.
 * @return A new {@link Font} instance that is guaranteed to write the given
 * string within the bounds of the given {@link Rectangle}.
 */
public Font scaleFont(
    final JLabel label, final Rectangle dst, final Graphics graphics ) {
  assert label != null;
  assert dst != null;
  assert graphics != null;

  final var font = label.getFont();
  final var text = label.getText();

  final var frc = ((Graphics2D) graphics).getFontRenderContext();

  final var dstWidthPx = dst.getWidth();
  final var dstHeightPx = dst.getHeight();

  var minSizePt = 1f;
  var maxSizePt = 1000f;
  var scaledFont = font;
  float scaledPt = scaledFont.getSize();

  while( maxSizePt - minSizePt > 1f ) {
    scaledFont = scaledFont.deriveFont( scaledPt );

    final var layout = new TextLayout( text, scaledFont, frc );
    final var fontWidthPx = layout.getVisibleAdvance();

    final var metrics = scaledFont.getLineMetrics( text, frc );
    final var fontHeightPx = metrics.getHeight();

    if( (fontWidthPx > dstWidthPx) || (fontHeightPx > dstHeightPx) ) {
      maxSizePt = scaledPt;
    }
    else {
      minSizePt = scaledPt;
    }

    scaledPt = (minSizePt + maxSizePt) / 2;
  }

  return scaledFont.deriveFont( (float) Math.floor( scaledPt ) );
}

Imagine you want to add a label to a component that has rectangular bounds r such that the label completely fills the component's area. One could write:

final Font DEFAULT_FONT = new Font( "DejaVu Sans", BOLD, 12 );
final Color COLOUR_LABEL = new Color( 33, 33, 33 );

// TODO: Return a valid container component instance.
final var r = getComponent().getBounds();
final var graphics = getComponent().getGraphics();

final int width = (int) r.getWidth();
final int height = (int) r.getHeight();

final var label = new JLabel( text );
label.setFont( DEFAULT_FONT );
label.setSize( width, height );
label.setForeground( COLOUR_LABEL );

final var scaledFont = scaleFont( label, r, graphics );
label.setFont( scaledFont );
生活了然无味 2024-07-26 08:37:11

将所有宽度变量更改为 float 而不是 int 以获得更好的结果。

public static Font scaleFontToFit(String text, int width, Graphics g, Font pFont)
{
    float fontSize = pFont.getSize();
    float fWidth = g.getFontMetrics(pFont).stringWidth(text);
    if(fWidth <= width)
        return pFont;
    fontSize = ((float)width / fWidth) * fontSize;
    return pFont.deriveFont(fontSize);
}

Change all width variables to float instead of int for better result.

public static Font scaleFontToFit(String text, int width, Graphics g, Font pFont)
{
    float fontSize = pFont.getSize();
    float fWidth = g.getFontMetrics(pFont).stringWidth(text);
    if(fWidth <= width)
        return pFont;
    fontSize = ((float)width / fWidth) * fontSize;
    return pFont.deriveFont(fontSize);
}
七颜 2024-07-26 08:37:11

您可以使用插值搜索:

public static Font scaleFont(String text, Rectangle rect, Graphics g, Font pFont) {
    float min=0.1f;
    float max=72f;
    float size=18.0f;
    Font font=pFont;

    while(max - min <= 0.1) {
        font = g.getFont().deriveFont(size);
        FontMetrics fm = g.getFontMetrics(font);
        int width = fm.stringWidth(text);
        if (width == rect.width) {
            return font;
        } else {
            if (width < rect.width) {
                min = size;
            } else {
                max = size;
            }
            size = Math.min(max, Math.max(min, size * (float)rect.width / (float)width));
        }
    }
    return font;
}

You could use interpolation search:

public static Font scaleFont(String text, Rectangle rect, Graphics g, Font pFont) {
    float min=0.1f;
    float max=72f;
    float size=18.0f;
    Font font=pFont;

    while(max - min <= 0.1) {
        font = g.getFont().deriveFont(size);
        FontMetrics fm = g.getFontMetrics(font);
        int width = fm.stringWidth(text);
        if (width == rect.width) {
            return font;
        } else {
            if (width < rect.width) {
                min = size;
            } else {
                max = size;
            }
            size = Math.min(max, Math.max(min, size * (float)rect.width / (float)width));
        }
    }
    return font;
}
森林迷了鹿 2024-07-26 08:37:11
private Font scaleFont ( String text, Rectangle rect, Graphics gc )
{
    final float fMinimumFont = 0.1f;
    float fMaximumFont = 1000f;

    /* Use Point2d.Float to hold ( font, width of font in pixels ) pairs. */
    Point2D.Float lowerPoint = new Point2D.Float ( fMinimumFont, getWidthInPixelsOfString ( text, fMinimumFont, gc ) );
    Point2D.Float upperPoint = new Point2D.Float ( fMaximumFont, getWidthInPixelsOfString ( text, fMaximumFont, gc ) );
    Point2D.Float midPoint = new Point2D.Float ();

    for ( int i = 0; i < 50; i++ )
    {
        float middleFont = ( lowerPoint.x + upperPoint.x ) / 2;

        midPoint.setLocation ( middleFont, getWidthInPixelsOfString ( text, middleFont, gc ) );

        if ( midPoint.y >= rect.getWidth () * .95 && midPoint.y <= rect.getWidth () )
            break;
        else if ( midPoint.y < rect.getWidth () )
            lowerPoint.setLocation ( midPoint );
        else if ( midPoint.y > rect.getWidth () )
            upperPoint.setLocation ( midPoint );
    }

    fMaximumFont = midPoint.x;

    Font font = gc.getFont ().deriveFont ( fMaximumFont );

    /* Now use Point2d.Float to hold ( font, height of font in pixels ) pairs. */
    lowerPoint.setLocation ( fMinimumFont, getHeightInPixelsOfString ( text, fMinimumFont, gc ) );
    upperPoint.setLocation ( fMaximumFont, getHeightInPixelsOfString ( text, fMaximumFont, gc ) );

    if ( upperPoint.y < rect.getHeight () )
        return font;

    for ( int i = 0; i < 50; i++ )
    {
        float middleFont = ( lowerPoint.x + upperPoint.x ) / 2;

        midPoint.setLocation ( middleFont, getHeightInPixelsOfString ( text, middleFont, gc ) );

        if ( midPoint.y >= rect.getHeight () * .95 && midPoint.y <= rect.getHeight () )
            break;
        else if ( midPoint.y < rect.getHeight () )
            lowerPoint.setLocation ( midPoint );
        else if ( midPoint.y > rect.getHeight () )
            upperPoint.setLocation ( midPoint );
    }

    fMaximumFont = midPoint.x;

    font = gc.getFont ().deriveFont ( fMaximumFont );

    return font;
}


private float getWidthInPixelsOfString ( String str, float fontSize, Graphics gc )
{
    Font font = gc.getFont ().deriveFont ( fontSize );

    return getWidthInPixelsOfString ( str, font, gc );
}

private float getWidthInPixelsOfString ( String str, Font font, Graphics gc )
{
    FontMetrics fm = gc.getFontMetrics ( font );
    int nWidthInPixelsOfCurrentFont = fm.stringWidth ( str );

    return (float) nWidthInPixelsOfCurrentFont;
}


private float getHeightInPixelsOfString ( String string, float fontSize, Graphics gc )
{
    Font font = gc.getFont ().deriveFont ( fontSize );

    return getHeightInPixelsOfString ( string, font, gc );
}

private float getHeightInPixelsOfString ( String string, Font font, Graphics gc )
{
    FontMetrics metrics = gc.getFontMetrics ( font );
    int nHeightInPixelsOfCurrentFont = (int) metrics.getStringBounds ( string, gc ).getHeight () - metrics.getDescent () - metrics.getLeading ();

    return (float) nHeightInPixelsOfCurrentFont * .75f;
}
private Font scaleFont ( String text, Rectangle rect, Graphics gc )
{
    final float fMinimumFont = 0.1f;
    float fMaximumFont = 1000f;

    /* Use Point2d.Float to hold ( font, width of font in pixels ) pairs. */
    Point2D.Float lowerPoint = new Point2D.Float ( fMinimumFont, getWidthInPixelsOfString ( text, fMinimumFont, gc ) );
    Point2D.Float upperPoint = new Point2D.Float ( fMaximumFont, getWidthInPixelsOfString ( text, fMaximumFont, gc ) );
    Point2D.Float midPoint = new Point2D.Float ();

    for ( int i = 0; i < 50; i++ )
    {
        float middleFont = ( lowerPoint.x + upperPoint.x ) / 2;

        midPoint.setLocation ( middleFont, getWidthInPixelsOfString ( text, middleFont, gc ) );

        if ( midPoint.y >= rect.getWidth () * .95 && midPoint.y <= rect.getWidth () )
            break;
        else if ( midPoint.y < rect.getWidth () )
            lowerPoint.setLocation ( midPoint );
        else if ( midPoint.y > rect.getWidth () )
            upperPoint.setLocation ( midPoint );
    }

    fMaximumFont = midPoint.x;

    Font font = gc.getFont ().deriveFont ( fMaximumFont );

    /* Now use Point2d.Float to hold ( font, height of font in pixels ) pairs. */
    lowerPoint.setLocation ( fMinimumFont, getHeightInPixelsOfString ( text, fMinimumFont, gc ) );
    upperPoint.setLocation ( fMaximumFont, getHeightInPixelsOfString ( text, fMaximumFont, gc ) );

    if ( upperPoint.y < rect.getHeight () )
        return font;

    for ( int i = 0; i < 50; i++ )
    {
        float middleFont = ( lowerPoint.x + upperPoint.x ) / 2;

        midPoint.setLocation ( middleFont, getHeightInPixelsOfString ( text, middleFont, gc ) );

        if ( midPoint.y >= rect.getHeight () * .95 && midPoint.y <= rect.getHeight () )
            break;
        else if ( midPoint.y < rect.getHeight () )
            lowerPoint.setLocation ( midPoint );
        else if ( midPoint.y > rect.getHeight () )
            upperPoint.setLocation ( midPoint );
    }

    fMaximumFont = midPoint.x;

    font = gc.getFont ().deriveFont ( fMaximumFont );

    return font;
}


private float getWidthInPixelsOfString ( String str, float fontSize, Graphics gc )
{
    Font font = gc.getFont ().deriveFont ( fontSize );

    return getWidthInPixelsOfString ( str, font, gc );
}

private float getWidthInPixelsOfString ( String str, Font font, Graphics gc )
{
    FontMetrics fm = gc.getFontMetrics ( font );
    int nWidthInPixelsOfCurrentFont = fm.stringWidth ( str );

    return (float) nWidthInPixelsOfCurrentFont;
}


private float getHeightInPixelsOfString ( String string, float fontSize, Graphics gc )
{
    Font font = gc.getFont ().deriveFont ( fontSize );

    return getHeightInPixelsOfString ( string, font, gc );
}

private float getHeightInPixelsOfString ( String string, Font font, Graphics gc )
{
    FontMetrics metrics = gc.getFontMetrics ( font );
    int nHeightInPixelsOfCurrentFont = (int) metrics.getStringBounds ( string, gc ).getHeight () - metrics.getDescent () - metrics.getLeading ();

    return (float) nHeightInPixelsOfCurrentFont * .75f;
}
很快妥协 2024-07-26 08:37:11

您可以使用二分搜索模式(高/低)和一定粒度(1、0.5 或 0.25 点)来提高效率。

例如,猜18,太高了吗? 移动到9,太低了吗? 13.5,太低了? 15.75,太高了? 14!

You can improve the efficiency using a binary search pattern - high/low with some granularity - either 1, 0.5 or 0.25 points.

For example, guess at 18, too high? Move to 9, too Low? 13.5, too low? 15.75, too high? 14!

倥絔 2024-07-26 08:37:11

一种不同的、明显的方法是在位图上预先绘制文本,然后缩小位图以适合矩形; 但是,由于手工制作的字体设计和提示等,找到正确的字体大小会产生最好看的(尽管可能不是最快的)结果。

A different, obvious way would be to have the text pre-drawn on a bitmap, and then shrink the bitmap to fit the rectangle; but, because of hand-crafted font design and hinting etc., finding the right font size produces the best-looking (although perhaps not the quickest) result.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文