/* i-net software provides programming examples for illustration only, without warranty either expressed or implied, including, but not limited to, the implied warranties of merchantability and/or fitness for a particular purpose. This programming example assumes that you are familiar with the programming language being demonstrated and the tools used to create and debug procedures. i-net software support professionals can help explain the functionality of a particular procedure, but they will not modify these examples to provide added functionality or construct procedures to meet your specific needs. © i-net software 1998-2013 */ using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Drawing.Text; using System.IO; using System.Runtime.InteropServices; using System.Linq; using Inet.Viewer.Data; namespace Inet.Viewer.Data { /// /// Enum for the glyph orientation /// public enum GlyphOrientation { /// /// The glyphs always point upwards, regardless of text or block progression. /// GLYPH_UP = 0, /// /// The glyphs always point to the right, regardless of text or block progression. /// GLYPH_RIGHT = 1, /// /// The glyphs always point to the left, regardless of text or block progression. /// GLYPH_LEFT = 2, /// /// The glyphs always point downwards, regardless of text or block progression. /// GLYPH_DOWN = 3 } /// /// enum for the different line styles /// public enum LineStyle { /// /// Line has no style, not visible. Not valid for line element. NoLine = 0, /// /// Line has single line style. Single = 1, /// /// Line has double line style. Not valid for line element. Double = 2, /// /// Line has dashed line style. Dashed = 3, /// /// Line has dotted line style. Dotted = 4 } /// /// This class implements the IPagePainter interface and does all the painting. This class wraps the painting methods of the Graphics /// internal class Graphics2DPainter { private const int MaxHighQualityImagePixelCount = 1000000; /// /// For a resolution of 92 dpi /// public const int TwipsToPixel = 15; private readonly Dictionary allGraphics = new Dictionary(); private readonly bool isPrinting; private Graphics graphics; private int graphicsID; private PageInfo pageInfo; private System.Drawing.Drawing2D.Matrix originalTransform; private Region originalClip; /// /// If transparent colors will be supported. /// If set to false, the "transparent" Color will be set to the white background /// private bool transparency = true; private bool fontAutoScaling = true; // when this painter is reused the helper objects won't be loaded again, as they are already available private bool onFirstPaint = true; // make sure reset() is done private IList fonts = new List(8); private IList adorns = new List(8); private IList links = new List(8); private float zoom = 1f; private Brush currFillBrush; private Pen currPen; private Adornment currAdornment; private int currFontID = 0; private int pageNr = 0; private Image graphicsImage; private StringFormat format = new StringFormat(StringFormat.GenericTypographic); public Matrix internalTransform = new Matrix(); internal Graphics2DPainter(bool isPrinting) { this.TextBlocks = new List(); this.isPrinting = isPrinting; format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.NoWrap | StringFormatFlags.FitBlackBox; format.SetMeasurableCharacterRanges(new CharacterRange[] { new CharacterRange(0, 1) }); } /// /// Sets the current adornment. /// public virtual int Adornment { set { if (value >= adorns.Count) { value = 0; } currAdornment = (Adornment)adorns[value]; } } /// /// Sets the current pen. /// public virtual Pen Pen { get { return currPen; } set { value.Color = GetOpaquePaint(value.Color); currPen = value; } } /// /// Sets the current brush. /// public virtual Brush Brush { get { return currFillBrush; } set { currFillBrush = GetOpaquePaint(value); } } /// /// Sets the current font. /// public virtual int Font { set { if (value >= fonts.Count) { // Exception for very exoctic case: if page n of m has a field with html-text. Afterwards it will be tried to add the pixel based font // to the amp. But at this time it is to late. value = 0; } currFontID = value; } } /// /// Gets or sets the image needed to create the different graphics objects /// public Image GraphicsImage { get { return graphicsImage; } set { graphicsImage = value; } } /// /// Sets the zoom factor for this painter. /// /// new zoom factor public virtual float ZoomFactor { get { return zoom; } set { if (zoom != value) { Scale(value / zoom); zoom = value; } } } /// /// /// public virtual void AddFont(Font embeddedFont, int style, int sizeTwips) { if (!onFirstPaint) { return; } // Embedding Font font = new Font(embeddedFont.FontFamily, sizeTwips, GetFontStyle(style, embeddedFont.FontFamily), GraphicsUnit.Pixel); fonts.Add(font); } /// /// /// public virtual void AddFont(string name, int style, int sizeTwips) { if (!onFirstPaint) { return; } // No Embedding try { FontFamily fontFam = null; // handle generic fonts if ("serif".Equals(name.ToLower())) { fontFam = FontFamily.GenericSerif; } else if ("sansserif".Equals(name.ToLower())) { fontFam = FontFamily.GenericSansSerif; } else if ("monospaced".Equals(name.ToLower())) { fontFam = FontFamily.GenericMonospace; } else { // no generic font ( in java logical font) // set to get the FontFamily to check the fontStyle fontFam = new FontFamily(name); } FontStyle fontStyle = GetFontStyle(style, fontFam); Font font = new Font(fontFam.Name, sizeTwips, fontStyle, GraphicsUnit.Pixel); fonts.Add(font); } catch (ArgumentException) { // try font instead of font family Font font = new Font(name, sizeTwips, GraphicsUnit.Pixel); FontStyle fontStyle = GetFontStyle(style, font); font = new Font(name, sizeTwips, fontStyle, GraphicsUnit.Pixel); fonts.Add(font); } } /// /// This method gets the FontFamily from the font and invokes the GetFontStyle(int style, FontFamily family) method /// /// the font style in ints that represent the enum /// The font to get the FontStyle from /// private static FontStyle GetFontStyle(int style, Font font) { return GetFontStyle(style, font.FontFamily); } /// /// Converts the inet-Clear Reports fontstyle to the C# FontStyle /// /// The font style in ints that represent the enum /// The Fontfaimly /// The c# FontStyle private static FontStyle GetFontStyle(int style, FontFamily family) { FontStyle fStyle = FontStyle.Regular; if ((style & 1) > 0) { fStyle += (int)FontStyle.Bold; } if ((style & 2) > 0) { fStyle += (int)FontStyle.Italic; } if ((style & 4) > 0) { fStyle += (int)FontStyle.Underline; } if ((style & 8) > 0) { fStyle += (int)FontStyle.Strikeout; } if (!family.IsStyleAvailable(fStyle)) { // Some Fonts (for example Aharoni/ Segui) don't support Regular style. This would normally throw an exception, // so we have to check the available style first before setting it. if (family.IsStyleAvailable(FontStyle.Regular)) { fStyle = FontStyle.Regular; } else if (family.IsStyleAvailable(FontStyle.Bold)) { fStyle = FontStyle.Bold; } else if (family.IsStyleAvailable(FontStyle.Italic)) { fStyle = FontStyle.Italic; } } return fStyle; } /// /// /// public virtual void AddAdornment(Adornment adornment) { if (onFirstPaint) { adorns.Add(adornment); } } /// /// /// public virtual void WriteReportInfo(ReportInfo info) { fontAutoScaling = info.IsFontScaling; } /// /// /// Also /// public virtual void WritePageInfo(PageInfo info) { this.pageNr = info.PageNr; this.pageInfo = info; if (!isPrinting) { CreateImage(info); } } /// /// Creates the Image with the new image size /// /// private void CreateImage(PageInfo info) { if (info != null) { int width = (int)(info.Width / TwipsToPixel * zoom); int height = (int)(info.Height / TwipsToPixel * zoom); this.GraphicsImage = new Bitmap(width, height,PixelFormat.Format24bppRgb); this.Graphics = Graphics.FromImage(this.GraphicsImage); this.Graphics.Clear(Color.White); } } /// /// Sets the clipping. /// /// the clipping outline public virtual void SetClip(GraphicsPath path) { graphics.SetClip(path); } /// /// Sets the clipping to a rectangle. /// /// x coordinate /// y coordinate /// the width /// the height public virtual void SetClip(int x, int y, int width, int height) { graphics.SetClip(new Rectangle(x, y, width, height)); } /// /// Resets the clipping. /// public virtual void ResetClip() { graphics.Clip = originalClip; } /// /// Draws a string. /// /// the string to draw /// x coordinate /// y coordinate /// rotation degree /// alignment /// glyph orientation /// the width /// the maximum width public virtual void DrawString(string str, int x, int y, int rotDeg, int align, int glyphOrientation, int width, int maxWidth) { graphics.TextRenderingHint = TextRenderingHint.AntiAlias; Font font = (Font)fonts[currFontID]; if ((int)graphicsID == 0) { SizeF size = graphics.MeasureString(str, font); // Don't draw if font is smaller than one pixel if (size.Height * zoom < 10) { return; } } Point[] p = { new Point(x, y) }; internalTransform.TransformPoints(p); int docX = p[0].X; int docY = p[0].Y; // Textrotation System.Drawing.Drawing2D.Matrix restore = graphics.Transform; try { Font scaledFont = font; if (fontAutoScaling && maxWidth > 0) { scaledFont = AutoscaleFont(font, str, width, glyphOrientation); } graphics.TranslateTransform(x, y); graphics.RotateTransform(-rotDeg); graphics.TranslateTransform(-x, -y); if (glyphOrientation != (int)GlyphOrientation.GLYPH_UP) { DrawVerticalText(str, scaledFont, x, y, glyphOrientation, docX, docY); } else { // the y-position is based on the baseline, but in C# it is based on the toptline int ascent = font.FontFamily.GetCellAscent(font.Style); float emHeight = font.FontFamily.GetEmHeight(font.Style); float factor = font.Size / emHeight; float baseline = ascent * factor; graphics.TranslateTransform(x, y - baseline); graphics.DrawString(str, scaledFont, this.Pen.Brush, 0, 0, format); TextBlocks.Add(new TextBlock(str, graphics.Transform, scaledFont, GetStringBounds(str, scaledFont, glyphOrientation).ToSize(), docX, docY)); } } catch (Exception exc) { ViewerUtils.PrintStackTrace(exc); } graphics.Transform = restore; } /// /// Draws a string with floating-point precession. /// /// the string to draw /// x coordinate /// y coordinate /// the width internal void DrawString(string str, float x, float y, float width) { graphics.TextRenderingHint = TextRenderingHint.AntiAlias; Font font = (Font)fonts[currFontID]; if (fontAutoScaling && width > 0) { font = AutoscaleFont(font, str, width, (int)GlyphOrientation.GLYPH_UP); } int ascent = font.FontFamily.GetCellAscent(font.Style); float emHeight = font.FontFamily.GetEmHeight(font.Style); float factor = font.Size / emHeight; float baseline = ascent * factor; System.Drawing.Drawing2D.Matrix restore = graphics.Transform; graphics.TranslateTransform(x, y - baseline); graphics.DrawString(str, font, this.Pen.Brush, 0, 0, format); graphics.Transform = restore; } /// /// Computes an autoscaled font which matches the specified width when drawing the /// specified string. /// /// the font to scale /// the text string /// the width the autoscaled font will draw the string /// the glyph orientation /// the autoscaled font private System.Drawing.Font AutoscaleFont(System.Drawing.Font font, string str, float width, int glyphOrientation) { Font scaledFont = font; SizeF strBounds; strBounds = GetStringBounds(str, scaledFont, glyphOrientation); float actualWidth = (float)strBounds.Width; int counter = 0; // To scale with a 2/3 pixel precision. The transformation has to be considered here. // Clear Reports has a default transformation of 1:115 besides advanced-HTML where it is set to 1:1 double xScale = Math.Abs(graphics.Transform.Elements[3]); if (xScale == 0) { xScale = 1; } // If xScale is in the twips area, it is enough to have it to make pixel exactly. // Without it it would ongoingly scale because the width with cutting the after commas transmitted double limit = (xScale < 0.1d ? 0.67d : 1d) / xScale; while (Math.Abs(actualWidth - width) > limit && counter < 100 && actualWidth >= 0.01f) { float tmpSize = scaledFont.Size; // make the steps smaller, to avoid a jumping back and forth of the width. This could happen for very wide lines float factor = (width / actualWidth + counter) / (counter + 1); float newSize = scaledFont.Size * factor; scaledFont = new Font(scaledFont.FontFamily, newSize, scaledFont.Style, GraphicsUnit.Pixel); strBounds = GetStringBounds(str, scaledFont /*,frc*/, glyphOrientation); actualWidth = (float)strBounds.Width; counter++; } return scaledFont; } /// /// Computes the bounds of the given string, depending on its glyph orientation /// String whose bounds are to be computed /// Font to use to compute the bounds /// Glyph orientation of the individual glyphs /// Bounds of string, depending on its glyph orientation private SizeF GetStringBounds(string str, Font tmpFont, int glyphOrientation) { if (glyphOrientation == (int)GlyphOrientation.GLYPH_UP) { // standard, as before return graphics.MeasureString(str, tmpFont, int.MaxValue, format); } else if (glyphOrientation == (int)GlyphOrientation.GLYPH_DOWN) { // go through character by charater. For the glyph rotation the glyphs have to be separated. // For example in arabic two connect charactes won't be connected anymore. float totalWidth = 0; for (int i = 0; i < str.Length; i++) { totalWidth += MeasureCharacter(tmpFont, str[i]).Width; } return new SizeF(totalWidth, graphics.MeasureString(str, tmpFont, int.MaxValue, format).Height); } else { // sidewards glyph rotation // Calculate the amount of characters multiplied Ascent+Descent, according to the drawVerticalText() algorithm return graphics.MeasureString(str, tmpFont, int.MaxValue, format); } } /// /// Measures the size of a single letter. /// /// the font /// the char /// the size private SizeF MeasureCharacter(Font font, char chr) { RectangleF r = graphics.MeasureCharacterRanges(chr + string.Empty, font, new Rectangle(0, 0, int.MaxValue, int.MaxValue), format)[0].GetBounds(graphics); return new SizeF(r.Width, r.Height); } /// /// Draws the vertical text with the different glyph Orientations /// /// The string to be drawn /// the font the string should be drawn with /// top left corner /// top left corner /// The glyph orientation /// the original x coordinate in the document /// the original y coordinate in the document private void DrawVerticalText(string str, Font font, int x, int y, int glyphOrientation, int docX, int docY) { char[] txt = str.ToCharArray(); float emHeight = font.FontFamily.GetEmHeight(font.Style); float factor = font.Size / emHeight; float ascent = font.FontFamily.GetCellAscent(font.Style) * factor; float descent = font.FontFamily.GetCellDescent(font.Style) * factor; float baseline = ascent; Graphics g = graphics; System.Drawing.Drawing2D.Matrix restore = g.Transform; RectangleF[] charBounds = new RectangleF[txt.Length]; for (int i = 0; i < txt.Length; i++) { SizeF b = MeasureCharacter(font, txt[i]); int width = (int)b.Width; int height = (int)b.Height; switch (glyphOrientation) { case (int)GlyphOrientation.GLYPH_RIGHT: g.TranslateTransform(x, y); g.RotateTransform(90f); g.TranslateTransform(-x, -y); g.TranslateTransform((width - ascent) / 2 - width, -descent - baseline); break; case (int)GlyphOrientation.GLYPH_LEFT: g.TranslateTransform(x, y); g.RotateTransform(-90f); g.TranslateTransform(-x, -y); g.TranslateTransform((ascent - width) / 2, ascent - baseline); break; case (int)GlyphOrientation.GLYPH_DOWN: g.TranslateTransform(x, y); g.RotateTransform(180f); g.TranslateTransform(-x, -y); g.TranslateTransform(-width, ascent / 2 - baseline); goto default; default: break; } // In Java the Paint is used and not FillPaint, that is why we need to use the Pen as a Brush g.DrawString(txt[i] + string.Empty, font, new SolidBrush(this.Pen.Color), x, y, format); Region reg = new Region(new RectangleF(x, y, width, height)); reg.Transform(g.Transform); charBounds[i] = reg.GetBounds(g); if (glyphOrientation != (int)GlyphOrientation.GLYPH_DOWN) { x += height; } else { x += width; } g.Transform = restore; } TextBlocks.Add(new TextBlock(str, g.Transform, font, charBounds, docX, docY)); } /// /// Draws a line. /// /// x coordinate of the first point /// y coordinate of the first point /// x coordinate of the second point /// y coordinate of the second point public virtual void DrawLine(int x1, int y1, int x2, int y2) { x1 = PixelX(x1); x2 = PixelX(x2); y1 = PixelY(y1); y2 = PixelY(y2); graphics.DrawLine(this.Pen, x1, y1, x2, y2); } /// /// Draws a line with a specified style and width. /// /// x coordinate of the first point /// y coordinate of the first point /// x coordinate of the second point /// y coordinate of the second point /// the style /// the width public virtual void DrawLine(int x1, int y1, int x2, int y2, LineStyle style, int width) { if (style <= 0) { return; // no Line } Pen pen = SetLineStyle(style, width, false); x1 = PixelX(x1); x2 = PixelX(x2); y1 = PixelY(y1); y2 = PixelY(y2); graphics.DrawLine(pen, x1, y1, x2, y2); if (style == LineStyle.Double) { if (width == 0) { // hairline width = 1; } // double line if (y1 == y2) { graphics.DrawLine(pen, x1, y1 + 2 * width, x2, y2 + 2 * width); } else { graphics.DrawLine(pen, x1 + 2 * width, y1, x2 + 2 * width, y2); } } } /// /// Sets the linetype for lines and box elements /// all valid line types besides NO_LINE /// width in twips /// If the line ends with a square private Pen SetLineStyle(LineStyle style, float width, bool styleBeSquare) { Pen pen = new Pen(this.Pen.Color); float[] dashes; switch (style) { case LineStyle.Dashed: { float dash = Math.Max(75, width); // 75(twips) so that is 5px dahs for a 1px line dashes = new float[] { dash / 15f, dash / 15f }; pen.DashPattern = dashes; pen.DashStyle = DashStyle.Custom; break; } case LineStyle.Dotted: { // not needed as the Dot rendering in .Net renders is equals the Java implementation pen.DashStyle = DashStyle.Dot; break; } default: dashes = null; break; } pen.Width = width; pen.DashCap = DashCap.Flat; return pen; } /// /// Draws a rectangle. /// /// x coordinate of the top left point /// y coordinate of the top left point /// the width /// the height public virtual void DrawRect(int x, int y, int width, int height) { x = PixelX(x); y = PixelY(y); // performance optimization, only draw when visible if (!graphics.ClipBounds.IntersectsWith(new Rectangle(x, y, Math.Abs(width) + 1, Math.Abs(height + 1)))) { return; } graphics.DrawRectangle(this.Pen, x, y, width, height); } /// /// Draws a rounded rectangle. /// /// x coordinate of the top left point /// y coordinate of the top left point /// the width /// the height /// the width of an arc /// the height of an arc public virtual void DrawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { x = PixelX(x); y = PixelY(y); // performance optimization, only draw when visible if (!graphics.ClipBounds.IntersectsWith(new Rectangle(x, y, Math.Abs(width) + 1, Math.Abs(height + 1)))) { return; } DrawRoundedRectangle(graphics, this.Pen, x, y, width, height, arcWidth, arcHeight); } /// /// Fills a rounded rectangle. /// /// x coordinate of the top left point /// y coordinate of the top left point /// the width /// the height /// the width of an arc /// the height of an arc public virtual void FillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { // performance optimization, only draw when visible if (!graphics.ClipBounds.IntersectsWith(new Rectangle(x, y, Math.Abs(width) + 1, Math.Abs(height + 1)))) { return; } FillRoundedRectangle(graphics, Brush, new RoundedRectangle(x, y, width, height, arcWidth, arcHeight)); } /// /// Draws a blured shadow for the specified rounded rectangle. /// /// the rounded rectangle /// the line width private void DrawShadow(RoundedRectangle rec, int lineWidth) { Brush brush = new Pen(Color.FromArgb(192, this.Pen.Color)).Brush; int shadowOffset = 20; int blurSize = 20; float scale = 1 / 15f; using (Bitmap bitmap = new Bitmap((int)(scale * rec.Width + 2 * blurSize), (int)(scale * rec.Height + 2 * blurSize))) { using (Graphics g = Graphics.FromImage(bitmap)) { g.SmoothingMode = SmoothingMode.AntiAlias; g.ScaleTransform(scale, scale, MatrixOrder.Append); GraphicsPath pathShadow = GetRoundedRectanglePath(new RoundedRectangle(blurSize, blurSize, rec.Width + lineWidth, rec.Height + lineWidth, rec.ArcWidth, rec.ArcHeight)); g.FillPath(brush, pathShadow); } BlurAlpha(bitmap, 1); Region clipPre = graphics.Clip; graphics.ExcludeClip(new Region(GetRoundedRectanglePath(rec))); graphics.DrawImage(bitmap, rec.X + shadowOffset, rec.Y + shadowOffset, bitmap.Width / scale, bitmap.Height / scale); graphics.Clip = clipPre; } } /// /// Draws and fills a rounded rectangle. /// /// x coordinate of the top left point /// y coordinate of the top left point /// the width /// the height public virtual void DrawFillRect(int x, int y, int width, int height) { x = PixelX(x); y = PixelY(y); // performance optimization, only draw when visible if (!graphics.ClipBounds.IntersectsWith(new RectangleF(x, y, Math.Abs(width) + 1, Math.Abs(height) + 1))) { return; } // to avoid NullPointerException when a wrong protocol is used. The Adornment should be there always if (currAdornment == null) { return; } int arcWidth = currAdornment.EllipseWidth; int arcHeight = currAdornment.EllipseHeight; int lineWidth = currAdornment.LineWidth; RoundedRectangle rec = new RoundedRectangle(x, y, width, height, arcWidth, arcHeight); if (IsVisible(Brush)) { FillRoundedRectangle(graphics, Brush, rec); } if (!IsVisible(this.Pen.Color)) { // don't draw if transparent return; } if ((arcWidth | arcHeight) != 0) { // rounded rectangle will be drawn with one style if (currAdornment.TopStyle != LineStyle.NoLine) { Pen linePen = SetLineStyle(currAdornment.TopStyle, lineWidth, true); DrawRoundedRectangle(graphics, linePen, rec); if (currAdornment.TopStyle == LineStyle.Double) { rec = new RoundedRectangle(x - 2 * lineWidth, y - 2 * lineWidth, width + 4 * lineWidth, height + 4 * lineWidth, arcWidth + 4 * lineWidth, arcHeight + 4 * lineWidth); DrawRoundedRectangle(graphics, linePen, rec); } } // draw shadow first if (currAdornment.ShowShadow) { DrawShadow(rec, lineWidth); } return; } int l = x; int r = x + width; int t = y; int b = y + height; // positions for double borders int dl = l; int dr = r; int dt = t; int db = b; if (currAdornment.TopStyle == LineStyle.Double) { dt -= 2 * lineWidth; } if (currAdornment.RightStyle == LineStyle.Double) { dr += 2 * lineWidth; } if (currAdornment.BottomStyle == LineStyle.Double) { db += 2 * lineWidth; } if (currAdornment.LeftStyle == LineStyle.Double) { dl -= 2 * lineWidth; } if (currAdornment.ShowShadow) { DrawShadow(new RoundedRectangle(dl, dt, dr - dl, db - dt, 0, 0), lineWidth); } Pen rectPen = (Pen)Pen.Clone(); rectPen.Width = lineWidth; if (currAdornment.TopStyle != LineStyle.NoLine && (currAdornment.TopStyle == currAdornment.RightStyle && currAdornment.TopStyle == currAdornment.LeftStyle && currAdornment.TopStyle == currAdornment.BottomStyle)) { // use drawRect if all lines have the same style. The dotted lines will be displayed better rectPen = SetLineStyle(currAdornment.TopStyle, lineWidth, true); graphics.DrawRectangle(rectPen, l, t, r - l, b - t); if (t != dt) { graphics.DrawRectangle(rectPen, dl, dt, dr - dl, db - dt); } } else { // Top if (currAdornment.TopStyle != LineStyle.NoLine) { rectPen = SetLineStyle(currAdornment.TopStyle, lineWidth, true); this.Pen.Width = lineWidth; graphics.DrawLine(rectPen, l, t, r, t); if (t != dt) { graphics.DrawLine(rectPen, dl, dt, dr, dt); } } // Right if (currAdornment.RightStyle != LineStyle.NoLine) { rectPen = SetLineStyle(currAdornment.RightStyle, lineWidth, true); graphics.DrawLine(rectPen, r, t, r, b); if (r != dr) { graphics.DrawLine(rectPen, dr, dt, dr, db); } } // Bottom if (currAdornment.BottomStyle != LineStyle.NoLine) { rectPen = SetLineStyle(currAdornment.BottomStyle, lineWidth, true); graphics.DrawLine(rectPen, l, b, r, b); if (b != db) { graphics.DrawLine(rectPen, dl, db, dr, db); } } // Left if (currAdornment.LeftStyle != LineStyle.NoLine) { rectPen = SetLineStyle(currAdornment.LeftStyle, lineWidth, true); graphics.DrawLine(rectPen, l, t, l, b); if (l != dl) { graphics.DrawLine(rectPen, dl, dt, dl, db); } } } } /// /// Fills a rectangle. /// /// x coordinate of the top left point /// y coordinate of the top left point /// the width /// the height public virtual void FillRect(int x, int y, int width, int height) { FillRect(x, y, width, height, false); } /// /// Fills a rectangle. /// /// x coordinate of the top left point /// y coordinate of the top left point /// the width /// the height /// smooth flag public virtual void FillRect(int x, int y, int width, int height, bool smooth) { // Performance: only draw when visible if (!graphics.ClipBounds.IntersectsWith(new Rectangle(x, y, Math.Abs(width) + 1, Math.Abs(height + 1)))) { return; } if (IsVisible(Brush)) { TextRenderingHint origAntiAliasing = graphics.TextRenderingHint; if (!smooth) { graphics.TextRenderingHint = TextRenderingHint.AntiAlias; } graphics.FillRectangle(Brush, x, y, width, height); if (!smooth) { graphics.TextRenderingHint = origAntiAliasing; } } } /// /// Draws an image. /// /// the image data as array of bytes /// x coordinate of the top left point /// y coordinate of the top left point /// the width /// the height /// the hash value public virtual void DrawImage(byte[] imgBytes, int x, int y, int width, int height, int hashValue) { // hash Value is currently ignored if (!graphics.ClipBounds.IntersectsWith(new RectangleF(x, y, Math.Abs(width) + 1, Math.Abs(height + 1)))) { return; } Bitmap img = ReadImage(imgBytes); if (img == null) { return; } try { System.Drawing.Drawing2D.Matrix oldTransform = graphics.Transform; // use of the Matrix of WPF as it contains caluclation for the Determinatn System.Windows.Media.Matrix mat = new System.Windows.Media.Matrix(); double determinant = mat.Determinant; double scaleFactor = 1d; if (determinant >= 0) { scaleFactor = Math.Sqrt(determinant); } int pixelsWidth = (int)(width * scaleFactor); int pixelsHeight = (int)(height * scaleFactor); if (pixelsWidth * pixelsHeight > MaxHighQualityImagePixelCount) { // image to big ( > 1000x1000 ) - to avoid OutOfMemoryException graphics.DrawImage(img, x, y, width, height); return; } // Picture would not be shown anyways. Additionaly helps to avoid endless loop if (pixelsWidth <= 0 || pixelsHeight <= 0) { return; } try { graphics.ScaleTransform((float)(1 / scaleFactor), (float)(1 / scaleFactor)); graphics.InterpolationMode = InterpolationMode.High; graphics.DrawImage(img, x, y, pixelsWidth, pixelsHeight); } finally { graphics.Transform = oldTransform; } } catch (Exception th) { ViewerUtils.PrintStackTrace(th); } } /// /// Create a Bitmap out of the byte data /// the byte data of the image /// A bitmap and if something went wrong null private Bitmap ReadImage(byte[] imgBytes) { try { MemoryStream ms = new MemoryStream(imgBytes); Bitmap img = new Bitmap(Image.FromStream(ms)); return img; } catch (Exception e1) { // IOException - when error during reading imagedata ViewerUtils.PrintStackTrace(e1); return null; } } /// /// Draws a polygon /// /// array with points of the polygon public virtual void DrawPolygon(Point[] polygon) { // performance optimization, only draw when visible GraphicsPath path = new GraphicsPath(); path.AddPolygon(polygon); if (!graphics.ClipBounds.IntersectsWith(path.GetBounds())) { return; } graphics.DrawPolygon(this.Pen, polygon); } /// /// Fills a polygon /// /// array with points of the polygon public virtual void FillPolygon(Point[] polygon) { GraphicsPath path = new GraphicsPath(); path.AddPolygon(polygon); if (!graphics.ClipBounds.IntersectsWith(path.GetBounds())) { return; } if (IsVisible(Brush)) { graphics.FillPolygon(Brush, polygon); } } /// /// Draws an oval. /// /// x coordinate /// y coordinate /// the width /// the height public virtual void DrawOval(int x, int y, int width, int height) { if (!graphics.ClipBounds.IntersectsWith(new Rectangle(x, y, Math.Abs(width) + 1, Math.Abs(height + 1)))) { return; } graphics.DrawEllipse(this.Pen, x, y, width, height); } /// /// Fills an oval. /// /// x coordinate /// y coordinate /// the width /// the height public virtual void FillOval(int x, int y, int width, int height) { if (!graphics.ClipBounds.IntersectsWith(new Rectangle(x, y, Math.Abs(width) + 1, Math.Abs(height + 1)))) { return; } if (IsVisible(Brush)) { graphics.FillEllipse(Brush, x, y, width, height); } } /// /// Draws an arc. /// /// x coordinate /// y coordinate /// the width /// the height /// start arngle /// arc angle public virtual void DrawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { // performance optimization, only draw when visible if (!graphics.ClipBounds.IntersectsWith(new Rectangle(x, y, Math.Abs(width) + 1, Math.Abs(height + 1)))) { return; } graphics.DrawArc(this.Pen, x, y, width, height, startAngle, arcAngle); } /// /// Fills an arc. /// /// x coordinate /// y coordinate /// the width /// the height /// start arngle /// arc angle public virtual void FillArc(int x, int y, int width, int height, int startAngle, int arcAngle) { // performance optimization, only draw when visible if (!graphics.ClipBounds.IntersectsWith(new Rectangle(x, y, Math.Abs(width) + 1, Math.Abs(height + 1)))) { return; } if (IsVisible(Brush)) { graphics.FillPie(Brush, x, y, width, height, 360 - startAngle - arcAngle, arcAngle); } } /// /// Adds a subreport on-demand. /// /// the page clip of the subreport public virtual void AddSubreportOnDemand(PageClip clip) { if (onFirstPaint) { links.Add(clip); } } /// /// Adds a hyperlink. /// /// the page clip of the hyperlink public virtual void AddHyperlink(PageClip clip) { if (onFirstPaint) { if (clip.LinkType == LinkType.Hyperlink) { // merge hyperlink with a tooltip at the same location if any exists try { PageClip toolTipClip = links.First(l => l.LinkType == LinkType.ToolTip && l.EqualLocation(clip)); links.Remove(toolTipClip); clip.ToolTip = toolTipClip.ToolTip; } catch (InvalidOperationException) { // no such tooltip exists => ignore } } links.Add(clip); } } /// /// Called after the rendering is done. /// public virtual void FinishPage() { onFirstPaint = false; } /// /// Gets and sets the graphics instance. /// public virtual Graphics Graphics { get { return graphics; } set { graphics = (Graphics)value; graphicsID = 0; allGraphics.Clear(); if (graphics != null) { // Create Graphics with ID 0 Create(0); graphics.SmoothingMode = SmoothingMode.AntiAlias; Scale(1 / 15.0 * zoom); } } } /// /// Invokes a ScaleTransform on the grahpics with this factor /// /// 100% = 1.0 private void Scale(double factor) { if (graphics != null) { graphics.ScaleTransform((float)factor, (float)factor); originalTransform = graphics.Transform; originalClip = graphics.Clip; } } /// /// Clears any added elements (links/tooltips). /// public virtual void ClearElements() { foreach (Font font in fonts) { font.Dispose(); } fonts.Clear(); // Synchronized, as access from MouseListener from SwingPageView lock (links) { links.Clear(); } adorns.Clear(); onFirstPaint = true; } /// /// Creates a graphics state. /// /// the ID of the graphics state public virtual void Create(int newGraphicsID) { if (graphics == null) { this.graphics = Graphics.FromImage(graphicsImage); } // store current state StoreState(); this.graphicsID = newGraphicsID; } /// /// Stores the current graphics state (brush, pen) in the map under the current graphics ID. /// private void StoreState() { if (graphics == null) { return; } GraphicsState oldState; allGraphics.TryGetValue(graphicsID, out oldState); if (oldState == null) { oldState = new GraphicsState(); allGraphics[graphicsID] = oldState; } oldState.SaveDataFromGraphics(this.graphics, this, internalTransform); } /// /// is invoked to set a chart or a bean to the normal state. /// the main grapics will be switched to the current graphics. /// Doesen't dispose but delete the Graphics State out of the Hahstable and switches to index 0 /// public virtual void Dispose() { allGraphics.Remove(graphicsID); graphicsID = 0; ((GraphicsState)allGraphics[0]).RestoreDataToGrapics(graphics, this); } /// /// Switches to a specified graphics state. /// /// the graphics state to switch to public virtual void ChangeCurrentGraphics(int newGraphicsID) { if (newGraphicsID == graphicsID) { return; } StoreState(); // restore state this.graphicsID = newGraphicsID; GraphicsState newState = (GraphicsState)allGraphics[this.graphicsID]; newState.RestoreDataToGrapics(this.graphics, this); } /// /// Applies a transform matrix via multiplication. /// /// the matrix to multiplicate public virtual void Transform(System.Drawing.Drawing2D.Matrix transform) { System.Drawing.Drawing2D.Matrix t = graphics.Transform; t.Multiply(transform); graphics.Transform = t; internalTransform.Multiply(transform); } /// /// Sets the transform. /// /// the transform to set public virtual void SetTransform(System.Drawing.Drawing2D.Matrix transform) { internalTransform = transform; System.Drawing.Drawing2D.Matrix newTransform = originalTransform.Clone(); newTransform.Multiply(transform, MatrixOrder.Prepend); try { graphics.Transform = newTransform; } catch (ArgumentException ae) { Console.WriteLine("Something wrong with Set Transform\n\n" + ae.StackTrace); } } /// /// Sets the clip. /// public virtual Region Clip { set { graphics.Clip = value; } } /// /// Draws a shape. /// /// the shape to draw public virtual void Draw(GraphicsPath shape) { RectangleF clip = graphics.ClipBounds; Pen pen = this.Pen; if (clip.Size.Width != 0 && clip.Size.Height != 0) { graphics.DrawPath(this.Pen, shape); } } /// /// Fills a shape. /// /// the shape to draw public virtual void Fill(GraphicsPath shape) { RectangleF clip = graphics.ClipBounds; if (clip.Size.Width != 0 && clip.Size.Height != 0) { graphics.FillPath(Brush, shape); } } /// /// Checks if a color is visible (not transparent) /// private bool IsVisible(Color c) { return ((Color)c).A != 0; } /// /// Checks if a Brush is visible (not transparent) /// private bool IsVisible(Brush brush) { if (brush is SolidBrush) { return IsVisible(((SolidBrush)brush).Color); } else if (brush is LinearGradientBrush) { LinearGradientBrush lgBrush = (LinearGradientBrush)brush; foreach (Color col in lgBrush.LinearColors) { // if one color is visible the gradient will be visible if (IsVisible(col)) { return true; } } // only if no color is visible it will be false return false; } else { return IsVisible(new Pen(brush).Color); } } /// /// Returns a non transparent Color if transparency is not allowed /// private Color GetOpaquePaint(Color color) { if (transparency) { return color; } Color aColor = (Color)color; float alpha = aColor.A / 255F; int back = (int)(255 * (1 - alpha)); return Color.FromArgb(back + (int)(aColor.R * alpha), back + (int)(aColor.G * alpha), back + (int)(aColor.B * alpha)); } /// /// Returns a non transparent Brush if transparency is not allowed /// private Brush GetOpaquePaint(Brush p) { if (transparency) { return p; } if (p is LinearGradientBrush) { LinearGradientBrush gradient = (LinearGradientBrush)p; Color color1 = (Color)GetOpaquePaint(gradient.LinearColors[0]); if (!IsVisible(color1)) { color1 = Color.White; } Color color2 = (Color)GetOpaquePaint(gradient.LinearColors[1]); if (!IsVisible(color2)) { color2 = Color.White; } return new LinearGradientBrush(gradient.Rectangle, color1, color2, LinearGradientMode.Horizontal); } return p; } /// /// Gets the links. /// internal virtual IList Links { get { return links; } } /// /// Sets the graphics object to null /// internal virtual void ClearGraphics() { graphics = null; } /// /// Automatic scaling of text blocks to their original sizes. /// internal bool FontAutoScaling { set { this.fontAutoScaling = value; } } /// /// Implementation for RoundedRectangles as C# does not offer this class as Java does /// /// the graphics to paint into /// the pen to use /// x coordinate /// y coordinate /// the width /// the height /// the width of an arc /// the height of an arc private static void DrawRoundedRectangle(Graphics graphics, Pen pen, int x, int y, int width, int height, int arcWidth, int arcHeight) { GraphicsPath path = GetRoundedRectanglePath(x, y, width, height, arcWidth, arcHeight); graphics.DrawPath(pen, path); } /// /// Overload for Rectangle /// /// the graphics to paint into /// the pen to use /// the rectangle to draw private static void DrawRoundedRectangle(Graphics graphics, Pen pen, RoundedRectangle rec) { DrawRoundedRectangle(graphics, pen, rec.X, rec.Y, rec.Width, rec.Height, rec.ArcWidth, rec.ArcHeight); } /// /// Fills the grapchis with the RoundedRectangle /// The RoundedRectangle will be transformed into a GraphicsPath and than drawn into the graphics objec /// /// the graphics to paint into /// the brush to use /// the rounded rectangle private static void FillRoundedRectangle(Graphics graphics, Brush brush, RoundedRectangle rec) { GraphicsPath path = GetRoundedRectanglePath(rec); RectangleF clip = graphics.ClipBounds; if (clip.Size.Width != 0 && clip.Size.Height != 0) { graphics.FillPath(brush, path); } } /// /// Method that creates a Graphics Path for a RoundedRectangle /// /// the rounded rectangle /// the created graphics path private static GraphicsPath GetRoundedRectanglePath(RoundedRectangle rec) { return GetRoundedRectanglePath(rec.X, rec.Y, rec.Width, rec.Height, rec.ArcWidth, rec.ArcHeight); } /// /// Method that creates a Graphics Path for a RoundedRectangle as a RoundedRectangle does not exist in System.Drawings /// /// x coordinate /// y coordinate /// the width /// the height /// the width of an arc /// the height of an arc /// the created graphics path private static GraphicsPath GetRoundedRectanglePath(int x, int y, int width, int height, int arcWidth, int arcHeight) { // to make sure the rounded corneders are the maximum a circle (wouldn't render proberly otherwise) arcWidth = Math.Min(arcWidth, width); arcHeight = Math.Min(arcHeight, height); GraphicsPath path = new GraphicsPath(); bool drawArc = arcWidth > 0 && arcHeight > 0; int arcWidthHalf = arcWidth / 2; int arcHeightHalf = arcHeight / 2; // draw straight lines only if there is no arc. It would cause strange graphic effects if it would be drawn if (!drawArc) { // top path.AddLine(x + arcWidthHalf, y, x + width - arcWidthHalf, y); } if (drawArc) { // upper right arc path.AddArc(x + width - arcWidth, y, arcWidth, arcHeight, 270, 90); } else { // right path.AddLine(x + width, y + arcHeightHalf, x + width, y + height - arcHeightHalf); } if (drawArc) { // lower right arc path.AddArc(x + width - arcWidth, y + height - arcHeight, arcWidth, arcHeight, 0, 90); } else { // bottom path.AddLine(x + width - arcWidthHalf, y + height, x + arcWidthHalf, y + height); } if (drawArc) { // lower left arc path.AddArc(x, y + height - arcHeight, arcWidth, arcHeight, 90, 90); } else { // left path.AddLine(x, y + height - arcHeightHalf, x, y + arcHeightHalf); } if (drawArc) { path.AddArc(x, y, arcWidth, arcHeight, 180, 90); // upper left arc } path.CloseFigure(); return path; } /// /// Blurs the alpha channel of the specified image. All RGB values are set /// to 0 (black). /// /// the image to blur /// the blur radius private static void BlurAlpha(Bitmap image, int radius) { if (radius < 1) { return; } int w = image.Width; int h = image.Height; int size = w * h; Rectangle rect = new Rectangle(0, 0, w, h); int[] dst = new int[size]; int[] src = new int[size]; BitmapData bitmapData = image.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); Marshal.Copy(bitmapData.Scan0, src, 0, src.Length); image.UnlockBits(bitmapData); int w1 = w - 1; int h1 = h - 1; int div = 2 * radius + 1; int[] alpha = new int[size]; int[] lookupSum = new int[256 * div]; for (int i = 0; i < lookupSum.Length; i++) { lookupSum[i] = i / div; } int[] vmin = new int[Math.Max(w, h)]; int[] vmax = new int[Math.Max(w, h)]; int yi = 0, yw = 0; const int XFF000000 = unchecked((int)0xff000000); for (int y = 0; y < h; y++) { int sum = 0; for (int i = -radius; i <= radius; i++) { int p = src[yi + Math.Min(w1, Math.Max(i, 0))]; sum += (int)((uint)(p & XFF000000) >> 24); } for (int x = 0; x < w; x++) { alpha[yi] = lookupSum[sum]; if (y == 0) { vmin[x] = Math.Min(x + radius + 1, w1); vmax[x] = Math.Max(x - radius, 0); } sum += (int)((uint)(src[yw + vmin[x]] & XFF000000) >> 24) - (int)((uint)(src[yw + vmax[x]] & XFF000000) >> 24); yi++; } yw += w; } for (int x = 0; x < w; x++) { int sum = 0; int yp = -radius * w; for (int i = -radius; i <= radius; i++) { yi = Math.Max(0, yp) + x; sum += alpha[yi]; yp += w; } yi = x; for (int y = 0; y < h; y++) { dst[yi] = (int)((uint)(lookupSum[sum] << 24)); if (x == 0) { vmin[y] = Math.Min(y + radius + 1, h1) * w; vmax[y] = Math.Max(y - radius, 0) * w; } sum += alpha[x + vmin[y]] - alpha[x + vmax[y]]; yi += w; } } bitmapData = image.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); Marshal.Copy(dst, 0, bitmapData.Scan0, dst.Length); image.UnlockBits(bitmapData); } /// /// Returns the nearest full pixel coordinate in terms of the device space /// for the specified X coordinate. /// Used to avoid rendering at sub pixel positions when scaling down. /// /// the x coordinate /// the full pixel x coordinate private int PixelX(int x) { return (int)PixelX((float)x); } /// /// Returns the nearest full pixel coordinate in terms of the device space /// for the specified Y coordinate. /// Used to avoid rendering at subpixel positions when scaling down. /// /// the y coordinate /// the full pixel y coordinate private int PixelY(int y) { return (int)PixelY((float)y); } /// /// Returns the nearest full pixel coordinate in terms of the device space /// for the specified X coordinate. /// Used to avoid rendering at subpixel positions when scaling down. /// /// the x coordinate /// the full pixel x coordinate private float PixelX(float x) { if (!isPrinting) { float f = graphics.Transform.Elements[0]; if (f > 0 && f < 1f) { return (float)(Math.Round(x * f) / f); } } return x; } /// /// Returns the nearest full pixel coordinate in terms of the device space /// for the specified Y coordinate. /// Used to avoid rendering at subpixel positions when scaling down. /// /// the y coordinate /// the full pixel y coordinate private float PixelY(float y) { if (!isPrinting) { float f = graphics.Transform.Elements[3]; if (f > 0 && f < 1f) { return (float)(Math.Round(y * f) / f); } } return y; } /// /// List of all text blocks present on this page. /// public IList TextBlocks { get; set; } /// /// Represents a Rectangle with rounded corners with Integer values. The arcs for the 4 corners are the same. Setting of the arc Properties /// and is only needed once and applies for all corners. /// /// All Properties use Integer values. For use with float values use the class private class RoundedRectangle { private int x; private int y; private int width; private int height; private int arcWidth; private int arcHeight; /// /// Constructor to fill all the neccesary Properties of this class /// /// x coordinate /// y coordinate /// the width /// the height /// the width of an arc /// the height of an arc public RoundedRectangle(int x, int y, int width, int height, int arcWidth, int arcHeight) { this.x = x; this.y = y; this.width = width; this.height = height; this.arcWidth = arcWidth; this.arcHeight = arcHeight; } /// /// Gets or sets the x coordinate. /// public int X { get { return x; } set { x = value; } } /// /// Gets or sets the y coordinate. /// public int Y { get { return y; } set { y = value; } } /// /// Gets or sets the width of the rectangle /// public int Width { get { return width; } set { width = value; } } /// /// Gets or sets the height of the rectangle /// public int Height { get { return height; } set { height = value; } } /// /// Gets or sets the width of one arc. (Is used for all 4 arcs) /// public int ArcWidth { get { return arcWidth; } set { arcWidth = value; } } /// /// Gets or sets the height of one arc. (Is used for all 4 arcs) /// public int ArcHeight { get { return arcHeight; } set { arcHeight = value; } } } /// /// Represents a Rectangle with rounded corners with Float values. The arcs for the 4 corners are the same. Setting of the arc Properties /// and is only needed once and applies for all corners. /// /// All Properties use Float values. For use with Integer values use the class private class RoundedRectangleF { private float x; private float y; private float width; private float height; private float arcWidth; private float arcHeight; /// /// Constructor to fill all the neccesary Properties of this class /// /// X Property /// Y Property /// Width Property /// Height Property /// ArcWidth /// ArcHeight public RoundedRectangleF(float x, float y, float width, float height, float arcWidth, float arcHeight) { this.x = x; this.y = y; this.width = width; this.height = height; this.arcWidth = arcWidth; this.arcHeight = arcHeight; } /// /// Gets or sets the x coordinate. /// public float X { get { return x; } set { x = value; } } /// /// Gets or sets the y coordinate. /// public float Y { get { return y; } set { y = value; } } /// /// Gets or sets the width of the rectangle /// public float Width { get { return width; } set { width = value; } } /// /// Gets or sets the height of the rectangle /// public float Height { get { return height; } set { height = value; } } /// /// Gets or sets the width of one arc. (Is used for all 4 arcs) /// public float ArcWidth { get { return arcWidth; } set { arcWidth = value; } } /// /// Gets or sets the height of one arc. (Is used for all 4 arcs) /// public float ArcHeight { get { return arcHeight; } set { arcHeight = value; } } } } }