summaryrefslogtreecommitdiffstats
path: root/src/GridText.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 17:56:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 17:56:50 +0000
commit8e9f8f2d0dcdddec35091ddbbdc79650763ec922 (patch)
tree4f88ed317d44fb4e5d9ab77e565bf1d910a8f560 /src/GridText.c
parentInitial commit. (diff)
downloadlynx-upstream.tar.xz
lynx-upstream.zip
Adding upstream version 2.9.0dev.6.upstream/2.9.0dev.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/GridText.c')
-rw-r--r--src/GridText.c14954
1 files changed, 14954 insertions, 0 deletions
diff --git a/src/GridText.c b/src/GridText.c
new file mode 100644
index 0000000..46cbb51
--- /dev/null
+++ b/src/GridText.c
@@ -0,0 +1,14954 @@
+/*
+ * $LynxId: GridText.c,v 1.325 2020/02/25 01:41:00 tom Exp $
+ *
+ * Character grid hypertext object
+ * ===============================
+ */
+
+#include <HTUtils.h>
+#include <HTString.h>
+#include <HTAccess.h>
+#include <HTAnchor.h>
+#include <HTParse.h>
+#include <HTTP.h>
+#include <HTAlert.h>
+#include <HTCJK.h>
+#include <HTFile.h>
+#include <UCDefs.h>
+#include <UCAux.h>
+#include <HText.h>
+
+#include <assert.h>
+
+#include <GridText.h>
+#include <LYCurses.h>
+#include <LYUtils.h>
+#include <LYStrings.h>
+#include <LYStructs.h>
+#include <LYGlobalDefs.h>
+#include <LYGetFile.h>
+#include <LYClean.h>
+#include <LYMail.h>
+#include <LYList.h>
+#include <LYCharSets.h>
+#include <LYCharUtils.h> /* LYUCTranslateBack... */
+#include <UCMap.h>
+#include <LYEdit.h>
+#include <LYPrint.h>
+#include <LYPrettySrc.h>
+#include <LYSearch.h>
+#include <TRSTable.h>
+#include <LYHistory.h>
+#ifdef EXP_CHARTRANS_AUTOSWITCH
+#include <UCAuto.h>
+#endif /* EXP_CHARTRANS_AUTOSWITCH */
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+#ifdef USE_COLOR_STYLE
+#include <AttrList.h>
+#include <LYHash.h>
+#include <LYStyle.h>
+#endif
+
+#ifdef EXP_WCWIDTH_SUPPORT
+# ifdef HAVE_WCWIDTH
+# ifdef HAVE_WCHAR_H
+# include <wchar.h>
+# endif
+# else
+# include <wcwidth.h>
+# define wcwidth(n) mk_wcwidth(n)
+# endif
+#endif
+
+#include <LYJustify.h>
+
+#define is_CJK2(b) (IS_CJK_TTY && is8bits(UCH(b)))
+
+#ifdef USE_CURSES_PADS
+# define DISPLAY_COLS (LYwideLines ? MAX_COLS : LYcols)
+# define WRAP_COLS(text) ((text)->stbl ? \
+ (LYtableCols <= 0 \
+ ? DISPLAY_COLS \
+ : (LYtableCols * LYcols)/12) - LYbarWidth \
+ : LYcolLimit)
+#else
+# define DISPLAY_COLS LYcols
+# define WRAP_COLS(text) LYcolLimit
+#endif
+
+#define FirstHTLine(text) ((text)->last_line->next)
+#define LastHTLine(text) ((text)->last_line)
+
+static void HText_trimHightext(HText *text, int final, int stop_before);
+
+#define IS_UTF_FIRST(ch) (text->T.output_utf8 && \
+ (UCH((ch))&0xc0) == 0xc0)
+
+#define IS_UTF_EXTRA(ch) (text->T.output_utf8 && \
+ (UCH((ch))&0xc0) == 0x80)
+
+#define IS_UTF8_EXTRA(ch) (!(text && text->T.output_utf8) || \
+ !is8bits(ch) || \
+ (UCH(line->data[i] & 0xc0) == 0xc0))
+
+/* a test in compact form: how many extra UTF-8 chars after initial? - kw */
+#define UTF8_XNEGLEN(c) (c&0xC0? 0 :c&32? 1 :c&16? 2 :c&8? 3 :c&4? 4 :c&2? 5:0)
+#define UTF_XLEN(c) UTF8_XNEGLEN(((char)~(c)))
+
+#ifdef KANJI_CODE_OVERRIDE
+HTkcode last_kcode = NOKANJI; /* 1997/11/14 (Fri) 09:09:26 */
+#endif
+
+#undef CHAR_WIDTH
+
+#ifdef CJK_EX
+#define CHAR_WIDTH 6
+#else
+#define CHAR_WIDTH 1
+#endif
+
+/* Exports
+*/
+HText *HTMainText = NULL; /* Equivalent of main window */
+HTParentAnchor *HTMainAnchor = NULL; /* Anchor for HTMainText */
+
+const char *HTAppName = LYNX_NAME; /* Application name */
+const char *HTAppVersion = LYNX_VERSION; /* Application version */
+
+static int HTFormNumber = 0;
+static int HTFormFields = 0;
+static char *HTCurSelectGroup = NULL; /* Form select group name */
+static int HTCurSelectGroupCharset = -1; /* ... and name's charset */
+int HTCurSelectGroupType = F_RADIO_TYPE; /* Group type */
+char *HTCurSelectGroupSize = NULL; /* Length of select */
+static char *HTCurSelectedOptionValue = NULL; /* Select choice */
+
+const char *checked_box = "[X]";
+const char *unchecked_box = "[ ]";
+const char *checked_radio = "(*)";
+const char *unchecked_radio = "( )";
+
+static BOOLEAN underline_on = FALSE;
+static BOOLEAN bold_on = FALSE;
+
+#ifdef USE_SOURCE_CACHE
+int LYCacheSource = SOURCE_CACHE_NONE;
+int LYCacheSourceForAborted = SOURCE_CACHE_FOR_ABORTED_DROP;
+#endif
+
+#ifdef USE_SCROLLBAR
+BOOLEAN LYShowScrollbar = FALSE;
+BOOLEAN LYsb_arrow = TRUE;
+int LYsb_begin = -1;
+int LYsb_end = -1;
+#endif
+
+#ifndef VMS /* VMS has a better way - right? - kw */
+#define CHECK_FREE_MEM
+#endif
+
+#ifdef CHECK_FREE_MEM
+static void *LY_check_calloc(size_t nmemb, size_t size);
+
+#define LY_CALLOC LY_check_calloc
+#else
+ /* using the regular calloc */
+#define LY_CALLOC calloc
+#endif
+
+/*
+ * The HTPool.data[] array has to align the same as malloc() would, to make the
+ * ALLOC_POOL scheme portable. For many platforms, that is the same as the
+ * number of bytes in a pointer. It may be larger, e.g., on machines which
+ * have more stringent requirements for floating point. 32-bits are plenty for
+ * representing styles, but we may need 64-bit or 128-bit alignment.
+ *
+ * The real issue is that performance is degraded if the alignment is not met,
+ * and some platforms such as Tru64 generate lots of warning messages.
+ */
+#ifndef ALIGN_SIZE
+#define ALIGN_SIZE sizeof(double)
+#endif
+
+#define BITS_DIR 2
+#define BITS_POS 14
+
+#define MASK_DIR ((1U << BITS_DIR) - 1)
+#define CAST_DIR(n) ((MASK_DIR) & (unsigned)(n))
+
+#define MASK_POS ((1U << BITS_POS) - 1)
+#define CAST_POS(n) ((MASK_POS) & (unsigned)(n))
+
+typedef struct {
+ unsigned sc_direction:BITS_DIR; /* on or off */
+ unsigned sc_horizpos:BITS_POS; /* horizontal position of this change */
+ unsigned sc_style:16; /* which style to change to */
+} HTStyleChange;
+
+#if defined(USE_COLOR_STYLE)
+#define MAX_STYLES_ON_LINE 64
+ /* buffers used when current line is being aggregated, in split_line() */
+static HTStyleChange stylechanges_buffers[2][MAX_STYLES_ON_LINE];
+#endif
+
+typedef HTStyleChange pool_data;
+
+enum {
+ POOL_SIZE = ((8192
+ - 4 * sizeof(void *)
+ - sizeof(struct _HTPool *)
+ - sizeof(int))
+ / sizeof(pool_data))
+};
+
+typedef struct _HTPool {
+ pool_data data[POOL_SIZE];
+ struct _HTPool *prev;
+ unsigned used;
+} HTPool;
+
+/************************************************************************
+These are generic macros for any pools (provided those structures have the
+same members as HTPool). Pools are used for allocation of groups of
+objects of the same type T. Pools are represented as a list of structures of
+type P (called pool chunks here). Structure P has an array of N objects of
+type T named 'data' (the number N in the array can be chosen arbitrary),
+pointer to the previous pool chunk named 'prev', and the number of used items
+in that pool chunk named 'used'. Here is a definition of the structure P:
+ struct P
+ {
+ T data[N];
+ struct P* prev;
+ int used;
+ };
+ It's recommended that sizeof(P) be memory page size minus 32 in order malloc'd
+chunks to fit in machine page size.
+ Allocation of 'n' items in the pool is implemented by incrementing member
+'used' by 'n' if (used+n <= N), or malloc a new pool chunk and
+allocating 'n' items in that new chunk. It's the task of the programmer to
+assert that 'n' is <= N. Only entire pool may be freed - this limitation makes
+allocation algorithms trivial and fast - so the use of pools is limited to
+objects that are freed in batch, that are not deallocated not in the batch, and
+not reallocated.
+ Pools greatly reduce memory fragmentation and memory allocation/deallocation
+speed due to the simple algorithms used. Due to the fact that memory is
+'allocated' in array, alignment overhead is minimal. Allocating strings in a
+pool provided their length will never exceed N and is much smaller than N seems
+to be very efficient.
+ [Several types of memory-hungry objects are stored in the pool now: styles,
+lines, anchors, and FormInfo. Arrays of HTStyleChange are stored as is,
+other objects are stored using a cast.]
+
+ Pool is referenced by the pointer to the last chunk that contains free slots.
+Functions that allocate memory in the pool update that pointer if needed.
+There are 3 functions - POOL_NEW, POOL_FREE, and ALLOC_IN_POOL.
+
+ - VH
+
+*************************************************************************/
+
+#define POOLallocstyles(ptr, n) ptr = ALLOC_IN_POOL(&HTMainText->pool, (unsigned) ((n) * sizeof(pool_data)))
+#define POOLallocHTLine(ptr, size) ptr = (HTLine*) ALLOC_IN_POOL(&HTMainText->pool, (unsigned) LINE_SIZE(size))
+#define POOLallocstring(ptr, len) ptr = (char*) ALLOC_IN_POOL(&HTMainText->pool, (unsigned) ((len) + 1))
+#define POOLtypecalloc(T, ptr) ptr = (T*) ALLOC_IN_POOL(&HTMainText->pool, (unsigned) sizeof(T))
+
+/**************************************************************************/
+/*
+ * Allocates 'n' items in the pool of type 'HTPool' pointed by 'poolptr'.
+ * Returns a pointer to the "allocated" memory if successful.
+ * Updates 'poolptr' if necessary.
+ */
+static void *ALLOC_IN_POOL(HTPool ** ppoolptr, unsigned request)
+{
+ HTPool *pool = *ppoolptr;
+ pool_data *ptr;
+ unsigned n;
+ unsigned j;
+
+ if (!pool) {
+ outofmem(__FILE__, "ALLOC_IN_POOL");
+ } else {
+ n = request;
+ if (n == 0)
+ n = 1;
+ j = (n % ALIGN_SIZE);
+ if (j != 0)
+ n += (unsigned) (ALIGN_SIZE - j);
+ n /= sizeof(pool_data);
+
+ if (POOL_SIZE >= (pool->used + n)) {
+ ptr = pool->data + pool->used;
+ pool->used += n;
+ } else {
+ HTPool *newpool = (HTPool *) LY_CALLOC((size_t) 1, sizeof(HTPool));
+
+ if (!newpool) {
+ outofmem(__FILE__, "ALLOC_IN_POOL");
+ } else {
+ newpool->prev = pool;
+ newpool->used = n;
+ ptr = newpool->data;
+ *ppoolptr = newpool;
+ }
+ }
+ }
+ return ptr;
+}
+
+/*
+ * Returns a pointer to initialized pool of type 'HTPool', or NULL if fails.
+ */
+static HTPool *POOL_NEW(void)
+{
+ HTPool *poolptr = (HTPool *) LY_CALLOC((size_t) 1, sizeof(HTPool));
+
+ if (poolptr) {
+ poolptr->prev = NULL;
+ poolptr->used = 0;
+ }
+ return poolptr;
+}
+
+/*
+ * Frees a pool of type 'HTPool' pointed by poolptr.
+ */
+static void POOL_FREE(HTPool * poolptr)
+{
+ HTPool *cur = poolptr;
+ HTPool *prev;
+
+ while (cur) {
+ prev = cur->prev;
+ free(cur);
+ cur = prev;
+ }
+}
+
+/**************************************************************************/
+/**************************************************************************/
+
+typedef struct _line {
+ struct _line *next;
+ struct _line *prev;
+ unsigned short offset; /* Implicit initial spaces */
+ unsigned short size; /* Number of characters */
+#if defined(USE_COLOR_STYLE)
+ HTStyleChange *styles;
+ unsigned short numstyles;
+#endif
+ char data[1]; /* Space for terminator at least! */
+} HTLine;
+
+ /* Allow for terminator */
+#define LINE_SIZE(size) (sizeof(HTLine) + (size_t)(size))
+
+#ifndef HTLINE_NOT_IN_POOL
+#define HTLINE_NOT_IN_POOL 0 /* debug with this set to 1 */
+#endif
+
+#if HTLINE_NOT_IN_POOL
+#define allocHTLine(ptr, size) { ptr = (HTLine *)calloc(1, LINE_SIZE(size)); }
+#define freeHTLine(self, ptr) { \
+ if (ptr && ptr != TEMP_LINE(self, 0) && ptr != TEMP_LINE(self, 1)) \
+ FREE(ptr); \
+ }
+#else
+#define allocHTLine(ptr, size) POOLallocHTLine(ptr, size)
+#define freeHTLine(self, ptr) {}
+#endif
+
+/*
+ * Last line buffer; the second is used in split_line(). Not in pool!
+ * We cannot wrap in middle of multibyte sequences, so allocate 2 extra
+ * for a workspace. This is stored in the HText, to prevent confusion
+ * between different documents. Note also that it is declared with an
+ * HTLine at the beginning so pointers will be properly aligned.
+ */
+typedef struct {
+ HTLine base;
+ char data[MAX_LINE + 2];
+} HTLineTemp;
+
+#define TEMP_LINE(p,n) ((HTLine *)&(p->temp_line[n]))
+
+typedef struct _TextAnchor {
+ struct _TextAnchor *next;
+ struct _TextAnchor *prev; /* www_user_search only! */
+ int sgml_offset; /* used for updating position after reparsing */
+ int number; /* For user interface */
+ int show_number; /* For user interface (unique-urls) */
+ int line_num; /* Place in document */
+ short line_pos; /* Bytes/chars - extent too */
+ short extent; /* (see HText_trimHightext) */
+ BOOL show_anchor; /* Show the anchor? */
+ BOOL inUnderline; /* context is underlined */
+ BOOL expansion_anch; /* TEXTAREA edit new anchor */
+ char link_type; /* Normal, internal, or form? */
+ FormInfo *input_field; /* Info for form links */
+ HiliteList lites;
+
+ HTChildAnchor *anchor;
+} TextAnchor;
+
+typedef struct {
+ char *name; /* ID value of TAB */
+ int column; /* Zero-based column value */
+} HTTabID;
+
+typedef enum {
+ S_text,
+ S_esc,
+ S_dollar,
+ S_paren,
+ S_nonascii_text,
+ S_dollar_paren,
+ S_jisx0201_text
+} eGridState; /* Escape sequence? */
+
+#ifdef USE_TH_JP_AUTO_DETECT
+typedef enum { /* Detected Kanji code */
+ DET_SJIS,
+ DET_EUC,
+ DET_NOTYET,
+ DET_MIXED
+} eDetectedKCode;
+
+typedef enum {
+ SJIS_state_neutral,
+ SJIS_state_in_kanji,
+ SJIS_state_has_bad_code
+} eSJIS_status;
+
+typedef enum {
+ EUC_state_neutral,
+ EUC_state_in_kanji,
+ EUC_state_in_kana,
+ EUC_state_has_bad_code
+} eEUC_status;
+#endif
+
+/* Notes on struct _HText:
+ * next_line is valid if stale is false.
+ * top_of_screen line means the line at the top of the screen
+ * or just under the title if there is one.
+ */
+struct _HText {
+ HTParentAnchor *node_anchor;
+
+ HTLine *last_line;
+ HTLineTemp temp_line[2];
+ int Lines; /* Number of them */
+ TextAnchor *first_anchor; /* double-linked on demand */
+ TextAnchor *last_anchor;
+ TextAnchor *last_anchor_before_stbl;
+ TextAnchor *last_anchor_before_split;
+ HTList *forms; /* also linked internally */
+ int last_anchor_number; /* user number */
+ BOOL source; /* Is the text source? */
+ BOOL toolbar; /* Toolbar set? */
+ HTList *tabs; /* TAB IDs */
+ HTList *hidden_links; /* Content-less links ... */
+ int hiddenlinkflag; /* ... and how to treat them */
+ BOOL no_cache; /* Always refresh? */
+ char LastChar; /* For absorbing white space */
+
+/* For Internal use: */
+ HTStyle *style; /* Current style */
+ int display_on_the_fly; /* Lines left */
+ int top_of_screen; /* Line number */
+ HTLine *top_of_screen_line; /* Top */
+ HTLine *next_line; /* Bottom + 1 */
+ unsigned permissible_split; /* in last line */
+ BOOL in_line_1; /* of paragraph */
+ BOOL stale; /* Must refresh */
+ BOOL page_has_target; /* has target on screen */
+ BOOL has_utf8; /* has utf-8 on screen or line */
+ BOOL had_utf8; /* had utf-8 when last displayed */
+ int next_number; /* next a->number value */
+#ifdef DISP_PARTIAL
+ int first_lineno_last_disp_partial;
+ int last_lineno_last_disp_partial;
+#endif
+ STable_info *stbl;
+ HTList *enclosed_stbl;
+
+ HTkcode kcode; /* Kanji code? */
+ HTkcode specified_kcode; /* Specified Kanji code */
+#ifdef USE_TH_JP_AUTO_DETECT
+ eDetectedKCode detected_kcode;
+ eSJIS_status SJIS_status;
+ eEUC_status EUC_status;
+#endif
+ eGridState state; /* Escape sequence? */
+ int kanji_buf; /* Lead multibyte */
+ int in_sjis; /* SJIS flag */
+ int halted; /* emergency halt */
+
+ BOOL have_8bit_chars; /* Any non-ASCII chars? */
+ LYUCcharset *UCI; /* node_anchor UCInfo */
+ int UCLYhndl; /* charset we are fed */
+ UCTransParams T;
+
+ HTStream *target; /* Output stream */
+ HTStreamClass targetClass; /* Output routines */
+
+ HTPool *pool; /* this HText memory pool */
+
+#ifdef USE_SOURCE_CACHE
+ /*
+ * Parse settings when this HText was generated.
+ */
+ BOOL clickable_images;
+ BOOL pseudo_inline_alts;
+ BOOL verbose_img;
+ BOOL raw_mode;
+ BOOL historical_comments;
+ BOOL minimal_comments;
+ BOOL soft_dquotes;
+ short old_dtd;
+ short keypad_mode;
+ short disp_lines; /* Screen size */
+ short disp_cols; /* Used for reports only */
+#endif
+};
+
+/* exported */
+void *HText_pool_calloc(HText *text, unsigned size)
+{
+ return (void *) ALLOC_IN_POOL(&text->pool, size);
+}
+
+static void HText_AddHiddenLink(HText *text, TextAnchor *textanchor);
+
+#ifdef USE_JUSTIFY_ELTS
+BOOL can_justify_here;
+BOOL can_justify_here_saved;
+
+BOOL can_justify_this_line; /* =FALSE if line contains form objects */
+int wait_for_this_stacked_elt; /* -1 if can justify contents of the
+
+ element on the op of stack. If positive - specifies minimal stack depth
+ plus 1 at which we can justify element (can be MAX_LINE+2 if
+ ok_justify ==FALSE or in psrcview. */
+BOOL form_in_htext; /*to indicate that we are in form (since HTML_FORM is
+
+ not stacked in the HTML.c */
+BOOL in_DT = FALSE;
+
+#ifdef DEBUG_JUSTIFY
+BOOL can_justify_stack_depth; /* can be 0 or 1 if all code is correct */
+#endif
+
+typedef struct {
+ int byte_len; /*length in bytes */
+ int cell_len; /*length in cells */
+} ht_run_info;
+
+static int justify_start_position; /* this is an index of char from which
+
+ justification can start (eg after "* " preceding <li> text) */
+
+static int ht_num_runs; /*the number of runs filled */
+static ht_run_info ht_runs[MAX_LINE];
+static BOOL this_line_was_split;
+static TextAnchor *last_anchor_of_previous_line;
+static BOOL have_raw_nbsps = FALSE;
+
+void ht_justify_cleanup(void)
+{
+ wait_for_this_stacked_elt = !ok_justify
+# ifdef USE_PRETTYSRC
+ || psrc_view
+# endif
+ ? 30000 /*MAX_NESTING */ + 2 /*some unreachable value */
+ : -1;
+ can_justify_here = TRUE;
+ can_justify_this_line = TRUE;
+ form_in_htext = FALSE;
+
+ last_anchor_of_previous_line = NULL;
+ this_line_was_split = FALSE;
+ in_DT = FALSE;
+ have_raw_nbsps = FALSE;
+}
+
+void mark_justify_start_position(void *text)
+{
+ if (text && ((HText *) text)->last_line)
+ justify_start_position = ((HText *) text)->last_line->size;
+}
+
+#define REALLY_CAN_JUSTIFY(text) ( (wait_for_this_stacked_elt<0) && \
+ ( text->style->alignment == HT_LEFT || \
+ text->style->alignment == HT_JUSTIFY) && \
+ !IS_CJK_TTY && !in_DT && \
+ can_justify_here && can_justify_this_line && !form_in_htext )
+
+#endif /* USE_JUSTIFY_ELTS */
+
+/*
+ * Boring static variable used for moving cursor across
+ */
+#define UNDERSCORES(n) \
+ ((n) >= MAX_LINE ? underscore_string : &underscore_string[(MAX_LINE-1)] - (n))
+
+static char underscore_string[MAX_LINE + 1];
+char star_string[MAX_LINE + 1];
+
+static int ctrl_chars_on_this_line = 0; /* num of ctrl chars in current line */
+static int utfxtra_on_this_line = 0; /* num of UTF-8 extra bytes in line,
+
+ they *also* count as ctrl chars. */
+#ifdef EXP_WCWIDTH_SUPPORT
+static int utfxtracells_on_this_line = 0; /* num of UTF-8 extra cells in line */
+static int utfextracells(const char *s);
+#endif
+#ifdef WIDEC_CURSES
+# ifdef EXP_WCWIDTH_SUPPORT /* TODO: support for !WIDEC_CURSES */
+#define UTFXTRA_ON_THIS_LINE utfxtracells_on_this_line
+# else
+#define UTFXTRA_ON_THIS_LINE 0
+# endif
+#else
+#define UTFXTRA_ON_THIS_LINE utfxtra_on_this_line
+#endif
+
+static HTStyle default_style =
+{0, NULL, "(Unstyled)", 0, NULL, "",
+ (HTFont) 0, 1, HT_BLACK, 0, 0,
+ 0, 0, 0, HT_LEFT, 1, 0, 0,
+ NO, NO, 0, 0, 0};
+
+static HTList *loaded_texts = NULL; /* A list of all those in memory */
+HTList *search_queries = NULL; /* isindex and whereis queries */
+
+#ifdef LY_FIND_LEAKS
+static void free_all_texts(void);
+#endif
+
+static BOOL HText_TrueEmptyLine(HTLine *line, HText *text, int IgnoreSpaces);
+
+static int HText_TrueLineSize(HTLine *line, HText *text, int IgnoreSpaces);
+
+#ifdef CHECK_FREE_MEM
+
+/*
+ * text->halted = 1: have set fake 'Z' and output a message
+ * 2: next time when HText_appendCharacter is called
+ * it will append *** MEMORY EXHAUSTED ***, then set
+ * to 3.
+ * 3: normal text output will be suppressed (but not anchors,
+ * form fields etc.)
+ */
+static void HText_halt(void)
+{
+ if (HTFormNumber > 0)
+ HText_DisableCurrentForm();
+ if (!HTMainText)
+ return;
+ if (HTMainText->halted < 2)
+ HTMainText->halted = 2;
+}
+
+#define MIN_NEEDED_MEM 5000
+
+/*
+ * Check whether factor*min(bytes,MIN_NEEDED_MEM) is available,
+ * or bytes if factor is 0.
+ * MIN_NEEDED_MEM and factor together represent a security margin,
+ * to take account of all the memory allocations where we don't check
+ * and of buffers which may be emptied before HTCheckForInterupt()
+ * is (maybe) called and other things happening, with some chance of
+ * success.
+ * This just tries to malloc() the to-be-checked-for amount of memory,
+ * which might make the situation worse depending how allocation works.
+ * There should be a better way... - kw
+ */
+static BOOL mem_is_avail(int factor, size_t bytes)
+{
+ void *p;
+
+ if (bytes < MIN_NEEDED_MEM && factor > 0)
+ bytes = MIN_NEEDED_MEM;
+ if (factor == 0)
+ factor = 1;
+ p = malloc((size_t) factor * bytes);
+ if (p) {
+ FREE(p);
+ return YES;
+ } else {
+ return NO;
+ }
+}
+
+/*
+ * Replacement for calloc which checks for "enough" free memory
+ * (with some security margins) and tries various recovery actions
+ * if deemed necessary. - kw
+ */
+static void *LY_check_calloc(size_t nmemb, size_t size)
+{
+ int i, n;
+
+ if (mem_is_avail(4, nmemb * size)) {
+ return (calloc(nmemb, size));
+ }
+ n = HTList_count(loaded_texts);
+ for (i = n - 1; i > 0; i--) {
+ HText *t = (HText *) HTList_objectAt(loaded_texts, i);
+
+ CTRACE((tfp,
+ "\nBUG *** Emergency freeing document %d/%d for '%s'%s!\n",
+ i + 1, n,
+ ((t && t->node_anchor &&
+ t->node_anchor->address) ?
+ t->node_anchor->address : "unknown anchor"),
+ ((t && t->node_anchor &&
+ t->node_anchor->post_data) ?
+ " with POST data" : "")));
+ HTList_removeObjectAt(loaded_texts, i);
+ HText_free(t);
+ if (mem_is_avail(4, nmemb * size)) {
+ return (calloc(nmemb, size));
+ }
+ }
+ LYFakeZap(YES);
+ if (!HTMainText || HTMainText->halted <= 1) {
+ if (!mem_is_avail(2, nmemb * size)) {
+ HText_halt();
+ if (mem_is_avail(0, (size_t) 700)) {
+ HTAlert(gettext("Memory exhausted, display interrupted!"));
+ }
+ } else {
+ if ((!HTMainText || HTMainText->halted == 0) &&
+ mem_is_avail(0, (size_t) 700)) {
+ HTAlert(gettext("Memory exhausted, will interrupt transfer!"));
+ if (HTMainText)
+ HTMainText->halted = 1;
+ }
+ }
+ }
+ return (calloc(nmemb, size));
+}
+
+#endif /* CHECK_FREE_MEM */
+
+#ifdef USE_COLOR_STYLE
+/*
+ * Color style information is stored with the multibyte-character offset into
+ * the string at which the style would apply. Compute the corresponding column
+ * so we can compare it with the updated column value after writing strings
+ * with curses.
+ *
+ * The offsets count multibyte characters. Other parts of the code assume each
+ * character uses one cell, but some CJK (or UTF-8) codes use two cells. We
+ * need to know the number of cells.
+ */
+static int StyleToCols(HText *text, HTLine *line, int nstyle)
+{
+ int result = line->offset; /* this much is spaces one byte/cell */
+ int nchars = line->styles[nstyle].sc_horizpos;
+ char *data = line->data;
+ char *last = line->size + data;
+ int utf_extra;
+
+ while (nchars > 0 && data < last) {
+ if (IsSpecialAttrChar(*data) && *data != LY_SOFT_NEWLINE) {
+ ++data;
+ } else {
+ utf_extra = (int) utf8_length(text->T.output_utf8, data);
+ if (utf_extra++) {
+ result += LYstrExtent(data, utf_extra, 2);
+ data += utf_extra;
+ } else if (is_CJK2(*data)) {
+ data += 2;
+ result += 2;
+ } else {
+ ++data;
+ ++result;
+ }
+ --nchars;
+ }
+ }
+
+ return result;
+}
+#endif
+
+/*
+ * Clear highlight information for a given anchor
+ * (text was allocated in the pool).
+ */
+static void LYClearHiText(TextAnchor *a)
+{
+ FREE(a->lites.hl_info);
+
+ a->lites.hl_base.hl_text = NULL;
+ a->lites.hl_len = 0;
+}
+
+#define LYFreeHiText(a) FREE((a)->lites.hl_info)
+
+/*
+ * Set the initial highlight information for a given anchor.
+ */
+static void LYSetHiText(TextAnchor *a,
+ const char *text,
+ unsigned len)
+{
+ if (text != NULL) {
+ POOLallocstring(a->lites.hl_base.hl_text, len + 1);
+ memcpy(a->lites.hl_base.hl_text, text, (size_t) len);
+ *(a->lites.hl_base.hl_text + len) = '\0';
+
+ a->lites.hl_len = 1;
+ }
+}
+
+/*
+ * Add highlight information for the next line of a anchor.
+ */
+static void LYAddHiText(TextAnchor *a,
+ const char *text,
+ int x)
+{
+ HiliteInfo *have = a->lites.hl_info;
+ size_t need = (unsigned) (a->lites.hl_len - 1);
+ size_t want;
+
+ a->lites.hl_len = (short) (a->lites.hl_len + 1);
+ want = (size_t) (a->lites.hl_len) * sizeof(HiliteInfo);
+ if (have != NULL) {
+ have = (HiliteInfo *) realloc(have, want);
+ } else {
+ have = (HiliteInfo *) malloc(want);
+ }
+ a->lites.hl_info = have;
+
+ POOLallocstring(have[need].hl_text, strlen(text) + 1);
+ strcpy(have[need].hl_text, text);
+ have[need].hl_x = (short) x;
+}
+
+/*
+ * Return an offset to skip leading blanks in the highlighted link. That is
+ * needed to avoid having the color-style paint the leading blanks.
+ */
+#ifdef USE_COLOR_STYLE
+static int LYAdjHiTextPos(TextAnchor *a, int count)
+{
+ char *result;
+
+ if (count >= a->lites.hl_len)
+ result = NULL;
+ else if (count > 0)
+ result = a->lites.hl_info[count - 1].hl_text;
+ else
+ result = a->lites.hl_base.hl_text;
+
+ return (result != 0) ? (int) (LYSkipBlanks(result) - result) : 0;
+}
+
+#else
+#define LYAdjHiTextPos(a,count) 0
+#endif
+
+/*
+ * Get the highlight text, counting from zero.
+ */
+static char *LYGetHiTextStr(TextAnchor *a, int count)
+{
+ char *result;
+
+ if (count >= a->lites.hl_len)
+ result = NULL;
+ else if (count > 0)
+ result = a->lites.hl_info[count - 1].hl_text;
+ else
+ result = a->lites.hl_base.hl_text;
+ result += LYAdjHiTextPos(a, count);
+ return result;
+}
+
+/*
+ * Get the X-ordinate at which to draw the corresponding highlight-text
+ */
+static int LYGetHiTextPos(TextAnchor *a, int count)
+{
+ int result;
+
+ if (count >= a->lites.hl_len)
+ result = -1;
+ else if (count > 0)
+ result = a->lites.hl_info[count - 1].hl_x;
+ else
+ result = a->line_pos;
+ result += LYAdjHiTextPos(a, count);
+ return result;
+}
+
+/*
+ * Copy highlighting information from anchor 'b' to 'a'.
+ */
+static void LYCopyHiText(TextAnchor *a, TextAnchor *b)
+{
+ int count;
+ char *s;
+
+ LYClearHiText(a);
+ for (count = 0;; ++count) {
+ if ((s = LYGetHiTextStr(b, count)) == NULL)
+ break;
+ if (count == 0) {
+ LYSetHiText(a, s, (unsigned) strlen(s));
+ } else {
+ LYAddHiText(a, s, LYGetHiTextPos(b, count));
+ }
+ }
+}
+
+static void HText_getChartransInfo(HText *me)
+{
+ me->UCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor, UCT_STAGE_HTEXT);
+ if (me->UCLYhndl < 0) {
+ int chndl = current_char_set;
+
+ HTAnchor_setUCInfoStage(me->node_anchor, chndl,
+ UCT_STAGE_HTEXT, UCT_SETBY_STRUCTURED);
+ me->UCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor,
+ UCT_STAGE_HTEXT);
+ }
+ me->UCI = HTAnchor_getUCInfoStage(me->node_anchor, UCT_STAGE_HTEXT);
+}
+
+static void PerFormInfo_free(PerFormInfo * form)
+{
+ if (form) {
+ FREE(form->data.submit_action);
+ FREE(form->data.submit_enctype);
+ FREE(form->data.submit_title);
+ FREE(form->accept_cs);
+ FREE(form->thisacceptcs);
+ FREE(form);
+ }
+}
+
+static void free_form_fields(FormInfo * input_field)
+{
+ /*
+ * Free form fields.
+ */
+ if (input_field->type == F_OPTION_LIST_TYPE &&
+ input_field->select_list != NULL) {
+ /*
+ * Free off option lists if present.
+ * It should always be present for F_OPTION_LIST_TYPE
+ * unless we had invalid markup which prevented
+ * HText_setLastOptionValue from finishing its job
+ * and left the input field in an insane state. - kw
+ */
+ OptionType *optptr = input_field->select_list;
+ OptionType *tmp;
+
+ while (optptr) {
+ tmp = optptr;
+ optptr = tmp->next;
+ FREE(tmp->name);
+ FREE(tmp->cp_submit_value);
+ FREE(tmp);
+ }
+ input_field->select_list = NULL;
+ /*
+ * Don't free the value field on option
+ * lists since it points to a option value
+ * same for orig value.
+ */
+ input_field->value = NULL;
+ input_field->orig_value = NULL;
+ input_field->cp_submit_value = NULL;
+ input_field->orig_submit_value = NULL;
+ } else {
+ FREE(input_field->value);
+ FREE(input_field->orig_value);
+ FREE(input_field->cp_submit_value);
+ FREE(input_field->orig_submit_value);
+ }
+ FREE(input_field->name);
+ FREE(input_field->submit_action);
+ FREE(input_field->submit_enctype);
+ FREE(input_field->submit_title);
+
+ FREE(input_field->accept_cs);
+}
+
+static void FormList_delete(HTList *forms)
+{
+ HTList *cur = forms;
+ PerFormInfo *form;
+
+ while ((form = (PerFormInfo *) HTList_nextObject(cur)) != NULL)
+ PerFormInfo_free(form);
+ HTList_delete(forms);
+}
+
+#ifdef DISP_PARTIAL
+static void ResetPartialLinenos(HText *text)
+{
+ if (text != 0) {
+ text->first_lineno_last_disp_partial = -1;
+ text->last_lineno_last_disp_partial = -1;
+ }
+}
+#endif
+
+/* Creation Method
+ * ---------------
+ */
+HText *HText_new(HTParentAnchor *anchor)
+{
+#if defined(VMS) && defined(VAXC) && !defined(__DECC)
+#include <lib$routines.h>
+ int status, VMType = 3, VMTotal;
+#endif /* VMS && VAXC && !__DECC */
+ HTLine *line = NULL;
+ HText *self = typecalloc(HText);
+
+ if (!self)
+ outofmem(__FILE__, "HText_New");
+
+ CTRACE((tfp, "GridText: start HText_new\n"));
+
+#if defined(VMS) && defined (VAXC) && !defined(__DECC)
+ status = lib$stat_vm(&VMType, &VMTotal);
+ CTRACE((tfp, "GridText: VMTotal = %d\n", VMTotal));
+#endif /* VMS && VAXC && !__DECC */
+
+ /*
+ * If the previously shown text had UTF-8 characters on screen,
+ * remember this in the newly created object. Do this now, before
+ * the previous object may become invalid. - kw
+ */
+ if (HTMainText) {
+ self->had_utf8 = HTMainText->has_utf8;
+ HTMainText->has_utf8 = NO;
+ }
+
+ if (!loaded_texts) {
+ loaded_texts = HTList_new();
+#ifdef LY_FIND_LEAKS
+ atexit(free_all_texts);
+#endif
+ }
+
+ /*
+ * Links between anchors & documents are a 1-1 relationship. If
+ * an anchor is already linked to a document we didn't call
+ * HTuncache_current_document(), so we'll check now
+ * and free it before reloading. - Dick Wesseling (ftu@fi.ruu.nl)
+ */
+ if (anchor->document) {
+ HTList_removeObject(loaded_texts, anchor->document);
+ CTRACE((tfp, "GridText: Auto-uncaching\n"));
+
+ HTAnchor_delete_links(anchor);
+ ((HText *) anchor->document)->node_anchor = NULL;
+ HText_free((HText *) anchor->document);
+ anchor->document = NULL;
+ }
+
+ HTList_addObject(loaded_texts, self);
+#if defined(VMS) && defined(VAXC) && !defined(__DECC)
+ while (HTList_count(loaded_texts) > HTCacheSize &&
+ VMTotal > HTVirtualMemorySize)
+#else
+ if (HTList_count(loaded_texts) > HTCacheSize)
+#endif /* VMS && VAXC && !__DECC */
+ {
+ CTRACE((tfp, "GridText: Freeing off cached doc.\n"));
+ HText_free((HText *) HTList_removeFirstObject(loaded_texts));
+#if defined(VMS) && defined (VAXC) && !defined(__DECC)
+ status = lib$stat_vm(&VMType, &VMTotal);
+ CTRACE((tfp, "GridText: VMTotal reduced to %d\n", VMTotal));
+#endif /* VMS && VAXC && !__DECC */
+ }
+
+ self->pool = POOL_NEW();
+ if (!self->pool)
+ outofmem(__FILE__, "HText_New");
+
+ line = self->last_line = TEMP_LINE(self, 0);
+ line->next = line->prev = line;
+ line->offset = line->size = 0;
+ line->data[line->size] = '\0';
+#ifdef USE_COLOR_STYLE
+ line->numstyles = 0;
+ line->styles = stylechanges_buffers[0];
+#endif
+ self->Lines = 0;
+ self->first_anchor = self->last_anchor = NULL;
+ self->last_anchor_before_split = NULL;
+ self->style = &default_style;
+ self->top_of_screen = 0;
+ self->node_anchor = anchor;
+ self->last_anchor_number = 0; /* Numbering of them for references */
+ self->stale = YES;
+ self->toolbar = NO;
+ self->tabs = NULL;
+ self->next_number = 1;
+#ifdef USE_SOURCE_CACHE
+ /*
+ * Remember the parse settings.
+ */
+ self->clickable_images = clickable_images;
+ self->pseudo_inline_alts = pseudo_inline_alts;
+ self->verbose_img = verbose_img;
+ self->raw_mode = LYUseDefaultRawMode;
+ self->historical_comments = historical_comments;
+ self->minimal_comments = minimal_comments;
+ self->soft_dquotes = soft_dquotes;
+ self->old_dtd = (short) Old_DTD;
+ self->keypad_mode = (short) keypad_mode;
+ self->disp_lines = (short) LYlines;
+ self->disp_cols = (short) DISPLAY_COLS;
+#endif
+ /*
+ * If we are going to render the List Page, always merge in hidden
+ * links to get the numbering consistent if form fields are numbered
+ * and show up as hidden links in the list of links.
+ * If we are going to render a bookmark file, also always merge in
+ * hidden links, to get the link numbers consistent with the counting
+ * in remove_bookmark_link(). Normally a bookmark file shouldn't
+ * contain any entries with empty titles, but it might happen. - kw
+ */
+ if (anchor->bookmark ||
+ LYIsUIPage3(anchor->address, UIP_LIST_PAGE, 0) ||
+ LYIsUIPage3(anchor->address, UIP_ADDRLIST_PAGE, 0))
+ self->hiddenlinkflag = HIDDENLINKS_MERGE;
+ else
+ self->hiddenlinkflag = LYHiddenLinks;
+ self->hidden_links = NULL;
+ self->no_cache = (BOOLEAN) ((anchor->no_cache ||
+ anchor->post_data)
+ ? YES
+ : NO);
+ self->LastChar = '\0';
+
+#ifndef USE_PRETTYSRC
+ if (HTOutputFormat == WWW_SOURCE)
+ self->source = YES;
+ else
+ self->source = NO;
+#else
+ /* mark_htext_as_source == TRUE if we are parsing html file (and psrc_view
+ * is set temporary to false at creation time)
+ *
+ * psrc_view == TRUE if source of the text produced by some lynx module
+ * (like ftp browsers) is requested). - VH
+ */
+ self->source = (BOOL) (LYpsrc
+ ? mark_htext_as_source || psrc_view
+ : HTOutputFormat == WWW_SOURCE);
+ mark_htext_as_source = FALSE;
+#endif
+ HTAnchor_setDocument(anchor, (HyperDoc *) self);
+ HTFormNumber = 0; /* no forms started yet */
+ HTMainText = self;
+ HTMainAnchor = anchor;
+ self->display_on_the_fly = 0;
+ self->kcode = NOKANJI;
+ self->specified_kcode = NOKANJI;
+#ifdef USE_TH_JP_AUTO_DETECT
+ self->detected_kcode = DET_NOTYET;
+ self->SJIS_status = SJIS_state_neutral;
+ self->EUC_status = EUC_state_neutral;
+#endif
+ self->state = S_text;
+ self->kanji_buf = '\0';
+ self->in_sjis = 0;
+ self->have_8bit_chars = NO;
+ HText_getChartransInfo(self);
+ UCSetTransParams(&self->T,
+ self->UCLYhndl, self->UCI,
+ current_char_set,
+ &LYCharSet_UC[current_char_set]);
+
+ /*
+ * Check the kcode setting if the anchor has a charset element. -FM
+ */
+ HText_setKcode(self, anchor->charset,
+ HTAnchor_getUCInfoStage(anchor, UCT_STAGE_HTEXT));
+
+ /*
+ * Check to see if our underline and star_string need initialization
+ * if the underline is not filled with dots.
+ */
+ if (underscore_string[0] != '.') {
+ /*
+ * Create an array of dots for the UNDERSCORES macro. -FM
+ */
+ memset(underscore_string, '.', (size_t) (MAX_LINE - 1));
+ underscore_string[(MAX_LINE - 1)] = '\0';
+ underscore_string[MAX_LINE] = '\0';
+ /*
+ * Create an array of underscores for the STARS macro. -FM
+ */
+ memset(star_string, '_', (size_t) (MAX_LINE - 1));
+ star_string[(MAX_LINE - 1)] = '\0';
+ star_string[MAX_LINE] = '\0';
+ }
+
+ underline_on = FALSE; /* reset */
+ bold_on = FALSE;
+
+#ifdef DISP_PARTIAL
+ /*
+ * By this function we create HText object
+ * so we may start displaying the document while downloading. - LP
+ */
+ if (display_partial_flag) {
+ display_partial = TRUE; /* enable HTDisplayPartial() */
+ NumOfLines_partial = 0; /* initialize */
+ }
+
+ /*
+ * These two fields should only be set to valid line numbers
+ * by calls of display_page during partial displaying. This
+ * is just so that the FIRST display_page AFTER that can avoid
+ * repainting the same lines on the screen. - kw
+ */
+ ResetPartialLinenos(self);
+#endif
+
+#ifdef USE_JUSTIFY_ELTS
+ ht_justify_cleanup();
+#endif
+ return self;
+}
+
+/* Creation Method 2
+ * ---------------
+ *
+ * Stream is assumed open and left open.
+ */
+HText *HText_new2(HTParentAnchor *anchor,
+ HTStream *stream)
+{
+ HText *result = HText_new(anchor);
+
+ if (stream) {
+ result->target = stream;
+ result->targetClass = *stream->isa; /* copy action procedures */
+ }
+ return result;
+}
+
+/* Free Entire Text
+ * ----------------
+ */
+void HText_free(HText *self)
+{
+ if (!self)
+ return;
+
+#if HTLINE_NOT_IN_POOL
+ {
+ HTLine *f = FirstHTLine(self);
+ HTLine *l = self->last_line;
+
+ while (l != f) { /* Free off line array */
+ self->last_line = l->prev;
+ freeHTLine(self, l);
+ l = self->last_line;
+ }
+ freeHTLine(self, f);
+ }
+#endif
+
+ while (self->first_anchor) { /* Free off anchor array */
+ TextAnchor *l = self->first_anchor;
+
+ self->first_anchor = l->next;
+
+ if (l->link_type == INPUT_ANCHOR && l->input_field) {
+ free_form_fields(l->input_field);
+ }
+
+ LYFreeHiText(l);
+ }
+ FormList_delete(self->forms);
+
+ /*
+ * Free the tabs list. -FM
+ */
+ if (self->tabs) {
+ HTTabID *Tab = NULL;
+ HTList *cur = self->tabs;
+
+ while (NULL != (Tab = (HTTabID *) HTList_nextObject(cur))) {
+ FREE(Tab->name);
+ FREE(Tab);
+ }
+ HTList_delete(self->tabs);
+ self->tabs = NULL;
+ }
+
+ /*
+ * Free the hidden links list. -FM
+ */
+ if (self->hidden_links) {
+ LYFreeStringList(self->hidden_links);
+ self->hidden_links = NULL;
+ }
+
+ /*
+ * Invoke HTAnchor_delete() to free the node_anchor
+ * if it is not a destination of other links. -FM
+ */
+ if (self->node_anchor) {
+ HTAnchor_resetUCInfoStage(self->node_anchor, -1, UCT_STAGE_STRUCTURED,
+ UCT_SETBY_NONE);
+ HTAnchor_resetUCInfoStage(self->node_anchor, -1, UCT_STAGE_HTEXT,
+ UCT_SETBY_NONE);
+#ifdef USE_SOURCE_CACHE
+ /* Remove source cache files and chunks always, even if the
+ * HTAnchor_delete call does not actually remove the anchor.
+ * Keeping them would just be a waste of space - they won't
+ * be used any more after the anchor has been disassociated
+ * from a HText structure. - kw
+ */
+ HTAnchor_clearSourceCache(self->node_anchor);
+#endif
+
+ HTAnchor_delete_links(self->node_anchor);
+
+ HTAnchor_setDocument(self->node_anchor, (HyperDoc *) 0);
+
+ if (HTAnchor_delete(self->node_anchor->parent))
+ /*
+ * Make sure HTMainAnchor won't point
+ * to an invalid structure. - KW
+ */
+ HTMainAnchor = NULL;
+ }
+
+ POOL_FREE(self->pool);
+ FREE(self);
+}
+
+/* Display Methods
+ * ---------------
+ */
+
+/* Output a line
+ * -------------
+ */
+static int display_line(HTLine *line,
+ HText *text,
+ int scrline GCC_UNUSED,
+ const char *target GCC_UNUSED)
+{
+ register int i, j;
+ char buffer[7];
+ char *data;
+ size_t utf_extra = 0;
+ char LastDisplayChar = ' ';
+
+#ifdef USE_COLOR_STYLE
+ int current_style = 0;
+
+#define inunderline NO
+#define inbold NO
+#else
+ BOOL inbold = NO, inunderline = NO;
+#endif
+#if defined(SHOW_WHEREIS_TARGETS) && !defined(USE_COLOR_STYLE)
+ const char *cp_tgt;
+ int i_start_tgt = 0, i_after_tgt;
+ int HitOffset, LenNeeded;
+ BOOL intarget = NO;
+
+#else
+#define intarget NO
+#endif /* SHOW_WHEREIS_TARGETS && !USE_COLOR_STYLE */
+
+#if !(defined(NCURSES_VERSION) || defined(WIDEC_CURSES))
+ text->has_utf8 = NO; /* use as per-line flag, except with ncurses */
+#endif
+
+#if defined(WIDEC_CURSES)
+ /*
+ * FIXME: this should not be necessary, but in some wide-character pages
+ * the output line wraps, foiling our attempt to just use newlines to
+ * advance to the next page.
+ */
+ LYmove(scrline + (no_title ? 0 : TITLE_LINES) - 1, 0);
+#endif
+
+ /*
+ * Set up the multibyte character buffer,
+ * and clear the line to which we will be
+ * writing.
+ */
+ buffer[0] = buffer[1] = buffer[2] = '\0';
+ LYclrtoeol();
+
+ /*
+ * Add offset, making sure that we do not
+ * go over the COLS limit on the display.
+ */
+ j = (int) line->offset;
+ if (j >= DISPLAY_COLS)
+ j = DISPLAY_COLS - 1;
+#ifdef USE_SLANG
+ SLsmg_forward(j);
+ i = j;
+#else
+#ifdef USE_COLOR_STYLE
+ if (line->size == 0)
+ i = j;
+ else
+#endif
+ for (i = 0; i < j; i++)
+ LYaddch(' ');
+#endif /* USE_SLANG */
+
+ /*
+ * Add the data, making sure that we do not
+ * go over the COLS limit on the display.
+ */
+ data = line->data;
+ i++;
+
+#ifndef USE_COLOR_STYLE
+#if defined(SHOW_WHEREIS_TARGETS)
+ /*
+ * If the target is on this line, it will be emphasized.
+ */
+ i_after_tgt = i;
+ if (target) {
+ cp_tgt = LYno_attr_mb_strstr(data,
+ target,
+ text->T.output_utf8, YES,
+ &HitOffset,
+ &LenNeeded);
+ if (cp_tgt) {
+ if (((int) line->offset + LenNeeded) >= DISPLAY_COLS) {
+ cp_tgt = NULL;
+ } else {
+ text->page_has_target = YES;
+ i_start_tgt = i + HitOffset;
+ i_after_tgt = i + LenNeeded;
+ }
+ }
+ } else {
+ cp_tgt = NULL;
+ }
+#endif /* SHOW_WHEREIS_TARGETS */
+#endif /* USE_COLOR_STYLE */
+
+ while ((i <= DISPLAY_COLS) && ((buffer[0] = *data) != '\0')) {
+
+#ifndef USE_COLOR_STYLE
+#if defined(SHOW_WHEREIS_TARGETS)
+ if (cp_tgt && i >= i_after_tgt) {
+ if (intarget) {
+ cp_tgt = LYno_attr_mb_strstr(data,
+ target,
+ text->T.output_utf8, YES,
+ &HitOffset,
+ &LenNeeded);
+ if (cp_tgt) {
+ i_start_tgt = i + HitOffset;
+ i_after_tgt = i + LenNeeded;
+ }
+ if (!cp_tgt || i_start_tgt != i) {
+ LYstopTargetEmphasis();
+ intarget = NO;
+ if (inbold)
+ lynx_start_bold();
+ if (inunderline)
+ lynx_start_underline();
+ }
+ }
+ }
+#endif /* SHOW_WHEREIS_TARGETS */
+#endif /* USE_COLOR_STYLE */
+
+ data++;
+
+#if defined(USE_COLOR_STYLE)
+#define CStyle line->styles[current_style]
+
+ while (current_style < line->numstyles &&
+ i >= (int) (CStyle.sc_horizpos + line->offset + 1)) {
+ LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction);
+ current_style++;
+ }
+#endif
+ switch (buffer[0]) {
+
+#ifndef USE_COLOR_STYLE
+ case LY_UNDERLINE_START_CHAR:
+ if (dump_output_immediately && use_underscore) {
+ LYaddch('_');
+ i++;
+ } else {
+ inunderline = YES;
+ if (!intarget) {
+#if defined(PDCURSES)
+ if (LYShowColor == SHOW_COLOR_NEVER)
+ lynx_start_bold();
+ else
+ lynx_start_underline();
+#else
+ lynx_start_underline();
+#endif /* PDCURSES */
+ }
+ }
+ break;
+
+ case LY_UNDERLINE_END_CHAR:
+ if (dump_output_immediately && use_underscore) {
+ LYaddch('_');
+ i++;
+ } else {
+ inunderline = NO;
+ if (!intarget) {
+#if defined(PDCURSES)
+ if (LYShowColor == SHOW_COLOR_NEVER)
+ lynx_stop_bold();
+ else
+ lynx_stop_underline();
+#else
+ lynx_stop_underline();
+#endif /* PDCURSES */
+ }
+ }
+ break;
+
+ case LY_BOLD_START_CHAR:
+ inbold = YES;
+ if (!intarget)
+ lynx_start_bold();
+ break;
+
+ case LY_BOLD_END_CHAR:
+ inbold = NO;
+ if (!intarget)
+ lynx_stop_bold();
+ break;
+
+#endif /* !USE_COLOR_STYLE */
+ case LY_SOFT_NEWLINE:
+ if (!dump_output_immediately) {
+ LYaddch('+');
+ i++;
+#if defined(SHOW_WHEREIS_TARGETS) && !defined(USE_COLOR_STYLE)
+ i_after_tgt++;
+#endif
+ }
+ break;
+
+ case LY_SOFT_HYPHEN:
+ if (*data != '\0' ||
+ isspace(UCH(LastDisplayChar)) ||
+ LastDisplayChar == '-') {
+ /*
+ * Ignore the soft hyphen if it is not the last character in
+ * the line. Also ignore it if is first character following
+ * the margin, or if it is preceded by a white character (we
+ * loaded 'M' into LastDisplayChar if it was a multibyte
+ * character) or hyphen, though it should have been excluded by
+ * HText_appendCharacter() or by split_line() in those cases.
+ * -FM
+ */
+ break;
+ } else {
+ /*
+ * Make it a hard hyphen and fall through. -FM
+ */
+ buffer[0] = '-';
+ }
+ /* FALLTHRU */
+
+ default:
+#ifndef USE_COLOR_STYLE
+#if defined(SHOW_WHEREIS_TARGETS)
+ if (!intarget && cp_tgt && i >= i_start_tgt) {
+ /*
+ * Start the emphasis.
+ */
+ if (data > cp_tgt) {
+ LYstartTargetEmphasis();
+ intarget = YES;
+ }
+ }
+#endif /* SHOW_WHEREIS_TARGETS */
+#endif /* USE_COLOR_STYLE */
+ if (text->T.output_utf8 && is8bits(buffer[0])) {
+ text->has_utf8 = YES;
+ utf_extra = utf8_length(text->T.output_utf8, data - 1);
+ LastDisplayChar = 'M';
+ }
+ if (utf_extra) {
+ LYStrNCpy(&buffer[1], data, utf_extra);
+ LYaddstr(buffer);
+ buffer[1] = '\0';
+ data += utf_extra;
+ utf_extra = 0;
+ } else if (is_CJK2(buffer[0])) {
+ /*
+ * For CJK strings, by Masanobu Kimura.
+ */
+ if (i <= DISPLAY_COLS) {
+ buffer[1] = *data;
+ buffer[2] = '\0';
+ data++;
+ i++;
+ LYaddstr(buffer);
+ buffer[1] = '\0';
+ /*
+ * For now, load 'M' into LastDisplayChar, but we should
+ * check whether it's white and if so, use ' '. I don't
+ * know if there actually are white CJK characters, and
+ * we're loading ' ' for multibyte spacing characters in
+ * this code set, but this will become an issue when the
+ * development code set's multibyte character handling is
+ * used. -FM
+ */
+ LastDisplayChar = 'M';
+#ifndef USE_SLANG
+ {
+ int y, x;
+
+ getyx(LYwin, y, x);
+ (void) y;
+ if (x >= DISPLAY_COLS || x == 0)
+ break;
+ }
+#endif
+ }
+ } else {
+ LYaddstr(buffer);
+ LastDisplayChar = buffer[0];
+ }
+ i++;
+ } /* end of switch */
+ } /* end of while */
+
+#if !(defined(NCURSES_VERSION) || defined(WIDEC_CURSES))
+ if (text->has_utf8) {
+ LYtouchline(scrline);
+ text->has_utf8 = NO; /* we had some, but have dealt with it. */
+ }
+#endif
+ /*
+ * Add the return.
+ */
+ LYaddch('\n');
+
+#if defined(SHOW_WHEREIS_TARGETS) && !defined(USE_COLOR_STYLE)
+ if (intarget)
+ LYstopTargetEmphasis();
+#else
+#undef intarget
+#endif /* SHOW_WHEREIS_TARGETS && !USE_COLOR_STYLE */
+#ifndef USE_COLOR_STYLE
+ lynx_stop_underline();
+ lynx_stop_bold();
+#else
+ while (current_style < line->numstyles) {
+ LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction);
+ current_style++;
+ }
+#undef CStyle
+#endif
+ return (0);
+}
+
+/* Output the title line
+ * ---------------------
+ */
+static void display_title(HText *text)
+{
+ char *title = NULL;
+ char percent[40];
+ unsigned char *tmp = NULL;
+ int i = 0, j = 0;
+ int limit;
+
+#ifdef USE_COLOR_STYLE
+ int toolbar = 0;
+#endif
+
+ /*
+ * Make sure we have a text structure. -FM
+ */
+ if (!text)
+ return;
+
+ lynx_start_title_color();
+#ifdef USE_COLOR_STYLE
+/* turn the TITLE style on */
+ if (last_colorattr_ptr > 0) {
+ LynxChangeStyle(s_title, STACK_ON);
+ } else {
+ LynxChangeStyle(s_title, ABS_ON);
+ }
+#endif /* USE_COLOR_STYLE */
+
+ /*
+ * Load the title field. -FM
+ */
+ StrAllocCopy(title,
+ (HTAnchor_title(text->node_anchor) ?
+ HTAnchor_title(text->node_anchor) : " ")); /* "" -> " " */
+ LYReduceBlanks(title);
+
+ /*
+ * Generate the page indicator (percent) string.
+ */
+ limit = LYscreenWidth();
+ if (limit < 10) {
+ percent[0] = '\0';
+ } else if ((display_lines) <= 0 && LYlines > 0 &&
+ text->top_of_screen <= 99999 && text->Lines <= 999999) {
+ sprintf(percent, gettext(" (l%d of %d)"),
+ text->top_of_screen, text->Lines);
+ } else if ((text->Lines >= display_lines) && (display_lines > 0)) {
+ int total_pages = ((text->Lines + display_lines)
+ / display_lines);
+ int start_of_last_page = ((text->Lines <= display_lines)
+ ? 0
+ : (text->Lines - display_lines));
+
+ sprintf(percent, gettext(" (p%d of %d)"),
+ ((text->top_of_screen > start_of_last_page)
+ ? total_pages
+ : ((text->top_of_screen + display_lines) / (display_lines))),
+ total_pages);
+ } else {
+ percent[0] = '\0';
+ }
+
+ /* Update the terminal-emulator title */
+ if (update_term_title) {
+ CTRACE((tfp, "update_term_title:%s\n", title));
+ fprintf(stderr, "\033]0;%s%sLynx\007", title, *title ? " - " : "");
+ fflush(stderr);
+ }
+
+ /*
+ * Generate and display the title string, with page indicator
+ * if appropriate, preceded by the toolbar token if appropriate,
+ * and truncated if necessary. -FM & KW
+ */
+ if (IS_CJK_TTY) {
+ if (*title &&
+ (tmp = typecallocn(unsigned char, (strlen(title) * 2 + 256)))) {
+ if (kanji_code == EUC) {
+ TO_EUC((unsigned char *) title, tmp);
+ } else if (kanji_code == SJIS) {
+ TO_SJIS((unsigned char *) title, tmp);
+ } else {
+ for (i = 0, j = 0; title[i]; i++) {
+ if (title[i] != CH_ESC) { /* S/390 -- gil -- 1487 */
+ tmp[j++] = UCH(title[i]);
+ }
+ }
+ tmp[j] = '\0';
+ }
+ StrAllocCopy(title, (const char *) tmp);
+ FREE(tmp);
+ }
+ }
+ LYmove(0, 0);
+ LYclrtoeol();
+#if defined(SH_EX) && defined(KANJI_CODE_OVERRIDE)
+ LYaddstr(str_kcode(last_kcode));
+#endif
+ if (HText_hasToolbar(text)) {
+ LYaddch('#');
+#ifdef USE_COLOR_STYLE
+ toolbar = 1;
+#endif
+ }
+#ifdef USE_COLOR_STYLE
+ if (s_forw_backw != NOSTYLE && (nhist || nhist_extra > 1)) {
+ chtype c = nhist ? ACS_LARROW : ' ';
+
+ /* turn the FORWBACKW.ARROW style on */
+ LynxChangeStyle(s_forw_backw, STACK_ON);
+ if (nhist) {
+ LYaddch(c);
+ LYaddch(c);
+ LYaddch(c);
+ } else
+ LYmove(0, 3 + toolbar);
+ if (nhist_extra > 1) {
+ LYaddch(ACS_RARROW);
+ LYaddch(ACS_RARROW);
+ LYaddch(ACS_RARROW);
+ }
+ LynxChangeStyle(s_forw_backw, STACK_OFF);
+ }
+#endif /* USE_COLOR_STYLE */
+#ifdef WIDEC_CURSES
+ i = limit - LYbarWidth - (int) strlen(percent) - LYstrCells(title);
+ if (i <= 0) { /* title is truncated */
+ i = limit - LYbarWidth - (int) strlen(percent) - 3;
+ if (i <= 0) { /* no room at all */
+ title[0] = '\0';
+ } else {
+ strcpy(title + LYstrFittable(title, i), "...");
+ }
+ i = 0;
+ }
+ LYmove(0, i);
+#else
+ i = (limit - 1) - (int) (strlen(percent) + strlen(title));
+ if (i >= CHAR_WIDTH) {
+ LYmove(0, i);
+ } else {
+ /*
+ * Truncation takes into account the possibility that
+ * multibyte characters might be present. -HS (H. Senshu)
+ */
+ int last;
+
+ last = (int) strlen(percent) + CHAR_WIDTH;
+ if (limit - 3 >= last) {
+ title[(limit - 3) - last] = '.';
+ title[(limit - 2) - last] = '.';
+ title[(limit - 1) - last] = '\0';
+ } else {
+ title[(limit - 1) - last] = '\0';
+ }
+ LYmove(0, CHAR_WIDTH);
+ }
+#endif
+ LYaddstr(title);
+ if (percent[0] != '\0')
+ LYaddstr(percent);
+ LYaddch('\n');
+ FREE(title);
+
+#if defined(USE_COLOR_STYLE) && defined(CAN_CUT_AND_PASTE)
+ if (s_hot_paste != NOSTYLE) { /* Only if the user set the style */
+ LYmove(0, LYcolLimit);
+ LynxChangeStyle(s_hot_paste, STACK_ON);
+ LYaddch(ACS_RARROW);
+ LynxChangeStyle(s_hot_paste, STACK_OFF);
+ LYmove(1, 0); /* As after \n */
+ }
+#endif /* USE_COLOR_STYLE */
+
+#ifdef USE_COLOR_STYLE
+/* turn the TITLE style off */
+ LynxChangeStyle(s_title, STACK_OFF);
+#endif /* USE_COLOR_STYLE */
+ lynx_stop_title_color();
+
+ return;
+}
+
+/* Output the scrollbar
+ * ---------------------
+ */
+#ifdef USE_SCROLLBAR
+static void display_scrollbar(HText *text)
+{
+ int i;
+ int h = display_lines - 2 * (LYsb_arrow != 0); /* Height of the scrollbar */
+ int off = (LYsb_arrow != 0); /* Start of the scrollbar */
+ int top_skip, bot_skip, sh, shown;
+
+ LYsb_begin = LYsb_end = -1;
+ if (!LYShowScrollbar || !text || h <= 2
+ || text->Lines <= display_lines)
+ return;
+
+ if (text->top_of_screen >= text->Lines - display_lines) {
+ /* Only part of the screen shows actual text */
+ shown = text->Lines - text->top_of_screen;
+
+ if (shown <= 0)
+ shown = 1;
+ } else
+ shown = display_lines;
+ /* Each cell of scrollbar represents text->Lines/h lines of text. */
+ /* Always smaller than h */
+ sh = (shown * h + text->Lines / 2) / text->Lines;
+ if (sh <= 0)
+ sh = 1;
+ if (sh >= h - 1)
+ sh = h - 2; /* Position at ends indicates BEG and END */
+
+ if (text->top_of_screen == 0)
+ top_skip = 0;
+ else if (text->Lines - (text->top_of_screen + display_lines - 1) <= 0)
+ top_skip = h - sh;
+ else {
+ /* text->top_of_screen between 1 and text->Lines - display_lines
+ corresponds to top_skip between 1 and h - sh - 1 */
+ /* Use rounding to get as many positions into top_skip==h - sh - 1
+ as into top_skip == 1:
+ 1--->1, text->Lines - display_lines + 1--->h - sh. */
+ top_skip = (int) (1 +
+ 1. * (h - sh - 1) * text->top_of_screen
+ / (text->Lines - display_lines + 1));
+ }
+ bot_skip = h - sh - top_skip;
+
+ LYsb_begin = top_skip;
+ LYsb_end = h - bot_skip;
+
+ if (LYsb_arrow) {
+#ifdef USE_COLOR_STYLE
+ int s = top_skip ? s_sb_aa : s_sb_naa;
+
+ if (last_colorattr_ptr > 0) {
+ LynxChangeStyle(s, STACK_ON);
+ } else {
+ LynxChangeStyle(s, ABS_ON);
+ }
+#endif /* USE_COLOR_STYLE */
+ LYmove(1, LYcolLimit + LYshiftWin);
+ addch_raw(ACS_UARROW);
+#ifdef USE_COLOR_STYLE
+ LynxChangeStyle(s, STACK_OFF);
+#endif /* USE_COLOR_STYLE */
+ }
+#ifdef USE_COLOR_STYLE
+ if (last_colorattr_ptr > 0) {
+ LynxChangeStyle(s_sb_bg, STACK_ON);
+ } else {
+ LynxChangeStyle(s_sb_bg, ABS_ON);
+ }
+#endif /* USE_COLOR_STYLE */
+
+ for (i = 1; i <= h; i++) {
+#ifdef USE_COLOR_STYLE
+ if (i - 1 <= top_skip && i > top_skip)
+ LynxChangeStyle(s_sb_bar, STACK_ON);
+ if (i - 1 <= h - bot_skip && i > h - bot_skip)
+ LynxChangeStyle(s_sb_bar, STACK_OFF);
+#endif /* USE_COLOR_STYLE */
+ LYmove(i + off, LYcolLimit + LYshiftWin);
+ if (i > top_skip && i <= h - bot_skip) {
+ LYaddch(ACS_BLOCK);
+ } else {
+ LYaddch(ACS_CKBOARD);
+ }
+ }
+#ifdef USE_COLOR_STYLE
+ LynxChangeStyle(s_sb_bg, STACK_OFF);
+#endif /* USE_COLOR_STYLE */
+
+ if (LYsb_arrow) {
+#ifdef USE_COLOR_STYLE
+ int s = bot_skip ? s_sb_aa : s_sb_naa;
+
+ if (last_colorattr_ptr > 0) {
+ LynxChangeStyle(s, STACK_ON);
+ } else {
+ LynxChangeStyle(s, ABS_ON);
+ }
+#endif /* USE_COLOR_STYLE */
+ LYmove(h + 2, LYcolLimit + LYshiftWin);
+ addch_raw(ACS_DARROW);
+#ifdef USE_COLOR_STYLE
+ LynxChangeStyle(s, STACK_OFF);
+#endif /* USE_COLOR_STYLE */
+ }
+ return;
+}
+#else
+#define display_scrollbar(text) /*nothing */
+#endif /* USE_SCROLLBAR */
+
+/* Output a page
+ * -------------
+ */
+static void display_page(HText *text,
+ int line_number,
+ const char *target)
+{
+ HTLine *line = NULL;
+ int i;
+ int title_lines = TITLE_LINES;
+
+#if defined(USE_COLOR_STYLE) && defined(SHOW_WHEREIS_TARGETS)
+ const char *cp;
+#endif
+ char tmp[7];
+ TextAnchor *Anchor_ptr = NULL;
+ int stop_before_for_anchors;
+ FormInfo *FormInfo_ptr;
+ BOOL display_flag = FALSE;
+ HTAnchor *link_dest;
+ HTAnchor *link_dest_intl = NULL;
+ static int last_nlinks = 0;
+ static int charset_last_displayed = -1;
+
+#ifdef DISP_PARTIAL
+ int last_disp_partial = -1;
+#endif
+
+ lynx_mode = NORMAL_LYNX_MODE;
+
+ if (text == NULL) {
+ /*
+ * Check whether to force a screen clear to enable scrollback,
+ * or as a hack to fix a reverse clear screen problem for some
+ * curses packages. - shf@access.digex.net & seldon@eskimo.com
+ */
+ if (enable_scrollback) {
+ LYaddch('*');
+ LYrefresh();
+ LYclear();
+ }
+ LYaddstr("\n\nError accessing document!\nNo data available!\n");
+ LYrefresh();
+ nlinks = 0; /* set number of links to 0 */
+ return;
+ }
+#ifdef DISP_PARTIAL
+ if (display_partial || recent_sizechange || text->stale) {
+ /* Reset them, will be set near end if all is okay. - kw */
+ ResetPartialLinenos(text);
+ }
+#endif /* DISP_PARTIAL */
+
+ tmp[0] = tmp[1] = tmp[2] = '\0';
+ if (target && *target == '\0')
+ target = NULL;
+ text->page_has_target = NO;
+ if (display_lines <= 0) {
+ /* No screen space to display anything!
+ * returning here makes it more likely we will survive if
+ * an xterm is temporarily made very small. - kw */
+ return;
+ }
+
+ line_number = HText_getPreferredTopLine(text, line_number);
+
+ for (i = 0, line = FirstHTLine(text); /* Find line */
+ i < line_number && (line != text->last_line);
+ i++, line = line->next) { /* Loop */
+#ifndef VMS
+ if (!LYNoCore) {
+ assert(line->next != NULL);
+ } else if (line->next == NULL) {
+ if (enable_scrollback) {
+ LYaddch('*');
+ LYrefresh();
+ LYclear();
+ }
+ LYaddstr("\n\nError drawing page!\nBad HText structure!\n");
+ LYrefresh();
+ nlinks = 0; /* set number of links to 0 */
+ return;
+ }
+#else
+ assert(line->next != NULL);
+#endif /* !VMS */
+ } /* Loop */
+
+ if (LYlowest_eightbit[current_char_set] <= 255 &&
+ (current_char_set != charset_last_displayed) &&
+ /*
+ * current_char_set has changed since last invocation,
+ * and it's not just 7-bit.
+ * Also we don't want to do this for -dump and -source etc.
+ */
+ LYCursesON) {
+#ifdef EXP_CHARTRANS_AUTOSWITCH
+ UCChangeTerminalCodepage(current_char_set,
+ &LYCharSet_UC[current_char_set]);
+#endif /* EXP_CHARTRANS_AUTOSWITCH */
+ charset_last_displayed = current_char_set;
+ }
+
+ /*
+ * Check whether to force a screen clear to enable scrollback,
+ * or as a hack to fix a reverse clear screen problem for some
+ * curses packages. - shf@access.digex.net & seldon@eskimo.com
+ */
+ if (enable_scrollback) {
+ LYaddch('*');
+ LYrefresh();
+ LYclear();
+ }
+#ifdef USE_COLOR_STYLE
+ /*
+ * Reset stack of color attribute changes to avoid color leaking,
+ * except if what we last displayed from this text was the previous
+ * screenful, in which case carrying over the state might be beneficial
+ * (although it shouldn't generally be needed any more). - kw
+ */
+ if (text->stale ||
+ line_number != text->top_of_screen + (display_lines)) {
+ last_colorattr_ptr = 0;
+ }
+#endif
+
+ text->top_of_screen = line_number;
+ text->top_of_screen_line = line;
+ if (no_title) {
+ LYmove(0, 0);
+ title_lines = 0;
+ } else {
+ display_title(text); /* will move cursor to top of screen */
+ }
+ display_flag = TRUE;
+
+#ifdef USE_COLOR_STYLE
+#ifdef DISP_PARTIAL
+ if (display_partial ||
+ line_number != text->first_lineno_last_disp_partial ||
+ line_number > text->last_lineno_last_disp_partial)
+#endif /* DISP_PARTIAL */
+ ResetCachedStyles();
+#endif /* USE_COLOR_STYLE */
+
+#ifdef DISP_PARTIAL
+ if (display_partial && text->stbl) {
+ stop_before_for_anchors = Stbl_getStartLineDeep(text->stbl);
+ if (stop_before_for_anchors > line_number + (display_lines))
+ stop_before_for_anchors = line_number + (display_lines);
+ } else
+#endif
+ stop_before_for_anchors = line_number + (display_lines);
+
+ /*
+ * Output the page.
+ */
+ if (line) {
+#if defined(USE_COLOR_STYLE) && defined(SHOW_WHEREIS_TARGETS)
+ char *data;
+ int offset, LenNeeded;
+#endif
+#ifdef DISP_PARTIAL
+ if (display_partial ||
+ line_number != text->first_lineno_last_disp_partial)
+ text->has_utf8 = NO;
+#else
+ text->has_utf8 = NO;
+#endif
+ for (i = 0; i < (display_lines); i++) {
+ /*
+ * Verify and display each line.
+ */
+#ifndef VMS
+ if (!LYNoCore) {
+ assert(line != NULL);
+ } else if (line == NULL) {
+ if (enable_scrollback) {
+ LYaddch('*');
+ LYrefresh();
+ LYclear();
+ }
+ LYaddstr("\n\nError drawing page!\nBad HText structure!\n");
+ LYrefresh();
+ nlinks = 0; /* set number of links to 0 */
+ return;
+ }
+#else
+ assert(line != NULL);
+#endif /* !VMS */
+
+#ifdef DISP_PARTIAL
+ if (!display_partial &&
+ line_number == text->first_lineno_last_disp_partial &&
+ i + line_number <= text->last_lineno_last_disp_partial)
+ LYmove((i + title_lines + 1), 0);
+ else
+#endif
+ display_line(line, text, i + 1, target);
+
+#if defined(SHOW_WHEREIS_TARGETS)
+#ifdef USE_COLOR_STYLE /* otherwise done in display_line - kw */
+ /*
+ * If the target is on this line, recursively
+ * seek and emphasize it. -FM
+ */
+ data = (char *) line->data;
+ offset = (int) line->offset;
+ while (non_empty(target) &&
+ (cp = LYno_attr_mb_strstr(data,
+ target,
+ text->T.output_utf8, YES,
+ NULL,
+ &LenNeeded)) != NULL &&
+ ((int) line->offset + LenNeeded) <= DISPLAY_COLS) {
+ size_t itmp = 0;
+ size_t written = 0;
+ int x_off = offset + (int) (cp - data);
+ size_t len = strlen(target);
+ size_t utf_extra = 0;
+
+ text->page_has_target = YES;
+
+ /*
+ * Start the emphasis.
+ */
+ LYstartTargetEmphasis();
+
+ /*
+ * Output the target characters.
+ */
+ for (;
+ written < len && (tmp[0] = data[itmp]) != '\0';
+ itmp++) {
+ if (IsSpecialAttrChar(tmp[0]) && tmp[0] != LY_SOFT_NEWLINE) {
+ /*
+ * Ignore special characters.
+ */
+ x_off--;
+
+ } else if (&data[itmp] >= cp) {
+ if (cp == &data[itmp]) {
+ /*
+ * First printable character of target.
+ */
+ LYmove((i + title_lines),
+ line->offset + LYstrExtent2(line->data,
+ x_off - line->offset));
+ }
+ /*
+ * Output all the printable target chars.
+ */
+ utf_extra = utf8_length(text->T.output_utf8, data + itmp);
+ if (utf_extra) {
+ LYStrNCpy(&tmp[1], &line->data[itmp + 1], utf_extra);
+ itmp += utf_extra;
+ LYaddstr(tmp);
+ tmp[1] = '\0';
+ written += (utf_extra + 1);
+ } else if (IS_CJK_TTY && is8bits(tmp[0])) {
+ /*
+ * For CJK strings, by Masanobu Kimura.
+ */
+ tmp[1] = data[++itmp];
+ LYaddstr(tmp);
+ tmp[1] = '\0';
+ written += 2;
+ } else {
+ LYaddstr(tmp);
+ written++;
+ }
+ }
+ }
+
+ /*
+ * Stop the emphasis, and reset the offset and
+ * data pointer for our current position in the
+ * line. -FM
+ */
+ LYstopTargetEmphasis();
+ data = (char *) &data[itmp];
+ offset = (int) (data - line->data + line->offset);
+
+ } /* end while */
+ LYmove((i + title_lines + 1), 0);
+#endif /* USE_COLOR_STYLE */
+#endif /* SHOW_WHEREIS_TARGETS */
+
+ /*
+ * Stop if this is the last line. Otherwise, make sure
+ * display_flag is set and process the next line. -FM
+ */
+ if (line == text->last_line) {
+ /*
+ * Clear remaining lines of display.
+ */
+ for (i++; i < (display_lines); i++) {
+ LYmove((i + title_lines), 0);
+ LYclrtoeol();
+ }
+ break;
+ }
+#ifdef DISP_PARTIAL
+ if (display_partial) {
+ /*
+ * Remember as fully shown during last partial display,
+ * if it was not the last text line. - kw
+ */
+ last_disp_partial = i + line_number;
+ }
+#endif /* DISP_PARTIAL */
+ display_flag = TRUE;
+ line = line->next;
+ } /* end of "Verify and display each line." loop */
+ }
+ /* end "Output the page." */
+ text->next_line = line; /* Line after screen */
+ text->stale = NO; /* Display is up-to-date */
+
+ /*
+ * Add the anchors to Lynx structures.
+ */
+ nlinks = 0;
+ for (Anchor_ptr = text->first_anchor;
+ Anchor_ptr != NULL && Anchor_ptr->line_num <= stop_before_for_anchors;
+ Anchor_ptr = Anchor_ptr->next) {
+
+ if (Anchor_ptr->line_num >= line_number
+ && Anchor_ptr->line_num < stop_before_for_anchors) {
+ char *hi_string = LYGetHiTextStr(Anchor_ptr, 0);
+
+ /*
+ * Load normal hypertext anchors.
+ */
+ if (Anchor_ptr->show_anchor
+ && non_empty(hi_string)
+ && (Anchor_ptr->link_type & HYPERTEXT_ANCHOR)) {
+ int count;
+ char *s;
+
+ for (count = 0;; ++count) {
+ s = LYGetHiTextStr(Anchor_ptr, count);
+ if (count == 0)
+ LYSetHilite(nlinks, s);
+ if (s == NULL)
+ break;
+ if (count != 0) {
+ LYAddHilite(nlinks, s, LYGetHiTextPos(Anchor_ptr, count));
+ }
+ }
+
+ links[nlinks].inUnderline = Anchor_ptr->inUnderline;
+
+ links[nlinks].sgml_offset = Anchor_ptr->sgml_offset;
+ links[nlinks].anchor_number = Anchor_ptr->number;
+ links[nlinks].anchor_line_num = Anchor_ptr->line_num;
+
+ link_dest = HTAnchor_followLink(Anchor_ptr->anchor);
+ {
+ auto char *cp_AnchorAddress = NULL;
+
+ if (traversal) {
+ cp_AnchorAddress = stub_HTAnchor_address(link_dest);
+ } else if (track_internal_links) {
+ if (Anchor_ptr->link_type == INTERNAL_LINK_ANCHOR) {
+ link_dest_intl = HTAnchor_followTypedLink(Anchor_ptr->anchor,
+ HTInternalLink);
+ if (link_dest_intl && link_dest_intl != link_dest) {
+
+ CTRACE((tfp,
+ "GridText: display_page: unexpected typed link to %s!\n",
+ link_dest_intl->parent->address));
+ link_dest_intl = NULL;
+ }
+ } else {
+ link_dest_intl = NULL;
+ }
+ if (link_dest_intl) {
+ char *cp2 = HTAnchor_address(link_dest_intl);
+
+ cp_AnchorAddress = cp2;
+ } else {
+ cp_AnchorAddress = HTAnchor_address(link_dest);
+ }
+ } else {
+ cp_AnchorAddress = HTAnchor_address(link_dest);
+ }
+ FREE(links[nlinks].lname);
+
+ if (cp_AnchorAddress != NULL)
+ links[nlinks].lname = cp_AnchorAddress;
+ else
+ StrAllocCopy(links[nlinks].lname, empty_string);
+ }
+
+ links[nlinks].lx = Anchor_ptr->line_pos;
+ links[nlinks].ly = ((Anchor_ptr->line_num + 1) - line_number);
+ if (link_dest_intl)
+ links[nlinks].type = WWW_INTERN_LINK_TYPE;
+ else
+ links[nlinks].type = WWW_LINK_TYPE;
+ links[nlinks].target = empty_string;
+ links[nlinks].l_form = NULL;
+
+ nlinks++;
+ display_flag = TRUE;
+
+ } else if (Anchor_ptr->link_type == INPUT_ANCHOR
+ && Anchor_ptr->input_field->type != F_HIDDEN_TYPE) {
+ /*
+ * Handle form fields.
+ */
+ lynx_mode = FORMS_LYNX_MODE;
+
+ FormInfo_ptr = Anchor_ptr->input_field;
+
+ links[nlinks].sgml_offset = Anchor_ptr->sgml_offset;
+ links[nlinks].anchor_number = Anchor_ptr->number;
+ links[nlinks].anchor_line_num = Anchor_ptr->line_num;
+
+ links[nlinks].l_form = FormInfo_ptr;
+ links[nlinks].lx = Anchor_ptr->line_pos;
+ links[nlinks].ly = ((Anchor_ptr->line_num + 1) - line_number);
+ links[nlinks].type = WWW_FORM_LINK_TYPE;
+ links[nlinks].inUnderline = Anchor_ptr->inUnderline;
+ links[nlinks].target = empty_string;
+ StrAllocCopy(links[nlinks].lname, empty_string);
+
+ if (FormInfo_ptr->type == F_RADIO_TYPE) {
+ LYSetHilite(nlinks,
+ FormInfo_ptr->num_value
+ ? checked_radio
+ : unchecked_radio);
+ } else if (FormInfo_ptr->type == F_CHECKBOX_TYPE) {
+ LYSetHilite(nlinks,
+ FormInfo_ptr->num_value
+ ? checked_box
+ : unchecked_box);
+ } else if (FormInfo_ptr->type == F_PASSWORD_TYPE) {
+ LYSetHilite(nlinks,
+ STARS(LYstrCells(FormInfo_ptr->value)));
+ } else { /* TEXT type */
+ LYSetHilite(nlinks,
+ FormInfo_ptr->value);
+ }
+
+ nlinks++;
+ /*
+ * Bold the link after incrementing nlinks.
+ */
+ LYhighlight(FALSE, (nlinks - 1), target);
+
+ display_flag = TRUE;
+
+ } else {
+ /*
+ * Not showing anchor.
+ */
+ if (non_empty(hi_string))
+ CTRACE((tfp,
+ "\nGridText: Not showing link, hightext=%s\n",
+ hi_string));
+ }
+ }
+
+ if (nlinks == MAXLINKS) {
+ /*
+ * Links array is full. If interactive, tell user
+ * to use half-page or two-line scrolling. -FM
+ */
+ if (LYCursesON) {
+ HTAlert(MAXLINKS_REACHED);
+ }
+ CTRACE((tfp, "\ndisplay_page: MAXLINKS reached.\n"));
+ break;
+ }
+ } /* end of loop "Add the anchors to Lynx structures." */
+
+ /*
+ * Free any un-reallocated links[] entries
+ * from the previous page draw. -FM
+ */
+ LYFreeHilites(nlinks, last_nlinks);
+ last_nlinks = nlinks;
+
+ /*
+ * If Anchor_ptr is not NULL and is not pointing to the last
+ * anchor, then there are anchors farther down in the document,
+ * and we need to flag this for traversals.
+ */
+ more_links = FALSE;
+ if (traversal && Anchor_ptr) {
+ if (Anchor_ptr->next)
+ more_links = TRUE;
+ }
+
+ if (!display_flag) {
+ /*
+ * Nothing on the page.
+ */
+ LYaddstr("\n Document is empty");
+ }
+ display_scrollbar(text);
+
+#ifdef DISP_PARTIAL
+ if (display_partial && display_flag &&
+ last_disp_partial >= text->top_of_screen &&
+ !enable_scrollback &&
+ !recent_sizechange) { /* really remember them if ok - kw */
+ text->first_lineno_last_disp_partial = text->top_of_screen;
+ text->last_lineno_last_disp_partial = last_disp_partial;
+ } else {
+ ResetPartialLinenos(text);
+ }
+#endif /* DISP_PARTIAL */
+
+#if !defined(WIDEC_CURSES)
+ if (text->has_utf8 || text->had_utf8) {
+ /*
+ * For other than ncurses, repainting is taken care of
+ * by touching lines in display_line and highlight. - kw 1999-10-07
+ */
+ text->had_utf8 = text->has_utf8;
+ clearok(curscr, TRUE);
+ } else if (IS_CJK_TTY) {
+ /*
+ * For non-multibyte curses.
+ *
+ * Full repainting is necessary, otherwise only part of a multibyte
+ * character sequence might be written because of curses output
+ * optimizations.
+ */
+ clearok(curscr, TRUE);
+ }
+#endif /* WIDEC_CURSES */
+
+ LYrefresh();
+ return;
+}
+
+/* Object Building methods
+ * -----------------------
+ *
+ * These are used by a parser to build the text in an object
+ */
+void HText_beginAppend(HText *text)
+{
+ text->permissible_split = 0;
+ text->in_line_1 = YES;
+
+}
+
+/*
+ * LYcols_cu is the notion that the display library has of the screen width.
+ * Checks of the line length (as the non-UTF-8-aware display library would see
+ * it) against LYcols_cu are used to try to prevent lines with UTF-8 chars from
+ * being wrapped by the library when they shouldn't. If there is no display
+ * library involved, i.e., dump_output_immediately, no such limit should be
+ * imposed. MAX_COLS should be just as good as any other large value. (But
+ * don't use INT_MAX or something close to it to, avoid over/underflow.) - kw
+ */
+#ifdef USE_SLANG
+#define LYcols_cu(text) (dump_output_immediately ? MAX_COLS : SLtt_Screen_Cols)
+#else
+#ifdef WIDEC_CURSES
+#define LYcols_cu(text) WRAP_COLS(text)
+#else
+#define LYcols_cu(text) (dump_output_immediately ? MAX_COLS : DISPLAY_COLS)
+#endif
+#endif
+
+/* Add a new line of text
+ * ----------------------
+ *
+ * On entry,
+ *
+ * split is zero for newline function, else number of characters
+ * before split.
+ * text->display_on_the_fly
+ * may be set to indicate direct output of the finished line.
+ * On exit,
+ * A new line has been made, justified according to the
+ * current style. Text after the split (if split nonzero)
+ * is taken over onto the next line.
+ *
+ * If display_on_the_fly is set, then it is decremented and
+ * the finished line is displayed.
+ */
+
+static int set_style_by_embedded_chars(char *s,
+ char *e,
+ unsigned start_c,
+ unsigned end_c)
+{
+ int ret = NO;
+
+ while (--e >= s) {
+ if (UCH(*e) == UCH(end_c))
+ break;
+ if (UCH(*e) == UCH(start_c)) {
+ ret = YES;
+ break;
+ }
+ }
+ return ret;
+}
+
+static void move_anchors_in_region(HTLine *line, int line_number,
+ TextAnchor **prev_anchor, /*updates++ */
+ int *prev_head_processed,
+ int sbyte,
+ int ebyte,
+ int shift) /* Likewise */
+{
+ /*
+ * Update anchor positions for anchors that start on this line. Note: we
+ * rely on a->line_pos counting bytes, not characters. That's one reason
+ * why HText_trimHightext has to be prevented from acting on these anchors
+ * in partial display mode before we get a chance to deal with them here.
+ */
+ TextAnchor *a;
+ int head_processed = *prev_head_processed;
+
+ /*
+ * We need to know whether (*prev_anchor)->line_pos is "in new coordinates"
+ * or in old ones. If prev_anchor' head was touched on the previous
+ * iteration, we set head_processed. The tail may need to be treated now.
+ */
+ for (a = *prev_anchor;
+ a && a->line_num <= line_number;
+ a = a->next, head_processed = 0) {
+ /* extent==0 needs to be special-cased; happens if no text for
+ the anchor was processed yet. */
+ /* Subtract one so that the space is not inserted at the end
+ of the anchor... */
+ int last = a->line_pos + (a->extent ? a->extent - 1 : 0);
+
+ /* Include the anchors started on the previous line */
+ if (a->line_num < line_number - 1)
+ continue;
+ if (a->line_num == line_number - 1)
+ last -= line->prev->size + 1; /* Fake "\n" "between" lines counted too */
+ if (last < sbyte) /* Completely before the start */
+ continue;
+
+ if (!head_processed /* a->line_pos is not edited yet */
+ && a->line_num == line_number
+ && a->line_pos >= ebyte) /* Completely after the end */
+ break;
+ /* Now we know that the anchor context intersects the chunk */
+
+ /* Fix the start */
+ if (!head_processed && a->line_num == line_number
+ && a->line_pos >= sbyte) {
+ a->line_pos = (short) (a->line_pos + shift);
+ a->extent = (short) (a->extent - shift);
+ head_processed = 1;
+ }
+ /* Fix the end */
+ if (last < ebyte) {
+ a->extent = (short) (a->extent + shift);
+ } else {
+ break; /* Keep this `a' for the next step */
+ }
+ }
+ *prev_anchor = a;
+ *prev_head_processed = head_processed;
+}
+
+/*
+ * Given a line and two int arrays of old/now position, this function
+ * creates a new line where spaces have been inserted/removed
+ * in appropriate places - so that characters at/after the old
+ * position end up at/after the new position, for each pair, if possible.
+ * Some necessary changes for anchors starting on this line are also done
+ * here if needed. Updates 'prev_anchor' internally.
+ * Returns a newly allocated HTLine* if changes were made
+ * (caller has to free the old one).
+ * Returns NULL if no changes needed. (Remove-spaces code may be buggy...)
+ * - kw
+ */
+static HTLine *insert_blanks_in_line(HTLine *line, int line_number,
+ HText *text,
+ TextAnchor **prev_anchor, /*updates++ */
+ int ninserts,
+ int *oldpos, /* Measured in cells */
+ int *newpos) /* Likewise */
+{
+ int ioldc = 0; /* count visible characters */
+ int ip; /* count insertion pairs */
+
+#if defined(USE_COLOR_STYLE)
+ int istyle = 0;
+#endif
+ int added_chars = 0;
+ int shift = 0;
+ int head_processed;
+ HTLine *mod_line;
+ char *newdata;
+ char *s = line->data;
+ char *pre = s;
+ char *copied = line->data, *t;
+
+ if (!(line && line->size && ninserts))
+ return NULL;
+ for (ip = 0; ip < ninserts; ip++)
+ if (newpos[ip] > oldpos[ip] &&
+ (newpos[ip] - oldpos[ip]) > added_chars)
+ added_chars = newpos[ip] - oldpos[ip];
+ if (line->size + added_chars > MAX_LINE - 2)
+ return NULL;
+ if (line == text->last_line) {
+ if (line == TEMP_LINE(text, 0))
+ mod_line = TEMP_LINE(text, 1);
+ else
+ mod_line = TEMP_LINE(text, 0);
+ } else {
+ allocHTLine(mod_line, (unsigned) (line->size + added_chars));
+ }
+ if (!mod_line)
+ return NULL;
+ if (!*prev_anchor)
+ *prev_anchor = text->first_anchor;
+ head_processed = (*prev_anchor && (*prev_anchor)->line_num < line_number);
+ memcpy(mod_line, line, LINE_SIZE(0));
+ t = newdata = mod_line->data;
+ ip = 0;
+ while (ip <= ninserts) {
+ /* line->size is in bytes, so it may be larger than needed... */
+ int curlim = (ip < ninserts
+ ? oldpos[ip]
+ : ((int) line->size <= MAX_LINE
+ ? MAX_LINE + 1
+ : (int) line->size + 1));
+
+ pre = s;
+
+ /* Fast forward to char==curlim or EOL. Stop *before* the
+ style-change chars. */
+ while (*s) {
+ if (text && text->T.output_utf8
+ && UCH(*s) >= 0x80 && UCH(*s) < 0xC0) {
+ pre = s + 1;
+ } else if (!IsSpecialAttrChar(*s)) { /* At a "displayed" char */
+ if (ioldc >= curlim)
+ break;
+ ioldc++;
+ pre = s + 1;
+#ifdef EXP_WCWIDTH_SUPPORT
+ if (text && text->T.output_utf8 && IS_UTF_FIRST(*s))
+ ioldc += utfextracells(s);
+#endif
+ }
+ s++;
+ }
+
+ /* Now s is at the "displayed" char, pre is before the style change */
+ if (ip) /* Fix anchor positions */
+ move_anchors_in_region(line, line_number, prev_anchor /*updates++ */ ,
+ &head_processed,
+ (int) (copied - line->data), (int) (pre - line->data),
+ shift);
+#if defined(USE_COLOR_STYLE) /* Move styles too */
+#define NStyle mod_line->styles[istyle]
+ for (;
+ istyle < line->numstyles && (int) NStyle.sc_horizpos < curlim;
+ istyle++)
+ /* Should not we include OFF-styles at curlim? */
+ NStyle.sc_horizpos = CAST_POS(NStyle.sc_horizpos + shift);
+#endif
+ while (copied < pre) /* Copy verbatim to byte == pre */
+ *t++ = *copied++;
+ if (ip < ninserts) { /* Insert spaces */
+ int delta = newpos[ip] - oldpos[ip] - shift;
+
+ if (delta < 0) { /* Not used yet? */
+ while (delta++ < 0 && t > newdata && t[-1] == ' ')
+ t--, shift--;
+ } else
+ shift = newpos[ip] - oldpos[ip];
+ while (delta-- > 0)
+ *t++ = ' ';
+ }
+ ip++;
+ }
+ while (pre < s) /* Copy remaining style-codes */
+ *t++ = *pre++;
+ /* Check whether the last anchor continues on the next line */
+ if (head_processed
+ && *prev_anchor
+ && (*prev_anchor)->line_num == line_number) {
+ (*prev_anchor)->extent = (short) ((*prev_anchor)->extent + shift);
+ }
+ *t = '\0';
+ mod_line->size = (unsigned short) (t - newdata);
+ return mod_line;
+}
+
+#if defined(USE_COLOR_STYLE)
+#define direction2s(d) ((d) == STACK_OFF \
+ ? "OFF" \
+ : ((d) == STACK_ON \
+ ? "ON" \
+ : "*ON"))
+
+/*
+ * Found an OFF change not part of an adjacent matched pair.
+ *
+ * Walk backward looking for the corresponding ON change.
+ * Move everything after split_pos to be at split_pos.
+ *
+ * This can only work correctly if all changes are correctly nested! If this
+ * fails, assume it is safer to leave whatever comes before the OFF on the
+ * previous line alone.
+ */
+static HTStyleChange *skip_matched_and_correct_offsets(HTStyleChange *end,
+ HTStyleChange *start,
+ unsigned split_pos)
+{
+ HTStyleChange *result = 0;
+ int level = 0;
+ HTStyleChange *tmp = end;
+
+ CTRACE_STYLE((tfp, "SKIP Style %d %d (%s), split %u\n",
+ tmp->sc_horizpos,
+ tmp->sc_style,
+ direction2s(tmp->sc_direction),
+ split_pos));
+ for (; tmp >= start; tmp--) {
+ CTRACE_STYLE((tfp, "... %d %d (%s)\n",
+ tmp->sc_horizpos,
+ tmp->sc_style,
+ direction2s(tmp->sc_direction)));
+ if (tmp->sc_style == end->sc_style) {
+ if (tmp->sc_direction == STACK_OFF) {
+ level--;
+ } else if (tmp->sc_direction == STACK_ON) {
+ if (++level == 0) {
+ result = tmp;
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ if (tmp->sc_horizpos > split_pos) {
+ tmp->sc_horizpos = CAST_POS(split_pos);
+ }
+ }
+ return result;
+}
+#endif /* USE_COLOR_STYLE */
+
+#define reset_horizpos(value) value = 0, value ^= MASK_POS
+
+static void split_line(HText *text, unsigned split)
+{
+ HTStyle *style = text->style;
+ int spare;
+ int indent = (text->in_line_1
+ ? text->style->indent1st
+ : text->style->leftIndent);
+ int new_offset;
+ short alignment;
+ TextAnchor *a;
+ int CurLine = text->Lines;
+ int HeadTrim = 0;
+ int SpecialAttrChars = 0;
+ int TailTrim = 0;
+ int s, s_post, s_pre, t_underline = underline_on, t_bold = bold_on;
+ char *p;
+ char *cp;
+ int ctrl_chars_on_previous_line = 0;
+
+#ifndef WIDEC_CURSES
+ int utfxtra_on_previous_line = UTFXTRA_ON_THIS_LINE;
+#endif
+
+ HTLine *previous = text->last_line;
+ HTLine *line;
+
+ /*
+ * Set new line.
+ */
+ if (previous == TEMP_LINE(text, 0))
+ line = TEMP_LINE(text, 1);
+ else
+ line = TEMP_LINE(text, 0);
+ if (line == NULL)
+ return;
+ memset(line, 0, (size_t) LINE_SIZE(0));
+
+ ctrl_chars_on_this_line = 0; /*reset since we are going to a new line */
+ utfxtra_on_this_line = 0; /*reset too, we'll count them */
+#ifdef EXP_WCWIDTH_SUPPORT
+ utfxtracells_on_this_line = 0;
+#endif
+ text->LastChar = ' ';
+
+#ifdef DEBUG_APPCH
+ CTRACE((tfp, "GridText: split_line(%p,%d) called\n", text, split));
+ CTRACE((tfp, " previous=%s\n", previous->data));
+ CTRACE((tfp, " bold_on=%d, underline_on=%d\n", bold_on, underline_on));
+#endif
+
+ cp = previous->data;
+
+ /* Float LY_SOFT_NEWLINE to the start */
+ if (cp[0] == LY_BOLD_START_CHAR
+ || cp[0] == LY_UNDERLINE_START_CHAR) {
+ switch (cp[1]) {
+ case LY_SOFT_NEWLINE:
+ cp[1] = cp[0];
+ cp[0] = LY_SOFT_NEWLINE;
+ break;
+ case LY_BOLD_START_CHAR:
+ case LY_UNDERLINE_START_CHAR:
+ if (cp[2] == LY_SOFT_NEWLINE) {
+ cp[2] = cp[1];
+ cp[1] = cp[0];
+ cp[0] = LY_SOFT_NEWLINE;
+ }
+ break;
+ }
+ }
+ if (split > previous->size) {
+ CTRACE((tfp,
+ "*** split_line: split==%u greater than last_line->size==%d !\n",
+ split, previous->size));
+ if (split > MAX_LINE) {
+ split = previous->size;
+ if ((cp = strrchr(previous->data, ' ')) &&
+ cp - previous->data > 1)
+ split = (unsigned) (cp - previous->data);
+ CTRACE((tfp, " split adjusted to %u.\n", split));
+ }
+ }
+
+ text->Lines++;
+
+ previous->next->prev = line;
+ line->prev = previous;
+ line->next = previous->next;
+ previous->next = line;
+ text->last_line = line;
+ line->size = 0;
+ line->offset = 0;
+ text->permissible_split = 0; /* 12/13/93 */
+ line->data[0] = '\0';
+
+ alignment = style->alignment;
+
+ if (split > 0) { /* Restore flags to the value at the splitting point */
+ if (!(dump_output_immediately && use_underscore))
+ t_underline = set_style_by_embedded_chars(previous->data,
+ previous->data + split,
+ LY_UNDERLINE_START_CHAR, LY_UNDERLINE_END_CHAR);
+
+ t_bold = set_style_by_embedded_chars(previous->data,
+ previous->data + split,
+ LY_BOLD_START_CHAR, LY_BOLD_END_CHAR);
+
+ }
+
+ if (!(dump_output_immediately && use_underscore) && t_underline) {
+ line->data[line->size++] = LY_UNDERLINE_START_CHAR;
+ line->data[line->size] = '\0';
+ ctrl_chars_on_this_line++;
+ SpecialAttrChars++;
+ }
+ if (t_bold) {
+ line->data[line->size++] = LY_BOLD_START_CHAR;
+ line->data[line->size] = '\0';
+ ctrl_chars_on_this_line++;
+ SpecialAttrChars++;
+ }
+
+ /*
+ * Split at required point
+ */
+ if (split > 0) { /* Delete space at "split" splitting line */
+ char *prevdata = previous->data, *linedata = line->data;
+ unsigned plen;
+ int i;
+
+ /* Split the line. -FM */
+ prevdata[previous->size] = '\0';
+ previous->size = (unsigned short) split;
+
+ /*
+ * Trim any spaces or soft hyphens from the beginning
+ * of our new line. -FM
+ */
+ p = prevdata + split;
+ while (((*p == ' '
+#ifdef USE_JUSTIFY_ELTS
+ /* if justification is allowed for prev line, then raw
+ * HT_NON_BREAK_SPACE are still present in data[] (they'll be
+ * substituted at the end of this function with ' ') - VH
+ */
+ || *p == HT_NON_BREAK_SPACE
+#endif
+ )
+ && (HeadTrim || text->first_anchor ||
+ underline_on || bold_on ||
+ alignment != HT_LEFT ||
+ style->wordWrap || style->freeFormat ||
+ style->spaceBefore || style->spaceAfter)) ||
+ *p == LY_SOFT_HYPHEN) {
+ p++;
+ HeadTrim++;
+ }
+
+ plen = (unsigned) strlen(p);
+ if (plen) { /* Count funny characters */
+ for (i = (int) (plen - 1); i >= 0; i--) {
+ if (p[i] == LY_UNDERLINE_START_CHAR ||
+ p[i] == LY_UNDERLINE_END_CHAR ||
+ p[i] == LY_BOLD_START_CHAR ||
+ p[i] == LY_BOLD_END_CHAR ||
+ p[i] == LY_SOFT_HYPHEN) {
+ ctrl_chars_on_this_line++;
+ } else if (IS_UTF_EXTRA(p[i])) {
+ utfxtra_on_this_line++;
+#ifdef EXP_WCWIDTH_SUPPORT
+ } else if (IS_UTF_FIRST(p[i])) {
+ utfxtracells_on_this_line += utfextracells(&p[i]);
+#endif
+ }
+ if (p[i] == LY_SOFT_HYPHEN &&
+ (int) text->permissible_split < i)
+ text->permissible_split = (unsigned) (i + 1);
+ }
+ ctrl_chars_on_this_line += utfxtra_on_this_line;
+
+ /* Add the data to the new line. -FM */
+ strcat(linedata, p);
+ line->size = (unsigned short) (line->size + plen);
+ }
+ }
+
+ /*
+ * Economize on space.
+ */
+ p = previous->data + previous->size - 1;
+ while (p >= previous->data
+ && (*p == ' '
+#ifdef USE_JUSTIFY_ELTS
+ /* if justification is allowed for prev line, then raw
+ * HT_NON_BREAK_SPACE are still present in data[] (they'll be
+ * substituted at the end of this function with ' ') - VH
+ */
+ || *p == HT_NON_BREAK_SPACE
+#endif
+ )
+#ifdef USE_PRETTYSRC
+ && !psrc_view /*don't strip trailing whites - since next line can
+ start with LY_SOFT_NEWLINE - so we don't lose spaces when
+ 'p'rinting this text to file -VH */
+#endif
+ && (ctrl_chars_on_this_line || HeadTrim || text->first_anchor ||
+ underline_on || bold_on ||
+ alignment != HT_LEFT ||
+ style->wordWrap || style->freeFormat ||
+ style->spaceBefore || style->spaceAfter)) {
+ p--; /* Strip trailers. */
+ }
+ /* Strip trailers. */
+ TailTrim = (int) (previous->data + previous->size - 1 - p);
+ previous->size = (unsigned short) (previous->size - TailTrim);
+ p[1] = '\0';
+
+ /*
+ * s is the effective split position, given by either a non-zero
+ * value of split or by the size of the previous line before
+ * trimming. - kw
+ */
+ if (split == 0) {
+ s = previous->size + TailTrim; /* the original size */
+ } else {
+ s = (int) split;
+ }
+ s_post = s + HeadTrim;
+ s_pre = s - TailTrim;
+
+#ifdef DEBUG_SPLITLINE
+#ifdef DEBUG_APPCH
+ if (s != (int) split)
+#endif
+ CTRACE((tfp, "GridText: split_line(%u [now:%d]) called\n", split, s));
+#endif
+
+#if defined(USE_COLOR_STYLE)
+ if (previous->styles == stylechanges_buffers[0])
+ line->styles = stylechanges_buffers[1];
+ else
+ line->styles = stylechanges_buffers[0];
+ line->numstyles = 0;
+ {
+ HTStyleChange *from = previous->styles + previous->numstyles - 1;
+ HTStyleChange *to = line->styles + MAX_STYLES_ON_LINE - 1;
+ HTStyleChange *scan, *at_end;
+
+ /* Color style changes after the split position
+ * are transferred to the new line. Ditto for changes
+ * in the trimming region, but we stop when we reach an OFF change.
+ * The second loop below may then handle remaining changes. - kw */
+ while (from >= previous->styles && to >= line->styles) {
+ *to = *from;
+ if ((int) to->sc_horizpos > s_post) {
+ to->sc_horizpos = CAST_POS(to->sc_horizpos
+ + SpecialAttrChars
+ - s_post);
+ } else if ((int) to->sc_horizpos > s_pre &&
+ (to->sc_direction == STACK_ON ||
+ to->sc_direction == ABS_ON)) {
+ if ((int) to->sc_horizpos < s)
+ to->sc_horizpos = 0;
+ else
+ to->sc_horizpos = CAST_POS(SpecialAttrChars);
+ } else {
+ break;
+ }
+ to--;
+ from--;
+ }
+ /* FROM may be invalid, otherwise it is either an ON change at or
+ before s_pre, or is an OFF change at or before s_post. */
+
+ scan = from;
+ at_end = from;
+ /* Now on the previous line we have a correctly nested but
+ possibly non-terminated sequence of style changes.
+ Terminate it, and duplicate unterminated changes at the
+ beginning of the new line. */
+ while (scan >= previous->styles && at_end >= previous->styles) {
+ /* The algorithm: scan back though the styles on the previous line.
+ a) If OFF, skip the matched group.
+ Report a bug on failure.
+ b) If ON, (try to) cancel the corresponding ON at at_end,
+ and the corresponding OFF at to;
+ If not, put the corresponding OFF at at_end, and copy to to;
+ */
+ if (scan->sc_direction == STACK_OFF) {
+ scan = skip_matched_and_correct_offsets(scan, previous->styles,
+ (unsigned) s_pre);
+ if (!scan) {
+ CTRACE((tfp, "BUG: styles improperly nested.\n"));
+ break;
+ }
+ } else if (scan->sc_direction == STACK_ON) {
+ if (at_end->sc_direction == STACK_ON
+ && at_end->sc_style == scan->sc_style
+ && (int) at_end->sc_horizpos >= s_pre)
+ at_end--;
+ else if (at_end >= previous->styles + MAX_STYLES_ON_LINE - 1) {
+ CTRACE((tfp, "BUG: style overflow before split_line.\n"));
+ break;
+ } else {
+ at_end++;
+ at_end->sc_direction = STACK_OFF;
+ at_end->sc_style = scan->sc_style;
+ at_end->sc_horizpos = CAST_POS(s_pre);
+ CTRACE_STYLE((tfp,
+ "split_line, %d:style[%d] %d (dir=%d)\n",
+ s_pre,
+ (int) (at_end - from),
+ scan->sc_style,
+ at_end->sc_direction));
+ }
+ if (to < line->styles + MAX_STYLES_ON_LINE - 1
+ && to[1].sc_direction == STACK_OFF
+ && to[1].sc_horizpos <= (unsigned) SpecialAttrChars
+ && to[1].sc_style == scan->sc_style)
+ to++;
+ else if (to >= line->styles) {
+ *to = *scan;
+ to->sc_horizpos = CAST_POS(SpecialAttrChars);
+ to--;
+ } else {
+ CTRACE((tfp, "BUG: style overflow after split_line.\n"));
+ break;
+ }
+ }
+ if ((int) scan->sc_horizpos > s_pre) {
+ scan->sc_horizpos = CAST_POS(s_pre);
+ }
+ scan--;
+ }
+ line->numstyles = (unsigned short) (line->styles
+ + MAX_STYLES_ON_LINE
+ - 1 - to);
+ if (line->numstyles > 0 && line->numstyles < MAX_STYLES_ON_LINE) {
+ int n;
+
+ for (n = 0; n < line->numstyles; n++)
+ line->styles[n] = to[n + 1];
+ } else if (line->numstyles == 0) {
+ reset_horizpos(line->styles[0].sc_horizpos);
+ }
+ previous->numstyles = (unsigned short) (at_end - previous->styles + 1);
+ if (previous->numstyles == 0) {
+ reset_horizpos(previous->styles[0].sc_horizpos);
+ }
+ }
+#endif /*USE_COLOR_STYLE */
+
+ {
+ HTLine *temp;
+
+ allocHTLine(temp, previous->size);
+ if (!temp)
+ outofmem(__FILE__, "split_line_2");
+
+ memcpy(temp, previous, LINE_SIZE(previous->size));
+#if defined(USE_COLOR_STYLE)
+ POOLallocstyles(temp->styles, previous->numstyles);
+ if (!temp->styles)
+ outofmem(__FILE__, "split_line_2");
+ memcpy(temp->styles, previous->styles, sizeof(HTStyleChange) * previous->numstyles);
+#endif
+ previous = temp;
+ }
+
+ previous->prev->next = previous; /* Link in new line */
+ previous->next->prev = previous; /* Could be same node of course */
+
+ /*
+ * Terminate finished line for printing.
+ */
+ previous->data[previous->size] = '\0';
+
+ /*
+ * Align left, right or center.
+ */
+ spare = 0;
+ if (
+#ifdef USE_JUSTIFY_ELTS
+ this_line_was_split ||
+#endif
+ (alignment == HT_CENTER ||
+ alignment == HT_RIGHT) || text->stbl) {
+ /* Calculate spare character positions if needed */
+ for (cp = previous->data; *cp; cp++) {
+ if (*cp == LY_UNDERLINE_START_CHAR ||
+ *cp == LY_UNDERLINE_END_CHAR ||
+ *cp == LY_BOLD_START_CHAR ||
+ *cp == LY_BOLD_END_CHAR ||
+#ifndef WIDEC_CURSES
+ IS_UTF_EXTRA(*cp) ||
+#endif
+ *cp == LY_SOFT_HYPHEN) {
+ ctrl_chars_on_previous_line++;
+ }
+ }
+ if ((previous->size > 0) &&
+ (int) (previous->data[previous->size - 1] == LY_SOFT_HYPHEN))
+ ctrl_chars_on_previous_line--;
+
+ /* @@ first line indent */
+#ifdef WIDEC_CURSES
+ spare = WRAP_COLS(text)
+ - (int) style->rightIndent
+ - indent
+ + ctrl_chars_on_previous_line
+ - LYstrExtent2(previous->data, previous->size);
+ if (spare < 0 && LYwideLines) /* Can be wider than screen */
+ spare = 0;
+#else
+ spare = WRAP_COLS(text)
+ - (int) style->rightIndent
+ - indent
+ + ctrl_chars_on_previous_line
+ - previous->size;
+ if (spare < 0 && LYwideLines) /* Can be wider than screen */
+ spare = 0;
+
+ if (spare > 0 && !dump_output_immediately &&
+ text->T.output_utf8 && ctrl_chars_on_previous_line) {
+ utfxtra_on_previous_line -= UTFXTRA_ON_THIS_LINE;
+ if (utfxtra_on_previous_line) {
+ int spare_cu = (LYcols_cu(text) -
+ utfxtra_on_previous_line - indent +
+ ctrl_chars_on_previous_line - previous->size);
+
+ /*
+ * Shift non-leftaligned UTF-8 lines that would be
+ * mishandled by the display library towards the left
+ * if this would make them fit. The resulting display
+ * will not be as intended, but this is better than
+ * having them split by curses. (Curses cursor movement
+ * optimization may still cause wrong positioning within
+ * the line, in particular after a sequence of spaces).
+ * - kw
+ */
+ if (spare_cu < spare) {
+ if (spare_cu >= 0) {
+ if (alignment == HT_CENTER &&
+ (int) (previous->offset + indent + spare / 2 +
+ previous->size)
+ - ctrl_chars_on_previous_line
+ + utfxtra_on_previous_line <= LYcols_cu(text))
+ /* do nothing - it still fits - kw */ ;
+ else {
+ spare = spare_cu;
+ }
+ } else if (indent + (int) previous->offset + spare_cu >= 0) { /* subtract overdraft from effective indentation */
+ indent += (int) previous->offset + spare_cu;
+ previous->offset = 0;
+ spare = 0;
+ }
+ }
+ }
+ }
+#endif
+ }
+
+ new_offset = previous->offset;
+ switch (style->alignment) {
+ case HT_CENTER:
+ new_offset += indent + spare / 2;
+ break;
+ case HT_RIGHT:
+ new_offset += indent + spare;
+ break;
+ case HT_LEFT:
+ case HT_JUSTIFY: /* Not implemented */
+ default:
+ new_offset += indent;
+ break;
+ } /* switch */
+ previous->offset = (unsigned short) ((new_offset < 0) ? 0 : new_offset);
+
+ if (text->stbl) {
+ /*
+ * Notify simple table stuff of line split, so that it can
+ * set the last cell's length. The last cell should and
+ * its row should really end here, or on one of the following
+ * lines with no more characters added after the break.
+ * We don't know whether a cell has been started, so ignore
+ * errors here.
+ * This call is down here because we need the
+ * ctrl_chars_on_previous_line, which have just been re-
+ * counted above. - kw
+ */
+ Stbl_lineBreak(text->stbl,
+ text->Lines - 1,
+ previous->offset,
+ previous->size - ctrl_chars_on_previous_line);
+ }
+
+ text->in_line_1 = NO; /* unless caller sets it otherwise */
+
+ /*
+ * If we split the line, adjust the anchor
+ * structure values for the new line. -FM
+ */
+
+ if (s > 0) { /* if not completely empty */
+ int moved = 0;
+
+ /* In the algorithm below we move or not move anchors between
+ lines using some heuristic criteria. However, it is
+ desirable not to have two consequent anchors on different
+ lines *in a wrong order*! (How can this happen?)
+ So when the "reasonable choice" is not unique, we use the
+ MOVED flag to choose one.
+ */
+ /* Our operations can make a non-empty all-whitespace link
+ empty. So what? */
+ if ((a = text->last_anchor_before_split) == 0)
+ a = text->first_anchor;
+
+ for (; a; a = a->next) {
+ if (a->line_num == CurLine) {
+ int len = a->extent, n = a->number, start = a->line_pos;
+ int end = start + len;
+
+ text->last_anchor_before_split = a;
+
+ /* Which anchors do we leave on the previous line?
+ a) empty finished (We need a cut-off value.
+ "Just because": those before s;
+ this is the only case when we use s, not s_pre/s_post);
+ b) Those which start before s_pre;
+ */
+ if (start < s_pre) {
+ if (end <= s_pre)
+ continue; /* No problem */
+
+ CTRACE_SPLITLINE((tfp, "anchor %d: no relocation", n));
+ if (end > s_post) {
+ CTRACE_SPLITLINE((tfp, " of the start.\n"));
+ a->extent = (short) (a->extent
+ - (TailTrim + HeadTrim)
+ + SpecialAttrChars);
+ } else {
+ CTRACE_SPLITLINE((tfp, ", cut the end.\n"));
+ a->extent = (short) (s_pre - start);
+ }
+ continue;
+ } else if (start < s && !len
+ && (!n || (a->show_anchor && !moved))) {
+ CTRACE_SPLITLINE((tfp,
+ "anchor %d: no relocation, empty-finished",
+ n));
+ a->line_pos = (short) s_pre; /* Leave at the end of line */
+ continue;
+ }
+
+ /* The rest we relocate */
+ moved = 1;
+ a->line_num++;
+ CTRACE_SPLITLINE((tfp,
+ "anchor %d: (T,H,S)=(%d,%d,%d); (line,pos,ext):(%d,%d,%d), ",
+ n, TailTrim, HeadTrim, SpecialAttrChars,
+ a->line_num, a->line_pos, a->extent));
+ if (end < s_post) { /* Move the end to s_post */
+ CTRACE_SPLITLINE((tfp, "Move end +%d, ", s_post - end));
+ len += s_post - end;
+ }
+ if (start < s_post) { /* Move the start to s_post */
+ CTRACE_SPLITLINE((tfp, "Move start +%d, ", s_post - start));
+ len -= s_post - start;
+ start = s_post;
+ }
+ a->line_pos = (short) (start - s_post + SpecialAttrChars);
+ a->extent = (short) len;
+
+ CTRACE_SPLITLINE((tfp, "->(%d,%d,%d)\n",
+ a->line_num, a->line_pos, a->extent));
+ } else if (a->line_num > CurLine)
+ break;
+ }
+ }
+#ifdef USE_JUSTIFY_ELTS
+ /* now perform justification - by VH */
+
+ if (this_line_was_split
+ && spare > 0
+ && !text->stbl /* We don't inform TRST on the cell width change yet */
+ && justify_max_void_percent > 0
+ && justify_max_void_percent <= 100
+ && justify_max_void_percent >= ((100 * spare)
+ / (WRAP_COLS(text)
+ - (int) style->rightIndent
+ - indent
+ + ctrl_chars_on_previous_line))) {
+ /* this is the only case when we need justification */
+ char *jp = previous->data + justify_start_position;
+ ht_run_info *r = ht_runs;
+ char c;
+ int d_, r_;
+ HTLine *jline;
+
+ ht_num_runs = 0;
+ r->byte_len = r->cell_len = 0;
+
+ for (; (c = *jp) != 0; ++jp) {
+ if (c == ' ') {
+ ++r;
+ ++ht_num_runs;
+ r->byte_len = r->cell_len = 0;
+ continue;
+ }
+ ++r->byte_len;
+ if (IsSpecialAttrChar(c))
+ continue;
+
+ ++r->cell_len;
+ if (c == HT_NON_BREAK_SPACE) {
+ *jp = ' '; /* substitute it */
+ continue;
+ }
+ if (text->T.output_utf8 && is8bits(c)) {
+ int utf_extra = (int) utf8_length(text->T.output_utf8, jp);
+
+ r->byte_len += utf_extra;
+ jp += utf_extra;
+ }
+ }
+ ++ht_num_runs;
+
+ if (ht_num_runs != 1) {
+ int *oldpos = (int *) malloc(sizeof(int)
+ * 2 * (size_t) (ht_num_runs - 1));
+ int *newpos = oldpos + ht_num_runs - 1;
+ int i = 1;
+
+ if (oldpos == NULL)
+ outofmem(__FILE__, "split_line_3");
+
+ d_ = spare / (ht_num_runs - 1);
+ r_ = spare % (ht_num_runs - 1);
+
+ /* The first run is not moved, proceed to the second one */
+ oldpos[0] = justify_start_position + ht_runs[0].cell_len + 1;
+ newpos[0] = oldpos[0] + (d_ + (r_-- > 0));
+ while (i < ht_num_runs - 1) {
+ int delta = ht_runs[i].cell_len + 1;
+
+ oldpos[i] = oldpos[i - 1] + delta;
+ newpos[i] = newpos[i - 1] + delta + (d_ + (r_-- > 0));
+ i++;
+ }
+ jline = insert_blanks_in_line(previous, CurLine, text,
+ &last_anchor_of_previous_line /*updates++ */ ,
+ ht_num_runs - 1, oldpos, newpos);
+ free(oldpos);
+ if (jline == NULL)
+ outofmem(__FILE__, "split_line_4");
+ previous->next->prev = jline;
+ previous->prev->next = jline;
+
+ freeHTLine(text, previous);
+
+ previous = jline;
+ }
+ if (justify_start_position) {
+ char *p2 = previous->data;
+
+ for (; p2 < previous->data + justify_start_position; ++p2)
+ *p2 = (char) (*p2 == HT_NON_BREAK_SPACE ? ' ' : *p2);
+ }
+ } else {
+ if (REALLY_CAN_JUSTIFY(text)) {
+ char *p2;
+
+ /* it was permitted to justify line, but this function was called
+ * to end paragraph - we must substitute HT_NON_BREAK_SPACEs with
+ * spaces in previous line
+ */
+ if (line->size && !text->stbl) {
+ CTRACE((tfp,
+ "BUG: justification: shouldn't happen - new line is not empty!\n\t'%.*s'\n",
+ line->size, line->data));
+ }
+
+ for (p2 = previous->data; *p2; ++p2)
+ if (*p2 == HT_NON_BREAK_SPACE)
+ *p2 = ' ';
+ } else if (have_raw_nbsps) {
+ /* this is very rare case, that can happen in forms placed in
+ table cells */
+ unsigned i;
+
+ for (i = 0; i < previous->size; ++i)
+ if (previous->data[i] == HT_NON_BREAK_SPACE)
+ previous->data[i] = ' ';
+
+ /*next line won't be justified, so substitute nbsps in it too */
+ for (i = 0; i < line->size; ++i)
+ if (line->data[i] == HT_NON_BREAK_SPACE)
+ line->data[i] = ' ';
+ }
+
+ /* else HT_NON_BREAK_SPACEs were substituted with spaces in
+ HText_appendCharacter */
+ }
+ /* cleanup */
+ can_justify_this_line = TRUE;
+ justify_start_position = 0;
+ this_line_was_split = FALSE;
+ have_raw_nbsps = FALSE;
+#endif /* USE_JUSTIFY_ELTS */
+ return;
+} /* split_line */
+
+#ifdef DEBUG_SPLITLINE
+static void do_new_line(HText *text, const char *fn, int ln)
+{
+ CTRACE_SPLITLINE((tfp, "new_line %s@%d\n", fn, ln));
+ split_line(text, 0);
+}
+
+#define new_line(text) do_new_line(text, __FILE__, __LINE__)
+#else
+#define new_line(text) split_line(text, 0)
+#endif
+
+/* Allow vertical blank space
+ * --------------------------
+ */
+static void blank_lines(HText *text, int newlines)
+{
+ if (HText_TrueEmptyLine(text->last_line, text, FALSE)) { /* No text on current line */
+ HTLine *line = text->last_line->prev;
+ BOOL first = (BOOL) (line == text->last_line);
+
+ if (no_title && first)
+ return;
+
+#ifdef USE_COLOR_STYLE
+ /* Style-change petty requests at the start of the document: */
+ if (first && newlines == 1)
+ return; /* Do not add a blank line at start */
+#endif
+
+ while (line != NULL &&
+ line != text->last_line &&
+ HText_TrueEmptyLine(line, text, FALSE)) {
+ if (newlines == 0)
+ break;
+ newlines--; /* Don't bother: already blank */
+ line = line->prev;
+ }
+ } else {
+ newlines++; /* Need also to finish this line */
+ }
+
+ for (; newlines; newlines--) {
+ new_line(text);
+ }
+ text->in_line_1 = YES;
+}
+
+/* New paragraph in current style
+ * ------------------------------
+ * See also: setStyle.
+ */
+void HText_appendParagraph(HText *text)
+{
+ int after = text->style->spaceAfter;
+ int before = text->style->spaceBefore;
+
+ blank_lines(text, ((after > before) ? after : before));
+}
+
+/* Set Style
+ * ---------
+ *
+ * Does not filter unnecessary style changes.
+ */
+void HText_setStyle(HText *text, HTStyle *style)
+{
+ int after, before;
+
+ if (!style)
+ return; /* Safety */
+ after = text->style->spaceAfter;
+ before = style->spaceBefore;
+
+ CTRACE((tfp, "GridText: Change to style %s\n", GetHTStyleName(style)));
+
+ blank_lines(text, ((after > before) ? after : before));
+
+ text->style = style;
+}
+
+/* Append a character to the text object
+ * -------------------------------------
+ */
+void HText_appendCharacter(HText *text, int ch)
+{
+ HTLine *line;
+ HTStyle *style;
+ int indent;
+ int actual;
+
+#ifdef DEBUG_APPCH
+#ifdef CJK_EX
+ static unsigned char save_ch = 0;
+#endif
+
+ if (TRACE) {
+ char *special = NULL; /* make trace a little more readable */
+
+ switch (ch) {
+ case HT_NON_BREAK_SPACE:
+ special = "HT_NON_BREAK_SPACE";
+ break;
+ case HT_EN_SPACE:
+ special = "HT_EN_SPACE";
+ break;
+ case LY_UNDERLINE_START_CHAR:
+ special = "LY_UNDERLINE_START_CHAR";
+ break;
+ case LY_UNDERLINE_END_CHAR:
+ special = "LY_UNDERLINE_END_CHAR";
+ break;
+ case LY_BOLD_START_CHAR:
+ special = "LY_BOLD_START_CHAR";
+ break;
+ case LY_BOLD_END_CHAR:
+ special = "LY_BOLD_END_CHAR";
+ break;
+ case LY_SOFT_HYPHEN:
+ special = "LY_SOFT_HYPHEN";
+ break;
+ case LY_SOFT_NEWLINE:
+ special = "LY_SOFT_NEWLINE";
+ break;
+ default:
+ special = NULL;
+ break;
+ }
+
+ if (special != NULL) {
+ CTRACE((tfp, "add(%s %d special char) %d/%d\n", special, ch,
+ HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
+ } else {
+#ifdef CJK_EX /* 1998/08/30 (Sun) 13:26:23 */
+ if (save_ch == 0) {
+ if (IS_SJIS_HI1(ch) || IS_SJIS_HI2(ch)) {
+ save_ch = ch;
+ } else {
+ CTRACE((tfp, "add(%c) %d/%d\n", ch,
+ HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
+ }
+ } else {
+ CTRACE((tfp, "add(%c%c) %d/%d\n", save_ch, ch,
+ HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
+ save_ch = 0;
+ }
+#else
+ if (UCH(ch) < 0x80) {
+ CTRACE((tfp, "add(%c) %d/%d\n", UCH(ch),
+ HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
+ } else {
+ CTRACE((tfp, "add(%02x) %d/%d\n", UCH(ch),
+ HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
+ }
+#endif /* CJK_EX */
+ }
+ } /* trace only */
+#endif /* DEBUG_APPCH */
+
+ /*
+ * Make sure we don't crash on NULLs.
+ */
+ if (!text)
+ return;
+
+ if (text->halted > 1) {
+ /*
+ * We should stop outputting more text, because low memory was
+ * detected. - kw
+ */
+ if (text->halted == 2) {
+ /*
+ * But if we haven't done so yet, first append a warning.
+ * We should still have a few bytes left for that :).
+ * We temporarily reset test->halted to 0 for this, since
+ * this function will get called recursively. - kw
+ */
+ text->halted = 0;
+ text->kanji_buf = '\0';
+ HText_appendText(text, gettext(" *** MEMORY EXHAUSTED ***"));
+ }
+ text->halted = 3;
+ return;
+ }
+#ifdef USE_TH_JP_AUTO_DETECT
+ if ((HTCJK == JAPANESE) && (text->detected_kcode != DET_MIXED) &&
+ (text->specified_kcode != SJIS) && (text->specified_kcode != EUC)) {
+ unsigned char c;
+ eDetectedKCode save_d_kcode;
+
+ c = UCH(ch);
+ save_d_kcode = text->detected_kcode;
+ switch (text->SJIS_status) {
+ case SJIS_state_has_bad_code:
+ break;
+ case SJIS_state_neutral:
+ if (IS_SJIS_HI1(c) || IS_SJIS_HI2(c)) {
+ text->SJIS_status = SJIS_state_in_kanji;
+ } else if ((c & 0x80) && !IS_SJIS_X0201KANA(c)) {
+ text->SJIS_status = SJIS_state_has_bad_code;
+ if (text->EUC_status == EUC_state_has_bad_code)
+ text->detected_kcode = DET_MIXED;
+ else
+ text->detected_kcode = DET_EUC;
+ }
+ break;
+ case SJIS_state_in_kanji:
+ if (IS_SJIS_LO(c)) {
+ text->SJIS_status = SJIS_state_neutral;
+ } else {
+ text->SJIS_status = SJIS_state_has_bad_code;
+ if (text->EUC_status == EUC_state_has_bad_code)
+ text->detected_kcode = DET_MIXED;
+ else
+ text->detected_kcode = DET_EUC;
+ }
+ break;
+ }
+ switch (text->EUC_status) {
+ case EUC_state_has_bad_code:
+ break;
+ case EUC_state_neutral:
+ if (IS_EUC_HI(c)) {
+ text->EUC_status = EUC_state_in_kanji;
+ } else if (c == 0x8e) {
+ text->EUC_status = EUC_state_in_kana;
+ } else if (c & 0x80) {
+ text->EUC_status = EUC_state_has_bad_code;
+ if (text->SJIS_status == SJIS_state_has_bad_code)
+ text->detected_kcode = DET_MIXED;
+ else
+ text->detected_kcode = DET_SJIS;
+ }
+ break;
+ case EUC_state_in_kanji:
+ if (IS_EUC_LOX(c)) {
+ text->EUC_status = EUC_state_neutral;
+ } else {
+ text->EUC_status = EUC_state_has_bad_code;
+ if (text->SJIS_status == SJIS_state_has_bad_code)
+ text->detected_kcode = DET_MIXED;
+ else
+ text->detected_kcode = DET_SJIS;
+ }
+ break;
+ case EUC_state_in_kana:
+ if ((0xA1 <= c) && (c <= 0xDF)) {
+ text->EUC_status = EUC_state_neutral;
+ } else {
+ text->EUC_status = EUC_state_has_bad_code;
+ if (text->SJIS_status == SJIS_state_has_bad_code)
+ text->detected_kcode = DET_MIXED;
+ else
+ text->detected_kcode = DET_SJIS;
+ }
+ break;
+ }
+ if (save_d_kcode != text->detected_kcode) {
+ switch (text->detected_kcode) {
+ case DET_SJIS:
+ CTRACE((tfp,
+ "TH_JP_AUTO_DETECT: This document's kcode seems SJIS.\n"));
+ break;
+ case DET_EUC:
+ CTRACE((tfp,
+ "TH_JP_AUTO_DETECT: This document's kcode seems EUC.\n"));
+ break;
+ case DET_MIXED:
+ CTRACE((tfp,
+ "TH_JP_AUTO_DETECT: This document's kcode seems mixed!\n"));
+ break;
+ default:
+ CTRACE((tfp,
+ "TH_JP_AUTO_DETECT: This document's kcode is unexpected!\n"));
+ break;
+ }
+ }
+ }
+#endif /* USE_TH_JP_AUTO_DETECT */
+ /*
+ * Make sure we don't hang on escape sequences.
+ */
+ if (ch == CH_ESC && !IS_CJK_TTY) { /* decimal 27 S/390 -- gil -- 1504 */
+ return;
+ }
+#ifndef USE_SLANG
+ /*
+ * Block 8-bit chars not allowed by the current display character
+ * set if they are below what LYlowest_eightbit indicates.
+ * Slang used its own replacements, so for USE_SLANG blocking here
+ * is not necessary to protect terminals from those characters.
+ * They should have been filtered out or translated by an earlier
+ * processing stage anyway. - kw
+ */
+#ifndef EBCDIC /* S/390 -- gil -- 1514 */
+ if (is8bits(ch) && !IS_CJK_TTY &&
+ !text->T.transp && !text->T.output_utf8 &&
+ UCH(ch) < LYlowest_eightbit[current_char_set]) {
+ return;
+ }
+#endif /* EBCDIC */
+#endif /* !USE_SLANG */
+ if (UCH(ch) == 155 && !IS_CJK_TTY) { /* octal 233 */
+ if (!HTPassHighCtrlRaw &&
+ !text->T.transp && !text->T.output_utf8 &&
+ (155 < LYlowest_eightbit[current_char_set])) {
+ return;
+ }
+ }
+
+ line = text->last_line;
+ style = text->style;
+
+ indent = text->in_line_1 ? (int) style->indent1st : (int) style->leftIndent;
+
+ if (IS_CJK_TTY) {
+ switch (text->state) {
+ case S_text:
+ if (ch == CH_ESC) { /* S/390 -- gil -- 1536 */
+ /*
+ * Setting up for CJK escape sequence handling (based on
+ * Takuya ASADA's (asada@three-a.co.jp) CJK Lynx). -FM
+ */
+ text->state = S_esc;
+ text->kanji_buf = '\0';
+ return;
+ }
+ break;
+
+ case S_esc:
+ /*
+ * Expecting '$'or '(' following CJK ESC.
+ */
+ if (ch == '$') {
+ text->state = S_dollar;
+ return;
+ } else if (ch == '(') {
+ text->state = S_paren;
+ return;
+ } else {
+ text->state = S_text;
+ }
+ /* FALLTHRU */
+
+ case S_dollar:
+ /*
+ * Expecting '@', 'B', 'A' or '(' after CJK "ESC$".
+ */
+ if (ch == '@' || ch == 'B' || ch == 'A') {
+ text->state = S_nonascii_text;
+ if (ch == '@' || ch == 'B')
+ text->kcode = JIS;
+ return;
+ } else if (ch == '(') {
+ text->state = S_dollar_paren;
+ return;
+ } else {
+ text->state = S_text;
+ }
+ break;
+
+ case S_dollar_paren:
+ /*
+ * Expecting 'C' after CJK "ESC$(".
+ */
+ if (ch == 'C') {
+ text->state = S_nonascii_text;
+ return;
+ } else {
+ text->state = S_text;
+ }
+ break;
+
+ case S_paren:
+ /*
+ * Expecting 'B', 'J', 'T' or 'I' after CJK "ESC(".
+ */
+ if (ch == 'B' || ch == 'J' || ch == 'T') {
+ /*
+ * Can split here. -FM
+ */
+ text->permissible_split = text->last_line->size;
+ text->state = S_text;
+ return;
+ } else if (ch == 'I') {
+ text->state = S_jisx0201_text;
+ /*
+ * Can split here. -FM
+ */
+ text->permissible_split = text->last_line->size;
+ text->kcode = JIS;
+ return;
+ } else {
+ text->state = S_text;
+ }
+ break;
+
+ case S_nonascii_text:
+ /*
+ * Expecting CJK ESC after non-ASCII text.
+ */
+ if (ch == CH_ESC) { /* S/390 -- gil -- 1553 */
+ text->state = S_esc;
+ text->kanji_buf = '\0';
+ if (HTCJK == JAPANESE) {
+ text->kcode = NOKANJI;
+ }
+ return;
+ } else if (UCH(ch) < 32) {
+ text->state = S_text;
+ text->kanji_buf = '\0';
+ if (HTCJK == JAPANESE) {
+ text->kcode = NOKANJI;
+ }
+ } else {
+ ch |= 0200;
+ }
+ break;
+
+ /*
+ * JIS X0201 Kana in JIS support. - by ASATAKU
+ */
+ case S_jisx0201_text:
+ if (ch == CH_ESC) { /* S/390 -- gil -- 1570 */
+ text->state = S_esc;
+ text->kanji_buf = '\0';
+ text->kcode = NOKANJI;
+ return;
+ } else {
+ text->kanji_buf = '\216';
+ ch |= 0200;
+ }
+ break;
+ } /* end switch */
+
+ if (!text->kanji_buf) {
+ if ((ch & 0200) != 0) {
+ /*
+ * JIS X0201 Kana in SJIS support. - by ASATAKU
+ */
+ if ((text->kcode != JIS)
+#ifdef USE_TH_JP_AUTO_DETECT
+ && (text->specified_kcode != EUC)
+ && (text->detected_kcode != DET_EUC)
+#endif
+ && (
+#ifdef KANJI_CODE_OVERRIDE
+ (last_kcode == SJIS) ||
+ ((last_kcode == NOKANJI) &&
+#endif
+ ((text->kcode == SJIS) ||
+#ifdef USE_TH_JP_AUTO_DETECT
+ ((text->detected_kcode == DET_SJIS) &&
+ (text->specified_kcode == NOKANJI)) ||
+#endif
+ ((text->kcode == NOKANJI) &&
+ (text->specified_kcode == SJIS)))
+#ifdef KANJI_CODE_OVERRIDE
+ )
+#endif
+ ) &&
+ (UCH(ch) >= 0xA1) &&
+ (UCH(ch) <= 0xDF)) {
+ if (conv_jisx0201kana) {
+ unsigned char c = UCH(ch);
+ unsigned char kb = UCH(text->kanji_buf);
+
+ JISx0201TO0208_SJIS(c,
+ (unsigned char *) &kb,
+ (unsigned char *) &c);
+ ch = (char) c;
+ text->kanji_buf = kb;
+ }
+ /* 1998/01/19 (Mon) 09:06:15 */
+ text->permissible_split = (int) text->last_line->size;
+ } else {
+ text->kanji_buf = ch;
+ /*
+ * Can split here. -FM
+ */
+ text->permissible_split = text->last_line->size;
+ return;
+ }
+ }
+ } else {
+ goto check_WrapSource;
+ }
+ } else if (ch == CH_ESC) { /* S/390 -- gil -- 1587 */
+ return;
+ }
+#ifdef CJK_EX /* MOJI-BAKE Fix! 1997/10/12 -- 10/31 (Fri) 00:22:57 - JH7AYN */
+ if (IS_CJK_TTY && /* added condition - kw */
+ (ch == LY_BOLD_START_CHAR || ch == LY_BOLD_END_CHAR)) {
+ text->permissible_split = (int) line->size; /* Can split here */
+ if (HTCJK == JAPANESE)
+ text->kcode = NOKANJI;
+ }
+#endif
+
+ if (IsSpecialAttrChar(ch) && ch != LY_SOFT_NEWLINE) {
+#if !defined(USE_COLOR_STYLE) || !defined(NO_DUMP_WITH_BACKSPACES)
+ if (line->size >= (MAX_LINE - 1)) {
+ return;
+ }
+#if defined(USE_COLOR_STYLE) && !defined(NO_DUMP_WITH_BACKSPACES)
+ if (with_backspaces && !IS_CJK_TTY && !text->T.output_utf8) {
+#endif
+ if (ch == LY_UNDERLINE_START_CHAR) {
+ line->data[line->size++] = LY_UNDERLINE_START_CHAR;
+ line->data[line->size] = '\0';
+ underline_on = TRUE;
+ if (!(dump_output_immediately && use_underscore))
+ ctrl_chars_on_this_line++;
+ return;
+ } else if (ch == LY_UNDERLINE_END_CHAR) {
+ line->data[line->size++] = LY_UNDERLINE_END_CHAR;
+ line->data[line->size] = '\0';
+ underline_on = FALSE;
+ if (!(dump_output_immediately && use_underscore))
+ ctrl_chars_on_this_line++;
+ return;
+ } else if (ch == LY_BOLD_START_CHAR) {
+ line->data[line->size++] = LY_BOLD_START_CHAR;
+ line->data[line->size] = '\0';
+ bold_on = TRUE;
+ ctrl_chars_on_this_line++;
+ return;
+ } else if (ch == LY_BOLD_END_CHAR) {
+ line->data[line->size++] = LY_BOLD_END_CHAR;
+ line->data[line->size] = '\0';
+ bold_on = FALSE;
+ ctrl_chars_on_this_line++;
+ return;
+ } else if (ch == LY_SOFT_HYPHEN) {
+ int i;
+
+ /*
+ * Ignore the soft hyphen if it is the first character
+ * on the line, or if it is preceded by a space or
+ * hyphen. -FM
+ */
+ if (line->size < 1 || text->permissible_split >= line->size) {
+ return;
+ }
+
+ for (i = (int) (text->permissible_split + 1);
+ line->data[i];
+ i++) {
+ if (!IsSpecialAttrChar(UCH(line->data[i])) &&
+ !isspace(UCH(line->data[i])) &&
+ UCH(line->data[i]) != '-' &&
+ UCH(line->data[i]) != HT_NON_BREAK_SPACE &&
+ UCH(line->data[i]) != HT_EN_SPACE) {
+ break;
+ }
+ }
+ if (line->data[i] == '\0') {
+ return;
+ }
+ }
+#if defined(USE_COLOR_STYLE) && !defined(NO_DUMP_WITH_BACKSPACES)
+ } else {
+ /* if (with_backspaces && HTCJK==HTNOCJK && !text->T.output_utf8) */
+ return;
+ }
+#endif
+
+#else
+ return;
+#endif
+ } else if (ch == LY_SOFT_NEWLINE) {
+ line->data[line->size++] = LY_SOFT_NEWLINE;
+ line->data[line->size] = '\0';
+ return;
+ }
+
+ if (text->T.output_utf8) {
+ /*
+ * Some extra checks for UTF-8 output here to make sure
+ * memory is not overrun. For a non-first char, append
+ * to the line here and return. - kw
+ */
+ if (IS_UTF_EXTRA(ch)) {
+ if ((line->size > (MAX_LINE - 1))
+ || (indent + (int) (line->offset + line->size)
+ + UTFXTRA_ON_THIS_LINE
+ - ctrl_chars_on_this_line
+ + ((line->size > 0) &&
+ (int) (line->data[line->size - 1] ==
+ LY_SOFT_HYPHEN ?
+ 1 : 0)) >= LYcols_cu(text))
+ ) {
+ if (!text->permissible_split || text->source) {
+ text->permissible_split = line->size;
+ while (text->permissible_split > 0 &&
+ IS_UTF_EXTRA(line->data[text->permissible_split - 1]))
+ text->permissible_split--;
+ if (text->permissible_split &&
+ (line->data[text->permissible_split - 1] & 0x80))
+ text->permissible_split--;
+ if (text->permissible_split == line->size)
+ text->permissible_split = 0;
+ }
+ split_line(text, text->permissible_split);
+ line = text->last_line;
+ if (text->source && line->size - ctrl_chars_on_this_line
+ + UTFXTRA_ON_THIS_LINE == 0)
+ HText_appendCharacter(text, LY_SOFT_NEWLINE);
+ }
+ line->data[line->size++] = (char) ch;
+ line->data[line->size] = '\0';
+ utfxtra_on_this_line++;
+ ctrl_chars_on_this_line++;
+#ifdef EXP_WCWIDTH_SUPPORT
+ /* update utfxtracells_on_this_line on last byte of UTF-8 sequence */
+ {
+ /* find start position of UTF-8 sequence */
+ int utff = line->size - 2;
+ int utf_xlen;
+
+ while (utff > 0 && IS_UTF_EXTRA(line->data[utff]))
+ utff--;
+ utf_xlen = UTF_XLEN(line->data[utff]);
+
+ if (line->size - utff == utf_xlen + 1) /* have last byte */
+ utfxtracells_on_this_line += utfextracells(&(line->data[utff]));
+ }
+#endif
+ return;
+ } else if (ch & 0x80) { /* a first char of UTF-8 sequence - kw */
+ if ((line->size > (MAX_LINE - 7))) {
+ if (!text->permissible_split || text->source) {
+ text->permissible_split = line->size;
+ while (text->permissible_split > 0 &&
+ (line->data[text->permissible_split - 1] & 0xc0)
+ == 0x80) {
+ text->permissible_split--;
+ }
+ if (text->permissible_split == line->size)
+ text->permissible_split = 0;
+ }
+ split_line(text, text->permissible_split);
+ line = text->last_line;
+ if (text->source && line->size - ctrl_chars_on_this_line
+ + UTFXTRA_ON_THIS_LINE == 0)
+ HText_appendCharacter(text, LY_SOFT_NEWLINE);
+ }
+ }
+ }
+
+ /*
+ * New Line.
+ */
+ if (ch == '\n') {
+ new_line(text);
+ text->in_line_1 = YES; /* First line of new paragraph */
+ /*
+ * There are some pages written in
+ * different kanji codes. - TA & kw
+ */
+ if (HTCJK == JAPANESE)
+ text->kcode = NOKANJI;
+ return;
+ }
+
+ /*
+ * Convert EN_SPACE to a space here so that it doesn't get collapsed.
+ */
+ if (ch == HT_EN_SPACE)
+ ch = ' ';
+
+#ifdef SH_EX /* 1997/11/01 (Sat) 12:08:54 */
+ if (ch == 0x0b) { /* ^K ??? */
+ ch = '\r';
+ }
+ if (ch == 0x1a) { /* ^Z ??? */
+ ch = '\r';
+ }
+#endif
+
+ /*
+ * I'm going to cheat here in a BIG way. Since I know that all
+ * \r's will be trapped by HTML_put_character I'm going to use
+ * \r to mean go down a line but don't start a new paragraph.
+ * i.e., use the second line indenting.
+ */
+ if (ch == '\r') {
+ new_line(text);
+ text->in_line_1 = NO;
+ /*
+ * There are some pages written in
+ * different kanji codes. - TA & kw
+ */
+ if (HTCJK == JAPANESE)
+ text->kcode = NOKANJI;
+ return;
+ }
+
+ /*
+ * Tabs.
+ */
+ if (ch == '\t') {
+ const HTTabStop *Tab;
+ int target, target_cu; /* Where to tab to */
+ int here, here_cu; /* in _cu we try to guess what curses thinks */
+
+ if (line->size > 0 && line->data[line->size - 1] == LY_SOFT_HYPHEN) {
+ /*
+ * A tab shouldn't follow a soft hyphen, so
+ * if one does, we'll dump the soft hyphen. -FM
+ */
+ line->data[--line->size] = '\0';
+ ctrl_chars_on_this_line--;
+ }
+ here = ((int) (line->size + line->offset) + indent)
+ - ctrl_chars_on_this_line; /* Consider special chars GAB */
+ here_cu = here + UTFXTRA_ON_THIS_LINE;
+ if (style->tabs) { /* Use tab table */
+ for (Tab = style->tabs;
+ Tab->position <= here;
+ Tab++) {
+ if (!Tab->position) {
+ new_line(text);
+ return;
+ }
+ }
+ target = Tab->position;
+ } else if (text->in_line_1) { /* Use 2nd indent */
+ if (here >= (int) style->leftIndent) {
+ new_line(text); /* wrap */
+ return;
+ } else {
+ target = (int) style->leftIndent;
+ }
+ } else { /* Default tabs align with left indent mod 8 */
+#ifdef DEFAULT_TABS_8
+ target = (((int) line->offset + (int) line->size + 8) & (-8))
+ + (int) style->leftIndent;
+#else
+ new_line(text);
+ return;
+#endif
+ }
+
+ if (target >= here)
+ target_cu = target;
+ else
+ target_cu = target + (here_cu - here);
+
+ if (target > WRAP_COLS(text) - (int) style->rightIndent &&
+ HTOutputFormat != WWW_SOURCE) {
+ new_line(text);
+ } else {
+ /*
+ * Can split here. -FM
+ */
+ text->permissible_split = line->size;
+ if (target_cu > WRAP_COLS(text))
+ target -= target_cu - WRAP_COLS(text);
+ if (line->size == 0) {
+ line->offset = (unsigned short) (line->offset + (target - here));
+ } else {
+ for (; here < target; here++) {
+ /* Put character into line */
+ line->data[line->size++] = ' ';
+ line->data[line->size] = '\0';
+ }
+ }
+ }
+ return;
+ }
+ /* if tab */
+ check_WrapSource:
+ if ((text->source || dont_wrap_pre) && text == HTMainText) {
+ /*
+ * If we're displaying document source, wrap long lines to keep all of
+ * the source visible.
+ */
+ int target = (int) (line->offset + line->size) - ctrl_chars_on_this_line;
+ int target_cu = target + UTFXTRA_ON_THIS_LINE;
+
+ if (target >= WRAP_COLS(text) - style->rightIndent -
+ ((IS_CJK_TTY && text->kanji_buf) ? 1 : 0) ||
+ (text->T.output_utf8 &&
+ target_cu + UTF_XLEN(ch) >= LYcols_cu(text))) {
+ int saved_kanji_buf;
+ eGridState saved_state;
+ BOOL add_blank = (dont_wrap_pre
+ && line->size
+ && (line->data[line->size - 1] == ' '));
+
+ new_line(text);
+ line = text->last_line;
+
+ saved_kanji_buf = text->kanji_buf;
+ saved_state = text->state;
+ text->kanji_buf = '\0';
+ text->state = S_text;
+ HText_appendCharacter(text, LY_SOFT_NEWLINE);
+ if (add_blank)
+ HText_appendCharacter(text, ' ');
+ text->kanji_buf = saved_kanji_buf;
+ text->state = saved_state;
+ }
+ }
+
+ if (ch == ' ') {
+ /*
+ * Can split here. -FM
+ */
+ text->permissible_split = text->last_line->size;
+ /*
+ * There are some pages written in
+ * different kanji codes. - TA
+ */
+ if (HTCJK == JAPANESE)
+ text->kcode = NOKANJI;
+ }
+
+ /*
+ * Check for end of line.
+ *
+ * Notes:
+ * 1) text->permissible_split is nonzero if we found a place to split the
+ * line. If there is no such place, we still will wrap at the display
+ * limits (the comparison against LYcols_cu). Furthermore, if the
+ * curses-pads feature is active, we will ignore the first comparison
+ * (against WRAP_COLS) to allow wide preformatted text to be displayed
+ * without wrapping.
+ * 2) ctrl_chars_on_this_line are nonprintable bytes used for formatting.
+ */
+ actual = ((indent + (int) line->offset + (int) line->size) +
+ ((line->size > 0) &&
+ (int) (line->data[line->size - 1] == LY_SOFT_HYPHEN ? 1 : 0))
+ - ctrl_chars_on_this_line);
+
+ if ((
+#if !defined(USE_SLANG) && !defined(PDCURSES)
+ (text->permissible_split
+#ifdef USE_CURSES_PADS
+ || !LYwideLines
+#endif
+ ) &&
+#endif
+ (actual
+ + (int) style->rightIndent
+ + ((IS_CJK_TTY && text->kanji_buf) ? 1 : 0)
+#ifdef EXP_WCWIDTH_SUPPORT
+ + utfxtracells_on_this_line
+#endif
+ ) >= WRAP_COLS(text))
+ || (text->T.output_utf8
+ && ((actual
+ + UTFXTRA_ON_THIS_LINE
+ + UTF_XLEN(ch)
+ ) > (LYcols_cu(text) - 1)))) {
+
+ if (style->wordWrap && HTOutputFormat != WWW_SOURCE) {
+#ifdef USE_JUSTIFY_ELTS
+ if (REALLY_CAN_JUSTIFY(text))
+ this_line_was_split = TRUE;
+#endif
+ split_line(text, text->permissible_split);
+ if (ch == ' ') {
+ return; /* Ignore space causing split */
+ }
+
+ } else if (HTOutputFormat == WWW_SOURCE) {
+ /*
+ * For source output we don't want to wrap this stuff
+ * unless absolutely necessary. - LJM
+ * !
+ * If we don't wrap here we might get a segmentation fault.
+ * but let's see what happens
+ */
+ if ((int) line->size >= (int) (MAX_LINE - 1)) {
+ new_line(text); /* try not to linewrap */
+ }
+ } else {
+ /*
+ * For normal stuff like pre let's go ahead and
+ * wrap so the user can see all of the text.
+ */
+ if ((dump_output_immediately || (crawl && traversal))
+ && dont_wrap_pre) {
+ if ((int) line->size >= (int) (MAX_LINE - 1)) {
+ new_line(text);
+ }
+ } else {
+ new_line(text);
+ }
+ }
+ } else if ((int) line->size >= (int) (MAX_LINE - 1)) {
+ /*
+ * Never overrun memory if DISPLAY_COLS is set to a large value - KW
+ */
+ new_line(text);
+ }
+
+ /*
+ * Insert normal characters.
+ */
+ if (ch == HT_NON_BREAK_SPACE
+#ifdef USE_JUSTIFY_ELTS
+ && !REALLY_CAN_JUSTIFY(text)
+#endif
+ )
+ ch = ' ';
+#ifdef USE_JUSTIFY_ELTS
+ else
+ have_raw_nbsps = TRUE;
+#endif
+
+ /* we leave raw HT_NON_BREAK_SPACE otherwise (we'll substitute it later) */
+
+ if (ch & 0x80)
+ text->have_8bit_chars = YES;
+
+ /*
+ * Kanji character handling.
+ */
+ {
+ HTFont font = style->font;
+ unsigned char hi, lo, tmp[2];
+
+ line = text->last_line; /* May have changed */
+
+ if (IS_CJK_TTY && text->kanji_buf) {
+ hi = UCH(text->kanji_buf);
+ lo = UCH(ch);
+
+ if (HTCJK == JAPANESE) {
+ if (text->kcode != JIS) {
+ if (IS_SJIS_2BYTE(hi, lo)) {
+ if (IS_EUC(hi, lo)) {
+#ifdef KANJI_CODE_OVERRIDE
+ if (last_kcode != NOKANJI)
+ text->kcode = last_kcode;
+ else
+#endif
+ if (text->specified_kcode != NOKANJI)
+ text->kcode = text->specified_kcode;
+#ifdef USE_TH_JP_AUTO_DETECT
+ else if (text->detected_kcode == DET_EUC)
+ text->kcode = EUC;
+ else if (text->detected_kcode == DET_SJIS)
+ text->kcode = SJIS;
+#endif
+ else if (IS_EUC_X0201KANA(hi, lo) &&
+ (text->kcode != EUC))
+ text->kcode = SJIS;
+ } else
+ text->kcode = SJIS;
+ } else if (IS_EUC(hi, lo))
+ text->kcode = EUC;
+ else
+ text->kcode = NOKANJI;
+ }
+
+ switch (kanji_code) {
+ case EUC:
+ if (text->kcode == SJIS) {
+ SJIS_TO_EUC1(hi, lo, tmp);
+ line->data[line->size++] = (char) tmp[0];
+ line->data[line->size++] = (char) tmp[1];
+ } else if (IS_EUC(hi, lo)) {
+ if (conv_jisx0201kana) {
+ JISx0201TO0208_EUC(hi, lo, &hi, &lo);
+ }
+ line->data[line->size++] = (char) hi;
+ line->data[line->size++] = (char) lo;
+ } else {
+ CTRACE((tfp,
+ "This character (%X:%X) doesn't seem Japanese\n",
+ hi, lo));
+ line->data[line->size++] = '=';
+ line->data[line->size++] = '=';
+ }
+ break;
+
+ case SJIS:
+ if ((text->kcode == EUC) || (text->kcode == JIS)) {
+ if (!conv_jisx0201kana && IS_EUC_X0201KANA(hi, lo))
+ line->data[line->size++] = (char) lo;
+ else {
+ EUC_TO_SJIS1(hi, lo, tmp);
+ line->data[line->size++] = (char) tmp[0];
+ line->data[line->size++] = (char) tmp[1];
+ }
+ } else if (IS_SJIS_2BYTE(hi, lo)) {
+ line->data[line->size++] = (char) hi;
+ line->data[line->size++] = (char) lo;
+ } else {
+ line->data[line->size++] = '=';
+ line->data[line->size++] = '=';
+ CTRACE((tfp,
+ "This character (%X:%X) doesn't seem Japanese\n",
+ hi, lo));
+ }
+ break;
+
+ default:
+ break;
+ }
+ } else {
+ line->data[line->size++] = (char) hi;
+ line->data[line->size++] = (char) lo;
+ }
+ text->kanji_buf = 0;
+ } else if (!conv_jisx0201kana
+ && (HTCJK == JAPANESE)
+ && IS_SJIS_X0201KANA(UCH((ch))) &&
+ (kanji_code == EUC)) {
+ line->data[line->size++] = (char) UCH(0x8e);
+ line->data[line->size++] = (char) ch;
+ } else if (IS_CJK_TTY) {
+ line->data[line->size++] = (char) ((kanji_code != NOKANJI) ?
+ ch :
+ (font & HT_CAPITALS) ?
+ TOUPPER(ch) : ch);
+ } else {
+ line->data[line->size++] = /* Put character into line */
+ (char) (font & HT_CAPITALS ? TOUPPER(ch) : ch);
+ }
+ line->data[line->size] = '\0';
+ if (font & HT_DOUBLE) /* Do again if doubled */
+ HText_appendCharacter(text, HT_NON_BREAK_SPACE);
+ /* NOT a permissible split */
+
+ if (ch == LY_SOFT_HYPHEN) {
+ ctrl_chars_on_this_line++;
+ /*
+ * Can split here. -FM
+ */
+ text->permissible_split = text->last_line->size;
+ }
+ if (ch == LY_SOFT_NEWLINE) {
+ ctrl_chars_on_this_line++;
+ }
+ }
+ return;
+}
+
+#ifdef USE_COLOR_STYLE
+/* Insert a style change into the current line
+ * -------------------------------------------
+ */
+void _internal_HTC(HText *text, int style, int dir)
+{
+ HTLine *line;
+
+ /* can't change style if we have no text to change style with */
+ if (text != 0) {
+
+ line = text->last_line;
+
+ if (line->numstyles > 0 && dir == 0 &&
+ line->styles[line->numstyles - 1].sc_direction &&
+ line->styles[line->numstyles - 1].sc_style == (unsigned) style &&
+ (int) line->styles[line->numstyles - 1].sc_horizpos
+ == (int) line->size - ctrl_chars_on_this_line) {
+ /*
+ * If this is an OFF change directly preceded by an
+ * ON for the same style, just remove the previous one. - kw
+ */
+ line->numstyles--;
+ } else if (line->numstyles < MAX_STYLES_ON_LINE) {
+ line->styles[line->numstyles].sc_horizpos = CAST_POS(line->size);
+ /*
+ * Special chars for bold and underlining usually don't
+ * occur with color style, but soft hyphen can.
+ * And in UTF-8 display mode all non-initial bytes are
+ * counted as ctrl_chars. - kw
+ */
+ if ((int) line->styles[line->numstyles].sc_horizpos >= ctrl_chars_on_this_line) {
+ line->styles[line->numstyles].sc_horizpos =
+ CAST_POS(line->styles[line->numstyles].sc_horizpos
+ - ctrl_chars_on_this_line);
+ }
+ line->styles[line->numstyles].sc_style = (unsigned short) style;
+ line->styles[line->numstyles].sc_direction = CAST_DIR(dir);
+ CTRACE_STYLE((tfp, "internal_HTC %d:style[%d] %d (dir=%d)\n",
+ line->size,
+ line->numstyles,
+ style,
+ dir));
+ line->numstyles++;
+ }
+ }
+}
+#endif
+
+/* Set LastChar element in the text object.
+ * ----------------------------------------
+ */
+void HText_setLastChar(HText *text, int ch)
+{
+ if (!text)
+ return;
+
+ text->LastChar = (char) ch;
+}
+
+/* Get LastChar element in the text object.
+ * ----------------------------------------
+ */
+char HText_getLastChar(HText *text)
+{
+ if (!text)
+ return ('\0');
+
+ return ((char) text->LastChar);
+}
+
+/* Simple table handling - private
+ * -------------------------------
+ */
+
+/*
+ * HText_insertBlanksInStblLines fixes up table lines when simple table
+ * processing is closed, by calling insert_blanks_in_line for lines
+ * that need fixup. Also recalculates alignment for those lines,
+ * does additional updating of anchor positions, and makes sure the
+ * display of the lines on screen will be updated after partial display
+ * upon return to mainloop. - kw
+ */
+static int HText_insertBlanksInStblLines(HText *me, int ncols)
+{
+ HTLine *line;
+ HTLine *mod_line, *first_line = NULL;
+ int *oldpos;
+ int *newpos;
+ int ninserts, lineno;
+ int last_lineno, first_lineno_pass2;
+
+#ifdef EXP_NESTED_TABLES
+ int last_nonempty = -1;
+#endif
+ int added_chars_before = 0;
+ int lines_changed = 0;
+ int max_width = 0, indent, spare, table_offset;
+ HTStyle *style;
+ short alignment;
+ int i = 0;
+
+ lineno = Stbl_getStartLine(me->stbl);
+ if (lineno < 0 || lineno > me->Lines)
+ return -1;
+ /*
+ * oldpos, newpos: allocate space for two int arrays.
+ */
+ oldpos = typecallocn(int, 2 * (size_t)ncols);
+ if (!oldpos)
+ return -1;
+ else
+ newpos = oldpos + ncols;
+ for (line = FirstHTLine(me); i < lineno; line = line->next, i++) {
+ if (!line) {
+ free(oldpos);
+ return -1;
+ }
+ }
+ first_lineno_pass2 = last_lineno = me->Lines;
+ for (; line && lineno <= last_lineno; line = line->next, lineno++) {
+ ninserts = Stbl_getFixupPositions(me->stbl, lineno, oldpos, newpos);
+ if (ninserts < 0)
+ continue;
+ if (!first_line) {
+ first_line = line;
+ first_lineno_pass2 = lineno;
+ if (TRACE) {
+ int ip;
+
+ CTRACE((tfp, "line %d first to adjust -- newpos:", lineno));
+ for (ip = 0; ip < ncols; ip++)
+ CTRACE((tfp, " %d", newpos[ip]));
+ CTRACE((tfp, "\n"));
+ }
+ }
+ if (line == me->last_line) {
+ if (line->size == 0 || HText_TrueEmptyLine(line, me, FALSE))
+ continue;
+ /*
+ * Last ditch effort to end the table with a line break,
+ * if HTML_end_element didn't do it. - kw
+ */
+ if (first_line == line) /* obscure: all table on last line... */
+ first_line = NULL;
+ new_line(me);
+ line = me->last_line->prev;
+ if (first_line == NULL)
+ first_line = line;
+ }
+ if (ninserts == 0) {
+ /* Do it also for no positions (but not error) */
+ int width = HText_TrueLineSize(line, me, FALSE);
+
+ if (width > max_width)
+ max_width = width;
+#ifdef EXP_NESTED_TABLES
+ if (nested_tables) {
+ if (width && last_nonempty < lineno)
+ last_nonempty = lineno;
+ }
+#endif
+ CTRACE((tfp, "line %d true/max width:%d/%d oldpos: NONE\n",
+ lineno, width, max_width));
+ continue;
+ }
+ mod_line = insert_blanks_in_line(line, lineno, me,
+ &me->last_anchor_before_stbl /*updates++ */ ,
+ ninserts, oldpos, newpos);
+ if (mod_line) {
+ if (line == me->last_line) {
+ me->last_line = mod_line;
+ } else {
+ added_chars_before += (mod_line->size - line->size);
+ }
+ line->prev->next = mod_line;
+ line->next->prev = mod_line;
+ lines_changed++;
+ if (line == first_line)
+ first_line = mod_line;
+ freeHTLine(me, line);
+ line = mod_line;
+#ifdef DISP_PARTIAL
+ /*
+ * Make sure modified lines get fully re-displayed after
+ * loading with partial display is done.
+ */
+ if (me->first_lineno_last_disp_partial >= 0) {
+ if (me->first_lineno_last_disp_partial >= lineno) {
+ ResetPartialLinenos(me);
+ } else if (me->last_lineno_last_disp_partial >= lineno) {
+ me->last_lineno_last_disp_partial = lineno - 1;
+ }
+ }
+#endif
+ } {
+ int width = HText_TrueLineSize(line, me, FALSE);
+
+ if (width > max_width)
+ max_width = width;
+#ifdef EXP_NESTED_TABLES
+ if (nested_tables) {
+ if (width && last_nonempty < lineno)
+ last_nonempty = lineno;
+ }
+#endif
+ if (TRACE) {
+ int ip;
+
+ CTRACE((tfp, "line %d true/max width:%d/%d oldpos:",
+ lineno, width, max_width));
+ for (ip = 0; ip < ninserts; ip++)
+ CTRACE((tfp, " %d", oldpos[ip]));
+ CTRACE((tfp, "\n"));
+ }
+ }
+ }
+ /*
+ * Line offsets have been set based on the paragraph style, and
+ * have already been updated for centering or right-alignment
+ * for each line in split_line. Here we want to undo all that, and
+ * align the table as a whole (i.e. all lines for which
+ * Stbl_getFixupPositions returned >= 0). All those lines have to
+ * get the same offset, for the simple table formatting mechanism
+ * to make sense, and that may not actually be the case at this point.
+ *
+ * What indentation and alignment do we want for the table as
+ * a whole? Let's take most style properties from me->style.
+ * With some luck, it is the appropriate style for the element
+ * enclosing the TABLE. But let's take alignment from the attribute
+ * of the TABLE itself instead, if it was specified.
+ *
+ * Note that this logic assumes that all lines have been finished
+ * by split_line. The order of calls made by HTML_end_element for
+ * HTML_TABLE should take care of this.
+ */
+ style = me->style;
+ alignment = Stbl_getAlignment(me->stbl);
+ if (alignment == HT_ALIGN_NONE)
+ alignment = style->alignment;
+ indent = style->leftIndent;
+ /* Calculate spare character positions */
+ spare = WRAP_COLS(me) -
+ (int) style->rightIndent - indent - max_width;
+ if (spare < 0 && (int) style->rightIndent + spare >= 0) {
+ /*
+ * Not enough room! But we can fit if we ignore right indentation,
+ * so let's do that.
+ */
+ spare = 0;
+ } else if (spare < 0) {
+ spare += style->rightIndent; /* ignore right indent, but need more */
+ }
+ if (spare < 0 && indent + spare >= 0) {
+ /*
+ * Still not enough room. But we can move to the left.
+ */
+ indent += spare;
+ spare = 0;
+ } else if (spare < 0) {
+ /*
+ * Still not enough. Something went wrong. Try the best we
+ * can do.
+ */
+ CTRACE((tfp,
+ "BUG: insertBlanks: resulting table too wide by %d positions!\n",
+ -spare));
+ indent = spare = 0;
+ }
+ /*
+ * Align left, right or center.
+ */
+ switch (alignment) {
+ case HT_CENTER:
+ table_offset = indent + spare / 2;
+ break;
+ case HT_RIGHT:
+ table_offset = indent + spare;
+ break;
+ case HT_LEFT:
+ case HT_JUSTIFY:
+ default:
+ table_offset = indent;
+ break;
+ } /* switch */
+
+ CTRACE((tfp, "changing offsets"));
+ for (line = first_line, lineno = first_lineno_pass2;
+ line && lineno <= last_lineno && line != me->last_line;
+ line = line->next, lineno++) {
+ ninserts = Stbl_getFixupPositions(me->stbl, lineno, oldpos, newpos);
+ if (ninserts >= 0 && (int) line->offset != table_offset) {
+#ifdef DISP_PARTIAL
+ /* As above make sure modified lines get fully re-displayed */
+ if (me->first_lineno_last_disp_partial >= 0) {
+ if (me->first_lineno_last_disp_partial >= lineno) {
+ ResetPartialLinenos(me);
+ } else if (me->last_lineno_last_disp_partial >= lineno) {
+ me->last_lineno_last_disp_partial = lineno - 1;
+ }
+ }
+#endif
+ CTRACE((tfp, " %d:%d", lineno, table_offset - line->offset));
+ line->offset = (unsigned short) (table_offset > 0
+ ? table_offset
+ : 0);
+ }
+ }
+#ifdef EXP_NESTED_TABLES
+ if (nested_tables) {
+ if (max_width)
+ Stbl_update_enclosing(me->stbl, max_width, last_nonempty);
+ }
+#endif
+ CTRACE((tfp, " %d:done\n", lineno));
+ free(oldpos);
+ return lines_changed;
+}
+
+/* Simple table handling - public functions
+ * ----------------------------------------
+ */
+
+/* Cancel simple table handling
+*/
+void HText_cancelStbl(HText *me)
+{
+ if (!me || !me->stbl) {
+ CTRACE((tfp, "cancelStbl: ignored.\n"));
+ return;
+ }
+ CTRACE((tfp, "cancelStbl: ok, will do.\n"));
+#ifdef EXP_NESTED_TABLES
+ if (nested_tables) {
+ STable_info *stbl = me->stbl;
+
+ while (stbl) {
+ STable_info *enclosing = Stbl_get_enclosing(stbl);
+
+ Stbl_free(stbl);
+ stbl = enclosing;
+ }
+ } else
+#endif
+ Stbl_free(me->stbl);
+ me->stbl = NULL;
+}
+
+/* Start simple table handling
+*/
+void HText_startStblTABLE(HText *me, int alignment)
+{
+ if (me) {
+#ifdef EXP_NESTED_TABLES
+ STable_info *current = me->stbl;
+#endif
+
+#ifdef EXP_NESTED_TABLES
+ if (nested_tables) {
+ if (current)
+ new_line(me);
+ } else
+#endif
+ {
+ if (me->stbl) {
+ HText_cancelStbl(me); /* auto cancel previously open table */
+ }
+ }
+
+ me->stbl = Stbl_startTABLE(alignment);
+ if (me->stbl) {
+ CTRACE((tfp, "startStblTABLE: started.\n"));
+#ifdef EXP_NESTED_TABLES
+ if (nested_tables) {
+ Stbl_set_enclosing(me->stbl, current, me->last_anchor_before_stbl);
+ }
+#endif
+ me->last_anchor_before_stbl = me->last_anchor;
+ } else {
+ CTRACE((tfp, "startStblTABLE: failed.\n"));
+ }
+ }
+}
+
+#ifdef EXP_NESTED_TABLES
+static void free_enclosed_stbl(HText *me)
+{
+ if (me != NULL && me->enclosed_stbl != NULL) {
+ HTList *list = me->enclosed_stbl;
+ STable_info *stbl;
+
+ while (NULL != (stbl = (STable_info *) HTList_nextObject(list))) {
+ CTRACE((tfp, "endStblTABLE: finally free %p\n", (void *) me->stbl));
+ Stbl_free(stbl);
+ }
+ HTList_delete(me->enclosed_stbl);
+ me->enclosed_stbl = NULL;
+ }
+}
+
+#else
+#define free_enclosed_stbl(me) /* nothing */
+#endif
+
+/* Finish simple table handling
+ * Return TRUE if the table is nested inside another table.
+ */
+BOOLEAN HText_endStblTABLE(HText *me)
+{
+ int ncols, lines_changed = 0;
+ STable_info *enclosing = NULL;
+
+ if (!me || !me->stbl) {
+ CTRACE((tfp, "endStblTABLE: ignored.\n"));
+ free_enclosed_stbl(me);
+ return FALSE;
+ }
+ CTRACE((tfp, "endStblTABLE: ok, will try.\n"));
+
+ ncols = Stbl_finishTABLE(me->stbl);
+ CTRACE((tfp, "endStblTABLE: ncols = %d.\n", ncols));
+
+ if (ncols > 0) {
+ lines_changed = HText_insertBlanksInStblLines(me, ncols);
+ CTRACE((tfp, "endStblTABLE: changed %d lines, done.\n", lines_changed));
+#ifdef DISP_PARTIAL
+ /* allow HTDisplayPartial() to redisplay the changed lines.
+ * There is no harm if we got several stbl in the document, hope so.
+ */
+ NumOfLines_partial -= lines_changed; /* fake */
+#endif /* DISP_PARTIAL */
+ }
+#ifdef EXP_NESTED_TABLES
+ if (nested_tables) {
+ enclosing = Stbl_get_enclosing(me->stbl);
+ me->last_anchor_before_stbl = Stbl_get_last_anchor_before(me->stbl);
+ if (enclosing == NULL) {
+ Stbl_free(me->stbl);
+ free_enclosed_stbl(me);
+ } else {
+ if (me->enclosed_stbl == NULL)
+ me->enclosed_stbl = HTList_new();
+ HTList_addObject(me->enclosed_stbl, me->stbl);
+ CTRACE((tfp, "endStblTABLE: postpone free %p\n", (void *) me->stbl));
+ }
+ me->stbl = enclosing;
+ } else {
+ Stbl_free(me->stbl);
+ me->stbl = NULL;
+ }
+#else
+ Stbl_free(me->stbl);
+ me->stbl = NULL;
+#endif
+
+ CTRACE((tfp, "endStblTABLE: have%s enclosing table (%p)\n",
+ enclosing == 0 ? " NO" : "", (void *) enclosing));
+
+ return (BOOLEAN) (enclosing != 0);
+}
+
+/* Start simple table row
+*/
+void HText_startStblTR(HText *me, int alignment)
+{
+ if (!me || !me->stbl)
+ return;
+ if (Stbl_addRowToTable(me->stbl, alignment, me->Lines) < 0) {
+ HText_cancelStbl(me); /* give up */
+ }
+}
+
+/* Finish simple table row
+*/
+void HText_endStblTR(HText *me)
+{
+ if (!me || !me->stbl)
+ return;
+ /* should this do something?? */
+}
+
+/* Start simple table cell
+*/
+void HText_startStblTD(HText *me, int colspan,
+ int rowspan,
+ int alignment,
+ int isheader)
+{
+ if (!me || !me->stbl)
+ return;
+ if (colspan < 0)
+ colspan = 1;
+ if (colspan > TRST_MAXCOLSPAN) {
+ CTRACE((tfp, "*** COLSPAN=%d is too large, ignored!\n", colspan));
+ colspan = 1;
+ }
+ if (rowspan > TRST_MAXROWSPAN) {
+ CTRACE((tfp, "*** ROWSPAN=%d is too large, ignored!\n", rowspan));
+ rowspan = 1;
+ }
+ if (Stbl_addCellToTable(me->stbl, colspan, rowspan, alignment, isheader,
+ me->Lines,
+ HText_LastLineOffset(me),
+ HText_LastLineSize(me, FALSE)) < 0) {
+ HText_cancelStbl(me); /* give up */
+ }
+}
+
+/* Finish simple table cell
+*/
+void HText_endStblTD(HText *me)
+{
+ if (!me || !me->stbl)
+ return;
+ if (Stbl_finishCellInTable(me->stbl, TRST_ENDCELL_ENDTD,
+ me->Lines,
+ HText_LastLineOffset(me),
+ HText_LastLineSize(me, FALSE)) < 0) {
+ HText_cancelStbl(me); /* give up */
+ }
+}
+
+/* Remember COL info / Start a COLGROUP and remember info
+*/
+void HText_startStblCOL(HText *me, int span,
+ int alignment,
+ int isgroup)
+{
+ if (!me || !me->stbl)
+ return;
+ if (span <= 0)
+ span = 1;
+ if (span > TRST_MAXCOLSPAN) {
+ CTRACE((tfp, "*** SPAN=%d is too large, ignored!\n", span));
+ span = 1;
+ }
+ if (Stbl_addColInfo(me->stbl, span, alignment, isgroup) < 0) {
+ HText_cancelStbl(me); /* give up */
+ }
+}
+
+/* Finish a COLGROUP
+*/
+void HText_endStblCOLGROUP(HText *me)
+{
+ if (!me || !me->stbl)
+ return;
+ if (Stbl_finishColGroup(me->stbl) < 0) {
+ HText_cancelStbl(me); /* give up */
+ }
+}
+
+/* Start a THEAD / TFOOT / TBODY - remember its alignment info
+*/
+void HText_startStblRowGroup(HText *me, int alignment)
+{
+ if (!me || !me->stbl)
+ return;
+ if (Stbl_addRowGroup(me->stbl, alignment) < 0) {
+ HText_cancelStbl(me); /* give up */
+ }
+}
+
+static void compute_show_number(TextAnchor *a)
+{
+ HTAnchor *cur, *tst;
+ TextAnchor *b;
+ int match;
+
+ a->show_number = a->number;
+ if (unique_urls
+ && HTMainText != 0
+ && HTMainText->first_anchor != 0
+ && a->anchor != 0
+ && (cur = a->anchor->dest) != 0
+ && cur->parent != 0
+ && cur->parent->address != 0) {
+
+ match = 0;
+ for (b = HTMainText->first_anchor; b != a; b = b->next) {
+ if (b->anchor != 0
+ && (tst = b->anchor->dest) != 0
+ && tst->parent != 0
+ && tst->parent->address != 0
+ && !strcmp(cur->parent->address,
+ tst->parent->address)
+ && !strcmp(NonNull(a->anchor->tag), NonNull(b->anchor->tag))) {
+ match = b->show_number;
+ break;
+ }
+ }
+ if (match)
+ a->show_number = match;
+ else
+ a->show_number = HTMainText->next_number++;
+ }
+}
+
+/* Anchor handling
+ * ---------------
+ */
+static void add_link_number(HText *text, TextAnchor *a, int save_position)
+{
+ char marker[32];
+
+ /*
+ * If we are doing link_numbering add the link number.
+ */
+ if ((a->number > 0)
+#ifdef USE_PRETTYSRC
+ && (text->source ? !psrcview_no_anchor_numbering : 1)
+#endif
+ && links_are_numbered()) {
+ char saved_lastchar = text->LastChar;
+ int saved_linenum = text->Lines;
+ HTAnchor *link_dest;
+ char *link_text;
+
+ compute_show_number(a);
+
+ if (dump_links_inline
+ && (link_dest = HTAnchor_followLink(a->anchor)) != 0
+ && (link_text = HTAnchor_address(link_dest)) != 0) {
+ HText_appendText(text, "[");
+ HText_appendText(text, link_text);
+ HText_appendText(text, "]");
+ } else {
+ sprintf(marker, "[%d]", a->show_number);
+ HText_appendText(text, marker);
+ }
+ if (saved_linenum && text->Lines && saved_lastchar != ' ')
+ text->LastChar = ']'; /* if marker not after space caused split */
+ if (save_position) {
+ a->line_num = text->Lines;
+ a->line_pos = (short) text->last_line->size;
+ }
+ }
+}
+
+/* Start an anchor field
+*/
+int HText_beginAnchor(HText *text, int underline,
+ HTChildAnchor *anc)
+{
+ TextAnchor *a;
+
+ POOLtypecalloc(TextAnchor, a);
+
+ if (a == NULL)
+ outofmem(__FILE__, "HText_beginAnchor");
+
+ a->inUnderline = (BOOLEAN) underline;
+
+ a->sgml_offset = SGML_offset();
+ a->line_num = text->Lines;
+ a->line_pos = (short) text->last_line->size;
+ if (text->last_anchor) {
+ text->last_anchor->next = a;
+ } else {
+ text->first_anchor = a;
+ }
+ a->next = 0;
+ a->anchor = anc;
+ a->extent = 0;
+ a->link_type = HYPERTEXT_ANCHOR;
+ text->last_anchor = a;
+
+ if (track_internal_links
+ && HTAnchor_followTypedLink(anc, HTInternalLink)) {
+ a->number = ++(text->last_anchor_number);
+ a->link_type = INTERNAL_LINK_ANCHOR;
+ } else if (HTAnchor_followLink(anc)) {
+ a->number = ++(text->last_anchor_number);
+ } else {
+ a->number = 0;
+ }
+ a->show_number = 0;
+
+ if (number_links_on_left)
+ add_link_number(text, a, TRUE);
+ return (a->number);
+}
+
+/* If !really, report whether the anchor is empty. */
+static BOOL HText_endAnchor0(HText *text, int number,
+ int really)
+{
+ TextAnchor *a;
+
+ /*
+ * The number argument is set to 0 in HTML.c and
+ * LYCharUtils.c when we want to end the anchor
+ * for the immediately preceding HText_beginAnchor()
+ * call. If it's greater than 0, we want to handle
+ * a particular anchor. This allows us to set links
+ * for positions indicated by NAME or ID attributes,
+ * without needing to close any anchor with an HREF
+ * within which that link might be embedded. -FM
+ */
+ if (number <= 0 || number == text->last_anchor->number) {
+ a = text->last_anchor;
+ } else {
+ for (a = text->first_anchor; a; a = a->next) {
+ if (a->number == number) {
+ break;
+ }
+ }
+ if (a == NULL) {
+ /*
+ * There's no anchor with that number,
+ * so we'll default to the last anchor,
+ * and cross our fingers. -FM
+ */
+ a = text->last_anchor;
+ }
+ }
+
+ CTRACE((tfp, "GridText:HText_endAnchor0: number:%d link_type:%d\n",
+ a->number, a->link_type));
+ if (a->link_type == INPUT_ANCHOR) {
+ /*
+ * Shouldn't happen, but put test here anyway to be safe. - LE
+ */
+
+ CTRACE((tfp,
+ "BUG: HText_endAnchor0: internal error: last anchor was input field!\n"));
+ return FALSE;
+ }
+
+ if (a->number) {
+ /*
+ * If it goes somewhere...
+ */
+ int i, j, k, l;
+ BOOL remove_numbers_on_empty = (BOOL) ((links_are_numbered() &&
+ ((text->hiddenlinkflag != HIDDENLINKS_MERGE)
+ || (LYNoISMAPifUSEMAP &&
+ !(text->node_anchor && text->node_anchor->bookmark)
+ && HTAnchor_isISMAPScript
+ (HTAnchor_followLink(a->anchor))))));
+ HTLine *last = text->last_line;
+ HTLine *prev = text->last_line->prev;
+ HTLine *start = last;
+ int CurBlankExtent = 0;
+ int BlankExtent = 0;
+ int extent_adjust = 0;
+
+ /* Find the length taken by the anchor */
+ l = text->Lines; /* lineno of last */
+
+ /* the last line of an anchor may contain a trailing blank,
+ * which will be trimmed later. Discount it from the extent.
+ */
+ if (l > a->line_num) {
+ for (i = start->size; i > 0; --i) {
+ if (isspace(UCH(start->data[i - 1]))) {
+ --extent_adjust;
+ } else {
+ break;
+ }
+ }
+ }
+
+ while (l > a->line_num) {
+ extent_adjust += start->size;
+ start = start->prev;
+ l--;
+ }
+ /* Now start is the start line of the anchor */
+ extent_adjust += start->size - a->line_pos;
+ start = last; /* Used later */
+
+ /*
+ * Check if the anchor content has only
+ * white and special characters, starting
+ * with the content on the last line. -FM
+ */
+ a->extent = (short) (a->extent + extent_adjust);
+ if (a->extent > (int) last->size) {
+ /*
+ * The anchor extends over more than one line,
+ * so set up to check the entire last line. -FM
+ */
+ i = last->size;
+ } else {
+ /*
+ * The anchor is restricted to the last line,
+ * so check from the start of the anchor. -FM
+ */
+ i = a->extent;
+ }
+ k = j = (last->size - i);
+ while (j < (int) last->size) {
+ if (!IsSpecialAttrChar(last->data[j]) &&
+ !isspace(UCH(last->data[j])) &&
+ last->data[j] != HT_NON_BREAK_SPACE &&
+ last->data[j] != HT_EN_SPACE)
+ break;
+ i--;
+ j++;
+ }
+ if (i == 0) {
+ if (a->extent > (int) last->size) {
+ /*
+ * The anchor starts on a preceding line, and
+ * the last line has only white and special
+ * characters, so declare the entire extent
+ * of the last line as blank. -FM
+ */
+ CurBlankExtent = BlankExtent = last->size;
+ } else {
+ /*
+ * The anchor starts on the last line, and
+ * has only white or special characters, so
+ * declare the anchor's extent as blank. -FM
+ */
+ CurBlankExtent = BlankExtent = a->extent;
+ }
+ }
+ /*
+ * While the anchor starts on a line preceding
+ * the one we just checked, and the one we just
+ * checked has only white and special characters,
+ * check whether the anchor's content on the
+ * immediately preceding line also has only
+ * white and special characters. -FM
+ */
+ while (i == 0 &&
+ (a->extent > CurBlankExtent ||
+ (a->extent == CurBlankExtent &&
+ k == 0 &&
+ prev != text->last_line &&
+ (prev->size == 0 ||
+ prev->data[prev->size - 1] == ']')))) {
+ start = prev;
+ k = j = prev->size - a->extent + CurBlankExtent;
+ if (j < 0) {
+ /*
+ * The anchor starts on a preceding line,
+ * so check all of this line. -FM
+ */
+ j = 0;
+ i = prev->size;
+ } else {
+ /*
+ * The anchor starts on this line. -FM
+ */
+ i = a->extent - CurBlankExtent;
+ }
+ while (j < (int) prev->size) {
+ if (!IsSpecialAttrChar(prev->data[j]) &&
+ !isspace(UCH(prev->data[j])) &&
+ prev->data[j] != HT_NON_BREAK_SPACE &&
+ prev->data[j] != HT_EN_SPACE)
+ break;
+ i--;
+ j++;
+ }
+ if (i == 0) {
+ if (a->extent > (CurBlankExtent + (int) prev->size) ||
+ (a->extent == CurBlankExtent + (int) prev->size &&
+ k == 0 &&
+ prev->prev != text->last_line &&
+ (prev->prev->size == 0 ||
+ prev->prev->data[prev->prev->size - 1] == ']'))) {
+ /*
+ * This line has only white and special
+ * characters, so treat its entire extent
+ * as blank, and decrement the pointer for
+ * the line to be analyzed. -FM
+ */
+ CurBlankExtent += prev->size;
+ BlankExtent = CurBlankExtent;
+ prev = prev->prev;
+ } else {
+ /*
+ * The anchor starts on this line, and it
+ * has only white or special characters, so
+ * declare the anchor's extent as blank. -FM
+ */
+ BlankExtent = a->extent;
+ break;
+ }
+ }
+ }
+ if (!really) { /* Just report whether it is empty */
+ a->extent = (short) (a->extent - extent_adjust);
+ return (BOOL) (i == 0);
+ }
+ if (i == 0) {
+ /*
+ * It's an invisible anchor probably from an ALT=""
+ * or an ignored ISMAP attribute due to a companion
+ * USEMAP. -FM
+ */
+ a->show_anchor = NO;
+
+ CTRACE((tfp,
+ "HText_endAnchor0: hidden (line,pos,ext,BlankExtent):(%d,%d,%d,%d)",
+ a->line_num, a->line_pos, a->extent,
+ BlankExtent));
+
+ /*
+ * If links are numbered, then try to get rid of the
+ * numbered bracket and adjust the anchor count. -FM
+ *
+ * Well, let's do this only if -hiddenlinks=merged is not in
+ * effect, or if we can be reasonably sure that
+ * this is the result of an intentional non-generation of
+ * anchor text via NO_ISMAP_IF_USEMAP. In other cases it can
+ * actually be a feature that numbered links alert the viewer
+ * to the presence of a link which is otherwise not selectable -
+ * possibly caused by HTML errors. - kw
+ */
+ if (remove_numbers_on_empty) {
+ int NumSize = 0;
+ TextAnchor *anc;
+
+ /*
+ * Set start->data[j] to the close-square-bracket,
+ * or to the beginning of the line on which the
+ * anchor start. -FM
+ */
+ if (start == last) {
+ /*
+ * The anchor starts on the last line. -FM
+ */
+ j = (last->size - a->extent - 1);
+ } else {
+ /*
+ * The anchor starts on a previous line. -FM
+ */
+ prev = start->prev;
+ j = (start->size - a->extent + CurBlankExtent - 1);
+ }
+ if (j < 0)
+ j = 0;
+ i = j;
+
+ /*
+ * If start->data[j] is a close-square-bracket, verify
+ * that it's the end of the numbered bracket, and if so,
+ * strip the numbered bracket. If start->data[j] is not
+ * a close-square-bracket, check whether we had a wrap
+ * and the close-square-bracket is at the end of the
+ * previous line. If so, strip the numbered bracket
+ * from that line. -FM
+ */
+ if (start->data[j] == ']') {
+ j--;
+ NumSize++;
+ while (j >= 0 && isdigit(UCH(start->data[j]))) {
+ j--;
+ NumSize++;
+ }
+ while (j < 0) {
+ j++;
+ NumSize--;
+ }
+ if (start->data[j] == '[') {
+ /*
+ * The numbered bracket is entirely
+ * on this line. -FM
+ */
+ NumSize++;
+ if (start == last && (int) text->permissible_split > j) {
+ if ((int) text->permissible_split - NumSize < j)
+ text->permissible_split = (unsigned) j;
+ else
+ text->permissible_split -= (unsigned) NumSize;
+ }
+ k = j + NumSize;
+ while (k < (int) start->size)
+ start->data[j++] = start->data[k++];
+ for (anc = a; anc; anc = anc->next) {
+ if (anc->line_num == a->line_num &&
+ anc->line_pos >= NumSize) {
+ anc->line_pos = (short) (anc->line_pos - NumSize);
+ }
+ }
+ start->size = (unsigned short) j;
+ start->data[j++] = '\0';
+ while (j < k)
+ start->data[j++] = '\0';
+ } else if (prev && prev->size > 1) {
+ k = (i + 1);
+ j = (prev->size - 1);
+ while ((j >= 0) && IsSpecialAttrChar(prev->data[j]))
+ j--;
+ i = (j + 1);
+ while (j >= 0 &&
+ isdigit(UCH(prev->data[j]))) {
+ j--;
+ NumSize++;
+ }
+ while (j < 0) {
+ j++;
+ NumSize--;
+ }
+ if (prev->data[j] == '[') {
+ /*
+ * The numbered bracket started on the
+ * previous line, and part of it was
+ * wrapped to this line. -FM
+ */
+ while (i < (int) prev->size)
+ prev->data[j++] = prev->data[i++];
+ prev->size = (unsigned short) j;
+ prev->data[j] = '\0';
+ while (j < i)
+ prev->data[j++] = '\0';
+ if (start == last && text->permissible_split > 0) {
+ if ((int) text->permissible_split < k)
+ text->permissible_split = 0;
+ else
+ text->permissible_split -= (unsigned) k;
+ }
+ j = 0;
+ i = k;
+ while (k < (int) start->size)
+ start->data[j++] = start->data[k++];
+ for (anc = a; anc; anc = anc->next) {
+ if (anc->line_num == a->line_num &&
+ anc->line_pos >= i) {
+ anc->line_pos = (short) (anc->line_pos - i);
+ }
+ }
+ start->size = (unsigned short) j;
+ start->data[j++] = '\0';
+ while (j < k)
+ start->data[j++] = '\0';
+ } else {
+ /*
+ * Shucks! We didn't find the
+ * numbered bracket. -FM
+ */
+ a->show_anchor = YES;
+ }
+ } else {
+ /*
+ * Shucks! We didn't find the
+ * numbered bracket. -FM
+ */
+ a->show_anchor = YES;
+ }
+ } else if (prev && prev->size > 2) {
+ j = (prev->size - 1);
+ while ((j >= 0) && IsSpecialAttrChar(prev->data[j]))
+ j--;
+ if (j < 0)
+ j = 0;
+ if ((j >= 2) &&
+ (prev->data[j] == ']' &&
+ isdigit(UCH(prev->data[j - 1])))) {
+ j--;
+ NumSize++;
+ while (j >= 0 &&
+ isdigit(UCH(prev->data[j]))) {
+ j--;
+ NumSize++;
+ }
+ while (j < 0) {
+ j++;
+ NumSize--;
+ }
+ if (prev->data[j] == '[') {
+ /*
+ * The numbered bracket is all on the
+ * previous line, and the anchor content
+ * was wrapped to the last line. -FM
+ */
+ NumSize++;
+ k = j + NumSize;
+ while (k < (int) prev->size)
+ prev->data[j++] = prev->data[k++];
+ prev->size = (unsigned short) j;
+ prev->data[j++] = '\0';
+ while (j < k)
+ prev->data[j++] = '\0';
+ } else {
+ /*
+ * Shucks! We didn't find the
+ * numbered bracket. -FM
+ */
+ a->show_anchor = YES;
+ }
+ } else {
+ /*
+ * Shucks! We didn't find the
+ * numbered bracket. -FM
+ */
+ a->show_anchor = YES;
+ }
+ } else {
+ /*
+ * Shucks! We didn't find the
+ * numbered bracket. -FM
+ */
+ a->show_anchor = YES;
+ }
+ }
+ } else {
+ if (!number_links_on_left)
+ add_link_number(text, a, FALSE);
+ /*
+ * The anchor's content is not restricted to only
+ * white and special characters, so we'll show it
+ * as a link. -FM
+ */
+ a->show_anchor = YES;
+ if (BlankExtent) {
+ CTRACE((tfp,
+ "HText_endAnchor0: blanks (line,pos,ext,BlankExtent):(%d,%d,%d,%d)",
+ a->line_num, a->line_pos, a->extent,
+ BlankExtent));
+ }
+ }
+ if (a->show_anchor == NO) {
+ /*
+ * The anchor's content is restricted to white
+ * and special characters, so set its number
+ * and extent to zero, decrement the visible
+ * anchor number counter, and add this anchor
+ * to the hidden links list. -FM
+ */
+ a->extent = 0;
+ if (text->hiddenlinkflag != HIDDENLINKS_MERGE) {
+ a->number = 0;
+ text->last_anchor_number--;
+ HText_AddHiddenLink(text, a);
+ }
+ } else {
+ /*
+ * The anchor's content is not restricted to white
+ * and special characters, so we'll display the
+ * content, but shorten its extent by any trailing
+ * blank lines we've detected. -FM
+ */
+ a->extent = (short) (a->extent - ((BlankExtent < a->extent)
+ ? BlankExtent
+ : 0));
+ }
+ if (BlankExtent || a->extent <= 0 || a->number <= 0) {
+ CTRACE((tfp,
+ "->[%d](%d,%d,%d,%d)\n",
+ a->number,
+ a->line_num, a->line_pos, a->extent,
+ BlankExtent));
+ }
+ } else {
+ if (!really) /* Just report whether it is empty */
+ return FALSE;
+ /*
+ * It's a named anchor without an HREF, so it
+ * should be registered but not shown as a
+ * link. -FM
+ */
+ a->show_anchor = NO;
+ a->extent = 0;
+ }
+ return FALSE;
+}
+
+void HText_endAnchor(HText *text, int number)
+{
+ HText_endAnchor0(text, number, 1);
+}
+
+/*
+ This returns whether the given anchor has blank content. Shamelessly copied
+ from HText_endAnchor. The values returned are meaningful only for "normal"
+ links - like ones produced by <a href=".">foo</a>, no inputs, etc. - VH
+*/
+#ifdef MARK_HIDDEN_LINKS
+BOOL HText_isAnchorBlank(HText *text, int number)
+{
+ return HText_endAnchor0(text, number, 0);
+}
+#endif /* MARK_HIDDEN_LINKS */
+
+void HText_appendText(HText *text, const char *str)
+{
+ const char *p;
+
+ if (str != NULL &&
+ text != NULL &&
+ text->halted != 3) {
+ for (p = str; *p; p++) {
+ HText_appendCharacter(text, *p);
+ }
+ }
+}
+
+static int remove_special_attr_chars(char *buf)
+{
+ register char *cp;
+ register int soft_newline_count = 0;
+
+ for (cp = buf; *cp != '\0'; cp++) {
+ /*
+ * Don't print underline chars.
+ */
+ soft_newline_count += (*cp == LY_SOFT_NEWLINE);
+ if (!IsSpecialAttrChar(*cp)) {
+ *buf++ = *cp;
+ }
+ }
+ *buf = '\0';
+ return soft_newline_count;
+}
+
+/*
+ * This function trims blank lines from the end of the document, and
+ * then gets the hightext from the text by finding the char position,
+ * and brings the anchors in line with the text by adding the text
+ * offset to each of the anchors.
+ */
+void HText_endAppend(HText *text)
+{
+ HTLine *line_ptr;
+
+ if (!text)
+ return;
+
+ CTRACE((tfp, "GridText: Entering HText_endAppend\n"));
+
+ /*
+ * Create a blank line at the bottom.
+ */
+ new_line(text);
+
+ if (text->halted) {
+ if (text->stbl) {
+ HText_cancelStbl(text);
+ }
+ /*
+ * If output was stopped because memory was low, and we made
+ * it to the end of the document, reset those flags and hope
+ * things are better now. - kw
+ */
+ LYFakeZap(NO);
+ text->halted = 0;
+ } else if (text->stbl) {
+ /*
+ * Could happen if TABLE end tag was missing.
+ * Alternatively we could cancel in this case. - kw
+ */
+ HText_endStblTABLE(text);
+ }
+
+ /*
+ * Get the first line.
+ */
+ if (LYtrimBlankLines && (line_ptr = FirstHTLine(text)) != 0) {
+ /*
+ * Remove blank lines at the end of the document.
+ */
+ while (text->last_line->data[0] == '\0' && text->Lines > 0) {
+ HTLine *next_to_the_last_line = text->last_line->prev;
+
+ CTRACE((tfp, "GridText: Removing bottom blank line: `%s'\n",
+ text->last_line->data));
+ /*
+ * line_ptr points to the first line.
+ */
+ next_to_the_last_line->next = line_ptr;
+ line_ptr->prev = next_to_the_last_line;
+ freeHTLine(text, text->last_line);
+ text->last_line = next_to_the_last_line;
+ text->Lines--;
+ CTRACE((tfp, "GridText: New bottom line: `%s'\n",
+ text->last_line->data));
+ }
+ }
+
+ /*
+ * Fix up the anchor structure values and
+ * create the hightext strings. -FM
+ */
+ HText_trimHightext(text, TRUE, -1);
+}
+
+/*
+ * This function gets the hightext from the text by finding the char
+ * position, and brings the anchors in line with the text by adding the text
+ * offset to each of the anchors.
+ *
+ * `Forms input' fields cannot be displayed properly without this function
+ * to be invoked (detected in display_partial mode).
+ *
+ * If final is set, this is the final fixup; if not set, we don't have
+ * to do everything because there should be another call later.
+ *
+ * BEFORE this function has treated a TextAnchor, its line_pos and
+ * extent fields are counting bytes in the HTLine data, including
+ * invisible special attribute chars and counting UTF-8 multibyte
+ * characters as multiple bytes.
+ *
+ * AFTER the adjustment, the anchor line_pos (and hightext offset if
+ * applicable) fields indicate x positions in terms of displayed character
+ * cells, and the extent field apparently is unimportant; the anchor text has
+ * been copied to the hightext fields (which should have been NULL up to that
+ * point), with special attribute chars removed.
+ *
+ * This needs to be done so that display_page finds the anchors in the
+ * form it expects when it sets the links[] elements.
+ */
+static void HText_trimHightext(HText *text,
+ int final,
+ int stop_before)
+{
+ int cur_line, cur_shift;
+ TextAnchor *anchor_ptr;
+ TextAnchor *prev_a = NULL;
+ HTLine *line_ptr;
+ HTLine *line_ptr2;
+ unsigned char ch;
+ char *hilite_str;
+ int hilite_len;
+ int actual_len;
+ int count_line;
+
+ if (!text)
+ return;
+
+ if (final) {
+ CTRACE((tfp, "GridText: Entering HText_trimHightext (final)\n"));
+ } else {
+ if (stop_before < 0 || stop_before > text->Lines)
+ stop_before = text->Lines;
+ CTRACE((tfp,
+ "GridText: Entering HText_trimHightext (partial: 0..%d/%d)\n",
+ stop_before, text->Lines));
+ }
+
+ /*
+ * Get the first line.
+ */
+ line_ptr = FirstHTLine(text);
+ cur_line = 0;
+
+ /*
+ * Fix up the anchor structure values and
+ * create the hightext strings. -FM
+ */
+ for (anchor_ptr = text->first_anchor;
+ anchor_ptr != NULL;
+ prev_a = anchor_ptr, anchor_ptr = anchor_ptr->next) {
+ int anchor_col;
+
+ re_parse:
+ /*
+ * Find the right line.
+ */
+ for (; anchor_ptr->line_num > cur_line;
+ line_ptr = line_ptr->next, cur_line++) {
+ ; /* null body */
+ }
+
+ if (!final) {
+ /*
+ * If this is not the final call, stop when we have reached
+ * the last line, or the very end of preceding line.
+ * The last line is probably still not finished. - kw
+ */
+ if (cur_line >= stop_before)
+ break;
+ if (anchor_ptr->line_num >= text->Lines - 1
+ && anchor_ptr->line_pos >= (int) text->last_line->prev->size)
+ break;
+ /*
+ * Also skip this anchor if it looks like HText_endAnchor
+ * is not yet done with it. - kw
+ */
+ if (!anchor_ptr->extent && anchor_ptr->number &&
+ (anchor_ptr->link_type & HYPERTEXT_ANCHOR) &&
+ !anchor_ptr->show_anchor &&
+ anchor_ptr->number == text->last_anchor_number)
+ continue;
+ }
+
+ /*
+ * If hightext has already been set, then we must have already
+ * done the trimming & adjusting for this anchor, so avoid
+ * doing it a second time. - kw
+ */
+ if (LYGetHiTextStr(anchor_ptr, 0) != NULL)
+ continue;
+
+ if (anchor_ptr->line_pos > (int) line_ptr->size) {
+ anchor_ptr->line_pos = (short) line_ptr->size;
+ }
+ if (anchor_ptr->line_pos < 0) {
+ anchor_ptr->line_pos = 0;
+ anchor_ptr->line_num = cur_line;
+ }
+ CTRACE((tfp,
+ "GridText: Anchor found on line:%d col:%d [%05d:%d] ext:%d\n",
+ cur_line,
+ anchor_ptr->line_pos,
+ anchor_ptr->sgml_offset,
+ anchor_ptr->number,
+ anchor_ptr->extent));
+
+ cur_shift = 0;
+ /*
+ * Strip off any spaces or SpecialAttrChars at the beginning,
+ * if they exist, but only on HYPERTEXT_ANCHORS.
+ */
+ if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) {
+ ch = UCH(line_ptr->data[anchor_ptr->line_pos]);
+ while (isspace(ch) ||
+ IsSpecialAttrChar(ch)) {
+ anchor_ptr->line_pos++;
+ anchor_ptr->extent--;
+ cur_shift++;
+ ch = UCH(line_ptr->data[anchor_ptr->line_pos]);
+ }
+ }
+ if (anchor_ptr->extent < 0) {
+ anchor_ptr->extent = 0;
+ }
+
+ CTRACE((tfp, "anchor text: '%s'\n", line_ptr->data));
+ /*
+ * If the link begins with an end of line and we have more lines, then
+ * start the highlighting on the next line. -FM.
+ *
+ * But if an empty anchor is at the end of line and empty, keep it
+ * where it is, unless the previous anchor in the list (if any) already
+ * starts later. - kw
+ */
+ if ((unsigned) anchor_ptr->line_pos >= strlen(line_ptr->data)) {
+ if (cur_line < text->Lines &&
+ (anchor_ptr->extent ||
+ anchor_ptr->line_pos != (int) line_ptr->size ||
+ (prev_a && /* How could this happen? */
+ (prev_a->line_num > anchor_ptr->line_num)))) {
+ anchor_ptr->line_num++;
+ anchor_ptr->line_pos = 0;
+ CTRACE((tfp, "found anchor at end of line\n"));
+ goto re_parse;
+ } else {
+ CTRACE((tfp, "found anchor at end of line, leaving it there\n"));
+ }
+ }
+
+ /*
+ * Copy the link name into the data structure.
+ */
+ if (anchor_ptr->extent > 0
+ && anchor_ptr->line_pos >= 0) {
+ int size = (int) line_ptr->size - anchor_ptr->line_pos;
+
+ if (size > anchor_ptr->extent)
+ size = anchor_ptr->extent;
+ LYClearHiText(anchor_ptr);
+ LYSetHiText(anchor_ptr,
+ &line_ptr->data[anchor_ptr->line_pos],
+ (unsigned) size);
+ } else {
+ LYClearHiText(anchor_ptr);
+ LYSetHiText(anchor_ptr, "", 0);
+ }
+
+ /*
+ * If the anchor extends over more than one line, copy that into the
+ * data structure.
+ */
+ hilite_str = LYGetHiTextStr(anchor_ptr, 0);
+ hilite_len = (int) strlen(hilite_str);
+ actual_len = anchor_ptr->extent;
+
+ line_ptr2 = line_ptr;
+ assert(line_ptr2 != 0);
+
+ count_line = cur_line;
+ while (actual_len > hilite_len) {
+ HTLine *old_line_ptr2 = line_ptr2;
+
+ count_line++;
+ if ((line_ptr2 = line_ptr2->next) == NULL)
+ break;
+
+ if (!final
+ && count_line >= stop_before) {
+ LYClearHiText(anchor_ptr);
+ break;
+ } else if (old_line_ptr2 == text->last_line) {
+ break;
+ }
+
+ /*
+ * Double check that we have a line pointer, and if so, copy into
+ * highlight text.
+ */
+ if (line_ptr2) {
+ char *hi_string = NULL;
+ int hi_offset = line_ptr2->offset;
+
+ StrnAllocCopy(hi_string,
+ line_ptr2->data,
+ (size_t) (actual_len - hilite_len));
+ actual_len -= (int) strlen(hi_string);
+ /*handle LY_SOFT_NEWLINEs -VH */
+ hi_offset += remove_special_attr_chars(hi_string);
+
+ if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) {
+ LYTrimTrailing(hi_string);
+ }
+ if (non_empty(hi_string)) {
+ LYAddHiText(anchor_ptr, hi_string, hi_offset);
+ } else if (actual_len > hilite_len) {
+ LYAddHiText(anchor_ptr, "", hi_offset);
+ }
+ FREE(hi_string);
+ }
+ }
+
+ if (!final
+ && count_line >= stop_before) {
+ break;
+ }
+
+ hilite_str = LYGetHiTextStr(anchor_ptr, 0);
+ remove_special_attr_chars(hilite_str);
+ if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) {
+ LYTrimTrailing(hilite_str);
+ }
+
+ /*
+ * Save the offset (bytes) of the anchor in the line's data.
+ */
+ anchor_col = anchor_ptr->line_pos;
+
+ /*
+ * Subtract any formatting characters from the x position of the link.
+ */
+#ifdef WIDEC_CURSES
+ if (anchor_ptr->line_pos > 0) {
+ /*
+ * LYstrExtent filters out the formatting characters, so we do not
+ * have to count them here, except for soft newlines.
+ */
+ anchor_ptr->line_pos = (short) LYstrExtent2(line_ptr->data, anchor_col);
+ if (line_ptr->data[0] == LY_SOFT_NEWLINE)
+ anchor_ptr->line_pos = (short) (anchor_ptr->line_pos + 1);
+ }
+#else /* 8-bit curses, etc. */
+ if (anchor_ptr->line_pos > 0) {
+ register int offset = 0, i = 0;
+ int have_soft_newline_in_1st_line = 0;
+
+ for (; i < anchor_col; i++) {
+ if (IS_UTF_EXTRA(line_ptr->data[i]) ||
+ IsSpecialAttrChar(line_ptr->data[i])) {
+ offset++;
+ have_soft_newline_in_1st_line += (line_ptr->data[i] == LY_SOFT_NEWLINE);
+ }
+ }
+ anchor_ptr->line_pos = (short) (anchor_ptr->line_pos - offset);
+ /*handle LY_SOFT_NEWLINEs -VH */
+ anchor_ptr->line_pos = (short) (anchor_ptr->line_pos + have_soft_newline_in_1st_line);
+ }
+#endif /* WIDEC_CURSES */
+
+ /*
+ * Set the line number.
+ */
+ anchor_ptr->line_pos = (short) (anchor_ptr->line_pos + line_ptr->offset);
+ anchor_ptr->line_num = cur_line;
+
+ CTRACE((tfp, "GridText: add link on line %d col %d [%d] %s\n",
+ cur_line, anchor_ptr->line_pos,
+ anchor_ptr->number, "in HText_trimHightext"));
+ }
+}
+
+/* Return the anchor associated with this node
+*/
+HTParentAnchor *HText_nodeAnchor(HText *text)
+{
+ return text->node_anchor;
+}
+
+/* GridText specials
+ * =================
+ */
+
+/*
+ * HText_childNextNumber() returns the anchor with index [number],
+ * using a pointer from the previous number (=optimization) or NULL.
+ */
+HTChildAnchor *HText_childNextNumber(int number, void **prev)
+{
+ /* Sorry, TextAnchor is not declared outside this file, use a cast. */
+ TextAnchor *a = (TextAnchor *) *prev;
+
+ if (!HTMainText || number <= 0)
+ return (HTChildAnchor *) 0; /* Fail */
+ if (number == 1 || !a)
+ a = HTMainText->first_anchor;
+
+ /* a strange thing: positive a->number's are sorted,
+ * and between them several a->number's may be 0 -- skip them
+ */
+ for (; a && a->number != number; a = a->next) ;
+
+ if (!a)
+ return (HTChildAnchor *) 0; /* Fail */
+ *prev = (void *) a;
+ return a->anchor;
+}
+
+/*
+ * For the -unique-urls option, find the anchor-number of the first occurrence
+ * of a given address.
+ */
+int HText_findAnchorNumber(void *avoid)
+{
+ TextAnchor *a = (TextAnchor *) avoid;
+
+ if (a->number > 0 && a->show_number == 0)
+ compute_show_number(a);
+
+ return a->show_number;
+}
+
+static const char *inputFieldDesc(FormInfo * input)
+{
+ const char *result = 0;
+
+ switch (input->type) {
+ case F_TEXT_TYPE:
+ result = gettext("text entry field");
+ break;
+ case F_PASSWORD_TYPE:
+ result = gettext("password entry field");
+ break;
+ case F_CHECKBOX_TYPE:
+ result = gettext("checkbox");
+ break;
+ case F_RADIO_TYPE:
+ result = gettext("radio button");
+ break;
+ case F_SUBMIT_TYPE:
+ result = gettext("submit button");
+ break;
+ case F_RESET_TYPE:
+ result = gettext("reset button");
+ break;
+ case F_BUTTON_TYPE:
+ result = gettext("script button");
+ break;
+ case F_OPTION_LIST_TYPE:
+ result = gettext("popup menu");
+ break;
+ case F_HIDDEN_TYPE:
+ result = gettext("hidden form field");
+ break;
+ case F_TEXTAREA_TYPE:
+ result = gettext("text entry area");
+ break;
+ case F_RANGE_TYPE:
+ result = gettext("range entry field");
+ break;
+ case F_FILE_TYPE:
+ result = gettext("file entry field");
+ break;
+ case F_TEXT_SUBMIT_TYPE:
+ result = gettext("text-submit field");
+ break;
+ case F_IMAGE_SUBMIT_TYPE:
+ result = gettext("image-submit button");
+ break;
+ case F_KEYGEN_TYPE:
+ result = gettext("keygen field");
+ break;
+ default:
+ result = gettext("unknown form field");
+ break;
+ }
+ return result;
+}
+
+/*
+ * HText_FormDescNumber() returns a description of the form field
+ * with index N. The index corresponds to the [number] we print
+ * for the field. -FM & LE
+ */
+void HText_FormDescNumber(int number,
+ const char **desc)
+{
+ TextAnchor *a;
+
+ if (!desc)
+ return;
+
+ if (!(HTMainText && HTMainText->first_anchor) || number <= 0) {
+ *desc = gettext("unknown field or link");
+ return;
+ }
+
+ for (a = HTMainText->first_anchor; a; a = a->next) {
+ if (a->number == number) {
+ if (!(a->input_field && a->input_field->type)) {
+ *desc = gettext("unknown field or link");
+ return;
+ }
+ break;
+ }
+ }
+
+ if (a != NULL)
+ *desc = inputFieldDesc(a->input_field);
+}
+
+/* HTGetRelLinkNum returns the anchor number to which follow_link_number()
+ * is to jump (input was 123+ or 123- or 123+g or 123-g or 123 or 123g)
+ * num is the number specified
+ * rel is 0 or '+' or '-'
+ * cur is the current link
+ */
+int HTGetRelLinkNum(int num,
+ int rel,
+ int cur)
+{
+ TextAnchor *a, *l = 0;
+ int scrtop = HText_getTopOfScreen(); /*XXX +1? */
+ int curline = links[cur].anchor_line_num;
+ int curpos = links[cur].lx;
+ int on_screen = (curline >= scrtop && curline < (scrtop + display_lines));
+
+ /* curanchor may or may not be the "current link", depending whether it's
+ * on the current screen
+ */
+ int curanchor = links[cur].anchor_number;
+
+ CTRACE((tfp, "HTGetRelLinkNum(%d,%d,%d) -- HTMainText=%p\n",
+ num, rel, cur, (void *) HTMainText));
+ CTRACE((tfp,
+ " scrtop=%d, curline=%d, curanchor=%d, display_lines=%d, %s\n",
+ scrtop, curline, curanchor, display_lines,
+ on_screen ? "on_screen" : "0"));
+ if (!HTMainText)
+ return 0;
+ if (rel == 0)
+ return num;
+
+ /* if cur numbered link is on current page, use it */
+ if (on_screen && curanchor) {
+ CTRACE((tfp, "curanchor=%d at line %d on screen\n", curanchor, curline));
+ if (rel == '+')
+ return curanchor + num;
+ else if (rel == '-')
+ return curanchor - num;
+ else
+ return num; /* shouldn't happen */
+ }
+
+ /* no current link on screen, or current link is not numbered
+ * -- find previous closest numbered link
+ */
+ for (a = HTMainText->first_anchor; a; a = a->next) {
+ CTRACE((tfp, " a->line_num=%d, a->number=%d\n", a->line_num, a->number));
+ if (a->line_num >= scrtop)
+ break;
+ if (a->number == 0)
+ continue;
+ l = a;
+ curanchor = l->number;
+ }
+ CTRACE((tfp, " a=%p, l=%p, curanchor=%d\n", (void *) a, (void *) l, curanchor));
+ if (on_screen) { /* on screen but not a numbered link */
+ for (; a; a = a->next) {
+ if (a->number) {
+ l = a;
+ curanchor = l->number;
+ }
+ if (curline == a->line_num && curpos == a->line_pos)
+ break;
+ }
+ }
+ if (rel == '+') {
+ return curanchor + num;
+ } else if (rel == '-') {
+ if (l)
+ return curanchor + 1 - num;
+ else {
+ for (; a && a->number == 0; a = a->next) ;
+ return a ? a->number - num : 0;
+ }
+ } else
+ return num; /* shouldn't happen */
+}
+
+/*
+ * HTGetLinkInfo returns some link info based on the number.
+ *
+ * If want_go is not 0, caller requests to know a line number for
+ * the link indicated by number. It will be returned in *go_line, and
+ * *linknum will be set to an index into the links[] array, to use after
+ * the line in *go_line has been made the new top screen line.
+ * *hightext and *lname are unchanged. - KW
+ *
+ * If want_go is 0 and the number doesn't represent an input field, info
+ * on the link indicated by number is deposited in *hightext and *lname.
+ */
+int HTGetLinkInfo(int number,
+ int want_go,
+ int *go_line,
+ int *linknum,
+ char **hightext,
+ char **lname)
+{
+ TextAnchor *a;
+ HTAnchor *link_dest;
+
+ HTAnchor *link_dest_intl = NULL;
+ int anchors_this_line = 0, anchors_this_screen = 0;
+ int prev_anchor_line = -1, prev_prev_anchor_line = -1;
+
+ if (!HTMainText)
+ return (NO);
+
+ for (a = HTMainText->first_anchor; a; a = a->next) {
+ /*
+ * Count anchors, first on current line if there is more
+ * than one. We have to count all links, including form
+ * field anchors and others with a->number == 0, because
+ * they are or will be included in the links[] array.
+ * The exceptions are hidden form fields and anchors with
+ * show_anchor not set, because they won't appear in links[]
+ * and don't count towards nlinks. - KW
+ */
+ if ((a->show_anchor) &&
+ !(a->link_type == INPUT_ANCHOR
+ && a->input_field->type == F_HIDDEN_TYPE)) {
+ if (a->line_num == prev_anchor_line) {
+ anchors_this_line++;
+ } else {
+ /*
+ * This anchor is on a different line than the previous one.
+ * Remember which was the line number of the previous anchor,
+ * for use in screen positioning later. - KW
+ */
+ anchors_this_line = 1;
+ prev_prev_anchor_line = prev_anchor_line;
+ prev_anchor_line = a->line_num;
+ }
+ if (a->line_num >= HTMainText->top_of_screen) {
+ /*
+ * Count all anchors starting with the top line of the
+ * currently displayed screen. Just keep on counting
+ * beyond this screen's bottom line - we'll know whether
+ * a found anchor is below the current screen by a check
+ * against nlinks later. - KW
+ */
+ anchors_this_screen++;
+ }
+ }
+
+ if (a->number == number) {
+ /*
+ * We found it. Now process it, depending
+ * on what kind of info is requested. - KW
+ */
+ if (want_go || a->link_type == INPUT_ANCHOR) {
+ if (a->show_anchor == NO) {
+ /*
+ * The number requested has been assigned to an anchor
+ * without any selectable text, so we cannot position
+ * on it. The code for suppressing such anchors in
+ * HText_endAnchor() may not have applied, or it may
+ * have failed. Return a failure indication so that
+ * the user will notice that something is wrong,
+ * instead of positioning on some other anchor which
+ * might result in inadvertent activation. - KW
+ */
+ return (NO);
+ }
+ if (anchors_this_screen > 0 &&
+ anchors_this_screen <= nlinks &&
+ a->line_num >= HTMainText->top_of_screen &&
+ a->line_num < HTMainText->top_of_screen + (display_lines)) {
+ /*
+ * If the requested anchor is within the current screen,
+ * just set *go_line so that the screen window won't move
+ * (keep it as it is), and set *linknum to the index of
+ * this link in the current links[] array. - KW
+ */
+ *go_line = HTMainText->top_of_screen;
+ if (linknum)
+ *linknum = anchors_this_screen - 1;
+ } else {
+ /*
+ * if the requested anchor is not within the currently
+ * displayed screen, set *go_line such that the top line
+ * will be either
+ * (1) the line immediately below the previous
+ * anchor, or
+ * (2) about one third of a screenful above the line
+ * with the target, or
+ * (3) the first line of the document -
+ * whichever comes last. In all cases the line with our
+ * target will end up being the first line with any links
+ * on the new screen, so that we can use the
+ * anchors_this_line counter to point to the anchor in
+ * the new links[] array. - kw
+ */
+ int max_offset = SEARCH_GOAL_LINE - 1;
+
+ if (max_offset < 0)
+ max_offset = 0;
+ else if (max_offset >= display_lines)
+ max_offset = display_lines - 1;
+ *go_line = prev_anchor_line - max_offset;
+ if (*go_line <= prev_prev_anchor_line)
+ *go_line = prev_prev_anchor_line + 1;
+ if (*go_line < 0)
+ *go_line = 0;
+ if (linknum)
+ *linknum = anchors_this_line - 1;
+ }
+ return (LINK_LINE_FOUND);
+ } else {
+ *hightext = LYGetHiTextStr(a, 0);
+ link_dest = HTAnchor_followLink(a->anchor);
+ {
+ char *cp_freeme = NULL;
+
+ if (traversal) {
+ cp_freeme = stub_HTAnchor_address(link_dest);
+ } else if (track_internal_links) {
+ if (a->link_type == INTERNAL_LINK_ANCHOR) {
+ link_dest_intl =
+ HTAnchor_followTypedLink(a->anchor, HTInternalLink);
+ if (link_dest_intl && link_dest_intl != link_dest) {
+
+ CTRACE((tfp,
+ "HTGetLinkInfo: unexpected typed link to %s!\n",
+ link_dest_intl->parent->address));
+ link_dest_intl = NULL;
+ }
+ }
+ if (link_dest_intl) {
+ char *cp2 = HTAnchor_address(link_dest_intl);
+
+ FREE(*lname);
+ *lname = cp2;
+ return (WWW_INTERN_LINK_TYPE);
+ } else {
+ cp_freeme = HTAnchor_address(link_dest);
+ }
+ } else {
+ cp_freeme = HTAnchor_address(link_dest);
+ }
+ StrAllocCopy(*lname, cp_freeme);
+ FREE(cp_freeme);
+ }
+ return (WWW_LINK_TYPE);
+ }
+ }
+ }
+ return (NO);
+}
+
+static BOOLEAN same_anchor_or_field(int numberA,
+ FormInfo * formA,
+ int numberB,
+ FormInfo * formB,
+ int ta_same)
+{
+ if (numberA > 0 || numberB > 0) {
+ if (numberA == numberB)
+ return (YES);
+ else if (!ta_same)
+ return (NO);
+ }
+ if (formA || formB) {
+ if (formA == formB) {
+ return (YES);
+ } else if (!ta_same) {
+ return (NO);
+ } else if (!(formA && formB)) {
+ return (NO);
+ }
+ } else {
+ return (NO);
+ }
+ if (formA->type != formB->type ||
+ formA->type != F_TEXTAREA_TYPE ||
+ formB->type != F_TEXTAREA_TYPE) {
+ return (NO);
+ }
+ if (formA->number != formB->number)
+ return (NO);
+ if (!formA->name || !formB->name)
+ return (YES);
+ return (BOOL) (strcmp(formA->name, formB->name) == 0);
+}
+
+#define same_anchor_as_link(i,a,ta_same) (BOOL) (i >= 0 && a && \
+ same_anchor_or_field(links[i].anchor_number,\
+ (links[i].type == WWW_FORM_LINK_TYPE) ? links[i].l_form : NULL,\
+ a->number,\
+ (a->link_type == INPUT_ANCHOR) ? a->input_field : NULL,\
+ ta_same))
+#define same_anchors(a1,a2,ta_same) (BOOL) (a1 && a2 && \
+ same_anchor_or_field(a1->number,\
+ (a1->link_type == INPUT_ANCHOR) ? a1->input_field : NULL,\
+ a2->number,\
+ (a2->link_type == INPUT_ANCHOR) ? a2->input_field : NULL,\
+ ta_same))
+
+/*
+ * Are there more textarea lines belonging to the same textarea before
+ * (direction < 0) or after (direction > 0) the current one?
+ * On entry, curlink must be the index in links[] of a textarea field. - kw
+ */
+BOOL HText_TAHasMoreLines(int curlink,
+ int direction)
+{
+ TextAnchor *a;
+ TextAnchor *prev_a = NULL;
+
+ if (!HTMainText)
+ return (NO);
+ if (direction < 0) {
+ for (a = HTMainText->first_anchor; a; prev_a = a, a = a->next) {
+ if (a->link_type == INPUT_ANCHOR &&
+ links[curlink].l_form == a->input_field) {
+ return same_anchors(a, prev_a, TRUE);
+ }
+ if (links[curlink].anchor_number &&
+ a->number >= links[curlink].anchor_number)
+ break;
+ }
+ return NO;
+ } else {
+ for (a = HTMainText->first_anchor; a; a = a->next) {
+ if (a->link_type == INPUT_ANCHOR &&
+ links[curlink].l_form == a->input_field) {
+ return same_anchors(a, a->next, TRUE);
+ }
+ if (links[curlink].anchor_number &&
+ a->number >= links[curlink].anchor_number)
+ break;
+ }
+ return NO;
+ }
+}
+
+/*
+ * HTGetLinkOrFieldStart - moving to previous or next link or form field.
+ *
+ * On input,
+ * curlink: current link, as index in links[] array (-1 if none)
+ * direction: whether to move up or down (or stay where we are)
+ * ta_skip: if FALSE, input fields belonging to the same textarea are
+ * are treated as different fields, as usual;
+ * if TRUE, fields of the same textarea are treated as a
+ * group for skipping.
+ * The caller wants information for positioning on the new link to be
+ * deposited in *go_line and (if linknum is not NULL) *linknum.
+ *
+ * On failure (no more links in the requested direction) returns NO
+ * and doesn't change *go_line or *linknum. Otherwise, LINK_DO_ARROWUP
+ * may be returned, and *go_line and *linknum not changed, to indicate that
+ * the caller should use a normal PREV_LINK or PREV_PAGE mechanism.
+ * Otherwise:
+ * The number (0-based counting) for the new top screen line will be returned
+ * in *go_line, and *linknum will be set to an index into the links[] array,
+ * to use after the line in *go_line has been made the new top screen
+ * line. - kw
+ */
+int HTGetLinkOrFieldStart(int curlink,
+ int *go_line,
+ int *linknum,
+ int direction,
+ int ta_skip)
+{
+ TextAnchor *a;
+ int anchors_this_line = 0;
+ int prev_anchor_line = -1, prev_prev_anchor_line = -1;
+
+ struct agroup {
+ TextAnchor *anc;
+ int prev_anchor_line;
+ int anchors_this_line;
+ int anchors_this_group;
+ } previous, current;
+ struct agroup *group_to_go = NULL;
+
+ if (!HTMainText)
+ return (NO);
+
+ previous.anc = current.anc = NULL;
+ previous.prev_anchor_line = current.prev_anchor_line = -1;
+ previous.anchors_this_line = current.anchors_this_line = 0;
+ previous.anchors_this_group = current.anchors_this_group = 0;
+
+ for (a = HTMainText->first_anchor; a; a = a->next) {
+ /*
+ * Count anchors, first on current line if there is more
+ * than one. We have to count all links, including form
+ * field anchors and others with a->number == 0, because
+ * they are or will be included in the links[] array.
+ * The exceptions are hidden form fields and anchors with
+ * show_anchor not set, because they won't appear in links[]
+ * and don't count towards nlinks. - KW
+ */
+ if ((a->show_anchor) &&
+ !(a->link_type == INPUT_ANCHOR
+ && a->input_field->type == F_HIDDEN_TYPE)) {
+ if (a->line_num == prev_anchor_line) {
+ anchors_this_line++;
+ } else {
+ /*
+ * This anchor is on a different line than the previous one.
+ * Remember which was the line number of the previous anchor,
+ * for use in screen positioning later. - KW
+ */
+ anchors_this_line = 1;
+ prev_prev_anchor_line = prev_anchor_line;
+ prev_anchor_line = a->line_num;
+ }
+
+ if (!same_anchors(current.anc, a, ta_skip)) {
+ previous.anc = current.anc;
+ previous.prev_anchor_line = current.prev_anchor_line;
+ previous.anchors_this_line = current.anchors_this_line;
+ previous.anchors_this_group = current.anchors_this_group;
+ current.anc = a;
+ current.prev_anchor_line = prev_prev_anchor_line;
+ current.anchors_this_line = anchors_this_line;
+ current.anchors_this_group = 1;
+ } else {
+ current.anchors_this_group++;
+ }
+ if (curlink >= 0) {
+ if (same_anchor_as_link(curlink, a, ta_skip)) {
+ if (direction == -1) {
+ group_to_go = &previous;
+ break;
+ } else if (direction == 0) {
+ group_to_go = &current;
+ break;
+ }
+ } else if (direction > 0 &&
+ same_anchor_as_link(curlink, previous.anc, ta_skip)) {
+ group_to_go = &current;
+ break;
+ }
+ } else {
+ if (a->line_num >= HTMainText->top_of_screen) {
+ if (direction < 0) {
+ group_to_go = &previous;
+ break;
+ } else if (direction == 0) {
+ if (previous.anc) {
+ group_to_go = &previous;
+ break;
+ } else {
+ group_to_go = &current;
+ break;
+ }
+ } else {
+ group_to_go = &current;
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (!group_to_go && curlink < 0 && direction <= 0) {
+ group_to_go = &current;
+ }
+ if (group_to_go) {
+ a = group_to_go->anc;
+ if (a) {
+ int max_offset;
+
+ /*
+ * We know where to go; most of the stuff below is just
+ * tweaks to try to position the new screen in a specific
+ * way.
+ *
+ * In some cases going to a previous link can be done
+ * via the normal LYK_PREV_LINK action, which may give
+ * better positioning of the new screen. - kw
+ */
+ if (a->line_num < HTMainText->top_of_screen &&
+ a->line_num >= HTMainText->top_of_screen - (display_lines)) {
+ if ((curlink < 0 &&
+ group_to_go->anchors_this_group == 1) ||
+ (direction < 0 &&
+ group_to_go != &current &&
+ current.anc &&
+ current.anc->line_num >= HTMainText->top_of_screen &&
+ group_to_go->anchors_this_group == 1) ||
+ (a->next &&
+ a->next->line_num >= HTMainText->top_of_screen)) {
+ return (LINK_DO_ARROWUP);
+ }
+ }
+ /*
+ * The fundamental limitation of the current anchors_this_line
+ * counter method is that we only can set *linknum to the right
+ * index into the future links[] array if the line with our link
+ * ends up being the first line with any links (that count) on
+ * the new screen. Subject to that restriction we still have
+ * some vertical liberty (sometimes), and try to make the best
+ * of it. It may be a question of taste though. - kw
+ */
+ if (a->line_num <= (display_lines)) {
+ max_offset = 0;
+ } else if (a->line_num < HTMainText->top_of_screen) {
+ int screensback =
+ (HTMainText->top_of_screen - a->line_num + (display_lines) - 1)
+ / (display_lines);
+
+ max_offset = a->line_num - (HTMainText->top_of_screen -
+ screensback * (display_lines));
+ } else if (HTMainText->Lines - a->line_num <= (display_lines)) {
+ max_offset = a->line_num - (HTMainText->Lines + 1
+ - (display_lines));
+ } else if (a->line_num >=
+ HTMainText->top_of_screen + (display_lines)) {
+ int screensahead =
+ (a->line_num - HTMainText->top_of_screen) / (display_lines);
+
+ max_offset = a->line_num - HTMainText->top_of_screen -
+ screensahead * (display_lines);
+ } else {
+ max_offset = SEARCH_GOAL_LINE - 1;
+ }
+
+ /* Stuff below should remain unchanged if line positioning
+ is tweaked. - kw */
+ if (max_offset < 0)
+ max_offset = 0;
+ else if (max_offset >= display_lines)
+ max_offset = display_lines - 1;
+ *go_line = a->line_num - max_offset;
+ if (*go_line <= group_to_go->prev_anchor_line)
+ *go_line = group_to_go->prev_anchor_line + 1;
+
+ if (*go_line < 0)
+ *go_line = 0;
+ if (linknum)
+ *linknum = group_to_go->anchors_this_line - 1;
+ return (LINK_LINE_FOUND);
+ }
+ }
+ return (NO);
+}
+
+/*
+ * This function finds the line indicated by line_num in the
+ * HText structure indicated by text, and searches that line
+ * for the first hit with the string indicated by target. If
+ * there is no hit, FALSE is returned. If there is a hit, then
+ * a copy of the line starting at that first hit is loaded into
+ * *data with all IsSpecial characters stripped, its offset and
+ * the printable target length (without IsSpecial, or extra CJK
+ * or utf8 characters) are loaded into *offset and *tLen, and
+ * TRUE is returned. -FM
+ */
+BOOL HText_getFirstTargetInLine(HText *text, int line_num,
+ int utf_flag,
+ int *offset,
+ int *tLen,
+ char **data,
+ const char *target)
+{
+ HTLine *line;
+ char *LineData;
+ int LineOffset, HitOffset, LenNeeded, i;
+ const char *cp;
+
+ /*
+ * Make sure we have an HText structure, that line_num is
+ * in its range, and that we have a target string. -FM
+ */
+ if (!(text &&
+ line_num >= 0 &&
+ line_num <= text->Lines &&
+ non_empty(target))) {
+ return (FALSE);
+ }
+
+ /*
+ * Find the line and set up its data and offset -FM
+ */
+ for (i = 0, line = FirstHTLine(text);
+ i < line_num && (line != text->last_line);
+ i++, line = line->next) {
+ if (line->next == NULL) {
+ return (FALSE);
+ }
+ }
+ if (!(line && line->data[0]))
+ return (FALSE);
+ LineData = (char *) line->data;
+ LineOffset = (int) line->offset;
+
+ /*
+ * If the target is on the line, load the offset of
+ * its first character and the subsequent line data,
+ * strip any special characters from the loaded line
+ * data, and return TRUE. -FM
+ */
+ if (((cp = LYno_attr_mb_strstr(LineData,
+ target,
+ utf_flag, YES,
+ &HitOffset,
+ &LenNeeded)) != NULL) &&
+ (LineOffset + LenNeeded) <= DISPLAY_COLS) {
+ /*
+ * We had a hit so load the results,
+ * remove IsSpecial characters from
+ * the allocated data string, and
+ * return TRUE. -FM
+ */
+ *offset = (LineOffset + HitOffset);
+ *tLen = (LenNeeded - HitOffset);
+ StrAllocCopy(*data, cp);
+ remove_special_attr_chars(*data);
+ return (TRUE);
+ }
+
+ /*
+ * The line does not contain the target. -FM
+ */
+ return (FALSE);
+}
+
+/*
+ * HText_getNumOfLines returns the number of lines in the
+ * current document.
+ */
+int HText_getNumOfLines(void)
+{
+ return (HTMainText ? HTMainText->Lines : 0);
+}
+
+/*
+ * HText_getNumOfBytes returns the size of the document, as rendered. This
+ * may be different from the original filesize.
+ */
+int HText_getNumOfBytes(void)
+{
+ int result = -1;
+ HTLine *line = NULL;
+
+ if (HTMainText != 0) {
+ for (line = FirstHTLine(HTMainText);
+ line != HTMainText->last_line;
+ line = line->next) {
+ result += 1 + (int) strlen(line->data);
+ }
+ }
+ return result;
+}
+
+/*
+ * HText_getTitle returns the title of the
+ * current document.
+ */
+const char *HText_getTitle(void)
+{
+ return (HTMainText ?
+ HTAnchor_title(HTMainText->node_anchor) : 0);
+}
+
+#ifdef USE_COLOR_STYLE
+const char *HText_getStyle(void)
+{
+ return (HTMainText ?
+ HTAnchor_style(HTMainText->node_anchor) : 0);
+}
+#endif
+
+/*
+ * HText_getSugFname returns the suggested filename of the current
+ * document (normally derived from a Content-Disposition header with
+ * attachment; filename=name.suffix). -FM
+ */
+const char *HText_getSugFname(void)
+{
+ return (HTMainText ?
+ HTAnchor_SugFname(HTMainText->node_anchor) : 0);
+}
+
+/*
+ * HTCheckFnameForCompression receives the address of an allocated
+ * string containing a filename, and an anchor pointer, and expands
+ * or truncates the string's suffix if appropriate, based on whether
+ * the anchor indicates that the file is compressed. We assume
+ * that the file was not uncompressed (as when downloading), and
+ * believe the headers about whether it's compressed or not. -FM
+ *
+ * Added third arg - if strip_ok is FALSE, we don't trust the anchor
+ * info enough to remove a compression suffix if the anchor object
+ * does not indicate compression. - kw
+ */
+void HTCheckFnameForCompression(char **fname,
+ HTParentAnchor *anchor,
+ int strip_ok)
+{
+ char *fn = *fname;
+ char *dot = NULL;
+ char *cp = NULL;
+ const char *suffix = "";
+ CompressFileType method;
+ CompressFileType second;
+
+ /*
+ * Make sure we have a string and anchor. -FM
+ */
+ if (!(fn && anchor))
+ return;
+
+ /*
+ * Make sure we have a file, not directory, name. -FM
+ */
+ if (*(fn = LYPathLeaf(fn)) == '\0')
+ return;
+
+ method = HTContentToCompressType(anchor);
+
+ /*
+ * If no Content-Encoding has been detected via the anchor
+ * pointer, but strip_ok is not set, there is nothing left
+ * to do. - kw
+ */
+ if ((method == cftNone) && !strip_ok)
+ return;
+
+ /*
+ * Treat .tgz specially
+ */
+ if ((dot = strrchr(fn, '.')) != NULL
+ && !strcasecomp(dot, ".tgz")) {
+ if (method == cftNone) {
+ strcpy(dot, ".tar");
+ }
+ return;
+ }
+
+ /*
+ * Seek the last dot, and check whether
+ * we have a gzip or compress suffix. -FM
+ */
+ if ((dot = strrchr(fn, '.')) != NULL) {
+ int rootlen = 0;
+
+ if (HTCompressFileType(fn, ".", &rootlen) != cftNone) {
+ if (method == cftNone) {
+ /*
+ * It has a suffix which signifies a gzipped
+ * or compressed file for us, but the anchor
+ * claims otherwise, so tweak the suffix. -FM
+ */
+ *dot = '\0';
+ }
+ return;
+ }
+ if ((second = HTCompressFileType(fn, "-_", &rootlen)) != cftNone) {
+ cp = fn + rootlen;
+ if (method == cftNone) {
+ /*
+ * It has a tail which signifies a gzipped
+ * file for us, but the anchor claims otherwise,
+ * so tweak the suffix. -FM
+ */
+ if (cp == dot + 1)
+ cp--;
+ *cp = '\0';
+ } else {
+ /*
+ * The anchor claims it's gzipped, and we
+ * believe it, so force this tail to the
+ * conventional suffix. -FM
+ */
+#ifdef VMS
+ *cp = '-';
+#else
+ *cp = '.';
+#endif /* VMS */
+ if (second == cftCompress)
+ LYUpperCase(cp);
+ else
+ LYLowerCase(cp);
+ }
+ return;
+ }
+ }
+
+ suffix = HTCompressTypeToSuffix(method);
+
+ /*
+ * Add the appropriate suffix. -FM
+ */
+ if (*suffix) {
+ if (!dot) {
+ StrAllocCat(*fname, suffix);
+ } else if (*++dot == '\0') {
+ StrAllocCat(*fname, suffix + 1);
+ } else {
+ StrAllocCat(*fname, suffix);
+#ifdef VMS
+ (*fname)[strlen(*fname) - strlen(suffix)] = '-';
+#endif /* !VMS */
+ }
+ }
+}
+
+/*
+ * HText_getLastModified returns the Last-Modified header
+ * if available, for the current document. -FM
+ */
+const char *HText_getLastModified(void)
+{
+ return (HTMainText ?
+ HTAnchor_last_modified(HTMainText->node_anchor) : 0);
+}
+
+/*
+ * HText_getDate returns the Date header
+ * if available, for the current document. -FM
+ */
+const char *HText_getDate(void)
+{
+ return (HTMainText ?
+ HTAnchor_date(HTMainText->node_anchor) : 0);
+}
+
+/*
+ * HText_getServer returns the Server header
+ * if available, for the current document. -FM
+ */
+const char *HText_getServer(void)
+{
+ return (HTMainText ?
+ HTAnchor_server(HTMainText->node_anchor) : 0);
+}
+
+/*
+ * Returns the full text of HTTP headers, if available, for the current
+ * document.
+ */
+const char *HText_getHttpHeaders(void)
+{
+ return (HTMainText ?
+ HTAnchor_http_headers(HTMainText->node_anchor) : 0);
+}
+
+/*
+ * HText_pageDisplay displays a screen of text
+ * starting from the line 'line_num'-1.
+ * This is the primary call for lynx.
+ */
+void HText_pageDisplay(int line_num,
+ char *target)
+{
+#ifdef DISP_PARTIAL
+ if (debug_display_partial || (LYTraceLogFP != NULL)) {
+ CTRACE((tfp, "GridText: HText_pageDisplay at line %d started\n", line_num));
+ }
+
+ if (display_partial) {
+ int stop_before = -1;
+
+ /*
+ * Garbage is reported from forms input fields in incremental mode.
+ * So we start HText_trimHightext() to forget this side effect.
+ * This function was split-out from HText_endAppend().
+ * It may not be the best solution but it works. - LP
+ *
+ * (FALSE = indicate that we are in partial mode)
+ * Multiple calls of HText_trimHightext works without problem now.
+ */
+ if (HTMainText && HTMainText->stbl)
+ stop_before = Stbl_getStartLineDeep(HTMainText->stbl);
+ HText_trimHightext(HTMainText, FALSE, stop_before);
+ }
+#endif
+ display_page(HTMainText, line_num - 1, target);
+
+#ifdef DISP_PARTIAL
+ if (display_partial && debug_display_partial)
+ LYSleepMsg();
+#endif
+
+ is_www_index = HTAnchor_isIndex(HTMainAnchor);
+
+#ifdef DISP_PARTIAL
+ if (debug_display_partial || (LYTraceLogFP != NULL)) {
+ CTRACE((tfp, "GridText: HText_pageDisplay finished\n"));
+ }
+#endif
+}
+
+/*
+ * Return YES if we have a whereis search target on the displayed
+ * page. - kw
+ */
+BOOL HText_pageHasPrevTarget(void)
+{
+ if (!HTMainText)
+ return NO;
+ else
+ return HTMainText->page_has_target;
+}
+
+/*
+ * Find the number of the closest anchor to the given document offset. Used
+ * in reparsing, this will usually find an exact match, as a link shifts around
+ * on the display. It will not find a match when (for example) the source view
+ * shows images that are not links in the html.
+ */
+int HText_closestAnchor(HText *text, int offset)
+{
+ int result = -1;
+ int absdiff = 0;
+ int newdiff;
+ TextAnchor *Anchor_ptr = NULL;
+ TextAnchor *closest = NULL;
+
+ for (Anchor_ptr = text->first_anchor;
+ Anchor_ptr != NULL;
+ Anchor_ptr = Anchor_ptr->next) {
+ if (Anchor_ptr->sgml_offset == offset) {
+ result = Anchor_ptr->number;
+ break;
+ } else {
+ newdiff = abs(Anchor_ptr->sgml_offset - offset);
+ if (absdiff == 0 || absdiff > newdiff) {
+ absdiff = newdiff;
+ closest = Anchor_ptr;
+ }
+ }
+ }
+ if (result < 0 && closest != 0) {
+ result = closest->number;
+ }
+
+ return result;
+}
+
+/*
+ * Find the offset for the given anchor, e.g., the inverse of
+ * HText_closestAnchor().
+ */
+int HText_locateAnchor(HText *text, int anchor_number)
+{
+ int result = -1;
+ TextAnchor *Anchor_ptr = NULL;
+
+ for (Anchor_ptr = text->first_anchor;
+ Anchor_ptr != NULL;
+ Anchor_ptr = Anchor_ptr->next) {
+ if (Anchor_ptr->number == anchor_number) {
+ result = Anchor_ptr->sgml_offset;
+ break;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * This is supposed to give the same result as the inline checks in
+ * display_page(), so we can decide which anchors will be visible.
+ */
+static BOOL anchor_is_numbered(TextAnchor *Anchor_ptr)
+{
+ BOOL result = FALSE;
+
+ if (Anchor_ptr->show_anchor
+ && (Anchor_ptr->link_type & HYPERTEXT_ANCHOR)) {
+ result = TRUE;
+ } else if (Anchor_ptr->link_type == INPUT_ANCHOR
+ && Anchor_ptr->input_field->type != F_HIDDEN_TYPE) {
+ result = TRUE;
+ }
+ return result;
+}
+
+/*
+ * Return the absolute line number (counting from the beginning of the
+ * document) for the given absolute anchor number. Normally line numbers are
+ * computed within the screen, and for that we use the links[] array. A few
+ * uses require the absolute anchor number. For example, reparsing a document,
+ * e.g., switching between normal and source views will alter the line numbers
+ * of each link, and may require adjusting the top line number used for the
+ * display, before we recompute the links[] array.
+ */
+int HText_getAbsLineNumber(HText *text,
+ int anchor_number)
+{
+ int result = -1;
+
+ if (anchor_number >= 0 && text != 0) {
+ TextAnchor *Anchor_ptr = NULL;
+
+ for (Anchor_ptr = text->first_anchor;
+ Anchor_ptr != NULL;
+ Anchor_ptr = Anchor_ptr->next) {
+ if (anchor_is_numbered(Anchor_ptr)
+ && Anchor_ptr->number == anchor_number) {
+ result = Anchor_ptr->line_num;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+/*
+ * Compute the link-number in a page, given the top line number of the page and
+ * the absolute anchor number.
+ */
+int HText_anchorRelativeTo(HText *text, int top_lineno, int anchor_number)
+{
+ int result = 0;
+ int from_top = 0;
+ TextAnchor *Anchor_ptr = NULL;
+
+ for (Anchor_ptr = text->first_anchor;
+ Anchor_ptr != NULL;
+ Anchor_ptr = Anchor_ptr->next) {
+ if (Anchor_ptr->number == anchor_number) {
+ result = from_top;
+ break;
+ }
+ if (!anchor_is_numbered(Anchor_ptr))
+ continue;
+ if (Anchor_ptr->line_num >= top_lineno) {
+ ++from_top;
+ }
+ }
+ return result;
+}
+
+/*
+ * HText_LinksInLines returns the number of links in the
+ * 'Lines' number of lines beginning with 'line_num'-1. -FM
+ */
+int HText_LinksInLines(HText *text,
+ int line_num,
+ int Lines)
+{
+ int total = 0;
+ int start = (line_num - 1);
+ int end = (start + Lines);
+ TextAnchor *Anchor_ptr = NULL;
+
+ if (!text)
+ return total;
+
+ for (Anchor_ptr = text->first_anchor;
+ Anchor_ptr != NULL && Anchor_ptr->line_num <= end;
+ Anchor_ptr = Anchor_ptr->next) {
+ if (Anchor_ptr->line_num >= start &&
+ Anchor_ptr->line_num < end &&
+ Anchor_ptr->show_anchor &&
+ !(Anchor_ptr->link_type == INPUT_ANCHOR
+ && Anchor_ptr->input_field->type == F_HIDDEN_TYPE))
+ ++total;
+ }
+
+ return total;
+}
+
+void HText_setStale(HText *text)
+{
+ text->stale = YES;
+}
+
+void HText_refresh(HText *text)
+{
+ if (text->stale)
+ display_page(text, text->top_of_screen, "");
+}
+
+int HText_sourceAnchors(HText *text)
+{
+ return (text ? text->last_anchor_number : -1);
+}
+
+BOOL HText_canScrollUp(HText *text)
+{
+ return (BOOL) (text->top_of_screen != 0);
+}
+
+/*
+ * Check if there is more info below this page.
+ */
+BOOL HText_canScrollDown(void)
+{
+ HText *text = HTMainText;
+
+ return (BOOL) ((text != 0)
+ && ((text->top_of_screen + display_lines) <= text->Lines));
+}
+
+/* Scroll actions
+*/
+void HText_scrollTop(HText *text)
+{
+ display_page(text, 0, "");
+}
+
+void HText_scrollDown(HText *text)
+{
+ display_page(text, text->top_of_screen + display_lines, "");
+}
+
+void HText_scrollUp(HText *text)
+{
+ display_page(text, text->top_of_screen - display_lines, "");
+}
+
+void HText_scrollBottom(HText *text)
+{
+ display_page(text, text->Lines - display_lines, "");
+}
+
+/* Browsing functions
+ * ==================
+ */
+
+/* Bring to front and highlight it
+*/
+BOOL HText_select(HText *text)
+{
+ if (text != HTMainText) {
+ /*
+ * Reset flag for whereis search string - cannot be true here
+ * since text is not our HTMainText. - kw
+ */
+ if (text)
+ text->page_has_target = NO;
+
+#ifdef DISP_PARTIAL
+ /* Reset these for the previous and current text. - kw */
+ ResetPartialLinenos(text);
+ ResetPartialLinenos(HTMainText);
+#endif /* DISP_PARTIAL */
+
+#ifdef CAN_SWITCH_DISPLAY_CHARSET
+ /* text->UCLYhndl is not reset by META, so use a more circumvent way */
+ if (text->node_anchor->UCStages->s[UCT_STAGE_HTEXT].LYhndl
+ != current_char_set)
+ Switch_Display_Charset(text->node_anchor->UCStages->s[UCT_STAGE_HTEXT].LYhndl, SWITCH_DISPLAY_CHARSET_MAYBE);
+#endif
+ assert(text != NULL);
+ if (HTMainText) {
+ if (HText_hasUTF8OutputSet(HTMainText) &&
+ HTLoadedDocumentEightbit() &&
+ IS_UTF8_TTY) {
+ text->had_utf8 = HTMainText->has_utf8;
+ } else {
+ text->had_utf8 = NO;
+ }
+ HTMainText->has_utf8 = NO;
+ text->has_utf8 = NO;
+ }
+
+ HTMainText = text;
+ HTMainAnchor = text->node_anchor;
+
+ /*
+ * Make this text the most current in the loaded texts list. -FM
+ */
+ if (loaded_texts && HTList_removeObject(loaded_texts, text))
+ HTList_addObject(loaded_texts, text);
+ }
+ return YES;
+}
+
+/*
+ * This function returns TRUE if doc's post_data, address
+ * and isHEAD elements are identical to those of a loaded
+ * (memory cached) text. -FM
+ */
+BOOL HText_POSTReplyLoaded(DocInfo *doc)
+{
+ HText *text = NULL;
+ HTList *cur = loaded_texts;
+ bstring *post_data;
+ char *address;
+ BOOL is_head;
+
+ /*
+ * Make sure we have the structures. -FM
+ */
+ if (!cur || !doc)
+ return (FALSE);
+
+ /*
+ * Make sure doc is for a POST reply. -FM
+ */
+ if ((post_data = doc->post_data) == NULL ||
+ (address = doc->address) == NULL)
+ return (FALSE);
+ is_head = doc->isHEAD;
+
+ /*
+ * Loop through the loaded texts looking for a
+ * POST reply match. -FM
+ */
+ while (NULL != (text = (HText *) HTList_nextObject(cur))) {
+ if (text->node_anchor &&
+ text->node_anchor->post_data &&
+ BINEQ(post_data, text->node_anchor->post_data) &&
+ text->node_anchor->address &&
+ !strcmp(address, text->node_anchor->address) &&
+ is_head == text->node_anchor->isHEAD) {
+ return (TRUE);
+ }
+ }
+
+ return (FALSE);
+}
+
+BOOL HTFindPoundSelector(const char *selector)
+{
+ TextAnchor *a;
+
+ CTRACE((tfp, "FindPound: searching for \"%s\"\n", selector));
+ for (a = HTMainText->first_anchor; a != 0; a = a->next) {
+
+ if (a->anchor && a->anchor->tag) {
+ if (!strcmp(a->anchor->tag, selector)) {
+
+ www_search_result = a->line_num + 1;
+
+ CTRACE((tfp, "FindPound: Selecting anchor [%d] at line %d\n",
+ a->number, www_search_result));
+ if (!strcmp(selector, LYToolbarName)) {
+ --www_search_result;
+ }
+ return (YES);
+ }
+ }
+ }
+ return (NO);
+}
+
+BOOL HText_selectAnchor(HText *text, HTChildAnchor *anchor)
+{
+ TextAnchor *a;
+ int l;
+
+ for (a = text->first_anchor; a; a = a->next) {
+ if (a->anchor == anchor)
+ break;
+ }
+ if (!a) {
+ CTRACE((tfp, "HText: No such anchor in this text!\n"));
+ return NO;
+ }
+
+ if (text != HTMainText) { /* Comment out by ??? */
+ HTMainText = text; /* Put back in by tbl 921208 */
+ HTMainAnchor = text->node_anchor;
+ }
+ l = a->line_num;
+
+ CTRACE((tfp, "HText: Selecting anchor [%d] at line %d\n",
+ a->number, l));
+
+ if (!text->stale &&
+ (l >= text->top_of_screen) &&
+ (l < text->top_of_screen + display_lines + 1))
+ return YES;
+
+ www_search_result = l - (display_lines / 3); /* put in global variable */
+
+ return YES;
+}
+
+/* Editing functions - NOT IMPLEMENTED
+ * =================
+ *
+ * These are called from the application. There are many more functions
+ * not included here from the original text object.
+ */
+
+/* Style handling:
+*/
+/* Apply this style to the selection
+*/
+void HText_applyStyle(HText *me GCC_UNUSED, HTStyle *style GCC_UNUSED)
+{
+
+}
+
+/* Update all text with changed style.
+*/
+void HText_updateStyle(HText *me GCC_UNUSED, HTStyle *style GCC_UNUSED)
+{
+
+}
+
+/* Return style of selection
+*/
+HTStyle *HText_selectionStyle(HText *me GCC_UNUSED, HTStyleSheet *sheet GCC_UNUSED)
+{
+ return 0;
+}
+
+/* Paste in styled text
+*/
+void HText_replaceSel(HText *me GCC_UNUSED, const char *aString GCC_UNUSED,
+ HTStyle *aStyle GCC_UNUSED)
+{
+}
+
+/* Apply this style to the selection and all similarly formatted text
+ * (style recovery only)
+ */
+void HTextApplyToSimilar(HText *me GCC_UNUSED, HTStyle *style GCC_UNUSED)
+{
+
+}
+
+/* Select the first unstyled run.
+ * (style recovery only)
+ */
+void HTextSelectUnstyled(HText *me GCC_UNUSED, HTStyleSheet *sheet GCC_UNUSED)
+{
+
+}
+
+/* Anchor handling:
+*/
+void HText_unlinkSelection(HText *me GCC_UNUSED)
+{
+
+}
+
+HTAnchor *HText_referenceSelected(HText *me GCC_UNUSED)
+{
+ return 0;
+}
+
+int HText_getTopOfScreen(void)
+{
+ HText *text = HTMainText;
+
+ return text != 0 ? text->top_of_screen : 0;
+}
+
+int HText_getLines(HText *text)
+{
+ return text->Lines;
+}
+
+/*
+ * Constrain the line number to be within the document. The line number is
+ * zero-based.
+ */
+int HText_getPreferredTopLine(HText *text, int line_number)
+{
+ int last_screen = text->Lines - (display_lines - 2);
+
+ if (text->Lines < display_lines) {
+ line_number = 0;
+ } else if (line_number > text->Lines) {
+ line_number = last_screen;
+ } else if (line_number < 0) {
+ line_number = 0;
+ }
+ return line_number;
+}
+
+HTAnchor *HText_linkSelTo(HText *me GCC_UNUSED,
+ HTAnchor * anchor GCC_UNUSED)
+{
+ return 0;
+}
+
+/*
+ * Utility for freeing the list of previous isindex and whereis queries. -FM
+ */
+void HTSearchQueries_free(void)
+{
+ LYFreeStringList(search_queries);
+ search_queries = NULL;
+}
+
+/*
+ * Utility for listing isindex and whereis queries, making
+ * any repeated queries the most current in the list. -FM
+ */
+void HTAddSearchQuery(char *query)
+{
+ char *new_query = NULL;
+ char *old;
+ HTList *cur;
+
+ if (!non_empty(query))
+ return;
+
+ StrAllocCopy(new_query, query);
+
+ if (!search_queries) {
+ search_queries = HTList_new();
+#ifdef LY_FIND_LEAKS
+ atexit(HTSearchQueries_free);
+#endif
+ HTList_addObject(search_queries, new_query);
+ return;
+ }
+
+ cur = search_queries;
+ while (NULL != (old = (char *) HTList_nextObject(cur))) {
+ if (!strcmp(old, new_query)) {
+ HTList_removeObject(search_queries, old);
+ FREE(old);
+ break;
+ }
+ }
+ HTList_addObject(search_queries, new_query);
+
+ return;
+}
+
+int do_www_search(DocInfo *doc)
+{
+ bstring *searchstring = NULL;
+ bstring *temp = NULL;
+ char *cp;
+ char *tmpaddress = NULL;
+ int ch;
+ RecallType recall;
+ int QueryTotal;
+ int QueryNum;
+ BOOLEAN PreviousSearch = FALSE;
+ int code;
+
+ /*
+ * Load the default query buffer
+ */
+ if ((cp = StrChr(doc->address, '?')) != NULL) {
+ /*
+ * This is an index from a previous search.
+ * Use its query as the default.
+ */
+ PreviousSearch = TRUE;
+ BStrCopy0(searchstring, ++cp);
+ for (cp = searchstring->str; *cp; cp++)
+ if (*cp == '+')
+ *cp = ' ';
+ HTUnEscape(searchstring->str);
+ BStrCopy(temp, searchstring);
+ /*
+ * Make sure it's treated as the most recent query. -FM
+ */
+ HTAddSearchQuery(searchstring->str);
+ } else {
+ /*
+ * New search; no default.
+ */
+ BStrCopy0(searchstring, "");
+ BStrCopy0(temp, "");
+ }
+
+ /*
+ * Prompt for a query string.
+ */
+ if (isBEmpty(searchstring)) {
+ if (HTMainAnchor->isIndexPrompt)
+ _statusline(HTMainAnchor->isIndexPrompt);
+ else
+ _statusline(ENTER_DATABASE_QUERY);
+ } else
+ _statusline(EDIT_CURRENT_QUERY);
+ QueryTotal = (search_queries ? HTList_count(search_queries) : 0);
+ recall = (((PreviousSearch && QueryTotal >= 2) ||
+ (!PreviousSearch && QueryTotal >= 1)) ? RECALL_URL : NORECALL);
+ QueryNum = QueryTotal;
+
+ get_query:
+ if ((ch = LYgetBString(&searchstring, FALSE, 0, recall)) < 0 ||
+ isBEmpty(searchstring) ||
+ ch == UPARROW_KEY ||
+ ch == DNARROW_KEY) {
+
+ if (recall && ch == UPARROW_KEY) {
+ if (PreviousSearch) {
+ /*
+ * Use the second to last query in the list. -FM
+ */
+ QueryNum = 1;
+ PreviousSearch = FALSE;
+ } else {
+ /*
+ * Go back to the previous query in the list. -FM
+ */
+ QueryNum++;
+ }
+ if (QueryNum >= QueryTotal)
+ /*
+ * Roll around to the last query in the list. -FM
+ */
+ QueryNum = 0;
+ if ((cp = (char *) HTList_objectAt(search_queries,
+ QueryNum)) != NULL) {
+ BStrCopy0(searchstring, cp);
+ if (!isBEmpty(temp) &&
+ !strcmp(temp->str, searchstring->str)) {
+ _statusline(EDIT_CURRENT_QUERY);
+ } else if ((!isBEmpty(temp) && QueryTotal == 2) ||
+ (isBEmpty(temp) && QueryTotal == 1)) {
+ _statusline(EDIT_THE_PREV_QUERY);
+ } else {
+ _statusline(EDIT_A_PREV_QUERY);
+ }
+ goto get_query;
+ }
+ } else if (recall && ch == DNARROW_KEY) {
+ if (PreviousSearch) {
+ /*
+ * Use the first query in the list. -FM
+ */
+ QueryNum = QueryTotal - 1;
+ PreviousSearch = FALSE;
+ } else {
+ /*
+ * Advance to the next query in the list. -FM
+ */
+ QueryNum--;
+ }
+ if (QueryNum < 0)
+ /*
+ * Roll around to the first query in the list. -FM
+ */
+ QueryNum = QueryTotal - 1;
+ if ((cp = (char *) HTList_objectAt(search_queries,
+ QueryNum)) != NULL) {
+ BStrCopy0(searchstring, cp);
+ if (!isBEmpty(temp) &&
+ !strcmp(temp->str, searchstring->str)) {
+ _statusline(EDIT_CURRENT_QUERY);
+ } else if ((!isBEmpty(temp) && QueryTotal == 2) ||
+ (isBEmpty(temp) && QueryTotal == 1)) {
+ _statusline(EDIT_THE_PREV_QUERY);
+ } else {
+ _statusline(EDIT_A_PREV_QUERY);
+ }
+ goto get_query;
+ }
+ }
+
+ /*
+ * Search cancelled.
+ */
+ HTInfoMsg(CANCELLED);
+ code = NULLFILE;
+ } else {
+
+ LYTrimLeading(searchstring->str);
+ LYTrimTrailing(searchstring->str);
+ if (isBEmpty(searchstring)) {
+ HTInfoMsg(CANCELLED);
+ code = NULLFILE;
+ } else if (!LYforce_no_cache &&
+ !isBEmpty(temp) &&
+ !strcmp(temp->str, searchstring->str)) {
+ /*
+ * Don't resubmit the same query unintentionally.
+ */
+ HTUserMsg(USE_C_R_TO_RESUB_CUR_QUERY);
+ code = NULLFILE;
+ } else {
+
+ /*
+ * Add searchstring to the query list,
+ * or make it the most current. -FM
+ */
+ HTAddSearchQuery(searchstring->str);
+
+ /*
+ * Show the URL with the new query.
+ */
+ if ((cp = StrChr(doc->address, '?')) != NULL)
+ *cp = '\0';
+ StrAllocCopy(tmpaddress, doc->address);
+ StrAllocCat(tmpaddress, "?");
+ StrAllocCat(tmpaddress, searchstring->str);
+ user_message(WWW_WAIT_MESSAGE, tmpaddress);
+#ifdef SYSLOG_REQUESTED_URLS
+ LYSyslog(tmpaddress);
+#endif
+ FREE(tmpaddress);
+ if (cp)
+ *cp = '?';
+
+ /*
+ * OK, now we do the search.
+ */
+ if (HTSearch(searchstring->str, HTMainAnchor)) {
+ auto char *cp_freeme = NULL;
+
+ if (traversal)
+ cp_freeme = stub_HTAnchor_address((HTAnchor *) HTMainAnchor);
+ else
+ cp_freeme = HTAnchor_address((HTAnchor *) HTMainAnchor);
+ StrAllocCopy(doc->address, cp_freeme);
+ FREE(cp_freeme);
+
+ CTRACE((tfp, "\ndo_www_search: newfile: %s\n", doc->address));
+
+ /*
+ * Yah, the search succeeded.
+ */
+ code = NORMAL;
+ } else {
+
+ /*
+ * Either the search failed (Yuk), or we got redirection.
+ * If it's redirection, use_this_url_instead is set, and
+ * mainloop() will deal with it such that security features
+ * and restrictions are checked before acting on the URL, or
+ * rejecting it. -FM
+ */
+ code = NOT_FOUND;
+ }
+ }
+ }
+ BStrFree(searchstring);
+ BStrFree(temp);
+ return code;
+}
+
+static void write_offset(FILE *fp, HTLine *line)
+{
+ int i;
+
+ if (line->data[0]) {
+ for (i = 0; i < (int) line->offset; i++) {
+ fputc(' ', fp);
+ }
+ }
+}
+
+static void write_hyphen(FILE *fp)
+{
+ if (dump_output_immediately &&
+ LYRawMode &&
+ LYlowest_eightbit[current_char_set] <= 173 &&
+ (LYCharSet_UC[current_char_set].enc == UCT_ENC_8859 ||
+ (LYCharSet_UC[current_char_set].like8859 & UCT_R_8859SPECL)) != 0) {
+ fputc(0xad, fp); /* the iso8859 byte for SHY */
+ } else {
+ fputc('-', fp);
+ }
+}
+
+/*
+ * Returns the length after trimming trailing blanks. Modify the string as
+ * needed so that any special character which follows a trailing blank is moved
+ * before the (trimmed) blank, so the result which will be dumped has no
+ * trailing blanks.
+ */
+static int TrimmedLength(char *string)
+{
+ int result = (int) strlen(string);
+
+ if (!HTisDocumentSource()) {
+ int adjust = result;
+ int ch;
+
+ while (adjust > 0) {
+ ch = UCH(string[adjust - 1]);
+ if (isspace(ch) || IsSpecialAttrChar(ch)) {
+ --adjust;
+ } else {
+ break;
+ }
+ }
+ if (result != adjust) {
+ char *dst = string + adjust;
+ char *src = dst;
+
+ for (;;) {
+ src = LYSkipBlanks(src);
+ if ((*dst++ = *src++) == '\0')
+ break;
+ }
+ result = (int) (dst - string - 1);
+ }
+ }
+ return result;
+}
+
+typedef struct _AnchorIndex {
+ struct _AnchorIndex *next;
+ int type; /* field type */
+ int size; /* character-width of field */
+ int length; /* byte-count for field's data */
+ int offset; /* byte-offset in line's data */
+ char filler; /* character to use for filler */
+ const char *value; /* field's value */
+} AnchorIndex;
+
+static unsigned countHTLines(void)
+{
+ unsigned result = 0;
+ HTLine *line = FirstHTLine(HTMainText);
+
+ while (line != 0) {
+ ++result;
+ if (line == HTMainText->last_line)
+ break;
+ line = line->next;
+ }
+ CTRACE((tfp, "countHTLines %u\n", result));
+ return result;
+}
+
+/*
+ * The TextAnchor list is not organized to allow efficient dumping of a page.
+ * Make an array with one item per line of the page, and store (by byte-offset)
+ * pointers to the TextAnchor's we want to use.
+ */
+static AnchorIndex **allocAnchorIndex(unsigned *size)
+{
+ AnchorIndex **result = NULL;
+ AnchorIndex *p, *q;
+ TextAnchor *anchor = NULL;
+ FormInfo *input = NULL;
+
+ *size = countHTLines();
+ if (*size != 0) {
+ result = typecallocn(AnchorIndex *, *size + 1);
+ if (result == NULL)
+ outofmem(__FILE__, "allocAnchorIndex");
+
+ for (anchor = HTMainText->first_anchor;
+ anchor != NULL;
+ anchor = anchor->next) {
+
+ if (anchor->link_type == INPUT_ANCHOR
+ && anchor->show_anchor
+ && anchor->line_num < (int) *size
+ && (input = anchor->input_field) != NULL) {
+ CTRACE2(TRACE_GRIDTEXT,
+ (tfp, "line %d.%d %d %s->%s(%s)\n",
+ anchor->line_num,
+ anchor->line_pos,
+ input->size,
+ inputFieldDesc(input),
+ input->value,
+ input->orig_value));
+ switch (input->type) {
+ case F_SUBMIT_TYPE:
+ case F_RESET_TYPE:
+ case F_TEXT_SUBMIT_TYPE:
+ case F_IMAGE_SUBMIT_TYPE:
+ CTRACE2(TRACE_GRIDTEXT, (tfp, "skipping\n"));
+ continue;
+ case F_TEXT_TYPE:
+ case F_PASSWORD_TYPE:
+ case F_CHECKBOX_TYPE:
+ case F_RADIO_TYPE:
+ case F_OPTION_LIST_TYPE:
+ case F_TEXTAREA_TYPE:
+ case F_RANGE_TYPE:
+ case F_FILE_TYPE:
+ p = typecalloc(AnchorIndex);
+ if (p == NULL)
+ outofmem(__FILE__, "allocAnchorIndex");
+
+ p->type = input->type;
+ p->size = input->size;
+ p->offset = anchor->line_pos;
+ p->value = input->value;
+
+ switch (input->type) {
+ case F_TEXTAREA_TYPE:
+ case F_TEXT_TYPE:
+ case F_PASSWORD_TYPE:
+ p->filler = '_';
+ break;
+ case F_OPTION_LIST_TYPE:
+ p->filler = '_';
+ break;
+ case F_CHECKBOX_TYPE:
+ p->value = (input->num_value
+ ? checked_box
+ : unchecked_box);
+ break;
+ case F_RADIO_TYPE:
+ p->value = (input->num_value
+ ? checked_radio
+ : unchecked_radio);
+ break;
+ default:
+ p->filler = ' ';
+ break;
+ }
+ p->length = (int) strlen(p->value);
+
+ if ((q = result[anchor->line_num]) != NULL) {
+ /* insert, ordering by offset */
+ if (q->offset < p->offset) {
+ while (q->next != NULL
+ && q->next->offset < p->offset) {
+ q = q->next;
+ }
+ p->next = q->next;
+ q->next = p;
+ } else {
+ p->next = q;
+ result[anchor->line_num] = p;
+ }
+ } else {
+ result[anchor->line_num] = p;
+ }
+ break;
+ }
+ }
+ }
+ }
+ return result;
+}
+
+/*
+ * Free the data allocated in allocAnchorIndex().
+ */
+static void freeAnchorIndex(AnchorIndex ** inx, unsigned inx_size)
+{
+ AnchorIndex *cur;
+ unsigned num;
+
+ if (inx != 0) {
+ if (inx_size != 0) {
+ for (num = 0; num < inx_size; ++num) {
+ while ((cur = inx[num]) != NULL) {
+ inx[num] = cur->next;
+ free(cur);
+ }
+ }
+ }
+ free(inx);
+ }
+}
+
+/*
+ * Return the column (counting from zero) at which a field should be overlaid
+ * on the form.
+ */
+static int FieldFirst(AnchorIndex * p, int wrap)
+{
+ return (wrap ? 0 : (p)->offset);
+}
+
+/*
+ * Return the column (counting from zero) just past the field in a form.
+ */
+static int FieldLast(AnchorIndex * p, int wrap)
+{
+ return ((p)->size - wrap) + FieldFirst(p, wrap);
+}
+
+/*
+ * Print the contents of the file in HTMainText to
+ * the file descriptor fp.
+ * If is_email is TRUE add ">" before each "From " line.
+ * If is_reply is TRUE add ">" to the beginning of each
+ * line to specify the file is a reply to message.
+ */
+void print_wwwfile_to_fd(FILE *fp,
+ int is_email,
+ int is_reply)
+{
+ int line_num, byte_num, byte_count, byte_offset;
+ int first = TRUE;
+ HTLine *line;
+ AnchorIndex **inx; /* sorted index of input-fields */
+ AnchorIndex *cur = 0; /* current input-field */
+ unsigned inx_size; /* number of entries in inx[] */
+ int in_field = -1; /* if positive, is index in cur->value[] */
+ int this_wrap = 0; /* current wrapping point of cur->value[] */
+ int next_wrap = 0; /* next wrapping point of cur->value[] */
+
+#ifndef NO_DUMP_WITH_BACKSPACES
+ HText *text = HTMainText;
+ BOOL in_b = FALSE;
+ BOOL in_u = FALSE;
+ BOOL bs = (BOOL) (!is_email && !is_reply
+ && text != 0
+ && with_backspaces
+ && !IS_CJK_TTY
+ && !text->T.output_utf8);
+#endif
+
+ if (!HTMainText)
+ return;
+
+ /*
+ * Build an index of anchors for each line, so we can override the
+ * static text which is stored in the list of HTLine's.
+ */
+ inx = allocAnchorIndex(&inx_size);
+
+ line = FirstHTLine(HTMainText);
+ for (line_num = 0;; ++line_num, line = line->next) {
+ if (in_field >= 0) {
+ this_wrap = next_wrap;
+ next_wrap = 0; /* FIXME - allow for multiple continuations */
+ CTRACE2(TRACE_GRIDTEXT,
+ (tfp, "wrap %d:%d, offset %d\n",
+ in_field, cur ? cur->length : -1, this_wrap));
+ } else {
+ cur = inx[line_num];
+ }
+
+ CTRACE2(TRACE_GRIDTEXT, (tfp, "dump %d:%s\n", line_num, line->data));
+
+ if (first) {
+ first = FALSE;
+ if (is_reply) {
+ fputc('>', fp);
+ } else if (is_email && !StrNCmp(line->data, "From ", 5)) {
+ fputc('>', fp);
+ }
+ } else if (line->data[0] != LY_SOFT_NEWLINE) {
+ fputc('\n', fp);
+ /*
+ * Add news-style quotation if requested. -FM
+ */
+ if (is_reply) {
+ fputc('>', fp);
+ } else if (is_email && !StrNCmp(line->data, "From ", 5)) {
+ fputc('>', fp);
+ }
+ }
+
+ write_offset(fp, line);
+
+ /*
+ * Add data.
+ */
+ byte_offset = line->offset;
+ byte_count = TrimmedLength(line->data);
+ for (byte_num = 0; byte_num < byte_count; byte_num += 1) {
+ int cell_chr, temp_chr;
+ size_t cell_len, temp_len;
+ const char *cell_ptr, *temp_ptr, *try_utf8;
+
+ cell_ptr = &line->data[byte_num];
+ cell_len = 1;
+ cell_chr = UCH(*cell_ptr);
+
+ while (cur != 0 && FieldLast(cur, this_wrap) < byte_offset) {
+ CTRACE2(TRACE_GRIDTEXT,
+ (tfp, "skip field since last %d < %d\n",
+ FieldLast(cur, this_wrap), byte_offset));
+ cur = cur->next;
+ in_field = -1;
+ }
+ if (cur != 0 && in_field >= 0) {
+ CTRACE2(TRACE_GRIDTEXT,
+ (tfp, "compare %d to [%d..%d]\n",
+ byte_offset,
+ FieldFirst(cur, this_wrap),
+ FieldLast(cur, this_wrap) - 1));
+ }
+ if (cur != 0
+ && FieldFirst(cur, this_wrap) <= byte_offset
+ && FieldLast(cur, this_wrap) > byte_offset) {
+ int off2 = ((in_field > 0)
+ ? in_field
+ : (byte_offset - FieldFirst(cur, this_wrap)));
+
+ /*
+ * On the first time (for each line that the field appears on),
+ * check if this field wraps. If it does, save the offset into
+ * the field which will be used to adjust the beginning of the
+ * continuation line.
+ */
+ if (byte_offset == FieldFirst(cur, this_wrap)) {
+ next_wrap = 0;
+ if (cur->size - this_wrap + byte_num > byte_count) {
+ CTRACE((tfp, "size %d, offset %d, length %d\n",
+ cur->size,
+ cur->offset,
+ cur->length));
+ CTRACE((tfp, "byte_count %d, byte_num %d\n",
+ byte_count, byte_num));
+ next_wrap = byte_count - byte_num;
+ CTRACE2(TRACE_GRIDTEXT,
+ (tfp, "field will wrap: %d\n", next_wrap));
+ }
+ }
+
+ if (off2 >= 0 && off2 < cur->length) {
+ temp_ptr = &(cur->value[off2]);
+ try_utf8 = temp_ptr;
+ temp_chr = (int) UCGetUniFromUtf8String(&try_utf8);
+ if (temp_chr > 127) {
+ temp_len = (size_t) (try_utf8 - temp_ptr) + 1;
+ } else {
+ temp_chr = UCH(*temp_ptr);
+ temp_len = 1;
+ }
+ } else {
+ temp_ptr = &(cur->filler);
+ temp_len = 1;
+ temp_chr = UCH(*temp_ptr);
+ }
+
+ if (cell_chr != temp_chr) {
+ CTRACE2(TRACE_GRIDTEXT,
+ (tfp, "line %d %d/%d [%d..%d] map %d %04X->%04X\n",
+ line_num,
+ off2, cur->length,
+ FieldFirst(cur, this_wrap),
+ FieldLast(cur, this_wrap) - 1,
+ byte_offset, cell_chr, temp_chr));
+ cell_chr = temp_chr;
+ cell_ptr = temp_ptr;
+ cell_len = temp_len;
+ }
+ off2 += (int) temp_len;
+ byte_offset += (int) temp_len;
+ if ((off2 >= cur->size) &&
+ (off2 >= cur->length || F_TEXTLIKE(cur->type))) {
+ in_field = -1;
+ this_wrap = 0;
+ next_wrap = 0;
+ } else {
+ in_field = off2;
+ }
+ } else {
+ byte_offset++;
+ }
+
+ if (!IsSpecialAttrChar(cell_chr)) {
+#ifndef NO_DUMP_WITH_BACKSPACES
+ size_t n;
+
+ if (in_b) {
+ IGNORE_RC(fwrite(cell_ptr, sizeof(char), cell_len, fp));
+
+ for (n = 0; n < cell_len; ++n) {
+ fputc('\b', fp);
+ }
+ IGNORE_RC(fwrite(cell_ptr, sizeof(char), cell_len, fp));
+ } else if (in_u) {
+ for (n = 0; n < cell_len; ++n) {
+ fputc('_', fp);
+ }
+ for (n = 0; n < cell_len; ++n) {
+ fputc('\b', fp);
+ }
+ IGNORE_RC(fwrite(cell_ptr, sizeof(char), cell_len, fp));
+ } else
+#endif
+ IGNORE_RC(fwrite(cell_ptr, sizeof(char), cell_len, fp));
+ } else if (cell_chr == LY_SOFT_HYPHEN &&
+ (byte_num + 1) >= byte_count) {
+ write_hyphen(fp);
+ } else if (dump_output_immediately && use_underscore) {
+ switch (cell_chr) {
+ case LY_UNDERLINE_START_CHAR:
+ case LY_UNDERLINE_END_CHAR:
+ fputc('_', fp);
+ break;
+ case LY_BOLD_START_CHAR:
+ case LY_BOLD_END_CHAR:
+ break;
+ }
+ }
+#ifndef NO_DUMP_WITH_BACKSPACES
+ else if (bs) {
+ switch (cell_chr) {
+ case LY_UNDERLINE_START_CHAR:
+ if (!in_b)
+ in_u = TRUE; /*favor bold over underline */
+ break;
+ case LY_UNDERLINE_END_CHAR:
+ in_u = FALSE;
+ break;
+ case LY_BOLD_START_CHAR:
+ if (in_u)
+ in_u = FALSE; /* turn it off */
+ in_b = TRUE;
+ break;
+ case LY_BOLD_END_CHAR:
+ in_b = FALSE;
+ break;
+ }
+ }
+#endif
+ }
+
+ if (line == HTMainText->last_line)
+ break;
+
+#ifdef VMS
+ if (HadVMSInterrupt)
+ break;
+#endif /* VMS */
+ }
+ fputc('\n', fp);
+
+ freeAnchorIndex(inx, inx_size);
+}
+
+/*
+ * Print the contents of the file in HTMainText to
+ * the file descriptor fp.
+ * First output line is "thelink", ie, the URL for this file.
+ */
+void print_crawl_to_fd(FILE *fp, char *thelink,
+ char *thetitle)
+{
+ register int i;
+ int first = TRUE;
+ int limit;
+ HTLine *line;
+
+ if (!HTMainText)
+ return;
+
+ line = FirstHTLine(HTMainText);
+ fprintf(fp, "THE_URL:%s\n", thelink);
+ if (thetitle != NULL) {
+ fprintf(fp, "THE_TITLE:%s\n", thetitle);
+ }
+
+ for (;; line = line->next) {
+ if (!first && line->data[0] != LY_SOFT_NEWLINE)
+ fputc('\n', fp);
+ first = FALSE;
+ write_offset(fp, line);
+
+ /*
+ * Add data.
+ */
+ limit = TrimmedLength(line->data);
+ for (i = 0; i < limit; i++) {
+ int ch = UCH(line->data[i]);
+
+ if (!IsSpecialAttrChar(ch)) {
+ fputc(ch, fp);
+ } else if (ch == LY_SOFT_HYPHEN &&
+ (i + 1) >= limit) { /* last char on line */
+ write_hyphen(fp);
+ }
+ }
+
+ if (!HTMainText || (line == HTMainText->last_line)) {
+ break;
+ }
+ }
+ fputc('\n', fp);
+
+ /*
+ * Add the References list if appropriate
+ */
+ if ((no_list == FALSE) &&
+ (dump_links_inline == FALSE) &&
+ links_are_numbered()) {
+ printlist(fp, FALSE);
+ }
+#ifdef VMS
+ HadVMSInterrupt = FALSE;
+#endif /* VMS */
+}
+
+static void adjust_search_result(DocInfo *doc, int tentative_result,
+ int start_line)
+{
+ if (tentative_result > 0) {
+ int anch_line = -1;
+ TextAnchor *a;
+ int nl_closest = -1;
+ int goal = SEARCH_GOAL_LINE;
+ int max_offset;
+ BOOL on_screen = (BOOL) (tentative_result > HTMainText->top_of_screen &&
+ tentative_result <= HTMainText->top_of_screen +
+ display_lines);
+
+ if (goal < 1)
+ goal = 1;
+ else if (goal > display_lines)
+ goal = display_lines;
+ max_offset = goal - 1;
+
+ if (on_screen && nlinks > 0) {
+ int i;
+
+ for (i = 0; i < nlinks; i++) {
+ if (doc->line + links[i].ly - 1 <= tentative_result)
+ nl_closest = i;
+ if (doc->line + links[i].ly - 1 >= tentative_result)
+ break;
+ }
+ if (nl_closest >= 0 &&
+ doc->line + links[nl_closest].ly - 1 == tentative_result) {
+ www_search_result = doc->line;
+ doc->link = nl_closest;
+ return;
+ }
+ }
+
+ /* find last anchor before or on target line */
+ for (a = HTMainText->first_anchor;
+ a && a->line_num <= tentative_result - 1; a = a->next) {
+ anch_line = a->line_num + 1;
+ }
+ /* position such that the anchor found is on first screen line,
+ if it is not too far above the target line; but also try to
+ make sure we move forward. */
+ if (anch_line >= 0 &&
+ anch_line >= tentative_result - max_offset &&
+ (anch_line > start_line ||
+ tentative_result <= HTMainText->top_of_screen)) {
+ www_search_result = anch_line;
+ } else if (tentative_result - start_line > 0 &&
+ tentative_result - (start_line + 1) <= max_offset) {
+ www_search_result = start_line + 1;
+ } else if (tentative_result > HTMainText->top_of_screen &&
+ tentative_result <= start_line && /* have wrapped */
+ tentative_result <= HTMainText->top_of_screen + goal) {
+ www_search_result = HTMainText->top_of_screen + 1;
+ } else if (tentative_result <= goal)
+ www_search_result = 1;
+ else
+ www_search_result = tentative_result - max_offset;
+ if (www_search_result == doc->line) {
+ if (nl_closest >= 0) {
+ doc->link = nl_closest;
+ return;
+ }
+ }
+ }
+}
+
+/*
+ * see also link_has_target
+ */
+static BOOL anchor_has_target(TextAnchor *a, char *target)
+{
+ char *text = NULL;
+ const char *last = "?";
+ int count;
+
+ /*
+ * Combine the parts of the link's text using the highlighting information,
+ * and compare the target against that.
+ */
+ for (count = 0; count < 10; ++count) {
+ const char *part = LYGetHiTextStr(a, count);
+
+ if (part == NULL || part == last) {
+ if (text != NULL && LYno_attr_strstr(text, target)) {
+ return TRUE;
+ }
+ break;
+ }
+ StrAllocCat(text, part);
+ last = part;
+ }
+
+ return field_has_target(a->input_field, target);
+}
+
+static TextAnchor *line_num_to_anchor(int line_num)
+{
+ TextAnchor *a;
+
+ if (HTMainText != 0) {
+ a = HTMainText->first_anchor;
+ while (a != 0 && a->line_num < line_num) {
+ a = a->next;
+ }
+ } else {
+ a = 0;
+ }
+ return a;
+}
+
+static int line_num_in_text(HText *text, HTLine *line)
+{
+ int result = 1;
+ HTLine *temp = FirstHTLine(text);
+
+ while (temp != line) {
+ temp = temp->next;
+ ++result;
+ }
+ return result;
+}
+
+/* Computes the 'prev' pointers on demand, and returns the one for the given
+ * anchor.
+ */
+static TextAnchor *get_prev_anchor(TextAnchor *a)
+{
+ TextAnchor *p, *q;
+
+ if (a->prev == 0) {
+ if ((p = HTMainText->first_anchor) != 0) {
+ while ((q = p->next) != 0) {
+ q->prev = p;
+ p = q;
+ }
+ }
+ }
+ return a->prev;
+}
+
+static int www_search_forward(int start_line,
+ DocInfo *doc,
+ char *target,
+ HTLine *line,
+ int count)
+{
+ int wrapped = 0;
+ TextAnchor *a = line_num_to_anchor(count - 1);
+ int tentative_result = -1;
+
+ for (;;) {
+ while ((a != NULL) && a->line_num == (count - 1)) {
+ if (a->show_anchor &&
+ !(a->link_type == INPUT_ANCHOR
+ && a->input_field->type == F_HIDDEN_TYPE)) {
+ if (anchor_has_target(a, target)) {
+ adjust_search_result(doc, count, start_line);
+ return 1;
+ }
+ }
+ a = a->next;
+ }
+
+ if (LYno_attr_strstr(line->data, target)) {
+ tentative_result = count;
+ break;
+ } else if ((count == start_line && wrapped) || wrapped > 1) {
+ HTUserMsg2(STRING_NOT_FOUND, target);
+ return -1;
+ } else if (line == HTMainText->last_line) {
+ count = 0;
+ wrapped++;
+ a = HTMainText->first_anchor;
+ }
+ line = line->next;
+ count++;
+ }
+ if (tentative_result > 0) {
+ adjust_search_result(doc, tentative_result, start_line);
+ }
+ return 0;
+}
+
+static int www_search_backward(int start_line,
+ DocInfo *doc,
+ char *target,
+ HTLine *line,
+ int count)
+{
+ int wrapped = 0;
+ TextAnchor *a = line_num_to_anchor(count - 1);
+ int tentative_result = -1;
+
+ for (;;) {
+ while ((a != NULL) && a->line_num == (count - 1)) {
+ if (a->show_anchor &&
+ !(a->link_type == INPUT_ANCHOR
+ && a->input_field->type == F_HIDDEN_TYPE)) {
+ if (anchor_has_target(a, target)) {
+ adjust_search_result(doc, count, start_line);
+ return 1;
+ }
+ }
+ a = get_prev_anchor(a);
+ }
+
+ if (LYno_attr_strstr(line->data, target)) {
+ tentative_result = count;
+ break;
+ } else if ((count == start_line && wrapped) || wrapped > 1) {
+ HTUserMsg2(STRING_NOT_FOUND, target);
+ return -1;
+ } else if (line == FirstHTLine(HTMainText)) {
+ count = line_num_in_text(HTMainText, LastHTLine(HTMainText)) + 1;
+ wrapped++;
+ a = HTMainText->last_anchor;
+ }
+ line = line->prev;
+ count--;
+ }
+ if (tentative_result > 0) {
+ adjust_search_result(doc, tentative_result, start_line);
+ }
+ return 0;
+}
+
+void www_user_search(int start_line,
+ DocInfo *doc,
+ char *target,
+ int direction)
+{
+ HTLine *line;
+ int count;
+
+ if (!HTMainText) {
+ return;
+ }
+
+ /*
+ * Advance to the start line.
+ */
+ line = FirstHTLine(HTMainText);
+ if (start_line + direction > 0) {
+ for (count = 1;
+ count < start_line + direction;
+ line = line->next, count++) {
+ if (line == HTMainText->last_line) {
+ line = FirstHTLine(HTMainText);
+ count = 1;
+ break;
+ }
+ }
+ } else {
+ line = HTMainText->last_line;
+ count = line_num_in_text(HTMainText, line);
+ }
+
+ if (direction >= 0)
+ www_search_forward(start_line, doc, target, line, count);
+ else
+ www_search_backward(start_line, doc, target, line, count);
+}
+
+void user_message(const char *message,
+ const char *argument)
+{
+ if (message == NULL) {
+ mustshow = FALSE;
+ } else {
+ char *temp = NULL;
+
+ HTSprintf0(&temp, message, NonNull(argument));
+ statusline(temp);
+ FREE(temp);
+ }
+}
+
+/*
+ * HText_getOwner returns the owner of the
+ * current document.
+ */
+const char *HText_getOwner(void)
+{
+ return (HTMainText ?
+ HTAnchor_owner(HTMainText->node_anchor) : 0);
+}
+
+/*
+ * HText_setMainTextOwner sets the owner for the
+ * current document.
+ */
+void HText_setMainTextOwner(const char *owner)
+{
+ if (!HTMainText)
+ return;
+
+ HTAnchor_setOwner(HTMainText->node_anchor, owner);
+}
+
+/*
+ * HText_getRevTitle returns the RevTitle element of the
+ * current document, used as the subject for mailto comments
+ * to the owner.
+ */
+const char *HText_getRevTitle(void)
+{
+ return (HTMainText ?
+ HTAnchor_RevTitle(HTMainText->node_anchor) : 0);
+}
+
+/*
+ * HText_getContentBase returns the Content-Base header
+ * of the current document.
+ */
+const char *HText_getContentBase(void)
+{
+ return (HTMainText ?
+ HTAnchor_content_base(HTMainText->node_anchor) : 0);
+}
+
+/*
+ * HText_getContentLocation returns the Content-Location header
+ * of the current document.
+ */
+const char *HText_getContentLocation(void)
+{
+ return (HTMainText ?
+ HTAnchor_content_location(HTMainText->node_anchor) : 0);
+}
+
+/*
+ * HText_getMessageID returns the Message-ID of the
+ * current document.
+ */
+const char *HText_getMessageID(void)
+{
+ return (HTMainText ?
+ HTAnchor_messageID(HTMainText->node_anchor) : NULL);
+}
+
+void HTuncache_current_document(void)
+{
+ /*
+ * Should remove current document from memory.
+ */
+ if (HTMainText) {
+ HTParentAnchor *htmain_anchor = HTMainText->node_anchor;
+
+ if (htmain_anchor) {
+ if (!(HTOutputFormat && HTOutputFormat == WWW_SOURCE)) {
+ FREE(htmain_anchor->UCStages);
+ }
+ }
+ CTRACE((tfp, "\nHTuncache.. freeing document for '%s'%s\n",
+ ((htmain_anchor &&
+ htmain_anchor->address) ?
+ htmain_anchor->address : "unknown anchor"),
+ ((htmain_anchor &&
+ htmain_anchor->post_data)
+ ? " with POST data"
+ : "")));
+ HTList_removeObject(loaded_texts, HTMainText);
+ HText_free(HTMainText);
+ HTMainText = NULL;
+ } else {
+ CTRACE((tfp, "HTuncache.. HTMainText already is NULL!\n"));
+ }
+}
+
+/*
+ * This magic FREE(anchor->UCStages) call
+ * stolen from HTuncache_current_document() above.
+ */
+static void magicUncache(void)
+{
+ if (!(HTOutputFormat && HTOutputFormat == WWW_SOURCE)) {
+ FREE(HTMainAnchor->UCStages);
+ }
+ /* avoid null-reference later */
+ if (!HTOutputFormat)
+ HTOutputFormat = WWW_SOURCE;
+}
+
+#ifdef USE_SOURCE_CACHE
+
+/* dummy - kw */
+static HTProtocol scm =
+{
+ "source-cache-mem", 0, 0
+};
+
+static BOOLEAN useSourceCache(void)
+{
+ BOOLEAN result = FALSE;
+
+ if (LYCacheSource == SOURCE_CACHE_FILE) {
+ result = (BOOLEAN) (HTMainAnchor->source_cache_file != 0);
+ CTRACE((tfp, "SourceCache: file-cache%s found\n",
+ result ? "" : " not"));
+ }
+ return result;
+}
+
+static BOOLEAN useMemoryCache(void)
+{
+ BOOLEAN result = FALSE;
+
+ if (LYCacheSource == SOURCE_CACHE_MEMORY) {
+ result = (BOOLEAN) (HTMainAnchor->source_cache_chunk != 0);
+ CTRACE((tfp, "SourceCache: memory-cache%s found\n",
+ result ? "" : " not"));
+ }
+ return result;
+}
+
+BOOLEAN HTreparse_document(void)
+{
+ BOOLEAN ok = FALSE;
+
+ if (!HTMainAnchor || LYCacheSource == SOURCE_CACHE_NONE) {
+ CTRACE((tfp, "HTreparse_document returns FALSE\n"));
+ } else if (useSourceCache()) {
+ FILE *fp;
+ HTFormat format;
+ int ret;
+
+ CTRACE((tfp, "SourceCache: Reparsing file %s\n",
+ HTMainAnchor->source_cache_file));
+
+ magicUncache();
+
+ /*
+ * This is more or less copied out of HTLoadFile(), except we don't
+ * get a content encoding. This may be overkill. -dsb
+ */
+ if (HTMainAnchor->content_type) {
+ format = HTAtom_for(HTMainAnchor->content_type);
+ } else {
+ format = HTFileFormat(HTMainAnchor->source_cache_file, NULL, NULL);
+ format = HTCharsetFormat(format, HTMainAnchor,
+ UCLYhndl_for_unspec);
+ /* not UCLYhndl_HTFile_for_unspec - we are talking about remote
+ * documents...
+ */
+ }
+ CTRACE((tfp, " Content type is \"%s\"\n", format->name));
+
+ fp = fopen(HTMainAnchor->source_cache_file, "r");
+ if (!fp) {
+ CTRACE((tfp, " Cannot read file %s\n", HTMainAnchor->source_cache_file));
+ (void) LYRemoveTemp(HTMainAnchor->source_cache_file);
+ FREE(HTMainAnchor->source_cache_file);
+ } else {
+
+ if (HText_HaveUserChangedForms(HTMainText)) {
+ /*
+ * Issue a warning. Will not restore changed forms, currently.
+ */
+ HTAlert(RELOADING_FORM);
+ }
+ /* Set HTMainAnchor->protocol or HTMainAnchor->physical to convince
+ * the SourceCacheWriter to not regenerate the cache file (which
+ * would be an unnecessary "loop"). - kw
+ */
+ HTAnchor_setProtocol(HTMainAnchor, &HTFile);
+ ret = HTParseFile(format, HTOutputFormat, HTMainAnchor, fp, NULL);
+ LYCloseInput(fp);
+ if (ret == HT_PARTIAL_CONTENT) {
+ HTInfoMsg(gettext("Loading incomplete."));
+ CTRACE((tfp,
+ "SourceCache: `%s' has been accessed, partial content.\n",
+ HTLoadedDocumentURL()));
+ }
+ ok = (BOOL) (ret == HT_LOADED || ret == HT_PARTIAL_CONTENT);
+
+ CTRACE((tfp, "Reparse file %s\n", (ok ? "succeeded" : "failed")));
+ }
+ } else if (useMemoryCache()) {
+ HTFormat format = WWW_HTML;
+ int ret;
+
+ CTRACE((tfp, "SourceCache: Reparsing from memory chunk %p\n",
+ (void *) HTMainAnchor->source_cache_chunk));
+
+ magicUncache();
+
+ if (HTMainAnchor->content_type) {
+ format = HTAtom_for(HTMainAnchor->content_type);
+ } else {
+ /*
+ * This is only done to make things aligned with SOURCE_CACHE_NONE
+ * and SOURCE_CACHE_FILE when switching to source mode since the
+ * original document's charset will be LYPushAssumed() and then
+ * LYPopAssumed(). See LYK_SOURCE in mainloop if you change
+ * something here. No user-visible benefits, seems just '=' Info
+ * Page will show source's effective charset as "(assumed)".
+ */
+ format = HTCharsetFormat(format, HTMainAnchor,
+ UCLYhndl_for_unspec);
+ }
+ /* not UCLYhndl_HTFile_for_unspec - we are talking about remote documents... */
+
+ if (HText_HaveUserChangedForms(HTMainText)) {
+ /*
+ * Issue a warning. Will not restore changed forms, currently.
+ */
+ HTAlert(RELOADING_FORM);
+ }
+ /* Set HTMainAnchor->protocol or HTMainAnchor->physical to convince
+ * the SourceCacheWriter to not regenerate the cache chunk (which
+ * would be an unnecessary "loop"). - kw
+ */
+ HTAnchor_setProtocol(HTMainAnchor, &scm); /* cheating -
+ anything != &HTTP or &HTTPS would do - kw */
+ ret = HTParseMem(format, HTOutputFormat, HTMainAnchor,
+ HTMainAnchor->source_cache_chunk, NULL);
+ ok = (BOOL) (ret == HT_LOADED);
+
+ CTRACE((tfp, "Reparse memory %s\n", (ok ? "succeeded" : "failed")));
+ }
+
+ return ok;
+}
+
+BOOLEAN HTcan_reparse_document(void)
+{
+ BOOLEAN result = FALSE;
+
+ if (!HTMainAnchor || LYCacheSource == SOURCE_CACHE_NONE) {
+ result = FALSE;
+ } else if (useSourceCache()) {
+ result = LYCanReadFile(HTMainAnchor->source_cache_file);
+ } else if (useMemoryCache()) {
+ result = TRUE;
+ }
+
+ CTRACE((tfp, "HTcan_reparse_document -> %d\n", result));
+ return result;
+}
+
+static void trace_setting_change(const char *name,
+ int prev_setting,
+ int new_setting)
+{
+ if (prev_setting != new_setting)
+ CTRACE((tfp,
+ "HTdocument_settings_changed: %s setting has changed (was %d, now %d)\n",
+ name, prev_setting, new_setting));
+}
+
+BOOLEAN HTdocument_settings_changed(void)
+{
+ /*
+ * Annoying Hack(TM): If we don't have a source cache, we can't
+ * reparse anyway, so pretend the settings haven't changed.
+ */
+ if (!HTMainText || !HTcan_reparse_document())
+ return FALSE;
+
+ if (TRACE) {
+ /*
+ * If we're tracing, note everying that has changed.
+ */
+ trace_setting_change("CLICKABLE_IMAGES",
+ HTMainText->clickable_images, clickable_images);
+ trace_setting_change("PSEUDO_INLINE_ALTS",
+ HTMainText->pseudo_inline_alts,
+ pseudo_inline_alts);
+ trace_setting_change("VERBOSE_IMG",
+ HTMainText->verbose_img,
+ verbose_img);
+ trace_setting_change("RAW_MODE", HTMainText->raw_mode,
+ LYUseDefaultRawMode);
+ trace_setting_change("HISTORICAL_COMMENTS",
+ HTMainText->historical_comments,
+ historical_comments);
+ trace_setting_change("MINIMAL_COMMENTS",
+ HTMainText->minimal_comments, minimal_comments);
+ trace_setting_change("SOFT_DQUOTES",
+ HTMainText->soft_dquotes, soft_dquotes);
+ trace_setting_change("OLD_DTD", HTMainText->old_dtd, Old_DTD);
+ trace_setting_change("KEYPAD_MODE",
+ HTMainText->keypad_mode, keypad_mode);
+ if (HTMainText->disp_lines != LYlines || HTMainText->disp_cols != DISPLAY_COLS)
+ CTRACE((tfp,
+ "HTdocument_settings_changed: Screen size has changed (was %dx%d, now %dx%d)\n",
+ HTMainText->disp_cols,
+ HTMainText->disp_lines,
+ DISPLAY_COLS,
+ LYlines));
+ }
+
+ return (BOOLEAN) (HTMainText->clickable_images != clickable_images ||
+ HTMainText->pseudo_inline_alts != pseudo_inline_alts ||
+ HTMainText->verbose_img != verbose_img ||
+ HTMainText->raw_mode != LYUseDefaultRawMode ||
+ HTMainText->historical_comments != historical_comments ||
+ (HTMainText->minimal_comments != minimal_comments &&
+ !historical_comments) ||
+ HTMainText->soft_dquotes != soft_dquotes ||
+ HTMainText->old_dtd != Old_DTD ||
+ HTMainText->keypad_mode != keypad_mode ||
+ HTMainText->disp_cols != DISPLAY_COLS);
+}
+#endif
+
+int HTisDocumentSource(void)
+{
+ return (HTMainText != 0) ? HTMainText->source : FALSE;
+}
+
+const char *HTLoadedDocumentURL(void)
+{
+ if (!HTMainText)
+ return ("");
+
+ if (HTMainText->node_anchor && HTMainText->node_anchor->address)
+ return (HTMainText->node_anchor->address);
+ else
+ return ("");
+}
+
+bstring *HTLoadedDocumentPost_data(void)
+{
+ if (HTMainText
+ && HTMainText->node_anchor
+ && HTMainText->node_anchor->post_data)
+ return (HTMainText->node_anchor->post_data);
+ else
+ return (0);
+}
+
+const char *HTLoadedDocumentTitle(void)
+{
+ if (!HTMainText)
+ return ("");
+
+ if (HTMainText->node_anchor && HTMainText->node_anchor->title)
+ return (HTMainText->node_anchor->title);
+ else
+ return ("");
+}
+
+BOOLEAN HTLoadedDocumentIsHEAD(void)
+{
+ if (!HTMainText)
+ return (FALSE);
+
+ if (HTMainText->node_anchor && HTMainText->node_anchor->isHEAD)
+ return (HTMainText->node_anchor->isHEAD);
+ else
+ return (FALSE);
+}
+
+BOOLEAN HTLoadedDocumentIsSafe(void)
+{
+ if (!HTMainText)
+ return (FALSE);
+
+ if (HTMainText->node_anchor && HTMainText->node_anchor->safe)
+ return (HTMainText->node_anchor->safe);
+ else
+ return (FALSE);
+}
+
+const char *HTLoadedDocumentCharset(void)
+{
+ const char *result = NULL;
+
+ if (HTMainText &&
+ HTMainText->node_anchor) {
+ result = HTMainText->node_anchor->charset;
+ }
+
+ return result;
+}
+
+BOOL HTLoadedDocumentEightbit(void)
+{
+ if (!HTMainText)
+ return (NO);
+ else
+ return (HTMainText->have_8bit_chars);
+}
+
+void HText_setNodeAnchorBookmark(const char *bookmark)
+{
+ if (!HTMainText)
+ return;
+
+ if (HTMainText->node_anchor)
+ HTAnchor_setBookmark(HTMainText->node_anchor, bookmark);
+}
+
+const char *HTLoadedDocumentBookmark(void)
+{
+ if (!HTMainText)
+ return (NULL);
+
+ if (HTMainText->node_anchor && HTMainText->node_anchor->bookmark)
+ return (HTMainText->node_anchor->bookmark);
+ else
+ return (NULL);
+}
+
+int HText_LastLineSize(HText *text, int IgnoreSpaces)
+{
+ if (!text || !text->last_line || !text->last_line->size)
+ return 0;
+ return HText_TrueLineSize(text->last_line, text, IgnoreSpaces);
+}
+
+BOOL HText_LastLineEmpty(HText *text, int IgnoreSpaces)
+{
+ if (!text || !text->last_line || !text->last_line->size)
+ return TRUE;
+ return HText_TrueEmptyLine(text->last_line, text, IgnoreSpaces);
+}
+
+int HText_LastLineOffset(HText *text)
+{
+ if (!text || !text->last_line)
+ return 0;
+ return text->last_line->offset;
+}
+
+int HText_PreviousLineSize(HText *text, int IgnoreSpaces)
+{
+ HTLine *line;
+
+ if (!text || !text->last_line)
+ return 0;
+ if (!(line = text->last_line->prev))
+ return 0;
+ return HText_TrueLineSize(line, text, IgnoreSpaces);
+}
+
+BOOL HText_PreviousLineEmpty(HText *text, int IgnoreSpaces)
+{
+ HTLine *line;
+
+ if (!text || !text->last_line)
+ return TRUE;
+ if (!(line = text->last_line->prev))
+ return TRUE;
+ return HText_TrueEmptyLine(line, text, IgnoreSpaces);
+}
+
+/*
+ * Compute the "true" line size.
+ */
+static int HText_TrueLineSize(HTLine *line, HText *text, int IgnoreSpaces)
+{
+ size_t i;
+ int true_size = 0;
+
+ if (!(line && line->size))
+ return 0;
+
+ if (IgnoreSpaces) {
+ for (i = 0; i < line->size; i++) {
+ if (!IsSpecialAttrChar(UCH(line->data[i])) &&
+ IS_UTF8_EXTRA(line->data[i]) &&
+ !isspace(UCH(line->data[i])) &&
+ UCH(line->data[i]) != HT_NON_BREAK_SPACE &&
+ UCH(line->data[i]) != HT_EN_SPACE) {
+ true_size++;
+#ifdef EXP_WCWIDTH_SUPPORT
+ if (text && text->T.output_utf8 &&
+ IS_UTF_FIRST(line->data[i])) {
+ true_size += utfextracells(&(line->data[i]));
+ }
+#endif
+ }
+ }
+ } else {
+ for (i = 0; i < line->size; i++) {
+ if (!IsSpecialAttrChar(line->data[i]) &&
+ IS_UTF8_EXTRA(line->data[i])) {
+ true_size++;
+#ifdef EXP_WCWIDTH_SUPPORT
+ if (text && text->T.output_utf8 &&
+ IS_UTF_FIRST(line->data[i])) {
+ true_size += utfextracells(&(line->data[i]));
+ }
+#endif
+ }
+ }
+ }
+ return true_size;
+}
+
+/*
+ * Tell if the line is really empty. This is invoked much more often than
+ * HText_TrueLineSize(), and most lines are not empty. So it is faster to
+ * do this check than to check if the line size happens to be zero.
+ */
+static BOOL HText_TrueEmptyLine(HTLine *line, HText *text, int IgnoreSpaces)
+{
+ size_t i;
+
+ if (!(line && line->size))
+ return TRUE;
+
+ if (IgnoreSpaces) {
+ for (i = 0; i < line->size; i++) {
+ if (!IsSpecialAttrChar(UCH(line->data[i])) &&
+ IS_UTF8_EXTRA(line->data[i]) &&
+ !isspace(UCH(line->data[i])) &&
+ UCH(line->data[i]) != HT_NON_BREAK_SPACE &&
+ UCH(line->data[i]) != HT_EN_SPACE) {
+ return FALSE;
+ }
+ }
+ } else {
+ for (i = 0; i < line->size; i++) {
+ if (!IsSpecialAttrChar(line->data[i]) &&
+ IS_UTF8_EXTRA(line->data[i])) {
+ return FALSE;
+ }
+ }
+ }
+ return TRUE;
+}
+
+void HText_NegateLineOne(HText *text)
+{
+ if (text) {
+ text->in_line_1 = NO;
+ }
+ return;
+}
+
+BOOL HText_inLineOne(HText *text)
+{
+ if (text) {
+ return text->in_line_1;
+ }
+ return YES;
+}
+
+/*
+ * This function is for removing the first of two
+ * successive blank lines. It should be called after
+ * checking the situation with HText_LastLineSize()
+ * and HText_PreviousLineSize(). Any characters in
+ * the removed line (i.e., control characters, or it
+ * wouldn't have tested blank) should have been
+ * reiterated by split_line() in the retained blank
+ * line. -FM
+ */
+void HText_RemovePreviousLine(HText *text)
+{
+ HTLine *line, *previous;
+
+ if (!(text && text->Lines > 1))
+ return;
+
+ line = text->last_line->prev;
+ previous = line->prev;
+ previous->next = text->last_line;
+ text->last_line->prev = previous;
+ text->Lines--;
+ freeHTLine(text, line);
+}
+
+/*
+ * NOTE: This function presently is correct only if the
+ * alignment is HT_LEFT. The offset is still zero,
+ * because that's not determined for HT_CENTER or
+ * HT_RIGHT until subsequent characters are received
+ * and split_line() is called. -FM
+ */
+int HText_getCurrentColumn(HText *text)
+{
+ int column = 0;
+ BOOL IgnoreSpaces = FALSE;
+
+ if (text) {
+ column = ((text->in_line_1
+ ? (int) text->style->indent1st
+ : (int) text->style->leftIndent)
+ + (int) text->last_line->offset
+ + HText_LastLineSize(text, IgnoreSpaces));
+ }
+ return column;
+}
+
+int HText_getMaximumColumn(HText *text)
+{
+ int column = DISPLAY_COLS;
+
+ if (text) {
+ column -= (int) text->style->rightIndent;
+ }
+ return column;
+}
+
+/*
+ * NOTE: This function uses HText_getCurrentColumn() which
+ * presently is correct only if the alignment is
+ * HT_LEFT. -FM
+ */
+void HText_setTabID(HText *text, const char *name)
+{
+ HTTabID *Tab = NULL;
+ HTList *cur = text->tabs;
+ HTList *last = NULL;
+
+ if (!text || isEmpty(name))
+ return;
+
+ if (!cur) {
+ cur = text->tabs = HTList_new();
+ } else {
+ while (NULL != (Tab = (HTTabID *) HTList_nextObject(cur))) {
+ if (Tab->name && !strcmp(Tab->name, name))
+ return; /* Already set. Keep the first value. */
+ last = cur;
+ }
+ if (last)
+ cur = last;
+ }
+ if (!Tab) { /* New name. Create a new node */
+ Tab = typecalloc(HTTabID);
+ if (Tab == NULL)
+ outofmem(__FILE__, "HText_setTabID");
+ HTList_addObject(cur, Tab);
+ StrAllocCopy(Tab->name, name);
+ }
+
+ Tab->column = HText_getCurrentColumn(text);
+ return;
+}
+
+int HText_getTabIDColumn(HText *text, const char *name)
+{
+ int column = 0;
+ HTTabID *Tab;
+ HTList *cur = text->tabs;
+
+ if (text && non_empty(name) && cur) {
+ while (NULL != (Tab = (HTTabID *) HTList_nextObject(cur))) {
+ if (Tab->name && !strcmp(Tab->name, name))
+ break;
+ }
+ if (Tab)
+ column = Tab->column;
+ }
+ return column;
+}
+
+/*
+ * This function is for saving the address of a link
+ * which had an attribute in the markup that resolved
+ * to a URL (i.e., not just a NAME or ID attribute),
+ * but was found in HText_endAnchor() to have no visible
+ * content for use as a link name. It loads the address
+ * into text->hidden_links, whose count can be determined
+ * via HText_HiddenLinks(), below. The addresses can be
+ * retrieved via HText_HiddenLinkAt(), below, based on
+ * count. -FM
+ */
+static void HText_AddHiddenLink(HText *text, TextAnchor *textanchor)
+{
+ HTAnchor *dest;
+
+ /*
+ * Make sure we have an HText structure and anchor. -FM
+ */
+ if (!(text && textanchor && textanchor->anchor))
+ return;
+
+ /*
+ * Create the hidden links list
+ * if it hasn't been already. -FM
+ */
+ if (text->hidden_links == NULL)
+ text->hidden_links = HTList_new();
+
+ /*
+ * Store the address, in reverse list order
+ * so that first in will be first out on
+ * retrievals. -FM
+ */
+ if ((dest = HTAnchor_followLink(textanchor->anchor)) &&
+ (text->hiddenlinkflag != HIDDENLINKS_IGNORE ||
+ HTList_isEmpty(text->hidden_links))) {
+ char *value = HTAnchor_address(dest);
+ BOOL ignore = FALSE;
+
+ if (unique_urls) {
+ int cnt;
+ char *check;
+
+ for (cnt = 0;; ++cnt) {
+
+ check = (char *) HTList_objectAt(text->hidden_links, cnt);
+ if (check == 0)
+ break;
+ if (!strcmp(check, value)) {
+ ignore = TRUE;
+ break;
+ }
+ }
+ }
+ if (ignore) {
+ FREE(value);
+ } else {
+ HTList_appendObject(text->hidden_links, value);
+ }
+ }
+
+ return;
+}
+
+/*
+ * This function returns the number of addresses
+ * that are loaded in text->hidden_links. -FM
+ */
+int HText_HiddenLinkCount(HText *text)
+{
+ int count = 0;
+
+ if (text && text->hidden_links)
+ count = HTList_count((HTList *) text->hidden_links);
+
+ return (count);
+}
+
+/*
+ * This function returns the address, corresponding to
+ * a hidden link, at the position (zero-based) in the
+ * text->hidden_links list of the number argument. -FM
+ */
+const char *HText_HiddenLinkAt(HText *text, int number)
+{
+ char *href = NULL;
+
+ if (text && text->hidden_links && number >= 0)
+ href = (char *) HTList_objectAt((HTList *) text->hidden_links, number);
+
+ return (href);
+}
+
+/*
+ * Form methods
+ * These routines are used to build forms consisting
+ * of input fields
+ */
+static BOOLEAN HTFormDisabled = FALSE;
+static PerFormInfo *HTCurrentForm;
+
+static BOOLEAN addFormAction(FormInfo * f)
+{
+ BOOLEAN result = FALSE;
+
+ if (HTCurrentForm != NULL) {
+ result = TRUE;
+ f->submit_action = NULL;
+ StrAllocCopy(f->submit_action, HTCurrentForm->data.submit_action);
+ if (HTCurrentForm->data.submit_enctype != NULL)
+ StrAllocCopy(f->submit_enctype, HTCurrentForm->data.submit_enctype);
+ if (HTCurrentForm->data.submit_title != NULL)
+ StrAllocCopy(f->submit_title, HTCurrentForm->data.submit_title);
+ f->submit_method = HTCurrentForm->data.submit_method;
+ }
+ return result;
+}
+
+void HText_beginForm(char *action,
+ char *method,
+ char *enctype,
+ char *title,
+ const char *accept_cs)
+{
+ PerFormInfo *newform;
+ int HTFormMethod = URL_GET_METHOD;
+ char *HTFormAction = NULL;
+ char *HTFormEnctype = NULL;
+ char *HTFormTitle = NULL;
+ char *HTFormAcceptCharset = NULL;
+
+ HTFormNumber++;
+
+ HTFormFields = 0;
+ HTFormDisabled = FALSE;
+
+ /*
+ * Check the ACTION. -FM
+ */
+ if (action != NULL) {
+ if (isMAILTO_URL(action)) {
+ HTFormMethod = URL_MAIL_METHOD;
+ }
+ StrAllocCopy(HTFormAction, action);
+ } else
+ StrAllocCopy(HTFormAction, HTLoadedDocumentURL());
+
+ /*
+ * Check the METHOD. -FM
+ */
+ if (method != NULL && HTFormMethod != URL_MAIL_METHOD)
+ if (!strcasecomp(method, "post") || !strcasecomp(method, "pget"))
+ HTFormMethod = URL_POST_METHOD;
+
+ /*
+ * Check the ENCTYPE. -FM
+ */
+ if (non_empty(enctype)) {
+ StrAllocCopy(HTFormEnctype, enctype);
+ if (HTFormMethod != URL_MAIL_METHOD &&
+ !strncasecomp(enctype, "multipart/form-data", 19))
+ HTFormMethod = URL_POST_METHOD;
+ } else {
+ FREE(HTFormEnctype);
+ }
+
+ /*
+ * Check the TITLE. -FM
+ */
+ if (non_empty(title))
+ StrAllocCopy(HTFormTitle, title);
+ else
+ FREE(HTFormTitle);
+
+ /*
+ * Check for an ACCEPT_CHARSET. If present, store it and
+ * convert to lowercase and collapse spaces. - kw
+ */
+ if (accept_cs != NULL) {
+ StrAllocCopy(HTFormAcceptCharset, accept_cs);
+ LYRemoveBlanks(HTFormAcceptCharset);
+ LYLowerCase(HTFormAcceptCharset);
+ }
+
+ /*
+ * Create a new "PerFormInfo" structure to hold info on the current form.
+ * This will be appended to the forms list kept by the HText object if and
+ * when we reach a HText_endForm.
+ */
+ newform = typecalloc(PerFormInfo);
+ if (newform == NULL)
+ outofmem(__FILE__, "HText_beginForm");
+
+ PerFormInfo_free(HTCurrentForm); /* shouldn't happen here - kw */
+ HTCurrentForm = newform;
+
+ newform->number = HTFormNumber;
+ newform->data.submit_action = HTFormAction;
+ newform->data.submit_enctype = HTFormEnctype;
+ newform->data.submit_method = HTFormMethod;
+ newform->data.submit_title = HTFormTitle;
+ newform->accept_cs = HTFormAcceptCharset;
+
+ CTRACE((tfp, "BeginForm: action:%s Method:%d%s%s%s%s%s%s\n",
+ HTFormAction, HTFormMethod,
+ (HTFormTitle ? " Title:" : ""),
+ NonNull(HTFormTitle),
+ (HTFormEnctype ? " Enctype:" : ""),
+ NonNull(HTFormEnctype),
+ (HTFormAcceptCharset ? " Accept-charset:" : ""),
+ NonNull(HTFormAcceptCharset)));
+}
+
+void HText_endForm(HText *text)
+{
+ if (text != NULL) {
+ if (HTFormFields == 1 && text->first_anchor) {
+ /*
+ * Support submission of a single text input field in
+ * the form via <return> instead of a submit button. -FM
+ */
+ TextAnchor *a;
+
+ /*
+ * Go through list of anchors and get our input field. -FM
+ */
+ for (a = text->first_anchor; a != NULL; a = a->next) {
+ if (a->link_type == INPUT_ANCHOR &&
+ a->input_field->number == HTFormNumber &&
+ a->input_field->type != F_TEXTAREA_TYPE &&
+ F_TEXTLIKE(a->input_field->type)) {
+ /*
+ * Got it. Make it submitting. -FM
+ */
+ if (addFormAction(a->input_field)) {
+ a->input_field->type = F_TEXT_SUBMIT_TYPE;
+ if (HTFormDisabled)
+ a->input_field->disabled = TRUE;
+ }
+ break;
+ }
+ }
+ }
+
+ /*
+ * Append info on the current form to the HText object's list of forms.
+ * HText_beginInput call will have set some of the data in the
+ * PerFormInfo structure (if there were any form fields at all).
+ */
+ if (HTCurrentForm) {
+ if (HTFormDisabled)
+ HTCurrentForm->disabled = TRUE;
+ if (!text->forms)
+ text->forms = HTList_new();
+ HTList_appendObject(text->forms, HTCurrentForm);
+ HTCurrentForm = NULL;
+ } else {
+ CTRACE((tfp, "endForm: HTCurrentForm is missing!\n"));
+ }
+ } else {
+ CTRACE((tfp, "endForm: HText is missing!\n"));
+ }
+
+ FREE(HTCurSelectGroup);
+ FREE(HTCurSelectGroupSize);
+ FREE(HTCurSelectedOptionValue);
+ HTFormFields = 0;
+ HTFormDisabled = FALSE;
+}
+
+void HText_beginSelect(char *name,
+ int name_cs,
+ int multiple,
+ char *size)
+{
+ /*
+ * Save the group name.
+ */
+ StrAllocCopy(HTCurSelectGroup, name);
+ HTCurSelectGroupCharset = name_cs;
+
+ /*
+ * If multiple then all options are actually checkboxes.
+ */
+ if (multiple)
+ HTCurSelectGroupType = F_CHECKBOX_TYPE;
+ /*
+ * If not multiple then all options are radio buttons.
+ */
+ else
+ HTCurSelectGroupType = F_RADIO_TYPE;
+
+ /*
+ * Length of an option list.
+ */
+ StrAllocCopy(HTCurSelectGroupSize, size);
+
+ CTRACE((tfp, "HText_beginSelect: name=%s type=%d size=%s\n",
+ ((HTCurSelectGroup == NULL) ?
+ "<NULL>" : HTCurSelectGroup),
+ HTCurSelectGroupType,
+ ((HTCurSelectGroupSize == NULL) ?
+ "<NULL>" : HTCurSelectGroupSize)));
+ CTRACE((tfp, "HText_beginSelect: name_cs=%d \"%s\"\n",
+ HTCurSelectGroupCharset,
+ (HTCurSelectGroupCharset >= 0 ?
+ LYCharSet_UC[HTCurSelectGroupCharset].MIMEname : "<UNKNOWN>")));
+}
+
+/*
+ * This function returns the number of the option whose
+ * value currently is being accumulated for a select
+ * block. - LE && FM
+ */
+int HText_getOptionNum(HText *text)
+{
+ TextAnchor *a;
+ OptionType *op;
+ int n = 1; /* start count at 1 */
+
+ if (!(text && text->last_anchor))
+ return (0);
+
+ a = text->last_anchor;
+ if (!(a->link_type == INPUT_ANCHOR && a->input_field &&
+ a->input_field->type == F_OPTION_LIST_TYPE))
+ return (0);
+
+ for (op = a->input_field->select_list; op; op = op->next)
+ n++;
+ CTRACE((tfp, "HText_getOptionNum: Got number '%d'.\n", n));
+ return (n);
+}
+
+/*
+ * This function checks for a numbered option pattern
+ * as the prefix for an option value. If present, and
+ * we are in the correct keypad mode, it returns a
+ * pointer to the actual value, following that prefix.
+ * Otherwise, it returns the original pointer.
+ */
+static char *HText_skipOptionNumPrefix(char *opname)
+{
+ /*
+ * Check if we are in the correct keypad mode.
+ */
+ if (fields_are_numbered()) {
+ /*
+ * Skip the option number embedded in the option name so the
+ * extra chars won't mess up cgi scripts processing the value.
+ * The format is (nnn)__ where nnn is a number and there is a
+ * minimum of 5 chars (no underscores if (nnn) exceeds 5 chars).
+ * See HTML.c. If the chars don't exactly match this format,
+ * just use all of opname. - LE
+ */
+ char *cp = opname;
+
+ if ((non_empty(cp) && *cp++ == '(') &&
+ *cp && isdigit(UCH(*cp++))) {
+ while (*cp && isdigit(UCH(*cp)))
+ ++cp;
+ if (*cp && *cp++ == ')') {
+ int i = (int) (cp - opname);
+
+ while (i < 5) {
+ if (*cp != '_')
+ break;
+ i++;
+ cp++;
+ }
+ if (i < 5) {
+ cp = opname;
+ }
+ } else {
+ cp = opname;
+ }
+ } else {
+ cp = opname;
+ }
+ return (cp);
+ }
+
+ return (opname);
+}
+
+/*
+ * We couldn't set the value field for the previous option tag so we have to do
+ * it now. Assume that the last anchor was the previous options' tag.
+ */
+char *HText_setLastOptionValue(HText *text, char *value,
+ char *submit_value,
+ int order,
+ int checked,
+ int val_cs,
+ int submit_val_cs)
+{
+ char *cp, *cp1;
+ char *ret_Value = NULL;
+ unsigned char *tmp = NULL;
+ int number = 0, i, j;
+
+ if (!(value
+ && text
+ && text->last_anchor
+ && text->last_anchor->input_field
+ && text->last_anchor->link_type == INPUT_ANCHOR)) {
+ CTRACE((tfp, "HText_setLastOptionValue: invalid call! value:%s!\n",
+ (value ? value : "<NULL>")));
+ return NULL;
+ }
+
+ CTRACE((tfp,
+ "Entering HText_setLastOptionValue: value:\"%s\", checked:%s\n",
+ value, (checked ? "on" : "off")));
+
+ /*
+ * Strip end spaces, newline is also whitespace.
+ */
+ if (*value) {
+ cp = &value[strlen(value) - 1];
+ while ((cp >= value) && (isspace(UCH(*cp)) ||
+ IsSpecialAttrChar(UCH(*cp))))
+ cp--;
+ *(cp + 1) = '\0';
+ }
+
+ /*
+ * Find first non space
+ */
+ cp = value;
+ while (isspace(UCH(*cp)) ||
+ IsSpecialAttrChar(UCH(*cp)))
+ cp++;
+ if (HTCurSelectGroupType == F_RADIO_TYPE &&
+ LYSelectPopups &&
+ fields_are_numbered()) {
+ /*
+ * Collapse any space between the popup option
+ * prefix and actual value. -FM
+ */
+ if ((cp1 = HText_skipOptionNumPrefix(cp)) > cp) {
+ i = 0, j = (int) (cp1 - cp);
+ while (isspace(UCH(cp1[i])) ||
+ IsSpecialAttrChar(UCH(cp1[i]))) {
+ i++;
+ }
+ if (i > 0) {
+ while (cp1[i] != '\0')
+ cp[j++] = cp1[i++];
+ cp[j] = '\0';
+ }
+ }
+ }
+
+ if (HTCurSelectGroupType == F_CHECKBOX_TYPE) {
+ StrAllocCopy(text->last_anchor->input_field->value, cp);
+ text->last_anchor->input_field->value_cs = val_cs;
+ /*
+ * Put the text on the screen as well.
+ */
+ HText_appendText(text, cp);
+
+ } else if (LYSelectPopups == FALSE) {
+ StrAllocCopy(text->last_anchor->input_field->value,
+ (submit_value ? submit_value : cp));
+ text->last_anchor->input_field->value_cs = (submit_value ?
+ submit_val_cs : val_cs);
+ /*
+ * Put the text on the screen as well.
+ */
+ HText_appendText(text, cp);
+
+ } else {
+ /*
+ * Create a linked list of option values.
+ */
+ OptionType *op_ptr = text->last_anchor->input_field->select_list;
+ OptionType *new_ptr = NULL;
+ BOOLEAN first_option = FALSE;
+
+ /*
+ * Deal with newlines or tabs.
+ */
+ LYReduceBlanks(value);
+
+ if (!op_ptr) {
+ /*
+ * No option items yet.
+ */
+ if (text->last_anchor->input_field->type != F_OPTION_LIST_TYPE) {
+ CTRACE((tfp,
+ "HText_setLastOptionValue: last input_field not F_OPTION_LIST_TYPE (%d)\n",
+ F_OPTION_LIST_TYPE));
+ CTRACE((tfp, " but %d, ignoring!\n",
+ text->last_anchor->input_field->type));
+ return NULL;
+ }
+
+ new_ptr = typecalloc(OptionType);
+ if (new_ptr == NULL)
+ outofmem(__FILE__, "HText_setLastOptionValue");
+
+ text->last_anchor->input_field->select_list = new_ptr;
+ first_option = TRUE;
+ } else {
+ while (op_ptr->next) {
+ number++;
+ op_ptr = op_ptr->next;
+ }
+ number++; /* add one more */
+
+ op_ptr->next = new_ptr = typecalloc(OptionType);
+ if (new_ptr == NULL)
+ outofmem(__FILE__, "HText_setLastOptionValue");
+ }
+
+ new_ptr->name = NULL;
+ new_ptr->cp_submit_value = NULL;
+ new_ptr->next = NULL;
+ /*
+ * Find first non-space again, convert_to_spaces above may have
+ * changed the string. - kw
+ */
+ cp = value;
+ while (isspace(UCH(*cp)) ||
+ IsSpecialAttrChar(UCH(*cp)))
+ cp++;
+ for (i = 0, j = 0; cp[i]; i++) {
+ if (cp[i] == HT_NON_BREAK_SPACE ||
+ cp[i] == HT_EN_SPACE) {
+ cp[j++] = ' ';
+ } else if (cp[i] != LY_SOFT_HYPHEN &&
+ !IsSpecialAttrChar(UCH(cp[i]))) {
+ cp[j++] = cp[i];
+ }
+ }
+ cp[j] = '\0';
+ if (IS_CJK_TTY) {
+ if ((tmp = typecallocn(unsigned char, strlen(cp) * 2 + 1)) != 0) {
+ if (kanji_code == EUC) {
+ TO_EUC((unsigned char *) cp, tmp);
+ val_cs = current_char_set;
+ } else if (kanji_code == SJIS) {
+ TO_SJIS((unsigned char *) cp, tmp);
+ val_cs = current_char_set;
+ } else {
+ for (i = 0, j = 0; cp[i]; i++) {
+ if (cp[i] != CH_ESC) { /* S/390 -- gil -- 1604 */
+ tmp[j++] = UCH(cp[i]);
+ }
+ }
+ }
+ StrAllocCopy(new_ptr->name, (const char *) tmp);
+ FREE(tmp);
+ } else {
+ outofmem(__FILE__, "HText_setLastOptionValue");
+ }
+ } else {
+ StrAllocCopy(new_ptr->name, cp);
+ }
+ StrAllocCopy(new_ptr->cp_submit_value,
+ (submit_value ? submit_value :
+ HText_skipOptionNumPrefix(new_ptr->name)));
+ new_ptr->value_cs = (submit_value ? submit_val_cs : val_cs);
+
+ if (first_option) {
+ FormInfo *last_input = text->last_anchor->input_field;
+
+ StrAllocCopy(HTCurSelectedOptionValue, new_ptr->name);
+ last_input->num_value = 0;
+ /*
+ * If this is the first option in a popup select list,
+ * HText_beginInput may have allocated the value and
+ * cp_submit_value fields, so free them now to avoid
+ * a memory leak. - kw
+ */
+ FREE(last_input->value);
+ FREE(last_input->cp_submit_value);
+
+ last_input->value = last_input->select_list->name;
+ last_input->orig_value = last_input->select_list->name;
+ last_input->cp_submit_value = last_input->select_list->cp_submit_value;
+ last_input->orig_submit_value = last_input->select_list->cp_submit_value;
+ last_input->value_cs = new_ptr->value_cs;
+ } else {
+ int newlen = (int) strlen(new_ptr->name);
+ int curlen = (int) (HTCurSelectedOptionValue
+ ? strlen(HTCurSelectedOptionValue)
+ : 0);
+
+ /*
+ * Make the selected Option Value as long as
+ * the longest option.
+ */
+ if (newlen > curlen)
+ StrAllocCat(HTCurSelectedOptionValue,
+ UNDERSCORES(newlen - curlen));
+ }
+
+ if (checked) {
+ int curlen = (int) strlen(new_ptr->name);
+ int newlen = (HTCurSelectedOptionValue
+ ? (int) strlen(HTCurSelectedOptionValue)
+ : 0);
+ FormInfo *last_input = text->last_anchor->input_field;
+
+ /*
+ * Set the default option as this one.
+ */
+ last_input->num_value = number;
+ last_input->value = new_ptr->name;
+ last_input->orig_value = new_ptr->name;
+ last_input->cp_submit_value = new_ptr->cp_submit_value;
+ last_input->orig_submit_value = new_ptr->cp_submit_value;
+ last_input->value_cs = new_ptr->value_cs;
+ StrAllocCopy(HTCurSelectedOptionValue, new_ptr->name);
+ if (newlen > curlen)
+ StrAllocCat(HTCurSelectedOptionValue,
+ UNDERSCORES(newlen - curlen));
+ }
+
+ /*
+ * Return the selected Option value to be sent to the screen.
+ */
+ if (order == LAST_ORDER) {
+ /*
+ * Change the value.
+ */
+ if (HTCurSelectedOptionValue == 0)
+ StrAllocCopy(HTCurSelectedOptionValue, "");
+ text->last_anchor->input_field->size =
+ (int) strlen(HTCurSelectedOptionValue);
+ ret_Value = HTCurSelectedOptionValue;
+ }
+ }
+
+ if (TRACE) {
+ CTRACE((tfp, "HText_setLastOptionValue:%s value=\"%s\"\n",
+ (order == LAST_ORDER) ? " LAST_ORDER" : "",
+ value));
+ CTRACE((tfp, " val_cs=%d \"%s\"",
+ val_cs,
+ (val_cs >= 0 ?
+ LYCharSet_UC[val_cs].MIMEname : "<UNKNOWN>")));
+ if (submit_value) {
+ CTRACE((tfp, " (submit_val_cs %d \"%s\") submit_value%s=\"%s\"\n",
+ submit_val_cs,
+ (submit_val_cs >= 0 ?
+ LYCharSet_UC[submit_val_cs].MIMEname : "<UNKNOWN>"),
+ (HTCurSelectGroupType == F_CHECKBOX_TYPE) ?
+ "(ignored)" : "",
+ submit_value));
+ } else {
+ CTRACE((tfp, "\n"));
+ }
+ }
+ return (ret_Value);
+}
+
+/*
+ * Assign a form input anchor.
+ * Returns the number of characters to leave
+ * blank so that the input field can fit.
+ */
+int HText_beginInput(HText *text,
+ int underline,
+ InputFieldData * I)
+{
+ TextAnchor *a;
+ FormInfo *f;
+ const char *cp_option = NULL;
+ char *IValue = NULL;
+ unsigned char *tmp = NULL;
+ int i, j;
+ int adjust_marker = 0;
+ int MaximumSize;
+ char marker[16];
+
+ CTRACE((tfp, "GridText: Entering HText_beginInput type=%s\n", NonNull(I->type)));
+
+ POOLtypecalloc(TextAnchor, a);
+
+ POOLtypecalloc(FormInfo, f);
+ if (a == NULL || f == NULL)
+ outofmem(__FILE__, "HText_beginInput");
+
+ a->sgml_offset = SGML_offset();
+ a->inUnderline = (BOOLEAN) underline;
+ a->line_num = text->Lines;
+ a->line_pos = (short) text->last_line->size;
+
+ /*
+ * If this is a radio button, or an OPTION we're converting
+ * to a radio button, and it's the first with this name, make
+ * sure it's checked by default. Otherwise, if it's checked,
+ * uncheck the default or any preceding radio button with this
+ * name that was checked. -FM
+ */
+ if (I->type != NULL && !strcmp(I->type, "OPTION") &&
+ HTCurSelectGroupType == F_RADIO_TYPE && LYSelectPopups == FALSE) {
+ I->type = "RADIO";
+ I->name = HTCurSelectGroup;
+ I->name_cs = HTCurSelectGroupCharset;
+ }
+ if (I->name && I->type && !strcasecomp(I->type, "radio")) {
+ if (!text->last_anchor) {
+ I->checked = TRUE;
+ } else {
+ TextAnchor *b;
+ int i2 = 0;
+
+ for (b = text->first_anchor; b != NULL; b = b->next) {
+ if (b->link_type == INPUT_ANCHOR &&
+ b->input_field->type == F_RADIO_TYPE &&
+ b->input_field->number == HTFormNumber) {
+ if (!strcmp(b->input_field->name, I->name)) {
+ if (I->checked && b->input_field->num_value) {
+ b->input_field->num_value = 0;
+ StrAllocCopy(b->input_field->orig_value, "0");
+ break;
+ }
+ i2++;
+ }
+ }
+ }
+ if (i2 == 0)
+ I->checked = TRUE;
+ }
+ }
+
+ a->next = 0;
+ a->anchor = NULL;
+ a->link_type = INPUT_ANCHOR;
+ a->show_anchor = YES;
+
+ LYClearHiText(a);
+ a->extent = 2;
+
+ a->input_field = f;
+
+ f->select_list = 0;
+ f->number = HTFormNumber;
+ f->disabled = HTFormDisabled || I->disabled;
+ f->readonly = I->readonly;
+ f->no_cache = NO;
+
+ HTFormFields++;
+
+ /*
+ * Set up VALUE.
+ */
+ if (I->value) {
+ StrAllocCopy(IValue, I->value);
+ }
+ if (IValue &&
+ IS_CJK_TTY &&
+ ((I->type == NULL) || strcasecomp(I->type, "hidden"))) {
+ if ((tmp = typecallocn(unsigned char, strlen(IValue) * 2 + 1)) != 0) {
+ if (kanji_code == EUC) {
+ TO_EUC((unsigned char *) IValue, tmp);
+ I->value_cs = current_char_set;
+ } else if (kanji_code == SJIS) {
+ TO_SJIS((unsigned char *) IValue, tmp);
+ I->value_cs = current_char_set;
+ } else {
+ for (i = 0, j = 0; IValue[i]; i++) {
+ if (IValue[i] != CH_ESC) { /* S/390 -- gil -- 1621 */
+ tmp[j++] = UCH(IValue[i]);
+ }
+ }
+ }
+ StrAllocCopy(IValue, (const char *) tmp);
+ FREE(tmp);
+ }
+ }
+
+ /*
+ * Special case of OPTION.
+ * Is handled above if radio type and LYSelectPopups is FALSE.
+ */
+ /* set the values and let the parsing below do the work */
+ if (I->type != NULL && !strcmp(I->type, "OPTION")) {
+ cp_option = I->type;
+ if (HTCurSelectGroupType == F_RADIO_TYPE)
+ I->type = "OPTION_LIST";
+ else
+ I->type = "CHECKBOX";
+ I->name = HTCurSelectGroup;
+ I->name_cs = HTCurSelectGroupCharset;
+
+ /*
+ * The option's size parameter actually gives the length and not
+ * the width of the list. Perform the conversion here
+ * and get rid of the allocated HTCurSelect....
+ * 0 is ok as it means any length (arbitrary decision).
+ */
+ if (HTCurSelectGroupSize != NULL) {
+ f->size_l = atoi(HTCurSelectGroupSize);
+ FREE(HTCurSelectGroupSize);
+ }
+ }
+
+ /*
+ * Set SIZE.
+ */
+ if (I->size != 0) {
+ f->size = I->size;
+ /*
+ * Leave at zero for option lists.
+ */
+ if (f->size == 0 && cp_option == NULL) {
+ f->size = 20; /* default */
+ }
+ } else {
+ f->size = 20; /* default */
+ }
+
+ /*
+ * Set MAXLENGTH.
+ */
+ if (I->maxlength != NULL) {
+ f->maxlength = (unsigned) atoi(I->maxlength);
+ } else {
+ f->maxlength = 0; /* 0 means infinite */
+ }
+
+ /*
+ * Set CHECKED
+ * (num_value is only relevant to check and radio types).
+ */
+ if (I->checked == TRUE)
+ f->num_value = 1;
+ else
+ f->num_value = 0;
+
+ /*
+ * Set TYPE.
+ */
+ if (I->type != NULL) {
+ if (!strcasecomp(I->type, "password")) {
+ f->type = F_PASSWORD_TYPE;
+ } else if (!strcasecomp(I->type, "checkbox")) {
+ f->type = F_CHECKBOX_TYPE;
+ } else if (!strcasecomp(I->type, "radio")) {
+ f->type = F_RADIO_TYPE;
+ } else if (!strcasecomp(I->type, "submit")) {
+ f->type = F_SUBMIT_TYPE;
+ } else if (!strcasecomp(I->type, "image")) {
+ f->type = F_IMAGE_SUBMIT_TYPE;
+ } else if (!strcasecomp(I->type, "reset")) {
+ f->type = F_RESET_TYPE;
+ } else if (!strcasecomp(I->type, "OPTION_LIST")) {
+ f->type = F_OPTION_LIST_TYPE;
+ } else if (!strcasecomp(I->type, "hidden")) {
+ f->type = F_HIDDEN_TYPE;
+ HTFormFields--;
+ f->size = 0;
+ } else if (!strcasecomp(I->type, "textarea")) {
+ f->type = F_TEXTAREA_TYPE;
+ } else if (!strcasecomp(I->type, "range")) {
+ f->type = F_RANGE_TYPE;
+ } else if (!strcasecomp(I->type, "file")) {
+ f->type = F_FILE_TYPE;
+ CTRACE((tfp, "ok, got a file uploader\n"));
+ } else if (!strcasecomp(I->type, "keygen")) {
+ f->type = F_KEYGEN_TYPE;
+ } else if (!strcasecomp(I->type, "button")) {
+ f->type = F_BUTTON_TYPE;
+ } else {
+ /*
+ * Note that TYPE="scribble" defaults to TYPE="text". -FM
+ */
+ f->type = F_TEXT_TYPE; /* default */
+ }
+ } else {
+ f->type = F_TEXT_TYPE;
+ }
+
+ /*
+ * Set NAME.
+ */
+ if (I->name != NULL) {
+ StrAllocCopy(f->name, I->name);
+ f->name_cs = I->name_cs;
+ } else {
+ if (f->type == F_RESET_TYPE ||
+ f->type == F_SUBMIT_TYPE ||
+ f->type == F_IMAGE_SUBMIT_TYPE) {
+ /*
+ * Set name to empty string.
+ */
+ StrAllocCopy(f->name, "");
+ } else {
+ /*
+ * Error! NAME must be present.
+ */
+ CTRACE((tfp,
+ "GridText: No name present in input field; not displaying\n"));
+ FREE(IValue);
+ return (0);
+ }
+ }
+
+ /*
+ * Add this anchor to the anchor list
+ */
+ if (text->last_anchor) {
+ text->last_anchor->next = a;
+ } else {
+ text->first_anchor = a;
+ }
+
+ /*
+ * Set VALUE, if it exists. Otherwise, if it's not
+ * an option list make it a zero-length string. -FM
+ */
+ if (IValue != NULL) {
+ /*
+ * OPTION VALUE is not actually the value to be seen but is to
+ * be sent....
+ */
+ if (f->type == F_OPTION_LIST_TYPE ||
+ f->type == F_CHECKBOX_TYPE) {
+ /*
+ * Fill both with the value. The f->value may be
+ * overwritten in HText_setLastOptionValue....
+ */
+ StrAllocCopy(f->value, IValue);
+ StrAllocCopy(f->cp_submit_value, IValue);
+ } else {
+ StrAllocCopy(f->value, IValue);
+ }
+ f->value_cs = I->value_cs;
+ } else if (f->type != F_OPTION_LIST_TYPE) {
+ StrAllocCopy(f->value, "");
+ /*
+ * May be an empty INPUT field. The text entered will then
+ * probably be in the current display character set. - kw
+ */
+ f->value_cs = current_char_set;
+ }
+
+ /*
+ * Run checks and fill in necessary values.
+ */
+ if (f->type == F_RESET_TYPE) {
+ if (non_empty(f->value)) {
+ f->size = (int) strlen(f->value);
+ } else {
+ StrAllocCopy(f->value, "Reset");
+ f->size = 5;
+ }
+ } else if (f->type == F_BUTTON_TYPE) {
+ if (non_empty(f->value)) {
+ f->size = (int) strlen(f->value);
+ } else {
+ StrAllocCopy(f->value, "BUTTON");
+ f->size = 5;
+ }
+ } else if (f->type == F_IMAGE_SUBMIT_TYPE ||
+ f->type == F_SUBMIT_TYPE) {
+ if (non_empty(f->value)) {
+ f->size = (int) strlen(f->value);
+ } else if (f->type == F_IMAGE_SUBMIT_TYPE) {
+ StrAllocCopy(f->value, "[IMAGE]-Submit");
+ f->size = 14;
+ } else {
+ StrAllocCopy(f->value, "Submit");
+ f->size = 6;
+ }
+ addFormAction(f);
+ } else if (f->type == F_RADIO_TYPE || f->type == F_CHECKBOX_TYPE) {
+ f->size = 3;
+ if (IValue == NULL)
+ StrAllocCopy(f->value, (f->type == F_CHECKBOX_TYPE ? "on" : ""));
+
+ }
+ FREE(IValue);
+
+ /*
+ * Set original values.
+ */
+ if (f->type == F_RADIO_TYPE || f->type == F_CHECKBOX_TYPE) {
+ if (f->num_value)
+ StrAllocCopy(f->orig_value, "1");
+ else
+ StrAllocCopy(f->orig_value, "0");
+ } else if (f->type == F_OPTION_LIST_TYPE) {
+ f->orig_value = NULL;
+ } else {
+ StrAllocCopy(f->orig_value, f->value);
+ }
+
+ /*
+ * Store accept-charset if present, converting to lowercase
+ * and collapsing spaces. - kw
+ */
+ if (I->accept_cs) {
+ StrAllocCopy(f->accept_cs, I->accept_cs);
+ LYRemoveBlanks(f->accept_cs);
+ LYLowerCase(f->accept_cs);
+ }
+
+ /*
+ * Add numbers to form fields if needed. - LE & FM
+ */
+ switch (f->type) {
+ /*
+ * Do not supply number for hidden fields, nor
+ * for types that are not yet implemented.
+ */
+ case F_HIDDEN_TYPE:
+#ifndef USE_FILE_UPLOAD
+ case F_FILE_TYPE:
+#endif
+ case F_RANGE_TYPE:
+ case F_KEYGEN_TYPE:
+ case F_BUTTON_TYPE:
+ a->number = 0;
+ break;
+
+ default:
+ if (fields_are_numbered())
+ a->number = ++(text->last_anchor_number);
+ else
+ a->number = 0;
+ break;
+ }
+ if (fields_are_numbered() && (a->number > 0)) {
+ if (HTMainText != 0) {
+ HText_findAnchorNumber(a);
+ } else {
+ a->show_number = a->number;
+ }
+ sprintf(marker, "[%d]", a->show_number);
+ adjust_marker = (int) strlen(marker);
+ if (number_fields_on_left) {
+ BOOL had_bracket = (BOOL) (f->type == F_OPTION_LIST_TYPE);
+
+ HText_appendText(text, had_bracket ? (marker + 1) : marker);
+ if (had_bracket)
+ HText_appendCharacter(text, '[');
+ }
+ a->line_num = text->Lines;
+ a->line_pos = (short) text->last_line->size;
+ } else {
+ *marker = '\0';
+ }
+
+ /*
+ * Restrict SIZE to maximum allowable size.
+ */
+ MaximumSize = WRAP_COLS(text) + 1 - adjust_marker;
+ switch (f->type) {
+
+ case F_SUBMIT_TYPE:
+ case F_IMAGE_SUBMIT_TYPE:
+ case F_RESET_TYPE:
+ case F_TEXT_TYPE:
+ case F_TEXTAREA_TYPE:
+ /*
+ * For submit and reset buttons, and for text entry
+ * fields and areas, we limit the size element to that
+ * of one line for the current style because that's
+ * the most we could highlight on overwrites, and/or
+ * handle in the line editor. The actual values for
+ * text entry lines can be long, and will be scrolled
+ * horizontally within the editing window. -FM
+ */
+ MaximumSize -= (1 +
+ (int) text->style->leftIndent +
+ (int) text->style->rightIndent);
+
+ /* If we are numbering form links, place is taken by [nn] */
+ if (fields_are_numbered()) {
+ if (!number_fields_on_left
+ && f->type == F_TEXT_TYPE
+ && MaximumSize > a->line_pos + 10)
+ MaximumSize -= a->line_pos;
+ else
+ MaximumSize -= (int) strlen(marker);
+ }
+
+ /*
+ * Save value for submit/reset buttons so they
+ * will be visible when printing the page. - LE
+ */
+ if (f->type == F_SUBMIT_TYPE)
+ FREE(I->value);
+ I->value = f->value;
+ break;
+
+ default:
+ /*
+ * For all other fields we limit the size element to
+ * 10 less than the screen width, because either they
+ * are types with small placeholders, and/or are a
+ * type which is handled via a popup window. -FM
+ */
+ MaximumSize -= 10;
+ break;
+ }
+
+ if (MaximumSize < 1)
+ MaximumSize = 1;
+
+ if (f->size > MaximumSize)
+ f->size = MaximumSize;
+
+ /*
+ * Add this anchor to the anchor list
+ */
+ text->last_anchor = a;
+
+ if (HTCurrentForm) { /* should always apply! - kw */
+ if (!HTCurrentForm->first_field) {
+ HTCurrentForm->first_field = f;
+ }
+ HTCurrentForm->last_field = f;
+ HTCurrentForm->nfields++; /* will count hidden fields - kw */
+ /*
+ * Set the no_cache flag if the METHOD is POST. -FM
+ */
+ if (HTCurrentForm->data.submit_method == URL_POST_METHOD)
+ f->no_cache = TRUE;
+ /*
+ * Propagate form field's accept-charset attribute to enclosing
+ * form if the form itself didn't have an accept-charset - kw
+ */
+ if (f->accept_cs && !HTCurrentForm->accept_cs) {
+ StrAllocCopy(HTCurrentForm->accept_cs, f->accept_cs);
+ }
+ if (!text->forms) {
+ text->forms = HTList_new();
+ }
+ } else {
+ CTRACE((tfp, "beginInput: HTCurrentForm is missing!\n"));
+ }
+
+ CTRACE((tfp, "Input link: name=%s\nvalue=%s\nsize=%d\n",
+ f->name,
+ NonNull(f->value),
+ f->size));
+ CTRACE((tfp, "Input link: name_cs=%d \"%s\" (from %d \"%s\")\n",
+ f->name_cs,
+ (f->name_cs >= 0 ?
+ LYCharSet_UC[f->name_cs].MIMEname : "<UNKNOWN>"),
+ I->name_cs,
+ (I->name_cs >= 0 ?
+ LYCharSet_UC[I->name_cs].MIMEname : "<UNKNOWN>")));
+ CTRACE((tfp, " value_cs=%d \"%s\" (from %d \"%s\")\n",
+ f->value_cs,
+ (f->value_cs >= 0 ?
+ LYCharSet_UC[f->value_cs].MIMEname : "<UNKNOWN>"),
+ I->value_cs,
+ (I->value_cs >= 0 ?
+ LYCharSet_UC[I->value_cs].MIMEname : "<UNKNOWN>")));
+
+ /*
+ * Return the SIZE of the input field.
+ */
+ if (I->size && f->size > adjust_marker) {
+ f->size -= adjust_marker;
+ }
+ return (f->size);
+}
+
+/*
+ * If we're numbering fields on the right, do it. Note that some fields may
+ * be too long for the line - we'll lose the marker in that case rather than
+ * truncate the field.
+ */
+void HText_endInput(HText *text)
+{
+ if (fields_are_numbered()
+ && !number_fields_on_left
+ && text != NULL
+ && text->last_anchor != NULL
+ && text->last_anchor->number > 0) {
+ char marker[20];
+
+ sprintf(marker, "[%d]", text->last_anchor->show_number);
+ HText_appendText(text, marker);
+ }
+}
+
+/*
+ * Get a translation (properly: transcoding) quality, factoring in
+ * our ability to translate (an UCTQ_t) and a possible q parameter
+ * on the given charset string, for cs_from -> givenmime.
+ * The parsed input string will be mutilated on exit(!).
+ * Note that results are not normalised to 1.0, but results from
+ * different calls of this function can be compared. - kw
+ *
+ * Obsolete, it was planned to use here a quality parameter UCTQ_t,
+ * which is boolean now.
+ */
+static double get_trans_q(int cs_from,
+ char *givenmime)
+{
+ double df = 1.0;
+ BOOL tq;
+ char *p;
+
+ if (!givenmime || !(*givenmime))
+ return 0.0;
+ if ((p = StrChr(givenmime, ';')) != NULL) {
+ *p++ = '\0';
+ }
+ if (!strcmp(givenmime, "*"))
+ tq = UCCanTranslateFromTo(cs_from,
+ UCGetLYhndl_byMIME("utf-8"));
+ else
+ tq = UCCanTranslateFromTo(cs_from,
+ UCGetLYhndl_byMIME(givenmime));
+ if (!tq)
+ return 0.0;
+ if (non_empty(p)) {
+ char *pair, *field = p, *pval, *ptok;
+
+ /* Get all the parameters to the Charset */
+ while ((pair = HTNextTok(&field, ";", "\"", NULL)) != NULL) {
+ if ((ptok = HTNextTok(&pair, "= ", NULL, NULL)) != NULL &&
+ (pval = HTNextField(&pair)) != NULL) {
+ if (0 == strcasecomp(ptok, "q")) {
+ df = strtod(pval, NULL);
+ break;
+ }
+ }
+ }
+ return (df * tq);
+ } else {
+ return tq;
+ }
+}
+
+/*
+ * Find the best charset for submission, if we have an ACCEPT_CHARSET
+ * list. It factors in how well we can translate (just as guess, and
+ * not a very good one..) and possible ";q=" factors. Yes this is
+ * more general than it needs to be here.
+ *
+ * Input is cs_in and acceptstring.
+ *
+ * Will return charset handle as int.
+ * best_csname will point to a newly allocated MIME string for the
+ * charset corresponding to the return value if return value >= 0.
+ * - kw
+ */
+static int find_best_target_cs(char **best_csname,
+ int cs_from,
+ const char *acceptstring)
+{
+ char *paccept = NULL;
+ double bestq = -1.0;
+ char *bestmime = NULL;
+ char *field, *nextfield;
+
+ StrAllocCopy(paccept, acceptstring);
+ nextfield = paccept;
+ while ((field = HTNextTok(&nextfield, ",", "\"", NULL)) != NULL) {
+ double q;
+
+ if (*field != '\0') {
+ /* Get the Charset */
+ q = get_trans_q(cs_from, field);
+ if (q > bestq) {
+ bestq = q;
+ bestmime = field;
+ }
+ }
+ }
+ if (bestmime) {
+ if (!strcmp(bestmime, "*")) /* non-standard for HTML attribute.. */
+ StrAllocCopy(*best_csname, "utf-8");
+ else
+ StrAllocCopy(*best_csname, bestmime);
+ FREE(paccept);
+ if (bestq > 0)
+ return (UCGetLYhndl_byMIME(*best_csname));
+ else
+ return (-1);
+ }
+ FREE(paccept);
+ return (-1);
+}
+
+#ifdef USE_FILE_UPLOAD
+static void load_a_file(const char *val_used,
+ bstring **result)
+{
+ FILE *fd;
+ size_t bytes;
+ char bfr[BUFSIZ + 1];
+
+ CTRACE((tfp, "Ok, about to convert \"%s\" to mime/thingy\n", val_used));
+
+ if (*val_used) { /* ignore empty form field */
+ if ((fd = fopen(val_used, BIN_R)) == 0) {
+ HTAlert(gettext("Can't open file for uploading"));
+ } else {
+ while ((bytes = fread(bfr, sizeof(char), sizeof(bfr) - 1, fd)) != 0) {
+ HTSABCat(result, bfr, (int) bytes);
+ }
+ LYCloseInput(fd);
+ }
+ }
+}
+
+static const char *guess_content_type(const char *filename)
+{
+ HTAtom *encoding;
+ const char *desc;
+ HTFormat format = HTFileFormat(filename, &encoding, &desc);
+
+ return (format != 0 && non_empty(format->name))
+ ? format->name
+ : STR_PLAINTEXT;
+}
+#endif /* USE_FILE_UPLOAD */
+
+static void cannot_transcode(BOOL *had_warning,
+ const char *target_csname)
+{
+ if (*had_warning == NO) {
+ *had_warning = YES;
+ _user_message(CANNOT_TRANSCODE_FORM,
+ target_csname ? target_csname : "UNKNOWN");
+ LYSleepAlert();
+ }
+}
+
+#define SPECIAL_8BIT 1
+#define SPECIAL_FORM 2
+
+static unsigned check_form_specialchars(const char *value)
+{
+ unsigned result = 0;
+ const char *p;
+
+ for (p = value;
+ non_empty(p) && (result != (SPECIAL_8BIT | SPECIAL_FORM));
+ p++) {
+ if ((*p == HT_NON_BREAK_SPACE) ||
+ (*p == HT_EN_SPACE) ||
+ (*p == LY_SOFT_HYPHEN)) {
+ result |= SPECIAL_FORM;
+ } else if ((*p & 0x80) != 0) {
+ result |= SPECIAL_8BIT;
+ }
+ }
+ return result;
+}
+
+/*
+ * Scan the given data, adding characters to the MIME-boundary to keep it from
+ * matching any part of the data.
+ */
+static void UpdateBoundary(char **Boundary,
+ bstring *data)
+{
+ size_t j;
+ size_t have = strlen(*Boundary);
+ size_t last = (size_t) BStrLen(data);
+ char *text = BStrData(data);
+ char *want = *Boundary;
+
+ for (j = 0; (long) j <= (long) (last - have); ++j) {
+ if (want[0] == text[j]
+ && !memcmp(want, text + j, have)) {
+ char temp[2];
+
+ temp[0] = (char) (isdigit(UCH(text[have + j])) ? 'a' : '0');
+ temp[1] = '\0';
+ StrAllocCat(want, temp);
+ ++have;
+ }
+ }
+ *Boundary = want;
+}
+
+/*
+ * Convert a string to base64
+ */
+static char *convert_to_base64(const char *src,
+ size_t len)
+{
+#define B64_LINE 76
+
+ static const char basis_64[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ char *dest;
+ size_t rlen; /* length of result string */
+ unsigned char c1, c2, c3;
+ const char *eol;
+ char *r;
+ const char *str;
+ size_t eollen;
+ int chunk;
+
+ str = src;
+ eol = "\n";
+ eollen = 1;
+
+ /* calculate the length of the result */
+ rlen = (len + 2) / 3 * 4; /* encoded bytes */
+ if (rlen) {
+ /* add space for EOL */
+ rlen += ((rlen - 1) / B64_LINE + 1) * eollen;
+ }
+
+ /* allocate a result buffer */
+ if ((dest = (char *) malloc(rlen + 1)) == NULL) {
+ outofmem(__FILE__, "convert_to_base64");
+ }
+ r = dest;
+
+ /* encode */
+ for (chunk = 0; len > 0; len -= 3, chunk++) {
+ if (chunk == (B64_LINE / 4)) {
+ const char *c = eol;
+ const char *e = eol + eollen;
+
+ while (c < e)
+ *r++ = *c++;
+ chunk = 0;
+ }
+ c1 = UCH(*str++);
+ c2 = UCH(*str++);
+ *r++ = basis_64[c1 >> 2];
+ *r++ = basis_64[((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)];
+ if (len > 2) {
+ c3 = UCH(*str++);
+ *r++ = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)];
+ *r++ = basis_64[c3 & 0x3F];
+ } else if (len == 2) {
+ *r++ = basis_64[(c2 & 0xF) << 2];
+ *r++ = '=';
+ } else { /* len == 1 */
+ *r++ = '=';
+ *r++ = '=';
+ }
+ }
+ if (rlen) {
+ /* append eol to the result string */
+ const char *c = eol;
+ const char *e = eol + eollen;
+
+ while (c < e)
+ *r++ = *c++;
+ }
+ *r = '\0';
+
+ return dest;
+}
+
+typedef enum {
+ NO_QUOTE /* no quoting needed */
+ ,QUOTE_MULTI /* multipart */
+ ,QUOTE_BASE64 /* encode as base64 */
+ ,QUOTE_SPECIAL /* escape special characters only */
+} QuoteData;
+
+typedef struct {
+ int type; /* the type of this field */
+ BOOL first; /* true if this begins a submission part */
+ char *name; /* the name of this field */
+ char *value; /* the nominal value of this field */
+ bstring *data; /* its data, which is usually the same as the value */
+ QuoteData quote; /* how to quote/translate the data */
+} PostData;
+
+static char *escape_or_quote_name(const char *name,
+ QuoteData quoting,
+ const char *MultipartContentType)
+{
+ char *escaped1 = NULL;
+
+ switch (quoting) {
+ case NO_QUOTE:
+ StrAllocCopy(escaped1, name);
+ break;
+ case QUOTE_MULTI:
+ case QUOTE_BASE64:
+ StrAllocCopy(escaped1, "Content-Disposition: form-data");
+ HTSprintf(&escaped1, "; name=\"%s\"", name);
+ if (MultipartContentType)
+ HTSprintf(&escaped1, MultipartContentType, STR_PLAINTEXT);
+ if (quoting == QUOTE_BASE64)
+ StrAllocCat(escaped1, "\r\nContent-Transfer-Encoding: base64");
+ StrAllocCat(escaped1, "\r\n\r\n");
+ break;
+ case QUOTE_SPECIAL:
+ escaped1 = HTEscapeSP(name, URL_XALPHAS);
+ break;
+ }
+ return escaped1;
+}
+
+static char *escape_or_quote_value(const char *value,
+ QuoteData quoting)
+{
+ char *escaped2 = NULL;
+
+ switch (quoting) {
+ case NO_QUOTE:
+ case QUOTE_MULTI:
+ StrAllocCopy(escaped2, NonNull(value));
+ break;
+ case QUOTE_BASE64:
+ /* FIXME: this is redundant */
+ escaped2 = convert_to_base64(value, strlen(value));
+ break;
+ case QUOTE_SPECIAL:
+ escaped2 = HTEscapeSP(value, URL_XALPHAS);
+ break;
+ }
+ return escaped2;
+}
+
+/*
+ * Check if we should encode the data in base64. We can, only if we're using
+ * a multipart content type. We should, if we're sending mail and the data
+ * contains long lines or nonprinting characters.
+ */
+static int check_if_base64_needed(int submit_method,
+ bstring *data)
+{
+ int width = 0;
+ BOOL printable = TRUE;
+ char *text = BStrData(data);
+
+ if (text != 0) {
+ int col = 0;
+ int n;
+ int length = BStrLen(data);
+
+ for (n = 0; n < length; ++n) {
+ int ch = UCH(text[n]);
+
+ if (is8bits(ch) || ((ch < 32 && ch != '\n'))) {
+ CTRACE((tfp, "nonprintable %d:%#x\n", n, ch));
+ printable = FALSE;
+ }
+ if (ch == '\n' || ch == '\r') {
+ if (width < col)
+ width = col;
+ col = 0;
+ } else {
+ ++col;
+ }
+ }
+ if (width < col)
+ width = col;
+ }
+ return !printable && ((submit_method == URL_MAIL_METHOD) && (width > 72));
+}
+
+PerFormInfo *HText_PerFormInfo(int number)
+{
+ return (PerFormInfo *) HTList_objectAt(HTMainText->forms, number - 1);
+}
+
+/*
+ * HText_SubmitForm - generate submit data from form fields.
+ * For mailto forms, send the data.
+ * For other methods, set fields in structure pointed to by doc
+ * appropriately for next request.
+ * Returns 1 if *doc set appropriately for next request,
+ * 0 otherwise. - kw
+ */
+int HText_SubmitForm(FormInfo * submit_item, DocInfo *doc,
+ const char *link_name,
+ const char *link_value)
+{
+ BOOL had_chartrans_warning = NO;
+ BOOL have_accept_cs = NO;
+ BOOL success;
+ BOOLEAN PlainText = FALSE;
+ BOOLEAN SemiColon = FALSE;
+ BOOL skip_field = FALSE;
+ const char *out_csname;
+ const char *target_csname = NULL;
+ PerFormInfo *thisform;
+ PostData *my_data = NULL;
+ TextAnchor *anchor_ptr;
+ bstring *my_query = NULL;
+ char *Boundary = NULL;
+ char *MultipartContentType = NULL;
+ char *content_type_out = NULL;
+ char *copied_name_used = NULL;
+ char *copied_val_used = NULL;
+ char *escaped1 = NULL;
+ char *escaped2 = NULL;
+ char *last_textarea_name = NULL;
+ const char *name_used = "";
+ char *previous_blanks = NULL;
+ const char *val_used = "";
+ int anchor_count = 0;
+ int anchor_limit = 0;
+ int form_number = submit_item->number;
+ int result = 0;
+ int target_cs = -1;
+ int textarea_lineno = 0;
+ unsigned form_is_special = 0;
+
+ CTRACE((tfp, "SubmitForm\n link_name=%s\n link_value=%s\n", link_name, link_value));
+ if (!HTMainText)
+ return 0;
+
+ thisform = HText_PerFormInfo(form_number);
+ /* Sanity check */
+ if (!thisform) {
+ CTRACE((tfp, "SubmitForm: form %d not in HTMainText's list!\n",
+ form_number));
+ } else if (thisform->number != form_number) {
+ CTRACE((tfp, "SubmitForm: failed sanity check, %d!=%d !\n",
+ thisform->number, form_number));
+ thisform = NULL;
+ }
+
+ if (isEmpty(submit_item->submit_action)) {
+ CTRACE((tfp, "SubmitForm: no action given\n"));
+ return 0;
+ }
+
+ /*
+ * If we're mailing, make sure it's a mailto ACTION. -FM
+ */
+ if ((submit_item->submit_method == URL_MAIL_METHOD) &&
+ !isMAILTO_URL(submit_item->submit_action)) {
+ HTAlert(BAD_FORM_MAILTO);
+ return 0;
+ }
+
+ /*
+ * Check the ENCTYPE and set up the appropriate variables. -FM
+ */
+ if (submit_item->submit_enctype &&
+ !strncasecomp(submit_item->submit_enctype, STR_PLAINTEXT, 10)) {
+ /*
+ * Do not hex escape, and use physical newlines
+ * to separate name=value pairs. -FM
+ */
+ PlainText = TRUE;
+ } else if (submit_item->submit_enctype &&
+ !strncasecomp(submit_item->submit_enctype,
+ "application/sgml-form-urlencoded", 32)) {
+ /*
+ * Use semicolons instead of ampersands as the
+ * separators for name=value pairs. -FM
+ */
+ SemiColon = TRUE;
+ } else if (submit_item->submit_enctype &&
+ !strncasecomp(submit_item->submit_enctype,
+ "multipart/form-data", 19)) {
+ /*
+ * Use the multipart MIME format. Later we will ensure it does not
+ * occur within the content.
+ */
+ StrAllocCopy(Boundary, "xnyLAaB03X");
+ }
+
+ /*
+ * Determine in what character encoding (aka. charset) we should
+ * submit. We call this target_cs and the MIME name for it
+ * target_csname.
+ * TODO: - actually use ACCEPT-CHARSET stuff from FORM
+ * TODO: - deal with list in ACCEPT-CHARSET, find a "best"
+ * charset to submit
+ */
+
+ /* Look at ACCEPT-CHARSET on the submitting field if we have one. */
+ if (thisform && submit_item->accept_cs &&
+ strcasecomp(submit_item->accept_cs, "UNKNOWN")) {
+ have_accept_cs = YES;
+ target_cs = find_best_target_cs(&thisform->thisacceptcs,
+ current_char_set,
+ submit_item->accept_cs);
+ }
+ /* Look at ACCEPT-CHARSET on form as a whole if submitting field
+ * didn't have one. */
+ if (thisform && !have_accept_cs && thisform->accept_cs &&
+ strcasecomp(thisform->accept_cs, "UNKNOWN")) {
+ have_accept_cs = YES;
+ target_cs = find_best_target_cs(&thisform->thisacceptcs,
+ current_char_set,
+ thisform->accept_cs);
+ }
+ if (have_accept_cs && (target_cs >= 0) && thisform->thisacceptcs) {
+ target_csname = thisform->thisacceptcs;
+ }
+
+ if (target_cs < 0 &&
+ non_empty(HTMainText->node_anchor->charset)) {
+ target_cs = UCGetLYhndl_byMIME(HTMainText->node_anchor->charset);
+ if (target_cs >= 0) {
+ target_csname = HTMainText->node_anchor->charset;
+ } else {
+ target_cs = UCLYhndl_for_unspec; /* always >= 0 */
+ target_csname = LYCharSet_UC[target_cs].MIMEname;
+ }
+ }
+ if (target_cs < 0) {
+ target_cs = UCLYhndl_for_unspec; /* always >= 0 */
+ }
+
+ /*
+ * Go through list of anchors and get a "max." charset parameter - kw
+ */
+ for (anchor_ptr = HTMainText->first_anchor;
+ anchor_ptr != NULL;
+ anchor_ptr = anchor_ptr->next) {
+
+ if (anchor_ptr->link_type != INPUT_ANCHOR)
+ continue;
+
+ if (anchor_ptr->input_field->number == form_number &&
+ !anchor_ptr->input_field->disabled) {
+
+ FormInfo *form_ptr = anchor_ptr->input_field;
+ char *val = (form_ptr->cp_submit_value != NULL
+ ? form_ptr->cp_submit_value
+ : form_ptr->value);
+
+ unsigned field_is_special = check_form_specialchars(val);
+ unsigned name_is_special = check_form_specialchars(form_ptr->name);
+
+ form_is_special = (field_is_special | name_is_special);
+
+ if (field_is_special == 0) {
+ /* already ok */
+ } else if (target_cs < 0) {
+ /* already confused */
+ } else if ((field_is_special & SPECIAL_8BIT) == 0
+ && (LYCharSet_UC[target_cs].enc == UCT_ENC_8859
+ || (LYCharSet_UC[target_cs].like8859 & UCT_R_8859SPECL))) {
+ /* those specials will be trivial */
+ } else if (UCNeedNotTranslate(form_ptr->value_cs, target_cs)) {
+ /* already ok */
+ } else if (UCCanTranslateFromTo(form_ptr->value_cs, target_cs)) {
+ /* also ok */
+ } else if (UCCanTranslateFromTo(target_cs, form_ptr->value_cs)) {
+ target_cs = form_ptr->value_cs; /* try this */
+ target_csname = NULL; /* will be set after loop */
+ } else {
+ target_cs = -1; /* don't know what to do */
+ }
+
+ /* Same for name */
+ if (name_is_special == 0) {
+ /* already ok */
+ } else if (target_cs < 0) {
+ /* already confused */
+ } else if ((name_is_special & SPECIAL_8BIT) == 0
+ && (LYCharSet_UC[target_cs].enc == UCT_ENC_8859
+ || (LYCharSet_UC[target_cs].like8859 & UCT_R_8859SPECL))) {
+ /* those specials will be trivial */
+ } else if (UCNeedNotTranslate(form_ptr->name_cs, target_cs)) {
+ /* already ok */
+ } else if (UCCanTranslateFromTo(form_ptr->name_cs, target_cs)) {
+ /* also ok */
+ } else if (UCCanTranslateFromTo(target_cs, form_ptr->name_cs)) {
+ target_cs = form_ptr->value_cs; /* try this */
+ target_csname = NULL; /* will be set after loop */
+ } else {
+ target_cs = -1; /* don't know what to do */
+ }
+
+ ++anchor_limit;
+ } else if (anchor_ptr->input_field->number > form_number) {
+ break;
+ }
+ }
+
+ /*
+ * If we have input fields (we expect this), make an array of them so we
+ * can organize the data.
+ */
+ if (anchor_limit != 0) {
+ my_data = typecallocn(PostData, (size_t) anchor_limit);
+ if (my_data == 0)
+ outofmem(__FILE__, "HText_SubmitForm");
+ }
+
+ if (target_csname == NULL) {
+ if (target_cs >= 0) {
+ if ((form_is_special & SPECIAL_8BIT) != 0) {
+ target_csname = LYCharSet_UC[target_cs].MIMEname;
+ } else if ((form_is_special & SPECIAL_FORM) != 0) {
+ target_csname = LYCharSet_UC[target_cs].MIMEname;
+ } else {
+ target_csname = "us-ascii";
+ }
+ } else {
+ target_csname = "us-ascii";
+ target_cs = UCLYhndl_for_unspec; /* always >= 0 */
+ }
+ } else if (target_cs < 0) {
+ target_cs = UCLYhndl_for_unspec; /* always >= 0 */
+ }
+
+ if (submit_item->submit_method == URL_GET_METHOD && Boundary == NULL) {
+ char *temp = NULL;
+
+ StrAllocCopy(temp, submit_item->submit_action);
+ /*
+ * Method is GET. Clip out any anchor in the current URL.
+ */
+ strtok(temp, "#");
+ /*
+ * Clip out any old query in the current URL.
+ */
+ strtok(temp, "?");
+ /*
+ * Add the lead question mark for the new URL.
+ */
+ StrAllocCat(temp, "?");
+ BStrCat0(my_query, temp);
+ free(temp);
+ } else {
+ /*
+ * We are submitting POST content to a server,
+ * so load content_type_out. This will be put in
+ * the post_content_type element if all goes well. -FM, kw
+ */
+ if (SemiColon == TRUE) {
+ StrAllocCopy(content_type_out,
+ "application/sgml-form-urlencoded");
+ } else if (PlainText == TRUE) {
+ StrAllocCopy(content_type_out,
+ STR_PLAINTEXT);
+ } else if (Boundary != NULL) {
+ StrAllocCopy(content_type_out,
+ "multipart/form-data");
+ } else {
+ StrAllocCopy(content_type_out,
+ "application/x-www-form-urlencoded");
+ }
+
+ /*
+ * If the ENCTYPE is not multipart/form-data, append the
+ * charset we'll be sending to the post_content_type, IF
+ * (1) there was an explicit accept-charset attribute, OR
+ * (2) we have 8-bit or special chars, AND the document had
+ * an explicit (recognized and accepted) charset parameter,
+ * AND it or target_csname is different from iso-8859-1,
+ * OR
+ * (3) we have 8-bit or special chars, AND the document had
+ * no explicit (recognized and accepted) charset parameter,
+ * AND target_cs is different from the currently effective
+ * assumed charset (which should have been set by the user
+ * so that it reflects what the server is sending, if the
+ * document is rendered correctly).
+ * For multipart/form-data the equivalent will be done later,
+ * separately for each form field. - kw
+ */
+ if (have_accept_cs
+ || ((form_is_special & SPECIAL_8BIT) != 0
+ || (form_is_special & SPECIAL_FORM) != 0)) {
+ if (target_cs >= 0 && target_csname) {
+ if (Boundary == NULL) {
+ if ((HTMainText->node_anchor->charset &&
+ (strcmp(HTMainText->node_anchor->charset,
+ "iso-8859-1") ||
+ strcmp(target_csname, "iso-8859-1"))) ||
+ (!HTMainText->node_anchor->charset &&
+ target_cs != UCLYhndl_for_unspec)) {
+ HTSprintf(&content_type_out, "; charset=%s", target_csname);
+ }
+ }
+ } else {
+ cannot_transcode(&had_chartrans_warning, target_csname);
+ }
+ }
+ }
+
+ out_csname = target_csname;
+
+ /*
+ * Build up a list of the input fields and their associated values.
+ */
+ for (anchor_ptr = HTMainText->first_anchor;
+ anchor_ptr != NULL;
+ anchor_ptr = anchor_ptr->next) {
+
+ if (anchor_ptr->link_type != INPUT_ANCHOR)
+ continue;
+
+ if (anchor_ptr->input_field->number == form_number &&
+ !anchor_ptr->input_field->disabled) {
+
+ FormInfo *form_ptr = anchor_ptr->input_field;
+ int out_cs;
+ QuoteData quoting = (PlainText
+ ? NO_QUOTE
+ : (Boundary
+ ? QUOTE_MULTI
+ : QUOTE_SPECIAL));
+
+ assert(my_data != NULL);
+
+ if (form_ptr->type != F_TEXTAREA_TYPE)
+ textarea_lineno = 0;
+
+ CTRACE((tfp, "SubmitForm[%d/%d]: ",
+ anchor_count + 1, anchor_limit));
+
+ name_used = NonNull(form_ptr->name);
+
+ switch (form_ptr->type) {
+ case F_RESET_TYPE:
+ CTRACE((tfp, "reset\n"));
+ break;
+#ifdef USE_FILE_UPLOAD
+ case F_FILE_TYPE:
+ val_used = NonNull(form_ptr->value);
+ CTRACE((tfp, "I will submit \"%s\" (from %s)\n",
+ val_used, name_used));
+ break;
+#endif
+ case F_SUBMIT_TYPE:
+ case F_TEXT_SUBMIT_TYPE:
+ case F_IMAGE_SUBMIT_TYPE:
+ if (!(non_empty(form_ptr->name) &&
+ !strcmp(form_ptr->name, link_name))) {
+ CTRACE((tfp, "skipping submit field with "));
+ CTRACE((tfp, "name \"%s\" for link_name \"%s\", %s.\n",
+ form_ptr->name ? form_ptr->name : "???",
+ link_name ? link_name : "???",
+ non_empty(form_ptr->name) ?
+ "not current link" : "no field name"));
+ break;
+ }
+ if (!(form_ptr->type == F_TEXT_SUBMIT_TYPE ||
+ (non_empty(form_ptr->value) &&
+ !strcmp(form_ptr->value, link_value)))) {
+ CTRACE((tfp, "skipping submit field with "));
+ CTRACE((tfp, "name \"%s\" for link_name \"%s\", %s!\n",
+ form_ptr->name ? form_ptr->name : "???",
+ link_name ? link_name : "???",
+ "values are different"));
+ break;
+ }
+ /* FALLTHRU */
+ case F_RADIO_TYPE:
+ case F_CHECKBOX_TYPE:
+ case F_TEXTAREA_TYPE:
+ case F_PASSWORD_TYPE:
+ case F_TEXT_TYPE:
+ case F_OPTION_LIST_TYPE:
+ case F_HIDDEN_TYPE:
+ /*
+ * Be sure to actually look at the option submit value.
+ */
+ if (form_ptr->cp_submit_value != NULL) {
+ val_used = form_ptr->cp_submit_value;
+ } else {
+ val_used = form_ptr->value;
+ }
+
+ /*
+ * Charset-translate value now, because we need to know the
+ * charset parameter for multipart bodyparts. - kw
+ */
+ if (check_form_specialchars(val_used) != 0) {
+ /* We should translate back. */
+ StrAllocCopy(copied_val_used, val_used);
+ success = FALSE;
+ if (HTCJK == JAPANESE) {
+ if ((0 <= target_cs) &&
+ !strcmp(LYCharSet_UC[target_cs].MIMEname, "euc-jp")) {
+ TO_EUC((const unsigned char *) val_used,
+ (unsigned char *) copied_val_used);
+ success = YES;
+ } else if ((0 <= target_cs) &&
+ !strcmp(LYCharSet_UC[target_cs].MIMEname,
+ "shift_jis")) {
+ TO_SJIS((const unsigned char *) val_used,
+ (unsigned char *) copied_val_used);
+ success = YES;
+ }
+ }
+ if (!success) {
+ success = LYUCTranslateBackFormData(&copied_val_used,
+ form_ptr->value_cs,
+ target_cs, PlainText);
+ }
+ CTRACE((tfp, "field \"%s\" %d %s -> %d %s %s\n",
+ NonNull(form_ptr->name),
+ form_ptr->value_cs,
+ ((form_ptr->value_cs >= 0)
+ ? LYCharSet_UC[form_ptr->value_cs].MIMEname
+ : "???"),
+ target_cs,
+ target_csname ? target_csname : "???",
+ success ? "OK" : "FAILED"));
+ if (success) {
+ val_used = copied_val_used;
+ }
+ } else { /* We can use the value directly. */
+ CTRACE((tfp, "field \"%s\" %d %s OK\n",
+ NonNull(form_ptr->name),
+ target_cs,
+ target_csname ? target_csname : "???"));
+ success = YES;
+ }
+ if (!success) {
+ cannot_transcode(&had_chartrans_warning, target_csname);
+ out_cs = form_ptr->value_cs;
+ } else {
+ out_cs = target_cs;
+ }
+ if (out_cs >= 0)
+ out_csname = LYCharSet_UC[out_cs].MIMEname;
+ if (Boundary) {
+ StrAllocCopy(MultipartContentType,
+ "\r\nContent-Type: %s");
+ if (!success && form_ptr->value_cs < 0) {
+ /* This is weird. */
+ out_csname = "UNKNOWN-8BIT";
+ } else if (!success) {
+ target_csname = NULL;
+ } else {
+ if (!target_csname) {
+ target_csname = LYCharSet_UC[target_cs].MIMEname;
+ }
+ }
+ if (strcmp(out_csname, "iso-8859-1"))
+ HTSprintf(&MultipartContentType, "; charset=%s", out_csname);
+ }
+
+ /*
+ * Charset-translate name now, because we need to know the
+ * charset parameter for multipart bodyparts. - kw
+ */
+ if (form_ptr->type == F_TEXTAREA_TYPE) {
+ textarea_lineno++;
+ if (textarea_lineno > 1 &&
+ last_textarea_name && form_ptr->name &&
+ !strcmp(last_textarea_name, form_ptr->name)) {
+ break;
+ }
+ }
+
+ if (check_form_specialchars(name_used) != 0) {
+ /* We should translate back. */
+ StrAllocCopy(copied_name_used, name_used);
+ success = LYUCTranslateBackFormData(&copied_name_used,
+ form_ptr->name_cs,
+ target_cs, PlainText);
+ CTRACE((tfp, "name \"%s\" %d %s -> %d %s %s\n",
+ NonNull(form_ptr->name),
+ form_ptr->name_cs,
+ ((form_ptr->name_cs >= 0)
+ ? LYCharSet_UC[form_ptr->name_cs].MIMEname
+ : "???"),
+ target_cs,
+ target_csname ? target_csname : "???",
+ success ? "OK" : "FAILED"));
+ if (success) {
+ name_used = copied_name_used;
+ }
+ if (Boundary) {
+ if (!success) {
+ StrAllocCopy(MultipartContentType, "");
+ target_csname = NULL;
+ } else {
+ if (!target_csname)
+ target_csname = LYCharSet_UC[target_cs].MIMEname;
+ }
+ }
+ } else { /* We can use the name directly. */
+ CTRACE((tfp, "name \"%s\" %d %s OK\n",
+ NonNull(form_ptr->name),
+ target_cs,
+ target_csname ? target_csname : "???"));
+ success = YES;
+ if (Boundary) {
+ StrAllocCopy(copied_name_used, name_used);
+ }
+ }
+ if (!success) {
+ cannot_transcode(&had_chartrans_warning, target_csname);
+ }
+ if (Boundary) {
+ /*
+ * According to RFC 1867, Non-ASCII field names
+ * "should be encoded according to the prescriptions
+ * of RFC 1522 [...]. I don't think RFC 1522 actually
+ * is meant to apply to parameters like this, and it
+ * is unknown whether any server would make sense of
+ * it, so for now just use some quoting/escaping and
+ * otherwise leave 8-bit values as they are.
+ * Non-ASCII characters in form field names submitted
+ * as multipart/form-data can only occur if the form
+ * provider specifically asked for it anyway. - kw
+ */
+ HTMake822Word(&copied_name_used, FALSE);
+ name_used = copied_name_used;
+ }
+
+ break;
+ default:
+ CTRACE((tfp, "What type is %d?\n", form_ptr->type));
+ break;
+ }
+
+ skip_field = FALSE;
+ my_data[anchor_count].first = TRUE;
+ my_data[anchor_count].type = form_ptr->type;
+
+ /*
+ * Using the values of 'name_used' and 'val_used' computed in the
+ * previous case-statement, compute the 'first' and 'data' values
+ * for the current input field.
+ */
+ switch (form_ptr->type) {
+
+ default:
+ skip_field = TRUE;
+ break;
+
+#ifdef USE_FILE_UPLOAD
+ case F_FILE_TYPE:
+ load_a_file(val_used, &(my_data[anchor_count].data));
+ break;
+#endif /* USE_FILE_UPLOAD */
+
+ case F_SUBMIT_TYPE:
+ case F_TEXT_SUBMIT_TYPE:
+ case F_IMAGE_SUBMIT_TYPE:
+ if ((non_empty(form_ptr->name) &&
+ !strcmp(form_ptr->name, link_name)) &&
+ (form_ptr->type == F_TEXT_SUBMIT_TYPE ||
+ (non_empty(form_ptr->value) &&
+ !strcmp(form_ptr->value, link_value)))) {
+ ;
+ } else {
+ skip_field = TRUE;
+ }
+ break;
+
+ case F_RADIO_TYPE:
+ case F_CHECKBOX_TYPE:
+ /*
+ * Only add if selected.
+ */
+ if (form_ptr->num_value) {
+ ;
+ } else {
+ skip_field = TRUE;
+ }
+ break;
+
+ case F_TEXTAREA_TYPE:
+ if (!last_textarea_name ||
+ strcmp(last_textarea_name, form_ptr->name)) {
+ textarea_lineno = 1;
+ last_textarea_name = form_ptr->name;
+ } else {
+ my_data[anchor_count].first = FALSE;
+ }
+ break;
+
+ case F_PASSWORD_TYPE:
+ case F_TEXT_TYPE:
+ case F_OPTION_LIST_TYPE:
+ case F_HIDDEN_TYPE:
+ break;
+ }
+
+ /*
+ * If we did not decide to skip the current field, populate the
+ * values in the array for it.
+ */
+ if (!skip_field) {
+ StrAllocCopy(my_data[anchor_count].name, name_used);
+ StrAllocCopy(my_data[anchor_count].value, val_used);
+ if (my_data[anchor_count].data == 0)
+ BStrCat0(my_data[anchor_count].data, val_used);
+ my_data[anchor_count].quote = quoting;
+ if (quoting == QUOTE_MULTI
+ && check_if_base64_needed(submit_item->submit_method,
+ my_data[anchor_count].data)) {
+ CTRACE((tfp, "will encode as base64\n"));
+ my_data[anchor_count].quote = QUOTE_BASE64;
+ escaped2 =
+ convert_to_base64(BStrData(my_data[anchor_count].data),
+ (size_t)
+ BStrLen(my_data[anchor_count].data));
+ BStrCopy0(my_data[anchor_count].data, escaped2);
+ FREE(escaped2);
+ }
+ }
+ ++anchor_count;
+
+ FREE(copied_name_used);
+ FREE(copied_val_used);
+
+ } else if (anchor_ptr->input_field->number > form_number) {
+ break;
+ }
+ }
+
+ FREE(copied_name_used);
+
+ if (my_data != 0) {
+ BOOL first_one = TRUE;
+
+ /*
+ * If we're using a MIME-boundary, make it unique.
+ */
+ if (content_type_out != 0 && Boundary != 0) {
+ Boundary = 0;
+ StrAllocCopy(Boundary, "LYNX");
+ for (anchor_count = 0; anchor_count < anchor_limit; ++anchor_count) {
+ if (my_data[anchor_count].data != 0) {
+ UpdateBoundary(&Boundary, my_data[anchor_count].data);
+ }
+ }
+ HTSprintf(&content_type_out, "; boundary=%s", Boundary);
+ }
+
+ for (anchor_count = 0; anchor_count < anchor_limit; ++anchor_count) {
+
+ if (my_data[anchor_count].name != 0
+ && my_data[anchor_count].value != 0) {
+
+ CTRACE((tfp,
+ "processing [%d:%d] name=%s(first:%d, value=%s, data=%p)\n",
+ anchor_count + 1,
+ anchor_limit,
+ NonNull(my_data[anchor_count].name),
+ my_data[anchor_count].first,
+ NonNull(my_data[anchor_count].value),
+ (void *) my_data[anchor_count].data));
+
+ if (my_data[anchor_count].first) {
+ if (first_one) {
+ if (Boundary) {
+ HTBprintf(&my_query, "--%s\r\n", Boundary);
+ }
+ first_one = FALSE;
+ } else {
+ if (PlainText) {
+ BStrCat0(my_query, "\n");
+ } else if (SemiColon) {
+ BStrCat0(my_query, ";");
+ } else if (Boundary) {
+ HTBprintf(&my_query, "\r\n--%s\r\n", Boundary);
+ } else {
+ BStrCat0(my_query, "&");
+ }
+ }
+ }
+
+ /* append a null to the string */
+ HTSABCat(&(my_data[anchor_count].data), "", 1);
+ name_used = my_data[anchor_count].name;
+ val_used = my_data[anchor_count].value;
+
+ } else {
+ /* there is no data to send */
+ continue;
+ }
+
+ switch (my_data[anchor_count].type) {
+ case F_TEXT_TYPE:
+ case F_PASSWORD_TYPE:
+ case F_OPTION_LIST_TYPE:
+ case F_HIDDEN_TYPE:
+ escaped1 = escape_or_quote_name(my_data[anchor_count].name,
+ my_data[anchor_count].quote,
+ MultipartContentType);
+
+ escaped2 = escape_or_quote_value(val_used,
+ my_data[anchor_count].quote);
+
+ HTBprintf(&my_query,
+ "%s%s%s%s%s",
+ escaped1,
+ (Boundary ? "" : "="),
+ (PlainText ? "\n" : ""),
+ escaped2,
+ ((PlainText && *escaped2) ? "\n" : ""));
+ break;
+ case F_CHECKBOX_TYPE:
+ case F_RADIO_TYPE:
+ escaped1 = escape_or_quote_name(my_data[anchor_count].name,
+ my_data[anchor_count].quote,
+ MultipartContentType);
+
+ escaped2 = escape_or_quote_value(val_used,
+ my_data[anchor_count].quote);
+
+ HTBprintf(&my_query,
+ "%s%s%s%s%s",
+ escaped1,
+ (Boundary ? "" : "="),
+ (PlainText ? "\n" : ""),
+ escaped2,
+ ((PlainText && *escaped2) ? "\n" : ""));
+ break;
+ case F_SUBMIT_TYPE:
+ case F_TEXT_SUBMIT_TYPE:
+ case F_IMAGE_SUBMIT_TYPE:
+ /*
+ * If it has a non-zero length name (e.g., because
+ * its IMAGE_SUBMIT_TYPE is to be handled homologously
+ * to an image map, or a SUBMIT_TYPE in a set of
+ * multiple submit buttons, or a single type="text"
+ * that's been converted to a TEXT_SUBMIT_TYPE),
+ * include the name=value pair, or fake name.x=0 and
+ * name.y=0 pairs for IMAGE_SUBMIT_TYPE. -FM
+ */
+ escaped1 = escape_or_quote_name(my_data[anchor_count].name,
+ my_data[anchor_count].quote,
+ MultipartContentType);
+
+ escaped2 = escape_or_quote_value(val_used,
+ my_data[anchor_count].quote);
+
+ if (my_data[anchor_count].type == F_IMAGE_SUBMIT_TYPE) {
+ /*
+ * It's a clickable image submit button. Fake a 0,0
+ * coordinate pair, which typically returns the image's
+ * default. -FM
+ */
+ if (Boundary) {
+ *(StrChr(escaped1, '=') + 1) = '\0';
+ HTBprintf(&my_query,
+ "%s\"%s.x\"\r\n\r\n0\r\n--%s\r\n%s\"%s.y\"\r\n\r\n0",
+ escaped1,
+ my_data[anchor_count].name,
+ Boundary,
+ escaped1,
+ my_data[anchor_count].name);
+ } else {
+ HTBprintf(&my_query,
+ "%s.x=0%s%s.y=0%s",
+ escaped1,
+ (PlainText ?
+ "\n" : (SemiColon ?
+ ";" : "&")),
+ escaped1,
+ ((PlainText && *escaped1) ?
+ "\n" : ""));
+ }
+ } else {
+ /*
+ * It's a standard submit button. Use the name=value
+ * pair. = FM
+ */
+ HTBprintf(&my_query,
+ "%s%s%s%s%s",
+ escaped1,
+ (Boundary ? "" : "="),
+ (PlainText ? "\n" : ""),
+ escaped2,
+ ((PlainText && *escaped2) ? "\n" : ""));
+ }
+ break;
+ case F_RESET_TYPE:
+ /* ignore */
+ break;
+ case F_TEXTAREA_TYPE:
+ escaped2 = escape_or_quote_value(val_used,
+ my_data[anchor_count].quote);
+
+ if (my_data[anchor_count].first) {
+ /*
+ * Names are different so this is the first textarea or a
+ * different one from any before it.
+ */
+ if (PlainText) {
+ FREE(previous_blanks);
+ } else if (Boundary) {
+ StrAllocCopy(previous_blanks, "\r\n");
+ } else {
+ StrAllocCopy(previous_blanks, "%0d%0a");
+ }
+ escaped1 = escape_or_quote_name(name_used,
+ my_data[anchor_count].quote,
+ MultipartContentType);
+
+ HTBprintf(&my_query,
+ "%s%s%s%s%s",
+ escaped1,
+ (Boundary ? "" : "="),
+ (PlainText ? "\n" : ""),
+ escaped2,
+ ((PlainText && *escaped2) ? "\n" : ""));
+ } else {
+ const char *marker = (PlainText
+ ? "\n"
+ : (Boundary
+ ? "\r\n"
+ : "%0d%0a"));
+
+ /*
+ * This is a continuation of a previous textarea.
+ */
+ if (escaped2[0] != '\0') {
+ if (previous_blanks) {
+ BStrCat0(my_query, previous_blanks);
+ FREE(previous_blanks);
+ }
+ BStrCat0(my_query, escaped2);
+ if (PlainText || Boundary)
+ BStrCat0(my_query, marker);
+ else
+ StrAllocCopy(previous_blanks, marker);
+ } else {
+ StrAllocCat(previous_blanks, marker);
+ }
+ }
+ break;
+ case F_RANGE_TYPE:
+ /* not implemented */
+ break;
+#ifdef USE_FILE_UPLOAD
+ case F_FILE_TYPE:
+ if (PlainText) {
+ StrAllocCopy(escaped1, my_data[anchor_count].name);
+ } else if (Boundary) {
+ const char *t = guess_content_type(val_used);
+ char *copied_fname = NULL;
+
+ StrAllocCopy(escaped1, "Content-Disposition: form-data");
+ HTSprintf(&escaped1, "; name=\"%s\"",
+ my_data[anchor_count].name);
+
+ StrAllocCopy(copied_fname, val_used);
+ HTMake822Word(&copied_fname, FALSE);
+ HTSprintf(&escaped1, "; filename=\"%s\"", copied_fname);
+ FREE(copied_fname);
+
+ /* Should we take into account the encoding? */
+ HTSprintf(&escaped1, "\r\nContent-Type: %s", t);
+ if (my_data[anchor_count].quote == QUOTE_BASE64)
+ StrAllocCat(escaped1,
+ "\r\nContent-Transfer-Encoding: base64");
+ StrAllocCat(escaped1, "\r\n\r\n");
+ } else {
+ escaped1 = HTEscapeSP(my_data[anchor_count].name, URL_XALPHAS);
+ }
+
+ HTBprintf(&my_query,
+ "%s%s%s",
+ escaped1,
+ (Boundary ? "" : "="),
+ (PlainText ? "\n" : ""));
+ /*
+ * If we have anything more than the trailing null we added,
+ * append the file-data to the query.
+ */
+ if (BStrLen(my_data[anchor_count].data) > 1) {
+ HTSABCat(&my_query,
+ BStrData(my_data[anchor_count].data),
+ BStrLen(my_data[anchor_count].data) - 1);
+ if (PlainText)
+ HTBprintf(&my_query, "\n");
+ }
+ break;
+#endif /* USE_FILE_UPLOAD */
+ case F_KEYGEN_TYPE:
+ case F_BUTTON_TYPE:
+ /* not implemented */
+ break;
+ }
+
+ FREE(escaped1);
+ FREE(escaped2);
+ }
+ if (Boundary) {
+ HTBprintf(&my_query, "\r\n--%s--\r\n", Boundary);
+ }
+ if (TRACE) {
+ CTRACE((tfp, "Query %d{", BStrLen(my_query)));
+ trace_bstring(my_query);
+ CTRACE((tfp, "}\n"));
+ }
+ }
+
+ if (submit_item->submit_method == URL_MAIL_METHOD) {
+ HTUserMsg2(gettext("Submitting %s"), submit_item->submit_action);
+ HTSABCat(&my_query, "", 1); /* append null */
+ mailform((submit_item->submit_action + 7),
+ (isEmpty(submit_item->submit_title)
+ ? NonNull(HText_getTitle())
+ : submit_item->submit_title),
+ BStrData(my_query),
+ content_type_out);
+ result = 0;
+ BStrFree(my_query);
+ FREE(content_type_out);
+ } else {
+ _statusline(SUBMITTING_FORM);
+
+ /*
+ * File-URLs (whether via GET or POST) cannot provide search queries.
+ * The relevant RFCs 1630, 1738 are silent on what to do with
+ * unexpected query parameters in a file-URL.
+ *
+ * Internet Explorer trims the query string here (after all, a "?" is
+ * not a legal part of a Windows filename), and other browsers copy the
+ * behavior. We do this for compatibility, in case someone cares.
+ */
+ if (my_query != 0 &&
+ my_query->len > 5 &&
+ !strncmp(my_query->str, "file:", (size_t) 5)) {
+ strtok(my_query->str, "?");
+ }
+ if (submit_item->submit_method == URL_POST_METHOD || Boundary) {
+ LYFreePostData(doc);
+ doc->post_data = my_query;
+ doc->post_content_type = content_type_out; /* don't free c_t_out */
+ CTRACE((tfp, "GridText - post_data set:\n%s\n", content_type_out));
+ StrAllocCopy(doc->address, submit_item->submit_action);
+ } else { /* GET_METHOD */
+ HTSABCat(&my_query, "", 1); /* append null */
+ StrAllocCopy(doc->address, BStrData(my_query));
+ LYFreePostData(doc);
+ FREE(content_type_out);
+ HTSABFree(&my_query);
+ }
+ result = 1;
+ }
+
+ FREE(MultipartContentType);
+ FREE(previous_blanks);
+ FREE(Boundary);
+ if (my_data != 0) {
+ for (anchor_count = 0; anchor_count < anchor_limit; ++anchor_count) {
+ FREE(my_data[anchor_count].name);
+ FREE(my_data[anchor_count].value);
+ BStrFree(my_data[anchor_count].data);
+ }
+ FREE(my_data);
+ }
+
+ return (result);
+}
+
+void HText_DisableCurrentForm(void)
+{
+ TextAnchor *anchor_ptr;
+
+ HTFormDisabled = TRUE;
+ if (HTMainText != NULL) {
+ /*
+ * Go through list of anchors and set the disabled flag.
+ */
+ for (anchor_ptr = HTMainText->first_anchor;
+ anchor_ptr != NULL;
+ anchor_ptr = anchor_ptr->next) {
+
+ if (anchor_ptr->link_type == INPUT_ANCHOR &&
+ anchor_ptr->input_field->number == HTFormNumber) {
+
+ anchor_ptr->input_field->disabled = TRUE;
+ }
+ }
+ }
+ return;
+}
+
+void HText_ResetForm(FormInfo * form)
+{
+ TextAnchor *anchor_ptr;
+
+ _statusline(RESETTING_FORM);
+ if (HTMainText == 0)
+ return;
+
+ /*
+ * Go through list of anchors and reset values.
+ */
+ for (anchor_ptr = HTMainText->first_anchor;
+ anchor_ptr != NULL;
+ anchor_ptr = anchor_ptr->next) {
+ if (anchor_ptr->link_type == INPUT_ANCHOR) {
+ if (anchor_ptr->input_field->number == form->number) {
+
+ if (anchor_ptr->input_field->type == F_RADIO_TYPE ||
+ anchor_ptr->input_field->type == F_CHECKBOX_TYPE) {
+
+ if (anchor_ptr->input_field->orig_value[0] == '0')
+ anchor_ptr->input_field->num_value = 0;
+ else
+ anchor_ptr->input_field->num_value = 1;
+
+ } else if (anchor_ptr->input_field->type ==
+ F_OPTION_LIST_TYPE) {
+ anchor_ptr->input_field->value =
+ anchor_ptr->input_field->orig_value;
+
+ anchor_ptr->input_field->cp_submit_value =
+ anchor_ptr->input_field->orig_submit_value;
+
+ } else {
+ StrAllocCopy(anchor_ptr->input_field->value,
+ anchor_ptr->input_field->orig_value);
+ }
+ } else if (anchor_ptr->input_field->number > form->number) {
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * This function is called before reloading/reparsing current document to find
+ * whether any forms content was changed by user so any information will be
+ * lost.
+ */
+BOOLEAN HText_HaveUserChangedForms(HText *text)
+{
+ TextAnchor *anchor_ptr;
+
+ if (text == 0)
+ return FALSE;
+
+ /*
+ * Go through list of anchors to check if any value was changed.
+ * This code based on HText_ResetForm()
+ */
+ for (anchor_ptr = text->first_anchor;
+ anchor_ptr != NULL;
+ anchor_ptr = anchor_ptr->next) {
+ if (anchor_ptr->link_type == INPUT_ANCHOR) {
+
+ if (anchor_ptr->input_field->type == F_RADIO_TYPE ||
+ anchor_ptr->input_field->type == F_CHECKBOX_TYPE) {
+
+ if ((anchor_ptr->input_field->orig_value[0] == '0' &&
+ anchor_ptr->input_field->num_value == 1) ||
+ (anchor_ptr->input_field->orig_value[0] != '0' &&
+ anchor_ptr->input_field->num_value == 0))
+ return TRUE;
+
+ } else if (anchor_ptr->input_field->type == F_OPTION_LIST_TYPE) {
+ if (strcmp(anchor_ptr->input_field->value,
+ anchor_ptr->input_field->orig_value))
+ return TRUE;
+
+ if (strcmp(anchor_ptr->input_field->cp_submit_value,
+ anchor_ptr->input_field->orig_submit_value))
+ return TRUE;
+
+ } else {
+ if (strcmp(anchor_ptr->input_field->value,
+ anchor_ptr->input_field->orig_value))
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+void HText_activateRadioButton(FormInfo * form)
+{
+ TextAnchor *anchor_ptr;
+ int form_number = form->number;
+
+ if (!HTMainText)
+ return;
+ for (anchor_ptr = HTMainText->first_anchor;
+ anchor_ptr != NULL;
+ anchor_ptr = anchor_ptr->next) {
+ if (anchor_ptr->link_type == INPUT_ANCHOR &&
+ anchor_ptr->input_field->type == F_RADIO_TYPE) {
+
+ if (anchor_ptr->input_field->number == form_number) {
+
+ /* if it has the same name and its on */
+ if (!strcmp(anchor_ptr->input_field->name, form->name) &&
+ anchor_ptr->input_field->num_value) {
+ anchor_ptr->input_field->num_value = 0;
+ break;
+ }
+ } else if (anchor_ptr->input_field->number > form_number) {
+ break;
+ }
+
+ }
+ }
+
+ form->num_value = 1;
+}
+
+#ifdef LY_FIND_LEAKS
+/*
+ * Purpose: Free all currently loaded HText objects in memory.
+ * Arguments: void
+ * Return Value: void
+ * Remarks/Portability/Dependencies/Restrictions:
+ * Usage of this function should really be limited to program
+ * termination.
+ * Revision History:
+ * 05-27-94 created Lynx 2-3-1 Garrett Arch Blythe
+ */
+static void free_all_texts(void)
+{
+ HText *cur = NULL;
+
+ if (!loaded_texts)
+ return;
+
+ /*
+ * Simply loop through the loaded texts list killing them off.
+ */
+ while (loaded_texts && !HTList_isEmpty(loaded_texts)) {
+ if ((cur = (HText *) HTList_removeLastObject(loaded_texts)) != NULL) {
+ HText_free(cur);
+ }
+ }
+
+ /*
+ * Get rid of the text list.
+ */
+ if (loaded_texts) {
+ HTList_delete(loaded_texts);
+ }
+
+ /*
+ * Insurance for bad HTML.
+ */
+ FREE(HTCurSelectGroup);
+ FREE(HTCurSelectGroupSize);
+ FREE(HTCurSelectedOptionValue);
+ PerFormInfo_free(HTCurrentForm);
+
+ return;
+}
+#endif /* LY_FIND_LEAKS */
+
+/*
+ * stub_HTAnchor_address is like HTAnchor_address, but it returns the
+ * parent address for child links. This is only useful for traversal's
+ * where one does not want to index a text file N times, once for each
+ * of N internal links. Since the parent link has already been taken,
+ * it won't go again, hence the (incorrect) links won't cause problems.
+ */
+char *stub_HTAnchor_address(HTAnchor * me)
+{
+ char *addr = NULL;
+
+ if (me)
+ StrAllocCopy(addr, me->parent->address);
+ return addr;
+}
+
+void HText_setToolbar(HText *text)
+{
+ if (text)
+ text->toolbar = TRUE;
+ return;
+}
+
+BOOL HText_hasToolbar(HText *text)
+{
+ return (BOOL) ((text && text->toolbar) ? TRUE : FALSE);
+}
+
+void HText_setNoCache(HText *text)
+{
+ if (text)
+ text->no_cache = TRUE;
+ return;
+}
+
+BOOL HText_hasNoCacheSet(HText *text)
+{
+ return (BOOL) ((text && text->no_cache) ? TRUE : FALSE);
+}
+
+BOOL HText_hasUTF8OutputSet(HText *text)
+{
+ return (BOOL) ((text && text->T.output_utf8) ? TRUE : FALSE);
+}
+
+/*
+ * Check charset and set the kcode element. -FM
+ * Info on the input charset may be passed in in two forms,
+ * as a string (if given explicitly) and as a pointer to
+ * a LYUCcharset (from chartrans mechanism); either can be NULL.
+ * For Japanese the kcode will be reset at a space or explicit
+ * line or paragraph break, so what we set here may not last for
+ * long. It's potentially more important not to set HTCJK to
+ * NOCJK unless we are sure. - kw
+ */
+void HText_setKcode(HText *text, const char *charset,
+ LYUCcharset *p_in)
+{
+ BOOL charset_explicit;
+
+ if (!text)
+ return;
+
+ /*
+ * Check whether we have some kind of info. - kw
+ */
+ if (!charset && !p_in) {
+ return;
+ }
+ charset_explicit = (BOOLEAN) (charset ? TRUE : FALSE);
+ /*
+ * If no explicit charset string, use the implied one. - kw
+ */
+ if (isEmpty(charset)) {
+ charset = p_in->MIMEname;
+ }
+ /*
+ * Check whether we have a specified charset. -FM
+ */
+ if (isEmpty(charset)) {
+ return;
+ }
+
+ /*
+ * We've included the charset, and not forced a download offer,
+ * only if the currently selected character set can handle it,
+ * so check the charset value and set the text->kcode element
+ * appropriately. -FM
+ */
+ /* If charset isn't specified explicitly nor assumed,
+ * p_in->MIMEname would be set as display charset.
+ * So text->kcode sholud be set as SJIS or EUC here only if charset
+ * is specified explicitly, otherwise text->kcode would cause
+ * mishandling Japanese strings. -- TH
+ */
+ if (charset_explicit && (!strcmp(charset, "shift_jis") ||
+ !strcmp(charset, "x-sjis") || /* 1997/11/28 (Fri) 18:11:33 */
+ !strcmp(charset, "x-shift-jis"))) {
+ text->kcode = SJIS;
+ } else if (charset_explicit
+#ifdef EXP_JAPANESEUTF8_SUPPORT
+ && strcmp(charset, "utf-8")
+#endif
+ && ((p_in && (p_in->enc == UCT_ENC_CJK)) ||
+ !strcmp(charset, "x-euc") || /* 1997/11/28 (Fri) 18:11:24 */
+ !strcmp(charset, "euc-jp") ||
+ !StrNCmp(charset, "x-euc-", 6) ||
+ !strcmp(charset, "euc-kr") ||
+ !strcmp(charset, "iso-2022-kr") ||
+ !strcmp(charset, "big5") ||
+ !strcmp(charset, "cn-big5") ||
+ !strcmp(charset, "euc-cn") ||
+ !strcmp(charset, "gb2312") ||
+ !StrNCmp(charset, "cn-gb", 5) ||
+ !strcmp(charset, "iso-2022-cn"))) {
+ text->kcode = EUC;
+ } else {
+ /*
+ * If we get to here, it's not CJK, so disable that if
+ * it is enabled. But only if we are quite sure. -FM & kw
+ */
+ text->kcode = NOKANJI;
+ if (IS_CJK_TTY) {
+ if (!p_in || ((p_in->enc != UCT_ENC_CJK)
+#ifdef EXP_JAPANESEUTF8_SUPPORT
+ && (p_in->enc != UCT_ENC_UTF8)
+#endif
+ )) {
+ HTCJK = NOCJK;
+ }
+ }
+ }
+
+ if (charset_explicit
+#ifdef EXP_JAPANESEUTF8_SUPPORT
+ && strcmp(charset, "utf-8")
+#endif
+ ) {
+ text->specified_kcode = text->kcode;
+ } else {
+ if (UCAssume_MIMEcharset) {
+ if (!strcmp(UCAssume_MIMEcharset, "euc-jp"))
+ text->kcode = text->specified_kcode = EUC;
+ else if (!strcmp(UCAssume_MIMEcharset, "shift_jis"))
+ text->kcode = text->specified_kcode = SJIS;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Set a permissible split at the current end of the last line. -FM
+ */
+void HText_setBreakPoint(HText *text)
+{
+ if (!text)
+ return;
+
+ /*
+ * Can split here. -FM
+ */
+ text->permissible_split = text->last_line->size;
+
+ return;
+}
+
+/*
+ * This function determines whether a document which
+ * would be sought via the a URL that has a fragment
+ * directive appended is otherwise identical to the
+ * currently loaded document, and if so, returns
+ * FALSE, so that any no_cache directives can be
+ * overridden "safely", on the grounds that we are
+ * simply acting on the equivalent of a paging
+ * command. Otherwise, it returns TRUE, i.e, that
+ * the target document might differ from the current,
+ * based on any caching directives or analyses which
+ * claimed or suggested this. -FM
+ */
+BOOL HText_AreDifferent(HTParentAnchor *anchor,
+ const char *full_address)
+{
+ HTParentAnchor *MTanc;
+ char *MTaddress;
+ char *MTpound;
+
+ /*
+ * Do we have a loaded document and both
+ * arguments for this function?
+ */
+ if (!(HTMainText && anchor && full_address))
+ return TRUE;
+
+ /*
+ * Do we have both URLs?
+ */
+ MTanc = HTMainText->node_anchor;
+ if (!(MTanc->address && anchor->address))
+ return (TRUE);
+
+ /*
+ * Do we have a fragment associated with the target?
+ */
+ if (findPoundSelector(full_address) == NULL)
+ return (TRUE);
+
+ /*
+ * Always treat client-side image map menus
+ * as potentially stale, so we'll create a
+ * fresh menu from the LynxMaps HTList.
+ */
+ if (isLYNXIMGMAP(anchor->address))
+ return (TRUE);
+
+ /*
+ * Do the docs differ in the type of request?
+ */
+ if (MTanc->isHEAD != anchor->isHEAD)
+ return (TRUE);
+
+ /*
+ * Are the actual URLs different, after factoring
+ * out a "LYNXIMGMAP:" leader in the MainText URL
+ * and its fragment, if present?
+ */
+ MTaddress = (isLYNXIMGMAP(MTanc->address)
+ ? MTanc->address + LEN_LYNXIMGMAP
+ : MTanc->address);
+ MTpound = trimPoundSelector(MTaddress);
+ if (strcmp(MTaddress, anchor->address)) {
+ restorePoundSelector(MTpound);
+ return (TRUE);
+ }
+ restorePoundSelector(MTpound);
+
+ /*
+ * If the MainText is not an image map menu,
+ * do the docs have different POST contents?
+ */
+ if (MTaddress == MTanc->address) {
+ if (MTanc->post_data) {
+ if (anchor->post_data) {
+ if (!BINEQ(MTanc->post_data, anchor->post_data)) {
+ /*
+ * Both have contents, and they differ.
+ */
+ return (TRUE);
+ }
+ } else {
+ /*
+ * The loaded document has content, but the
+ * target doesn't, so they're different.
+ */
+ return (TRUE);
+ }
+ } else if (anchor->post_data) {
+ /*
+ * The loaded document does not have content, but
+ * the target does, so they're different.
+ */
+ return (TRUE);
+ }
+ }
+
+ /*
+ * We'll assume the target is a position in the currently
+ * displayed document, and thus can ignore any header, META,
+ * or other directives not to use a cached rendition. -FM
+ */
+ return (FALSE);
+}
+
+#define CanTrimTextArea(c) \
+ (LYtrimInputFields ? isspace(c) : ((c) == '\r' || (c) == '\n'))
+
+/*
+ * Re-render the text of a tagged ("[123]") HTLine (arg1), with the tag
+ * number incremented by some value (arg5). The re-rendered string may
+ * be allowed to expand in the event of a tag width change (eg, 99 -> 100)
+ * as controlled by arg6 (CHOP or NOCHOP). Arg4 is either (the address
+ * of) a value which must match, in order for the tag to be incremented,
+ * or (the address of) a 0-value, which will match any value, and cause
+ * any valid tag to be incremented. Arg2 is a pointer to the first/only
+ * anchor that exists on the line; we may need to adjust their position(s)
+ * on the line. Arg3 when non-0 indicates the number of new digits that
+ * were added to the 2nd line in a line crossing pair.
+ *
+ * All tags fields in a line which individually match an expected new value,
+ * are incremented. Line crossing [tags] are handled (PITA).
+ *
+ * Untagged or improperly tagged lines are not altered.
+ *
+ * Returns the number of chars added to the original string's length, if
+ * any.
+ *
+ * --KED 02/03/99
+ */
+static int increment_tagged_htline(HTLine *ht, TextAnchor *a, int *lx_val,
+ int *old_val,
+ int incr,
+ int mode)
+{
+ char buf[MAX_LINE];
+ char lxbuf[MAX_LINE * 2];
+
+ TextAnchor *st_anchor = a;
+ TextAnchor *nxt_anchor;
+
+ char *p = ht->data;
+ char *s = buf;
+ char *lx = lxbuf;
+ char *t;
+
+ BOOLEAN plx = FALSE;
+ BOOLEAN valid;
+
+ int val;
+ int n;
+ int new_n;
+ int pre_n;
+ int post_n;
+ int fixup = 0;
+
+ /*
+ * Cleanup for the 2nd half of a line crosser, whose number of tag
+ * digits grew by some number of places (usually 1 when it does
+ * happen, though it *could* be more). The tag chars were already
+ * rendered into the 2nd line of the pair, but the positioning and
+ * other effects haven't been rippled through any other anchors on
+ * the (2nd) line. So we do that here, as a special case, since
+ * the part of the tag that's in the 2nd line of the pair, will not
+ * be found by the tag string parsing code. Double PITA.
+ *
+ * [see comments below on line crosser caused problems]
+ */
+ if (*lx_val != 0) {
+ nxt_anchor = st_anchor;
+ while ((nxt_anchor) && (nxt_anchor->line_num == a->line_num)) {
+ nxt_anchor->line_pos = (short) (nxt_anchor->line_pos + *lx_val);
+ nxt_anchor = nxt_anchor->next;
+ }
+ fixup = *lx_val;
+ *lx_val = 0;
+ if (st_anchor)
+ st_anchor = st_anchor->next;
+ }
+
+ /*
+ * Walk thru the line looking for tags (ie, "[nnn]" strings).
+ */
+ while (*p != '\0') {
+ if (*p != '[') {
+ *s++ = *p++;
+ continue;
+
+ } else {
+ *s++ = *p++;
+ t = p;
+ n = 0;
+ valid = TRUE; /* p = t = byte after '[' */
+
+ /*
+ * Make sure there are only digits between "[" and "]".
+ */
+ while (*t != ']') {
+ if (*t == '\0') { /* uhoh - we have a potential line crosser */
+ valid = FALSE;
+ plx = TRUE;
+ break;
+ }
+ if (isdigit(UCH(*t++))) {
+ n++;
+ continue;
+ } else {
+ valid = FALSE;
+ break;
+ }
+ }
+
+ /*
+ * If the format is OK, we check to see if the value is what
+ * we expect. If not, we have a random [nn] string in the text,
+ * and leave it alone.
+ *
+ * [It is *possible* to have a false match here, *if* there are
+ * two identical [nn] strings (including the numeric value of
+ * nn), one of which is the [tag], and the other being part of
+ * a document. In such a case, the 1st [nn] string will get
+ * incremented; the 2nd one won't, which makes it a 50-50 chance
+ * of being correct, if and when such an unlikely juxtaposition
+ * of text ever occurs. Further validation tests of the [nnn]
+ * string are probably not possible, since little of the actual
+ * anchor-associated-text is retained in the TextAnchor or the
+ * FormInfo structs. Fortunately, I think the current method is
+ * more than adequate to weed out 99.999% of any possible false
+ * matches, just as it stands. Caveat emptor.]
+ */
+ if ((valid) && (n > 0)) {
+ val = atoi(p);
+ if ((val == *old_val) || (*old_val == 0)) { /* 0 matches all */
+ if (*old_val != 0)
+ (*old_val)++;
+ val += incr;
+ sprintf(s, "%d", val);
+ new_n = (int) strlen(s);
+ s += new_n;
+ p += n;
+
+ /*
+ * If the number of digits in an existing [tag] increased
+ * (eg, [99] --> [100], etc), we need to "adjust" its
+ * horizontal position, and that of all subsequent tags
+ * that may be on the same line. PITA.
+ *
+ * [This seems to work as long as a tag isn't a line
+ * crosser; when it is, the position of anchors on either
+ * side of the split tag, seem to "float" and try to be
+ * as "centered" as possible. Which means that simply
+ * incrementing the line_pos by the fixed value of the
+ * number of digits that got added to some tag in either
+ * line doesn't work quite right, and the text for (say)
+ * a button may get stomped on by another copy of itself,
+ * but offset by a few chars, when it is selected (eg,
+ * "Box Office" may end up looking like "BoBox Office" or
+ * "Box Officece", etc.
+ *
+ * Dunno how to fix that behavior ATT, but at least the
+ * tag numbers themselves are correct. -KED /\oo/\ ]
+ */
+ if ((new_n -= n) != 0) {
+ nxt_anchor = st_anchor;
+ while ((nxt_anchor) &&
+ (nxt_anchor->line_num == a->line_num)) {
+ nxt_anchor->line_pos = (short) (nxt_anchor->line_pos
+ + new_n);
+ nxt_anchor = nxt_anchor->next;
+ }
+ if (st_anchor)
+ st_anchor = st_anchor->next;
+ }
+ }
+ }
+
+ /*
+ * Unfortunately, valid [tag] strings *can* be split across two
+ * lines. Perhaps it would be best to just prevent that from
+ * happening, but a look into that code, makes me wonder. Anyway,
+ * we can handle such tags without *too* much trouble in here [I
+ * think], though since such animals are rather rare, it makes it
+ * a bit difficult to test thoroughly (ie, Beyond here, there be
+ * Dragons).
+ *
+ * We use lxbuf[] to deal with the two lines involved.
+ */
+ pre_n = (int) strlen(p); /* count of 1st part chars in this line */
+ post_n = (int) strlen(ht->next->data);
+ if (plx
+ && (pre_n + post_n + 2 < (int) sizeof(lxbuf))) {
+ strcpy(lx, p); /* <- 1st part of a possible lx'ing tag */
+ strcat(lx, ht->next->data); /* tack on NEXT line */
+
+ t = lx;
+ n = 0;
+ valid = TRUE;
+
+ /*
+ * Go hunting again for just digits, followed by tag end ']'.
+ */
+ while (*t != ']') {
+ if (isdigit(UCH(*t++))) {
+ n++;
+ continue;
+ } else {
+ valid = FALSE;
+ break;
+ }
+ }
+
+ /*
+ * It *looks* like a line crosser; now we value test it to
+ * find out for sure [but see the "false match" warning,
+ * above], and if it matches, increment it into the buffer,
+ * along with the 2nd line's text.
+ */
+ if ((valid)
+ && (n > 0)
+ && (n + post_n + 2) < MAX_LINE) {
+ val = atoi(lx);
+ if ((val == *old_val) || (*old_val == 0)) {
+ const char *r;
+
+ if (*old_val != 0)
+ (*old_val)++;
+ val += incr;
+ sprintf(lx, "%d", val);
+ new_n = (int) strlen(lx);
+ if ((r = StrChr(ht->next->data, ']')) == 0) {
+ r = "";
+ }
+ strcat(lx, r);
+
+ /*
+ * We keep the the same number of chars from the
+ * adjusted tag number in the current line; any
+ * extra chars due to a digits increase, will be
+ * stuffed into the next line.
+ *
+ * Keep track of any digits added, for the next
+ * pass through.
+ */
+ s = StrNCpy(s, lx, pre_n) + pre_n;
+ lx += pre_n;
+ strcpy(ht->next->data, lx);
+
+ *lx_val = new_n - n;
+ }
+ }
+ break; /* had an lx'er, so we're done with this line */
+ }
+ }
+ }
+
+ *s = '\0';
+
+ n = (int) strlen(ht->data);
+ if (mode == CHOP) {
+ *(buf + n) = '\0';
+ } else if (strlen(buf) > ht->size) {
+ /* we didn't allocate enough space originally - increase it */
+ HTLine *temp;
+
+ allocHTLine(temp, strlen(buf));
+ if (!temp)
+ outofmem(__FILE__, "increment_tagged_htline");
+
+ memcpy(temp, ht, LINE_SIZE(0));
+#if defined(USE_COLOR_STYLE)
+ POOLallocstyles(temp->styles, ht->numstyles);
+ if (!temp->styles)
+ outofmem(__FILE__, "increment_tagged_htline");
+ memcpy(temp->styles, ht->styles, sizeof(HTStyleChange) * ht->numstyles);
+#endif
+ ht = temp;
+ ht->prev->next = ht; /* Link in new line */
+ ht->next->prev = ht; /* Could be same node of course */
+ }
+ strcpy(ht->data, buf);
+
+ return ((int) strlen(buf) - n + fixup);
+}
+
+/*
+ * Creates a new anchor and associated struct's appropriate for a form
+ * TEXTAREA, and links them into the lists following the current anchor
+ * position (as specified by arg1).
+ *
+ * Exits with arg1 now pointing at the new TextAnchor, and arg2 pointing
+ * at the new, associated HTLine.
+ *
+ * --KED 02/13/99
+ */
+static void insert_new_textarea_anchor(TextAnchor **curr_anchor, HTLine **exit_htline)
+{
+ TextAnchor *anchor = *curr_anchor;
+ HTLine *htline;
+
+ TextAnchor *a = 0;
+ FormInfo *f = 0;
+ HTLine *l = 0;
+
+ int curr_tag = 0; /* 0 ==> match any [tag] number */
+ int lx = 0; /* 0 ==> no line crossing [tag]; it's a new line */
+ int i;
+
+ /*
+ * Find line in the text that matches ending anchorline of
+ * the TEXTAREA.
+ *
+ * [Yes, Virginia ... we *do* have to go thru this for each
+ * anchor being added, since there is NOT a 1-to-1 mapping
+ * between anchors and htlines. I suppose we could create
+ * YAS (Yet Another Struct), but there are too many structs{}
+ * floating around in here, as it is. IMNSHO.]
+ */
+ for (htline = FirstHTLine(HTMainText), i = 0; anchor->line_num != i; i++) {
+ htline = htline->next;
+ if (htline == HTMainText->last_line)
+ break;
+ }
+
+ /*
+ * Clone and initialize the struct's needed to add a new TEXTAREA
+ * anchor.
+ */
+ allocHTLine(l, MAX_LINE);
+ POOLtypecalloc(TextAnchor, a);
+
+ POOLtypecalloc(FormInfo, f);
+ if (a == NULL || l == NULL || f == NULL)
+ outofmem(__FILE__, "insert_new_textarea_anchor");
+
+ /* Init all the fields in the new TextAnchor. */
+ /* [anything "special" needed based on ->show_anchor value ?] */
+ a->next = anchor->next;
+ a->number = anchor->number;
+ a->line_pos = anchor->line_pos;
+ a->extent = anchor->extent;
+ a->sgml_offset = SGML_offset();
+ a->line_num = anchor->line_num + 1;
+ LYCopyHiText(a, anchor);
+ a->link_type = anchor->link_type;
+ a->input_field = f;
+ a->show_anchor = anchor->show_anchor;
+ a->inUnderline = anchor->inUnderline;
+ a->expansion_anch = TRUE;
+ a->anchor = NULL;
+
+ /* Just the (seemingly) relevant fields in the new FormInfo. */
+ /* [do we need to do anything "special" based on ->disabled] */
+ StrAllocCopy(f->name, anchor->input_field->name);
+ f->number = anchor->input_field->number;
+ f->type = anchor->input_field->type;
+ StrAllocCopy(f->orig_value, "");
+ f->size = anchor->input_field->size;
+ f->maxlength = anchor->input_field->maxlength;
+ f->no_cache = anchor->input_field->no_cache;
+ f->disabled = anchor->input_field->disabled;
+ f->readonly = anchor->input_field->readonly;
+ f->value_cs = current_char_set; /* use current setting - kw */
+
+ /* Init all the fields in the new HTLine (but see the #if). */
+ l->next = htline->next;
+ l->prev = htline;
+ l->offset = htline->offset;
+ l->size = htline->size;
+#if defined(USE_COLOR_STYLE)
+ /* dup styles[] if needed [no need in TEXTAREA (?); leave 0's] */
+ l->numstyles = htline->numstyles;
+ /*we fork the pointers! */
+ l->styles = htline->styles;
+#endif
+ strcpy(l->data, htline->data);
+
+ /*
+ * Link in the new HTLine.
+ */
+ htline->next->prev = l;
+ htline->next = l;
+
+ if (fields_are_numbered()) {
+ a->number++;
+ increment_tagged_htline(l, a, &lx, &curr_tag, 1, CHOP);
+ }
+
+ /*
+ * If we're at the tail end of the TextAnchor or HTLine list(s),
+ * the new node becomes the last node.
+ */
+ if (anchor == HTMainText->last_anchor)
+ HTMainText->last_anchor = a;
+ if (htline == HTMainText->last_line)
+ HTMainText->last_line = l;
+
+ /*
+ * Link in the new TextAnchor and point the entry anchor arg at it;
+ * point the entry HTLine arg at it, too.
+ */
+ anchor->next = a;
+ *curr_anchor = a;
+
+ *exit_htline = l->next;
+
+ return;
+}
+
+/*
+ * If new anchors were added to expand a TEXTAREA, we need to ripple the
+ * new line numbers [and char counts ?] thru the subsequent anchors.
+ *
+ * If form lines are getting [nnn] tagged, we need to update the displayed
+ * tag values to match (which means rerendering them ... sigh).
+ *
+ * Finally, we need to update various HTMainText and other counts, etc.
+ *
+ * [dunno if the char counts really *need* to be done, or if we're using
+ * the exactly proper values/algorithms ... seems to be OK though ...]
+ *
+ * --KED 02/13/99
+ */
+static void update_subsequent_anchors(int newlines,
+ TextAnchor *start_anchor,
+ HTLine *start_htline,
+ int start_tag)
+{
+ TextAnchor *anchor;
+ HTLine *htline = start_htline;
+
+ int line_adj = 0;
+ int tag_adj = 0;
+ int lx = 0;
+ int hang = 0; /* for HANG detection of a nasty intermittent */
+ int hang_detect = 100000; /* ditto */
+
+ CTRACE((tfp, "GridText: adjusting struct's to add %d new line(s)\n", newlines));
+
+ /*
+ * Update numeric fields of the rest of the anchors.
+ *
+ * [We bypass bumping ->number if it has a value of 0, which takes care
+ * of the ->input_field->type == F_HIDDEN_TYPE (as well as any other
+ * "hidden" anchors, if such things exist). Seems like the "right
+ * thing" to do. I think.]
+ */
+ anchor = start_anchor->next; /* begin updating with the NEXT anchor */
+ while (anchor) {
+ if (fields_are_numbered() &&
+ (anchor->number != 0))
+ anchor->number += newlines;
+ anchor->line_num += newlines;
+ anchor = anchor->next;
+ }
+
+ /*
+ * Update/rerender anchor [tags], if they are being numbered.
+ *
+ * [If a number tag (eg, "[177]") is itself broken across a line
+ * boundary, this fixup only partially works. While the tag
+ * numbering is done properly across the pair of lines, the
+ * horizontal positioning on *either* side of the split, can get
+ * out of sync by a char or two when it gets selected. See the
+ * [comments] in increment_tagged_htline() for some more detail.
+ *
+ * I suppose THE fix is to prevent such tag-breaking in the first
+ * place (dunno where yet, though). Ah well ... at least the tag
+ * numbers themselves are correct from top to bottom now.
+ *
+ * All that said, about the only time this will be a problem in
+ * *practice*, is when a page has near 1000 links or more (possibly
+ * after a TEXTAREA expansion), and has line crossing tag(s), and
+ * the tag numbers in a line crosser go from initially all 3 digit
+ * numbers, to some mix of 3 and 4 digits (or all 4 digits) as a
+ * result of the expansion process. Oh, you also need a "clump" of
+ * anchors all on the same lines.
+ *
+ * Yes, it *can* happen, but in real life, it probably won't be
+ * seen very much ...]
+ *
+ * [This may also be an artifact of bumping into the right hand
+ * screen edge (or RHS margin), since we don't even *think* about
+ * relocating an anchor to the following line, when [tag] digits
+ * expansion pushes things too far in that direction.]
+ */
+ if (fields_are_numbered()) {
+ anchor = start_anchor->next;
+ while (htline != FirstHTLine(HTMainText)) {
+
+ while (anchor) {
+ if ((anchor->number - newlines) == start_tag)
+ break;
+
+ /*** A HANG (infinite loop) *has* occurred here, with */
+ /*** the values of anchor and anchor->next being the */
+ /*** the same, OR with anchor->number "magically" and */
+ /*** suddenly taking on an anchor-pointer-like value. */
+ /*** */
+ /*** The same code and same doc have both passed and */
+ /*** failed at different times, which indicates some */
+ /*** sort of content/html dependency, or some kind of */
+ /*** a "race" condition, but I'll be damned if I can */
+ /*** find it after tons of CTRACE's, printf()'s, gdb */
+ /*** breakpoints and watchpoints, etc. */
+ /*** */
+ /*** I have added a hang detector (with error msg and */
+ /*** beep) here, to break the loop and warn the user, */
+ /*** until it can be isolated and fixed. */
+ /*** */
+ /*** [One UGLY intermittent .. gak ..! 02/22/99 KED] */
+
+ hang++;
+ if ((anchor == anchor->next) || (hang >= hang_detect))
+ goto hang_detected;
+
+ anchor = anchor->next;
+ }
+
+ if (anchor) {
+ line_adj = increment_tagged_htline(htline, anchor, &lx,
+ &start_tag, newlines,
+ NOCHOP);
+ htline->size = (unsigned short) (htline->size + line_adj);
+ tag_adj += line_adj;
+
+ } else {
+
+ break; /* out of anchors ... we're done */
+ }
+
+ htline = htline->next;
+ }
+ }
+
+ finish:
+ /*
+ * Fixup various global variables.
+ */
+ nlinks += newlines;
+ HTMainText->Lines += newlines;
+ HTMainText->last_anchor_number += newlines;
+
+ more_text = HText_canScrollDown();
+
+ CTRACE((tfp, "GridText: TextAnchor and HTLine struct's adjusted\n"));
+
+ return;
+
+ hang_detected: /* ugliness has happened; inform user and do the best we can */
+
+ HTAlert(gettext("Hang Detect: TextAnchor struct corrupted - suggest aborting!"));
+ goto finish;
+}
+
+/*
+ * Check if the given anchor is a TEXTAREA belonging to the given form.
+ *
+ * KED's note -
+ * [Finding the TEXTAREA we're actually *in* with these attributes isn't
+ * foolproof. The form number isn't unique to a given TEXTAREA, and there
+ * *could* be TEXTAREA's with the same "name". If that should ever be true,
+ * we'll actually get the data from the *1st* TEXTAREA in the page that
+ * matches. We should probably assign a unique id to each TEXTAREA in a page,
+ * and match on that, to avoid this (potential) problem.
+ *
+ * Since the odds of "false matches" *actually* happening in real life seem
+ * rather small though, we'll hold off doing this, for a rainy day ...]
+ */
+static BOOLEAN IsFormsTextarea(FormInfo * form, TextAnchor *anchor_ptr)
+{
+ return (BOOLEAN) ((anchor_ptr->link_type == INPUT_ANCHOR) &&
+ (anchor_ptr->input_field->type == F_TEXTAREA_TYPE) &&
+ (anchor_ptr->input_field->number == form->number) &&
+ !strcmp(anchor_ptr->input_field->name, form->name));
+}
+
+static char *readEditedFile(char *ed_temp)
+{
+ struct stat stat_info;
+ size_t size;
+
+ FILE *fp;
+
+ char *ebuf;
+
+ CTRACE((tfp, "GridText: entered HText_EditTextArea()\n"));
+
+ /*
+ * Read back the edited temp file into our buffer.
+ */
+ if ((stat(ed_temp, &stat_info) < 0) ||
+ !S_ISREG(stat_info.st_mode) ||
+ ((size = (size_t) stat_info.st_size) == 0)) {
+ size = 0;
+ ebuf = typecalloc(char);
+
+ if (!ebuf)
+ outofmem(__FILE__, "HText_EditTextArea");
+ } else {
+ ebuf = typecallocn(char, size + 1);
+
+ if (!ebuf) {
+ /*
+ * This could be huge - don't exit if we don't have enough
+ * memory for it. With some luck, the user may be even able
+ * to recover the file manually from the temp space while
+ * the lynx session is not over. - kw
+ */
+ HTAlwaysAlert(NULL, MEMORY_EXHAUSTED_FILE);
+ return 0;
+ }
+
+ if ((fp = fopen(ed_temp, "r")) != 0) {
+ size = fread(ebuf, (size_t) 1, size, fp);
+ LYCloseInput(fp);
+ ebuf[size] = '\0'; /* Terminate! - kw */
+ } else {
+ size = 0;
+ }
+ }
+
+ /*
+ * Nuke any blank lines from the end of the edited data.
+ */
+ while ((size != 0)
+ && (CanTrimTextArea(UCH(ebuf[size - 1])) || (ebuf[size - 1] == '\0')))
+ ebuf[--size] = '\0';
+
+ return ebuf;
+}
+
+static int finish_ExtEditForm(LinkInfo * form_link, TextAnchor *start_anchor,
+ char *ed_temp,
+ int orig_cnt)
+{
+ TextAnchor *anchor_ptr;
+ TextAnchor *end_anchor = NULL;
+ BOOLEAN wrapalert = FALSE;
+
+ int entry_line = form_link->anchor_line_num;
+ int exit_line = 0;
+ int line_cnt = 1;
+
+ HTLine *htline = NULL;
+
+ char *ebuf;
+ char *line;
+ char *lp;
+ char *cp;
+ int match_tag = 0;
+ int newlines = 0;
+ int len, len0;
+ int display_size;
+ int wanted_fieldlen_wrap = -1; /* not yet asked; 0 means don't. */
+ char *skip_at = NULL;
+ int skip_num = 0, i;
+ size_t line_used = MAX_LINE;
+
+ CTRACE((tfp, "GridText: entered HText_EditTextArea()\n"));
+
+ if ((ebuf = readEditedFile(ed_temp)) == 0) {
+ return 0;
+ }
+
+ /*
+ * Copy each line from the temp file into the corresponding anchor
+ * struct. Add new lines to the TEXTAREA if needed. (Always leave
+ * the user with a blank line at the end of the TEXTAREA.)
+ */
+ if ((line = typeMallocn(char, line_used)) == 0)
+ outofmem(__FILE__, "HText_EditTextArea");
+
+ anchor_ptr = start_anchor;
+ display_size = anchor_ptr->input_field->size;
+ if (display_size <= 4 ||
+ display_size >= MAX_LINE)
+ wanted_fieldlen_wrap = 0;
+
+ len = 0;
+ lp = ebuf;
+
+ while ((line_cnt <= orig_cnt) || (*lp) || ((len != 0) && (*lp == '\0'))) {
+
+ if (skip_at) {
+ len0 = (int) (skip_at - lp);
+ LYStrNCpy(line, lp, len0);
+ lp = skip_at + skip_num;
+ skip_at = NULL;
+ skip_num = 0;
+
+ assert(lp != NULL);
+ } else {
+ len0 = 0;
+ }
+ line[len0] = '\0';
+
+ if ((cp = StrChr(lp, '\n')) != 0)
+ len = (int) (cp - lp);
+ else
+ len = (int) strlen(lp);
+
+ if (wanted_fieldlen_wrap < 0 &&
+ !wrapalert &&
+ len0 + len >= display_size &&
+ (cp = StrChr(lp, ' ')) != NULL &&
+ (cp - lp) < display_size - 1) {
+
+ LYFixCursesOn("ask for confirmation:");
+ LYerase(); /* don't show previous state */
+ if (HTConfirmDefault(gettext("Wrap lines to fit displayed area?"),
+ NO)) {
+ wanted_fieldlen_wrap = display_size - 1;
+ } else {
+ wanted_fieldlen_wrap = 0;
+ }
+ }
+
+ if (wanted_fieldlen_wrap > 0 &&
+ len0 + len > wanted_fieldlen_wrap) {
+
+ for (i = wanted_fieldlen_wrap - len0;
+ i + len0 >= wanted_fieldlen_wrap / 4; i--) {
+
+ if (isspace(UCH(lp[i]))) {
+ len = i + 1;
+ cp = lp + i;
+ if (cp[1] != '\n' &&
+ isspace(UCH(cp[1])) &&
+ !isspace(UCH(cp[2]))) {
+ len++;
+ cp++;
+ }
+ if (!isspace(UCH(cp[1]))) {
+ while (*cp && *cp != '\r' && *cp != '\n' &&
+ (cp - lp) <= len + (3 * wanted_fieldlen_wrap / 4))
+ cp++; /* search for next line break */
+ if (*cp == '\r' && cp[1] == '\n')
+ cp++;
+ if (*cp == '\n' &&
+ (cp[1] == '\r' || cp[1] == '\n' ||
+ !isspace(UCH(cp[1])))) {
+ *cp = ' ';
+ while (isspace(UCH(*(cp - 1)))) {
+ skip_num++;
+ cp--;
+ }
+ skip_at = cp;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if (wanted_fieldlen_wrap > 0 &&
+ (len0 + len) > wanted_fieldlen_wrap) {
+
+ i = len - 1;
+ while (len0 + i + 1 > wanted_fieldlen_wrap &&
+ isspace(UCH(lp[i])))
+ i--;
+ if (len0 + i + 1 > wanted_fieldlen_wrap)
+ len = wanted_fieldlen_wrap - len0;
+ }
+
+ /*
+ * Check if the new text will fit in the buffer. HTML does not define
+ * a "maxlength" attribute for TEXTAREA; its data can grow as needed.
+ * Lynx will not adjust the display to reflect larger amounts of text;
+ * it relies on the rows/cols attributes as well as the initial content
+ * of the text area for the layout.
+ */
+ if ((size_t) (len0 + len) >= line_used) {
+ line_used = (size_t) (3 * (len0 + len)) / 2;
+ if ((line = typeRealloc(char, line, line_used)) == 0)
+ outofmem(__FILE__, "HText_EditTextArea");
+ }
+
+ strncat(line, lp, (size_t) len);
+ *(line + len0 + len) = '\0';
+
+ /*
+ * If there are more lines in the edit buffer than were in the
+ * original TEXTAREA, we need to add a new line/anchor, continuing
+ * on until the edit buffer is empty.
+ */
+ if (line_cnt > orig_cnt) {
+ insert_new_textarea_anchor(&end_anchor, &htline);
+
+ assert(end_anchor != NULL);
+ assert(end_anchor->input_field != NULL);
+
+ anchor_ptr = end_anchor; /* make the new anchor current */
+ newlines++;
+ }
+
+ assert(anchor_ptr != NULL);
+
+ /*
+ * Finally copy the new line from the edit buffer into the anchor.
+ */
+ StrAllocCopy(anchor_ptr->input_field->value, line);
+
+ /*
+ * Keep track of 1st blank line in any trailing blank lines, for
+ * later cursor repositioning.
+ */
+ if (len0 + len > 0)
+ exit_line = 0;
+ else if (exit_line == 0)
+ exit_line = anchor_ptr->line_num;
+
+ /*
+ * And do the next line of edited text, for the next anchor ...
+ */
+ lp += len;
+ if (*lp && isspace(UCH(*lp)))
+ lp++;
+
+ end_anchor = anchor_ptr;
+ anchor_ptr = anchor_ptr->next;
+
+ if (anchor_ptr)
+ match_tag = anchor_ptr->number;
+
+ line_cnt++;
+ }
+
+ CTRACE((tfp, "GridText: edited text inserted into lynx struct's\n"));
+
+ /*
+ * If we've added any new lines/anchors, we need to adjust various
+ * things in all anchor-bearing lines following the last newly added
+ * line/anchor. The fun stuff starts here ...
+ */
+ if (newlines > 0)
+ update_subsequent_anchors(newlines, end_anchor, htline, match_tag);
+
+ /*
+ * Cleanup time.
+ */
+ FREE(line);
+ FREE(ebuf);
+
+ /*
+ * Return the offset needed to move the cursor from its current
+ * (on entry) line number, to the 1st blank line of the trailing
+ * (group of) blank line(s), which is where we want to be. Let
+ * the caller deal with moving us there, however ... :-) ...
+ */
+ return (exit_line - entry_line);
+}
+
+/*
+ * Transfer the initial contents of a TEXTAREA to a temp file, invoke the
+ * user's editor on that file, then transfer the contents of the resultant
+ * edited file back into the TEXTAREA (expanding the size of the area, if
+ * required).
+ *
+ * Returns the number of lines that the cursor should be moved so that it
+ * will end up on the 1st blank line of whatever number of trailing blank
+ * lines there are in the TEXTAREA (there will *always* be at least one).
+ *
+ * --KED 02/01/99
+ */
+int HText_EditTextArea(LinkInfo * form_link)
+{
+ char *ed_temp;
+ FILE *fp;
+
+ TextAnchor *anchor_ptr;
+ TextAnchor *start_anchor = NULL;
+ BOOLEAN firstanchor = TRUE;
+
+ char ed_offset[DigitsOf(int) + 3];
+ int start_line = 0;
+ int entry_line = form_link->anchor_line_num;
+ int orig_cnt = 0;
+ int offset = 0;
+
+ FormInfo *form = form_link->l_form;
+
+ CTRACE((tfp, "GridText: entered HText_EditTextArea()\n"));
+
+ if ((ed_temp = typeMallocn(char, LY_MAXPATH)) == 0) {
+ outofmem(__FILE__, "HText_EditTextArea");
+ } else if ((fp = LYOpenTemp(ed_temp, "", "w")) != 0) {
+
+ /*
+ * Begin at the beginning, to find 1st anchor in the TEXTAREA, then
+ * write all of its lines (anchors) out to the edit temp file.
+ */
+ anchor_ptr = HTMainText->first_anchor;
+
+ while (anchor_ptr) {
+
+ if (IsFormsTextarea(form, anchor_ptr)) {
+
+ if (firstanchor) {
+ firstanchor = FALSE;
+ start_anchor = anchor_ptr;
+ start_line = anchor_ptr->line_num;
+ }
+ orig_cnt++;
+
+ /*
+ * Write the anchors' text to the temp edit file.
+ */
+ fputs(anchor_ptr->input_field->value, fp);
+ fputc('\n', fp);
+
+ } else {
+
+ if (!firstanchor)
+ break;
+ }
+ anchor_ptr = anchor_ptr->next;
+ }
+ LYCloseTempFP(fp);
+
+ if (start_anchor != 0) {
+ CTRACE((tfp, "GridText: TEXTAREA name=|%s| dumped to tempfile\n", form->name));
+ CTRACE((tfp, "GridText: invoking editor (%s) on tempfile\n", editor));
+
+ /*
+ * Go edit the TEXTAREA temp file, with the initial editor line
+ * corresponding to the TEXTAREA line the cursor is on (if such
+ * positioning is supported by the editor [as lynx knows it]).
+ */
+ ed_offset[0] = 0; /* pre-ANSI compilers don't initialize aggregates - TD */
+ if (((entry_line - start_line) > 0) && editor_can_position())
+ sprintf(ed_offset, "%d", ((entry_line - start_line) + 1));
+
+ edit_temporary_file(ed_temp, ed_offset, NULL);
+
+ CTRACE((tfp, "GridText: returned from editor (%s)\n", editor));
+
+ if (!form->disabled)
+ offset = finish_ExtEditForm(form_link, start_anchor, ed_temp, orig_cnt);
+
+ CTRACE((tfp, "GridText: exiting HText_EditTextArea()\n"));
+ }
+ (void) LYRemoveTemp(ed_temp);
+ FREE(ed_temp);
+ }
+
+ /*
+ * Return the offset needed to move the cursor from its current
+ * (on entry) line number, to the 1st blank line of the trailing
+ * (group of) blank line(s), which is where we want to be. Let
+ * the caller deal with moving us there, however ... :-) ...
+ */
+ return offset;
+}
+
+/*
+ * Similar to HText_EditTextArea, but assume a single-line text field -TD
+ */
+void HText_EditTextField(LinkInfo * form_link)
+{
+ char *ed_temp;
+ FILE *fp;
+
+ FormInfo *form = form_link->l_form;
+
+ CTRACE((tfp, "GridText: entered HText_EditTextField()\n"));
+
+ ed_temp = typeMallocn(char, LY_MAXPATH);
+
+ if ((fp = LYOpenTemp(ed_temp, "", "w")) == 0) {
+ FREE(ed_temp);
+ return;
+ }
+
+ /*
+ * Write the anchors' text to the temp edit file.
+ */
+ fputs(form->value, fp);
+ fputc('\n', fp);
+
+ LYCloseTempFP(fp);
+
+ CTRACE((tfp, "GridText: text field |%s| dumped to tempfile\n", form_link->lname));
+ CTRACE((tfp, "GridText: invoking editor (%s) on tempfile\n", editor));
+
+ edit_temporary_file(ed_temp, "", NULL);
+
+ CTRACE((tfp, "GridText: returned from editor (%s)\n", editor));
+
+ if (!form->disabled) {
+ char *ebuf;
+ char *p;
+
+ if ((ebuf = readEditedFile(ed_temp)) != 0) {
+ /*
+ * Only use the first line of the result.
+ */
+ for (p = ebuf; *p != '\0'; ++p) {
+ if (*p == '\n' || *p == '\r') {
+ *p = '\0';
+ break;
+ }
+ }
+ StrAllocCopy(form->value, ebuf);
+ FREE(ebuf);
+ }
+ }
+
+ (void) LYRemoveTemp(ed_temp);
+ FREE(ed_temp);
+
+ CTRACE((tfp, "GridText: exiting HText_EditTextField()\n"));
+}
+
+/*
+ * Expand the size of a TEXTAREA by a fixed number of lines (as specified
+ * by arg2).
+ *
+ * --KED 02/14/99
+ */
+void HText_ExpandTextarea(LinkInfo * form_link, int newlines)
+{
+ TextAnchor *anchor_ptr;
+ TextAnchor *end_anchor = NULL;
+ BOOLEAN firstanchor = TRUE;
+
+ FormInfo *form = form_link->l_form;
+
+ HTLine *htline = NULL;
+
+ int match_tag = 0;
+ int i;
+
+ CTRACE((tfp, "GridText: entered HText_ExpandTextarea()\n"));
+
+ if (newlines < 1)
+ return;
+
+ /*
+ * Begin at the beginning, to find the TEXTAREA, then on to find
+ * the last line (anchor) in it.
+ */
+ anchor_ptr = HTMainText->first_anchor;
+
+ while (anchor_ptr) {
+
+ if (IsFormsTextarea(form, anchor_ptr)) {
+
+ if (firstanchor)
+ firstanchor = FALSE;
+
+ end_anchor = anchor_ptr;
+
+ } else {
+
+ if (!firstanchor)
+ break;
+ }
+ anchor_ptr = anchor_ptr->next;
+ }
+
+ for (i = 1; i <= newlines; i++) {
+ insert_new_textarea_anchor(&end_anchor, &htline);
+
+ /*
+ * Make the new line blank.
+ */
+ StrAllocCopy(end_anchor->input_field->value, "");
+
+ /*
+ * And go add another line ...
+ */
+ if (end_anchor->next)
+ match_tag = end_anchor->next->number;
+ }
+
+ CTRACE((tfp, "GridText: %d blank line(s) added to TEXTAREA name=|%s|\n",
+ newlines, form->name));
+
+ /*
+ * We need to adjust various things in all anchor bearing lines
+ * following the last newly added line/anchor. Fun stuff.
+ */
+ update_subsequent_anchors(newlines, end_anchor, htline, match_tag);
+
+ CTRACE((tfp, "GridText: exiting HText_ExpandTextarea()\n"));
+
+ return;
+}
+
+/*
+ * Insert the contents of a file into a TEXTAREA between the cursor line,
+ * and the line preceding it.
+ *
+ * Returns the number of lines that the cursor should be moved so that it
+ * will end up on the 1st line in the TEXTAREA following the inserted file
+ * (if we decide to do that).
+ *
+ * --KED 02/21/99
+ */
+int HText_InsertFile(LinkInfo * form_link)
+{
+ struct stat stat_info;
+ size_t size;
+
+ FILE *fp;
+ char *fn;
+
+ TextAnchor *anchor_ptr;
+ TextAnchor *prev_anchor = NULL;
+ TextAnchor *end_anchor = NULL;
+ BOOLEAN firstanchor = TRUE;
+ BOOLEAN truncalert = FALSE;
+
+ FormInfo *form = form_link->l_form;
+
+ HTLine *htline = NULL;
+
+ TextAnchor *a = 0;
+ FormInfo *f = 0;
+ HTLine *l = 0;
+
+ char *fbuf = 0;
+ char *line = 0;
+ char *lp;
+ char *cp;
+ int entry_line = form_link->anchor_line_num;
+ int file_cs;
+ int match_tag = 0;
+ int newlines = 0;
+ int len;
+ int i;
+
+ CTRACE((tfp, "GridText: entered HText_InsertFile()\n"));
+
+ /*
+ * Get the filename of the insert file.
+ */
+ if (!(fn = GetFileName())) {
+ HTInfoMsg(FILE_INSERT_CANCELLED);
+ CTRACE((tfp,
+ "GridText: file insert cancelled - no filename provided\n"));
+ return (0);
+ }
+ if (no_dotfiles || !show_dotfiles) {
+ if (*LYPathLeaf(fn) == '.') {
+ HTUserMsg(FILENAME_CANNOT_BE_DOT);
+ return (0);
+ }
+ }
+
+ /*
+ * Read it into our buffer (abort on 0-length file).
+ */
+ if ((stat(fn, &stat_info) < 0) ||
+ ((size = (size_t) stat_info.st_size) == 0)) {
+ HTInfoMsg(FILE_INSERT_0_LENGTH);
+ CTRACE((tfp,
+ "GridText: file insert aborted - file=|%s|- was 0-length\n",
+ fn));
+ FREE(fn);
+ return (0);
+
+ } else {
+
+ if ((fbuf = typecallocn(char, size + 1)) == NULL) {
+ /*
+ * This could be huge - don't exit if we don't have enough
+ * memory for it. - kw
+ */
+ free(fn);
+ HTAlert(MEMORY_EXHAUSTED_FILE);
+ return 0;
+ }
+
+ /* Try to make the same assumption for the charset of the inserted
+ * file as we would for normal loading of that file, i.e. taking
+ * assume_local_charset and suffix mappings into account.
+ * If there is a mismatch with the display character set, characters
+ * may be displayed wrong, too bad; but the user has a chance to
+ * correct this by editing the lines, which will update f->value_cs
+ * again. - kw
+ */
+ LYGetFileInfo(fn, 0, 0, 0, 0, 0, &file_cs);
+
+ fp = fopen(fn, "r");
+ if (!fp) {
+ free(fbuf);
+ free(fn);
+ HTAlert(FILE_CANNOT_OPEN_R);
+ return 0;
+ }
+ size = fread(fbuf, (size_t) 1, size, fp);
+ LYCloseInput(fp);
+ FREE(fn);
+ fbuf[size] = '\0'; /* Terminate! - kw */
+ }
+
+ /*
+ * Begin at the beginning, to find the TEXTAREA we're in, then
+ * the current cursorline.
+ */
+ anchor_ptr = HTMainText->first_anchor;
+
+ while (anchor_ptr) {
+
+ if (IsFormsTextarea(form, anchor_ptr)) {
+ if (anchor_ptr->line_num == entry_line)
+ break;
+ }
+ prev_anchor = anchor_ptr;
+ anchor_ptr = anchor_ptr->next;
+ }
+
+ if (anchor_ptr == NULL) {
+ CTRACE((tfp, "BUG: could not find anchor for TEXTAREA.\n"));
+ FREE(line);
+ FREE(fbuf);
+ return 0;
+ }
+
+ /*
+ * Clone a new TEXTAREA line/anchor using the cursorline anchor as
+ * a template, but link it in BEFORE the cursorline anchor/htline.
+ *
+ * [We can probably combine this with insert_new_textarea_anchor()
+ * along with a flag to indicate "insert before" as we do here,
+ * or the "normal" mode of operation (add after "current" anchor/
+ * line). Beware of the differences ... some are a bit subtle to
+ * notice.]
+ */
+ for (htline = FirstHTLine(HTMainText), i = 0;
+ anchor_ptr->line_num != i; i++) {
+ htline = htline->next;
+ if (htline == HTMainText->last_line)
+ break;
+ }
+
+ allocHTLine(l, MAX_LINE);
+ POOLtypecalloc(TextAnchor, a);
+
+ POOLtypecalloc(FormInfo, f);
+ if (a == NULL || l == NULL || f == NULL)
+ outofmem(__FILE__, "HText_InsertFile");
+
+ /* Init all the fields in the new TextAnchor. */
+ /* [anything "special" needed based on ->show_anchor value ?] */
+ /* *INDENT-EQLS* */
+ a->next = anchor_ptr;
+ a->number = anchor_ptr->number;
+ a->show_number = anchor_ptr->show_number;
+ a->line_pos = anchor_ptr->line_pos;
+ a->extent = anchor_ptr->extent;
+ a->sgml_offset = SGML_offset();
+ a->line_num = anchor_ptr->line_num;
+ LYCopyHiText(a, anchor_ptr);
+ a->link_type = anchor_ptr->link_type;
+ a->input_field = f;
+ a->show_anchor = anchor_ptr->show_anchor;
+ a->inUnderline = anchor_ptr->inUnderline;
+ a->expansion_anch = TRUE;
+ a->anchor = NULL;
+
+ /* Just the (seemingly) relevant fields in the new FormInfo. */
+ /* [do we need to do anything "special" based on ->disabled] */
+ StrAllocCopy(f->name, anchor_ptr->input_field->name);
+ f->number = anchor_ptr->input_field->number;
+ f->type = anchor_ptr->input_field->type;
+ StrAllocCopy(f->orig_value, "");
+ f->size = anchor_ptr->input_field->size;
+ f->maxlength = anchor_ptr->input_field->maxlength;
+ f->no_cache = anchor_ptr->input_field->no_cache;
+ f->disabled = anchor_ptr->input_field->disabled;
+ f->readonly = anchor_ptr->input_field->readonly;
+ f->value_cs = (file_cs >= 0) ? file_cs : current_char_set;
+
+ /* Init all the fields in the new HTLine (but see the #if). */
+ l->offset = htline->offset;
+ l->size = htline->size;
+#if defined(USE_COLOR_STYLE)
+ /* dup styles[] if needed [no need in TEXTAREA (?); leave 0's] */
+ l->numstyles = htline->numstyles;
+ /*we fork the pointers! */
+ l->styles = htline->styles;
+#endif
+ strcpy(l->data, htline->data);
+
+ /*
+ * If we're at the head of the TextAnchor list, the new node becomes
+ * the first node.
+ */
+ if (anchor_ptr == HTMainText->first_anchor)
+ HTMainText->first_anchor = a;
+
+ /*
+ * Link in the new TextAnchor, and corresponding HTLine.
+ */
+ if (prev_anchor)
+ prev_anchor->next = a;
+
+ htline = htline->prev;
+ l->next = htline->next;
+ l->prev = htline;
+ htline->next->prev = l;
+ htline->next = l;
+
+ /*
+ * update_subsequent_anchors() expects htline to point to 1st potential
+ * line needing fixup; we need to do this just in case the inserted file
+ * was only a single line (yes, it's pathological ... ).
+ */
+ htline = htline->next; /* ->new (current) htline, for 1st inserted line */
+ htline = htline->next; /* ->1st potential (following) [tag] fixup htline */
+
+ anchor_ptr = a;
+ newlines++;
+
+ /*
+ * Copy each line from the insert file into the corresponding anchor
+ * struct.
+ *
+ * Begin with the new line/anchor we just added (above the cursorline).
+ */
+ if ((line = typeMallocn(char, MAX_LINE)) == 0)
+ outofmem(__FILE__, "HText_InsertFile");
+
+ match_tag = anchor_ptr->number;
+
+ lp = fbuf;
+
+ while (*lp) {
+
+ if ((cp = StrChr(lp, '\n')) != 0)
+ len = (int) (cp - lp);
+ else
+ len = (int) strlen(lp);
+
+ if (len >= MAX_LINE) {
+ if (!truncalert) {
+ HTAlert(gettext("Very long lines have been truncated!"));
+ truncalert = TRUE;
+ }
+ len = MAX_LINE - 1;
+ if (lp[len])
+ lp[len + 1] = '\0'; /* prevent next iteration */
+ }
+ LYStrNCpy(line, lp, len);
+
+ /*
+ * If not the first line from the insert file, we need to add
+ * a new line/anchor, continuing on until the buffer is empty.
+ */
+ if (!firstanchor) {
+ insert_new_textarea_anchor(&end_anchor, &htline);
+ anchor_ptr = end_anchor; /* make the new anchor current */
+ newlines++;
+ }
+
+ /*
+ * Copy the new line from the buffer into the anchor.
+ */
+ StrAllocCopy(anchor_ptr->input_field->value, line);
+
+ /*
+ * insert_new_textarea_anchor always uses current_char_set,
+ * we may want something else, so fix it up. - kw
+ */
+ if (file_cs >= 0)
+ anchor_ptr->input_field->value_cs = file_cs;
+
+ /*
+ * And do the next line of insert text, for the next anchor ...
+ */
+ lp += len;
+ if (*lp)
+ lp++;
+
+ firstanchor = FALSE;
+ end_anchor = anchor_ptr;
+ anchor_ptr = anchor_ptr->next;
+ }
+
+ CTRACE((tfp, "GridText: file inserted into lynx struct's\n"));
+
+ /*
+ * Now adjust various things in all anchor-bearing lines following the
+ * last newly added line/anchor. Some say this is the fun part ...
+ */
+ update_subsequent_anchors(newlines, end_anchor, htline, match_tag);
+
+ /*
+ * Cleanup time.
+ */
+ FREE(line);
+ FREE(fbuf);
+
+ CTRACE((tfp, "GridText: exiting HText_InsertFile()\n"));
+
+ return (newlines);
+}
+
+#ifdef USE_COLOR_STYLE
+static int GetColumn(void)
+{
+ int result;
+
+#ifdef USE_SLANG
+ result = SLsmg_get_column();
+#else
+ int y, x;
+
+ LYGetYX(y, x);
+ result = x;
+ (void) y;
+#endif
+ return result;
+}
+
+static BOOL DidWrap(int y0, int x0)
+{
+ BOOL result = NO;
+
+#ifndef USE_SLANG
+ int y, x;
+
+ LYGetYX(y, x);
+ (void) x0;
+ if (x >= DISPLAY_COLS || ((x == 0) && (y != y0)))
+ result = YES;
+#endif
+ return result;
+}
+#endif /* USE_COLOR_STYLE */
+
+/*
+ * This function draws the part of line 'line', pointed by 'str' (which can be
+ * non terminated with null - i.e., is line->data+N) drawing 'len' bytes (not
+ * characters) of it. It doesn't check whether the 'len' bytes crosses a
+ * character boundary (if multibyte chars are in string). Assumes that the
+ * cursor is positioned in the place where the 1st char of string should be
+ * drawn.
+ *
+ * This code is based on display_line. This code was tested with ncurses only
+ * (since no support for lss is available for Slang) -HV.
+ */
+#ifdef USE_COLOR_STYLE
+static void redraw_part_of_line(HTLine *line, const char *str,
+ int len,
+ HText *text)
+{
+ register int i;
+ char buffer[7];
+ const char *data, *end_of_data;
+ size_t utf_extra = 0;
+
+#ifdef USE_COLOR_STYLE
+ int current_style = 0;
+ int tcols, scols;
+#endif
+ char LastDisplayChar = ' ';
+ int YP, XP;
+
+ LYGetYX(YP, XP);
+
+ i = XP;
+
+ /* Set up the multibyte character buffer */
+ buffer[0] = buffer[1] = buffer[2] = '\0';
+
+ data = str;
+ end_of_data = data + len;
+ i++;
+
+ /* this assumes that the part of line to be drawn fits in the screen */
+ while (data < end_of_data) {
+ buffer[0] = *data;
+ data++;
+
+#if defined(USE_COLOR_STYLE)
+#define CStyle line->styles[current_style]
+
+ tcols = GetColumn();
+ scols = StyleToCols(text, line, current_style);
+
+ while (current_style < line->numstyles &&
+ tcols >= scols) {
+ LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction);
+ current_style++;
+ scols = StyleToCols(text, line, current_style);
+ }
+#endif
+ switch (buffer[0]) {
+
+#ifndef USE_COLOR_STYLE
+ case LY_UNDERLINE_START_CHAR:
+ if (dump_output_immediately && use_underscore) {
+ LYaddch('_');
+ i++;
+ } else {
+ lynx_start_underline();
+ }
+ break;
+
+ case LY_UNDERLINE_END_CHAR:
+ if (dump_output_immediately && use_underscore) {
+ LYaddch('_');
+ i++;
+ } else {
+ lynx_stop_underline();
+ }
+ break;
+
+ case LY_BOLD_START_CHAR:
+ lynx_start_bold();
+ break;
+
+ case LY_BOLD_END_CHAR:
+ lynx_stop_bold();
+ break;
+
+#endif
+ case LY_SOFT_NEWLINE:
+ if (!dump_output_immediately) {
+ LYaddch('+');
+ i++;
+ }
+ break;
+
+ case LY_SOFT_HYPHEN:
+ if (*data != '\0' ||
+ isspace(UCH(LastDisplayChar)) ||
+ LastDisplayChar == '-') {
+ /*
+ * Ignore the soft hyphen if it is not the last character in
+ * the line. Also ignore it if it first character following
+ * the margin, or if it is preceded by a white character (we
+ * loaded 'M' into LastDisplayChar if it was a multibyte
+ * character) or hyphen, though it should have been excluded by
+ * HText_appendCharacter() or by split_line() in those cases.
+ * -FM
+ */
+ break;
+ } else {
+ /*
+ * Make it a hard hyphen and fall through. -FM
+ */
+ buffer[0] = '-';
+ }
+ /* FALLTHRU */
+
+ default:
+ if (text->T.output_utf8 && is8bits(buffer[0])) {
+ utf_extra = utf8_length(text->T.output_utf8, data - 1);
+ LastDisplayChar = 'M';
+ }
+ if (utf_extra) {
+ LYStrNCpy(&buffer[1], data, utf_extra);
+ LYaddstr(buffer);
+ buffer[1] = '\0';
+ data += utf_extra;
+ utf_extra = 0;
+ } else if (is_CJK2(buffer[0])) {
+ /*
+ * For CJK strings, by Masanobu Kimura.
+ */
+ if (i <= DISPLAY_COLS) {
+ buffer[1] = *data;
+ buffer[2] = '\0';
+ data++;
+ i++;
+ LYaddstr(buffer);
+ buffer[1] = '\0';
+ /*
+ * For now, load 'M' into LastDisplayChar, but we should
+ * check whether it's white and if so, use ' '. I don't
+ * know if there actually are white CJK characters, and
+ * we're loading ' ' for multibyte spacing characters in
+ * this code set, but this will become an issue when the
+ * development code set's multibyte character handling is
+ * used. -FM
+ */
+ LastDisplayChar = 'M';
+ }
+ } else {
+ LYaddstr(buffer);
+ LastDisplayChar = buffer[0];
+ }
+ if (DidWrap(YP, XP))
+ break;
+ i++;
+ } /* end of switch */
+ } /* end of while */
+
+#ifndef USE_COLOR_STYLE
+ lynx_stop_underline();
+ lynx_stop_bold();
+#else
+
+ while (current_style < line->numstyles) {
+ LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction);
+ current_style++;
+ }
+
+#undef CStyle
+#endif
+ return;
+}
+#endif /* USE_COLOR_STYLE */
+
+#ifndef USE_COLOR_STYLE
+/*
+ * Function move_to_glyph is called from LYMoveToLink and does all
+ * the real work for it.
+ * The pair LYMoveToLink()/move_to_glyph() is similar to the pair
+ * redraw_lines_of_link()/redraw_part_of_line(), some key differences:
+ * LYMoveToLink/move_to_glyph redraw_*
+ * -----------------------------------------------------------------
+ * - used without color style - used with color style
+ * - handles showing WHEREIS target - WHEREIS handled elsewhere
+ * - handles only one line - handles first two lines for
+ * hypertext anchors
+ * - right columns position for UTF-8
+ * by redrawing as necessary
+ * - currently used for highlight - currently used for highlight
+ * ON and OFF OFF
+ *
+ * Eventually the two sets of function should be unified, and should handle
+ * UTF-8 positioning, both lines of hypertext anchors, and WHEREIS in all
+ * cases. If possible. The complex WHEREIS target logic in LYhighlight()
+ * could then be completely removed. - kw
+ */
+static void move_to_glyph(int YP,
+ int XP,
+ int XP_draw_min,
+ const char *data,
+ int datasize,
+ unsigned offset,
+ const char *target,
+ const char *hightext,
+ int flags,
+ int utf_flag)
+{
+ char buffer[7];
+ const char *end_of_data;
+ size_t utf_extra = 0;
+
+#if defined(SHOW_WHEREIS_TARGETS)
+ const char *cp_tgt;
+ int i_start_tgt = 0, i_after_tgt;
+ int HitOffset, LenNeeded;
+#endif /* SHOW_WHEREIS_TARGETS */
+ BOOL intarget = NO;
+ BOOL inunderline = NO;
+ BOOL inbold = NO;
+ BOOL drawing = NO;
+ BOOL inU = NO;
+ BOOL hadutf8 = NO;
+ BOOL incurlink = NO;
+ BOOL drawingtarget = NO;
+ BOOL flag = NO;
+ const char *sdata = data;
+ char LastDisplayChar = ' ';
+
+ int i = (int) offset; /* FIXME: should be columns, not offset? */
+ int last_i = DISPLAY_COLS;
+ int XP_link = XP; /* column of link */
+ int XP_next = XP; /* column to move to when done drawing */
+ int linkvlen;
+
+ int len;
+
+ if (no_title)
+ YP -= TITLE_LINES;
+
+ if (flags & 1)
+ flag = YES;
+ if (flags & 2)
+ inU = YES;
+ /* Set up the multibyte character buffer */
+ buffer[0] = buffer[1] = buffer[2] = '\0';
+ /*
+ * Add offset, making sure that we do not
+ * go over the COLS limit on the display.
+ */
+ if (hightext != 0) {
+#ifdef WIDEC_CURSES
+ last_i = i + LYstrExtent2(data, datasize);
+#endif
+ linkvlen = LYmbcsstrlen(hightext, utf_flag, YES);
+ } else {
+ linkvlen = 0;
+ }
+ if (i >= last_i)
+ i = last_i - 1;
+
+ /*
+ * Scan through the data, making sure that we do not
+ * go over the COLS limit on the display etc.
+ */
+ len = datasize;
+ end_of_data = data + len;
+
+#if defined(SHOW_WHEREIS_TARGETS)
+ /*
+ * If the target overlaps with the part of this line that
+ * we are drawing, it will be emphasized.
+ */
+ i_after_tgt = i;
+ if (target) {
+ cp_tgt = LYno_attr_mb_strstr(sdata,
+ target,
+ utf_flag, YES,
+ &HitOffset,
+ &LenNeeded);
+ if (cp_tgt) {
+ if ((int) offset + LenNeeded > last_i ||
+ ((int) offset + HitOffset >= XP + linkvlen)) {
+ cp_tgt = NULL;
+ } else {
+ i_start_tgt = i + HitOffset;
+ i_after_tgt = i + LenNeeded;
+ }
+ }
+ } else {
+ cp_tgt = NULL;
+ }
+#endif /* SHOW_WHEREIS_TARGETS */
+
+ /*
+ * Iterate through the line data from the start, keeping track of
+ * the display ("glyph") position in i. Drawing will be turned
+ * on when either the first UTF-8 sequence (that occurs after
+ * XP_draw_min) is found, or when we reach the link itself (if
+ * highlight is non-NULL). - kw
+ */
+ while ((i <= last_i) && data < end_of_data && (*data != '\0')) {
+
+ if (hightext && i >= XP && !incurlink) {
+
+ /*
+ * We reached the position of link itself, and hightext is
+ * non-NULL. We switch data from being a pointer into the HTLine
+ * to being a pointer into hightext. Normally (as long as this
+ * routine is applied to normal hyperlink anchors) the text in
+ * hightext will be identical to that part of the HTLine that
+ * data was already pointing to, except that special attribute
+ * chars LY_BOLD_START_CHAR etc., have been stripped out (see
+ * HText_trimHightext). So the switching should not result in
+ * any different display, but it ensures that it doesn't go
+ * unnoticed if somehow hightext got messed up somewhere else.
+ * This is also useful in preparation for using this function
+ * for something else than normal hyperlink anchors, i.e., form
+ * fields.
+ * Turn on drawing here or make sure it gets turned on before the
+ * next actual normal character is handled. - kw
+ */
+ data = hightext;
+ len = (int) strlen(hightext);
+ end_of_data = hightext + len;
+ last_i = i + len;
+ XP_next += linkvlen;
+ incurlink = YES;
+#ifdef SHOW_WHEREIS_TARGETS
+ if (cp_tgt) {
+ if (flag && i_after_tgt >= XP)
+ i_after_tgt = XP - 1;
+ }
+#endif
+ /*
+ * The logic of where to set in-target drawing target etc.
+ * and when to react to it should be cleaned up (here and
+ * further below). For now this seems to work but isn't
+ * very clear. The complications arise from reproducing
+ * the behavior (previously done in LYhighlight()) for target
+ * strings that fall into or overlap a link: use target
+ * emphasis for the target string, except for the first
+ * and last character of the anchor text if the anchor is
+ * highlighted as "current link". - kw
+ */
+ if (!drawing) {
+#ifdef SHOW_WHEREIS_TARGETS
+ if (intarget) {
+ if (i_after_tgt > i) {
+ LYmove(YP, i);
+ if (flag) {
+ drawing = YES;
+ drawingtarget = NO;
+ if (inunderline)
+ inU = YES;
+ lynx_start_link_color(flag, inU);
+ } else {
+ drawing = YES;
+ drawingtarget = YES;
+ LYstartTargetEmphasis();
+ }
+ }
+ }
+#endif /* SHOW_WHEREIS_TARGETS */
+ } else {
+#ifdef SHOW_WHEREIS_TARGETS
+ if (intarget && i_after_tgt > i) {
+ if (flag && (data == hightext)) {
+ drawingtarget = NO;
+ LYstopTargetEmphasis();
+ }
+ } else if (!intarget)
+#endif /* SHOW_WHEREIS_TARGETS */
+ {
+ if (inunderline)
+ inU = YES;
+ if (inunderline)
+ lynx_stop_underline();
+ if (inbold)
+ lynx_stop_bold();
+ lynx_start_link_color(flag, inU);
+ }
+
+ }
+ }
+ if (i >= last_i || data >= end_of_data)
+ break;
+ if ((buffer[0] = *data) == '\0')
+ break;
+#if defined(SHOW_WHEREIS_TARGETS)
+ /*
+ * Look for a subsequent occurrence of the target string,
+ * if we had a previous one and have now stepped past it. - kw
+ */
+ if (cp_tgt && i >= i_after_tgt) {
+ if (intarget) {
+
+ if (incurlink && flag && i == last_i - 1)
+ cp_tgt = NULL;
+ else
+ cp_tgt = LYno_attr_mb_strstr(sdata,
+ target,
+ utf_flag, YES,
+ &HitOffset,
+ &LenNeeded);
+ if (cp_tgt) {
+ i_start_tgt = i + HitOffset;
+ i_after_tgt = i + LenNeeded;
+ if (incurlink) {
+ if (flag && i_start_tgt == XP_link)
+ i_start_tgt++;
+ if (flag && i_start_tgt == last_i - 1)
+ i_start_tgt++;
+ if (flag && i_after_tgt >= last_i)
+ i_after_tgt = last_i - 1;
+ if (flag && i_start_tgt >= last_i)
+ cp_tgt = NULL;
+ } else if (i_start_tgt == last_i) {
+ if (flag)
+ i_start_tgt++;
+ }
+ }
+ if (!cp_tgt || i_start_tgt != i) {
+ intarget = NO;
+ if (drawing) {
+ if (drawingtarget) {
+ drawingtarget = NO;
+ LYstopTargetEmphasis();
+ if (incurlink) {
+ lynx_start_link_color(flag, inU);
+ }
+ }
+ if (!incurlink) {
+ if (inbold)
+ lynx_start_bold();
+ if (inunderline)
+ lynx_start_underline();
+ }
+ }
+ }
+ }
+ }
+#endif /* SHOW_WHEREIS_TARGETS */
+
+ /*
+ * Advance data to point to the next input char (for the
+ * next round). Advance sdata, used for searching for a
+ * target string, so that they stay in synch. As long
+ * as we are not within the highlight text, data and sdata
+ * have identical values. After we have switched data to
+ * point into hightext, sdata remains a pointer into the
+ * HTLine (so that we don't miss a partial target match at
+ * the end of the anchor text). So sdata has to sometimes
+ * skip additional special attribute characters that are
+ * not present in highlight in order to stay in synch. - kw
+ */
+ data++;
+ if (incurlink) {
+ while (IsNormalChar(*sdata)) {
+ ++sdata;
+ }
+ }
+
+ switch (buffer[0]) {
+
+ case LY_UNDERLINE_START_CHAR:
+ if (!drawing || !incurlink)
+ inunderline = YES;
+ if (drawing && !intarget && !incurlink)
+ lynx_start_underline();
+ break;
+
+ case LY_UNDERLINE_END_CHAR:
+ inunderline = NO;
+ if (drawing && !intarget && !incurlink)
+ lynx_stop_underline();
+ break;
+
+ case LY_BOLD_START_CHAR:
+ if (!drawing || !incurlink)
+ inbold = YES;
+ if (drawing && !intarget && !incurlink)
+ lynx_start_bold();
+ break;
+
+ case LY_BOLD_END_CHAR:
+ inbold = NO;
+ if (drawing && !intarget && !incurlink)
+ lynx_stop_bold();
+ break;
+
+ case LY_SOFT_NEWLINE:
+ if (drawing) {
+ LYaddch('+');
+ }
+ i++;
+ break;
+
+ case LY_SOFT_HYPHEN:
+ if (*data != '\0' ||
+ isspace(UCH(LastDisplayChar)) ||
+ LastDisplayChar == '-') {
+ /*
+ * Ignore the soft hyphen if it is not the last
+ * character in the line. Also ignore it if it
+ * first character following the margin, or if it
+ * is preceded by a white character (we loaded 'M'
+ * into LastDisplayChar if it was a multibyte
+ * character) or hyphen, though it should have
+ * been excluded by HText_appendCharacter() or by
+ * split_line() in those cases. -FM
+ */
+ break;
+ } else {
+ /*
+ * Make it a hard hyphen and fall through. -FM
+ */
+ buffer[0] = '-';
+ }
+ /* FALLTHRU */
+
+ default:
+ /*
+ * We have got an actual normal displayable character, or
+ * the start of one. Before proceeding check whether
+ * drawing needs to be turned on now. - kw
+ */
+#if defined(SHOW_WHEREIS_TARGETS)
+ if (incurlink && intarget && flag && i_after_tgt > i) {
+ if (i == last_i - 1) {
+ i_after_tgt = i;
+ } else if (i == last_i - 2 && IS_CJK_TTY &&
+ is8bits(buffer[0])) {
+ i_after_tgt = i;
+ cp_tgt = NULL;
+ if (drawing) {
+ if (drawingtarget) {
+ LYstopTargetEmphasis();
+ drawingtarget = NO;
+ lynx_start_link_color(flag, inU);
+ }
+ }
+ }
+ }
+ if (cp_tgt && i >= i_start_tgt && sdata > cp_tgt) {
+ if (!intarget ||
+ (intarget && incurlink && !drawingtarget)) {
+
+ if (incurlink && drawing &&
+ !(flag &&
+ (i == XP_link || i == last_i - 1))) {
+ lynx_stop_link_color(flag, inU);
+ }
+ if (incurlink && !drawing) {
+ LYmove(YP, i);
+ if (inunderline)
+ inU = YES;
+ if (flag && (i == XP_link || i == last_i - 1)) {
+ lynx_start_link_color(flag, inU);
+ drawingtarget = NO;
+ } else {
+ LYstartTargetEmphasis();
+ drawingtarget = YES;
+ }
+ drawing = YES;
+ } else if (incurlink && drawing &&
+ intarget && !drawingtarget &&
+ (flag &&
+ (i == XP_link))) {
+ if (inunderline)
+ inU = YES;
+ lynx_start_link_color(flag, inU);
+ } else if (drawing &&
+ !(flag &&
+ (i == XP_link || (incurlink && i == last_i - 1)))) {
+ LYstartTargetEmphasis();
+ drawingtarget = YES;
+ }
+ intarget = YES;
+ }
+ } else
+#endif /* SHOW_WHEREIS_TARGETS */
+ if (incurlink) {
+ if (!drawing) {
+ LYmove(YP, i);
+ if (inunderline)
+ inU = YES;
+ lynx_start_link_color(flag, inU);
+ drawing = YES;
+ }
+ }
+
+ i++;
+#ifndef WIDEC_CURSES
+ if (utf_flag && is8bits(buffer[0])) {
+ hadutf8 = YES;
+ utf_extra = utf8_length(utf_flag, data - 1);
+ LastDisplayChar = 'M';
+ }
+#endif
+ if (utf_extra) {
+ LYStrNCpy(&buffer[1], data, utf_extra);
+ if (!drawing && i >= XP_draw_min) {
+ LYmove(YP, i - 1);
+ drawing = YES;
+#if defined(SHOW_WHEREIS_TARGETS)
+ if (intarget) {
+ drawingtarget = YES;
+ LYstartTargetEmphasis();
+ } else
+#endif /* SHOW_WHEREIS_TARGETS */
+ {
+ if (inbold)
+ lynx_start_bold();
+ if (inunderline)
+ lynx_start_underline();
+ }
+ }
+ LYaddstr(buffer);
+ buffer[1] = '\0';
+ sdata += utf_extra;
+ data += utf_extra;
+ utf_extra = 0;
+ } else if (IS_CJK_TTY && is8bits(buffer[0])
+ && (!conv_jisx0201kana && (kanji_code != SJIS))) {
+ /*
+ * For CJK strings, by Masanobu Kimura.
+ */
+ if (drawing && (i <= last_i)) {
+ buffer[1] = *data;
+ LYaddstr(buffer);
+ buffer[1] = '\0';
+ }
+ i++;
+ sdata++;
+ data++;
+ /*
+ * For now, load 'M' into LastDisplayChar, but we should
+ * check whether it's white and if so, use ' '. I don't
+ * know if there actually are white CJK characters, and
+ * we're loading ' ' for multibyte spacing characters in
+ * this code set, but this will become an issue when the
+ * development code set's multibyte character handling is
+ * used. -FM
+ */
+ LastDisplayChar = 'M';
+ } else {
+ if (drawing) {
+ LYaddstr(buffer);
+ }
+ LastDisplayChar = buffer[0];
+ }
+ } /* end of switch */
+ } /* end of while */
+
+ if (!drawing) {
+ LYmove(YP, XP_next);
+ lynx_start_link_color(flag, inU);
+ } else {
+#if defined(SHOW_WHEREIS_TARGETS)
+ if (drawingtarget) {
+ LYstopTargetEmphasis();
+ lynx_start_link_color(flag, inU);
+ }
+#endif /* SHOW_WHEREIS_TARGETS */
+ if (hadutf8) {
+ LYtouchline(YP);
+ }
+ }
+ return;
+}
+#endif /* !USE_COLOR_STYLE */
+
+#ifndef USE_COLOR_STYLE
+/*
+ * Move cursor position to a link's place in the display.
+ * The "moving to" is done by scanning through the line's
+ * character data in the corresponding HTLine of HTMainText,
+ * and starting to draw when a UTF-8 encoded non-ASCII character
+ * is encountered before the link (with some protection against
+ * overwriting form fields). This refreshing of preceding data is
+ * necessary for preventing curses's or slang's display logic from
+ * getting too clever; their logic counts character positions wrong
+ * since they don't know about multi-byte characters that take up
+ * only one screen position. So we have to make them forget their
+ * idea of what's in a screen line drawn previously.
+ * If hightext is non-NULL, it should be the anchor text for a normal
+ * link as stored in a links[] element, and the anchor text will be
+ * drawn too, with appropriate attributes. - kw
+ */
+void LYMoveToLink(int cur,
+ const char *target,
+ const char *hightext,
+ int flag,
+ int inU,
+ int utf_flag)
+{
+#define pvtTITLE_HEIGHT 1
+ HTLine *todr;
+ int i, n = 0;
+ int XP_draw_min = 0;
+ int flags = ((flag == TRUE) ? 1 : 0) | (inU ? 2 : 0);
+
+ /*
+ * We need to protect changed form text fields preceding this
+ * link on the same line against overwriting. - kw
+ */
+ for (i = cur - 1; i >= 0; i++) {
+ if (links[i].ly < links[cur].ly)
+ break;
+ if (links[i].type == WWW_FORM_LINK_TYPE) {
+ XP_draw_min = links[i].ly + links[i].l_form->size;
+ break;
+ }
+ }
+
+ /* Find the right HTLine. */
+ if (!HTMainText) {
+ todr = NULL;
+ } else if (HTMainText->stale) {
+ todr = FirstHTLine(HTMainText);
+ n = links[cur].ly - pvtTITLE_HEIGHT + HTMainText->top_of_screen;
+ } else {
+ todr = HTMainText->top_of_screen_line;
+ n = links[cur].ly - pvtTITLE_HEIGHT;
+ }
+ for (i = 0; i < n && todr; i++) {
+ todr = (todr == HTMainText->last_line) ? NULL : todr->next;
+ }
+ if (todr) {
+ if (target && *target == '\0')
+ target = NULL;
+ move_to_glyph(links[cur].ly, links[cur].lx, XP_draw_min,
+ todr->data, todr->size, todr->offset,
+ target, hightext, flags, utf_flag);
+ } else {
+ /* This should not happen. */
+ move_to_glyph(links[cur].ly, links[cur].lx, XP_draw_min,
+ "", 0, (unsigned) links[cur].lx,
+ target, hightext, flags, utf_flag);
+ }
+}
+#endif /* !USE_COLOR_STYLE */
+
+/*
+ * This is used only if compiled with lss support. It's called to redraw a
+ * regular link when it's being unhighlighted in LYhighlight().
+ */
+#ifdef USE_COLOR_STYLE
+void redraw_lines_of_link(int cur)
+{
+#define pvtTITLE_HEIGHT 1
+ HTLine *todr1;
+ int lines_back;
+ int row, col, count;
+ const char *text;
+
+ if (HTMainText->next_line == HTMainText->last_line) {
+ /* we are at the last page - that is partially filled */
+ lines_back = HTMainText->Lines - (links[cur].ly - pvtTITLE_HEIGHT +
+ HTMainText->top_of_screen);
+ } else {
+ lines_back = display_lines - (links[cur].ly - pvtTITLE_HEIGHT);
+ }
+ todr1 = HTMainText->next_line;
+ while (lines_back-- > 0)
+ todr1 = todr1->prev;
+
+ row = links[cur].ly;
+ if (no_title)
+ row -= TITLE_LINES;
+
+ for (count = 0;
+ row <= display_lines && (text = LYGetHiliteStr(cur, count)) != NULL;
+ ++count) {
+ col = LYGetHilitePos(cur, count);
+ if (col >= 0) {
+ LYmove(row, col);
+ redraw_part_of_line(todr1, text, (int) strlen(text), HTMainText);
+ }
+ todr1 = todr1->next;
+ row++;
+ }
+#undef pvtTITLE_HEIGHT
+ return;
+}
+#endif
+
+#ifdef USE_PRETTYSRC
+void HTMark_asSource(void)
+{
+ if (HTMainText)
+ HTMainText->source = TRUE;
+}
+#endif
+
+HTkcode HText_getKcode(HText *text)
+{
+ return text->kcode;
+}
+
+void HText_updateKcode(HText *text, HTkcode kcode)
+{
+ text->kcode = kcode;
+}
+
+HTkcode HText_getSpecifiedKcode(HText *text)
+{
+ return text->specified_kcode;
+}
+
+void HText_updateSpecifiedKcode(HText *text, HTkcode kcode)
+{
+ text->specified_kcode = kcode;
+}
+
+int HTMainText_Get_UCLYhndl(void)
+{
+ return (HTMainText ?
+ HTAnchor_getUCLYhndl(HTMainText->node_anchor, UCT_STAGE_MIME)
+ : -1);
+}
+
+#ifdef USE_CACHEJAR
+static int LYHandleCache(const char *arg,
+ HTParentAnchor *anAnchor,
+ HTFormat format_out,
+ HTStream *sink)
+{
+ HTFormat format_in = WWW_HTML;
+ HTStream *target = NULL;
+ char c;
+ char *buf = NULL;
+ char *title = NULL;
+ char *address = NULL;
+ char *content_type = NULL;
+ char *content_language = NULL;
+ char *content_encoding = NULL;
+ char *content_location = NULL;
+ char *content_disposition = NULL;
+ char *content_md5 = NULL;
+ char *message_id = NULL;
+ char *date = NULL;
+ char *owner = NULL;
+ char *subject = NULL;
+ char *expires = NULL;
+ char *ETag = NULL;
+ char *server = NULL;
+ char *FileCache = NULL;
+ char *last_modified = NULL;
+ char *cache_control = NULL;
+
+#ifdef USE_SOURCE_CACHE
+ char *source_cache_file = NULL;
+#endif
+ off_t Size = 0;
+ int x = -1;
+
+ /*
+ * Check if there is something to do.
+ */
+ if (HTList_count(loaded_texts) == 0) {
+ HTProgress(CACHE_JAR_IS_EMPTY);
+ LYSleepMsg();
+ HTNoDataOK = 1;
+ return (HT_NO_DATA);
+ }
+
+ /*
+ * If # of LYNXCACHE:/# is number ask user if he/she want to delete it.
+ */
+ if (sscanf(arg, STR_LYNXCACHE "/%d", &x) == 1 && x > 0) {
+ CTRACE((tfp, "LYNXCACHE number is %d\n", x));
+ _statusline(CACHE_D_OR_CANCEL);
+ c = (char) LYgetch_single();
+ if (c == 'D') {
+ HText *t = (HText *) HTList_objectAt(loaded_texts, x - 1);
+
+ HTList_removeObjectAt(loaded_texts, x - 1);
+ HText_free(t);
+ }
+ return (HT_NO_DATA);
+ }
+
+ /*
+ * If we get to here, it was a LYNXCACHE:/ URL for creating and displaying
+ * the Cache Jar Page.
+ * Set up an HTML stream and return an updated Cache Jar Page.
+ */
+ target = HTStreamStack(format_in,
+ format_out,
+ sink, anAnchor);
+ if (target == NULL) {
+ HTSprintf0(&buf, CANNOT_CONVERT_I_TO_O,
+ HTAtom_name(format_in), HTAtom_name(format_out));
+ HTAlert(buf);
+ FREE(buf);
+ return (HT_NOT_LOADED);
+ }
+
+ /*
+ * Load HTML strings into buf and pass buf to the target for parsing and
+ * rendering.
+ */
+#define PUTS(buf) (*target->isa->put_block)(target, buf, (int) strlen(buf))
+
+ HTSprintf0(&buf,
+ "<html>\n<head>\n<title>%s</title>\n</head>\n<body>\n",
+ CACHE_JAR_TITLE);
+ PUTS(buf);
+ HTSprintf0(&buf, "<h1>%s (%s)%s<a href=\"%s%s\">%s</a></h1>\n",
+ LYNX_NAME, LYNX_VERSION,
+ HELP_ON_SEGMENT,
+ helpfilepath, CACHE_JAR_HELP, CACHE_JAR_TITLE);
+ PUTS(buf);
+
+ /*
+ * Max number of cached documents is always same as HTCacheSize.
+ * We count them from oldest to newest. Currently cached document
+ * is *never* listed, resulting in maximal entries of Cache Jar
+ * to be HTCacheSize - 1
+ */
+ for (x = HTList_count(loaded_texts) - 1; x > 0; x--) {
+ /*
+ * The number of the document in the cache list, its title in a link,
+ * and its address and memory allocated for each cached document.
+ */
+ HText *cachedoc = (HText *) HTList_objectAt(loaded_texts, x);
+
+ if (cachedoc != 0) {
+ HTParentAnchor *docanchor = cachedoc->node_anchor;
+
+ if (docanchor != 0) {
+#ifdef USE_SOURCE_CACHE
+ source_cache_file = docanchor->source_cache_file;
+#endif
+ Size = docanchor->content_length;
+ StrAllocCopy(title, docanchor->title);
+ StrAllocCopy(address, docanchor->address);
+ content_type = docanchor->content_type;
+ content_language = docanchor->content_language;
+ content_encoding = docanchor->content_encoding;
+ content_location = docanchor->content_location;
+ content_disposition = docanchor->content_disposition;
+ content_md5 = docanchor->content_md5;
+ message_id = docanchor->message_id;
+ owner = docanchor->owner;
+ StrAllocCopy(subject, docanchor->subject);
+ date = docanchor->date;
+ expires = docanchor->expires;
+ ETag = docanchor->ETag;
+ StrAllocCopy(server, docanchor->server);
+ FileCache = docanchor->FileCache;
+ last_modified = docanchor->last_modified;
+ cache_control = docanchor->cache_control;
+ }
+ }
+
+ LYEntify(&address, TRUE);
+ if (isEmpty(title))
+ StrAllocCopy(title, NO_TITLE);
+ else
+ LYEntify(&title, TRUE);
+
+ HTSprintf0(&buf,
+ "<p><em>%d.</em> Title: <a href=\"%s%d\">%s</a><br />URL: <a href=\"%s\">%s</a><br />",
+ x, STR_LYNXCACHE, x, title, address, address);
+ PUTS(buf);
+ if (Size > 0) {
+ HTSprintf0(&buf, "Size: %" PRI_off_t " ", CAST_off_t (Size));
+
+ PUTS(buf);
+ }
+ if (cachedoc != NULL && cachedoc->Lines > 0) {
+ HTSprintf0(&buf, "Lines: %d ", cachedoc->Lines);
+ PUTS(buf);
+ }
+ if (FileCache != NULL) {
+ HTSprintf0(&buf, "File-Cache: <a href=\"file://%s\">%s</a> ",
+ FileCache, FileCache);
+ PUTS(buf);
+ }
+ if (cache_control != NULL) {
+ HTSprintf0(&buf, "Cache-Control: %s ", cache_control);
+ PUTS(buf);
+ }
+ if (content_type != NULL) {
+ HTSprintf0(&buf, "Content-Type: %s ", content_type);
+ PUTS(buf);
+ }
+ if (content_language != NULL) {
+ HTSprintf0(&buf, "Content-Language: %s ", content_language);
+ PUTS(buf);
+ }
+ if (content_encoding != NULL) {
+ HTSprintf0(&buf, "Content-Encoding: %s ", content_encoding);
+ PUTS(buf);
+ }
+ if (content_location != NULL) {
+ HTSprintf0(&buf, "Content-Location: %s ", content_location);
+ PUTS(buf);
+ }
+ if (content_disposition != NULL) {
+ HTSprintf0(&buf, "Content-Disposition: %s ", content_disposition);
+ PUTS(buf);
+ }
+ if (content_md5 != NULL) {
+ HTSprintf0(&buf, "Content-MD5: %s ", content_md5);
+ PUTS(buf);
+ }
+ if (message_id != NULL) {
+ HTSprintf0(&buf, "Message-ID: %s ", message_id);
+ PUTS(buf);
+ }
+ if (subject != NULL) {
+ LYEntify(&subject, TRUE);
+ HTSprintf0(&buf, "Subject: %s ", subject);
+ PUTS(buf);
+ }
+ if (owner != NULL) {
+ HTSprintf0(&buf, "Owner: <a href=%s>%s</a> ", owner, owner);
+ PUTS(buf);
+ }
+ if (date != NULL) {
+ HTSprintf0(&buf, "Date: %s ", date);
+ PUTS(buf);
+ }
+ if (expires != NULL) {
+ HTSprintf0(&buf, "Expires: %s ", expires);
+ PUTS(buf);
+ }
+ if (last_modified != NULL) {
+ HTSprintf0(&buf, "Last-Modified: %s ", last_modified);
+ PUTS(buf);
+ }
+ if (ETag != NULL) {
+ HTSprintf0(&buf, "ETag: %s ", ETag);
+ PUTS(buf);
+ }
+ if (server != NULL) {
+ LYEntify(&server, TRUE);
+ HTSprintf0(&buf, "Server: <em>%s</em> ", server);
+ PUTS(buf);
+ }
+#ifdef USE_SOURCE_CACHE
+ if (source_cache_file != NULL) {
+ HTSprintf0(&buf,
+ "Source-Cache-File: <a href=\"file://%s\">%s</a>",
+ source_cache_file, source_cache_file);
+ PUTS(buf);
+ }
+#endif
+ HTSprintf0(&buf, "<br />");
+ PUTS(buf);
+ }
+ HTSprintf0(&buf, "</body></html>");
+ PUTS(buf);
+ FREE(subject);
+ FREE(title);
+ FREE(address);
+ FREE(server);
+
+ /*
+ * Free the target to complete loading of the Cache Jar Page, and report a
+ * successful load.
+ */
+ (*target->isa->_free) (target);
+ FREE(buf);
+ return (HT_LOADED);
+}
+
+#ifdef GLOBALDEF_IS_MACRO
+#define _LYCACHE_C_GLOBALDEF_1_INIT { "LYNXCACHE",LYHandleCache,0}
+GLOBALDEF(HTProtocol, LYLynxCache, _LYCACHE_C_GLOBALDEF_1_INIT);
+#else
+GLOBALDEF HTProtocol LYLynxCache =
+{"LYNXCACHE", LYHandleCache, 0};
+#endif /* GLOBALDEF_IS_MACRO */
+#endif /* USE_CACHEJAR */
+
+#ifdef EXP_WCWIDTH_SUPPORT
+static int utfextracells(const char *s)
+{
+ UCode_t ucs = UCGetUniFromUtf8String(&s);
+ int result = 0;
+
+ if (ucs > 0) {
+ int cells = wcwidth((wchar_t) ucs);
+
+ if (cells > 1)
+ result = (cells - 1);
+ }
+ return result;
+}
+#endif