mirror of
https://github.com/netsurf-browser/netsurf
synced 2024-12-19 10:42:36 +03:00
ebe1b05114
remove unecessary inclusion of desktop search header in content header which has knock on effect of not having ctype or string system headers dragged in unecessarily. Futher this highlighted use of ctype API where internal ascii processing ought to be used.
1683 lines
38 KiB
C
1683 lines
38 KiB
C
/*
|
|
* Copyright 2006 James Bursa <bursa@users.sourceforge.net>
|
|
* Copyright 2006 Adrian Lees <adrianl@users.sourceforge.net>
|
|
*
|
|
* This file is part of NetSurf, http://www.netsurf-browser.org/
|
|
*
|
|
* NetSurf is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; version 2 of the License.
|
|
*
|
|
* NetSurf is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
*
|
|
* plain text content handling implementation.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <parserutils/input/inputstream.h>
|
|
|
|
#include "utils/errors.h"
|
|
#include "utils/corestrings.h"
|
|
#include "utils/http.h"
|
|
#include "utils/log.h"
|
|
#include "utils/messages.h"
|
|
#include "utils/utils.h"
|
|
#include "utils/utf8.h"
|
|
#include "utils/nsoption.h"
|
|
#include "netsurf/content.h"
|
|
#include "netsurf/keypress.h"
|
|
#include "netsurf/browser_window.h"
|
|
#include "netsurf/plotters.h"
|
|
#include "netsurf/layout.h"
|
|
#include "content/content_protected.h"
|
|
#include "content/content_factory.h"
|
|
#include "content/hlcache.h"
|
|
#include "content/textsearch.h"
|
|
#include "content/handlers/css/utils.h"
|
|
#include "desktop/selection.h"
|
|
#include "desktop/gui_internal.h"
|
|
|
|
#include "text/textplain.h"
|
|
|
|
struct textplain_line {
|
|
size_t start;
|
|
size_t length;
|
|
};
|
|
|
|
/**
|
|
* plain text content
|
|
*/
|
|
typedef struct textplain_content {
|
|
struct content base;
|
|
|
|
lwc_string *encoding;
|
|
void *inputstream;
|
|
char *utf8_data;
|
|
size_t utf8_data_size;
|
|
size_t utf8_data_allocated;
|
|
unsigned long physical_line_count;
|
|
struct textplain_line *physical_line;
|
|
int formatted_width;
|
|
struct browser_window *bw;
|
|
|
|
struct selection *sel; /** Selection state */
|
|
|
|
} textplain_content;
|
|
|
|
|
|
#define CHUNK 32768 /* Must be a power of 2 */
|
|
#define MARGIN 4
|
|
|
|
#define TAB_WIDTH 8 /* must be power of 2 currently */
|
|
#define TEXT_SIZE 10 * PLOT_STYLE_SCALE /* Unscaled text size in pt */
|
|
|
|
static plot_font_style_t textplain_style = {
|
|
.family = PLOT_FONT_FAMILY_MONOSPACE,
|
|
.size = TEXT_SIZE,
|
|
.weight = 400,
|
|
.flags = FONTF_NONE,
|
|
.background = 0xffffff,
|
|
.foreground = 0x000000,
|
|
};
|
|
|
|
static int textplain_tab_width = 256; /* try for a sensible default */
|
|
|
|
static lwc_string *textplain_default_charset;
|
|
|
|
|
|
/**
|
|
* Clean up after the text content handler
|
|
*/
|
|
static void textplain_fini(void)
|
|
{
|
|
if (textplain_default_charset != NULL) {
|
|
lwc_string_unref(textplain_default_charset);
|
|
textplain_default_charset = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Work around feature in libparserutils
|
|
*
|
|
* if the client provides an encoding up front, but does not provide a
|
|
* charset detection callback, then libparserutils will replace the
|
|
* provided encoding with UTF-8. This breaks our input handling.
|
|
*
|
|
* Avoid this by providing a callback that does precisely nothing,
|
|
* thus preserving whatever charset information we decided on in
|
|
* textplain_create.
|
|
*/
|
|
static parserutils_error
|
|
textplain_charset_hack(const uint8_t *data,
|
|
size_t len,
|
|
uint16_t *mibenum,
|
|
uint32_t *source)
|
|
{
|
|
return PARSERUTILS_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* setup plain text render.
|
|
*
|
|
* \param[in] c content object.
|
|
* \param[in] encoding the encoding of the content.
|
|
* \return NSERROR_OK else appropriate error code.
|
|
*/
|
|
static nserror
|
|
textplain_create_internal(textplain_content *c, lwc_string *encoding)
|
|
{
|
|
char *utf8_data;
|
|
parserutils_inputstream *stream;
|
|
parserutils_error error;
|
|
|
|
textplain_style.size = (nsoption_int(font_size) * PLOT_STYLE_SCALE) / 10;
|
|
|
|
utf8_data = malloc(CHUNK);
|
|
if (utf8_data == NULL)
|
|
goto no_memory;
|
|
|
|
error = parserutils_inputstream_create(lwc_string_data(encoding), 0,
|
|
textplain_charset_hack, &stream);
|
|
if (error == PARSERUTILS_BADENCODING) {
|
|
/* Fall back to Windows-1252 */
|
|
error = parserutils_inputstream_create("Windows-1252", 0,
|
|
textplain_charset_hack, &stream);
|
|
}
|
|
if (error != PARSERUTILS_OK) {
|
|
free(utf8_data);
|
|
goto no_memory;
|
|
}
|
|
|
|
c->encoding = lwc_string_ref(encoding);
|
|
c->inputstream = stream;
|
|
c->utf8_data = utf8_data;
|
|
c->utf8_data_size = 0;
|
|
c->utf8_data_allocated = CHUNK;
|
|
c->physical_line = 0;
|
|
c->physical_line_count = 0;
|
|
c->formatted_width = 0;
|
|
c->bw = NULL;
|
|
c->sel = selection_create((struct content *)c);
|
|
|
|
return NSERROR_OK;
|
|
|
|
no_memory:
|
|
content_broadcast_error(&c->base, NSERROR_NOMEM, NULL);
|
|
|
|
return NSERROR_NOMEM;
|
|
}
|
|
|
|
|
|
/**
|
|
* Create a CONTENT_TEXTPLAIN.
|
|
*/
|
|
static nserror
|
|
textplain_create(const content_handler *handler,
|
|
lwc_string *imime_type,
|
|
const struct http_parameter *params,
|
|
llcache_handle *llcache,
|
|
const char *fallback_charset,
|
|
bool quirks,
|
|
struct content **c)
|
|
{
|
|
textplain_content *text;
|
|
nserror error;
|
|
lwc_string *encoding;
|
|
|
|
text = calloc(1, sizeof(textplain_content));
|
|
if (text == NULL) {
|
|
return NSERROR_NOMEM;
|
|
}
|
|
|
|
error = content__init(&text->base, handler, imime_type, params,
|
|
llcache, fallback_charset, quirks);
|
|
if (error != NSERROR_OK) {
|
|
free(text);
|
|
return error;
|
|
}
|
|
|
|
error = http_parameter_list_find_item(params, corestring_lwc_charset,
|
|
&encoding);
|
|
if (error != NSERROR_OK) {
|
|
encoding = lwc_string_ref(textplain_default_charset);
|
|
}
|
|
|
|
error = textplain_create_internal(text, encoding);
|
|
if (error != NSERROR_OK) {
|
|
lwc_string_unref(encoding);
|
|
free(text);
|
|
return error;
|
|
}
|
|
|
|
lwc_string_unref(encoding);
|
|
|
|
*c = (struct content *) text;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* copy utf8 encoded data
|
|
*/
|
|
static bool
|
|
textplain_copy_utf8_data(textplain_content *c, const uint8_t *buf, size_t len)
|
|
{
|
|
if (c->utf8_data_size + len >= c->utf8_data_allocated) {
|
|
/* Compute next multiple of chunk above the required space */
|
|
size_t allocated;
|
|
char *utf8_data;
|
|
|
|
allocated = (c->utf8_data_size + len + CHUNK - 1) & ~(CHUNK - 1);
|
|
utf8_data = realloc(c->utf8_data, allocated);
|
|
if (utf8_data == NULL)
|
|
return false;
|
|
|
|
c->utf8_data = utf8_data;
|
|
c->utf8_data_allocated = allocated;
|
|
}
|
|
|
|
memcpy(c->utf8_data + c->utf8_data_size, buf, len);
|
|
c->utf8_data_size += len;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* drain input
|
|
*/
|
|
static bool
|
|
textplain_drain_input(textplain_content *c,
|
|
parserutils_inputstream *stream,
|
|
parserutils_error terminator)
|
|
{
|
|
static const uint8_t *u_fffd = (const uint8_t *) "\xef\xbf\xfd";
|
|
const uint8_t *ch;
|
|
size_t chlen, offset = 0;
|
|
|
|
while (parserutils_inputstream_peek(stream, offset, &ch, &chlen) !=
|
|
terminator) {
|
|
/* Replace all instances of NUL with U+FFFD */
|
|
if (chlen == 1 && *ch == 0) {
|
|
if (offset > 0) {
|
|
/* Obtain pointer to start of input data */
|
|
parserutils_inputstream_peek(stream, 0,
|
|
&ch, &chlen);
|
|
/* Copy from it up to the start of the NUL */
|
|
if (textplain_copy_utf8_data(c, ch,
|
|
offset) == false)
|
|
return false;
|
|
}
|
|
|
|
/* Emit U+FFFD */
|
|
if (textplain_copy_utf8_data(c, u_fffd, 3) == false)
|
|
return false;
|
|
|
|
/* Advance inputstream past the NUL we just read */
|
|
parserutils_inputstream_advance(stream, offset + 1);
|
|
/* Reset the read offset */
|
|
offset = 0;
|
|
} else {
|
|
/* Accumulate input */
|
|
offset += chlen;
|
|
|
|
if (offset > CHUNK) {
|
|
/* Obtain pointer to start of input data */
|
|
parserutils_inputstream_peek(stream, 0,
|
|
&ch, &chlen);
|
|
|
|
/* Emit the data we've read */
|
|
if (textplain_copy_utf8_data(c, ch,
|
|
offset) == false)
|
|
return false;
|
|
|
|
/* Advance the inputstream */
|
|
parserutils_inputstream_advance(stream, offset);
|
|
/* Reset the read offset */
|
|
offset = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (offset > 0) {
|
|
/* Obtain pointer to start of input data */
|
|
parserutils_inputstream_peek(stream, 0, &ch, &chlen);
|
|
/* Emit any data remaining */
|
|
if (textplain_copy_utf8_data(c, ch, offset) == false)
|
|
return false;
|
|
|
|
/* Advance the inputstream past the data we've read */
|
|
parserutils_inputstream_advance(stream, offset);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Process data for CONTENT_TEXTPLAIN.
|
|
*/
|
|
static bool
|
|
textplain_process_data(struct content *c, const char *data, unsigned int size)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
parserutils_inputstream *stream = text->inputstream;
|
|
parserutils_error error;
|
|
|
|
error = parserutils_inputstream_append(stream,
|
|
(const uint8_t *) data, size);
|
|
if (error != PARSERUTILS_OK) {
|
|
goto no_memory;
|
|
}
|
|
|
|
if (textplain_drain_input(text, stream, PARSERUTILS_NEEDDATA) == false)
|
|
goto no_memory;
|
|
|
|
return true;
|
|
|
|
no_memory:
|
|
content_broadcast_error(c, NSERROR_NOMEM, NULL);
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Convert a CONTENT_TEXTPLAIN for display.
|
|
*/
|
|
static bool textplain_convert(struct content *c)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
parserutils_inputstream *stream = text->inputstream;
|
|
parserutils_error error;
|
|
|
|
error = parserutils_inputstream_append(stream, NULL, 0);
|
|
if (error != PARSERUTILS_OK) {
|
|
return false;
|
|
}
|
|
|
|
if (textplain_drain_input(text, stream, PARSERUTILS_EOF) == false)
|
|
return false;
|
|
|
|
parserutils_inputstream_destroy(stream);
|
|
text->inputstream = NULL;
|
|
|
|
content_set_ready(c);
|
|
content_set_done(c);
|
|
content_set_status(c, messages_get("Done"));
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Calculate the line height, in pixels
|
|
*
|
|
* \return Line height, in pixels
|
|
*/
|
|
static float textplain_line_height(void)
|
|
{
|
|
/* Size is in points, so convert to pixels.
|
|
* Then use a constant line height of 1.2 x font size.
|
|
*/
|
|
return FIXTOFLT(FDIV((FMUL(FLTTOFIX(1.2), FMUL(nscss_screen_dpi, INTTOFIX((textplain_style.size / PLOT_STYLE_SCALE))))), F_72));
|
|
}
|
|
|
|
|
|
/**
|
|
* Reformat a CONTENT_TEXTPLAIN to a new width.
|
|
*/
|
|
static void textplain_reformat(struct content *c, int width, int height)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
char *utf8_data = text->utf8_data;
|
|
size_t utf8_data_size = text->utf8_data_size;
|
|
unsigned long line_count = 0;
|
|
struct textplain_line *line = text->physical_line;
|
|
struct textplain_line *line1;
|
|
size_t i, space, col;
|
|
size_t columns = 80;
|
|
int character_width;
|
|
size_t line_start;
|
|
nserror res;
|
|
|
|
NSLOG(netsurf, INFO, "content %p w:%d h:%d", c, width, height);
|
|
|
|
/* compute available columns (assuming monospaced font) - use 8
|
|
* characters for better accuracy
|
|
*/
|
|
res = guit->layout->width(&textplain_style,
|
|
"ABCDEFGH", 8,
|
|
&character_width);
|
|
if (res != NSERROR_OK) {
|
|
return;
|
|
}
|
|
|
|
columns = (width - MARGIN - MARGIN) * 8 / character_width;
|
|
textplain_tab_width = (TAB_WIDTH * character_width) / 8;
|
|
|
|
text->formatted_width = width;
|
|
|
|
text->physical_line_count = 0;
|
|
|
|
if (!line) {
|
|
text->physical_line = line =
|
|
malloc(sizeof(struct textplain_line) * (1024 + 3));
|
|
if (!line)
|
|
goto no_memory;
|
|
}
|
|
|
|
line[line_count++].start = line_start = 0;
|
|
space = 0;
|
|
i = 0;
|
|
col = 0;
|
|
while (i < utf8_data_size) {
|
|
size_t csize; /* number of bytes in character */
|
|
uint32_t chr;
|
|
bool term;
|
|
size_t next_col;
|
|
parserutils_error perror;
|
|
|
|
perror = parserutils_charset_utf8_to_ucs4((const uint8_t *)utf8_data + i, utf8_data_size - i, &chr, &csize);
|
|
if (perror != PARSERUTILS_OK) {
|
|
chr = 0xfffd;
|
|
}
|
|
|
|
term = (chr == '\n' || chr == '\r');
|
|
|
|
next_col = col + 1;
|
|
|
|
if (chr == '\t') {
|
|
next_col = (next_col + TAB_WIDTH - 1) & ~(TAB_WIDTH - 1);
|
|
}
|
|
|
|
if (term || next_col >= columns) {
|
|
if (line_count % 1024 == 0) {
|
|
line1 = realloc(line,
|
|
sizeof(struct textplain_line) *
|
|
(line_count + 1024 + 3));
|
|
if (!line1)
|
|
goto no_memory;
|
|
text->physical_line = line = line1;
|
|
}
|
|
|
|
if (term) {
|
|
line[line_count-1].length = i - line_start;
|
|
|
|
/* skip second char of CR/LF or LF/CR pair */
|
|
if (i + 1 < utf8_data_size &&
|
|
utf8_data[i+1] != utf8_data[i] &&
|
|
(utf8_data[i+1] == '\n' ||
|
|
utf8_data[i+1] == '\r')) {
|
|
i++;
|
|
}
|
|
} else {
|
|
if (space) {
|
|
/* break at last space in line */
|
|
i = space;
|
|
line[line_count-1].length = (i + 1) - line_start;
|
|
} else
|
|
line[line_count-1].length = i - line_start;
|
|
}
|
|
|
|
line[line_count++].start = line_start = i + 1;
|
|
col = 0;
|
|
space = 0;
|
|
} else {
|
|
col++;
|
|
if (chr == ' ')
|
|
space = i;
|
|
}
|
|
i += csize;
|
|
}
|
|
line[line_count-1].length = i - line[line_count-1].start;
|
|
line[line_count].start = utf8_data_size;
|
|
|
|
text->physical_line_count = line_count;
|
|
c->width = width;
|
|
c->height = line_count * textplain_line_height() + MARGIN + MARGIN;
|
|
|
|
return;
|
|
|
|
no_memory:
|
|
NSLOG(netsurf, INFO, "out of memory (line_count %lu)", line_count);
|
|
return;
|
|
}
|
|
|
|
|
|
/**
|
|
* Destroy a CONTENT_TEXTPLAIN and free all resources it owns.
|
|
*/
|
|
|
|
static void textplain_destroy(struct content *c)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
|
|
lwc_string_unref(text->encoding);
|
|
|
|
if (text->inputstream != NULL) {
|
|
parserutils_inputstream_destroy(text->inputstream);
|
|
}
|
|
|
|
if (text->physical_line != NULL) {
|
|
free(text->physical_line);
|
|
}
|
|
|
|
if (text->utf8_data != NULL) {
|
|
free(text->utf8_data);
|
|
}
|
|
|
|
if (text->sel != NULL) {
|
|
selection_destroy(text->sel);
|
|
}
|
|
}
|
|
|
|
|
|
static nserror textplain_clone(const struct content *old, struct content **newc)
|
|
{
|
|
const textplain_content *old_text = (textplain_content *) old;
|
|
textplain_content *text;
|
|
nserror error;
|
|
const uint8_t *data;
|
|
size_t size;
|
|
|
|
text = calloc(1, sizeof(textplain_content));
|
|
if (text == NULL)
|
|
return NSERROR_NOMEM;
|
|
|
|
error = content__clone(old, &text->base);
|
|
if (error != NSERROR_OK) {
|
|
content_destroy(&text->base);
|
|
return error;
|
|
}
|
|
|
|
/* Simply replay create/process/convert */
|
|
error = textplain_create_internal(text, old_text->encoding);
|
|
if (error != NSERROR_OK) {
|
|
content_destroy(&text->base);
|
|
return error;
|
|
}
|
|
|
|
data = content__get_source_data(&text->base, &size);
|
|
if (size > 0) {
|
|
if (textplain_process_data(&text->base,
|
|
(const char *)data,
|
|
size) == false) {
|
|
content_destroy(&text->base);
|
|
return NSERROR_NOMEM;
|
|
}
|
|
}
|
|
|
|
if (old->status == CONTENT_STATUS_READY ||
|
|
old->status == CONTENT_STATUS_DONE) {
|
|
if (textplain_convert(&text->base) == false) {
|
|
content_destroy(&text->base);
|
|
return NSERROR_CLONE_FAILED;
|
|
}
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
static content_type textplain_content_type(void)
|
|
{
|
|
return CONTENT_TEXTPLAIN;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return byte offset within UTF8 textplain content.
|
|
*
|
|
* given the co-ordinates of a point within a textplain content. 'dir'
|
|
* specifies the direction in which to search (-1 = above-left, +1 =
|
|
* below-right) if the co-ordinates are not contained within a line.
|
|
*
|
|
* \param[in] c content of type CONTENT_TEXTPLAIN
|
|
* \param[in] x x ordinate of point
|
|
* \param[in] y y ordinate of point
|
|
* \param[in] dir direction of search if not within line
|
|
* \return byte offset of character containing (or nearest to) point
|
|
*/
|
|
static size_t
|
|
textplain_offset_from_coords(struct content *c, int x, int y, int dir)
|
|
{
|
|
textplain_content *textc = (textplain_content *) c;
|
|
float line_height = textplain_line_height();
|
|
struct textplain_line *line;
|
|
const char *text;
|
|
unsigned nlines;
|
|
size_t length;
|
|
int idx;
|
|
|
|
assert(c != NULL);
|
|
|
|
y = (int)((float)(y - MARGIN) / line_height);
|
|
x -= MARGIN;
|
|
|
|
nlines = textc->physical_line_count;
|
|
if (!nlines)
|
|
return 0;
|
|
|
|
if (y <= 0) y = 0;
|
|
else if ((unsigned)y >= nlines)
|
|
y = nlines - 1;
|
|
|
|
line = &textc->physical_line[y];
|
|
text = textc->utf8_data + line->start;
|
|
length = line->length;
|
|
idx = 0;
|
|
|
|
while (x > 0) {
|
|
size_t next_offset = 0;
|
|
int width = INT_MAX;
|
|
|
|
while (next_offset < length && text[next_offset] != '\t') {
|
|
next_offset = utf8_next(text, length, next_offset);
|
|
}
|
|
|
|
if (next_offset < length) {
|
|
guit->layout->width(&textplain_style,
|
|
text,
|
|
next_offset,
|
|
&width);
|
|
}
|
|
|
|
if (x <= width) {
|
|
int pixel_offset;
|
|
size_t char_offset;
|
|
|
|
guit->layout->position(&textplain_style,
|
|
text, next_offset, x,
|
|
&char_offset, &pixel_offset);
|
|
|
|
idx += char_offset;
|
|
break;
|
|
}
|
|
|
|
x -= width;
|
|
length -= next_offset;
|
|
text += next_offset;
|
|
idx += next_offset;
|
|
|
|
/* check if it's within the tab */
|
|
width = textplain_tab_width - (width % textplain_tab_width);
|
|
if (x <= width) break;
|
|
|
|
x -= width;
|
|
length--;
|
|
text++;
|
|
idx++;
|
|
}
|
|
|
|
return line->start + idx;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle mouse clicks and movements in a TEXTPLAIN content window.
|
|
*
|
|
* \param c content of type textplain
|
|
* \param bw browser window
|
|
* \param mouse mouse state on action
|
|
* \param x coordinate of mouse
|
|
* \param y coordinate of mouse
|
|
*/
|
|
static nserror
|
|
textplain_mouse_action(struct content *c,
|
|
struct browser_window *bw,
|
|
browser_mouse_state mouse,
|
|
int x, int y)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT;
|
|
union content_msg_data msg_data;
|
|
const char *status = 0;
|
|
size_t idx;
|
|
int dir = 0;
|
|
|
|
browser_window_set_drag_type(bw, DRAGGING_NONE, NULL);
|
|
|
|
idx = textplain_offset_from_coords(c, x, y, dir);
|
|
if (selection_click(text->sel, text->bw, mouse, idx)) {
|
|
|
|
if (selection_dragging(text->sel)) {
|
|
browser_window_set_drag_type(bw,
|
|
DRAGGING_SELECTION, NULL);
|
|
status = messages_get("Selecting");
|
|
}
|
|
|
|
} else {
|
|
if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) {
|
|
browser_window_page_drag_start(bw, x, y);
|
|
pointer = BROWSER_POINTER_MOVE;
|
|
}
|
|
}
|
|
|
|
msg_data.explicit_status_text = status;
|
|
content_broadcast(c, CONTENT_MSG_STATUS, &msg_data);
|
|
|
|
msg_data.pointer = pointer;
|
|
content_broadcast(c, CONTENT_MSG_POINTER, &msg_data);
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle mouse tracking (including drags) in a TEXTPLAIN content window.
|
|
*
|
|
* \param c content of type textplain
|
|
* \param bw browser window
|
|
* \param mouse state of mouse buttons and modifier keys
|
|
* \param x coordinate of mouse
|
|
* \param y coordinate of mouse
|
|
*/
|
|
static nserror
|
|
textplain_mouse_track(struct content *c,
|
|
struct browser_window *bw,
|
|
browser_mouse_state mouse,
|
|
int x, int y)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
|
|
if (browser_window_get_drag_type(bw) == DRAGGING_SELECTION && !mouse) {
|
|
int dir = -1;
|
|
size_t idx;
|
|
|
|
if (selection_dragging_start(text->sel))
|
|
dir = 1;
|
|
|
|
idx = textplain_offset_from_coords(c, x, y, dir);
|
|
selection_track(text->sel, mouse, idx);
|
|
|
|
browser_window_set_drag_type(bw, DRAGGING_NONE, NULL);
|
|
}
|
|
|
|
switch (browser_window_get_drag_type(bw)) {
|
|
|
|
case DRAGGING_SELECTION: {
|
|
int dir = -1;
|
|
size_t idx;
|
|
|
|
if (selection_dragging_start(text->sel)) dir = 1;
|
|
|
|
idx = textplain_offset_from_coords(c, x, y, dir);
|
|
selection_track(text->sel, mouse, idx);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
textplain_mouse_action(c, bw, mouse, x, y);
|
|
break;
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle keypresses.
|
|
*
|
|
* \param c content of type CONTENT_TEXTPLAIN
|
|
* \param key The UCS4 character codepoint
|
|
* \return true if key handled, false otherwise
|
|
*/
|
|
static bool textplain_keypress(struct content *c, uint32_t key)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
struct selection *sel = text->sel;
|
|
|
|
switch (key) {
|
|
case NS_KEY_COPY_SELECTION:
|
|
selection_copy_to_clipboard(sel);
|
|
return true;
|
|
|
|
case NS_KEY_CLEAR_SELECTION:
|
|
selection_clear(sel, true);
|
|
return true;
|
|
|
|
case NS_KEY_SELECT_ALL:
|
|
selection_select_all(sel);
|
|
return true;
|
|
|
|
case NS_KEY_ESCAPE:
|
|
/* if there's no selection, leave Escape for the caller */
|
|
return selection_clear(sel, true);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Redraw a text string with highlighting
|
|
* (for selection/search)
|
|
*
|
|
* \param utf8_text pointer to UTF-8 text string
|
|
* \param utf8_len length of string, in bytes
|
|
* \param offset byte offset within textual representation
|
|
* \param x x ordinate at which to plot text
|
|
* \param y y ordinate at which to plot text
|
|
* \param clip pointer to current clip rectangle
|
|
* \param height height of text string
|
|
* \param scale current display scale (1.0 = 100%)
|
|
* \param text Content being redrawn.
|
|
* \param sel Selection context
|
|
* \param search Search context
|
|
* \param ctx current redraw context
|
|
* \return true iff successful and redraw should proceed
|
|
*/
|
|
static bool
|
|
text_draw(const char *utf8_text,
|
|
size_t utf8_len,
|
|
size_t offset,
|
|
int x,
|
|
int y,
|
|
const struct rect *clip,
|
|
int height,
|
|
float scale,
|
|
textplain_content *text,
|
|
const struct selection *sel,
|
|
const struct redraw_context *ctx)
|
|
{
|
|
bool highlighted = false;
|
|
plot_font_style_t plot_fstyle;
|
|
nserror res;
|
|
|
|
/* Need scaled text size to pass to plotters */
|
|
plot_fstyle = textplain_style;
|
|
plot_fstyle.size *= scale;
|
|
|
|
/* is this box part of a selection? */
|
|
if (ctx->interactive == true) {
|
|
unsigned len = utf8_len;
|
|
unsigned start_idx;
|
|
unsigned end_idx;
|
|
|
|
/* first try the browser window's current selection */
|
|
if (selection_highlighted(sel,
|
|
offset,
|
|
offset + len,
|
|
&start_idx,
|
|
&end_idx)) {
|
|
highlighted = true;
|
|
}
|
|
|
|
/* what about the current search operation, if any? */
|
|
if (!highlighted &&
|
|
(text->base.textsearch.context != NULL) &&
|
|
content_textsearch_ishighlighted(text->base.textsearch.context,
|
|
offset,
|
|
offset + len,
|
|
&start_idx,
|
|
&end_idx)) {
|
|
highlighted = true;
|
|
}
|
|
|
|
/* \todo make search terms visible within selected text */
|
|
if (highlighted) {
|
|
struct rect r;
|
|
unsigned endtxt_idx = end_idx;
|
|
bool clip_changed = false;
|
|
bool text_visible = true;
|
|
int startx, endx;
|
|
plot_style_t pstyle_fill_hback = *plot_style_fill_white;
|
|
plot_font_style_t fstyle_hback = plot_fstyle;
|
|
|
|
if (end_idx > utf8_len) {
|
|
/* adjust for trailing space, not present in
|
|
* utf8_text
|
|
*/
|
|
assert(end_idx == utf8_len + 1);
|
|
endtxt_idx = utf8_len;
|
|
}
|
|
|
|
res = guit->layout->width(&textplain_style,
|
|
utf8_text,
|
|
start_idx,
|
|
&startx);
|
|
if (res != NSERROR_OK) {
|
|
startx = 0;
|
|
}
|
|
|
|
res = guit->layout->width(&textplain_style,
|
|
utf8_text,
|
|
endtxt_idx,
|
|
&endx);
|
|
if (res != NSERROR_OK) {
|
|
endx = 0;
|
|
}
|
|
|
|
if (scale != 1.0) {
|
|
startx *= scale;
|
|
endx *= scale;
|
|
}
|
|
|
|
/* draw any text preceding highlighted portion */
|
|
if (start_idx > 0) {
|
|
res = ctx->plot->text(ctx,
|
|
&plot_fstyle,
|
|
x,
|
|
y + (int)(height * 0.75 * scale),
|
|
utf8_text,
|
|
start_idx);
|
|
if (res != NSERROR_OK) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
pstyle_fill_hback.fill_colour = textplain_style.foreground;
|
|
|
|
/* highlighted portion */
|
|
r.x0 = x + startx;
|
|
r.y0 = y;
|
|
r.x1 = x + endx;
|
|
r.y1 = y + height * scale;
|
|
res = ctx->plot->rectangle(ctx, &pstyle_fill_hback, &r);
|
|
if (res != NSERROR_OK) {
|
|
return false;
|
|
}
|
|
|
|
if (start_idx > 0) {
|
|
int px0 = max(x + startx, clip->x0);
|
|
int px1 = min(x + endx, clip->x1);
|
|
|
|
if (px0 < px1) {
|
|
r.x0 = px0;
|
|
r.y0 = clip->y0;
|
|
r.x1 = px1;
|
|
r.y1 = clip->y1;
|
|
res = ctx->plot->clip(ctx, &r);
|
|
if (res != NSERROR_OK) {
|
|
return false;
|
|
}
|
|
|
|
clip_changed = true;
|
|
} else {
|
|
text_visible = false;
|
|
}
|
|
}
|
|
|
|
fstyle_hback.background =
|
|
pstyle_fill_hback.fill_colour;
|
|
fstyle_hback.foreground = colour_to_bw_furthest(
|
|
pstyle_fill_hback.fill_colour);
|
|
|
|
if (text_visible &&
|
|
(ctx->plot->text(ctx,
|
|
&fstyle_hback,
|
|
x,
|
|
y + (int)(height * 0.75 * scale),
|
|
utf8_text,
|
|
endtxt_idx) != NSERROR_OK)) {
|
|
return false;
|
|
}
|
|
|
|
/* draw any text succeeding highlighted portion */
|
|
if (endtxt_idx < utf8_len) {
|
|
int px0 = max(x + endx, clip->x0);
|
|
if (px0 < clip->x1) {
|
|
|
|
r.x0 = px0;
|
|
r.y0 = clip->y0;
|
|
r.x1 = clip->x1;
|
|
r.y1 = clip->y1;
|
|
res = ctx->plot->clip(ctx, &r);
|
|
if (res != NSERROR_OK) {
|
|
return false;
|
|
}
|
|
|
|
clip_changed = true;
|
|
|
|
res = ctx->plot->text(ctx,
|
|
&plot_fstyle,
|
|
x,
|
|
y + (int)(height * 0.75 * scale),
|
|
utf8_text,
|
|
utf8_len);
|
|
if (res != NSERROR_OK) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (clip_changed &&
|
|
(ctx->plot->clip(ctx, clip) != NSERROR_OK)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!highlighted) {
|
|
res = ctx->plot->text(ctx,
|
|
&plot_fstyle,
|
|
x,
|
|
y + (int) (height * 0.75 * scale),
|
|
utf8_text,
|
|
utf8_len);
|
|
if (res != NSERROR_OK) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Draw a CONTENT_TEXTPLAIN using the current set of plotters (plot).
|
|
*
|
|
* x, y, clip_[xy][01] are in target coordinates.
|
|
*
|
|
* \param c content of type CONTENT_TEXTPLAIN
|
|
* \param data redraw data for this content redraw
|
|
* \param clip current clip region
|
|
* \param ctx current redraw context
|
|
* \return true if successful, false otherwise
|
|
*/
|
|
static bool
|
|
textplain_redraw(struct content *c,
|
|
struct content_redraw_data *data,
|
|
const struct rect *clip,
|
|
const struct redraw_context *ctx)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
struct browser_window *bw = text->bw;
|
|
char *utf8_data = text->utf8_data;
|
|
long lineno;
|
|
int x = data->x;
|
|
int y = data->y;
|
|
unsigned long line_count = text->physical_line_count;
|
|
float line_height = textplain_line_height();
|
|
float scaled_line_height = line_height * data->scale;
|
|
long line0 = (clip->y0 - y * data->scale) / scaled_line_height - 1;
|
|
long line1 = (clip->y1 - y * data->scale) / scaled_line_height + 1;
|
|
struct textplain_line *line = text->physical_line;
|
|
size_t length;
|
|
plot_style_t *plot_style_highlight;
|
|
nserror res;
|
|
|
|
if (line0 < 0)
|
|
line0 = 0;
|
|
if (line1 < 0)
|
|
line1 = 0;
|
|
if (line_count < (unsigned long) line0)
|
|
line0 = line_count;
|
|
if (line_count < (unsigned long) line1)
|
|
line1 = line_count;
|
|
if (line1 < line0)
|
|
line1 = line0;
|
|
|
|
res = ctx->plot->rectangle(ctx, plot_style_fill_white, clip);
|
|
if (res != NSERROR_OK) {
|
|
return false;
|
|
}
|
|
|
|
if (!line)
|
|
return true;
|
|
|
|
/* choose a suitable background colour for any highlighted text */
|
|
if ((data->background_colour & 0x808080) == 0x808080)
|
|
plot_style_highlight = plot_style_fill_black;
|
|
else
|
|
plot_style_highlight = plot_style_fill_white;
|
|
|
|
/* Set up font plot style */
|
|
textplain_style.background = data->background_colour;
|
|
|
|
x = (x + MARGIN) * data->scale;
|
|
y = (y + MARGIN) * data->scale;
|
|
for (lineno = line0; lineno != line1; lineno++) {
|
|
const char *text_d = utf8_data + line[lineno].start;
|
|
int tab_width = textplain_tab_width * data->scale;
|
|
size_t offset = 0;
|
|
int tx = x;
|
|
|
|
if (!tab_width) tab_width = 1;
|
|
|
|
length = line[lineno].length;
|
|
if (!length)
|
|
continue;
|
|
|
|
while (offset < length) {
|
|
size_t next_offset = offset;
|
|
int width;
|
|
int ntx;
|
|
nserror res;
|
|
|
|
while ((next_offset < length) &&
|
|
(text_d[next_offset] != '\t')) {
|
|
next_offset = utf8_next(text_d,
|
|
length,
|
|
next_offset);
|
|
}
|
|
|
|
if (!text_draw(text_d + offset,
|
|
next_offset - offset,
|
|
line[lineno].start + offset,
|
|
tx,
|
|
y + (lineno * scaled_line_height),
|
|
clip,
|
|
line_height,
|
|
data->scale,
|
|
text,
|
|
text->sel,
|
|
ctx)) {
|
|
return false;
|
|
}
|
|
|
|
if (next_offset >= length)
|
|
break;
|
|
|
|
res = guit->layout->width(&textplain_style,
|
|
&text_d[offset],
|
|
next_offset - offset,
|
|
&width);
|
|
/* locate end of string and align to next tab position */
|
|
if (res == NSERROR_OK) {
|
|
tx += (int)(width * data->scale);
|
|
}
|
|
|
|
ntx = x + ((1 + (tx - x) / tab_width) * tab_width);
|
|
|
|
/* if the tab character lies within the
|
|
* selection, if any, then we must draw it as
|
|
* a filled rectangle so that it's consistent
|
|
* with background of the selected text
|
|
*/
|
|
|
|
if (bw) {
|
|
unsigned tab_ofst = line[lineno].start + next_offset;
|
|
struct selection *sel = text->sel;
|
|
bool highlighted = false;
|
|
|
|
unsigned start_idx, end_idx;
|
|
if (selection_highlighted(sel,
|
|
tab_ofst,
|
|
tab_ofst + 1,
|
|
&start_idx,
|
|
&end_idx)) {
|
|
highlighted = true;
|
|
}
|
|
|
|
|
|
if (!highlighted &&
|
|
(c->textsearch.context != NULL)) {
|
|
unsigned start_idx, end_idx;
|
|
if (content_textsearch_ishighlighted(
|
|
c->textsearch.context,
|
|
tab_ofst,
|
|
tab_ofst + 1,
|
|
&start_idx,
|
|
&end_idx)) {
|
|
highlighted = true;
|
|
}
|
|
}
|
|
|
|
if (highlighted) {
|
|
struct rect rect;
|
|
rect.x0 = tx;
|
|
rect.y0 = y + (lineno * scaled_line_height);
|
|
rect.x1 = ntx;
|
|
rect.y1 = rect.y0 + scaled_line_height;
|
|
res = ctx->plot->rectangle(ctx,
|
|
plot_style_highlight,
|
|
&rect);
|
|
if (res != NSERROR_OK) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
offset = next_offset + 1;
|
|
tx = ntx;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle a window containing a CONTENT_TEXTPLAIN being opened.
|
|
*/
|
|
static nserror
|
|
textplain_open(struct content *c,
|
|
struct browser_window *bw,
|
|
struct content *page,
|
|
struct object_params *params)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
|
|
text->bw = bw;
|
|
|
|
/* text selection */
|
|
selection_init(text->sel);
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle a window containing a CONTENT_TEXTPLAIN being closed.
|
|
*/
|
|
static nserror textplain_close(struct content *c)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
|
|
text->bw = NULL;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return an textplain content's selection context
|
|
*/
|
|
static char *textplain_get_selection(struct content *c)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
|
|
return selection_get_copy(text->sel);
|
|
}
|
|
|
|
|
|
/**
|
|
* Convert a character offset within a line of text into the
|
|
* horizontal co-ordinate
|
|
*
|
|
* The conversion takes into account the font being used and any tabs
|
|
* in the text
|
|
*
|
|
* \param text line of text
|
|
* \param offset char offset within text
|
|
* \param length line length
|
|
* \return x ordinate
|
|
*/
|
|
static int
|
|
textplain_coord_from_offset(const char *text, size_t offset, size_t length)
|
|
{
|
|
int x = 0;
|
|
|
|
while (offset > 0) {
|
|
size_t next_offset = 0;
|
|
int tx;
|
|
|
|
while (next_offset < offset && text[next_offset] != '\t') {
|
|
next_offset = utf8_next(text, length, next_offset);
|
|
}
|
|
|
|
guit->layout->width(&textplain_style, text, next_offset, &tx);
|
|
|
|
x += tx;
|
|
|
|
if (next_offset >= offset)
|
|
break;
|
|
|
|
/* align to next tab boundary */
|
|
next_offset++;
|
|
x = (1 + (x / textplain_tab_width)) * textplain_tab_width;
|
|
offset -= next_offset;
|
|
text += next_offset;
|
|
length -= next_offset;
|
|
}
|
|
|
|
return x;
|
|
}
|
|
|
|
|
|
/**
|
|
* Retrieve number of lines in content
|
|
*
|
|
* \param[in] c Content to retrieve line count from
|
|
* \return Number of lines
|
|
*/
|
|
static unsigned long textplain_line_count(struct content *c)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
|
|
assert(c != NULL);
|
|
|
|
return text->physical_line_count;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a pointer to the requested line of text.
|
|
*
|
|
* \param[in] c content of type CONTENT_TEXTPLAIN
|
|
* \param[in] lineno line number
|
|
* \param[out] poffset receives byte offset of line start within text
|
|
* \param[out] plen receives length of returned line
|
|
* \return pointer to text, or NULL if invalid line number
|
|
*/
|
|
static char *
|
|
textplain_get_line(struct content *c,
|
|
unsigned lineno,
|
|
size_t *poffset,
|
|
size_t *plen)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
struct textplain_line *line;
|
|
|
|
assert(c != NULL);
|
|
|
|
if (lineno >= text->physical_line_count)
|
|
return NULL;
|
|
line = &text->physical_line[lineno];
|
|
|
|
*poffset = line->start;
|
|
*plen = line->length;
|
|
return text->utf8_data + line->start;
|
|
}
|
|
|
|
|
|
/**
|
|
* Find line number of byte in text
|
|
*
|
|
* Given a byte offset within the text, return the line number
|
|
* of the line containing that offset.
|
|
*
|
|
* \param[in] c content of type CONTENT_TEXTPLAIN
|
|
* \param[in] offset byte offset within textual representation
|
|
* \return line number, or -1 if offset invalid (larger than size)
|
|
*/
|
|
static int textplain_find_line(struct content *c, unsigned offset)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
struct textplain_line *line;
|
|
int nlines;
|
|
int lineno = 0;
|
|
|
|
assert(c != NULL);
|
|
|
|
line = text->physical_line;
|
|
nlines = text->physical_line_count;
|
|
|
|
if (offset > text->utf8_data_size) {
|
|
return -1;
|
|
}
|
|
|
|
/* \todo - implement binary search here */
|
|
while (lineno < nlines && line[lineno].start < offset) {
|
|
lineno++;
|
|
}
|
|
if (line[lineno].start > offset) {
|
|
lineno--;
|
|
}
|
|
|
|
return lineno;
|
|
}
|
|
|
|
|
|
/**
|
|
* Finds all occurrences of a given string in a textplain content
|
|
*
|
|
* \param c the content to be searched
|
|
* \param context The search context to add the entry to.
|
|
* \param pattern the string pattern to search for
|
|
* \param p_len pattern length
|
|
* \param case_sens whether to perform a case sensitive search
|
|
* \return NSERROR_OK on success else error code on faliure
|
|
*/
|
|
static nserror
|
|
textplain_textsearch_find(struct content *c,
|
|
struct textsearch_context *context,
|
|
const char *pattern,
|
|
int p_len,
|
|
bool case_sens)
|
|
{
|
|
int nlines = textplain_line_count(c);
|
|
int line;
|
|
nserror res = NSERROR_OK;
|
|
|
|
for(line = 0; line < nlines; line++) {
|
|
size_t offset, length;
|
|
const char *text;
|
|
|
|
text = textplain_get_line(c, line, &offset, &length);
|
|
if (text) {
|
|
while (length > 0) {
|
|
unsigned match_length;
|
|
size_t start_idx;
|
|
const char *new_text;
|
|
const char *pos;
|
|
|
|
pos = content_textsearch_find_pattern(
|
|
text,
|
|
length,
|
|
pattern,
|
|
p_len,
|
|
case_sens,
|
|
&match_length);
|
|
if (!pos)
|
|
break;
|
|
|
|
/* found string in line => add to list */
|
|
start_idx = offset + (pos - text);
|
|
res = content_textsearch_add_match(context,
|
|
start_idx,
|
|
start_idx + match_length,
|
|
NULL,
|
|
NULL);
|
|
if (res != NSERROR_OK) {
|
|
return res;
|
|
}
|
|
|
|
new_text = pos + match_length;
|
|
offset += (new_text - text);
|
|
length -= (new_text - text);
|
|
text = new_text;
|
|
}
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* Given a range of byte offsets within a UTF8 textplain content,
|
|
* return a box that fully encloses the text
|
|
*
|
|
* \param[in] c content of type CONTENT_TEXTPLAIN
|
|
* \param[in] start byte offset of start of text range
|
|
* \param[in] end byte offset of end
|
|
* \param[out] r rectangle to be completed
|
|
*/
|
|
static void
|
|
textplain_coords_from_range(struct content *c,
|
|
unsigned start,
|
|
unsigned end,
|
|
struct rect *r)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
float line_height = textplain_line_height();
|
|
char *utf8_data;
|
|
struct textplain_line *line;
|
|
unsigned lineno = 0;
|
|
unsigned nlines;
|
|
|
|
assert(c != NULL);
|
|
assert(start <= end);
|
|
assert(end <= text->utf8_data_size);
|
|
|
|
utf8_data = text->utf8_data;
|
|
nlines = text->physical_line_count;
|
|
line = text->physical_line;
|
|
|
|
/* find start */
|
|
lineno = textplain_find_line(c, start);
|
|
|
|
r->y0 = (int)(MARGIN + lineno * line_height);
|
|
|
|
if (lineno + 1 <= nlines || line[lineno + 1].start >= end) {
|
|
/* \todo - it may actually be more efficient just to
|
|
* run forwards most of the time
|
|
*/
|
|
|
|
/* find end */
|
|
lineno = textplain_find_line(c, end);
|
|
|
|
r->x0 = 0;
|
|
r->x1 = text->formatted_width;
|
|
} else {
|
|
/* single line */
|
|
const char *text = utf8_data + line[lineno].start;
|
|
|
|
r->x0 = textplain_coord_from_offset(text,
|
|
start - line[lineno].start,
|
|
line[lineno].length);
|
|
|
|
r->x1 = textplain_coord_from_offset(text,
|
|
end - line[lineno].start,
|
|
line[lineno].length);
|
|
}
|
|
|
|
r->y1 = (int)(MARGIN + (lineno + 1) * line_height);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a pointer to the raw UTF-8 data, as opposed to the reformatted
|
|
* text to fit the window width. Thus only hard newlines are preserved
|
|
* in the saved/copied text of a selection.
|
|
*
|
|
* \param[in] c content of type CONTENT_TEXTPLAIN
|
|
* \param[in] start starting byte offset within UTF-8 text
|
|
* \param[in] end ending byte offset
|
|
* \param[out] plen receives validated length
|
|
* \return pointer to text, or NULL if no text
|
|
*/
|
|
static char *
|
|
textplain_get_raw_data(struct content *c,
|
|
unsigned start,
|
|
unsigned end,
|
|
size_t *plen)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
size_t utf8_size;
|
|
|
|
assert(c != NULL);
|
|
|
|
utf8_size = text->utf8_data_size;
|
|
|
|
/* any text at all? */
|
|
if (!utf8_size) return NULL;
|
|
|
|
/* clamp to valid offset range */
|
|
if (start >= utf8_size) start = utf8_size;
|
|
if (end >= utf8_size) end = utf8_size;
|
|
|
|
*plen = end - start;
|
|
|
|
return text->utf8_data + start;
|
|
}
|
|
|
|
|
|
/**
|
|
* get bounds of a free text search match
|
|
*/
|
|
static nserror
|
|
textplain_textsearch_bounds(struct content *c,
|
|
unsigned start_idx,
|
|
unsigned end_idx,
|
|
struct box *start_box,
|
|
struct box *end_box,
|
|
struct rect *bounds)
|
|
{
|
|
textplain_coords_from_range(c, start_idx, end_idx, bounds);
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* invalidate a region based on offsets into the text cauing a redraw
|
|
*/
|
|
static nserror
|
|
textplain_textselection_redraw(struct content *c,
|
|
unsigned start_idx,
|
|
unsigned end_idx)
|
|
{
|
|
struct rect r;
|
|
|
|
if (end_idx <= start_idx) {
|
|
return NSERROR_BAD_PARAMETER;
|
|
}
|
|
|
|
textplain_coords_from_range(c, start_idx, end_idx, &r);
|
|
|
|
content__request_redraw(c, r.x0, r.y0, r.x1 - r.x0, r.y1 - r.y0);
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
static nserror
|
|
textplain_textselection_copy(struct content *c,
|
|
unsigned start_idx,
|
|
unsigned end_idx,
|
|
struct selection_string *selstr)
|
|
{
|
|
const char *text;
|
|
size_t length;
|
|
bool res = false;
|
|
|
|
text = textplain_get_raw_data(c, start_idx, end_idx, &length);
|
|
if (text != NULL) {
|
|
res = selection_string_append(text, length, false, NULL, selstr);
|
|
}
|
|
if (res == false) {
|
|
return NSERROR_NOMEM;
|
|
}
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
static nserror
|
|
textplain_textselection_get_end(struct content *c, unsigned *end_idx)
|
|
{
|
|
*end_idx = textplain_size(c);
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
/**
|
|
* plain text content handler table
|
|
*/
|
|
static const content_handler textplain_content_handler = {
|
|
.fini = textplain_fini,
|
|
.create = textplain_create,
|
|
.process_data = textplain_process_data,
|
|
.data_complete = textplain_convert,
|
|
.reformat = textplain_reformat,
|
|
.destroy = textplain_destroy,
|
|
.mouse_track = textplain_mouse_track,
|
|
.mouse_action = textplain_mouse_action,
|
|
.keypress = textplain_keypress,
|
|
.redraw = textplain_redraw,
|
|
.open = textplain_open,
|
|
.close = textplain_close,
|
|
.get_selection = textplain_get_selection,
|
|
.clone = textplain_clone,
|
|
.type = textplain_content_type,
|
|
.textsearch_find = textplain_textsearch_find,
|
|
.textsearch_bounds = textplain_textsearch_bounds,
|
|
.textselection_redraw = textplain_textselection_redraw,
|
|
.textselection_copy = textplain_textselection_copy,
|
|
.textselection_get_end = textplain_textselection_get_end,
|
|
.no_share = true,
|
|
};
|
|
|
|
|
|
/* exported interface documented in html/textplain.h */
|
|
nserror textplain_init(void)
|
|
{
|
|
lwc_error lerror;
|
|
nserror error;
|
|
|
|
lerror = lwc_intern_string("Windows-1252",
|
|
SLEN("Windows-1252"),
|
|
&textplain_default_charset);
|
|
if (lerror != lwc_error_ok) {
|
|
return NSERROR_NOMEM;
|
|
}
|
|
|
|
error = content_factory_register_handler("text/plain",
|
|
&textplain_content_handler);
|
|
if (error != NSERROR_OK) {
|
|
lwc_string_unref(textplain_default_charset);
|
|
}
|
|
|
|
error = content_factory_register_handler("application/json",
|
|
&textplain_content_handler);
|
|
if (error != NSERROR_OK) {
|
|
lwc_string_unref(textplain_default_charset);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* exported interface documented in html/textplain.h */
|
|
size_t textplain_size(struct content *c)
|
|
{
|
|
textplain_content *text = (textplain_content *) c;
|
|
|
|
assert(c != NULL);
|
|
|
|
return text->utf8_data_size;
|
|
}
|