/** \file * \brief External API * * See Copyright Notice in cd.h */ #include #include #include #include #include #include #include "cd.h" #include "wd.h" #include "cd_private.h" #include "cdirgb.h" /* This appears only here to avoid changing the cd.h header fo bug fixes */ #define CD_VERSION_FIX "" #define CD_VERSION_FIX_NUMBER 0 /* #define CD_VERSION_FIX_DATE "" */ const char cd_ident[] = "$CD: " CD_VERSION CD_VERSION_FIX " " CD_COPYRIGHT " $\n" "$URL: www.tecgraf.puc-rio.br/cd $\n"; static char *tecver = "TECVERID.str:CD:LIB:" CD_VERSION CD_VERSION_FIX; char* cdVersion(void) { (void)cd_ident; (void)tecver; return CD_VERSION CD_VERSION_FIX; } char* cdVersionDate(void) { #ifdef CD_VERSION_FIX_DATE return CD_VERSION_FIX_DATE; #else return CD_VERSION_DATE; #endif } int cdVersionNumber(void) { return CD_VERSION_NUMBER+CD_VERSION_FIX_NUMBER; } static void cd_setdefaultfunc(cdCanvas* canvas) { canvas->cxGetTextSize = cdgettextsizeEX; canvas->cxGetFontDim = cdgetfontdimEX; canvas->cxRect = cdSimRect; } static void cd_setdefaultattrib(cdCanvas* canvas) { /* clipping attributes */ canvas->clip_mode = CD_CLIPOFF; /* color attributes */ canvas->foreground = CD_BLACK; canvas->background = CD_WHITE; canvas->back_opacity = CD_TRANSPARENT; canvas->write_mode = CD_REPLACE; /* primitive attributes */ canvas->mark_type = CD_STAR; canvas->mark_size = 10; canvas->line_width = 1; canvas->line_style = CD_CONTINUOUS; canvas->line_cap = CD_CAPFLAT; canvas->line_join = CD_MITER; canvas->hatch_style = CD_HORIZONTAL; canvas->interior_style = CD_SOLID; canvas->fill_mode = CD_EVENODD; strcpy(canvas->font_type_face, "System"); canvas->font_style = CD_PLAIN; canvas->font_size = CD_STANDARD; canvas->text_alignment = CD_BASE_LEFT; canvas->matrix[0] = 1; /* identity */ canvas->matrix[3] = 1; /* everything else is 0 because of the memset */ } void cdUpdateAttributes(cdCanvas* canvas) { cdCtxCanvas* ctxcanvas = canvas->ctxcanvas; if (canvas->cxBackground) canvas->cxBackground(ctxcanvas, canvas->background); if (canvas->cxForeground) canvas->cxForeground(ctxcanvas, canvas->foreground); if (canvas->cxBackOpacity) canvas->cxBackOpacity(ctxcanvas, canvas->back_opacity); if (canvas->cxWriteMode) canvas->cxWriteMode(ctxcanvas, canvas->write_mode); if (canvas->cxLineStyle) canvas->cxLineStyle(ctxcanvas, canvas->line_style); if (canvas->cxLineWidth) canvas->cxLineWidth(ctxcanvas, canvas->line_width); if (canvas->cxLineCap) canvas->cxLineCap(ctxcanvas, canvas->line_cap); if (canvas->cxLineJoin) canvas->cxLineJoin(ctxcanvas, canvas->line_join); if (canvas->cxHatch) canvas->cxHatch(ctxcanvas, canvas->hatch_style); if (canvas->stipple && canvas->cxStipple) canvas->cxStipple(ctxcanvas, canvas->stipple_w, canvas->stipple_h, canvas->stipple); if (canvas->pattern && canvas->cxPattern) canvas->cxPattern(ctxcanvas, canvas->pattern_w, canvas->pattern_h, canvas->pattern); if (canvas->cxInteriorStyle) canvas->cxInteriorStyle(ctxcanvas, canvas->interior_style); if (canvas->native_font[0] && canvas->cxNativeFont) canvas->cxNativeFont(ctxcanvas, canvas->native_font); else if (canvas->cxFont) canvas->cxFont(ctxcanvas, canvas->font_type_face, canvas->font_style, canvas->font_size); if (canvas->cxTextAlignment) canvas->cxTextAlignment(ctxcanvas, canvas->text_alignment); if (canvas->cxTextOrientation) canvas->cxTextOrientation(ctxcanvas, canvas->text_orientation); if (canvas->use_matrix && canvas->cxTransform) canvas->cxTransform(ctxcanvas, canvas->matrix); if (canvas->clip_mode == CD_CLIPAREA && canvas->cxClipArea) canvas->cxClipArea(ctxcanvas, canvas->clip_rect.xmin, canvas->clip_rect.xmax, canvas->clip_rect.ymin, canvas->clip_rect.ymax); if (canvas->clip_mode == CD_CLIPAREA && canvas->cxFClipArea) canvas->cxFClipArea(ctxcanvas, canvas->clip_frect.xmin, canvas->clip_frect.xmax, canvas->clip_frect.ymin, canvas->clip_frect.ymax); if (canvas->clip_mode == CD_CLIPPOLYGON && canvas->clip_poly) canvas->cxPoly(ctxcanvas, CD_CLIP, canvas->clip_poly, canvas->clip_poly_n); if (canvas->clip_mode == CD_CLIPPOLYGON && canvas->clip_fpoly) canvas->cxFPoly(ctxcanvas, CD_CLIP, canvas->clip_fpoly, canvas->clip_poly_n); if (canvas->clip_mode != CD_CLIPOFF && canvas->cxClip) canvas->cxClip(ctxcanvas, canvas->clip_mode); } static void set_userdata_attrib(cdCtxCanvas* ctxcanvas, char* data) { cdCanvas* canvas = ((cdCtxCanvasBase*)ctxcanvas)->canvas; canvas->userdata = data; } static char* get_userdata_attrib(cdCtxCanvas* ctxcanvas) { cdCanvas* canvas = ((cdCtxCanvasBase*)ctxcanvas)->canvas; return canvas->userdata; } static cdAttribute userdata_attrib = { "USERDATA", set_userdata_attrib, get_userdata_attrib }; cdCanvas* cdCreateCanvasf(cdContext *context, const char* format, ...) { char data[10240]; va_list arglist; va_start(arglist, format); vsnprintf(data, 10240, format, arglist); va_end(arglist); return cdCreateCanvas(context, data); } cdCanvas *cdCreateCanvas(cdContext* context, void *data_str) { cdCanvas *canvas; /* useful for NULL drivers, that do nothing and exist only for portability */ if (!context) return NULL; { static int first = 1; char* env = getenv("CD_QUIET"); if (first && env && strcmp(env, "NO") == 0) /* default is quiet */ { printf("CD " CD_VERSION CD_VERSION_FIX " " CD_COPYRIGHT "\n"); first = 0; } } /* allocates and initialize everything with 0s */ canvas = (cdCanvas*)malloc(sizeof(cdCanvas)); memset(canvas, 0, sizeof(cdCanvas)); canvas->signature[0] = 'C'; canvas->signature[1] = 'D'; canvas->vector_font = cdCreateVectorFont(canvas); canvas->simulation = cdCreateSimulation(canvas); canvas->context = context; /* initialize default attributes, must be before creating the canvas */ cd_setdefaultattrib(canvas); context->cxCreateCanvas(canvas, data_str); if (!canvas->ctxcanvas) { cdKillVectorFont(canvas->vector_font); cdKillSimulation(canvas->simulation); memset(canvas, 0, sizeof(cdCanvas)); free(canvas); return NULL; } /* default simulation functions */ cd_setdefaultfunc(canvas); /* initialize canvas table */ context->cxInitTable(canvas); /* update the default attributes, must be after InitTable */ cdCanvasActivate(canvas); cdUpdateAttributes(canvas); /* must be after creating the canvas, so that we know canvas width and height */ canvas->clip_rect.xmax = canvas->w-1; canvas->clip_rect.ymax = canvas->h-1; wdSetDefaults(canvas); cdRegisterAttribute(canvas, &userdata_attrib); return canvas; } void cdKillCanvas(cdCanvas *canvas) { assert(canvas); if (!_cdCheckCanvas(canvas)) return; cdCanvasDeactivate(canvas); canvas->cxKillCanvas(canvas->ctxcanvas); if (canvas->pattern) free(canvas->pattern); if (canvas->stipple) free(canvas->stipple); if (canvas->poly) free(canvas->poly); if (canvas->clip_poly) free(canvas->clip_poly); if (canvas->fpoly) free(canvas->fpoly); if (canvas->clip_fpoly) free(canvas->clip_fpoly); if (canvas->line_dashes) free(canvas->line_dashes); if (canvas->path) free(canvas->path); cdKillVectorFont(canvas->vector_font); cdKillSimulation(canvas->simulation); memset(canvas, 0, sizeof(cdCanvas)); free(canvas); } cdContext* cdCanvasGetContext(cdCanvas *canvas) { assert(canvas); if (!_cdCheckCanvas(canvas)) return NULL; return canvas->context; } int cdCanvasActivate(cdCanvas *canvas) { assert(canvas); if (!_cdCheckCanvas(canvas)) return CD_ERROR; if (!canvas->cxActivate) return CD_OK; if (canvas->cxActivate(canvas->ctxcanvas) == CD_ERROR) return CD_ERROR; return CD_OK; } void cdCanvasDeactivate(cdCanvas *canvas) { assert(canvas); if (!_cdCheckCanvas(canvas) || !canvas->cxDeactivate) return; canvas->cxDeactivate(canvas->ctxcanvas); } unsigned long cdContextCaps(cdContext *context) { if (!context) return (unsigned long)CD_ERROR; return context->caps; } int cdContextIsPlus(cdContext *context) { if (!context) return CD_ERROR; return context->type&CD_CTX_PLUS? 1: 0; } int cdContextType(cdContext *context) { if (!context) return CD_ERROR; return context->type&0x00FF; } int cdCanvasSimulate(cdCanvas* canvas, int mode) { int old_sim_mode; cdContext* context; assert(canvas); if (!_cdCheckCanvas(canvas)) return CD_ERROR; context = canvas->context; old_sim_mode = canvas->sim_mode; if (mode == CD_QUERY || cdCanvasGetContext(canvas) == CD_IMAGERGB) return old_sim_mode; /* default simulation functions */ cd_setdefaultfunc(canvas); /* initialize canvas table */ context->cxInitTable(canvas); canvas->sim_mode = mode; if (mode == CD_SIM_NONE) return old_sim_mode; /* when simulation is active must not set driver transform */ canvas->cxTransform = NULL; if (mode & CD_SIM_LINE) { canvas->cxLine = cdSimLine; canvas->cxFLine = NULL; } if (mode & CD_SIM_RECT) { canvas->cxRect = cdSimRect; canvas->cxFRect = NULL; } if (mode & CD_SIM_BOX) { canvas->cxBox = cdSimBox; canvas->cxFBox = NULL; } if (mode & CD_SIM_ARC) { canvas->cxArc = cdSimArc; canvas->cxFArc = NULL; } if (mode & CD_SIM_SECTOR) { canvas->cxSector = cdSimSector; canvas->cxFSector = NULL; } if (mode & CD_SIM_CHORD) { canvas->cxChord = cdSimChord; canvas->cxFChord = NULL; } if (mode & CD_SIM_TEXT) { canvas->cxText = cdSimulationText; canvas->cxFText = NULL; canvas->cxNativeFont = NULL; canvas->cxFont = cdSimulationFont; canvas->cxGetFontDim = cdSimulationGetFontDim; canvas->cxGetTextSize = cdSimulationGetTextSize; canvas->cxTextOrientation = NULL; cdSimulationInitText(canvas->simulation); canvas->cxFont(canvas->ctxcanvas, canvas->font_type_face, canvas->font_style, canvas->font_size); } else canvas->cxFont(canvas->ctxcanvas, canvas->font_type_face, canvas->font_style, canvas->font_size); if (mode & CD_SIM_POLYLINE || mode & CD_SIM_POLYGON) { /* can NOT replace canvas->cxPoly because it will be used by the simulation, handle polygon simulation in cdCanvasEnd by calling cdPoly */ canvas->cxFPoly = NULL; } return old_sim_mode; } cdState* cdCanvasSaveState(cdCanvas* canvas) { cdState* state; assert(canvas); if (!_cdCheckCanvas(canvas)) return NULL; state = (cdState*)malloc(sizeof(cdState)); memcpy(state, canvas, sizeof(cdCanvas)); /* is actually a cdCanvas */ if (state->pattern) { int size = state->pattern_w*state->pattern_h*sizeof(long); state->pattern = (long*)malloc(size); memcpy(state->pattern, canvas->pattern, size); } if (state->stipple) { int size = state->stipple_w*state->stipple_h; state->stipple = (unsigned char*)malloc(size); memcpy(state->stipple, canvas->stipple, size); } if (state->clip_poly) { int size = state->clip_poly_n*sizeof(cdPoint); state->clip_poly = (cdPoint*)malloc(size); memcpy(state->clip_poly, canvas->clip_poly, size); } if (state->clip_fpoly) { int size = state->clip_poly_n*sizeof(cdfPoint); state->clip_fpoly = (cdfPoint*)malloc(size); memcpy(state->clip_fpoly, canvas->clip_fpoly, size); } if (state->line_dashes) { int size = state->line_dashes_count*sizeof(int); state->line_dashes = (int*)malloc(size); memcpy(state->line_dashes, canvas->line_dashes, size); } return state; } void cdReleaseState(cdState* state) { assert(state); if (!state) return; if (state->stipple) free(state->stipple); if (state->pattern) free(state->pattern); if (state->clip_poly) free(state->clip_poly); if (state->clip_fpoly) free(state->clip_fpoly); if (state->line_dashes) free(state->line_dashes); free(state); } void cdCanvasRestoreState(cdCanvas* canvas, cdState* state) { assert(canvas); assert(state); if (!state || !_cdCheckCanvas(canvas)) return; /* clippling must be done in low level because origin and invert y axis */ canvas->clip_poly_n = state->clip_poly_n; if (canvas->clip_poly) { free(canvas->clip_poly); canvas->clip_poly = NULL; } if (canvas->clip_fpoly) { free(canvas->clip_fpoly); canvas->clip_fpoly = NULL; } if (state->clip_poly) { int size = state->clip_poly_n*sizeof(cdPoint); canvas->clip_poly = (cdPoint*)malloc(size); memcpy(canvas->clip_poly, state->clip_poly, size); } if (state->clip_fpoly) { int size = state->clip_poly_n*sizeof(cdfPoint); canvas->clip_fpoly = (cdfPoint*)malloc(size); memcpy(canvas->clip_fpoly, state->clip_fpoly, size); } cdCanvasClip(canvas, CD_CLIPOFF); if (canvas->clip_fpoly) canvas->cxFPoly(canvas->ctxcanvas, CD_CLIP, state->clip_fpoly, state->clip_poly_n); else if (canvas->clip_poly) canvas->cxPoly(canvas->ctxcanvas, CD_CLIP, state->clip_poly, state->clip_poly_n); cdCanvasClipArea(canvas, state->clip_rect.xmin, state->clip_rect.xmax, state->clip_rect.ymin, state->clip_rect.ymax); if (canvas->cxFClipArea) canvas->cxFClipArea(canvas->ctxcanvas, state->clip_frect.xmin, state->clip_frect.xmax, state->clip_frect.ymin, state->clip_frect.ymax); else if (canvas->cxClipArea) canvas->cxClipArea(canvas->ctxcanvas, state->clip_rect.xmin, state->clip_rect.xmax, state->clip_rect.ymin, state->clip_rect.ymax); cdCanvasClip(canvas, state->clip_mode); /* regular attributes */ cdCanvasSetBackground(canvas, state->background); cdCanvasSetForeground(canvas, state->foreground); cdCanvasBackOpacity(canvas, state->back_opacity); cdCanvasWriteMode(canvas, state->write_mode); cdCanvasLineStyle(canvas, state->line_style); cdCanvasLineWidth(canvas, state->line_width); cdCanvasLineCap(canvas, state->line_cap); cdCanvasLineJoin(canvas, state->line_join); cdCanvasFillMode(canvas, state->fill_mode); cdCanvasLineStyleDashes(canvas, state->line_dashes, state->line_dashes_count); cdCanvasHatch(canvas, state->hatch_style); if (state->stipple) cdCanvasStipple(canvas, state->stipple_w, state->stipple_h, state->stipple); if (state->pattern) cdCanvasPattern(canvas, state->pattern_w, state->pattern_h, state->pattern); cdCanvasInteriorStyle(canvas, state->interior_style); if (state->native_font[0]) cdCanvasNativeFont(canvas, state->native_font); else cdCanvasFont(canvas, state->font_type_face, state->font_style, state->font_size); cdCanvasTextAlignment(canvas, state->text_alignment); cdCanvasTextOrientation(canvas, state->text_orientation); cdCanvasMarkType(canvas, state->mark_type); cdCanvasMarkSize(canvas, state->mark_size); cdCanvasOrigin(canvas, state->origin.x, state->origin.y); if (state->use_matrix) cdCanvasTransform(canvas, state->matrix); wdCanvasWindow(canvas, state->window.xmin, state->window.xmax, state->window.ymin, state->window.ymax); wdCanvasViewport(canvas, state->viewport.xmin, state->viewport.xmax, state->viewport.ymin, state->viewport.ymax); cdCanvasSimulate(canvas, state->sim_mode); /* complex clipping regions are not saved */ /* driver internal attributes are not saved */ } static cdAttribute* cd_findattrib(cdCanvas *canvas, const char* name, int *a) { int i; for (i=0; i < canvas->attrib_n; i++) { if (strcmp(name, canvas->attrib_list[i]->name) == 0) { if (a) *a = i; return canvas->attrib_list[i]; } } return NULL; } void cdRegisterAttribute(cdCanvas *canvas, cdAttribute* attrib) { cdAttribute* old_attrib; int a; assert(canvas); assert(attrib); if (!attrib || !_cdCheckCanvas(canvas)) return; old_attrib = cd_findattrib(canvas, attrib->name, &a); if (old_attrib) canvas->attrib_list[a] = attrib; else { canvas->attrib_list[canvas->attrib_n] = attrib; canvas->attrib_n++; } } void cdCanvasSetAttribute(cdCanvas* canvas, const char* name, char *data) { cdAttribute* attrib; assert(canvas); if (!_cdCheckCanvas(canvas)) return; attrib = cd_findattrib(canvas, name, NULL); if (attrib && attrib->set) attrib->set(canvas->ctxcanvas, data); } void cdCanvasSetfAttribute(cdCanvas* canvas, const char* name, const char* format, ...) { char data[10240]; va_list arglist; va_start(arglist, format); vsnprintf(data, 10240, format, arglist); va_end(arglist); cdCanvasSetAttribute(canvas, name, data); } char* cdCanvasGetAttribute(cdCanvas* canvas, const char* name) { cdAttribute* attrib; assert(canvas); if (!_cdCheckCanvas(canvas)) return NULL; attrib = cd_findattrib(canvas, name, NULL); if (attrib && attrib->get) return attrib->get(canvas->ctxcanvas); return NULL; } int cdCanvasPlay(cdCanvas* canvas, cdContext* context, int xmin, int xmax, int ymin, int ymax, void *data) { assert(context); assert(canvas); if (!_cdCheckCanvas(canvas) || !context || !context->cxPlay) return CD_ERROR; /* the all can be 0 here, do not use cdCheckBoxSize */ if (xmin > xmax) _cdSwapInt(xmin, xmax); if (ymin > ymax) _cdSwapInt(ymin, ymax); return context->cxPlay(canvas, xmin, xmax, ymin, ymax, data); } int cdContextRegisterCallback(cdContext *context, int cb, cdCallback func) { assert(context); if (!context || !context->cxRegisterCallback) return CD_ERROR; return context->cxRegisterCallback(cb, func); } void cdCanvasFlush(cdCanvas* canvas) { assert(canvas); if (!_cdCheckCanvas(canvas) || !canvas->cxFlush) return; canvas->cxFlush(canvas->ctxcanvas); } void cdCanvasClear(cdCanvas* canvas) { assert(canvas); if (!_cdCheckCanvas(canvas) || !canvas->cxClear) return; canvas->cxClear(canvas->ctxcanvas); } /* hidden function to simply control invert_yaxis behavior. several features will NOT behave as expected, such as arc direction, text position, image position and vector text */ int cdCanvasYAxisMode(cdCanvas* canvas, int invert) { int old_invert_yaxis; assert(canvas); if (!_cdCheckCanvas(canvas)) return CD_ERROR; if (invert == CD_QUERY) return canvas->invert_yaxis; old_invert_yaxis = canvas->invert_yaxis; canvas->invert_yaxis = invert; return old_invert_yaxis; } int cdCanvasUpdateYAxis(cdCanvas* canvas, int* y) { assert(canvas); assert(y); if (!_cdCheckCanvas(canvas)) return CD_ERROR; if(!(canvas->context->caps&CD_CAP_YAXIS)) { *y = _cdInvertYAxis(canvas, *y); if (canvas->use_origin) *y -= 2*canvas->origin.y; } return *y; } double cdfCanvasUpdateYAxis(cdCanvas* canvas, double* y) { assert(canvas); assert(y); if (!_cdCheckCanvas(canvas)) return CD_ERROR; if(!(canvas->context->caps&CD_CAP_YAXIS)) { *y = _cdInvertYAxis(canvas, *y); if (canvas->use_origin) *y -= 2*canvas->origin.y; } return *y; } int cdCanvasInvertYAxis(cdCanvas* canvas, int y) { int yi; assert(canvas); if (!_cdCheckCanvas(canvas)) return CD_ERROR; yi = _cdInvertYAxis(canvas, y); if (canvas->use_origin) yi -= 2*canvas->origin.y; return yi; } double cdfCanvasInvertYAxis(cdCanvas* canvas, double y) { double yi; assert(canvas); if (!_cdCheckCanvas(canvas)) return CD_ERROR; yi = _cdInvertYAxis(canvas, y); if (canvas->use_origin) yi -= 2*canvas->origin.y; return yi; } void cdCanvasGetSize(cdCanvas* canvas, int *width, int *height, double *width_mm, double *height_mm) { assert(canvas); if (!_cdCheckCanvas(canvas)) return; if (width) *width = canvas->w; if (height) *height = canvas->h; if (width_mm) *width_mm = canvas->w_mm; if (height_mm) *height_mm = canvas->h_mm; } void cdCanvasMM2Pixel(cdCanvas* canvas, double mm_dx, double mm_dy, int *dx, int *dy) { assert(canvas); if (!_cdCheckCanvas(canvas)) return; if (dx) *dx = cdRound(mm_dx*canvas->xres); if (dy) *dy = cdRound(mm_dy*canvas->yres); } void cdCanvasPixel2MM(cdCanvas* canvas, int dx, int dy, double *mm_dx, double *mm_dy) { assert(canvas); if (!_cdCheckCanvas(canvas)) return; if (mm_dx) *mm_dx = ((double)dx)/canvas->xres; if (mm_dy) *mm_dy = ((double)dy)/canvas->yres; } void cdfCanvasMM2Pixel(cdCanvas* canvas, double mm_dx, double mm_dy, double *dx, double *dy) { assert(canvas); if (!_cdCheckCanvas(canvas)) return; if (dx) *dx = mm_dx*canvas->xres; if (dy) *dy = mm_dy*canvas->yres; } void cdfCanvasPixel2MM(cdCanvas* canvas, double dx, double dy, double *mm_dx, double *mm_dy) { assert(canvas); if (!_cdCheckCanvas(canvas)) return; if (mm_dx) *mm_dx = dx/canvas->xres; if (mm_dy) *mm_dy = dy/canvas->yres; } /***** Context Plus Functions ********/ static int use_context_plus = 0; static cdContext* context_plus[CD_CTXPLUS_COUNT] = {NULL, NULL, NULL, NULL, NULL, NULL}; int cdUseContextPlus(int use) { int old_use_context_plus = use_context_plus; if (use == CD_QUERY) return use_context_plus; use_context_plus = use; return old_use_context_plus; } void cdInitContextPlusList(cdContext* ctx_list[]) { int ctx; for (ctx = 0; ctx < CD_CTXPLUS_COUNT; ctx++) if (ctx_list[ctx] != NULL) context_plus[ctx] = ctx_list[ctx]; } cdContext* cdGetContextPlus(int ctx) { if (ctx < 0 || ctx >= CD_CTXPLUS_COUNT) return NULL; return context_plus[ctx]; } /***** OLD Compatibility Functions ********/ int cdRegisterCallback(cdContext *context, int cb, cdCallback func) { return cdContextRegisterCallback(context, cb, func); } cdContext* cdGetContext(cdCanvas* canvas) { return cdCanvasGetContext(canvas); } int * cdGetClipPoly(int *n) { if (n) *n = 0; return NULL; } double* wdGetClipPoly(int *n) { if (n) *n = 0; return NULL; }