pjs/lib/layout/laytext.c

8718 строки
216 KiB
C

/* -*- 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"
#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;
Bool lineBufMeasured;
/* start at the current text position in this text block */
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<bytestocopy; i++)
{
*text_ptr++ = *cptr++;
cnt++;
}
}
else
{
*text_ptr++ = *cptr++;
cnt++;
}
}
}
*text_ptr = *cptr;
}
else
{
XP_BCOPY(w_start, tmp_buf, line_length + 1);
}
/*
* Now we catch those nasty non-breaking space special
* characters and make them spaces.
*/
if (has_nbsp != FALSE)
{
char *tmp_ptr;
tmp_ptr = tmp_buf;
while (*tmp_ptr != '\0')
{
if (((unsigned char)*tmp_ptr == NON_BREAKING_SPACE)
&& (CS_USER_DEFINED_ENCODING != charset))
{
*tmp_ptr = ' ';
}
if(multi_byte)
tmp_ptr = INTL_NextChar(charset, tmp_ptr);
else
tmp_ptr++;
}
}
/* don't need this any more since we're converting
* elsewhere -- erik
tmp_buf = FE_TranslateISOText(context, charset, tmp_buf);
*/
line_length = XP_STRLEN(tmp_buf);
if ((line_length > 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;
/* start at the current text position in this text block */
text = (char *) &block->text_buffer[ block->buffer_read_index ];
#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
/*
* 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 <NOBR> 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 <NOBR> 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 <NOBR> 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; (i<len) && (*tmp_ptr != '\0'); i++) {
*to_ptr++ = *tmp_ptr++;
}
}
else {
to_ptr++;
tmp_ptr++;
}
}
*w_end = tchar1;
w_start = tmp_buf;
w_end = to_ptr;
*w_end = '\0';
}
/*
* Make this Front End specific text, and count
* the length of the word.
*/
/* don't need this any more since we're converting
* elsewhere -- erik
w_start = FE_TranslateISOText(context, charset, w_start);
*/
word_len = XP_STRLEN(w_start);
/*
* Append this word to the line buffer.
* It may be necessary to expand the line
* buffer.
*/
if ((state->line_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.
*************************************/
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);
}
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(!state->top_state->is_binary)
FE_SetProgressBarPercent(context, percent);
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_ParseSinglePreformattedText ( lo_DocState * state, LO_TextBlock * block, Bool parseAllText, char * text );
static void lo_ParseDoubleText ( lo_DocState * state, LO_TextBlock * block, Bool parseAllText, char * text );
static void lo_ParseDoublePreformattedText ( 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 <NOBR> 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_SkipCharacter ( LO_TextBlock * block )
{
++block->buffer_read_index;
}
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;
}
JSBool
lo_ColorStringToData(const char *color, uint32 *data, void *closure)
{
LO_Color col;
if (!LO_ParseStyleSheetRGB((char *)color, &col.red, &col.green, &col.blue))
return JS_FALSE;
*data = *(uint32 *)&col;
return JS_TRUE;
}
#define FONT_WEIGHT_BOLDER 0x10000
#define FONT_WEIGHT_LIGHTER 0x20000
JSBool
lo_SSUnitsToData(const char *str, uint32 *data, void *closure)
{
/* XXX NYI */
*data = XP_ATOI(str);
return JS_TRUE;
}
static JSBool
FontWeightToData(const char *str, uint32 *data, void *closure)
{
uint32 weight;
/* XXX use proper CSS-value parsing stuff */
if (!strcasecomp(str, "bolder")) {
weight = FONT_WEIGHT_BOLDER;
} else if (!strcasecomp(str, "lighter")) {
weight = FONT_WEIGHT_LIGHTER;
} else {
weight = XP_ATOI(str);
weight -= weight % 100;
}
*data = weight;
return JS_TRUE;
}
#define SET_ATTR_BIT_IF(style, bit) \
if (!strcasecomp(decors, style)) \
attrs |= bit;
static JSBool
TextDecorationToData(const char *decors, uint32 *data, void *closure)
{
uint32 attrs = 0;
/* XXX handle multiple tokens */
SET_ATTR_BIT_IF(BLINK_STYLE, LO_ATTR_BLINK)
else
SET_ATTR_BIT_IF(STRIKEOUT_STYLE, LO_ATTR_STRIKEOUT)
else
SET_ATTR_BIT_IF(UNDERLINE_STYLE, LO_ATTR_UNDERLINE)
else
if (!strcasecomp(decors, "none"))
attrs = 0;
*data = attrs;
return JS_TRUE;
}
#undef SET_ATTR_BIT_IF
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 = state->top_state->current_node;
LO_TextAttr text_attr, *newptr = NULL;
JSBool copied = JS_FALSE;
LO_Color col;
if (!cx || !db || !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
fprintf(stderr, "setting textattr on <%s>\n",
((DOM_Element *)node)->tagName);
#endif
} else {
#ifdef DEBUG_shaver
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;
}
/* check "color" property */
if (!DOM_StyleGetProperty(cx, db, node, COLOR_STYLE, &entry))
/* XXX report error? return tptr unmodified? */
return NULL;
if (entry) {
if (!DOM_GetCleanEntryData(cx, entry, lo_ColorStringToData, (uint32*)&col,
NULL))
return NULL;
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))
return NULL;
if (entry) {
COW();
if (!DOM_GetCleanEntryData(cx, entry, lo_ColorStringToData, (uint32*)&col,
NULL))
return NULL;
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))
return NULL;
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);
}
}
/* XXX font-size requires STYLESTRUCT_StringToSSNumber-alike */
/* check font-weight */
if (!DOM_StyleGetProperty(cx, db, node, FONTWEIGHT_STYLE, &entry))
return NULL;
if (entry) {
uint32 weight;
/*
* For "bolder" and "lighter", we need to keep them dirty because they're
* dependent on the enclosing state, so we have to recalculate each time.
* OPTIMIZATION: let FontWeightToData parse them and make the data be
* some magic value > 0xffff, to save the strcasecomps on each pass.
*/
if (!DOM_GetCleanEntryData(cx, entry, FontWeightToData, &weight, NULL))
return NULL;
if (weight) {
if (!newptr->font_weight) {
COW();
newptr->font_weight = 400;
}
if (weight == FONT_WEIGHT_BOLDER) {
weight = MAX(newptr->font_weight + 100, 100);
} else if (weight == FONT_WEIGHT_LIGHTER) {
weight = MIN(newptr->font_weight - 100, 900);
}
if (weight != (uint32)newptr->font_weight) {
COW();
newptr->font_weight = weight;
}
}
}
/* font-style */
if (!DOM_StyleGetProperty(cx, db, node, FONTSTYLE_STYLE, &entry))
return NULL;
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))
return NULL;
if (entry) {
uint32 attrs;
#define ATTRMASK (LO_ATTR_BLINK | LO_ATTR_STRIKEOUT | LO_ATTR_UNDERLINE)
if (!DOM_GetCleanEntryData(cx, entry, TextDecorationToData, &attrs, NULL))
return NULL;
attrs = (tptr->attrmask & ~ATTRMASK) | (attrs & ATTRMASK);
if (attrs != tptr->attrmask) {
COW();
newptr->attrmask = attrs;
}
#undef ATTRMASK
}
#undef COW
if (!isMutable && copied)
return lo_FetchTextAttr(state, &text_attr);
return tptr;
}
#endif
#ifdef TEST_16BIT
#undef XP_WIN16
#endif /* TEST_16BIT */
#ifdef PROFILE
#pragma profile off
#endif