123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680 |
- /* Copyright (C) 1998, 1999 Aladdin Enterprises. All rights reserved.
-
- This file is part of AFPL Ghostscript.
-
- AFPL Ghostscript is distributed with NO WARRANTY OF ANY KIND. No author or
- distributor accepts any responsibility for the consequences of using it, or
- for whether it serves any particular purpose or works at all, unless he or
- she says so in writing. Refer to the Aladdin Free Public License (the
- "License") for full details.
-
- Every copy of AFPL Ghostscript must include a copy of the License, normally
- in a plain ASCII text file named PUBLIC. The License grants you the right
- to copy, modify and redistribute AFPL Ghostscript, but only under certain
- conditions described in the License. Among other things, the License
- requires that the copyright notice and this notice be preserved on all
- copies.
- */
- /* $Id: gdevclj.c,v 1.2 2000/09/19 19:00:12 lpd Exp $ */
- /*
- * H-P Color LaserJet 5/5M device; based on the PaintJet.
- */
- #include "math_.h"
- #include "gx.h"
- #include "gsparam.h"
- #include "gdevprn.h"
- #include "gdevpcl.h"
- typedef struct gx_device_clj_s gx_device_clj;
- struct gx_device_clj_s {
- gx_device_common;
- gx_prn_device_common;
- bool rotated;
- };
- #define pclj ((gx_device_clj *)pdev)
- /*
- * The HP Color LaserJet 5/5M provides a rather unexpected speed/performance
- * tradeoff.
- *
- * When generating rasters, only the fixed (simple) color spaces provide
- * reasonable performance (in this case, reasonable != good). However, in
- * these modes, certain of the fully-saturated primary colors (cyan, blue,
- * green, and red) are rendered differently as rasters as opposed to colored
- * geometric objects. Hence, the color of the output will be other than what
- * is expected.
- *
- * Alternatively, the direct color, 1-bit per pixel scheme can be used. This
- * will produce the expected colors, but performance will deteriorate
- * significantly (observed printing time will be about 3 times longer than
- * when using the simple color mode).
- *
- * Note that when using the latter mode to view output from the PCL
- * interpreter, geometric objects and raster rendered with other than
- * geometric color spaces will have the same appearance as if sent directly
- * to the CLJ, but rasters generated from simple color spaces will have a
- * different appearance. To make the latter rasters match in appearance, the
- * faster printing mode must be used (in which the case the other objects
- * will not have the same appearance).
- */
- #define USE_FAST_MODE
- /* X_DPI and Y_DPI must be the same */
- #define X_DPI 300
- #define Y_DPI 300
- /*
- * Array of paper sizes, and the corresponding offsets.
- */
- typedef struct clj_paper_size_s {
- uint tag; /* paper type tag */
- int orient; /* logical page orientation to use */
- float width, height; /* in pts; +- 5 pts */
- gs_point offsets; /* offsets in the given orientation */
- } clj_paper_size;
- /*
- * The Color LaserJet prints page sizes up to 11.8" wide (A4 size) in
- * long-edge-feed (landscape) orientation. Only executive, letter, and
- * A4 size are supported for color, so we don't bother to list the others.
- */
- private const clj_paper_size clj_paper_sizes[] = {
- /* U.S. letter size comes first so it will be the default. */
- { 2, 1, 11.00 * 72.0, 8.50 * 72.0, { .200 * 72.0, 0.0 } },
- { 1, 1, 10.50 * 72.0, 7.25 * 72.0, { .200 * 72.0, 0.0 } },
- { 26, 1, 11.69 * 72.0, 8.27 * 72.0, { .197 * 72.0, 0.0 } }
- };
- /*
- * The supported set of resolutions.
- *
- * The Color LaserJet 5/5M is actually a pseudo-contone device, with hardware
- * capable of providing about 16 levels of intensity. The current code does
- * not take advantage of this feature, because it is not readily controllable
- * via PCL. Rather, the device is modeled as a bi-level device in each of
- * three color planes. The maximum supported resolution for such an arrangement
- * is 300 dpi.
- *
- * The CLJ does support raster scaling, but to invoke that scaling, even for
- * integral factors, involves a large performance penalty. Hence, only those
- * resolutions that can be supported without invoking raster scaling are
- * included here. These resolutions are always the same in the fast and slow
- * scan directions, so only a single value is listed here.
- *
- * All valuse are in dots per inch.
- */
- private const float supported_resolutions[] = { 75.0, 100.0, 150.0, 300.0 };
- /* indicate the maximum supported resolution and scan-line length (pts) */
- #define CLJ_MAX_RES 300.0
- #define CLJ_MAX_SCANLINE (12.0 * 72.0)
- /*
- * Determine a requested resolution pair is supported.
- */
- private bool
- is_supported_resolution(
- const float HWResolution[2]
- )
- {
- int i;
- for (i = 0; i < countof(supported_resolutions); i++) {
- if (HWResolution[0] == supported_resolutions[i])
- return HWResolution[0] == HWResolution[1];
- }
- return false;
- }
- /* ---------------- Standard driver ---------------- */
- /*
- * Find the paper size information corresponding to a given pair of dimensions.
- * If rotatep != 0, *rotatep is set to true if the page must be rotated 90
- * degrees to fit.
- *
- * A return value of 0 indicates the paper size is not supported.
- *
- * Note that for the standard driver, rotation is not allowed.
- */
- private const clj_paper_size *
- get_paper_size(
- const float MediaSize[2],
- bool * rotatep
- )
- {
- static const float tolerance = 5.0;
- float width = MediaSize[0];
- float height = MediaSize[1];
- const clj_paper_size * psize = 0;
- int i;
- for (i = 0, psize = clj_paper_sizes; i < countof(clj_paper_sizes); i++, psize++) {
- if ( (fabs(width - psize->width) <= tolerance) &&
- (fabs(height - psize->height) <= tolerance) ) {
- if (rotatep != 0)
- *rotatep = false;
- return psize;
- } else if ( (fabs(width - psize->height) <= tolerance) &&
- (fabs(height - psize->width) <= tolerance) ) {
- if (rotatep != 0)
- *rotatep = true;
- return psize;
- }
- }
- return 0;
- }
- /*
- * Get the (PostScript style) default matrix for the current page size.
- *
- * For all of the supported sizes, the page will be printed with long-edge
- * feed (the CLJ does support some additional sizes, but only for monochrome).
- * As will all HP laser printers, the printable region marin is 12 pts. from
- * the edge of the physical page.
- */
- private void
- clj_get_initial_matrix( gx_device *pdev, gs_matrix *pmat)
- {
- floatp fs_res = pdev->HWResolution[0] / 72.0;
- floatp ss_res = pdev->HWResolution[1] / 72.0;
- const clj_paper_size *psize;
- psize = get_paper_size(pdev->MediaSize, NULL);
- /* if the paper size is not recognized, not much can be done */
- /* This shouldn't be possible since clj_put_params rejects */
- /* unknown media sizes. */
- if (psize == 0) {
- pmat->xx = fs_res;
- pmat->xy = 0.0;
- pmat->yx = 0.0;
- pmat->yy = -ss_res;
- pmat->tx = 0.0;
- pmat->ty = pdev->MediaSize[1] * ss_res;
- return;
- }
-
- if (pclj->rotated) {
- pmat->xx = 0.0;
- pmat->xy = ss_res;
- pmat->yx = fs_res;
- pmat->yy = 0.0;
- pmat->tx = -psize->offsets.x * fs_res;
- pmat->ty = -psize->offsets.y * ss_res;
- } else {
- pmat->xx = fs_res;
- pmat->xy = 0.0;
- pmat->yx = 0.0;
- pmat->yy = -ss_res;
- pmat->tx = -psize->offsets.x * fs_res;
- pmat->ty = pdev->height + psize->offsets.y * ss_res;
- }
- }
- /*
- * Get parameters, including InputAttributes for all supported page sizes.
- * We associate each page size with a different "media source", since that
- * is currently the only way to register multiple page sizes.
- */
- private int
- clj_get_params(gx_device *pdev, gs_param_list *plist)
- {
- gs_param_dict mdict;
- int code = gdev_prn_get_params(pdev, plist);
- int ecode = code;
- int i;
- code = gdev_begin_input_media(plist, &mdict, countof(clj_paper_sizes));
- if (code < 0)
- ecode = code;
- else {
- for (i = 0; i < countof(clj_paper_sizes); ++i) {
- code = gdev_write_input_page_size(i, &mdict,
- clj_paper_sizes[i].width,
- clj_paper_sizes[i].height);
- if (code < 0)
- ecode = code;
- }
- code = gdev_end_input_media(plist, &mdict);
- if (code < 0)
- ecode = code;
- }
- return ecode;
- }
- /*
- * Get the media size being set by put_params, if any. Return 0 if no media
- * size is being set, 1 (and set mediasize[]) if the size is being set, <0
- * on error.
- */
- private int
- clj_media_size(float mediasize[2], gs_param_list *plist)
- {
- gs_param_float_array fres;
- gs_param_float_array fsize;
- gs_param_int_array hwsize;
- int have_pagesize = 0;
- if ( (param_read_float_array(plist, "HWResolution", &fres) == 0) &&
- !is_supported_resolution(fres.data) )
- return_error(gs_error_rangecheck);
- if ( (param_read_float_array(plist, "PageSize", &fsize) == 0) ||
- (param_read_float_array(plist, ".MediaSize", &fsize) == 0) ) {
- mediasize[0] = fsize.data[0];
- mediasize[1] = fsize.data[1];
- have_pagesize = 1;
- }
- if (param_read_int_array(plist, "HWSize", &hwsize) == 0) {
- mediasize[0] = ((float)hwsize.data[0]) / fres.data[0];
- mediasize[1] = ((float)hwsize.data[1]) / fres.data[1];
- have_pagesize = 1;
- }
- return have_pagesize;
- }
- /*
- * Special put_params routine, to make certain the desired MediaSize and
- * HWResolution are supported.
- */
- private int
- clj_put_params(
- gx_device * pdev,
- gs_param_list * plist
- )
- {
- float mediasize[2];
- bool rotate = false;
- int have_pagesize = clj_media_size(mediasize, plist);
- if (have_pagesize < 0)
- return have_pagesize;
- if (have_pagesize) {
- if (get_paper_size(mediasize, &rotate) == 0 || rotate)
- return_error(gs_error_rangecheck);
- }
- return gdev_prn_put_params(pdev, plist);
- }
- /*
- * Pack and then compress a scanline of data. Return the size of the compressed
- * data produced.
- *
- * Input is arranged with one byte per pixel, but only the three low-order bits
- * are used. These bits are in order ymc, with yellow being the highest order
- * bit.
- *
- * Output is arranged in three planes, with one bit per pixel per plane. The
- * Color LaserJet 5/5M does support more congenial pixel encodings, but use
- * of anything other than the fixed palettes seems to result in very poor
- * performance.
- *
- * Only compresion mode 2 is used. Compression mode 1 (pure run length) has
- * an advantage over compression mode 2 only in cases in which very long runs
- * occur (> 128 bytes). Since both methods provide good compression in that
- * case, it is not worth worrying about, and compression mode 2 provides much
- * better worst-case behavior. Compression mode 3 requires considerably more
- * effort to generate, so it is useful only when it is known a prior that
- * scanlines repeat frequently.
- */
- private void
- pack_and_compress_scanline(
- const byte * pin,
- int in_size,
- byte * pout[3],
- int out_size[3]
- )
- {
- #define BUFF_SIZE \
- ( ((int)(CLJ_MAX_RES * CLJ_MAX_SCANLINE / 72.0) + sizeof(ulong) - 1) \
- / sizeof(ulong) )
- ulong buff[3 * BUFF_SIZE];
- byte * p_c = (byte *)buff;
- byte * p_m = (byte *)(buff + BUFF_SIZE);
- byte * p_y = (byte *)(buff + 2 * BUFF_SIZE);
- ulong * ptrs[3];
- byte c_val = 0, m_val = 0, y_val = 0;
- ulong mask = 0x80;
- int i;
- /* pack the input for 4-bits per index */
- for (i = 0; i < in_size; i++) {
- uint ival = *pin++;
- if (ival != 0) {
- if ((ival & 0x4) != 0)
- y_val |= mask;
- if ((ival & 0x2) != 0)
- m_val |= mask;
- if ((ival & 0x1) != 0)
- c_val |= mask;
- }
- if ((mask >>= 1) == 0) {
- /* NB - write out in byte units */
- *p_c++ = c_val;
- c_val = 0L;
- *p_m++ = m_val;
- m_val = 0L;
- *p_y++ = y_val;
- y_val = 0L;
- mask = 0x80;
- }
- }
- if (mask != 0x80) {
- /* NB - write out in byte units */
- *p_c++ = c_val;
- *p_m++ = m_val;
- *p_y++ = y_val;
- }
- /* clear to up a longword boundary */
- while ((((ulong)p_c) & (sizeof(ulong) - 1)) != 0) {
- *p_c++ = 0;
- *p_m++ = 0;
- *p_y++ = 0;
- }
- ptrs[0] = (ulong *)p_c;
- ptrs[1] = (ulong *)p_m;
- ptrs[2] = (ulong *)p_y;
- for (i = 0; i < 3; i++) {
- ulong * p_start = buff + i * BUFF_SIZE;
- ulong * p_end = ptrs[i];
- /* eleminate trailing 0's */
- while ((p_end > p_start) && (p_end[-1] == 0))
- p_end--;
- if (p_start == p_end)
- out_size[i] = 0;
- else
- out_size[i] = gdev_pcl_mode2compress(p_start, p_end, pout[i]);
- }
- #undef BUFF_SIZE
- }
- /*
- * Send the page to the printer. Compress each scan line.
- */
- private int
- clj_print_page(
- gx_device_printer * pdev,
- FILE * prn_stream
- )
- {
- gs_memory_t *mem = pdev->memory;
- bool rotate;
- const clj_paper_size * psize = get_paper_size(pdev->MediaSize, &rotate);
- int lsize = pdev->width;
- int clsize = (lsize + (lsize + 255) / 128) / 8;
- byte * data = 0;
- byte * cdata[3];
- int blank_lines = 0;
- int i;
- floatp fs_res = pdev->HWResolution[0] / 72.0;
- floatp ss_res = pdev->HWResolution[1] / 72.0;
- int imageable_width, imageable_height;
- /* no paper size at this point is a serious error */
- if (psize == 0)
- return_error(gs_error_unregistered);
- /* allocate memory for the raw and compressed data */
- if ((data = gs_alloc_bytes(mem, lsize, "clj_print_page(data)")) == 0)
- return_error(gs_error_VMerror);
- if ((cdata[0] = gs_alloc_bytes(mem, 3 * clsize, "clj_print_page(cdata)")) == 0) {
- gs_free_object(mem, data, "clj_print_page(data)");
- return_error(gs_error_VMerror);
- }
- cdata[1] = cdata[0] + clsize;
- cdata[2] = cdata[1] + clsize;
- /* Imageable area is without the margins. Note that the actual rotation
- * of page size into pdev->width & height has been done. We just use
- * rotate to access the correct offsets. */
- if (pclj->rotated) {
- imageable_width = pdev->width - (2 * psize->offsets.x) * fs_res;
- imageable_height = pdev->height - (2 * psize->offsets.y) * ss_res;
- }
- else {
- imageable_width = pdev->width - (2 * psize->offsets.y) * ss_res;
- imageable_height = pdev->height - (2 * psize->offsets.x) * fs_res;
- }
- /* start the page. The pcl origin (0, 150 dots by default, y
- increasing down the long edge side of the page) needs to be
- offset such that it coincides with the offsets of the imageable
- area. This calculation should be independant of rotation but
- only the rotated case has been tested with a real device. */
- fprintf( prn_stream,
- "\033E\033&u300D\033&l%da1x%dO\033*p0x0y+50x-100Y\033*t%dR"
- #ifdef USE_FAST_MODE
- "\033*r-3U"
- #else
- "\033*v6W\001\002\003\001\001\001"
- #endif
- "\033*r0f%ds%dt1A\033*b2M",
- psize->tag,
- pclj->rotated,
- (int)(pdev->HWResolution[0]),
- imageable_width,
- imageable_height
- );
- /* process each scanline */
- for (i = 0; i < imageable_height; i++) {
- int clen[3];
- gdev_prn_copy_scan_lines(pdev, i, data, lsize);
- /* The 'lsize' bytes of data have the blank margin area at the end due */
- /* to the 'initial_matrix' offsets that are applied. */
- pack_and_compress_scanline(data, imageable_width, cdata, clen);
- if ((clen[0] == 0) && (clen[1] == 0) && (clen[2] == 0))
- ++blank_lines;
- else {
- if (blank_lines != 0) {
- fprintf(prn_stream, "\033*b%dY", blank_lines);
- blank_lines = 0;
- }
- fprintf(prn_stream, "\033*b%dV", clen[0]);
- fwrite(cdata[0], sizeof(byte), clen[0], prn_stream);
- fprintf(prn_stream, "\033*b%dV", clen[1]);
- fwrite(cdata[1], sizeof(byte), clen[1], prn_stream);
- fprintf(prn_stream, "\033*b%dW", clen[2]);
- fwrite(cdata[2], sizeof(byte), clen[2], prn_stream);
- }
- }
- /* PCL will take care of blank lines at the end */
- fputs("\033*rC\f", prn_stream);
- /* free the buffers used */
- gs_free_object(mem, cdata[0], "clj_print_page(cdata)");
- gs_free_object(mem, data, "clj_print_page(data)");
- return 0;
- }
- /* CLJ device methods */
- #define CLJ_PROCS(get_params, put_params)\
- gdev_prn_open, /* open_device */\
- clj_get_initial_matrix, /* get_initial matrix */\
- NULL, /* sync_output */\
- gdev_prn_output_page, /* output_page */\
- gdev_prn_close, /* close_device */\
- gdev_pcl_3bit_map_rgb_color, /* map_rgb_color */\
- gdev_pcl_3bit_map_color_rgb, /* map_color_rgb */\
- NULL, /* fill_rectangle */\
- NULL, /* tile_rectangle */\
- NULL, /* copy_mono */\
- NULL, /* copy_color */\
- NULL, /* obsolete draw_line */\
- NULL, /* get_bits */\
- get_params, /* get_params */\
- put_params, /* put_params */\
- NULL, /* map_cmyk_color */\
- NULL, /* get_xfont_procs */\
- NULL, /* get_xfont_device */\
- NULL, /* map_rgb_alpha_color */\
- gx_page_device_get_page_device /* get_page_device */
- private gx_device_procs cljet5_procs = {
- CLJ_PROCS(clj_get_params, clj_put_params)
- };
- /* CLJ device structure */
- #define CLJ_DEVICE_BODY(procs, dname, rotated)\
- prn_device_body(\
- gx_device_clj,\
- procs, /* procedures */\
- dname, /* device name */\
- 110, /* width - will be overridden subsequently */\
- 85, /* height - will be overridden subsequently */\
- X_DPI, Y_DPI, /* resolutions - current must be the same */\
- 0.167, 0.167, /* margins (left, bottom, right, top */\
- 0.167, 0.167,\
- 3, /* num_components - 3 colors, 1 bit per pixel */\
- 8, /* depth - pack into bytes */\
- 1, 1, /* max_gray=max_component=1 */\
- 2, 2, /* dithered_grays=dithered_components=2 */ \
- clj_print_page /* routine to output page */\
- ),\
- rotated /* rotated - may be overridden subsequently */
- gx_device_clj gs_cljet5_device = {
- CLJ_DEVICE_BODY(cljet5_procs, "cljet5", 0 /*false*/)
- };
- /* ---------------- Driver with page rotation ---------------- */
- /*
- * For use with certain PCL interpreters, which don't implement
- * setpagedevice, we provide a version of this driver that attempts to
- * handle page rotation at the driver level. This version breaks an
- * invariant that all drivers must obey, namely, that drivers are not
- * allowed to change the parameters passed by put_params (they can only
- * accept or reject them). Consequently, this driver must not be used in
- * any context other than these specific PCL interpreters. We support this
- * hack only because these PCL interpreters can't be changed to handle page
- * rotation properly.
- */
- /*
- * Special get_params routine, to fake MediaSize, width, and height if
- * we were in a 'rotated' state.
- */
- private int
- clj_pr_get_params( gx_device *pdev, gs_param_list *plist )
- {
- int code;
- /* First un-rotate the MediaSize, etc. if we were in a rotated mode */
- if (pclj->rotated) {
- float ftmp;
- int itmp;
- ftmp = pdev->MediaSize[0];
- pdev->MediaSize[0] = pdev->MediaSize[1];
- pdev->MediaSize[1] = ftmp;
- itmp = pdev->width;
- pdev->width = pdev->height;
- pdev->height = itmp;
- }
- /* process the parameter list */
- code = gdev_prn_get_params(pdev, plist);
- /* Now re-rotate the page size if needed */
- if (pclj->rotated) {
- float ftmp;
- int itmp;
- ftmp = pdev->MediaSize[0];
- pdev->MediaSize[0] = pdev->MediaSize[1];
- pdev->MediaSize[1] = ftmp;
- itmp = pdev->width;
- pdev->width = pdev->height;
- pdev->height = itmp;
- }
- return code;
- }
- /*
- * Special put_params routine, to intercept changes in the MediaSize, and to
- * make certain the desired MediaSize and HWResolution are supported.
- *
- * This function will rotate MediaSize if it is needed by the device in
- * order to print this size page.
- */
- private int
- clj_pr_put_params(
- gx_device * pdev,
- gs_param_list * plist
- )
- {
- float mediasize[2];
- int code = 0;
- bool rotate = false;
- int have_pagesize = clj_media_size(mediasize, plist);
- if (have_pagesize < 0)
- return have_pagesize;
- if (have_pagesize) {
- if (get_paper_size(mediasize, &rotate) == 0)
- return_error(gs_error_rangecheck);
- if (rotate) {
- /* We need to rotate the requested page size, so synthesize a new */
- /* parameter list in front of the requestor's list to force the */
- /* rotated page size. */
- gs_param_float_array pf_array;
- gs_c_param_list alist;
- float ftmp = mediasize[0];
- mediasize[0] = mediasize[1];
- mediasize[1] = ftmp;
- pf_array.data = mediasize;
- pf_array.size = 2;
- pf_array.persistent = false;
- gs_c_param_list_write(&alist, pdev->memory);
- code = param_write_float_array((gs_param_list *)&alist, ".MediaSize", &pf_array);
- gs_c_param_list_read(&alist);
- /* stick this synthesized parameter on the front of the existing list */
- gs_c_param_list_set_target(&alist, plist);
- if ((code = gdev_prn_put_params(pdev, (gs_param_list *)&alist)) >= 0)
- pclj->rotated = true;
- gs_c_param_list_release(&alist);
- } else {
- if ((code = gdev_prn_put_params(pdev, plist)) >= 0)
- pclj->rotated = false;
- }
- } else
- code = gdev_prn_put_params(pdev, plist);
- return code;
- }
- /* CLJ device methods -- se above for CLJ_PROCS */
- private gx_device_procs cljet5pr_procs = {
- CLJ_PROCS(clj_pr_get_params, clj_pr_put_params)
- };
- /* CLJ device structure -- see above for CLJ_DEVICE_BODY */
- gx_device_clj gs_cljet5pr_device = {
- CLJ_DEVICE_BODY(cljet5pr_procs, "cljet5pr", 1 /*true*/)
- };
|