1057 lines
29 KiB
C++
1057 lines
29 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:
|
|
// Sample implementation of IDWriteTextRenderer converting DWrite text layout to XPS Canvas
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "common.h"
|
|
#include "LayoutToCanvasBuilder.h"
|
|
|
|
static const WCHAR g_layoutBrushKey[] = L"textLayoutBrush";
|
|
|
|
//
|
|
// Static utility function converting DWRITE_GLYPH_RUN_DESCRIPTION::clusterMap to XPS OM array of XPS_GLYPH_MAPPING structures
|
|
//
|
|
HRESULT
|
|
LayoutToCanvasBuilder::ClusterMapToMappingArray(
|
|
const UINT16 *clusterMap,
|
|
UINT32 mapLen, // number of elements in clusterMap array
|
|
UINT32 glyphsArrayLen, // number of elements in glyphs array
|
|
UINT32 resultMaxCount, // size of output buffer resultGlyphMapping (max number of elements)
|
|
XPS_GLYPH_MAPPING* resultGlyphMapping, // output buffer
|
|
UINT32* resultGlyphMappingCount // number of elements returned in resultGlyphMapping
|
|
)
|
|
{
|
|
// assumption:
|
|
// clusterMap[0] <= clusterMap[1] <= ... <= clusterMap[mapLen-1] < glyphsArrayLen
|
|
|
|
HRESULT hr = S_OK;
|
|
UINT32 i = 0; // number of elements added to resultGlyphMapping array
|
|
UINT32 mapPos = 0; // position in clusterMap array
|
|
|
|
*resultGlyphMappingCount = 0;
|
|
|
|
while (mapPos < mapLen && i < resultMaxCount)
|
|
{
|
|
UINT32 codePointRangeLen = 1, glyphIndexRangeLen = 1;
|
|
while (mapPos + codePointRangeLen < mapLen &&
|
|
clusterMap[mapPos + codePointRangeLen] == clusterMap[mapPos])
|
|
{
|
|
codePointRangeLen++;
|
|
}
|
|
if (mapPos + codePointRangeLen == mapLen)
|
|
{
|
|
// end of cluster map
|
|
glyphIndexRangeLen = glyphsArrayLen - clusterMap[mapPos];
|
|
}
|
|
else
|
|
{
|
|
glyphIndexRangeLen = clusterMap[mapPos + codePointRangeLen] - clusterMap[mapPos];
|
|
}
|
|
|
|
if (codePointRangeLen > 1 || glyphIndexRangeLen > 1)
|
|
{
|
|
// Add mapping entry for 1 : N, N : 1 and M : N code point to glyph index clusters
|
|
XPS_GLYPH_MAPPING& mappingEntry = resultGlyphMapping[i];
|
|
mappingEntry.unicodeStringStart = mapPos;
|
|
mappingEntry.unicodeStringLength = UINT16(codePointRangeLen);
|
|
mappingEntry.glyphIndicesStart = clusterMap[mapPos];
|
|
mappingEntry.glyphIndicesLength = UINT16(glyphIndexRangeLen);
|
|
i++;
|
|
}
|
|
|
|
mapPos += codePointRangeLen;
|
|
}
|
|
|
|
if (mapPos < mapLen && i >= resultMaxCount)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_MORE_DATA);
|
|
}
|
|
*resultGlyphMappingCount = i;
|
|
return hr;
|
|
}
|
|
|
|
LayoutToCanvasBuilder::LayoutToCanvasBuilder(
|
|
IXpsOMObjectFactory* xpsFactory
|
|
)
|
|
: _refCount(1),
|
|
_xpsFactory(NULL), _xpsCanvas(NULL), _xpsResources(NULL),
|
|
_fontMapSize(0)
|
|
{
|
|
_xpsFactory = xpsFactory;
|
|
_xpsFactory->AddRef();
|
|
}
|
|
|
|
//
|
|
// This internal method creates empty canvas with resource dictionary holding one solid color brush.
|
|
// It also creates empty IXpsOMPartResources object.
|
|
//
|
|
HRESULT
|
|
LayoutToCanvasBuilder::CreateRootCanvasAndResources()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
IXpsOMDictionary* canvasDictionary = NULL;
|
|
IXpsOMSolidColorBrush* blueSolidBrush = NULL;
|
|
|
|
hr = _xpsFactory->CreateCanvas( &_xpsCanvas );
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _xpsFactory->CreatePartResources( &_xpsResources );
|
|
}
|
|
|
|
// Create dictionary and add to canvas
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _xpsFactory->CreateDictionary( &canvasDictionary );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _xpsCanvas->SetDictionaryLocal( canvasDictionary );
|
|
}
|
|
|
|
// Create solid color brush and add to canvas dictionary
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
XPS_COLOR rgbBlue;
|
|
rgbBlue.colorType = XPS_COLOR_TYPE_SRGB;
|
|
rgbBlue.value.sRGB.alpha = 0xFF;
|
|
rgbBlue.value.sRGB.red = 0;
|
|
rgbBlue.value.sRGB.green = 0;
|
|
rgbBlue.value.sRGB.blue = 0xFF;
|
|
hr = _xpsFactory->CreateSolidColorBrush(
|
|
&rgbBlue,
|
|
NULL, // no color profile for this color type
|
|
&blueSolidBrush
|
|
);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = canvasDictionary->Append(
|
|
g_layoutBrushKey,
|
|
blueSolidBrush
|
|
);
|
|
}
|
|
|
|
// cleanup
|
|
if (blueSolidBrush)
|
|
{
|
|
blueSolidBrush->Release();
|
|
blueSolidBrush = NULL;
|
|
}
|
|
if (canvasDictionary)
|
|
{
|
|
canvasDictionary->Release();
|
|
canvasDictionary = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
LayoutToCanvasBuilder::~LayoutToCanvasBuilder()
|
|
{
|
|
for (UINT i = 0; i < _fontMapSize; i++)
|
|
{
|
|
_fontMap[i].fontResource->Release();
|
|
}
|
|
_fontMapSize = 0;
|
|
|
|
if (_xpsFactory)
|
|
{
|
|
_xpsFactory->Release();
|
|
_xpsFactory = NULL;
|
|
}
|
|
|
|
if (_xpsCanvas)
|
|
{
|
|
_xpsCanvas->Release();
|
|
_xpsCanvas = NULL;
|
|
}
|
|
|
|
if (_xpsResources)
|
|
{
|
|
_xpsResources->Release();
|
|
_xpsResources = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
//static
|
|
HRESULT
|
|
LayoutToCanvasBuilder::CreateInstance(
|
|
IXpsOMObjectFactory* xpsFactory,
|
|
LayoutToCanvasBuilder** ppNewInstance
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
LayoutToCanvasBuilder* pResult = NULL;
|
|
|
|
pResult = new(std::nothrow) LayoutToCanvasBuilder(xpsFactory);
|
|
if (!pResult)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pResult->CreateRootCanvasAndResources();
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*ppNewInstance = pResult;
|
|
(*ppNewInstance)->AddRef();
|
|
}
|
|
|
|
if (pResult)
|
|
{
|
|
pResult->Release();
|
|
pResult = NULL;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
IXpsOMCanvas*
|
|
LayoutToCanvasBuilder::GetCanvas()
|
|
{
|
|
return _xpsCanvas;
|
|
}
|
|
|
|
IXpsOMPartResources*
|
|
LayoutToCanvasBuilder::GetResources()
|
|
{
|
|
return _xpsResources;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
LayoutToCanvasBuilder::QueryInterface(
|
|
REFIID riid,
|
|
void** ppvObject
|
|
)
|
|
{
|
|
if (ppvObject == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
*ppvObject = NULL;
|
|
|
|
if (IsEqualGUID(riid, __uuidof(IUnknown)) ||
|
|
IsEqualGUID(riid, __uuidof(IDWritePixelSnapping)) ||
|
|
IsEqualGUID(riid, __uuidof(IDWriteTextRenderer)))
|
|
{
|
|
IUnknown* pUnk = static_cast<IUnknown*>(this);
|
|
pUnk->AddRef();
|
|
*ppvObject = pUnk;
|
|
return S_OK;
|
|
}
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
ULONG
|
|
LayoutToCanvasBuilder::AddRef()
|
|
{
|
|
return ++_refCount;
|
|
}
|
|
|
|
ULONG
|
|
LayoutToCanvasBuilder::Release()
|
|
{
|
|
ULONG newCount = --_refCount;
|
|
if (newCount == 0)
|
|
delete this;
|
|
return newCount;
|
|
}
|
|
|
|
// IDWritePixelSnapping methods
|
|
STDMETHODIMP
|
|
LayoutToCanvasBuilder::IsPixelSnappingDisabled(
|
|
void* /* clientDrawingContext */,
|
|
BOOL* isDisabled
|
|
)
|
|
{
|
|
*isDisabled = FALSE;
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
// returns identity matrix - our abstract coordinates are device independent pixels.
|
|
//
|
|
STDMETHODIMP
|
|
LayoutToCanvasBuilder::GetCurrentTransform(
|
|
void* /* clientDrawingContext */,
|
|
DWRITE_MATRIX* transform
|
|
)
|
|
{
|
|
transform->m11 = transform->m22 = 1.0;
|
|
transform->m12 = transform->m21 = transform->dx = transform->dy = 0.0;
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
// Returns 1.0 because we use DIP pixels
|
|
//
|
|
STDMETHODIMP
|
|
LayoutToCanvasBuilder::GetPixelsPerDip(
|
|
void* /* clientDrawingContext */,
|
|
FLOAT* pixelsPerDip
|
|
)
|
|
{
|
|
// DIP used in XPS (96 DPI)
|
|
*pixelsPerDip = FLOAT(1.0);
|
|
return S_OK;
|
|
}
|
|
|
|
// IDWriteTextRenderer
|
|
STDMETHODIMP
|
|
LayoutToCanvasBuilder::DrawGlyphRun(
|
|
void* /* clientDrawingContext */,
|
|
FLOAT baselineOriginX,
|
|
FLOAT baselineOriginY,
|
|
DWRITE_MEASURING_MODE /* measuringMode */,
|
|
DWRITE_GLYPH_RUN const* glyphRun,
|
|
DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription,
|
|
IUnknown* /* clientDrawingEffect */
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// supported font types in xps
|
|
DWRITE_FONT_FACE_TYPE fontFaceType =
|
|
glyphRun->fontFace->GetType();
|
|
|
|
if (fontFaceType != DWRITE_FONT_FACE_TYPE_CFF &&
|
|
fontFaceType != DWRITE_FONT_FACE_TYPE_TRUETYPE &&
|
|
fontFaceType != DWRITE_FONT_FACE_TYPE_TRUETYPE_COLLECTION)
|
|
{
|
|
// XPS does not support this type of font - just ignore this glyph run.
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
const FLOAT positionScale = 100.0f / glyphRun->fontEmSize;
|
|
|
|
IXpsOMFontResource* fontResource = NULL;
|
|
IXpsOMGlyphs* xpsGlyphs = NULL;
|
|
IXpsOMVisualCollection* canvasVisuals = NULL;
|
|
IXpsOMGlyphsEditor* xpsGlyphsEditor = NULL;
|
|
XPS_GLYPH_INDEX *glyphIndexVector = NULL;
|
|
PWSTR pszUnicodeString = NULL;
|
|
|
|
// Find or create XPS font resource for this font face
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = FindOrCreateFontResource( glyphRun->fontFace, &fontResource );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _xpsFactory->CreateGlyphs( fontResource, &xpsGlyphs );
|
|
}
|
|
|
|
// Add new Glyphs element to canvas
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _xpsCanvas->GetVisuals( &canvasVisuals );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = canvasVisuals->Append( xpsGlyphs );
|
|
}
|
|
|
|
// Now set Glyphs properties
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
XPS_POINT glyphsBaselineOrigin = { baselineOriginX, baselineOriginY };
|
|
hr = xpsGlyphs->SetOrigin( &glyphsBaselineOrigin );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = xpsGlyphs->SetFontRenderingEmSize( glyphRun->fontEmSize );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = xpsGlyphs->SetFillBrushLookup( g_layoutBrushKey );
|
|
}
|
|
|
|
if (SUCCEEDED(hr) &&
|
|
fontFaceType == DWRITE_FONT_FACE_TYPE_TRUETYPE_COLLECTION)
|
|
{
|
|
// set font face index
|
|
hr = xpsGlyphs->SetFontFaceIndex( (SHORT)glyphRun->fontFace->GetIndex() );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
DWRITE_FONT_SIMULATIONS dwriteFontSim =
|
|
glyphRun->fontFace->GetSimulations();
|
|
|
|
if (dwriteFontSim & DWRITE_FONT_SIMULATIONS_BOLD)
|
|
{
|
|
if (dwriteFontSim & DWRITE_FONT_SIMULATIONS_OBLIQUE)
|
|
{
|
|
hr = xpsGlyphs->SetStyleSimulations( XPS_STYLE_SIMULATION_BOLDITALIC );
|
|
}
|
|
else
|
|
{
|
|
hr = xpsGlyphs->SetStyleSimulations( XPS_STYLE_SIMULATION_BOLD );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (dwriteFontSim & DWRITE_FONT_SIMULATIONS_OBLIQUE)
|
|
{
|
|
hr = xpsGlyphs->SetStyleSimulations( XPS_STYLE_SIMULATION_ITALIC );
|
|
}
|
|
}
|
|
}
|
|
|
|
// The rest of the properties must be set through glyphs editor interface because of interdependencies
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = xpsGlyphs->GetGlyphsEditor( &xpsGlyphsEditor );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
glyphIndexVector = (XPS_GLYPH_INDEX *)CoTaskMemAlloc( glyphRun->glyphCount * sizeof(XPS_GLYPH_INDEX) );
|
|
if (!glyphIndexVector)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
for (UINT32 i = 0; i < glyphRun->glyphCount; i++)
|
|
{
|
|
// NOTE: these values may need extra adjustment depending on transformation, IsSideways and bidiLevel
|
|
glyphIndexVector[i].index = glyphRun->glyphIndices[i];
|
|
// advanceWidth, horizontal and vertical offset in Indices attribute in XPS Glyphs element are in 1/100 of font em size.
|
|
glyphIndexVector[i].advanceWidth = FLOAT(glyphRun->glyphAdvances[i] * positionScale);
|
|
glyphIndexVector[i].horizontalOffset = (glyphRun->glyphOffsets != NULL) ? FLOAT(glyphRun->glyphOffsets[i].advanceOffset * positionScale) : 0;
|
|
glyphIndexVector[i].verticalOffset = (glyphRun->glyphOffsets != NULL) ? FLOAT(glyphRun->glyphOffsets[i].ascenderOffset * positionScale) : 0;
|
|
}
|
|
|
|
hr = xpsGlyphsEditor->SetGlyphIndices(
|
|
(UINT32)glyphRun->glyphCount,
|
|
glyphIndexVector
|
|
);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = xpsGlyphsEditor->SetIsSideways( glyphRun->isSideways );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = xpsGlyphsEditor->SetBidiLevel( glyphRun->bidiLevel );
|
|
}
|
|
|
|
// Check for unicode string and cluster map
|
|
if (SUCCEEDED(hr) &&
|
|
glyphRunDescription->string && glyphRunDescription->stringLength > 0)
|
|
{
|
|
// IXpsOMGlyphsEditor::SetUnicodeString expects null terminated string but glyphRunDescription->string may not be so terminated.
|
|
pszUnicodeString = (PWSTR)CoTaskMemAlloc( (glyphRunDescription->stringLength + 1) * sizeof(WCHAR) );
|
|
if ( !pszUnicodeString )
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = StringCchCopyN(
|
|
pszUnicodeString,
|
|
glyphRunDescription->stringLength + 1,
|
|
glyphRunDescription->string,
|
|
glyphRunDescription->stringLength
|
|
);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = xpsGlyphsEditor->SetUnicodeString( pszUnicodeString );
|
|
}
|
|
|
|
// fill in glyph mapping array and call xpsGlyphsEditor->SetGlyphMappings
|
|
if (SUCCEEDED(hr) &&
|
|
glyphRunDescription->clusterMap)
|
|
{
|
|
//
|
|
// This sample uses GLYPH_MAPPING_MAX_COUNT constant to limit number of non-trivial (1:N, N:1 or M:N) mappings
|
|
// between unicode codepoints and glyph indexes for each glyph run.
|
|
// Complete implementation should handle ERROR_MORE_DATA result from ClusterMapToMappingArray function.
|
|
//
|
|
XPS_GLYPH_MAPPING glyphMapVector[GLYPH_MAPPING_MAX_COUNT];
|
|
UINT32 glyphMappingCount = 0;
|
|
hr = ClusterMapToMappingArray(
|
|
glyphRunDescription->clusterMap,
|
|
glyphRunDescription->stringLength,
|
|
glyphRun->glyphCount,
|
|
ARRAYSIZE(glyphMapVector),
|
|
glyphMapVector,
|
|
&glyphMappingCount
|
|
);
|
|
|
|
if (SUCCEEDED(hr) &&
|
|
glyphMappingCount > 0)
|
|
{
|
|
hr = xpsGlyphsEditor->SetGlyphMappings(
|
|
glyphMappingCount,
|
|
glyphMapVector
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = xpsGlyphsEditor->ApplyEdits();
|
|
}
|
|
|
|
// release local resources here
|
|
if (pszUnicodeString)
|
|
{
|
|
CoTaskMemFree(pszUnicodeString);
|
|
pszUnicodeString = NULL;
|
|
}
|
|
if (glyphIndexVector)
|
|
{
|
|
CoTaskMemFree(glyphIndexVector);
|
|
glyphIndexVector = NULL;
|
|
}
|
|
if (fontResource)
|
|
{
|
|
fontResource->Release();
|
|
fontResource = NULL;
|
|
}
|
|
if (xpsGlyphs)
|
|
{
|
|
xpsGlyphs->Release();
|
|
xpsGlyphs = NULL;
|
|
}
|
|
if (canvasVisuals)
|
|
{
|
|
canvasVisuals->Release();
|
|
canvasVisuals = NULL;
|
|
}
|
|
if (xpsGlyphsEditor)
|
|
{
|
|
xpsGlyphsEditor->Release();
|
|
xpsGlyphsEditor = NULL;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
STDMETHODIMP
|
|
LayoutToCanvasBuilder::DrawUnderline(
|
|
void* /* clientDrawingContext */,
|
|
FLOAT baselineOriginX,
|
|
FLOAT baselineOriginY,
|
|
DWRITE_UNDERLINE const* underline,
|
|
IUnknown* /* clientDrawingEffect */
|
|
)
|
|
{
|
|
XPS_POINT begin, end;
|
|
|
|
begin.x = baselineOriginX;
|
|
begin.y = baselineOriginY + underline->offset;
|
|
end.x = baselineOriginX + underline->width;
|
|
end.y = baselineOriginY + underline->offset;
|
|
|
|
return AddLinePath( &begin, &end, underline->thickness );
|
|
// underline->runHeight - not used
|
|
// underline->flowDirection - not used
|
|
// underline->localeName - not used
|
|
}
|
|
|
|
STDMETHODIMP
|
|
LayoutToCanvasBuilder::DrawStrikethrough(
|
|
void* /* clientDrawingContext */,
|
|
FLOAT baselineOriginX,
|
|
FLOAT baselineOriginY,
|
|
DWRITE_STRIKETHROUGH const* strikethrough,
|
|
IUnknown* /* clientDrawingEffect */
|
|
)
|
|
{
|
|
XPS_POINT begin, end;
|
|
|
|
begin.x = baselineOriginX;
|
|
begin.y = baselineOriginY + strikethrough->offset;
|
|
end.x = baselineOriginX + strikethrough->width;
|
|
end.y = baselineOriginY + strikethrough->offset;
|
|
|
|
return AddLinePath(&begin, &end, strikethrough->thickness);
|
|
// strikethrough->runHeight - not used
|
|
// strikethrough->flowDirection - not used
|
|
// strikethrough->localeName - not used
|
|
}
|
|
|
|
//
|
|
// NOTE: This method is not implemented ! Does nothing.
|
|
//
|
|
STDMETHODIMP
|
|
LayoutToCanvasBuilder::DrawInlineObject(
|
|
void* /* clientDrawingContext */,
|
|
FLOAT /* originX */,
|
|
FLOAT /* originY */,
|
|
IDWriteInlineObject* /* inlineObject */,
|
|
BOOL /* isSideways */,
|
|
BOOL /* isRightToLeft */,
|
|
IUnknown* /* clientDrawingEffect */
|
|
)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
// This method looks for font file object (by COM identity) in resource map. If found, the corresponding XPS font resource is used.
|
|
// If not, a new font resource is created and font file data is copied to new XPS font resource.
|
|
//
|
|
HRESULT
|
|
LayoutToCanvasBuilder::FindOrCreateFontResource(
|
|
IDWriteFontFace* fontFace,
|
|
IXpsOMFontResource** ppXpsFontResource
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
UINT32 numFiles = 1;
|
|
UINT_PTR fontFileKey = 0; // we will use IDWriteFontFile COM object identity to detect equal fonts
|
|
|
|
IDWriteFontFile* fontFile = NULL;
|
|
IUnknown* pUnk = NULL;
|
|
|
|
*ppXpsFontResource = NULL; // reset output argument
|
|
|
|
hr = fontFace->GetFiles(&numFiles, &fontFile);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (numFiles != 1)
|
|
{
|
|
// Font face type is verified by caller. It can not be stored in more that one file.
|
|
hr = E_UNEXPECTED;
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = fontFile->QueryInterface( __uuidof(IUnknown), reinterpret_cast<void **>(&pUnk) );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
fontFileKey = UINT_PTR(pUnk);
|
|
if (pUnk)
|
|
{
|
|
pUnk->Release();
|
|
pUnk = NULL;
|
|
}
|
|
|
|
for (UINT i = 0; i < _fontMapSize; i++)
|
|
{
|
|
if (_fontMap[i].key == fontFileKey)
|
|
{
|
|
// font is already in map and _xpsResources collection
|
|
*ppXpsFontResource = _fontMap[i].fontResource;
|
|
(*ppXpsFontResource)->AddRef();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (*ppXpsFontResource)
|
|
{
|
|
// Nothing more to do - output argument is set and hr is S_OK.
|
|
}
|
|
else
|
|
{
|
|
// This is a new font file. We have to create XPS resource for it.
|
|
|
|
// First, create temporary storage with IStream implementation for font bytes. HGLOBAL memory is used in this sample.
|
|
// NOTE: Font data may be large - temp file is recommended for complete implementation.
|
|
IStream* fontResourceStream = NULL;
|
|
IDWriteFontFileLoader* fontFileLoader = NULL;
|
|
IDWriteFontFileStream* fontFileStream = NULL;
|
|
IOpcPartUri* fontPartUri = NULL;
|
|
IXpsOMFontResource* pNewFontResource = NULL;
|
|
IXpsOMFontResourceCollection* fontResources = NULL;
|
|
|
|
const void *fontFileRef = NULL;
|
|
UINT32 fontFileRefSize = 0;
|
|
UINT64 bytesLeft = 0, readOffset = 0;
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = CreateStreamOnHGlobal( // Win32 API
|
|
NULL, // let implementation take care of memory
|
|
TRUE, // release memory when done
|
|
&fontResourceStream
|
|
);
|
|
}
|
|
|
|
// Copy font data to temporary storage
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = fontFile->GetReferenceKey(&fontFileRef, &fontFileRefSize);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = fontFile->GetLoader(&fontFileLoader);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = fontFileLoader->CreateStreamFromKey(
|
|
fontFileRef,
|
|
fontFileRefSize,
|
|
&fontFileStream
|
|
);
|
|
fontFileLoader->Release();
|
|
fontFileLoader = NULL;
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = fontFileStream->GetFileSize(&bytesLeft);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
while (bytesLeft && SUCCEEDED(hr))
|
|
{
|
|
const void *fragment = NULL;
|
|
PVOID fragmentContext = NULL;
|
|
bool bFragmentContextAcquired = false;
|
|
UINT64 readSize = min(bytesLeft, 16384);
|
|
|
|
hr = fontFileStream->ReadFileFragment(
|
|
&fragment,
|
|
readOffset,
|
|
readSize,
|
|
&fragmentContext
|
|
);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
bFragmentContextAcquired = true;
|
|
hr = fontResourceStream->Write(
|
|
fragment,
|
|
ULONG(readSize),
|
|
NULL
|
|
);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
bytesLeft -= readSize;
|
|
readOffset += readSize;
|
|
}
|
|
|
|
if (bFragmentContextAcquired)
|
|
{
|
|
fontFileStream->ReleaseFileFragment(fragmentContext);
|
|
}
|
|
}
|
|
fontFileStream->Release();
|
|
fontFileStream = NULL;
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LARGE_INTEGER liZero = {0};
|
|
hr = fontResourceStream->Seek(liZero, STREAM_SEEK_SET, NULL);
|
|
}
|
|
|
|
// Create new part URI for this resource
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = GenerateNewFontPartUri(&fontPartUri);
|
|
}
|
|
|
|
// NOTE: See XPS spec section 2.1.7.2
|
|
// This sample will obfuscate all font resources. It is allowed by XPS specification.
|
|
// This sample does not set RestrictedFont relationship for any font resource!
|
|
// This may result in incorrect XPS file if one or more fonts have Print&Preview flag!
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _xpsFactory->CreateFontResource(
|
|
fontResourceStream,
|
|
XPS_FONT_EMBEDDING_OBFUSCATED,
|
|
fontPartUri,
|
|
FALSE, //isObfSourceStream
|
|
&pNewFontResource
|
|
);
|
|
}
|
|
|
|
// New font resource must be added to map and fonts collection before returning it to caller
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _xpsResources->GetFontResources(&fontResources);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = fontResources->Append(pNewFontResource);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (_fontMapSize < FONT_MAP_MAX_SIZE)
|
|
{
|
|
_fontMap[_fontMapSize].key = fontFileKey;
|
|
_fontMap[_fontMapSize].fontResource = pNewFontResource;
|
|
_fontMap[_fontMapSize].fontResource->AddRef();
|
|
_fontMapSize++;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Do nothing here.
|
|
//
|
|
// NOTE:
|
|
// This sample limits number of entries with FONT_MAP_MAX_SIZE constant. If _fontMap is full and
|
|
// the same font file is used in another glyph run, it may be added as a new font resource part in XPS.
|
|
// This may result in large XPS file!
|
|
// Complete implementation should dynamically increase _fontMap array size to avoid font data duplication.
|
|
//
|
|
}
|
|
|
|
*ppXpsFontResource = pNewFontResource;
|
|
(*ppXpsFontResource)->AddRef();
|
|
}
|
|
|
|
// release objects used for converting DWrite font file to XPS font resource
|
|
if (fontResourceStream)
|
|
{
|
|
fontResourceStream->Release();
|
|
fontResourceStream = NULL;
|
|
}
|
|
if (fontFileLoader)
|
|
{
|
|
fontFileLoader->Release();
|
|
fontFileLoader = NULL;
|
|
}
|
|
if (fontFileStream)
|
|
{
|
|
fontFileStream->Release();
|
|
fontFileStream = NULL;
|
|
}
|
|
if (fontPartUri)
|
|
{
|
|
fontPartUri->Release();
|
|
fontPartUri = NULL;
|
|
}
|
|
if (pNewFontResource)
|
|
{
|
|
pNewFontResource->Release();
|
|
pNewFontResource = NULL;
|
|
}
|
|
if (fontResources)
|
|
{
|
|
fontResources->Release();
|
|
fontResources = NULL;
|
|
}
|
|
}
|
|
|
|
if (fontFile)
|
|
{
|
|
fontFile->Release();
|
|
fontFile = NULL;
|
|
}
|
|
if (pUnk)
|
|
{
|
|
pUnk->Release();
|
|
pUnk = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Returns IOpcPartUri object built from URI '/Resources/Fonts/__guid__.odttf'
|
|
//
|
|
HRESULT
|
|
LayoutToCanvasBuilder::GenerateNewFontPartUri(
|
|
IOpcPartUri** ppPartUri
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
GUID guid;
|
|
WCHAR guidString[128] = {0};
|
|
WCHAR uriString[256] = {0};
|
|
|
|
hr = CoCreateGuid(&guid);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = StringFromGUID2( guid, guidString, ARRAYSIZE(guidString) );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = StringCchCopy( uriString, ARRAYSIZE(uriString), L"/Resources/Fonts/" );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// guid string start and ends with curly brackets so they are removed
|
|
hr = StringCchCatN( uriString, ARRAYSIZE(uriString), guidString + 1, wcslen(guidString) - 2 );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = StringCchCat( uriString, ARRAYSIZE(uriString), L".odttf" );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _xpsFactory->CreatePartUri( uriString, ppPartUri );
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// This methods creates a line path and adds it to current canvas visuals.
|
|
// It is used by callback methods DrawUnderline and DrawStrikethrough.
|
|
//
|
|
HRESULT
|
|
LayoutToCanvasBuilder::AddLinePath(
|
|
const XPS_POINT *beginPoint,
|
|
const XPS_POINT *endPoint,
|
|
FLOAT thickness
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
IXpsOMVisualCollection* canvasVisuals = NULL;
|
|
IXpsOMPath* linePath = NULL;
|
|
IXpsOMGeometry* lineGeom = NULL;
|
|
IXpsOMGeometryFigureCollection* geomFigures = NULL;
|
|
IXpsOMGeometryFigure* lineFigure = NULL;
|
|
|
|
// Create Path element and add to canvas
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _xpsFactory->CreatePath(&linePath);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _xpsCanvas->GetVisuals(&canvasVisuals);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = canvasVisuals->Append(linePath);
|
|
}
|
|
|
|
// Set necessary path properties
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = linePath->SetStrokeBrushLookup(g_layoutBrushKey);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = linePath->SetSnapsToPixels(TRUE);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = linePath->SetStrokeThickness(thickness);
|
|
}
|
|
|
|
// Create geometry and assign it to Path.Data property
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _xpsFactory->CreateGeometry(&lineGeom);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = linePath->SetGeometryLocal( lineGeom );
|
|
}
|
|
|
|
// create geometry figure and add it to geometry
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _xpsFactory->CreateGeometryFigure( beginPoint, &lineFigure );
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = lineGeom->GetFigures( &geomFigures );
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = geomFigures->Append( lineFigure );
|
|
}
|
|
|
|
// set line segment in figure
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
XPS_SEGMENT_TYPE segmType = XPS_SEGMENT_TYPE_LINE;
|
|
FLOAT segmentData[2] = { endPoint->x , endPoint->y };
|
|
BOOL segmStroke = TRUE;
|
|
|
|
hr = lineFigure->SetSegments(
|
|
1, // segment count
|
|
2, // segment data count
|
|
&segmType, // segment types array (1 element)
|
|
segmentData,
|
|
&segmStroke // segment stroke array (1 element)
|
|
);
|
|
}
|
|
|
|
// line figure is not closed or filled
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = lineFigure->SetIsClosed( FALSE );
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = lineFigure->SetIsFilled( FALSE );
|
|
}
|
|
|
|
if (canvasVisuals)
|
|
{
|
|
canvasVisuals->Release();
|
|
canvasVisuals = NULL;
|
|
}
|
|
if (linePath)
|
|
{
|
|
linePath->Release();
|
|
linePath = NULL;
|
|
}
|
|
if (lineGeom)
|
|
{
|
|
lineGeom->Release();
|
|
lineGeom = NULL;
|
|
}
|
|
if (geomFigures)
|
|
{
|
|
geomFigures->Release();
|
|
geomFigures = NULL;
|
|
}
|
|
if (lineFigure)
|
|
{
|
|
lineFigure->Release();
|
|
lineFigure = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|