Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * Copyright 2006 James Bursa <bursa@users.sourceforge.net>
  3.  * Copyright 2006 Adrian Lees <adrianl@users.sourceforge.net>
  4.  *
  5.  * This file is part of NetSurf, http://www.netsurf-browser.org/
  6.  *
  7.  * NetSurf is free software; you can redistribute it and/or modify
  8.  * it under the terms of the GNU General Public License as published by
  9.  * the Free Software Foundation; version 2 of the License.
  10.  *
  11.  * NetSurf is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.  * GNU General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU General Public License
  17.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  18.  */
  19.  
  20. /** \file
  21.  * Content for text/plain (implementation).
  22.  */
  23.  
  24. #include <assert.h>
  25. #include <errno.h>
  26. #include <stddef.h>
  27. #include <string.h>
  28. #include <strings.h>
  29. #include <math.h>
  30.  
  31. #include <parserutils/input/inputstream.h>
  32.  
  33. #include "content/content_protected.h"
  34. #include "content/hlcache.h"
  35. #include "css/css.h"
  36. #include "css/utils.h"
  37. #include "desktop/browser.h"
  38. #include "desktop/gui.h"
  39. #include "desktop/options.h"
  40. #include "desktop/plotters.h"
  41. #include "desktop/search.h"
  42. #include "desktop/selection.h"
  43. #include "render/font.h"
  44. #include "render/search.h"
  45. #include "render/textplain.h"
  46. #include "render/html.h"
  47. #include "utils/http.h"
  48. #include "utils/log.h"
  49. #include "utils/messages.h"
  50. #include "utils/utils.h"
  51. #include "utils/utf8.h"
  52.  
  53. struct textplain_line {
  54.         size_t  start;
  55.         size_t  length;
  56. };
  57.  
  58. typedef struct textplain_content {
  59.         struct content base;
  60.  
  61.         lwc_string *encoding;
  62.         void *inputstream;
  63.         char *utf8_data;
  64.         size_t utf8_data_size;
  65.         size_t utf8_data_allocated;
  66.         unsigned long physical_line_count;
  67.         struct textplain_line *physical_line;
  68.         int formatted_width;
  69.         struct browser_window *bw;
  70.  
  71.         struct selection sel;   /** Selection state */
  72.  
  73.         /** Context for free text search, or NULL if none */
  74.         struct search_context *search;
  75. } textplain_content;
  76.  
  77.  
  78. #define CHUNK 32768 /* Must be a power of 2 */
  79. #define MARGIN 4
  80.  
  81.  
  82. #define TAB_WIDTH 8  /* must be power of 2 currently */
  83. #define TEXT_SIZE 10 * FONT_SIZE_SCALE  /* Unscaled text size in pt */
  84.  
  85. static plot_font_style_t textplain_style = {
  86.         .family = PLOT_FONT_FAMILY_MONOSPACE,
  87.         .size = TEXT_SIZE,
  88.         .weight = 400,
  89.         .flags = FONTF_NONE,
  90.         .background = 0xffffff,
  91.         .foreground = 0x000000,
  92. };
  93.  
  94. static int textplain_tab_width = 256;  /* try for a sensible default */
  95.  
  96. static void textplain_fini(void);
  97. static nserror textplain_create(const content_handler *handler,
  98.                 lwc_string *imime_type, const http_parameter *params,
  99.                 llcache_handle *llcache, const char *fallback_charset,
  100.                 bool quirks, struct content **c);
  101. static nserror textplain_create_internal(textplain_content *c,
  102.                 lwc_string *charset);
  103. static bool textplain_process_data(struct content *c,
  104.                 const char *data, unsigned int size);
  105. static bool textplain_convert(struct content *c);
  106. static void textplain_mouse_track(struct content *c, struct browser_window *bw,
  107.                         browser_mouse_state mouse, int x, int y);
  108. static void textplain_mouse_action(struct content *c, struct browser_window *bw,
  109.                         browser_mouse_state mouse, int x, int y);
  110. static void textplain_reformat(struct content *c, int width, int height);
  111. static void textplain_destroy(struct content *c);
  112. static bool textplain_redraw(struct content *c, struct content_redraw_data *data,
  113.                 const struct rect *clip, const struct redraw_context *ctx);
  114. static void textplain_open(struct content *c, struct browser_window *bw,
  115.                 struct content *page, struct object_params *params);
  116. void textplain_close(struct content *c);
  117. struct selection *textplain_get_selection(struct content *c);
  118. struct search_context *textplain_get_search(struct content *c);
  119. static nserror textplain_clone(const struct content *old,
  120.                 struct content **newc);
  121. static content_type textplain_content_type(void);
  122.  
  123. static parserutils_error textplain_charset_hack(const uint8_t *data, size_t len,
  124.                 uint16_t *mibenum, uint32_t *source);
  125. static bool textplain_drain_input(textplain_content *c,
  126.                 parserutils_inputstream *stream, parserutils_error terminator);
  127. static bool textplain_copy_utf8_data(textplain_content *c,
  128.                 const uint8_t *buf, size_t len);
  129. static int textplain_coord_from_offset(const char *text, size_t offset,
  130.         size_t length);
  131. static float textplain_line_height(void);
  132.  
  133. static const content_handler textplain_content_handler = {
  134.         .fini = textplain_fini,
  135.         .create = textplain_create,
  136.         .process_data = textplain_process_data,
  137.         .data_complete = textplain_convert,
  138.         .reformat = textplain_reformat,
  139.         .destroy = textplain_destroy,
  140.         .mouse_track = textplain_mouse_track,
  141.         .mouse_action = textplain_mouse_action,
  142.         .redraw = textplain_redraw,
  143.         .open = textplain_open,
  144.         .close = textplain_close,
  145.         .get_selection = textplain_get_selection,
  146.         .clone = textplain_clone,
  147.         .type = textplain_content_type,
  148.         .no_share = true,
  149. };
  150.  
  151. static lwc_string *textplain_charset;
  152. static lwc_string *textplain_default_charset;
  153.  
  154. /**
  155.  * Initialise the text content handler
  156.  */
  157. nserror textplain_init(void)
  158. {
  159.         lwc_error lerror;
  160.         nserror error;
  161.  
  162.         lerror = lwc_intern_string("charset", SLEN("charset"),
  163.                         &textplain_charset);
  164.         if (lerror != lwc_error_ok) {
  165.                 return NSERROR_NOMEM;
  166.         }
  167.  
  168.         lerror = lwc_intern_string("Windows-1252", SLEN("Windows-1252"),
  169.                         &textplain_default_charset);
  170.         if (lerror != lwc_error_ok) {
  171.                 lwc_string_unref(textplain_charset);
  172.                 return NSERROR_NOMEM;
  173.         }
  174.  
  175.         error = content_factory_register_handler("text/plain",
  176.                         &textplain_content_handler);
  177.         if (error != NSERROR_OK) {
  178.                 lwc_string_unref(textplain_default_charset);
  179.                 lwc_string_unref(textplain_charset);
  180.         }
  181.  
  182.         return error;
  183. }
  184.  
  185. /**
  186.  * Clean up after the text content handler
  187.  */
  188. void textplain_fini(void)
  189. {
  190.         if (textplain_default_charset != NULL) {
  191.                 lwc_string_unref(textplain_default_charset);
  192.                 textplain_default_charset = NULL;
  193.         }
  194.  
  195.         if (textplain_charset != NULL) {
  196.                 lwc_string_unref(textplain_charset);
  197.                 textplain_charset = NULL;
  198.         }
  199. }
  200.  
  201. /**
  202.  * Create a CONTENT_TEXTPLAIN.
  203.  */
  204.  
  205. nserror textplain_create(const content_handler *handler,
  206.                 lwc_string *imime_type, const http_parameter *params,
  207.                 llcache_handle *llcache, const char *fallback_charset,
  208.                 bool quirks, struct content **c)
  209. {
  210.         textplain_content *text;
  211.         nserror error;
  212.         lwc_string *encoding;
  213.  
  214.         text = calloc(1, sizeof(textplain_content));
  215.         if (text == NULL)
  216.                 return NSERROR_NOMEM;
  217.  
  218.         error = content__init(&text->base, handler, imime_type, params,
  219.                         llcache, fallback_charset, quirks);
  220.         if (error != NSERROR_OK) {
  221.                 free(text);
  222.                 return error;
  223.         }
  224.  
  225.         error = http_parameter_list_find_item(params, textplain_charset,
  226.                         &encoding);
  227.         if (error != NSERROR_OK) {
  228.                 encoding = lwc_string_ref(textplain_default_charset);
  229.         }
  230.  
  231.         error = textplain_create_internal(text, encoding);
  232.         if (error != NSERROR_OK) {
  233.                 lwc_string_unref(encoding);
  234.                 free(text);
  235.                 return error;
  236.         }
  237.  
  238.         lwc_string_unref(encoding);
  239.  
  240.         *c = (struct content *) text;
  241.  
  242.         return NSERROR_OK;
  243. }
  244.  
  245. /*
  246.  * Hack around bug in libparserutils: if the client provides an
  247.  * encoding up front, but does not provide a charset detection
  248.  * callback, then libparserutils will replace the provided encoding
  249.  * with UTF-8. This breaks our input handling.
  250.  *
  251.  * We avoid this by providing a callback that does precisely nothing,
  252.  * thus preserving whatever charset information we decided on in
  253.  * textplain_create.
  254.  */
  255. parserutils_error textplain_charset_hack(const uint8_t *data, size_t len,
  256.                 uint16_t *mibenum, uint32_t *source)
  257. {
  258.         return PARSERUTILS_OK;
  259. }
  260.  
  261. nserror textplain_create_internal(textplain_content *c, lwc_string *encoding)
  262. {
  263.         char *utf8_data;
  264.         parserutils_inputstream *stream;
  265.         parserutils_error error;
  266.         union content_msg_data msg_data;
  267.  
  268.         textplain_style.size = (nsoption_int(font_size) * FONT_SIZE_SCALE) / 10;
  269.  
  270.         utf8_data = malloc(CHUNK);
  271.         if (utf8_data == NULL)
  272.                 goto no_memory;
  273.  
  274.         error = parserutils_inputstream_create(lwc_string_data(encoding), 0,
  275.                         textplain_charset_hack, ns_realloc, NULL, &stream);
  276.         if (error == PARSERUTILS_BADENCODING) {
  277.                 /* Fall back to Windows-1252 */
  278.                 error = parserutils_inputstream_create("Windows-1252", 0,
  279.                                 textplain_charset_hack, ns_realloc, NULL,
  280.                                 &stream);
  281.         }
  282.         if (error != PARSERUTILS_OK) {
  283.                 free(utf8_data);
  284.                 goto no_memory;
  285.         }
  286.        
  287.         c->encoding = lwc_string_ref(encoding);
  288.         c->inputstream = stream;
  289.         c->utf8_data = utf8_data;
  290.         c->utf8_data_size = 0;
  291.         c->utf8_data_allocated = CHUNK;
  292.         c->physical_line = 0;
  293.         c->physical_line_count = 0;
  294.         c->formatted_width = 0;
  295.         c->bw = NULL;
  296.  
  297.         selection_prepare(&c->sel, (struct content *)c, false);
  298.  
  299.         return NSERROR_OK;
  300.  
  301. no_memory:
  302.         msg_data.error = messages_get("NoMemory");
  303.         content_broadcast(&c->base, CONTENT_MSG_ERROR, msg_data);
  304.         return NSERROR_NOMEM;
  305. }
  306.  
  307. bool textplain_drain_input(textplain_content *c,
  308.                 parserutils_inputstream *stream,
  309.                 parserutils_error terminator)
  310. {
  311.         static const uint8_t *u_fffd = (const uint8_t *) "\xef\xbf\xfd";
  312.         const uint8_t *ch;
  313.         size_t chlen, offset = 0;
  314.  
  315.         while (parserutils_inputstream_peek(stream, offset, &ch, &chlen) !=
  316.                         terminator) {
  317.                 /* Replace all instances of NUL with U+FFFD */
  318.                 if (chlen == 1 && *ch == 0) {
  319.                         if (offset > 0) {
  320.                                 /* Obtain pointer to start of input data */
  321.                                 parserutils_inputstream_peek(stream, 0,
  322.                                                 &ch, &chlen);
  323.                                 /* Copy from it up to the start of the NUL */
  324.                                 if (textplain_copy_utf8_data(c, ch,
  325.                                                 offset) == false)
  326.                                         return false;
  327.                         }
  328.  
  329.                         /* Emit U+FFFD */
  330.                         if (textplain_copy_utf8_data(c, u_fffd, 3) == false)
  331.                                 return false;
  332.  
  333.                         /* Advance inputstream past the NUL we just read */
  334.                         parserutils_inputstream_advance(stream, offset + 1);
  335.                         /* Reset the read offset */
  336.                         offset = 0;
  337.                 } else {
  338.                         /* Accumulate input */
  339.                         offset += chlen;
  340.  
  341.                         if (offset > CHUNK) {
  342.                                 /* Obtain pointer to start of input data */
  343.                                 parserutils_inputstream_peek(stream, 0,
  344.                                                 &ch, &chlen);
  345.  
  346.                                 /* Emit the data we've read */
  347.                                 if (textplain_copy_utf8_data(c, ch,
  348.                                                 offset) == false)
  349.                                         return false;
  350.  
  351.                                 /* Advance the inputstream */
  352.                                 parserutils_inputstream_advance(stream, offset);
  353.                                 /* Reset the read offset */
  354.                                 offset = 0;
  355.                         }
  356.                 }
  357.         }
  358.  
  359.         if (offset > 0) {
  360.                 /* Obtain pointer to start of input data */
  361.                 parserutils_inputstream_peek(stream, 0, &ch, &chlen);  
  362.                 /* Emit any data remaining */
  363.                 if (textplain_copy_utf8_data(c, ch, offset) == false)
  364.                         return false;
  365.  
  366.                 /* Advance the inputstream past the data we've read */
  367.                 parserutils_inputstream_advance(stream, offset);
  368.         }
  369.  
  370.         return true;
  371. }
  372.  
  373. bool textplain_copy_utf8_data(textplain_content *c,
  374.                 const uint8_t *buf, size_t len)
  375. {
  376.         if (c->utf8_data_size + len >= c->utf8_data_allocated) {
  377.                 /* Compute next multiple of chunk above the required space */
  378.                 size_t allocated;
  379.                 char *utf8_data;
  380.  
  381.                 allocated = (c->utf8_data_size + len + CHUNK - 1) & ~(CHUNK - 1);
  382.                 utf8_data = realloc(c->utf8_data, allocated);
  383.                 if (utf8_data == NULL)
  384.                         return false;
  385.  
  386.                 c->utf8_data = utf8_data;
  387.                 c->utf8_data_allocated = allocated;
  388.         }
  389.  
  390.         memcpy(c->utf8_data + c->utf8_data_size, buf, len);
  391.         c->utf8_data_size += len;
  392.  
  393.         return true;
  394. }
  395.  
  396.  
  397. /**
  398.  * Process data for CONTENT_TEXTPLAIN.
  399.  */
  400.  
  401. bool textplain_process_data(struct content *c,
  402.                 const char *data, unsigned int size)
  403. {
  404.         textplain_content *text = (textplain_content *) c;
  405.         parserutils_inputstream *stream = text->inputstream;
  406.         union content_msg_data msg_data;
  407.         parserutils_error error;
  408.  
  409.         error = parserutils_inputstream_append(stream,
  410.                         (const uint8_t *) data, size);
  411.         if (error != PARSERUTILS_OK) {
  412.                 goto no_memory;
  413.         }
  414.  
  415.         if (textplain_drain_input(text, stream, PARSERUTILS_NEEDDATA) == false)
  416.                 goto no_memory;
  417.  
  418.         return true;
  419.  
  420. no_memory:
  421.         msg_data.error = messages_get("NoMemory");
  422.         content_broadcast(c, CONTENT_MSG_ERROR, msg_data);
  423.         return false;
  424. }
  425.  
  426.  
  427. /**
  428.  * Convert a CONTENT_TEXTPLAIN for display.
  429.  */
  430.  
  431. bool textplain_convert(struct content *c)
  432. {
  433.         textplain_content *text = (textplain_content *) c;
  434.         parserutils_inputstream *stream = text->inputstream;
  435.         parserutils_error error;
  436.  
  437.         error = parserutils_inputstream_append(stream, NULL, 0);
  438.         if (error != PARSERUTILS_OK) {
  439.                 return false;
  440.         }
  441.  
  442.         if (textplain_drain_input(text, stream, PARSERUTILS_EOF) == false)
  443.                 return false;
  444.  
  445.         parserutils_inputstream_destroy(stream);
  446.         text->inputstream = NULL;
  447.  
  448.         content_set_ready(c);
  449.         content_set_done(c);
  450.         content_set_status(c, messages_get("Done"));
  451.  
  452.         return true;
  453. }
  454.  
  455.  
  456. /**
  457.  * Reformat a CONTENT_TEXTPLAIN to a new width.
  458.  */
  459.  
  460. void textplain_reformat(struct content *c, int width, int height)
  461. {
  462.         textplain_content *text = (textplain_content *) c;
  463.         char *utf8_data = text->utf8_data;
  464.         size_t utf8_data_size = text->utf8_data_size;
  465.         unsigned long line_count = 0;
  466.         struct textplain_line *line = text->physical_line;
  467.         struct textplain_line *line1;
  468.         size_t i, space, col;
  469.         size_t columns = 80;
  470.         int character_width;
  471.         size_t line_start;
  472.  
  473.         /* compute available columns (assuming monospaced font) - use 8
  474.          * characters for better accuracy */
  475.         if (!nsfont.font_width(&textplain_style, "ABCDEFGH", 8, &character_width))
  476.                 return;
  477.         columns = (width - MARGIN - MARGIN) * 8 / character_width;
  478.         textplain_tab_width = (TAB_WIDTH * character_width) / 8;
  479.  
  480.         text->formatted_width = width;
  481.  
  482.         text->physical_line_count = 0;
  483.  
  484.         if (!line) {
  485.                 text->physical_line = line =
  486.                         malloc(sizeof(struct textplain_line) * (1024 + 3));
  487.                 if (!line)
  488.                         goto no_memory;
  489.         }
  490.  
  491.         line[line_count++].start = line_start = 0;
  492.         space = 0;
  493.         for (i = 0, col = 0; i != utf8_data_size; i++) {
  494.                 bool term = (utf8_data[i] == '\n' || utf8_data[i] == '\r');
  495.                 size_t next_col = col + 1;
  496.  
  497.                 if (utf8_data[i] == '\t')
  498.                         next_col = (next_col + TAB_WIDTH - 1) & ~(TAB_WIDTH - 1);
  499.  
  500.                 if (term || next_col >= columns) {
  501.                         if (line_count % 1024 == 0) {
  502.                                 line1 = realloc(line,
  503.                                                 sizeof(struct textplain_line) *
  504.                                                 (line_count + 1024 + 3));
  505.                                 if (!line1)
  506.                                         goto no_memory;
  507.                                 text->physical_line = line = line1;
  508.                         }
  509.                         if (term) {
  510.                                 line[line_count-1].length = i - line_start;
  511.  
  512.                                 /* skip second char of CR/LF or LF/CR pair */
  513.                                 if (i + 1 < utf8_data_size &&
  514.                                         utf8_data[i+1] != utf8_data[i] &&
  515.                                         (utf8_data[i+1] == '\n' || utf8_data[i+1] == '\r'))
  516.                                         i++;
  517.                         }
  518.                         else {
  519.                                 if (space) {
  520.                                         /* break at last space in line */
  521.                                         i = space;
  522.                                         line[line_count-1].length = (i + 1) - line_start;
  523.  
  524.                                 } else
  525.                                         line[line_count-1].length = i - line_start;
  526.                         }
  527.                         line[line_count++].start = line_start = i + 1;
  528.                         col = 0;
  529.                         space = 0;
  530.                 } else {
  531.                         col++;
  532.                         if (utf8_data[i] == ' ')
  533.                                 space = i;
  534.                 }
  535.         }
  536.         line[line_count-1].length = i - line[line_count-1].start;
  537.         line[line_count].start = utf8_data_size;
  538.  
  539.         text->physical_line_count = line_count;
  540.         c->width = width;
  541.         c->height = line_count * textplain_line_height() + MARGIN + MARGIN;
  542.  
  543.         return;
  544.  
  545. no_memory:
  546.         LOG(("out of memory (line_count %lu)", line_count));
  547.         return;
  548. }
  549.  
  550.  
  551. /**
  552.  * Destroy a CONTENT_TEXTPLAIN and free all resources it owns.
  553.  */
  554.  
  555. void textplain_destroy(struct content *c)
  556. {
  557.         textplain_content *text = (textplain_content *) c;
  558.  
  559.         lwc_string_unref(text->encoding);
  560.  
  561.         if (text->inputstream != NULL) {
  562.                 parserutils_inputstream_destroy(text->inputstream);
  563.         }
  564.  
  565.         if (text->physical_line != NULL) {
  566.                 free(text->physical_line);
  567.         }
  568.  
  569.         if (text->utf8_data != NULL) {
  570.                 free(text->utf8_data);
  571.         }
  572. }
  573.  
  574.  
  575. nserror textplain_clone(const struct content *old, struct content **newc)
  576. {
  577.         const textplain_content *old_text = (textplain_content *) old;
  578.         textplain_content *text;
  579.         nserror error;
  580.         const char *data;
  581.         unsigned long size;
  582.  
  583.         text = calloc(1, sizeof(textplain_content));
  584.         if (text == NULL)
  585.                 return NSERROR_NOMEM;
  586.  
  587.         error = content__clone(old, &text->base);
  588.         if (error != NSERROR_OK) {
  589.                 content_destroy(&text->base);
  590.                 return error;
  591.         }
  592.  
  593.         /* Simply replay create/process/convert */
  594.         error = textplain_create_internal(text, old_text->encoding);
  595.         if (error != NSERROR_OK) {
  596.                 content_destroy(&text->base);
  597.                 return error;
  598.         }
  599.  
  600.         data = content__get_source_data(&text->base, &size);
  601.         if (size > 0) {
  602.                 if (textplain_process_data(&text->base, data, size) == false) {
  603.                         content_destroy(&text->base);
  604.                         return NSERROR_NOMEM;
  605.                 }
  606.         }
  607.  
  608.         if (old->status == CONTENT_STATUS_READY ||
  609.                         old->status == CONTENT_STATUS_DONE) {
  610.                 if (textplain_convert(&text->base) == false) {
  611.                         content_destroy(&text->base);
  612.                         return NSERROR_CLONE_FAILED;
  613.                 }
  614.         }
  615.  
  616.         return NSERROR_OK;
  617. }
  618.  
  619. content_type textplain_content_type(void)
  620. {
  621.         return CONTENT_TEXTPLAIN;
  622. }
  623.  
  624. /**
  625.  * Handle mouse tracking (including drags) in a TEXTPLAIN content window.
  626.  *
  627.  * \param  c      content of type textplain
  628.  * \param  bw     browser window
  629.  * \param  mouse  state of mouse buttons and modifier keys
  630.  * \param  x      coordinate of mouse
  631.  * \param  y      coordinate of mouse
  632.  */
  633.  
  634. void textplain_mouse_track(struct content *c, struct browser_window *bw,
  635.                 browser_mouse_state mouse, int x, int y)
  636. {
  637.         textplain_content *text = (textplain_content *) c;
  638.  
  639.         if (browser_window_get_drag_type(bw) == DRAGGING_SELECTION && !mouse) {
  640.                 int dir = -1;
  641.                 size_t idx;
  642.  
  643.                 if (selection_dragging_start(&text->sel))
  644.                         dir = 1;
  645.  
  646.                 idx = textplain_offset_from_coords(c, x, y, dir);
  647.                 selection_track(&text->sel, mouse, idx);
  648.  
  649.                 browser_window_set_drag_type(bw, DRAGGING_NONE, NULL);
  650.         }
  651.  
  652.         switch (browser_window_get_drag_type(bw)) {
  653.  
  654.                 case DRAGGING_SELECTION: {
  655.                         int dir = -1;
  656.                         size_t idx;
  657.  
  658.                         if (selection_dragging_start(&text->sel)) dir = 1;
  659.  
  660.                         idx = textplain_offset_from_coords(c, x, y, dir);
  661.                         selection_track(&text->sel, mouse, idx);
  662.                 }
  663.                 break;
  664.  
  665.                 default:
  666.                         textplain_mouse_action(c, bw, mouse, x, y);
  667.                         break;
  668.         }
  669. }
  670.  
  671.  
  672. /**
  673.  * Handle mouse clicks and movements in a TEXTPLAIN content window.
  674.  *
  675.  * \param  c      content of type textplain
  676.  * \param  bw     browser window
  677.  * \param  click  type of mouse click
  678.  * \param  x      coordinate of mouse
  679.  * \param  y      coordinate of mouse
  680.  */
  681.  
  682. void textplain_mouse_action(struct content *c, struct browser_window *bw,
  683.                 browser_mouse_state mouse, int x, int y)
  684. {
  685.         textplain_content *text = (textplain_content *) c;
  686.         browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT;
  687.         union content_msg_data msg_data;
  688.         const char *status = 0;
  689.         size_t idx;
  690.         int dir = 0;
  691.  
  692.         browser_window_set_drag_type(bw, DRAGGING_NONE, NULL);
  693.  
  694.         idx = textplain_offset_from_coords(c, x, y, dir);
  695.         if (selection_click(&text->sel, mouse, idx)) {
  696.  
  697.                 if (selection_dragging(&text->sel)) {
  698.                         browser_window_set_drag_type(bw,
  699.                                         DRAGGING_SELECTION, NULL);
  700.                         status = messages_get("Selecting");
  701.                 }
  702.  
  703.         } else {
  704.                 if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) {
  705.                         browser_window_page_drag_start(bw, x, y);
  706.                         pointer = BROWSER_POINTER_MOVE;
  707.                 }
  708.         }
  709.  
  710.         msg_data.explicit_status_text = status;
  711.         content_broadcast(c, CONTENT_MSG_STATUS, msg_data);
  712.  
  713.         msg_data.pointer = pointer;
  714.         content_broadcast(c, CONTENT_MSG_POINTER, msg_data);
  715. }
  716.  
  717.  
  718. /**
  719.  * Draw a CONTENT_TEXTPLAIN using the current set of plotters (plot).
  720.  *
  721.  * \param  c     content of type CONTENT_TEXTPLAIN
  722.  * \param  data  redraw data for this content redraw
  723.  * \param  clip  current clip region
  724.  * \param  ctx   current redraw context
  725.  * \return true if successful, false otherwise
  726.  *
  727.  * x, y, clip_[xy][01] are in target coordinates.
  728.  */
  729.  
  730. bool textplain_redraw(struct content *c, struct content_redraw_data *data,
  731.                 const struct rect *clip, const struct redraw_context *ctx)
  732. {
  733.         textplain_content *text = (textplain_content *) c;
  734.         struct browser_window *bw = text->bw;
  735.         const struct plotter_table *plot = ctx->plot;
  736.         char *utf8_data = text->utf8_data;
  737.         long lineno;
  738.         int x = data->x;
  739.         int y = data->y;
  740.         unsigned long line_count = text->physical_line_count;
  741.         float line_height = textplain_line_height();
  742.         float scaled_line_height = line_height * data->scale;
  743.         long line0 = (clip->y0 - y * data->scale) / scaled_line_height - 1;
  744.         long line1 = (clip->y1 - y * data->scale) / scaled_line_height + 1;
  745.         struct textplain_line *line = text->physical_line;
  746.         size_t length;
  747.         plot_style_t *plot_style_highlight;
  748.  
  749.         if (line0 < 0)
  750.                 line0 = 0;
  751.         if (line1 < 0)
  752.                 line1 = 0;
  753.         if (line_count < (unsigned long) line0)
  754.                 line0 = line_count;
  755.         if (line_count < (unsigned long) line1)
  756.                 line1 = line_count;
  757.         if (line1 < line0)
  758.                 line1 = line0;
  759.  
  760.         if (!plot->rectangle(clip->x0, clip->y0, clip->x1, clip->y1,
  761.                         plot_style_fill_white))
  762.                 return false;
  763.  
  764.         if (!line)
  765.                 return true;
  766.  
  767.         /* choose a suitable background colour for any highlighted text */
  768.         if ((data->background_colour & 0x808080) == 0x808080)
  769.                 plot_style_highlight = plot_style_fill_black;
  770.         else
  771.                 plot_style_highlight = plot_style_fill_white;
  772.  
  773.         /* Set up font plot style */
  774.         textplain_style.background = data->background_colour;
  775.  
  776.         x = (x + MARGIN) * data->scale;
  777.         y = (y + MARGIN) * data->scale;
  778.         for (lineno = line0; lineno != line1; lineno++) {
  779.                 const char *text_d = utf8_data + line[lineno].start;
  780.                 int tab_width = textplain_tab_width * data->scale;
  781.                 size_t offset = 0;
  782.                 int tx = x;
  783.  
  784.                 if (!tab_width) tab_width = 1;
  785.  
  786.                 length = line[lineno].length;
  787.                 if (!length)
  788.                         continue;
  789.  
  790.                 while (offset < length) {
  791.                         size_t next_offset = offset;
  792.                         int width;
  793.                         int ntx;
  794.  
  795.                         while (next_offset < length && text_d[next_offset] != '\t')
  796.                                 next_offset = utf8_next(text_d, length, next_offset);
  797.  
  798.                         if (!text_redraw(text_d + offset, next_offset - offset,
  799.                                         line[lineno].start + offset, 0,
  800.                                         &textplain_style,
  801.                                         tx, y + (lineno * scaled_line_height),
  802.                                         clip, line_height, data->scale, false,
  803.                                         (struct content *)text, &text->sel,
  804.                                         text->search, ctx))
  805.                                 return false;
  806.  
  807.                         if (next_offset >= length)
  808.                                 break;
  809.  
  810.                         /* locate end of string and align to next tab position */
  811.                         if (nsfont.font_width(&textplain_style, &text_d[offset],
  812.                                         next_offset - offset, &width))
  813.                                 tx += (int)(width * data->scale);
  814.  
  815.                         ntx = x + ((1 + (tx - x) / tab_width) * tab_width);
  816.  
  817.                         /* if the tab character lies within the selection, if any,
  818.                            then we must draw it as a filled rectangle so that it's
  819.                            consistent with background of the selected text */
  820.  
  821.                         if (bw) {
  822.                                 unsigned tab_ofst = line[lineno].start + next_offset;
  823.                                 struct selection *sel = &text->sel;
  824.                                 bool highlighted = false;
  825.  
  826.                                 if (selection_defined(sel)) {
  827.                                         unsigned start_idx, end_idx;
  828.                                         if (selection_highlighted(sel,
  829.                                                 tab_ofst, tab_ofst + 1,
  830.                                                 &start_idx, &end_idx))
  831.                                                 highlighted = true;
  832.                                 }
  833.  
  834.                                 if (!highlighted && (text->search != NULL)) {
  835.                                         unsigned start_idx, end_idx;
  836.                                         if (search_term_highlighted(c,
  837.                                                         tab_ofst, tab_ofst + 1,
  838.                                                         &start_idx, &end_idx,
  839.                                                         text->search))
  840.                                                 highlighted = true;
  841.                                 }
  842.  
  843.                                 if (highlighted) {
  844.                                         int sy = y + (lineno * scaled_line_height);
  845.                                         if (!plot->rectangle(tx, sy,
  846.                                                             ntx, sy + scaled_line_height,
  847.                                                             plot_style_highlight))
  848.                                                 return false;
  849.                                 }
  850.                         }
  851.  
  852.                         offset = next_offset + 1;
  853.                         tx = ntx;
  854.                 }
  855.         }
  856.  
  857.         return true;
  858. }
  859.  
  860.  
  861. /**
  862.  * Handle a window containing a CONTENT_TEXTPLAIN being opened.
  863.  */
  864.  
  865. void textplain_open(struct content *c, struct browser_window *bw,
  866.                 struct content *page, struct object_params *params)
  867. {
  868.         textplain_content *text = (textplain_content *) c;
  869.  
  870.         text->bw = bw;
  871.  
  872.         /* text selection */
  873.         selection_init(&text->sel, NULL);
  874. }
  875.  
  876.  
  877. /**
  878.  * Handle a window containing a CONTENT_TEXTPLAIN being closed.
  879.  */
  880.  
  881. void textplain_close(struct content *c)
  882. {
  883.         textplain_content *text = (textplain_content *) c;
  884.  
  885.         if (text->search != NULL)
  886.                 search_destroy_context(text->search);
  887.  
  888.         text->bw = NULL;
  889. }
  890.  
  891.  
  892. /**
  893.  * Return an textplain content's selection context
  894.  */
  895.  
  896. struct selection *textplain_get_selection(struct content *c)
  897. {
  898.         textplain_content *text = (textplain_content *) c;
  899.  
  900.         return &text->sel;
  901. }
  902.  
  903.  
  904. /**
  905.  * Set an TEXTPLAIN content's search context
  906.  *
  907.  * \param c     content of type text
  908.  * \param s     search context, or NULL if none
  909.  */
  910.  
  911. void textplain_set_search(struct content *c, struct search_context *s)
  912. {
  913.         textplain_content *text = (textplain_content *) c;
  914.  
  915.         text->search = s;
  916. }
  917.  
  918.  
  919. /**
  920.  * Return an TEXTPLAIN content's search context
  921.  *
  922.  * \param c     content of type text
  923.  * \return content's search context, or NULL if none
  924.  */
  925.  
  926. struct search_context *textplain_get_search(struct content *c)
  927. {
  928.         textplain_content *text = (textplain_content *) c;
  929.  
  930.         return text->search;
  931. }
  932.  
  933. /**
  934.  * Retrieve number of lines in content
  935.  *
  936.  * \param h  Content to retrieve line count from
  937.  * \return Number of lines
  938.  */
  939. unsigned long textplain_line_count(struct content *c)
  940. {
  941.         textplain_content *text = (textplain_content *) c;
  942.  
  943.         assert(c != NULL);
  944.  
  945.         return text->physical_line_count;
  946. }
  947.  
  948. /**
  949.  * Retrieve the size (in bytes) of text data
  950.  *
  951.  * \param h  Content to retrieve size of
  952.  * \return Size, in bytes, of data
  953.  */
  954. size_t textplain_size(struct content *c)
  955. {
  956.         textplain_content *text = (textplain_content *) c;
  957.  
  958.         assert(c != NULL);
  959.  
  960.         return text->utf8_data_size;
  961. }
  962.  
  963. /**
  964.  * Return byte offset within UTF8 textplain content, given the co-ordinates
  965.  * of a point within a textplain content. 'dir' specifies the direction in
  966.  * which to search (-1 = above-left, +1 = below-right) if the co-ordinates are not
  967.  * contained within a line.
  968.  *
  969.  * \param  h     content of type CONTENT_TEXTPLAIN
  970.  * \param  x     x ordinate of point
  971.  * \param  y     y ordinate of point
  972.  * \param  dir   direction of search if not within line
  973.  * \return byte offset of character containing (or nearest to) point
  974.  */
  975.  
  976. size_t textplain_offset_from_coords(struct content *c, int x, int y, int dir)
  977. {
  978.         textplain_content *textc = (textplain_content *) c;
  979.         float line_height = textplain_line_height();
  980.         struct textplain_line *line;
  981.         const char *text;
  982.         unsigned nlines;
  983.         size_t length;
  984.         int idx;
  985.  
  986.         assert(c != NULL);
  987.  
  988.         y = (int)((float)(y - MARGIN) / line_height);
  989.         x -= MARGIN;
  990.  
  991.         nlines = textc->physical_line_count;
  992.         if (!nlines)
  993.                 return 0;
  994.  
  995.         if (y <= 0) y = 0;
  996.         else if ((unsigned)y >= nlines)
  997.                 y = nlines - 1;
  998.  
  999.         line = &textc->physical_line[y];
  1000.         text = textc->utf8_data + line->start;
  1001.         length = line->length;
  1002.         idx = 0;
  1003.  
  1004.         while (x > 0) {
  1005.                 size_t next_offset = 0;
  1006.                 int width = INT_MAX;
  1007.  
  1008.                 while (next_offset < length && text[next_offset] != '\t')
  1009.                         next_offset = utf8_next(text, length, next_offset);
  1010.  
  1011.                 if (next_offset < length)
  1012.                         nsfont.font_width(&textplain_style, text, next_offset, &width);
  1013.  
  1014.                 if (x <= width) {
  1015.                         int pixel_offset;
  1016.                         size_t char_offset;
  1017.  
  1018.                         nsfont.font_position_in_string(&textplain_style,
  1019.                                 text, next_offset, x,
  1020.                                 &char_offset, &pixel_offset);
  1021.  
  1022.                         idx += char_offset;
  1023.                         break;
  1024.                 }
  1025.  
  1026.                 x -= width;
  1027.                 length -= next_offset;
  1028.                 text += next_offset;
  1029.                 idx += next_offset;
  1030.  
  1031.                 /* check if it's within the tab */
  1032.                 width = textplain_tab_width - (width % textplain_tab_width);
  1033.                 if (x <= width) break;
  1034.  
  1035.                 x -= width;
  1036.                 length--;
  1037.                 text++;
  1038.                 idx++;
  1039.         }
  1040.  
  1041.         return line->start + idx;
  1042. }
  1043.  
  1044.  
  1045. /**
  1046.  * Given a byte offset within the text, return the line number
  1047.  * of the line containing that offset (or -1 if offset invalid)
  1048.  *
  1049.  * \param  c       content of type CONTENT_TEXTPLAIN
  1050.  * \param  offset  byte offset within textual representation
  1051.  * \return line number, or -1 if offset invalid (larger than size)
  1052.  */
  1053.  
  1054. int textplain_find_line(struct content *c, unsigned offset)
  1055. {
  1056.         textplain_content *text = (textplain_content *) c;
  1057.         struct textplain_line *line;
  1058.         int nlines;
  1059.         int lineno = 0;
  1060.  
  1061.         assert(c != NULL);
  1062.  
  1063.         line = text->physical_line;
  1064.         nlines = text->physical_line_count;
  1065.  
  1066.         if (offset > text->utf8_data_size)
  1067.                 return -1;
  1068.  
  1069. /* \todo - implement binary search here */
  1070.         while (lineno < nlines && line[lineno].start < offset)
  1071.                 lineno++;
  1072.         if (line[lineno].start > offset)
  1073.                 lineno--;
  1074.  
  1075.         return lineno;
  1076. }
  1077.  
  1078.  
  1079. /**
  1080.  * Convert a character offset within a line of text into the
  1081.  * horizontal co-ordinate, taking into account the font being
  1082.  * used and any tabs in the text
  1083.  *
  1084.  * \param  text    line of text
  1085.  * \param  offset  char offset within text
  1086.  * \param  length  line length
  1087.  * \return x ordinate
  1088.  */
  1089.  
  1090. int textplain_coord_from_offset(const char *text, size_t offset, size_t length)
  1091. {
  1092.         int x = 0;
  1093.  
  1094.         while (offset > 0) {
  1095.                 size_t next_offset = 0;
  1096.                 int tx;
  1097.  
  1098.                 while (next_offset < offset && text[next_offset] != '\t')
  1099.                         next_offset = utf8_next(text, length, next_offset);
  1100.  
  1101.                 nsfont.font_width(&textplain_style, text, next_offset, &tx);
  1102.                 x += tx;
  1103.  
  1104.                 if (next_offset >= offset)
  1105.                         break;
  1106.  
  1107.                 /* align to next tab boundary */
  1108.                 next_offset++;
  1109.                 x = (1 + (x / textplain_tab_width)) * textplain_tab_width;
  1110.                 offset -= next_offset;
  1111.                 text += next_offset;
  1112.                 length -= next_offset;
  1113.         }
  1114.  
  1115.         return x;
  1116. }
  1117.  
  1118.  
  1119. /**
  1120.  * Given a range of byte offsets within a UTF8 textplain content,
  1121.  * return a box that fully encloses the text
  1122.  *
  1123.  * \param  h      content of type CONTENT_TEXTPLAIN
  1124.  * \param  start  byte offset of start of text range
  1125.  * \param  end    byte offset of end
  1126.  * \param  r      rectangle to be completed
  1127.  */
  1128.  
  1129. void textplain_coords_from_range(struct content *c, unsigned start,
  1130.                 unsigned end, struct rect *r)
  1131. {
  1132.         textplain_content *text = (textplain_content *) c;
  1133.         float line_height = textplain_line_height();
  1134.         char *utf8_data;
  1135.         struct textplain_line *line;
  1136.         unsigned lineno = 0;
  1137.         unsigned nlines;
  1138.  
  1139.         assert(c != NULL);
  1140.         assert(start <= end);
  1141.         assert(end <= text->utf8_data_size);
  1142.  
  1143.         utf8_data = text->utf8_data;
  1144.         nlines = text->physical_line_count;
  1145.         line = text->physical_line;
  1146.  
  1147.         /* find start */
  1148.         lineno = textplain_find_line(c, start);
  1149.  
  1150.         r->y0 = (int)(MARGIN + lineno * line_height);
  1151.  
  1152.         if (lineno + 1 <= nlines || line[lineno + 1].start >= end) {
  1153.                 /* \todo - it may actually be more efficient just to run
  1154.                         forwards most of the time */
  1155.  
  1156.                 /* find end */
  1157.                 lineno = textplain_find_line(c, end);
  1158.  
  1159.                 r->x0 = 0;
  1160.                 r->x1 = text->formatted_width;
  1161.         }
  1162.         else {
  1163.                 /* single line */
  1164.                 const char *text = utf8_data + line[lineno].start;
  1165.  
  1166.                 r->x0 = textplain_coord_from_offset(text, start - line[lineno].start,
  1167.                                 line[lineno].length);
  1168.  
  1169.                 r->x1 = textplain_coord_from_offset(text, end - line[lineno].start,
  1170.                                 line[lineno].length);
  1171.         }
  1172.  
  1173.         r->y1 = (int)(MARGIN + (lineno + 1) * line_height);
  1174. }
  1175.  
  1176.  
  1177. /**
  1178.  * Return a pointer to the requested line of text.
  1179.  *
  1180.  * \param  h                content of type CONTENT_TEXTPLAIN
  1181.  * \param  lineno           line number
  1182.  * \param  poffset          receives byte offset of line start within text
  1183.  * \param  plen             receives length of returned line
  1184.  * \return pointer to text, or NULL if invalid line number
  1185.  */
  1186.  
  1187. char *textplain_get_line(struct content *c, unsigned lineno,
  1188.                 size_t *poffset, size_t *plen)
  1189. {
  1190.         textplain_content *text = (textplain_content *) c;
  1191.         struct textplain_line *line;
  1192.  
  1193.         assert(c != NULL);
  1194.  
  1195.         if (lineno >= text->physical_line_count)
  1196.                 return NULL;
  1197.         line = &text->physical_line[lineno];
  1198.  
  1199.         *poffset = line->start;
  1200.         *plen = line->length;
  1201.         return text->utf8_data + line->start;
  1202. }
  1203.  
  1204.  
  1205. /**
  1206.  * Return a pointer to the raw UTF-8 data, as opposed to the reformatted
  1207.  * text to fit the window width. Thus only hard newlines are preserved
  1208.  * in the saved/copied text of a selection.
  1209.  *
  1210.  * \param  h                content of type CONTENT_TEXTPLAIN
  1211.  * \param  start            starting byte offset within UTF-8 text
  1212.  * \param  end              ending byte offset
  1213.  * \param  plen             receives validated length
  1214.  * \return pointer to text, or NULL if no text
  1215.  */
  1216.  
  1217. char *textplain_get_raw_data(struct content *c, unsigned start, unsigned end,
  1218.                 size_t *plen)
  1219. {
  1220.         textplain_content *text = (textplain_content *) c;
  1221.         size_t utf8_size;
  1222.  
  1223.         assert(c != NULL);
  1224.  
  1225.         utf8_size = text->utf8_data_size;
  1226.  
  1227.         /* any text at all? */
  1228.         if (!utf8_size) return NULL;
  1229.  
  1230.         /* clamp to valid offset range */
  1231.         if (start >= utf8_size) start = utf8_size;
  1232.         if (end >= utf8_size) end = utf8_size;
  1233.  
  1234.         *plen = end - start;
  1235.  
  1236.         return text->utf8_data + start;
  1237. }
  1238.  
  1239. /**
  1240.  * Calculate the line height, in pixels
  1241.  *
  1242.  * \return Line height, in pixels
  1243.  */
  1244. float textplain_line_height(void)
  1245. {
  1246.         /* Size is in points, so convert to pixels.
  1247.          * Then use a constant line height of 1.2 x font size.
  1248.          */
  1249.         return FIXTOFLT(FDIV((FMUL(FLTTOFIX(1.2), FMUL(nscss_screen_dpi,
  1250.                 INTTOFIX((textplain_style.size / FONT_SIZE_SCALE))))), F_72));
  1251. }
  1252.  
  1253. /**
  1254.  * Get the browser window containing a textplain content
  1255.  *
  1256.  * \param  c    text/plain content
  1257.  * \return the browser window
  1258.  */
  1259. struct browser_window *textplain_get_browser_window(struct content *c)
  1260. {
  1261.         textplain_content *text = (textplain_content *) c;
  1262.  
  1263.         assert(c != NULL);
  1264.         assert(c->handler == &textplain_content_handler);
  1265.  
  1266.         return text->bw;
  1267. }
  1268.  
  1269.