#include "stdafx.h"
#include <stdio.h>
class CXpsGenerator
{
HDC m_hDC;
int m_dpi;
HGDIOBJ m_pen;
HGDIOBJ m_brush;
public:
CStringA m_xps;
int m_fillMode;
int m_penAlpha;
int m_brushAlpha;
CXpsGenerator()
{
m_pen = GetStockObject(BLACK_PEN);
m_brush = GetStockObject(WHITE_BRUSH);
m_fillMode = 0;
m_penAlpha = 255;
m_brushAlpha = 255;
}
void SelectPen(HGDIOBJ pen)
{
m_pen = pen;
}
void SelectBrush(HGDIOBJ brush)
{
m_brush = brush;
}
void WritePen()
{
if (m_pen == NULL)
{
return;
}
LOGPEN lp;
GetObject(m_pen, sizeof(lp), & lp);
int width = lp.lopnWidth.x;
if (width <= 0)
{
width = 1;
}
m_xps.AppendFormat("StrokeThickness=\"%d\" ", width);
m_xps.AppendFormat("Stroke=\"#%02X%02X%02X%02X\" ", m_penAlpha,
GetRValue(lp.lopnColor), GetGValue(lp.lopnColor), GetBValue(lp.lopnColor));
// pen style and EXTPEN missing
}
void WriteBrush()
{
if (m_brush == NULL)
{
return;
}
LOGBRUSH lb;
GetObject(m_brush, sizeof(lb), & lb);
switch (lb.lbStyle)
{
case PS_SOLID:
m_xps.AppendFormat("Fill=\"#%02X%02X%02X%02X\" ", m_brushAlpha,
GetRValue(lb.lbColor), GetGValue(lb.lbColor), GetBValue(lb.lbColor));
break;
// other brush style missing
}
}
void WritePath()
{
::EndPath(m_hDC);
int n = ::GetPath(m_hDC, NULL, NULL, 0);
if (n <= 0)
{
return;
}
int m = (n + 3 ) / 4 * 4;
BYTE * pType = new BYTE[m * (1 + sizeof(POINT))];
POINT * pPoint = (POINT *) (pType + m);
GetPath(m_hDC, pPoint, pType, n);
m_xps.Append("<Path ");
WritePen();
WriteBrush();
m_xps.AppendFormat("Data=\"F%d", m_fillMode);
for (int i = 0; i < n; i ++)
{
switch (pType[i] & ~ PT_CLOSEFIGURE)
{
case PT_MOVETO:
m_xps.Append(" M");
break;
case PT_LINETO:
m_xps.Append(" L");
break;
case PT_BEZIERTO:
m_xps.Append(" C");
pType[i + 1] |= 32; // avoid generating C to two subsequent points
pType[i + 2] |= 32;
break;
}
m_xps.AppendFormat(" %d,%d", pPoint[i].x, pPoint[i].y);
if (pType[i] & PT_CLOSEFIGURE)
{
m_xps.Append("z");
}
}
m_xps.Append("\" />\r\n");
delete [] pType;
}
void Rectangle(int left, int top, int right, int bottom)
{
BeginPath(m_hDC);
::Rectangle(m_hDC, left, top, right, bottom);
WritePath();
}
void Ellipse(int left, int top, int right, int bottom)
{
BeginPath(m_hDC);
::Ellipse(m_hDC, left, top, right, bottom);
WritePath();
}
void StartPage(double width, double height, HDC hDC)
{
m_hDC = hDC;
m_dpi = GetDeviceCaps(hDC, LOGPIXELSX);
m_dpi = 300;
m_xps.Append("<FixedPage xmlns=\"http://schemas.microsoft.com/xps/2005/06\" ");
m_xps.Append("xmlns:x=\"http://schemas.microsoft.com/xps/2005/06/resourcedictionary-key\" xml:lang=\"en-us\" ");
m_xps.AppendFormat("Width=\"%f\" Height=\"%f\" >\r\n", width * 96, height * 96);
if (m_dpi != 96) // scale to 96 dpi
{
m_xps.AppendFormat("<Canvas RenderTransform=\"matrix(%f, 0, 0, %f, 0, 0)\" >\r\n",
96.0 / m_dpi, 96.0 / m_dpi);
}
}
void EndPage()
{
if (m_dpi != 96)
{
m_xps.Append("</Canvas>\r\n");
}
m_xps += "</FixedPage>\r\n";
}
};
int _tmain(int argc, _TCHAR* argv[])
{
CString s;
CXpsGenerator xps;
HDC hDC = GetDC(NULL);
xps.StartPage(8.5, 11, hDC);
HBRUSH yellowBrush = CreateSolidBrush(RGB(0xFF, 0xFF, 0));
HPEN bluePen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0xFF));
xps.SelectBrush(yellowBrush);
xps.SelectPen(bluePen);
xps.Rectangle(48, 48, 192, 192);
xps.SelectPen(GetStockObject(BLACK_PEN));
HBRUSH cyanBrush = CreateSolidBrush(RGB(0xFF, 0, 0xFF));
xps.SelectBrush(cyanBrush);
xps.m_brushAlpha = 128;
xps.Ellipse(120, 120, 120 + 192, 120 + 128);
xps.m_brushAlpha = 255;
xps.EndPage();
DeleteObject(yellowBrush);
DeleteObject(bluePen);
DeleteObject(cyanBrush);
ReleaseDC(NULL, hDC);
puts(xps.m_xps);
}
The code should be quite easy to read. The CXpsGenerator class supports generating XML markup for simple pages with vector graphics using simple brushes and pens. It's interface is similar to GDI. More methods can be added to support more brushes, more pens, more vector primitives, images and texts. To generate the full XPS container, you also need a way to generate .zip container and create other streams which make a XPS document complete.
Here is the markup generated:
<FixedPage xmlns="http://schemas.microsoft.com/xps/2005/06"
xmlns:x="http://schemas.microsoft.com/xps/2005/06/resourcedictionary-key"
xml:lang="en-us" Width="816.000000" Height="1056.000000" >
<Path StrokeThickness="1" Stroke="#FF0000FF" Fill="#FFFFFF00"
Data="F0 M 191,48 L 48,48 L 48,191 L 191,191z" />
<Path StrokeThickness="1" Stroke="#FF000000" Fill="#80FF00FF"
Data="F0 M 311,184 C 311,148 268,120 216,120 C 163,120 120,148 120,184 C 120,219 163,247 216,247 C 268,247 311,219 311,184" />
</FixedPage>
It's definitely more costly to generate XPS Documents on your own, comparing with using the XPS Document Writter. But there are a few possible benefits:
-
Less dependency.
-
More efficient, because you're bypassing GDI, DDI and spooler.
-
Making use of more XPS features, which may not be accessible through GDI:
-
Transparency: XPS Document Writer can understand a few transparency simulation pattens.
-
Brushes: more complicated linear gradient brushes, radial gradient brushes, visual brushes, more tiling modes, viewbox.
-
Pen: more pen styles.
-
Opacity mask.
-
More compressed image file format support.
-
Resource dictionary.
-
Extra meta data.