Webgis
打印实现技术细节
——兼论如何实现打印预览
褫其华衮,示人本相系列之三
cheungmine
在我的上一篇文章《
Webgis 打印实现原理——褫其华衮,示人本相系列之二》(见:http://blog.csdn.net/cheungmine/archive/2006/09/18/1238708.aspx)里,提到了Webgis的打印原理。其实,这个原理不仅仅适用于Webgis的打印,还适合实现打印和打印预览。打印地图的过程其实就是在打印DC上把地图绘制一遍的过程,和显示地图不同的是,需要计算比例尺,进行坐标换算。
一、准备知识
为了理解这个过程,我先假设一下我用到的组件,一个基本的绘图系统需要下面
2个元件:
1)地图绘制组件(
MapDraw)——用来在给定的DC平面上画图。必须使用DC设备坐标来画图。如:::LineTo(hDC, vx, vy) 中必须使用DC坐标,对于屏幕DC,(vx, vy)就是象素;对于打印机DC,就是点。
2)坐标变换组件(
Viewport)——用来把地图坐标变换到DC平面的坐标,和它们的反变换。
这样,显示和打印的过程就是:
..
地图数据—
>MapDraw载入—>Viewport变换到DC坐标
—>
MapDraw绘制DC
(关于上面
2个组件的实现已经超出了本文的范围。我仅讲述如何实现打印。)
设给定要打印的数据范围:
rcData(Xmin,Ymin,Xmax,Ymax)。给定打印比例尺:fScale(如=1/500)。假设我们已经取得DC(这个DC可以是屏幕DC,也可以是打印DC,或图象DC)。任何一个DC都有属性:Width(宽)、Height(高或长Length)、xDPI(水平方向分辨率)、yDPI(竖直方向分辨率)。
其中,
rcData、Width和Height这些参数输入到Viewport组件,就可以实现下面的变换:
..
地图坐标DP(dx, dy) —
> DC坐标VP(vx, vy)
DC坐标VP(vx, vy) —
> 地图坐标DP(dx, dy)
Viewport组件有
DP2VP和VP2DP函数实现这对变换。可以看出,Viewport是这里的核心。实现Viewport并不难,如果有必要,我会在以后的文章中专门讲这个变换类。其余的细节,比如页边距、对齐方式等等就是细节。
可以把
DC看作是一张纸,它的坐标的(0,0)点就在纸的左上角,你在这张纸上绘图所用到的尺子(度量),它的刻度不是m、cm、mm或inch,而是点(dot)。你只有用这个dot来在这张纸上作业。至于如何换算到实际的度量单位,比如毫米(mm),那就全靠纸的属性决定:xDPI和yDPI,它们分别表示2个方向上的DC分辨率(DPI——dots per inch,每英寸点数)。按比例打印地图需要注意的问题是,纸张的大小限制了每页可打印的数据范围,所以必须计算出页面的数据范围,打印每页的时候都要做Viewport变换。
我的经验是,绝对不要使用Windows GDI提供的坐标映射API。我们只要使用MM_TEXT映射(这是默认的),其余的事情交给Viewport来做。再重复一次,绝对不要使用Windows GDI提供的坐标映射API来做坐标变换,那只会把你搞糊涂。起码我的聪明程度还不足以架驭这些API来做复杂的事情。
二、打印预览
实现打印预览比实现打印本身还复杂。如果你有能力花半年时间写一个类似
Mircosoft Office Document Image Writer(MODI)的东西,恭喜你,你实现了打印预览。其实就是MODI本身还是有问题,比如最大分辩率是300dpi(太小了,不是么)。虽然可以通过安装虚拟打印机的方式,来预览打印结果,但是这个模式的前提是使用了第3方的软件,而通常那是要付费的。本文所讲的打印预览不依赖于需要付费的方式,而且能够实现Webgis的打印。原理在前面的文章中已有论述。实现的具体步骤是:
..
地图数据—
>MapDraw载入—>Viewport变换到DC坐标
..
—>
MapDraw绘制到图象DC—
>Tiff2Pdf生成PDF文件
..
可以看到,这里使用的
DC是图象DC。稍后可以看到如何使用它。下面我把实现打印预览的伪代码写出来,整个过程就一目了然拉:
//
//
下面结构定义了打印页面的属性
//
struct PRINT_PDFPAGE
{
//
页面尺寸(单位:象素),页面是纸张去掉页边距的剩余部分
SIZE tifPage;
//
下面的属性可以构造出坐标变换类Viewport:
RECTF rcPageData; //
页面数据范围,如:
//
(20532.123, 1234.678, 34562.112, 2316.870)
RECT rcPageView; //
页面数据范围对应的DC范围,如:
//
(0, 0, 2048, 2048)
};
//
//
下面的结构定义了绘制结构
//
struct DRAW_INFO
{
HDC hDC; //
页面图象DC
COLORREF crBkgnd;
//
底色:=CLR_INVALID (-1)表示无底色,
// 0x0 ~ 0xffffff
表示有底色
//
因为PDF是由图象生成的,而图象默认是黑色背景,所以要
//
填充图象的底色
Viewport vwp;//
当前的坐标变换类
};
///
//
下面的方法实现了打印预览PDF
//
使用CxImage创建多页TIFF, 然后使用tiff2pdf转换成pdf
// CxImage
和CxImageTIF是cximage提供的类,参见:http://www.xdp.it/
// tiff2pdf
是LIBTIFF提供的工具,需要改成一个函数
///
void PrintPDF( BSTR
bstrPdfFileName, // PDF
文件全路径名
int
nMapUnit, //
地图单位
float
fScale, //
打印比例尺
RECT
rcMargin, //
页边距
short pdfDPI, //
打印分辨率(dpi), x和y方向一致
FLOAT
inPaperWidth,
// 纸张宽(单位:inch)
FLOAT
inPaperLength, //
纸张高(单位:inch)
COLORREFcrBkgnd
//
背景色
)
{
//
临时多页TIF文件名
CComBSTR
bstrMultiTifs(bstrPdfFileName);
bstrMultiTifs += ".tif";
//
页面数组
vector<PRINT_PDFPAGE> arrPdfPages;
//
计算打印页面数组。CalcPDFPages方法的实现就是计算每个页面的数据范围
// CalcPDFPages
的原型如:
//
CalcPDFPages(int, FLOAT, RECT, FLOAT, FLOAT, FLOAT,
//
vector<PRINT_PDFPAGE>&);
CalcPDFPages(nMapUnit, fScale, rcMargin, pdfDPI,
inPaperWidth,
inPaperLength,
arrPdfPages);
int nPages = (int) arrPdfPages.size();
//
用于创建多页TIF的类CMultiTifs。实现这个类很简单,你只要了解如何使用
// CxImage
和CxImageTIF类就行。比如下面的代码创建了一个3页的TIF文件
// multipage.tif
:
//
CxImage *pimage[3];
//
pimage[0]=&image1;
//
pimage[1]=&image2;
//
pimage[2]=&image3;
//
FILE* hFile;
//
hFile = fopen("multipage.tif","w+b");
//
CxImageTIF multiimage;
//
multiimage.Encode(hFile,pimage,3);
//
fclose(hFile);
// CMultiTifs
类要自己实现
CMultiTifsmtifs(bstrMultiTifs, nPages);
//
打印每一页
for (int iPage=0; iPage<nPages; iPage++)
{
//
当前页结构
PRINT_PDFPAGE
pdfPage = arrPdfPages[iPage];
// CImage
是ATL71提供的C++类。我觉得这个类的唯一好处就是提供了GetDC()
//
这个东西。这个类存在BUG,这也是我不直接使用它的DC而是使用MemDC的原
//
因。我不会自己创建符合我需要的DC,只好请CImage代劳拉。
CImageimgDC; // 仅仅通过
imgDC取得位图DC
BOOL bRes = imgDC.Create(16, 16, 32); // 创建一个小的位图
ATLASSERT(bRes);
//
注意:如果直接使用imgDC的DC,当更换打印DPI时,可能发现只打印一小部
//
分。我觉得它的DC,多次生成有问题。所以使用了HBITMAP转了一下。
HDC hdcImg = imgDC.GetDC(); //
取得位图DC
HDC hMemDC= ::CreateCompatibleDC(hdcImg);
HBITMAP hbmp = ::CreateCompatibleBitmap(hdcImg,
pdfPage.tifPage.cx,
pdfPage.tifPage.cy);
ATLASSERT(hbmp);
//
保存DC...
int nSavedDC = ::SaveDC(hMemDC);
::SelectObject(hMemDC, hbmp);
//
//
填充绘制结构
//
DRAW_INFO
di;
di.hDC = hMemDC;
di.crBkgnd = crBkgnd;
//
生成坐标变换类
di.vwp.Init(pdfPage.rcPageView, pdfPage.rcPageData);
//
//
绘制页面
//
DrawMap(&di);
//
恢复DC
::RestoreDC(hMemDC, nSavedDC);
DeleteDC(hMemDC);
imgDC.ReleaseDC();
imgDC.Destroy();
//
使用CxImage重新打开文件
CxImage* pximg = new CxImage();
ATLASSERT(pximg);
boolbrc = pximg->CreateFromHBITMAP((HBITMAP) hbmp);
DeleteObject(hbmp); //
使用完毕,删除之
ATLASSERT(brc);
//
添加图片
mtifs.AddPage(pximg);
}
//
创建多页tif
BOOL bRes = mtifs.Encode();
ATLASSERT(bRes);
//
转换TIF到PDF
//
下面的转换使用了开源的libtiff库v3.8.2。
//
你需要包装成自己的方法:tiff2pdf
tiff2pdf(bstrMultiTifs, bstrPdfFileName, pdfDPI, inPaperWidth, inPaperLength);
}
三、结束语
好拉,终于说的差不多了。一个小问题,知道如何计算页面图象的尺寸么?假设打印分辨率是
300dpi,而纸张是8.27 x 11.69 inch(A4)大小。则页面图象就是:
宽:
8.27*300= 2481 象素
高:
11.69*300 = 3507 象素
这样,你的纸张就是
2481 x 3507 大小。你在这张纸上的所有度量都是参照这个大小来进行的。最后,你在把这个页面图象转换成PDF的时候,一定不要忘记告诉它你要的DPI(这里是300)啊!
既然有了PDF,就可以使用Adobe Reader来使用它拉。用Adobe Reader预览你的地图,难道不比自己写一个来的划算么?
我花了很长时间在编辑我的文章,最后显示出来还是不整齐.我没办法拉!而且无法加入图象!遗憾!!