I am trying to insert images "into" a cell in excel using Epplus.
using the following code
private static void SetImage(ExcelWorksheet sheet, ExcelRange cell)
{
using (WebClient client = new WebClient())
using (Stream stream = client.OpenRead("https://..."))
using (Bitmap bitmap = new Bitmap(stream))
{
var picture = sheet.Drawings.AddPicture(Guid.NewGuid().ToString(), bitmap);
picture.From.Column = cell.Start.Column - 1;
picture.From.Row = cell.Start.Row - 1;
picture.To.Column = cell.Start.Column;
picture.To.Row = cell.Start.Row;
}
}
-
var cell = sheet.Cells[2, 2];
SetImage(sheet, cell);
cell = sheet.Cells[3, 2];
SetImage(sheet, cell);
However it always seems to have an overlap to the right.
If I adjust the cell widths and heights the overlap changes but never disappears
So I abandoned the
picture.To.Column = cell.Start.Column;
picture.To.Row = cell.Start.Row;
since I just could not get it to work and decided to calculated my own dimensions using:
picture.SetSize(width, height);
The trick is to understand how Excel actually calculates widths and heights.
Height of a cell: Its measured in points, but we want pixels. There are 72 points in an inch. One can convert points to pixel using the following formula points* (1/72.0) * DPI. DPI is dots per inch and can be found using the following method:
using (Graphics graphics = Graphics.FromHwnd(IntPtr.Zero))
{
float dpiY = graphics.DpiY;
}
So to calculate the height of a cell in pixels I used
private static int GetHeightInPixels(ExcelRange cell)
{
using (Graphics graphics = Graphics.FromHwnd(IntPtr.Zero))
{
float dpiY = graphics.DpiY;
return (int)(cell.Worksheet.Row(cell.Start.Row).Height * (1 / 72.0) * dpiY);
}
}
Width of a cell: This is a bit trickier. Basically the width of a cell in excel is equal to the number of characters (formatted using the default font) that a cell can contain horizontally.
For example
This colum is of length 12 and can contain 12 numbers in the Calibri(11) font.
That is also my excel default since my body default is calibri(11)
Here is an article explaining it in more depth.
The next question is how on earth does one translate that to pixels.
Firstly we need to discover what the length of a character is in the default font. One could use TextRenderer.MeasureText in the System.Windows.Forms namespace. However I am using .Net Core and needed another way. Another way is to use the the System.Drawing.Common core lib which is now in preview.
public static float MeasureString(string s, Font font)
{
using (var g = Graphics.FromHwnd(IntPtr.Zero))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
return g.MeasureString(s, font, int.MaxValue, StringFormat.GenericTypographic).Width;
}
}
I then used that method to calculate the width in pixels as follows
private static int GetWidthInPixels(ExcelRange cell)
{
double columnWidth = cell.Worksheet.Column(cell.Start.Column).Width;
Font font = new Font(cell.Style.Font.Name, cell.Style.Font.Size, FontStyle.Regular);
double pxBaseline = Math.Round(MeasureString("1234567890", font) / 10);
return (int)(columnWidth * pxBaseline);
}
Edit: Please note that overlap still happens when the zoom factor is set to more than 100% under display settings
I'm trying to get the height of a string. I've come across MeasureString but it's inaccurate for what I need it for - too small for what MS Word is showing (okay, this is probably another topic so if you can point me to the right resources...)
So, I found out about TextMetrics. I want to try it but I couldn't understand how to use it. Here is my sample code:
static public double MeasureGroup2()
{
TextMetrics txt = new TextMetrics();
double height;
height = txt.Height;
return height;
}
Where/how can I set the font and font size, and the string for it to return the values I need?
P.S. My end goal is to measure the height of a string the same way MS Word "measures" or renders it. I'm open to other solutions other than these two.
Additional info:
Code for MeasureString:
static public double MeasureGroup1(string TextFont, int FSize)
{
string Str = "Mgkfps";
Bitmap Bmp = new Bitmap(99,99);
Graphics DrawGraphics = Graphics.FromImage(Bmp);
GraphicsUnit Unit = GraphicsUnit.Point;
DrawGraphics.PageUnit = Unit;
DrawGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
StringFormat format = StringFormat.GenericTypographic;
// Set up string.
System.Drawing.Font stringFont = new System.Drawing.Font(TextFont, FSize, FontStyle.Regular, Unit);
// Measure string.
SizeF stringSize = new SizeF();
stringSize = DrawGraphics.MeasureString(Str, stringFont, 99, format);
DrawGraphics.Dispose();
format.Dispose();
stringFont.Dispose();
return stringSize.Height;
//return TextSize;
}
Notes:
Using Arial 11, and Arial 12, the output of this code needs to be multiplied by 1.0294 to be the same as how MS Word is showing. Physical measurements were made using Word's ruler, Photoshop, and a lot of ratio-and-proportion. (Maybe this is the problem?)
static public double MeasureGroup2(string TextFont, int FSize)
{
string Str = "Mgkfps";
double height;
Bitmap Bmp = new Bitmap(99, 99);
Graphics DrawGraphics = Graphics.FromImage(Bmp);
GraphicsUnit Unit = GraphicsUnit.Point;
DrawGraphics.PageUnit = Unit;
DrawGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
StringFormat format = StringFormat.GenericTypographic;
// Set up string.
System.Drawing.Font stringFont = new System.Drawing.Font(TextFont, FSize, FontStyle.Regular, Unit);
Rectangle Rect = new Rectangle(0, 0, 99, 99);
Size sz = new Size(99,99);
sz = TextRenderer.MeasureText(DrawGraphics, Str, stringFont, sz ,TextFormatFlags.NoPadding);
height = sz.Height;
return height;
}
First of all, thank you everyone for helping me out!
When using TextMetrics to get the dimensions of a text, refer to this post here:
https://www.cyotek.com/blog/retrieving-font-and-text-metrics-using-csharp
He has a sample project down at the bottom of the page.
As for where I was hoping to use it, which is getting the height of a text in MS Word:
I assumed that the text height and line spacing, when set to single, is the same. This is not the case. There is a small amount of space, the leading, that is not measured by MeasureString. That is why I was hoping to use the GetTextMetrics function since it returns a struct that includes the leading. Anyway, it appears to be used for something else.
Fortunately, and apparently, there is a method native to the .NET framework that returns the value I need which is FontFamily.GetLineSpacing.
LineHeight = stringFont.Size * FF.GetLineSpacing(FontStyle.Regular)/FF.GetEmHeight(FontStyle.Regular);
Reference:
https://answers.microsoft.com/en-us/office/forum/office_2013_release-word/what-reasons-make-different-line-spacing-between/ca1267f9-0cfe-4f26-8048-bbd7a6a46398?auth=1
https://learn.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-obtain-font-metrics
I am making an application that creates a watermark on a PDF that the user selects and I can't seem to get the watermark to appear on the selected PDF but I also get no errors. Any help would be appreciated.
I am using PDFsharp version 1.50.4000
public void WaterMarkPDF(string sourceFileName)
{
try
{
string watermark = "watermark";
int emSize = 100;
string file ="test.pdf";
File.Copy(sourceFileName, file, true);
File.SetAttributes(file, File.GetAttributes(file) & ~FileAttributes.ReadOnly);
// Take in pdf from the form
PdfDocument document = PdfReader.Open(file);
// change the version cause sometimes newer versions break it
if (document.Version < 14)
document.Version = 14;
XFont font = new XFont("Times New Roman", emSize, XFontStyle.BoldItalic);
for (int idx = 0; idx < document.Pages.Count; idx++)
{
var page = document.Pages[idx];
// Get an XGraphics object for drawing beneath the existing content.
var gfx = XGraphics.FromPdfPage(page, XGraphicsPdfPageOptions.Prepend);
// Get the size (in points) of the text.
var size = gfx.MeasureString(watermark, font);
// Define a rotation transformation at the center of the page.
gfx.TranslateTransform(page.Width / 2, page.Height / 2);
gfx.RotateTransform(-Math.Atan(page.Height / page.Width) * 180 / Math.PI);
gfx.TranslateTransform(-page.Width / 2, -page.Height / 2);
// Create a string format.
var format = new XStringFormat();
format.Alignment = XStringAlignment.Near;
format.LineAlignment = XLineAlignment.Near;
// Create a dimmed red brush.
XBrush brush = new XSolidBrush(XColor.FromArgb(128, 255, 0, 0));
// Draw the string.
gfx.DrawString(watermark, font, brush,
new XPoint((page.Width - size.Width) / 2, (page.Height - size.Height) / 2),
format);
// Save the document...
document.Save(file);
// ...and start a viewer.
Process.Start(file);
}
}
catch (Exception e)
{
throw e;
}
}
Maybe try XGraphicsPdfPageOptions.Appendinstead of XGraphicsPdfPageOptions.Prepend.
Call document.Save and Process.Startoutside the for loop.
Update: Explanation: With XGraphicsPdfPageOptions.Prepend the watermark is drawn below the original PDF page. Most PDF files consist of black text on transparent background and the watermark will be visible there (you can check this by activating the Transparency Grid in Adobe Reader). For PDF pages with a solid background (e.g. images, tables with a background colour, ...) the watermark will not be visible.
The PDFsharp source code includes a Watermark sample:
http://pdfsharp.net/wiki/Watermark-sample.ashx
There are two variants that add a semi-transparent text on top of the existing PDF page. These variants also work for PDF pages without transparency.
The system I am working on stamps PDF's with certain information. It does this by creating a lime green text box in the top right corner of the document. It then draws a certain string on top of the green space. This works for thousands of PDFs but for one the text is invisible even though the box is drawn. I can still select the text and copy it to something else, but it is invisible in the PDF.
Unfortunately, I cannot share the PDF but it is a PDF 1.4. What would cause this?
The code for stamping:
private static XGraphics drawString(XGraphics xgr, PdfPage page, string printString, int pageNumber = 0)
{
XFont font = new XFont("Verdana", 10, XFontStyle.BoldItalic);
var textSize = xgr.MeasureString(printString, font);
var width = textSize.Width;
var height = textSize.Height;
double xMin = 0;
double yMin = 0;
if (page.Rotate == 90)
{
xMin = page.Height - textSize.Width;
var state = xgr.Save();
xgr.DrawRectangle(XBrushes.LimeGreen, xMin, yMin, width, height);
xgr.Restore(state);
xgr.DrawString(printString, font, XBrushes.Black, new XRect(0, 0, page.Height, page.Width), topRight());
}
else
{
xMin = page.Width - textSize.Width;
var state = xgr.Save();
xgr.DrawRectangle(XBrushes.LimeGreen, xMin, yMin, width, height);
xgr.Restore(state);
xgr.DrawString(printString, font, XBrushes.Black, new XRect(0, 0, page.Width, page.Height), topRight());
}
return xgr;
}
private static XStringFormat topRight()
{
XStringFormat format = new XStringFormat();
format.Alignment = XStringAlignment.Far;
format.LineAlignment = XLineAlignment.Near;
return format;
}
I have tried using Dipose() on xgr and reinitialising it before each of its draw actions. I have tried saving and restoring the state of xgr between draw actions as seen in the code. I have tried various fonts and font sizes with no luck either.
Let me know what metadata about the PDF is relevant and I will share that.
Using PdfSharp 1.5 GDI, I have been having this issue as well. Some pdfs would be ok, others would have 1 or 2 pages that were ok, and then others would have no pages that were ok. Text could be selected, but text could not be seen. Changing the renderMode fixes the issue.
In PdfGraphicsState.cs, there is a condition to check if _realizedRenderingMode != renderMode. However, _realizedRenderingMode and renderMode is 0 by default, so it never enters codeblock, nor does it look like there is a method for changing the renderMode unless you change the font to italic, bold, strikeout, or underline:
int _realizedRenderingMode; // Reference: TABLE 5.2 Text state operators / Page 398
public void RealizeFont(XFont font, XBrush brush, int renderingMode)
{
const string format = Config.SignificantFigures3;
// So far rendering mode 0 (fill text) and 2 (fill, then stroke text) only.
RealizeBrush(brush, _renderer._colorMode, renderingMode, font.Size); // _renderer.page.document.Options.ColorMode);
// Realize rendering mode.
if (_realizedRenderingMode != renderingMode)
{
_renderer.AppendFormatInt("{0} Tr\n", renderingMode);
_realizedRenderingMode = renderingMode;
}
Removing the condition would suffice in fixing the issue, but the renderingMode seems to only be needed 1 time for the BT (Begin Text) in XGraphicsPdfRenderer.cs.
internal void BeginTextMode()
{
if (_streamMode != StreamMode.Text)
{
_streamMode = StreamMode.Text;
_content.Append("BT\n");
// Text matrix is empty after BT
_gfxState.RealizedTextPosition = new XPoint();
_gfxState.ItalicSimulationOn = false;
}
}
So, I ended up modifying XGraphics to include XGraphicsPdfRendererOptions and passing the variable to the various methods so it can be changed regardless of location:
private XGraphicsPdfRendererOptions _renderOptions { get; set; }
public XGraphicsPdfRendererOptions RenderOptions
{
get
{
if (_renderOptions == null)
{
_renderOptions = new XGraphicsPdfRendererOptions();
}
return _renderOptions;
}
set
{
_renderOptions = value;
}
}
Keep in mind that the renderMode is originally based on whether the font is italic, bold, strikeout, or underline, which I don't really see how those relate to renderMode, in XGraphicsPdfRenderer.cs:
//bool bold = (font.Style & XFontStyle.Bold) != 0;
//bool italic = (font.Style & XFontStyle.Italic) != 0;
bool italicSimulation = (font.GlyphTypeface.StyleSimulations & XStyleSimulations.ItalicSimulation) != 0;
bool boldSimulation = (font.GlyphTypeface.StyleSimulations & XStyleSimulations.BoldSimulation) != 0;
bool strikeout = (font.Style & XFontStyle.Strikeout) != 0;
bool underline = (font.Style & XFontStyle.Underline) != 0;
Realize(font, brush, boldSimulation ? 2 : 0);
Class and enum:
public class XGraphicsPdfRendererOptions
{
public XGraphicsPdfRenderMode RenderMode { get; set; }
public bool IncludeRenderModeForPage { get; set; }
public bool IncludeRenderModeForObject { get; set; }
}
public enum XGraphicsPdfRenderMode
{
Text_Render_Mode_Fill = 0,
Text_Render_Mode_Stroke = 1,
Text_Render_Mode_Fill_Stroke = 2,
Text_Render_Mode_Invisible = 3
}
Usage:
gfx.RenderOptions = new XGraphicsPdfRendererOptions() { RenderMode = XGraphicsPdfRenderMode.Text_Render_Mode_Fill, IncludeRenderModeForPage = true };
https://github.com/zaryk/PDFsharp
I've happened across this issue a couple of times now on separate projects. I found that the simplest fix is to dispose of the XGraphics object and then simply re-instantiate it using your current PdfPage instance.
PdfSharp.Pdf.PdfPage Page = Document.AddPage();
PdfSharp.Drawing.XGraphics gfx = PdfSharp.Drawing.XGraphics.FromPdfPage(Page);
//Build your pdf here until transparency issue occurs
//Issue has occured so re-instantiate gfx object
gfx.Dispose();
gfx = PdfSharp.Drawing.XGraphics.FromPdfPage(Page);
//Continue as normal
Below is the code I use to hide something in document and edit the document and again save. Let me know if it helps you
private PdfDocument FormatPdfDocument(PdfDocument document, List<string> packingTypes, string carrierName)
{
XFont PackingTypeFont = new XFont("Calibri", 10, XFontStyle.Bold);
var i = 0;
foreach (PdfPage page in document.Pages)
{
using (var gfx = XGraphics.FromPdfPage(page))
{
var packingType = packingTypes.ElementAtOrDefault(i++) ?? "PackingType Not Found";
if (carrierName == "xxxx")
{
var packingTypeBounds = new XRect(64, 62, 200, 12);
gfx.DrawRectangle(XBrushes.White, packingTypeBounds);
gfx.DrawString(packingType, PackingTypeFont, XBrushes.Black, packingTypeBounds, XStringFormats.TopLeft);
var logoBounds = new XRect(0, 0, 130, 50);
gfx.DrawRectangle(XBrushes.White, logoBounds);
}
else if (carrierName == "yyyy")
{
var packingTypeBounds = new XRect(200, 0, 200, 12);
gfx.DrawString(packingType, PackingTypeFont, XBrushes.Black, packingTypeBounds, XStringFormats.TopLeft);
}
else if (carrierName == "zzzz")
{
var packingTypeBounds = new XRect(410, 20, 200, 12);
var state = gfx.Save();
gfx.RotateAtTransform(90, new XPoint { X = 410, Y = 20 });
gfx.DrawString(packingType, PackingTypeFont, XBrushes.Black, packingTypeBounds, XStringFormats.TopLeft);
gfx.Restore(state);
}
}
}
return document;
}
This works fine for me till date without any issues
Some possible causes to answer your question "What would cause this?":
Are you using the latest version PDFsharp 1.50 beta 3b?
IIRC there is a bug in 1.32 that can lead to unexpected behavior because some properties are not reset.
Since you see the rectangle this might be the cause.
Since you see the rectangle, this probably does not apply:
There are two ways of modifying existing pages: "append" and "prepend". Your code snippet does not show how you do it.
With "prepend" your lime rectangle could be hidden under a white filled rectangle. Watch the PDF in Adobe Reader with active Transparency Grid and check that you see the grid where the rectangle should be.
Since you see the rectangle, this probably does not apply:
Maybe your text goes to the wrong position. Check the MediaBox and CropBox settings of the PDF page you are modifying. Normally pages start at (0,0), but you cannot be sure.
Locate your text in the PDF file and compare the text position with MediaBox and CropBox.
It could be an unknown bug in PDFsharp. If you do not find a PDF that allows to replicate the issue which you can share then it will be very difficult to fix the bug. But maybe one of the options above leads to success.
So I managed to find a way to make it work. I ran the stamping process twice on the document and it worked as expected. Fortunately, stamping twice does not affect regular documents which actually work normally.
Still an issue in 1.50 for some PDF files...
As a workaround, I create a PNG file using System.Drawing.DrawString.
Bitmap bmp = new Bitmap((int)p.Width.Point, 30);
Graphics gra = Graphics.FromImage(bmp);
gra.DrawString("Hello world", new Font("Verdana", 20), Brushes.Red, new PointF(0, 0));
bmp.Save("test.png", System.Drawing.Imaging.ImageFormat.Png);
Then I use XGraphics.DrawImage from this PNG file.
XGraphics xg = XGraphics.FromPdfPage(p, XGraphicsPdfPageOptions.Append);
XImage xi = XImage.FromFile("test.png");
// Add it at the bottom of the page
xg.DrawImage(xi, 0, p.Height-32, p.Width, 30);
This always ends up on top.
I am using GhostScript.Net to rasterize PDF to page images before sending the page images to the printer. I am doing this so that I can always rasterize to 300dpi. This allows me to print the PDF in a reasonable amount of time regardless of the size of any image in the PDF (mainly scanned PDFs).
However, it strikes me that in some cases there will not be a need to rasterize as high as 300dpi. It may be possible to rasterize to 200dpi or even 100dpi depending on the content of the page.
Has anyone attempted to determine the maximum DPI for the content of a PDF page? Perhaps using iTextSharp?
My current code is this:
var dpiList = new List<int> {50, 100, 150, 200, 250, 300, 350, 400, 450, 500};
string inputPdfPath = #"C:\10page.pdf";
string outputPath = #"C:\Print\";
var lastInstalledVersion =
GhostscriptVersionInfo.GetLastInstalledVersion(
GhostscriptLicense.GPL | GhostscriptLicense.AFPL,
GhostscriptLicense.GPL);
var rasterizer = new GhostscriptRasterizer();
rasterizer.Open(inputPdfPath, lastInstalledVersion, true);
var imageFiles = new List<string>();
for (int pageNumber = 1; pageNumber <= 10; pageNumber++)
{
foreach (var dpi in dpiList)
{
string pageFilePath = System.IO.Path.Combine(outputPath,
string.Format("{0}-{1}-{2}.png", pageNumber, Guid.NewGuid().ToString("N").Substring(0, 8), dpi));
System.Drawing.Image img = rasterizer.GetPage(dpi, dpi, pageNumber);
img.Save(pageFilePath, ImageFormat.Png);
imageFiles.Add(pageFilePath);
Console.WriteLine(pageFilePath);
}
}
var imageCount = 0;
var pd = new PrintDocument();
pd.PrintPage += delegate(object o, PrintPageEventArgs args)
{
var i = System.Drawing.Image.FromFile(imageFiles[imageCount]);
var pageBounds = args.PageBounds;
var margin = 48;
var imageBounds = new System.Drawing.Rectangle
{
Height = pageBounds.Height - margin,
Width = pageBounds.Width - margin,
Location = new System.Drawing.Point(margin / 2, margin / 2)
};
args.Graphics.DrawImage(i, imageBounds);
imageCount++;
};
foreach (var imagefile in imageFiles)
{
pd.Print();
}
PDF pages don't have a resolution. Images within them can be considered to have a resolution, which is given by the width of the image on the page, divided by the number of image samples in the x direction, and the height of the image on the page divided by the number of image samples in the y direction.
So this leaves calculating the width and height of the image on the page. This is given by the image matrix, modified by the Current Transformation Matrix. So in order to work out the width and height on the page, you need to interpret the content stream up to the point where the image is rendered, tracking the graphics state CTM.
For general PDF files, the only way to know this is to use a PDF interpreter. In the strictly limited case where the whole page content is a single image you can gamble that there is no scaling taking place and simply divide the media width by the image width, and the media height by the image height to give the x and y resolutions.
However this definitely won't work in the general case.