blob: 1be387de8a4fd757d8d5da4850ecf5ba4749c5ae [file]
/* See LICENSE file for copyright and license details. */
#include <stdint.h>
#include "../grapheme.h"
#include "../gen/case.h"
#include "util.h"
static inline enum case_property
get_case_property(uint_least32_t cp)
{
if (likely(cp <= 0x10FFFF)) {
return (enum case_property)
case_minor[case_major[cp >> 8] + (cp & 0xff)];
} else {
return CASE_PROP_OTHER;
}
}
static inline int_least32_t
get_case_offset(uint_least32_t cp, const uint_least16_t *major,
const int_least32_t *minor)
{
if (likely(cp <= 0x10FFFF)) {
/*
* this value might be larger than or equal to 0x110000
* for the special-case-mapping. This needs to be handled
* separately
*/
return minor[major[cp >> 8] + (cp & 0xff)];
} else {
return 0;
}
}
static inline size_t
to_case(const void *src, size_t srclen, void *dest, size_t destlen,
size_t srcnumprocess, uint_least8_t final_sigma_level,
size_t (*get_codepoint)(const void *, size_t, size_t, uint_least32_t *),
size_t (*set_codepoint)(uint_least32_t, void *, size_t, size_t),
const uint_least16_t *major, const int_least32_t *minor,
const struct special_case *sc)
{
enum case_property prop;
size_t srcoff, destoff, res, tmp, off, i;
uint_least32_t cp, tmp_cp;
int_least32_t map;
for (srcoff = 0, destoff = 0; srcoff < srcnumprocess; srcoff += res) {
/* read in next source codepoint */
res = get_codepoint((const char *)src, srclen, srcoff, &cp);
if (sc == lower_special) {
/*
* For the special Final_Sigma-rule (see SpecialCasing.txt),
* which is the only non-localized case-dependent rule,
* we apply a different mapping when a sigma is at the
* end of a word.
*
* Before: cased case-ignorable*
* After: not(case-ignorable* cased)
*
* We check the after-condition on demand, but the before-
* condition is best checked using the "level"-heuristic
* also used in the sentence and line breaking-implementations.
*/
if (cp == UINT32_C(0x03A3) && /* GREEK CAPITAL LETTER SIGMA */
(final_sigma_level == 1 ||
final_sigma_level == 2)) {
/*
* check succeeding characters by first skipping
* all case-ignorable characters and then checking
* if the succeeding character is cased, invalidating
* the after-condition
*/
for (tmp = srcoff + res, prop = NUM_CASE_PROPS; tmp < srclen; ) {
tmp += get_codepoint(src, srclen, tmp, &tmp_cp);
prop = get_case_property(tmp_cp);
if (prop != CASE_PROP_CASE_IGNORABLE &&
prop != CASE_PROP_BOTH_CASED_CASE_IGNORABLE) {
break;
}
}
/*
* Now prop is something other than case-ignorable.
* If it is something other than cased, we know
* that the after-condition holds
*/
if (prop != CASE_PROP_CASED &&
prop != CASE_PROP_BOTH_CASED_CASE_IGNORABLE) {
/*
* write GREEK SMALL LETTER FINAL SIGMA to
* destination
*/
destoff += set_codepoint(UINT32_C(0x03C2),
dest,
destlen,
destoff);
/* reset Final_Sigma-state and continue */
final_sigma_level = 0;
continue;
}
}
/* update state */
prop = get_case_property(cp);
if ((final_sigma_level == 0 ||
final_sigma_level == 1) &&
(prop == CASE_PROP_CASED ||
prop == CASE_PROP_BOTH_CASED_CASE_IGNORABLE)) {
/* sequence has begun */
final_sigma_level = 1;
} else if ((final_sigma_level == 1 ||
final_sigma_level == 2) &&
(prop == CASE_PROP_CASE_IGNORABLE ||
prop == CASE_PROP_BOTH_CASED_CASE_IGNORABLE)) {
/* case-ignorable sequence begins or continued */
final_sigma_level = 2;
} else {
/* sequence broke */
final_sigma_level = 0;
}
}
/* get and handle case mapping */
if (unlikely((map = get_case_offset(cp, major, minor)) >=
INT32_C(0x110000))) {
/* we have a special case and the offset in the sc-array
* is the difference to 0x110000*/
off = (uint_least32_t)map - UINT32_C(0x110000);
for (i = 0; i < sc[off].cplen; i++) {
if (likely(destoff < destlen)) {
/*
* write special mapping to destination
*/
destoff += set_codepoint(sc[off].cp[i],
dest,
destlen,
destoff);
} else {
/*
* further increase destoff to indicate
* how much buffer space we need
*/
destoff += set_codepoint(sc[off].cp[i],
NULL, 0, 0);
}
}
} else {
/* we have a simple mapping */
if (likely(destoff < destlen)) {
destoff += set_codepoint((uint_least32_t)((int_least32_t)cp + map),
dest, destlen, destoff);
} else {
destoff += set_codepoint((uint_least32_t)((int_least32_t)cp + map),
NULL, 0, 0);
}
}
}
if (set_codepoint == set_codepoint_utf8 && destlen > 0) {
/*
* NUL-terminate destination to always ensure NUL-termination,
* unless in check mode.
* Just like with snprintf() a return value >= destlen indicates
* truncation.
*/
((char *)dest)[(destoff < destlen) ? destoff : (destlen - 1)] = '\0';
}
return destoff;
}
static inline size_t
to_titlecase(const void *src, size_t srclen, void *dest, size_t destlen,
size_t (*get_codepoint)(const void *, size_t, size_t, uint_least32_t *),
size_t (*set_codepoint)(uint_least32_t, void *, size_t, size_t))
{
enum case_property prop;
size_t next_wb, srcoff, destoff, res;
uint_least32_t cp;
for (srcoff = destoff = 0; ; ) {
if (get_codepoint == get_codepoint_utf8) {
if ((next_wb = grapheme_next_word_break_utf8((const char *)src + srcoff,
srclen - srcoff)) == 0) {
/* we consumed all of the string */
break;
}
} else {
if ((next_wb = grapheme_next_word_break((const uint_least32_t *)src + srcoff,
srclen - srcoff)) == 0) {
/* we consumed all of the string */
break;
}
}
for (; next_wb > 0 && srcoff < srclen; next_wb -= res, srcoff += res) {
/* check if we have a cased character */
res = get_codepoint(src, srclen, srcoff, &cp);
prop = get_case_property(cp);
if (prop == CASE_PROP_CASED ||
prop == CASE_PROP_BOTH_CASED_CASE_IGNORABLE) {
break;
} else {
/* write the data to the output verbatim, it if permits */
destoff += set_codepoint_utf8(cp, dest, destlen, destoff);
}
}
if (next_wb > 0) {
/* get character length */
res = get_codepoint(src, srclen, srcoff, &cp);
/* we have a cased character at srcoff, map it to titlecase */
if (get_codepoint == get_codepoint_utf8) {
destoff += to_case((const char *)src + srcoff,
srclen - srcoff,
(char *)dest + destoff,
(destoff < destlen) ? (destlen - destoff) : 0,
res, 0,
get_codepoint_utf8,
set_codepoint_utf8, title_major,
title_minor, title_special);
} else {
destoff += to_case((const uint_least32_t *)src + srcoff,
srclen - srcoff,
(uint_least32_t *)dest + destoff,
(destoff < destlen) ? (destlen - destoff) : 0,
res, 0,
get_codepoint,
set_codepoint, title_major,
title_minor, title_special);
}
/* we consumed a character */
srcoff += res;
next_wb -= res;
}
/* cast the rest of the codepoints in the word to lowercase */
if (get_codepoint == get_codepoint_utf8) {
destoff += to_case((const char *)src + srcoff,
srclen - srcoff,
(char *)dest + destoff,
(destoff < destlen) ? (destlen - destoff) : 0,
next_wb, 1,
get_codepoint_utf8,
set_codepoint_utf8, lower_major,
lower_minor, lower_special);
} else {
destoff += to_case((const uint_least32_t *)src + srcoff,
srclen - srcoff,
(uint_least32_t *)dest + destoff,
(destoff < destlen) ? (destlen - destoff) : 0,
next_wb, 1,
get_codepoint,
set_codepoint, lower_major,
lower_minor, lower_special);
}
srcoff += next_wb;
}
if (set_codepoint == set_codepoint_utf8) {
/*
* NUL-terminate destination to always ensure NUL-termination.
* Just like with snprintf() a return value >= destlen indicates
* truncation.
*/
((char *)dest)[(destoff < destlen) ? destoff : (destlen - 1)] = '\0';
}
return destoff;
}
size_t
grapheme_to_uppercase(const uint_least32_t *src, size_t srclen, uint_least32_t *dest, size_t destlen)
{
return to_case(src, srclen, dest, destlen, srclen, 0, get_codepoint, set_codepoint,
upper_major, upper_minor, upper_special);
}
size_t
grapheme_to_lowercase(const uint_least32_t *src, size_t srclen, uint_least32_t *dest, size_t destlen)
{
return to_case(src, srclen, dest, destlen, srclen, 0, get_codepoint, set_codepoint,
lower_major, lower_minor, lower_special);
}
size_t
grapheme_to_titlecase(const uint_least32_t *src, size_t srclen, uint_least32_t *dest, size_t destlen)
{
return to_titlecase(src, srclen, dest, destlen, get_codepoint,
set_codepoint);
}
size_t
grapheme_to_uppercase_utf8(const char *src, size_t srclen, char *dest, size_t destlen)
{
return to_case(src, srclen, dest, destlen, srclen, 0, get_codepoint_utf8, set_codepoint_utf8,
upper_major, upper_minor, upper_special);
}
size_t
grapheme_to_lowercase_utf8(const char *src, size_t srclen, char *dest, size_t destlen)
{
return to_case(src, srclen, dest, destlen, srclen, 0, get_codepoint_utf8, set_codepoint_utf8,
lower_major, lower_minor, lower_special);
}
size_t
grapheme_to_titlecase_utf8(const char *src, size_t srclen, char *dest, size_t destlen)
{
return to_titlecase(src, srclen, dest, destlen, get_codepoint_utf8,
set_codepoint_utf8);
}
static inline bool
is_case(const void *src, size_t srclen,
size_t srcnumprocess,
size_t (*get_codepoint)(const void *, size_t, size_t, uint_least32_t *),
const uint_least16_t *major, const int_least32_t *minor,
const struct special_case *sc, size_t *output)
{
size_t srcoff, new_srcoff, tmp, res, off, i;
uint_least32_t cp, tmp_cp;
int_least32_t map;
for (srcoff = 0; srcoff < srcnumprocess; srcoff = new_srcoff) {
/* read in next source codepoint */
new_srcoff = srcoff + get_codepoint(src, srclen, srcoff, &cp);
/* get and handle case mapping */
if (unlikely((map = get_case_offset(cp, major, minor)) >=
INT32_C(0x110000))) {
/* we have a special case and the offset in the sc-array
* is the difference to 0x110000*/
off = (uint_least32_t)map - UINT32_C(0x110000);
for (i = 0, tmp = srcoff; i < sc[off].cplen; i++, tmp += res) {
res = get_codepoint(src, srclen, srcoff, &tmp_cp);
if (tmp_cp != sc[off].cp[i]) {
/* we have a difference */
if (output) {
*output = tmp;
}
return false;
}
}
new_srcoff = tmp;
} else {
/* we have a simple mapping */
if (cp != (uint_least32_t)((int_least32_t)cp + map)) {
/* we have a difference */
if (output) {
*output = srcoff;
}
return false;
}
}
}
if (output) {
*output = srcoff;
}
return true;
}
static inline bool
is_titlecase(const void *src, size_t srclen,
size_t (*get_codepoint)(const void *, size_t, size_t, uint_least32_t *),
size_t *output)
{
enum case_property prop;
size_t next_wb, srcoff, res, tmp_output;
uint_least32_t cp;
for (srcoff = 0; ; ) {
if (get_codepoint == get_codepoint_utf8) {
if ((next_wb = grapheme_next_word_break_utf8((const char *)src + srcoff,
srclen - srcoff)) == 0) {
/* we consumed all of the string */
break;
}
} else {
if ((next_wb = grapheme_next_word_break((const uint_least32_t *)src + srcoff,
srclen - srcoff)) == 0) {
/* we consumed all of the string */
break;
}
}
for (; next_wb > 0 && srcoff < srclen; next_wb -= res, srcoff += res) {
/* check if we have a cased character */
res = get_codepoint(src, srclen, srcoff, &cp);
prop = get_case_property(cp);
if (prop == CASE_PROP_CASED ||
prop == CASE_PROP_BOTH_CASED_CASE_IGNORABLE) {
break;
}
}
if (next_wb > 0) {
/* get character length */
res = get_codepoint(src, srclen, srcoff, &cp);
/* we have a cased character at srcoff, check if it's titlecase */
if (get_codepoint == get_codepoint_utf8) {
if (!is_case((const char *)src + srcoff,
srclen - srcoff, res,
get_codepoint_utf8, title_major,
title_minor, title_special, &tmp_output)) {
if (output) {
*output = srcoff + tmp_output;
}
return false;
}
} else {
if (!is_case((const uint_least32_t *)src + srcoff,
srclen - srcoff, res,
get_codepoint, title_major,
title_minor, title_special, &tmp_output)) {
if (output) {
*output = srcoff + tmp_output;
}
return false;
}
}
/*
* we consumed a character (make sure to never
* underflow next_wb; this should not happen,
* but it's better to be sure)
*/
srcoff += res;
next_wb -= (res <= next_wb) ? res : next_wb;
}
/* check if the rest of the codepoints in the word are lowercase */
if (get_codepoint == get_codepoint_utf8) {
if (!is_case((const char *)src + srcoff,
srclen - srcoff, next_wb,
get_codepoint_utf8, lower_major,
lower_minor, lower_special, &tmp_output)) {
if (output) {
*output = srcoff + tmp_output;
}
return false;
}
} else {
if (!is_case((const uint_least32_t *)src + srcoff,
srclen - srcoff, next_wb,
get_codepoint, lower_major,
lower_minor, lower_special, &tmp_output)) {
if (output) {
*output = srcoff + tmp_output;
}
return false;
}
}
srcoff += next_wb;
}
if (output) {
*output = srcoff;
}
return true;
}
bool
grapheme_is_uppercase(const uint_least32_t *src, size_t srclen, size_t *caselen)
{
return is_case(src, srclen, srclen, get_codepoint,
upper_major, upper_minor, upper_special, caselen);
}
bool
grapheme_is_lowercase(const uint_least32_t *src, size_t srclen, size_t *caselen)
{
return is_case(src, srclen, srclen, get_codepoint,
lower_major, lower_minor, lower_special, caselen);
}
bool
grapheme_is_titlecase(const uint_least32_t *src, size_t srclen, size_t *caselen)
{
return is_titlecase(src, srclen, get_codepoint, caselen);
}
bool
grapheme_is_uppercase_utf8(const char *src, size_t srclen, size_t *caselen)
{
return is_case(src, srclen, srclen, get_codepoint_utf8,
upper_major, upper_minor, upper_special, caselen);
}
bool
grapheme_is_lowercase_utf8(const char *src, size_t srclen, size_t *caselen)
{
return is_case(src, srclen, srclen, get_codepoint_utf8,
lower_major, lower_minor, lower_special, caselen);
}
bool
grapheme_is_titlecase_utf8(const char *src, size_t srclen, size_t *caselen)
{
return is_titlecase(src, srclen, get_codepoint_utf8, caselen);
}