2017-04-18 19 views
0

我有一些区域(矩形)的文字图像。文本可以是任何长度,一个字或长字符串(必须包装)。我需要:(!带包装)WPF的第三方允许在矩形中绘制包裹文本?

  1. 计算字体大小(用字符串,字体和矩形)在这个矩形
  2. 绘制文本

主要的要求:得到最大的字体大小的,在文字应填充所有矩形。

我做了什么。 我在NuGet中找到了不错的第三方:ImageProcessor。但是,ImageFactory.Watermark只获取起点,而不是矩形。

OK,我实现了自己的解决方案:

  1. 尝试绘制矩形中的文本具有非常大的字体大小(使用MeasureText,不是真正的渲染)。
  2. 例如,我的矩形有height = 100,但MeasureString返回200.很好,将fontSize从50更改为25.
  3. 随着新的fontSize每个字符变得更小,不仅高度和宽度太!这就是为什么我更换newFontSize = oldFontSize *(是measuredHeight/requiredHeight)newFontSize = oldFontSize * Math.Sqrt(是measuredHeight/requiredHeight)
  4. 看起来更好。但我仍然有问题。

问题1:我使用WPF中GDI +的Graphics.MeasureString。这不是线程安全的,我必须使用锁。

问题2:MeasureString返回错误的高度,就好像文本有很大的空白。例如,我有3个矩形接近对方:

  • RECTANGLE1
  • RECTANGLE2
  • RECTANGLE3

渲染后,我看到他们之间非常大的空间。

我会很高兴得到我需要的第三方!如果不是,修复我的代码也会很棒。谢谢!

代码:

private static void RenderText(string text, RectangleF rectangle, 
     string fontFamily, int maximumFontSize, Color textColor, Graphics graphics) 
    { 
     var font = new Font(fontFamily, maximumFontSize, FontStyle.Regular); 
     var stringFormat = new StringFormat 
     { 
      // LineAlignment = StringAlignment.Center, 
      FormatFlags = StringFormatFlags.NoClip | StringFormatFlags.FitBlackBox, 
      Trimming = StringTrimming.None 
     }; 

     var previewSize = graphics.MeasureString(text, font, 
      new SizeF(rectangle.Width, rectangle.Height), stringFormat); 

     if (previewSize.Height > rectangle.Height) 
     { 
      var scale = Math.Sqrt(rectangle.Height/previewSize.Height); 
      font = new Font(fontFamily, (float) (maximumFontSize * scale), FontStyle.Regular); 
     } 

     graphics.DrawString(text, font, new SolidBrush(textColor), rectangle, stringFormat); 
    } 

附加说明:

我试图让这个是在同一时间两个文本换行和字体缩放。以this sketch为例。

+1

呜..为什么到GDI和MeasureText和手动呈现?你能否简短解释一下,为什么你不能在边框中使用标准的基本TextBlock(使用给定的字体)(使用颜色作为矩形)并将其渲染为图像(然后将该图像用作水印)?如果你有使用自定义字体的问题 - > ie:http://stackoverflow.com/questions/3765647/using-a-custom-font-in-wpf – quetzalcoatl

+0

因为这是渲染器,这个应用程序获取一些基本的图像与2个矩形(哪里应该是文本),2个字符串和新的图像大小。我必须调整图像大小,在图像上绘制文本,然后将此图像保存为jpg。用户在用户界面中看不到包含文字的图像。 – TimeCoder

+0

此外!即使所有这些都发生在UI中:我如何使用TextBlock?文字可以有任意长度,我必须更改FontSize。 WPF控件可以打包文本,但TextBlock如何用文本(var长度)填充控件的固定高度? – TimeCoder

回答

1

“因为(..)用户没有看到文本在此图像中UI”

这其实并不重要。窗口/屏幕只是一个表面。您可以使用WPF组件打印到打印机或渲染到位图。你不需要在UI上看到它们。

this answer,看看如何在关屏组件设置为给定大小,然后将其渲染为位图。

“即使这一切发生在用户界面:(..)如何TextBlock的可以用文字(VAR长度)控制的固定高度填写”

如果我读了你的要求正确地说,你不是在做一个包装到身高的包装,而是在缩放这种适合的字体。

在这种情况下,就像Bijington在评论中说的那样,只需使用ViewBox组件即可。请参阅下面的代码 - 我设置ViewBox根据可用区域向上或向下缩放。与窗口一起玩,看看“文字”是如何“缩放”的。

实际上,它不是文本本身 - 所有文本框都具有相同的样式。这是正在被缩放的这个文本所在的领域。把它想象为适用于ViewBox内容的智能“缩放”。

<Window x:Class="stack43479959.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:local="clr-namespace:stack43479959" 
     mc:Ignorable="d" 
     Title="MainWindow" Height="350" Width="525"> 
    <UniformGrid Rows="3" Columns="3"> 
     <Grid> 
      <Border HorizontalAlignment="Center" VerticalAlignment="Center" 
        Padding="5" 
        BorderThickness="1" BorderBrush="Red"> 
       <Viewbox StretchDirection="Both" Stretch="Uniform"> 
        <TextBlock TextWrapping="WrapWithOverflow"> 
Lorem ipsum dolor sit amet, consectetur adipiscing elit,<LineBreak/> 
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.<LineBreak/> 
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.<LineBreak/> 
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.<LineBreak/> 
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 
        </TextBlock> 
       </Viewbox> 
      </Border> 
     </Grid> 

     <Grid> 
      <Border HorizontalAlignment="Center" VerticalAlignment="Center" 
        Padding="5" 
        BorderThickness="1" BorderBrush="Red"> 
       <Viewbox StretchDirection="Both" Stretch="Uniform"> 
        <TextBlock TextWrapping="WrapWithOverflow"> 
Lorem ipsum dolor sit amet,<LineBreak/> 
sed do eiusmod tempor 
        </TextBlock> 
       </Viewbox> 
      </Border> 
     </Grid> 

     <Grid> 
      <Border HorizontalAlignment="Center" VerticalAlignment="Center" 
        Padding="5" 
        BorderThickness="1" BorderBrush="Red"> 
       <Viewbox StretchDirection="Both" Stretch="Uniform"> 
        <TextBlock TextWrapping="WrapWithOverflow"> 
Lorem ipsum<LineBreak/> 
dolor sit amet 
        </TextBlock> 
       </Viewbox> 
      </Border> 
     </Grid> 

     <Grid> 
      <Border HorizontalAlignment="Center" VerticalAlignment="Center" 
        Padding="5" 
        BorderThickness="1" BorderBrush="Red"> 
       <Viewbox StretchDirection="Both" Stretch="Uniform"> 
        <TextBlock TextWrapping="WrapWithOverflow"> 
Lorem ipsum<LineBreak/> 
dolor sit amet 
        </TextBlock> 
       </Viewbox> 
      </Border> 
     </Grid> 

     <Grid> 
      <Border HorizontalAlignment="Center" VerticalAlignment="Center" 
        Padding="5" 
        BorderThickness="1" BorderBrush="Red"> 
       <Viewbox StretchDirection="Both" Stretch="Uniform"> 
        <TextBlock TextWrapping="WrapWithOverflow"> 
L<LineBreak/> 
O 
        </TextBlock> 
       </Viewbox> 
      </Border> 
     </Grid> 

     <Grid> 
      <Border HorizontalAlignment="Center" VerticalAlignment="Center" 
        Padding="5" 
        BorderThickness="1" BorderBrush="Red"> 
       <Viewbox StretchDirection="Both" Stretch="Uniform"> 
        <TextBlock TextWrapping="WrapWithOverflow"> 
L 
        </TextBlock> 
       </Viewbox> 
      </Border> 
     </Grid> 
    </UniformGrid> 
</Window> 
+0

谢谢你的努力!主要问题是:在我的情况下,文本矩形同时具有两种责任:文本换行和更改理想文本拟合的字体大小。请根据您的代码查看示例:https://gyazo.com/8bdf63cf2f1a8b9a4bdaa3690b9560ff – TimeCoder

+0

(1)IIRC,它不关心高度 - 它总是使用宽度,(2)它不会通知你是否有'溢出'或不。 – quetzalcoatl

+0

@TimeCoder:然而,你仍然可以混合使用我提出的和你现在拥有的东西:运行一个循环,它会尝试各种字体大小,并尝试渲染并检查它是否溢出。您可以使用“屏幕外控制”来进行包装和渲染。将离屏文本块设置为所需的宽度和字体大小,给它无限的垂直空间(根本不要设置),然后渲染它。然后,检查TextBlock.ActualHeight以查看写入文本需要多少空间。如果超过限制,请更改字体大小,重试 - > 100%WPF,不要手动测量文本,不要手动换行。 – quetzalcoatl

0

感谢大家的帮助。我完成了这个任务,作品非常酷!我不能分享所有的代码(商业项目),但是有一些关键时刻(我浪费了很多时间)。

使用WPF渲染 - 好主意。只需创建网格+图像+文本块(不在UI中,在内存中)。文字块的例子:

 var titleTextBlock = new TextBlock 
     { 
      FontSize = layout.TitleFontSize, 
      FontFamily = new FontFamily(layout.TitleFontFamily), 
      LineStackingStrategy = LineStackingStrategy.BlockLineHeight, 

      LineHeight = layout.TitleFontSize, 
      FontWeight = FontWeights.Normal, 
      Foreground = new SolidColorBrush(Color.FromRgb(18, 75, 14)), 
      VerticalAlignment = VerticalAlignment.Top, 
      HorizontalAlignment = HorizontalAlignment.Left, 
       Width = layout.TitleRectangle.Width, 
      Margin = new Thickness(layout.TitleRectangle.Left, 
       layout.TitleRectangle.Top, 0, 0), 
      TextWrapping = TextWrapping.WrapWithOverflow 
     }; 
     Grid.SetRow(titleTextBlock, 0); 
     Grid.SetColumn(titleTextBlock, 0); 
     grid.Children.Add(titleTextBlock); 

增量配件:

private static void FitToRectangle(TextBlock textBlock, RectangleF rectangle, bool fitToHeight) 
    { 
     while (true) 
     { 
      if (fitToHeight) 
      { 
       textBlock.Measure(new Size(double.PositiveInfinity, rectangle.Height)); 
      } 
      else 
      { 
       textBlock.Measure(new Size(rectangle.Width, double.PositiveInfinity)); 
      } 

      var size = textBlock.DesiredSize; 
      var rect = new Rect(size); 
      textBlock.Arrange(rect); 

      if ((fitToHeight && textBlock.ActualHeight > rectangle.Height) || 
       (!fitToHeight && textBlock.ActualWidth > rectangle.Width)) 
      { 
       textBlock.FontSize -= 5; 
       textBlock.LineHeight -= 5; 
      } 
      else 
      { 
       if (fitToHeight) 
       { 
        FitToRectangle(textBlock, rectangle, false); 
       } 
       break; 
      } 
     } 
  1. 适合宽度也很重要!如果文本有非常VeriongWordWithoutSeparators,“包装”只是在中间切割并包装! “WrapWithOverflow” - 长字的宽度将会大于限制。这就是为什么我们需要在高度拟合后进行最终的宽度配合。
  2. 我需要大量的渲染并行线程。您只能在STA线程内创建网格。如果您将UI线程的SyncContext传递到Task.StartNew - 这将工作,但UI线程将在呈现期间冻结。这有什么帮助:

    public static Task StartSTATask(Action<object> func, object parameter) 
    { 
        var tcs = new TaskCompletionSource<object>(); 
        var thread = new Thread(param => 
        { 
         try 
         { 
          func(param); 
          tcs.SetResult(null); 
         } 
         catch (Exception e) 
         { 
          tcs.SetException(e); 
         } 
        }); 
    
        thread.SetApartmentState(ApartmentState.STA); 
        thread.Start(parameter); 
        return tcs.Task; 
    } 
    
  3. 不要忘记措施和排列网格,在文本框拟合后。

  4. 最后,渲染 “关闭屏幕” 的代码控制到文件:

     var drawingVisual = new DrawingVisual(); 
    
         using (var ctx = drawingVisual.RenderOpen()) 
         { 
          var visualBrush = new VisualBrush 
          { 
           AutoLayoutContent = true, 
           Visual = grid 
          }; 
    
          ctx.DrawRectangle(visualBrush, null, 
           new Rect(0, 0, layout.Size.Width, layout.Size.Height)); 
         } 
    
         var bmp = new RenderTargetBitmap((int) layout.Size.Width, (int) layout.Size.Height, 
          96, 96, PixelFormats.Pbgra32); 
         bmp.Render(drawingVisual); 
    
         var encoder = new JpegBitmapEncoder(); 
    
         encoder.Frames.Add(BitmapFrame.Create(bmp)); 
         using (Stream stm = File.Create(filename)) 
         { 
          encoder.Save(stm); 
          lock (this.sync) 
          { 
           this.rendered++; 
           UpdatePercentage(); 
          } 
         }