/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ #include "xp.h" #include "pa_tags.h" #include "layout.h" #include "laylayer.h" #include "layers.h" #include "libi18n.h" #include "edt.h" #include "laystyle.h" #include "laytrav.h" #ifdef DOM #include "domstyle.h" #include "lm_dom.h" #include "laydom.h" #endif /* * Turn this define on to get the new multibyte parsing code #define FAST_MULTI */ #define FAST_EDITOR #ifdef TEST_16BIT #define XP_WIN16 #endif /* TEST_16BIT */ #ifdef XP_WIN16 #define SIZE_LIMIT 32000 #endif /* XP_WIN16 */ #ifdef XP_UNIX #define TEXT_CHUNK_LIMIT 500 #else #define TEXT_CHUNK_LIMIT 1600 #endif #ifdef XP_MAC #define NON_BREAKING_SPACE 0x07 #else #define NON_BREAKING_SPACE 160 #endif #ifdef PROFILE #pragma profile on #endif int32 lo_correct_text_element_width(LO_TextInfo *); static void lo_insert_quote_characters(MWContext *context, lo_DocState *state); static LO_TextStruct * lo_new_text_element(MWContext *context, lo_DocState *state, ED_Element *edit_element, intn edit_offset ); void lo_LayoutFormattedText(MWContext *context, lo_DocState *state, LO_TextBlock * block); void lo_LayoutPreformattedText(MWContext *context, lo_DocState *state, LO_TextBlock * block); LO_TextBlock * lo_NewTextBlock (MWContext * context, lo_DocState * state, char * text, uint16 formatMode ); int32 lo_compute_text_basline_inc (lo_DocState * state, LO_TextBlock * block, LO_TextStruct * text_data ); uint32 lo_FindBlockOffset (LO_TextBlock * block, LO_TextStruct * fromElement ); void lo_RelayoutTextElements (MWContext * context, lo_DocState * state, LO_TextBlock * block, LO_TextStruct * fromElement ); Bool lo_CanUseBreakTable (lo_DocState * state ); Bool lo_UseBreakTable (LO_TextBlock * block ); void lo_AppendTextToBlock (MWContext *context, lo_DocState *state, LO_TextBlock * block, char *text ); void lo_LayoutTextBlock (MWContext * context, lo_DocState * state, Bool flushLastLine ); static void lo_FlushText (MWContext * context, lo_DocState * state ); static void lo_SetupBreakState (LO_TextBlock * block ); Bool lo_GrowTextBlock (LO_TextBlock * block, uint32 length ); typedef enum { kSimpleSBTextParseAttribute = 0, kMBTextParseAttribute, kThaiTextParseAttribute } loTextParseAttribute; static loTextParseAttribute lo_GetTextParseAttributes (lo_DocState * state); #ifdef XP_MAC #define kStaticMeasureTextBufferSize 512 static uint16 gMeasureTextBuffer[ kStaticMeasureTextBufferSize ]; /* This needs to go in fe_proto.h - will move it there once we're out of the branch */ #define FE_MeasureText(context, text, charLocs) \ (*context->funcs->MeasureText)(context, text, charLocs) #endif #ifdef XP_MAC Bool gCallNewText = TRUE; #endif LO_TextBlock * lo_NewTextBlock ( MWContext * context, lo_DocState * state, char * text, uint16 formatMode ) { LO_TextBlock * block; uint32 length; length = strlen ( text ); block = (LO_TextBlock *)lo_NewElement ( context, state, LO_TEXTBLOCK, NULL, 0 ); if ( block == NULL ) { state->top_state->out_of_memory = TRUE; return NULL; } block->type = LO_TEXTBLOCK; block->x_offset = 0; block->ele_id = NEXT_ELEMENT; block->x = state->x; block->y = state->y; block->y_offset = 0; block->width = 0; block->height = 0; block->line_height = 0; block->next = NULL; block->prev = NULL; block->text_attr = NULL; block->anchor_href = NULL; block->ele_attrmask = 0; block->format_mode = 0; block->startTextElement = NULL; block->endTextElement = NULL; block->text_buffer = NULL; block->buffer_length = 0; block->buffer_write_index = 0; block->last_buffer_write_index = 0; block->buffer_read_index = 0; block->last_line_break = 0; block->break_table = NULL; block->break_length = 0; block->break_write_index = 0; block->break_read_index = 0; block->last_break_offset = 0; block->multibyte_index = 0; block->multibyte_length = 0; block->old_break = NULL; block->old_break_pos = 0; block->old_break_width = 0; block->totalWidth = 0; block->totalChars = 0; block->break_pending = 0; block->last_char_is_whitespace = 0; block->ascent = 0; block->descent = 0; block->text_buffer = XP_ALLOC ( length + 1 ); if ( block->text_buffer == NULL ) { XP_FREE ( block ); state->top_state->out_of_memory = TRUE; return NULL; } block->buffer_length = length + 1; XP_BCOPY ( text, block->text_buffer, length ); block->text_buffer[ length ] = '\0'; block->buffer_write_index = length; /* default text does not have a break table */ block->break_table = NULL; block->break_length = 0; block->format_mode = formatMode; /* * Since we're creating a new text block, grab some of the text * state out of the layout state. */ block->anchor_href = state->current_anchor; if ( state->font_stack != NULL ) { /* TEXTATTR HERE */ #ifdef DOM block->text_attr = lo_GetCurrentTextAttr(state, context); #else block->text_attr = state->font_stack->text_attr; #endif } if (state->breakable != FALSE) { block->ele_attrmask |= LO_ELE_BREAKABLE; } state->cur_text_block = block; lo_AppendToLineList ( context, state, (LO_Element *) block, 0 ); return block; } void lo_SetDefaultFontAttr(lo_DocState *state, LO_TextAttr *tptr, MWContext *context) { tptr->size = DEFAULT_BASE_FONT_SIZE; tptr->fontmask = 0; tptr->no_background = TRUE; tptr->attrmask = 0; tptr->fg.red = STATE_DEFAULT_FG_RED(state); tptr->fg.green = STATE_DEFAULT_FG_GREEN(state); tptr->fg.blue = STATE_DEFAULT_FG_BLUE(state); tptr->bg.red = STATE_DEFAULT_BG_RED(state); tptr->bg.green = STATE_DEFAULT_BG_GREEN(state); tptr->bg.blue = STATE_DEFAULT_BG_BLUE(state); tptr->charset = INTL_DefaultTextAttributeCharSetID(context); tptr->font_face = NULL; tptr->FE_Data = NULL; tptr->point_size = 0; tptr->font_weight = 0; } /************************************* * Function: lo_DefaultFont * * Description: This function sets up the text attribute * structure for the default text drawing font. * This is the font that sits at the bottom of the font * stack, and can never be popped off. * * Params: Document state * * Returns: A pointer to a lo_FontStack structure. * Returns a NULL on error (such as out of memory); *************************************/ lo_FontStack * lo_DefaultFont(lo_DocState *state, MWContext *context) { LO_TextAttr tmp_attr; lo_FontStack *fptr; LO_TextAttr *tptr; /* * Fill in default font information. */ lo_SetDefaultFontAttr(state, &tmp_attr, context); tptr = lo_FetchTextAttr(state, &tmp_attr); fptr = XP_NEW(lo_FontStack); if (fptr == NULL) { return(NULL); } fptr->tag_type = P_UNKNOWN; fptr->text_attr = tptr; fptr->next = NULL; return(fptr); } /* ** lo_PushAlignment ** ** Pushes a new alignment entry onto the alignment stack. Alignment ** entries are tagged with both the actually alignment type (CENTER, ** LEFT, RIGHT, MIDDLE, etc.) as well as the tag that contained the ** alignment attribute. The latter is so we can accurately lookup the ** alignment for a given tag type.. */ void lo_PushAlignment(lo_DocState *state, intn tag_type, int32 alignment) { lo_AlignStack *aptr; aptr = XP_NEW(lo_AlignStack); if (aptr == NULL) { state->top_state->out_of_memory = TRUE; return; } aptr->type = tag_type; aptr->alignment = alignment; aptr->next = state->align_stack; state->align_stack = aptr; } /* ** lo_PopAlignment ** ** Pops off the top entry from the alignment stack. */ lo_AlignStack * lo_PopAlignment(lo_DocState *state) { lo_AlignStack *aptr; aptr = state->align_stack; if (aptr != NULL) { state->align_stack = aptr->next; aptr->next = NULL; } return(aptr); } void lo_PushLineHeight(lo_DocState *state, int32 height) { lo_LineHeightStack *aptr; aptr = XP_NEW(lo_LineHeightStack); if (aptr == NULL) { state->top_state->out_of_memory = TRUE; return; } aptr->height = height; aptr->next = state->line_height_stack; state->line_height_stack = aptr; } lo_LineHeightStack * lo_PopLineHeight(lo_DocState *state) { lo_LineHeightStack *aptr; aptr = state->line_height_stack; if (aptr != NULL) { state->line_height_stack = aptr->next; aptr->next = NULL; } return(aptr); } /************************************* * Function: lo_FreshText * * Description: This function clears out the information from the * document state that refers to text in the midst of being * laid out. It is called to clear the layout state before * Laying out new text potentially in a new font. * * Params: Document state structure. * * Returns: Nothing *************************************/ void lo_FreshText(lo_DocState *state) { state->text_info.max_width = 0; state->text_info.ascent = 0; state->text_info.descent = 0; state->text_info.lbearing = 0; state->text_info.rbearing = 0; state->line_buf_len = 0; state->break_pos = -1; state->break_width = 0; state->last_char_CR = FALSE; #ifdef EDITOR if( !state->edit_force_offset ) { state->edit_current_offset = 0; } #endif } /************************************* * Function: lo_new_text_element * * Description: Create a new text element structure based * on the current text information stored in the document * state. * * Params: Document state * * Returns: A pointer to a LO_TextStruct structure. * Returns a NULL on error (such as out of memory); *************************************/ static LO_TextStruct * lo_new_text_element(MWContext *context, lo_DocState *state, ED_Element *edit_element, intn edit_offset ) { LO_TextStruct *text_ele = NULL; #ifdef DEBUG assert (state); #endif if (state == NULL) { return(NULL); } text_ele = (LO_TextStruct *)lo_NewElement(context, state, LO_TEXT, edit_element, edit_offset); if (text_ele == NULL) { #ifdef DEBUG assert (state->top_state->out_of_memory); #endif return(NULL); } text_ele->type = LO_TEXT; text_ele->ele_id = NEXT_ELEMENT; text_ele->x = state->x; text_ele->x_offset = 0; text_ele->y = state->y; text_ele->y_offset = 0; text_ele->width = 0; text_ele->height = 0; text_ele->line_height = 0; text_ele->next = NULL; text_ele->prev = NULL; if (state->line_buf_len > 0) { text_ele->text = PA_ALLOC((state->line_buf_len + 1) * sizeof(char)); if (text_ele->text != NULL) { char *text_buf; char *line; PA_LOCK(line, char *, state->line_buf); PA_LOCK(text_buf, char *, text_ele->text); XP_BCOPY(line, text_buf, state->line_buf_len); text_buf[state->line_buf_len] = '\0'; PA_UNLOCK(text_ele->text); PA_UNLOCK(state->line_buf); text_ele->text_len = (int16)state->line_buf_len; } else { state->top_state->out_of_memory = TRUE; /* * free text element && return; it's no different * than if we had run out of memory allocating * the text element */ lo_FreeElement(context, (LO_Element *)text_ele, FALSE); return(NULL); } } else { text_ele->text = NULL; text_ele->text_len = 0; } /* Fix for bug#124552. Look at both the state structure and the current text block for anchor information. */ text_ele->anchor_href = state->current_anchor; if (state->current_anchor == NULL && state->cur_text_block != NULL && state->cur_text_block->anchor_href != NULL) { text_ele->anchor_href = state->cur_text_block->anchor_href; } if (state->font_stack == NULL) { text_ele->text_attr = NULL; } else { text_ele->text_attr = state->cur_text_block->text_attr; } text_ele->ele_attrmask = 0; if (state->breakable != FALSE) { text_ele->ele_attrmask |= LO_ELE_BREAKABLE; } text_ele->sel_start = -1; text_ele->sel_end = -1; return(text_ele); } /* * A bogus an probably too expensive function to fill in the * textinfo for whatever font is now on the font stack. */ MODULE_PRIVATE void lo_fillin_text_info(MWContext *context, lo_DocState *state) { LO_TextStruct tmp_text; PA_Block buff; char *str; memset (&tmp_text, 0, sizeof (tmp_text)); buff = PA_ALLOC(1); if (buff == NULL) { return; } PA_LOCK(str, char *, buff); str[0] = ' '; PA_UNLOCK(buff); tmp_text.text = buff; tmp_text.text_len = 1; /* if we have a text block, use it's font info, otherwise get it from the state */ if ( state->cur_text_block == NULL ) { /* TEXTATTR HERE */ #ifdef DOM tmp_text.text_attr = lo_GetCurrentTextAttr(state, context); #else tmp_text.text_attr = state->font_stack->text_attr; #endif } else { tmp_text.text_attr = state->cur_text_block->text_attr; } FE_GetTextInfo(context, &tmp_text, &(state->text_info)); PA_FREE(buff); } /************************************* * Function: lo_NewLinefeed * * Description: Create a new linefeed element structure based * on the current information stored in the document * state. * * Params: Document state, linefeed type * * Returns: A pointer to a LO_LinefeedStruct structure. * Returns a NULL on error (such as out of memory); *************************************/ LO_LinefeedStruct * lo_NewLinefeed(lo_DocState *state, MWContext * context, uint32 break_type, uint32 clear_type) { LO_LinefeedStruct *linefeed = NULL; if (state == NULL) { return(NULL); } linefeed = (LO_LinefeedStruct *)lo_NewElement(context, state, LO_LINEFEED, NULL, 0); #ifdef DEBUG assert (state); #endif if (linefeed == NULL) { #ifdef DEBUG assert (state->top_state->out_of_memory); #endif state->top_state->out_of_memory = TRUE; return(NULL); } lo_FillInLineFeed( context, state, break_type, clear_type, linefeed ); return(linefeed); } /************************************* * Function: lo_InsertLineBreak * * Description: This function forces a linebreak at this time. * Forcing the current text insetion point down one line * and back to the left margin. * It also maintains the linefeed state: * 0 - middle of a line. * 1 - at left margin below a line of text. * 2 - at left margin below a blank line. * * Params: Window context, Document state, break type and a boolean * That describes whether this is a breaking linefeed or not. * (Breaking linefeed is one inserted just to break a formatted * line to the current document width.) * * Returns: Nothing *************************************/ void lo_InsertLineBreak(MWContext *context, lo_DocState *state, uint32 break_type, uint32 clear_type, Bool breaking) { /* int32 line_width; */ Bool scroll_at_bottom; scroll_at_bottom = FALSE; /* * If this is an auto-scrolling doc, we will be scrolling * later if we are at the bottom now. */ if ((state->is_a_subdoc == SUBDOC_NOT)&& (state->display_blocked == FALSE)&& (state->top_state->auto_scroll > 0)) { int32 doc_x, doc_y; FE_GetDocPosition(context, FE_VIEW, &doc_x, &doc_y); if ((doc_y + state->win_height + state->win_bottom) >= state->y) { scroll_at_bottom = TRUE; } } /* * This line is done, flush it into the line table and * display it to the front end. */ lo_FlushLineList(context, state, break_type, clear_type, breaking); if (state->top_state->out_of_memory != FALSE) { return; } lo_UpdateStateAfterLineBreak( context, state, TRUE ); lo_UpdateFEProgressBar(context, state); lo_UpdateFEDocSize( context, state ); /* * Tell the front end how big the document is right now. * Only do this for the top level document. */ if ((state->display_blocked != FALSE)&& #ifdef EDITOR (!state->edit_relayout_display_blocked)&& #endif (state->display_blocking_element_y > 0)&& (state->y > (state->display_blocking_element_y + state->win_height))) { int32 y; state->display_blocked = FALSE; y = state->display_blocking_element_y; state->display_blocking_element_y = 0; if (!lo_InsideLayer(state)) { LO_SetDocumentDimensions(context, state->max_width, state->y); } FE_SetDocPosition(context, FE_VIEW, 0, y); if (context->compositor) { XP_Rect rect; rect.left = 0; rect.top = y; rect.right = state->win_width; rect.bottom = y + state->win_height; CL_UpdateDocumentRect(context->compositor, &rect, (PRBool)FALSE); } } /* * Reset the left and right margins */ /* lo_FindLineMargins(context, state, TRUE); state->x = state->left_margin; */ if ((state->is_a_subdoc == SUBDOC_NOT)&& (state->top_state->auto_scroll > 0)&& ((state->line_num - 1) > state->top_state->auto_scroll)) { Bool redraw; int32 old_y, dy, new_y; int32 doc_x, doc_y; old_y = state->y; FE_GetDocPosition(context, FE_VIEW, &doc_x, &doc_y); lo_ClipLines(context, state, 1); /* * Calculate how much of the top was clipped * off, and if any of that area was in the users * window we need to redraw their window. */ redraw = FALSE; dy = old_y - state->y; if (dy >= doc_y) { redraw = TRUE; } new_y = doc_y - dy; if (new_y < 0) { new_y = 0; } FE_SetDocPosition(context, FE_VIEW, doc_x, new_y); if (!lo_InsideLayer(state)) { LO_SetDocumentDimensions(context, state->max_width, state->y); } if (redraw != FALSE) { FE_ClearView(context, FE_VIEW ); if (context->compositor) { XP_Rect rect; rect.left = 0; rect.top = new_y; rect.right = state->win_width; rect.bottom = new_y + state->win_height; CL_UpdateDocumentRect(context->compositor, &rect, (PRBool)FALSE); } } } if ((state->is_a_subdoc == SUBDOC_NOT)&& (state->display_blocked == FALSE)&& (state->top_state->auto_scroll > 0)) { int32 doc_x, doc_y; FE_GetDocPosition(context, FE_VIEW, &doc_x, &doc_y); if (((doc_y + state->win_height) < state->y)&& (scroll_at_bottom != FALSE)) { int32 y; y = state->y - state->win_height + state->win_bottom; if (y < 0) { y = 0; } else { FE_SetDocPosition(context, FE_VIEW, 0, y); } } } lo_insert_quote_characters(context, state); } /************************************* * Function: lo_HardLineBreak * * Description: This function forces a linebreak at this time. * Forcing the current text insetion point down one line * and back to the left margin. * * Params: Window context, Document state and a boolean * That describes whether this is a breaking linefeed or not. * (Breaking linefeed is one inserted just to break a formatted * line to the current document width.) * * Returns: Nothing *************************************/ void lo_HardLineBreak(MWContext *context, lo_DocState *state, Bool breaking) { lo_InsertLineBreak ( context, state, LO_LINEFEED_BREAK_HARD, LO_CLEAR_NONE, breaking ); } /************************************* * Function: lo_HardLineBreakWithBreak * * Returns: Nothing *************************************/ void lo_HardLineBreakWithClearType(MWContext *context, lo_DocState *state, uint32 clear_type, Bool breaking) { lo_InsertLineBreak ( context, state, LO_LINEFEED_BREAK_HARD, clear_type, breaking ); } /************************************* * Function: lo_SoftLineBreak * * Description: This function adds a single soft line break. * * Params: Window context, Document state and a boolean that * describes if this is a breaking linefeed. * * Returns: Nothing *************************************/ void lo_SoftLineBreak(MWContext *context, lo_DocState *state, Bool breaking) { lo_InsertLineBreak ( context, state, LO_LINEFEED_BREAK_SOFT, LO_CLEAR_NONE, breaking); } /************************************* * Function: lo_SetSoftLineBreakState * * Description: Add soft linebreaks to bring the linefeed state to the * specified level. * Linefeed states: * 0 - middle of a line. * 1 - at left margin below a line of text. * 2 - at left margin below a blank line. * * Params: Window context, Document state, a boolean that * describes if this is a breaking linefeed, and the desired * linefeed state. * * Returns: Nothing *************************************/ void lo_SetSoftLineBreakState(MWContext *context, lo_DocState *state, Bool breaking, intn linefeed_state) { lo_SetLineBreakState ( context, state, breaking, LO_LINEFEED_BREAK_SOFT, linefeed_state, FALSE); } /************************************* * Function: lo_SetLineBreakState * * Description: Add linebreaks to bring the linefeed state to the * specified level. * Linefeed states: * 0 - middle of a line. * 1 - at left margin below a line of text. * 2 - at left margin below a blank line. * * Params: Window context, Document state, a boolean that * describes if this is a breaking linefeed, and the desired * linefeed state. * * Returns: Nothing *************************************/ void lo_SetLineBreakState(MWContext *context, lo_DocState *state, Bool breaking, uint32 break_type, intn linefeed_state, Bool relayout) { /* * Linefeeds are partially disabled if we are placing * preformatted text. */ if (state->preformatted != PRE_TEXT_NO) { if ((breaking == FALSE)&&(state->linefeed_state < 1)&& (state->top_state->out_of_memory == FALSE)) { if (relayout == FALSE) { lo_InsertLineBreak(context, state, break_type, LO_CLEAR_NONE, breaking); } else { lo_rl_AddBreakAndFlushLine( context, state, break_type, LO_CLEAR_NONE, breaking); } } return; } /* check for the style sheet display: inline property. * ignore newlines if it is set */ if(state->top_state && state->top_state->style_stack) { StyleStruct *style = STYLESTACK_GetStyleByIndex( state->top_state->style_stack, 0); if(style) { char * property = STYLESTRUCT_GetString(style, DISPLAY_STYLE); if(property && !strcasecomp(property, INLINE_STYLE)) { XP_FREE(property); return; } XP_FREEIF(property); } } if (linefeed_state > 2) { linefeed_state = 2; } while ((state->linefeed_state < linefeed_state)&& (state->top_state->out_of_memory == FALSE)) { if (relayout == FALSE) { lo_InsertLineBreak(context, state, break_type, LO_CLEAR_NONE, breaking); } else { lo_rl_AddBreakAndFlushLine( context, state, break_type, LO_CLEAR_NONE, breaking ); } } } /************************************* * Function: lo_InsertWordBreak * * Description: This insert a word break into the laid out * element chain for the current line. A word break is * basically an empty text element structure. All * word break elements should probably be removed * from the layout chain once the line list for this * line is flushed. * * Params: Window context and document state. * * Returns: Nothing *************************************/ void lo_InsertWordBreak(MWContext *context, lo_DocState *state) { LO_TextStruct *text_ele = NULL; if (state == NULL) { return; } text_ele = (LO_TextStruct *)lo_NewElement(context, state, LO_TEXT, NULL, 0); if (text_ele == NULL) { #ifdef DEBUG assert (state->top_state->out_of_memory); #endif return; } text_ele->type = LO_TEXT; text_ele->ele_id = NEXT_ELEMENT; text_ele->x = state->x; text_ele->x_offset = 0; text_ele->y = state->y; text_ele->y_offset = 0; text_ele->width = 0; text_ele->height = 0; text_ele->line_height = 0; text_ele->next = NULL; text_ele->prev = NULL; text_ele->block_offset = 0; text_ele->doc_width = 0; #if 0 text_ele->text = PA_ALLOC(sizeof(char)); if (text_ele->text != NULL) { PA_LOCK(text_buf, char *, text_ele->text); text_buf[0] = '\0'; PA_UNLOCK(text_ele->text); } else { state->top_state->out_of_memory = TRUE; } #else text_ele->text = NULL; #endif text_ele->text_len = 0; text_ele->anchor_href = state->current_anchor; /* TEXTATTR HERE */ if (state->font_stack == NULL) { text_ele->text_attr = NULL; } else { #ifdef DOM text_ele->text_attr = lo_GetCurrentTextAttr(state, context); #else text_ele->text_attr = state->font_stack->text_attr; #endif } text_ele->ele_attrmask = 0; if (state->breakable != FALSE) { text_ele->ele_attrmask |= LO_ELE_BREAKABLE; } text_ele->sel_start = -1; text_ele->sel_end = -1; text_ele->bullet_type = WORDBREAK; lo_AppendToLineList(context, state, (LO_Element *)text_ele, 0); state->old_break = text_ele; state->old_break_block = state->cur_text_block; state->old_break_pos = 0; state->old_break_width = 0; } int32 lo_baseline_adjust(MWContext *context, lo_DocState * state, LO_Element *ele_list, int32 old_baseline, int32 old_line_height) { LO_Element *eptr; int32 baseline; int32 new_height; int32 baseline_adjust; LO_TextInfo text_info; baseline = 0; new_height = 0; eptr = ele_list; while (eptr != NULL) { switch (eptr->type) { case LO_TEXT: FE_GetTextInfo(context, (LO_TextStruct *)eptr, &text_info); if (text_info.ascent > baseline) { baseline = text_info.ascent; } if ((text_info.ascent + text_info.descent) > new_height) { new_height = text_info.ascent + text_info.descent; } break; case LO_FORM_ELE: if (eptr->lo_form.baseline > baseline) { baseline = eptr->lo_form.baseline; } if (eptr->lo_any.height > new_height) { new_height = eptr->lo_any.height; } break; case LO_IMAGE: if ((old_baseline - eptr->lo_any.y_offset) > baseline) { baseline = old_baseline - eptr->lo_any.y_offset; } if ((eptr->lo_image.height + (2 * eptr->lo_image.border_width) + (2 * eptr->lo_image.border_vert_space)) > new_height) { new_height = eptr->lo_image.height + (2 * eptr->lo_image.border_width) + (2 * eptr->lo_image.border_vert_space); } break; case LO_HRULE: case LO_BULLET: if ((old_baseline - eptr->lo_any.y_offset) > baseline) { baseline = old_baseline - eptr->lo_any.y_offset; } if (eptr->lo_any.height > new_height) { new_height = eptr->lo_any.height; } break; default: break; } eptr = eptr->lo_any.next; } baseline_adjust = old_baseline - baseline; return(baseline_adjust); } /************************************* * Function: lo_BreakOldElement * * Description: This function goes back to a previous * element in the element chain, that is still on the * same line, and breaks the line there, putting the * remaining elements (if any) on the next line. * * Params: Window context and document state. * * Returns: Nothing *************************************/ void lo_BreakOldElement(MWContext *context, lo_DocState *state) { char *text_buf; char *break_ptr; char *word_ptr; char *new_buf; PA_Block new_block; intn word_len; int32 save_width; int32 old_baseline; int32 old_line_height; int32 adjust; LO_TextStruct *text_data; LO_TextStruct *new_text_data; LO_Element *tptr; int16 charset; int multi_byte; LO_TextBlock * block; #ifdef LOCAL_DEBUG XP_TRACE(("lo_BreakOldElement, flush text.\n")); #endif /* LOCAL_DEBUG */ if (state == NULL) { return; } /* BRAIN DAMAGE: Make sure the correct element is at the end of the list for this block */ block = state->old_break_block; if ( block == NULL ) { return; } /* * If this text block is using the new breaktable algorithm, * call that code */ if ( lo_UseBreakTable ( block ) ) { lo_BreakOldTextBlockElement ( context, state ); return; } charset = block->text_attr->charset; multi_byte = (INTL_CharSetType(charset) != SINGLEBYTE); /* * Move to the element we will break */ text_data = state->old_break; /* * If there is no text there to break * it is an error. */ if ( text_data == NULL ) { return; } /* * Later operations will trash the width field. * So save it now to restore later. */ save_width = state->width; /* * If this buffer is just an empty string, then we are * breaking at a previously inserted word break element. * Knowing that, the breaking is easier, so it is special * cased here. */ if (text_data->text == NULL) { LO_Element *line_ptr; /* * Back the state up to this element's * location, break off the rest of the elements * and save them for later. * Flush this line, and insert a linebreak. */ state->x = text_data->x; state->y = text_data->y; tptr = text_data->next; text_data->next = NULL; PA_UNLOCK(text_data->text); state->width = text_data->width; state->x += state->width; lo_SoftLineBreak(context, state, TRUE); /* * The remaining elements go on the next line. * The nextline may already have special mail * bullets on it. */ line_ptr = state->line_list; while ((line_ptr != NULL)&&(line_ptr->lo_any.next != NULL)) { line_ptr = line_ptr->lo_any.next; } if (line_ptr == NULL) { state->line_list = tptr; if (tptr != NULL) { tptr->lo_any.prev = NULL; } } else { line_ptr->lo_any.next = tptr; if (tptr != NULL) { tptr->lo_any.prev = line_ptr; } } /* * If there are no elements to place on the next * line, and we have new text buffered, * remove any spaces at the start of that new * text since new lines are not allowed to begin * with whitespace. */ if ((tptr == NULL)&&(state->line_buf_len != 0)) { char *cptr; int32 wlen; PA_LOCK(text_buf, char *, state->line_buf); cptr = text_buf; wlen = 0; while ((XP_IS_SPACE(*cptr))&&(*cptr != '\0')) { cptr++; wlen++; } if (wlen) { LO_TextStruct tmp_text; memset (&tmp_text, 0, sizeof (tmp_text)); /* * We removed space, move the string up * and recalculate its current width. */ XP_BCOPY(cptr, text_buf, (state->line_buf_len - wlen + 1)); state->line_buf_len -= (intn) wlen; PA_UNLOCK(state->line_buf); tmp_text.text = state->line_buf; tmp_text.text_len = (int16)state->line_buf_len; tmp_text.text_attr = block->text_attr; FE_GetTextInfo(context, &tmp_text, &(state->text_info)); /* * Override the saved width since we did want * to change the real width in this case. */ save_width = lo_correct_text_element_width( &(state->text_info)); } else { PA_UNLOCK(state->line_buf); } } } /* * We are breaking somewhere in the middle of an old * text element, this will mean splitting it into 2 text * elements, and putting a line break between them. */ else { LO_TextInfo text_info; int32 base_change; PA_LOCK(text_buf, char *, text_data->text); /* * Locate our break location, and a pointer to * the remaining word (with its preceeding space removed). */ break_ptr = (char *)(text_buf + state->old_break_pos); /* * On multibyte, we often break on character which is * not space. */ if (multi_byte == FALSE || XP_IS_SPACE(*break_ptr)) { *break_ptr = '\0'; word_ptr = (char *)(break_ptr + 1); } else { word_ptr = (char *)break_ptr; } /* * Copy the remaining word into its own block. */ word_len = XP_STRLEN(word_ptr); new_block = PA_ALLOC((word_len + 1)); if (new_block == NULL) { state->top_state->out_of_memory = TRUE; return; } PA_LOCK(new_buf, char *, new_block); XP_BCOPY(word_ptr, new_buf, (word_len + 1)); new_buf[word_len] = '\0'; /* * Back the state up to this element's * location, break off the rest of the elements * and save them for later. * Flush this line, and insert a linebreak. */ state->x = text_data->x; state->y = text_data->y; tptr = text_data->next; text_data->next = NULL; if (word_ptr != break_ptr) text_data->text_len = text_data->text_len - word_len - 1; else text_data->text_len = text_data->text_len - word_len; FE_GetTextInfo(context, text_data, &text_info); state->width = lo_correct_text_element_width(&text_info); PA_UNLOCK(text_data->text); state->x += state->width; /* * Make the split element know its new width. */ text_data->width = state->width; /* * If the element that caused this break has a different * baseline than the element we are breaking, we need to * preserve that difference after the break. */ base_change = state->baseline - (text_data->y_offset + text_info.ascent); old_baseline = state->baseline; old_line_height = state->line_height; /* * Reset element_id so they are still sequencial. */ state->top_state->element_id = text_data->ele_id + 1; /* * If we are breaking an anchor, we need to make sure the * linefeed gets its anchor href set properly. */ if (text_data->anchor_href != NULL) { LO_AnchorData *tmp_anchor; tmp_anchor = state->current_anchor; state->current_anchor = text_data->anchor_href; lo_SoftLineBreak(context, state, TRUE); state->current_anchor = tmp_anchor; } else { lo_SoftLineBreak(context, state, TRUE); } adjust = lo_baseline_adjust(context, state, tptr, old_baseline, old_line_height); state->baseline = old_baseline - adjust; state->line_height = (intn) old_line_height - adjust; /* * If there was really no remaining word, free the * unneeded buffer. */ if (word_len == 0) { LO_Element *eptr; LO_Element *line_ptr; PA_UNLOCK(new_block); PA_FREE(new_block); line_ptr = state->line_list; while ((line_ptr != NULL)&& (line_ptr->lo_any.next != NULL)) { line_ptr = line_ptr->lo_any.next; } if (line_ptr == NULL) { state->line_list = tptr; if (tptr != NULL) { tptr->lo_any.prev = NULL; } } else { line_ptr->lo_any.next = tptr; if (tptr != NULL) { tptr->lo_any.prev = line_ptr; } } state->width = 0; eptr = tptr; while (eptr != NULL) { eptr->lo_any.ele_id = NEXT_ELEMENT; eptr->lo_any.y_offset -= adjust; eptr = eptr->lo_any.next; } } /* * Else create a new text element for the remaining word. * and stick it in the begining of the next line of * text elements. */ else { LO_Element *line_ptr; LO_Element *eptr; int32 baseline_inc; LO_TextInfo text_info; baseline_inc = -1 * adjust; new_text_data = lo_new_text_element(context, state, text_data->edit_element, text_data->edit_offset+text_data->text_len+1 ); if (new_text_data == NULL) { return; } if (new_text_data->text != NULL) { PA_FREE(new_text_data->text); new_text_data->text = NULL; new_text_data->text_len = 0; } new_text_data->anchor_href = text_data->anchor_href; new_text_data->text_attr = text_data->text_attr; new_text_data->x = state->x; new_text_data->y = state->y; #ifdef LOCAL_DEBUG XP_TRACE(("lo_BreakOldElement, left over word (%s)\n", new_buf)); #endif /* LOCAL_DEBUG */ PA_UNLOCK(new_block); new_text_data->text = new_block; new_text_data->text_len = word_len; FE_GetTextInfo(context, new_text_data, &text_info); new_text_data->width = lo_correct_text_element_width(&text_info); /* * Some fonts (particulatly italic ones with curly * tails on letters like 'f') have a left bearing * that extends back into the previous character. * Since in this case the previous character is * probably not in the same font, we move forward * to avoid overlap. */ if (text_info.lbearing < 0) { new_text_data->x_offset = text_info.lbearing * -1; } /* * The baseline of the text element just inserted in * the line may be less than or greater than the * baseline of the rest of the line due to font * changes. If the baseline is less, this is easy, * we just increase y_offest to move the text down * so the baselines line up. For greater baselines, * we can't move the text up to line up the baselines * because we will overlay the previous line, so we * have to move all rest of the elements in this line * down. * * If the baseline is zero, we are the first element * on the line, and we get to set the baseline. */ if (state->baseline == 0) { state->baseline = text_info.ascent; } else if (text_info.ascent < state->baseline) { new_text_data->y_offset = state->baseline - text_info.ascent; } else { baseline_inc = baseline_inc + (text_info.ascent - state->baseline); state->baseline = text_info.ascent; } /* * Now that we have broken, and added the new * element, we need to move it down to restore the * baseline difference that previously existed. */ new_text_data->y_offset -= base_change; /* * Calculate the height of this new * text element. */ new_text_data->height = text_info.ascent + text_info.descent; state->x += new_text_data->width; /* * Stick this new text element at the beginning * of the remaining line elements * There may be some special mail bullets already * on the line that we have to insert after. */ line_ptr = state->line_list; while ((line_ptr != NULL)&& (line_ptr->lo_any.next != NULL)) { line_ptr = line_ptr->lo_any.next; } if (line_ptr == NULL) { state->line_list = (LO_Element *)new_text_data; new_text_data->prev = NULL; } else { line_ptr->lo_any.next = (LO_Element *)new_text_data; new_text_data->prev = line_ptr; } new_text_data->next = tptr; if (tptr != NULL) { tptr->lo_any.prev = (LO_Element *)new_text_data; } eptr = tptr; while (eptr != NULL) { eptr->lo_any.ele_id = NEXT_ELEMENT; eptr->lo_any.y_offset += baseline_inc; eptr = eptr->lo_any.next; } if ((new_text_data->y_offset + new_text_data->height) > state->line_height) { state->line_height = (intn) new_text_data->y_offset + new_text_data->height; } state->at_begin_line = FALSE; } } /* * If we are at the beginning of a line, and there is * remaining text to place here, remove leading space * which is not allowed at the start of lines. * ERIC, make a test case for this, I suspect right now * this code is never being executed and may contain an error. */ if ((state->at_begin_line != FALSE)&&(tptr != NULL)&& (tptr->type == LO_TEXT)) { char *cptr; int32 wlen; LO_TextStruct *tmp_text; LO_TextInfo text_info; tmp_text = (LO_TextStruct *)tptr; PA_LOCK(text_buf, char *, tmp_text->text); cptr = text_buf; wlen = 0; while ((XP_IS_SPACE(*cptr))&&(*cptr != '\0')) { cptr++; wlen++; } if (wlen) { XP_BCOPY(cptr, text_buf, (tmp_text->text_len - wlen + 1)); tmp_text->text_len -= (intn) wlen; PA_UNLOCK(tmp_text->text); FE_GetTextInfo(context, tmp_text, &text_info); tmp_text->width = lo_correct_text_element_width( &text_info); } else { PA_UNLOCK(tmp_text->text); } } /* * Upgrade forward the x and y text positions in the document * state. */ while (tptr != NULL) { lo_UpdateElementPosition ( state, tptr ); tptr = tptr->lo_any.next; } state->at_begin_line = FALSE; state->width = save_width; } void lo_UpdateElementPosition ( lo_DocState * state, LO_Element * element ) { element->lo_any.x = state->x; element->lo_any.y = state->y; state->x = state->x + element->lo_any.width; /* move any element specific items */ switch ( element->lo_any.type ) { case LO_IMAGE: CL_MoveLayer(element->lo_image.layer, element->lo_any.x, element->lo_any.y); break; case LO_EMBED: CL_MoveLayer(element->lo_embed.objTag.layer, element->lo_any.x, element->lo_any.y); break; } } /************************************* * Function: lo_correct_text_element_width * * Description: Calculate the correct width of this text element * if it is a complete element surrounded by elements of potentially * different fonts, so we have to take care not to truncate * any slanted characters at either end of the element. * * Params: LO_TextInfo structure for this text element's text string. * * Returns: The width this element would have if it stood alone. *************************************/ int32 lo_correct_text_element_width(LO_TextInfo *text_info) { int32 x_offset; int32 width; width = text_info->max_width; x_offset = 0; /* * For text that leans into the previous character. */ if (text_info->lbearing < 0) { x_offset = text_info->lbearing * -1; width += x_offset; } /* * For text that leans right into the following characters. */ if (text_info->rbearing > text_info->max_width) { width += (text_info->rbearing - text_info->max_width); } return(width); } PRIVATE int32 lo_characters_in_line(lo_DocState *state) { int32 cnt; LO_Element *eptr; cnt = 0; eptr = state->line_list; while (eptr != NULL) { if (eptr->type == LO_TEXT) { cnt += eptr->lo_text.text_len; } eptr = eptr->lo_any.next; } return(cnt); } void lo_PreformatedText(MWContext *context, lo_DocState *state, char *text) { LO_TextBlock * block; block = lo_NewTextBlock ( context, state, text, state->preformatted ); if ( !state->top_state->out_of_memory && ( block != NULL ) ) { block->buffer_read_index = 0; lo_LayoutPreformattedText ( context, state, block ); } } void lo_LayoutPreformattedText(MWContext *context, lo_DocState *state, LO_TextBlock * block) { char *tptr; char *w_start; char *w_end; char *text_buf; char tchar1; Bool have_CR; Bool line_break; Bool white_space; LO_TextStruct text_data; char *tmp_buf; PA_Block tmp_block; int32 tab_count, ignore_cnt, line_length; int16 charset; int multi_byte; int kinsoku_class, last_kinsoku_class; int i; int bytestocopy; char * text = NULL; Bool lineBufMeasured; /* start at the current text position in this text block */ if (block->text_buffer) text = (char *) &block->text_buffer[ block->buffer_read_index ]; kinsoku_class = PROHIBIT_NOWHERE; lineBufMeasured = FALSE; /* * Initialize the structures to 0 (mark) */ memset (&text_data, 0, sizeof (LO_TextStruct)); /* * Error conditions */ if ((state == NULL)||(state->cur_ele_type != LO_TEXT)||(text == NULL)) { return; } charset = block->text_attr->charset; multi_byte = (INTL_CharSetType(charset) != SINGLEBYTE); /* * Move through this text fragment, expand tabs, honor LF/CR. */ have_CR = state->last_char_CR; tptr = text; while ((*tptr != '\0')&&(state->top_state->out_of_memory == FALSE)) { Bool has_nbsp; Bool in_word; Bool wrap_break; Bool pre_wrap_break; /* * white_space is a tag to tell us if the current word * ends in whitespace. */ white_space = FALSE; line_break = FALSE; has_nbsp = FALSE; if (multi_byte) has_nbsp = TRUE; /* * Find the end of the line, counting tabs. */ tab_count = 0; ignore_cnt = 0; line_length = 0; w_start = tptr; /* * If the last character processed was a CR, and the next * char is a LF, ignore it. Otherwise we know we * can break the line on the first CR or LF found. */ if ((have_CR != FALSE)&&(*tptr == LF)) { ignore_cnt++; tptr++; } have_CR = FALSE; in_word = FALSE; wrap_break = FALSE; pre_wrap_break = FALSE; #ifdef XP_WIN16 while ((*tptr != CR)&&(*tptr != LF)&&(*tptr != '\0')&& ((line_length + (tab_count * state->tab_stop)) < TEXT_CHUNK_LIMIT)&& (((state->line_buf_len + line_length + (tab_count * state->tab_stop))) < SIZE_LIMIT)) #else while ((*tptr != CR)&&(*tptr != LF)&&(*tptr != '\0') &&((line_length + (tab_count * state->tab_stop)) < TEXT_CHUNK_LIMIT) ) #endif /* XP_WIN16 */ { /* * In the special wrapping preformatted text * we need to chunk by word instead of by * line. */ if ((state->preformatted == PRE_TEXT_WRAP)|| (state->preformatted == PRE_TEXT_COLS)) { if(multi_byte && (! (INTL_CharSetType(charset) & CS_SPACE))) { last_kinsoku_class = kinsoku_class; kinsoku_class = INTL_KinsokuClass(charset, (unsigned char *)tptr); /* We need to conser PROHIBIT_WORD_BREAK for UTF8 case */ if(( PROHIBIT_WORD_BREAK == kinsoku_class) || (0x00 == (*tptr & 0x80))) { if ((in_word == FALSE)&&(!XP_IS_SPACE(*tptr)) ) { in_word = TRUE; } else if ((in_word != FALSE)&& (XP_IS_SPACE(*tptr))) { wrap_break = TRUE; break; } } else { if( (line_length != 0) && (PROHIBIT_END_OF_LINE != last_kinsoku_class) && (PROHIBIT_BEGIN_OF_LINE != kinsoku_class) ) { wrap_break = TRUE; break; } } } else { if ((in_word == FALSE)&&(!XP_IS_SPACE(*tptr))) { in_word = TRUE; } else if ((in_word != FALSE)&& (XP_IS_SPACE(*tptr))) { wrap_break = TRUE; break; } } } if ((*tptr == FF)||(*tptr == VTAB)) { /* * Ignore the form feeds * thrown in by some platforms. * Ignore vertical tabs since we don't know * what else to do with them. */ ignore_cnt++; } else if (*tptr == TAB) { tab_count++; } else if (!multi_byte && (unsigned char)*tptr == NON_BREAKING_SPACE) { /* *tptr = ' '; Replace this later */ has_nbsp = TRUE; line_length++; } else { if(multi_byte) line_length += INTL_CharLen(charset, (unsigned char*)tptr); else line_length++; } if(multi_byte) tptr = INTL_NextChar(charset, tptr); else tptr++; } line_length = line_length + (state->tab_stop * tab_count); if ((state->line_buf_len + line_length) > TEXT_CHUNK_LIMIT) { lo_FlushLineBuffer(context, state); if (state->cur_ele_type != LO_TEXT) { lo_FreshText(state); state->cur_ele_type = LO_TEXT; } } #ifdef XP_WIN16 if ((state->line_buf_len + line_length) >= SIZE_LIMIT) { line_break = TRUE; } #endif /* XP_WIN16 */ /* * Terminate the word, saving the char we replaced * with the terminator so it can be restored later. */ w_end = tptr; tchar1 = *w_end; *w_end = '\0'; tmp_block = PA_ALLOC(line_length + 1); if (tmp_block == NULL) { *w_end = tchar1; state->top_state->out_of_memory = TRUE; break; } PA_LOCK(tmp_buf, char *, tmp_block); if ((tab_count)||(ignore_cnt)) { char *cptr; char *text_ptr; int32 cnt; text_ptr = tmp_buf; cptr = w_start; cnt = lo_characters_in_line(state); cnt += state->line_buf_len; while (*cptr != '\0') { if ((*cptr == LF)||(*cptr == FF)|| (*cptr == VTAB)) { /* * Ignore any linefeeds that must have * been after CR, and form feeds. * Ignore vertical tabs since we * don't know what else to do with them. */ cptr++; } else if (*cptr == TAB) { int32 i, tab_pos; tab_pos = ((cnt / state->tab_stop) + 1) * state->tab_stop; for (i=0; i<(tab_pos - cnt); i++) { *text_ptr++ = ' '; } cnt = tab_pos; cptr++; } else { /* * Bug #77467 * If multibyte, character != char by default, so copy * the CHARACTER, not the char */ if(multi_byte) { bytestocopy = INTL_CharLen(charset, (unsigned char*)cptr); for (i=0; i 0)&&(XP_IS_SPACE(tmp_buf[line_length - 1]))) { state->trailing_space = TRUE; } #ifdef LOCAL_DEBUG XP_TRACE(("Found Preformatted text (%s)\n", tmp_buf)); #endif /* LOCAL_DEBUG */ PA_UNLOCK(tmp_block); #if WHAT /* * If this is an empty string, just throw it out * and move on */ if (*w_start == '\0') { *w_end = tchar1; #ifdef LOCAL_DEBUG XP_TRACE(("Throwing out empty string!\n")); #endif /* LOCAL_DEBUG */ continue; } #endif /* * If we have extra text, Append it to the line buffer. * It may be necessary to expand the line * buffer. */ if (*w_start != '\0') { int32 old_len; Bool old_begin_line; old_len = state->line_buf_len; old_begin_line = state->at_begin_line; if ((state->line_buf_len + line_length + 1) > state->line_buf_size) { state->line_buf = PA_REALLOC( state->line_buf, (state->line_buf_size + line_length + LINE_BUF_INC)); if (state->line_buf == NULL) { *w_end = tchar1; state->top_state->out_of_memory = TRUE; break; } state->line_buf_size += (line_length + LINE_BUF_INC); } PA_LOCK(text_buf, char *, state->line_buf); PA_LOCK(tmp_buf, char *, tmp_block); XP_BCOPY(tmp_buf, (char *)(text_buf + state->line_buf_len), (line_length + 1)); state->line_buf_len += (intn) line_length; PA_UNLOCK(state->line_buf); PA_UNLOCK(tmp_block); /* we have not measured this new text yet */ lineBufMeasured = FALSE; /* * Having added text, we cannot be at the start * of a line */ state->cur_ele_type = LO_TEXT; state->at_begin_line = FALSE; #ifdef OLD_WAY /* * Most common case is appending to the same line. * assume that is what we are doing here. */ text_data.text = state->line_buf; text_data.text_len = (int16)state->line_buf_len; text_data.text_attr = block->text_attr; FE_GetTextInfo(context, &text_data, &(state->text_info)); state->width = lo_correct_text_element_width( &(state->text_info)); /* * If this is a special wrapping pre, and we would * wrap here, break before this, and strip all * following whitespace so there is none at * the start of the next line. * If we were at the beginning of the line before * this, then obviously trying to wrap here will * be pointless, and will in fact cause an * infinite loop. * * Also wrap here is we are in fixed column wrapping * pre text, and we would pass our set column. */ if (((state->preformatted == PRE_TEXT_WRAP)&& (old_begin_line == FALSE)&& ((state->x + state->width) > state->right_margin))|| ((state->preformatted == PRE_TEXT_COLS)&& (old_begin_line == FALSE)&& (state->preformat_cols > 0)&& (state->line_buf_len > state->preformat_cols))) { PA_LOCK(text_buf, char *, state->line_buf); text_buf[old_len] = '\0'; PA_UNLOCK(state->line_buf); state->line_buf_len = old_len; *w_end = tchar1; tptr = w_start; while ((XP_IS_SPACE(*tptr))&&(*tptr != '\0')) { tptr++; } line_break = TRUE; w_start = tptr; w_end = tptr; tchar1 = *w_end; } #else text_data.text = state->line_buf; text_data.text_len = (int16)state->line_buf_len; text_data.text_attr = block->text_attr; FE_GetTextInfo ( context, &text_data, &(state->text_info) ); /* update the block's font info cache */ block->ascent = state->text_info.ascent; block->descent = state->text_info.descent; /* * If this is a special wrapping pre, then we need to measure this line of text to see * if we need to wrap. */ if ( ( state->preformatted == PRE_TEXT_WRAP ) && ( old_begin_line == FALSE ) ) { state->width = lo_correct_text_element_width ( &(state->text_info) ); lineBufMeasured = TRUE; /* * If this line is now too long, wrap */ if ( ( state->x + state->width ) > state->right_margin ) { pre_wrap_break = TRUE; } } /* * Now check to see if we need to wrap based on being too long for the special pre modes */ if ( ( pre_wrap_break != FALSE ) || ( ( state->preformatted == PRE_TEXT_COLS ) && ( old_begin_line == FALSE ) && ( state->preformat_cols > 0 ) && ( state->line_buf_len > state->preformat_cols ) )) { PA_LOCK(text_buf, char *, state->line_buf); text_buf[old_len] = '\0'; PA_UNLOCK(state->line_buf); state->line_buf_len = old_len; *w_end = tchar1; tptr = w_start; while ((XP_IS_SPACE(*tptr))&&(*tptr != '\0')) { tptr++; } line_break = TRUE; w_start = tptr; w_end = tptr; tchar1 = *w_end; } #endif } if (tchar1 == LF) { line_break = TRUE; } else if (tchar1 == CR) { line_break = TRUE; have_CR = TRUE; } /* update the buffer position to refleft the new word */ block->buffer_read_index = tptr - (char *) block->text_buffer; /* * If we are breaking the line here, flush the * line_buf, and then insert a linebreak. */ if (line_break != FALSE) { #ifdef LOCAL_DEBUG XP_TRACE(("LineBreak, flush text.\n")); #endif /* LOCAL_DEBUG */ /* * Flush the line and insert the linebreak. */ PA_LOCK(text_buf, char *, state->line_buf); text_data.text = state->line_buf; text_data.text_len = (int16)state->line_buf_len; text_data.text_attr = block->text_attr; FE_GetTextInfo(context, &text_data,&(state->text_info)); PA_UNLOCK(state->line_buf); state->width = lo_correct_text_element_width( &(state->text_info)); lo_FlushLineBuffer(context, state); lineBufMeasured = TRUE; #ifdef EDITOR /* LTNOTE: do something here like: */ /*state->edit_current_offset += (word_ptr - text_buf);*/ #endif if (state->top_state->out_of_memory != FALSE) { PA_FREE(tmp_block); return; } /* * Put on a linefeed element. * This line is finished and will be added * to the line array. */ lo_SoftLineBreak(context, state, TRUE); state->line_buf_len = 0; state->width = 0; /* * having just broken the line, we have no break * position. */ state->break_pos = -1; state->break_width = 0; } *w_end = tchar1; if ((*tptr == CR)||(*tptr == LF)) { tptr++; } PA_FREE(tmp_block); } #ifndef OLD_WAY /* * If we haven't measured this line of text yet, do so now */ if ( !lineBufMeasured ) { text_data.text = state->line_buf; text_data.text_len = (int16)state->line_buf_len; text_data.text_attr = block->text_attr; FE_GetTextInfo ( context, &text_data, &(state->text_info) ); state->width = lo_correct_text_element_width ( &(state->text_info) ); } #endif /* * Because we just might get passed text broken between the * CR and the LF, we need to save this state. */ if ((tptr > text)&&(*(tptr - 1) == CR)) { state->last_char_CR = TRUE; } if ( ( state->cur_ele_type != LO_TEXT ) || ( state->line_buf_len == 0 ) ) { state->cur_text_block = NULL; } } #define CAPITALIZE 0 #define UPPERCASE 1 #define LOWERCASE 2 /* transform the text inline. * "capitalize" : uppercase first letter of each word. * "uppercase" : uppercase every letter * "lowercase" : lowercase every letter * else : do nothing */ PRIVATE void lo_transform_text(char *ptr, int method) { XP_Bool possible_first_letter = TRUE; for(; *ptr; ptr++) { switch(method) { case CAPITALIZE: if(!(XP_IS_SPACE(*ptr))) { if(possible_first_letter) { *ptr = XP_TO_UPPER(*ptr); possible_first_letter = FALSE; } } else /* is a space */ { possible_first_letter = TRUE; } break; case UPPERCASE: *ptr = XP_TO_UPPER(*ptr); break; case LOWERCASE: *ptr = XP_TO_LOWER(*ptr); break; default: XP_ASSERT(0); } } } /* see lo_transform_text * * This function just maps the string method to an int */ PRIVATE void lo_transform_text_from_string_method(char *ptr, char *method) { if(!strcasecomp(method, "capitalize")) lo_transform_text(ptr, CAPITALIZE); else if(!strcasecomp(method, "lowercase")) lo_transform_text(ptr, LOWERCASE); else if(!strcasecomp(method, "uppercase")) lo_transform_text(ptr, UPPERCASE); } /************************************* * Function: lo_FormatText * * Description: This function creates a text block element and then calls the * format text function for it. * * Params: Window context and document state., and the text to be formatted. * * Returns: Nothing *************************************/ void lo_FormatText(MWContext *context, lo_DocState *state, char *text) { LO_TextBlock * block; /* can we use the new style layout? */ if ( lo_CanUseBreakTable ( state ) ) { block = state->cur_text_block; /* flush any existing text in a partial buffer */ if ( ( block != NULL ) && lo_UseBreakTable ( block ) ) { lo_LayoutTextBlock ( context, state, TRUE ); } /* parse the new text and flush all but any stragglers */ lo_AppendTextToBlock ( context, state, NULL, text ); lo_LayoutTextBlock ( context, state, FALSE ); } else { block = lo_NewTextBlock ( context, state, text, state->preformatted ); if ( !state->top_state->out_of_memory && ( block != NULL ) ) { block->buffer_read_index = 0; lo_LayoutFormattedText ( context, state, block ); } } } /************************************* * Function: lo_FormatText * * Description: This function formats text by breaking it into lines * at word boundries. Word boundries are whitespace, or special * word break tags. * * Params: Window context and document state., and the text to be formatted. * * Returns: Nothing *************************************/ void lo_LayoutFormattedText(MWContext *context, lo_DocState *state, LO_TextBlock * block) { char *tptr; char *w_start; char *w_end; char *text_buf; char tchar1; int32 word_len; Bool line_break; Bool word_break; Bool prev_word_breakable; Bool white_space; int16 charset; Bool multi_byte; LO_TextStruct text_data; char * text = NULL; #ifdef XP_OS2 /* performance */ int32 maxw; /* performance - max char width for font */ int32 estwidth; /* performance - estimated width for line */ int textsw; /* performance */ maxw = 0; textsw = 0; /* performance - need to mark no width taken */ #endif /* start at the current text position in this text block */ if (block->text_buffer) text = (char *) &block->text_buffer[ block->buffer_read_index ]; /* * Initialize the structures to 0 (mark) */ memset (&text_data, 0, sizeof (LO_TextStruct)); /* * Error conditions */ if ((state == NULL)||(state->cur_ele_type != LO_TEXT)||(text == NULL)) { return; } charset = block->text_attr->charset; if ((INTL_CharSetType(charset) == SINGLEBYTE) || (INTL_CharSetType(charset) & CS_SPACE)) { multi_byte = FALSE; } else { multi_byte = TRUE; } /* * Move through this text fragment, breaking it up into * words, and then grouping the words into lines. */ tptr = text; prev_word_breakable = FALSE; while ((*tptr != '\0')&&(state->top_state->out_of_memory == FALSE)) { PA_Block nbsp_block; Bool has_nbsp; int32 w_char_cnt; #ifdef XP_WIN16 int32 ccnt; #endif /* XP_WIN16 */ Bool mb_sp; /* Allow space between multibyte */ /* * white_space is a tag to tell us if the currenct word * contains nothing but whitespace. * word_break tells us if there was whitespace * before this word so we know we can break it. */ white_space = FALSE; word_break = FALSE; line_break = FALSE; nbsp_block = NULL; has_nbsp = FALSE; mb_sp = FALSE; if (multi_byte == FALSE) { /* check for textTransform properties and apply them */ if(state->top_state && state->top_state->style_stack) { char *property; StyleStruct *style_struct = STYLESTACK_GetStyleByIndex( state->top_state->style_stack, 0); if(style_struct) { property = STYLESTRUCT_GetString(style_struct, TEXT_TRANSFORM_STYLE); if(property) { lo_transform_text_from_string_method(tptr, property); } } } /* * Find the start of the word, skipping whitespace. */ w_start = tptr; while ((XP_IS_SPACE(*tptr))&&(*tptr != '\0')) { tptr++; } /* * if tptr has been moved at all, that means * there was some whitespace to skip, which means * we are allowed to put a linebreak before this * word if we want to. */ if (tptr != w_start) { int32 new_break_holder; int32 min_width; int32 indent; w_start = tptr; word_break = TRUE; new_break_holder = state->x + state->width; min_width = new_break_holder - state->break_holder; indent = state->list_stack->old_left_margin - state->win_left; min_width += indent; if (min_width > state->min_width) { state->min_width = min_width; } /* If we are not within content, allow break_holder * to be set to the new position where a line break can occur. * This fixes BUG #70782 */ if (state->breakable != FALSE) { state->break_holder = new_break_holder; } } /* * If we are in text that is supposed to be * justified, we want each word to be in a separate * text element, so if we just found a word break, * and there is already a word in the line buffer, * flush that word into its own element. */ if ((state->align_stack != NULL)&& (state->align_stack->alignment ==LO_ALIGN_JUSTIFY)&& (word_break != FALSE)&& (state->cur_ele_type == LO_TEXT)&& (state->line_buf_len != 0)) { /* set the current text offset for this new word */ block->buffer_read_index = tptr - (char *) block->text_buffer; lo_FlushLineBuffer(context, state); if (state->top_state->out_of_memory != FALSE) { return; } } /* * Find the end of the word. * Terminate the word, saving the char we replaced * with the terminator so it can be restored later. */ w_char_cnt = 0; #ifdef XP_WIN16 ccnt = state->line_buf_len; while ((!XP_IS_SPACE(*tptr))&&(*tptr != '\0')&&(ccnt < SIZE_LIMIT)) { if ((unsigned char)*tptr == NON_BREAKING_SPACE) { has_nbsp = TRUE; } tptr++; w_char_cnt++; ccnt++; } if (ccnt >= SIZE_LIMIT) { line_break = TRUE; } #else while ((!XP_IS_SPACE(*tptr))&&(*tptr != '\0')) { if ((unsigned char)*tptr == NON_BREAKING_SPACE) { /* *tptr = ' '; Replace this later */ has_nbsp = TRUE; } tptr++; w_char_cnt++; } #endif /* XP_WIN16 */ } else { has_nbsp = TRUE; /* * Find the start of the word, skipping whitespace. */ w_start = tptr; while ((XP_IS_SPACE(*tptr))&&(*tptr != '\0')) { tptr = INTL_NextChar(charset, tptr); } if (w_start != tptr) mb_sp = TRUE; /* * if tptr has been moved at all, that means * there was some whitespace to skip, which means * we are allowed to put a linebreak before this * word if we want to. */ /* * If this char is a two-byte thing, we can break * before it. */ if ((tptr != w_start)||((unsigned char)*tptr > 127)) { int32 new_break_holder; int32 min_width; int32 indent; /* If it's multibyte character, it always be able to break */ if (tptr == w_start) prev_word_breakable = TRUE; w_start = tptr; word_break = TRUE; new_break_holder = state->x + state->width; min_width = new_break_holder - state->break_holder; indent = state->list_stack->old_left_margin - state->win_left; min_width += indent; if (min_width > state->min_width) { state->min_width = min_width; } /* If we are not within content, allow break_holder * to be set to the new position where a line break can occur. * This fixes BUG #70782 */ if (state->breakable != FALSE) { state->break_holder = new_break_holder; } } else if (prev_word_breakable) { int32 new_break_holder; int32 min_width; int32 indent; prev_word_breakable = FALSE; w_start = tptr; word_break = TRUE; new_break_holder = state->x + state->width; min_width = new_break_holder - state->break_holder; indent = state->list_stack->old_left_margin - state->win_left; min_width += indent; if (min_width > state->min_width) { state->min_width = min_width; } /* If we are not within content, allow break_holder * to be set to the new position where a line break can occur. * This fixes BUG #70782 */ if (state->breakable != FALSE) { state->break_holder = new_break_holder; } } /* * Find the end of the word. * Terminate the word, saving the char we replaced * with the terminator so it can be restored later. */ w_char_cnt = 0; #ifdef XP_WIN16 ccnt = state->line_buf_len; while (( ((unsigned char)*tptr < 128) || (INTL_KinsokuClass(charset, (unsigned char *)tptr) == PROHIBIT_WORD_BREAK ) ) && (!XP_IS_SPACE(*tptr)) && (*tptr != '\0') && (ccnt < SIZE_LIMIT)) { intn c_len; char *tptr2; tptr2 = INTL_NextChar(charset, tptr); c_len = (intn)(tptr2 - tptr); tptr = tptr2; w_char_cnt += c_len; ccnt += c_len; } if (ccnt >= SIZE_LIMIT) { line_break = TRUE; } #else #if 0 while ( /* Change the order so we have better performance */ (*tptr != '\0') && (!XP_IS_SPACE(*tptr)) && ( ((unsigned char)*tptr < 128) || ((CS_UTF8 == charset) /* hack, since we know only CS_UTF8 have PROHIBIT_WORD_BREAK*/ && (INTL_KinsokuClass(charset, (unsigned char *)tptr) == PROHIBIT_WORD_BREAK )) ) ) #else while ( (*tptr != '\0') && (!XP_IS_SPACE(*tptr)) && ( ((unsigned char)*tptr < 128) || ((CS_UTF8 == charset) && (*(unsigned char *)tptr <= 0xE2)) /* In case of CS_UTF8, some code range like CJK character baundary is breakable. * While in range UCS2 < 0x2000 (roman), character baundary is not breakable. */ ) ) #endif { intn c_len; char *tptr2; tptr2 = INTL_NextChar(charset, tptr); c_len = (intn)(tptr2 - tptr); tptr = tptr2; w_char_cnt += c_len; } #endif /* XP_WIN16 */ } /* multi byte */ if (w_char_cnt > TEXT_CHUNK_LIMIT) { tptr = (char *)(tptr - (w_char_cnt - TEXT_CHUNK_LIMIT)); w_char_cnt = TEXT_CHUNK_LIMIT; } if ((state->line_buf_len + w_char_cnt) > TEXT_CHUNK_LIMIT) { lo_FlushLineBuffer(context, state); if (state->top_state->out_of_memory != FALSE) { return; } if (state->cur_ele_type != LO_TEXT) { lo_FreshText(state); state->cur_ele_type = LO_TEXT; } } if (multi_byte != FALSE) { if ((w_start == tptr)&&((unsigned char)*tptr > 127)) { tptr = INTL_NextChar(charset, tptr); } } w_end = tptr; tchar1 = *w_end; *w_end = '\0'; /* * If the "word" is just an empty string, this * is just whitespace that we may wish to compress out. */ if (*w_start == '\0') { white_space = TRUE; } /* * compress out whitespace if the last word added was also * whitespace. */ if ((white_space != FALSE)&&(state->trailing_space != FALSE)) { *w_end = tchar1; #ifdef LOCAL_DEBUG XP_TRACE(("Discarding(%s)\n", w_start)); #endif /* LOCAL_DEBUG */ continue; } /* * This places the preceeding space in front of * separate words on a line. * Unecessary if last item was trailng space. * * If there was a word break before this word, so we know it * was supposed to be separate, and if we are not at the * beginning of the line, and if the * preceeding word is not already whitespace, then add * a space before this word. */ if ((word_break != FALSE)&& (state->at_begin_line == FALSE)&& (state->trailing_space == FALSE)) { /* * Since word_break is true, we know * we skipped some spaces previously * so we know there is space to back up * the word pointer inside the buffer. */ if ((multi_byte == FALSE)||mb_sp) { w_start--; *w_start = ' '; } /* * If we are formatting breakable text * set break position to be just before this word. * This is where we will break this line if the * new word makes it too long. */ if (state->breakable != FALSE) { state->break_pos = state->line_buf_len; state->break_width = state->width; } } #ifdef LOCAL_DEBUG XP_TRACE(("Found Word (%s)\n", w_start)); #endif /* LOCAL_DEBUG */ /* * If this is an empty string, just throw it out * and move on */ if (*w_start == '\0') { *w_end = tchar1; #ifdef LOCAL_DEBUG XP_TRACE(("Throwing out empty string!\n")); #endif /* LOCAL_DEBUG */ continue; } /* * Now we catch those nasty non-breaking space special * characters and make them spaces. Yuck, so that * relayout in tables will still see the non-breaking * spaces, we need to copy the buffer here. */ if (has_nbsp != FALSE) { char *tmp_ptr; char *to_ptr; char *tmp_buf; nbsp_block = PA_ALLOC(XP_STRLEN(w_start) + 1); if (nbsp_block == NULL) { *w_end = tchar1; state->top_state->out_of_memory = TRUE; break; } PA_LOCK(tmp_buf, char *, nbsp_block); tmp_ptr = w_start; to_ptr = tmp_buf; while (*tmp_ptr != '\0') { *to_ptr = *tmp_ptr; if (((unsigned char)*to_ptr == NON_BREAKING_SPACE) && (CS_USER_DEFINED_ENCODING != charset)) { *to_ptr = ' '; } if(multi_byte) { int i; int len = INTL_CharLen(charset, (unsigned char *)tmp_ptr); to_ptr++; tmp_ptr++; for (i=1; (iline_buf_len + word_len + 1) > state->line_buf_size) { state->line_buf = PA_REALLOC( state->line_buf, (state->line_buf_size + word_len + LINE_BUF_INC)); if (state->line_buf == NULL) { *w_end = tchar1; if ((has_nbsp != FALSE)&&(nbsp_block != NULL)) { PA_UNLOCK(nbsp_block); PA_FREE(nbsp_block); nbsp_block = NULL; } state->top_state->out_of_memory = TRUE; break; } state->line_buf_size += (word_len + LINE_BUF_INC); } PA_LOCK(text_buf, char *, state->line_buf); XP_BCOPY(w_start, (char *)(text_buf + state->line_buf_len), (word_len + 1)); state->line_buf_len += word_len; PA_UNLOCK(state->line_buf); /* * Having added a word, we cannot be at the start of a line */ state->cur_ele_type = LO_TEXT; state->at_begin_line = FALSE; /* * Most common case is appending to the same line. * assume that is what we are doing here. */ text_data.text = state->line_buf; text_data.text_len = (int16)state->line_buf_len; text_data.text_attr = state->cur_text_block->text_attr; FE_GetTextInfo(context, &text_data, &(state->text_info)); state->width = lo_correct_text_element_width(&(state->text_info)); /* udpate the block's font info cache */ block->ascent = state->text_info.ascent; block->descent = state->text_info.descent; /* * Set line_break based on document window width */ #ifdef XP_WIN16 if (((state->x + state->width) > state->right_margin)||(line_break != FALSE)) #else if ((state->x + state->width) > state->right_margin) #endif /* XP_WIN16 */ { /* * INTL kinsoku line break, some of characters are not allowed to put * in the end of line or beginning of line */ if (multi_byte && (state->break_pos != -1)) { int cur_wordtype, pre_wordtype, pre_break_pos; cur_wordtype = INTL_KinsokuClass(charset, (unsigned char *) w_start); PA_LOCK(text_buf, char *, state->line_buf); pre_break_pos = INTL_PrevCharIdx(charset, (unsigned char *)text_buf, state->break_pos); pre_wordtype = INTL_KinsokuClass(charset, (unsigned char *)(text_buf + pre_break_pos)); if (pre_wordtype == PROHIBIT_END_OF_LINE || (cur_wordtype == PROHIBIT_BEGIN_OF_LINE && XP_IS_ALPHA(*(text_buf+pre_break_pos)) == FALSE)) state->break_pos = pre_break_pos; PA_UNLOCK(state->line_buf); } line_break = TRUE; } else { line_break = FALSE; } /* * We cannot break a line if we have no break positions. * Usually happens with a single line of unbreakable text. */ if ((line_break != FALSE)&&(state->break_pos == -1)) { /* * It may be possible to break a previous * text element on the same line. */ if (state->old_break_pos != -1) { lo_BreakOldElement(context, state); line_break = FALSE; } #ifdef XP_WIN16 else if (ccnt >= SIZE_LIMIT) { state->break_pos = state->line_buf_len - 1; } else { line_break = FALSE; } #else else { line_break = FALSE; } #endif /* XP_WIN16 */ } /* * If we are breaking the line here, flush the * line_buf, and then insert a linebreak. */ if (line_break != FALSE) { char *break_ptr; char *word_ptr; char *new_buf; PA_Block new_block; #ifdef LOCAL_DEBUG XP_TRACE(("LineBreak, flush text.\n")); #endif /* LOCAL_DEBUG */ /* * Find the breaking point, and the pointer * to the remaining word without its leading * space. */ PA_LOCK(text_buf, char *, state->line_buf); break_ptr = (char *)(text_buf + state->break_pos); /* word_ptr = (char *)(break_ptr + 1); */ word_ptr = break_ptr; if ((multi_byte == FALSE)||mb_sp) { word_ptr++; } /* * Copy the remaining word into its * own buffer. */ word_len = XP_STRLEN(word_ptr); new_block = PA_ALLOC((word_len + 1) * sizeof(char)); if (new_block == NULL) { PA_UNLOCK(state->line_buf); *w_end = tchar1; if ((has_nbsp != FALSE)&&(nbsp_block != NULL)) { PA_UNLOCK(nbsp_block); PA_FREE(nbsp_block); nbsp_block = NULL; } state->top_state->out_of_memory = TRUE; break; } PA_LOCK(new_buf, char *, new_block); XP_BCOPY(word_ptr, new_buf, (word_len + 1)); *break_ptr = '\0'; state->line_buf_len = state->line_buf_len - word_len; if ((multi_byte == FALSE)||(word_ptr != break_ptr)) { state->line_buf_len--; } text_data.text = state->line_buf; text_data.text_len = (int16)state->line_buf_len; text_data.text_attr = state->cur_text_block->text_attr; FE_GetTextInfo(context, &text_data,&(state->text_info)); PA_UNLOCK(state->line_buf); state->width = lo_correct_text_element_width( &(state->text_info)); lo_FlushLineBuffer(context, state); #ifdef EDITOR state->edit_current_offset += (word_ptr - text_buf); #endif if (state->top_state->out_of_memory != FALSE) { PA_UNLOCK(new_block); PA_FREE(new_block); if ((has_nbsp != FALSE)&&(nbsp_block != NULL)) { PA_UNLOCK(nbsp_block); PA_FREE(nbsp_block); nbsp_block = NULL; } return; } /* * Put on a linefeed element. * This line is finished and will be added * to the line array. */ lo_SoftLineBreak(context, state, TRUE); /* * If there was no remaining word, free up * the unnecessary buffer, and empty out * the line buffer. */ if (word_len == 0) { PA_UNLOCK(new_block); PA_FREE(new_block); state->line_buf_len = 0; state->width = 0; } else { PA_LOCK(text_buf, char *,state->line_buf); XP_BCOPY(new_buf, text_buf, (word_len + 1)); PA_UNLOCK(state->line_buf); PA_UNLOCK(new_block); PA_FREE(new_block); state->line_buf_len = word_len; text_data.text = state->line_buf; text_data.text_len = (int16)state->line_buf_len; text_data.text_attr = state->cur_text_block->text_attr; FE_GetTextInfo(context, &text_data, &(state->text_info)); state->width = lo_correct_text_element_width( &(state->text_info)); /* * Having added text, we are no longer at the * start of the line. */ state->at_begin_line = FALSE; state->cur_ele_type = LO_TEXT; } /* * having just broken the line, we have no break * position. */ state->break_pos = -1; state->break_width = 0; } else { /* this word fits, so update the text buffer position */ block->buffer_read_index = tptr - (char *) block->text_buffer; if (white_space != FALSE) { state->trailing_space = TRUE; } else { state->trailing_space = FALSE; } } *w_end = tchar1; /* * Free up the extra block used for non-breaking * spaces if we had to allocate one. */ if ((has_nbsp != FALSE)&&(nbsp_block != NULL)) { PA_UNLOCK(nbsp_block); PA_FREE(nbsp_block); nbsp_block = NULL; } } /* * if last char is multibyte, break position need to be set to * end of string */ if (multi_byte != FALSE && *tptr == '\0' && prev_word_breakable != FALSE) state->break_pos = state->line_buf_len; if ( ( state->cur_ele_type != LO_TEXT ) || ( state->line_buf_len == 0 ) ) { state->cur_text_block = NULL; } } /************************************* * Function: lo_FlushLineBuffer * * Description: Flush out the current line buffer of text * into a new text element, and add that element to * the end of the line list of elements. * * Params: Window context and document state. * * Returns: Nothing *************************************/ void lo_FlushLineBuffer(MWContext *context, lo_DocState *state) { LO_TextStruct *text_data; int32 baseline_inc; LO_TextBlock * block; baseline_inc = 0; #ifdef DEBUG assert (state); #endif block = state->cur_text_block; /* bail if we have nothing to do with text */ if ( ( block == NULL ) || ( state->cur_ele_type != LO_TEXT ) ) { return; } /* * If we're currently using the new break table layout, then bail to it */ if ( lo_UseBreakTable ( block ) ) { lo_FlushText ( context, state ); return; } /* * Make sure we have some text to flush */ if ( state->line_buf_len == 0 ) { return; } /* * LTNOTE: probably should be grabbing state edit_element and offset from * state. */ text_data = lo_new_text_element(context, state, NULL, 0); if (text_data == NULL) { state->top_state->out_of_memory = TRUE; return; } state->linefeed_state = 0; /* * Some fonts (particulatly italic ones with curly tails * on letters like 'f') have a left bearing that extends * back into the previous character. Since in this case the * previous character is probably not in the same font, we * move forward to avoid overlap. * * Those same funny fonts can extend past the last character, * and we also have to catch that, and advance the following text * to eliminate cutoff. */ if (state->text_info.lbearing < 0) { text_data->x_offset = state->text_info.lbearing * -1; } text_data->width = state->width; /* * record the current doc width and text buffer offset for use * during relayout. */ text_data->doc_width = state->right_margin - state->x; text_data->block_offset = block->buffer_read_index; XP_ASSERT(block->buffer_read_index <= 65535); baseline_inc = lo_compute_text_basline_inc ( state, block, text_data ); lo_AppendToLineList(context, state, (LO_Element *)text_data, baseline_inc); if ( block->startTextElement == NULL ) { block->startTextElement = text_data; block->endTextElement = text_data; } else { block->endTextElement = text_data; } text_data->height = state->text_info.ascent + state->text_info.descent; /* * If the element we just flushed had a breakable word * position in it, save that position in case we have * to go back and break this element before we finish * the line. */ if (state->break_pos != -1) { state->old_break = text_data; state->old_break_block = block; state->old_break_pos = state->break_pos; state->old_break_width = state->break_width; } state->line_buf_len = 0; state->x += state->width; state->width = 0; state->cur_ele_type = LO_NONE; } void lo_FlushTextBlock ( MWContext *context, lo_DocState *state ) { lo_FlushLineBuffer ( context, state ); state->cur_text_block = NULL; } /* Only the first call here actually changes the text color */ void lo_ChangeBodyTextFGColor(MWContext *context, lo_DocState *state, LO_Color *color) { if( (state->top_state->body_attr & BODY_ATTR_TEXT) != 0) return; /* Set the flag so we don't change the color again unless we relayout from the URL again. */ state->top_state->body_attr |= BODY_ATTR_TEXT; lo_SetBodyTextFGColor(context, state, color); } /* This REALLY sets the color. "state" may be NULL and it will be figured out */ void lo_SetBodyTextFGColor(MWContext *context, lo_DocState *state, LO_Color *color) { int32 doc_id; lo_TopState *top_state; lo_FontStack *fptr; LO_TextAttr *attr; if( !context ) return; if( !state ) { doc_id = XP_DOCID(context); top_state = lo_FetchTopState(doc_id); if (top_state != NULL && top_state->doc_state == NULL) return; state = top_state->doc_state; if (color == NULL) color = &lo_master_colors[LO_COLOR_FG]; } state->text_fg = *color; fptr = state->font_stack; /* * If we're inside a layer, then we want this color change * to only affect text in the layer. So, we push a font * (a copy of the top of the stack) onto the font stack * and change its color. This font will be popped in the * closing of the layer. */ if (lo_InsideLayer(state)) { LO_TextAttr tmp_attr; if (fptr) lo_CopyTextAttr(fptr->text_attr, &tmp_attr); else lo_SetDefaultFontAttr(state, &tmp_attr, context); tmp_attr.fg.red = STATE_DEFAULT_FG_RED(state); tmp_attr.fg.green = STATE_DEFAULT_FG_GREEN(state); tmp_attr.fg.blue = STATE_DEFAULT_FG_BLUE(state); attr = lo_FetchTextAttr(state, &tmp_attr); lo_PushFont(state, P_BODY, attr); } else if (fptr != NULL) { attr = fptr->text_attr; attr->fg.red = STATE_DEFAULT_FG_RED(state); attr->fg.green = STATE_DEFAULT_FG_GREEN(state); attr->fg.blue = STATE_DEFAULT_FG_BLUE(state); } } /* * Something has changed (probably the default FG and BG colors) * since the font stack was initialized in this state. * We need to reinitialie it to the new default font. * WARNING: This function depends on the assumption that no * elements have yet been placed in this state. */ void lo_ResetFontStack(MWContext *context, lo_DocState *state) { if (state->font_stack != NULL) { lo_FontStack *fstack; lo_FontStack *fptr; fptr = state->font_stack; while (fptr != NULL) { fstack = fptr; fptr = fptr->next; XP_DELETE(fstack); } state->font_stack = NULL; } state->font_stack = lo_DefaultFont(state, context); } /************************************* * Function: lo_PushFont * * Description: Push the text attribute information for a new * font onto the font stack. Also save the type of the * tag that caused the change. * * Params: Document state, tag type, and the text attribute * structure for the new font. * * Returns: Nothing *************************************/ void lo_PushFont(lo_DocState *state, intn tag_type, LO_TextAttr *attr) { lo_FontStack *fptr; fptr = XP_NEW(lo_FontStack); if (fptr == NULL) { return; } fptr->tag_type = tag_type; fptr->text_attr = attr; fptr->next = state->font_stack; state->font_stack = fptr;; } /************************************* * Function: lo_PopFontStack * * Description: This function pops the next font * off the font stack, and return the text attribute of the * previous font. * The last font on the font stack cannot be popped off. * * Params: Document state, and the tag type that caused the change. * * Returns: The LO_TextAttr structure of the font just passed. *************************************/ #ifndef DOM PRIVATE LO_TextAttr * lo_PopFontStack(lo_DocState *state, intn tag_type) { LO_TextAttr *attr; lo_FontStack *fptr; if (state->font_stack->next == NULL) { #ifdef LOCAL_DEBUG XP_TRACE(("Popped too many fonts!\n")); #endif /* LOCAL_DEBUG */ return(NULL); } fptr = state->font_stack; attr = fptr->text_attr; if (fptr->tag_type != tag_type) { #ifdef LOCAL_DEBUG XP_TRACE(("Warning: Font popped by different TAG than pushed it %d != %d\n", fptr->tag_type, tag_type)); #endif /* LOCAL_DEBUG */ } state->font_stack = fptr->next; XP_DELETE(fptr); return(attr); } #endif LO_TextAttr * lo_PopFont(lo_DocState *state, intn tag_type) { LO_TextAttr *attr; lo_FontStack *fptr; /* * This should never happen, but we are patching a * more serious problem that causes us to be called * here after the font stack has been freed. */ if ((state->font_stack == NULL)||(state->font_stack->next == NULL)) { #ifdef LOCAL_DEBUG XP_TRACE(("Popped too many fonts!\n")); #endif /* LOCAL_DEBUG */ return(NULL); } fptr = state->font_stack; attr = NULL; if (fptr->tag_type != P_ANCHOR) { attr = fptr->text_attr; if (fptr->tag_type != tag_type) { #ifdef LOCAL_DEBUG XP_TRACE(("Warning: Font popped by different TAG than pushed it %d != %d\n", fptr->tag_type, tag_type)); #endif /* LOCAL_DEBUG */ } state->font_stack = fptr->next; XP_DELETE(fptr); } else { while ((fptr->next != NULL)&&(fptr->next->tag_type == P_ANCHOR)) { fptr = fptr->next; } if (fptr->next->next != NULL) { lo_FontStack *f_tmp; f_tmp = fptr->next; fptr->next = fptr->next->next; attr = f_tmp->text_attr; XP_DELETE(f_tmp); } } return(attr); } void lo_PopAllAnchors(lo_DocState *state) { lo_FontStack *fptr; if (state->font_stack->next == NULL) { #ifdef LOCAL_DEBUG XP_TRACE(("Popped too many fonts!\n")); #endif /* LOCAL_DEBUG */ return; } /* * Remove all anchors on top of the font stack */ fptr = state->font_stack; while ((fptr->tag_type == P_ANCHOR)&&(fptr->next != NULL)) { lo_FontStack *f_tmp; f_tmp = fptr; fptr = fptr->next; XP_DELETE(f_tmp); } state->font_stack = fptr; /* * Remove all anchors buried in the stack */ while (fptr->next != NULL) { /* * Reset spurrious anchor color text entries */ if ((fptr->text_attr != NULL)&& (fptr->text_attr->attrmask & LO_ATTR_ANCHOR)) { LO_TextAttr tmp_attr; lo_CopyTextAttr(fptr->text_attr, &tmp_attr); tmp_attr.attrmask = tmp_attr.attrmask & (~LO_ATTR_ANCHOR); tmp_attr.fg.red = STATE_DEFAULT_FG_RED(state); tmp_attr.fg.green = STATE_DEFAULT_FG_GREEN(state); tmp_attr.fg.blue = STATE_DEFAULT_FG_BLUE(state); tmp_attr.bg.red = STATE_DEFAULT_BG_RED(state); tmp_attr.bg.green = STATE_DEFAULT_BG_GREEN(state); tmp_attr.bg.blue = STATE_DEFAULT_BG_BLUE(state); fptr->text_attr = lo_FetchTextAttr(state, &tmp_attr); } if (fptr->next->tag_type == P_ANCHOR) { lo_FontStack *f_tmp; f_tmp = fptr->next; fptr->next = fptr->next->next; XP_DELETE(f_tmp); } else { fptr = fptr->next; } } } void lo_FormatBullet(MWContext *context, lo_DocState *state, LO_BulletStruct *bullet, int32 *line_height, int32 *baseline) { LO_TextStruct tmp_text; LO_TextInfo text_info; LO_TextAttr *tptr; PA_Block buff; char *str; #define MIN_BULLET_SIZE 5 bullet->ele_id = NEXT_ELEMENT; /* bullet = (LO_BulletStruct *)lo_NewElement(context, state, LO_BULLET, NULL, 0); */ if (bullet == NULL) { #ifdef DEBUG assert (state->top_state->out_of_memory); #endif return; } /* TEXTATTR HERE */ tptr = bullet->text_attr; memset (&tmp_text, 0, sizeof (tmp_text)); buff = PA_ALLOC(1); if (buff == NULL) { state->top_state->out_of_memory = TRUE; return; } PA_LOCK(str, char *, buff); str[0] = ' '; PA_UNLOCK(buff); tmp_text.text = buff; tmp_text.text_len = 1; tmp_text.text_attr = tptr; FE_GetTextInfo(context, &tmp_text, &text_info); PA_FREE(buff); /* contain the bullet size so that it doesn't extend off the * left side of the page since we are using a negative offset * to place the bullet * * also subtract one to avoid the header code at the bottom * from triggering and messing up the alignment */ if(bullet->bullet_size*2 >= state->x-state->win_left) bullet->bullet_size = ((state->x-state->win_left)/2)-1; /* enforce a minumum bullet size */ if(bullet->bullet_size < 1) bullet->bullet_size = MIN_BULLET_SIZE; bullet->x = state->x - (2 * bullet->bullet_size); if (bullet->x < state->win_left) { bullet->x = state->win_left; } bullet->x_offset = 0; bullet->y = state->y; bullet->y_offset = (text_info.ascent + text_info.descent - bullet->bullet_size) / 2; bullet->width = bullet->bullet_size; bullet->height = bullet->bullet_size; *line_height = text_info.ascent + text_info.descent; *baseline = text_info.ascent; } void lo_UpdateStateAfterBullet(MWContext * context, lo_DocState *state, LO_BulletStruct *bullet, int32 line_height, int32 baseline) { state->baseline = baseline; state->line_height = line_height; /* * Clean up state */ /* * Supporting old mistakes made in some other browsers. * I will put the "correct code" here, but comment it out, since * some other browsers allowed headers inside lists, so we should to, sigh. state->linefeed_state = 0; */ state->at_begin_line = TRUE; state->cur_ele_type = LO_BULLET; if (bullet->x == state->win_left) { state->x += (bullet->x_offset + (2 * bullet->width)); } /* * Make at_begin_line be accurate * so we can detect the header * linefeed state deception later. */ state->at_begin_line = FALSE; /* * After much soul-searching (and brow-beating * by Jamie, I've agreed that really whitespace * should be compressed out at the start of a * list item. They can always add non-breaking * spaces if they want them. * Setting trailing space true means it won't * let the users add whitespace because it * thinks there already is some. */ state->trailing_space = TRUE; } void lo_PlaceBullet(MWContext *context, lo_DocState *state) { LO_BulletStruct *bullet = NULL; int32 line_height, baseline; #ifndef DOM LO_TextAttr tmp_attr; #endif LO_TextStruct tmp_text; LO_TextInfo text_info; LO_TextAttr *tptr; PA_Block buff; char *str; bullet = (LO_BulletStruct *)lo_NewElement(context, state, LO_BULLET, NULL, 0); if (bullet == NULL) { #ifdef DEBUG assert (state->top_state->out_of_memory); #endif return; } bullet->type = LO_BULLET; bullet->next = NULL; bullet->prev = NULL; bullet->FE_Data = NULL; bullet->level = state->list_stack->level; bullet->bullet_type = state->list_stack->bullet_type; /* try and get a bullet type from style sheets */ if(state && state->top_state && state->top_state->style_stack) { StyleStruct *style_struct = STYLESTACK_GetStyleByIndex( state->top_state->style_stack, 0); if(style_struct) { char *list_style_prop = STYLESTRUCT_GetString( style_struct, LIST_STYLE_TYPE_STYLE); if(list_style_prop) { bullet->bullet_type = lo_list_bullet_type(list_style_prop, P_UNUM_LIST); XP_FREE(list_style_prop); } } } bullet->ele_attrmask = 0; bullet->sel_start = -1; bullet->sel_end = -1; /* TEXTATTR HERE */ #ifdef DOM tptr = lo_GetCurrentTextAttr(state, context); #else if(state->font_stack) { lo_CopyTextAttr(state->font_stack->text_attr, &tmp_attr); } else { lo_SetDefaultFontAttr(state, &tmp_attr, context); } tptr = lo_FetchTextAttr(state, &tmp_attr); #endif memset (&tmp_text, 0, sizeof (tmp_text)); buff = PA_ALLOC(1); if (buff == NULL) { state->top_state->out_of_memory = TRUE; return; } PA_LOCK(str, char *, buff); str[0] = ' '; PA_UNLOCK(buff); tmp_text.text = buff; tmp_text.text_len = 1; tmp_text.text_attr = tptr; FE_GetTextInfo(context, &tmp_text, &text_info); PA_FREE(buff); bullet->bullet_size = (text_info.ascent + text_info.descent) / 2; bullet->text_attr = tptr; lo_FormatBullet(context, state, bullet, &line_height, &baseline); lo_AppendToLineList(context, state, (LO_Element *)bullet, 0); lo_UpdateStateAfterBullet(context, state, bullet, line_height, baseline); } void lo_FormatBulletStr(MWContext *context, lo_DocState *state, LO_TextStruct *bullet_text, int32 *line_height, int32 *baseline) { LO_TextInfo text_info; FE_GetTextInfo(context, bullet_text, &text_info); bullet_text->x = state->x - (bullet_text->height / 2) - bullet_text->width; if (bullet_text->x < state->win_left) { bullet_text->x = state->win_left; } bullet_text->x_offset = 0; bullet_text->y = state->y; bullet_text->y_offset = 0; state->baseline = text_info.ascent; state->line_height = (intn) bullet_text->height; *baseline = text_info.ascent; *line_height = bullet_text->height; } void lo_UpdateStateAfterBulletStr(MWContext *context, lo_DocState *state, LO_TextStruct *bullet_text, int32 line_height, int32 baseline) { state->baseline = baseline; state->line_height = line_height; /* * Clean up state */ /* * Supporting old mistakes made in some other browsers. * I will put the "correct code" here, but comment it out, since * some other browsers allowed headers inside lists, so we should to, sigh. state->linefeed_state = 0; state->at_begin_line = FALSE; */ state->at_begin_line = TRUE; state->cur_ele_type = LO_TEXT; } void lo_PlaceBulletStr(MWContext *context, lo_DocState *state) { intn len; char str2[22]; char *str; char *str3; PA_Block buff; LO_TextStruct *bullet_text = NULL; LO_TextInfo text_info; int bullet_type; int32 line_height, baseline; bullet_text = (LO_TextStruct *)lo_NewElement(context, state, LO_TEXT, NULL, 0); if (bullet_text == NULL) { #ifdef DEBUG assert (state->top_state->out_of_memory); #endif return; } bullet_type = state->list_stack->bullet_type; /* try and get a bullet type from style sheets */ if(state && state->top_state && state->top_state->style_stack) { StyleStruct *style_struct = STYLESTACK_GetStyleByIndex( state->top_state->style_stack, 0); if(style_struct) { char *list_style_prop = STYLESTRUCT_GetString( style_struct, LIST_STYLE_TYPE_STYLE); if(list_style_prop) { bullet_type = lo_list_bullet_type(list_style_prop, P_NUM_LIST); XP_FREE(list_style_prop); } } } if( EDT_IS_EDITOR( context )) { switch( bullet_type ){ case BULLET_ALPHA_L: str = "A"; break; case BULLET_ALPHA_S: str = "a"; break; case BULLET_NUM_S_ROMAN: str = "x"; break; case BULLET_NUM_L_ROMAN: str = "X"; break; default: str = "#"; break; } len = XP_STRLEN(str); buff = PA_ALLOC(len + 1); if (buff != NULL) { PA_LOCK(str3, char *, buff); XP_STRCPY(str3, str); PA_UNLOCK(buff); } } else { if (bullet_type == BULLET_ALPHA_S) { buff = lo_ValueToAlpha(state->list_stack->value, FALSE, &len); } else if (bullet_type == BULLET_ALPHA_L) { buff = lo_ValueToAlpha(state->list_stack->value, TRUE, &len); } else if (bullet_type == BULLET_NUM_S_ROMAN) { buff = lo_ValueToRoman(state->list_stack->value, FALSE, &len); } else if (bullet_type == BULLET_NUM_L_ROMAN) { buff = lo_ValueToRoman(state->list_stack->value, TRUE, &len); } else { XP_SPRINTF(str2, "%d.", (intn)state->list_stack->value); len = XP_STRLEN(str2); buff = PA_ALLOC(len + 1); if (buff != NULL) { PA_LOCK(str, char *, buff); XP_STRCPY(str, str2); PA_UNLOCK(buff); } else { state->top_state->out_of_memory = TRUE; } } } if (buff == NULL) { return; } bullet_text->bullet_type = bullet_type; bullet_text->text = buff; bullet_text->text_len = len; /* TEXTATTR HERE */ #ifdef DOM bullet_text->text_attr = lo_GetCurrentTextAttr(state, context); #else bullet_text->text_attr = state->font_stack->text_attr; #endif FE_GetTextInfo(context, bullet_text, &text_info); bullet_text->width = lo_correct_text_element_width(&text_info); bullet_text->height = text_info.ascent + text_info.descent; bullet_text->line_height = 0; bullet_text->y_offset = 0; bullet_text->x_offset = 0; bullet_text->type = LO_TEXT; bullet_text->ele_id = NEXT_ELEMENT; lo_FormatBulletStr(context, state, bullet_text, &line_height, &baseline); bullet_text->anchor_href = state->current_anchor; bullet_text->ele_attrmask = 0; if (state->breakable != FALSE) { bullet_text->ele_attrmask |= LO_ELE_BREAKABLE; } bullet_text->sel_start = -1; bullet_text->sel_end = -1; bullet_text->next = NULL; bullet_text->prev = NULL; bullet_text->FE_Data = NULL; lo_AppendToLineList(context, state, (LO_Element *)bullet_text, 0); state->baseline = text_info.ascent; state->line_height = (intn) bullet_text->height; lo_UpdateStateAfterBulletStr(context, state, bullet_text, line_height, baseline); } static LO_Element * lo_make_quote_text(MWContext *context, lo_DocState *state, int32 margin) { PA_Block buff; char *str; LO_TextStruct *quote_text; LO_TextAttr tmp_attr; LO_TextInfo text_info; quote_text = (LO_TextStruct *)lo_NewElement(context, state, LO_TEXT, NULL, 0); if (quote_text == NULL) { #ifdef DEBUG assert (state->top_state->out_of_memory); #endif return(NULL); } buff = PA_ALLOC(2); if (buff == NULL) { state->top_state->out_of_memory = TRUE; return(NULL); } PA_LOCK(str, char *, buff); str[0] = '>'; str[1] = '\0'; PA_UNLOCK(buff); quote_text->text = buff; quote_text->text_len = 1; /* * Fill in default fixed font information. */ /* TEXTATTR HERE -- why does this not use the font stack? */ lo_SetDefaultFontAttr(state, &tmp_attr, context); tmp_attr.fontmask |= LO_FONT_FIXED; quote_text->text_attr = lo_FetchTextAttr(state, &tmp_attr); FE_GetTextInfo(context, quote_text, &text_info); quote_text->width = lo_correct_text_element_width(&text_info); quote_text->height = text_info.ascent + text_info.descent; quote_text->type = LO_TEXT; quote_text->ele_id = 0; quote_text->x = margin; if (quote_text->x < state->win_left) { quote_text->x = state->win_left; } quote_text->x_offset = 0; quote_text->y = state->y; quote_text->y_offset = 0; quote_text->line_height = 0; quote_text->anchor_href = state->current_anchor; quote_text->ele_attrmask = 0; if (state->breakable != FALSE) { quote_text->ele_attrmask |= LO_ELE_BREAKABLE; } quote_text->bullet_type = BULLET_MQUOTE; quote_text->sel_start = -1; quote_text->sel_end = -1; quote_text->next = NULL; quote_text->prev = NULL; quote_text->FE_Data = NULL; state->baseline = text_info.ascent; return((LO_Element *)quote_text); } static LO_Element * lo_make_quote_bullet(MWContext *context, lo_DocState *state, int32 margin) { PA_Block buff; char *str; LO_BulletStruct *bullet = NULL; LO_TextAttr tmp_attr; LO_TextInfo text_info; LO_TextStruct tmp_text; LO_TextAttr *tptr; int32 bullet_size; bullet = (LO_BulletStruct *)lo_NewElement(context, state, LO_BULLET, NULL, 0); if (bullet == NULL) { #ifdef DEBUG assert (state->top_state->out_of_memory); #endif return(NULL); } /* TEXTATTR HERE -- why does this not use font/style info? */ lo_SetDefaultFontAttr(state, &tmp_attr, context); tmp_attr.fg.red = 0; tmp_attr.fg.green = 0; tmp_attr.fg.blue = 255; #ifdef DOM tptr = lo_FillInTextStyleInfo(state, context, &tmp_attr, JS_TRUE); #else tptr = lo_FetchTextAttr(state, &tmp_attr); #endif memset (&tmp_text, 0, sizeof (tmp_text)); buff = PA_ALLOC(1); if (buff == NULL) { state->top_state->out_of_memory = TRUE; return(NULL); } PA_LOCK(str, char *, buff); str[0] = ' '; PA_UNLOCK(buff); tmp_text.text = buff; tmp_text.text_len = 1; tmp_text.text_attr = tptr; FE_GetTextInfo(context, &tmp_text, &text_info); PA_FREE(buff); bullet_size = text_info.ascent + text_info.descent; if (bullet_size < 5) { bullet_size = 5; } bullet->type = LO_BULLET; bullet->ele_id = 0; bullet->x = margin; if (bullet->x < state->win_left) { bullet->x = state->win_left; } bullet->x_offset = 0; bullet->y = state->y; bullet->y_offset = 0; bullet->width = 5; bullet->height = bullet_size; bullet->next = NULL; bullet->prev = NULL; bullet->FE_Data = NULL; bullet->level = state->list_stack->level; bullet->bullet_type = BULLET_MQUOTE; bullet->text_attr = tptr; bullet->ele_attrmask = 0; bullet->sel_start = -1; bullet->sel_end = -1; state->baseline = text_info.ascent; return((LO_Element *)bullet); } static void lo_insert_quote_characters(MWContext *context, lo_DocState *state) { LO_Element *eptr; LO_Element *elist; lo_ListStack *lptr; elist = NULL; lptr = state->list_stack; while (lptr != NULL) { eptr = NULL; if (lptr->quote_type == QUOTE_JWZ) { eptr = lo_make_quote_text(context, state, lptr->mquote_x); } else if (lptr->quote_type == QUOTE_CITE) { eptr = lo_make_quote_bullet(context, state, lptr->mquote_x); } if (eptr != NULL) { eptr->lo_any.next = elist; elist = eptr; } lptr = lptr->next; } eptr = elist; while (eptr != NULL) { LO_Element *tmp_ele; tmp_ele = eptr; eptr = eptr->lo_any.next; tmp_ele->lo_any.next = NULL; tmp_ele->lo_any.ele_id = NEXT_ELEMENT; lo_AppendToLineList(context, state, tmp_ele, 0); state->line_height = (intn)tmp_ele->lo_any.height; state->at_begin_line = TRUE; state->cur_ele_type = LO_TEXT; } } void lo_PlaceQuoteMarker(MWContext *context, lo_DocState *state, lo_ListStack *lptr) { LO_Element *eptr; if (lptr != NULL) { eptr = NULL; if (lptr->quote_type == QUOTE_JWZ) { eptr = lo_make_quote_text(context, state, lptr->mquote_x); } else if (lptr->quote_type == QUOTE_CITE) { eptr = lo_make_quote_bullet(context, state, lptr->mquote_x); } if (eptr != NULL) { eptr->lo_any.ele_id = NEXT_ELEMENT; lo_AppendToLineList(context, state, eptr, 0); state->line_height = (intn)eptr->lo_any.height; state->at_begin_line = TRUE; if (lptr->quote_type == QUOTE_JWZ) { state->cur_ele_type = LO_TEXT; } else if (lptr->quote_type == QUOTE_CITE) { state->cur_ele_type = LO_BULLET; } } } } void lo_UpdateStateAfterLineBreak( MWContext *context, lo_DocState *state, Bool updateFE ) { int32 line_width; /* * if this linefeed has a zero height, make it the height * of the current font. */ if (state->line_height == 0) { state->line_height = state->text_info.ascent + state->text_info.descent; if ((state->line_height <= 0) #ifndef DOM &&(state->font_stack != NULL)&& (state->font_stack->text_attr != NULL) #endif ) { lo_fillin_text_info(context, state); state->line_height = state->text_info.ascent + state->text_info.descent; } /* * This should never happen, but we have it * covered just in case it does :-) */ if (state->line_height <= 0) { state->line_height = state->default_line_height; } } if (state->end_last_line != NULL) { line_width = state->end_last_line->lo_any.x + state->win_right; } else { line_width = state->x + state->win_right; } if (line_width > state->max_width) { state->max_width = line_width; } /* if LineHeightStack exists use it to offset the new Y value */ if(state->cur_ele_type != LO_SUBDOC && state->line_height_stack) { state->y += state->line_height_stack->height; } else { state->y = state->y + state->line_height; } state->x = state->left_margin; state->width = 0; state->at_begin_line = TRUE; state->trailing_space = FALSE; state->line_height = 0; state->break_holder = state->x; state->linefeed_state++; if (state->linefeed_state > 2) { state->linefeed_state = 2; } /* * Reset the left and right margins */ lo_FindLineMargins(context, state, updateFE); state->x = state->left_margin; } void lo_UpdateFEProgressBar( MWContext *context, lo_DocState *state ) { if (state->is_a_subdoc == SUBDOC_NOT) { int32 percent; if (state->top_state->total_bytes < 1) { percent = -1; } else { percent = (100 * state->top_state->layout_bytes) / state->top_state->total_bytes; if (percent > 100) { percent = 100; } } if ((percent == 100)||(percent < 0)|| (percent > (state->top_state->layout_percent + 1))) { #if !defined(SMOOTH_PROGRESS) if(!state->top_state->is_binary) FE_SetProgressBarPercent(context, percent); #endif /* !defined(SMOOTH_PROGRESS) */ state->top_state->layout_percent = (intn)percent; } } } void lo_UpdateFEDocSize( MWContext *context, lo_DocState *state ) { /* * Tell the front end how big the document is right now. * Only do this for the top level document. */ if ((state->is_a_subdoc == SUBDOC_NOT) &&(state->display_blocked == FALSE) #ifdef EDITOR &&(!state->edit_relayout_display_blocked) #endif ) { /* * Don't resize the layer if we're laying out a block. This * will be done when the line is added to the block. */ if (!lo_InsideLayer(state)) { LO_SetDocumentDimensions(context, state->max_width, state->y); } } } void lo_FillInLineFeed( MWContext *context, lo_DocState *state, int32 break_type, uint32 clear_type, LO_LinefeedStruct *linefeed ) { linefeed->type = LO_LINEFEED; linefeed->ele_id = NEXT_ELEMENT; linefeed->x = state->x; linefeed->x_offset = 0; linefeed->y = state->y; linefeed->y_offset = 0; /* * If we're laying out a block, we want the contents of the block * to determine the size of the block. The right margin is nothing * more than a hint for where to wrap the contents. We don't want * the linefeed to extend out to the right margin, because it * unnecessarily extends the block contents. */ if (state->layer_nest_level > 0) { linefeed->width = 0; } else linefeed->width = state->right_margin - state->x; if (linefeed->width < 0) { linefeed->width = 0; } linefeed->height = state->line_height; /* * if this linefeed has a zero height, make it the height * of the current font. */ if (linefeed->height == 0) { linefeed->height = state->text_info.ascent + state->text_info.descent; if ((linefeed->height <= 0) #ifndef DOM &&(state->font_stack != NULL)&& (state->font_stack->text_attr != NULL) #endif ) { lo_fillin_text_info(context, state); linefeed->height = state->text_info.ascent + state->text_info.descent; } /* * This should never happen, but we have it * covered just in case it does :-) */ if (linefeed->height <= 0) { linefeed->height = state->default_line_height; } } linefeed->line_height = linefeed->height; linefeed->FE_Data = NULL; linefeed->anchor_href = state->current_anchor; #ifdef DOM linefeed->text_attr = lo_GetCurrentTextAttr(state, context); #else if (state->font_stack == NULL) { LO_TextAttr tmp_attr; LO_TextAttr *tptr; /* * Fill in default font information. */ lo_SetDefaultFontAttr(state, &tmp_attr, context); tptr = lo_FetchTextAttr(state, &tmp_attr); linefeed->text_attr = tptr; } else { /* TEXTATTR HERE */ linefeed->text_attr = state->font_stack->text_attr; } #endif linefeed->baseline = state->baseline; linefeed->ele_attrmask = 0; linefeed->sel_start = -1; linefeed->sel_end = -1; linefeed->next = NULL; linefeed->prev = NULL; linefeed->break_type = (uint8) break_type; linefeed->clear_type = (uint8) clear_type; } Bool lo_CanUseBreakTable ( lo_DocState * state ) { Bool useBreakTable; useBreakTable = TRUE; #ifndef FAST_MULTI /* * We also need some sort of check for the script - for example * Arabic should go through the old algorithm for now. */ if ( kMBTextParseAttribute == lo_GetTextParseAttributes(state) ) { useBreakTable = FALSE; } #endif /* * Justified text is currently broken, route it through old layout for now */ if ( ( state->align_stack != NULL ) && ( state->align_stack->alignment == LO_ALIGN_JUSTIFY ) ) { useBreakTable = FALSE; } #ifndef FAST_EDITOR if ( EDT_IS_EDITOR( context ) ) { useBreakTable = FALSE; } #endif #ifdef XP_MAC if ( !gCallNewText ) { useBreakTable = FALSE; } #endif return useBreakTable; } /* * Is the current text that's being layed out using the break table * layout algorithm? */ Bool lo_UseBreakTable ( LO_TextBlock * block ) { Bool useBreakTable; useBreakTable = FALSE; if ( block != NULL ) { if ( block->break_table != NULL ) { useBreakTable = TRUE; } } return useBreakTable; } int32 lo_compute_text_basline_inc ( lo_DocState * state, LO_TextBlock * block, LO_TextStruct * text_data ) { int32 line_inc; int32 baseline_inc; /* * The baseline of the text element just added to the line may be * less than or greater than the baseline of the rest of the line * due to font changes. If the baseline is less, this is easy, * we just increase y_offest to move the text down so the baselines * line up. For greater baselines, we can't move the text up to * line up the baselines because we will overlay the previous line, * so we have to move all the previous elements in this line down. * * If the baseline is zero, we are the first element on the line, * and we get to set the baseline. */ line_inc = 0; baseline_inc = 0; if (state->baseline == 0) { state->baseline = block->ascent; if (state->line_height < (state->baseline + block->descent)) { state->line_height = state->baseline + block->descent; } } else if (block->ascent < state->baseline) { text_data->y_offset = state->baseline - block->ascent; if ((text_data->y_offset + block->ascent + block->descent) > state->line_height) { line_inc = text_data->y_offset + block->ascent + block->descent - state->line_height; } } else { baseline_inc = block->ascent - state->baseline; if ((text_data->y_offset + block->ascent + block->descent - baseline_inc) > state->line_height) { line_inc = text_data->y_offset + block->ascent + block->descent - state->line_height - baseline_inc; } } state->baseline += (intn) baseline_inc; state->line_height += (intn) (baseline_inc + line_inc); return baseline_inc; } void lo_FlushTextElement ( MWContext * context, lo_DocState * state, LO_TextBlock * block, LO_TextStruct * element ) { int32 baseline_inc; /* update the text layout state as if we had just layed this element out */ block->buffer_read_index = element->block_offset; state->width = element->width; element->ele_id = NEXT_ELEMENT; element->x = state->x; element->y = state->y; element->y_offset = 0; element->sel_start = -1; element->sel_end = -1; baseline_inc = lo_compute_text_basline_inc ( state, block, element ); element->prev = NULL; element->next = NULL; lo_AppendToLineList ( context, state, (LO_Element *) element, baseline_inc ); state->line_buf_len = 0; state->x += state->width; state->width = 0; state->cur_ele_type = LO_NONE; /* update the element list for this block */ if ( block->startTextElement == NULL ) { block->startTextElement = element; } block->endTextElement = element; } uint32 lo_FindBlockOffset ( LO_TextBlock * block, LO_TextStruct * fromElement ) { uint32 blockOffset; LO_Element * endElement; LO_Element * element; blockOffset = 0; if ( fromElement != NULL ) { /* run through all elements in this text block. the correct block offset is belongs to */ /* the previous element in this list */ element = (LO_Element *) block->startTextElement; endElement = (LO_Element *) block->endTextElement; while ( element != NULL ) { if ( element == endElement ) { break; } /* is it the one we're looking for? */ if ( element == (LO_Element *) fromElement ) { break; } if ( element->type == LO_TEXT ) { blockOffset = element->lo_text.block_offset; } element = element->lo_any.next; } } return blockOffset; } void lo_RelayoutTextElements ( MWContext * context, lo_DocState * state, LO_TextBlock * block, LO_TextStruct * fromElement ) { LO_Element * next; LO_Element * element; LO_Element * startElement; LO_Element * endElement; uint32 lineWidth; Bool done; Bool fastPreformat; /* start at the beginning of this text block */ block->buffer_read_index = 0; block->break_read_index = 0; /* we will rebuild the element list for this block as we go */ startElement = (LO_Element *) block->startTextElement; endElement = (LO_Element *) block->endTextElement; block->startTextElement = NULL; block->endTextElement = NULL; if ( fromElement == NULL ) fromElement = (LO_TextStruct *)lo_tv_GetNextLayoutElement ( state, (LO_Element*)block, FALSE ); if ( fromElement == NULL) fromElement = (LO_TextStruct *) startElement; /* sanity check */ if ( fromElement == NULL ) return; /* * We need to run through all elements up to start element and place them * back in the line list. We also need to recycle anything that's not text * (linefeeds and bullets) */ element = startElement; while ( element != (LO_Element *) fromElement ) { next = lo_tv_GetNextLayoutElement ( state, element, FALSE ); /* if this element is text, put it on the line list */ switch ( element->lo_any.type ) { case LO_TEXT: lo_PrepareElementForReuse ( context, state, element, element->lo_any.edit_element, element->lo_any.edit_offset ); lo_FlushTextElement ( context, state, block, (LO_TextStruct *) element ); break; case LO_LINEFEED: /* recycle this element */ element->lo_any.prev = NULL; element->lo_any.next = NULL; lo_RecycleElements( context, state, element ); /* and then add a new linefeed */ lo_rl_AddSoftBreakAndFlushLine ( context, state ); break; default: element->lo_any.prev = NULL; element->lo_any.next = NULL; lo_RecycleElements( context, state, element ); break; } element = next; } /* * Now, we may not need to lay any of the following elements out as out * layout environment may not have changed. So, run through the remaining * elements until we find the first one that's changed. * * We have to layout the last element using the proper code path * so that we can correctly update the state record with the last * break position and other flags. * * Column and line wrapped preformatted text can always reuse the * elements as it's wrapping will never change. Word wrapped * preformatted text may change if the document width changes. */ element = (LO_Element *) fromElement; fastPreformat = ( block->format_mode == PRE_TEXT_YES ) || ( block->format_mode == PRE_TEXT_COLS ); while ( element != NULL ) { next = lo_tv_GetNextLayoutElement ( state, element, FALSE ); /* if this element is text, see if it will fit. otherwise recycle it */ switch ( element->lo_any.type ) { case LO_TEXT: /* * We only assume this element can be reused if the * line width is exactly the same as last time. If the * line is longer, we could potentially reuse this * element (the next one may appear on this line as * well) but we won't be able to set the state's * old_break_position, which may be needed! * * We can also always flush column or line wrapped * preformatted text (word wrapped preformatted text * may need to be layed out again as it's wrapping may * change). */ lineWidth = state->right_margin - state->x; if ( fastPreformat || ( element->lo_text.doc_width == lineWidth ) ) { lo_PrepareElementForReuse ( context, state, element, element->lo_any.edit_element, element->lo_any.edit_offset ); lo_FlushTextElement ( context, state, block, (LO_TextStruct *) element ); } else { /* the size has changed, we must relayout this element */ done = TRUE; } break; case LO_LINEFEED: /* recycle this element */ element->lo_any.prev = NULL; element->lo_any.next = NULL; lo_RecycleElements( context, state, element ); /* Fix for bug 129639: Only add the new linefeed for preformatted text. Calling lo_rl_AddSoftBreakAndFlushLine() causes extra line feeds to be generated for regular text. */ if (fastPreformat) lo_rl_AddSoftBreakAndFlushLine ( context, state ); break; default: element->lo_any.prev = NULL; element->lo_any.next = NULL; lo_RecycleElements( context, state, element ); break; } if ( element == endElement ) break; element = next; } } LO_Element * lo_RelayoutTextBlock ( MWContext * context, lo_DocState * state, LO_TextBlock * block, LO_TextStruct * fromElement ) { LO_Element * next; LO_Element * endElement; LO_Element * lo_ele; state->cur_text_block = block; /* * Update some of the global state information */ state->breakable = block->ele_attrmask & LO_ELE_BREAKABLE; /* get the next element for the overall relayout process */ if ( block->endTextElement != NULL ) { next = lo_tv_GetNextLayoutElement ( state, (LO_Element *) block->endTextElement, FALSE ); } else { next = lo_tv_GetNextLayoutElement ( state, (LO_Element *) block, FALSE ); } /* * In the relayout case we might be able to skip layout for some * elements that have not changed. This happens frequenty for * typing in the editor and occasionaly in table layout (very * occasionally on resizes). * * To do this, we run through the text elements until we come * across one whose width does not match the layout width or whose * text has changed. That element and all others are then * recycled. */ if ( EDT_IS_EDITOR( context ) ) { /* * for the editor we don't want to relayout the text elements * that precede the current one. we just want to start laying * out afresh from this specified element to the end of the * text block. the editor will take care of merging the * elements back in */ block->buffer_read_index = lo_FindBlockOffset ( block, fromElement ); state->edit_current_element = block->edit_element; state->edit_current_offset = 0; /* if we're laying this element out, then we need to reinsert it on the line list */ if ( ( block->startTextElement == fromElement ) || ( fromElement == NULL ) ) { /* record the current edit element for later use */ lo_PrepareElementForReuse ( context, state, (LO_Element *) block, block->edit_element, block->edit_offset ); block->ele_id = NEXT_ELEMENT; block->x = state->x; block->y = state->y; block->x_offset = 0; block->y_offset = 0; /* free all the text elements that we're going to reflow */ endElement = (LO_Element *) block->endTextElement; lo_ele = (LO_Element *) block->startTextElement; while ( lo_ele != NULL ) { LO_Element * next_ele; next_ele = lo_ele->lo_any.next; lo_ele->lo_any.next = NULL; lo_ele->lo_any.prev = NULL; lo_RecycleElements( context, state, lo_ele ); if ( lo_ele == endElement ) { break; } lo_ele = next_ele; } block->startTextElement = NULL; block->endTextElement = NULL; block->prev = NULL; block->next = NULL; lo_AppendToLineList ( context, state, (LO_Element *) block, 0 ); } else { LO_TextStruct * lastText; Bool hitFromElement; /* We're reflowing from somewhere within the text block * (past the first element). We need to reset the * endTextElement as well as recycle from the fromElement * to the end of the text block */ hitFromElement = FALSE; endElement = (LO_Element *) block->endTextElement; lo_ele = (LO_Element *) block->startTextElement; lastText = block->startTextElement; while ( lo_ele != NULL ) { LO_Element * next_ele; next_ele = lo_ele->lo_any.next; if ( lo_ele == (LO_Element *) fromElement ) { hitFromElement = TRUE; } /* if we've found the fromElement, we need to start recycling */ if ( hitFromElement ) { lo_ele->lo_any.next = NULL; lo_ele->lo_any.prev = NULL; lo_RecycleElements( context, state, lo_ele ); } if ( lo_ele == endElement ) { break; } /* if we haven't hit the from element and this is a text element, it may be the new end */ /* element for the block */ if ( !hitFromElement && ( lo_ele->type == LO_TEXT ) ) { lastText = &lo_ele->lo_text; } lo_ele = next_ele; } /* reset the endElement for the block */ block->endTextElement = lastText; } } else { /* put the text block back in the line list and then add any existing elements that we can */ block->prev = NULL; block->next = NULL; block->ele_id = NEXT_ELEMENT; block->x = state->x; block->y = state->y; lo_AppendToLineList ( context, state, (LO_Element *) block, 0 ); lo_RelayoutTextElements ( context, state, block, fromElement ); } /* Because we're lame for now we just delete all the old linefeeds * and lay the text out afresh */ /* Tell everybody we're laying out text */ if (state->cur_ele_type != LO_TEXT) { lo_FreshText(state); state->cur_ele_type = LO_TEXT; } /* actually layout the text */ state->preformatted = block->format_mode; if ( lo_UseBreakTable ( block ) ) { lo_SetupBreakState ( block ); /* be sure to set up the editor offset */ if ( EDT_IS_EDITOR( context ) ) { state->edit_force_offset = TRUE; state->edit_current_offset = block->buffer_read_index; } lo_LayoutTextBlock ( context, state, TRUE ); } else if ( block->format_mode == PRE_TEXT_NO ) { lo_LayoutFormattedText ( context, state, block ); } else { lo_LayoutPreformattedText ( context, state, block ); } /* * If there's text left in the line buffer, then flush it. * * BRAIN DAMAGE: We don't want to do that here - there may be a * following text block that continues this same text buffer. */ lo_FlushLineBuffer(context, state); return next; } Bool lo_ChangeText ( LO_TextBlock * block, char * text ) { uint32 length; /* * Reset the text contents for this text block. If we have a break table, * then we need to rebuild it. */ #ifdef LOCAL_DEBUG XP_TRACE( ("Setting text for text block %lx to %s", block, text) ); #endif length = XP_STRLEN ( text ) + 1; if ( length > block->buffer_write_index ) { if ( !lo_GrowTextBlock ( block, length - block->buffer_write_index ) ) { return FALSE; } } if ( lo_UseBreakTable ( block ) ) { block->buffer_write_index = 0; block->last_buffer_write_index = 0; block->break_write_index = 0; block->last_break_offset = 0; lo_AppendTextToBlock ( NULL, NULL, block, text ); } else { /* for old style text we just want to replace the buffer */ XP_BCOPY ( text, (char *) block->text_buffer, length ); block->buffer_write_index = length; } return TRUE; } /* * * =========================================================================== * * New text layout * * ========================================================================== */ /* * Break Table constants */ #define MAX_NATURAL_LENGTH 0xAL #define LINE_FEED 0xBL #define BYTE_LENGTH 0xCL #define WORD_LENGTH 0xDL #define LONG_LENGTH 0xEL #define MULTI_BYTE 0xFL #define MULTI_BYTE_DATA_SIZE 16 /* * Break Position Magic Constants */ #define OVERRAN_BREAK_TABLE -1 #define BREAK_LINEFEED -2 #define TEXT_BUFFER_INC 256 #define BREAK_TABLE_INC 64 typedef struct BreakState { uint32 buffer_read_index; uint32 break_read_index; uint32 multibyte_index; uint32 multibyte_length; uint32 last_line_break; uint32 lineLength; } BreakState; static LO_TextBlock * lo_CurrentTextBlock ( MWContext * context, lo_DocState * state ); /* routines to walk through our break table */ static uint8 * lo_GetNextTextPosition ( LO_TextBlock * block, uint32 * outWordLength, uint32 * outLineLength, Bool * canBreak ); static uint8 * lo_GetPrevTextPosition ( LO_TextBlock * block, uint32 * outWordLength, uint32 * outLineLength, Bool * canBreak ); static uint8 * lo_RestoreBreakState ( LO_TextBlock * block, BreakState * state, uint32 * lineLength ); static void lo_SetLineBreak ( LO_TextBlock * block, Bool skipSpace ); static uint8 * lo_GetLineStart ( LO_TextBlock * block ); static void lo_SkipCharacter ( LO_TextBlock * block ); static Bool lo_SkipInitialSpace ( LO_TextBlock * block ); static Bool lo_SetBreakPosition ( LO_TextBlock * block ); static Bool lo_SetMultiByteRun ( LO_TextBlock * block, int32 charSize, Bool breakable, Bool eachCharBreakable ); static Bool lo_SetBreakCommand ( LO_TextBlock * block, uint32 command, uint32 commandLength ); static void lo_CopyText ( uint8 * src, uint8 * dst, uint32 length ); static void lo_CopyTextToLineBuffer ( lo_DocState * state, uint8 * src, uint32 length ); static uint32 lo_FindLineBreak ( MWContext * context, lo_DocState * state, LO_TextBlock * block, uint8 * text, uint16 * widthTable, uint32 * width, int32 * minWidth, Bool * allTextFits ); /* the parsers */ static void lo_ParseSingleText ( lo_DocState * state, LO_TextBlock * block, Bool parseAllText, char * text ); static void lo_ParseThaiText ( lo_DocState * state, LO_TextBlock * block, Bool parseAllText, char * text ); static void lo_ParseDoubleText ( lo_DocState * state, LO_TextBlock * block, Bool parseAllText, char * text ); extern int32 lo_correct_text_element_width(LO_TextInfo *text_info); #ifdef LOG static Bool gHaveLog = FALSE; #endif /* * Some helpful macros for code inlining */ #define SAVE_BREAK_STATE(block,state,line_length) \ (state)->buffer_read_index = (block)->buffer_read_index; \ (state)->break_read_index = (block)->break_read_index; \ (state)->multibyte_index = (block)->multibyte_index; \ (state)->multibyte_length = (block)->multibyte_length; \ (state)->last_line_break = (block)->last_line_break; \ (state)->lineLength = line_length; static LO_TextBlock * lo_CurrentTextBlock ( MWContext * context, lo_DocState * state ) { LO_TextBlock * textBlock; textBlock = state->cur_text_block; if ( textBlock == NULL ) { textBlock = (LO_TextBlock *)lo_NewElement ( context, state, LO_TEXTBLOCK, NULL, 0 ); textBlock->type = LO_TEXTBLOCK; textBlock->x_offset = 0; textBlock->ele_id = NEXT_ELEMENT; textBlock->x = state->x; textBlock->y = state->y; textBlock->y_offset = 0; textBlock->width = 0; textBlock->height = 0; textBlock->line_height = 0; textBlock->next = NULL; textBlock->prev = NULL; textBlock->text_attr = NULL; textBlock->anchor_href = NULL; textBlock->ele_attrmask = 0; textBlock->format_mode = 0; textBlock->startTextElement = NULL; textBlock->endTextElement = NULL; textBlock->text_buffer = NULL; textBlock->buffer_length = 0; textBlock->buffer_write_index = 0; textBlock->last_buffer_write_index = 0; textBlock->buffer_read_index = 0; textBlock->last_line_break = 0; textBlock->break_table = NULL; textBlock->break_length = 0; textBlock->break_write_index = 0; textBlock->break_read_index = 0; textBlock->last_break_offset = 0; textBlock->multibyte_index = 0; textBlock->multibyte_length = 0; textBlock->old_break = NULL; textBlock->old_break_pos = 0; textBlock->old_break_width = 0; textBlock->totalWidth = 0; textBlock->totalChars = 0; textBlock->break_pending = 0; textBlock->last_char_is_whitespace = 0; textBlock->ascent = 0; textBlock->descent = 0; /* BRAIN DAMAGE: Add this enum to lo_ele.h! */ textBlock->text_buffer = XP_ALLOC ( TEXT_BUFFER_INC ); textBlock->buffer_length = TEXT_BUFFER_INC; textBlock->break_table = XP_ALLOC ( BREAK_TABLE_INC ); textBlock->break_length = BREAK_TABLE_INC * 2; /* Since we're creating a new text block, grab some of the * text state out of the layout state. */ textBlock->anchor_href = state->current_anchor; if ( state->font_stack != NULL ) { /* TEXTATTR HERE */ #ifdef DOM textBlock->text_attr = lo_GetCurrentTextAttr(state, context); #else textBlock->text_attr = state->font_stack->text_attr; #endif } if (state->breakable != FALSE) { textBlock->ele_attrmask |= LO_ELE_BREAKABLE; } state->cur_text_block = textBlock; lo_AppendToLineList ( context, state, (LO_Element *) textBlock, 0 ); } return textBlock; } void lo_AppendTextToBlock ( MWContext *context, lo_DocState *state, LO_TextBlock * block, char *text ) { Bool parseAllText; /* * We have several cases in which we can just bail: * 1. The text string and the line buffer is empty. * 2. The text string is all whitespace and we already have a trailing * space. */ if ( ( state != NULL ) && ( state->line_buf_len == 0 ) ) { char * t_ptr; /* if this string is empty, bail */ if ( *text == '\0' ) { return; } /* if it's only whitespace and we have a trailing space, then bail */ if ( state->trailing_space ) { t_ptr = text; while ( *t_ptr != '\0' ) { if ( !XP_IS_SPACE( *t_ptr ) ) { break; } ++t_ptr; } if ( ( *t_ptr == '\0' ) && ( t_ptr != text ) ) { return; } } } /* If we don't have a block, create one if we have a valid state * record. Otherwise we have an error */ if ( block == NULL ) { /* the editor may call us with a NULL state and context record, in this case we must always have a block */ XP_ASSERT(( state != NULL ) && ( context != NULL )); if ( ( state != NULL ) && ( context != NULL ) ) { block = lo_CurrentTextBlock ( context, state ); } } /* OPTIMIZATION: If the parser could tell us if we have a split * buffer then we could intelligently set parseAllText here and * not buffer words that we think may be split across a buffer but * in reality are whole. */ /* If we're in an editor context, then we can always parse the * whole buffer of text, we never need to worry about partial * buffers being passed to us. */ parseAllText = EDT_IS_EDITOR( context ); /* Scan through the text, removing whitespace and adding words to * the text buffer as we come across them. * * Particular things we have to deal with: * - Preformatted text (normal, word wrapped and column wrapped) * - Normal single byte text * - Multibyte text * - non-breaking spaces */ switch( lo_GetTextParseAttributes ( state )) { case kSimpleSBTextParseAttribute: lo_ParseSingleText ( state, block, parseAllText, text ); break; case kMBTextParseAttribute: lo_ParseDoubleText ( state, block, parseAllText, text ); break; case kThaiTextParseAttribute: lo_ParseThaiText ( state, block, parseAllText, text ); break; default: XP_ASSERT(0); /* wrong text parse attribute value */ break; } } static loTextParseAttribute lo_GetTextParseAttributes ( lo_DocState * state) { /* XP_ASSERT(NULL != state); */ if ( state != NULL ) { uint16 charset = state->font_stack->text_attr->charset; if ( charset == CS_TIS620 ) return kThaiTextParseAttribute; if ( (INTL_CharSetType ( charset ) != SINGLEBYTE ) && !( INTL_CharSetType ( charset ) & CS_SPACE ) ) return kMBTextParseAttribute; } return kSimpleSBTextParseAttribute; } static void lo_ParseThaiText ( lo_DocState * state, LO_TextBlock * block, Bool parseAllText, char * text ) { XP_ASSERT(0); /* delete when we really have the code */ /* Call the single byte one untill we have the real lo_ParseThaiText ready */ lo_ParseSingleText(state, block, parseAllText, text); } static void lo_ParseSingleText ( lo_DocState * state, LO_TextBlock * block, Bool parseAllText, char * text ) { uint8 * t_ptr; uint8 * w_start; uint8 * w_end; uint8 * line_buff; uint32 w_length; Bool skipped_space; uint32 textLength; /* check for textTransform properties and aply them */ if( ( state != NULL ) && ( state->top_state && state->top_state->style_stack ) ) { char * property; StyleStruct *style_struct = STYLESTACK_GetStyleByIndex( state->top_state->style_stack, 0); if( style_struct ) { property = STYLESTRUCT_GetString(style_struct, TEXT_TRANSFORM_STYLE); if(property) { lo_transform_text_from_string_method(text, property); } } } t_ptr = (uint8 *) text; skipped_space = FALSE; if ( ( state != NULL ) && ( state->line_buf_len > 0 ) ) { PA_LOCK(line_buff, uint8 *, state->line_buf); textLength = state->line_buf_len; if ( *line_buff == '\0' ) { line_buff = NULL; textLength = 0; state->line_buf_len = 0; } } else { line_buff = NULL; textLength = 0; } /* Make sure the text block has enough space to hold this block of text */ textLength += XP_STRLEN ( text ); lo_GrowTextBlock ( block, textLength + 1 ); /* * If there's anything in the line buffer, then pull that out now */ if ( line_buff != NULL ) { /* was there any trailing space left for us? */ skipped_space = state->trailing_space; /* skip any white space at the head of our text */ while ( ( *line_buff != '\0' ) && XP_IS_SPACE ( *line_buff ) ) { line_buff++; state->line_buf_len--; skipped_space = TRUE; } /* * if we skipped any space and we're not at the end of the buffer, * then insert a break position */ if ( *line_buff != '\0' ) { if ( skipped_space ) { if ( !lo_SetBreakPosition ( block ) ) { state->top_state->out_of_memory = TRUE; return; } /* If the space was real whitespace and not a trailing * space from a previous layout, then copy the space * to the text block. */ if ( !state->trailing_space ) { block->text_buffer[ block->buffer_write_index ] = ' '; block->buffer_write_index++; block->last_buffer_write_index++; skipped_space = TRUE; } skipped_space = FALSE; } /* copy in the line buffer. it is only allowed to be one word */ lo_CopyText ( line_buff, &block->text_buffer[ block->buffer_write_index ], state->line_buf_len ); block->buffer_write_index += state->line_buf_len; state->line_buf_len = 0; } } /* The last chunk of text may have left a single piece of * whitespace at the end of the buffer. If so, we need to skip any * whitespace at the front of the buffer so we don't have to worry * about this inside the main loop. * * When called from the editor, we may not have a state * record. However, we also won't need to worry about this case as * it will have taken care of it for us. */ if ( ( state != NULL ) && ( state->trailing_space ) && ( *t_ptr != '\0' ) ) { skipped_space = TRUE; while ( ( *t_ptr != '\0' ) && XP_IS_SPACE ( *t_ptr ) ) { t_ptr++; } } while ( *t_ptr != '\0' ) { w_start = t_ptr; /* skip past any whitespace before this word. */ while ( ( *w_start != '\0' ) && XP_IS_SPACE ( *w_start ) ) { w_start++; } /* run through the text and find the end of the word */ w_end = w_start; w_length = 0; while ( ( *w_end != '\0' ) && !XP_IS_SPACE ( *w_end ) ) { w_end++; w_length++; } /* If we hit the end of the buffer then we may be inside a * partial word. This can cause problems with interword * kerning, multibyte characters and other contextually * sensitive script systems. * * We buffer this word in the line_buff. If this is truly the * end of the text, then we'll be called to flush the last * line. We'll do this by appending this word to our text * block and then laying out the last of the text. * * If this word is just split across a buffer, then it will be * inserted to the beginning of the next text block. * * If the caller tells us to parse the whole buffer, then we * don't care. */ if ( !parseAllText && ( *w_end == '\0' ) && ( w_length > 0 ) && ( state != NULL ) ) { if ( w_start != t_ptr ) { /* put a space in the line buffer */ lo_CopyTextToLineBuffer ( state, (uint8 *) " ", 1 ); if ( state->top_state->out_of_memory ) { return; } } /* put this text in the line buffer */ lo_CopyTextToLineBuffer ( state, w_start, w_length ); if ( state->top_state->out_of_memory ) { return; } /* we now have text after the whitespace */ skipped_space = FALSE; break; } /* If we skipped some white space, then we know that we can * put a break here. */ if ( w_start != t_ptr ) { skipped_space = TRUE; /* add the break position */ if ( !lo_SetBreakPosition ( block ) ) { if ( state != NULL ) { state->top_state->out_of_memory = TRUE; } return; } if ( block->buffer_length < ( block->buffer_write_index + 1 ) ) { if ( !lo_GrowTextBlock ( block, 1 ) ) { if ( state != NULL ) { state->top_state->out_of_memory = TRUE; } return; } } block->text_buffer[ block->buffer_write_index ] = ' '; block->buffer_write_index++; /* BRAIN DAMAGE: Add a new field to the text block struct * to indicate how many chars to skip when calculating the * length of the next run. */ block->last_buffer_write_index++; } else { skipped_space = FALSE; } /* if we found anything, add it to the buffer */ if ( w_length > 0 ) { if ( block->buffer_length < ( block->buffer_write_index + w_length ) ) { if ( !lo_GrowTextBlock ( block, w_length ) ) { if ( state != NULL ) { state->top_state->out_of_memory = TRUE; } return; } } lo_CopyText ( w_start, &block->text_buffer[ block->buffer_write_index ], w_length ); block->buffer_write_index += w_length; /* we now have text after the whitespace */ skipped_space = FALSE; } t_ptr = w_end; } /* * Remember whether the last thing we added was whitespace */ block->last_char_is_whitespace = skipped_space; } /* * Parse State Table for two byte text */ typedef enum { kUnprohibited = PROHIBIT_NOWHERE, kBeginProhibited = PROHIBIT_BEGIN_OF_LINE, kEndProhibited = PROHIBIT_END_OF_LINE, kWordBreakProhibited = PROHIBIT_WORD_BREAK, kSingleByte, kBreakableSpace, kFlushFinalRun, kNumCharTypes } ParseState; /* * Flags for the state table command */ #define SET_BREAKABLE 0x01 /* dump the current run as a single breakable run */ #define SET_MULTI_BREAKABLE 0x02 /* dump the current run as a mutibyte breakable run */ #define DUMP_TEXT_AND_BREAK 0x04 /* dump the text to the buffer with no break point and then stop processing */ #define CARRY_LAST_CHAR 0x08 /* move the last char of this run into the next state */ #define INC_RUN_LENGTH 0x10 /* inc the length of this run (we could do without this one) */ #define INSERT_WHITESPACE 0x20 /* insert a single whitespace into the text buffer */ #define SKIP_CHAR 0x40 /* skip the current char */ #define MAINTAIN_CHAR_TYPE 0x80 /* don't change the curCharType */ /* * Things to Note: * * 1. SET_BREAKABLE means to insert a normal break command. This run can * be broken at the end of the run. * 2. SET_MULTI_BREAKABLE means that we have a run which can be broken at * the end of every character. * */ /* * The mutibyte breakable run data word is 16 bits big. It is organized as: * * BIT: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 * FIELD: CHAR SIZE RUN LENGTH */ #define MULTI_CHAR_SIZE_MASK 0xE000 #define MULTI_CHAR_SIZE_SHIFT 12 #define MULTI_LENGTH_MASK 0x1FFF /* * The state table */ static uint8 gParseTable[ kNumCharTypes ][ kNumCharTypes ] = { /* current char next char operations */ /* Unprohibited two byte text */ /* kUnprohibited, kUnprohibited, */ INC_RUN_LENGTH, /* kUnprohibited, kBeginProhibited, */ SET_MULTI_BREAKABLE + CARRY_LAST_CHAR, /* kUnprohibited, kEndProhibited, */ SET_MULTI_BREAKABLE, /* kUnprohibited, kWordBreakProhibited, */ SET_MULTI_BREAKABLE, /* kUnprohibited, kSingleByte, */ SET_MULTI_BREAKABLE, /* kUnprohibited, kBreakableSpace, */ SET_MULTI_BREAKABLE + INSERT_WHITESPACE + SKIP_CHAR, /* kUnprohibited, kFlushFinalRun, */ SET_MULTI_BREAKABLE, /* Begin Line Prohibited two byte text */ /* kBeginProhibited, kUnprohibited, */ SET_BREAKABLE, /* kBeginProhibited, kBeginProhibited, */ INC_RUN_LENGTH, /* kBeginProhibited, kEndProhibited, */ SET_BREAKABLE, /* kBeginProhibited, kWordBreakProhibited, */ SET_BREAKABLE, /* kBeginProhibited, kSingleByte, */ SET_BREAKABLE, /* kBeginProhibited, kBreakableSpace, */ SET_BREAKABLE + INSERT_WHITESPACE + SKIP_CHAR, /* kBeginProhibited, kFlushFinalRun, */ DUMP_TEXT_AND_BREAK, /* End Line Prohibited two byte text */ /* kEndProhibited, kUnprohibited, */ INC_RUN_LENGTH + SET_BREAKABLE, /* kEndProhibited, kBeginProhibited, */ INC_RUN_LENGTH + SET_BREAKABLE, /* kEndProhibited, kEndProhibited, */ INC_RUN_LENGTH, /* kEndProhibited, kWordBreakProhibited, */ INC_RUN_LENGTH, /* kEndProhibited, kSingleByte, */ INC_RUN_LENGTH, /* kEndProhibited, kBreakableSpace, */ INC_RUN_LENGTH + MAINTAIN_CHAR_TYPE, /* BIZZARE CASE! */ /* kEndProhibited, kFlushFinalRun, */ DUMP_TEXT_AND_BREAK, /* not much we can do here */ /* Word Break Prohibited two byte text */ /* kWordBreakProhibited, kUnprohibited, */ SET_BREAKABLE, /* kWordBreakProhibited, kBeginProhibited, */ INC_RUN_LENGTH, /* kWordBreakProhibited, kEndProhibited, */ SET_BREAKABLE, /* kWordBreakProhibited, kWordBreakProhibited, */ INC_RUN_LENGTH, /* kWordBreakProhibited, kSingleByte, */ INC_RUN_LENGTH, /* kWordBreakProhibited, kBreakableSpace, */ SET_BREAKABLE + INSERT_WHITESPACE + SKIP_CHAR, /* kWordBreakProhibited, kFlushFinalRun, */ DUMP_TEXT_AND_BREAK, /* Single byte text */ /* kSingleByte, kUnprohibited, */ SET_BREAKABLE, /* kSingleByte, kBeginProhibited, */ INC_RUN_LENGTH, /* kSingleByte, kEndProhibited, */ SET_BREAKABLE, /* kSingleByte, kWordBreakProhibited, */ SET_BREAKABLE, /* kSingleByte, kSingleByte, */ INC_RUN_LENGTH, /* kSingleByte, kBreakableSpace, */ SET_BREAKABLE + INSERT_WHITESPACE + SKIP_CHAR, /* kSingleByte, kFlushFinalRun, */ DUMP_TEXT_AND_BREAK, /* Single byte breakable space */ /* kBreakableSpace, kUnprohibited, */ 0, /* kBreakableSpace, kBeginProhibited, */ 0, /* kBreakableSpace, kEndProhibited, */ 0, /* kBreakableSpace, kWordBreakProhibited, */ 0, /* kBreakableSpace, kSingleByte, */ 0, /* kBreakableSpace, kBreakableSpace, */ SKIP_CHAR, /* kBreakableSpace, kFlushFinalRun, */ 0, /* These are never hit */ /* kFlushFinalRun, kUnprohibited, */ 0, /* kFlushFinalRun, kBeginProhibited, */ 0, /* kFlushFinalRun, kEndProhibited, */ 0, /* kFlushFinalRun, kWordBreakProhibited, */ 0, /* kFlushFinalRun, kSingleByte, */ 0, /* kFlushFinalRun, kBreakableSpace, */ 0, /* kFlushFinalRun, kFlushFinalRun, */ 0, }; static void lo_ParseDoubleText ( lo_DocState * state, LO_TextBlock * block, Bool parseAllText, char * text ) { char * tptr; char * wordStart; char * nextWordStart; int32 runLength; int32 nextRunLength; int32 curCharBytes; int32 nextCharBytes; int16 charset; ParseState curCharType; ParseState nextCharType; uint8 parseCommand; uint32 textLength; Bool startNewRun; Bool eachCharBreakable; Bool processLastRun; tptr = text; wordStart = text; runLength = 0; textLength = 0; processLastRun = FALSE; /* BRAIN DAMAGE: We need to see if there's anything in the line buffer for us */ /* Make sure the text block has enough space to hold this block of text */ textLength += XP_STRLEN ( text ); lo_GrowTextBlock ( block, textLength + 1 ); charset = block->text_attr->charset; curCharType = kSingleByte; curCharBytes = 1; eachCharBreakable = FALSE; /* if we have a trailing space, then set our state to be a space */ if ( state->trailing_space ) { curCharType = kBreakableSpace; runLength = curCharBytes; } startNewRun = FALSE; while ( ( *tptr != '\0' ) || processLastRun ) { if ( processLastRun ) { /* force the last run to be flushed */ nextCharType = kFlushFinalRun; nextCharBytes = 0; } else { /* do we have an ascii char? */ if ( ( (unsigned char) *tptr ) <= 0x7F ) { nextCharBytes = 1; /* is the next char a breakable space? */ if ( XP_IS_SPACE( *tptr ) ) { nextCharType = kBreakableSpace; } else /* it's a normal char */ { nextCharType = kSingleByte; } } else { /* multibyte, do that international thing */ nextCharBytes = INTL_CharLen( charset, (unsigned char *) tptr); nextCharType = (ParseState) INTL_KinsokuClass( charset, (unsigned char *) tptr ); } } /* now get our parse command */ parseCommand = gParseTable[ curCharType ][ nextCharType ]; nextRunLength = nextCharBytes; nextWordStart = tptr; /* Unprohibited multibyte check - we need to catch cases where * our byte size changes. We might want to add a command bit * for this check, for now I shall do it this way. */ if ( ( curCharType == kUnprohibited ) && ( nextCharType == kUnprohibited ) ) { if ( nextCharBytes != curCharBytes ) { /* ok, our char size changed. we need to dump this run */ parseCommand |= SET_BREAKABLE; } } /* process the command */ if ( parseCommand & CARRY_LAST_CHAR ) { /* move the last char of this run into the next run */ runLength -= curCharBytes; nextWordStart -= curCharBytes; /* and move the carried char of the previous run to the next one */ nextRunLength = curCharBytes + nextCharBytes; /* if the old run is empty, we don't want to do anything else */ if ( runLength == 0 ) { parseCommand = 0; startNewRun = TRUE; } } if ( parseCommand & INC_RUN_LENGTH ) { runLength += nextCharBytes; } if ( parseCommand & SET_BREAKABLE ) { lo_CopyText ( (uint8 *) wordStart, &block->text_buffer[ block->buffer_write_index ], runLength ); block->buffer_write_index += runLength; /* set a breakable run */ lo_SetBreakPosition ( block ); startNewRun = TRUE; } if ( parseCommand & SET_MULTI_BREAKABLE ) { lo_CopyText ( (uint8 *) wordStart, &block->text_buffer[ block->buffer_write_index ], runLength ); block->buffer_write_index += runLength; /* set an unbreakable run */ lo_SetMultiByteRun ( block, curCharBytes, TRUE, eachCharBreakable ); startNewRun = TRUE; } if ( parseCommand & DUMP_TEXT_AND_BREAK ) { /* copy the text out but don't set a break (only happens when flushing at the end) */ lo_CopyText ( (uint8 *) wordStart, &block->text_buffer[ block->buffer_write_index ], runLength ); block->buffer_write_index += runLength; /* now break out of the loop - we're done */ break; } if ( startNewRun ) { wordStart = nextWordStart; runLength = nextRunLength; startNewRun = FALSE; } if ( parseCommand & SKIP_CHAR ) { wordStart++; } if ( parseCommand & INSERT_WHITESPACE ) { block->text_buffer[ block->buffer_write_index ] = ' '; block->buffer_write_index++; block->last_buffer_write_index++; } curCharBytes = nextCharBytes; if ( !( parseCommand & MAINTAIN_CHAR_TYPE ) ) { curCharType = nextCharType; } eachCharBreakable = curCharType == kUnprohibited; tptr += nextCharBytes; /* if we got here with processLastRun, then we need to bail */ if ( processLastRun ) { break; } /* if we're on the last character, then we may have a partial * run left over. if we're parsing all text then we need to * dump it. */ if ( parseAllText && ( *tptr == 0 ) && ( runLength > 0 ) ) { processLastRun = TRUE; } } /* if we've ended and we have a run, then we need to save it in the line buffer */ if ( ( runLength > 0 ) && ( *wordStart != 0 ) ) { lo_CopyTextToLineBuffer ( state,(uint8 *) wordStart, runLength ); } } static void lo_FlushText ( MWContext * context, lo_DocState * state ) { LO_TextBlock * block; char * text_buf; block = state->cur_text_block; if ( block != NULL ) { /* add any text to the text block that may be sitting in the line buffer */ /* BRAIN DAMAGE: These should both be handled the same way */ if ( kMBTextParseAttribute == lo_GetTextParseAttributes(state) ) { if ( state->line_buf_len > 0 ) { PA_LOCK(text_buf, char *, state->line_buf); lo_ParseDoubleText ( state, block, TRUE, text_buf ); PA_UNLOCK(state->line_buf); } } else { lo_AppendTextToBlock ( context, state, block, "" ); } lo_LayoutTextBlock ( context, state, TRUE ); } } static void lo_SetupBreakState ( LO_TextBlock * block ) { Bool canBreak; uint32 wordLength; uint32 lineLength; uint8 * runEnd; uint32 searchReadIndex; BreakState breakState; searchReadIndex = block->buffer_read_index; block->buffer_read_index = 0; block->break_read_index = 0; block->last_line_break = 0; /* We need to update our state to be at the current text position * (specified by buffer_read_index) */ lineLength = 0; /* run through the break table until we get to the correct read index */ while ( block->buffer_read_index < searchReadIndex ) { SAVE_BREAK_STATE ( block, &breakState, lineLength ); runEnd = lo_GetNextTextPosition ( block, &wordLength, &lineLength, &canBreak ); if ( runEnd == NULL ) { /* this should not happen, but just in case, let's do something kinda reasonable */ lo_RestoreBreakState ( block, &breakState, NULL ); break; } } /* If we're not at the beginning of the text buffer, then we need * to increment buffer_read_index so that we skip the breakable * space we're currently at. */ if ( ( block->buffer_read_index > 0 ) && XP_IS_SPACE ( block->text_buffer[ block->buffer_read_index ] ) ) { block->buffer_read_index++; } block->last_line_break = block->buffer_read_index; } void lo_LayoutTextBlock ( MWContext * context, lo_DocState * state, Bool flushLastLine ) { LO_TextBlock * block; Bool allTextFits; Bool canBreakAtStart; LO_TextStruct * text_data; LO_TextStruct msTextData; uint32 width; uint32 lineLength; uint8 * text; int32 baseline_inc; int32 line_inc; int32 minWidth; int32 * minWidthPtr; BreakState breakState; uint16 * charLocs; Bool freeMeasureBuffer; Bool justify; block = state->cur_text_block; if ( block == NULL ) { return; } justify = ( state->align_stack != NULL ) && ( state->align_stack->alignment == LO_ALIGN_JUSTIFY ); /* bail if we're at the end of this block */ /* XXX MATT XXX sometimes buffer_read_index may be larger then * buffer_write_index, don't know why XXX */ if ( block->buffer_read_index >= block->buffer_write_index ) { /* if there's also no text in the line buffer, clear this block */ if ( state->line_buf_len == 0 ) { state->cur_ele_type = LO_NONE; state->cur_text_block = NULL; state->trailing_space = block->last_char_is_whitespace; } return; } lineLength = 0; charLocs = NULL; freeMeasureBuffer = FALSE; /* find the width of an average character */ if ( block->totalWidth == 0 ) { memset (&msTextData, 0, sizeof (LO_TextStruct)); msTextData.text = (PA_Block) "aeiou"; msTextData.text_attr = block->text_attr; msTextData.text_len = 5; FE_GetTextInfo ( context, &msTextData, &state->text_info ); block->totalWidth = state->text_info.max_width; block->totalChars = msTextData.text_len; } #ifdef XP_MAC /* do a quick test to see if we could overflow a UInt16 */ if ( ( block->buffer_write_index * block->totalWidth / block->totalChars ) < 65535 ) { /* measure the text using the fast measure text */ if ( ( block->buffer_write_index + 1 ) < kStaticMeasureTextBufferSize ) { charLocs = gMeasureTextBuffer; } else { charLocs = XP_ALLOC( ( block->buffer_write_index + 1 ) * sizeof(uint16) ); freeMeasureBuffer = TRUE; } if ( charLocs != NULL ) { memset (&msTextData, 0, sizeof (LO_TextStruct)); msTextData.text = (PA_Block) block->text_buffer; msTextData.text_attr = block->text_attr; msTextData.text_len = block->buffer_write_index; FE_MeasureText ( context, &msTextData, (int16 *) charLocs ); /* BRAIN DAMAGE: We need to figure out for sure if the width has ever exceeded a uint16! */ /* the actual interface for MeasureText says it takes an array of signed ints, however */ /* the data stuffed in there is unsigned (we quickly overflow a int16 with an 8Kb buffer) */ } } #endif /* Given the current layout state, run through this text block and * convert all that we can into text elements. If flushLastLine * is set, we want to flush all the text, rather than keeping the * last bit in case more text comes. */ allTextFits = FALSE; /* if we're laying out a table, then we need to calculate min widths */ minWidthPtr = state->need_min_width ? &minWidth : NULL; while ( !allTextFits ) { canBreakAtStart = FALSE; /* Save the current break state in case we need to delay on the last line */ SAVE_BREAK_STATE ( block, &breakState, lineLength ); /* We need to handle the case where we're at the beginning of * the line and the first character is a breakable space. */ if ( state->at_begin_line ) { canBreakAtStart = lo_SkipInitialSpace ( block ); #ifdef LOG PR_LogPrint ( "canBreakAtStart: %d\n", canBreakAtStart ); PR_LogFlush(); #endif } /* we're looking for a new break position */ state->break_pos = -1; state->break_width = -1; text = lo_GetLineStart ( block ); lineLength = lo_FindLineBreak ( context, state, block, text, charLocs, &width, minWidthPtr, &allTextFits ); /* update the state's min_width if we need to - this will be constant even if we don't flush this line */ if ( minWidthPtr != NULL ) { if ( minWidth > state->min_width ) { state->min_width = minWidth; } } /* if this line is too long, we either need to break at the * beginning of the run (if we can) or break at an old * element. If we can't do either of those then we just have * to make the line too big */ if ( ( width > ( state->right_margin - state->x ) ) && ( state->x > state->left_margin ) ) { /* could we have broken at the start of this line? */ if ( canBreakAtStart ) { #ifdef LOG PR_LogPrint ( "Too long - Breaking at the start of the line\n" ); PR_LogFlush(); #endif /* break the line here */ lo_SoftLineBreak( context, state, TRUE ); /* BUG BUG: We're restoring the break state to the * beginning of the buffer - ie to before the space we * skipped above. We need to fix the space skipping * mechanism to remove this case (we can go into an * infinite loop here if there's not enough space for * the first word). * * Should be able to have a new flag "canSkipSpace" * but then actually don't skip it. Then if the line * does fit and it's at the beginning, we can skip the * space. lo_FindLineBreak should probably be the one * to do this work so that the width we get back is * correct. */ lo_RestoreBreakState ( block, &breakState, NULL ); /* if all the text fits (ie there was only an * unbreakable run left in this block), then we need * to stick with this break position. Otherwise we can * go find a new one */ if ( !allTextFits ) { continue; } } else /* do we have an old break position we can use? */ if ( state->old_break_pos != -1 && state->old_break_block != NULL ) { #ifdef LOG PR_LogPrint ( "Too long - Breaking at the old_break_pos: %ld, width %ld\n" state->old_break_pos, state->old_break_width ); PR_LogFlush(); #endif lo_BreakOldElement ( context, state ); lo_RestoreBreakState ( block, &breakState, NULL ); /* if all the text fits (ie there was only an * unbreakable run left in this block), then we need * to stick with this break position. Otherwise we can * go find a new one */ if ( !allTextFits ) { continue; } } /* we're screwed, we just have to make this line too long */ } /* We may not necessarily want to flush the whole buffer out * to layout elements (the case where were processing part of * a text chunk based on what netlib has streamed to us). * * We know we do want to flush this next line out if we still * have more text in this buffer to process or layout really * does want us to flush this whole buffer (because some other * element is after us). */ if ( !allTextFits || flushLastLine ) { state->width = width; if ( lineLength > 0 ) { text_data = (LO_TextStruct *)lo_NewElement ( context, state, LO_TEXT, NULL, 0 ); if (text_data == NULL) { #ifdef DEBUG assert (state->top_state->out_of_memory); #endif break; } text_data->type = LO_TEXT; text_data->ele_id = NEXT_ELEMENT; text_data->x = state->x; text_data->x_offset = 0; text_data->y = state->y; text_data->y_offset = 0; text_data->width = width; text_data->height = 0; text_data->line_height = 0; text_data->next = NULL; text_data->prev = NULL; text_data->text = (PA_Block) text; /* HACK: The editor needs spaces to be included in the length of the last text element on a line. If we do not do this, navigation between lines gets broken. So, for "1111 2222", if we break the line after the first number, the text element should contain "1111 " rather than "1111". The fix is to add 1 to the length of the text element once we have ensured that the extra character being added is a space and that this text element is going to be followed by a linebreak. */ if (EDT_IS_EDITOR( context ) && !allTextFits && !justify && XP_IS_SPACE(((char *) text_data->text)[lineLength])) text_data->text_len = lineLength + 1; else text_data->text_len = lineLength; text_data->anchor_href = block->anchor_href; text_data->text_attr = block->text_attr; text_data->ele_attrmask = block->ele_attrmask; /* BRAIN DAMAGE: Set LO_ELE_INVISIBLE to mark the element as not having a valid text ptr */ XP_ASSERT ( !(text_data->ele_attrmask & LO_ELE_INVISIBLE ) ); text_data->ele_attrmask |= LO_ELE_INVISIBLE; text_data->sel_start = -1; text_data->sel_end = -1; text_data->doc_width = state->right_margin - state->x; text_data->doc_width = 0; text_data->block_offset = block->buffer_read_index; XP_ASSERT(block->buffer_read_index <= 65535); /* * Some fonts (particulatly italic ones with curly tails * on letters like 'f') have a left bearing that extends * back into the previous character. Since in this case the * previous character is probably not in the same font, we * move forward to avoid overlap. * * Those same funny fonts can extend past the last * character, and we also have to catch that, and * advance the following text to eliminate cutoff. */ if ( state->text_info.lbearing < 0 ) { text_data->x_offset = state->text_info.lbearing * -1; } baseline_inc = 0; line_inc = 0; /* The baseline of the text element just added to the * line may be less than or greater than the baseline * of the rest of the line due to font changes. If * the baseline is less, this is easy, we just * increase y_offest to move the text down so the * baselines line up. For greater baselines, we can't * move the text up to line up the baselines because * we will overlay the previous line, so we have to * move all the previous elements in this line down. * * If the baseline is zero, we are the first element * on the line, and we get to set the baseline. */ if ( state->baseline == 0 ) { state->baseline = state->text_info.ascent; if (state->line_height < (state->baseline + state->text_info.descent)) { state->line_height = state->baseline + state->text_info.descent; } } else if ( state->text_info.ascent < state->baseline ) { text_data->y_offset = state->baseline - state->text_info.ascent; if ( ( text_data->y_offset + state->text_info.ascent + state->text_info.descent ) > state->line_height ) { line_inc = text_data->y_offset + state->text_info.ascent + state->text_info.descent - state->line_height; } } else { baseline_inc = state->text_info.ascent - state->baseline; if ( ( text_data->y_offset + state->text_info.ascent + state->text_info.descent - baseline_inc ) > state->line_height) { line_inc = text_data->y_offset + state->text_info.ascent + state->text_info.descent - state->line_height - baseline_inc; } } /* Append this element to layout's linelist and our * own list of text elements belonging to this block */ lo_AppendToLineList ( context, state, (LO_Element *) text_data, baseline_inc ); if ( block->startTextElement == NULL ) { block->startTextElement = text_data; block->endTextElement = text_data; } else { block->endTextElement = text_data; } /* we know we're not at the beginning of the line anymore */ state->at_begin_line = FALSE; state->baseline += (intn) baseline_inc; state->line_height += (intn) (baseline_inc + line_inc); text_data->height = state->text_info.ascent + state->text_info.descent; /* * If the element we just flushed had a breakable word * position in it, save that position in case we have * to go back and break this element before we finish * the line. */ if ( state->break_pos != -1 ) { state->old_break = text_data; state->old_break_block = block; state->old_break_pos = state->break_pos; state->old_break_width = state->break_width; } state->linefeed_state = 0; state->x += state->width; state->width = 0; } /* If we're still processing text in this buffer, put a * linebreak out there */ if ( !allTextFits && !justify ) { lo_SoftLineBreak(context, state, TRUE); /* tell the break engine that we broke the line here */ lo_SetLineBreak ( block, !justify ); } #ifdef EDITOR /* tell the editor where we are */ state->edit_current_offset = block->last_line_break; #endif if ( !( allTextFits && !flushLastLine ) ) { /* Skip the break character if it's whitespace. We * don't need to worry about non-breaking spaces here * as if the space was non-breaking, we would not have * broken the line here */ if ( !allTextFits && XP_IS_SPACE ( *text ) ) { /* BRAIN DAMAGE: We should be able to do this at the start of the line */ /* lo_SkipCharacter ( block ); */ } } } } if ( flushLastLine ) { state->cur_ele_type = LO_NONE; state->cur_text_block = NULL; state->trailing_space = block->last_char_is_whitespace; } else if ( allTextFits ) { /* we're still inside a group of text elements */ state->cur_ele_type = LO_TEXT; state->trailing_space = block->last_char_is_whitespace; /* We're not going to flush the last line, so restore our break state to the start of the line */ lo_RestoreBreakState ( block, &breakState, NULL ); } if ( freeMeasureBuffer ) { XP_FREE( charLocs ); } } int32 lo_ComputeTextMinWidth ( lo_DocState * state, int32 wordWidth, Bool canBreak ); int32 lo_ComputeTextMinWidth ( lo_DocState * state, int32 wordWidth, Bool canBreak ) { int32 new_break_holder; int32 min_width; int32 indent; new_break_holder = state->x + wordWidth; min_width = new_break_holder - state->break_holder; indent = state->list_stack->old_left_margin - state->win_left; min_width += indent; /* If we are not within content, allow break_holder * to be set to the new position where a line break can occur. * This fixes BUG #70782 */ if ( ( state->breakable != FALSE ) && canBreak) { state->break_holder = new_break_holder; } return min_width; } static uint32 lo_FindLineBreak ( MWContext * context, lo_DocState * state, LO_TextBlock * block, uint8 * text, uint16 * widthTable, uint32 * width, int32 * minWidth, Bool * allTextFits ) { LO_TextStruct text_data; uint32 breakCount; Bool skipEndSpace; Bool haveTooShort; Bool canBreak; BreakState tooShortBreak; Bool haveTooLong; uint32 wordLength; uint32 breakChar; uint32 lineLength; uint32 prevLineLength; uint8 * wordStart; LO_TextInfo text_info; uint32 runLength; int32 docWidth; BreakState breakState; uint8 * runEnd; int32 oldBreakPos; int32 oldBreakWidth; int32 lineWidth; Bool justify; #ifdef BREAK_GUESS_TRACK uint32 numForwardMoves; uint32 numBackwardMoves; numForwardMoves = 0; numBackwardMoves = 0; #endif *allTextFits = FALSE; memset (&text_data, 0, sizeof (LO_TextStruct)); text_data.text = (PA_Block) text; text_data.text_attr = block->text_attr; if ( minWidth != NULL ) { *minWidth = 0; } justify = ( state->align_stack != NULL ) && ( state->align_stack->alignment == LO_ALIGN_JUSTIFY ); /* guess where we want to break this line */ docWidth = state->right_margin - state->x; if ( docWidth < 0 ) { /* we should never get here - the line before us needs to have been broken */ docWidth = 0; } lineLength = block->buffer_write_index - block->buffer_read_index; if ( state->breakable ) { breakChar = docWidth * block->totalChars / block->totalWidth; if ( breakChar > lineLength ) { breakChar = lineLength; } } else { breakChar = lineLength; } /* We first need to walk through the word runs until we get to the * first one before our break character. * * OPTIMIZATION: Make lo_GetNextTextPosition take a breakChar and * have it walk forward to that position in an inner loop. This * will save us a ton of calls (we currently spend about 5% if our * time in lo_GetNextTextPosition - not huge but significant). */ lineLength = 0; breakCount = 0; skipEndSpace = FALSE; wordStart = text; haveTooShort = FALSE; haveTooLong = FALSE; oldBreakPos = -1; oldBreakWidth = -1; lineWidth = 0; #ifdef LOG if ( !gHaveLog ) { PR_SetLogFile ( "TextLog" ); gHaveLog = true; } PR_LogPrint ( "Finding initial break position\n" ); #endif /* get the next break position */ while ( TRUE ) { prevLineLength = lineLength; #ifdef LOG PR_LogPrint ( "Get next break position\n" ); PR_LogFlush(); #endif /* Save the current break position in case it ends up being the one we need */ if ( breakCount > 0 && canBreak ) { oldBreakPos = lineLength; } SAVE_BREAK_STATE ( block, &breakState, lineLength ); runEnd = lo_GetNextTextPosition ( block, &wordLength, &lineLength, &canBreak ); if ( runEnd == NULL ) { #ifdef LOG PR_LogPrint ( "End of break table\n" ); PR_LogFlush(); #endif /* we hit the end of the break table */ runEnd = lo_RestoreBreakState ( block, &breakState, &lineLength ); break; } /* do we need to calculate min_width's? */ if ( minWidth != NULL ) { int32 min_width; if ( widthTable != NULL ) { uint32 startWordWidth; uint32 endWordWidth; startWordWidth = widthTable[ block->last_line_break + prevLineLength ]; endWordWidth = widthTable[ block->last_line_break + lineLength ]; runLength = endWordWidth - startWordWidth; } else { text_data.text = (PA_Block) wordStart; text_data.text_len = lineLength - prevLineLength; FE_GetTextInfo ( context, &text_data, &text_info ); runLength = text_info.max_width; } /* add the width of this word into our line width */ lineWidth += runLength; /* compute the real min width based on the last break position */ min_width = lo_ComputeTextMinWidth ( state, lineWidth, canBreak ); if ( min_width > *minWidth ) { *minWidth = min_width; } wordStart = runEnd; } #ifdef LOG PR_LogPrint ( "wordlen: %lu, lineLength: %lu, canBreak: %d, runEnd: %s\n", wordLength, lineLength, canBreak, runEnd ); PR_LogFlush(); #endif /* Are we where we want to be yet? */ if ( lineLength >= breakChar ) { #ifdef LOG PR_LogPrint ( "Moved past, back up\n" ); PR_LogFlush(); #endif /* if we moved past it then back up if we can */ if ( ( lineLength > breakChar ) && ( breakCount > 0 ) ) { runEnd = lo_RestoreBreakState ( block, &breakState, &lineLength ); --breakCount; } break; } /* if we're justifying text, then we just dump the next break position */ if ( justify ) { break; } /* we can now back up to something */ ++breakCount; } /* So now we're looking at the nearest break position to where we * guessed we'd want to be. Now we loop measuring this line of * text until we find the best break position */ #ifdef LOG PR_LogPrint ( "Finding actual break position\n" ); PR_LogFlush(); #endif while ( TRUE ) { if ( widthTable != NULL ) { uint32 startLineWidth; uint32 endLineWidth; startLineWidth = widthTable[ block->last_line_break ]; endLineWidth = widthTable[ block->last_line_break + lineLength ]; runLength = endLineWidth - startLineWidth; } else { text_data.text = (PA_Block) text; text_data.text_len = lineLength; FE_GetTextInfo ( context, &text_data, &text_info ); runLength = text_info.max_width; } #ifdef LOG PR_LogPrint ( "lineLength: %lu, width: %lu, docWidth: %lu, text: %s\n", lineLength, runLength, docWidth, text ); PR_LogFlush(); #endif /* if we're justified text, then we just dump the word we have now */ if ( justify ) { /* save this break position */ if ( canBreak ) { oldBreakPos = lineLength; oldBreakWidth = runLength; /* if the next char along is a space, then we need to include it */ if ( ( runEnd != NULL ) && XP_IS_SPACE ( *runEnd ) ) { ++lineLength; } } break; } else /* are we non-breakable text? */ if ( !state->breakable ) { /* We always want to move to the end of the text block. We * should already be there from the loop above. */ #ifdef LOG PR_LogPrint ( "Non-breakable, always get next break point\n" ); PR_LogFlush(); #endif /* go forward one break position and measure again (including min width) */ SAVE_BREAK_STATE ( block, &tooShortBreak, lineLength ); haveTooShort = TRUE; #ifdef BREAK_GUESS_TRACK ++numForwardMoves; #endif wordStart = runEnd; runEnd = lo_GetNextTextPosition ( block, &wordLength, &lineLength, &canBreak ); if ( runEnd == NULL ) { /* we've already at the end of the line */ runEnd = lo_RestoreBreakState ( block, &tooShortBreak, &lineLength ); #ifdef LOG PR_LogPrint ( "Non-breakable text, hit end of block: %lu, runEnd: %s\n", lineLength, runEnd ); PR_LogFlush(); #endif /* update min_width */ if ( minWidth != NULL ) { int32 min_width; /* compute the real min width based on the last break position - we cannot break here */ min_width = lo_ComputeTextMinWidth ( state, runLength, FALSE ); if ( min_width > *minWidth ) { *minWidth = min_width; } } break; } } else /* have we gone too far? */ if ( runLength > docWidth ) { #ifdef LOG PR_LogPrint ( "Too long\n" ); PR_LogFlush(); #endif /* if we found a break position before this one that was too short, choose the too short one */ if ( haveTooShort ) { #ifdef LOG PR_LogPrint ( "Using too short\n" ); PR_LogFlush(); #endif runEnd = lo_RestoreBreakState ( block, &tooShortBreak, &lineLength ); break; } /* if we have something to back up to, then do so. Otherwise we have to break here */ if ( breakCount > 0 ) { #ifdef LOG PR_LogPrint ( "Backing up to previous break position\n" ); PR_LogFlush(); #endif #ifdef BREAK_GUESS_TRACK ++numBackwardMoves; #endif /* mark that we've been to far and back up one */ haveTooLong = TRUE; runEnd = lo_GetPrevTextPosition ( block, &wordLength, &lineLength, &canBreak ); if ( runEnd == NULL ) { /* we need to break at a previous break position on this line... */ break; } wordStart = runEnd; --breakCount; continue; } else { #ifdef LOG PR_LogPrint ( "Nothing to back up to, bailing\n" ); PR_LogFlush(); #endif break; } } else /* have we not gone far enough? */ if ( runLength < docWidth ) { #ifdef LOG PR_LogPrint ( "Too short\n" ); PR_LogFlush(); #endif /* if we have a too long break position, then we know we're straddling the break point, choose */ /* this one */ if ( haveTooLong ) { #ifdef LOG PR_LogPrint ( "Using this break, next is too long\n" ); PR_LogFlush(); #endif break; } /* save this break position */ if ( canBreak ) { oldBreakPos = lineLength; oldBreakWidth = runLength; } /* go forward one break position and measure again (including min width) */ SAVE_BREAK_STATE ( block, &tooShortBreak, lineLength ); haveTooShort = TRUE; #ifdef BREAK_GUESS_TRACK ++numForwardMoves; #endif wordStart = runEnd; runEnd = lo_GetNextTextPosition ( block, &wordLength, &lineLength, &canBreak ); if ( runEnd == NULL ) { /* we've already at the end of the line */ runEnd = lo_RestoreBreakState ( block, &tooShortBreak, &lineLength ); #ifdef LOG PR_LogPrint ( "No next break position, use this one. length: %lu, runEnd: %s\n", lineLength, runEnd ); PR_LogFlush(); #endif break; } /* * Update min width. */ if ( minWidth != NULL ) { int32 min_width; if ( widthTable != NULL ) { uint32 startLineWidth; uint32 endLineWidth; startLineWidth = widthTable[ block->last_line_break ]; endLineWidth = widthTable[ block->last_line_break + lineLength ]; runLength = endLineWidth - startLineWidth; } else { text_data.text = (PA_Block) wordStart; text_data.text_len = wordLength; FE_GetTextInfo ( context, &text_data, &text_info ); /* add the length of this word into the length of the line */ runLength += text_info.max_width; } /* compute the real min width based on the last break position */ min_width = lo_ComputeTextMinWidth ( state, runLength, canBreak ); if ( min_width > *minWidth ) { *minWidth = min_width; } } ++breakCount; continue; } else { /* we're spot on! */ break; } } /* We may be in a nasty case where our current break position is * before our last saved one in oldBreakPos. This can happen when * we move backwards from our initial break guess. To correct * this, we need to back up from our current break point, get the * new position and then move forward again. */ if ( ( oldBreakPos != -1 ) && ( oldBreakPos >= lineLength ) ) { uint32 dummyWordLength; uint32 prevBreakPos; Bool dummyCanBreak; uint8 * prevRunEnd; #ifdef BREAK_GUESS_TRACK ++numBackwardMoves; #endif prevBreakPos = lineLength; SAVE_BREAK_STATE ( block, &breakState, prevBreakPos ); prevRunEnd = lo_GetPrevTextPosition ( block, &dummyWordLength, &prevBreakPos, &dummyCanBreak ); if ( prevRunEnd != NULL ) { /* we found a valid previous break, so use it */ oldBreakPos = prevBreakPos; oldBreakWidth = -1; } else { /* nothing to back up to, so don't record any old break */ oldBreakPos = -1; oldBreakWidth = -1; } /* restore the current break state */ lo_RestoreBreakState ( block, &breakState, &prevBreakPos ); } text_data.text = (PA_Block) text; /* If we don't have a width for the oldBreakPos, measure one now */ if ( ( oldBreakPos != -1 ) && ( oldBreakWidth == -1 ) ) { if ( widthTable != NULL ) { uint32 startLineWidth; uint32 endLineWidth; startLineWidth = widthTable[ block->last_line_break ]; endLineWidth = widthTable[ block->last_line_break + oldBreakPos ]; oldBreakWidth = endLineWidth - startLineWidth; } else { text_data.text_len = oldBreakPos; FE_GetTextInfo ( context, &text_data, &text_info ); oldBreakWidth = text_info.max_width; } } text_data.text_len = lineLength; /* if we're breaking at a space at the end of this line, don't measure it */ if ( skipEndSpace ) { --text_data.text_len; } /* BRAIN DAMAGE: We don't need this - already got all the info */ if ( widthTable != NULL ) { uint32 startLineWidth; uint32 endLineWidth; /* this is really lame */ text_data.text = (PA_Block) text; text_data.text_len = 1; FE_GetTextInfo ( context, &text_data, &text_info ); startLineWidth = widthTable[ block->last_line_break ]; endLineWidth = widthTable[ block->last_line_break + lineLength ]; text_info.max_width = endLineWidth - startLineWidth; } else { text_data.text = (PA_Block) text; text_data.text_len = lineLength; FE_GetTextInfo ( context, &text_data, &text_info ); } /* update our char width average */ block->totalWidth += text_info.max_width; block->totalChars += lineLength; *width = lo_correct_text_element_width( &text_info ); /* BRAIN DAMAGE: Pass this into the FE call */ state->text_info = text_info; /* check to see if we're at the end of the buffer */ if ( block->buffer_read_index == block->buffer_write_index ) { *allTextFits = TRUE; } /* save the last break position */ if ( oldBreakPos != -1 ) { state->break_pos = oldBreakPos; state->break_width = oldBreakWidth; } #ifdef LOG PR_LogPrint ( "Final lineLength: %lu, allTextFits: %d, text: %s\n", text_data.text_len, *allTextFits, text ); PR_LogFlush(); #endif #ifdef BREAK_GUESS_TRACK XP_TRACE(("Num forward, backward break moves after initial guess: %ld, %ld", numForwardMoves, numBackwardMoves )); #endif return lineLength; } static uint8 * lo_GetNextTextPosition ( LO_TextBlock * block, uint32 * outWordLength, uint32 * outLineLength, Bool * canBreak ) { uint32 breakCommand; uint32 breakLong; uint32 breakIndex; uint32 lineLength; uint32 nibbleCount; uint32 dataNibbles; int32 wordLength=0; uint32 * breakTable; uint8 * endTextRun; /* * Sanity check for already being at the end of the buffer */ if ( block->buffer_read_index == block->buffer_write_index ) { *outWordLength = 0; *canBreak = FALSE; return NULL; } /* are we in a run of breakable multibyte characters? */ if ( block->multibyte_length > 0 ) { uint16 charSize; /* BRAIN DAMAGE */ /* turn this next line on when the uint16 multibyte_char_size field is added to LO_TextBlock */ #if 0 charSize = block->multibyte_char_size; #else charSize = 2; #endif block->multibyte_index += charSize; /* are we at the end of this run? */ if ( block->multibyte_index == block->multibyte_length ) { block->multibyte_length = 0; block->multibyte_index = 0; } /* bump by one character */ *outWordLength = charSize; (*outLineLength) += charSize; *canBreak = TRUE; block->buffer_read_index += 2; endTextRun = &block->text_buffer[ block->buffer_read_index ]; return endTextRun; } /* assume we will be able to break */ *canBreak = TRUE; lineLength = block->buffer_read_index; breakIndex = block->break_read_index; /* are we at the end of the break table? */ if ( breakIndex < block->break_write_index ) { /* nope, so grab the next break position */ breakTable = &block->break_table[ breakIndex >> 3 ]; /* cache this in the TextBlock */ breakLong = ( *breakTable++ ) << ( ( breakIndex & 0x7 ) << 2 ); wordLength = 0; /* get the next break command */ breakCommand = breakLong >> 28; breakLong <<= 4; if ( ( ++breakIndex & 0x7 ) == 0 ) { breakLong = *breakTable++; } if ( breakCommand <= MAX_NATURAL_LENGTH ) { /* a nibble of length data, we already have all the info we need */ wordLength = breakCommand; dataNibbles = 0; } else if ( breakCommand == LINE_FEED ) { /* we should only get this when parsing preformatted text */ wordLength = BREAK_LINEFEED; dataNibbles = 0; } else if ( breakCommand == BYTE_LENGTH ) { dataNibbles = 2; } else if ( breakCommand == WORD_LENGTH ) { dataNibbles = 4; } else if ( breakCommand == MULTI_BYTE ) { dataNibbles = 4; } else { /* a 24 bits of data */ dataNibbles = 6; } if ( dataNibbles > 0 ) { /* read in the actual count and the tail command header */ for ( nibbleCount = dataNibbles; nibbleCount > 0; --nibbleCount ) { wordLength <<= 4; wordLength |= breakLong >> 28; breakLong <<= 4; if ( ( ++breakIndex & 0x7 ) == 0 ) { breakLong = *breakTable++; } } /* now skip the tail end of the command */ ++breakIndex; } /* if multi byte, then extract the real data */ if ( breakCommand == MULTI_BYTE ) { int32 runLength; *canBreak = TRUE; runLength = wordLength & MULTI_LENGTH_MASK; block->multibyte_length = runLength; block->multibyte_index = 2; /* make sure we're not already at the end of the run */ if ( block->multibyte_index == block->multibyte_length ) { block->multibyte_index = 0; block->multibyte_length = 0; } /* extract the real word length from the command */ wordLength = ( wordLength & MULTI_CHAR_SIZE_MASK ) >> MULTI_CHAR_SIZE_SHIFT; /* MAJOR BRAIN DAMAGE: WE NEED TO STORE THIS IN THE TEXT BLOCK AS IT WILL NOT */ /* ALWAYS BE TWO BYTE!!!! */ XP_ASSERT( wordLength == 2 ); } lineLength += wordLength; /* if we actually have a word here and are not the first word * on the line, then add one to the length to account for the * interword space. We know we're not the first word on the * line if we're not at the linebreak or if we're at the start * of the buffer but have already skipped a break position * (this happens when the first character of the buffer is a * breaking space). */ if ( ( wordLength > 0 ) && ( ( block->last_line_break != block->buffer_read_index ) || ( ( block->buffer_read_index == 0 ) && ( block->break_read_index > 0 ) ) ) ) { /* only true if there's a space here */ if ( XP_IS_SPACE ( block->text_buffer[ block->buffer_read_index ] ) ) { lineLength++; (*outLineLength)++; } } } else { /* We're at the end of the break table. We may have some text * after this last break. Either way, we cannot break here */ *canBreak = FALSE; if ( lineLength < block->buffer_write_index ) { lineLength = block->buffer_write_index; wordLength = lineLength - block->buffer_read_index; } } block->break_read_index = breakIndex; block->buffer_read_index = lineLength; endTextRun = &block->text_buffer[ lineLength ]; *outLineLength += wordLength; *outWordLength = wordLength; return endTextRun; } static Bool lo_ExtractPrevBreakCommand ( LO_TextBlock * block, Bool * multiByte, uint32 * command ) { Bool hasPrev; uint32 breakCommand; uint32 commandData; uint32 breakLong; uint32 breakIndex; uint32 * breakTable; uint32 nibbleCount; uint32 dataNibbles; Bool readPrevCommand; commandData = 0; *multiByte = FALSE; breakIndex = block->break_read_index; hasPrev = block->break_read_index > 0; if ( hasPrev ) { readPrevCommand = TRUE; /* are we at the absolute end of the buffer? */ if ( block->buffer_read_index == block->buffer_write_index ) { /* Yup, so back up to the last break offset. */ commandData = block->buffer_read_index - block->last_break_offset; /* was there really a true ending break command? */ if ( commandData > 0 ) { readPrevCommand = FALSE; /* this length was before the breaking space, add it back in */ --commandData; } } /* extract the previous command from the table if we need to */ if ( readPrevCommand ) { /* nope, so back up within the break table */ --breakIndex; breakTable = &block->break_table[ breakIndex >> 3 ]; breakLong = *breakTable; /* shift the command down and extract the data */ breakCommand = ( breakLong >> ( ( 7 - ( breakIndex & 0x7 ) ) << 2 ) ) & 0xF; if ( breakCommand <= MAX_NATURAL_LENGTH ) { /* a nibble of length data, we already have all the info we need */ dataNibbles = 0; commandData = breakCommand; } else if ( breakCommand == LINE_FEED ) { /* we should only get this when parsing preformatted text */ dataNibbles = 0; commandData = breakCommand; } else if ( breakCommand == BYTE_LENGTH ) { /* a byte of length data */ dataNibbles = 2; } else if ( breakCommand == WORD_LENGTH ) { /* a short of length data */ dataNibbles = 4; } else if ( breakCommand == MULTI_BYTE ) { /* 16 bits of data */ dataNibbles = 4; *multiByte = TRUE; } else { /* a 24 bits of data */ dataNibbles = 6; } if ( dataNibbles > 0 ) { /* skip the command tail */ if ( ( --breakIndex & 0x7 ) == 7 ) { breakLong = *--breakTable; } /* read in the actual count and the tail command header */ for ( nibbleCount = 0; nibbleCount < dataNibbles; ++nibbleCount ) { uint32 nibble; /* grab the next nibble */ nibble = ( breakLong >> ( ( 7 - ( breakIndex & 0x7 ) ) << 2 ) ) & 0xF; commandData |= nibble << ( nibbleCount << 2 ); if ( ( --breakIndex & 0x7 ) == 7 ) { breakLong = *--breakTable; } } } } } block->break_read_index = breakIndex; *command = commandData; return hasPrev; } static uint8 * lo_GetPrevTextPosition ( LO_TextBlock * block, uint32 * outWordLength, uint32 * outLineLength, Bool * canBreak ) { uint32 wordLength; uint32 breakCommand; uint32 lineLength; uint8 * endTextRun; uint32 totalSkip; Bool havePrevCommand; Bool multiByte; *canBreak = FALSE; totalSkip = 0; /* * Sanity check for already being at the beginning of the line */ if ( block->buffer_read_index == block->last_line_break ) { *outWordLength = 0; return NULL; } /* are we in a run of breakable multibyte characters? */ if ( block->multibyte_length > 0 ) { uint16 charSize; /* BRAIN DAMAGE */ /* turn this next line on when the uint16 multibyte_char_size field is added to LO_TextBlock */ #if 0 charSize = block->multibyte_char_size; #else charSize = 2; #endif block->multibyte_index -= charSize; /* are we at the end of this run? */ if ( block->multibyte_index == 0 ) { block->multibyte_length = 0; } /* bump by one character */ *outWordLength = charSize; (*outLineLength) -= charSize; *canBreak = TRUE; block->buffer_read_index -= 2; endTextRun = &block->text_buffer[ block->buffer_read_index ]; return endTextRun; } /* back up to the previous command */ havePrevCommand = lo_ExtractPrevBreakCommand ( block, &multiByte, &breakCommand ); if ( !havePrevCommand ) { *outWordLength = 0; return NULL; } /* back ourselves up in the buffer */ if ( multiByte ) { uint32 charSize; *canBreak = TRUE; /* extract the real word length from the command */ charSize = ( breakCommand & MULTI_CHAR_SIZE_MASK ) >> MULTI_CHAR_SIZE_SHIFT; block->multibyte_length = breakCommand & MULTI_LENGTH_MASK; block->multibyte_index = block->multibyte_length - charSize; /* make sure we're not already at the beginning of the run */ if ( block->multibyte_index == 0 ) { block->multibyte_length = 0; } /* MAJOR BRAIN DAMAGE: WE NEED TO STORE THIS IN THE TEXT BLOCK AS IT WILL NOT */ /* ALWAYS BE TWO BYTE!!!! */ XP_ASSERT( charSize == 2 ); /* the length of this run is the char size */ wordLength = charSize; } else { wordLength = breakCommand; } lineLength = block->buffer_read_index; /* if we actually have a word here and are not the first word on * the line, then subtract one to the length to account for the * interword space. */ if ( ( wordLength > 0 ) && ( block->last_line_break != ( lineLength - wordLength )) ) { /* only true if there's a space here */ if ( XP_IS_SPACE ( block->text_buffer[ lineLength - wordLength - 1 ] ) ) { lineLength--; (*outLineLength)--; } } lineLength -= wordLength; block->buffer_read_index = lineLength; endTextRun = &block->text_buffer[ lineLength ]; *outLineLength -= wordLength; *outWordLength = wordLength; /* We can't break if we've backed up all the way to the start of the buffer and there is */ /* no break position there */ if ( ( block->break_read_index == 0 ) && ( wordLength == 0 ) ) { *canBreak = FALSE; } else { *canBreak = TRUE; } return endTextRun; } static Bool lo_SetBreakCommand ( LO_TextBlock * block, uint32 command, uint32 commandLength ) { uint32 break_write_index; uint32 * break_table; /* record the current break position as it may be the last entry in the break table */ block->last_break_offset = block->buffer_write_index; block->last_buffer_write_index = block->buffer_write_index; break_write_index = block->break_write_index; /* grow the break table if we need to - always have space for one long of data */ if ( ( break_write_index + 8 ) > block->break_length ) { /* allocate in bytes, count in nibbles */ block->break_length += BREAK_TABLE_INC * 2; block->break_table = XP_REALLOC ( block->break_table, block->break_length / 2 ); } break_table = block->break_table; if ( break_table != NULL ) { uint32 nibble_index; nibble_index = break_write_index & 0x7; /* write the command */ if ( nibble_index == 0 ) { /* we're long aligned, write the sucker */ break_table[ break_write_index >> 3 ] = command; } else { uint32 table_long; uint32 table_index; table_index = break_write_index >> 3; table_long = break_table[ table_index ]; table_long |= command >> ( nibble_index << 2 ); break_table[ table_index ] = table_long; /* how many nibbles did we write, and were they enough? */ nibble_index = 0x8 - nibble_index; if ( commandLength > nibble_index ) { /* need to write out some more data */ break_table[ table_index + 1 ] = command << ( nibble_index << 2 ); } } break_write_index += commandLength; } block->break_write_index = break_write_index; return break_table != NULL; } static Bool lo_SetBreakPosition ( LO_TextBlock * block ) { uint32 w_length; uint32 data_long; uint32 command_size; Bool success; /* how many characters were added for this run */ w_length = block->buffer_write_index - block->last_buffer_write_index; XP_ASSERT ( w_length >= 0 ); if ( w_length <= MAX_NATURAL_LENGTH ) { /* one data nibble */ command_size = 1; data_long = w_length << 28; } else if ( w_length <= 255 ) { /* a command nibble, two data nibbles and then a terminator nibble */ command_size = 4; data_long = ( BYTE_LENGTH << 28 ) | ( w_length << 20 ) | ( BYTE_LENGTH << 16 ); } else if ( w_length <= 65535 ) { /* a command nibble, four data nibbles and then a terminator nibble */ command_size = 6; data_long = ( WORD_LENGTH << 28 ) | ( w_length << 12 ) | ( WORD_LENGTH << 8 ); } else { /* we can have 24 bits of data at most... */ XP_ASSERT ( w_length <= ( ( 1 << 24 ) - 1 ) ); /* a command nibble, six data nibbles and then a terminator nibble */ command_size = 8; data_long = ( LONG_LENGTH << 28 ) | ( w_length << 4 ) | ( LONG_LENGTH ); } success = lo_SetBreakCommand ( block, data_long, command_size ); return success; } static Bool lo_SetMultiByteRun ( LO_TextBlock * block, int32 charSize, Bool breakable, Bool eachCharBreakable ) { uint32 w_length; uint32 data_long; uint32 command_size; Bool success; /* how many characters were added for this run */ w_length = block->buffer_write_index - block->last_buffer_write_index; XP_ASSERT ( w_length >= 0 ); /* * There are two cases where we can use a normal simple break * entry: 1. We are not breakable on every char and the text is * breakable (the normal break table case). 2. We can break on * every char, but we only have one char in the run */ if ( breakable ) { if ( !eachCharBreakable || ( charSize == w_length ) ) { return lo_SetBreakPosition ( block ); } } /* BRAIN DAMAGE: We need to handle overflow of our data word */ /* this break command always has 16 bits of data and so it is 24 bits big */ command_size = 6; data_long = w_length; data_long |= ( charSize << MULTI_CHAR_SIZE_SHIFT ) & MULTI_CHAR_SIZE_MASK; XP_ASSERT( w_length <= MULTI_LENGTH_MASK ); data_long |= ( w_length & MULTI_LENGTH_MASK ); /* set the command nibbles */ data_long = ( (uint32) MULTI_BYTE << ( MULTI_BYTE_DATA_SIZE + 4 ) ) | (uint32) ( data_long << 4 ) | (uint32) MULTI_BYTE; data_long <<= 8; success = lo_SetBreakCommand ( block, data_long, command_size ); return success; } void lo_BreakOldTextBlockElement(MWContext *context, lo_DocState *state) { LO_TextBlock * block; LO_TextStruct * text_data; LO_TextStruct * new_text_data; char * text; char * breakPtr; int32 save_width; uint32 newTextlength; LO_TextInfo text_info; int32 base_change; int32 old_baseline; int32 old_line_height; int32 adjust; int32 baseline_inc=0; LO_Element * tptr; LO_Element * eptr; LO_Element * line_ptr; /* note that the block can be null for a word break element */ block = state->old_break_block; /* Move to the element we will break */ text_data = state->old_break; /* If there is no text there to break it is an error. */ if ( text_data == NULL ) { return; } new_text_data = NULL; /* * Later operations will trash the width field. * So save it now to restore later. */ save_width = state->width; /* * If this element has no text, then it's a special word break * element. We can simply remove it and then add a linefeed and then * insert the remaining text on the line buffer */ if ( text_data->text == NULL ) { /* * Back up the state to this element's location */ state->x = text_data->x; state->y = text_data->y; tptr = text_data->next; text_data->next = NULL; state->width = text_data->width; state->x += state->width; /* add a line feed */ lo_SoftLineBreak(context, state, TRUE); } else { /* We're trying to break inside an actual text element. We * need to shorten this element to point up to this break * position, then add a linefeed, then create a new element * that contains the remaining text and place it on the line * buffer */ /* if we're breaking an element, we must have a text block */ if ( block == NULL ) { return; } PA_LOCK(text, char *, text_data->text); /* * Back the state up to this element's * location, break off the rest of the elements * and save them for later. * Flush this line, and insert a linebreak. */ state->x = text_data->x; state->y = text_data->y; tptr = text_data->next; text_data->next = NULL; breakPtr = &text[ state->old_break_pos ]; newTextlength = text_data->text_len - state->old_break_pos; text_data->block_offset = text_data->block_offset - text_data->text_len + state->old_break_pos; text_data->text_len = state->old_break_pos; FE_GetTextInfo(context, text_data, &text_info); state->width = lo_correct_text_element_width( &text_info ); text_data->width = state->width; PA_UNLOCK(text_data->text); state->x += state->width; /* this element should point at the space before the next word, so skip it */ if ( XP_IS_SPACE ( *breakPtr ) ) { ++breakPtr; --newTextlength; } /* * If the element that caused this break has a different * baseline than the element we are breaking, we need to * preserve that difference after the break. */ base_change = state->baseline - (text_data->y_offset + text_info.ascent); old_baseline = state->baseline; old_line_height = state->line_height; /* * Reset element_id so they are still sequencial. */ state->top_state->element_id = text_data->ele_id + 1; /* * If we are breaking an anchor, we need to make sure the * linefeed gets its anchor href set properly. */ if (text_data->anchor_href != NULL) { LO_AnchorData *tmp_anchor; tmp_anchor = state->current_anchor; state->current_anchor = text_data->anchor_href; lo_SoftLineBreak(context, state, TRUE); state->current_anchor = tmp_anchor; } else { lo_SoftLineBreak(context, state, TRUE); } adjust = lo_baseline_adjust( context, state, tptr, old_baseline, old_line_height ); state->baseline = old_baseline - adjust; state->line_height = (intn) old_line_height - adjust; /* now create a new text element */ baseline_inc = -1 * adjust; new_text_data = (LO_TextStruct *)lo_NewElement ( context, state, LO_TEXT, text_data->edit_element, text_data->edit_offset+text_data->text_len+1 ); if (text_data == NULL) { #ifdef DEBUG assert (state->top_state->out_of_memory); #endif return; } new_text_data->type = LO_TEXT; new_text_data->ele_id = NEXT_ELEMENT; new_text_data->x = state->x; new_text_data->x_offset = 0; new_text_data->y = state->y; new_text_data->y_offset = 0; new_text_data->line_height = 0; new_text_data->height = 0; new_text_data->next = NULL; new_text_data->prev = NULL; new_text_data->anchor_href = block->anchor_href; new_text_data->text_attr = block->text_attr; new_text_data->ele_attrmask = block->ele_attrmask; /* BRAIN DAMAGE: Set LO_ELE_INVISIBLE to mark the element as not having a valid text ptr */ XP_ASSERT ( !(new_text_data->ele_attrmask & LO_ELE_INVISIBLE ) ); new_text_data->ele_attrmask |= LO_ELE_INVISIBLE; new_text_data->sel_start = -1; new_text_data->sel_end = -1; new_text_data->doc_width = state->right_margin - state->x; new_text_data->doc_width = 0; new_text_data->block_offset = text_data->block_offset + text_data->text_len; XP_ASSERT(new_text_data->block_offset <= 65535); new_text_data->text = (PA_Block) breakPtr; new_text_data->text_len = newTextlength; FE_GetTextInfo(context, new_text_data, &text_info); new_text_data->width = lo_correct_text_element_width(&text_info); /* * Some fonts (particulatly italic ones with curly * tails on letters like 'f') have a left bearing * that extends back into the previous character. * Since in this case the previous character is * probably not in the same font, we move forward * to avoid overlap. */ if (text_info.lbearing < 0) { new_text_data->x_offset = text_info.lbearing * -1; } /* * The baseline of the text element just inserted in * the line may be less than or greater than the * baseline of the rest of the line due to font * changes. If the baseline is less, this is easy, * we just increase y_offest to move the text down * so the baselines line up. For greater baselines, * we can't move the text up to line up the baselines * because we will overlay the previous line, so we * have to move all rest of the elements in this line * down. * * If the baseline is zero, we are the first element * on the line, and we get to set the baseline. */ if (state->baseline == 0) { state->baseline = text_info.ascent; } else if (text_info.ascent < state->baseline) { new_text_data->y_offset = state->baseline - text_info.ascent; } else { baseline_inc = baseline_inc + (text_info.ascent - state->baseline); state->baseline = text_info.ascent; } /* * Now that we have broken, and added the new * element, we need to move it down to restore the * baseline difference that previously existed. */ new_text_data->y_offset -= base_change; /* * Calculate the height of this new * text element. */ new_text_data->height = text_info.ascent + text_info.descent; state->x += new_text_data->width; } /* if our previous element was the last text element for this * block, then our new element is the new end */ if ( block->endTextElement == text_data ) { block->endTextElement = new_text_data; } /* * Now add the remaining elements to the line list */ /* first find the end of the line list */ line_ptr = state->line_list; while ((line_ptr != NULL)&&(line_ptr->lo_any.next != NULL)) { line_ptr = line_ptr->lo_any.next; } /* now add the new_text_data if there is one */ if ( new_text_data != NULL ) { if (line_ptr == NULL) { state->line_list = (LO_Element *) new_text_data; new_text_data->prev = NULL; line_ptr = (LO_Element *) new_text_data; } else { line_ptr->lo_any.next = (LO_Element *) new_text_data; new_text_data->prev = line_ptr; line_ptr = (LO_Element *) new_text_data; } } /* and then add tptr */ if ( tptr != NULL ) { if (line_ptr == NULL) { state->line_list = tptr; tptr->lo_any.prev = NULL; line_ptr = tptr; } else { line_ptr->lo_any.next = tptr; tptr->lo_any.prev = line_ptr; line_ptr = tptr; } } /* if we've added a new element then increment the element id's of the following elements */ if ( new_text_data != NULL ) { eptr = tptr; while (eptr != NULL) { eptr->lo_any.ele_id = NEXT_ELEMENT; eptr->lo_any.y_offset += baseline_inc; eptr = eptr->lo_any.next; } /* and bump the line height if we need to */ if ( ( new_text_data->y_offset + new_text_data->height ) > state->line_height ) { state->line_height = (intn) new_text_data->y_offset + new_text_data->height; } } /* * Upgrade forward the x and y text positions in the document * state. */ while ( tptr != NULL ) { lo_UpdateElementPosition ( state, tptr ); tptr = tptr->lo_any.next; } state->at_begin_line = FALSE; state->width = save_width; } static uint8 * lo_GetLineStart ( LO_TextBlock * block ) { return &block->text_buffer[ block->last_line_break ]; } static void lo_SetLineBreak ( LO_TextBlock * block, Bool skipSpace ) { /* if the current character is a space, we can skip it as we're breaking here */ /* NOT FOR PREFORMATTED TEXT THOUGH */ if ( skipSpace && ( XP_IS_SPACE ( block->text_buffer[ block->buffer_read_index ] ) ) ) { ++block->buffer_read_index; } block->last_line_break = block->buffer_read_index; } static uint8 * lo_RestoreBreakState ( LO_TextBlock * block, BreakState * state, uint32 * lineLength ) { uint8 * runEnd; block->buffer_read_index = state->buffer_read_index; block->break_read_index = state->break_read_index; block->multibyte_index = state->multibyte_index; block->multibyte_length = state->multibyte_length; block->last_line_break = state->last_line_break; if ( lineLength != NULL ) { *lineLength = state->lineLength; } runEnd = &block->text_buffer[ block->buffer_read_index ]; return runEnd; } static Bool lo_SkipInitialSpace ( LO_TextBlock * block ) { Bool canBreak; uint32 breakLong; uint32 breakIndex; canBreak = FALSE; breakIndex = block->break_read_index; /* do we have a break point at the current text offset */ if ( ( breakIndex == 0 ) && ( breakIndex < block->break_write_index ) && XP_IS_SPACE ( block->text_buffer[ 0 ] ) ) { breakLong = block->break_table[ breakIndex >> 3 ]; breakLong >>= ( 7 - ( breakIndex & 0x7 ) ) << 2; if ( breakLong == 0 ) { block->buffer_read_index++; block->break_read_index++; block->last_line_break++; canBreak = TRUE; } } return canBreak; } Bool lo_GrowTextBlock ( LO_TextBlock * block, uint32 length ) { Bool success = TRUE; uint32 growBy; uint32 oldTextBase; uint32 offset; LO_TextStruct * textElement; LO_TextStruct * endElement; if ( block->buffer_length < ( block->buffer_write_index + length ) ) { /* need to make sure that the new size is enough to contain the new data */ growBy = TEXT_BUFFER_INC; if ( growBy < length ) { growBy = length; } oldTextBase = (uint32) block->text_buffer; growBy += block->buffer_length; block->text_buffer = XP_REALLOC ( block->text_buffer, growBy ); block->buffer_length = growBy; success = block->text_buffer != NULL; /* update any break table related information */ if ( success && ( block->break_table != NULL ) ) { /* Run through all the text elements and adjust their text * addresses to point to the newly relocated block */ textElement = block->startTextElement; endElement = block->endTextElement; while ( textElement != NULL ) { if ( textElement->type == LO_TEXT ) { offset = (uint32) textElement->text - oldTextBase; textElement->text = (PA_Block) ( (uint32) block->text_buffer + offset ); } if ( textElement == endElement ) { break; } textElement = (LO_TextStruct *) textElement->next; } } } return success; } static void lo_CopyText ( uint8 * src, uint8 * dst, uint32 length ) { uint8 c; /* copy a text string, converting non breaking spaces to normal spaces as we go */ while ( length-- ) { c = *src++; if ( c == NON_BREAKING_SPACE ) { c = ' '; } *dst++ = c; } } static void lo_CopyTextToLineBuffer ( lo_DocState * state, uint8 * src, uint32 length ) { char * text_buf; /* do we need to grow the buffer? */ if ( ( state->line_buf_len + length + 1 ) > state->line_buf_size ) { state->line_buf = PA_REALLOC ( state->line_buf, ( state->line_buf_size + length + LINE_BUF_INC ) ); if ( state->line_buf == NULL ) { state->top_state->out_of_memory = TRUE; return; } } PA_LOCK(text_buf, char *, state->line_buf); XP_BCOPY ( (char *) src, (char *) ( text_buf + state->line_buf_len ), ( length + 1 ) ); state->line_buf_len += length; text_buf[ state->line_buf_len ] = 0; PA_UNLOCK(state->line_buf); } #ifdef DOM LO_TextAttr * lo_GetCurrentTextAttr(lo_DocState *state, MWContext *context) { LO_TextAttr tmp_attr, *tptr, *styleptr; JSBool isMutable; if (!state->font_stack) { /* * XXX we should keep a text_attr with the default values around so that * we can just bump the refcount instead of copying all that data and * stuff. */ lo_SetDefaultFontAttr(state, &tmp_attr, context); tptr = &tmp_attr; isMutable = JS_TRUE; } else { /* * we can just dup the pointer, because lo_FillInTextStyleInfo will * copy-on-write for us. */ tptr = state->font_stack->text_attr; tptr->refcnt++; isMutable = JS_FALSE; } styleptr = lo_FillInTextStyleInfo(state, context, tptr, isMutable); if (styleptr != tptr) isMutable = JS_FALSE; /* already fetched, so no need to refetch */ tptr = styleptr; if (isMutable) /* we're working on the stack-allocated text_attr, so get a heap copy */ tptr = lo_FetchTextAttr(state, tptr); return tptr; } #ifdef DEBUG_shaver /* #define DEBUG_shaver_textattr */ #endif LO_TextAttr * lo_FillInTextStyleInfo(lo_DocState *state, MWContext *context, LO_TextAttr *tptr, JSBool isMutable) { JSContext *cx = context->mocha_context; DOM_StyleDatabase *db = state->top_state->style_db; DOM_AttributeEntry *entry; DOM_Node *node = ACTIVE_NODE(state); LO_TextAttr text_attr, *newptr = NULL; JSBool copied = JS_FALSE; LO_Color col; if (!node) return tptr; if (node->type != NODE_TYPE_TEXT) { if (node->type == NODE_TYPE_ELEMENT) { DOM_HTMLElementPrivate *priv = node->data; if (priv && (priv->tagtype == P_LIST_ITEM || priv->tagtype == P_IMAGE || priv->tagtype == P_HRULE /* layout NYI */)) { #ifdef DEBUG_shaver_textattr fprintf(stderr, "setting textattr on <%s>\n", ((DOM_Element *)node)->tagName); #endif } else { #ifdef DEBUG_shaver_textattr fprintf(stderr, "NOT setting textattr on <%s>\n", ((DOM_Element *)node)->tagName); #endif return tptr; } } } #define COW() \ if (!isMutable && !copied) { \ lo_CopyTextAttr(tptr, &text_attr); \ copied = JS_TRUE; \ } if (!isMutable) { XP_BZERO(&text_attr, sizeof(text_attr)); newptr = &text_attr; } else { newptr = tptr; } if (!db) { /* find enclosing link object, and set the appropriate default color */ DOM_StyleToken pseudo; DOM_Element *link; #ifdef DEBUG_shaver_textattr fprintf(stderr, "no db, setting default attrs "); #endif if (!state->current_anchor) { #ifdef DEBUG_shaver fprintf(stderr, "no anchor, done\n"); #endif goto done; } link = state->current_anchor->node; XP_ASSERT(link && link->node.type == NODE_TYPE_ELEMENT); pseudo = DOM_GetElementPseudo(cx, link); if (pseudo) { /* XXX assuming that we only set pseudo on links! */ LO_Color col; #ifdef DEBUG_shaver_textattr fprintf(stderr, "pseudo is %s", pseudo); #endif COW(); if (lo_underline_anchors()) newptr->attrmask |= LO_ATTR_UNDERLINE; if (!XP_STRCMP(pseudo, "link")) { col.red = STATE_UNVISITED_ANCHOR_RED(state); col.blue = STATE_UNVISITED_ANCHOR_BLUE(state); col.green = STATE_UNVISITED_ANCHOR_GREEN(state); newptr->fg = col; } else if (!XP_STRCMP(pseudo, "visited")) { col.red = STATE_VISITED_ANCHOR_RED(state); col.blue = STATE_VISITED_ANCHOR_BLUE(state); col.green = STATE_VISITED_ANCHOR_GREEN(state); newptr->fg = col; } } #ifdef DEBUG_shaver_textattr fputs("\n", stderr); #endif goto done; } /* check "color" property */ if (!DOM_StyleGetProperty(cx, db, node, COLOR_STYLE, &entry)) /* XXX report error? return NULL? */ goto done; if (entry) { if (!DOM_GetCleanEntryData(cx, entry, lo_ColorStringToData, NULL, (uint32*)&col, NULL)) goto done; if (*(uint32 *)&col != *(uint32 *)&tptr->fg) { COW(); newptr->fg = col; } } /* check bgcolor property, including transparent */ if (!DOM_StyleGetProperty(cx, db, node, BG_COLOR_STYLE, &entry)) goto done; if (entry) { COW(); if (!DOM_GetCleanEntryData(cx, entry, lo_ColorStringToData, NULL, (uint32*)&col, NULL)) goto done; newptr->bg = col; if (!strcasecomp(entry->value, "transparent")) newptr->no_background = TRUE; else newptr->no_background = FALSE; } /* check font-face */ if (lo_face_attribute()) { if (!DOM_StyleGetProperty(cx, db, node, FONTFACE_STYLE, &entry)) goto done; if (entry) { COW(); /* XXXshaver use GetCleanEntryData when I figure out mem mgmt */ #if 0 if (newptr->font_face) XP_FREE(newptr->font_face); #endif newptr->font_face = lo_FetchFontFace(context, state, (char *)entry->value); } } /* check font-size */ if (!DOM_StyleGetProperty(cx, db, node, FONTSIZE_STYLE, &entry)) return NULL; if (entry) { double *fontsize; struct SSUnitContext arg; arg.context = context; if (!DOM_GetCleanEntryData(cx, entry, lo_ParseFontSizeToData, lo_DeleteFontSize, (uint32*)&fontsize, (void *)&arg)) return NULL; if (newptr->point_size != *fontsize) { COW(); newptr->point_size = *fontsize; } } /* check font-weight */ if (!DOM_StyleGetProperty(cx, db, node, FONTWEIGHT_STYLE, &entry)) goto done; if (entry) { uint32 weight; /* * For "bolder" and "lighter", we need to keep them dirty because they're * dependent on the enclosing state. */ if (!DOM_GetCleanEntryData(cx, entry, FontWeightToData, NULL, &weight, NULL)) goto done; if (weight) { if (!newptr->font_weight) { COW(); newptr->font_weight = 400; } if (weight == FONT_WEIGHT_BOLDER) { weight = MAX(newptr->font_weight + 100, 100); entry->dirty = JS_TRUE; } else if (weight == FONT_WEIGHT_LIGHTER) { weight = MIN(newptr->font_weight - 100, 900); entry->dirty = JS_TRUE; } if (weight != (uint32)newptr->font_weight) { COW(); newptr->font_weight = weight; } } } /* font-style */ if (!DOM_StyleGetProperty(cx, db, node, FONTSTYLE_STYLE, &entry)) goto done; if (entry) { if (!strcasecomp(entry->value, NORMAL_STYLE)) { /* { font-style:normal} */ if (newptr->fontmask & LO_FONT_ITALIC) { COW(); newptr->fontmask &= ~LO_FONT_ITALIC; } } else if (!strcasecomp(entry->value, ITALIC_STYLE)) { /* { font-style:italic} */ if (!(newptr->fontmask & LO_FONT_ITALIC)) { COW(); newptr->fontmask |= LO_FONT_ITALIC; } } /* XXX need LO_FONT_OBLIQUE */ } /* text-decoration */ if (!DOM_StyleGetProperty(cx, db, node, TEXTDECORATION_STYLE, &entry)) goto done; if (entry) { uint32 attrs; #define ATTRMASK (LO_ATTR_BLINK | LO_ATTR_STRIKEOUT | LO_ATTR_UNDERLINE) if (!DOM_GetCleanEntryData(cx, entry, TextDecorationToData, NULL, &attrs, NULL)) goto done; attrs = (tptr->attrmask & ~ATTRMASK) | (attrs & ATTRMASK); if (attrs != tptr->attrmask) { COW(); newptr->attrmask = attrs; } #undef ATTRMASK } #undef COW done: if (!isMutable && copied) { tptr = lo_FetchTextAttr(state, &text_attr); #ifdef DEBUG_shaver_textattr fprintf(stderr, "returning new TextAttr: %p\n", tptr); #endif return tptr; } #ifdef DEBUG_shaver_textattr fprintf(stderr, "returning unmodified TextAttr %p\n", tptr); #endif return tptr; } #define GET_UNSHARED_TEXT_ATTR(new, old) \ PR_BEGIN_MACRO \ if ((old)->refcnt != 1) { \ (new) = lo_NewCopyTextAttr(state, (old)); \ (old)->refcnt--; \ } else { \ (new) = (old); \ } \ PR_END_MACRO void lo_SetColor( LO_Element *ele, LO_Color *color, lo_DocState *state, Bool background) { LO_TextAttr *new_attr; switch (ele->lo_any.type) { case LO_TEXTBLOCK: GET_UNSHARED_TEXT_ATTR(new_attr, ele->lo_textBlock.text_attr); ele->lo_textBlock.text_attr = new_attr; break; case LO_TEXT: GET_UNSHARED_TEXT_ATTR(new_attr, ele->lo_text.text_attr); ele->lo_text.text_attr = new_attr; break; case LO_BULLET: GET_UNSHARED_TEXT_ATTR(new_attr, ele->lo_bullet.text_attr); ele->lo_bullet.text_attr = new_attr; break; default: return; } if (background) { new_attr->bg = *color; new_attr->no_background = FALSE; } else { new_attr->fg = *color; } } #endif /* DOM */ #ifdef TEST_16BIT #undef XP_WIN16 #endif /* TEST_16BIT */ #ifdef PROFILE #pragma profile off #endif