netsurf/render/textplain.c
Vincent Sanders 08a86bfa0e clean up doccomments and formatting in texplain content handler
There is an intention to replace the render interface and it will be
helpful to reduce the size of the exposed API as much as
possible. This is the first step towards that goal.
2017-06-01 22:43:45 +01:00

1356 lines
31 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 <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 "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/hlcache.h"
#include "css/utils.h"
#include "utils/nsoption.h"
#include "desktop/search.h"
#include "desktop/selection.h"
#include "desktop/gui_internal.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 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;
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;
}
/**
* Create a CONTENT_TEXTPLAIN.
*/
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)
{
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;
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.
*/
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 / FONT_SIZE_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;
LOG("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:
LOG("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);
}
}
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 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;
}
static content_type textplain_content_type(void)
{
return CONTENT_TEXTPLAIN;
}
/**
* 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 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 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 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 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 (selection_defined(sel)) {
selection_clear(sel, true);
return true;
}
/* if there's no selection, leave Escape for the caller */
return false;
}
return false;
}
/**
* Terminate a search.
*
* \param c content of type text
*/
static 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;
}
/**
* Handle search.
*
* \param c content of type text
* \param gui_data front end private data
* \param flags search flags
* \param string search string
*/
static 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;
}
}
/**
* 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_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;
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;
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) {
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 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.
*/
static 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
*/
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;
}
/**
* 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,
.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,
};
/* exported interface documented in render/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);
}
return error;
}
/* exported interface documented in render/textplain.h */
unsigned long textplain_line_count(struct content *c)
{
textplain_content *text = (textplain_content *) c;
assert(c != NULL);
return text->physical_line_count;
}
/* exported interface documented in render/textplain.h */
size_t textplain_size(struct content *c)
{
textplain_content *text = (textplain_content *) c;
assert(c != NULL);
return text->utf8_data_size;
}
/* exported interface documented in render/textplain.h */
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;
}
/* exported interface documented in render/textplain.h */
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);
}
/* exported interface documented in render/textplain.h */
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;
}
/* exported interface documented in render/textplain.h */
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;
}
/* exported interface documented in render/textplain.h */
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;
}
/* exported interface documented in render/textplain.h */
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;
}