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.
958 lines
19 KiB
958 lines
19 KiB
//
|
|
// Shared message catalog class for the CUPS PPD Compiler.
|
|
//
|
|
// Copyright 2007-2017 by Apple Inc.
|
|
// Copyright 2002-2006 by Easy Software Products.
|
|
//
|
|
// Licensed under Apache License v2.0. See the file "LICENSE" for more information.
|
|
//
|
|
|
|
//
|
|
// Include necessary headers...
|
|
//
|
|
|
|
#include "ppdc-private.h"
|
|
|
|
|
|
//
|
|
// Character encodings...
|
|
//
|
|
|
|
typedef enum
|
|
{
|
|
PPDC_CS_AUTO,
|
|
PPDC_CS_UTF8,
|
|
PPDC_CS_UTF16BE,
|
|
PPDC_CS_UTF16LE
|
|
} ppdc_cs_t;
|
|
|
|
|
|
//
|
|
// Local functions...
|
|
//
|
|
|
|
#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
|
|
static void apple_add_message(CFStringRef key, CFStringRef val, ppdcCatalog *c);
|
|
#endif /* __APPLE__ && CUPS_BUNDLEDIR */
|
|
static int get_utf8(char *&ptr);
|
|
static int get_utf16(cups_file_t *fp, ppdc_cs_t &cs);
|
|
static int put_utf8(int ch, char *&ptr, char *end);
|
|
static int put_utf16(cups_file_t *fp, int ch);
|
|
|
|
|
|
//
|
|
// 'ppdcCatalog::ppdcCatalog()' - Create a shared message catalog.
|
|
//
|
|
|
|
ppdcCatalog::ppdcCatalog(const char *l, // I - Locale
|
|
const char *f) // I - Message catalog file
|
|
: ppdcShared()
|
|
{
|
|
PPDC_NEW;
|
|
|
|
locale = new ppdcString(l);
|
|
filename = new ppdcString(f);
|
|
messages = new ppdcArray();
|
|
|
|
if (l && strcmp(l, "en"))
|
|
{
|
|
// Try loading the base messages for this locale...
|
|
char pofile[1024]; // Message catalog file
|
|
|
|
|
|
#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
|
|
char applelang[256]; // Apple language ID
|
|
CFURLRef url; // URL to cups.strings file
|
|
CFReadStreamRef stream = NULL; // File stream
|
|
CFPropertyListRef plist = NULL; // Localization file
|
|
|
|
snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", _cupsAppleLanguage(l, applelang, sizeof(applelang)));
|
|
if (access(pofile, 0))
|
|
{
|
|
// Try alternate lproj directory names...
|
|
const char *tl = l; // Temporary locale string
|
|
|
|
if (!strncmp(l, "en", 2))
|
|
tl = "English";
|
|
else if (!strncmp(l, "nb", 2))
|
|
tl = "no";
|
|
else if (!strncmp(l, "nl", 2))
|
|
tl = "Dutch";
|
|
else if (!strncmp(l, "fr", 2))
|
|
tl = "French";
|
|
else if (!strncmp(l, "de", 2))
|
|
tl = "German";
|
|
else if (!strncmp(l, "it", 2))
|
|
tl = "Italian";
|
|
else if (!strncmp(l, "ja", 2))
|
|
tl = "Japanese";
|
|
else if (!strncmp(l, "es", 2))
|
|
tl = "Spanish";
|
|
|
|
snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", tl);
|
|
}
|
|
|
|
url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (UInt8 *)pofile, (CFIndex)strlen(pofile), false);
|
|
if (url)
|
|
{
|
|
stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
|
|
|
|
if (stream)
|
|
{
|
|
/*
|
|
* Read the property list containing the localization data.
|
|
*/
|
|
|
|
CFReadStreamOpen(stream);
|
|
|
|
plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0, kCFPropertyListImmutable, NULL, NULL);
|
|
|
|
if (plist && CFGetTypeID(plist) == CFDictionaryGetTypeID())
|
|
CFDictionaryApplyFunction((CFDictionaryRef)plist, (CFDictionaryApplierFunction)apple_add_message, this);
|
|
|
|
if (plist)
|
|
CFRelease(plist);
|
|
|
|
CFRelease(stream);
|
|
}
|
|
|
|
CFRelease(url);
|
|
}
|
|
|
|
#else
|
|
_cups_globals_t *cg = _cupsGlobals();
|
|
// Global information
|
|
|
|
snprintf(pofile, sizeof(pofile), "%s/%s/cups_%s.po", cg->localedir, l, l);
|
|
|
|
if (load_messages(pofile) && strchr(l, '_'))
|
|
{
|
|
// Try the base locale...
|
|
char baseloc[3]; // Base locale...
|
|
|
|
|
|
strlcpy(baseloc, l, sizeof(baseloc));
|
|
snprintf(pofile, sizeof(pofile), "%s/%s/cups_%s.po", cg->localedir,
|
|
baseloc, baseloc);
|
|
|
|
load_messages(pofile);
|
|
}
|
|
#endif /* __APPLE__ && CUPS_BUNDLEDIR */
|
|
}
|
|
|
|
if (f && *f)
|
|
load_messages(f);
|
|
}
|
|
|
|
|
|
//
|
|
// 'ppdcCatalog::~ppdcCatalog()' - Destroy a shared message catalog.
|
|
//
|
|
|
|
ppdcCatalog::~ppdcCatalog()
|
|
{
|
|
PPDC_DELETE;
|
|
|
|
locale->release();
|
|
filename->release();
|
|
messages->release();
|
|
}
|
|
|
|
|
|
//
|
|
// 'ppdcCatalog::add_message()' - Add a new message.
|
|
//
|
|
|
|
void
|
|
ppdcCatalog::add_message(
|
|
const char *id, // I - Message ID to add
|
|
const char *string) // I - Translation string
|
|
{
|
|
ppdcMessage *m; // Current message
|
|
char text[1024]; // Text to translate
|
|
|
|
|
|
// Range check input...
|
|
if (!id)
|
|
return;
|
|
|
|
// Verify that we don't already have the message ID...
|
|
for (m = (ppdcMessage *)messages->first();
|
|
m;
|
|
m = (ppdcMessage *)messages->next())
|
|
if (!strcmp(m->id->value, id))
|
|
{
|
|
if (string)
|
|
{
|
|
m->string->release();
|
|
m->string = new ppdcString(string);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Add the message...
|
|
if (!string)
|
|
{
|
|
snprintf(text, sizeof(text), "TRANSLATE %s", id);
|
|
string = text;
|
|
}
|
|
|
|
messages->add(new ppdcMessage(id, string));
|
|
}
|
|
|
|
|
|
//
|
|
// 'ppdcCatalog::find_message()' - Find a message in a catalog...
|
|
//
|
|
|
|
const char * // O - Message text
|
|
ppdcCatalog::find_message(
|
|
const char *id) // I - Message ID
|
|
{
|
|
ppdcMessage *m; // Current message
|
|
|
|
|
|
if (!*id)
|
|
return (id);
|
|
|
|
for (m = (ppdcMessage *)messages->first();
|
|
m;
|
|
m = (ppdcMessage *)messages->next())
|
|
if (!strcmp(m->id->value, id))
|
|
return (m->string->value);
|
|
|
|
return (id);
|
|
}
|
|
|
|
|
|
//
|
|
// 'ppdcCatalog::load_messages()' - Load messages from a .po file.
|
|
//
|
|
|
|
int // O - 0 on success, -1 on failure
|
|
ppdcCatalog::load_messages(
|
|
const char *f) // I - Message catalog file
|
|
{
|
|
cups_file_t *fp; // Message file
|
|
char line[4096], // Line buffer
|
|
*ptr, // Pointer into buffer
|
|
id[4096], // Translation ID
|
|
str[4096]; // Translation string
|
|
int linenum; // Line number
|
|
|
|
|
|
// Open the message catalog file...
|
|
if ((fp = cupsFileOpen(f, "r")) == NULL)
|
|
return (-1);
|
|
|
|
if ((ptr = (char *)strrchr(f, '.')) == NULL)
|
|
goto unknown_load_format;
|
|
else if (!strcmp(ptr, ".strings"))
|
|
{
|
|
/*
|
|
* Read messages in macOS ".strings" format, which are either UTF-8/UTF-16
|
|
* text files of the format:
|
|
*
|
|
* "id" = "str";
|
|
*
|
|
* Strings files can also contain C-style comments.
|
|
*/
|
|
|
|
ppdc_cs_t cs = PPDC_CS_AUTO; // Character set for file
|
|
int ch; // Current character from file
|
|
char *end; // End of buffer
|
|
|
|
|
|
id[0] = '\0';
|
|
str[0] = '\0';
|
|
ptr = NULL;
|
|
end = NULL;
|
|
|
|
while ((ch = get_utf16(fp, cs)) != 0)
|
|
{
|
|
if (ptr)
|
|
{
|
|
if (ch == '\\')
|
|
{
|
|
if ((ch = get_utf16(fp, cs)) == 0)
|
|
break;
|
|
|
|
if (ch == 'n')
|
|
ch = '\n';
|
|
else if (ch == 't')
|
|
ch = '\t';
|
|
}
|
|
else if (ch == '\"')
|
|
{
|
|
*ptr = '\0';
|
|
ptr = NULL;
|
|
}
|
|
|
|
if (ptr)
|
|
put_utf8(ch, ptr, end);
|
|
}
|
|
else if (ch == '/')
|
|
{
|
|
// Start of a comment?
|
|
if ((ch = get_utf16(fp, cs)) == 0)
|
|
break;
|
|
|
|
if (ch == '*')
|
|
{
|
|
// Skip C comment...
|
|
int lastch = 0;
|
|
|
|
while ((ch = get_utf16(fp, cs)) != 0)
|
|
{
|
|
if (ch == '/' && lastch == '*')
|
|
break;
|
|
|
|
lastch = ch;
|
|
}
|
|
}
|
|
else if (ch == '/')
|
|
{
|
|
// Skip C++ comment...
|
|
while ((ch = get_utf16(fp, cs)) != 0)
|
|
if (ch == '\n')
|
|
break;
|
|
}
|
|
}
|
|
else if (ch == '\"')
|
|
{
|
|
// Start quoted string...
|
|
if (id[0])
|
|
{
|
|
ptr = str;
|
|
end = str + sizeof(str) - 1;
|
|
}
|
|
else
|
|
{
|
|
ptr = id;
|
|
end = id + sizeof(id) - 1;
|
|
}
|
|
}
|
|
else if (ch == ';')
|
|
{
|
|
// Add string...
|
|
add_message(id, str);
|
|
id[0] = '\0';
|
|
}
|
|
}
|
|
}
|
|
else if (!strcmp(ptr, ".po") || !strcmp(ptr, ".gz"))
|
|
{
|
|
/*
|
|
* Read messages from the catalog file until EOF...
|
|
*
|
|
* The format is the GNU gettext .po format, which is fairly simple:
|
|
*
|
|
* msgid "some text"
|
|
* msgstr "localized text"
|
|
*
|
|
* The ID and localized text can span multiple lines using the form:
|
|
*
|
|
* msgid ""
|
|
* "some long text"
|
|
* msgstr ""
|
|
* "localized text spanning "
|
|
* "multiple lines"
|
|
*/
|
|
|
|
int which, // In msgid?
|
|
haveid, // Did we get a msgid string?
|
|
havestr; // Did we get a msgstr string?
|
|
|
|
linenum = 0;
|
|
id[0] = '\0';
|
|
str[0] = '\0';
|
|
haveid = 0;
|
|
havestr = 0;
|
|
which = 0;
|
|
|
|
while (cupsFileGets(fp, line, sizeof(line)))
|
|
{
|
|
linenum ++;
|
|
|
|
// Skip blank and comment lines...
|
|
if (line[0] == '#' || !line[0])
|
|
continue;
|
|
|
|
// Strip the trailing quote...
|
|
if ((ptr = (char *)strrchr(line, '\"')) == NULL)
|
|
{
|
|
_cupsLangPrintf(stderr,
|
|
_("ppdc: Expected quoted string on line %d of %s."),
|
|
linenum, f);
|
|
cupsFileClose(fp);
|
|
return (-1);
|
|
}
|
|
|
|
*ptr = '\0';
|
|
|
|
// Find start of value...
|
|
if ((ptr = strchr(line, '\"')) == NULL)
|
|
{
|
|
_cupsLangPrintf(stderr,
|
|
_("ppdc: Expected quoted string on line %d of %s."),
|
|
linenum, f);
|
|
cupsFileClose(fp);
|
|
return (-1);
|
|
}
|
|
|
|
ptr ++;
|
|
|
|
// Unquote the text...
|
|
char *sptr, *dptr; // Source/destination pointers
|
|
|
|
for (sptr = ptr, dptr = ptr; *sptr;)
|
|
{
|
|
if (*sptr == '\\')
|
|
{
|
|
sptr ++;
|
|
if (isdigit(*sptr))
|
|
{
|
|
*dptr = 0;
|
|
|
|
while (isdigit(*sptr))
|
|
{
|
|
*dptr = *dptr * 8 + *sptr - '0';
|
|
sptr ++;
|
|
}
|
|
|
|
dptr ++;
|
|
}
|
|
else
|
|
{
|
|
if (*sptr == 'n')
|
|
*dptr++ = '\n';
|
|
else if (*sptr == 'r')
|
|
*dptr++ = '\r';
|
|
else if (*sptr == 't')
|
|
*dptr++ = '\t';
|
|
else
|
|
*dptr++ = *sptr;
|
|
|
|
sptr ++;
|
|
}
|
|
}
|
|
else
|
|
*dptr++ = *sptr++;
|
|
}
|
|
|
|
*dptr = '\0';
|
|
|
|
// Create or add to a message...
|
|
if (!strncmp(line, "msgid", 5))
|
|
{
|
|
if (haveid && havestr)
|
|
add_message(id, str);
|
|
|
|
strlcpy(id, ptr, sizeof(id));
|
|
str[0] = '\0';
|
|
haveid = 1;
|
|
havestr = 0;
|
|
which = 1;
|
|
}
|
|
else if (!strncmp(line, "msgstr", 6))
|
|
{
|
|
if (!haveid)
|
|
{
|
|
_cupsLangPrintf(stderr,
|
|
_("ppdc: Need a msgid line before any "
|
|
"translation strings on line %d of %s."),
|
|
linenum, f);
|
|
cupsFileClose(fp);
|
|
return (-1);
|
|
}
|
|
|
|
strlcpy(str, ptr, sizeof(str));
|
|
havestr = 1;
|
|
which = 2;
|
|
}
|
|
else if (line[0] == '\"' && which == 2)
|
|
strlcat(str, ptr, sizeof(str));
|
|
else if (line[0] == '\"' && which == 1)
|
|
strlcat(id, ptr, sizeof(id));
|
|
else
|
|
{
|
|
_cupsLangPrintf(stderr, _("ppdc: Unexpected text on line %d of %s."),
|
|
linenum, f);
|
|
cupsFileClose(fp);
|
|
return (-1);
|
|
}
|
|
}
|
|
|
|
if (haveid && havestr)
|
|
add_message(id, str);
|
|
}
|
|
else
|
|
goto unknown_load_format;
|
|
|
|
/*
|
|
* Close the file and return...
|
|
*/
|
|
|
|
cupsFileClose(fp);
|
|
|
|
return (0);
|
|
|
|
/*
|
|
* Unknown format error...
|
|
*/
|
|
|
|
unknown_load_format:
|
|
|
|
_cupsLangPrintf(stderr,
|
|
_("ppdc: Unknown message catalog format for \"%s\"."), f);
|
|
cupsFileClose(fp);
|
|
return (-1);
|
|
}
|
|
|
|
|
|
//
|
|
// 'ppdcCatalog::save_messages()' - Save the messages to a .po file.
|
|
//
|
|
|
|
int // O - 0 on success, -1 on error
|
|
ppdcCatalog::save_messages(
|
|
const char *f) // I - File to save to
|
|
{
|
|
cups_file_t *fp; // Message file
|
|
ppdcMessage *m; // Current message
|
|
char *ptr; // Pointer into string
|
|
int utf16; // Output UTF-16 .strings file?
|
|
int ch; // Current character
|
|
|
|
|
|
// Open the file...
|
|
if ((ptr = (char *)strrchr(f, '.')) == NULL)
|
|
return (-1);
|
|
|
|
if (!strcmp(ptr, ".gz"))
|
|
fp = cupsFileOpen(f, "w9");
|
|
else
|
|
fp = cupsFileOpen(f, "w");
|
|
|
|
if (!fp)
|
|
return (-1);
|
|
|
|
// For .strings files, write a BOM for big-endian output...
|
|
utf16 = !strcmp(ptr, ".strings");
|
|
|
|
if (utf16)
|
|
put_utf16(fp, 0xfeff);
|
|
|
|
// Loop through all of the messages...
|
|
for (m = (ppdcMessage *)messages->first();
|
|
m;
|
|
m = (ppdcMessage *)messages->next())
|
|
{
|
|
if (utf16)
|
|
{
|
|
put_utf16(fp, '\"');
|
|
|
|
ptr = m->id->value;
|
|
while ((ch = get_utf8(ptr)) != 0)
|
|
switch (ch)
|
|
{
|
|
case '\n' :
|
|
put_utf16(fp, '\\');
|
|
put_utf16(fp, 'n');
|
|
break;
|
|
case '\\' :
|
|
put_utf16(fp, '\\');
|
|
put_utf16(fp, '\\');
|
|
break;
|
|
case '\"' :
|
|
put_utf16(fp, '\\');
|
|
put_utf16(fp, '\"');
|
|
break;
|
|
default :
|
|
put_utf16(fp, ch);
|
|
break;
|
|
}
|
|
|
|
put_utf16(fp, '\"');
|
|
put_utf16(fp, ' ');
|
|
put_utf16(fp, '=');
|
|
put_utf16(fp, ' ');
|
|
put_utf16(fp, '\"');
|
|
|
|
ptr = m->string->value;
|
|
while ((ch = get_utf8(ptr)) != 0)
|
|
switch (ch)
|
|
{
|
|
case '\n' :
|
|
put_utf16(fp, '\\');
|
|
put_utf16(fp, 'n');
|
|
break;
|
|
case '\\' :
|
|
put_utf16(fp, '\\');
|
|
put_utf16(fp, '\\');
|
|
break;
|
|
case '\"' :
|
|
put_utf16(fp, '\\');
|
|
put_utf16(fp, '\"');
|
|
break;
|
|
default :
|
|
put_utf16(fp, ch);
|
|
break;
|
|
}
|
|
|
|
put_utf16(fp, '\"');
|
|
put_utf16(fp, ';');
|
|
put_utf16(fp, '\n');
|
|
}
|
|
else
|
|
{
|
|
cupsFilePuts(fp, "msgid \"");
|
|
for (ptr = m->id->value; *ptr; ptr ++)
|
|
switch (*ptr)
|
|
{
|
|
case '\n' :
|
|
cupsFilePuts(fp, "\\n");
|
|
break;
|
|
case '\\' :
|
|
cupsFilePuts(fp, "\\\\");
|
|
break;
|
|
case '\"' :
|
|
cupsFilePuts(fp, "\\\"");
|
|
break;
|
|
default :
|
|
cupsFilePutChar(fp, *ptr);
|
|
break;
|
|
}
|
|
cupsFilePuts(fp, "\"\n");
|
|
|
|
cupsFilePuts(fp, "msgstr \"");
|
|
for (ptr = m->string->value; *ptr; ptr ++)
|
|
switch (*ptr)
|
|
{
|
|
case '\n' :
|
|
cupsFilePuts(fp, "\\n");
|
|
break;
|
|
case '\\' :
|
|
cupsFilePuts(fp, "\\\\");
|
|
break;
|
|
case '\"' :
|
|
cupsFilePuts(fp, "\\\"");
|
|
break;
|
|
default :
|
|
cupsFilePutChar(fp, *ptr);
|
|
break;
|
|
}
|
|
cupsFilePuts(fp, "\"\n");
|
|
|
|
cupsFilePutChar(fp, '\n');
|
|
}
|
|
}
|
|
|
|
cupsFileClose(fp);
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
|
|
//
|
|
// 'apple_add_message()' - Add a message from a localization dictionary.
|
|
//
|
|
|
|
static void
|
|
apple_add_message(CFStringRef key, // I - Localization key
|
|
CFStringRef val, // I - Localized value
|
|
ppdcCatalog *c) // I - Message catalog
|
|
{
|
|
char id[1024], // Message id
|
|
str[1024]; // Localized message
|
|
|
|
|
|
if (CFStringGetCString(key, id, sizeof(id), kCFStringEncodingUTF8) &&
|
|
CFStringGetCString(val, str, sizeof(str), kCFStringEncodingUTF8))
|
|
c->add_message(id, str);
|
|
}
|
|
#endif /* __APPLE__ && CUPS_BUNDLEDIR */
|
|
|
|
|
|
//
|
|
// 'get_utf8()' - Get a UTF-8 character.
|
|
//
|
|
|
|
static int // O - Unicode character or 0 on EOF
|
|
get_utf8(char *&ptr) // IO - Pointer to character
|
|
{
|
|
int ch; // Current character
|
|
|
|
|
|
if ((ch = *ptr++ & 255) < 0xc0)
|
|
return (ch);
|
|
|
|
if ((ch & 0xe0) == 0xc0)
|
|
{
|
|
// Two-byte UTF-8...
|
|
if ((*ptr & 0xc0) != 0x80)
|
|
return (0);
|
|
|
|
ch = ((ch & 0x1f) << 6) | (*ptr++ & 0x3f);
|
|
}
|
|
else if ((ch & 0xf0) == 0xe0)
|
|
{
|
|
// Three-byte UTF-8...
|
|
if ((*ptr & 0xc0) != 0x80)
|
|
return (0);
|
|
|
|
ch = ((ch & 0x0f) << 6) | (*ptr++ & 0x3f);
|
|
|
|
if ((*ptr & 0xc0) != 0x80)
|
|
return (0);
|
|
|
|
ch = (ch << 6) | (*ptr++ & 0x3f);
|
|
}
|
|
else if ((ch & 0xf8) == 0xf0)
|
|
{
|
|
// Four-byte UTF-8...
|
|
if ((*ptr & 0xc0) != 0x80)
|
|
return (0);
|
|
|
|
ch = ((ch & 0x07) << 6) | (*ptr++ & 0x3f);
|
|
|
|
if ((*ptr & 0xc0) != 0x80)
|
|
return (0);
|
|
|
|
ch = (ch << 6) | (*ptr++ & 0x3f);
|
|
|
|
if ((*ptr & 0xc0) != 0x80)
|
|
return (0);
|
|
|
|
ch = (ch << 6) | (*ptr++ & 0x3f);
|
|
}
|
|
|
|
return (ch);
|
|
}
|
|
|
|
|
|
//
|
|
// 'get_utf16()' - Get a UTF-16 character...
|
|
//
|
|
|
|
static int // O - Unicode character or 0 on EOF
|
|
get_utf16(cups_file_t *fp, // I - File to read from
|
|
ppdc_cs_t &cs) // IO - Character set of file
|
|
{
|
|
int ch; // Current character
|
|
unsigned char buffer[3]; // Bytes
|
|
|
|
|
|
if (cs == PPDC_CS_AUTO)
|
|
{
|
|
// Get byte-order-mark, if present...
|
|
if (cupsFileRead(fp, (char *)buffer, 2) != 2)
|
|
return (0);
|
|
|
|
if (buffer[0] == 0xfe && buffer[1] == 0xff)
|
|
{
|
|
// Big-endian UTF-16...
|
|
cs = PPDC_CS_UTF16BE;
|
|
|
|
if (cupsFileRead(fp, (char *)buffer, 2) != 2)
|
|
return (0);
|
|
}
|
|
else if (buffer[0] == 0xff && buffer[1] == 0xfe)
|
|
{
|
|
// Little-endian UTF-16...
|
|
cs = PPDC_CS_UTF16LE;
|
|
|
|
if (cupsFileRead(fp, (char *)buffer, 2) != 2)
|
|
return (0);
|
|
}
|
|
else if (buffer[0] == 0x00 && buffer[1] != 0x00)
|
|
{
|
|
// No BOM, assume big-endian UTF-16...
|
|
cs = PPDC_CS_UTF16BE;
|
|
}
|
|
else if (buffer[0] != 0x00 && buffer[1] == 0x00)
|
|
{
|
|
// No BOM, assume little-endian UTF-16...
|
|
cs = PPDC_CS_UTF16LE;
|
|
}
|
|
else
|
|
{
|
|
// No BOM, assume UTF-8...
|
|
cs = PPDC_CS_UTF8;
|
|
|
|
cupsFileRewind(fp);
|
|
}
|
|
}
|
|
else if (cs != PPDC_CS_UTF8)
|
|
{
|
|
if (cupsFileRead(fp, (char *)buffer, 2) != 2)
|
|
return (0);
|
|
}
|
|
|
|
if (cs == PPDC_CS_UTF8)
|
|
{
|
|
// UTF-8 character...
|
|
if ((ch = cupsFileGetChar(fp)) < 0)
|
|
return (0);
|
|
|
|
if ((ch & 0xe0) == 0xc0)
|
|
{
|
|
// Two-byte UTF-8...
|
|
if (cupsFileRead(fp, (char *)buffer, 1) != 1)
|
|
return (0);
|
|
|
|
if ((buffer[0] & 0xc0) != 0x80)
|
|
return (0);
|
|
|
|
ch = ((ch & 0x1f) << 6) | (buffer[0] & 0x3f);
|
|
}
|
|
else if ((ch & 0xf0) == 0xe0)
|
|
{
|
|
// Three-byte UTF-8...
|
|
if (cupsFileRead(fp, (char *)buffer, 2) != 2)
|
|
return (0);
|
|
|
|
if ((buffer[0] & 0xc0) != 0x80 ||
|
|
(buffer[1] & 0xc0) != 0x80)
|
|
return (0);
|
|
|
|
ch = ((((ch & 0x0f) << 6) | (buffer[0] & 0x3f)) << 6) |
|
|
(buffer[1] & 0x3f);
|
|
}
|
|
else if ((ch & 0xf8) == 0xf0)
|
|
{
|
|
// Four-byte UTF-8...
|
|
if (cupsFileRead(fp, (char *)buffer, 3) != 3)
|
|
return (0);
|
|
|
|
if ((buffer[0] & 0xc0) != 0x80 ||
|
|
(buffer[1] & 0xc0) != 0x80 ||
|
|
(buffer[2] & 0xc0) != 0x80)
|
|
return (0);
|
|
|
|
ch = ((((((ch & 0x07) << 6) | (buffer[0] & 0x3f)) << 6) |
|
|
(buffer[1] & 0x3f)) << 6) | (buffer[2] & 0x3f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// UTF-16 character...
|
|
if (cs == PPDC_CS_UTF16BE)
|
|
ch = (buffer[0] << 8) | buffer[1];
|
|
else
|
|
ch = (buffer[1] << 8) | buffer[0];
|
|
|
|
if (ch >= 0xd800 && ch <= 0xdbff)
|
|
{
|
|
// Handle multi-word encoding...
|
|
int lch;
|
|
|
|
if (cupsFileRead(fp, (char *)buffer, 2) != 2)
|
|
return (0);
|
|
|
|
if (cs == PPDC_CS_UTF16BE)
|
|
lch = (buffer[0] << 8) | buffer[1];
|
|
else
|
|
lch = (buffer[1] << 8) | buffer[0];
|
|
|
|
if (lch < 0xdc00 || lch >= 0xdfff)
|
|
return (0);
|
|
|
|
ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000;
|
|
}
|
|
}
|
|
|
|
return (ch);
|
|
}
|
|
|
|
|
|
//
|
|
// 'put_utf8()' - Add a UTF-8 character to a string.
|
|
//
|
|
|
|
static int // O - 0 on success, -1 on failure
|
|
put_utf8(int ch, // I - Unicode character
|
|
char *&ptr, // IO - String pointer
|
|
char *end) // I - End of buffer
|
|
{
|
|
if (ch < 0x80)
|
|
{
|
|
// One-byte ASCII...
|
|
if (ptr >= end)
|
|
return (-1);
|
|
|
|
*ptr++ = (char)ch;
|
|
}
|
|
else if (ch < 0x800)
|
|
{
|
|
// Two-byte UTF-8...
|
|
if ((ptr + 1) >= end)
|
|
return (-1);
|
|
|
|
*ptr++ = (char)(0xc0 | (ch >> 6));
|
|
*ptr++ = (char)(0x80 | (ch & 0x3f));
|
|
}
|
|
else if (ch < 0x10000)
|
|
{
|
|
// Three-byte UTF-8...
|
|
if ((ptr + 2) >= end)
|
|
return (-1);
|
|
|
|
*ptr++ = (char)(0xe0 | (ch >> 12));
|
|
*ptr++ = (char)(0x80 | ((ch >> 6) & 0x3f));
|
|
*ptr++ = (char)(0x80 | (ch & 0x3f));
|
|
}
|
|
else
|
|
{
|
|
// Four-byte UTF-8...
|
|
if ((ptr + 3) >= end)
|
|
return (-1);
|
|
|
|
*ptr++ = (char)(0xf0 | (ch >> 18));
|
|
*ptr++ = (char)(0x80 | ((ch >> 12) & 0x3f));
|
|
*ptr++ = (char)(0x80 | ((ch >> 6) & 0x3f));
|
|
*ptr++ = (char)(0x80 | (ch & 0x3f));
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
//
|
|
// 'put_utf16()' - Write a UTF-16 character to a file.
|
|
//
|
|
|
|
static int // O - 0 on success, -1 on failure
|
|
put_utf16(cups_file_t *fp, // I - File to write to
|
|
int ch) // I - Unicode character
|
|
{
|
|
unsigned char buffer[4]; // Output buffer
|
|
|
|
|
|
if (ch < 0x10000)
|
|
{
|
|
// One-word UTF-16 big-endian...
|
|
buffer[0] = (unsigned char)(ch >> 8);
|
|
buffer[1] = (unsigned char)ch;
|
|
|
|
if (cupsFileWrite(fp, (char *)buffer, 2) == 2)
|
|
return (0);
|
|
}
|
|
else
|
|
{
|
|
// Two-word UTF-16 big-endian...
|
|
ch -= 0x10000;
|
|
|
|
buffer[0] = (unsigned char)(0xd8 | (ch >> 18));
|
|
buffer[1] = (unsigned char)(ch >> 10);
|
|
buffer[2] = (unsigned char)(0xdc | ((ch >> 8) & 0x03));
|
|
buffer[3] = (unsigned char)ch;
|
|
|
|
if (cupsFileWrite(fp, (char *)buffer, 4) == 4)
|
|
return (0);
|
|
}
|
|
|
|
return (-1);
|
|
}
|