/* Character grid hypertext object ** =============================== */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* LYUCTranslateBack... */ #include #include #include #include #include #include #ifdef EXP_CHARTRANS_AUTOSWITCH #include #endif /* EXP_CHARTRANS_AUTOSWITCH */ #include #include /*#define DEBUG_APPCH 1*/ #ifdef USE_COLOR_STYLE #include #include unsigned int cached_styles[CACHEH][CACHEW]; #endif #include #ifdef USE_CURSES_PADS # define DISPLAY_COLS (LYwideLines ? MAX_COLS : LYcols) # define WRAP_COLS(text) ((text)->stbl ? \ (LYtableCols <= 0 \ ? DISPLAY_COLS : (LYtableCols * LYcols)/12) \ : LYcols) #else # define DISPLAY_COLS LYcols # define WRAP_COLS(text) LYcols #endif #define FirstHTLine(text) ((text)->last_line->next) #define LastHTLine(text) ((text)->last_line) PRIVATE void HText_trimHightext PARAMS(( HText * text, BOOLEAN final, int stop_before)); #ifdef USE_COLOR_STYLE PRIVATE void LynxResetScreenCache NOARGS { int i, j; for (i = 1; (i < CACHEH && i <= display_lines); i++) { for (j = 0; j < CACHEW; j++) cached_styles[i][j] = 0; } } #endif /* USE_COLOR_STYLE */ struct _HTStream { /* only know it as object */ CONST HTStreamClass * isa; /* ... */ }; #define TITLE_LINES 1 #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 PUBLIC HTkcode last_kcode = NOKANJI; /* 1997/11/14 (Fri) 09:09:26 */ #endif #ifdef CJK_EX #define CHAR_WIDTH 6 #else #define CHAR_WIDTH 1 #endif /* Exports */ PUBLIC HText * HTMainText = NULL; /* Equivalent of main window */ PUBLIC HTParentAnchor * HTMainAnchor = NULL; /* Anchor for HTMainText */ PUBLIC char * HTAppName = LYNX_NAME; /* Application name */ PUBLIC char * HTAppVersion = LYNX_VERSION; /* Application version */ PUBLIC int HTFormNumber = 0; PUBLIC int HTFormFields = 0; PUBLIC char * HTCurSelectGroup = NULL; /* Form select group name */ PRIVATE int HTCurSelectGroupCharset = -1; /* ... and name's charset */ PUBLIC int HTCurSelectGroupType = F_RADIO_TYPE; /* Group type */ PUBLIC char * HTCurSelectGroupSize = NULL; /* Length of select */ PRIVATE char * HTCurSelectedOptionValue = NULL; /* Select choice */ PUBLIC char * checked_box = "[X]"; PUBLIC char * unchecked_box = "[ ]"; PUBLIC char * checked_radio = "(*)"; PUBLIC char * unchecked_radio = "( )"; PRIVATE BOOLEAN underline_on = OFF; PRIVATE BOOLEAN bold_on = OFF; #ifdef USE_SOURCE_CACHE PUBLIC int LYCacheSource = SOURCE_CACHE_NONE; PUBLIC int LYCacheSourceForAborted = SOURCE_CACHE_FOR_ABORTED_DROP; #endif #ifdef USE_SCROLLBAR PUBLIC BOOLEAN LYShowScrollbar = FALSE; PUBLIC BOOLEAN LYsb_arrow = TRUE; PUBLIC int LYsb_begin = -1; PUBLIC int LYsb_end = -1; #endif #ifndef VMS /* VMS has a better way - right? - kw */ #define CHECK_FREE_MEM #endif #ifdef CHECK_FREE_MEM PRIVATE void * LY_check_calloc PARAMS((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 typedef struct { unsigned int direction:2; /* on or off */ unsigned int horizpos:14; /* horizontal position of this change */ unsigned int 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; int 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, n * sizeof(pool_data)) #define POOLallocHTLine(ptr, size) ptr = (HTLine*) ALLOC_IN_POOL(&HTMainText->pool, LINE_SIZE(size)) #define POOLallocstring(ptr, len) ptr = (char*) ALLOC_IN_POOL(&HTMainText->pool, len + 1) #define POOLtypecalloc(T, ptr) ptr = (T*) ALLOC_IN_POOL(&HTMainText->pool, sizeof(T)) /**************************************************************************/ /* * Allocates 'n' items in the pool of type 'HTPool' pointed by 'poolptr'. * Returns a pointer to the "allocated" memory or NULL if fails. * Updates 'poolptr' if necessary. */ PRIVATE pool_data* ALLOC_IN_POOL ARGS2( HTPool**, ppoolptr, unsigned, request) { HTPool* pool = *ppoolptr; pool_data* ptr; unsigned n; unsigned j; if (!pool) { ptr = NULL; } else { n = request; if (n == 0) n = 1; j = (n % ALIGN_SIZE); if (j != 0) n += (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(1, sizeof(HTPool)); if (!newpool) { ptr = NULL; } 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. */ PRIVATE HTPool* POOL_NEW NOARGS { HTPool* poolptr = (HTPool*)LY_CALLOC(1, sizeof(HTPool)); if (poolptr) { poolptr->prev = NULL; poolptr->used = 0; } return poolptr; } /* * Frees a pool of type 'HTPool' pointed by poolptr. */ PRIVATE void POOL_FREE ARGS1( 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; #define LINE_SIZE(size) (sizeof(HTLine)+(size)) /* Allow for terminator */ #define HTLINE_NOT_IN_POOL 0 /* debug with this set to 1 */ #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 number; /* For user interface */ 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; /* 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 */ BOOL IgnoreExcess; /* Ignore chars at wrap point */ /* 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 */ #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 enum _detected_kcode { DET_SJIS, DET_EUC, DET_NOTYET, DET_MIXED } detected_kcode; /* Detected Kanji code */ enum _SJIS_status { SJIS_state_neutral, SJIS_state_in_kanji, SJIS_state_has_bad_code } SJIS_status; enum _EUC_status { EUC_state_neutral, EUC_state_in_kanji, EUC_state_in_kana, EUC_state_has_bad_code } EUC_status; #endif enum grid_state { S_text, S_esc, S_dollar, S_paren, S_nonascii_text, S_dollar_paren, S_jisx0201_text } 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 */ PUBLIC void* HText_pool_calloc ARGS2( HText *, text, unsigned, size) { return (void*) ALLOC_IN_POOL(&text->pool, size); } PRIVATE void HText_AddHiddenLink PARAMS((HText *text, TextAnchor *textanchor)); #ifdef EXP_JUSTIFY_ELTS PUBLIC BOOL can_justify_here; PUBLIC BOOL can_justify_here_saved; PUBLIC BOOL can_justify_this_line;/* =FALSE if line contains form objects */ PUBLIC 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. */ PUBLIC BOOL form_in_htext;/*to indicate that we are in form (since HTML_FORM is not stacked in the HTML.c */ PUBLIC BOOL in_DT = FALSE; #ifdef DEBUG_JUSTIFY PUBLIC 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 "* " preceeding
  • 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; PUBLIC void ht_justify_cleanup NOARGS { 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; } PUBLIC void mark_justify_start_position ARGS1(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) && \ HTCJK == NOCJK && !in_DT && \ can_justify_here && can_justify_this_line && !form_in_htext ) #endif /* EXP_JUSTIFY_ELTS */ /* * Boring static variable used for moving cursor across */ #define UNDERSCORES(n) \ ((n) >= MAX_LINE ? underscore_string : &underscore_string[(MAX_LINE-1)] - (n)) /* * Memory leak fixed. * 05-29-94 Lynx 2-3-1 Garrett Arch Blythe * Changed to arrays. */ PRIVATE char underscore_string[MAX_LINE + 1]; PUBLIC char star_string[MAX_LINE + 1]; PRIVATE int ctrl_chars_on_this_line = 0; /* num of ctrl chars in current line */ PRIVATE int utfxtra_on_this_line = 0; /* num of UTF-8 extra bytes in line, they *also* count as ctrl chars. */ #ifdef WIDEC_CURSES #define UTFXTRA_ON_THIS_LINE 0 #else #define UTFXTRA_ON_THIS_LINE utfxtra_on_this_line #endif PRIVATE HTStyle default_style = { 0, "(Unstyled)", 0, "", (HTFont)0, 1, HT_BLACK, 0, 0, 0, 0, 0, HT_LEFT, 1, 0, 0, NO, NO, 0, 0, 0 }; PRIVATE HTList * loaded_texts = NULL; /* A list of all those in memory */ PUBLIC HTList * search_queries = NULL; /* isindex and whereis queries */ #ifdef LY_FIND_LEAKS PRIVATE void free_all_texts NOARGS; #endif PRIVATE BOOL HText_TrueEmptyLine PARAMS(( HTLine * line, HText * text, BOOL IgnoreSpaces)); PRIVATE int HText_TrueLineSize PARAMS(( HTLine * line, HText * text, BOOL 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.) */ PRIVATE void HText_halt NOARGS { 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 */ PRIVATE BOOL mem_is_avail ARGS2( size_t, factor, size_t, bytes) { void *p; if (bytes < MIN_NEEDED_MEM && factor > 0) bytes = MIN_NEEDED_MEM; if (factor == 0) factor = 1; p = malloc(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 */ PRIVATE void * LY_check_calloc ARGS2( 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, 700)) { HTAlert(gettext("Memory exhausted, display interrupted!")); } } else { if ((!HTMainText || HTMainText->halted == 0) && mem_is_avail(0, 700)) { HTAlert(gettext("Memory exhausted, will interrupt transfer!")); if (HTMainText) HTMainText->halted = 1; } } } return (calloc(nmemb, size)); } #endif /* CHECK_FREE_MEM */ /* * Clear highlight information for a given anchor * (text was allocated in the pool). */ PRIVATE void LYClearHiText ARGS1( 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. */ PRIVATE void LYSetHiText ARGS3( TextAnchor *, a, char *, text, int, len) { if (text != NULL) { POOLallocstring(a->lites.hl_base.hl_text, len + 1); memcpy(a->lites.hl_base.hl_text, text, len); *(a->lites.hl_base.hl_text + len) = '\0'; a->lites.hl_len = 1; } } /* * Add highlight information for the next line of a anchor. */ PRIVATE void LYAddHiText ARGS3( TextAnchor *, a, char *, text, int, x) { HiliteInfo *have = a->lites.hl_info; unsigned need = (a->lites.hl_len - 1); unsigned want = (a->lites.hl_len += 1) * 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 = 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 PRIVATE int LYAdjHiTextPos ARGS2( 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) ? (LYSkipBlanks(result) - result) : 0; } #else #define LYAdjHiTextPos(a,count) 0 #endif /* * Get the highlight text, counting from zero. */ PRIVATE char *LYGetHiTextStr ARGS2( 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 */ PRIVATE int LYGetHiTextPos ARGS2( 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'. */ PRIVATE void LYCopyHiText ARGS2( 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, strlen(s)); } else { LYAddHiText(a, s, LYGetHiTextPos(b, count)); } } } PRIVATE void HText_getChartransInfo ARGS1( 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); } PRIVATE void PerFormInfo_free ARGS1( PerFormInfo *, form) { if (form) { FREE(form->accept_cs); FREE(form->thisacceptcs); FREE(form); } } PRIVATE void free_form_fields ARGS1( 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); } PRIVATE void FormList_delete ARGS1( HTList *, forms) { HTList *cur = forms; PerFormInfo *form; while ((form = (PerFormInfo *)HTList_nextObject(cur)) != NULL) PerFormInfo_free(form); HTList_delete(forms); } /* Creation Method ** --------------- */ PUBLIC HText * HText_new ARGS1( HTParentAnchor *, anchor) { #if defined(VMS) && defined(VAXC) && !defined(__DECC) #include int status, VMType=3, VMTotal; #endif /* VMS && VAXC && !__DECC */ HTLine * line = NULL; HText * self = typecalloc(HText); if (!self) return self; 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) { if (HText_hasUTF8OutputSet(HTMainText) && HTLoadedDocumentEightbit() && LYCharSet_UC[current_char_set].enc == UCT_ENC_UTF8) { self->had_utf8 = HTMainText->has_utf8; } else { 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; #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 = Old_DTD; self->keypad_mode = keypad_mode; self->disp_lines = LYlines; self->disp_cols = 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 = ((anchor->no_cache || anchor->post_data) ? YES : NO); self->LastChar = '\0'; self->IgnoreExcess = FALSE; #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)); /* * Memory leak fixed. * 05-29-94 Lynx 2-3-1 Garrett Arch Blythe * 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, '.', (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, '_', (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 */ self->first_lineno_last_disp_partial = self->last_lineno_last_disp_partial = -1; #endif #ifdef EXP_JUSTIFY_ELTS ht_justify_cleanup(); #endif return self; } /* Creation Method 2 ** --------------- ** ** Stream is assumed open and left open. */ PUBLIC HText * HText_new2 ARGS2( HTParentAnchor *, anchor, HTStream *, stream) { HText * this = HText_new(anchor); if (stream) { this->target = stream; this->targetClass = *stream->isa; /* copy action procedures */ } return this; } /* Free Entire Text ** ---------------- */ PUBLIC void HText_free ARGS1( 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) { char * href = NULL; HTList * cur = self->hidden_links; while (NULL != (href = (char *)HTList_nextObject(cur))) FREE(href); HTList_delete(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 ** ------------- */ PRIVATE int display_line ARGS4( 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 /* * 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 > (int)DISPLAY_COLS - 1) j = (int)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) || defined(SLSC) #define CStyle line->styles[current_style] while (current_style < line->numstyles && i >= (int) (CStyle.horizpos + line->offset + 1)) { LynxChangeStyle (CStyle.style, CStyle.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 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 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: #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 */ i++; 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) { strncpy(&buffer[1], data, utf_extra); buffer[utf_extra+1] = '\0'; LYaddstr(buffer); buffer[1] = '\0'; data += utf_extra; utf_extra = 0; } else if (HTCJK != NOCJK && is8bits(buffer[0]) #ifndef CONV_JISX0201KANA_JISX0208KANA && kanji_code != SJIS #endif ) { /* * For CJK strings, by Masanobu Kimura. */ if (i >= DISPLAY_COLS) goto after_while; 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]; } } /* end of switch */ } /* end of while */ after_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.style, CStyle.direction); current_style++; } #undef CStyle #endif return(0); } /* Output the title line ** --------------------- */ PRIVATE void display_title ARGS1( HText *, text) { char *title = NULL; char percent[20]; char *cp = NULL; unsigned char *tmp = NULL; int i = 0, j = 0, toolbar = 0; int limit; /* * 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) : " ")); /* "" -> " " */ /* * There shouldn't be any \n in the title field, * but if there is, lets kill it now. Also trim * any trailing spaces. -FM */ if ((cp = strchr(title,'\n')) != NULL) *cp = '\0'; i = (*title ? (strlen(title) - 1) : 0); while ((i >= 0) && title[i] == ' ') title[i--] = '\0'; /* * Generate the page indicator (percent) string. */ limit = LYscreenWidth(); if (limit < 10) { percent[0] = '\0'; /* Null string */ } else if ((display_lines) <= 0 && LYlines > 0 && text->top_of_screen <= 99999 && text->Lines <= 999999) { sprintf(percent, " (l%d of %d)", text->top_of_screen, text->Lines); } else if ((text->Lines + 1) > (display_lines) && (display_lines) > 0) { /* * In a small attempt to correct the number of pages counted.... * GAB 07-14-94 * * In a bigger attempt (hope it holds up 8-).... * FM 02-08-95 */ int total_pages = (((text->Lines + 1) + (display_lines - 1))/(display_lines)); int start_of_last_page = ((text->Lines + 1) < display_lines) ? 0 : ((text->Lines + 1) - display_lines); sprintf(percent, " (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'; /* Null string */ } /* * 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 (HTCJK != NOCJK) { if (*title && (tmp = typecallocn(unsigned char, (strlen(title) + 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++] = 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('#'); toolbar = 1; } #ifdef USE_COLOR_STYLE if (s_forw_backw != NOSTYLE && (nhist || nhist_extra > 1)) { int 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 */ i = (limit - 1) - 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); } 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, LYcols - 1); 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 PRIVATE void display_scrollbar ARGS1( 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 + 1) <= display_lines) return; if (text->top_of_screen >= text->Lines + 1 - display_lines) { /* Only part of the screen shows actual text */ shown = text->Lines + 1 - 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 + 1); 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, LYcols + LYshiftWin - 1); 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, LYcols + LYshiftWin - 1); 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, LYcols + LYshiftWin - 1); 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 ** ------------- */ PRIVATE void display_page ARGS3( HText *, text, int, line_number, char *, target) { HTLine * line = NULL; int i; #if defined(USE_COLOR_STYLE) && defined(SHOW_WHEREIS_TARGETS) char *cp; #endif char tmp[7]; int last_screen; 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 */ text->first_lineno_last_disp_partial = text->last_lineno_last_disp_partial = -1; } #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; } last_screen = text->Lines - (display_lines - 2); line = text->last_line->prev; /* * Constrain the line number to be within the document. */ 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; 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 /* * Currently implemented only for LINUX */ 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; 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 */ LynxResetScreenCache(); #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 + 2), 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) { int itmp = 0; int written = 0; int x_pos = offset + (int)(cp - data); int len = strlen(target); size_t utf_extra = 0; int y; 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_pos--; } else if (&data[itmp] >= cp) { if (cp == &data[itmp]) { /* * First printable character of target. */ LYmove((i + 1), x_pos); } /* * Output all the printable target chars. */ utf_extra = utf8_length(text->T.output_utf8, data + itmp); if (utf_extra) { strncpy(&tmp[1], &line->data[itmp+1], utf_extra); tmp[utf_extra+1] = '\0'; itmp += utf_extra; LYaddstr(tmp); tmp[1] = '\0'; written += (utf_extra + 1); utf_extra = 0; } else if (HTCJK != NOCJK && 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(); LYGetYX(y, offset); data = (char *)&data[itmp]; /* * Adjust the cursor position, should we be at * the end of the line, or not have another hit * in it. -FM */ LYmove((i + 2), 0); } /* end while */ #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 + 1), 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].anchor_number = Anchor_ptr->number; links[nlinks].anchor_line_num = Anchor_ptr->line_num; link_dest = HTAnchor_followLink(Anchor_ptr->anchor); { /* * Memory leak fixed 05-27-94 * Garrett Arch Blythe */ auto char *cp_AnchorAddress = NULL; if (traversal) cp_AnchorAddress = stub_HTAnchor_address(link_dest); else { #ifndef DONT_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 #endif 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].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(strlen(FormInfo_ptr->value))); } else { /* TEXT type */ LYSetHilite(nlinks, FormInfo_ptr->value); } nlinks++; /* * Bold the link after incrementing nlinks. */ LYhighlight(OFF, (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 */ for (i = nlinks; i < last_nlinks; i++) { LYSetHilite(i, NULL); FREE(links[i].lname); } 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 { text->first_lineno_last_disp_partial = text->last_lineno_last_disp_partial = -1; } #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 (HTCJK != NOCJK) { /* * 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(); } /* Object Building methods ** ----------------------- ** ** These are used by a parser to build the text in an object */ PUBLIC void HText_beginAppend ARGS1( 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. Normally it is the same as LYcols, but there may be a difference via SLANG_MBCS_HACK. Checks of the line length (as the non-UTF-8-aware display library would see it) against LYcols_cu are is used to try to prevent that lines with UTF-8 chars get 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. */ #define new_line(text) split_line(text, 0) #define DEBUG_SPLITLINE #ifdef DEBUG_SPLITLINE #define CTRACE_SPLITLINE(p) CTRACE(p) #else #define CTRACE_SPLITLINE(p) /*nothing*/ #endif PRIVATE int set_style_by_embedded_chars ARGS4( char *, s, char *, e, unsigned char, start_c, unsigned char, end_c) { int ret = NO; while (--e >= s) { if (*e == end_c) break; if (*e == start_c) { ret = YES; break; } } return ret; } PRIVATE void move_anchors_in_region ARGS7( 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 += shift; a->extent -= shift; head_processed = 1; } /* Fix the end */ if ( last < ebyte ) 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 */ PRIVATE HTLine * insert_blanks_in_line ARGS7( 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, 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] /* Include'em all! */ : ((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; } 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, copied - line->data, pre - line->data, shift); #if defined(USE_COLOR_STYLE) /* Move styles too */ #define NStyle mod_line->styles[istyle] for (; istyle < line->numstyles && (int) NStyle.horizpos < curlim ; istyle++) /* Should not we include OFF-styles at curlim? */ NStyle.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 += shift; *t = '\0'; mod_line->size = t - newdata; return mod_line; } #if defined(USE_COLOR_STYLE) PRIVATE HTStyleChange * skip_matched_and_correct_offsets ARGS3( HTStyleChange *, end, HTStyleChange *, start, unsigned, split_pos) { /* 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. */ int level = 0; HTStyleChange *tmp = end; for (; tmp >= start; tmp--) { if (tmp->style == end->style) { if (tmp->direction == STACK_OFF) level--; else if (tmp->direction == STACK_ON) { if (++level == 0) return tmp; } else return 0; } if (tmp->horizpos > split_pos) tmp->horizpos = split_pos; } return 0; } #endif /* USE_COLOR_STYLE */ PRIVATE void split_line ARGS2( HText *, text, unsigned, split) { HTStyle * style = text->style; int spare; int indent = text->in_line_1 ? text->style->indent1st : text->style->leftIndent; 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; int ctrl_chars_on_previous_line = 0; int utfxtra_on_previous_line = UTFXTRA_ON_THIS_LINE; char * cp; 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, 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*/ text->LastChar = ' '; #ifdef DEBUG_APPCH CTRACE((tfp,"GridText: split_line(%p,%d) called\n", text, split)); 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==%d 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 = cp - previous->data; CTRACE((tfp, " split adjusted to %d.\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 = split; /* * Trim any spaces or soft hyphens from the beginning * of our new line. -FM */ p = prevdata + split; while (( (*p == ' ' #ifdef EXP_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 = strlen(p); if (plen) { /* Count funny characters */ for (i = (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++; } if (p[i] == LY_SOFT_HYPHEN && (int)text->permissible_split < i) text->permissible_split = i + 1; } ctrl_chars_on_this_line += utfxtra_on_this_line; /* Add the data to the new line. -FM */ strcat(linedata, p); line->size += plen; } } /* * Economize on space. */ p = previous->data + previous->size - 1; while (p >= previous->data && (*p == ' ' #ifdef EXP_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. */ } TailTrim = previous->data + previous->size - 1 - p; /* Strip trailers. */ 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 = 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(%d [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->horizpos > s_post) to->horizpos += - s_post + SpecialAttrChars; else if ((int) to->horizpos > s_pre && (to->direction == STACK_ON || to->direction == ABS_ON)) to->horizpos = ((int) to->horizpos < s) ? 0 : 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->direction == STACK_OFF) { scan = skip_matched_and_correct_offsets(scan, previous->styles, s_pre); if (!scan) { CTRACE((tfp, "BUG: styles improperly nested.\n")); break; } } else if (scan->direction == STACK_ON) { if ( at_end->direction == STACK_ON && at_end->style == scan->style && (int) at_end->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->direction = STACK_OFF; at_end->style = scan->style; at_end->horizpos = s_pre; } if ( to < line->styles + MAX_STYLES_ON_LINE - 1 && to[1].direction == STACK_OFF && to[1].horizpos <= (unsigned) SpecialAttrChars && to[1].style == scan->style ) to++; else if (to >= line->styles) { *to = *scan; to->horizpos = SpecialAttrChars; to--; } else { CTRACE((tfp, "BUG: style overflow after split_line.\n")); break; } } if ((int) scan->horizpos > s_pre) scan->horizpos = s_pre; scan--; } line->numstyles = 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) line->styles[0].horizpos = ~0; /* ?!!! */ previous->numstyles = at_end - previous->styles + 1; if (previous->numstyles == 0) previous->styles[0].horizpos = ~0; /* ?!!! */ } #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 EXP_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 || IS_UTF_EXTRA(*cp) || *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 */ spare = (WRAP_COLS(text)-1) - (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)-1) - 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) - 1)) /* do nothing - it still fits - kw */; else { spare = spare_cu; if (alignment == HT_CENTER) { /* * Can't move towars center all the way, * but at least make line contents appear * as far right as possible. - kw */ alignment = HT_RIGHT; } } } 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; } } } } } switch (style->alignment) { case HT_CENTER : previous->offset = previous->offset + indent + spare/2; break; case HT_RIGHT : previous->offset = previous->offset + indent + spare; break; case HT_LEFT : case HT_JUSTIFY : /* Not implemented */ default: previous->offset = previous->offset + indent; break; } /* switch */ 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 += -(TailTrim + HeadTrim) + SpecialAttrChars; } else { CTRACE_SPLITLINE((tfp, ", cut the end.\n")); a->extent = 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 = 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 = start - s_post + SpecialAttrChars; a->extent = len; CTRACE_SPLITLINE((tfp, "->(%d,%d,%d)\n", a->line_num,a->line_pos,a->extent)); } else if (a->line_num > CurLine) break; } } #ifdef EXP_JUSTIFY_ELTS /* now perform justification - by VH */ if (this_line_was_split && spare && !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) - 1) - (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 total_byte_len = 0, total_cell_len = 0; int d_, r_; HTLine * jline; ht_num_runs = 0; r->byte_len = r->cell_len = 0; for(; (c = *jp) != 0; ++jp) { if (c == ' ') { total_byte_len += r->byte_len; total_cell_len += r->cell_len; ++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 = utf8_length(text->T.output_utf8, jp); r->byte_len += utf_extra; jp += utf_extra; } } total_byte_len += r->byte_len; total_cell_len += r->cell_len; ++ht_num_runs; if (ht_num_runs != 1) { int *oldpos = (int*)malloc(sizeof(int)*2*(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((char*)oldpos); if (jline == NULL) outofmem(__FILE__, "split_line_4"); previous->next->prev = jline; previous->prev->next = jline; freeHTLine(text, previous); previous = jline; } { /* (ht_num_runs==1) */ if (justify_start_position) { char* p2 = previous->data; for( ; p2 < previous->data + justify_start_position; ++p2) *p2 = (*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")); } 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 /* EXP_JUSTIFY_ELTS */ return; } /* split_line */ /* Allow vertical blank space ** -------------------------- */ PRIVATE void blank_lines ARGS2( HText *, text, int, newlines) { if (HText_LastLineEmpty(text, FALSE)) { /* No text on current line */ HTLine * line = text->last_line->prev; #ifdef USE_COLOR_STYLE /* Style-change petty requests at the start of the document: */ if (line == text->last_line && 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. */ PUBLIC void HText_appendParagraph ARGS1( 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. */ PUBLIC void HText_setStyle ARGS2( 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", style->name)); blank_lines (text, ((after > before) ? after : before)); text->style = style; } /* Append a character to the text object ** ------------------------------------- */ PUBLIC void HText_appendCharacter ARGS2( HText *, text, int, ch) { HTLine * line; HTStyle * style; int indent; int limit = 0; 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; enum _detected_kcode 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 && HTCJK == NOCJK) { /* 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) && HTCJK == NOCJK && !text->T.transp && !text->T.output_utf8 && UCH(ch) < LYlowest_eightbit[current_char_set]) { return; } #endif /* EBCDIC */ #endif /* !USE_SLANG */ if (UCH(ch) == 155 && HTCJK == NOCJK) { /* 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 (HTCJK != NOCJK) { 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 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)) { #ifdef CONV_JISX0201KANA_JISX0208KANA 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; #endif /* 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 (HTCJK != NOCJK && /* 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 && HTCJK==NOCJK && !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 = ON; 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 = OFF; 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 = ON; 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 = OFF; 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 = (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) } /* if (with_backspaces && HTCJK==HTNOCJK && !text->T.output_utf8) */ else { 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)-1)) ) { 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++; 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] & 0x80) == 0xC0) { 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)-1) - (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)-1)) target -= target_cu - (WRAP_COLS(text)-1); if (line->size == 0) { line->offset = line->offset + target - here; } else { for (; heredata[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)-1) - style->rightIndent - (((HTCJK != NOCJK) && text->kanji_buf) ? 1 : 0) || (text->T.output_utf8 && target_cu + UTF_XLEN(ch) >= (LYcols_cu(text)-1))) { int saved_kanji_buf; int saved_state; 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); 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 if we should ignore characters at the wrap point. */ if (text->IgnoreExcess) { int nominal = (indent + (int)(line->offset + line->size) - ctrl_chars_on_this_line); int number; limit = (WRAP_COLS(text) - 1); if (fields_are_numbered() && !number_fields_on_left && text->last_anchor != 0 && (number = text->last_anchor->number) > 0) { limit -= (number > 99999 ? 6 : (number > 9999 ? 5 : (number > 999 ? 4 : (number > 99 ? 3 : (number > 9 ? 2 : 1))))) + 2; } if ((nominal + (int)style->rightIndent) >= limit || (nominal + UTFXTRA_ON_THIS_LINE) >= (LYcols_cu(text) - 1)) { return; } } /* * Check for end of line. */ actual = ((indent + (int)line->offset + (int)line->size) + ((line->size > 0) && (int)(line->data[line->size-1] == LY_SOFT_HYPHEN ? 1 : 0))); if (text->T.output_utf8) { actual += (UTFXTRA_ON_THIS_LINE - ctrl_chars_on_this_line + UTF_XLEN(ch)); limit = (LYcols_cu(text) - 1); } else { actual += (int)style->rightIndent - ctrl_chars_on_this_line + (((HTCJK != NOCJK) && text->kanji_buf) ? 1 : 0); limit = (WRAP_COLS(text) - 1); } if (actual >= limit) { if (style->wordWrap && HTOutputFormat != WWW_SOURCE) { #ifdef EXP_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 EXP_JUSTIFY_ELTS && !REALLY_CAN_JUSTIFY(text) #endif ) ch = ' '; #ifdef EXP_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 charactor handling. */ { HTFont font = style->font; unsigned char hi, lo, tmp[2]; line = text->last_line; /* May have changed */ if (HTCJK != NOCJK && 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++] = tmp[0]; line->data[line->size++] = tmp[1]; } else if (IS_EUC(hi, lo)) { #ifdef CONV_JISX0201KANA_JISX0208KANA JISx0201TO0208_EUC(hi, lo, &hi, &lo); #endif line->data[line->size++] = hi; line->data[line->size++] = 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)) { #ifndef CONV_JISX0201KANA_JISX0208KANA if (IS_EUC_X0201KANA(hi, lo)) line->data[line->size++] = lo; else #endif { EUC_TO_SJIS1(hi, lo, tmp); line->data[line->size++] = tmp[0]; line->data[line->size++] = tmp[1]; } } else if (IS_SJIS_2BYTE(hi, lo)) { line->data[line->size++] = hi; line->data[line->size++] = 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++] = hi; line->data[line->size++] = lo; } text->kanji_buf = 0; } #ifndef CONV_JISX0201KANA_JISX0208KANA else if ((HTCJK == JAPANESE) && IS_SJIS_X0201KANA(UCH((ch))) && (kanji_code == EUC)) { line->data[line->size++] = UCH(0x8e); line->data[line->size++] = ch; } #endif else if (HTCJK != NOCJK) { 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 ** ------------------------------------------- */ PUBLIC void _internal_HTC ARGS3(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].direction && line->styles[line->numstyles-1].style == (unsigned) style && (int) line->styles[line->numstyles-1].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].horizpos = 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].horizpos >= ctrl_chars_on_this_line) line->styles[line->numstyles].horizpos -= ctrl_chars_on_this_line; line->styles[line->numstyles].style = style; line->styles[line->numstyles].direction = dir; line->numstyles++; } } } #endif /* Set LastChar element in the text object. ** ---------------------------------------- */ PUBLIC void HText_setLastChar ARGS2( HText *, text, char, ch) { if (!text) return; text->LastChar = ch; } /* Get LastChar element in the text object. ** ---------------------------------------- */ PUBLIC char HText_getLastChar ARGS1( HText *, text) { if (!text) return('\0'); return((char)text->LastChar); } /* Set IgnoreExcess element in the text object. ** -------------------------------------------- */ PUBLIC void HText_setIgnoreExcess ARGS2( HText *, text, BOOL, ignore) { if (!text) return; text->IgnoreExcess = ignore; } /* 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 */ PRIVATE int HText_insertBlanksInStblLines ARGS2( HText *, me, int, ncols) { HTLine *line; HTLine *mod_line, *first_line = NULL; int * oldpos; int * newpos; int ninserts, lineno; int first_lineno, 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 = first_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 * 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) { me->first_lineno_last_disp_partial = me->last_lineno_last_disp_partial = -1; } 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)-1) - (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) { me->first_lineno_last_disp_partial = me->last_lineno_last_disp_partial = -1; } 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 = table_offset; } } #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 */ PUBLIC void HText_cancelStbl ARGS1( 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 */ PUBLIC void HText_startStblTABLE ARGS2( HText *, me, short, alignment) { #ifdef EXP_NESTED_TABLES STable_info *current = me->stbl; #endif if (!me) return; #ifdef EXP_NEST