xref: /reactos/base/applications/wordpad/print.c (revision 09dde2cf)
1 /*
2  * Wordpad implementation - Printing and print preview functions
3  *
4  * Copyright 2007-2008 by Alexander N. Sørnes <alex@thehandofagony.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #include <stdarg.h>
22 #include <windef.h>
23 #include <winbase.h>
24 #include <winreg.h>
25 #include <wingdi.h>
26 #include <winuser.h>
27 #include <richedit.h>
28 #include <commctrl.h>
29 #include <commdlg.h>
30 
31 #include "wordpad.h"
32 
33 typedef struct _previewinfo
34 {
35     int page;
36     int pages_shown;
37     int saved_pages_shown;
38     int *pageEnds, pageCapacity;
39     int textlength;
40     HDC hdc;
41     HDC hdc2;
42     RECT window;
43     RECT rcPage;
44     SIZE bmSize;
45     SIZE bmScaledSize;
46     SIZE spacing;
47     float zoomratio;
48     int zoomlevel;
49     LPWSTR wszFileName;
50 } previewinfo, *ppreviewinfo;
51 
52 static HGLOBAL devMode;
53 static HGLOBAL devNames;
54 
55 static RECT margins;
56 static previewinfo preview;
57 
58 extern const WCHAR wszPreviewWndClass[];
59 
60 static const WCHAR var_pagemargin[] = {'P','a','g','e','M','a','r','g','i','n',0};
61 static const WCHAR var_previewpages[] = {'P','r','e','v','i','e','w','P','a','g','e','s',0};
62 
63 static LPWSTR get_print_file_filter(HWND hMainWnd)
64 {
65     static WCHAR wszPrintFilter[MAX_STRING_LEN*2+6+4+1];
66     const WCHAR files_prn[] = {'*','.','P','R','N',0};
67     const WCHAR files_all[] = {'*','.','*','\0'};
68     LPWSTR p;
69     HINSTANCE hInstance = GetModuleHandleW(0);
70 
71     p = wszPrintFilter;
72     LoadStringW(hInstance, STRING_PRINTER_FILES_PRN, p, MAX_STRING_LEN);
73     p += lstrlenW(p) + 1;
74     lstrcpyW(p, files_prn);
75     p += lstrlenW(p) + 1;
76     LoadStringW(hInstance, STRING_ALL_FILES, p, MAX_STRING_LEN);
77     p += lstrlenW(p) + 1;
78     lstrcpyW(p, files_all);
79     p += lstrlenW(p) + 1;
80     *p = 0;
81 
82     return wszPrintFilter;
83 }
84 
85 void registry_set_pagemargins(HKEY hKey)
86 {
87     RegSetValueExW(hKey, var_pagemargin, 0, REG_BINARY, (LPBYTE)&margins, sizeof(RECT));
88 }
89 
90 void registry_read_pagemargins(HKEY hKey)
91 {
92     DWORD size = sizeof(RECT);
93 
94     if(!hKey || RegQueryValueExW(hKey, var_pagemargin, 0, NULL, (LPBYTE)&margins,
95                      &size) != ERROR_SUCCESS || size != sizeof(RECT))
96         SetRect(&margins, 1757, 1417, 1757, 1417);
97 }
98 
99 void registry_set_previewpages(HKEY hKey)
100 {
101     RegSetValueExW(hKey, var_previewpages, 0, REG_DWORD,
102                    (LPBYTE)&preview.pages_shown, sizeof(DWORD));
103 }
104 
105 void registry_read_previewpages(HKEY hKey)
106 {
107     DWORD size = sizeof(DWORD);
108     if(!hKey ||
109        RegQueryValueExW(hKey, var_previewpages, 0, NULL,
110                         (LPBYTE)&preview.pages_shown, &size) != ERROR_SUCCESS ||
111        size != sizeof(DWORD))
112     {
113         preview.pages_shown = 1;
114     } else {
115         if (preview.pages_shown < 1) preview.pages_shown = 1;
116         else if (preview.pages_shown > 2) preview.pages_shown = 2;
117     }
118 }
119 
120 
121 static void AddTextButton(HWND hRebarWnd, UINT string, UINT command, UINT id)
122 {
123     REBARBANDINFOW rb;
124     HINSTANCE hInstance = GetModuleHandleW(0);
125     WCHAR text[MAX_STRING_LEN];
126     HWND hButton;
127 
128     LoadStringW(hInstance, string, text, MAX_STRING_LEN);
129     hButton = CreateWindowW(WC_BUTTONW, text,
130                             WS_VISIBLE | WS_CHILD, 5, 5, 100, 15,
131                             hRebarWnd, ULongToHandle(command), hInstance, NULL);
132 
133     rb.cbSize = REBARBANDINFOW_V6_SIZE;
134     rb.fMask = RBBIM_SIZE | RBBIM_CHILDSIZE | RBBIM_STYLE | RBBIM_CHILD | RBBIM_IDEALSIZE | RBBIM_ID;
135     rb.fStyle = RBBS_NOGRIPPER | RBBS_VARIABLEHEIGHT;
136     rb.hwndChild = hButton;
137     rb.cyChild = rb.cyMinChild = 22;
138     rb.cx = rb.cxMinChild = 90;
139     rb.cxIdeal = 100;
140     rb.wID = id;
141 
142     SendMessageW(hRebarWnd, RB_INSERTBANDW, -1, (LPARAM)&rb);
143 }
144 
145 static HDC make_dc(void)
146 {
147     if(devNames && devMode)
148     {
149         LPDEVNAMES dn = GlobalLock(devNames);
150         LPDEVMODEW dm = GlobalLock(devMode);
151         HDC ret;
152 
153         ret = CreateDCW((LPWSTR)dn + dn->wDriverOffset,
154                          (LPWSTR)dn + dn->wDeviceOffset, 0, dm);
155 
156         GlobalUnlock(dn);
157         GlobalUnlock(dm);
158 
159         return ret;
160     } else
161     {
162         return 0;
163     }
164 }
165 
166 static LONG twips_to_centmm(int twips)
167 {
168     return MulDiv(twips, CENTMM_PER_INCH, TWIPS_PER_INCH);
169 }
170 
171 static LONG centmm_to_twips(int mm)
172 {
173     return MulDiv(mm, TWIPS_PER_INCH, CENTMM_PER_INCH);
174 }
175 
176 static LONG twips_to_pixels(int twips, int dpi)
177 {
178     return MulDiv(twips, dpi, TWIPS_PER_INCH);
179 }
180 
181 static LONG devunits_to_twips(int units, int dpi)
182 {
183     return MulDiv(units, TWIPS_PER_INCH, dpi);
184 }
185 
186 
187 static RECT get_print_rect(HDC hdc)
188 {
189     RECT rc;
190     int width, height;
191 
192     if(hdc)
193     {
194         int dpiY = GetDeviceCaps(hdc, LOGPIXELSY);
195         int dpiX = GetDeviceCaps(hdc, LOGPIXELSX);
196         width = devunits_to_twips(GetDeviceCaps(hdc, PHYSICALWIDTH), dpiX);
197         height = devunits_to_twips(GetDeviceCaps(hdc, PHYSICALHEIGHT), dpiY);
198     } else
199     {
200         width = centmm_to_twips(18500);
201         height = centmm_to_twips(27000);
202     }
203 
204     SetRect(&rc, margins.left, margins.top, width - margins.right, height - margins.bottom);
205 
206     return rc;
207 }
208 
209 void target_device(HWND hMainWnd, DWORD wordWrap)
210 {
211     HWND hEditorWnd = GetDlgItem(hMainWnd, IDC_EDITOR);
212 
213     if(wordWrap == ID_WORDWRAP_MARGIN)
214     {
215         int width = 0;
216         LRESULT result;
217         HDC hdc = make_dc();
218         RECT rc = get_print_rect(hdc);
219 
220         width = rc.right - rc.left;
221         if(!hdc)
222         {
223             HDC hMaindc = GetDC(hMainWnd);
224             hdc = CreateCompatibleDC(hMaindc);
225             ReleaseDC(hMainWnd, hMaindc);
226         }
227         result = SendMessageW(hEditorWnd, EM_SETTARGETDEVICE, (WPARAM)hdc, width);
228         DeleteDC(hdc);
229         if (result)
230             return;
231         /* otherwise EM_SETTARGETDEVICE failed, so fall back on wrapping
232          * to window using the NULL DC. */
233     }
234 
235     if (wordWrap != ID_WORDWRAP_NONE) {
236         SendMessageW(hEditorWnd, EM_SETTARGETDEVICE, 0, 0);
237     } else {
238         SendMessageW(hEditorWnd, EM_SETTARGETDEVICE, 0, 1);
239     }
240 
241 }
242 
243 static LPWSTR dialog_print_to_file(HWND hMainWnd)
244 {
245     OPENFILENAMEW ofn;
246     static WCHAR file[MAX_PATH] = {'O','U','T','P','U','T','.','P','R','N',0};
247     static const WCHAR defExt[] = {'P','R','N',0};
248     static LPWSTR file_filter;
249 
250     if(!file_filter)
251         file_filter = get_print_file_filter(hMainWnd);
252 
253     ZeroMemory(&ofn, sizeof(ofn));
254 
255     ofn.lStructSize = sizeof(ofn);
256     ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
257     ofn.hwndOwner = hMainWnd;
258     ofn.lpstrFilter = file_filter;
259     ofn.lpstrFile = file;
260     ofn.nMaxFile = MAX_PATH;
261     ofn.lpstrDefExt = defExt;
262 
263     if(GetSaveFileNameW(&ofn))
264         return file;
265     else
266         return FALSE;
267 }
268 
269 static void char_from_pagenum(HWND hEditorWnd, FORMATRANGE *fr, int page)
270 {
271     int i;
272 
273     fr->chrg.cpMin = 0;
274 
275     for(i = 1; i < page; i++)
276     {
277         int bottom = fr->rc.bottom;
278         fr->chrg.cpMin = SendMessageW(hEditorWnd, EM_FORMATRANGE, FALSE, (LPARAM)fr);
279         fr->rc.bottom = bottom;
280     }
281 }
282 
283 static HWND get_ruler_wnd(HWND hMainWnd)
284 {
285     return GetDlgItem(GetDlgItem(hMainWnd, IDC_REBAR), IDC_RULER);
286 }
287 
288 void redraw_ruler(HWND hRulerWnd)
289 {
290     RECT rc;
291 
292     GetClientRect(hRulerWnd, &rc);
293     InvalidateRect(hRulerWnd, &rc, TRUE);
294 }
295 
296 static void update_ruler(HWND hRulerWnd)
297 {
298      SendMessageW(hRulerWnd, WM_USER, 0, 0);
299      redraw_ruler(hRulerWnd);
300 }
301 
302 static void add_ruler_units(HDC hdcRuler, RECT* drawRect, BOOL NewMetrics, LONG EditLeftmost)
303 {
304     static HDC hdc;
305 
306     if(NewMetrics)
307     {
308         static HBITMAP hBitmap;
309         int i, x, y, RulerTextEnd;
310         int CmPixels;
311         int QuarterCmPixels;
312         HFONT hFont;
313         WCHAR FontName[] = {'M','S',' ','S','a','n','s',' ','S','e','r','i','f',0};
314 
315         if(hdc)
316         {
317             DeleteDC(hdc);
318             DeleteObject(hBitmap);
319         }
320 
321         hdc = CreateCompatibleDC(0);
322 
323         CmPixels = twips_to_pixels(centmm_to_twips(1000), GetDeviceCaps(hdc, LOGPIXELSX));
324         QuarterCmPixels = (int)((float)CmPixels / 4.0);
325 
326         hBitmap = CreateCompatibleBitmap(hdc, drawRect->right, drawRect->bottom);
327         SelectObject(hdc, hBitmap);
328         FillRect(hdc, drawRect, GetStockObject(WHITE_BRUSH));
329 
330         hFont = CreateFontW(10, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, FontName);
331 
332         SelectObject(hdc, hFont);
333         SetBkMode(hdc, TRANSPARENT);
334         SetTextAlign(hdc, TA_CENTER);
335         y = (int)(((float)drawRect->bottom - (float)drawRect->top) / 2.0) + 1;
336         RulerTextEnd = drawRect->right - EditLeftmost + 1;
337         for(i = 1, x = EditLeftmost; x < (drawRect->right - EditLeftmost + 1); i ++)
338         {
339             WCHAR str[3];
340             WCHAR format[] = {'%','d',0};
341             int x2 = x;
342 
343             x2 += QuarterCmPixels;
344             if(x2 > RulerTextEnd)
345                 break;
346 
347             MoveToEx(hdc, x2, y, NULL);
348             LineTo(hdc, x2, y+2);
349 
350             x2 += QuarterCmPixels;
351             if(x2 > RulerTextEnd)
352                 break;
353 
354             MoveToEx(hdc, x2, y - 3, NULL);
355             LineTo(hdc, x2, y + 3);
356 
357             x2 += QuarterCmPixels;
358             if(x2 > RulerTextEnd)
359                 break;
360 
361             MoveToEx(hdc, x2, y, NULL);
362             LineTo(hdc, x2, y+2);
363 
364             x += CmPixels;
365             if(x > RulerTextEnd)
366                 break;
367 
368             wsprintfW(str, format, i);
369             TextOutW(hdc, x, 5, str, lstrlenW(str));
370         }
371         DeleteObject(hFont);
372     }
373 
374     BitBlt(hdcRuler, 0, 0, drawRect->right, drawRect->bottom, hdc, 0, 0, SRCAND);
375 }
376 
377 static void paint_ruler(HWND hWnd, LONG EditLeftmost, BOOL NewMetrics)
378 {
379     PAINTSTRUCT ps;
380     HDC hdc = BeginPaint(hWnd, &ps);
381     HDC hdcPrint = make_dc();
382     RECT printRect = get_print_rect(hdcPrint);
383     RECT drawRect;
384 
385     GetClientRect(hWnd, &drawRect);
386     FillRect(hdc, &drawRect, GetSysColorBrush(COLOR_MENU));
387 
388     InflateRect(&drawRect, 0, -3);
389     drawRect.left = EditLeftmost;
390     drawRect.right = twips_to_pixels(printRect.right - margins.left, GetDeviceCaps(hdc, LOGPIXELSX));
391     FillRect(hdc, &drawRect, GetStockObject(WHITE_BRUSH));
392 
393     drawRect.top--;
394     drawRect.bottom++;
395     DrawEdge(hdc, &drawRect, EDGE_SUNKEN, BF_RECT);
396 
397     drawRect.left = drawRect.right - 1;
398     drawRect.right = twips_to_pixels(printRect.right + margins.right - margins.left, GetDeviceCaps(hdc, LOGPIXELSX));
399     DrawEdge(hdc, &drawRect, EDGE_ETCHED, BF_RECT);
400 
401     drawRect.left = 0;
402     drawRect.top = 0;
403     add_ruler_units(hdc, &drawRect, NewMetrics, EditLeftmost);
404 
405     SelectObject(hdc, GetStockObject(BLACK_BRUSH));
406     DeleteDC(hdcPrint);
407     EndPaint(hWnd, &ps);
408 }
409 
410 LRESULT CALLBACK ruler_proc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
411 {
412     static WNDPROC pPrevRulerProc;
413     static LONG EditLeftmost;
414     static BOOL NewMetrics;
415 
416     switch(msg)
417     {
418         case WM_USER:
419             if(wParam)
420             {
421                 EditLeftmost = ((POINTL*)wParam)->x;
422                 pPrevRulerProc = (WNDPROC)lParam;
423             }
424             NewMetrics = TRUE;
425             break;
426 
427         case WM_PAINT:
428             paint_ruler(hWnd, EditLeftmost, NewMetrics);
429             break;
430 
431         default:
432             return CallWindowProcW(pPrevRulerProc, hWnd, msg, wParam, lParam);
433     }
434 
435     return 0;
436 }
437 
438 static void print(LPPRINTDLGW pd, LPWSTR wszFileName)
439 {
440     FORMATRANGE fr;
441     DOCINFOW di;
442     HWND hEditorWnd = GetDlgItem(pd->hwndOwner, IDC_EDITOR);
443     int printedPages = 0;
444 
445     fr.hdc = pd->hDC;
446     fr.hdcTarget = pd->hDC;
447 
448     fr.rc = get_print_rect(fr.hdc);
449     SetRect(&fr.rcPage, 0, 0, fr.rc.right + margins.right, fr.rc.bottom + margins.bottom);
450 
451     ZeroMemory(&di, sizeof(di));
452     di.cbSize = sizeof(di);
453     di.lpszDocName = wszFileName;
454 
455     if(pd->Flags & PD_PRINTTOFILE)
456     {
457         di.lpszOutput = dialog_print_to_file(pd->hwndOwner);
458         if(!di.lpszOutput)
459             return;
460     }
461 
462     if(pd->Flags & PD_SELECTION)
463     {
464         SendMessageW(hEditorWnd, EM_EXGETSEL, 0, (LPARAM)&fr.chrg);
465     } else
466     {
467         GETTEXTLENGTHEX gt;
468         gt.flags = GTL_DEFAULT;
469         gt.codepage = 1200;
470         fr.chrg.cpMin = 0;
471         fr.chrg.cpMax = SendMessageW(hEditorWnd, EM_GETTEXTLENGTHEX, (WPARAM)&gt, 0);
472 
473         if(pd->Flags & PD_PAGENUMS)
474             char_from_pagenum(hEditorWnd, &fr, pd->nToPage);
475     }
476 
477     StartDocW(fr.hdc, &di);
478     do
479     {
480         if(StartPage(fr.hdc) <= 0)
481             break;
482 
483         fr.chrg.cpMin = SendMessageW(hEditorWnd, EM_FORMATRANGE, TRUE, (LPARAM)&fr);
484 
485         if(EndPage(fr.hdc) <= 0)
486             break;
487 
488         printedPages++;
489         if((pd->Flags & PD_PAGENUMS) && (printedPages > (pd->nToPage - pd->nFromPage)))
490             break;
491     }
492     while(fr.chrg.cpMin && fr.chrg.cpMin < fr.chrg.cpMax);
493 
494     EndDoc(fr.hdc);
495     SendMessageW(hEditorWnd, EM_FORMATRANGE, FALSE, 0);
496 }
497 
498 void dialog_printsetup(HWND hMainWnd)
499 {
500     PAGESETUPDLGW ps;
501 
502     ZeroMemory(&ps, sizeof(ps));
503     ps.lStructSize = sizeof(ps);
504     ps.hwndOwner = hMainWnd;
505     ps.Flags = PSD_INHUNDREDTHSOFMILLIMETERS | PSD_MARGINS;
506     SetRect(&ps.rtMargin, twips_to_centmm(margins.left), twips_to_centmm(margins.top),
507             twips_to_centmm(margins.right), twips_to_centmm(margins.bottom));
508     ps.hDevMode = devMode;
509     ps.hDevNames = devNames;
510 
511     if(PageSetupDlgW(&ps))
512     {
513         SetRect(&margins, centmm_to_twips(ps.rtMargin.left), centmm_to_twips(ps.rtMargin.top),
514                 centmm_to_twips(ps.rtMargin.right), centmm_to_twips(ps.rtMargin.bottom));
515         devMode = ps.hDevMode;
516         devNames = ps.hDevNames;
517         update_ruler(get_ruler_wnd(hMainWnd));
518     }
519 }
520 
521 void get_default_printer_opts(void)
522 {
523     PRINTDLGW pd;
524     ZeroMemory(&pd, sizeof(pd));
525 
526     ZeroMemory(&pd, sizeof(pd));
527     pd.lStructSize = sizeof(pd);
528     pd.Flags = PD_RETURNDC | PD_RETURNDEFAULT;
529     pd.hDevMode = devMode;
530 
531     PrintDlgW(&pd);
532 
533     devMode = pd.hDevMode;
534     devNames = pd.hDevNames;
535 }
536 
537 void print_quick(HWND hMainWnd, LPWSTR wszFileName)
538 {
539     PRINTDLGW pd;
540 
541     ZeroMemory(&pd, sizeof(pd));
542     pd.hwndOwner = hMainWnd;
543     pd.hDC = make_dc();
544 
545     print(&pd, wszFileName);
546     DeleteDC(pd.hDC);
547 }
548 
549 void dialog_print(HWND hMainWnd, LPWSTR wszFileName)
550 {
551     PRINTDLGW pd;
552     HWND hEditorWnd = GetDlgItem(hMainWnd, IDC_EDITOR);
553     int from = 0;
554     int to = 0;
555 
556     ZeroMemory(&pd, sizeof(pd));
557     pd.lStructSize = sizeof(pd);
558     pd.hwndOwner = hMainWnd;
559     pd.Flags = PD_RETURNDC | PD_USEDEVMODECOPIESANDCOLLATE;
560     pd.nMinPage = 1;
561     pd.nMaxPage = -1;
562     pd.hDevMode = devMode;
563     pd.hDevNames = devNames;
564 
565     SendMessageW(hEditorWnd, EM_GETSEL, (WPARAM)&from, (LPARAM)&to);
566     if(from == to)
567         pd.Flags |= PD_NOSELECTION;
568 
569     if(PrintDlgW(&pd))
570     {
571         devMode = pd.hDevMode;
572         devNames = pd.hDevNames;
573         print(&pd, wszFileName);
574         update_ruler(get_ruler_wnd(hMainWnd));
575     }
576 }
577 
578 static void preview_bar_show(HWND hMainWnd, BOOL show)
579 {
580     HWND hReBar = GetDlgItem(hMainWnd, IDC_REBAR);
581     int i;
582 
583     if(show)
584     {
585         REBARBANDINFOW rb;
586         HWND hStatic;
587         UINT num_pages_string = preview.pages_shown > 1 ? STRING_PREVIEW_ONEPAGE :
588                                                           STRING_PREVIEW_TWOPAGES;
589 
590         AddTextButton(hReBar, STRING_PREVIEW_PRINT, ID_PRINT, BANDID_PREVIEW_BTN1);
591         AddTextButton(hReBar, STRING_PREVIEW_NEXTPAGE, ID_PREVIEW_NEXTPAGE, BANDID_PREVIEW_BTN2);
592         AddTextButton(hReBar, STRING_PREVIEW_PREVPAGE, ID_PREVIEW_PREVPAGE, BANDID_PREVIEW_BTN3);
593         AddTextButton(hReBar, num_pages_string, ID_PREVIEW_NUMPAGES, BANDID_PREVIEW_BTN4);
594         AddTextButton(hReBar, STRING_PREVIEW_ZOOMIN, ID_PREVIEW_ZOOMIN, BANDID_PREVIEW_BTN5);
595         AddTextButton(hReBar, STRING_PREVIEW_ZOOMOUT, ID_PREVIEW_ZOOMOUT, BANDID_PREVIEW_BTN6);
596         AddTextButton(hReBar, STRING_PREVIEW_CLOSE, ID_FILE_EXIT, BANDID_PREVIEW_BTN7);
597 
598         hStatic = CreateWindowW(WC_STATICW, NULL,
599                                 WS_VISIBLE | WS_CHILD, 0, 0, 0, 0,
600                                 hReBar, NULL, NULL, NULL);
601 
602         rb.cbSize = REBARBANDINFOW_V6_SIZE;
603         rb.fMask = RBBIM_SIZE | RBBIM_CHILDSIZE | RBBIM_STYLE | RBBIM_CHILD | RBBIM_IDEALSIZE | RBBIM_ID;
604         rb.fStyle = RBBS_NOGRIPPER | RBBS_VARIABLEHEIGHT;
605         rb.hwndChild = hStatic;
606         rb.cyChild = rb.cyMinChild = 22;
607         rb.cx = rb.cxMinChild = 90;
608         rb.cxIdeal = 100;
609         rb.wID = BANDID_PREVIEW_BUFFER;
610 
611         SendMessageW(hReBar, RB_INSERTBANDW, -1, (LPARAM)&rb);
612     } else
613     {
614         for(i = 0; i <= PREVIEW_BUTTONS; i++)
615             SendMessageW(hReBar, RB_DELETEBAND, SendMessageW(hReBar, RB_IDTOINDEX, BANDID_PREVIEW_BTN1+i, 0), 0);
616     }
617 }
618 
619 static const int min_spacing = 10;
620 
621 static void update_preview_scrollbars(HWND hwndPreview, RECT *window)
622 {
623     SCROLLINFO sbi;
624     sbi.cbSize = sizeof(sbi);
625     sbi.fMask = SIF_PAGE|SIF_RANGE;
626     sbi.nMin = 0;
627     if (preview.zoomlevel == 0)
628     {
629         /* Hide scrollbars when zoomed out. */
630         sbi.nMax = 0;
631         sbi.nPage = window->right;
632         SetScrollInfo(hwndPreview, SB_HORZ, &sbi, TRUE);
633         sbi.nPage = window->bottom;
634         SetScrollInfo(hwndPreview, SB_VERT, &sbi, TRUE);
635     } else {
636         sbi.nMax = preview.bmScaledSize.cx * preview.pages_shown +
637                    min_spacing * (preview.pages_shown + 1);
638         sbi.nPage = window->right;
639         SetScrollInfo(hwndPreview, SB_HORZ, &sbi, TRUE);
640         /* Change in the horizontal scrollbar visibility affects the
641          * client rect, so update the client rect. */
642         GetClientRect(hwndPreview, window);
643         sbi.nMax = preview.bmScaledSize.cy + min_spacing * 2;
644         sbi.nPage = window->bottom;
645         SetScrollInfo(hwndPreview, SB_VERT, &sbi, TRUE);
646     }
647 }
648 
649 static void update_preview_sizes(HWND hwndPreview, BOOL zoomLevelUpdated)
650 {
651     RECT window;
652 
653     GetClientRect(hwndPreview, &window);
654 
655     /* The zoom ratio isn't updated for partial zoom because of resizing the window. */
656     if (zoomLevelUpdated || preview.zoomlevel != 1)
657     {
658         float ratio, ratioHeight, ratioWidth;
659         if (preview.zoomlevel == 2)
660         {
661             ratio = 1.0;
662         } else {
663             ratioHeight = (window.bottom - min_spacing * 2) / (float)preview.bmSize.cy;
664 
665             ratioWidth = (float)(window.right -
666                                  min_spacing * (preview.pages_shown + 1)) /
667                          (preview.pages_shown * preview.bmSize.cx);
668 
669             if(ratioWidth > ratioHeight)
670                 ratio = ratioHeight;
671             else
672                 ratio = ratioWidth;
673 
674             if (preview.zoomlevel == 1)
675                 ratio += (1.0 - ratio) / 2;
676         }
677         preview.zoomratio = ratio;
678     }
679 
680     preview.bmScaledSize.cx = preview.bmSize.cx * preview.zoomratio;
681     preview.bmScaledSize.cy = preview.bmSize.cy * preview.zoomratio;
682 
683     preview.spacing.cy = max(min_spacing, (window.bottom - preview.bmScaledSize.cy) / 2);
684 
685     preview.spacing.cx = (window.right -
686                           preview.bmScaledSize.cx * preview.pages_shown) /
687                          (preview.pages_shown + 1);
688     if (preview.spacing.cx < min_spacing)
689         preview.spacing.cx = min_spacing;
690 
691     update_preview_scrollbars(hwndPreview, &window);
692 }
693 
694 static void draw_margin_lines(HDC hdc, int x, int y, float ratio)
695 {
696     HPEN hPen, oldPen;
697     SIZE dpi;
698     RECT page_margin = preview.rcPage;
699 
700     dpi.cx = GetDeviceCaps(hdc, LOGPIXELSX);
701     dpi.cy = GetDeviceCaps(hdc, LOGPIXELSY);
702 
703     SetRect(&page_margin, preview.rcPage.left + margins.left, preview.rcPage.top + margins.top,
704             preview.rcPage.right - margins.right, preview.rcPage.bottom - margins.bottom);
705 
706     page_margin.left = (int)((float)twips_to_pixels(page_margin.left, dpi.cx) * ratio);
707     page_margin.top = (int)((float)twips_to_pixels(page_margin.top, dpi.cy) * ratio);
708     page_margin.bottom = (int)((float)twips_to_pixels(page_margin.bottom, dpi.cy) * ratio);
709     page_margin.right = (int)((float)twips_to_pixels(page_margin.right, dpi.cx) * ratio);
710 
711     OffsetRect(&page_margin, x, y);
712 
713     hPen = CreatePen(PS_DOT, 1, RGB(0,0,0));
714     oldPen = SelectObject(hdc, hPen);
715 
716     MoveToEx(hdc, x, page_margin.top, NULL);
717     LineTo(hdc, x + preview.bmScaledSize.cx, page_margin.top);
718     MoveToEx(hdc, x, page_margin.bottom, NULL);
719     LineTo(hdc, x + preview.bmScaledSize.cx, page_margin.bottom);
720 
721     MoveToEx(hdc, page_margin.left, y, NULL);
722     LineTo(hdc, page_margin.left, y + preview.bmScaledSize.cy);
723     MoveToEx(hdc, page_margin.right, y, NULL);
724     LineTo(hdc, page_margin.right, y + preview.bmScaledSize.cy);
725 
726     SelectObject(hdc, oldPen);
727     DeleteObject(hPen);
728 }
729 
730 static BOOL is_last_preview_page(int page)
731 {
732     return preview.pageEnds[page - 1] >= preview.textlength;
733 }
734 
735 void init_preview(HWND hMainWnd, LPWSTR wszFileName)
736 {
737     HINSTANCE hInstance = GetModuleHandleW(0);
738     preview.page = 1;
739     preview.hdc = 0;
740     preview.hdc2 = 0;
741     preview.wszFileName = wszFileName;
742     preview.zoomratio = 0;
743     preview.zoomlevel = 0;
744     preview_bar_show(hMainWnd, TRUE);
745 
746     CreateWindowExW(0, wszPreviewWndClass, NULL,
747             WS_VISIBLE | WS_CHILD | WS_VSCROLL | WS_HSCROLL,
748             0, 0, 200, 10, hMainWnd, (HMENU)IDC_PREVIEW, hInstance, NULL);
749 }
750 
751 void close_preview(HWND hMainWnd)
752 {
753     HWND hwndPreview = GetDlgItem(hMainWnd, IDC_PREVIEW);
754     preview.window.right = 0;
755     preview.window.bottom = 0;
756     preview.page = 0;
757     HeapFree(GetProcessHeap(), 0, preview.pageEnds);
758     preview.pageEnds = NULL;
759     preview.pageCapacity = 0;
760     if (preview.zoomlevel > 0)
761         preview.pages_shown = preview.saved_pages_shown;
762     if(preview.hdc) {
763         HBITMAP oldbm = GetCurrentObject(preview.hdc, OBJ_BITMAP);
764         DeleteDC(preview.hdc);
765         DeleteObject(oldbm);
766         preview.hdc = NULL;
767     }
768     if(preview.hdc2) {
769         HBITMAP oldbm = GetCurrentObject(preview.hdc2, OBJ_BITMAP);
770         DeleteDC(preview.hdc2);
771         DeleteObject(oldbm);
772         preview.hdc2 = NULL;
773     }
774 
775     preview_bar_show(hMainWnd, FALSE);
776     DestroyWindow(hwndPreview);
777 }
778 
779 BOOL preview_isactive(void)
780 {
781     return preview.page != 0;
782 }
783 
784 static void draw_preview(HWND hEditorWnd, FORMATRANGE* lpFr, RECT* paper, int page)
785 {
786     int bottom;
787 
788     if (!preview.pageEnds)
789     {
790         preview.pageCapacity = 32;
791         preview.pageEnds = HeapAlloc(GetProcessHeap(), 0,
792                                     sizeof(int) * preview.pageCapacity);
793         if (!preview.pageEnds) return;
794     } else if (page >= preview.pageCapacity) {
795         int *new_buffer;
796         new_buffer = HeapReAlloc(GetProcessHeap(), 0, preview.pageEnds,
797                                  sizeof(int) * preview.pageCapacity * 2);
798         if (!new_buffer) return;
799         preview.pageCapacity *= 2;
800         preview.pageEnds = new_buffer;
801     }
802 
803     FillRect(lpFr->hdc, paper, GetStockObject(WHITE_BRUSH));
804     if (page > 1 && is_last_preview_page(page - 1)) return;
805     lpFr->chrg.cpMin = page <= 1 ? 0 : preview.pageEnds[page-2];
806     bottom = lpFr->rc.bottom;
807     preview.pageEnds[page-1] = SendMessageW(hEditorWnd, EM_FORMATRANGE, TRUE, (LPARAM)lpFr);
808 
809     /* EM_FORMATRANGE sets fr.rc.bottom to indicate the area printed in,
810      * but we want to keep the original for drawing margins */
811     lpFr->rc.bottom = bottom;
812     SendMessageW(hEditorWnd, EM_FORMATRANGE, FALSE, 0);
813 }
814 
815 static void update_preview_buttons(HWND hMainWnd)
816 {
817     HWND hReBar = GetDlgItem(hMainWnd, IDC_REBAR);
818     EnableWindow(GetDlgItem(hReBar, ID_PREVIEW_PREVPAGE), preview.page > 1);
819     EnableWindow(GetDlgItem(hReBar, ID_PREVIEW_NEXTPAGE),
820                  !is_last_preview_page(preview.page) &&
821                  !is_last_preview_page(preview.page + preview.pages_shown - 1));
822     EnableWindow(GetDlgItem(hReBar, ID_PREVIEW_NUMPAGES),
823                  preview.pages_shown > 1 ||
824                  (!is_last_preview_page(1) && preview.zoomlevel == 0));
825     EnableWindow(GetDlgItem(hReBar, ID_PREVIEW_ZOOMIN), preview.zoomlevel < 2);
826     EnableWindow(GetDlgItem(hReBar, ID_PREVIEW_ZOOMOUT), preview.zoomlevel > 0);
827 }
828 
829 static LRESULT print_preview(HWND hwndPreview)
830 {
831     HPEN hPen, oldPen;
832     HDC hdc;
833     HRGN back_rgn, excl_rgn;
834     RECT window, background;
835     PAINTSTRUCT ps;
836     int x, y;
837 
838     hdc = BeginPaint(hwndPreview, &ps);
839     GetClientRect(hwndPreview, &window);
840     back_rgn = CreateRectRgnIndirect(&window);
841 
842     x = preview.spacing.cx - GetScrollPos(hwndPreview, SB_HORZ);
843     y = preview.spacing.cy - GetScrollPos(hwndPreview, SB_VERT);
844 
845     /* draw page outlines */
846     hPen = CreatePen(PS_SOLID|PS_INSIDEFRAME, 2, RGB(0,0,0));
847     oldPen = SelectObject(hdc, hPen);
848     SetRect(&background, x - 2, y - 2, x + preview.bmScaledSize.cx + 2,
849             y + preview.bmScaledSize.cy + 2);
850     Rectangle(hdc, background.left, background.top,
851               background.right, background.bottom);
852     excl_rgn = CreateRectRgnIndirect(&background);
853     CombineRgn(back_rgn, back_rgn, excl_rgn, RGN_DIFF);
854     if(preview.pages_shown > 1)
855     {
856         background.left += preview.bmScaledSize.cx + preview.spacing.cx;
857         background.right += preview.bmScaledSize.cx + preview.spacing.cx;
858         Rectangle(hdc, background.left, background.top,
859                   background.right, background.bottom);
860         SetRectRgn(excl_rgn, background.left, background.top,
861                    background.right, background.bottom);
862         CombineRgn(back_rgn, back_rgn, excl_rgn, RGN_DIFF);
863     }
864     SelectObject(hdc, oldPen);
865     DeleteObject(hPen);
866     FillRgn(hdc, back_rgn, GetStockObject(GRAY_BRUSH));
867     DeleteObject(excl_rgn);
868     DeleteObject(back_rgn);
869 
870     StretchBlt(hdc, x, y, preview.bmScaledSize.cx, preview.bmScaledSize.cy,
871                preview.hdc, 0, 0, preview.bmSize.cx, preview.bmSize.cy, SRCCOPY);
872 
873     draw_margin_lines(hdc, x, y, preview.zoomratio);
874 
875     if(preview.pages_shown > 1)
876     {
877         if (!is_last_preview_page(preview.page)) {
878             x += preview.spacing.cx + preview.bmScaledSize.cx;
879             StretchBlt(hdc, x, y,
880                        preview.bmScaledSize.cx, preview.bmScaledSize.cy,
881                        preview.hdc2, 0, 0,
882                        preview.bmSize.cx, preview.bmSize.cy, SRCCOPY);
883 
884             draw_margin_lines(hdc, x, y, preview.zoomratio);
885         } else {
886             InflateRect(&background, -2, -2);
887             FillRect(hdc, &background, GetStockObject(WHITE_BRUSH));
888         }
889     }
890 
891     preview.window = window;
892 
893     EndPaint(hwndPreview, &ps);
894 
895     return 0;
896 }
897 
898 static void update_preview_statusbar(HWND hMainWnd)
899 {
900     HWND hStatusbar = GetDlgItem(hMainWnd, IDC_STATUSBAR);
901     HINSTANCE hInst = GetModuleHandleW(0);
902     WCHAR *p;
903     WCHAR wstr[MAX_STRING_LEN];
904 
905     p = wstr;
906     if (preview.pages_shown < 2 || is_last_preview_page(preview.page))
907     {
908         static const WCHAR fmt[] = {' ','%','d','\0'};
909         p += LoadStringW(hInst, STRING_PREVIEW_PAGE, wstr, MAX_STRING_LEN);
910         wsprintfW(p, fmt, preview.page);
911     } else {
912         static const WCHAR fmt[] = {' ','%','d','-','%','d','\0'};
913         p += LoadStringW(hInst, STRING_PREVIEW_PAGES, wstr, MAX_STRING_LEN);
914         wsprintfW(p, fmt, preview.page, preview.page + 1);
915     }
916     SetWindowTextW(hStatusbar, wstr);
917 }
918 
919 /* Update for page changes. */
920 static void update_preview(HWND hMainWnd)
921 {
922     RECT paper;
923     HWND hEditorWnd = GetDlgItem(hMainWnd, IDC_EDITOR);
924     HWND hwndPreview = GetDlgItem(hMainWnd, IDC_PREVIEW);
925     HBITMAP hBitmapCapture;
926     FORMATRANGE fr;
927     HDC hdc = GetDC(hwndPreview);
928 
929     fr.hdcTarget = make_dc();
930     fr.rc = fr.rcPage = preview.rcPage;
931     fr.rc.left += margins.left;
932     fr.rc.top += margins.top;
933     fr.rc.bottom -= margins.bottom;
934     fr.rc.right -= margins.right;
935 
936     fr.chrg.cpMin = 0;
937     fr.chrg.cpMax = preview.textlength;
938 
939     SetRect(&paper, 0, 0, preview.bmSize.cx, preview.bmSize.cy);
940 
941     if (!preview.hdc) {
942         preview.hdc = CreateCompatibleDC(hdc);
943         hBitmapCapture = CreateCompatibleBitmap(hdc, preview.bmSize.cx, preview.bmSize.cy);
944         SelectObject(preview.hdc, hBitmapCapture);
945     }
946 
947     fr.hdc = preview.hdc;
948     draw_preview(hEditorWnd, &fr, &paper, preview.page);
949 
950     if(preview.pages_shown > 1)
951     {
952         if (!preview.hdc2)
953         {
954             preview.hdc2 = CreateCompatibleDC(hdc);
955             hBitmapCapture = CreateCompatibleBitmap(hdc,
956                                                     preview.bmSize.cx,
957                                                     preview.bmSize.cy);
958             SelectObject(preview.hdc2, hBitmapCapture);
959         }
960 
961         fr.hdc = preview.hdc2;
962         draw_preview(hEditorWnd, &fr, &fr.rcPage, preview.page + 1);
963     }
964     DeleteDC(fr.hdcTarget);
965     ReleaseDC(hwndPreview, hdc);
966 
967     InvalidateRect(hwndPreview, NULL, FALSE);
968     update_preview_buttons(hMainWnd);
969     update_preview_statusbar(hMainWnd);
970 }
971 
972 static void toggle_num_pages(HWND hMainWnd)
973 {
974     HWND hReBar = GetDlgItem(hMainWnd, IDC_REBAR);
975     WCHAR name[MAX_STRING_LEN];
976     HINSTANCE hInst = GetModuleHandleW(0);
977     int nPreviewPages;
978 
979     preview.pages_shown = preview.pages_shown > 1 ? 1 : 2;
980 
981     nPreviewPages = preview.zoomlevel > 0 ? preview.saved_pages_shown :
982                                             preview.pages_shown;
983 
984     LoadStringW(hInst, nPreviewPages > 1 ? STRING_PREVIEW_ONEPAGE :
985                                            STRING_PREVIEW_TWOPAGES,
986                 name, MAX_STRING_LEN);
987 
988     SetWindowTextW(GetDlgItem(hReBar, ID_PREVIEW_NUMPAGES), name);
989     update_preview_sizes(GetDlgItem(hMainWnd, IDC_PREVIEW), TRUE);
990     update_preview(hMainWnd);
991 }
992 
993 /* Returns the page shown that the point is in (1 or 2) or 0 if the point
994  * isn't inside either page */
995 static int preview_page_hittest(POINT pt)
996 {
997     RECT rc;
998     rc.left = preview.spacing.cx;
999     rc.right = rc.left + preview.bmScaledSize.cx;
1000     rc.top = preview.spacing.cy;
1001     rc.bottom = rc.top + preview.bmScaledSize.cy;
1002     if (PtInRect(&rc, pt))
1003         return 1;
1004 
1005     if (preview.pages_shown <= 1)
1006         return 0;
1007 
1008     rc.left += preview.bmScaledSize.cx + preview.spacing.cx;
1009     rc.right += preview.bmScaledSize.cx + preview.spacing.cx;
1010     if (PtInRect(&rc, pt))
1011         return is_last_preview_page(preview.page) ? 1 : 2;
1012 
1013     return 0;
1014 }
1015 
1016 LRESULT CALLBACK preview_proc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
1017 {
1018     switch(msg)
1019     {
1020         case WM_CREATE:
1021         {
1022             HWND hMainWnd = GetParent(hWnd);
1023             HWND hEditorWnd = GetDlgItem(hMainWnd, IDC_EDITOR);
1024             FORMATRANGE fr;
1025             GETTEXTLENGTHEX gt = {GTL_DEFAULT, 1200};
1026             HDC hdc = GetDC(hWnd);
1027             HDC hdcTarget = make_dc();
1028 
1029             fr.rc = preview.rcPage = get_print_rect(hdcTarget);
1030             preview.rcPage.bottom += margins.bottom;
1031             preview.rcPage.right += margins.right;
1032             preview.rcPage.top = preview.rcPage.left = 0;
1033             fr.rcPage = preview.rcPage;
1034 
1035             preview.bmSize.cx = twips_to_pixels(preview.rcPage.right, GetDeviceCaps(hdc, LOGPIXELSX));
1036             preview.bmSize.cy = twips_to_pixels(preview.rcPage.bottom, GetDeviceCaps(hdc, LOGPIXELSY));
1037 
1038             preview.textlength = SendMessageW(hEditorWnd, EM_GETTEXTLENGTHEX, (WPARAM)&gt, 0);
1039 
1040             fr.hdc = CreateCompatibleDC(hdc);
1041             fr.hdcTarget = hdcTarget;
1042             fr.chrg.cpMin = 0;
1043             fr.chrg.cpMax = preview.textlength;
1044             DeleteDC(fr.hdc);
1045             DeleteDC(hdcTarget);
1046             ReleaseDC(hWnd, hdc);
1047 
1048             update_preview_sizes(hWnd, TRUE);
1049             update_preview(hMainWnd);
1050             break;
1051         }
1052 
1053         case WM_PAINT:
1054             return print_preview(hWnd);
1055 
1056         case WM_SIZE:
1057         {
1058             update_preview_sizes(hWnd, FALSE);
1059             InvalidateRect(hWnd, NULL, FALSE);
1060             break;
1061         }
1062 
1063         case WM_VSCROLL:
1064         case WM_HSCROLL:
1065         {
1066             SCROLLINFO si;
1067             RECT rc;
1068             int nBar = (msg == WM_VSCROLL) ? SB_VERT : SB_HORZ;
1069             int origPos;
1070 
1071             GetClientRect(hWnd, &rc);
1072             si.cbSize = sizeof(si);
1073             si.fMask = SIF_ALL;
1074             GetScrollInfo(hWnd, nBar, &si);
1075             origPos = si.nPos;
1076             switch(LOWORD(wParam))
1077             {
1078                 case SB_TOP: /* == SB_LEFT */
1079                     si.nPos = si.nMin;
1080                     break;
1081                 case SB_BOTTOM: /* == SB_RIGHT */
1082                     si.nPos = si.nMax;
1083                     break;
1084                 case SB_LINEUP: /* == SB_LINELEFT */
1085                     si.nPos -= si.nPage / 10;
1086                     break;
1087                 case SB_LINEDOWN: /* == SB_LINERIGHT */
1088                     si.nPos += si.nPage / 10;
1089                     break;
1090                 case SB_PAGEUP: /* == SB_PAGELEFT */
1091                     si.nPos -= si.nPage;
1092                     break;
1093                 case SB_PAGEDOWN: /* SB_PAGERIGHT */
1094                     si.nPos += si.nPage;
1095                     break;
1096                 case SB_THUMBTRACK:
1097                     si.nPos = si.nTrackPos;
1098                     break;
1099             }
1100             si.fMask = SIF_POS;
1101             SetScrollInfo(hWnd, nBar, &si, TRUE);
1102             GetScrollInfo(hWnd, nBar, &si);
1103             if (si.nPos != origPos)
1104             {
1105                 int amount = origPos - si.nPos;
1106                 if (msg == WM_VSCROLL)
1107                     ScrollWindow(hWnd, 0, amount, NULL, NULL);
1108                 else
1109                     ScrollWindow(hWnd, amount, 0, NULL, NULL);
1110             }
1111             return 0;
1112         }
1113 
1114         case WM_SETCURSOR:
1115         {
1116             POINT pt;
1117             RECT rc;
1118             int bHittest = 0;
1119             DWORD messagePos = GetMessagePos();
1120             pt.x = (short)LOWORD(messagePos);
1121             pt.y = (short)HIWORD(messagePos);
1122             ScreenToClient(hWnd, &pt);
1123 
1124             GetClientRect(hWnd, &rc);
1125             if (PtInRect(&rc, pt))
1126             {
1127                 pt.x += GetScrollPos(hWnd, SB_HORZ);
1128                 pt.y += GetScrollPos(hWnd, SB_VERT);
1129                 bHittest = preview_page_hittest(pt);
1130             }
1131 
1132             if (bHittest)
1133                 SetCursor(LoadCursorW(GetModuleHandleW(0),
1134                                       MAKEINTRESOURCEW(IDC_ZOOM)));
1135             else
1136                 SetCursor(LoadCursorW(NULL, (WCHAR*)IDC_ARROW));
1137 
1138             return TRUE;
1139         }
1140 
1141         case WM_LBUTTONDOWN:
1142         {
1143             int page;
1144             POINT pt;
1145             pt.x = (short)LOWORD(lParam) + GetScrollPos(hWnd, SB_HORZ);
1146             pt.y = (short)HIWORD(lParam) + GetScrollPos(hWnd, SB_VERT);
1147             if ((page = preview_page_hittest(pt)) > 0)
1148             {
1149                 HWND hMainWnd = GetParent(hWnd);
1150 
1151                 /* Convert point from client coordinate to unzoomed page
1152                  * coordinate. */
1153                 pt.x -= preview.spacing.cx;
1154                 if (page > 1)
1155                     pt.x -= preview.bmScaledSize.cx + preview.spacing.cx;
1156                 pt.y -= preview.spacing.cy;
1157                 pt.x /= preview.zoomratio;
1158                 pt.y /= preview.zoomratio;
1159 
1160                 if (preview.zoomlevel == 0)
1161                     preview.saved_pages_shown = preview.pages_shown;
1162                 preview.zoomlevel = (preview.zoomlevel + 1) % 3;
1163                 preview.zoomratio = 0;
1164                 if (preview.zoomlevel == 0 && preview.saved_pages_shown > 1)
1165                 {
1166                     toggle_num_pages(hMainWnd);
1167                 } else if (preview.pages_shown > 1) {
1168                     if (page >= 2) preview.page++;
1169                     toggle_num_pages(hMainWnd);
1170                 } else {
1171                     update_preview_sizes(hWnd, TRUE);
1172                     InvalidateRect(hWnd, NULL, FALSE);
1173                     update_preview_buttons(hMainWnd);
1174                 }
1175 
1176                 if (preview.zoomlevel > 0) {
1177                     SCROLLINFO si;
1178                     /* Convert the coordinate back to client coordinate. */
1179                     pt.x *= preview.zoomratio;
1180                     pt.y *= preview.zoomratio;
1181                     pt.x += preview.spacing.cx;
1182                     pt.y += preview.spacing.cy;
1183                     /* Scroll to center view at that point on the page */
1184                     si.cbSize = sizeof(si);
1185                     si.fMask = SIF_PAGE;
1186                     GetScrollInfo(hWnd, SB_HORZ, &si);
1187                     pt.x -= si.nPage / 2;
1188                     SetScrollPos(hWnd, SB_HORZ, pt.x, TRUE);
1189                     GetScrollInfo(hWnd, SB_VERT, &si);
1190                     pt.y -= si.nPage / 2;
1191                     SetScrollPos(hWnd, SB_VERT, pt.y, TRUE);
1192                 }
1193             }
1194         }
1195 
1196         default:
1197             return DefWindowProcW(hWnd, msg, wParam, lParam);
1198     }
1199 
1200     return 0;
1201 }
1202 
1203 LRESULT preview_command(HWND hWnd, WPARAM wParam)
1204 {
1205     switch(LOWORD(wParam))
1206     {
1207         case ID_FILE_EXIT:
1208             PostMessageW(hWnd, WM_CLOSE, 0, 0);
1209             break;
1210 
1211         case ID_PREVIEW_NEXTPAGE:
1212         case ID_PREVIEW_PREVPAGE:
1213         {
1214             if(LOWORD(wParam) == ID_PREVIEW_NEXTPAGE)
1215                 preview.page++;
1216             else
1217                 preview.page--;
1218 
1219             update_preview(hWnd);
1220         }
1221         break;
1222 
1223         case ID_PREVIEW_NUMPAGES:
1224             toggle_num_pages(hWnd);
1225             break;
1226 
1227         case ID_PREVIEW_ZOOMIN:
1228             if (preview.zoomlevel < 2)
1229             {
1230                 if (preview.zoomlevel == 0)
1231                     preview.saved_pages_shown = preview.pages_shown;
1232                 preview.zoomlevel++;
1233                 preview.zoomratio = 0;
1234                 if (preview.pages_shown > 1)
1235                 {
1236                     /* Forced switch to one page when zooming in. */
1237                     toggle_num_pages(hWnd);
1238                 } else {
1239                     HWND hwndPreview = GetDlgItem(hWnd, IDC_PREVIEW);
1240                     update_preview_sizes(hwndPreview, TRUE);
1241                     InvalidateRect(hwndPreview, NULL, FALSE);
1242                     update_preview_buttons(hWnd);
1243                 }
1244             }
1245             break;
1246 
1247         case ID_PREVIEW_ZOOMOUT:
1248             if (preview.zoomlevel > 0)
1249             {
1250                 HWND hwndPreview = GetDlgItem(hWnd, IDC_PREVIEW);
1251                 preview.zoomlevel--;
1252                 preview.zoomratio = 0;
1253                 if (preview.zoomlevel == 0 && preview.saved_pages_shown > 1) {
1254                     toggle_num_pages(hWnd);
1255                 } else {
1256                     update_preview_sizes(hwndPreview, TRUE);
1257                     InvalidateRect(hwndPreview, NULL, FALSE);
1258                     update_preview_buttons(hWnd);
1259                 }
1260             }
1261             break;
1262 
1263         case ID_PRINT:
1264             dialog_print(hWnd, preview.wszFileName);
1265             SendMessageW(hWnd, WM_CLOSE, 0, 0);
1266             break;
1267     }
1268 
1269     return 0;
1270 }
1271