Home | History | Annotate | Download | only in cups
      1 /*
      2  * Transcoding support for CUPS.
      3  *
      4  * Copyright 2007-2014 by Apple Inc.
      5  * Copyright 1997-2007 by Easy Software Products.
      6  *
      7  * These coded instructions, statements, and computer programs are the
      8  * property of Apple Inc. and are protected by Federal copyright
      9  * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
     10  * which should have been included with this file.  If this file is
     11  * missing or damaged, see the license at "http://www.cups.org/".
     12  *
     13  * This file is subject to the Apple OS-Developed Software exception.
     14  */
     15 
     16 /*
     17  * Include necessary headers...
     18  */
     19 
     20 #include "cups-private.h"
     21 #include <limits.h>
     22 #include <time.h>
     23 #ifdef HAVE_ICONV_H
     24 #  include <iconv.h>
     25 #endif /* HAVE_ICONV_H */
     26 
     27 
     28 /*
     29  * Local globals...
     30  */
     31 
     32 #ifdef HAVE_ICONV_H
     33 static _cups_mutex_t	map_mutex = _CUPS_MUTEX_INITIALIZER;
     34 					/* Mutex to control access to maps */
     35 static iconv_t		map_from_utf8 = (iconv_t)-1;
     36 					/* Convert from UTF-8 to charset */
     37 static iconv_t		map_to_utf8 = (iconv_t)-1;
     38 					/* Convert from charset to UTF-8 */
     39 static cups_encoding_t	map_encoding = CUPS_AUTO_ENCODING;
     40 					/* Which charset is cached */
     41 #endif /* HAVE_ICONV_H */
     42 
     43 
     44 /*
     45  * '_cupsCharmapFlush()' - Flush all character set maps out of cache.
     46  */
     47 
     48 void
     49 _cupsCharmapFlush(void)
     50 {
     51 #ifdef HAVE_ICONV_H
     52   if (map_from_utf8 != (iconv_t)-1)
     53   {
     54     iconv_close(map_from_utf8);
     55     map_from_utf8 = (iconv_t)-1;
     56   }
     57 
     58   if (map_to_utf8 != (iconv_t)-1)
     59   {
     60     iconv_close(map_to_utf8);
     61     map_to_utf8 = (iconv_t)-1;
     62   }
     63 
     64   map_encoding = CUPS_AUTO_ENCODING;
     65 #endif /* HAVE_ICONV_H */
     66 }
     67 
     68 
     69 /*
     70  * 'cupsCharsetToUTF8()' - Convert legacy character set to UTF-8.
     71  */
     72 
     73 int					/* O - Count or -1 on error */
     74 cupsCharsetToUTF8(
     75     cups_utf8_t           *dest,	/* O - Target string */
     76     const char            *src,		/* I - Source string */
     77     const int             maxout,	/* I - Max output */
     78     const cups_encoding_t encoding)	/* I - Encoding */
     79 {
     80   cups_utf8_t	*destptr;		/* Pointer into UTF-8 buffer */
     81 #ifdef HAVE_ICONV_H
     82   size_t	srclen,			/* Length of source string */
     83 		outBytesLeft;		/* Bytes remaining in output buffer */
     84 #endif /* HAVE_ICONV_H */
     85 
     86 
     87  /*
     88   * Check for valid arguments...
     89   */
     90 
     91   DEBUG_printf(("2cupsCharsetToUTF8(dest=%p, src=\"%s\", maxout=%d, encoding=%d)", (void *)dest, src, maxout, encoding));
     92 
     93   if (!dest || !src || maxout < 1)
     94   {
     95     if (dest)
     96       *dest = '\0';
     97 
     98     DEBUG_puts("3cupsCharsetToUTF8: Bad arguments, returning -1");
     99     return (-1);
    100   }
    101 
    102  /*
    103   * Handle identity conversions...
    104   */
    105 
    106   if (encoding == CUPS_UTF8 || encoding <= CUPS_US_ASCII ||
    107       encoding >= CUPS_ENCODING_VBCS_END)
    108   {
    109     strlcpy((char *)dest, src, (size_t)maxout);
    110     return ((int)strlen((char *)dest));
    111   }
    112 
    113  /*
    114   * Handle ISO-8859-1 to UTF-8 directly...
    115   */
    116 
    117   destptr = dest;
    118 
    119   if (encoding == CUPS_ISO8859_1)
    120   {
    121     int		ch;			/* Character from string */
    122     cups_utf8_t	*destend;		/* End of UTF-8 buffer */
    123 
    124 
    125     destend = dest + maxout - 2;
    126 
    127     while (*src && destptr < destend)
    128     {
    129       ch = *src++ & 255;
    130 
    131       if (ch & 128)
    132       {
    133 	*destptr++ = (cups_utf8_t)(0xc0 | (ch >> 6));
    134 	*destptr++ = (cups_utf8_t)(0x80 | (ch & 0x3f));
    135       }
    136       else
    137 	*destptr++ = (cups_utf8_t)ch;
    138     }
    139 
    140     *destptr = '\0';
    141 
    142     return ((int)(destptr - dest));
    143   }
    144 
    145  /*
    146   * Convert input legacy charset to UTF-8...
    147   */
    148 
    149 #ifdef HAVE_ICONV_H
    150   _cupsMutexLock(&map_mutex);
    151 
    152   if (map_encoding != encoding)
    153   {
    154     char	toset[1024];		/* Destination character set */
    155 
    156     _cupsCharmapFlush();
    157 
    158     snprintf(toset, sizeof(toset), "%s//IGNORE", _cupsEncodingName(encoding));
    159 
    160     map_encoding  = encoding;
    161     map_from_utf8 = iconv_open(_cupsEncodingName(encoding), "UTF-8");
    162     map_to_utf8   = iconv_open("UTF-8", toset);
    163   }
    164 
    165   if (map_to_utf8 != (iconv_t)-1)
    166   {
    167     char *altdestptr = (char *)dest;	/* Silence bogus GCC type-punned */
    168 
    169     srclen       = strlen(src);
    170     outBytesLeft = (size_t)maxout - 1;
    171 
    172     iconv(map_to_utf8, (char **)&src, &srclen, &altdestptr, &outBytesLeft);
    173     *altdestptr = '\0';
    174 
    175     _cupsMutexUnlock(&map_mutex);
    176 
    177     return ((int)(altdestptr - (char *)dest));
    178   }
    179 
    180   _cupsMutexUnlock(&map_mutex);
    181 #endif /* HAVE_ICONV_H */
    182 
    183  /*
    184   * No iconv() support, so error out...
    185   */
    186 
    187   *destptr = '\0';
    188 
    189   return (-1);
    190 }
    191 
    192 
    193 /*
    194  * 'cupsUTF8ToCharset()' - Convert UTF-8 to legacy character set.
    195  */
    196 
    197 int					/* O - Count or -1 on error */
    198 cupsUTF8ToCharset(
    199     char		  *dest,	/* O - Target string */
    200     const cups_utf8_t	  *src,		/* I - Source string */
    201     const int		  maxout,	/* I - Max output */
    202     const cups_encoding_t encoding)	/* I - Encoding */
    203 {
    204   char		*destptr;		/* Pointer into destination */
    205 #ifdef HAVE_ICONV_H
    206   size_t	srclen,			/* Length of source string */
    207 		outBytesLeft;		/* Bytes remaining in output buffer */
    208 #endif /* HAVE_ICONV_H */
    209 
    210 
    211  /*
    212   * Check for valid arguments...
    213   */
    214 
    215   if (!dest || !src || maxout < 1)
    216   {
    217     if (dest)
    218       *dest = '\0';
    219 
    220     return (-1);
    221   }
    222 
    223  /*
    224   * Handle identity conversions...
    225   */
    226 
    227   if (encoding == CUPS_UTF8 ||
    228       encoding >= CUPS_ENCODING_VBCS_END)
    229   {
    230     strlcpy(dest, (char *)src, (size_t)maxout);
    231     return ((int)strlen(dest));
    232   }
    233 
    234  /*
    235   * Handle UTF-8 to ISO-8859-1 directly...
    236   */
    237 
    238   destptr = dest;
    239 
    240   if (encoding == CUPS_ISO8859_1 || encoding <= CUPS_US_ASCII)
    241   {
    242     int		ch,			/* Character from string */
    243 		maxch;			/* Maximum character for charset */
    244     char	*destend;		/* End of ISO-8859-1 buffer */
    245 
    246     maxch   = encoding == CUPS_ISO8859_1 ? 256 : 128;
    247     destend = dest + maxout - 1;
    248 
    249     while (*src && destptr < destend)
    250     {
    251       ch = *src++;
    252 
    253       if ((ch & 0xe0) == 0xc0)
    254       {
    255 	ch = ((ch & 0x1f) << 6) | (*src++ & 0x3f);
    256 
    257 	if (ch < maxch)
    258           *destptr++ = (char)ch;
    259 	else
    260           *destptr++ = '?';
    261       }
    262       else if ((ch & 0xf0) == 0xe0 ||
    263                (ch & 0xf8) == 0xf0)
    264         *destptr++ = '?';
    265       else if (!(ch & 0x80))
    266 	*destptr++ = (char)ch;
    267     }
    268 
    269     *destptr = '\0';
    270 
    271     return ((int)(destptr - dest));
    272   }
    273 
    274 #ifdef HAVE_ICONV_H
    275  /*
    276   * Convert input UTF-8 to legacy charset...
    277   */
    278 
    279   _cupsMutexLock(&map_mutex);
    280 
    281   if (map_encoding != encoding)
    282   {
    283     char	toset[1024];		/* Destination character set */
    284 
    285     _cupsCharmapFlush();
    286 
    287     snprintf(toset, sizeof(toset), "%s//IGNORE", _cupsEncodingName(encoding));
    288 
    289     map_encoding  = encoding;
    290     map_from_utf8 = iconv_open(_cupsEncodingName(encoding), "UTF-8");
    291     map_to_utf8   = iconv_open("UTF-8", toset);
    292   }
    293 
    294   if (map_from_utf8 != (iconv_t)-1)
    295   {
    296     char *altsrc = (char *)src;		/* Silence bogus GCC type-punned */
    297 
    298     srclen       = strlen((char *)src);
    299     outBytesLeft = (size_t)maxout - 1;
    300 
    301     iconv(map_from_utf8, &altsrc, &srclen, &destptr, &outBytesLeft);
    302     *destptr = '\0';
    303 
    304     _cupsMutexUnlock(&map_mutex);
    305 
    306     return ((int)(destptr - dest));
    307   }
    308 
    309   _cupsMutexUnlock(&map_mutex);
    310 #endif /* HAVE_ICONV_H */
    311 
    312  /*
    313   * No iconv() support, so error out...
    314   */
    315 
    316   *destptr = '\0';
    317 
    318   return (-1);
    319 }
    320 
    321 
    322 /*
    323  * 'cupsUTF8ToUTF32()' - Convert UTF-8 to UTF-32.
    324  *
    325  * 32-bit UTF-32 (actually 21-bit) maps to UTF-8 as follows...
    326  *
    327  *   UTF-32 char     UTF-8 char(s)
    328  *   --------------------------------------------------
    329  *	  0 to 127 = 0xxxxxxx (US-ASCII)
    330  *     128 to 2047 = 110xxxxx 10yyyyyy
    331  *   2048 to 65535 = 1110xxxx 10yyyyyy 10zzzzzz
    332  *	   > 65535 = 11110xxx 10yyyyyy 10zzzzzz 10xxxxxx
    333  *
    334  * UTF-32 prohibits chars beyond Plane 16 (> 0x10ffff) in UCS-4,
    335  * which would convert to five- or six-octet UTF-8 sequences...
    336  */
    337 
    338 int					/* O - Count or -1 on error */
    339 cupsUTF8ToUTF32(
    340     cups_utf32_t      *dest,		/* O - Target string */
    341     const cups_utf8_t *src,		/* I - Source string */
    342     const int         maxout)		/* I - Max output */
    343 {
    344   int		i;			/* Looping variable */
    345   cups_utf8_t	ch;			/* Character value */
    346   cups_utf8_t	next;			/* Next character value */
    347   cups_utf32_t	ch32;			/* UTF-32 character value */
    348 
    349 
    350  /*
    351   * Check for valid arguments and clear output...
    352   */
    353 
    354   DEBUG_printf(("2cupsUTF8ToUTF32(dest=%p, src=\"%s\", maxout=%d)", (void *)dest, src, maxout));
    355 
    356   if (dest)
    357     *dest = 0;
    358 
    359   if (!dest || !src || maxout < 1 || maxout > CUPS_MAX_USTRING)
    360   {
    361     DEBUG_puts("3cupsUTF8ToUTF32: Returning -1 (bad arguments)");
    362 
    363     return (-1);
    364   }
    365 
    366  /*
    367   * Convert input UTF-8 to output UTF-32...
    368   */
    369 
    370   for (i = maxout - 1; *src && i > 0; i --)
    371   {
    372     ch = *src++;
    373 
    374    /*
    375     * Convert UTF-8 character(s) to UTF-32 character...
    376     */
    377 
    378     if (!(ch & 0x80))
    379     {
    380      /*
    381       * One-octet UTF-8 <= 127 (US-ASCII)...
    382       */
    383 
    384       *dest++ = ch;
    385 
    386       DEBUG_printf(("4cupsUTF8ToUTF32: %02x => %08X", src[-1], ch));
    387       continue;
    388     }
    389     else if ((ch & 0xe0) == 0xc0)
    390     {
    391      /*
    392       * Two-octet UTF-8 <= 2047 (Latin-x)...
    393       */
    394 
    395       next = *src++;
    396       if ((next & 0xc0) != 0x80)
    397       {
    398         DEBUG_puts("3cupsUTF8ToUTF32: Returning -1 (bad UTF-8 sequence)");
    399 
    400 	return (-1);
    401       }
    402 
    403       ch32 = (cups_utf32_t)((ch & 0x1f) << 6) | (cups_utf32_t)(next & 0x3f);
    404 
    405      /*
    406       * Check for non-shortest form (invalid UTF-8)...
    407       */
    408 
    409       if (ch32 < 0x80)
    410       {
    411         DEBUG_puts("3cupsUTF8ToUTF32: Returning -1 (bad UTF-8 sequence)");
    412 
    413 	return (-1);
    414       }
    415 
    416       *dest++ = ch32;
    417 
    418       DEBUG_printf(("4cupsUTF8ToUTF32: %02x %02x => %08X",
    419                     src[-2], src[-1], (unsigned)ch32));
    420     }
    421     else if ((ch & 0xf0) == 0xe0)
    422     {
    423      /*
    424       * Three-octet UTF-8 <= 65535 (Plane 0 - BMP)...
    425       */
    426 
    427       next = *src++;
    428       if ((next & 0xc0) != 0x80)
    429       {
    430         DEBUG_puts("3cupsUTF8ToUTF32: Returning -1 (bad UTF-8 sequence)");
    431 
    432 	return (-1);
    433       }
    434 
    435       ch32 = (cups_utf32_t)((ch & 0x0f) << 6) | (cups_utf32_t)(next & 0x3f);
    436 
    437       next = *src++;
    438       if ((next & 0xc0) != 0x80)
    439       {
    440         DEBUG_puts("3cupsUTF8ToUTF32: Returning -1 (bad UTF-8 sequence)");
    441 
    442 	return (-1);
    443       }
    444 
    445       ch32 = (ch32 << 6) | (cups_utf32_t)(next & 0x3f);
    446 
    447      /*
    448       * Check for non-shortest form (invalid UTF-8)...
    449       */
    450 
    451       if (ch32 < 0x800)
    452       {
    453         DEBUG_puts("3cupsUTF8ToUTF32: Returning -1 (bad UTF-8 sequence)");
    454 
    455 	return (-1);
    456       }
    457 
    458       *dest++ = ch32;
    459 
    460       DEBUG_printf(("4cupsUTF8ToUTF32: %02x %02x %02x => %08X",
    461                     src[-3], src[-2], src[-1], (unsigned)ch32));
    462     }
    463     else if ((ch & 0xf8) == 0xf0)
    464     {
    465      /*
    466       * Four-octet UTF-8...
    467       */
    468 
    469       next = *src++;
    470       if ((next & 0xc0) != 0x80)
    471       {
    472         DEBUG_puts("3cupsUTF8ToUTF32: Returning -1 (bad UTF-8 sequence)");
    473 
    474 	return (-1);
    475       }
    476 
    477       ch32 = (cups_utf32_t)((ch & 0x07) << 6) | (cups_utf32_t)(next & 0x3f);
    478 
    479       next = *src++;
    480       if ((next & 0xc0) != 0x80)
    481       {
    482         DEBUG_puts("3cupsUTF8ToUTF32: Returning -1 (bad UTF-8 sequence)");
    483 
    484 	return (-1);
    485       }
    486 
    487       ch32 = (ch32 << 6) | (cups_utf32_t)(next & 0x3f);
    488 
    489       next = *src++;
    490       if ((next & 0xc0) != 0x80)
    491       {
    492         DEBUG_puts("3cupsUTF8ToUTF32: Returning -1 (bad UTF-8 sequence)");
    493 
    494 	return (-1);
    495       }
    496 
    497       ch32 = (ch32 << 6) | (cups_utf32_t)(next & 0x3f);
    498 
    499      /*
    500       * Check for non-shortest form (invalid UTF-8)...
    501       */
    502 
    503       if (ch32 < 0x10000)
    504       {
    505         DEBUG_puts("3cupsUTF8ToUTF32: Returning -1 (bad UTF-8 sequence)");
    506 
    507 	return (-1);
    508       }
    509 
    510       *dest++ = ch32;
    511 
    512       DEBUG_printf(("4cupsUTF8ToUTF32: %02x %02x %02x %02x => %08X",
    513                     src[-4], src[-3], src[-2], src[-1], (unsigned)ch32));
    514     }
    515     else
    516     {
    517      /*
    518       * More than 4-octet (invalid UTF-8 sequence)...
    519       */
    520 
    521       DEBUG_puts("3cupsUTF8ToUTF32: Returning -1 (bad UTF-8 sequence)");
    522 
    523       return (-1);
    524     }
    525 
    526    /*
    527     * Check for UTF-16 surrogate (illegal UTF-8)...
    528     */
    529 
    530     if (ch32 >= 0xd800 && ch32 <= 0xdfff)
    531       return (-1);
    532   }
    533 
    534   *dest = 0;
    535 
    536   DEBUG_printf(("3cupsUTF8ToUTF32: Returning %d characters", maxout - 1 - i));
    537 
    538   return (maxout - 1 - i);
    539 }
    540 
    541 
    542 /*
    543  * 'cupsUTF32ToUTF8()' - Convert UTF-32 to UTF-8.
    544  *
    545  * 32-bit UTF-32 (actually 21-bit) maps to UTF-8 as follows...
    546  *
    547  *   UTF-32 char     UTF-8 char(s)
    548  *   --------------------------------------------------
    549  *	  0 to 127 = 0xxxxxxx (US-ASCII)
    550  *     128 to 2047 = 110xxxxx 10yyyyyy
    551  *   2048 to 65535 = 1110xxxx 10yyyyyy 10zzzzzz
    552  *	   > 65535 = 11110xxx 10yyyyyy 10zzzzzz 10xxxxxx
    553  *
    554  * UTF-32 prohibits chars beyond Plane 16 (> 0x10ffff) in UCS-4,
    555  * which would convert to five- or six-octet UTF-8 sequences...
    556  */
    557 
    558 int					/* O - Count or -1 on error */
    559 cupsUTF32ToUTF8(
    560     cups_utf8_t        *dest,		/* O - Target string */
    561     const cups_utf32_t *src,		/* I - Source string */
    562     const int          maxout)		/* I - Max output */
    563 {
    564   cups_utf8_t	*start;			/* Start of destination string */
    565   int		i;			/* Looping variable */
    566   int		swap;			/* Byte-swap input to output */
    567   cups_utf32_t	ch;			/* Character value */
    568 
    569 
    570  /*
    571   * Check for valid arguments and clear output...
    572   */
    573 
    574   DEBUG_printf(("2cupsUTF32ToUTF8(dest=%p, src=%p, maxout=%d)", (void *)dest, (void *)src, maxout));
    575 
    576   if (dest)
    577     *dest = '\0';
    578 
    579   if (!dest || !src || maxout < 1)
    580   {
    581     DEBUG_puts("3cupsUTF32ToUTF8: Returning -1 (bad args)");
    582 
    583     return (-1);
    584   }
    585 
    586  /*
    587   * Check for leading BOM in UTF-32 and inverted BOM...
    588   */
    589 
    590   start = dest;
    591   swap  = *src == 0xfffe0000;
    592 
    593   DEBUG_printf(("4cupsUTF32ToUTF8: swap=%d", swap));
    594 
    595   if (*src == 0xfffe0000 || *src == 0xfeff)
    596     src ++;
    597 
    598  /*
    599   * Convert input UTF-32 to output UTF-8...
    600   */
    601 
    602   for (i = maxout - 1; *src && i > 0;)
    603   {
    604     ch = *src++;
    605 
    606    /*
    607     * Byte swap input UTF-32, if necessary...
    608     * (only byte-swapping 24 of 32 bits)
    609     */
    610 
    611     if (swap)
    612       ch = ((ch >> 24) | ((ch >> 8) & 0xff00) | ((ch << 8) & 0xff0000));
    613 
    614    /*
    615     * Check for beyond Plane 16 (invalid UTF-32)...
    616     */
    617 
    618     if (ch > 0x10ffff)
    619     {
    620       DEBUG_puts("3cupsUTF32ToUTF8: Returning -1 (character out of range)");
    621 
    622       return (-1);
    623     }
    624 
    625    /*
    626     * Convert UTF-32 character to UTF-8 character(s)...
    627     */
    628 
    629     if (ch < 0x80)
    630     {
    631      /*
    632       * One-octet UTF-8 <= 127 (US-ASCII)...
    633       */
    634 
    635       *dest++ = (cups_utf8_t)ch;
    636       i --;
    637 
    638       DEBUG_printf(("4cupsUTF32ToUTF8: %08x => %02x", (unsigned)ch, dest[-1]));
    639     }
    640     else if (ch < 0x800)
    641     {
    642      /*
    643       * Two-octet UTF-8 <= 2047 (Latin-x)...
    644       */
    645 
    646       if (i < 2)
    647       {
    648         DEBUG_puts("3cupsUTF32ToUTF8: Returning -1 (too long 2)");
    649 
    650         return (-1);
    651       }
    652 
    653       *dest++ = (cups_utf8_t)(0xc0 | ((ch >> 6) & 0x1f));
    654       *dest++ = (cups_utf8_t)(0x80 | (ch & 0x3f));
    655       i -= 2;
    656 
    657       DEBUG_printf(("4cupsUTF32ToUTF8: %08x => %02x %02x", (unsigned)ch,
    658                     dest[-2], dest[-1]));
    659     }
    660     else if (ch < 0x10000)
    661     {
    662      /*
    663       * Three-octet UTF-8 <= 65535 (Plane 0 - BMP)...
    664       */
    665 
    666       if (i < 3)
    667       {
    668         DEBUG_puts("3cupsUTF32ToUTF8: Returning -1 (too long 3)");
    669 
    670         return (-1);
    671       }
    672 
    673       *dest++ = (cups_utf8_t)(0xe0 | ((ch >> 12) & 0x0f));
    674       *dest++ = (cups_utf8_t)(0x80 | ((ch >> 6) & 0x3f));
    675       *dest++ = (cups_utf8_t)(0x80 | (ch & 0x3f));
    676       i -= 3;
    677 
    678       DEBUG_printf(("4cupsUTF32ToUTF8: %08x => %02x %02x %02x", (unsigned)ch,
    679                     dest[-3], dest[-2], dest[-1]));
    680     }
    681     else
    682     {
    683      /*
    684       * Four-octet UTF-8...
    685       */
    686 
    687       if (i < 4)
    688       {
    689         DEBUG_puts("3cupsUTF32ToUTF8: Returning -1 (too long 4)");
    690 
    691         return (-1);
    692       }
    693 
    694       *dest++ = (cups_utf8_t)(0xf0 | ((ch >> 18) & 0x07));
    695       *dest++ = (cups_utf8_t)(0x80 | ((ch >> 12) & 0x3f));
    696       *dest++ = (cups_utf8_t)(0x80 | ((ch >> 6) & 0x3f));
    697       *dest++ = (cups_utf8_t)(0x80 | (ch & 0x3f));
    698       i -= 4;
    699 
    700       DEBUG_printf(("4cupsUTF32ToUTF8: %08x => %02x %02x %02x %02x",
    701                     (unsigned)ch, dest[-4], dest[-3], dest[-2], dest[-1]));
    702     }
    703   }
    704 
    705   *dest = '\0';
    706 
    707   DEBUG_printf(("3cupsUTF32ToUTF8: Returning %d", (int)(dest - start)));
    708 
    709   return ((int)(dest - start));
    710 }
    711