| //======================================================================== |
| // |
| // This file is licensed under the GPLv2 or later |
| // |
| // Copyright (C) 2014 Rodrigo Rivas Costa <rodrigorivascosta@gmail.com> |
| // Copyright (C) 2014, 2017 Adrian Johnson <ajohnson@redneon.com> |
| // Copyright (C) 2017 Albert Astals Cid <aacid@kde.org> |
| // |
| // To see a description of the changes please see the Changelog file that |
| // came with your tarball or type make ChangeLog if you are building from git |
| // |
| //======================================================================== |
| |
| #include <cairo.h> |
| #ifdef CAIRO_HAS_WIN32_SURFACE |
| |
| #include <cairo-win32.h> |
| |
| #include "parseargs.h" |
| #include "pdftocairo-win32.h" |
| #include "Win32Console.h" |
| |
| #include <dlgs.h> |
| #include <commctrl.h> |
| #include <commdlg.h> |
| #include <windowsx.h> |
| #include <winspool.h> |
| |
| static HDC hdc; |
| static HGLOBAL hDevmode = nullptr; |
| static HGLOBAL hDevnames = nullptr; |
| static DEVMODEA *devmode; |
| static char *printerName; |
| |
| struct Win32Option |
| { |
| const char *name; |
| int value; |
| }; |
| |
| static const Win32Option win32PaperSource[] = |
| { |
| {"upper", DMBIN_UPPER}, |
| {"onlyone", DMBIN_ONLYONE}, |
| {"lower", DMBIN_LOWER}, |
| {"middle", DMBIN_MIDDLE}, |
| {"manual", DMBIN_MANUAL}, |
| {"envelope", DMBIN_ENVELOPE}, |
| {"envmanual", DMBIN_ENVMANUAL}, |
| {"auto", DMBIN_AUTO}, |
| {"tractor", DMBIN_TRACTOR}, |
| {"smallfmt", DMBIN_SMALLFMT}, |
| {"largefmt", DMBIN_LARGEFMT}, |
| {"largecapacity", DMBIN_LARGECAPACITY}, |
| {"formsource", DMBIN_FORMSOURCE}, |
| {nullptr, 0} |
| }; |
| |
| static void parseSource(GooString *source) |
| { |
| const Win32Option *option = win32PaperSource; |
| while (option->name) { |
| if (source->cmp(option->name) == 0) { |
| devmode->dmDefaultSource = option->value; |
| devmode->dmFields |= DM_DEFAULTSOURCE; |
| return; |
| } |
| option++; |
| } |
| fprintf(stderr, "Warning: Unknown paper source \"%s\"\n", source->getCString()); |
| } |
| |
| static const Win32Option win32DuplexMode[] = |
| { |
| {"off", DMDUP_SIMPLEX}, |
| {"short", DMDUP_HORIZONTAL}, |
| {"long", DMDUP_VERTICAL}, |
| {nullptr, 0} |
| }; |
| |
| static void parseDuplex(GooString *mode) |
| { |
| const Win32Option *option = win32DuplexMode; |
| while (option->name) { |
| if (mode->cmp(option->name) == 0) { |
| devmode->dmDuplex = option->value; |
| devmode->dmFields |= DM_DUPLEX; |
| return; |
| } |
| option++; |
| } |
| fprintf(stderr, "Warning: Unknown duplex mode \"%s\"\n", mode->getCString()); |
| } |
| |
| static void fillCommonPrinterOptions(GBool duplex) |
| { |
| if (duplex) { |
| devmode->dmDuplex = DMDUP_HORIZONTAL; |
| devmode->dmFields |= DM_DUPLEX; |
| } |
| } |
| |
| static void fillPagePrinterOptions(double w, double h) |
| { |
| w *= 254.0 / 72.0; // units are 0.1mm |
| h *= 254.0 / 72.0; |
| if (w > h) { |
| devmode->dmOrientation = DMORIENT_LANDSCAPE; |
| devmode->dmPaperWidth = h; |
| devmode->dmPaperLength = w; |
| } else { |
| devmode->dmOrientation = DMORIENT_PORTRAIT; |
| devmode->dmPaperWidth = w; |
| devmode->dmPaperLength = h; |
| } |
| devmode->dmPaperSize = 0; |
| devmode->dmFields |= DM_ORIENTATION | DM_PAPERWIDTH | DM_PAPERLENGTH; |
| } |
| |
| |
| static void fillPrinterOptions(GBool duplex, GooString *printOpt) |
| { |
| //printOpt format is: <opt1>=<val1>,<opt2>=<val2>,... |
| const char *nextOpt = printOpt->getCString(); |
| while (nextOpt && *nextOpt) |
| { |
| const char *comma = strchr(nextOpt, ','); |
| GooString opt; |
| if (comma) { |
| opt.Set(nextOpt, comma - nextOpt); |
| nextOpt = comma + 1; |
| } else { |
| opt.Set(nextOpt); |
| nextOpt = NULL; |
| } |
| //here opt is "<optN>=<valN> " |
| const char *equal = strchr(opt.getCString(), '='); |
| if (!equal) { |
| fprintf(stderr, "Warning: unknown printer option \"%s\"\n", opt.getCString()); |
| continue; |
| } |
| int iequal = equal - opt.getCString(); |
| GooString value(&opt, iequal + 1, opt.getLength() - iequal - 1); |
| opt.del(iequal, opt.getLength() - iequal); |
| //here opt is "<optN>" and value is "<valN>" |
| |
| if (opt.cmp("source") == 0) { |
| parseSource(&value); |
| } else if (opt.cmp("duplex") == 0) { |
| if (duplex) |
| fprintf(stderr, "Warning: duplex mode is specified both as standalone and printer options\n"); |
| else |
| parseDuplex( &value); |
| } else { |
| fprintf(stderr, "Warning: unknown printer option \"%s\"\n", opt.getCString()); |
| } |
| } |
| } |
| |
| static void getLocalPos(HWND wind, HWND dlg, RECT *rect) |
| { |
| GetClientRect(wind, rect); |
| MapWindowPoints(wind, dlg, (LPPOINT)rect, 2); |
| } |
| |
| static HWND createGroupBox(HWND parent, HINSTANCE hInstance, HMENU id, const char *label, RECT *rect) |
| { |
| HWND hwnd = CreateWindowA(WC_BUTTONA, |
| label, |
| WS_CHILD | WS_VISIBLE | BS_GROUPBOX, |
| rect->left, rect->top, |
| rect->right - rect->left, |
| rect->bottom - rect->top, |
| parent, id, |
| hInstance, NULL); |
| HFONT hFont = (HFONT)SendMessage(parent, WM_GETFONT, (WPARAM)0, (LPARAM)0); |
| if (hFont) |
| SendMessage(hwnd, WM_SETFONT, (WPARAM) hFont, (LPARAM)0); |
| return hwnd; |
| } |
| |
| static HWND createCheckBox(HWND parent, HINSTANCE hInstance, HMENU id, const char *label, RECT *rect) |
| { |
| HWND hwnd = CreateWindowA(WC_BUTTONA, |
| label, |
| WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX | WS_TABSTOP, |
| rect->left, rect->top, |
| rect->right - rect->left, |
| rect->bottom - rect->top, |
| parent, id, |
| hInstance, NULL); |
| HFONT hFont = (HFONT)SendMessage(parent, WM_GETFONT, (WPARAM)0, (LPARAM)0); |
| if (hFont) |
| SendMessage(hwnd, WM_SETFONT, (WPARAM) hFont, (LPARAM)0); |
| return hwnd; |
| } |
| |
| static HWND createStaticText(HWND parent, HINSTANCE hinstance, HMENU id, const char *text, RECT *rect) |
| { |
| HWND hwnd = CreateWindowA(WC_STATICA, |
| text, |
| WS_CHILD | WS_VISIBLE | SS_LEFT, |
| rect->left, rect->top, |
| rect->right - rect->left, |
| rect->bottom - rect->top, |
| parent, id, |
| hinstance, NULL); |
| HFONT hFont = (HFONT)SendMessage(parent, WM_GETFONT, (WPARAM)0, (LPARAM)0); |
| if (hFont) |
| SendMessage(hwnd, WM_SETFONT, (WPARAM) hFont, (LPARAM)0); |
| return hwnd; |
| } |
| |
| static HWND createPageScaleComboBox(HWND parent, HINSTANCE hinstance, HMENU id, RECT *rect) |
| { |
| HWND hwnd = CreateWindowA(WC_COMBOBOX, |
| "", |
| WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP | |
| CBS_DROPDOWNLIST, |
| rect->left, rect->top, |
| rect->right - rect->left, |
| rect->bottom - rect->top, |
| parent, id, |
| hinstance, NULL); |
| HFONT hFont = (HFONT)SendMessage(parent, WM_GETFONT, (WPARAM)0, (LPARAM)0); |
| if (hFont) |
| SendMessage(hwnd, WM_SETFONT, (WPARAM) hFont, (LPARAM)0); |
| ComboBox_AddString(hwnd, "None"); |
| ComboBox_AddString(hwnd, "Shrink to Printable Area"); |
| ComboBox_AddString(hwnd, "Fit to Printable Area"); |
| return hwnd; |
| } |
| |
| enum PageScale { NONE = 0, SHRINK = 1, FIT = 2 }; |
| |
| // used to set/get option values in printDialogHookProc |
| static PageScale pageScale; |
| static GBool centerPage; |
| static GBool useOrigPageSize; |
| |
| // PrintDlg callback to customize the print dialog with additional controls. |
| static UINT_PTR CALLBACK printDialogHookProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam) |
| { |
| if (uiMsg == WM_INITDIALOG) { |
| // Get the extant controls. See dlgs.h and prnsetup.dlg for the PrintDlg control ids. |
| HWND printerGroupWind = GetDlgItem(hdlg, grp4); |
| HWND printerComboWind = GetDlgItem(hdlg, cmb4); |
| HWND nameLabelWind = GetDlgItem(hdlg, stc6); |
| HWND statusLabelWind = GetDlgItem(hdlg, stc8); |
| HWND printRangeGroupWind = GetDlgItem(hdlg, grp1); |
| HWND radio1Wind = GetDlgItem(hdlg, rad1); |
| HWND radio2Wind = GetDlgItem(hdlg, rad3); |
| HWND copiesGroupWind = GetDlgItem(hdlg, grp2); |
| HWND okWind = GetDlgItem(hdlg, IDOK); |
| HWND cancelWind = GetDlgItem(hdlg, IDCANCEL); |
| if (!printerGroupWind || !printerComboWind || !nameLabelWind || !statusLabelWind || |
| !printRangeGroupWind || !radio1Wind || !radio2Wind || !copiesGroupWind || |
| !okWind || !cancelWind) |
| return 0; |
| |
| // Get the size and position of the above controls relative to the |
| // print dialog window |
| RECT printerGroupRect; |
| RECT printerComboRect; |
| RECT nameLabelRect; |
| RECT statusLabelRect; |
| RECT printRangeGroupRect; |
| RECT radio1Rect, radio2Rect; |
| RECT copiesGroupRect; |
| RECT okRect, cancelRect; |
| getLocalPos(printerGroupWind, hdlg, &printerGroupRect); |
| getLocalPos(printerComboWind, hdlg, &printerComboRect); |
| getLocalPos(nameLabelWind, hdlg, &nameLabelRect); |
| getLocalPos(statusLabelWind, hdlg, &statusLabelRect); |
| getLocalPos(printRangeGroupWind, hdlg, &printRangeGroupRect); |
| getLocalPos(radio1Wind, hdlg, &radio1Rect); |
| getLocalPos(radio2Wind, hdlg, &radio2Rect); |
| getLocalPos(copiesGroupWind, hdlg, &copiesGroupRect); |
| getLocalPos(okWind, hdlg, &okRect); |
| getLocalPos(cancelWind, hdlg, &cancelRect); |
| |
| // Calc space required for new group |
| int interGroupSpace = printRangeGroupRect.top - printerGroupRect.bottom; |
| int groupHeight = statusLabelRect.top - printerGroupRect.top + |
| printRangeGroupRect.bottom - radio1Rect.bottom; |
| |
| // Increase dialog size |
| RECT dlgRect; |
| GetWindowRect(hdlg, &dlgRect); |
| SetWindowPos(hdlg, nullptr, |
| dlgRect.left, dlgRect.top, |
| dlgRect.right - dlgRect.left, |
| dlgRect.bottom - dlgRect.top + interGroupSpace + groupHeight, |
| SWP_NOMOVE|SWP_NOREDRAW|SWP_NOZORDER); |
| |
| // Add new group and controls |
| HINSTANCE hinstance = (HINSTANCE)GetWindowLongPtr(hdlg, GWLP_HINSTANCE); |
| RECT pdfGroupBoxRect; |
| pdfGroupBoxRect.left = printRangeGroupRect.left; |
| pdfGroupBoxRect.right = copiesGroupRect.right; |
| pdfGroupBoxRect.top = printRangeGroupRect.bottom + interGroupSpace; |
| pdfGroupBoxRect.bottom = pdfGroupBoxRect.top + groupHeight; |
| createGroupBox(hdlg, hinstance, (HMENU)grp3, "PDF Print Options", &pdfGroupBoxRect); |
| |
| RECT textRect; |
| textRect.left = nameLabelRect.left; |
| textRect.right = nameLabelRect.left + 1.8*(printerComboRect.left - nameLabelRect.left); |
| textRect.top = pdfGroupBoxRect.top + nameLabelRect.top - printerGroupRect.top; |
| textRect.bottom = textRect.top + nameLabelRect.bottom - nameLabelRect.top; |
| createStaticText(hdlg, hinstance, (HMENU)stc1, "Page Scaling:", &textRect); |
| |
| RECT comboBoxRect; |
| comboBoxRect.left = textRect.right; |
| comboBoxRect.right = comboBoxRect.left + printerComboRect.right - printerComboRect.left;; |
| comboBoxRect.top = pdfGroupBoxRect.top + printerComboRect.top - printerGroupRect.top; |
| comboBoxRect.bottom = textRect.top + 4*(printerComboRect.bottom - printerComboRect.top); |
| HWND comboBoxWind = createPageScaleComboBox(hdlg, hinstance, (HMENU)cmb1, &comboBoxRect); |
| |
| RECT checkBox1Rect; |
| checkBox1Rect.left = radio1Rect.left; |
| checkBox1Rect.right = pdfGroupBoxRect.right - 10; |
| checkBox1Rect.top = pdfGroupBoxRect.top + statusLabelRect.top - printerGroupRect.top; |
| checkBox1Rect.bottom = checkBox1Rect.top + radio1Rect.bottom - radio1Rect.top; |
| HWND checkBox1Wind = createCheckBox(hdlg, hinstance, (HMENU)chx3, "Center", &checkBox1Rect); |
| |
| RECT checkBox2Rect; |
| checkBox2Rect.left = radio1Rect.left; |
| checkBox2Rect.right = pdfGroupBoxRect.right - 10; |
| checkBox2Rect.top = checkBox1Rect.top + radio2Rect.top - radio1Rect.top; |
| checkBox2Rect.bottom = checkBox2Rect.top + radio1Rect.bottom - radio1Rect.top; |
| HWND checkBox2Wind = createCheckBox(hdlg, hinstance, (HMENU)chx4, "Select page size using document page size", &checkBox2Rect); |
| |
| // Move OK and Cancel buttons down ensuring they are last in the Z order |
| // so that the tab order is correct. |
| SetWindowPos(okWind, |
| HWND_BOTTOM, |
| okRect.left, |
| okRect.top + interGroupSpace + groupHeight, |
| 0, 0, |
| SWP_NOSIZE); // keep current size |
| SetWindowPos(cancelWind, |
| HWND_BOTTOM, |
| cancelRect.left, |
| cancelRect.top + interGroupSpace + groupHeight, |
| 0, 0, |
| SWP_NOSIZE); // keep current size |
| |
| // Initialize control values |
| ComboBox_SetCurSel(comboBoxWind, pageScale); |
| Button_SetCheck(checkBox1Wind, centerPage ? BST_CHECKED : BST_UNCHECKED); |
| Button_SetCheck(checkBox2Wind, useOrigPageSize ? BST_CHECKED : BST_UNCHECKED); |
| |
| } else if (uiMsg == WM_COMMAND) { |
| // Save settings |
| UINT id = LOWORD(wParam); |
| if (id == cmb1) |
| pageScale = (PageScale)ComboBox_GetCurSel(GetDlgItem(hdlg, cmb1)); |
| if (id == chx3) |
| centerPage = IsDlgButtonChecked(hdlg, chx3); |
| if (id == chx4) |
| useOrigPageSize = IsDlgButtonChecked(hdlg, chx4); |
| } |
| return 0; |
| } |
| |
| void win32SetupPrinter(GooString *printer, GooString *printOpt, |
| GBool duplex, GBool setupdlg) |
| { |
| if (printer->getCString()[0] == 0) { |
| DWORD size = 0; |
| GetDefaultPrinterA(nullptr, &size); |
| printerName = (char*)gmalloc(size); |
| GetDefaultPrinterA(printerName, &size); |
| } else { |
| printerName = gstrndup(printer->getCString(), printer->getLength()); |
| } |
| |
| //Query the size of the DEVMODE struct |
| LONG szProp = DocumentPropertiesA(nullptr, nullptr, printerName, nullptr, nullptr, 0); |
| if (szProp < 0) { |
| fprintf(stderr, "Error: Printer \"%s\" not found\n", printerName); |
| exit(99); |
| } |
| devmode = (DEVMODEA*)gmalloc(szProp); |
| memset(devmode, 0, szProp); |
| devmode->dmSize = sizeof(DEVMODEA); |
| devmode->dmSpecVersion = DM_SPECVERSION; |
| //Load the current default configuration for the printer into devmode |
| if (DocumentPropertiesA(nullptr, nullptr, printerName, devmode, devmode, DM_OUT_BUFFER) < 0) { |
| fprintf(stderr, "Error: Printer \"%s\" not found\n", printerName); |
| exit(99); |
| } |
| |
| // Update devmode with selected print options |
| fillCommonPrinterOptions(duplex); |
| fillPrinterOptions(duplex, printOpt); |
| |
| // Call DocumentProperties again so the driver can update its private data |
| // with the modified print options. This will also display the printer |
| // properties dialog if setupdlg is true. |
| int ret; |
| DWORD mode = DM_IN_BUFFER | DM_OUT_BUFFER; |
| if (setupdlg) |
| mode |= DM_IN_PROMPT; |
| ret = DocumentPropertiesA(nullptr, nullptr, printerName, devmode, devmode, mode); |
| if (ret < 0) { |
| fprintf(stderr, "Error: Printer \"%s\" not found\n", printerName); |
| exit(99); |
| } |
| if (setupdlg && ret == IDCANCEL) |
| exit(0); |
| |
| hdc = CreateDCA(nullptr, printerName, nullptr, devmode); |
| if (!hdc) { |
| fprintf(stderr, "Error: Printer \"%s\" not found\n", printerName); |
| exit(99); |
| } |
| } |
| |
| void win32ShowPrintDialog(GBool *expand, GBool *noShrink, GBool *noCenter, |
| GBool *usePDFPageSize, GBool *allPages, |
| int *firstPage, int *lastPage, int maxPages) |
| { |
| PRINTDLG pd; |
| memset(&pd, 0, sizeof(PRINTDLG)); |
| pd.lStructSize = sizeof(PRINTDLG); |
| pd.Flags = PD_NOSELECTION | PD_ENABLEPRINTHOOK | PD_USEDEVMODECOPIESANDCOLLATE | PD_RETURNDC; |
| if (*allPages) { |
| pd.nFromPage = 1; |
| pd.nToPage = maxPages; |
| } else { |
| pd.Flags |= PD_PAGENUMS; |
| pd.nFromPage = *firstPage; |
| pd.nToPage = *lastPage; |
| } |
| pd.nCopies = 1; |
| pd.nMinPage = 1; |
| pd.nMaxPage = maxPages; |
| pd.lpfnPrintHook = printDialogHookProc; |
| if (!*expand && *noShrink) |
| pageScale = NONE; |
| else if (!*expand && !*noShrink) |
| pageScale = SHRINK; |
| else |
| pageScale = FIT; |
| centerPage = !*noCenter; |
| useOrigPageSize = *usePDFPageSize; |
| |
| if (PrintDlgA(&pd)) { |
| // Ok |
| hDevnames = pd.hDevNames; |
| DEVNAMES *devnames = (DEVNAMES*)GlobalLock(hDevnames); |
| printerName = (char*)devnames + devnames->wDeviceOffset; |
| hDevmode = pd.hDevMode; |
| devmode = (DEVMODEA*)GlobalLock(hDevmode); |
| hdc = pd.hDC; |
| if (pd.Flags & PD_PAGENUMS) { |
| *allPages = gFalse; |
| *firstPage = pd.nFromPage; |
| *lastPage = pd.nToPage; |
| } else { |
| *allPages = gTrue; |
| } |
| if (pageScale == NONE) { |
| *expand = gFalse; |
| *noShrink = gTrue; |
| } else if (pageScale == SHRINK) { |
| *expand = gFalse; |
| *noShrink = gFalse; |
| } else { |
| *expand = gTrue; |
| *noShrink = gFalse; |
| } |
| *noCenter = !centerPage; |
| *usePDFPageSize = useOrigPageSize; |
| } else { |
| // Cancel |
| exit(0); |
| } |
| } |
| |
| cairo_surface_t *win32BeginDocument(GooString *inputFileName, GooString *outputFileName) |
| { |
| DOCINFOA docinfo; |
| memset(&docinfo, 0, sizeof(docinfo)); |
| docinfo.cbSize = sizeof(docinfo); |
| if (inputFileName->cmp("fd://0") == 0) |
| docinfo.lpszDocName = "pdftocairo <stdin>"; |
| else |
| docinfo.lpszDocName = inputFileName->getCString(); |
| if (outputFileName) |
| docinfo.lpszOutput = outputFileName->getCString(); |
| if (StartDocA(hdc, &docinfo) <=0) { |
| fprintf(stderr, "Error: StartDoc failed\n"); |
| exit(99); |
| } |
| |
| return cairo_win32_printing_surface_create(hdc); |
| } |
| |
| void win32BeginPage(double *w, double *h, GBool changePageSize, GBool useFullPage) |
| { |
| if (changePageSize) |
| fillPagePrinterOptions(*w, *h); |
| if (DocumentPropertiesA(nullptr, nullptr, printerName, devmode, devmode, DM_IN_BUFFER | DM_OUT_BUFFER) < 0) { |
| fprintf(stderr, "Error: Printer \"%s\" not found\n", printerName); |
| exit(99); |
| } |
| ResetDCA(hdc, devmode); |
| |
| // Get actual paper size or if useFullPage is false the printable area. |
| // Transform the hdc scale to points to be consistent with other cairo backends |
| int x_dpi = GetDeviceCaps (hdc, LOGPIXELSX); |
| int y_dpi = GetDeviceCaps (hdc, LOGPIXELSY); |
| int x_off = GetDeviceCaps (hdc, PHYSICALOFFSETX); |
| int y_off = GetDeviceCaps (hdc, PHYSICALOFFSETY); |
| if (useFullPage) { |
| *w = GetDeviceCaps (hdc, PHYSICALWIDTH)*72.0/x_dpi; |
| *h = GetDeviceCaps (hdc, PHYSICALHEIGHT)*72.0/y_dpi; |
| } else { |
| *w = GetDeviceCaps (hdc, HORZRES)*72.0/x_dpi; |
| *h = GetDeviceCaps (hdc, VERTRES)*72.0/y_dpi; |
| } |
| XFORM xform; |
| xform.eM11 = x_dpi/72.0; |
| xform.eM12 = 0; |
| xform.eM21 = 0; |
| xform.eM22 = y_dpi/72.0; |
| if (useFullPage) { |
| xform.eDx = -x_off; |
| xform.eDy = -y_off; |
| } else { |
| xform.eDx = 0; |
| xform.eDy = 0; |
| } |
| SetGraphicsMode (hdc, GM_ADVANCED); |
| SetWorldTransform (hdc, &xform); |
| |
| StartPage(hdc); |
| } |
| |
| void win32EndPage(GooString *imageFileName) |
| { |
| EndPage(hdc); |
| } |
| |
| void win32EndDocument() |
| { |
| EndDoc(hdc); |
| DeleteDC(hdc); |
| if (hDevmode) { |
| GlobalUnlock(hDevmode); |
| GlobalFree(hDevmode); |
| } else { |
| gfree(devmode); |
| } |
| if (hDevnames) { |
| GlobalUnlock(hDevnames); |
| GlobalFree(hDevnames); |
| } else { |
| gfree(printerName); |
| } |
| } |
| |
| #endif // CAIRO_HAS_WIN32_SURFACE |