Evening :).
Today weāll look further into a page Content Context. weāll draw some shapes and through this learn a few more capabilities of the library.
The end result PDF created by the code that will be presented may be seen here.
The complete code for creating this example may be downloaded from the samples project in github. you can check it out in DrawingShapes.cpp
The topics that iāll go through are:
Drawing a polygon (including a line and rectangle)
Stroking and filling paths
Using Device CMYK and RGB coloring
Defining and Using reusable form XObjects
In the last post we went through the basics of creating a PDF file, Page and Content Context for that page.
So iāll just from there. after creating the page and content context with this code:
PDFPage* pdfPage = new PDFPage(); pdfPage->SetMediaBox(PDFRectangle(0,0,595,842)); PageContentContext* pageContentContext = pdfWriter.StartPageContentContext(pdfPage);
We wish to draw a line. We are doing this by constructing a Path. A path is a sequance of lines and curves. It can be used for either:
filling up an area, with a fill operator
Drawing a border around an area, with a stroke operator
Defining a frame on the page, so that any later graphics is only seen if it is in its area, with a clip operator
In our case weāll create a path from two points, and use a stroke operator to draw it.
This is how:
pageContentContext->q(); pageContentContext->w(2); pageContentContext->K(0,0,1,0); pageContentContext->m(10,500); pageContentContext->l(30,700); pageContentContext->s(); pageContentContext->Q();
Letās focus on the lines in the middle, 4 to 6. The first, pageContentContext->m(10,500), calls the m method. This method fits the ām' (moveto) operator of PDF. With this method it moves the current point to the (10,500) coordinate, starting a path from there.
The next line calls the l method with (30,700). It will add the line from the current point to (30,700) to the path. So now, effectively, we have a line. Now we need to tell PDF what to do with the newly created path. Well, the only logical thing to do is to stroke a line (cause you canāt fill it or clip according to it). So the next line calls the s method, which strokes the path we just created, in other words ā the line.
We have two other interesting lines here, 2 and 3.
The 2nd line calls the w method, to set up a line width. The line width is set to 2.
The 3rd line calls the K method. K is an operator that sets the current color to a CMYK color (more precisely a device CMYK color). In this case we set its Y value to 100% and the rest to 0%. so we get Yellow.
End result is a 2 points wide Yellow line from (10,500) to (30,700).
Note that there are surrounding q and Q calls. those are meant to save and later restore the graphic state, so that no inter effects happen between the different entities iām drawing here. You donāt have to do the same, if you donāt care about such separation.
Drawing a multi line path
Next weāll draw a parallelogram.
The code:
pageContentContext->q(); pageContentContext->w(2); pageContentContext->K(0,1,0,0); pageContentContext->m(40,500); pageContentContext->l(60,700); pageContentContext->l(160,700); pageContentContext->l(140,500); pageContentContext->s(); pageContentContext->Q();
K. letās look into the path here (middle lines, again 4-8).
we start a path by moving to (40,500) wth m. Then create a the left line of the parallelogram from (40,500) to (60,700) with a call to l. Then the top line by drawing a line from (60,700) to (160,700) with a notehr call to l. Last call to l draws the right line. Then ā Stroke with s.
Hey wait a minuteā¦what about the bottom line? The PDF operator s that we use here for stroking does the nice extra work of connecting the last point of the path with the first one, before stroking. So in essence ā itās being defined automatically.
Note that this time the color is Magenta, by calling K with (0,1,0,0) in the 3rd line. Line width is 2, similarly to the line we had earlier.
Drawing a filled rectangle
The good people who invented PDF figured out that itād be a good idea to do a shortcut for defining rectangles. Instead of having to go through all these path construction to draw the 4 lines, just do it in a single command. This is how we got the re operator. I followed suit with a method of the same name. The code for creating a filled rectangle is here:
pageContentContext->q(); pageContentContext->k(0,1,0,0); pageContentContext->re(200,400,100,300); pageContentContext->f(); pageContentContext->Q();
First, note the 3rd line. We are using the method re to define a rectangle that bottom left corner is at (200,400) and is 100 wide, and 300 tall.
The 4th line calss the method f, instead of s. Thatās because this time weād like to fill rather than stroke. The f method fills the area inside the path. There are various options of exactly how the fill is made, and so different methods like f. you can check out the PageContentContext class header for the options (or the PDF Reference, like for any of ther methods/operators here).
Note a very important difference in the 2nd line, in relation to what weāve seen before. Iām using the lowercase k method instead of the uppercase K method we used so far. This is becasue PDF has two color settings:
K ā For setting CMYK color to stroke actions
k ā For setting CMYK color to non-stroking actions (anything but stroke, like fill)
Now weāll learn about something very important called XObject Form. In PDF an XObject Form is a piece of reusable graphics that can be defined once but used in many locations. It is very usefull for VDP (Variable data printing) scenarios, where a lot of graphics can be reused between the pages of a document.
Defining a reusable form is much like defining a page ā you have to define rectangular bounderies and use a content context to draw it. Accordingly, weāll split this section to two:
The following code defines a form that draws a red triangle:
PDFFormXObject* formXObject = pdfWriter.StartFormXObject(PDFRectangle(0,0,400,400)); XObjectContentContext* xobjectContent = formXObject->GetContentContext(); xobjectContent->rg(1,0,0); xobjectContent->m(0,0); xobjectContent->l(200,400); xobjectContent->l(400,0); xobjectContent->f(); ObjectIDType formObjectID = formXObject->GetObjectID(); pdfWriter.EndFormXObjectAndRelease(formXObject);
The first line Creates a PDFFormXObject by calling PDFWriterās StartFormXObject method. The method receives a bounding box definition. In our case a 400X400 rectangle.
The second line asks for the content context of the form, so we can draw on it. The content context of the form is represented by a class of type XObjectContentContext. It is very similar to the PageContentContext we used so for for drawing content on pages. In fact, both derive from AbstractContentContext which is the class that defines all these wonderful operators that we use for drawing.
The next lines are just regular drawings like we know already. A Traiangle is drawn between (0,0), (200,400) and (400,0). It is filled with a call to f method.
Line 3 uses a new method ā rg. This method defines an RGB color. in our case the triad (1,0,0) is used, which means that Red gets 100%, while green and blue gets 0%. End result ā red triangle.
Letās discuss the last two lines:
ObjectIDType formObjectID = formXObject->GetObjectID(); pdfWriter.EndFormXObjectAndRelease(formXObject);
The first of those takes the form object ID. In PDF any object (page, form, content) has an ID. Weāll use this ID in order to place the form later. The last line calls the PDFWriter method EndFormXObjectAndRelease which finalizes the form writing, and releases the form object (so you donāt have to delete it).
Something of note. If youāll check out the complete code sample youāll see that before drawing the form code the following line is placed:
pdfWriter.PausePageContentContext(pageContentContext);
Before drawing the form, and after the page content was starting to be written, you MUST āpauseā the page content stream. Otherwise youāll get the form object definition in the page content definitionā¦and a whole mess will ensue. So if you came to a conclusion that you want to do anything other than drawing the page content ā pause it for a second. You can continue using the content context after you finished writing that other thing (in our case ā the form) by simply calling the next drawing method on the content context.
Normally you can avoid all that by just defining whatever it is that you want outside of the code that draws the page. But sometimes you canātā¦so you should know that itās possible to pause for a second the page content definition.
Placing the form on the page
To place the form on the page you have to first register it for usage. In registering you provide the form object ID to a special object of the page called the page Resources Dictionary. The Resources Dictionary contains mapping from simple string names, that are used in the content stream by operators such as Do, to object IDs.
In this case weād like to use a form in a page, so we have to register it.
here is how:
string formNameInPage = pdfPage->GetResourcesDictionary(). AddFormXObjectMapping(formObjectID);
What we did here is to get the page Resources Dictionary and then politely ask it to register the form and provide us the name it gave it. This is done by calling its AddFormXObjectMapping. The return value is a string, which we can now use with a Do method/operator (other resources types, such as fonts or color spaces, are used with other operators).
Now that we have the registration done, letās place the triangle somewhere:
pageContentContext->q(); pageContentContext->cm(0.5,0,0,0.5,120,100); pageContentContext->Do(formNameInPage); pageContentContext->Q();
The code here placed the form in (120,100) after resizing it to 50% of its size. The placing is done by calling the Do method with the form registered name we just received from the Resources Dictionary.
To set the position and resizing of the form we used the cm operator. The cm operator changes the current drawing matrix by the input matrix it receives. We went through a bit of that in the last postā¦so i wonāt return to it here. Just that the two last parameters are the relevant position (translation) of the new origina and that the first and fourth parameter (0.5 and 0.5) set the scale for the drawing.
Since this is a form we can place it again on the page. This time weāll place it in (350,100) with the size of 20%:
pageContentContext->q(); pageContentContext->cm(0.2,0,0,0.2,350,100); pageContentContext->Do(formNameInPage); pageContentContext->Q();
OK. Now we have completed this wonderful piece. you can find more information on the topics discussed here in the following wiki pages in the github project:
and another reminder for the code of this sample ā DrawingShapes.cpp
b.t.w, i created shortcuts to sample and project. you can use:
code.pdfhummus.com ā to get to the PDFWriter project in github
samples.pdfhummus.com ā to get to the samples project in github