XPS page event-based modifications | C++
What is the event-based approach in programming?
The event-based approach in programming is a paradigm that revolves around the concept of events and event handling. In this model, the flow of the program is determined by events, which can be user actions (like mouse clicks or key presses), system-generated notifications, or messages from other applications. Here are some key aspects of the event-based approach:
Events: Events represent significant occurrences in a program. These can include user interactions, changes in data, or messages from other parts of a system. For example, a button click or a file being loaded can trigger events.
Event Listeners: To respond to events, programmers use event listeners (or handlers). These are functions or methods that are defined to execute when a specific event occurs. For instance, an event listener can be set up to execute a function when a user clicks a button.
Asynchronous Execution: The event-based model often supports asynchronous programming, allowing programs to remain responsive while waiting for events to occur. For example, a web application can continue to operate while waiting for data from a server.
Decoupling: The event-based approach promotes decoupling between different parts of a program. Components can communicate through events without needing to know the details of each other’s implementation, making the code more modular and easier to maintain.
Common Use Cases: Event-driven programming is widely used in graphical user interfaces (GUIs), web applications, and systems that require real-time interactions. Frameworks and libraries like Node.js, React, and many others utilize event-driven patterns.
State Management: In an event-driven system, managing the state can be crucial since the application can be in different states depending on user interactions or events. Proper state management strategies are often employed to ensure the application behaves as expected.
Overall, the event-based approach is a powerful way to manage interactions and workflows within a program, making it particularly effective for applications that require responsiveness and user interaction.
Events that occur during the XPS document conversion
When you need to make changes to a specific page of an XPS document using the Aspose.Page API, you typically select the active document (if there are multiple documents in the XPS file), select the active page, and then make the changes themselves.
Now, suppose you need to make repeating changes to all pages in an XPS file and then convert the result to PDF or an image format. Some examples of such changes include placing a watermark over the pages or adding navigation hyperlinks. The direct way to make such changes involves traversing through the documents in the XPS package, traversing through the pages in the current active document, and then, finally, applying your changes. Therefore, the code to accomplish this task would look as follows:
1for (uint32_t i = 1; i <= document->DocumentCount(); i++)
2{
3 document->SelectActiveDocument(i);
4 for (uint32_t j = 1; j <= document->PageCount(); j++)
5 {
6 document->SelectActivePage(j);
7 // Your changes ...
8 }
9}
10document->SaveAsPdf(u"file-name.pdf", saveOptions);
If you also need to make some irregular changes before the repeating ones, this approach may lead to some confusion or excessive traversals through documents and pages, let alone these loops may seem a little cumbersome.
When you convert an XPS document to PDF or an image, the process occurs page by page. When the conversion job is ready to process the next page, it triggers a “before-page” event. The user can define the handling of such events by extending the BeforePageSavingEventHandler class, thereby taking advantage of some benefits outlined in the introductory section of this article.
Adding navigation hyperlinks example
Here, we will provide an example related to the case of navigation hyperlinks. And to make the task a little more complicated, we will convert only a subset of all pages to PDF, as specified by the PdfSaveOptions.PageNumbers
property.
The event handler class
Below is the extension of the BeforePageSavingEventHandler
class:
1 /// <summary>
2 /// The class to handle the before-page event while converting an XPS document.
3 /// </summary>
4 class NavigationInjector : public Aspose::Page::XPS::Features::EventBasedModifications::BeforePageSavingEventHandler
5 {
6 typedef NavigationInjector ThisType;
7 typedef Aspose::Page::XPS::Features::EventBasedModifications::BeforePageSavingEventHandler BaseType;
8
9 typedef ::System::BaseTypesInfo<BaseType> ThisTypeBaseTypesInfo;
10 RTTI_INFO_DECL();
11
12 public:
13
14 NavigationInjector(System::SharedPtr<Aspose::Page::XPS::XpsModel::XpsFont> font, System::ArrayPtr<int32_t> pageNumbers);
15
16 /// <summary>
17 /// The action itself to be triggered on a before-page event.
18 /// </summary>
19 /// <param name="args">The event arguments.</param>
20 void Handle(System::SharedPtr<Aspose::Page::XPS::Features::EventBasedModifications::BeforeSavingEventArgs<System::SharedPtr<Aspose::Page::XPS::Features::EventBasedModifications::PageAPI>>> args) override;
21
22 private:
23
24 System::SharedPtr<Aspose::Page::XPS::XpsModel::XpsFont> _font;
25 System::SharedPtr<System::Collections::Generic::SortedList<int32_t, int32_t>> _pageNumbers;
26
27 };
1ModifyXpsPageOnConversion::NavigationInjector::NavigationInjector(System::SharedPtr<Aspose::Page::XPS::XpsModel::XpsFont> font, System::ArrayPtr<int32_t> pageNumbers)
2{
3 _font = font;
4 if (pageNumbers == nullptr)
5 {
6 return;
7 }
8
9 // Turn the page number array into a sorted collection of unique values.
10 _pageNumbers = System::MakeObject<System::Collections::Generic::SortedList<int32_t, int32_t>>();
11 for (int32_t pn : pageNumbers)
12 {
13 _pageNumbers->idx_set(pn, 0);
14 }
15
16}
17
18void ModifyXpsPageOnConversion::NavigationInjector::Handle(System::SharedPtr<Aspose::Page::XPS::Features::EventBasedModifications::BeforeSavingEventArgs<System::SharedPtr<Aspose::Page::XPS::Features::EventBasedModifications::PageAPI>>> args)
19{
20 System::SharedPtr<PageAPI> api = args->get_ElementAPI();
21
22 System::SharedPtr<XpsGlyphs> glyphs;
23 // For all pages in the output PDF except the first one...
24 if (args->get_OutputPageNumber() > 1)
25 {
26 // ...insert a hyperlink to the first page...
27 glyphs = api->CreateGlyphs(_font, 15.f, 5.f, api->get_Height() - 10.f, u"[First]");
28 glyphs->set_Fill(api->CreateSolidColorBrush(System::Drawing::Color::get_Blue()));
29 glyphs->set_HyperlinkTarget(System::MakeObject<XpsPageLinkTarget>(_pageNumbers == nullptr ? 1 : _pageNumbers->get_Keys()->idx_get(0)));
30 api->Add<System::SharedPtr<XpsGlyphs>>(glyphs);
31
32 // ...and to the previous page.
33 glyphs = api->CreateGlyphs(_font, 15.f, 60.f, api->get_Height() - 10.f, u"[Prev]");
34 glyphs->set_Fill(api->CreateSolidColorBrush(System::Drawing::Color::get_Blue()));
35 glyphs->set_HyperlinkTarget(System::MakeObject<XpsPageLinkTarget>(_pageNumbers == nullptr ? args->get_AbsolutePageNumber() - 1 : _pageNumbers->get_Keys()->idx_get(args->get_OutputPageNumber() - 2)));
36 api->Add<System::SharedPtr<XpsGlyphs>>(glyphs);
37 }
38
39 // For all pages in the output PDF except the last one...
40 if ((_pageNumbers != nullptr && args->get_OutputPageNumber() < _pageNumbers->get_Count()) || (_pageNumbers == nullptr && args->get_OutputPageNumber() < api->get_TotalPageCount()))
41 {
42 // ...insert a hyperlink to the next page...
43 glyphs = api->CreateGlyphs(_font, 15.f, 110.f, api->get_Height() - 10.f, u"[Next]");
44 glyphs->set_Fill(api->CreateSolidColorBrush(System::Drawing::Color::get_Blue()));
45 glyphs->set_HyperlinkTarget(System::MakeObject<XpsPageLinkTarget>(_pageNumbers == nullptr ? args->get_AbsolutePageNumber() + 1 : _pageNumbers->get_Keys()->idx_get(args->get_OutputPageNumber())));
46 api->Add<System::SharedPtr<XpsGlyphs>>(glyphs);
47
48 // ...and to the last page.
49 glyphs = api->CreateGlyphs(_font, 15.f, 160.f, api->get_Height() - 10.f, u"[Last]");
50 glyphs->set_Fill(api->CreateSolidColorBrush(System::Drawing::Color::get_Blue()));
51 glyphs->set_HyperlinkTarget(System::MakeObject<XpsPageLinkTarget>(_pageNumbers == nullptr ? api->get_TotalPageCount() : _pageNumbers->get_Keys()->idx_get(_pageNumbers->get_Keys()->get_Count() - 1)));
52 api->Add<System::SharedPtr<XpsGlyphs>>(glyphs);
53 }
54
55 // Insert a page number in the bottom-right corner.
56 glyphs = api->CreateGlyphs(_font, 15.f, api->get_Width() - 20.f, api->get_Height() - 10.f, System::Convert::ToString(args->get_OutputPageNumber()));
57 glyphs->set_Fill(api->CreateSolidColorBrush(System::Drawing::Color::get_Black()));
58 api->Add<System::SharedPtr<XpsGlyphs>>(glyphs);
59
60 // Add an outline entry to display the links to the converted pages in the navigation pane of a PDF viewer.
61 api->AddOutlineEntry(System::String::Format(u"Page {0}", args->get_OutputPageNumber()), 1, args->get_AbsolutePageNumber());
62}
The handler class should be aware of the pages we want to save as PDF in order to create the correct hyperlink targets. Therefore, the constructor should take the options’ array property as an argument. If the array of page numbers is specified, we create a sorted collection of them, avoiding duplicates at the same time. (By the way, this solution is not entirely accurate. Can you figure out what might cause inconsistency in the output?) We will also need an XpsFont
object containing the font data for the hyperlink text.
The overridden Handle()
method is where it all happens. The method’s argument is an object that contains the modification API for the current page, the document number within the XPS package, the absolute page number across all documents, the relative page number within the current document (which is equal to the previous number in case of only one document in the package), and the output page number (which is equal to the absolute page number when we convert the entire package).
The logic of the following two if
blocks is quite straightforward. It is based on the analysis of the OutputPageNumber
event argument to omit some of the links where appropriate: the [First]
and [Prev]
links will be added to all output pages except the first one, while the [Next]
and [Last]
links will appear on all pages except the last one. The logic is also tailored for both cases, whether page numbers are specified or not.
After the if
blocks, there is code for adding a page number in the bottom-right corner of the page.
The last line adds the page’s outline entry, the item that will be displayed in the navigation pane of a PDF viewer (if supported).
The conversion code
Now that the “before-page” event handler is defined, we can write the code that converts the document:
We open an XPS file and then instantiate a stream object with the font data file. Next, we create an instance of the PdfSaveOptions
class and specify the numbers of pages we need to convert. The next line is where the “before-page” event handler becomes “connected” to the conversion job via the BeforePageSavingEventHandlers
collection option.
All that’s left to do is run the conversion to PDF using the document’s SaveAsPdf()
method.
Conclusion
In this article, we explored the key points of the event-based approach in programming, examined the direct method for modifying pages of an XPS document, and learned a more advanced and sophisticated technique for making repeating changes to all output pages during the conversion process, using the insertion of navigation hyperlinks as an example.
For the complete examples, explore our Example project.