netsurf/render/textplain.c

1366 lines
33 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
* Content for text/plain (implementation).
*/
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include <strings.h>
#include <math.h>
#include <parserutils/input/inputstream.h>
#include <parserutils/charset/utf8.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 "content/content_protected.h"
#include "content/hlcache.h"
#include "css/css.h"
#include "css/utils.h"
#include "desktop/browser.h"
#include "utils/nsoption.h"
#include "desktop/plotters.h"
#include "desktop/search.h"
#include "desktop/selection.h"
#include "desktop/textinput.h"
#include "desktop/font.h"
#include "render/search.h"
#include "render/textplain.h"
#include "render/html.h"
#include "render/search.h"
struct textplain_line {
size_t start;
size_t length;
};
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 */
/** Context for free text search, or NULL if none */
struct search_context *search;
/** Current search string, or NULL if none */
char *search_string;
} 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 * FONT_SIZE_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 void textplain_fini(void);
static nserror textplain_create(const content_handler *handler,
lwc_string *imime_type, const http_parameter *params,
llcache_handle *llcache, const char *fallback_charset,
bool quirks, struct content **c);
static nserror textplain_create_internal(textplain_content *c,
lwc_string *charset);
static bool textplain_process_data(struct content *c,
const char *data, unsigned int size);
static bool textplain_convert(struct content *c);
static void textplain_mouse_track(struct content *c, struct browser_window *bw,
browser_mouse_state mouse, int x, int y);
static void textplain_mouse_action(struct content *c, struct browser_window *bw,
browser_mouse_state mouse, int x, int y);
static bool textplain_keypress(struct content *c, uint32_t key);
static void textplain_search(struct content *c, void *gui_data,
search_flags_t flags, const char *string);
static void textplain_search_clear(struct content *c);
static void textplain_reformat(struct content *c, int width, int height);
static void textplain_destroy(struct content *c);
static bool textplain_redraw(struct content *c, struct content_redraw_data *data,
const struct rect *clip, const struct redraw_context *ctx);
static void textplain_open(struct content *c, struct browser_window *bw,
struct content *page, struct object_params *params);
void textplain_close(struct content *c);
char *textplain_get_selection(struct content *c);
static nserror textplain_clone(const struct content *old,
struct content **newc);
static content_type textplain_content_type(void);
static parserutils_error textplain_charset_hack(const uint8_t *data, size_t len,
uint16_t *mibenum, uint32_t *source);
static bool textplain_drain_input(textplain_content *c,
parserutils_inputstream *stream, parserutils_error terminator);
static bool textplain_copy_utf8_data(textplain_content *c,
const uint8_t *buf, size_t len);
static int textplain_coord_from_offset(const char *text, size_t offset,
size_t length);
static float textplain_line_height(void);
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,
.search = textplain_search,
.search_clear = textplain_search_clear,
.redraw = textplain_redraw,
.open = textplain_open,
.close = textplain_close,
.get_selection = textplain_get_selection,
.clone = textplain_clone,
.type = textplain_content_type,
.no_share = true,
};
static lwc_string *textplain_default_charset;
/**
* Initialise the text content handler
*/
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);
}
return error;
}
/**
* Clean up after the text content handler
*/
void textplain_fini(void)
{
if (textplain_default_charset != NULL) {
lwc_string_unref(textplain_default_charset);
textplain_default_charset = NULL;
}
}
/**
* Create a CONTENT_TEXTPLAIN.
*/
nserror textplain_create(const content_handler *handler,
lwc_string *imime_type, const 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;
}
/*
* Hack around bug 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.
*
* We avoid this by providing a callback that does precisely nothing,
* thus preserving whatever charset information we decided on in
* textplain_create.
*/
parserutils_error textplain_charset_hack(const uint8_t *data, size_t len,
uint16_t *mibenum, uint32_t *source)
{
return PARSERUTILS_OK;
}
nserror textplain_create_internal(textplain_content *c, lwc_string *encoding)
{
char *utf8_data;
parserutils_inputstream *stream;
parserutils_error error;
union content_msg_data msg_data;
textplain_style.size = (nsoption_int(font_size) * FONT_SIZE_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;
selection_prepare(&c->sel, (struct content *)c, false);
return NSERROR_OK;
no_memory:
msg_data.error = messages_get("NoMemory");
content_broadcast(&c->base, CONTENT_MSG_ERROR, msg_data);
return NSERROR_NOMEM;
}
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;
}
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;
}
/**
* Process data for CONTENT_TEXTPLAIN.
*/
bool textplain_process_data(struct content *c,
const char *data, unsigned int size)
{
textplain_content *text = (textplain_content *) c;
parserutils_inputstream *stream = text->inputstream;
union content_msg_data msg_data;
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:
msg_data.error = messages_get("NoMemory");
content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
return false;
}
/**
* Convert a CONTENT_TEXTPLAIN for display.
*/
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;
}
/**
* Reformat a CONTENT_TEXTPLAIN to a new width.
*/
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;
LOG(("content %p w:%d h:%d",c, width, height));
/* compute available columns (assuming monospaced font) - use 8
* characters for better accuracy */
if (!nsfont.font_width(&textplain_style, "ABCDEFGH", 8, &character_width))
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:
LOG(("out of memory (line_count %lu)", line_count));
return;
}
/**
* Destroy a CONTENT_TEXTPLAIN and free all resources it owns.
*/
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);
}
}
nserror textplain_clone(const struct content *old, struct content **newc)
{
const textplain_content *old_text = (textplain_content *) old;
textplain_content *text;
nserror error;
const char *data;
unsigned long 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, 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;
}
content_type textplain_content_type(void)
{
return CONTENT_TEXTPLAIN;
}
/**
* 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
*/
void 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;
}
}
/**
* 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
*/
void 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, 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);
}
/**
* Handle keypresses.
*
* \param c content of type CONTENT_TEXTPLAIN
* \param key The UCS4 character codepoint
* \return true if key handled, false otherwise
*/
bool textplain_keypress(struct content *c, uint32_t key)
{
textplain_content *text = (textplain_content *) c;
struct selection *sel = &text->sel;
switch (key) {
case KEY_COPY_SELECTION:
selection_copy_to_clipboard(sel);
return true;
case KEY_CLEAR_SELECTION:
selection_clear(sel, true);
return true;
case KEY_SELECT_ALL:
selection_select_all(sel);
return true;
case KEY_ESCAPE:
if (selection_defined(sel)) {
selection_clear(sel, true);
return true;
}
/* if there's no selection, leave Escape for the caller */
return false;
}
return false;
}
/**
* Handle search.
*
* \param c content of type text
* \param gui_data front end private data
* \param flags search flags
* \param string search string
*/
void textplain_search(struct content *c, void *gui_data,
search_flags_t flags, const char *string)
{
textplain_content *text = (textplain_content *) c;
assert(c != NULL);
if (string != NULL && text->search_string != NULL &&
strcmp(string, text->search_string) == 0 &&
text->search != NULL) {
/* Continue prev. search */
search_step(text->search, flags, string);
} else if (string != NULL) {
/* New search */
free(text->search_string);
text->search_string = strdup(string);
if (text->search_string == NULL)
return;
if (text->search != NULL) {
search_destroy_context(text->search);
text->search = NULL;
}
text->search = search_create_context(c, CONTENT_TEXTPLAIN,
gui_data);
if (text->search == NULL)
return;
search_step(text->search, flags, string);
} else {
/* Clear search */
textplain_search_clear(c);
free(text->search_string);
text->search_string = NULL;
}
}
/**
* Terminate a search.
*
* \param c content of type text
*/
void textplain_search_clear(struct content *c)
{
textplain_content *text = (textplain_content *) c;
assert(c != NULL);
free(text->search_string);
text->search_string = NULL;
if (text->search != NULL)
search_destroy_context(text->search);
text->search = NULL;
}
/**
* Draw a CONTENT_TEXTPLAIN using the current set of plotters (plot).
*
* \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
*
* x, y, clip_[xy][01] are in target coordinates.
*/
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;
const struct plotter_table *plot = ctx->plot;
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;
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;
if (!plot->rectangle(clip->x0, clip->y0, clip->x1, clip->y1,
plot_style_fill_white))
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;
while (next_offset < length && text_d[next_offset] != '\t')
next_offset = utf8_next(text_d, length, next_offset);
if (!text_redraw(text_d + offset, next_offset - offset,
line[lineno].start + offset, 0,
&textplain_style,
tx, y + (lineno * scaled_line_height),
clip, line_height, data->scale, false,
(struct content *)text, &text->sel,
text->search, ctx))
return false;
if (next_offset >= length)
break;
/* locate end of string and align to next tab position */
if (nsfont.font_width(&textplain_style, &text_d[offset],
next_offset - offset, &width))
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;
if (selection_defined(sel)) {
unsigned start_idx, end_idx;
if (selection_highlighted(sel,
tab_ofst, tab_ofst + 1,
&start_idx, &end_idx))
highlighted = true;
}
if (!highlighted && (text->search != NULL)) {
unsigned start_idx, end_idx;
if (search_term_highlighted(c,
tab_ofst, tab_ofst + 1,
&start_idx, &end_idx,
text->search))
highlighted = true;
}
if (highlighted) {
int sy = y + (lineno * scaled_line_height);
if (!plot->rectangle(tx, sy,
ntx, sy + scaled_line_height,
plot_style_highlight))
return false;
}
}
offset = next_offset + 1;
tx = ntx;
}
}
return true;
}
/**
* Handle a window containing a CONTENT_TEXTPLAIN being opened.
*/
void 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, NULL);
}
/**
* Handle a window containing a CONTENT_TEXTPLAIN being closed.
*/
void textplain_close(struct content *c)
{
textplain_content *text = (textplain_content *) c;
if (text->search != NULL)
search_destroy_context(text->search);
text->bw = NULL;
}
/**
* Return an textplain content's selection context
*/
char *textplain_get_selection(struct content *c)
{
textplain_content *text = (textplain_content *) c;
return selection_get_copy(&text->sel);
}
/**
* Retrieve number of lines in content
*
* \param c Content to retrieve line count from
* \return Number of lines
*/
unsigned long textplain_line_count(struct content *c)
{
textplain_content *text = (textplain_content *) c;
assert(c != NULL);
return text->physical_line_count;
}
/**
* Retrieve the size (in bytes) of text data
*
* \param c Content to retrieve size of
* \return Size, in bytes, of data
*/
size_t textplain_size(struct content *c)
{
textplain_content *text = (textplain_content *) c;
assert(c != NULL);
return text->utf8_data_size;
}
/**
* 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 c content of type CONTENT_TEXTPLAIN
* \param x x ordinate of point
* \param y y ordinate of point
* \param dir direction of search if not within line
* \return byte offset of character containing (or nearest to) point
*/
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)
nsfont.font_width(&textplain_style, text, next_offset, &width);
if (x <= width) {
int pixel_offset;
size_t char_offset;
nsfont.font_position_in_string(&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;
}
/**
* Given a byte offset within the text, return the line number
* of the line containing that offset (or -1 if offset invalid)
*
* \param c content of type CONTENT_TEXTPLAIN
* \param offset byte offset within textual representation
* \return line number, or -1 if offset invalid (larger than size)
*/
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;
}
/**
* Convert a character offset within a line of text into the
* horizontal co-ordinate, taking 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
*/
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);
nsfont.font_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;
}
/**
* Given a range of byte offsets within a UTF8 textplain content,
* return a box that fully encloses the text
*
* \param c content of type CONTENT_TEXTPLAIN
* \param start byte offset of start of text range
* \param end byte offset of end
* \param r rectangle to be completed
*/
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 requested line of text.
*
* \param c content of type CONTENT_TEXTPLAIN
* \param lineno line number
* \param poffset receives byte offset of line start within text
* \param plen receives length of returned line
* \return pointer to text, or NULL if invalid line number
*/
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;
}
/**
* 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 c content of type CONTENT_TEXTPLAIN
* \param start starting byte offset within UTF-8 text
* \param end ending byte offset
* \param plen receives validated length
* \return pointer to text, or NULL if no text
*/
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;
}
/**
* Calculate the line height, in pixels
*
* \return Line height, in pixels
*/
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 / FONT_SIZE_SCALE))))), F_72));
}
/**
* Get the browser window containing a textplain content
*
* \param c text/plain content
* \return the browser window
*/
struct browser_window *textplain_get_browser_window(struct content *c)
{
textplain_content *text = (textplain_content *) c;
assert(c != NULL);
assert(c->handler == &textplain_content_handler);
return text->bw;
}