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