2018-11-24 19:38:16 +00:00
namespace UglyToad.PdfPig.Writer
{
2018-12-08 18:04:02 +00:00
using Content ;
2018-11-27 20:00:38 +00:00
using Core ;
2019-12-28 17:47:50 +00:00
using Fonts ;
2019-08-05 19:26:10 +01:00
using Graphics.Colors ;
2018-11-27 20:00:38 +00:00
using Graphics.Operations ;
2018-12-12 00:09:15 +00:00
using Graphics.Operations.General ;
using Graphics.Operations.PathConstruction ;
2018-12-30 14:12:04 +00:00
using Graphics.Operations.SpecialGraphicsState ;
2018-11-27 20:00:38 +00:00
using Graphics.Operations.TextObjects ;
2018-12-02 16:14:55 +00:00
using Graphics.Operations.TextPositioning ;
2018-11-27 20:00:38 +00:00
using Graphics.Operations.TextShowing ;
using Graphics.Operations.TextState ;
2020-03-09 23:01:37 +01:00
using Images ;
2020-04-02 16:33:03 +01:00
using System ;
using System.Collections.Generic ;
using System.IO ;
2020-12-20 19:13:19 +00:00
using System.Linq ;
2020-04-25 17:15:26 +01:00
using PdfFonts ;
2020-03-09 23:01:37 +01:00
using Tokens ;
2020-04-25 17:15:26 +01:00
using Graphics.Operations.PathPainting ;
2020-08-21 10:50:17 +01:00
using Images.Png ;
2018-11-24 19:38:16 +00:00
2018-12-25 10:37:00 +00:00
/// <summary>
/// A builder used to add construct a page in a PDF document.
/// </summary>
2018-12-28 16:55:46 +00:00
public class PdfPageBuilder
2021-02-10 12:27:12 -06:00
{
2021-02-08 12:37:09 -06:00
// parent
2021-02-10 12:27:12 -06:00
private readonly PdfDocumentBuilder documentBuilder ;
2021-02-08 12:37:09 -06:00
// all page data other than content streams
internal readonly Dictionary < NameToken , IToken > pageDictionary = new Dictionary < NameToken , IToken > ( ) ;
2021-02-10 12:27:12 -06:00
2021-02-08 12:37:09 -06:00
// streams
2021-02-06 12:24:53 -06:00
internal readonly List < IPageContentStream > contentStreams ;
2021-02-10 12:27:12 -06:00
private IPageContentStream currentStream ;
2021-02-08 12:37:09 -06:00
// maps fonts added using PdfDocumentBuilder to page font names
2021-02-07 10:37:31 -06:00
private readonly Dictionary < Guid , NameToken > documentFonts = new Dictionary < Guid , NameToken > ( ) ;
2021-02-10 12:27:12 -06:00
internal int nextFontId = 1 ;
2019-08-11 14:55:59 -04:00
//a sequence number of ShowText operation to determine whether letters belong to same operation or not (letters that belong to different operations have less changes to belong to same word)
2019-12-21 18:09:49 +00:00
private int textSequence ;
2019-08-11 14:55:59 -04:00
2020-03-09 23:01:37 +01:00
private int imageKey = 1 ;
2022-12-09 08:44:56 -05:00
internal int? rotation ;
2021-02-08 12:37:09 -06:00
internal IReadOnlyDictionary < string , IToken > Resources = > pageDictionary . GetOrCreateDict ( NameToken . Resources ) ;
2020-03-09 23:01:37 +01:00
2018-12-25 10:37:00 +00:00
/// <summary>
/// The number of this page, 1-indexed.
/// </summary>
2018-11-24 19:38:16 +00:00
public int PageNumber { get ; }
2019-04-19 21:33:31 +01:00
2018-12-25 10:37:00 +00:00
/// <summary>
/// The current size of the page.
/// </summary>
2018-11-24 19:38:16 +00:00
public PdfRectangle PageSize { get ; set ; }
2019-01-03 22:25:26 +00:00
/// <summary>
/// Access to the underlying data structures for advanced use cases.
/// </summary>
2021-02-06 12:24:53 -06:00
public IContentStream CurrentStream = > currentStream ;
2020-12-19 10:41:36 +00:00
/// <summary>
/// Access to
/// </summary>
2021-02-06 12:24:53 -06:00
public IReadOnlyList < IContentStream > ContentStreams = > contentStreams ;
2019-01-03 22:25:26 +00:00
2018-12-25 10:37:00 +00:00
internal PdfPageBuilder ( int number , PdfDocumentBuilder documentBuilder )
2018-11-24 19:38:16 +00:00
{
this . documentBuilder = documentBuilder ? ? throw new ArgumentNullException ( nameof ( documentBuilder ) ) ;
PageNumber = number ;
2020-12-19 10:41:36 +00:00
2021-02-06 12:24:53 -06:00
currentStream = new DefaultContentStream ( ) ;
contentStreams = new List < IPageContentStream > ( ) { currentStream } ;
2020-12-19 10:41:36 +00:00
}
2021-02-06 12:24:53 -06:00
internal PdfPageBuilder ( int number , PdfDocumentBuilder documentBuilder , IEnumerable < CopiedContentStream > copied ,
2021-02-08 12:37:09 -06:00
Dictionary < NameToken , IToken > pageDict )
2021-02-06 12:24:53 -06:00
{
this . documentBuilder = documentBuilder ? ? throw new ArgumentNullException ( nameof ( documentBuilder ) ) ;
PageNumber = number ;
2021-02-08 12:37:09 -06:00
pageDictionary = pageDict ;
2021-02-06 12:24:53 -06:00
contentStreams = new List < IPageContentStream > ( ) ;
contentStreams . AddRange ( copied ) ;
currentStream = new DefaultContentStream ( ) ;
contentStreams . Add ( currentStream ) ;
}
2020-12-19 10:41:36 +00:00
/// <summary>
/// Allow to append a new content stream before the current one and select it
/// </summary>
public void NewContentStreamBefore ( )
{
2021-02-06 12:24:53 -06:00
var index = Math . Max ( contentStreams . IndexOf ( currentStream ) - 1 , 0 ) ;
2020-12-19 10:41:36 +00:00
2021-02-06 12:24:53 -06:00
currentStream = new DefaultContentStream ( ) ;
contentStreams . Insert ( index , currentStream ) ;
2020-12-19 10:41:36 +00:00
}
/// <summary>
/// Allow to append a new content stream after the current one and select it
/// </summary>
public void NewContentStreamAfter ( )
{
2021-02-06 12:24:53 -06:00
var index = Math . Min ( contentStreams . IndexOf ( currentStream ) + 1 , contentStreams . Count ) ;
2020-12-19 10:41:36 +00:00
2021-02-06 12:24:53 -06:00
currentStream = new DefaultContentStream ( ) ;
contentStreams . Insert ( index , currentStream ) ;
2020-12-19 10:41:36 +00:00
}
/// <summary>
/// Select a content stream from the list, by his index
/// </summary>
/// <param name="index">index of the content stream to be selected</param>
public void SelectContentStream ( int index )
{
2021-02-06 12:24:53 -06:00
if ( index < 0 | | index > = contentStreams . Count )
2020-12-19 10:41:36 +00:00
{
throw new IndexOutOfRangeException ( nameof ( index ) ) ;
}
2021-02-06 12:24:53 -06:00
currentStream = contentStreams [ index ] ;
2018-11-24 19:38:16 +00:00
}
2018-11-25 13:56:27 +00:00
2018-12-25 10:37:00 +00:00
/// <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>
2022-12-09 08:44:56 -05:00
public PdfPageBuilder DrawLine ( PdfPoint from , PdfPoint to , decimal lineWidth = 1 )
2018-12-12 00:09:15 +00:00
{
if ( lineWidth ! = 1 )
{
2021-02-06 12:24:53 -06:00
currentStream . Add ( new SetLineWidth ( lineWidth ) ) ;
2018-12-12 00:09:15 +00:00
}
2021-02-06 12:24:53 -06:00
currentStream . Add ( new BeginNewSubpath ( ( decimal ) from . X , ( decimal ) from . Y ) ) ;
currentStream . Add ( new AppendStraightLineSegment ( ( decimal ) to . X , ( decimal ) to . Y ) ) ;
currentStream . Add ( StrokePath . Value ) ;
2018-12-12 00:09:15 +00:00
if ( lineWidth ! = 1 )
{
2021-02-06 12:24:53 -06:00
currentStream . Add ( new SetLineWidth ( 1 ) ) ;
2018-12-12 00:09:15 +00:00
}
2022-12-09 08:44:56 -05:00
return this ;
2018-12-12 00:09:15 +00:00
}
2018-12-25 10:37:00 +00:00
/// <summary>
/// Draws a rectangle on the current page starting at the specified point with the given width, height and line width.
/// </summary>
2018-12-30 14:39:49 +00:00
/// <param name="position">The position of the rectangle, for positive width and height this is the bottom-left corner.</param>
2018-12-25 10:37:00 +00:00
/// <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>
2020-11-17 17:00:13 -04:00
/// <param name="fill">Whether to fill with the color set by <see cref="SetTextAndFillColor"/>.</param>
2022-12-09 08:44:56 -05:00
public PdfPageBuilder DrawRectangle ( PdfPoint position , decimal width , decimal height , decimal lineWidth = 1 , bool fill = false )
2018-12-12 00:09:15 +00:00
{
if ( lineWidth ! = 1 )
{
2021-02-06 12:24:53 -06:00
currentStream . Add ( new SetLineWidth ( lineWidth ) ) ;
2018-12-12 00:09:15 +00:00
}
2021-02-06 12:24:53 -06:00
currentStream . Add ( new AppendRectangle ( ( decimal ) position . X , ( decimal ) position . Y , width , height ) ) ;
2020-11-17 17:00:13 -04:00
if ( fill )
{
2021-02-06 12:24:53 -06:00
currentStream . Add ( FillPathEvenOddRuleAndStroke . Value ) ;
2020-11-17 17:00:13 -04:00
}
else
{
2021-02-06 12:24:53 -06:00
currentStream . Add ( StrokePath . Value ) ;
2020-11-17 17:00:13 -04:00
}
2018-12-12 00:09:15 +00:00
if ( lineWidth ! = 1 )
{
2021-02-06 12:24:53 -06:00
currentStream . Add ( new SetLineWidth ( lineWidth ) ) ;
2018-12-12 00:09:15 +00:00
}
2022-12-09 08:44:56 -05:00
return this ;
}
/// <summary>
/// Set the number of degrees by which the page is rotated clockwise when displayed or printed.
/// </summary>
public PdfPageBuilder SetRotation ( PageRotationDegrees degrees )
{
rotation = degrees . Value ;
return this ;
2018-12-12 00:09:15 +00:00
}
2022-06-08 13:20:06 +02:00
/// <summary>
/// Draws a triangle on the current page with the specified points and line width.
/// </summary>
/// <param name="point1">Position of the first corner of the triangle.</param>
/// <param name="point2">Position of the second corner of the triangle.</param>
/// <param name="point3">Position of the third corner of the triangle.</param>
/// <param name="lineWidth">The width of the line border of the triangle.</param>
/// <param name="fill">Whether to fill with the color set by <see cref="SetTextAndFillColor"/>.</param>
2022-12-09 08:44:56 -05:00
public PdfPageBuilder DrawTriangle ( PdfPoint point1 , PdfPoint point2 , PdfPoint point3 , decimal lineWidth = 1 , bool fill = false )
2022-06-08 13:20:06 +02:00
{
if ( lineWidth ! = 1 )
{
currentStream . Add ( new SetLineWidth ( lineWidth ) ) ;
}
currentStream . Add ( new BeginNewSubpath ( ( decimal ) point1 . X , ( decimal ) point1 . Y ) ) ;
currentStream . Add ( new AppendStraightLineSegment ( ( decimal ) point2 . X , ( decimal ) point2 . Y ) ) ;
currentStream . Add ( new AppendStraightLineSegment ( ( decimal ) point3 . X , ( decimal ) point3 . Y ) ) ;
currentStream . Add ( new AppendStraightLineSegment ( ( decimal ) point1 . X , ( decimal ) point1 . Y ) ) ;
if ( fill )
{
currentStream . Add ( FillPathEvenOddRuleAndStroke . Value ) ;
}
else
{
currentStream . Add ( StrokePath . Value ) ;
}
if ( lineWidth ! = 1 )
{
currentStream . Add ( new SetLineWidth ( lineWidth ) ) ;
}
2022-12-09 08:44:56 -05:00
return this ;
2022-06-08 13:20:06 +02:00
}
/// <summary>
/// Draws a circle on the current page centering at the specified point with the given diameter and line width.
/// </summary>
/// <param name="center">The center position of the circle.</param>
/// <param name="diameter">The diameter of the circle.</param>
/// <param name="lineWidth">The width of the line border of the circle.</param>
/// <param name="fill">Whether to fill with the color set by <see cref="SetTextAndFillColor"/>.</param>
2022-12-09 08:44:56 -05:00
public PdfPageBuilder DrawCircle ( PdfPoint center , decimal diameter , decimal lineWidth = 1 , bool fill = false )
2022-06-08 13:20:06 +02:00
{
DrawEllipsis ( center , diameter , diameter , lineWidth , fill ) ;
2022-12-09 08:44:56 -05:00
return this ;
2022-06-08 13:20:06 +02:00
}
/// <summary>
/// Draws an ellipsis on the current page centering at the specified point with the given width, height and line width.
/// </summary>
/// <param name="center">The center position of the ellipsis.</param>
/// <param name="width">The width of the ellipsis.</param>
/// <param name="height">The height of the ellipsis.</param>
/// <param name="lineWidth">The width of the line border of the ellipsis.</param>
/// <param name="fill">Whether to fill with the color set by <see cref="SetTextAndFillColor"/>.</param>
2022-12-09 08:44:56 -05:00
public PdfPageBuilder DrawEllipsis ( PdfPoint center , decimal width , decimal height , decimal lineWidth = 1 , bool fill = false )
2022-06-08 13:20:06 +02:00
{
width / = 2 ;
height / = 2 ;
// See here: https://spencermortensen.com/articles/bezier-circle/
2022-06-09 07:49:50 +02:00
decimal cc = 0.55228474983079 m ;
2022-06-08 13:20:06 +02:00
if ( lineWidth ! = 1 )
{
currentStream . Add ( new SetLineWidth ( lineWidth ) ) ;
}
currentStream . Add ( new BeginNewSubpath ( ( decimal ) center . X - width , ( decimal ) center . Y ) ) ;
currentStream . Add ( new AppendDualControlPointBezierCurve (
( decimal ) center . X - width , ( decimal ) center . Y + height * cc ,
( decimal ) center . X - width * cc , ( decimal ) center . Y + height ,
( decimal ) center . X , ( decimal ) center . Y + height
) ) ;
currentStream . Add ( new AppendDualControlPointBezierCurve (
( decimal ) center . X + width * cc , ( decimal ) center . Y + height ,
( decimal ) center . X + width , ( decimal ) center . Y + height * cc ,
( decimal ) center . X + width , ( decimal ) center . Y
) ) ;
currentStream . Add ( new AppendDualControlPointBezierCurve (
( decimal ) center . X + width , ( decimal ) center . Y - height * cc ,
( decimal ) center . X + width * cc , ( decimal ) center . Y - height ,
( decimal ) center . X , ( decimal ) center . Y - height
) ) ;
currentStream . Add ( new AppendDualControlPointBezierCurve (
( decimal ) center . X - width * cc , ( decimal ) center . Y - height ,
( decimal ) center . X - width , ( decimal ) center . Y - height * cc ,
( decimal ) center . X - width , ( decimal ) center . Y
) ) ;
if ( fill )
{
currentStream . Add ( FillPathEvenOddRuleAndStroke . Value ) ;
}
else
{
currentStream . Add ( StrokePath . Value ) ;
}
if ( lineWidth ! = 1 )
{
currentStream . Add ( new SetLineWidth ( lineWidth ) ) ;
}
2022-12-09 08:44:56 -05:00
return this ;
2022-06-08 13:20:06 +02:00
}
2018-12-30 14:12:04 +00:00
/// <summary>
/// Sets the stroke 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>
2022-12-09 08:44:56 -05:00
public PdfPageBuilder SetStrokeColor ( byte r , byte g , byte b )
2018-12-30 14:12:04 +00:00
{
2021-02-06 12:24:53 -06:00
currentStream . Add ( Push . Value ) ;
currentStream . Add ( new SetStrokeColorDeviceRgb ( RgbToDecimal ( r ) , RgbToDecimal ( g ) , RgbToDecimal ( b ) ) ) ;
2022-12-09 08:44:56 -05:00
return this ;
2018-12-30 14:12:04 +00:00
}
/// <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>
2022-12-09 08:44:56 -05:00
internal PdfPageBuilder SetStrokeColorExact ( decimal r , decimal g , decimal b )
2018-12-30 14:12:04 +00:00
{
2021-02-06 12:24:53 -06:00
currentStream . Add ( Push . Value ) ;
currentStream . Add ( new SetStrokeColorDeviceRgb ( CheckRgbDecimal ( r , nameof ( r ) ) ,
2018-12-30 14:12:04 +00:00
CheckRgbDecimal ( g , nameof ( g ) ) , CheckRgbDecimal ( b , nameof ( b ) ) ) ) ;
2022-12-09 08:44:56 -05:00
return this ;
2018-12-30 14:12:04 +00:00
}
/// <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>
2022-12-09 08:44:56 -05:00
public PdfPageBuilder SetTextAndFillColor ( byte r , byte g , byte b )
2018-12-30 14:12:04 +00:00
{
2021-02-06 12:24:53 -06:00
currentStream . Add ( Push . Value ) ;
currentStream . Add ( new SetNonStrokeColorDeviceRgb ( RgbToDecimal ( r ) , RgbToDecimal ( g ) , RgbToDecimal ( b ) ) ) ;
2022-12-09 08:44:56 -05:00
return this ;
2018-12-30 14:12:04 +00:00
}
/// <summary>
/// Restores the stroke, text and fill color to default (black).
/// </summary>
2022-12-09 08:44:56 -05:00
public PdfPageBuilder ResetColor ( )
2018-12-30 14:12:04 +00:00
{
2021-02-06 12:24:53 -06:00
currentStream . Add ( Pop . Value ) ;
2022-12-09 08:44:56 -05:00
return this ;
2018-12-30 14:12:04 +00:00
}
2018-12-25 10:37:00 +00:00
/// <summary>
/// Calculates the size and position of each letter in a given string in the provided font without changing the state of the page.
/// </summary>
/// <param name="text">The text to measure each letter of.</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 > MeasureText ( string text , decimal fontSize , PdfPoint position , PdfDocumentBuilder . AddedFont font )
2018-11-25 13:56:27 +00:00
{
if ( font = = null )
{
throw new ArgumentNullException ( nameof ( font ) ) ;
}
if ( text = = null )
{
throw new ArgumentNullException ( nameof ( text ) ) ;
}
2020-12-20 19:13:19 +00:00
if ( ! documentBuilder . Fonts . TryGetValue ( font . Id , out var fontStore ) )
2018-11-25 13:56:27 +00:00
{
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" ) ;
}
2020-12-20 19:13:19 +00:00
var fontProgram = fontStore . FontProgram ;
2018-12-11 21:29:39 +00:00
var fm = fontProgram . GetFontMatrix ( ) ;
2018-12-08 18:04:02 +00:00
var textMatrix = TransformationMatrix . FromValues ( 1 , 0 , 0 , 1 , position . X , position . Y ) ;
2018-11-27 20:00:38 +00:00
2021-02-06 15:13:22 -06:00
var letters = DrawLetters ( null , text , fontProgram , fm , fontSize , textMatrix ) ;
2018-12-25 10:37:00 +00:00
return letters ;
}
/// <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 )
2018-11-27 20:00:38 +00:00
{
2018-12-25 10:37:00 +00:00
throw new ArgumentNullException ( nameof ( font ) ) ;
}
2018-11-27 20:00:38 +00:00
2018-12-25 10:37:00 +00:00
if ( text = = null )
{
throw new ArgumentNullException ( nameof ( text ) ) ;
}
2018-11-27 20:00:38 +00:00
2020-12-20 19:13:19 +00:00
if ( ! documentBuilder . Fonts . TryGetValue ( font . Id , out var fontStore ) )
2018-12-25 10:37:00 +00:00
{
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 ) ) ;
2018-11-27 20:00:38 +00:00
}
2018-12-25 10:37:00 +00:00
if ( fontSize < = 0 )
2018-11-27 20:00:38 +00:00
{
2018-12-25 10:37:00 +00:00
throw new ArgumentOutOfRangeException ( nameof ( fontSize ) , "Font size must be greater than 0" ) ;
2018-11-27 20:00:38 +00:00
}
2018-11-25 13:56:27 +00:00
2021-02-06 15:13:22 -06:00
var fontName = GetAddedFont ( font ) ;
2020-12-20 19:13:19 +00:00
var fontProgram = fontStore . FontProgram ;
2018-12-25 10:37:00 +00:00
var fm = fontProgram . GetFontMatrix ( ) ;
var textMatrix = TransformationMatrix . FromValues ( 1 , 0 , 0 , 1 , position . X , position . Y ) ;
2021-02-06 15:13:22 -06:00
var letters = DrawLetters ( fontName , text , fontProgram , fm , fontSize , textMatrix ) ;
2018-12-25 10:37:00 +00:00
2021-02-06 12:24:53 -06:00
currentStream . Add ( BeginText . Value ) ;
2021-02-06 15:13:22 -06:00
currentStream . Add ( new SetFontAndSize ( fontName , fontSize ) ) ;
2021-02-06 12:24:53 -06:00
currentStream . Add ( new MoveToNextLineWithOffset ( ( decimal ) position . X , ( decimal ) position . Y ) ) ;
2020-01-04 10:04:02 +00:00
var bytesPerShow = new List < byte > ( ) ;
2019-12-28 14:42:27 +00:00
foreach ( var letter in text )
{
2020-01-04 10:04:02 +00:00
if ( char . IsWhiteSpace ( letter ) )
{
2021-02-06 12:24:53 -06:00
currentStream . Add ( new ShowText ( bytesPerShow . ToArray ( ) ) ) ;
2020-01-04 10:04:02 +00:00
bytesPerShow . Clear ( ) ;
}
2019-12-28 14:42:27 +00:00
var b = fontProgram . GetValueForCharacter ( letter ) ;
2020-01-04 10:04:02 +00:00
bytesPerShow . Add ( b ) ;
}
if ( bytesPerShow . Count > 0 )
{
2021-02-06 12:24:53 -06:00
currentStream . Add ( new ShowText ( bytesPerShow . ToArray ( ) ) ) ;
2019-12-28 14:42:27 +00:00
}
2021-02-06 12:24:53 -06:00
currentStream . Add ( EndText . Value ) ;
2018-12-25 10:37:00 +00:00
2018-12-08 18:04:02 +00:00
return letters ;
2021-02-07 10:37:31 -06:00
}
2022-02-15 11:26:13 +00:00
/// <summary>
/// Set the text rendering mode. This will apply to all future calls to AddText until called again.
///
/// To insert invisible text, for example output of OCR, use <c>TextRenderingMode.Neither</c>.
/// </summary>
/// <param name="mode">Text rendering mode to set.</param>
2022-12-09 08:44:56 -05:00
public PdfPageBuilder SetTextRenderingMode ( TextRenderingMode mode )
2022-02-15 11:26:13 +00:00
{
currentStream . Add ( new SetTextRenderingMode ( mode ) ) ;
2022-12-09 08:44:56 -05:00
return this ;
2022-02-15 11:26:13 +00:00
}
2021-02-07 10:37:31 -06:00
private NameToken GetAddedFont ( PdfDocumentBuilder . AddedFont font )
2021-02-06 15:13:22 -06:00
{
if ( ! documentFonts . TryGetValue ( font . Id , out NameToken value ) )
{
2021-02-07 10:37:31 -06:00
value = NameToken . Create ( $"F{nextFontId++}" ) ;
2021-02-08 12:37:09 -06:00
var resources = pageDictionary . GetOrCreateDict ( NameToken . Resources ) ;
var fonts = resources . GetOrCreateDict ( NameToken . Font ) ;
while ( fonts . ContainsKey ( value ) )
2021-02-06 15:13:22 -06:00
{
value = NameToken . Create ( $"F{nextFontId++}" ) ;
}
documentFonts [ font . Id ] = value ;
2021-02-08 12:37:09 -06:00
fonts [ value ] = font . Reference ;
2021-02-06 15:13:22 -06:00
}
return value ;
2018-11-25 13:56:27 +00:00
}
2020-03-09 23:01:37 +01:00
/// <summary>
/// Adds the JPEG image represented by the input bytes at the specified location.
/// </summary>
2020-03-15 17:30:30 +00:00
public AddedImage AddJpeg ( byte [ ] fileBytes , PdfRectangle placementRectangle )
2020-03-09 23:01:37 +01:00
{
using ( var stream = new MemoryStream ( fileBytes ) )
{
2020-03-15 17:30:30 +00:00
return AddJpeg ( stream , placementRectangle ) ;
2020-03-09 23:01:37 +01:00
}
}
/// <summary>
/// Adds the JPEG image represented by the input stream at the specified location.
/// </summary>
2020-03-15 17:30:30 +00:00
public AddedImage AddJpeg ( Stream fileStream , PdfRectangle placementRectangle )
2020-03-09 23:01:37 +01:00
{
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 ) ;
2021-02-10 12:27:12 -06:00
var resources = pageDictionary . GetOrCreateDict ( NameToken . Resources ) ;
2021-02-08 12:37:09 -06:00
var xObjects = resources . GetOrCreateDict ( NameToken . Xobject ) ;
2020-03-09 23:01:37 +01:00
var key = NameToken . Create ( $"I{imageKey++}" ) ;
2021-02-08 12:37:09 -06:00
xObjects [ key ] = reference ;
2020-03-09 23:01:37 +01:00
2021-02-06 12:24:53 -06:00
currentStream . Add ( Push . Value ) ;
2020-03-09 23:01:37 +01:00
// This needs to be the placement rectangle.
2021-02-06 12:24:53 -06:00
currentStream . Add ( new ModifyCurrentTransformationMatrix ( new [ ]
2020-03-09 23:01:37 +01:00
{
( decimal ) placementRectangle . Width , 0 ,
0 , ( decimal ) placementRectangle . Height ,
( decimal ) placementRectangle . BottomLeft . X , ( decimal ) placementRectangle . BottomLeft . Y
} ) ) ;
2021-02-06 12:24:53 -06:00
currentStream . Add ( new InvokeNamedXObject ( key ) ) ;
currentStream . Add ( Pop . Value ) ;
2020-03-15 17:30:30 +00:00
2021-02-06 12:24:53 -06:00
return new AddedImage ( reference . Data , info . Width , info . Height ) ;
2020-03-15 17:30:30 +00:00
}
/// <summary>
/// Adds the JPEG image previously added using <see cref="AddJpeg(byte[],PdfRectangle)"/>,
/// this will share the same image data to prevent duplication.
/// </summary>
/// <param name="image">An image previously added to this page or another page.</param>
/// <param name="placementRectangle">The size and location to draw the image on this page.</param>
2020-08-22 15:08:59 +01:00
public void AddJpeg ( AddedImage image , PdfRectangle placementRectangle ) = > AddImage ( image , placementRectangle ) ;
/// <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 )
2020-03-15 17:30:30 +00:00
{
2021-02-08 12:37:09 -06:00
var resources = pageDictionary . GetOrCreateDict ( NameToken . Resources ) ;
var xObjects = resources . GetOrCreateDict ( NameToken . Xobject ) ;
2020-03-15 17:30:30 +00:00
var key = NameToken . Create ( $"I{imageKey++}" ) ;
2021-02-08 12:37:09 -06:00
xObjects [ key ] = new IndirectReferenceToken ( image . Reference ) ;
2020-03-15 17:30:30 +00:00
2021-02-06 12:24:53 -06:00
currentStream . Add ( Push . Value ) ;
2020-03-15 17:30:30 +00:00
// This needs to be the placement rectangle.
2021-02-06 12:24:53 -06:00
currentStream . Add ( new ModifyCurrentTransformationMatrix ( new [ ]
2020-03-15 17:30:30 +00:00
{
( decimal ) placementRectangle . Width , 0 ,
0 , ( decimal ) placementRectangle . Height ,
( decimal ) placementRectangle . BottomLeft . X , ( decimal ) placementRectangle . BottomLeft . Y
} ) ) ;
2021-02-06 12:24:53 -06:00
currentStream . Add ( new InvokeNamedXObject ( key ) ) ;
currentStream . Add ( Pop . Value ) ;
2020-03-09 23:01:37 +01:00
}
2020-08-21 10:50:17 +01:00
/// <summary>
/// Adds the PNG image represented by the input bytes at the specified location.
/// </summary>
public AddedImage AddPng ( byte [ ] pngBytes , PdfRectangle placementRectangle )
{
using ( var memoryStream = new MemoryStream ( pngBytes ) )
{
return AddPng ( memoryStream , placementRectangle ) ;
}
}
/// <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 ( ) ;
}
2021-08-07 13:49:01 -04:00
var widthToken = new NumericToken ( png . Width ) ;
var heightToken = new NumericToken ( png . Height ) ;
IndirectReferenceToken smaskReference = null ;
2021-09-28 04:51:37 +03:00
if ( png . HasAlphaChannel & & documentBuilder . ArchiveStandard ! = PdfAStandard . A1B & & documentBuilder . ArchiveStandard ! = PdfAStandard . A1A )
2021-08-07 13:49:01 -04:00
{
var smaskData = new byte [ data . Length / 3 ] ;
for ( var rowIndex = 0 ; rowIndex < png . Height ; rowIndex + + )
{
for ( var colIndex = 0 ; colIndex < png . Width ; colIndex + + )
{
var pixel = png . GetPixel ( colIndex , rowIndex ) ;
var index = rowIndex * png . Width + colIndex ;
smaskData [ index ] = pixel . A ;
}
}
var compressedSmask = DataCompresser . CompressBytes ( smaskData ) ;
// Create a soft-mask.
var smaskDictionary = new Dictionary < NameToken , IToken >
{
{ NameToken . Type , NameToken . Xobject } ,
{ NameToken . Subtype , NameToken . Image } ,
{ NameToken . Width , widthToken } ,
{ NameToken . Height , heightToken } ,
{ NameToken . ColorSpace , NameToken . Devicegray } ,
{ NameToken . BitsPerComponent , new NumericToken ( png . Header . BitDepth ) } ,
{ NameToken . Decode , new ArrayToken ( new IToken [ ] { new NumericToken ( 0 ) , new NumericToken ( 1 ) } ) } ,
{ NameToken . Length , new NumericToken ( compressedSmask . Length ) } ,
{ NameToken . Filter , NameToken . FlateDecode }
} ;
smaskReference = documentBuilder . AddImage ( new DictionaryToken ( smaskDictionary ) , compressedSmask ) ;
}
2020-08-21 10:50:17 +01:00
var compressed = DataCompresser . CompressBytes ( data ) ;
var imgDictionary = new Dictionary < NameToken , IToken >
{
2021-08-07 13:49:01 -04:00
{ NameToken . Type , NameToken . Xobject } ,
{ NameToken . Subtype , NameToken . Image } ,
{ NameToken . Width , widthToken } ,
{ NameToken . Height , heightToken } ,
2020-08-21 10:50:17 +01:00
{ NameToken . BitsPerComponent , new NumericToken ( png . Header . BitDepth ) } ,
{ NameToken . ColorSpace , NameToken . Devicergb } ,
{ NameToken . Filter , NameToken . FlateDecode } ,
{ NameToken . Length , new NumericToken ( compressed . Length ) }
} ;
2021-08-07 13:49:01 -04:00
if ( smaskReference ! = null )
{
imgDictionary . Add ( NameToken . Smask , smaskReference ) ;
}
2020-08-21 10:50:17 +01:00
var reference = documentBuilder . AddImage ( new DictionaryToken ( imgDictionary ) , compressed ) ;
2021-02-08 12:37:09 -06:00
var resources = pageDictionary . GetOrCreateDict ( NameToken . Resources ) ;
var xObjects = resources . GetOrCreateDict ( NameToken . Xobject ) ;
2020-08-21 10:50:17 +01:00
var key = NameToken . Create ( $"I{imageKey++}" ) ;
2021-02-08 12:37:09 -06:00
xObjects [ key ] = reference ;
2020-08-21 10:50:17 +01:00
2021-02-06 12:24:53 -06:00
currentStream . Add ( Push . Value ) ;
2020-08-21 10:50:17 +01:00
// This needs to be the placement rectangle.
2021-02-06 12:24:53 -06:00
currentStream . Add ( new ModifyCurrentTransformationMatrix ( new [ ]
2020-08-21 10:50:17 +01:00
{
( decimal ) placementRectangle . Width , 0 ,
0 , ( decimal ) placementRectangle . Height ,
( decimal ) placementRectangle . BottomLeft . X , ( decimal ) placementRectangle . BottomLeft . Y
} ) ) ;
2021-02-06 12:24:53 -06:00
currentStream . Add ( new InvokeNamedXObject ( key ) ) ;
currentStream . Add ( Pop . Value ) ;
2020-08-21 10:50:17 +01:00
2021-02-06 12:24:53 -06:00
return new AddedImage ( reference . Data , png . Width , png . Height ) ;
2020-08-21 10:50:17 +01:00
}
2020-12-20 19:13:19 +00:00
/// <summary>
/// Copy a page from unknown source to this page
/// </summary>
/// <param name="srcPage">Page to be copied</param>
2022-12-09 08:44:56 -05:00
public PdfPageBuilder CopyFrom ( Page srcPage )
2020-12-20 19:13:19 +00:00
{
2021-02-06 12:24:53 -06:00
if ( currentStream . Operations . Count > 0 )
2020-12-20 19:13:19 +00:00
{
NewContentStreamAfter ( ) ;
}
2021-02-06 12:24:53 -06:00
var destinationStream = currentStream ;
2020-12-20 19:13:19 +00:00
if ( ! srcPage . Dictionary . TryGet ( NameToken . Resources , srcPage . pdfScanner , out DictionaryToken srcResourceDictionary ) )
{
// If the page doesn't have resources, then we copy the entire content stream, since not operation would collide
// with the ones already written
destinationStream . Operations . AddRange ( srcPage . Operations ) ;
2022-12-09 08:44:56 -05:00
return this ;
2020-12-20 19:13:19 +00:00
}
// TODO: How should we handle any other token in the page dictionary (Eg. LastModified, MediaBox, CropBox, BleedBox, TrimBox, ArtBox,
// BoxColorInfo, Rotate, Group, Thumb, B, Dur, Trans, Annots, AA, Metadata, PieceInfo, StructParents, ID, PZ, SeparationInfo, Tabs,
// TemplateInstantiated, PresSteps, UserUnit, VP)
var operations = new List < IGraphicsStateOperation > ( srcPage . Operations ) ;
// We need to relocate the resources, and we have to make sure that none of the resources collide with
// the already written operation's resources
2021-02-08 12:37:09 -06:00
var resources = pageDictionary . GetOrCreateDict ( NameToken . Resources ) ;
2020-12-20 19:13:19 +00:00
foreach ( var set in srcResourceDictionary . Data )
{
var nameToken = NameToken . Create ( set . Key ) ;
if ( nameToken = = NameToken . Font | | nameToken = = NameToken . Xobject )
{
// We have to skip this two because we have a separate dictionary for them
continue ;
}
2021-02-08 12:37:09 -06:00
if ( ! resources . ContainsKey ( nameToken ) )
2020-12-20 19:13:19 +00:00
{
// It means that this type of resources doesn't currently exist in the page, so we can copy it
// with no problem
2021-02-08 12:37:09 -06:00
resources [ nameToken ] = documentBuilder . CopyToken ( srcPage . pdfScanner , set . Value ) ;
2020-12-20 19:13:19 +00:00
continue ;
}
// TODO: I need to find a test case
// It would have ExtendedGraphics or colorspaces, etc...
}
// Special cases
// Since we don't directly add font's to the pages resources, we have to go look at the document's font
if ( srcResourceDictionary . TryGet ( NameToken . Font , srcPage . pdfScanner , out DictionaryToken fontsDictionary ) )
2021-02-10 12:27:12 -06:00
{
2021-02-08 12:37:09 -06:00
var pageFontsDictionary = resources . GetOrCreateDict ( NameToken . Font ) ;
2020-12-20 19:13:19 +00:00
foreach ( var fontSet in fontsDictionary . Data )
{
2021-02-06 15:13:22 -06:00
var fontName = NameToken . Create ( fontSet . Key ) ;
2021-02-08 12:37:09 -06:00
if ( pageFontsDictionary . ContainsKey ( fontName ) )
2020-12-20 19:13:19 +00:00
{
// This would mean that the imported font collide with one of the added font. so we have to rename it
2021-02-06 15:13:22 -06:00
var newName = NameToken . Create ( $"F{nextFontId++}" ) ;
2021-02-08 12:37:09 -06:00
while ( pageFontsDictionary . ContainsKey ( newName ) )
2021-02-06 15:13:22 -06:00
{
newName = NameToken . Create ( $"F{nextFontId++}" ) ;
}
2020-12-20 19:13:19 +00:00
// Set all the pertinent SetFontAndSize operations with the new name
operations = operations . Select ( op = >
{
if ( ! ( op is SetFontAndSize fontAndSizeOperation ) )
{
return op ;
}
if ( fontAndSizeOperation . Font . Data = = fontName )
{
2021-02-06 15:13:22 -06:00
return new SetFontAndSize ( newName , fontAndSizeOperation . Size ) ;
2020-12-20 19:13:19 +00:00
}
return op ;
} ) . ToList ( ) ;
fontName = newName ;
}
if ( ! ( fontSet . Value is IndirectReferenceToken fontReferenceToken ) )
{
throw new PdfDocumentFormatException ( $"Expected a IndirectReferenceToken for the font, got a {fontSet.Value.GetType().Name}" ) ;
}
2021-02-06 15:13:22 -06:00
pageFontsDictionary . Add ( fontName , documentBuilder . CopyToken ( srcPage . pdfScanner , fontReferenceToken ) ) ;
2020-12-20 19:13:19 +00:00
}
}
// Since we don't directly add xobjects's to the pages resources, we have to go look at the document's xobjects
if ( srcResourceDictionary . TryGet ( NameToken . Xobject , srcPage . pdfScanner , out DictionaryToken xobjectsDictionary ) )
{
2021-02-08 12:37:09 -06:00
var pageXobjectsDictionary = resources . GetOrCreateDict ( NameToken . Xobject ) ;
2020-12-20 19:13:19 +00:00
var xobjectNamesUsed = Enumerable . Range ( 0 , imageKey ) . Select ( i = > $"I{i}" ) ;
foreach ( var xobjectSet in xobjectsDictionary . Data )
{
var xobjectName = xobjectSet . Key ;
if ( xobjectName [ 0 ] = = 'I' & & xobjectNamesUsed . Any ( s = > s = = xobjectName ) )
{
// This would mean that the imported xobject collide with one of the added image. so we have to rename it
var newName = $"I{imageKey++}" ;
// Set all the pertinent SetFontAndSize operations with the new name
operations = operations . Select ( op = >
{
if ( ! ( op is InvokeNamedXObject invokeNamedOperation ) )
{
return op ;
}
if ( invokeNamedOperation . Name . Data = = xobjectName )
{
return new InvokeNamedXObject ( NameToken . Create ( newName ) ) ;
}
return op ;
} ) . ToList ( ) ;
xobjectName = newName ;
}
if ( ! ( xobjectSet . Value is IndirectReferenceToken fontReferenceToken ) )
{
throw new PdfDocumentFormatException ( $"Expected a IndirectReferenceToken for the XObject, got a {xobjectSet.Value.GetType().Name}" ) ;
}
2021-02-08 12:37:09 -06:00
pageXobjectsDictionary [ xobjectName ] = documentBuilder . CopyToken ( srcPage . pdfScanner , fontReferenceToken ) ;
2020-12-20 19:13:19 +00:00
}
}
destinationStream . Operations . AddRange ( operations ) ;
2022-12-09 08:44:56 -05:00
return this ;
2020-12-20 19:13:19 +00:00
}
2021-02-06 15:13:22 -06:00
private List < Letter > DrawLetters ( NameToken name , string text , IWritingFont font , TransformationMatrix fontMatrix , decimal fontSize , TransformationMatrix textMatrix )
2018-11-25 13:56:27 +00:00
{
2018-12-08 18:04:02 +00:00
var horizontalScaling = 1 ;
var rise = 0 ;
var letters = new List < Letter > ( ) ;
var renderingMatrix =
2019-12-21 18:09:49 +00:00
TransformationMatrix . FromValues ( ( double ) fontSize * horizontalScaling , 0 , 0 , ( double ) fontSize , 0 , rise ) ;
2018-12-08 18:04:02 +00:00
2019-12-21 18:09:49 +00:00
var width = 0.0 ;
2018-12-08 18:04:02 +00:00
2019-08-11 14:55:59 -04:00
textSequence + + ;
2018-11-25 13:56:27 +00:00
for ( var i = 0 ; i < text . Length ; i + + )
{
var c = text [ i ] ;
2018-12-25 10:37:00 +00:00
if ( ! font . TryGetBoundingBox ( c , out var rect ) )
2018-11-25 13:56:27 +00:00
{
2022-12-20 14:17:02 +10:00
throw new InvalidOperationException ( $"The font does not contain a character: '{c}' (0x{(int)c:X})." ) ;
2018-11-25 13:56:27 +00:00
}
2018-12-08 18:04:02 +00:00
if ( ! font . TryGetAdvanceWidth ( c , out var charWidth ) )
{
throw new InvalidOperationException ( $"The font does not contain a character: {c}." ) ;
}
var advanceRect = new PdfRectangle ( 0 , 0 , charWidth , 0 ) ;
advanceRect = textMatrix . Transform ( renderingMatrix . Transform ( fontMatrix . Transform ( advanceRect ) ) ) ;
var documentSpace = textMatrix . Transform ( renderingMatrix . Transform ( fontMatrix . Transform ( rect ) ) ) ;
2023-01-13 12:35:25 +01:00
var letter = new Letter (
c . ToString ( ) ,
documentSpace ,
advanceRect . BottomLeft ,
advanceRect . BottomRight ,
width ,
( double ) fontSize ,
FontDetails . GetDefault ( name ) ,
TextRenderingMode . Fill ,
GrayColor . Black ,
2019-12-21 18:09:49 +00:00
GrayColor . Black ,
( double ) fontSize ,
2019-08-16 18:34:57 -04:00
textSequence ) ;
2018-12-08 18:04:02 +00:00
letters . Add ( letter ) ;
2018-12-25 10:37:00 +00:00
2018-12-08 18:04:02 +00:00
var tx = advanceRect . Width * horizontalScaling ;
var ty = 0 ;
var translate = TransformationMatrix . GetTranslationMatrix ( tx , ty ) ;
width + = tx ;
textMatrix = translate . Multiply ( textMatrix ) ;
2018-11-25 13:56:27 +00:00
}
2018-12-08 18:04:02 +00:00
return letters ;
2018-11-25 13:56:27 +00:00
}
2018-12-30 14:12:04 +00:00
private static decimal RgbToDecimal ( byte value )
{
var res = Math . Max ( 0 , value / ( decimal ) byte . MaxValue ) ;
2020-01-04 10:04:02 +00:00
res = Math . Round ( Math . Min ( 1 , res ) , 4 ) ;
2018-12-30 14:12:04 +00:00
return res ;
}
private static decimal CheckRgbDecimal ( decimal value , string argument )
{
if ( value < 0 )
{
throw new ArgumentOutOfRangeException ( argument , $"Provided decimal for RGB color was less than zero: {value}." ) ;
}
if ( value > 1 )
{
throw new ArgumentOutOfRangeException ( argument , $"Provided decimal for RGB color was greater than one: {value}." ) ;
}
return value ;
}
2019-01-03 22:25:26 +00:00
2021-02-07 10:37:31 -06:00
/// <summary>
/// Provides access to the raw page data structures for advanced editing use cases.
2021-02-06 12:24:53 -06:00
/// </summary>
public interface IContentStream
2021-02-07 10:37:31 -06:00
{
/// <summary>
/// The operations making up the page content stream.
2019-01-03 22:25:26 +00:00
/// </summary>
2021-02-06 12:24:53 -06:00
List < IGraphicsStateOperation > Operations { get ; }
}
2021-02-10 20:18:03 -06:00
internal interface IPageContentStream : IContentStream
{
bool ReadOnly { get ; }
bool HasContent { get ; }
void Add ( IGraphicsStateOperation operation ) ;
IndirectReferenceToken Write ( IPdfStreamWriter writer ) ;
}
2021-02-06 12:24:53 -06:00
internal class DefaultContentStream : IPageContentStream
{
private readonly List < IGraphicsStateOperation > operations ;
public DefaultContentStream ( ) : this ( new List < IGraphicsStateOperation > ( ) )
{
}
public DefaultContentStream ( List < IGraphicsStateOperation > operations )
{
this . operations = operations ;
}
public bool ReadOnly = > false ;
2021-02-07 10:37:31 -06:00
public bool HasContent = > operations . Any ( ) ;
2021-02-06 12:24:53 -06:00
public void Add ( IGraphicsStateOperation operation )
{
operations . Add ( operation ) ;
}
public List < IGraphicsStateOperation > Operations = > operations ;
public IndirectReferenceToken Write ( IPdfStreamWriter writer )
2020-12-19 10:41:36 +00:00
{
2021-02-06 12:24:53 -06:00
using ( var memoryStream = new MemoryStream ( ) )
{
foreach ( var operation in operations )
{
operation . Write ( memoryStream ) ;
}
var bytes = memoryStream . ToArray ( ) ;
var stream = DataCompresser . CompressToStream ( bytes ) ;
return writer . WriteToken ( stream ) ;
}
2020-12-19 10:41:36 +00:00
}
2021-02-06 12:24:53 -06:00
}
internal class CopiedContentStream : IPageContentStream
{
private readonly IndirectReferenceToken token ;
2021-02-07 10:37:31 -06:00
public bool ReadOnly = > true ;
2021-02-08 12:37:09 -06:00
public bool HasContent = > true ;
2021-02-06 12:24:53 -06:00
public CopiedContentStream ( IndirectReferenceToken indirectReferenceToken )
{
token = indirectReferenceToken ;
}
public IndirectReferenceToken Write ( IPdfStreamWriter writer )
{
return token ;
}
2021-02-07 10:37:31 -06:00
2021-02-06 12:24:53 -06:00
public void Add ( IGraphicsStateOperation operation )
2019-01-03 22:25:26 +00:00
{
2021-02-06 12:24:53 -06:00
throw new NotSupportedException ( "Writing to a copied content stream is not supported." ) ;
2019-01-03 22:25:26 +00:00
}
2021-02-06 12:24:53 -06:00
2021-02-07 10:37:31 -06:00
public List < IGraphicsStateOperation > Operations = >
2021-02-06 12:24:53 -06:00
throw new NotSupportedException ( "Reading raw operations is not supported from a copied content stream." ) ;
2019-01-03 22:25:26 +00:00
}
2020-03-15 17:30:30 +00:00
2021-02-06 12:24:53 -06:00
2020-03-15 17:30:30 +00:00
/// <summary>
/// A key representing an image available to use for the current document builder.
/// Create it by adding an image to a page using <see cref="AddJpeg(byte[],PdfRectangle)"/>.
/// </summary>
public class AddedImage
{
/// <summary>
/// The Id uniquely identifying this image on the builder.
/// </summary>
internal Guid Id { get ; }
/// <summary>
/// The reference to the stored image XObject.
/// </summary>
internal IndirectReference Reference { get ; }
/// <summary>
/// The width of the raw image in pixels.
/// </summary>
public int Width { get ; }
/// <summary>
/// The height of the raw image in pixels.
/// </summary>
public int Height { get ; }
/// <summary>
/// Create a new <see cref="AddedImage"/>.
/// </summary>
internal AddedImage ( IndirectReference reference , int width , int height )
{
Id = Guid . NewGuid ( ) ;
Reference = reference ;
Width = width ;
Height = height ;
}
}
2021-02-08 12:37:09 -06:00
2018-11-24 19:38:16 +00:00
}
2021-09-28 04:51:37 +03:00
}