hackthelobby/libcaca/caca/string.c

633 lines
18 KiB
C

/*
* libcaca Colour ASCII-Art library
* Copyright © 2002—2018 Sam Hocevar <sam@hocevar.net>
* All Rights Reserved
*
* This library is free software. It comes without any warranty, to
* the extent permitted by applicable law. You can redistribute it
* and/or modify it under the terms of the Do What the Fuck You Want
* to Public License, Version 2, as published by Sam Hocevar. See
* http://www.wtfpl.net/ for more details.
*/
/*
* This file contains various canvas handling functions such as character
* and string drawing.
*/
#include "config.h"
#if !defined(__KERNEL__)
# include <stdio.h> /* BUFSIZ */
# include <string.h>
# include <stdlib.h>
# include <stdarg.h>
# if defined(HAVE_UNISTD_H)
# include <unistd.h>
# endif
# if defined(HAVE_SIGNAL_H)
# include <signal.h>
# endif
# if defined(HAVE_SYS_IOCTL_H)
# include <sys/ioctl.h>
# endif
#endif
#include "caca.h"
#include "caca_internals.h"
#if defined _WIN32 && defined __GNUC__ && __GNUC__ >= 3
# if !HAVE_VSNPRINTF_S
int vsnprintf_s(char *s, size_t n, size_t c,
const char *fmt, va_list ap) CACA_WEAK;
# endif
# if !HAVE_VSNPRINTF
int vsnprintf(char *s, size_t n, const char *fmt, va_list ap) CACA_WEAK;
# endif
#endif
/** \brief Set cursor position.
*
* Put the cursor at the given coordinates. Functions making use of the
* cursor will use the new values. Setting the cursor position outside the
* canvas is legal but the cursor will not be shown.
*
* This function never fails.
*
* \param cv A handle to the libcaca canvas.
* \param x X cursor coordinate.
* \param y Y cursor coordinate.
* \return This function always returns 0.
*/
int caca_gotoxy(caca_canvas_t *cv, int x, int y)
{
cv->frames[cv->frame].x = x;
cv->frames[cv->frame].y = y;
return 0;
}
/** \brief Get X cursor position.
*
* Retrieve the X coordinate of the cursor's position.
*
* This function never fails.
*
* \param cv A handle to the libcaca canvas.
* \return The cursor's X coordinate.
*/
int caca_wherex(caca_canvas_t const *cv)
{
return cv->frames[cv->frame].x;
}
/** \brief Get Y cursor position.
*
* Retrieve the Y coordinate of the cursor's position.
*
* This function never fails.
*
* \param cv A handle to the libcaca canvas.
* \return The cursor's Y coordinate.
*/
int caca_wherey(caca_canvas_t const *cv)
{
return cv->frames[cv->frame].y;
}
/** \brief Print an ASCII or Unicode character.
*
* Print an ASCII or Unicode character at the given coordinates, using
* the default foreground and background colour values.
*
* If the coordinates are outside the canvas boundaries, nothing is printed.
* If a fullwidth Unicode character gets overwritten, its remaining visible
* parts are replaced with spaces. If the canvas' boundaries would split the
* fullwidth character in two, a space is printed instead.
*
* The behaviour when printing non-printable characters or invalid UTF-32
* characters is undefined. To print a sequence of bytes forming an UTF-8
* character instead of an UTF-32 character, use the caca_put_str() function.
*
* This function returns the width of the printed character. If it is a
* fullwidth character, 2 is returned. Otherwise, 1 is returned.
*
* This function never fails.
*
* \param cv A handle to the libcaca canvas.
* \param x X coordinate.
* \param y Y coordinate.
* \param ch The character to print.
* \return The width of the printed character: 2 for a fullwidth character,
* 1 otherwise.
*/
int caca_put_char(caca_canvas_t *cv, int x, int y, uint32_t ch)
{
uint32_t *curchar, *curattr, attr;
int fullwidth, xmin, xmax, ret;
if(ch == CACA_MAGIC_FULLWIDTH)
return 1;
fullwidth = caca_utf32_is_fullwidth(ch);
ret = fullwidth ? 2 : 1;
if(x >= (int)cv->width || y < 0 || y >= (int)cv->height)
return ret;
if(x == -1 && fullwidth)
{
x = 0;
ch = ' ';
fullwidth = 0;
}
else if(x < 0)
return ret;
curchar = cv->chars + x + y * cv->width;
curattr = cv->attrs + x + y * cv->width;
attr = cv->curattr;
xmin = xmax = x;
/* When overwriting the right part of a fullwidth character,
* replace its left part with a space. */
if(x && curchar[0] == CACA_MAGIC_FULLWIDTH)
{
curchar[-1] = ' ';
xmin--;
}
if(fullwidth)
{
if(x + 1 == (int)cv->width)
ch = ' ';
else
{
xmax++;
/* When overwriting the left part of a fullwidth character,
* replace its right part with a space. */
if(x + 2 < (int)cv->width && curchar[2] == CACA_MAGIC_FULLWIDTH)
{
curchar[2] = ' ';
xmax++;
}
curchar[1] = CACA_MAGIC_FULLWIDTH;
curattr[1] = attr;
}
}
else
{
/* When overwriting the left part of a fullwidth character,
* replace its right part with a space. */
if(x + 1 != (int)cv->width && curchar[1] == CACA_MAGIC_FULLWIDTH)
{
curchar[1] = ' ';
xmax++;
}
}
/* Only add a dirty rectangle if we are pasting a different character
* or attribute at that place. This does not account for inconsistencies
* in the canvas, ie. if CACA_MAGIC_FULLWIDTH lies at illegal places,
* but it's the caller's responsibility not to corrupt the contents. */
if(!cv->dirty_disabled
&& (curchar[0] != ch || curattr[0] != attr))
caca_add_dirty_rect(cv, xmin, y, xmax - xmin + 1, 1);
curchar[0] = ch;
curattr[0] = attr;
return ret;
}
/** \brief Get the Unicode character at the given coordinates.
*
* Get the ASCII or Unicode value of the character at the given
* coordinates. If the value is less or equal to 127 (0x7f),
* the character can be printed as ASCII. Otherise, it must be handled
* as a UTF-32 value.
*
* If the coordinates are outside the canvas boundaries, a space (0x20)
* is returned.
*
* A special exception is when CACA_MAGIC_FULLWIDTH is returned. This
* value is guaranteed not to be a valid Unicode character, and indicates
* that the character at the left of the requested one is a fullwidth
* character.
*
* This function never fails.
*
* \param cv A handle to the libcaca canvas.
* \param x X coordinate.
* \param y Y coordinate.
* \return The Unicode character at the given coordinates.
*/
uint32_t caca_get_char(caca_canvas_t const *cv, int x, int y)
{
if(x < 0 || x >= (int)cv->width || y < 0 || y >= (int)cv->height)
return ' ';
return cv->chars[x + y * cv->width];
}
/** \brief Print a string.
*
* Print an UTF-8 string at the given coordinates, using the default
* foreground and background values. The coordinates may be outside the
* canvas boundaries (eg. a negative Y coordinate) and the string will
* be cropped accordingly if it is too long.
*
* See caca_put_char() for more information on how fullwidth characters
* are handled when overwriting each other or at the canvas' boundaries.
*
* This function returns the number of cells printed by the string. It is
* not the number of characters printed, because fullwidth characters
* account for two cells.
*
* This function never fails.
*
* \param cv A handle to the libcaca canvas.
* \param x X coordinate.
* \param y Y coordinate.
* \param s The string to print.
* \return The number of cells printed.
*/
int caca_put_str(caca_canvas_t *cv, int x, int y, char const *s)
{
size_t rd;
int len = 0;
if (y < 0 || y >= (int)cv->height || x >= (int)cv->width)
{
while (*s)
{
len += caca_utf32_is_fullwidth(caca_utf8_to_utf32(s, &rd)) ? 2 : 1;
s += rd ? rd : 1;
}
return len;
}
while (*s)
{
uint32_t ch = caca_utf8_to_utf32(s, &rd);
if (x + len >= -1 && x + len < (int)cv->width)
caca_put_char(cv, x + len, y, ch);
len += caca_utf32_is_fullwidth(ch) ? 2 : 1;
s += rd ? rd : 1;
}
return len;
}
/** \brief Print a formated string.
*
* Format a string at the given coordinates, using the default foreground
* and background values. The coordinates may be outside the canvas
* boundaries (eg. a negative Y coordinate) and the string will be cropped
* accordingly if it is too long. The syntax of the format string is the
* same as for the C printf() function.
*
* This function returns the number of cells printed by the string. It is
* not the number of characters printed, because fullwidth characters
* account for two cells.
*
* This function never fails.
*
* \param cv A handle to the libcaca canvas.
* \param x X coordinate.
* \param y Y coordinate.
* \param format The format string to print.
* \param ... Arguments to the format string.
* \return The number of cells printed.
*/
int caca_printf(caca_canvas_t *cv, int x, int y, char const *format, ...)
{
va_list args;
int ret;
va_start(args, format);
ret = caca_vprintf(cv, x, y, format, args);
va_end(args);
return ret;
}
/** \brief Print a formated string (va_list version).
*
* Format a string at the given coordinates, using the default foreground
* and background values. The coordinates may be outside the canvas
* boundaries (eg. a negative X coordinate) and the string will be cropped
* accordingly if it is too long. The syntax of the format string is the
* same as for the C vprintf() function.
*
* This function returns the number of cells printed by the string. It is
* not the number of characters printed, because fullwidth characters
* account for two cells.
*
* This function never fails.
*
* \param cv A handle to the libcaca canvas.
* \param x X coordinate.
* \param y Y coordinate.
* \param format The format string to print.
* \param args A va_list containting the arguments to the format string.
* \return The number of cells printed.
*/
int caca_vprintf(caca_canvas_t *cv, int x, int y, char const *format,
va_list args)
{
char tmp[BUFSIZ];
char *buf = tmp;
int bufsize = BUFSIZ, ret;
if(cv->width - x + 1 > BUFSIZ)
{
bufsize = cv->width - x + 1;
buf = malloc(bufsize);
}
#if defined(HAVE_VSNPRINTF_S)
vsnprintf_s(buf, bufsize, _TRUNCATE, format, args);
#elif defined(HAVE_VSNPRINTF)
vsnprintf(buf, bufsize, format, args);
#else
vsprintf(buf, format, args);
#endif
buf[bufsize - 1] = '\0';
ret = caca_put_str(cv, x, y, buf);
if(buf != tmp)
free(buf);
return ret;
}
/** \brief Clear the canvas.
*
* Clear the canvas using the current foreground and background colours.
*
* This function never fails.
*
* \param cv The canvas to clear.
* \return This function always returns 0.
*/
int caca_clear_canvas(caca_canvas_t *cv)
{
uint32_t attr = cv->curattr;
int n;
for(n = cv->width * cv->height; n--; )
{
cv->chars[n] = (uint32_t)' ';
cv->attrs[n] = attr;
}
if(!cv->dirty_disabled)
caca_add_dirty_rect(cv, 0, 0, cv->width, cv->height);
return 0;
}
/** \brief Set cursor handle.
*
* Set the canvas' handle. Blitting functions will use the handle value
* to put the canvas at the proper coordinates.
*
* This function never fails.
*
* \param cv A handle to the libcaca canvas.
* \param x X handle coordinate.
* \param y Y handle coordinate.
* \return This function always returns 0.
*/
int caca_set_canvas_handle(caca_canvas_t *cv, int x, int y)
{
cv->frames[cv->frame].handlex = x;
cv->frames[cv->frame].handley = y;
return 0;
}
/** \brief Get X handle position.
*
* Retrieve the X coordinate of the canvas' handle.
*
* This function never fails.
*
* \param cv A handle to the libcaca canvas.
* \return The canvas' handle's X coordinate.
*/
int caca_get_canvas_handle_x(caca_canvas_t const *cv)
{
return cv->frames[cv->frame].handlex;
}
/** \brief Get Y handle position.
*
* Retrieve the Y coordinate of the canvas' handle.
*
* This function never fails.
*
* \param cv A handle to the libcaca canvas.
* \return The canvas' handle's Y coordinate.
*/
int caca_get_canvas_handle_y(caca_canvas_t const *cv)
{
return cv->frames[cv->frame].handley;
}
/** \brief Blit a canvas onto another one.
*
* Blit a canvas onto another one at the given coordinates.
* An optional mask canvas can be used.
*
* If an error occurs, -1 is returned and \b errno is set accordingly:
* - \c EINVAL A mask was specified but the mask size and source canvas
* size do not match.
*
* \param dst The destination canvas.
* \param x X coordinate.
* \param y Y coordinate.
* \param src The source canvas.
* \param mask The mask canvas.
* \return 0 in case of success, -1 if an error occurred.
*/
int caca_blit(caca_canvas_t *dst, int x, int y,
caca_canvas_t const *src, caca_canvas_t const *mask)
{
int i, j, starti, startj, endi, endj, stride, bleed_left, bleed_right;
if(mask && (src->width != mask->width || src->height != mask->height))
{
seterrno(EINVAL);
return -1;
}
x -= src->frames[src->frame].handlex;
y -= src->frames[src->frame].handley;
starti = x < 0 ? -x : 0;
startj = y < 0 ? -y : 0;
endi = (x + src->width >= dst->width) ? dst->width - x : src->width;
endj = (y + src->height >= dst->height) ? dst->height - y : src->height;
stride = endi - starti;
if(starti > src->width || startj > src->height
|| starti >= endi || startj >= endj)
return 0;
bleed_left = bleed_right = 0;
for(j = startj; j < endj; j++)
{
int dstix = (j + y) * dst->width + starti + x;
int srcix = j * src->width + starti;
/* FIXME: we are ignoring the mask here */
if((starti + x) && dst->chars[dstix] == CACA_MAGIC_FULLWIDTH)
{
dst->chars[dstix - 1] = ' ';
bleed_left = 1;
}
if(endi + x < dst->width
&& dst->chars[dstix + stride] == CACA_MAGIC_FULLWIDTH)
{
dst->chars[dstix + stride] = ' ';
bleed_right = 1;
}
if(mask)
{
for(i = 0; i < stride; i++)
{
if(mask->chars[srcix + i] == (uint32_t)' ')
continue;
if(dst->chars[dstix + i] != src->chars[srcix + i] ||
dst->attrs[dstix + i] != src->attrs[srcix + i])
{
dst->chars[dstix + i] = src->chars[srcix + i];
dst->attrs[dstix + i] = src->attrs[srcix + i];
if(!dst->dirty_disabled)
caca_add_dirty_rect(dst, x + starti + i, y + j, 1, 1);
}
}
}
else
{
if(memcmp(dst->chars + dstix, src->chars + srcix, stride * 4) ||
memcmp(dst->attrs + dstix, src->attrs + srcix, stride * 4))
{
/* FIXME be more precise ? */
memcpy(dst->chars + dstix, src->chars + srcix, stride * 4);
memcpy(dst->attrs + dstix, src->attrs + srcix, stride * 4);
if(!dst->dirty_disabled)
caca_add_dirty_rect(dst, x + starti, y + j, stride, 1);
}
}
/* Fix split fullwidth chars */
if(src->chars[srcix] == CACA_MAGIC_FULLWIDTH)
dst->chars[dstix] = ' ';
if(endi < src->width && src->chars[endi] == CACA_MAGIC_FULLWIDTH)
dst->chars[dstix + stride - 1] = ' ';
}
return 0;
}
/** \brief Set a canvas' new boundaries.
*
* Set new boundaries for a canvas. This function can be used to crop a
* canvas, to expand it or for combinations of both actions. All frames
* are affected by this function.
*
* If an error occurs, -1 is returned and \b errno is set accordingly:
* - \c EINVAL Specified width or height is invalid.
* - \c EBUSY The canvas is in use by a display driver and cannot be resized.
* - \c ENOMEM Not enough memory for the requested canvas size. If this
* happens, the canvas handle becomes invalid and should not be used.
*
* \param cv The canvas to crop.
* \param x X coordinate of the top-left corner.
* \param y Y coordinate of the top-left corner.
* \param w The width of the cropped area.
* \param h The height of the cropped area.
* \return 0 in case of success, -1 if an error occurred.
*/
int caca_set_canvas_boundaries(caca_canvas_t *cv, int x, int y, int w, int h)
{
caca_canvas_t *new;
int f, saved_f, framecount;
if(cv->refcount)
{
seterrno(EBUSY);
return -1;
}
if(w < 0 || h < 0)
{
seterrno(EINVAL);
return -1;
}
new = caca_create_canvas(w, h);
framecount = caca_get_frame_count(cv);
saved_f = cv->frame;
for(f = 0; f < framecount; f++)
{
if(f)
caca_create_frame(new, framecount);
caca_set_frame(cv, f);
caca_set_frame(new, f);
caca_blit(new, -x, -y, cv, NULL);
free(cv->frames[f].chars);
free(cv->frames[f].attrs);
}
free(cv->frames);
cv->frames = new->frames;
free(new);
caca_set_frame(cv, saved_f);
_caca_load_frame_info(cv);
/* FIXME: this may be optimised somewhat */
if(!cv->dirty_disabled)
caca_add_dirty_rect(cv, 0, 0, cv->width, cv->height);
return 0;
}
/*
* Functions for the mingw32 runtime
*/
#if defined _WIN32 && defined __GNUC__ && __GNUC__ >= 3
# if !HAVE_VSNPRINTF_S
int vsnprintf_s(char *s, size_t n, size_t c, const char *fmt, va_list ap)
{
return vsnprintf(s, n, fmt, ap);
}
# endif
# if !HAVE_VSNPRINTF
int vsnprintf(char *s, size_t n, const char *fmt, va_list ap)
{
return 0;
}
# endif
#endif