【问题标题】:Need a way to scale a font to fit a rectangle需要一种方法来缩放字体以适应矩形
【发布时间】:2010-10-26 22:46:41
【问题描述】:

我刚刚编写了一些代码来缩放字体以适应矩形(的长度)。它从 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;
}

【问题讨论】:

    标签: java graphics fonts awt


    【解决方案1】:

    半伪代码:

    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 );
    

    【讨论】:

      【解决方案2】:
      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;
      }
      

      【讨论】:

        【解决方案3】:

        将所有宽度变量更改为 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);
        }
        

        【讨论】:

          【解决方案4】:

          您可以使用插值搜索:

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

          【讨论】:

          • 72f - 0.1f &lt;= 0.1ffalse,所以这个循环会迭代吗?
          【解决方案5】:

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

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

          【讨论】:

            【解决方案6】:

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-04-20
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多