summaryrefslogtreecommitdiff
path: root/content/handlers/text
diff options
context:
space:
mode:
authorVincent Sanders <vince@netsurf-browser.org>2018-05-10 11:34:26 +0100
committerVincent Sanders <vince@kyllikki.org>2018-05-10 13:37:02 +0100
commit2a03ea30490892ac52b3da325ab78e1aa888f83e (patch)
treed041e4a2aab3b224ad41612d47ea2119895e27ac /content/handlers/text
parent1b892391d7859398c212b9fda5b532308fa6e8fd (diff)
downloadnetsurf-2a03ea30490892ac52b3da325ab78e1aa888f83e.tar.gz
netsurf-2a03ea30490892ac52b3da325ab78e1aa888f83e.tar.bz2
move html and text content handlers where they belong
Diffstat (limited to 'content/handlers/text')
-rw-r--r--content/handlers/text/Makefile3
-rw-r--r--content/handlers/text/textplain.c1576
-rw-r--r--content/handlers/text/textplain.h139
3 files changed, 1718 insertions, 0 deletions
diff --git a/content/handlers/text/Makefile b/content/handlers/text/Makefile
new file mode 100644
index 000000000..83d5dbd1e
--- /dev/null
+++ b/content/handlers/text/Makefile
@@ -0,0 +1,3 @@
+# text content handler sources
+
+S_TEXT := textplain.c
diff --git a/content/handlers/text/textplain.c b/content/handlers/text/textplain.c
new file mode 100644
index 000000000..e6d167bc2
--- /dev/null
+++ b/content/handlers/text/textplain.c
@@ -0,0 +1,1576 @@
+/*
+ * 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 "html/search.h"
+#include "text/textplain.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;
+
+ 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:
+ content_broadcast_errorcode(&c->base, NSERROR_NOMEM);
+
+ 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;
+ 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_errorcode(c, NSERROR_NOMEM);
+ 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;
+
+ 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);
+ }
+}
+
+
+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;
+ }
+}
+
+
+/**
+ * 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,
+ struct search_context *search,
+ 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_defined(sel) &&
+ selection_highlighted(sel,
+ offset,
+ offset + len,
+ &start_idx,
+ &end_idx)) {
+ highlighted = true;
+ }
+
+ /* what about the current search operation, if any? */
+ if (!highlighted &&
+ (search != NULL) &&
+ search_term_highlighted((struct content *)text,
+ offset,
+ offset + len,
+ &start_idx,
+ &end_idx,
+ search)) {
+ 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,
+ 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, 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 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);
+ }
+
+ return error;
+}
+
+
+/* exported interface documented in html/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 html/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 html/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 html/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 html/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 html/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 html/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 html/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;
+}
diff --git a/content/handlers/text/textplain.h b/content/handlers/text/textplain.h
new file mode 100644
index 000000000..23917fb6c
--- /dev/null
+++ b/content/handlers/text/textplain.h
@@ -0,0 +1,139 @@
+/*
+ * 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
+ *
+ * Interface to content handler for plain text.
+ */
+
+#ifndef NETSURF_HTML_TEXTPLAIN_H
+#define NETSURF_HTML_TEXTPLAIN_H
+
+#include <stddef.h>
+#include "netsurf/mouse.h"
+
+struct content;
+struct hlcache_handle;
+struct http_parameter;
+struct rect;
+
+/**
+ * Initialise the text content handler
+ *
+ * \return NSERROR_OK on success else appropriate error code.
+ */
+nserror textplain_init(void);
+
+
+/**
+ * Retrieve number of lines in content
+ *
+ * \param[in] c Content to retrieve line count from
+ * \return Number of lines
+ */
+unsigned long textplain_line_count(struct content *c);
+
+
+/**
+ * Retrieve the size (in bytes) of text data
+ *
+ * \param[in] c Content to retrieve size of
+ * \return Size, in bytes, of data
+ */
+size_t textplain_size(struct content *c);
+
+
+/**
+ * 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
+ */
+size_t textplain_offset_from_coords(struct content *c, int x, int y, int dir);
+
+
+/**
+ * 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
+ */
+void textplain_coords_from_range(struct content *c,
+ unsigned start, unsigned end, struct rect *r);
+
+/**
+ * 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
+ */
+char *textplain_get_line(struct content *c, unsigned lineno,
+ size_t *poffset, size_t *plen);
+
+
+/**
+ * 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)
+ */
+int textplain_find_line(struct content *c, unsigned offset);
+
+
+/**
+ * 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
+ */
+char *textplain_get_raw_data(struct content *c, unsigned start, unsigned end, size_t *plen);
+
+
+/**
+ * Get the browser window containing a textplain content
+ *
+ * \param[in] c text/plain content
+ * \return the browser window
+ */
+struct browser_window *textplain_get_browser_window(struct content *c);
+
+#endif