diff options
Diffstat (limited to 'src/GridText.c')
-rw-r--r-- | src/GridText.c | 14954 |
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 = ¤t; + break; + } + } else if (direction > 0 && + same_anchor_as_link(curlink, previous.anc, ta_skip)) { + group_to_go = ¤t; + 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 = ¤t; + break; + } + } else { + group_to_go = ¤t; + break; + } + } + } + } + } + if (!group_to_go && curlink < 0 && direction <= 0) { + group_to_go = ¤t; + } + 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 != ¤t && + 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 |