You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
xrdp-proprietary/xorg/X11R7.6/rdp/rdpglyph.c

863 lines
24 KiB

/*
Copyright 2012 Jay Sorg
Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
http://msdn.microsoft.com/en-us/library/cc241863(v=prot.20).aspx
4.6.1 "d" Character
This topic has not yet been rated - Rate this topic
The following shows glyph image data (1 bpp format) for character
"d" extracted from a Cache Glyph (Revision 2) (section 2.2.2.2.1.2.6)
Secondary Drawing Order.
Glyph width = 5 pixels
Glyph height = 9 pixels
Glyph origin = (0, -9), marked with an "X" on the image grid
Bitmap = { 0x08, 0x08, 0x08, 0x78, 0x88, 0x88, 0x88, 0x88, 0x78 }
http://msdn.microsoft.com/en-us/library/cc241864(v=prot.20).aspx
4.6.2 "p" Character
This topic has not yet been rated - Rate this topic
The following shows glyph image data (1 bpp format) for character
"p" extracted from a Cache Glyph (Revision 2) (section 2.2.2.2.1.2.6)
Secondary Drawing Order.
Glyph width = 5 pixels
Glyph height = 8 pixels
Glyph origin = (0, -6), marked with an "X" on the image grid
Bitmap = { 0xF0, 0x88, 0x88, 0x88, 0x88, 0xF0, 0x80, 0x80 }
*/
#include "rdp.h"
#include "rdpdraw.h"
#include "rdpglyph.h"
extern DevPrivateKeyRec g_rdpPixmapIndex; /* from rdpmain.c */
extern int g_do_dirty_os; /* in rdpmain.c */
extern int g_do_alpha_glyphs; /* in rdpmain.c */
extern int g_do_glyph_cache; /* in rdpmain.c */
extern int g_doing_font; /* in rdpmain.c */
extern ScreenPtr g_pScreen; /* in rdpmain.c */
extern rdpScreenInfoRec g_rdpScreen; /* in rdpmain.c */
#define LOG_LEVEL 1
#define LLOG(_level, _args) \
do { if (_level < LOG_LEVEL) { ErrorF _args ; } } while (0)
#define LLOGLN(_level, _args) \
do { if (_level < LOG_LEVEL) { ErrorF _args ; ErrorF("\n"); } } while (0)
struct font_cache
{
int offset;
int baseline;
int width;
int height;
int crc;
int stamp;
};
static struct font_cache g_font_cache[12][256];
static int g_stamp = 0;
/*****************************************************************************/
static void
set_mono_pixel(char* data, int x, int y, int width, int pixel)
{
int start;
int shift;
width = (width + 7) / 8;
start = (y * width) + x / 8;
shift = x % 8;
if (pixel != 0)
{
data[start] = data[start] | (0x80 >> shift);
}
else
{
data[start] = data[start] & ~(0x80 >> shift);
}
}
/*****************************************************************************/
static int
lget_pixel(char* data, int x, int y, int depth, int stride_bytes)
{
int start;
int shift;
if (depth == 1)
{
start = (y * stride_bytes) + x / 8;
shift = x % 8;
return (data[start] & (0x01 << shift)) ? 0xff : 0;
}
else if (depth == 8)
{
return data[y * stride_bytes + x];
}
return 0;
}
/******************************************************************************/
static int
glyph_get_data(ScreenPtr pScreen, GlyphPtr glyph, struct rdp_font_char* rfd)
{
int i;
int j;
int src_xoff;
int src_yoff;
int src_stride_bytes;
int dst_stride_bytes;
int hh;
int ww;
int src_depth;
unsigned char pixel;
PicturePtr pPicture;
pixman_image_t *src;
uint32_t* pi32;
char* pi8;
pPicture = GlyphPicture(glyph)[pScreen->myNum];
if (pPicture == 0)
{
return 0;
}
src = image_from_pict(pPicture, FALSE, &src_xoff, &src_yoff);
if (src == 0)
{
return 0;
}
src_stride_bytes = pixman_image_get_stride(src);
if (g_do_alpha_glyphs)
{
dst_stride_bytes = (glyph->info.width + 3) & ~3;
rfd->bpp = 8;
}
else
{
dst_stride_bytes = (((glyph->info.width + 7) / 8) + 3) & ~3;
rfd->bpp = 1;
}
src_depth = pixman_image_get_depth(src);
ww = pixman_image_get_width(src);
hh = pixman_image_get_height(src);
if ((ww != glyph->info.width) || (hh != glyph->info.height) ||
((src_depth != 1) && (src_depth != 8)))
{
LLOGLN(0, ("glyph_get_data: bad glyph"));
free_pixman_pict(pPicture, src);
return 0;
}
rfd->data_bytes = glyph->info.height * dst_stride_bytes;
rfd->data = (char*)g_malloc(rfd->data_bytes, 1);
rfd->offset = -glyph->info.x;
rfd->baseline = -glyph->info.y;
rfd->width = glyph->info.width;
rfd->height = glyph->info.height;
pi32 = pixman_image_get_data(src);
pi8 = (char*)pi32;
for (j = 0; j < rfd->height; j++)
{
for (i = 0; i < rfd->width; i++)
{
pixel = lget_pixel(pi8, i, j, src_depth, src_stride_bytes);
if (g_do_alpha_glyphs)
{
rfd->data[j * dst_stride_bytes + i] = pixel;
}
else
{
if (pixel > 0x7f)
{
set_mono_pixel(rfd->data, i, j, rfd->width, 1);
}
else
{
set_mono_pixel(rfd->data, i, j, rfd->width, 0);
}
}
}
}
free_pixman_pict(pPicture, src);
return 0;
}
/******************************************************************************/
struct rdp_text*
create_rdp_text(ScreenPtr pScreen, int nlists, GlyphListPtr lists,
GlyphPtr* glyphs)
{
struct rdp_text* rv;
struct rdp_text* rtext;
struct rdp_text* last_rtext;
BoxRec box;
RegionRec reg1;
int n;
int lxoff;
int lyoff;
int count;
int lx;
int ly;
int font_index;
int max_height;
int min_height;
int force_new;
GlyphPtr glyph;
struct rdp_font_char* rfd;
LLOGLN(10, ("create_rdp_text: nlists %d", nlists));
max_height = 0;
min_height = 0x7fffffff;
lx = lists->xOff;
ly = lists->yOff;
lxoff = 0;
lyoff = 0;
force_new = 0;
rtext = (struct rdp_text*)g_malloc(sizeof(struct rdp_text), 1);
rtext->reg = RegionCreate(NullBox, 0);
rtext->flags = 3;
rtext->mixmode = 0;
rtext->x = lx;
rtext->y = ly;
rv = rtext;
last_rtext = rtext;
count = 0;
while (nlists--)
{
LLOGLN(10, ("lists->xOff %d lists->yOff %d", lists->xOff, lists->yOff));
if (count != 0)
{
lx += lists->xOff;
ly += lists->yOff;
force_new = 1;
}
count++;
n = lists->len;
lists++;
while (n--)
{
glyph = *glyphs++;
/* process glyph here */
if ((glyph->info.width > 0) && (glyph->info.height > 0))
{
if (force_new)
{
LLOGLN(10, ("create_rdp_text: too many chars"));
force_new = 0;
rtext = (struct rdp_text*)g_malloc(sizeof(struct rdp_text), 1);
rtext->reg = RegionCreate(NullBox, 0);
rtext->flags = 3;
rtext->mixmode = 0;
rtext->x = lx;
rtext->y = ly;
last_rtext->next = rtext;
last_rtext = rtext;
lxoff = 0;
lyoff = 0;
}
LLOGLN(10, ("x %d y %d width %d height %d xOff %d yOff %d "
"num_chars %d lxoff %d lyoff %d lx %d ly %d",
glyph->info.x, glyph->info.y,
glyph->info.width, glyph->info.height,
glyph->info.xOff, glyph->info.yOff, rtext->num_chars,
lxoff, lyoff, lx, ly));
rfd = (struct rdp_font_char*)g_malloc(sizeof(struct rdp_font_char), 1);
rtext->chars[rtext->num_chars] = rfd;
box.x1 = lx - glyph->info.x;
box.y1 = ly - glyph->info.y;
box.x2 = box.x1 + glyph->info.width;
box.y2 = box.y1 + glyph->info.height;
if (glyph->info.height > max_height)
{
max_height = glyph->info.height;
}
if (glyph->info.height < min_height)
{
min_height = glyph->info.height;
}
RegionInit(&reg1, &box, 0);
RegionUnion(rtext->reg, &reg1, rtext->reg);
RegionUninit(&reg1);
glyph_get_data(pScreen, glyph, rfd);
rfd->incby = lxoff;
lxoff = glyph->info.xOff;
lyoff = glyph->info.yOff;
rtext->num_chars++;
if (rtext->num_chars > 63)
{
force_new = 1;
}
}
else
{
lxoff += glyph->info.xOff;
lyoff += glyph->info.yOff;
}
lx += glyph->info.xOff;
ly += glyph->info.yOff;
}
}
if (max_height > 10)
{
font_index = 8;
}
else if (max_height < 7)
{
font_index = 6;
}
else
{
font_index = 7;
}
LLOGLN(10, ("create_rdp_text: min_height %d max_height %d font_index %d",
min_height, max_height, font_index));
rtext = rv;
while (rtext != 0)
{
rtext->font = font_index;
rtext = rtext->next;
}
return rv;
}
/******************************************************************************/
int
delete_rdp_text(struct rdp_text* rtext)
{
int index;
if (rtext == 0)
{
return 0;
}
for (index = 0; index < rtext->num_chars; index++)
{
if (rtext->chars[index] != 0)
{
g_free(rtext->chars[index]->data);
g_free(rtext->chars[index]);
}
}
RegionDestroy(rtext->reg);
delete_rdp_text(rtext->next);
g_free(rtext);
return 0;
}
/******************************************************************************/
static int
get_color(PicturePtr pPicture)
{
int src_xoff;
int src_yoff;
int rv;
uint32_t* pi32;
pixman_image_t *src;
src = image_from_pict(pPicture, FALSE, &src_xoff, &src_yoff);
if (src == 0)
{
return 0;
}
pi32 = pixman_image_get_data(src);
if (pi32 == 0)
{
return 0;
}
rv = *pi32;
LLOGLN(10, ("get_color: 0x%8.8x width %d height %d ", rv,
pixman_image_get_width(src),
pixman_image_get_height(src)));
free_pixman_pict(pPicture, src);
return rv;
}
/******************************************************************************/
static int
find_or_add_char(int font, struct rdp_font_char* rfd)
{
int crc;
int index;
int char_index;
int oldest;
crc = get_crc(rfd->data, rfd->data_bytes);
LLOGLN(10, ("find_or_add_char: crc 0x%8.8x", crc));
char_index = 0;
oldest = 0x7fffffff;
for (index = 0; index < 250; index++)
{
if ((g_font_cache[font][index].crc == crc) &&
(g_font_cache[font][index].width == rfd->width) &&
(g_font_cache[font][index].height == rfd->height) &&
(g_font_cache[font][index].offset == rfd->offset) &&
(g_font_cache[font][index].baseline == rfd->baseline))
{
g_stamp++;
g_font_cache[font][index].stamp = g_stamp;
LLOGLN(10, ("find_or_add_char: found char at %d %d", font, index));
return index;
}
if (g_font_cache[font][index].stamp < oldest)
{
oldest = g_font_cache[font][index].stamp;
char_index = index;
}
}
g_stamp++;
g_font_cache[font][char_index].stamp = g_stamp;
g_font_cache[font][char_index].crc = crc;
g_font_cache[font][char_index].width = rfd->width;
g_font_cache[font][char_index].height = rfd->height;
g_font_cache[font][char_index].offset = rfd->offset;
g_font_cache[font][char_index].baseline = rfd->baseline;
LLOGLN(10, ("find_or_add_char: adding char at %d %d", font, char_index));
if (rfd->bpp == 8)
{
rdpup_add_char_alpha(font, char_index, rfd->offset, rfd->baseline,
rfd->width, rfd->height,
rfd->data, rfd->data_bytes);
}
else
{
rdpup_add_char(font, char_index, rfd->offset, rfd->baseline,
rfd->width, rfd->height,
rfd->data, rfd->data_bytes);
}
return char_index;
}
/******************************************************************************/
int
rdp_text_chars_to_data(struct rdp_text* rtext)
{
int index;
int data_bytes;
int char_index;
struct rdp_font_char* rfd;
LLOGLN(10, ("rdp_text_chars_to_data: rtext->num_chars %d", rtext->num_chars));
data_bytes = 0;
for (index = 0; index < rtext->num_chars; index++)
{
rfd = rtext->chars[index];
if (rfd == 0)
{
LLOGLN(0, ("rdp_text_chars_to_data: error rfd is nil"));
continue;
}
char_index = find_or_add_char(rtext->font, rfd);
rtext->data[data_bytes] = char_index;
data_bytes++;
if (rfd->incby > 127)
{
rtext->data[data_bytes] = 0x80;
data_bytes++;
rtext->data[data_bytes] = (rfd->incby >> 0) & 0xff;
data_bytes++;
rtext->data[data_bytes] = (rfd->incby >> 8) & 0xff;
data_bytes++;
}
else
{
rtext->data[data_bytes] = rfd->incby;
data_bytes++;
}
}
rtext->data_bytes = data_bytes;
return 0;
}
/******************************************************************************/
/*
typedef struct _GlyphList {
INT16 xOff;
INT16 yOff;
CARD8 len;
PictFormatPtr format;
} GlyphListRec, *GlyphListPtr;
*/
9 years ago
/* see glyphstr.h but the following is not in there
typedef struct _XGlyphInfo {
unsigned short width;
unsigned short height;
short x;
short y;
short xOff;
short yOff;
} XGlyphInfo;
*/
static void
rdpGlyphu(CARD8 op, PicturePtr pSrc, PicturePtr pDst,
PictFormatPtr maskFormat, INT16 xSrc, INT16 ySrc,
int nlists, GlyphListPtr lists, GlyphPtr* glyphs,
BoxPtr extents)
{
BoxRec box;
RegionRec reg1;
RegionRec reg2;
DrawablePtr p;
int dirty_type;
int j;
int num_clips;
int post_process;
int reset_surface;
int got_id;
int fg_color;
WindowPtr pDstWnd;
PixmapPtr pDstPixmap;
rdpPixmapRec* pDstPriv;
rdpPixmapRec* pDirtyPriv;
struct image_data id;
struct rdp_text* rtext;
struct rdp_text* trtext;
LLOGLN(10, ("rdpGlyphu: xSrc %d ySrc %d", xSrc, ySrc));
p = pDst->pDrawable;
dirty_type = 0;
pDirtyPriv = 0;
post_process = 0;
reset_surface = 0;
got_id = 0;
if (p->type == DRAWABLE_PIXMAP)
{
pDstPixmap = (PixmapPtr)p;
pDstPriv = GETPIXPRIV(pDstPixmap);
if (XRDP_IS_OS(pDstPriv))
{
rdpup_check_dirty(pDstPixmap, pDstPriv);
post_process = 1;
if (g_do_dirty_os)
{
9 years ago
LLOGLN(10, ("rdpGlyphu: getting dirty"));
pDstPriv->is_dirty = 1;
dirty_type = RDI_IMGLL;
pDirtyPriv = pDstPriv;
}
else
{
rdpup_switch_os_surface(pDstPriv->rdpindex);
reset_surface = 1;
rdpup_get_pixmap_image_rect(pDstPixmap, &id);
got_id = 1;
LLOGLN(10, ("rdpGlyphu: offscreen"));
}
}
}
else
{
if (p->type == DRAWABLE_WINDOW)
{
pDstWnd = (WindowPtr)p;
if (pDstWnd->viewable)
{
post_process = 1;
rdpup_get_screen_image_rect(&id);
got_id = 1;
LLOGLN(10, ("rdpGlyphu: screen"));
}
}
}
if (!post_process)
{
return;
}
rtext = create_rdp_text(pDst->pDrawable->pScreen, nlists, lists, glyphs);
if (rtext == 0)
{
LLOGLN(0, ("rdpGlyphu: create_rdp_text failed"));
return;
}
fg_color = get_color(pSrc);
LLOGLN(10, ("rdpGlyphu: pDst->clientClipType %d pCompositeClip %p",
pDst->clientClipType, pDst->pCompositeClip));
if (pDst->pCompositeClip != 0)
{
box.x1 = p->x + extents->x1;
box.y1 = p->y + extents->y1;
box.x2 = p->x + extents->x2;
box.y2 = p->y + extents->y2;
RegionInit(&reg1, &box, 0);
RegionInit(&reg2, NullBox, 0);
RegionCopy(&reg2, pDst->pCompositeClip);
RegionIntersect(&reg1, &reg1, &reg2);
if (dirty_type != 0)
{
LLOGLN(10, ("1"));
draw_item_add_text_region(pDirtyPriv, &reg1, fg_color, GXcopy, rtext);
rtext = 0;
}
else if (got_id)
{
num_clips = REGION_NUM_RECTS(&reg1);
if (num_clips > 0)
{
LLOGLN(10, (" num_clips %d", num_clips));
rdpup_begin_update();
rdpup_set_fgcolor(fg_color);
trtext = rtext;
while (trtext != 0)
{
rdp_text_chars_to_data(trtext);
for (j = num_clips - 1; j >= 0; j--)
{
box = REGION_RECTS(&reg1)[j];
LLOGLN(10, ("2"));
rdpup_set_clip(box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1);
LLOGLN(10, ("rdpGlyphu: rdpup_draw_text"));
box = RegionExtents(trtext->reg)[0];
rdpup_draw_text(trtext->font, trtext->flags, trtext->mixmode,
box.x1 + p->x, box.y1 + p->y,
box.x2 + p->x, box.y2 + p->y,
//box.x1 + p->x, box.y1 + p->y,
//box.x2 + p->x, box.y2 + p->y,
0, 0, 0, 0,
trtext->x + p->x, trtext->y + p->y,
trtext->data, trtext->data_bytes);
}
trtext = trtext->next;
}
rdpup_reset_clip();
rdpup_end_update();
}
}
RegionUninit(&reg1);
RegionUninit(&reg2);
}
else
{
box.x1 = p->x + extents->x1;
box.y1 = p->y + extents->y1;
box.x2 = p->x + extents->x2;
box.y2 = p->y + extents->y2;
if (dirty_type != 0)
{
RegionInit(&reg1, &box, 0);
LLOGLN(10, ("3"));
draw_item_add_text_region(pDirtyPriv, &reg1, fg_color, GXcopy, rtext);
rtext = 0;
RegionUninit(&reg1);
}
else if (got_id)
{
rdpup_begin_update();
LLOGLN(10, ("4"));
rdpup_set_fgcolor(fg_color);
trtext = rtext;
while (trtext != 0)
{
LLOGLN(10, ("rdpGlyphu: rdpup_draw_text"));
rdp_text_chars_to_data(trtext);
box = RegionExtents(trtext->reg)[0];
rdpup_draw_text(trtext->font, trtext->flags, trtext->mixmode,
box.x1 + p->x, box.y1 + p->y,
box.x2 + p->x, box.y2 + p->y,
//box.x1 + p->x, box.y1 + p->y,
//box.x2 + p->x, box.y2 + p->y,
0, 0, 0, 0,
trtext->x + p->x, trtext->y + p->y,
trtext->data, trtext->data_bytes);
trtext = trtext->next;
}
rdpup_end_update();
}
}
if (reset_surface)
{
rdpup_switch_os_surface(-1);
}
delete_rdp_text(rtext);
}
/******************************************************************************/
static void
GlyphExtents(int nlist, GlyphListPtr list, GlyphPtr* glyphs, BoxPtr extents)
{
int x1;
int x2;
int y1;
int y2;
int n;
int x;
int y;
GlyphPtr glyph;
x = 0;
y = 0;
extents->x1 = MAXSHORT;
extents->x2 = MINSHORT;
extents->y1 = MAXSHORT;
extents->y2 = MINSHORT;
while (nlist--)
{
x += list->xOff;
y += list->yOff;
n = list->len;
list++;
while (n--)
{
glyph = *glyphs++;
x1 = x - glyph->info.x;
if (x1 < MINSHORT)
{
x1 = MINSHORT;
}
y1 = y - glyph->info.y;
if (y1 < MINSHORT)
{
y1 = MINSHORT;
}
x2 = x1 + glyph->info.width;
if (x2 > MAXSHORT)
{
x2 = MAXSHORT;
}
y2 = y1 + glyph->info.height;
if (y2 > MAXSHORT)
{
y2 = MAXSHORT;
}
if (x1 < extents->x1)
{
extents->x1 = x1;
}
if (x2 > extents->x2)
{
extents->x2 = x2;
}
if (y1 < extents->y1)
{
extents->y1 = y1;
}
if (y2 > extents->y2)
{
extents->y2 = y2;
}
x += glyph->info.xOff;
y += glyph->info.yOff;
}
}
}
/******************************************************************************/
static void
rdpGlypht(CARD8 op, PicturePtr pSrc, PicturePtr pDst,
PictFormatPtr maskFormat, INT16 xSrc, INT16 ySrc,
int nlists, GlyphListPtr lists, GlyphPtr* glyphs)
{
BoxRec extents;
GlyphExtents(nlists, lists, glyphs, &extents);
if ((extents.x2 <= extents.x1) || (extents.y2 <= extents.y1))
{
return;
}
rdpGlyphu(op, pSrc, pDst, maskFormat, xSrc, ySrc, nlists, lists,
glyphs, &extents);
}
/******************************************************************************/
/* make sure no glyph is too big */
/* returns boolean */
static int
rdpGlyphCheck(int nlist, GlyphListPtr list, GlyphPtr* glyphs)
{
int n;
GlyphPtr glyph;
while (nlist--)
{
n = list->len;
list++;
while (n--)
{
glyph = *glyphs++;
if ((glyph->info.width * glyph->info.height) > 8192)
{
LLOGLN(10, ("rdpGlyphCheck: too big"));
return 0;
}
}
}
return 1;
}
/******************************************************************************/
void
rdpGlyphs(CARD8 op, PicturePtr pSrc, PicturePtr pDst,
PictFormatPtr maskFormat,
INT16 xSrc, INT16 ySrc, int nlists, GlyphListPtr lists,
GlyphPtr* glyphs)
{
PictureScreenPtr ps;
LLOGLN(10, ("rdpGlyphs: op %d xSrc %d ySrc %d maskFormat %p",
op, xSrc, ySrc, maskFormat));
LLOGLN(10, ("rdpGlyphs: g_do_glyph_cache %d", g_do_glyph_cache));
if (g_do_glyph_cache && rdpGlyphCheck(nlists, lists, glyphs))
{
g_doing_font = 2;
rdpGlypht(op, pSrc, pDst, maskFormat, xSrc, ySrc, nlists, lists, glyphs);
ps = GetPictureScreen(g_pScreen);
ps->Glyphs = g_rdpScreen.Glyphs;
ps->Glyphs(op, pSrc, pDst, maskFormat, xSrc, ySrc,
nlists, lists, glyphs);
ps->Glyphs = rdpGlyphs;
}
else
{
g_doing_font = 1;
rdpup_set_hints(1, 1);
ps = GetPictureScreen(g_pScreen);
ps->Glyphs = g_rdpScreen.Glyphs;
ps->Glyphs(op, pSrc, pDst, maskFormat, xSrc, ySrc,
nlists, lists, glyphs);
ps->Glyphs = rdpGlyphs;
rdpup_set_hints(0, 1);
}
g_doing_font = 0;
LLOGLN(10, ("rdpGlyphs: out"));
}
/******************************************************************************/
int
rdpGlyphInit(void)
{
memset(&g_font_cache, 0, sizeof(g_font_cache));
return 0;
}