How to get the HEIGHT of the Run or Paragraph - c#

I found the Run or Paragraph in FlowDocument and now I need to know the HEIGHT of it.
i.e.
while (navigator.CompareTo(flowDocViewer.Document.ContentEnd) < 0)
{
TextPointerContext context = navigator.GetPointerContext(LogicalDirection.Backward);
Run run = navigator.Parent as Run;
// I need to get HEIGHT of Run in pixels somehow
Is it possible to do in fact?
Thank you!

A little function i am using. The input is a string containing a Section. You can easily render other blockelements like Paragraph.
You also can omit the second parameter of the Parse method.
The trick is not to measure the Paragraph, but the ViewBox which contains a RichTextBox. This is needed to actually render the Flowdocument. The ViewBox dynamically gets the size of the rtb. Maybe you even can do this without the ViewBox. I spent some time to figure this out and it works for me.
Note that Width of the RichTextBox is set to double.MaxValue. This means when you want to measure a single paragraph it has to be very long or everything is in one line. So this only makes sense when you know the Width of your output device. As this is a FlowDocument there is no Width, it flows ;)
I use this to paginate a FlowDocument where i know the paper size.
The returned Height is device independent units.
private double GetHeaderFooterHeight(string headerFooter)
{
var section = (Section)XamlReader.Parse(headerFooter, _pd.ParserContext);
var flowDoc = new FlowDocument();
flowDoc.Blocks.Add(section);
var richtextbox = new RichTextBox { Width = double.MaxValue, Document = flowDoc };
var viewbox = new Viewbox { Child = richtextbox };
viewbox.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
viewbox.Arrange(new Rect(viewbox.DesiredSize));
var size = new Size() { Height = viewbox.ActualHeight, Width = viewbox.ActualWidth };
return size.Height;
}

Related

How to scale text within a fixed rectangle with itext7?

I'm trying to make a pdf document with itext7 in c# which should have fixed rectangles containing varying text that should scale within the boundaries of the (invisible) rectangles.
I have tried to find if there's automatic scaling, but so far only found auto-scaling for formfields. Since the pdf will be used for plotting text, formfields are of no use.
Code below is a snippet placing a 'box' with fixed dimensions, where all the text should be shown scaled (on one line)
float fontSize = 22f;
Text lineTxt = new Text("A VERY LONG TEXT SHOULD BE SCALED").SetFont(lineFont).SetFontSize(fontSize);
iText.Kernel.Geom.Rectangle lineTxtRect = new iText.Kernel.Geom.Rectangle(100, posHeight - 200, (float)plotline.producttype_plotmaxwidthpts, (float)plotline.producttype_plotmaxheightpts);
Div lineDiv = new Div();
lineDiv.SetMaxHeight((float)plotline.producttype_plotmaxheightpts);
lineDiv.SetWidth((float)plotline.producttype_plotmaxwidthpts);
lineDiv.SetHeight((float)plotline.producttype_plotmaxheightpts);
lineDiv.SetVerticalAlignment(VerticalAlignment.MIDDLE);
lineDiv.SetBorder(new DashedBorder(1));
Paragraph linePara = new Paragraph().Add(lineTxt).
SetTextAlignment(iText.Layout.Properties.TextAlignment.CENTER).
SetBorder(new DottedBorder(1)).
SetMultipliedLeading(0.7f).
SetMaxHeight((float)plotline.producttype_plotmaxheightpts).
SetHeight((float)plotline.producttype_plotmaxheightpts);
lineDiv.Add(linePara);
new Canvas(PageCanvas, pdf, lineTxtRect).Add(lineDiv).SetBorder(new SolidBorder(1f));
Layout module of iText 7 allows you to simulate rendering of an element (by creating the renderer tree from the element and then using Layout method) and check whether it fits the given area (by checking LayoutResult object). Thus what you can do is check whether the text fits into your fixed rectangle with the given font size. Then you can just do a binary search on the font size.
Here is a sample code:
PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFileName));
Text lineTxt = new Text("A VERY LONG TEXT SHOULD BE SCALED");
iText.Kernel.Geom.Rectangle lineTxtRect =
new iText.Kernel.Geom.Rectangle(100,200,100,100);
Div lineDiv = new Div();
lineDiv.SetVerticalAlignment(VerticalAlignment.MIDDLE);
lineDiv.SetBorder(new DashedBorder(1));
Paragraph linePara =
new Paragraph()
.Add(lineTxt)
.SetTextAlignment(iText.Layout.Properties.TextAlignment.CENTER)
.SetBorder(new DottedBorder(1))
.SetMultipliedLeading(0.7f);
lineDiv.Add(linePara);
// 1 is the font size that is definitely small enough to draw all the text
float fontSizeL = 1;
// 20 is the maximum value of the font size you want to use
float fontSizeR = 20;
Canvas canvas =
new Canvas(
new PdfCanvas(pdfDocument.AddNewPage()),
pdfDocument,
lineTxtRect);
// Binary search on the font size
while (Math.Abs(fontSizeL - fontSizeR) > 1e-1) {
float curFontSize = (fontSizeL + fontSizeR) / 2;
lineDiv.SetFontSize(curFontSize);
// It is important to set parent for the current element renderer
// to a root renderer.
IRenderer renderer =
lineDiv.CreateRendererSubTree()
.SetParent(canvas.GetRenderer());
LayoutContext context =
new LayoutContext(
new LayoutArea(1, lineTxtRect));
if (renderer.Layout(context).GetStatus() == LayoutResult.FULL) {
// we can fit all the text with curFontSize
fontSizeL = curFontSize;
} else {
fontSizeR = curFontSize;
}
}
// Use the biggest font size that is still small enough to fit all the
// text.
lineDiv.SetFontSize(fontSizeL);
canvas.Add(lineDiv);
pdfDocument.Close();

Compress text to fit within a control's displayed width

In my C# WinForms application, I have a control in which I display some text to the user on screen. For time being, assume it is a TextBox.
My requirement is if the text does not fully fit within the displayed width of the control, I want to keep reducing the font size or compress the text in some other way to fit the displayed width of the control.
I understand in extreme situations, the text may not be readable at all. But that's fine.
Can I get a code example how to achieve this?
To measure the width of the font you'll have to determine it using TextRenderer. The following code illustrates how to achieve this, and to resize the font in the textbox.
var text = "Some unnecessarily long, long, long string.";
var size = default(SizeF);
// SizeF size; // Use this if you're on an older version of C# without default
do
{
using (var font = new Font(textBox1.Font.Name, textBox1.Font.SizeInPoints))
{
size = TextRenderer.MeasureText(text, font);
if (size.Width <= textBox1.Width)
textBox1.Text = text;
else
{
textBox1.Text = "Won't fit";
textBox1.Font = new Font(font.Name, font.SizeInPoints - 1f);
}
}
} while (size.Width > textBox1.Width);
You may want to adjust the by how much the font size decreases if it ends up too small for your liking.

Print multiple canvas WPF?

Today I'm working with WPF! My app is almost finished, except for the last feature. I have created a graphic element which represents a label like the ones you can see under the goods in a supermarket shelf. Print one of them is easy, using PrintDialog.PrintVisual(myVisualItem,"description");.
Now, I have to print a series of these labels, using PintVisual() in a loop, prints every element in a separate document! There's no overload of PrintVisual() which accepts an IEnumerable of visual item. I've seen online that i should paginate these elements, but I cannot find a way to do it What can I do? Thanks!
You could combine all canvas you got into one big canvas and then print this one.
Here is some sample code how to concat multiple canvas into one big one:
public Canvas CombineCanvases(params Canvas[] canvases)
{
// Force each canvas to update its desired size
foreach (var canvas in canvases) { canvas.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); }
var result = new Canvas
{
Height = canvases.Sum(x => x.DesiredSize.Height), // add all heights to get needed height
Width = canvases.Max(x => x.DesiredSize.Width) // find required width
};
var pos = 0d;
foreach (var canvas in canvases)
{
Canvas.SetLeft(canvas, 0);
Canvas.SetTop(canvas, pos); // position element at this pixel count
pos += canvas.DesiredSize.Height; // and increment it
result.Children.Add(canvas);
}
return result;
}
You need to pass all canvas's you want to print and print the result.
var c1 = new Canvas
{
Width = 100,
Height = 100,
Children = { new TextBlock { Text = "Canvas 1" } }
};
var c2 = new Canvas
{
Width = 100,
Height = 100,
Children = { new TextBlock { Text = "Canvas 2" } }
};
var canvas = CombineCanvases(c1, c2);
// print canvas here
Pagination would be if you check how many labels fit on one page and then not concat all canvas into one huge on, but just take as many as are needed to fill one page.
Then print the page and repeat this until all Labels were printed. This is called pagination.
If you believe they will all fit on one page, you could put them in a StackPanel with Orientation = Vertical. Then print the stack panel.
var panel = new StackPanel() { Orientation = Orientation.Vertical };
panel.Children.Add(canvas1);
panel.Children.Add(canvas2);
panel.UpdateLayout();
var dlg = new PrintDialog();
dlg.PrintVisual(panel, "description");

How to generate a image with text and images in C#

I have several paragraphs of text and couple of pictures between these paragraphs.
Now, I want to generate a picture using these materials, merging them vertically. But all the blocks of the text and pictures can not have bigger width than that of the generating picture, which means I have to zoom out the origin pictures, and fill each paragraph of text into a rectangle to fit the width.
Here is the tough thing:
To figure out the size of the rectangle to contain the text, I need use Graphics.MeasureString() method, which needs an instance of Graphics used to generate my picture(now, I'm using a blank template picture). But I do not know the exact size of this Graphics until I figure out all the sizes of rectangles and pictures.
Is there any method to get an instance of Graphics without source image?
Or is there any other method to do this work?
hope this could help dude .
http://chiragrdarji.wordpress.com/2008/05/09/generate-image-from-text-using-c-or-convert-text-in-to-image-using-c/
https://web.archive.org/web/20131231000000/http://tech.pro/tutorial/654/csharp-snippet-tutorial-how-to-draw-text-on-an-image
http://www.codeproject.com/Questions/388845/HOW-TO-MAKE-HIGH-QAULITY-IMAGE-WITH-TEXT-IN-Csharp
thank you
For people how are intrested in a WPF solution (as asked):
public static BitmapSource CreateImage(string text, double width, double heigth)
{
// create WPF control
var size = new Size(width, heigth);
var stackPanel = new StackPanel();
var header = new TextBlock();
header.Text = "Header";
header.FontWeight = FontWeights.Bold;
var content = new TextBlock();
content.TextWrapping = TextWrapping.Wrap;
content.Text = text;
stackPanel.Children.Add(header);
stackPanel.Children.Add(content);
// process layouting
stackPanel.Measure(size);
stackPanel.Arrange(new Rect(size));
// Render control to an image
RenderTargetBitmap rtb = new RenderTargetBitmap((int)stackPanel.ActualWidth, (int)stackPanel.ActualHeight, 96, 96, PixelFormats.Pbgra32);
rtb.Render(stackPanel);
return rtb;
}

WPF printing/xps issue

I have written the following chunk of code that prints my ListBox perfectly when being sent to a physical printer, however when trying to send it to the XPS printer driver or using the XpsDocumentWriter class (I assume they use the same code under the hood) I receive the following exception:
System.ArgumentException was unhandled by user code
Message=Width and Height must be non-negative.
Source=ReachFramework
StackTrace:
at System.Windows.Xps.Serialization.VisualSerializer.WriteTileBrush(String element, TileBrush brush, Rect bounds)
The exception obviously points to an item not having a correct width/height however I have debugged the code when sending it to the different printers (physical and XPS driver) and I haven't been able to find any differences.
Below is how I create the visual to send to the printer:
private ScrollViewer GeneratePrintableView()
{
ScrollViewer scrollView = new ScrollViewer();
Grid grid = new Grid { Background = Brushes.White, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions[0].Height = new GridLength(0, GridUnitType.Auto);
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions[1].Height = new GridLength(0, GridUnitType.Auto);
// Add the title and icon to the top
VisualBrush titleClone = new VisualBrush(this.TitleBar);
var titleRectangle = new Rectangle { Fill = titleClone, Width = this.TitleBar.ActualWidth, Height = this.TitleBar.ActualHeight };
grid.Children.Add(titleRectangle);
Grid.SetRow(titleRectangle, 0);
this.myListBox.Width = this.myListBox.ActualWidth;
this.myListBox.Height = this.myListBox.ActualHeight;
VisualBrush clone = new VisualBrush(this.myListBox) { Stretch = Stretch.None, AutoLayoutContent = true };
var rectangle = new Rectangle { Fill = clone, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };
Border border = new Border { Background = Brushes.White, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };
border.Child = rectangle;
grid.Children.Add(border);
Grid.SetRow(border, 1);
scrollView.Width = this.myListBox.ActualWidth;
scrollView.Height = this.myListBox.ActualHeight;
scrollView.Content = grid;
scrollView.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden;
return scrollView;
}
Here is the GetPage override in my DocumentPaginator implementation:
public override DocumentPage GetPage(int pageNumber)
{
Page page = new Page();
double z = 0.0;
this.grid = new Grid();
this.grid.RowDefinitions.Add(new RowDefinition());
this.grid.RowDefinitions[0].Height = new GridLength(0, GridUnitType.Auto);
this.grid.Children.Add(this.printViewer);
Grid.SetRow(this.printViewer, 0);
//Adjusting the vertical scroll offset depending on the page number
if (pageNumber + 1 == 1) //if First Page
{
this.printViewer.ScrollToVerticalOffset(0);
this.printViewer.UpdateLayout();
}
else if (pageNumber + 1 == _verticalPageCount) //if Last Page
{
if (this.printViewer.ScrollableHeight == 0) //If printing only single page and the contents fits only on one page
{
this.printViewer.ScrollToVerticalOffset(0);
this.printViewer.UpdateLayout();
}
else if (this.printViewer.ScrollableHeight <= this.printViewer.Height) //If scrollconentheight is less or equal than scrollheight
{
this.printViewer.Height = this.printViewer.ScrollableHeight;
this.printViewer.ScrollToEnd();
this.printViewer.UpdateLayout();
}
else //if the scrollcontentheight is greater than scrollheight then set the scrollviewer height to be the remainder between scrollcontentheight and scrollheight
{
this.printViewer.Height = (this.printViewer.ScrollableHeight % this.printViewer.Height) + 5;
this.printViewer.ScrollToEnd();
this.printViewer.UpdateLayout();
}
}
else //Other Pages
{
z = z + this.printViewer.Height;
this.printViewer.ScrollToVerticalOffset(z);
this.printViewer.UpdateLayout();
}
page.Content = this.grid; //put the grid into the page
page.Arrange(new Rect(this.originalMargin.Left, this.originalMargin.Top, this.ContentSize.Width, this.ContentSize.Height));
page.UpdateLayout();
return new DocumentPage(page);
}
Interestingly if I change the Fill of rectangle to a Brush instead of clone then I do not receive the exception and the outputted file is the correct size.
I have spent over a day trying to debug why this isn't working and I am hoping that someone out there has either seen a similar issue or is able to point out any mistakes I am making.
Thanks for any responses.
I had to give up finding a solution with VisualBrush. If there is a GroupBox in the Visual for the brush I could never get it to produce a XPS file. It always fails with
System.ArgumentException was unhandled by user code Message=Width and Height must be non-negative. Source=ReachFramework StackTrace: at System.Windows.Xps.Serialization.VisualSerializer.WriteTileBrush(String element, TileBrush brush, Rect bounds)
The workaround was to clone the content that should go in the VisualBrush (Is there an easy/built-in way to get an exact copy (clone) of a XAML element?) and use that directly in a Grid instead of an VisualBrush
Have you checked the value of ActualWidth and ActualHeight of myListBox when the VisualBrush is being created? I don't know from where myListBox comes, but if it is not rendered by the time you are generating your xps document you may run into problems. You can try to manually force the control to render and see if it makes any difference.
I was unable to rectify the problem however using this link Paginated printing of WPF visuals I was able to find a suitable solution to allow printing of complicated visuals within my WPF application.
It's 2016 now and it's still not fixed. The problem is using TileBrush or any descendant type (VisualBrush in your case). If you use absolute mapping, it works, it's the relative mapping that causes the problem. Calculate the final size yourself and set Viewport to this size, ViewportUnits to Absolute. Also make sure you don't use Stretch.

Categories