/// <summary> /// Draws the text in the provided font at the specified position and returns the letters which will be drawn. /// </summary> /// <param name="text">The text to draw to the page.</param> /// <param name="fontSize">The size of the font in user space units.</param> /// <param name="position">The position of the baseline (lower-left corner) to start drawing the text from.</param> /// <param name="font"> /// A font added to the document using <see cref="PdfDocumentBuilder.AddTrueTypeFont"/> /// or <see cref="PdfDocumentBuilder.AddStandard14Font"/> methods. /// </param> /// <returns>The letters from the input text with their corresponding size and position.</returns> public IReadOnlyList <Letter> AddText(string text, decimal fontSize, PdfPoint position, PdfDocumentBuilder.AddedFont font) { if (font == null) { throw new ArgumentNullException(nameof(font)); } if (text == null) { throw new ArgumentNullException(nameof(text)); } if (!documentBuilder.Fonts.TryGetValue(font.Id, out var fontStore)) { throw new ArgumentException($"No font has been added to the PdfDocumentBuilder with Id: {font.Id}. " + $"Use {nameof(documentBuilder.AddTrueTypeFont)} to register a font.", nameof(font)); } if (fontSize <= 0) { throw new ArgumentOutOfRangeException(nameof(fontSize), "Font size must be greater than 0"); } var fontProgram = fontStore.FontProgram; var fm = fontProgram.GetFontMatrix(); var textMatrix = TransformationMatrix.FromValues(1, 0, 0, 1, position.X, position.Y); var letters = DrawLetters(text, fontProgram, fm, fontSize, textMatrix); CurrentStream.Add(BeginText.Value); CurrentStream.Add(new SetFontAndSize(font.Name, fontSize)); CurrentStream.Add(new MoveToNextLineWithOffset((decimal)position.X, (decimal)position.Y)); var bytesPerShow = new List <byte>(); foreach (var letter in text) { if (char.IsWhiteSpace(letter)) { CurrentStream.Add(new ShowText(bytesPerShow.ToArray())); bytesPerShow.Clear(); } var b = fontProgram.GetValueForCharacter(letter); bytesPerShow.Add(b); } if (bytesPerShow.Count > 0) { CurrentStream.Add(new ShowText(bytesPerShow.ToArray())); } CurrentStream.Add(EndText.Value); return(letters); }
/// <summary> /// Adds the JPEG image represented by the input stream at the specified location. /// </summary> public AddedImage AddJpeg(Stream fileStream, PdfRectangle placementRectangle) { var startFrom = fileStream.Position; var info = JpegHandler.GetInformation(fileStream); byte[] data; using (var memory = new MemoryStream()) { fileStream.Seek(startFrom, SeekOrigin.Begin); fileStream.CopyTo(memory); data = memory.ToArray(); } var imgDictionary = new Dictionary <NameToken, IToken> { { NameToken.Type, NameToken.Xobject }, { NameToken.Subtype, NameToken.Image }, { NameToken.Width, new NumericToken(info.Width) }, { NameToken.Height, new NumericToken(info.Height) }, { NameToken.BitsPerComponent, new NumericToken(info.BitsPerComponent) }, { NameToken.ColorSpace, NameToken.Devicergb }, { NameToken.Filter, NameToken.DctDecode }, { NameToken.Length, new NumericToken(data.Length) } }; var reference = documentBuilder.AddImage(new DictionaryToken(imgDictionary), data); if (!resourcesDictionary.TryGetValue(NameToken.Xobject, out var xobjectsDict) || !(xobjectsDict is DictionaryToken xobjects)) { xobjects = new DictionaryToken(new Dictionary <NameToken, IToken>()); resourcesDictionary[NameToken.Xobject] = xobjects; } var key = NameToken.Create($"I{imageKey++}"); resourcesDictionary[NameToken.Xobject] = xobjects.With(key, new IndirectReferenceToken(reference)); CurrentStream.Add(Push.Value); // This needs to be the placement rectangle. CurrentStream.Add(new ModifyCurrentTransformationMatrix(new [] { (decimal)placementRectangle.Width, 0, 0, (decimal)placementRectangle.Height, (decimal)placementRectangle.BottomLeft.X, (decimal)placementRectangle.BottomLeft.Y })); CurrentStream.Add(new InvokeNamedXObject(key)); CurrentStream.Add(Pop.Value); return(new AddedImage(reference, info.Width, info.Height)); }
/// <summary> /// Draws a line on the current page between two points with the specified line width. /// </summary> /// <param name="from">The first point on the line.</param> /// <param name="to">The last point on the line.</param> /// <param name="lineWidth">The width of the line in user space units.</param> public void DrawLine(PdfPoint from, PdfPoint to, decimal lineWidth = 1) { if (lineWidth != 1) { CurrentStream.Add(new SetLineWidth(lineWidth)); } CurrentStream.Add(new BeginNewSubpath((decimal)from.X, (decimal)from.Y)); CurrentStream.Add(new AppendStraightLineSegment((decimal)to.X, (decimal)to.Y)); CurrentStream.Add(StrokePath.Value); if (lineWidth != 1) { CurrentStream.Add(new SetLineWidth(1)); } }
/// <summary> /// Draws a rectangle on the current page starting at the specified point with the given width, height and line width. /// </summary> /// <param name="position">The position of the rectangle, for positive width and height this is the bottom-left corner.</param> /// <param name="width">The width of the rectangle.</param> /// <param name="height">The height of the rectangle.</param> /// <param name="lineWidth">The width of the line border of the rectangle.</param> /// <param name="fill">Whether to fill with the color set by <see cref="SetTextAndFillColor"/>.</param> public void DrawRectangle(PdfPoint position, decimal width, decimal height, decimal lineWidth = 1, bool fill = false) { if (lineWidth != 1) { CurrentStream.Add(new SetLineWidth(lineWidth)); } CurrentStream.Add(new AppendRectangle((decimal)position.X, (decimal)position.Y, width, height)); if (fill) { CurrentStream.Add(FillPathEvenOddRuleAndStroke.Value); } else { CurrentStream.Add(StrokePath.Value); } if (lineWidth != 1) { CurrentStream.Add(new SetLineWidth(lineWidth)); } }
/// <summary> /// Adds the image previously added using <see cref="AddJpeg(byte[], PdfRectangle)"/> /// or <see cref="AddPng(byte[], PdfRectangle)"/> sharing the same image to prevent duplication. /// </summary> public void AddImage(AddedImage image, PdfRectangle placementRectangle) { if (!resourcesDictionary.TryGetValue(NameToken.Xobject, out var xobjectsDict) || !(xobjectsDict is DictionaryToken xobjects)) { xobjects = new DictionaryToken(new Dictionary <NameToken, IToken>()); resourcesDictionary[NameToken.Xobject] = xobjects; } var key = NameToken.Create($"I{imageKey++}"); resourcesDictionary[NameToken.Xobject] = xobjects.With(key, new IndirectReferenceToken(image.Reference)); CurrentStream.Add(Push.Value); // This needs to be the placement rectangle. CurrentStream.Add(new ModifyCurrentTransformationMatrix(new[] { (decimal)placementRectangle.Width, 0, 0, (decimal)placementRectangle.Height, (decimal)placementRectangle.BottomLeft.X, (decimal)placementRectangle.BottomLeft.Y })); CurrentStream.Add(new InvokeNamedXObject(key)); CurrentStream.Add(Pop.Value); }
/// <summary> /// Adds the PNG image represented by the input stream at the specified location. /// </summary> public AddedImage AddPng(Stream pngStream, PdfRectangle placementRectangle) { var png = Png.Open(pngStream); byte[] data; var pixelBuffer = new byte[3]; using (var memoryStream = new MemoryStream()) { for (var rowIndex = 0; rowIndex < png.Height; rowIndex++) { for (var colIndex = 0; colIndex < png.Width; colIndex++) { var pixel = png.GetPixel(colIndex, rowIndex); pixelBuffer[0] = pixel.R; pixelBuffer[1] = pixel.G; pixelBuffer[2] = pixel.B; memoryStream.Write(pixelBuffer, 0, pixelBuffer.Length); } } data = memoryStream.ToArray(); } var compressed = DataCompresser.CompressBytes(data); var imgDictionary = new Dictionary <NameToken, IToken> { { NameToken.Type, NameToken.Xobject }, { NameToken.Subtype, NameToken.Image }, { NameToken.Width, new NumericToken(png.Width) }, { NameToken.Height, new NumericToken(png.Height) }, { NameToken.BitsPerComponent, new NumericToken(png.Header.BitDepth) }, { NameToken.ColorSpace, NameToken.Devicergb }, { NameToken.Filter, NameToken.FlateDecode }, { NameToken.Length, new NumericToken(compressed.Length) } }; var reference = documentBuilder.AddImage(new DictionaryToken(imgDictionary), compressed); if (!resourcesDictionary.TryGetValue(NameToken.Xobject, out var xobjectsDict) || !(xobjectsDict is DictionaryToken xobjects)) { xobjects = new DictionaryToken(new Dictionary <NameToken, IToken>()); resourcesDictionary[NameToken.Xobject] = xobjects; } var key = NameToken.Create($"I{imageKey++}"); resourcesDictionary[NameToken.Xobject] = xobjects.With(key, new IndirectReferenceToken(reference)); CurrentStream.Add(Push.Value); // This needs to be the placement rectangle. CurrentStream.Add(new ModifyCurrentTransformationMatrix(new[] { (decimal)placementRectangle.Width, 0, 0, (decimal)placementRectangle.Height, (decimal)placementRectangle.BottomLeft.X, (decimal)placementRectangle.BottomLeft.Y })); CurrentStream.Add(new InvokeNamedXObject(key)); CurrentStream.Add(Pop.Value); return(new AddedImage(reference, png.Width, png.Height)); }
/// <summary> /// Restores the stroke, text and fill color to default (black). /// </summary> public void ResetColor() { CurrentStream.Add(Pop.Value); }
/// <summary> /// Sets the fill and text color for any following operations to the RGB value. Use <see cref="ResetColor"/> to reset. /// </summary> /// <param name="r">Red - 0 to 255</param> /// <param name="g">Green - 0 to 255</param> /// <param name="b">Blue - 0 to 255</param> public void SetTextAndFillColor(byte r, byte g, byte b) { CurrentStream.Add(Push.Value); CurrentStream.Add(new SetNonStrokeColorDeviceRgb(RgbToDecimal(r), RgbToDecimal(g), RgbToDecimal(b))); }
/// <summary> /// Sets the stroke color with the exact decimal value between 0 and 1 for any following operations to the RGB value. Use <see cref="ResetColor"/> to reset. /// </summary> /// <param name="r">Red - 0 to 1</param> /// <param name="g">Green - 0 to 1</param> /// <param name="b">Blue - 0 to 1</param> internal void SetStrokeColorExact(decimal r, decimal g, decimal b) { CurrentStream.Add(Push.Value); CurrentStream.Add(new SetStrokeColorDeviceRgb(CheckRgbDecimal(r, nameof(r)), CheckRgbDecimal(g, nameof(g)), CheckRgbDecimal(b, nameof(b)))); }