Home | History | Annotate | Download | only in skin
      1 /* Copyright (C) 2007-2008 The Android Open Source Project
      2 **
      3 ** This software is licensed under the terms of the GNU General Public
      4 ** License version 2, as published by the Free Software Foundation, and
      5 ** may be copied, distributed, and modified under those terms.
      6 **
      7 ** This program is distributed in the hope that it will be useful,
      8 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
      9 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     10 ** GNU General Public License for more details.
     11 */
     12 #include "android/skin/file.h"
     13 #include "android/utils/path.h"
     14 #include "android/charmap.h"
     15 #include "android/utils/bufprint.h"
     16 #include "android/utils/system.h"
     17 #include "android/utils/debug.h"
     18 
     19 //#include "qemu-common.h"
     20 
     21 /** UTILITY ROUTINES
     22  **/
     23 static SkinImage*
     24 skin_image_find_in( const char*  dirname, const char*  filename )
     25 {
     26     char   buffer[1024];
     27     char*  p   = buffer;
     28     char*  end = p + sizeof(buffer);
     29 
     30     p = bufprint( p, end, "%s" PATH_SEP "%s", dirname, filename );
     31     if (p >= end)
     32         return SKIN_IMAGE_NONE;
     33 
     34     return skin_image_find_simple(buffer);
     35 }
     36 
     37 /** SKIN BACKGROUND
     38  **/
     39 
     40 static void
     41 skin_background_done( SkinBackground*  background )
     42 {
     43     if (background->image)
     44         skin_image_unref(&background->image);
     45 }
     46 
     47 static int
     48 skin_background_init_from( SkinBackground*  background,
     49                            AConfig*         node,
     50                            const char*      basepath )
     51 {
     52     const char* img = aconfig_str(node, "image", NULL);
     53     int         x   = aconfig_int(node, "x", 0);
     54     int         y   = aconfig_int(node, "y", 0);
     55 
     56     background->valid = 0;
     57 
     58     if (img == NULL)   /* no background */
     59         return -1;
     60 
     61     background->image = skin_image_find_in( basepath, img );
     62     if (background->image == SKIN_IMAGE_NONE) {
     63         background->image = NULL;
     64         return -1;
     65     }
     66 
     67     background->rect.pos.x  = x;
     68     background->rect.pos.y  = y;
     69     background->rect.size.w = skin_image_w( background->image );
     70     background->rect.size.h = skin_image_h( background->image );
     71 
     72     background->valid = 1;
     73 
     74     return 0;
     75 }
     76 
     77 /** SKIN DISPLAY
     78  **/
     79 
     80 static void
     81 skin_display_done( SkinDisplay*  display )
     82 {
     83     qframebuffer_done( display->qfbuff );
     84 }
     85 
     86 static int
     87 skin_display_init_from( SkinDisplay*  display, AConfig*  node )
     88 {
     89     display->rect.pos.x  = aconfig_int(node, "x", 0);
     90     display->rect.pos.y  = aconfig_int(node, "y", 0);
     91     display->rect.size.w = aconfig_int(node, "width", 0);
     92     display->rect.size.h = aconfig_int(node, "height", 0);
     93     display->rotation    = aconfig_unsigned(node, "rotation", SKIN_ROTATION_0);
     94     display->bpp         = aconfig_int(node, "bpp", 16);
     95 
     96     display->valid = ( display->rect.size.w > 0 && display->rect.size.h > 0 );
     97 
     98     if (display->valid) {
     99         SkinRect  r;
    100         skin_rect_rotate( &r, &display->rect, -display->rotation );
    101         qframebuffer_init( display->qfbuff,
    102                            r.size.w,
    103                            r.size.h,
    104                            0,
    105                            display->bpp == 32 ? QFRAME_BUFFER_RGBX_8888
    106                                               : QFRAME_BUFFER_RGB565 );
    107 
    108         qframebuffer_fifo_add( display->qfbuff );
    109     }
    110     return display->valid ? 0 : -1;
    111 }
    112 
    113 /** SKIN BUTTON
    114  **/
    115 
    116 typedef struct
    117 {
    118     const char*     name;
    119     AndroidKeyCode  code;
    120 } KeyInfo;
    121 
    122 static KeyInfo  _keyinfo_table[] = {
    123     { "dpad-up",      kKeyCodeDpadUp },
    124     { "dpad-down",    kKeyCodeDpadDown },
    125     { "dpad-left",    kKeyCodeDpadLeft },
    126     { "dpad-right",   kKeyCodeDpadRight },
    127     { "dpad-center",  kKeyCodeDpadCenter },
    128     { "soft-left",    kKeyCodeSoftLeft },
    129     { "soft-right",   kKeyCodeSoftRight },
    130     { "search",       kKeyCodeSearch },
    131     { "camera",       kKeyCodeCamera },
    132     { "volume-up",    kKeyCodeVolumeUp },
    133     { "volume-down",  kKeyCodeVolumeDown },
    134     { "power",        kKeyCodePower },
    135     { "home",         kKeyCodeHome },
    136     { "back",         kKeyCodeBack },
    137     { "del",          kKeyCodeDel },
    138     { "0",            kKeyCode0 },
    139     { "1",            kKeyCode1 },
    140     { "2",            kKeyCode2 },
    141     { "3",            kKeyCode3 },
    142     { "4",            kKeyCode4 },
    143     { "5",            kKeyCode5 },
    144     { "6",            kKeyCode6 },
    145     { "7",            kKeyCode7 },
    146     { "8",            kKeyCode8 },
    147     { "9",            kKeyCode9 },
    148     { "star",         kKeyCodeStar },
    149     { "pound",        kKeyCodePound },
    150     { "phone-dial",   kKeyCodeCall },
    151     { "phone-hangup", kKeyCodeEndCall },
    152     { "q",            kKeyCodeQ },
    153     { "w",            kKeyCodeW },
    154     { "e",            kKeyCodeE },
    155     { "r",            kKeyCodeR },
    156     { "t",            kKeyCodeT },
    157     { "y",            kKeyCodeY },
    158     { "u",            kKeyCodeU },
    159     { "i",            kKeyCodeI },
    160     { "o",            kKeyCodeO },
    161     { "p",            kKeyCodeP },
    162     { "a",            kKeyCodeA },
    163     { "s",            kKeyCodeS },
    164     { "d",            kKeyCodeD },
    165     { "f",            kKeyCodeF },
    166     { "g",            kKeyCodeG },
    167     { "h",            kKeyCodeH },
    168     { "j",            kKeyCodeJ },
    169     { "k",            kKeyCodeK },
    170     { "l",            kKeyCodeL },
    171     { "DEL",          kKeyCodeDel },
    172     { "z",            kKeyCodeZ },
    173     { "x",            kKeyCodeX },
    174     { "c",            kKeyCodeC },
    175     { "v",            kKeyCodeV },
    176     { "b",            kKeyCodeB },
    177     { "n",            kKeyCodeN },
    178     { "m",            kKeyCodeM },
    179     { "COMMA",        kKeyCodeComma },
    180     { "PERIOD",       kKeyCodePeriod },
    181     { "ENTER",        kKeyCodeNewline },
    182     { "AT",           kKeyCodeAt },
    183     { "SPACE",        kKeyCodeSpace },
    184     { "SLASH",        kKeyCodeSlash },
    185     { "CAP",          kKeyCodeCapLeft },
    186     { "SYM",          kKeyCodeSym },
    187     { "ALT",          kKeyCodeAltLeft },
    188     { "ALT2",         kKeyCodeAltRight },
    189     { "CAP2",         kKeyCodeCapRight },
    190     { "tv",           kKeyCodeTV },
    191     { "epg",          kKeyCodeEPG },
    192     { "dvr",          kKeyCodeDVR },
    193     { "prev",         kKeyCodePrevious },
    194     { "next",         kKeyCodeNext },
    195     { "play",         kKeyCodePlay },
    196     { "pause",        kKeyCodePause },
    197     { "stop",         kKeyCodeStop },
    198     { "rev",          kKeyCodeRewind },
    199     { "ffwd",         kKeyCodeFastForward },
    200     { "bookmarks",    kKeyCodeBookmarks },
    201     { "window",       kKeyCodeCycleWindows },
    202     { "channel-up",   kKeyCodeChannelUp },
    203     { "channel-down", kKeyCodeChannelDown },
    204     { 0, 0 },
    205 };
    206 
    207 static unsigned
    208 keyinfo_lookup_code(const char *name)
    209 {
    210     KeyInfo *ki = _keyinfo_table;
    211     while(ki->name) {
    212         if(!strcmp(name, ki->name))
    213             return ki->code;
    214         ki++;
    215     }
    216     return 0;
    217 }
    218 
    219 
    220 static void
    221 skin_button_free( SkinButton*  button )
    222 {
    223     if (button) {
    224         skin_image_unref( &button->image );
    225         AFREE(button);
    226     }
    227 }
    228 
    229 static SkinButton*
    230 skin_button_create_from( AConfig*   node, const char*  basepath )
    231 {
    232     SkinButton*  button;
    233     ANEW0(button);
    234     if (button) {
    235         const char*  img = aconfig_str(node, "image", NULL);
    236         int          x   = aconfig_int(node, "x", 0);
    237         int          y   = aconfig_int(node, "y", 0);
    238 
    239         button->name       = node->name;
    240         button->rect.pos.x = x;
    241         button->rect.pos.y = y;
    242 
    243         if (img != NULL)
    244             button->image = skin_image_find_in( basepath, img );
    245 
    246         if (button->image == SKIN_IMAGE_NONE) {
    247             skin_button_free(button);
    248             return NULL;
    249         }
    250 
    251         button->rect.size.w = skin_image_w( button->image );
    252         button->rect.size.h = skin_image_h( button->image );
    253 
    254         button->keycode = keyinfo_lookup_code( button->name );
    255         if (button->keycode == 0) {
    256             dprint( "Warning: skin file button uses unknown key name '%s'", button->name );
    257         }
    258     }
    259     return button;
    260 }
    261 
    262 /** SKIN PART
    263  **/
    264 
    265 static void
    266 skin_part_free( SkinPart*  part )
    267 {
    268     if (part) {
    269         skin_background_done( part->background );
    270         skin_display_done( part->display );
    271 
    272         SKIN_PART_LOOP_BUTTONS(part,button)
    273             skin_button_free(button);
    274         SKIN_PART_LOOP_END
    275         part->buttons = NULL;
    276         AFREE(part);
    277     }
    278 }
    279 
    280 static SkinLocation*
    281 skin_location_create_from_v2( AConfig*  node, SkinPart*  parts )
    282 {
    283     const char*    partname = aconfig_str(node, "name", NULL);
    284     int            x        = aconfig_int(node, "x", 0);
    285     int            y        = aconfig_int(node, "y", 0);
    286     SkinRotation   rot      = aconfig_int(node, "rotation", SKIN_ROTATION_0);
    287     SkinPart*      part;
    288     SkinLocation*  location;
    289 
    290     if (partname == NULL) {
    291         dprint( "### WARNING: ignoring part location without 'name' element" );
    292         return NULL;
    293     }
    294 
    295     for (part = parts; part; part = part->next)
    296         if (!strcmp(part->name, partname))
    297             break;
    298 
    299     if (part == NULL) {
    300         dprint( "### WARNING: ignoring part location with unknown name '%s'", partname );
    301         return NULL;
    302     }
    303 
    304     ANEW0(location);
    305     location->part     = part;
    306     location->anchor.x = x;
    307     location->anchor.y = y;
    308     location->rotation = rot;
    309 
    310     return location;
    311 }
    312 
    313 static SkinPart*
    314 skin_part_create_from_v1( AConfig*  root, const char*  basepath )
    315 {
    316     SkinPart*  part;
    317     AConfig*  node;
    318     SkinBox   box;
    319 
    320     ANEW0(part);
    321     part->name = root->name;
    322 
    323     node = aconfig_find(root, "background");
    324     if (node)
    325         skin_background_init_from(part->background, node, basepath);
    326 
    327     node = aconfig_find(root, "display");
    328     if (node)
    329         skin_display_init_from(part->display, node);
    330 
    331     node = aconfig_find(root, "button");
    332     if (node) {
    333         for (node = node->first_child; node != NULL; node = node->next)
    334         {
    335             SkinButton*  button = skin_button_create_from(node, basepath);
    336 
    337             if (button != NULL) {
    338                 button->next  = part->buttons;
    339                 part->buttons = button;
    340             }
    341         }
    342     }
    343 
    344     skin_box_minmax_init( &box );
    345 
    346     if (part->background->valid)
    347         skin_box_minmax_update( &box, &part->background->rect );
    348 
    349     if (part->display->valid)
    350         skin_box_minmax_update( &box, &part->display->rect );
    351 
    352     SKIN_PART_LOOP_BUTTONS(part, button)
    353         skin_box_minmax_update( &box, &button->rect );
    354     SKIN_PART_LOOP_END
    355 
    356     if ( !skin_box_minmax_to_rect( &box, &part->rect ) ) {
    357         skin_part_free(part);
    358         part = NULL;
    359     }
    360 
    361     return part;
    362 }
    363 
    364 static SkinPart*
    365 skin_part_create_from_v2( AConfig*  root, const char*  basepath )
    366 {
    367     SkinPart*  part;
    368     AConfig*  node;
    369     SkinBox   box;
    370 
    371     ANEW0(part);
    372     part->name = root->name;
    373 
    374     node = aconfig_find(root, "background");
    375     if (node)
    376         skin_background_init_from(part->background, node, basepath);
    377 
    378     node = aconfig_find(root, "display");
    379     if (node)
    380         skin_display_init_from(part->display, node);
    381 
    382     node = aconfig_find(root, "buttons");
    383     if (node) {
    384         for (node = node->first_child; node != NULL; node = node->next)
    385         {
    386             SkinButton*  button = skin_button_create_from(node, basepath);
    387 
    388             if (button != NULL) {
    389                 button->next  = part->buttons;
    390                 part->buttons = button;
    391             }
    392         }
    393     }
    394 
    395     skin_box_minmax_init( &box );
    396 
    397     if (part->background->valid)
    398         skin_box_minmax_update( &box, &part->background->rect );
    399 
    400     if (part->display->valid)
    401         skin_box_minmax_update( &box, &part->display->rect );
    402 
    403     SKIN_PART_LOOP_BUTTONS(part, button)
    404         skin_box_minmax_update( &box, &button->rect );
    405     SKIN_PART_LOOP_END
    406 
    407     if ( !skin_box_minmax_to_rect( &box, &part->rect ) ) {
    408         skin_part_free(part);
    409         part = NULL;
    410     }
    411     return part;
    412 }
    413 
    414 /** SKIN LAYOUT
    415  **/
    416 
    417 static void
    418 skin_layout_free( SkinLayout*  layout )
    419 {
    420     if (layout) {
    421         SKIN_LAYOUT_LOOP_LOCS(layout,loc)
    422             AFREE(loc);
    423         SKIN_LAYOUT_LOOP_END
    424         layout->locations = NULL;
    425         AFREE(layout);
    426     }
    427 }
    428 
    429 SkinDisplay*
    430 skin_layout_get_display( SkinLayout*  layout )
    431 {
    432     SKIN_LAYOUT_LOOP_LOCS(layout,loc)
    433         SkinPart*  part = loc->part;
    434         if (part->display->valid) {
    435             return part->display;
    436         }
    437     SKIN_LAYOUT_LOOP_END
    438     return NULL;
    439 }
    440 
    441 SkinRotation
    442 skin_layout_get_dpad_rotation( SkinLayout*  layout )
    443 {
    444     if (layout->has_dpad_rotation)
    445         return layout->dpad_rotation;
    446 
    447     SKIN_LAYOUT_LOOP_LOCS(layout, loc)
    448         SkinPart*  part = loc->part;
    449         SKIN_PART_LOOP_BUTTONS(part,button)
    450             if (button->keycode == kKeyCodeDpadUp)
    451                 return loc->rotation;
    452         SKIN_PART_LOOP_END
    453     SKIN_LAYOUT_LOOP_END
    454 
    455     return SKIN_ROTATION_0;
    456 }
    457 
    458 
    459 static int
    460 skin_layout_event_decode( const char*  event, int  *ptype, int  *pcode, int *pvalue )
    461 {
    462     typedef struct {
    463         const char*  name;
    464         int          value;
    465     } EventName;
    466 
    467     static const EventName  _event_names[] = {
    468         { "EV_SW", 0x05 },
    469         { NULL, 0 },
    470     };
    471 
    472     const char*       x = strchr(event, ':');
    473     const char*       y = NULL;
    474     const EventName*  ev = _event_names;
    475 
    476     if (x != NULL)
    477         y = strchr(x+1, ':');
    478 
    479     if (x == NULL || y == NULL) {
    480         dprint( "### WARNING: invalid skin layout event format: '%s', should be '<TYPE>:<CODE>:<VALUE>'", event );
    481         return -1;
    482     }
    483 
    484     for ( ; ev->name != NULL; ev++ )
    485         if (!memcmp( event, ev->name, x - event ) && ev->name[x-event] == 0)
    486             break;
    487 
    488     if (!ev->name) {
    489         dprint( "### WARNING: unrecognized skin layout event name: %.*s", x-event, event );
    490         return -1;
    491     }
    492 
    493     *ptype  = ev->value;
    494     *pcode  = strtol(x+1, NULL, 0);
    495     *pvalue = strtol(y+1, NULL, 0);
    496     return 0;
    497 }
    498 
    499 static SkinLayout*
    500 skin_layout_create_from_v2( AConfig*  root, SkinPart*  parts )
    501 {
    502     SkinLayout*    layout;
    503     int            width, height;
    504     SkinLocation** ptail;
    505     AConfig*       node;
    506 
    507     ANEW0(layout);
    508 
    509     width  = aconfig_int( root, "width", 400 );
    510     height = aconfig_int( root, "height", 400 );
    511 
    512     node = aconfig_find( root, "event" );
    513     if (node != NULL) {
    514         skin_layout_event_decode( node->value,
    515                                   &layout->event_type,
    516                                   &layout->event_code,
    517                                   &layout->event_value );
    518     } else {
    519         layout->event_type  = 0x05;  /* close keyboard by default */
    520         layout->event_code  = 0;
    521         layout->event_value = 1;
    522     }
    523 
    524     layout->name  = root->name;
    525     layout->color = aconfig_unsigned( root, "color", 0x808080 ) | 0xff000000;
    526     ptail         = &layout->locations;
    527 
    528     node = aconfig_find( root, "dpad-rotation" );
    529     if (node != NULL) {
    530         layout->dpad_rotation     = aconfig_int( root, "dpad-rotation", 0 );
    531         layout->has_dpad_rotation = 1;
    532     }
    533 
    534     for (node = root->first_child; node; node = node->next)
    535     {
    536         if (!memcmp(node->name, "part", 4)) {
    537             SkinLocation*  location = skin_location_create_from_v2( node, parts );
    538             if (location == NULL) {
    539                 continue;
    540             }
    541             *ptail = location;
    542             ptail  = &location->next;
    543         }
    544     }
    545 
    546     if (layout->locations == NULL)
    547         goto Fail;
    548 
    549     layout->size.w = width;
    550     layout->size.h = height;
    551 
    552     return layout;
    553 
    554 Fail:
    555     skin_layout_free(layout);
    556     return NULL;
    557 }
    558 
    559 /** SKIN FILE
    560  **/
    561 
    562 static int
    563 skin_file_load_from_v1( SkinFile*  file, AConfig*  aconfig, const char*  basepath )
    564 {
    565     SkinPart*      part;
    566     SkinLayout*    layout;
    567     SkinLayout**   ptail = &file->layouts;
    568     SkinLocation*  location;
    569     int            nn;
    570 
    571     file->parts = part = skin_part_create_from_v1( aconfig, basepath );
    572     if (part == NULL)
    573         return -1;
    574 
    575     for (nn = 0; nn < 2; nn++)
    576     {
    577         ANEW0(layout);
    578 
    579         layout->color = 0xff808080;
    580 
    581         ANEW0(location);
    582 
    583         layout->event_type  = 0x05;  /* close keyboard by default */
    584         layout->event_code  = 0;
    585         layout->event_value = 1;
    586 
    587         location->part     = part;
    588         switch (nn) {
    589             case 0:
    590                 location->anchor.x = 0;
    591                 location->anchor.y = 0;
    592                 location->rotation = SKIN_ROTATION_0;
    593                 layout->size       = part->rect.size;
    594                 break;
    595 
    596 #if 0
    597             case 1:
    598                 location->anchor.x = part->rect.size.h;
    599                 location->anchor.y = 0;
    600                 location->rotation = SKIN_ROTATION_90;
    601                 layout->size.w     = part->rect.size.h;
    602                 layout->size.h     = part->rect.size.w;
    603                 layout->event_value = 0;
    604                 break;
    605 
    606             case 2:
    607                 location->anchor.x = part->rect.size.w;
    608                 location->anchor.y = part->rect.size.h;
    609                 location->rotation = SKIN_ROTATION_180;
    610                 layout->size       = part->rect.size;
    611                 break;
    612 #endif
    613             default:
    614                 location->anchor.x = 0;
    615                 location->anchor.y = part->rect.size.w;
    616                 location->rotation = SKIN_ROTATION_270;
    617                 layout->size.w     = part->rect.size.h;
    618                 layout->size.h     = part->rect.size.w;
    619                 layout->event_value = 0;
    620                 break;
    621         }
    622         layout->locations = location;
    623 
    624         *ptail = layout;
    625         ptail  = &layout->next;
    626     }
    627     file->version = 1;
    628     return 0;
    629 }
    630 
    631 static int
    632 skin_file_load_from_v2( SkinFile*  file, AConfig*  aconfig, const char*  basepath )
    633 {
    634     AConfig*  node;
    635 
    636     /* first, load all parts */
    637     node = aconfig_find(aconfig, "parts");
    638     if (node == NULL)
    639         return -1;
    640     else
    641     {
    642         SkinPart**  ptail = &file->parts;
    643         for (node = node->first_child; node != NULL; node = node->next)
    644         {
    645             SkinPart*  part = skin_part_create_from_v2( node, basepath );
    646             if (part == NULL) {
    647                 dprint( "## WARNING: can't load part '%s' from skin\n", node->name ? "<NULL>" : node->name );
    648                 continue;
    649             }
    650             part->next = NULL;
    651             *ptail     = part;
    652             ptail      = &part->next;
    653         }
    654     }
    655 
    656     if (file->parts == NULL)
    657         return -1;
    658 
    659     /* then load all layouts */
    660     node = aconfig_find(aconfig, "layouts");
    661     if (node == NULL)
    662         return -1;
    663     else
    664     {
    665         SkinLayout**  ptail = &file->layouts;
    666         for (node = node->first_child; node != NULL; node = node->next)
    667         {
    668             SkinLayout*  layout = skin_layout_create_from_v2( node, file->parts );
    669             if (layout == NULL) {
    670                 dprint( "## WARNING: ignoring layout in skin file" );
    671                 continue;
    672             }
    673             *ptail = layout;
    674             layout->next = NULL;
    675             ptail        = &layout->next;
    676         }
    677     }
    678     if (file->layouts == NULL)
    679         return -1;
    680 
    681     file->version = 2;
    682     return 0;
    683 }
    684 
    685 SkinFile*
    686 skin_file_create_from_aconfig( AConfig*   aconfig, const char*  basepath )
    687 {
    688     SkinFile*  file;
    689 
    690     ANEW0(file);
    691 
    692     if ( aconfig_find(aconfig, "parts") != NULL) {
    693         if (skin_file_load_from_v2( file, aconfig, basepath ) < 0) {
    694             goto BAD_FILE;
    695         }
    696         file->version = aconfig_int(aconfig, "version", 2);
    697         /* The file version must be 1 or higher */
    698         if (file->version <= 0) {
    699             dprint( "## WARNING: invalid skin version: %d", file->version);
    700             goto BAD_FILE;
    701         }
    702     }
    703     else {
    704         if (skin_file_load_from_v1( file, aconfig, basepath ) < 0) {
    705             goto BAD_FILE;
    706         }
    707         file->version = 1;
    708     }
    709     return file;
    710 
    711 BAD_FILE:
    712     skin_file_free( file );
    713     return NULL;
    714 }
    715 
    716 void
    717 skin_file_free( SkinFile*  file )
    718 {
    719     if (file) {
    720         SKIN_FILE_LOOP_LAYOUTS(file,layout)
    721             skin_layout_free(layout);
    722         SKIN_FILE_LOOP_END_LAYOUTS
    723         file->layouts = NULL;
    724 
    725         SKIN_FILE_LOOP_PARTS(file,part)
    726             skin_part_free(part);
    727         SKIN_FILE_LOOP_END_PARTS
    728         file->parts = NULL;
    729 
    730         AFREE(file);
    731     }
    732 }
    733