2025-11-28 00:35:46 +09:00

898 lines
27 KiB
C++

//+----------------------------------------------------------------------------
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Abstract:
// A sample demonstrating use of the XPS Object Model to combine XPS
// documents.
//-----------------------------------------------------------------------------
// Compile with /EHsc
#include <stdio.h>
#define NOMINMAX
#include <windows.h>
#include <comdef.h>
#include <XpsObjectModel.h>
#include <set>
#include <limits>
// This struct is used by std::set to compare PartUri instances. It calls ComparePartUri() to
// compare by value, not by pointer.
struct part_uri_less
{
bool operator()(
IOpcPartUri* uri1,
IOpcPartUri* uri2
) const
{
HRESULT hr;
int result;
if (FAILED(hr = uri1->ComparePartUri(uri2, &result)))
{
// Since this struct is used by an STL class, we throw an exception here. It will
// be caught in one of the PageReferenceAdder methods.
throw std::runtime_error("ERROR: ComparePartUri failed");
}
return result < 0;
}
};
// This struct is used by std::set to compare PartResource instances. In this case we are
// unconcerned with the value of the resource - we only want to know whether the pointers we have
// are to the same COM object. _com_ptr defines an operator< which performs a proper comparison by
// performing a QueryInterface() to IUnknown and comparing the returned pointer values.
struct part_less
{
bool operator()(
IXpsOMResource* part1,
IXpsOMResource* part2
) const
{
return part1 < static_cast<IXpsOMResource*>(part2);
}
};
// This class does the heavy lifting. Its constructor takes an XpsOMObjectFactory (for creating
// PartUris) and a PackageWriter. We then call AddPageReference(), which calls
// CollectPartResources() to check for part name conflicts, renames pages and resources as
// necessary, and adds the page to the PackageWriter.
class PageReferenceAdder
{
public:
PageReferenceAdder(
IXpsOMObjectFactory* xpsFactory,
IXpsOMPackageWriter* packageWriter) :
m_xpsFactory(xpsFactory),
m_packageWriter(packageWriter),
m_pageCounter(1),
m_partNames(NULL),
m_resources(NULL)
{
// We want to avoid throwing from the constructor so that clients
// of this class do not need to be exception-safe.
try
{
m_partNames = new PartNameCollection_t;
m_resources = new ResourceCollection_t;
}
catch (std::bad_alloc)
{
// We could get bad_alloc two ways - either the allocation
// for the collection itself could throw, or the constructor
// of the collection could throw when it attempts to allocate
// a sentinel node. In either case we do nothing - we check
// whether the pointer is NULL in AddPageReference.
}
}
~PageReferenceAdder()
{
if (m_partNames)
{
delete m_partNames;
}
if (m_resources)
{
delete m_resources;
}
}
HRESULT
AddPageReference(
IXpsOMPageReference* pageReference
)
{
HRESULT hr = S_OK;
IXpsOMPartResources* partResources = NULL;
IXpsOMPage* page = NULL;
IXpsOMStoryFragmentsResource* storyFragments = NULL;
IXpsOMPrintTicketResource* printTicket = NULL;
IXpsOMImageResource* thumbnail = NULL;
XPS_SIZE size;
// If we failed to initialize, report this to the client now.
if (!m_partNames || !m_resources)
{
return E_OUTOFMEMORY;
}
if (FAILED(hr = pageReference->CollectPartResources(&partResources)))
{
fwprintf(stderr, L"ERROR: Could not collect part resources: 0x%X\n", hr);
}
// Now we call AddResourceCollection, a templated function, for each of the resource
// collections. This function will loop through the collection and call RenameResource,
// passing the pointer to the resource as well as a prefix string. The prefix string allows
// us to create part names in accordance with the naming recommendations in the XPS spec.
// RenameResource does the appropriate renaming, then adds the resource to a collection so
// that we only process it once.
{
IXpsOMFontResourceCollection* fonts = NULL;
if (SUCCEEDED(hr))
{
if (FAILED(hr = partResources->GetFontResources(&fonts)))
{
fwprintf(stderr, L"ERROR: Could not get font resource collection: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
// We can't use the FAILED macro inline here due to the template parameter list.
hr = AddResourceCollection<
IXpsOMFontResource*,
IXpsOMFontResourceCollection*>(fonts, L"Resources/Fonts/");
if (FAILED(hr))
{
// Error already reported.
}
}
if (fonts)
{
fonts->Release();
fonts = NULL;
}
}
{
IXpsOMImageResourceCollection* images = NULL;
if (SUCCEEDED(hr))
{
if (FAILED(hr = partResources->GetImageResources(&images)))
{
fwprintf(stderr, L"ERROR: Could not get image resource collection: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
// We can't use the FAILED macro inline here due to the template parameter list.
hr = AddResourceCollection<
IXpsOMImageResource*,
IXpsOMImageResourceCollection*
>(images, L"Resources/Images/");
if (FAILED(hr))
{
// Error already reported.
}
}
if (images)
{
images->Release();
images = NULL;
}
}
{
IXpsOMColorProfileResourceCollection* colorProfiles = NULL;
if (SUCCEEDED(hr))
{
if (FAILED(hr = partResources->GetColorProfileResources(&colorProfiles)))
{
fwprintf(stderr, L"ERROR: Could not get color profile resource collection: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
// We can't use the FAILED macro inline here due to the template parameter list.
hr = AddResourceCollection<
IXpsOMColorProfileResource*,
IXpsOMColorProfileResourceCollection*
>(colorProfiles, L"Metadata/");
if (FAILED(hr))
{
// Error already reported.
}
}
if (colorProfiles)
{
colorProfiles->Release();
colorProfiles = NULL;
}
}
{
IXpsOMRemoteDictionaryResourceCollection* remoteDictionaries = NULL;
if (SUCCEEDED(hr))
{
if (FAILED(hr = partResources->GetRemoteDictionaryResources(&remoteDictionaries)))
{
fwprintf(stderr, L"ERROR: Could not get remote dictionary resource collection: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
// We can't use the FAILED macro inline here due to the template parameter list.
hr = AddResourceCollection<
IXpsOMRemoteDictionaryResource*,
IXpsOMRemoteDictionaryResourceCollection*
>(remoteDictionaries, L"Resources/Dictionaries/");
if (FAILED(hr))
{
// Error already reported.
}
}
if (remoteDictionaries)
{
remoteDictionaries->Release();
remoteDictionaries = NULL;
}
}
if (partResources)
{
partResources->Release();
partResources = NULL;
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = pageReference->GetPage(&page)))
{
fwprintf(stderr, L"ERROR: Could not get page: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
// If a PageReference does not have a page attached, GetPage() returns S_OK and a NULL
// page pointer. For package-bound PageReferences, this should never be the case, but
// we make the check to be thorough.
if (!page)
{
fwprintf(stderr, L"ERROR: PageReference has no Page\n");
hr = E_UNEXPECTED;
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = RenamePage(page)))
{
// Error already reported.
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = page->GetPageDimensions(&size)))
{
fwprintf(stderr, L"ERROR: Could not get page dimensions: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = pageReference->GetStoryFragmentsResource(&storyFragments)))
{
fwprintf(stderr, L"ERROR: Could not get story fragments: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = pageReference->GetPrintTicketResource(&printTicket)))
{
fwprintf(stderr, L"ERROR: Could not get print ticket: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = pageReference->GetThumbnailResource(&thumbnail)))
{
fwprintf(stderr, L"ERROR: Could not get thumbnail: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = m_packageWriter->AddPage(page, &size, NULL, storyFragments, printTicket, thumbnail)))
{
fwprintf(stderr, L"ERROR: Could not add page: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
// Now that we have added the page to the PackageWriter, we can discard it and reclaim
// the memory used.
if (FAILED(hr = pageReference->DiscardPage()))
{
fwprintf(stderr, L"ERROR: Could not discard page: 0x%X\n", hr);
}
}
if (thumbnail)
{
thumbnail->Release();
thumbnail = NULL;
}
if (printTicket)
{
printTicket->Release();
printTicket = NULL;
}
if (storyFragments)
{
storyFragments->Release();
storyFragments = NULL;
}
if (page)
{
page->Release();
page = NULL;
}
if (partResources)
{
partResources->Release();
partResources = NULL;
}
return hr;
}
private:
HRESULT
RenamePage(IXpsOMPage* page)
{
HRESULT hr = S_OK;
try
{
std::wstring newUri(L"Documents/1/Pages/");
// maximum value of an int in decimal is 10 characters long, plus the trailing NULL
wchar_t buffer[11];
if (_itow_s(m_pageCounter++, buffer, /* radix */ 10) != 0)
{
fwprintf(stderr, L"ERROR: _itow_s failed trying to convert %d\n", m_pageCounter - 1);
return E_UNEXPECTED;
}
newUri += buffer;
newUri += L".fpage";
IOpcPartUri* partName = NULL;
if (SUCCEEDED(hr))
{
if (FAILED(hr = m_xpsFactory->CreatePartUri(newUri.c_str(), &partName)))
{
fwprintf(stderr, L"ERROR: CreatePartUri failed: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = page->SetPartName(partName)))
{
fwprintf(stderr, L"ERROR: Could not set part name: 0x%X\n", hr);
}
}
if (partName)
{
partName->Release();
partName = NULL;
}
wprintf(L"Adding page %s\n", newUri.c_str());
}
catch (std::exception &e)
{
fwprintf(stderr, L"ERROR: Caught exception in RenamePage: %S\n", e.what());
return E_FAIL;
}
return hr;
}
template <typename Resource_t, typename ResourceCollection_t>
HRESULT
AddResourceCollection(ResourceCollection_t collection, LPCWSTR prefix)
{
HRESULT hr = S_OK;
UINT32 count = 0;
if (SUCCEEDED(hr))
{
if (FAILED(hr = collection->GetCount(&count)))
{
fwprintf(stderr, L"ERROR: Could not get count: 0x%X\n", hr);
}
}
for (UINT32 i = 0; i < count; i++)
{
Resource_t resource = NULL;
if (SUCCEEDED(hr))
{
if (FAILED(hr = collection->GetAt(i, &resource)))
{
fwprintf(stderr, L"ERROR: Could not get resource: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = RenameResource(resource, prefix)))
{
// Error already reported.
}
}
if (resource)
{
resource->Release();
resource = NULL;
}
}
return hr;
}
HRESULT
RenameResource(IXpsOMResource* partResource, LPCWSTR prefix)
{
HRESULT hr = S_OK;
IOpcPartUri* partName = NULL;
BSTR absoluteUri = NULL;
try
{
// Only continue if we have not seen this COM object before.
if (m_resources->find(partResource) == m_resources->end())
{
if (SUCCEEDED(hr))
{
if (FAILED(hr = partResource->GetPartName(&partName)))
{
fwprintf(stderr, L"ERROR: could not get part name for page: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = partName->GetAbsoluteUri(&absoluteUri)))
{
fwprintf(stderr, L"ERROR: could not get absolute URI: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
std::wstring absolute(absoluteUri);
size_t slash = absolute.find_last_of(L"/");
if (slash == std::wstring::npos)
{
slash = 0;
}
int counter = 0;
do
{
if (partName)
{
partName->Release();
partName = NULL;
}
std::wstring newUri(L"Documents/1/");
newUri += prefix;
if (counter == std::numeric_limits<int>::max())
{
fwprintf(stderr, L"ERROR: Too many conflicts on part name %s - giving up\n",
absolute.substr(slash).c_str());
hr = E_FAIL;
}
// maximum value of an int in decimal is 10 characters long, plus the trailing NULL
wchar_t buffer[11];
if (SUCCEEDED(hr))
{
if (_itow_s(counter++, buffer, /* radix */ 10) != 0)
{
fwprintf(stderr, L"ERROR: _itow_s failed trying to convert %d\n", counter);
hr = E_UNEXPECTED;
}
}
if (SUCCEEDED(hr))
{
if (counter != 1)
{
newUri += buffer;
}
newUri += absolute.substr(slash);
if (FAILED(hr = m_xpsFactory->CreatePartUri(newUri.c_str(), &partName)))
{
fwprintf(stderr, L"ERROR: CreatePartUri failed: 0x%X\n", hr);
}
}
} while (SUCCEEDED(hr) && (m_partNames->find(partName) != m_partNames->end()));
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = partResource->SetPartName(partName)))
{
fwprintf(stderr, L"ERROR: could not set part name for page: 0x%X\n", hr);
}
}
if (SUCCEEDED(hr))
{
m_partNames->insert(partName);
m_resources->insert(partResource);
}
}
}
catch (std::exception &e)
{
fwprintf(stderr, L"ERROR: Caught exception in RenameResource: %S\n", e.what());
hr = E_FAIL;
}
if (absoluteUri)
{
SysFreeString(absoluteUri);
absoluteUri = NULL;
}
if (partName)
{
partName->Release();
partName = NULL;
}
return hr;
}
// Declare these private so the compiler will not generate default implementations.
// It does not make sense for PageReferenceAdder to be copied or assigned.
PageReferenceAdder(const PageReferenceAdder&);
PageReferenceAdder& operator=(const PageReferenceAdder&);
private:
typedef std::set<IXpsOMResource*, part_less> ResourceCollection_t;
typedef std::set<IOpcPartUri*, part_uri_less> PartNameCollection_t;
PartNameCollection_t* m_partNames;
ResourceCollection_t* m_resources;
IXpsOMObjectFactory* m_xpsFactory;
IXpsOMPackageWriter* m_packageWriter;
int m_pageCounter;
};
void Usage(wchar_t *argv0)
{
fwprintf(stderr, L"XPS Object Model Document Rollup Sample\n\n");
fwprintf(stderr, L"\tUsage: %s <output filename> <input filename>...\n", argv0);
}
int
wmain(int argc, wchar_t* argv[])
{
HRESULT hr = S_OK;
IXpsOMObjectFactory* xpsFactory = NULL;
IOpcPartUri* partUri = NULL;
IXpsOMPackageWriter* packageWriter = NULL;
if (argc < 3)
{
Usage(argv[0]);
return 1;
}
if (FAILED(hr = CoInitializeEx(0, COINIT_MULTITHREADED)))
{
fwprintf(stderr, L"ERROR: CoInitializeEx failed with HRESULT 0x%X\n", hr);
return 1;
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = CoCreateInstance(
__uuidof(XpsOMObjectFactory),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(IXpsOMObjectFactory),
reinterpret_cast<void**>(&xpsFactory)
)
)
)
{
fwprintf(stderr, L"ERROR: Could not create XPS OM Object Factory: %08X\n", hr);
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = xpsFactory->CreatePartUri(L"/FixedDocumentSequence.fdseq", &partUri)))
{
fwprintf(stderr, L"ERROR: Could not create part URI: %x\n", hr);
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = xpsFactory->CreatePackageWriterOnFile(
argv[1],
NULL,
0,
TRUE,
XPS_INTERLEAVING_OFF,
partUri,
NULL,
NULL,
NULL,
NULL,
&packageWriter
)
)
)
{
fwprintf(stderr, L"ERROR: Could not create package writer: 0x%X\n", hr);
}
}
if (partUri)
{
partUri->Release();
partUri = NULL;
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = xpsFactory->CreatePartUri(L"/Documents/1/FixedDocument.fdoc", &partUri)))
{
fwprintf(stderr, L"ERROR: Could not create part URI: %x\n", hr);
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = packageWriter->StartNewDocument(partUri, NULL, NULL, NULL, NULL)))
{
fwprintf(stderr, L"ERROR: Could not start new document: 0x%X\n", hr);
}
}
if (partUri)
{
partUri->Release();
partUri = NULL;
}
PageReferenceAdder pageReferenceAdder(xpsFactory, packageWriter);
for (int file = 2; file < argc; file++)
{
if (SUCCEEDED(hr))
{
wprintf(L"Opening %s...\n", argv[file]);
IXpsOMPackage* inputPackage = NULL;
IXpsOMDocumentSequence* inputDocSeq = NULL;
IXpsOMDocumentCollection* documents = NULL;
UINT32 docCount = 0;
if (SUCCEEDED(hr))
{
if (FAILED(hr = xpsFactory->CreatePackageFromFile(
argv[file],
TRUE,
&inputPackage
)
)
)
{
fwprintf(stderr, L"ERROR: Could not open %s: %x\n", argv[file], hr);
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = inputPackage->GetDocumentSequence(&inputDocSeq)))
{
fwprintf(stderr, L"ERROR: Could not get document sequence for %s: %x\n", argv[file],
hr);
}
}
if (inputPackage)
{
inputPackage->Release();
inputPackage = NULL;
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = inputDocSeq->GetDocuments(&documents)))
{
fwprintf(stderr, L"ERROR: Could not get documents for %s: %x\n", argv[file],
hr);
}
}
if (inputDocSeq)
{
inputDocSeq->Release();
inputDocSeq = NULL;
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = documents->GetCount(&docCount)))
{
fwprintf(stderr, L"ERROR: Could not get document count for %s: %x\n", argv[file],
hr);
}
}
if (SUCCEEDED(hr))
{
for (UINT32 doc = 0; doc < docCount; doc++)
{
IXpsOMDocument* document = NULL;
IXpsOMPageReferenceCollection* pageReferences = NULL;
UINT32 pageCount = 0;
if (SUCCEEDED(hr))
{
if (FAILED(hr = documents->GetAt(doc, &document)))
{
fwprintf(stderr, L"ERROR: Could not get document %d for %s: %x\n", doc, argv[file],
hr);
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = document->GetPageReferences(&pageReferences)))
{
fwprintf(stderr, L"ERROR: Could not get page references for %s: %x\n", argv[file],
hr);
}
}
if (document)
{
document->Release();
document = NULL;
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = pageReferences->GetCount(&pageCount)))
{
fwprintf(stderr, L"ERROR: Could not get page count for document %d of %s: %x\n",
doc, argv[file], hr);
}
}
if (SUCCEEDED(hr))
{
for (UINT32 page = 0; page < pageCount; page++)
{
IXpsOMPageReference* pageReference = NULL;
if (SUCCEEDED(hr))
{
if (FAILED(hr = pageReferences->GetAt(page, &pageReference)))
{
fwprintf(stderr, L"ERROR: Could not get page reference %d of document %d of %s: %x\n",
page, doc, argv[file], hr);
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = pageReferenceAdder.AddPageReference(pageReference)))
{
// Error reported by PageReferenceAdder
}
}
if (pageReference)
{
pageReference->Release();
pageReference = NULL;
}
}
}
if (pageReferences)
{
pageReferences->Release();
pageReferences = NULL;
}
}
}
if (documents)
{
documents->Release();
documents = NULL;
}
}
}
if (SUCCEEDED(hr))
{
if (FAILED(hr = packageWriter->Close()))
{
fwprintf(stderr, L"ERROR: Could not close package writer: %x\n", hr);
}
}
if (SUCCEEDED(hr))
{
wprintf(L"Done!\n");
}
if (packageWriter)
{
packageWriter->Release();
packageWriter = NULL;
}
if (partUri)
{
partUri->Release();
partUri = NULL;
}
if (xpsFactory)
{
xpsFactory->Release();
xpsFactory = NULL;
}
CoUninitialize();
return SUCCEEDED(hr) ? 0 : 1;
}