[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
lynx-dev replacement split_line function
From: |
Klaus Weide |
Subject: |
lynx-dev replacement split_line function |
Date: |
Tue, 15 Dec 1998 06:14:57 -0600 (CST) |
Here is a replacement for function split_line in GridText.c that should
take care of the interaction between line splitting and link positioning
in uncommon situations.
Problem example - to be viewed with numbered links, not -hiddenlinks=merge,
80 columns screen width:
---------- snip ----------
<P>
<EM>The asterisk should be a selectable link: .............. anchor:<A
HREF=X> *</A>some more text.</EM><A HREF=Y>another link</A>
---------- snip ----------
Some problems of this kind - where the link text is one character short
at the beginning or end - were more visible with color styles (because
no special attribute chars are inserted which can provide a security
margin against wrong counting). But the above example shows that there
was also a problem for non-colorstyle.
For color styles, line splitting should now also better interact with
style changes.
I am sending the whole new function, rather than a patch, because this
should work as a drop-in replacement in both 2.8.1rel.2 and dev.n.
Patches would be quite long (maybe longer than the function).
Just replace the whole function split_line with the appended version.
Klaus
* split_line: Try to account for changes in anchor position and extent
in all possible cases. This improves some cases where the selectable
text could become too short and, in extreme cases, a link could become
erroneously "hidden".
* split_line: color style changes after the split position are moved to
the correct position in the new line.
---------- snip ----------
PRIVATE void split_line ARGS2(
HText *, text,
unsigned, split)
{
HTStyle * style = text->style;
HTLine * temp;
int spare;
#if defined(USE_COLOR_STYLE)
int inew;
#endif
int indent = text->in_line_1 ?
text->style->indent1st : text->style->leftIndent;
TextAnchor * a;
int CurLine = text->Lines;
int HeadTrim = 0;
int SpecialAttrChars = 0;
int TailTrim = 0;
int s;
#define DEBUG_SPLITLINE
/*
* Make new line.
*/
HTLine * previous = text->last_line;
int ctrl_chars_on_previous_line = 0;
char * cp;
/* can't wrap in middle of multibyte sequences, so allocate 2 extra */
HTLine * line = (HTLine *)LY_CALLOC(1, LINE_SIZE(MAX_LINE)+2);
if (line == NULL)
outofmem(__FILE__, "split_line_1");
ctrl_chars_on_this_line = 0; /*reset since we are going to a new line*/
text->LastChar = ' ';
#ifdef DEBUG_APPCH
CTRACE(tfp,"GridText: split_line(%p,%d) called\n", text, split);
CTRACE(tfp," bold_on=%d, underline_on=%d\n", bold_on, underline_on);
#endif
if (split > previous->size) {
CTRACE(tfp,
"*** split_line: split==%d greater than last_line->size==%d !\n",
split, previous->size);
if (split > MAX_LINE) {
split = previous->size;
if ((cp = strrchr(previous->data, ' ')) &&
cp - previous->data > 1)
split = cp - previous->data;
CTRACE(tfp, " split adjusted to %d.\n", split);
}
}
text->Lines++;
previous->next->prev = line;
line->prev = previous;
line->next = previous->next;
previous->next = line;
text->last_line = line;
line->size = 0;
line->offset = 0;
text->permissible_split = 0; /* 12/13/93 */
line->data[0] = '\0';
/*
* If we are not splitting and need an underline char, add it now. - FM
*/
if ((split < 1) &&
!(dump_output_immediately && use_underscore) && underline_on) {
line->data[line->size++] = LY_UNDERLINE_START_CHAR;
line->data[line->size] = '\0';
ctrl_chars_on_this_line++;
}
/*
* If we are not splitting and need a bold char, add it now. - FM
*/
if ((split < 1) && bold_on) {
line->data[line->size++] = LY_BOLD_START_CHAR;
line->data[line->size] = '\0';
ctrl_chars_on_this_line++;
}
/*
* Split at required point
*/
if (split > 0) { /* Delete space at "split" splitting line */
char *p, *prevdata = previous->data, *linedata = line->data;
unsigned plen;
int i;
/*
* Split the line. - FM
*/
prevdata[previous->size] = '\0';
previous->size = split;
/*
* Trim any spaces or soft hyphens from the beginning
* of our new line. - FM
*/
p = prevdata + split;
while ((*p == ' ' &&
(HeadTrim || text->first_anchor ||
underline_on || bold_on ||
text->style->alignment != HT_LEFT ||
text->style->wordWrap || text->style->freeFormat ||
text->style->spaceBefore || text->style->spaceAfter)) ||
*p == LY_SOFT_HYPHEN) {
p++;
HeadTrim++;
}
plen = strlen(p);
/*
* Add underline char if needed. - FM
*/
if (!(dump_output_immediately && use_underscore)) {
/*
* Make sure our global flag is correct. - FM
*/
underline_on = NO;
for (i = (split-1); i >= 0; i--) {
if (prevdata[i] == LY_UNDERLINE_END_CHAR) {
break;
}
if (prevdata[i] == LY_UNDERLINE_START_CHAR) {
underline_on = YES;
break;
}
}
/*
* Act on the global flag if set above. - FM
*/
if (underline_on && *p != LY_UNDERLINE_END_CHAR) {
linedata[line->size++] = LY_UNDERLINE_START_CHAR;
linedata[line->size] = '\0';
ctrl_chars_on_this_line++;
SpecialAttrChars++;
}
if (plen) {
for (i = (plen - 1); i >= 0; i--) {
if (p[i] == LY_UNDERLINE_START_CHAR) {
underline_on = YES;
break;
}
if (p[i] == LY_UNDERLINE_END_CHAR) {
underline_on = NO;
break;
}
}
for (i = (plen - 1); i >= 0; i--) {
if (p[i] == LY_UNDERLINE_START_CHAR ||
p[i] == LY_UNDERLINE_END_CHAR) {
ctrl_chars_on_this_line++;
}
}
}
}
/*
* Add bold char if needed, first making
* sure that our global flag is correct. - FM
*/
bold_on = NO;
for (i = (split - 1); i >= 0; i--) {
if (prevdata[i] == LY_BOLD_END_CHAR) {
break;
}
if (prevdata[i] == LY_BOLD_START_CHAR) {
bold_on = YES;
break;
}
}
/*
* Act on the global flag if set above. - FM
*/
if (bold_on && *p != LY_BOLD_END_CHAR) {
linedata[line->size++] = LY_BOLD_START_CHAR;
linedata[line->size] = '\0';
ctrl_chars_on_this_line++;
SpecialAttrChars++;;
}
if (plen) {
for (i = (plen - 1); i >= 0; i--) {
if (p[i] == LY_BOLD_START_CHAR) {
bold_on = YES;
break;
}
if (p[i] == LY_BOLD_END_CHAR) {
bold_on = NO;
break;
}
}
for (i = (plen - 1); i >= 0; i--) {
if (p[i] == LY_BOLD_START_CHAR ||
p[i] == LY_BOLD_END_CHAR ||
IS_UTF_EXTRA(p[i]) ||
p[i] == LY_SOFT_HYPHEN) {
ctrl_chars_on_this_line++;
}
if (p[i] == LY_SOFT_HYPHEN && (int)text->permissible_split < i)
{
text->permissible_split = i + 1;
}
}
}
/*
* Add the data to the new line. - FM
*/
strcat(linedata, p);
line->size += plen;
}
/*
* Economize on space.
*/
while ((previous->size > 0) &&
(previous->data[previous->size-1] == ' ') &&
(ctrl_chars_on_this_line || HeadTrim || text->first_anchor ||
underline_on || bold_on ||
text->style->alignment != HT_LEFT ||
text->style->wordWrap || text->style->freeFormat ||
text->style->spaceBefore || text->style->spaceAfter)) {
/*
* Strip trailers.
*/
previous->data[previous->size-1] = '\0';
previous->size--;
TailTrim++;
}
/*
* s is the effective split position, given by either a non-zero
* value of split or by the size of the previous line before
* trimming. - kw
*/
if (split == 0) {
s = previous->size + TailTrim; /* the original size */
} else {
s = split;
}
#ifdef DEBUG_SPLITLINE
#ifdef DEBUG_APPCH
if (s != (int)split)
#endif
CTRACE(tfp,"GridText: split_line(%d [now:%d]) called\n", split, s);
#endif
#if defined(USE_COLOR_STYLE)
#define LastStyle (previous->numstyles-1)
line->numstyles = 0;
inew = MAX_STYLES_ON_LINE - 1;
/*
* Color style changes after the split position + possible trimmed
* head characters are transferred to the new line. Ditto for changes
* within the trimming region, but be stop when we reach an OFF change.
* The second while loop below may then handle remaining changes. - kw
*/
while (previous->numstyles && inew >= 0) {
if (previous->styles[LastStyle].horizpos > s + HeadTrim) {
line->styles[inew].horizpos =
previous->styles[LastStyle].horizpos
- (s + HeadTrim) + SpecialAttrChars;
line->styles[inew].direction =
previous->styles[LastStyle].direction;
line->styles[inew].style = previous->styles[LastStyle].style;
inew --;
line->numstyles ++;
previous->numstyles --;
} else if (previous->styles[LastStyle].horizpos > s - TailTrim &&
(previous->styles[LastStyle].direction == STACK_ON ||
previous->styles[LastStyle].direction == ABS_ON)) {
line->styles[inew].horizpos =
(previous->styles[LastStyle].horizpos < s) ?
0 : SpecialAttrChars;
line->styles[inew].direction =
previous->styles[LastStyle].direction;
line->styles[inew].style = previous->styles[LastStyle].style;
inew --;
line->numstyles ++;
previous->numstyles --;
} else
break;
}
spare = previous->numstyles;
while (previous->numstyles && inew >= 0) {
if (previous->numstyles >= 2 &&
previous->styles[LastStyle].style
== previous->styles[previous->numstyles-2].style &&
previous->styles[LastStyle].horizpos
== previous->styles[previous->numstyles-2].horizpos &&
((previous->styles[LastStyle].direction == STACK_OFF &&
previous->styles[previous->numstyles-2].direction == STACK_ON) ||
(previous->styles[LastStyle].direction == ABS_OFF &&
previous->styles[previous->numstyles-2].direction == ABS_ON) ||
(previous->styles[LastStyle].direction == ABS_ON &&
previous->styles[previous->numstyles-2].direction == ABS_OFF)
)) {
/*
* Discard pairs of ON/OFF for the same color style, but only
* if they appear at the same position. - kw
*/
previous->numstyles -= 2;
if (spare > previous->numstyles)
spare = previous->numstyles;
} else if (spare > 0 && previous->styles[spare - 1].direction &&
previous->numstyles < MAX_STYLES_ON_LINE) {
/*
* The index variable spare walks backwards through the
* list of color style changes on the previous line, trying
* to find an ON change which isn't followed by a
* corresponding OFF. When it finds one, the missing OFF
* change is appended to the end, and an ON change is added
* at the beginning of the current line. The OFF change
* appended to the previous line may get removed again in
* the next iteration. - kw
*/
line->styles[inew].horizpos = 0;
line->styles[inew].direction = ON;
line->styles[inew].style = previous->styles[spare - 1].style;
inew --;
line->numstyles ++;
previous->styles[previous->numstyles].style = line->styles[inew +
1].style;
previous->styles[previous->numstyles].direction = ABS_OFF;
previous->styles[previous->numstyles].horizpos = previous->size;
previous->numstyles++;
spare --;
} else if (spare >= 2 &&
previous->styles[spare - 1].style == previous->styles[spare
- 2].style &&
((previous->styles[spare - 1].direction == STACK_OFF &&
previous->styles[spare - 2].direction == STACK_ON) ||
(previous->styles[spare - 1].direction == ABS_OFF &&
previous->styles[spare - 2].direction == ABS_ON) ||
(previous->styles[spare - 1].direction == STACK_ON &&
previous->styles[spare - 2].direction == STACK_OFF) ||
(previous->styles[spare - 1].direction == ABS_ON &&
previous->styles[spare - 2].direction == ABS_OFF)
)) {
/*
* Skip pairs of adjacent ON/OFF or OFF/ON changes.
*/
spare -= 2;
} else if (spare && !previous->styles[spare - 1].direction) {
/*
* Found an OFF change not part of a matched pair.
* Assume it is safer to leave whatever comes before
* it on the previous line alone. Setting spare to 0
* ensures that it won't be used in a following
* iteration. - kw
*/
spare = 0;
} else {
/*
* Nothing applied, so we are done with the loop. - kw
*/
break;
}
}
if (previous->numstyles > 0 && previous->styles[LastStyle].direction) {
CTRACE(tfp, "%s\n%s%s\n",
"........... Too many character styles on line:",
"........... ", previous->data);
}
if (line->numstyles > 0 && line->numstyles < MAX_STYLES_ON_LINE) {
int n;
inew ++;
for (n = 0; n < line->numstyles; n++)
line->styles[n] = line->styles[n + inew];
} else
if (line->numstyles == 0)
/* FIXME: RJP - shouldn't use 0xffffffff for largest integer */
line->styles[0].horizpos = 0xffffffff;
if (previous->numstyles == 0)
previous->styles[0].horizpos = 0xffffffff;
#endif /*USE_COLOR_STYLE*/
temp = (HTLine *)LY_CALLOC(1, LINE_SIZE(previous->size));
if (temp == NULL)
outofmem(__FILE__, "split_line_2");
memcpy(temp, previous, LINE_SIZE(previous->size));
FREE(previous);
previous = temp;
previous->prev->next = previous; /* Link in new line */
previous->next->prev = previous; /* Could be same node of course */
/*
* Terminate finished line for printing.
*/
previous->data[previous->size] = '\0';
/*
* Align left, right or center.
*/
spare = 0;
if (style->alignment == HT_CENTER ||
style->alignment == HT_RIGHT) {
/* Calculate spare character positions if needed */
for (cp = previous->data; *cp; cp++) {
if (*cp == LY_UNDERLINE_START_CHAR ||
*cp == LY_UNDERLINE_END_CHAR ||
*cp == LY_BOLD_START_CHAR ||
*cp == LY_BOLD_END_CHAR ||
IS_UTF_EXTRA(*cp) ||
*cp == LY_SOFT_HYPHEN)
ctrl_chars_on_previous_line++;
}
/* @@ first line indent */
spare = (LYcols-1) -
(int)style->rightIndent - indent +
ctrl_chars_on_previous_line - previous->size -
((previous->size > 0) &&
(int)(previous->data[previous->size-1] ==
LY_SOFT_HYPHEN ?
1 : 0));
}
switch (style->alignment) {
case HT_CENTER :
previous->offset = previous->offset + indent + spare/2;
break;
case HT_RIGHT :
previous->offset = previous->offset + indent + spare;
break;
case HT_LEFT :
case HT_JUSTIFY : /* Not implemented */
default:
previous->offset = previous->offset + indent;
break;
} /* switch */
text->chars = text->chars + previous->size + 1; /* 1 for the line */
text->in_line_1 = NO; /* unless caller sets it otherwise */
/*
* If we split the line, adjust the anchor
* structure values for the new line. - FM
*/
if (split > 0 || s > 0) { /* if not completely empty */
TextAnchor * prev_a = NULL;
for (a = text->first_anchor; a; prev_a = a, a = a->next) {
if (a->line_num == CurLine) {
int old_e = a->extent;
int a0 = a->line_pos, a1 = a->line_pos + a->extent;
int S, d, new_pos, new_ext;
if (a->link_type == INPUT_ANCHOR) {
if (a->line_pos >= s) {
a->start += (1 + SpecialAttrChars - HeadTrim -
TailTrim);
a->line_pos -= (s - SpecialAttrChars + HeadTrim);
a->line_num = text->Lines;
}
continue;
}
if (a0 < s - TailTrim && a1 <= s - TailTrim) {
continue; /* unaffected, leave it where it is. */
}
S = s + a->start - a->line_pos;
d = S - s; /* == a->start - a->line_pos */
new_ext = old_e = a->extent;
if (!old_e &&
(!a->number || a->show_anchor) &&
a0 <= s + HeadTrim) {
#ifdef DEBUG_SPLITLINE
CTRACE(tfp, "anchor %d case %d: ",
a->number,1);
#endif
/*
* It is meant to be empty, and/or endAnchor
* has seen it and recognized it as empty.
*/
new_pos = (a0 <= s) ? s - TailTrim :
s - TailTrim + 1;
if (prev_a && new_pos + d < prev_a->start) {
if (prev_a->start <= S - TailTrim + 1 +
SpecialAttrChars)
new_pos = prev_a->start - d;
else
new_pos = s - TailTrim + 1 + SpecialAttrChars;
}
} else if (old_e &&
a0 >= s - TailTrim && a0 <= s + HeadTrim &&
a1 <= s + HeadTrim) {
#ifdef DEBUG_SPLITLINE
CTRACE(tfp, "anchor %d case %d: ",
a->number,2);
#endif
/*
* endAnchor has seen it, it is effectively empty
* after our trimming, but endAnchor has for some
* reason not recognized this. In other words,
* this should not happen.
* Should we not adjust the extent and let it "leak"
* into the new line?
*/
new_pos = (a0 < s) ? s - TailTrim :
s - TailTrim + 1;
if (prev_a && new_pos + d < prev_a->start) {
if (prev_a->start <= S - TailTrim + 1 +
SpecialAttrChars)
new_pos = prev_a->start - d;
else
new_pos = s - TailTrim + 1 + SpecialAttrChars;
}
new_ext = 0;
} else if (a0 >= s + HeadTrim) {
#ifdef DEBUG_SPLITLINE
CTRACE(tfp, "anchor %d case %d: ",
a->number,3);
#endif
/*
* Completely after split, just shift.
*/
new_pos = a0 - TailTrim + 1 - HeadTrim + SpecialAttrChars;
} else if (!old_e) {
#ifdef DEBUG_SPLITLINE
CTRACE(tfp, "anchor %d case %d: ",
a->number,4);
#endif
/*
* No extent set, we may still be growing it.
*/
new_pos = s - TailTrim + 1 + SpecialAttrChars;
/*
* Ok, it's neither empty, nor is it completely
* before or after the split region (including trimmed
* stuff). So the anchor is either being split in
* the middle, with stuff remaining on both lines,
* or something is being nibbled off, either at
* the end (anchor stays on previous line) or at
* the beginning (anchor is on new line). Let's
* try in that order.
*/
} else if (a0 < s - TailTrim &&
a1 > s + HeadTrim) {
#ifdef DEBUG_SPLITLINE
CTRACE(tfp, "anchor %d case %d: ",
a->number,5);
#endif
new_pos = a0;
new_ext = old_e - TailTrim - HeadTrim + SpecialAttrChars;
} else if (a0 < s - TailTrim) {
#ifdef DEBUG_SPLITLINE
CTRACE(tfp, "anchor %d case %d: ",
a->number,6);
#endif
new_pos = a0;
new_ext = s - TailTrim - a0;
} else if (a1 > s + HeadTrim) {
#ifdef DEBUG_SPLITLINE
CTRACE(tfp, "anchor %d case %d: ",
a->number,7);
#endif
new_pos = s - TailTrim + 1 + SpecialAttrChars;
new_ext = old_e - (s + HeadTrim - a0);
} else {
CTRACE(tfp, "split_line anchor %d line %d: This should not
happen!\n",
a->number, a->line_num);
CTRACE(tfp,
"anchor %d: (T,H,S)=(%d,%d,%d);
(line,start,pos,ext):(%d,%d,%d,%d)!\n",
a->number,
TailTrim,HeadTrim,SpecialAttrChars,
a->line_num,a->start,a->line_pos,a->extent);
continue;
}
#ifdef DEBUG_SPLITLINE
CTRACE(tfp, "(T,H,S)=(%d,%d,%d);
(line,start,pos,ext):(%d,%d,%d,%d",
TailTrim,HeadTrim,SpecialAttrChars,
a->line_num,a->start,a->line_pos,a->extent);
#endif
if (new_pos != a->line_pos)
a->start = new_pos + d;
if (new_pos > s - TailTrim) {
new_pos -= s - TailTrim + 1;
a->line_num = text->Lines;
}
a->line_pos = new_pos;
a->extent = new_ext;
#ifdef DEBUG_SPLITLINE
CTRACE(tfp, ")->(%d,%d,%d,%d)\n",
a->line_num,a->start,a->line_pos,a->extent);
#endif
}
}
}
} /* split_line */
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- lynx-dev replacement split_line function,
Klaus Weide <=