1/*
2 * Copyright (c) 1999, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26#include "awt_Component.h"
27#include "awt_PrintControl.h"
28#include "awt.h"
29#include "awt_PrintDialog.h"
30#include <winspool.h>
31#include <float.h>
32#include <math.h>
33
34#define ROUNDTOINT(x) ((int)((x)+0.5))
35static const int DEFAULT_RES = 72;
36static const double TENTHS_MM_TO_POINTS = 3.527777778;
37static const double LOMETRIC_TO_POINTS = (72.0 / 254.0);
38
39
40/* Values must match those defined in WPrinterJob.java */
41static const DWORD SET_COLOR = 0x00000200;
42static const DWORD SET_ORIENTATION = 0x00004000;
43static const DWORD SET_DUP_VERTICAL = 0x00000010;
44static const DWORD SET_DUP_HORIZONTAL = 0x00000020;
45static const DWORD SET_RES_HIGH = 0x00000040;
46static const DWORD SET_RES_LOW = 0x00000080;
47
48
49/* These methods and fields are on sun.awt.windows.WPrinterJob */
50jfieldID  AwtPrintControl::dialogOwnerPeerID;
51jmethodID AwtPrintControl::getPrintDCID;
52jmethodID AwtPrintControl::setPrintDCID;
53jmethodID AwtPrintControl::getDevmodeID;
54jmethodID AwtPrintControl::setDevmodeID;
55jmethodID AwtPrintControl::getDevnamesID;
56jmethodID AwtPrintControl::setDevnamesID;
57jmethodID AwtPrintControl::getParentWindowID;
58jfieldID  AwtPrintControl::driverDoesMultipleCopiesID;
59jfieldID  AwtPrintControl::driverDoesCollationID;
60jmethodID AwtPrintControl::getWin32MediaID;
61jmethodID AwtPrintControl::setWin32MediaID;
62jmethodID AwtPrintControl::getWin32MediaTrayID;
63jmethodID AwtPrintControl::setWin32MediaTrayID;
64jmethodID AwtPrintControl::getColorID;
65jmethodID AwtPrintControl::getCopiesID;
66jmethodID AwtPrintControl::getSelectID;
67jmethodID AwtPrintControl::getDestID;
68jmethodID AwtPrintControl::getDialogID;
69jmethodID AwtPrintControl::getFromPageID;
70jmethodID AwtPrintControl::getMaxPageID;
71jmethodID AwtPrintControl::getMinPageID;
72jmethodID AwtPrintControl::getCollateID;
73jmethodID AwtPrintControl::getOrientID;
74jmethodID AwtPrintControl::getQualityID;
75jmethodID AwtPrintControl::getPrintToFileEnabledID;
76jmethodID AwtPrintControl::getPrinterID;
77jmethodID AwtPrintControl::setPrinterID;
78jmethodID AwtPrintControl::getResID;
79jmethodID AwtPrintControl::getSidesID;
80jmethodID AwtPrintControl::getToPageID;
81jmethodID AwtPrintControl::setToPageID;
82jmethodID AwtPrintControl::setNativeAttID;
83jmethodID AwtPrintControl::setRangeCopiesID;
84jmethodID AwtPrintControl::setResID;
85jmethodID AwtPrintControl::setJobAttributesID;
86
87
88BOOL AwtPrintControl::IsSupportedLevel(HANDLE hPrinter, DWORD dwLevel) {
89    BOOL isSupported = FALSE;
90    DWORD cbBuf = 0;
91    LPBYTE pPrinter = NULL;
92
93    DASSERT(hPrinter != NULL);
94
95    VERIFY(::GetPrinter(hPrinter, dwLevel, NULL, 0, &cbBuf) == 0);
96    if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
97        pPrinter = new BYTE[cbBuf];
98        if (::GetPrinter(hPrinter, dwLevel, pPrinter, cbBuf, &cbBuf)) {
99            isSupported = TRUE;
100        }
101        delete[] pPrinter;
102    }
103
104    return isSupported;
105}
106
107BOOL AwtPrintControl::FindPrinter(jstring printerName, LPBYTE pPrinterEnum,
108                                  LPDWORD pcbBuf, LPTSTR * foundPrinter,
109                                  LPTSTR * foundPort)
110{
111    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
112
113    DWORD cReturned = 0;
114
115    if (pPrinterEnum == NULL) {
116        // Compute size of buffer
117        DWORD cbNeeded = 0;
118        ::EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
119                           NULL, 2, NULL, 0, &cbNeeded, &cReturned);
120        ::EnumPrinters(PRINTER_ENUM_LOCAL,
121                       NULL, 5, NULL, 0, pcbBuf, &cReturned);
122        if (cbNeeded > (*pcbBuf)) {
123            *pcbBuf = cbNeeded;
124        }
125        return TRUE;
126    }
127
128    DASSERT(printerName != NULL);
129
130    DWORD cbBuf = *pcbBuf, dummyWord = 0;
131
132    JavaStringBuffer printerNameBuf(env, printerName);
133    LPTSTR lpcPrinterName = (LPTSTR)printerNameBuf;
134    DASSERT(lpcPrinterName != NULL);
135
136    // For NT, first do a quick check of all remote and local printers.
137    // This only allows us to search by name, though. PRINTER_INFO_4
138    // doesn't support port searches. So, if the user has specified the
139    // printer name as "LPT1:" (even though this is actually a port
140    // name), we won't find the printer here.
141    if (!::EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
142                        NULL, 4, pPrinterEnum, cbBuf, &dummyWord, &cReturned)) {
143        return FALSE;
144    }
145
146    for (DWORD i = 0; i < cReturned; i++) {
147        PRINTER_INFO_4 *info4 = (PRINTER_INFO_4 *)
148            (pPrinterEnum + i * sizeof(PRINTER_INFO_4));
149        if (info4->pPrinterName != NULL &&
150            _tcsicmp(lpcPrinterName, info4->pPrinterName) == 0) {
151
152            // Fix for BugTraq Id 4281380.
153            // Get the port name since some drivers may require
154            // this name to be passed to ::DeviceCapabilities().
155            HANDLE hPrinter = NULL;
156            if (::OpenPrinter(info4->pPrinterName, &hPrinter, NULL)) {
157                // Fix for BugTraq Id 4286812.
158                // Some drivers don't support PRINTER_INFO_5.
159                // In this case we try PRINTER_INFO_2, and if that
160                // isn't supported as well return NULL port name.
161                try {
162                    if (AwtPrintControl::IsSupportedLevel(hPrinter, 5)) {
163                        VERIFY(::GetPrinter(hPrinter, 5, pPrinterEnum, cbBuf,
164                                            &dummyWord));
165                        PRINTER_INFO_5 *info5 = (PRINTER_INFO_5 *)pPrinterEnum;
166                        *foundPrinter = info5->pPrinterName;
167                        // pPortName may specify multiple ports. We only want one.
168                        *foundPort = (info5->pPortName != NULL)
169                            ? _tcstok(info5->pPortName, TEXT(",")) : NULL;
170                    } else if (AwtPrintControl::IsSupportedLevel(hPrinter, 2)) {
171                        VERIFY(::GetPrinter(hPrinter, 2, pPrinterEnum, cbBuf,
172                                            &dummyWord));
173                        PRINTER_INFO_2 *info2 = (PRINTER_INFO_2 *)pPrinterEnum;
174                        *foundPrinter = info2->pPrinterName;
175                        // pPortName may specify multiple ports. We only want one.
176                        *foundPort = (info2->pPortName != NULL)
177                            ? _tcstok(info2->pPortName, TEXT(",")) : NULL;
178                    } else {
179                        *foundPrinter = info4->pPrinterName;
180                        // We failed to determine port name for the found printer.
181                        *foundPort = NULL;
182                    }
183                } catch (std::bad_alloc&) {
184                    VERIFY(::ClosePrinter(hPrinter));
185                    throw;
186                }
187
188                VERIFY(::ClosePrinter(hPrinter));
189
190                return TRUE;
191            }
192
193            return FALSE;
194        }
195    }
196
197    // We still haven't found the printer, /* or we're using 95/98. */
198    // PRINTER_INFO_5 supports both printer name and port name, so
199    // we'll test both. On NT, PRINTER_ENUM_LOCAL means just local
200    // printers. This is what we want, because we already tested all
201    // remote printer names above (and remote printer port names are
202    // the same as remote printer names). On 95/98, PRINTER_ENUM_LOCAL
203    // means both remote and local printers. This is also what we want
204    // because we haven't tested any printers yet.
205    if (!::EnumPrinters(PRINTER_ENUM_LOCAL,
206                        NULL, 5, pPrinterEnum, cbBuf, &dummyWord, &cReturned)) {
207        return FALSE;
208    }
209
210    for (DWORD i = 0; i < cReturned; i++) {
211        PRINTER_INFO_5 *info5 = (PRINTER_INFO_5 *)
212            (pPrinterEnum + i * sizeof(PRINTER_INFO_5));
213        // pPortName can specify multiple ports. Test them one at
214        // a time.
215        if (info5->pPortName != NULL) {
216            LPTSTR port = _tcstok(info5->pPortName, TEXT(","));
217            while (port != NULL) {
218                if (_tcsicmp(lpcPrinterName, port) == 0) {
219                    *foundPrinter = info5->pPrinterName;
220                    *foundPort = port;
221                    return TRUE;
222                }
223                port = _tcstok(NULL, TEXT(","));
224            }
225        }
226    }
227
228    return FALSE;
229}
230
231
232void AwtPrintControl::initIDs(JNIEnv *env, jclass cls)
233{
234    TRY;
235
236    jclass cls = env->FindClass("sun/awt/windows/WPrinterJob");
237    CHECK_NULL(cls);
238
239    AwtPrintControl::dialogOwnerPeerID =
240      env->GetFieldID(cls, "dialogOwnerPeer", "Ljava/awt/peer/ComponentPeer;");
241    DASSERT(AwtPrintControl::dialogOwnerPeerID != NULL);
242    CHECK_NULL(AwtPrintControl::dialogOwnerPeerID);
243
244    AwtPrintControl::getParentWindowID = env->GetMethodID(cls,
245                                       "getParentWindowID", "()J");
246    DASSERT(AwtPrintControl::getParentWindowID != NULL);
247    CHECK_NULL(AwtPrintControl::getParentWindowID);
248
249    AwtPrintControl::getPrintDCID = env->GetMethodID(cls, "getPrintDC", "()J");
250    DASSERT(AwtPrintControl::getPrintDCID != NULL);
251    CHECK_NULL(AwtPrintControl::getPrintDCID);
252
253    AwtPrintControl::setPrintDCID =
254        env->GetMethodID(cls, "setPrintDC", "(J)V");
255    DASSERT(AwtPrintControl::setPrintDCID != NULL);
256    CHECK_NULL(AwtPrintControl::setPrintDCID);
257
258    AwtPrintControl::getDevmodeID = env->GetMethodID(cls, "getDevMode", "()J");
259    DASSERT(AwtPrintControl::getDevmodeID != NULL);
260    CHECK_NULL(AwtPrintControl::getDevmodeID);
261
262    AwtPrintControl::setDevmodeID =
263        env->GetMethodID(cls, "setDevMode", "(J)V");
264    DASSERT(AwtPrintControl::setDevmodeID != NULL);
265    CHECK_NULL(AwtPrintControl::setDevmodeID);
266
267    AwtPrintControl::getDevnamesID =
268        env->GetMethodID(cls, "getDevNames", "()J");
269    DASSERT(AwtPrintControl::getDevnamesID != NULL);
270    CHECK_NULL(AwtPrintControl::getDevnamesID);
271
272    AwtPrintControl::setDevnamesID =
273        env->GetMethodID(cls, "setDevNames", "(J)V");
274    DASSERT(AwtPrintControl::setDevnamesID != NULL);
275    CHECK_NULL(AwtPrintControl::setDevnamesID);
276
277    AwtPrintControl::driverDoesMultipleCopiesID =
278      env->GetFieldID(cls, "driverDoesMultipleCopies", "Z");
279    DASSERT(AwtPrintControl::driverDoesMultipleCopiesID != NULL);
280    CHECK_NULL(AwtPrintControl::driverDoesMultipleCopiesID);
281
282    AwtPrintControl::driverDoesCollationID =
283      env->GetFieldID(cls, "driverDoesCollation", "Z");
284    DASSERT(AwtPrintControl::driverDoesCollationID != NULL);
285    CHECK_NULL(AwtPrintControl::driverDoesCollationID);
286
287    AwtPrintControl::getCopiesID =
288      env->GetMethodID(cls, "getCopiesAttrib", "()I");
289    DASSERT(AwtPrintControl::getCopiesID != NULL);
290    CHECK_NULL(AwtPrintControl::getCopiesID);
291
292    AwtPrintControl::getCollateID =
293      env->GetMethodID(cls, "getCollateAttrib","()I");
294    DASSERT(AwtPrintControl::getCollateID != NULL);
295    CHECK_NULL(AwtPrintControl::getCollateID);
296
297    AwtPrintControl::getOrientID =
298      env->GetMethodID(cls, "getOrientAttrib", "()I");
299    DASSERT(AwtPrintControl::getOrientID != NULL);
300    CHECK_NULL(AwtPrintControl::getOrientID);
301
302    AwtPrintControl::getFromPageID =
303      env->GetMethodID(cls, "getFromPageAttrib", "()I");
304    DASSERT(AwtPrintControl::getFromPageID != NULL);
305    CHECK_NULL(AwtPrintControl::getFromPageID);
306
307    AwtPrintControl::getToPageID =
308      env->GetMethodID(cls, "getToPageAttrib", "()I");
309    DASSERT(AwtPrintControl::getToPageID != NULL);
310    CHECK_NULL(AwtPrintControl::getToPageID);
311
312    AwtPrintControl::getMinPageID =
313      env->GetMethodID(cls, "getMinPageAttrib", "()I");
314    DASSERT(AwtPrintControl::getMinPageID != NULL);
315    CHECK_NULL(AwtPrintControl::getMinPageID);
316
317    AwtPrintControl::getMaxPageID =
318      env->GetMethodID(cls, "getMaxPageAttrib", "()I");
319    DASSERT(AwtPrintControl::getMaxPageID != NULL);
320    CHECK_NULL(AwtPrintControl::getMaxPageID);
321
322    AwtPrintControl::getDestID =
323      env->GetMethodID(cls, "getDestAttrib", "()Z");
324    DASSERT(AwtPrintControl::getDestID != NULL);
325    CHECK_NULL(AwtPrintControl::getDestID);
326
327    AwtPrintControl::getQualityID =
328      env->GetMethodID(cls, "getQualityAttrib", "()I");
329    DASSERT(AwtPrintControl::getQualityID != NULL);
330    CHECK_NULL(AwtPrintControl::getQualityID);
331
332    AwtPrintControl::getColorID =
333      env->GetMethodID(cls, "getColorAttrib", "()I");
334    DASSERT(AwtPrintControl::getColorID != NULL);
335    CHECK_NULL(AwtPrintControl::getColorID);
336
337    AwtPrintControl::getSidesID =
338      env->GetMethodID(cls, "getSidesAttrib", "()I");
339    DASSERT(AwtPrintControl::getSidesID != NULL);
340    CHECK_NULL(AwtPrintControl::getSidesID);
341
342    AwtPrintControl::getPrinterID =
343      env->GetMethodID(cls, "getPrinterAttrib", "()Ljava/lang/String;");
344    DASSERT(AwtPrintControl::getPrinterID != NULL);
345    CHECK_NULL(AwtPrintControl::getPrinterID);
346
347    AwtPrintControl::getWin32MediaID =
348        env->GetMethodID(cls, "getWin32MediaAttrib", "()[I");
349    DASSERT(AwtPrintControl::getWin32MediaID != NULL);
350    CHECK_NULL(AwtPrintControl::getWin32MediaID);
351
352    AwtPrintControl::setWin32MediaID =
353      env->GetMethodID(cls, "setWin32MediaAttrib", "(III)V");
354    DASSERT(AwtPrintControl::setWin32MediaID != NULL);
355    CHECK_NULL(AwtPrintControl::setWin32MediaID);
356
357    AwtPrintControl::getWin32MediaTrayID =
358        env->GetMethodID(cls, "getMediaTrayAttrib", "()I");
359    DASSERT(AwtPrintControl::getWin32MediaTrayID != NULL);
360    CHECK_NULL(AwtPrintControl::getWin32MediaTrayID);
361
362    AwtPrintControl::setWin32MediaTrayID =
363      env->GetMethodID(cls, "setMediaTrayAttrib", "(I)V");
364    DASSERT(AwtPrintControl::setWin32MediaTrayID != NULL);
365    CHECK_NULL(AwtPrintControl::setWin32MediaTrayID);
366
367    AwtPrintControl::getSelectID =
368      env->GetMethodID(cls, "getSelectAttrib", "()I");
369    DASSERT(AwtPrintControl::getSelectID != NULL);
370    CHECK_NULL(AwtPrintControl::getSelectID);
371
372    AwtPrintControl::getPrintToFileEnabledID =
373      env->GetMethodID(cls, "getPrintToFileEnabled", "()Z");
374    DASSERT(AwtPrintControl::getPrintToFileEnabledID != NULL);
375    CHECK_NULL(AwtPrintControl::getPrintToFileEnabledID);
376
377    AwtPrintControl::setNativeAttID =
378      env->GetMethodID(cls, "setNativeAttributes", "(III)V");
379    DASSERT(AwtPrintControl::setNativeAttID != NULL);
380    CHECK_NULL(AwtPrintControl::setNativeAttID);
381
382    AwtPrintControl::setRangeCopiesID =
383      env->GetMethodID(cls, "setRangeCopiesAttribute", "(IIZI)V");
384    DASSERT(AwtPrintControl::setRangeCopiesID != NULL);
385    CHECK_NULL(AwtPrintControl::setRangeCopiesID);
386
387    AwtPrintControl::setResID =
388      env->GetMethodID(cls, "setResolutionDPI", "(II)V");
389    DASSERT(AwtPrintControl::setResID != NULL);
390    CHECK_NULL(AwtPrintControl::setResID);
391
392    AwtPrintControl::setPrinterID =
393      env->GetMethodID(cls, "setPrinterNameAttrib", "(Ljava/lang/String;)V");
394    DASSERT(AwtPrintControl::setPrinterID != NULL);
395    CHECK_NULL(AwtPrintControl::setPrinterID);
396
397    AwtPrintControl::setJobAttributesID =
398        env->GetMethodID(cls, "setJobAttributes",
399        "(Ljavax/print/attribute/PrintRequestAttributeSet;IISSSSSSS)V");
400    DASSERT(AwtPrintControl::setJobAttributesID != NULL);
401    CHECK_NULL(AwtPrintControl::setJobAttributesID);
402
403    CATCH_BAD_ALLOC;
404}
405
406BOOL CALLBACK PrintDlgHook(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam)
407{
408    TRY;
409
410    if (iMsg == WM_INITDIALOG) {
411        SetForegroundWindow(hDlg);
412        return FALSE;
413    }
414    return FALSE;
415
416    CATCH_BAD_ALLOC_RET(TRUE);
417}
418
419BOOL AwtPrintControl::CreateDevModeAndDevNames(PRINTDLG *ppd,
420                                               LPTSTR pPrinterName,
421                                               LPTSTR pPortName)
422{
423    DWORD cbNeeded = 0;
424    LPBYTE pPrinter = NULL;
425    BOOL retval = FALSE;
426    HANDLE hPrinter;
427
428    try {
429        if (!::OpenPrinter(pPrinterName, &hPrinter, NULL)) {
430            goto done;
431        }
432        VERIFY(::GetPrinter(hPrinter, 2, NULL, 0, &cbNeeded) == 0);
433        if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
434            goto done;
435        }
436        pPrinter = new BYTE[cbNeeded];
437        if (!::GetPrinter(hPrinter, 2, pPrinter, cbNeeded, &cbNeeded)) {
438            goto done;
439        }
440        PRINTER_INFO_2 *info2 = (PRINTER_INFO_2 *)pPrinter;
441
442        // Create DEVMODE, if it exists.
443        if (info2->pDevMode != NULL) {
444            size_t devmodeSize =
445                sizeof(DEVMODE) + info2->pDevMode->dmDriverExtra;
446            ppd->hDevMode = ::GlobalAlloc(GHND, devmodeSize);
447            if (ppd->hDevMode == NULL) {
448                throw std::bad_alloc();
449            }
450            DEVMODE *devmode = (DEVMODE *)::GlobalLock(ppd->hDevMode);
451            DASSERT(!::IsBadWritePtr(devmode, devmodeSize));
452            memcpy(devmode, info2->pDevMode, devmodeSize);
453            VERIFY(::GlobalUnlock(ppd->hDevMode) == 0);
454            DASSERT(::GetLastError() == NO_ERROR);
455        }
456
457        // Create DEVNAMES.
458        if (pPortName != NULL) {
459            info2->pPortName = pPortName;
460        } else if (info2->pPortName != NULL) {
461            // pPortName may specify multiple ports. We only want one.
462            info2->pPortName = _tcstok(info2->pPortName, TEXT(","));
463        }
464
465        size_t lenDriverName = ((info2->pDriverName != NULL)
466                                    ? _tcslen(info2->pDriverName)
467                                    : 0) + 1;
468        size_t lenPrinterName = ((pPrinterName != NULL)
469                                     ? _tcslen(pPrinterName)
470                                     : 0) + 1;
471        size_t lenOutputName = ((info2->pPortName != NULL)
472                                    ? _tcslen(info2->pPortName)
473                                    : 0) + 1;
474        size_t devnameSize= sizeof(DEVNAMES) +
475                        lenDriverName*sizeof(TCHAR) +
476                        lenPrinterName*sizeof(TCHAR) +
477                        lenOutputName*sizeof(TCHAR);
478
479        ppd->hDevNames = ::GlobalAlloc(GHND, devnameSize);
480        if (ppd->hDevNames == NULL) {
481            throw std::bad_alloc();
482        }
483
484        DEVNAMES *devnames =
485            (DEVNAMES *)::GlobalLock(ppd->hDevNames);
486        DASSERT(!IsBadWritePtr(devnames, devnameSize));
487        LPTSTR lpcDevnames = (LPTSTR)devnames;
488
489        // note: all sizes are in characters, not in bytes
490        devnames->wDriverOffset = sizeof(DEVNAMES)/sizeof(TCHAR);
491        devnames->wDeviceOffset =
492            static_cast<WORD>(sizeof(DEVNAMES)/sizeof(TCHAR) + lenDriverName);
493        devnames->wOutputOffset =
494            static_cast<WORD>(sizeof(DEVNAMES)/sizeof(TCHAR) + lenDriverName + lenPrinterName);
495        if (info2->pDriverName != NULL) {
496            _tcscpy_s(lpcDevnames + devnames->wDriverOffset, devnameSize - devnames->wDriverOffset, info2->pDriverName);
497        } else {
498            *(lpcDevnames + devnames->wDriverOffset) = _T('\0');
499        }
500        if (pPrinterName != NULL) {
501            _tcscpy_s(lpcDevnames + devnames->wDeviceOffset, devnameSize - devnames->wDeviceOffset, pPrinterName);
502        } else {
503            *(lpcDevnames + devnames->wDeviceOffset) = _T('\0');
504        }
505        if (info2->pPortName != NULL) {
506            _tcscpy_s(lpcDevnames + devnames->wOutputOffset, devnameSize - devnames->wOutputOffset, info2->pPortName);
507        } else {
508            *(lpcDevnames + devnames->wOutputOffset) = _T('\0');
509        }
510        VERIFY(::GlobalUnlock(ppd->hDevNames) == 0);
511        DASSERT(::GetLastError() == NO_ERROR);
512    } catch (std::bad_alloc&) {
513        if (ppd->hDevNames != NULL) {
514            VERIFY(::GlobalFree(ppd->hDevNames) == NULL);
515            ppd->hDevNames = NULL;
516        }
517        if (ppd->hDevMode != NULL) {
518            VERIFY(::GlobalFree(ppd->hDevMode) == NULL);
519            ppd->hDevMode = NULL;
520        }
521        delete [] pPrinter;
522        VERIFY(::ClosePrinter(hPrinter));
523        hPrinter = NULL;
524        throw;
525    }
526
527    retval = TRUE;
528
529done:
530    delete [] pPrinter;
531    if (hPrinter) {
532        VERIFY(::ClosePrinter(hPrinter));
533        hPrinter = NULL;
534    }
535
536    return retval;
537}
538
539
540WORD AwtPrintControl::getNearestMatchingPaper(LPTSTR printer, LPTSTR port,
541                                      double origWid, double origHgt,
542                                      double* newWid, double *newHgt) {
543    const double epsilon = 0.50;
544    const double tolerance = (1.0 * 72.0);  // # inches * 72
545    int numPaperSizes = 0;
546    WORD *papers = NULL;
547    POINT *paperSizes = NULL;
548
549    if ((printer== NULL) || (port == NULL)) {
550        return 0;
551    }
552
553    SAVE_CONTROLWORD
554    numPaperSizes = (int)DeviceCapabilities(printer, port, DC_PAPERSIZE,
555                                              NULL, NULL);
556
557    if (numPaperSizes > 0) {
558        papers = (WORD*)SAFE_SIZE_ARRAY_ALLOC(safe_Malloc, sizeof(WORD), numPaperSizes);
559        paperSizes = (POINT *)SAFE_SIZE_ARRAY_ALLOC(safe_Malloc, sizeof(*paperSizes),
560                                          numPaperSizes);
561
562        DWORD result1 = DeviceCapabilities(printer, port,
563                                       DC_PAPERS, (LPTSTR) papers, NULL);
564
565        DWORD result2 = DeviceCapabilities(printer, port,
566                                       DC_PAPERSIZE, (LPTSTR) paperSizes,
567                                       NULL);
568
569        // REMIND: cache in papers and paperSizes
570        if (result1 == -1 || result2 == -1 ) {
571            free((LPTSTR) papers);
572            papers = NULL;
573            free((LPTSTR) paperSizes);
574            paperSizes = NULL;
575        }
576    }
577    RESTORE_CONTROLWORD
578
579    double closestWid = 0.0;
580    double closestHgt = 0.0;
581    WORD   closestMatch = 0;
582
583    if (paperSizes != NULL) {
584
585      /* Paper sizes are in 0.1mm units. Convert to 1/72"
586       * For each paper size, compute the difference from the paper size
587       * passed in. Use a least-squares difference, so paper much different
588       * in x or y should score poorly
589       */
590        double diffw = origWid;
591        double diffh = origHgt;
592        double least_square = diffw * diffw + diffh * diffh;
593        double tmp_ls;
594        double widpts, hgtpts;
595
596        for (int i=0;i<numPaperSizes;i++) {
597            widpts = paperSizes[i].x * LOMETRIC_TO_POINTS;
598            hgtpts = paperSizes[i].y * LOMETRIC_TO_POINTS;
599
600            if ((fabs(origWid - widpts) < epsilon) &&
601                (fabs(origHgt - hgtpts) < epsilon)) {
602                closestWid = origWid;
603                closestHgt = origHgt;
604                closestMatch = papers[i];
605                break;
606            }
607
608            diffw = fabs(widpts - origWid);
609            diffh = fabs(hgtpts - origHgt);
610            tmp_ls = diffw * diffw + diffh * diffh;
611            if ((diffw < tolerance) && (diffh < tolerance) &&
612                (tmp_ls < least_square)) {
613                least_square = tmp_ls;
614                closestWid = widpts;
615                closestHgt = hgtpts;
616                closestMatch = papers[i];
617            }
618        }
619    }
620
621    if (closestWid > 0) {
622        *newWid = closestWid;
623    }
624    if (closestHgt > 0) {
625        *newHgt = closestHgt;
626    }
627
628    if (papers != NULL) {
629        free((LPTSTR)papers);
630    }
631
632    if (paperSizes != NULL) {
633        free((LPTSTR)paperSizes);
634    }
635
636    return closestMatch;
637}
638
639/*
640 * Copy settings into a print dialog & any devmode
641 */
642BOOL AwtPrintControl::InitPrintDialog(JNIEnv *env,
643                                      jobject printCtrl, PRINTDLG &pd) {
644    HWND hwndOwner = NULL;
645    jobject dialogOwner =
646        env->GetObjectField(printCtrl, AwtPrintControl::dialogOwnerPeerID);
647    if (dialogOwner != NULL) {
648        AwtComponent *dialogOwnerComp =
649          (AwtComponent *)JNI_GET_PDATA(dialogOwner);
650
651        hwndOwner = dialogOwnerComp->GetHWnd();
652        env->DeleteLocalRef(dialogOwner);
653        dialogOwner = NULL;
654    }
655    jobject mdh = NULL;
656    jobject dest = NULL;
657    jobject select = NULL;
658    jobject dialog = NULL;
659    LPTSTR printName = NULL;
660    LPTSTR portName = NULL;
661
662    // If the user didn't specify a printer, then this call returns the
663    // name of the default printer.
664    jstring printerName = (jstring)
665      env->CallObjectMethod(printCtrl, AwtPrintControl::getPrinterID);
666
667    if (printerName != NULL) {
668
669        pd.hDevMode = AwtPrintControl::getPrintHDMode(env, printCtrl);
670        pd.hDevNames = AwtPrintControl::getPrintHDName(env, printCtrl);
671
672        LPTSTR getName = (LPTSTR)JNU_GetStringPlatformChars(env,
673                                                      printerName, NULL);
674        if (getName == NULL) {
675            env->DeleteLocalRef(printerName);
676            throw std::bad_alloc();
677        }
678
679        BOOL samePrinter = FALSE;
680
681        // check if given printername is same as the currently saved printer
682        if (pd.hDevNames != NULL ) {
683
684            DEVNAMES *devnames = (DEVNAMES *)::GlobalLock(pd.hDevNames);
685            if (devnames != NULL) {
686                LPTSTR lpdevnames = (LPTSTR)devnames;
687                printName = lpdevnames+devnames->wDeviceOffset;
688
689                if (!_tcscmp(printName, getName)) {
690
691                    samePrinter = TRUE;
692                    printName = _tcsdup(lpdevnames+devnames->wDeviceOffset);
693                    portName = _tcsdup(lpdevnames+devnames->wOutputOffset);
694
695                }
696            }
697            ::GlobalUnlock(pd.hDevNames);
698        }
699
700        if (!samePrinter) {
701            LPTSTR foundPrinter = NULL;
702            LPTSTR foundPort = NULL;
703            DWORD cbBuf = 0;
704            VERIFY(AwtPrintControl::FindPrinter(NULL, NULL, &cbBuf,
705                                                NULL, NULL));
706            LPBYTE buffer = new BYTE[cbBuf];
707
708            if (AwtPrintControl::FindPrinter(printerName, buffer, &cbBuf,
709                                             &foundPrinter, &foundPort) &&
710                (foundPrinter != NULL) && (foundPort != NULL)) {
711
712                printName = _tcsdup(foundPrinter);
713                portName = _tcsdup(foundPort);
714
715                if (!AwtPrintControl::CreateDevModeAndDevNames(&pd,
716                                                   foundPrinter, foundPort)) {
717                    delete [] buffer;
718                    if (printName != NULL) {
719                      free(printName);
720                    }
721                    if (portName != NULL) {
722                      free(portName);
723                    }
724                    env->DeleteLocalRef(printerName);
725                    return FALSE;
726                }
727
728                DASSERT(pd.hDevNames != NULL);
729            } else {
730                delete [] buffer;
731                if (printName != NULL) {
732                  free(printName);
733                }
734                if (portName != NULL) {
735                  free(portName);
736                }
737                env->DeleteLocalRef(printerName);
738                return FALSE;
739            }
740
741            delete [] buffer;
742        }
743        env->DeleteLocalRef(printerName);
744        // PrintDlg may change the values of hDevMode and hDevNames so we
745        // re-initialize our saved handles.
746        AwtPrintControl::setPrintHDMode(env, printCtrl, NULL);
747        AwtPrintControl::setPrintHDName(env, printCtrl, NULL);
748    } else {
749
750        // There is no default printer. This means that there are no
751        // printers installed at all.
752
753        if (printName != NULL) {
754          free(printName);
755        }
756        if (portName != NULL) {
757          free(portName);
758        }
759        // Returning TRUE means try to display the native print dialog
760        // which will either display an error message or prompt the
761        // user to install a printer.
762        return TRUE;
763    }
764
765    // Now, set-up the struct for the real calls to ::PrintDlg and ::CreateDC
766
767    pd.hwndOwner = hwndOwner;
768    pd.Flags = PD_ENABLEPRINTHOOK | PD_RETURNDC | PD_USEDEVMODECOPIESANDCOLLATE;
769    pd.lpfnPrintHook = (LPPRINTHOOKPROC)PrintDlgHook;
770
771    pd.nFromPage = (WORD)env->CallIntMethod(printCtrl,
772                                            AwtPrintControl::getFromPageID);
773    pd.nToPage = (WORD)env->CallIntMethod(printCtrl,
774                                          AwtPrintControl::getToPageID);
775    pd.nMinPage = (WORD)env->CallIntMethod(printCtrl,
776                                           AwtPrintControl::getMinPageID);
777    jint maxPage = env->CallIntMethod(printCtrl,
778                                      AwtPrintControl::getMaxPageID);
779
780    jint selectType = env->CallIntMethod(printCtrl,
781                                         AwtPrintControl::getSelectID);
782
783    pd.nMaxPage = (maxPage <= (jint)((WORD)-1)) ? (WORD)maxPage : (WORD)-1;
784    // In the event that the application displays the dialog before
785    // installing a Printable, but sets a page range, then max page will be 1
786    // since the default state of a PrinterJob is an empty "Book" Pageable.
787    // Windows pops up an error dialog in such a case which isn't very
788    // forthcoming about the exact problem.
789    // So if we detect this fix up such a problem here.
790    if (pd.nMinPage > pd.nFromPage) pd.nMinPage = pd.nFromPage;
791    if (pd.nMaxPage < pd.nToPage) pd.nMaxPage = pd.nToPage;
792    if (selectType != 0 && (pd.nFromPage > pd.nMinPage || pd.nToPage < pd.nMaxPage)) {
793        if (selectType == PD_SELECTION) {
794            pd.Flags |= PD_SELECTION;
795        } else {
796            pd.Flags |= PD_PAGENUMS;
797        }
798    }
799
800    if (env->CallBooleanMethod(printCtrl,
801                               AwtPrintControl::getDestID)) {
802      pd.Flags |= PD_PRINTTOFILE;
803    }
804
805    // selectType identifies whether No selection (2D) or
806    // SunPageSelection (AWT)
807    if (selectType != 0) {
808      pd.Flags |= selectType;
809    }
810
811    if (!env->CallBooleanMethod(printCtrl,
812                                AwtPrintControl::getPrintToFileEnabledID)) {
813      pd.Flags |= PD_DISABLEPRINTTOFILE;
814    }
815
816    if (pd.hDevMode != NULL) {
817      DEVMODE *devmode = (DEVMODE *)::GlobalLock(pd.hDevMode);
818      DASSERT(!IsBadWritePtr(devmode, sizeof(DEVMODE)));
819
820      WORD copies = (WORD)env->CallIntMethod(printCtrl,
821                                             AwtPrintControl::getCopiesID);
822      if (copies > 0) {
823          devmode->dmFields |= DM_COPIES;
824          devmode->dmCopies = copies;
825      }
826
827      jint orient = env->CallIntMethod(printCtrl,
828                                       AwtPrintControl::getOrientID);
829      if (orient == 0) {  // PageFormat.LANDSCAPE == 0
830        devmode->dmFields |= DM_ORIENTATION;
831        devmode->dmOrientation = DMORIENT_LANDSCAPE;
832      } else if (orient == 1) { // PageFormat.PORTRAIT == 1
833        devmode->dmFields |= DM_ORIENTATION;
834        devmode->dmOrientation = DMORIENT_PORTRAIT;
835      }
836
837      // -1 means unset, so we'll accept the printer default.
838      int collate = env->CallIntMethod(printCtrl,
839                                       AwtPrintControl::getCollateID);
840      if (collate == 1) {
841        devmode->dmFields |= DM_COLLATE;
842        devmode->dmCollate = DMCOLLATE_TRUE;
843      } else if (collate == 0) {
844        devmode->dmFields |= DM_COLLATE;
845        devmode->dmCollate = DMCOLLATE_FALSE;
846      }
847
848      int quality = env->CallIntMethod(printCtrl,
849                                       AwtPrintControl::getQualityID);
850      if (quality) {
851        devmode->dmFields |= DM_PRINTQUALITY;
852        devmode->dmPrintQuality = quality;
853      }
854
855      int color = env->CallIntMethod(printCtrl,
856                                     AwtPrintControl::getColorID);
857      if (color) {
858        devmode->dmFields |= DM_COLOR;
859        devmode->dmColor = color;
860      }
861
862      int sides = env->CallIntMethod(printCtrl,
863                                     AwtPrintControl::getSidesID);
864      if (sides) {
865        devmode->dmFields |= DM_DUPLEX;
866        devmode->dmDuplex = (int)sides;
867      }
868
869      jintArray obj = (jintArray)env->CallObjectMethod(printCtrl,
870                                       AwtPrintControl::getWin32MediaID);
871      jboolean isCopy;
872      jint *wid_ht = env->GetIntArrayElements(obj,
873                                              &isCopy);
874
875      double newWid = 0.0, newHt = 0.0;
876      if (wid_ht != NULL && wid_ht[0] != 0 && wid_ht[1] != 0) {
877        devmode->dmFields |= DM_PAPERSIZE;
878        devmode->dmPaperSize = AwtPrintControl::getNearestMatchingPaper(
879                                             printName,
880                                             portName,
881                                             (double)wid_ht[0],
882                                             (double)wid_ht[1],
883                                             &newWid, &newHt);
884
885      }
886      env->ReleaseIntArrayElements(obj, wid_ht, 0);
887      ::GlobalUnlock(pd.hDevMode);
888      devmode = NULL;
889    }
890
891    if (printName != NULL) {
892      free(printName);
893    }
894    if (portName != NULL) {
895      free(portName);
896    }
897
898    return TRUE;
899}
900
901
902/*
903 * Copy settings from print dialog & any devmode back into attributes
904 * or properties.
905 */
906extern "C" {
907extern void setCapabilities(JNIEnv *env, jobject WPrinterJob, HDC hdc);
908}
909BOOL AwtPrintControl::UpdateAttributes(JNIEnv *env,
910                                       jobject printCtrl, PRINTDLG &pd) {
911
912    DEVNAMES *devnames = NULL;
913    DEVMODE *devmode = NULL;
914    unsigned int copies = 1;
915    DWORD pdFlags = pd.Flags;
916    DWORD dmFields = 0, dmValues = 0;
917    bool newDC = false;
918
919    // This call ensures that default PrintService gets updated for the
920    // case where initially, there weren't any printers.
921    env->CallObjectMethod(printCtrl, AwtPrintControl::getPrinterID);
922
923    if (pd.hDevMode != NULL) {
924        devmode = (DEVMODE *)::GlobalLock(pd.hDevMode);
925        DASSERT(!IsBadReadPtr(devmode, sizeof(DEVMODE)));
926    }
927
928    if (devmode != NULL) {
929        /* Query the settings we understand and are interested in.
930         * For the flags that are set in dmFields, where the values
931         * are a simple enumeration, set the same bits in a clean dmFields
932         * variable, and set bits in a dmValues variable to indicate the
933         * selected value. These can all be passed up to Java in one
934         * call to sync up the Java view of this.
935         */
936
937        if (devmode->dmFields & DM_COPIES) {
938            dmFields |= DM_COPIES;
939            copies = devmode->dmCopies;
940            if (pd.nCopies == 1) {
941                env->SetBooleanField(printCtrl,
942                                     driverDoesMultipleCopiesID,
943                                     JNI_TRUE);
944            } else {
945              copies = pd.nCopies;
946            }
947        }
948
949        if (devmode->dmFields & DM_PAPERSIZE) {
950            env->CallVoidMethod(printCtrl, AwtPrintControl::setWin32MediaID,
951                                devmode->dmPaperSize, devmode->dmPaperWidth,
952                                devmode->dmPaperLength);
953
954        }
955
956        if (devmode->dmFields & DM_DEFAULTSOURCE) {
957            env->CallVoidMethod(printCtrl,
958                                AwtPrintControl::setWin32MediaTrayID,
959                                devmode->dmDefaultSource);
960        }
961
962        if (devmode->dmFields & DM_COLOR) {
963            dmFields |= DM_COLOR;
964            if (devmode->dmColor == DMCOLOR_COLOR) {
965                dmValues |= SET_COLOR;
966            }
967        }
968
969        if (devmode->dmFields & DM_ORIENTATION) {
970            dmFields |= DM_ORIENTATION;
971            if (devmode->dmOrientation == DMORIENT_LANDSCAPE) {
972                dmValues |= SET_ORIENTATION;
973            }
974        }
975
976        if (devmode->dmFields & DM_COLLATE) {
977            dmFields |= DM_COLLATE;
978            if (devmode->dmCollate == DMCOLLATE_TRUE) {
979                pdFlags |= PD_COLLATE;
980                env->SetBooleanField(printCtrl,
981                                     driverDoesCollationID,
982                                     JNI_TRUE);
983            } else {
984                pdFlags &= ~PD_COLLATE;
985            }
986        }
987
988        if (devmode->dmFields & DM_PRINTQUALITY) {
989            /* value < 0 indicates quality setting.
990             * value > 0 indicates X resolution. In that case
991             * hopefully we will also find y-resolution specified.
992             * If its not, assume its the same as x-res.
993             * Maybe Java code should try to reconcile this against
994             * the printers claimed set of supported resolutions.
995             */
996            if (devmode->dmPrintQuality < 0) {
997                if (dmFields |= DM_PRINTQUALITY) {
998                    if (devmode->dmPrintQuality == DMRES_HIGH) {
999                        dmValues |= SET_RES_HIGH;
1000                    } else if ((devmode->dmPrintQuality == DMRES_LOW) ||
1001                               (devmode->dmPrintQuality == DMRES_DRAFT)) {
1002                        dmValues |= SET_RES_LOW;
1003                    } else if (devmode->dmPrintQuality == DMRES_MEDIUM) {
1004                        /* default */
1005                    }
1006                }
1007            } else {
1008                int xRes = devmode->dmPrintQuality;
1009
1010                /* For some printers, printer quality can specify 1200IQ
1011                 * In this case, dmPrintQuality comes out 600 and
1012                 * dmYResolution comes out 2, similarly for 2400IQ
1013                 * dmPrintQuality comes out 600 and dmYResolution comes out 4
1014                 * which is not a valid resolution
1015                 * so for IQ setting, we modify y-resolution only when it is
1016                 * greater than 10.
1017                 */
1018                int yRes = (devmode->dmFields & DM_YRESOLUTION) &&
1019                           (devmode->dmYResolution > 10) ?
1020                           devmode->dmYResolution : devmode->dmPrintQuality;
1021
1022                env->CallVoidMethod(printCtrl, AwtPrintControl::setResID,
1023                                    xRes, yRes);
1024            }
1025        }
1026
1027        if (devmode->dmFields & DM_DUPLEX) {
1028            dmFields |= DM_DUPLEX;
1029            if (devmode->dmDuplex == DMDUP_HORIZONTAL) {
1030              dmValues |= SET_DUP_HORIZONTAL;
1031            } else if (devmode->dmDuplex == DMDUP_VERTICAL) {
1032                dmValues |= SET_DUP_VERTICAL;
1033            }
1034        }
1035
1036
1037        ::GlobalUnlock(pd.hDevMode);
1038        devmode = NULL;
1039    } else {
1040        copies = pd.nCopies;
1041    }
1042
1043    if (pd.hDevNames != NULL) {
1044        DEVNAMES *devnames = (DEVNAMES*)::GlobalLock(pd.hDevNames);
1045        DASSERT(!IsBadReadPtr(devnames, sizeof(DEVNAMES)));
1046        LPTSTR lpcNames = (LPTSTR)devnames;
1047        LPTSTR pbuf = (_tcslen(lpcNames + devnames->wDeviceOffset) == 0 ?
1048                      TEXT("") : lpcNames + devnames->wDeviceOffset);
1049        if (pbuf != NULL) {
1050            jstring jstr = JNU_NewStringPlatform(env, pbuf);
1051            env->CallVoidMethod(printCtrl,
1052                                AwtPrintControl::setPrinterID,
1053                                jstr);
1054            env->DeleteLocalRef(jstr);
1055        }
1056        pbuf = (_tcslen(lpcNames + devnames->wOutputOffset) == 0 ?
1057                      TEXT("") : lpcNames + devnames->wOutputOffset);
1058        if (pbuf != NULL) {
1059            if (wcscmp(pbuf, L"FILE:") == 0) {
1060                pdFlags |= PD_PRINTTOFILE;
1061            }
1062        }
1063        ::GlobalUnlock(pd.hDevNames);
1064        devnames = NULL;
1065    }
1066
1067
1068    env->CallVoidMethod(printCtrl, AwtPrintControl::setNativeAttID,
1069                        pdFlags,  dmFields, dmValues);
1070
1071
1072    // copies  & range are always set so no need to check for any flags
1073    env->CallVoidMethod(printCtrl, AwtPrintControl::setRangeCopiesID,
1074                        pd.nFromPage, pd.nToPage, (pdFlags & PD_PAGENUMS),
1075                        copies);
1076
1077    // repeated calls to printDialog should not leak handles
1078    HDC oldDC = AwtPrintControl::getPrintDC(env, printCtrl);
1079    if (pd.hDC != oldDC) {
1080        if (oldDC != NULL) {
1081            ::DeleteDC(oldDC);
1082        }
1083        AwtPrintControl::setPrintDC(env, printCtrl, pd.hDC);
1084        newDC = true;
1085    }
1086    // Need to update WPrinterJob with device resolution settings for
1087    // new or changed DC.
1088    setCapabilities(env, printCtrl, pd.hDC);
1089
1090    HGLOBAL oldG = AwtPrintControl::getPrintHDMode(env, printCtrl);
1091    if (pd.hDevMode != oldG) {
1092        AwtPrintControl::setPrintHDMode(env, printCtrl, pd.hDevMode);
1093    }
1094
1095    oldG = AwtPrintControl::getPrintHDName(env, printCtrl);
1096    if (pd.hDevNames != oldG) {
1097        AwtPrintControl::setPrintHDName(env, printCtrl, pd.hDevNames);
1098    }
1099
1100    return newDC;
1101}
1102
1103
1104BOOL AwtPrintControl::getDevmode( HANDLE hPrinter,
1105                                 LPTSTR printerName,
1106                                 LPDEVMODE *pDevMode) {
1107
1108    if (hPrinter == NULL || printerName == NULL || pDevMode == NULL) {
1109      return FALSE;
1110    }
1111
1112    SAVE_CONTROLWORD
1113
1114    DWORD dwNeeded = ::DocumentProperties(NULL, hPrinter, printerName,
1115                                        NULL, NULL, 0);
1116
1117    RESTORE_CONTROLWORD
1118
1119    if (dwNeeded <= 0) {
1120        *pDevMode = NULL;
1121        return FALSE;
1122    }
1123
1124    *pDevMode = (LPDEVMODE)GlobalAlloc(GPTR, dwNeeded);
1125
1126    if (*pDevMode == NULL) {
1127        return FALSE;
1128    }
1129
1130    DWORD dwRet = ::DocumentProperties(NULL,
1131                                       hPrinter,
1132                                       printerName,
1133                                       *pDevMode,
1134                                       NULL,
1135                                       DM_OUT_BUFFER);
1136
1137    RESTORE_CONTROLWORD
1138
1139    if (dwRet != IDOK)  {
1140        /* if failure, cleanup and return failure */
1141        GlobalFree(*pDevMode);
1142        *pDevMode = NULL;
1143        return FALSE;
1144    }
1145
1146    return TRUE;
1147}
1148