Difference between revisions of "Graphics System in PDF"

From
Jump to: navigation, search
(ColorSpaces)
(ColorSpaces)
Line 274: Line 274:
 
Colors in [[PDF]] are defined using colorspaces and color values mapped to these spaces. All available colorspaces are described in section ''8.6 “Colour Spaces”'' of the [[PDF]] specification. The colorspace in short, is a way to represent a particular color using specific “coordinates” in this space. As an example, for well-known sRGB colorspace these values are R, G and B components.  
 
Colors in [[PDF]] are defined using colorspaces and color values mapped to these spaces. All available colorspaces are described in section ''8.6 “Colour Spaces”'' of the [[PDF]] specification. The colorspace in short, is a way to represent a particular color using specific “coordinates” in this space. As an example, for well-known sRGB colorspace these values are R, G and B components.  
  
In order to stroke or fill using a particular color you’ll have to select the corresponding colorspace and set the desired color as current stroking or non-stroking color. Consider the sample code below:
+
In order to stroke or fill using a particular color you’ll have to select the corresponding colorspace and set the desired color as current stroking or non-stroking color.<div class="mw-collapsible mw-collapsed">Expand to see the sample code below:
 
+
<div class="mw-collapsible-content">
<div class="mw-collapsible mw-collapsed">
 
 
  <nowiki>
 
  <nowiki>
 
// create output PDF file
 
// create output PDF file
Line 326: Line 325:
 
}
 
}
 
  </nowiki>
 
  </nowiki>
 +
</div>
 
</div>
 
</div>
  

Revision as of 09:18, 4 May 2018

Overview

Graphics system in PDF is built around a few concepts:

  • Coordinate space, which defines the canvas on which all painting occurs. It determines the position, orientation, and scale of the text, graphics, and images that appear on a page. Paths and positions shall be defined in terms of pairs of coordinates on the Cartesian plane. A coordinate pair is a pair of real numbers x and y that locate a point horizontally and vertically within a two-dimensional coordinate space. A coordinate space is determined by the following properties with respect to the current page: the location of the origin, the orientation of the x and y axes, the lengths of the units along each axis.
  • Graphics state, which defines the current state of the drawing system: current stroking and non-stroking colors, masks, transformations, clipping, alpha etc. See section 8.4 ”Graphics State” of the PDF specification.
  • Drawing commands, which use current coordinate space and graphics state and may affect them if designed to do so. They often use graphics paths as an input, which in turn consist of atomic operations for combining lines and curves into a single figure.
  • ColorSpace, which defines the current color representation and transformation

Implementation in Fixed layout API

Coordinate space

ClippedContent class instances are being used as command containers and can be nested into each other providing a way to create combined clippings and other visual effects. Page class is inherited from ClippedContent thus providing the same functionality. Page maps directly to the document's page it represents and its coordinate space corresponds by default to the doc's page coordinate space.

Drawing commands

One may draw complex and simple figures by using the Path class that represents the PDF path object. It's possible to draw lines, curves, standard primitives, clipped shapes and more using this object. Please check the samples below demonstrating its functionality. The approach for using it is as follows: build the path, set container properties, add path to the container invoking desired operation(stroke, fill, stroke and fill).

Drawing a line

We create a Path object and add line to it by calling the self-explaining method Path.AppendLine. After that we set line width and color and stroke this path object by calling ClippedContent.StrokePath method.

// create output PDF file
using (FileStream outputStream = new FileStream("outfile.pdf", FileMode.Create, FileAccess.Write))
{
    // create new document
    using(FixedDocument document = new FixedDocument())
    {
        // create new page
        Page page = new Page(Boundaries.A4);

        // create new path representing a line
        Path path = new Path(10, 820);
        path.AppendLine(300,750);

        // set current non-stroking color             
        page.Content.SetDeviceStrokingColor(new double[] { 1, 0, 1 });                
        // set line width                
        page.Content.SetLineWidth(2.0);
        // stroke path
        page.Content.StrokePath(path);

        // add page to the document
        document.Pages.Add(page);

        // save to output stream
        document.Save(outputStream);
    }
}
 

The results produced by this code are below:

Drawing a line


Drawing a Bézier curve

Drawing a curve has lots in common with drawing a line, except we have to define control points for it as described in section 8.5.2.2 “Cubic Bézier Curves” of the PDF specification. The code is almost the same as for drawing a line except that AppendCubicBezier method is being used instead of AppendLine to define a Bézier curve.

// create output PDF file
using (FileStream outputStream = new FileStream("outfile.pdf", FileMode.Create, FileAccess.Write))
{
    // create new document and add empty page
    using(FixedDocument document = new FixedDocument())
    {
        Page page = new Page(Boundaries.A4);

        // create new path representing a line
        Path path = new Path(10, 750);
        path.AppendCubicBezier(100,700,200,720,300,840);
        // set current non-stroking color             
        page.Content. SetDeviceStrokingColor (new double[] {0.3, 0.5, 1 });

        // set line width                
        page.Content.SetLineWidth(2.0);
        // stroke path
        page.Content.StrokePath(path);

        document.Pages.Add(page);

        // save to output stream
        document.Save(outputStream);
    }
}
 

Here are the results:

Drawing bézier curve


Drawing a circle using Bézier curves

Now we'll show how to draw a complex but very common figure, a circle. In order to draw it we will combine four Bézier curves to one path and stroke it. The code for drawing a circle is as follows:

// create output PDF file
using (FileStream outputStream = new FileStream("outfile.pdf", FileMode.Create, FileAccess.Write))
{
    // radius of the circle
    double radius = 100;
    // circle constant
    double r_c = 0.5522847498 * radius;

    // create new document
    using(FixedDocument document = new FixedDocument())
    {
        // create new page
        Page page = new Page(Boundaries.A4);

        // create new path representing a circle
        Path path = new Path(0, radius);                
        path.AppendCubicBezier(r_c, radius, radius, r_c, radius, 0);
        path.AppendCubicBezier(radius, -r_c, r_c, -radius, 0, -radius);
        path.AppendCubicBezier(-r_c, -radius, -radius, -r_c, -radius, 0);
        path.AppendCubicBezier(-radius, r_c, -r_c, radius, 0, radius);

        // set current non-stroking color             
        page.Content.SetDeviceStrokingColor(new double[] { 0.3, 0.5, 1 });
        // set line width                
        page.Content.SetLineWidth(2.0);

        page.Content.ModifyCurrentTransformationMatrix(1,0,0,1,250,730);
        // stroke path
        page.Content.StrokePath(path);

        // add page to the document
        document.Pages.Add(page);

        // save to output stream
        document.Save(outputStream);
    }
}
 

The resulting circle it creates is shown below:

Drawing circle using bézier curves

This sample demonstrated how to create complex paths and combine several drawing commands together. Next sample will show how to draw filled shapes and apply clipping to achieve various effects.


Drawing a filled shape with clipping

In order to draw a clipped and filled shape on PDF page you have to set clipping path and draw using it. PDF specification defines several filling and clipping rules described in sections 8.5.3.3 "Filling" and 8.5.4 “Clipping Path Operators” respectively. These sections contain very detailed description of what areas of the path should be considered “fillable” or “clippable”, it’s important to understand what EvenOdd and NonZeroWinding rules are and reading of these sections is highly recommended.

// create output PDF file
using (FileStream outputStream = new FileStream("outfile.pdf", FileMode.Create, FileAccess.Write))
{
    // radius of the circle
    double radius = 100;
    // circle constant
    double r_c = 0.5522847498*radius;

    // create new document
    using(FixedDocument document = new FixedDocument())
    {
        // create new page
        Page page = new Page(Boundaries.A4);

        // create and construct new clipping path
        Path clippingPath = new Path(radius,-radius);   
                
        // outer rect
        clippingPath.AppendLine(radius, radius);
        clippingPath.AppendLine(-radius,radius);
        clippingPath.AppendLine(-radius, -radius); 
        clippingPath.AppendLine(radius, -radius);

        //inner small rect
        clippingPath.MoveTo(-20, 20);
        clippingPath.AppendLine(20, 20);
        clippingPath.AppendLine(20, -20);
        clippingPath.AppendLine(-20, -20);
        clippingPath.AppendLine(-20, 20);     
                                
        // create clipped content using non-zero winding rule and our clipping path,
        // it will clip everything that hits the inner small rect and we will get a "hole"
        // because rectangles in clipping path are drawn in opposite direction
        ClippedContent clippedContent = new ClippedContent(clippingPath, FillRule.Nonzero);                                  

        // create new path representing a circle
        Path path = new Path(0, radius);
        path.AppendCubicBezier(r_c, radius, radius, r_c, radius, 0);
        path.AppendCubicBezier(radius, -r_c, r_c, -radius, 0, -radius);
        path.AppendCubicBezier(-r_c, -radius, -radius, -r_c, -radius, 0);
        path.AppendCubicBezier(-radius, r_c, -r_c, radius, 0, radius);

        // set current non-stroking color             
        clippedContent.SetDeviceStrokingColor (new double[] { 0.3, 0.5, 1 });
        clippedContent.SetDeviceNonStrokingColor(new double[] { 0.3, 0.5, 1 });
        // set line width                
        clippedContent.SetLineWidth(2.0);
        // stroke path
        clippedContent.FillAndStrokePath(path);                

        // set current tranform
        page.Content.ModifyCurrentTransformationMatrix(1, 0, 0, 1, 250, 730);
        // append clipped content
        page.Content.AppendContent(clippedContent);             

        // add page to the document
        document.Pages.Add(page);

        // save to output stream
        document.Save(outputStream);
    }
}
 

The results are shown below:

Filled shape with clipping applied


Drawing standard primitives

A number of the static methods provided by Path class can be used to draw standard primitives like circle, ellipse, rect etc. See the code below:

using (Stream outputStream = File.Create("primitives.pdf"))
{
    // create document and add one page to it
    using(FixedDocument fixedDocument = new FixedDocument())
    {
        fixedDocument.Pages.Add(new Page());

        ClippedContent pageContent = fixedDocument.Pages[0].Content;
        pageContent.Translate(50,790);

        // create circle
        Path circle = Path.CreateCircle(0, 0, 40);    
        pageContent.SetDeviceNonStrokingColor(new double[]{1,0,0});                
        pageContent.FillAndStrokePath(circle);

        // create ellipse
        Path ellipse = Path.CreateEllipse(0, 0, 60, 40);
        pageContent.Translate(70,0);
        pageContent.SetDeviceNonStrokingColor(new double[] { 0, 1, 0 });
        pageContent.FillAndStrokePath(ellipse);

        // create roundrect
        Path roundRect = Path.CreateRoundRect(0, 0, 100, 60, 5);    
        pageContent.Translate(50, -30);
        pageContent.SetDeviceNonStrokingColor(new double[] { 0, 0, 1 });
        pageContent.FillAndStrokePath(roundRect);

        // save document
        fixedDocument.Save(outputStream);
    }
} 
 

Resulting PDF document looks as follows:

Drawing standard primitives


ColorSpaces

Colors in PDF are defined using colorspaces and color values mapped to these spaces. All available colorspaces are described in section 8.6 “Colour Spaces” of the PDF specification. The colorspace in short, is a way to represent a particular color using specific “coordinates” in this space. As an example, for well-known sRGB colorspace these values are R, G and B components.

In order to stroke or fill using a particular color you’ll have to select the corresponding colorspace and set the desired color as current stroking or non-stroking color.

Expand to see the sample code below:
// create output PDF file
using (FileStream outputStream = new FileStream("outfile.pdf", FileMode.Create, FileAccess.Write))
{
    // create new PDF document
    using(FixedDocument document = new FixedDocument())
    {
        Page page = new Page();
        // register RGB and CMYK color spaces. 
        // it's also possible to use Lab, Gray, Indexed, ICC based color spaces.
        document.ResourceManager.RegisterResource(new RgbColorSpace("CS_RGB"));
        document.ResourceManager.RegisterResource(new CmykColorSpace("CS_CMYK"));

        // use RGB color space and set non stroking color
        page.Content.SetNonStrokingColorSpace("CS_RGB");
        page.Content.SetNonStrokingColor(0.33, 0.66, 0.33);
        // use RGB color space and set stroking color
        page.Content.SetStrokingColorSpace("CS_RGB");
        page.Content.SetStrokingColor(0.77, 0.2, 0.33);

        // create path and fill it using the color created above
        Path path = new Path();
        path.AppendRectangle(10, 720, 300, 80);
        page.Content.FillAndStrokePath(path);

        // use CMYK color space, and set non-stroking color
        page.Content.SetNonStrokingColorSpace("CS_CMYK");
        page.Content.SetNonStrokingColor(0.1, 0.3, 0.2, 0);

        page.Content.ModifyCurrentTransformationMatrix(1, 0, 0, -1, 150, 850);
      
        // create path, fill it using CMYK color and stroke it using RGB color set above
        Path path2 = new Path(70, 80);
        path2.MoveTo(75, 40);
        path2.AppendCubicBezier(75, 37, 70, 25, 50, 25);
        path2.AppendCubicBezier(20, 25, 20, 62.5, 20, 62.5);
        path2.AppendCubicBezier(20, 80, 40, 102, 75, 120);
        path2.AppendCubicBezier(110, 102, 130, 80, 130, 62.5);
        path2.AppendCubicBezier(130, 62.5, 130, 25, 100, 25);
        path2.AppendCubicBezier(85, 25, 75, 37, 75, 40);
        path2.ClosePath();                    
        page.Content.FillAndStrokePath(path2);

        // add created page and save document
        document.Pages.Add(page);                                        
        document.Save(outputStream);     
    }           
}
 

This code shows how to set current colors using various colorspaces for filling and stroking of graphics paths. The image below demonstrates the result:

Colorspaces and colors

We used simple device colorspaces in this example, but it’s possible to load colorspaces described by ICC profiles provided with some reproduction devices or use complex colorspaces e.g. Lab or Indexed.

Device colorspaces

Every raster output device has a native colourspace, which often corresponds to one of the following process color models: gray (monochrome), RGB (red,green,blue) or CMYK (cyan,magenta,yellow,black).

The device colorspaces enable the author to specify color values that are directly related to their representation on an output device. Color values in these colorspaces map directly (or by means of a simple conversion formula) to device "colorants", such as quantities of ink or intensities of pixel components. This allows a PDF creating software to control colors with high precision for a particular device, but the results may differ from one device to another.

Output devices form final colors either by summing light sources together or by subtracting light from an illuminated source. Displays typically add colors (eInk is one of the exceptions), while printing with inks subtracts them. These two ways of forming colors are called additive and subtractive color models. The most widely used methods to specify the color using these models are known as RGB and CMYK, named so for names of the primary colors on which they are based. They map to the following device colorspaces:

  • DeviceGray controls the intensity of achromatic light, from black to white. Colors in this colorspace should be represented by a single number from the range [0.0,1.0] where 0.0 is black, 1.0 is white.
  • DeviceRGB controls the intensities of red, green, and blue light, the three additive primary colors used in modern displays. Colors in this colorspace should be represented by 3 numbers where each should be in the range [0.0,1.0] where 0.0 is the absence of the component, 1.0 means maximum intensity.
  • DeviceCMYK controls the amount of cyan, magenta, yellow, and black inks, the four subtractive process colors used in printing. Colors in this colorspace should be represented by 4 numbers where each should be in the range [0.0,1.0] where 0.0 is the absence of the colorant (no ink), 1.0 means maximum amount of colorant.

There is even a simple formula to convert the color from CMYK to RGB (DeviceCMYK to DeviceRGB):

red = 1.0 - min(1.0, cyan + black)
green = 1.0 - min(1.0, magenta + black) 
blue = 1.0 − min (1.0, yellow + black)
 

Graphics State

Drawing commands in PDF are being added in sequences and therefore the commands that change the state of the current drawing context may affect subsequent drawings. In order to avoid that, you can generate your drawings as isolated sequences and perform a quick rollback when you're done. It's here when the concept of the graphics state comes into play. The PDF specification defines it in section 8.4 “Graphics State”. In a few words, a graphics state is an internal object that is being used to maintain the state of the current graphics context: selected stroking and non-stroking colors, clipping, transformations etc. These states can be "enclosed" into each other thus making inner state saving possible in a form of a graphics state stack.

Basic usage

Basic technique for saving and restoring of the current graphics state requires a command to be added to the drawing commands sequence. Below is the code that shows it in action:

// create clipped content object for drawing
ClippedContent page = new ClippedContent();

// save current state
page.SaveGraphicsState();

… apply transforms, set colors, draw paths

// restore initial state
page.RestoreGraphicsState();

…continue drawing using restored state
 

Using graphics state from resources

The basic usage sample shows how to work with the current graphics state using Save/Restore commands, but it’s also possible to use graphics state objects that you have created in advance and designed to be reusable. E.g. a graphics state describing some alpha blending operations that you’d like to share and use for many independent drawings. There is a special class called GraphicsState that can be used to achieve this goal, it’s located under the Apitron.PDF.Kit.FixedLayout.Resources.GraphicsStates namespace. These state objects can be set using their resource identifiers and should be registered as resources first in order to be used.

See the code sample below showing how to set a named graphics state for drawing:

// create output PDF file
using (FileStream outputStream = new FileStream("outfile.pdf", FileMode.Create, FileAccess.Write))
{
    // create new PDF document
    using(FixedDocument document = new FixedDocument())
    {
        document.Pages.Add(new Page());

        // register font resource
        Font font = new Font("F1", StandardFonts.TimesItalic);
        document.ResourceManager.RegisterResource(font);

        // register graphics state resource
        GraphicsState gs = new GraphicsState("gs01") {FontResourceID = "F1", FontSize = 14};

        // register graphics state resource
        document.ResourceManager.RegisterResource(gs);
       
        Content content = document.Pages[0].Content;

        // font will be set using the graphics state resource
        content.SetGraphicsState("gs01");

        // draw text spiral
        for (double angle = 0; angle <= 4*3.14; angle += 0.2)
        {
            content.SaveGraphicsState();
            content.ModifyCurrentTransformationMatrix(Math.Cos(angle), -Math.Sin(angle), Math.Sin(angle),
                Math.Cos(angle), 200, 660);
            TextObject text = new TextObject();
            text.SetTextMatrix(1, 0, 0, 1, 200/(angle + 1), 0);
            text.SetTextRenderingMode(RenderingMode.FillText);
            text.AppendText("Hello world by Apitron!");
            content.AppendText(text);
            content.RestoreGraphicsState();
        }

        // save document
        document.Save(outputStream);
    }
}
 

This sample draws the text along the spiral and uses named graphics state resource object to set the current font. It also uses SaveGraphicsState and RestoreGraphicsState commands for saving and restoring the transformation changed during the drawing of each text line. Here is the resulting image showing the spiral text generated by the sample code above. You may notice that text in this PDF file uses standard TimesItalic font set by applying a named graphics state object:

Using graphics state from resources

Implementation in Flow layout API

There is no direct access to the graphics system, however existing or created using Fixed layout API Form XObjects can be included into the final document using ContentReference element.