Codebase list i3-gaps / e0eccab libi3 / ucs2_conversion.c
e0eccab

Tree @e0eccab (Download .tar.gz)

ucs2_conversion.c @e0eccabraw · history · blame

/*
 * vim:ts=4:sw=4:expandtab
 *
 * i3 - an improved dynamic tiling window manager
 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
 *
 */
#include "libi3.h"

#include <err.h>
#include <iconv.h>
#include <stdlib.h>
#include <string.h>

static iconv_t utf8_conversion_descriptor = (iconv_t)-1;
static iconv_t ucs2_conversion_descriptor = (iconv_t)-1;

/*
 * Converts the given string to UTF-8 from UCS-2 big endian. The return value
 * must be freed after use.
 *
 */
char *convert_ucs2_to_utf8(xcb_char2b_t *text, size_t num_glyphs) {
    /* Allocate the output buffer (UTF-8 is at most 4 bytes per glyph) */
    size_t buffer_size = num_glyphs * 4 + 1;
    char *buffer = scalloc(buffer_size, 1);

    /* We need to use an additional pointer, because iconv() modifies it */
    char *output = buffer;
    size_t output_size = buffer_size - 1;

    if (utf8_conversion_descriptor == (iconv_t)-1) {
        /* Get a new conversion descriptor */
        utf8_conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
        if (utf8_conversion_descriptor == (iconv_t)-1)
            err(EXIT_FAILURE, "Error opening the conversion context");
    } else {
        /* Reset the existing conversion descriptor */
        iconv(utf8_conversion_descriptor, NULL, NULL, NULL, NULL);
    }

    /* Do the conversion */
    size_t input_len = num_glyphs * sizeof(xcb_char2b_t);
    size_t rc = iconv(utf8_conversion_descriptor, (char **)&text,
                      &input_len, &output, &output_size);
    if (rc == (size_t)-1) {
        perror("Converting to UTF-8 failed");
        free(buffer);
        return NULL;
    }

    return buffer;
}

/*
 * Converts the given string to UCS-2 big endian for use with
 * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
 * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
 * returned. It has to be freed when done.
 *
 */
xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen) {
    /* Calculate the input buffer size (UTF-8 is strlen-safe) */
    size_t input_size = strlen(input);

    /* Calculate the output buffer size and allocate the buffer */
    size_t buffer_size = input_size * sizeof(xcb_char2b_t);
    xcb_char2b_t *buffer = smalloc(buffer_size);

    /* We need to use an additional pointer, because iconv() modifies it */
    size_t output_bytes_left = buffer_size;
    xcb_char2b_t *output = buffer;

    if (ucs2_conversion_descriptor == (iconv_t)-1) {
        /* Get a new conversion descriptor. //IGNORE is a GNU suffix that makes
         * iconv to silently discard characters that cannot be represented in
         * the target character set. */
        ucs2_conversion_descriptor = iconv_open("UCS-2BE//IGNORE", "UTF-8");
        if (ucs2_conversion_descriptor == (iconv_t)-1) {
            ucs2_conversion_descriptor = iconv_open("UCS-2BE", "UTF-8");
        }
        if (ucs2_conversion_descriptor == (iconv_t)-1) {
            err(EXIT_FAILURE, "Error opening the conversion context");
        }
    } else {
        /* Reset the existing conversion descriptor */
        iconv(ucs2_conversion_descriptor, NULL, NULL, NULL, NULL);
    }

    /* Do the conversion */
    size_t rc = iconv(ucs2_conversion_descriptor, &input, &input_size,
                      (char **)&output, &output_bytes_left);
    if (rc == (size_t)-1) {
        /* Conversion will only be partial. */
        perror("Converting to UCS-2 failed");
    }

    /* If no bytes where converted, this is equivalent to freeing buffer. */
    buffer_size -= output_bytes_left;
    buffer = srealloc(buffer, buffer_size);

    /* Return the resulting string's length */
    if (real_strlen != NULL) {
        *real_strlen = buffer_size / sizeof(xcb_char2b_t);
    }

    return buffer;
}