/* Copyright 1997 Acorn Computers Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /***************************************************/ /* File : Reformat.c */ /* */ /* Purpose: Functions to handle page reformatting. */ /* */ /* Author : A.D.Hodgkinson */ /* */ /* History: 03-Dec-96: Created */ /* 16-Apr-97: First merge with T.Cheal's */ /* table code... */ /* 22-May-97: Amazingly, *still* trying */ /* to get this to work. */ /* 18-Jun-97: Hpmh; works, but very slow. */ /* Will need to rewrite at */ /* some stage; for the moment, */ /* moved a few bits over to */ /* Tables.c as they fitted in */ /* better over there. */ /***************************************************/ #include <stdlib.h> #include <stdio.h> #include <string.h> #include "swis.h" #include "flex.h" #include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */ #include "wimp.h" #include "wimplib.h" #include "event.h" #include "svcprint.h" #include "Global.h" #include "FromROSLib.h" #include "MiscDefs.h" #include "Utils.h" #include "Browser.h" #include "Fetch.h" /* (Which itself includes URLstat.h) */ #include "FetchPage.h" #include "FontManage.h" #include "Forms.h" #include "History.h" #include "Images.h" #include "Memory.h" #include "Object.h" #include "Redraw.h" #include "Tables.h" #include "Toolbars.h" #include "Reformat.h" /* Local constant definitions */ #define BULLET_GAP 12 /* Static function prototypes */ static int reformat_istext (HStream * tp); static int reformat_useless_token (HStream * tp); static int reformat_newline_check (HStream * current, HStream * last, int offset); static int reformat_datasize (HStream * p); static _kernel_oserror * reformat_token_width (reformat_width_data * w, unsigned int flags); static void reformat_check_visited (browser_data * b, HStream * token); static _kernel_oserror * reformat_add_line (browser_data * b, reformat_cell * cell); static _kernel_oserror * reformat_add_line_chunk (browser_data * b, reformat_cell * cell); static _kernel_oserror * reformat_format_from_now (browser_data * b, int lastline); static _kernel_oserror * reformat_text_line_height (browser_data * b, HStream * tp, int * top, int * bot); static _kernel_oserror * reformat_check_height (int toplevel, browser_data * b, reformat_cell * d, int line, HStream * tp, HStream * tpLast, int offset); static int reformat_reformatter_r (unsigned int flags, browser_data * b, reformat_cell * d, HStream * streambase); /* Local compilation options */ #undef SUPPORT_NOBR // static void reformat_strip_prespace (browser_data * b, int chunk, HStream * tp); /*************************************************/ /* reformat_formatting() */ /* */ /* Returns 0 if reformatting is not in progress, */ /* else non-zero. */ /* */ /* Parameters: Pointer to the browser_data */ /* structure associated with the */ /* page which might be reformatting. */ /* */ /* Returns: 0 if reformatting is not in */ /* progress, else it is. */ /*************************************************/ int reformat_formatting(browser_data * b) { // if (b->suspend_format) Printf("reformat_formatting: %p suspend_format set\n",b); // else if (b->last_token) // { // if (!b->last_token->next) Printf("reformat_formatting: %p b->last_token->next = NULL\n",b); // else if (b->last_token->next && !(b->last_token->next->flags & HFlags_DealtWithToken)) Printf("reformat_formatting: %p last_token->next HFlags NULL\n",b); // else if (b->cell->nlines <= 0) Printf("reformat_formatting: %p will return b->final_token != NULL: %d\n",b,b->final_token != NULL); // else Printf("reformat_formatting: %p will return 1\n",b); // } // else if (b->cell->nlines <= 0) Printf("reformat_formatting: %p will return b->final_token != NULL: %d\n",b,b->final_token != NULL); // else Printf("reformat_formatting: %p will return 1\n",b); /* Reformatting had been suspended due to an error */ if (b->suspend_format) return 0; if (b->last_token) { /* If we've not overrun the tokens dealt with by the fetcher, */ /* the fetcher could have exitted and this could be the last */ /* token (so we've finished). */ /* */ /* The check for a 'next' token is done as final_token only */ /* records main token stream tokens dealt with by the */ /* preprocessor. It is possible for a precise set of */ /* circumstances to leave last_token and final_token */ /* equivalent due to table parsing, though there is more data */ /* in the main token stream after it (this happened during */ /* testing, so it isn't all that rare). */ if (!b->last_token->next) return 0; /* If the token after the last one dealt with by the */ /* fetcher had not been looked at by the preprocessor, */ /* the reformatter must have exitted for that reason. */ if (b->last_token->next && !(b->last_token->next->flags & HFlags_DealtWithToken)) return 0; } /* If there are no lines, are there any HStream structures? */ /* If so, the preprocessor has got stuff from the fetcher */ /* but the reformatter hasn't built anything with them yet. */ if (b->cell->nlines <= 0) return (b->final_token != NULL); /* If none of the above, we must still be formatting */ return 1; } /*************************************************/ /* reformat_stop() */ /* */ /* Suspends a reformat, flagging that this has */ /* been done in the browser_data structure. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* with the details of the reformat */ /* process to stop inside it. */ /*************************************************/ _kernel_oserror * reformat_stop(browser_data * b) { b->suspend_format = 1; return toolbars_set_button_states(b); } /*************************************************/ /* reformat_istext() */ /* */ /* Returns 1 if an HStream structure represents */ /* neither a horizontal rule nor an image. */ /* */ /* Parameters: Pointer to the HStream structure. */ /* */ /* Returns: 1 if the struct represents text, */ /* else 0. */ /*************************************************/ static int reformat_istext(HStream * tp) { return ( ((tp->style) & (IMG | HR)) == 0 && tp->tagno != TAG_TABLE && !ISOBJECT(tp) && ! ( tp->tagno == TAG_INPUT && HtmlINPUTtype(tp) == inputtype_IMAGE ) ); } /*************************************************/ /* reformat_token_width() */ /* */ /* Fills in the 'width' and 'bytes' fields of a */ /* reformat_width_data structure according to */ /* the contents of the token (HStream structure) */ /* that the reformat_width_data structure points */ /* to (this structure is defined in Reformat.h). */ /* */ /* The idea is to fit the token into maxwidth */ /* coordinates (in millipoints). The actual */ /* width is returned (which may be greater than, */ /* or less than maxwidth) and the number of */ /* bytes used to make that width is returned. */ /* */ /* Parameters: Pointer to a reformat_width_data */ /* structure (see Reformat.h); */ /* */ /* A flags word, as ppassed to */ /* reformat_reformatter. */ /* */ /* Returns: Fills in the structure's 'width' */ /* and 'bytes' fields with width and */ /* byte count information. */ /*************************************************/ static _kernel_oserror * reformat_token_width(reformat_width_data * w, unsigned int flags) { _kernel_oserror * e = NULL; BBox box; /* Deal with tables */ if (w->tp->tagno == TAG_TABLE) { reformat_cell * cellarray; table_stream * table = (table_stream *) w->tp; int size; int toplevel; #ifdef TRACE if (tl & (1u<<20)) Printf("reformat_token_width: Dealing with table, token %p\n",table); #endif tables_count_table(w->b, table); size = table->ColSpan * table->RowSpan; #ifdef TRACE if (tl & (1u<<20)) { Printf("reformat_token_width: Size of table is %d\n",size); if (!size) Printf(" (Table has no cells...)\n"); } #endif if (size) tables_position_table(w->b, table); #ifdef TRACE if (tl & (1u<<20)) Printf("reformat_token_width: Tag is now 0x%x\n",w->tp->tag); #endif /* If the table has no cells, may need to clear an existing cell array */ if (!size) { if (table->cells) HtmlFree(table->cells); table->ncells = 0; } else { /* If we've got an existing cell array and the number of cells has */ /* not changed, don't need to do anything. Otherwise, (re)allocate */ /* the cell array. */ if (!table->cells || table->ncells != size) { reformat_cell * old_cells = table->cells; /* Now allocate a new cell array and initialise the cell contents */ table->cells = HtmlMalloc(size * sizeof(reformat_cell), table); /* If the allocation fails, can't possibly continue so jump */ /* back to poll loop after reporting the error. */ if (!table->cells) show_error_cont(make_no_table_memory_error(9)); /* Otherwise, initialise the cell contents */ tables_init_table(w->b, table, table->cells); /* Copy over any old cells ('ncells' still holds the old number) */ if (table->ncells && old_cells) { memcpy(table->cells, old_cells, table->ncells * sizeof(reformat_cell)); /* Free the old cell array */ HtmlFree(old_cells); } /* Now update 'ncells' */ table->ncells = size; } } cellarray = table->cells; if (cellarray) { /* Find the width and height of the cells and fix their positions */ /* as millipoint offsets from the top left of the table. */ /* */ /* If the reformatter flags say not to generate lines, then we */ /* must be doing a width finding session as part of a parent */ /* table, so toplevel is 0. Otherwise, this is the highest level */ /* and toplevel is therefore set to 1. */ if (flags & Reformatter_Virtual) toplevel = 1; else toplevel = 0; w->width = tables_width_table (toplevel, w->b, table, w->maxwid, cellarray, flags); w->bytes = tables_height_table(toplevel, w->b, table, cellarray); tables_fix_table(w->b, table, cellarray); } #ifdef TRACE if (tl & (1u<<20)) Printf("reformat_token_width: Tag is now 0x%x\n",w->tp->tag); #endif /* Return the cell array pointer for convenience (may prove useful */ /* to have this in the reformatter some time...). */ w->data = (char *) cellarray; return e; } /* Deal with forms: Text-based elements */ else if ( w->tp->tagno == TAG_SELECT || w->tp->tagno == TAG_TEXTAREA || w->tp->tagno == TAG_INPUT ) { if ( w->tp->tagno == TAG_SELECT || w->tp->tagno == TAG_TEXTAREA || HtmlINPUTtype(w->tp) == inputtype_PASSWORD || HtmlINPUTtype(w->tp) == inputtype_TEXT ) { int h; int done = 0; int length; if (w->tp->tagno == TAG_TEXTAREA) { /* Text areas */ length = w->tp->cols; if (length == 1) length = 2; else if (length < 2) length = 30; done = 0; } else if (w->tp->tagno == TAG_SELECT) { /* SELECT elements (have pop-up menus) */ int width, l; int extra = 0; char * p; e = read_sprite_size("fgright", &width, NULL); if (e) return e; width += 32; /* Account for border and gap */ convert_to_points(width, &extra); /* Need to find out the actual used width, or we end up */ /* generally overestimating the required size. */ p = (char *) HtmlSELECToptions(w->tp) + 8; h = fm_find_token_font(w->b, w->tp, 0); l = 0; width = 0; length = 0; /* Not used, but stops compiler giving warnings */ /* Some fields can hold the selNONE or selMANY text, as well */ /* as the menu item texts. */ if (HtmlSELECTmultiple(w->tp) || *p == 0xff) { e = fm_get_string_width(h, lookup_token("selNONE:<None>",0,0), Reformat_AsWideAsPossible_MP, strlen(tokens), -1, NULL, &width); if (!e && width > l) l = width; } if (HtmlSELECTmultiple(w->tp)) { e = fm_get_string_width(h, lookup_token("selMANY:<Many>",0,0), Reformat_AsWideAsPossible_MP, strlen(tokens), -1, NULL, &width); if (!e && width > l) l = width; } /* Look for the width of the widest OPTION field */ while (*p != 0xff) { p++; e = fm_get_string_width(h, p, Reformat_AsWideAsPossible_MP, strlen(p), -1, NULL, &width); if (e) { erb = *e, e = &erb; width = 16000; /* Arbitrary! Give it 40 OS units on error */ break; } /* Record if this is the widest and skip to the next field */ if (width > l) l = width; p += strlen(p) + 1; p += strlen(p) + 1; } /* Set the width and flag that we've done the calculation */ /* already so code lower down doesn't try. */ w->width = l + extra; done = 1; } else { /* One line writable */ length = HtmlINPUTsize(w->tp); if (length == 1) length = 2; else if (length < 2) { length = w->tp->maxlen; if (length > 40) length = 40; } done = 0; } /* Some cases above may have worked out the width already. */ /* For those that haven't, do it on the basis of 'length', */ /* holding the maximum number of characters, and the */ /* width of the widest character possible in the item. */ if (!done) { BBox box; if (length == 1) length = 2; else if (length < 2) length = 20; /* Finding the width based on font BBox multipled by the */ /* field length usually is a significant overestimation, */ /* so use a number instead - this is typically closer. */ h = fm_find_token_font(w->b, w->tp, 0); e = fm_char_box(h, '0', &box); if (e) erb = *e, e = &erb; else { w->width = ((box.xmax - box.xmin) * length) + 20; convert_to_points(w->width, &w->width); } } w->bytes = 0; return e; } else switch(HtmlINPUTtype(w->tp)) { case inputtype_SUBMIT: /* No break - same as RESET */ case inputtype_BUTTON: /* Again, no break */ case inputtype_RESET: { const char * p; int h, length, end; p = form_button_text(w->tp); length = strlen(p); end = 0; while(end < length && p[end] != '\n') end++; h = fm_find_token_font(w->b, w->tp, 0); e = fm_get_string_width(h, p, Reformat_AsWideAsPossible_MP, end - w->offset, -1, &w->bytes, &w->width); if (e) erb = *e, e = &erb; w->width += 400 * 4 * 12; /* Account for border */ w->bytes = 0; return e; } break; case inputtype_CHECKBOX: { w->bytes = 0; read_sprite_size("fopton", &w->width, NULL); convert_to_points(w->width, &w->width); } break; case inputtype_RADIO: { w->bytes = 0; read_sprite_size("fradioon", &w->width, NULL); convert_to_points(w->width, &w->width); } break; case inputtype_HIDDEN: { w->width = w->bytes = 0; } break; case inputtype_IMAGE: { goto do_image; /* See below */ } break; } } /* Handle OBJECT, EMBED and APPLET tags */ else if (ISOBJECT(w->tp)) { RetError(reformat_get_object_size(w->b, w->tp, &box)); w->width = box.xmax - box.xmin; convert_to_points(w->width, &w->width); } /* Handle images */ else if (w->tp->style & IMG) { do_image: /* Used by switch statement above */ w->bytes = 0; w->width = 0; /* Now get the size of the image for reformatting purposes */ RetError(reformat_get_image_size(w->b, w->tp, &box)); w->width = box.xmax - box.xmin; convert_to_points(w->width, &w->width); } else if (w->tp->style & HR) { /* For a horizontal rule, there's no extra data to put in (so */ /* the w->bytes field is zero) and the width is as wide as */ /* the specified maximum width, unless the HR specifies a */ /* particular width directly. */ /* */ /* The special case of finding min/max widths is checked in */ /* the flags here, as otherwise any HR tag can force the */ /* width up to maxwid - which may be very large. */ w->bytes = 0; if (!HR_HAS_WIDTH(w->tp) || HR_WIDTH_UNITS(w->tp) == UNITS_PERCENT) { if ( !(flags & Reformatter_FindingWidest) && !(flags & Reformatter_FindingSmallest) ) { if (HR_HAS_WIDTH(w->tp)) w->width = (w->maxwid * HR_WIDTH(w->tp)) / 100; else w->width = w->maxwid; } else w->width = 0; } else { w->width = HR_WIDTH(w->tp) * 2; /* 1 'web pixel' = 2 OS units */ convert_to_points(w->width, &w->width); } } else if (ISBULLET(w->tp)) /* ISBULLET is defined in Fetch.h */ { /* For a bullet, there is again no extra data so bytes = 0, */ /* and the width is taken from the sprite width of the */ /* bullet point. */ w->bytes = 0; convert_to_points(reformat_bullet_width(w->tp->indent), &w->width); } else { int h, end, split; /* Can't do anything if there's no text */ if (!w->data || !*w->data) { w->bytes = 0; w->width = 0; } else { /* Loop round until a newline is found, or the end of the */ /* string is reached, starting at the offset into the data */ /* specified by the 'offset' field. */ end = w->offset; while (w->data[end] && w->data[end] != '\n') end++; /* If the text is preformatted, set a null split character. */ /* Else specify splitting on spaces. */ split = (w->tp->style & PRE) ? -1 : ' '; /* Get a font handle for rendering the token */ h = fm_find_token_font(w->b, w->tp, 0); /* If end > offset, the loop above must have gone through at least */ /* one non-newline character in the string, or there was no string */ /* to look through; find the width of the string (the call won't */ /* mind if there's no string, it'll just return 0) */ if (end > w->offset) e = fm_get_string_width(h, w->data + w->offset, w->maxwid, end - w->offset, split, &w->bytes, &w->width); /* If finding the minimum or maximum width for tables, add a little to */ /* the above width to account for italics etc. - the font manager will */ /* not have returned the upper limit on either side. */ if ( (flags & Reformatter_FindingWidest) || (flags & Reformatter_FindingSmallest) ) w->width += 3200; /* If the scan for a newline finished before the end of the string (so */ /* a newline was found) and the chunk of data defined by the call to */ /* fm_get_string_width above gave a string ending in a newline, add 1 */ /* to the bytes counter to ensure that the chunk includes it. */ if (w->data[end] && w->data[w->offset + w->bytes] == '\n') w->bytes++; } } return e; } /*************************************************/ /* reformat_useless_token() */ /* */ /* Checks to see if a token (HStream struct) is */ /* of any use to the reformatter. */ /* */ /* Parameters: Pointer to the HStream struct. */ /* */ /* Returns: 1 = the token is useless, else 0. */ /*************************************************/ static int reformat_useless_token(HStream * tp) { if (ISHEAD(tp)) return 1; if (ISNULL(tp)) return 1; if (tp->flags & HFlags_IgnoreObject) return 1; if ( ISBODY(tp) && !tp->tag && !tp->tagno && !tp->text && !tp->anchor && !tp->src && !tp->enctype && !tp->name && !tp->value && !tp->target && ( tp->style == 0x00000000 || tp->style == 0x80000000 ) ) return 1; return 0; } /*************************************************/ /* reformat_newline_check() */ /* */ /* Works out whether or not there should be a */ /* line break in the page, according to the */ /* token (HStream structure) that is currently */ /* being considered and the token that was last */ /* considered, and the offset into the data of */ /* the tokens. */ /* */ /* Parameters: Pointer to the current token (the */ /* HStream structure that is being */ /* dealt with by the reformatter, */ /* say, at the moment); */ /* */ /* Pointer to the last token that */ /* was dealt with; */ /* */ /* Data offset into the tokens. */ /* */ /* Returns: 0 if there is no line break */ /* needed, or a value between 1 and */ /* 8 that says 'yes, line break */ /* needed' and also holds details of */ /* the conditions that were met to */ /* determine the line break was */ /* needed. */ /*************************************************/ static int reformat_newline_check(HStream * current, HStream * last, int offset) { /* It generally looks better if there's a line break for tables, */ /* though this is theoretically not necessary. In practice you */ /* find various line spacing and redraw problems without the */ /* break - this may get sorted one day...! */ if (current->tagno == TAG_TABLE || last->tagno == TAG_TABLE) return 9; /* If the current token represents a horizontal rule and the last token also */ /* represented one, with no offset indicating extra data in the tokens (and */ /* provided that there is actually a last token!), then return 1. */ if (((current->style) & HR) || ((!offset) && (last) && ((last->style) & HR))) return 1; /* If we have a paragraph break but the last item was a <li> tag, and we are */ /* at the top of the line, then HTML like '<ul><li><p>Text' is being used - */ /* we should ignore the <p> tag. The position of this line of code relative */ /* to other checks in this function is obviously quite important (so be */ /* careful with it!)... */ if (!offset && (current->style & P) && (last->style & LI)) return 0; /* If the tag itself indicates that a linebreak is needed, and we are at the */ /* start of this line (offset into it is zero), return 2. This may be due to */ /* <P>, <BR> and the like, in the document source. */ if ((!offset) && ((current->style) & LINEBREAK)) return 2; /* If, again, this is the start of a line, and the pointer to the previous */ /* token dealt with is not null, proceed with other line break checks. */ if ((!offset) && (last)) { /* If the indentation has changed since the last token (e.g. a new list has */ /* been started or an old one closed between the two tokens) then return 3. */ if (current->indent != last->indent) return 3; /* If the header type changed between the two tokens, then provided the */ /* last token wasn't a list item (in which case a line break will already */ /* have been put in) return 4. */ if (((current->style & H_MASK) != (last->style & H_MASK)) && !(last->style & LI)) return 4; /* When certain tags are turned on or off, we want a linebreak. For example */ /* it looks tidier for preformatted text to have a gap above and below it, */ /* rather than having it touch the previous and following text. The macro */ /* LINEBREAKSW (defined in Reformat.h) has a list of these tags ORed */ /* together to form a mask - if this mask changes between the tokens, one */ /* of the listed tags must have turned on or off. So we want a line break; */ /* hence, return 5. */ if ((current->style & LINEBREAKSW) != (last->style & LINEBREAKSW)) return 5; /* CENTRED is defined in Reformat.h, and is 1 if the token holds data that */ /* should be displayed centred in the page. If switching from centred to */ /* uncentred text or vice versa, there must be a line break. So return 6. */ if (CENTRED(current) != CENTRED(last)) return 6; /* Some tags need a line break when they turn on, for example at the start */ /* of certain special kinds of list. The LINEBREAKON macro is defined in */ /* reformat.h and contains an ORed together list of such tags (just like */ /* the LINEBREAKSW macro used above) - so if the current token has one of */ /* these turned on, but the last token had it turned off, we want another */ /* line break; return 7. */ if ((current->style & LINEBREAKON) && !(last->style & LINEBREAKON)) return 7; /* Similarly, some tags need a line break when they turn off; for example, */ /* headers look better if they have a gap underneath them. So again, there */ /* is a macro (LINEBREAKOFF) in Reformat.h which has a list of these tags */ /* and a value of 8 is returned if we go from one of these being turned */ /* on to one being turned off. */ if (!(current->style & LINEBREAKOFF) && (last->style & LINEBREAKOFF)) return 8; } /* If none of the above conditions are met, we need no line break - return 0. */ return 0; } /*************************************************/ /* reformat_newline() */ /* */ /* Returns 1 if a line break should be inserted */ /* onto the page, or 0 if not. Does this by */ /* calling reformat_newline_check and is only */ /* interested in if the value the call returned */ /* was zero or not. */ /* */ /* Parameters: As for reformat_newline_check. */ /* */ /* Returns: 1 = line break required, else 0. */ /*************************************************/ int reformat_newline(HStream * current, HStream * last, int offset) { return (reformat_newline_check(current, last, offset) != 0); } /*************************************************/ /* reformat_datasize() */ /* */ /* Returns the size of standard data pointed to */ /* by an HStream (e.g. the length of any text */ /* string it points to). */ /* */ /* Parameters: Pointer to the HStream struct */ /* */ /* Returns: Size of associated data in bytes. */ /*************************************************/ static int reformat_datasize(HStream * tp) { /* Is this a text item? */ if (!reformat_istext(tp)) return 0; /* If the HStream has a pointer to some text, return the */ /* length of that text, else return zero. */ return (tp->text ? strlen(tp->text) : 0); } /*************************************************/ /* reformat_shift_vertically() */ /* */ /* Shifts all lines between two given line */ /* numbers (inclusive) by a given y coordinate, */ /* in OS units. A redraw is generated with */ /* Wimp_BlockCopy if moved lines lie inside the */ /* visible area of the browser window. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* holding information on the lines; */ /* */ /* The line number at which to start */ /* (inclusive); */ /* */ /* The line number at which to end */ /* (inclusive); */ /* */ /* The value to shift the lines by, */ /* in OS units - since the Y coords */ /* of the lines are for a window, */ /* a negative value would move the */ /* lines down the window and vice */ /* versa. */ /*************************************************/ _kernel_oserror * reformat_shift_vertically(browser_data * b, int start, int end, int y_shift) { _kernel_oserror * e; reformat_cell * cell = b->cell; int line; WimpGetWindowStateBlock s; if (!y_shift) return NULL; /* Limit check start and end, and exit if there appear */ /* to be no lines to move */ if (start > cell->nlines - 1) return NULL; if (start < 0) start = 0; if (end > cell->nlines - 1) end = cell->nlines - 1; if (end < 0) end = 0; if (start > end) line = start, start = end, end = line; if ((end < 0) || (start < 0) || (!cell->ldata)) return NULL; /* Alter the lines' y-coordinates */ for (line = start; line <= end; line ++) cell->ldata[line].y += y_shift; /* Have to force a redraw of the entire page to ensure */ /* that image positions are updated */ s.window_handle = b->window_handle; e = wimp_get_window_state(&s); if (e) return e; coords_box_toworkarea(&s.visible_area, (WimpRedrawWindowBlock *) &s); return wimp_force_redraw(s.window_handle, s.visible_area.xmin, s.visible_area.ymin, s.visible_area.xmax, s.visible_area.ymax); } /*************************************************/ /* reformat_format_from() */ /* */ /* Starts a new reformat session, possibly */ /* starting after a given last line (i.e. from */ /* a certain point in the page, rather than the */ /* whole page). */ /* */ /* The reformat session may be postponed until */ /* later according to the RefoTime and RefoWait */ /* entires in the Choices file. */ /* */ /* The defined way to ensure all line and chunk */ /* data for a given browser is freed, including */ /* that tied up in tables, is by calling with */ /* the browser_data struct, -1, 1, and -1 (see */ /* parameters list below). */ /* */ /* Parameters: A pointer to a browser_data */ /* structure for which the reformat */ /* is to take place; */ /* */ /* The number of the last valid */ /* reformat_line structure; */ /* */ /* 1 to format now, else the request */ /* may be delayed for some time; */ /* */ /* For requests generated by the */ /* resizing of images, pass the */ /* image number, else pass -1. */ /*************************************************/ _kernel_oserror * reformat_format_from(browser_data * b, int lastline, int immediate, int image) { _kernel_oserror * e; /* Do we delay these requests? Yes if told to in the Choices, */ /* in the function parameters, or if we're showing an external */ /* image (want to see that come in progressively, not wait for */ /* a reformat which is in this particular case completely */ /* trivial anyway). */ if ( !choices.refo_wait || immediate || b->displayed == Display_External_Image ) { reformat_format_from_now(b, lastline); } else { if (b->refo_time) { /* If there's already a pending request, is the line number */ /* specified for this one�lower than the one already there? */ /* If so, then use that higher line, else stick with the */ /* old one. Nothing else to do - the null handler takes */ /* care of it all. */ if (lastline < b->refo_line) b->refo_line = lastline; } else { /* This is the first reformat request; so set the timer and */ /* install a null handler for it. */ e = _swix(OS_ReadMonotonicTime, _OUT(0), &b->refo_time); if (e) return e; b->refo_line = lastline; register_null_claimant(Wimp_ENull, (WimpEventHandler *) reformat_format_timer, b); } } return NULL; } /*************************************************/ /* reformat_format_timer() */ /* */ /* Calls the reformatter after a delay given by */ /* the Choices file entry 'RefoTime', according */ /* to the start time specified by the */ /* 'refo_time' field of the browser_data */ /* structure. */ /* */ /* Parameters are as standard for a Wimp event */ /* handler. */ /*************************************************/ int reformat_format_timer(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle) { int timenow; /* If there appears to be no initiation time specified in the */ /* browser_data structure, dereigster and exit (being cautions...) */ if (!handle->refo_time) { handle->refo_time = 1; /* So that reformat_stop_pending will deregister this handler */ reformat_stop_pending(handle); return 0; } /* Otherwise, get the current time into 'timenow' */ if (_swix(OS_ReadMonotonicTime, _OUT(0), &timenow)) return 0; /* Do we need to reformat yet? */ if (timenow - handle->refo_time > choices.refo_time) { /* reformat_format_from_now will deregister this handler */ ChkError(reformat_format_from_now(handle, handle->refo_line)); } return 0; } /*************************************************/ /* reformat_format_from_now() */ /* */ /* Starts a new reformat session, possibly */ /* starting after a given last line (i.e. from */ /* a certain point in the page, rather than the */ /* whole page). */ /* */ /* The reformat session will be stared */ /* immediately - it will not be delayed. Don't */ /* call this directly - go through */ /* reformat_format_from instead. */ /* */ /* Parameters: A pointer to a browser_data */ /* structure for which the reformat */ /* is to take place; */ /* */ /* The number of the last valid */ /* reformat_line structure. */ /*************************************************/ static _kernel_oserror * reformat_format_from_now(browser_data * b, int lastline) { int bottom = 0; reformat_cell * cell = b->cell; /* If this browser had any queued reformats, cancel them */ if (b->refo_time) { if (b->refo_line < lastline) lastline = b->refo_line; reformat_stop_pending(b); } /* Line numbers less than -1 are invalid, and can't end */ /* on a line greater than the number of lines present! */ if (lastline < -1) lastline = -1; if (lastline >= cell->nlines) lastline = cell->nlines - 1; /* Clear the flag that says formatting is suspended due to error */ b->suspend_format = 0; /* Clear the field holding the last token number for which */ /* reformatting was definitely completed */ b->last_token = NULL; /* If lastline holds a valid line number, set bottom to the */ /* bottom y coordinate of the last line. Otherwise leave */ /* bottom set at 0 (we're reformatting to no lines). */ if (lastline >= 0) bottom = cell->ldata[lastline].y; /* If there's existing line data, need to ensure that all */ /* memory allocated in table cells is freed before getting */ /* rid of the lines after lastline. */ tables_free_memory(1, b, cell, lastline + 1); /* If we're reformatting to less than the current number of */ /* lines, 'chop off' those that are left over. */ if ((lastline + 1) < cell->nlines) { int size;//, topline; // /* Before anything else, mark the token currently displayed at the */ // /* top of the page so it can be shown after the reformat. */ // // topline = browser_top_line(b, cell, 0) - 1; // // if (topline >= 0 && (lastline + 1) < topline) // { // b->display_request = cell->cdata[cell->ldata[topline].chunks].t; // b->display_offset = cell->cdata[cell->ldata[topline].chunks].o; // b->display_vscroll = 0; // } /* Size = offset of line chunks for the last line, plus */ /* the number of line chunks times the size of a */ /* chunk. I.e. the offset for the end of the line */ /* chunks for the last line - so size = size of */ /* data that is needed for all the line chunks */ /* with lastline chunks present. */ if (lastline < 0) size = 0; else { /* If it turns out this line has no chunks, need to use the chunk address */ /* and number of chunks for the previous line; unless, of course, this is */ /* line 0, in which case there are no previous lines to refer to. */ if (!cell->ldata[lastline].n) { if (lastline == 0) size = 0; else size = (cell->ldata[lastline - 1].chunks + /* Base array offset into chunks */ cell->ldata[lastline - 1].n) * /* Add number of chunks for this line to get total number of chunks */ sizeof(reformat_line_chunk); /* Multiply by size of a reformat_line_chunk to get total size */ } /* Otherwise, we're OK; the last line has line chunks. */ else size = (cell->ldata[lastline].chunks + cell->ldata[lastline].n) * sizeof(reformat_line_chunk); } /* Clip the number of lines to the new value */ cell->nlines = lastline + 1; /* Make sure that the right amount of memory is allocated */ #ifdef TRACE if (tl & (1u<<12)) Printf("reformat_format_from: Chunk CK_LINE set to %d\n",(lastline + 1) * sizeof(reformat_line)); #endif memory_set_chunk_size(b, cell, CK_LINE, (lastline + 1) * sizeof(reformat_line)); #ifdef TRACE if (tl & (1u<<12)) Printf("reformat_format_from: Chunk CK_LDAT set to %d\n",size); #endif memory_set_chunk_size(b, cell, CK_LDAT, size); } if (!printing) { /* Ensure null events are claimed for the rest of the reformat */ if (!b->fetch_handler) fetchpage_claim_nulls(b); /* Update the status bar */ toolbars_update_status(b, Toolbars_Status_Formatting); } /* Ensure the pointer shape is OK */ browser_pointer_check(0, NULL, NULL, b); /* Redraw the bottom line of the window if not printing */ return (!printing ? browser_update_bottom(b, bottom) : NULL); } /*************************************************/ /* reformat_stop_pending() */ /* */ /* Stops any pending reformats from happening, */ /* deregistering handlers etc. as needed. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the queue. */ /*************************************************/ void reformat_stop_pending(browser_data * b) { if (b->refo_time) deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) reformat_format_timer, b); b->refo_time = 0; b->refo_line = Reformat_UnrealisticallyHighLineNumber; } /*************************************************/ /* reformat_get_image_size() */ /* */ /* Gets a BBox for a specified image in OS */ /* coordinates relative to the font base line */ /* and left hand edge. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the image; */ /* */ /* A token address for the image; */ /* */ /* Pointer to a BBox in which the */ /* relevant coords are returned. */ /* */ /* Assumes: Pointer to the BBox may not be */ /* NULL. */ /*************************************************/ _kernel_oserror * reformat_get_image_size(browser_data * b, HStream * tp, BBox * box) { _kernel_oserror * e; imgalign align; fm_face h; BBox hbox; int height = 0; /* We can't easily find out how high text is surrounding */ /* the image for align=top, align=middle or whatever, */ /* but we can do a reasonable guess based on whatever */ /* font the given token would use if it held text. */ h = fm_find_token_font(NULL, tp, 0); if (h) { if (!fm_font_box(h, &hbox)) height = hbox.ymax - hbox.xmin; fm_lose_font(NULL, h); } /* Get the image size from the image library */ e = image_get_token_image_size(b, tp, box); if (e) return e; /* Deal with alignments */ if (tp->style & IMG) /* It'll either be an IMG or an INPUT TYPE=IMAGE item */ { if ((tp->type & TYPE_ALIGN_MASK) == TYPE_MIDDLE) align = imgalign_MIDDLE; else if ((tp->type & TYPE_ALIGN_MASK) == TYPE_TOP) align = imgalign_TOP; else align = imgalign_NONE; } else align = HtmlINPUTalign(tp); switch (align) { case imgalign_MIDDLE: { int hh = height / 3; /* Technically '/ 2', but '/ 3' looks, on average, better in practice */ box->ymin -= box->ymax / 2; box->ymax /= 2; box->ymin += hh; box->ymax += hh; } break; case imgalign_TOP: { box->ymin = height - box->ymax; box->ymax = height; } break; } /* Deal with links - need to account for a border */ /* of maxlen * 2 pixels width. ISLINK is defined */ /* in Fetch.h. */ if (tp->style & IMG) { int b; b = tp->maxlen * 2; box->xmax += b; box->ymax += b; box->xmin -= b; box->ymin -= b; } return NULL; } /*************************************************/ /* reformat_get_object_size() */ /* */ /* Gets a BBox for a specified Object in OS */ /* coordinates relative to the font base line */ /* and left hand edge. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the Object; */ /* */ /* A token address for the Object; */ /* */ /* Pointer to a BBox in which the */ /* relevant coords are returned. */ /* */ /* Assumes: Pointer to the BBox may not be */ /* NULL. */ /*************************************************/ _kernel_oserror * reformat_get_object_size(browser_data * b, HStream * tp, BBox * box) { imgalign align; /* Get the Object size from the Object library */ RetError(object_get_token_object_size(b, tp, box)); /* Deal with alignments */ align = HtmlOBJECTalign(tp); switch (align) { case imgalign_MIDDLE: { box->ymin -= box->ymax / 2; box->ymax /= 2; } break; // Sort this out, as with image handling case imgalign_TOP: { box->ymin =- box->ymax; box->ymax = 0; } break; } /* Account for a border */ if (HtmlOBJECTborder(tp)) { int b; b = HtmlOBJECTborder(tp) * 2; box->xmax += b; box->ymax += b; box->xmin -= b; box->ymin -= b; } return NULL; } /*************************************************/ /* reformat_get_placeholder_size() */ /* */ /* Gets a BBox that a placeholder would fill */ /* based on given ALT text to plot inside it. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the placeholder; */ /* */ /* Pointer to an HStream struct */ /* representing the placeholder; */ /* */ /* Pointer to the ALT text (or NULL */ /* or a null string for no text); */ /* */ /* Pointer to a BBox, in which the */ /* required bounding box for the */ /* placeholder is written. */ /* */ /* Assumes: Pointer to the BBox may not be */ /* NULL. */ /*************************************************/ void reformat_get_placeholder_size(browser_data * b, HStream * tp, const char * text, BBox * box) { int h, temp, size; BBox fbox; /* Quick sanity check */ if (!box) return; box->xmin = box->ymin = 0; if (!b || !tp || !text || !*text) { box->xmax = ImageDefaultOSSize_X; box->ymax = ImageDefaultOSSize_Y; return; } /* Claim the font */ size = (fm_size(tp->fontsize) * 80) / 100; h = fm_find_font(b, "sans", size, size, 0, 0); /* Find the string width of the ALT text */ fm_get_string_width(h, text, Reformat_AsWideAsPossible_MP, strlen(text), -1, &temp, &box->xmax); convert_to_os(box->xmax, &box->xmax); /* Find the font height */ fm_font_box(h, &fbox); /* As well as subtracting ymin (the y minimum coordinate */ /* of the font bbox) from ymax, need to also add some */ /* height to give a gap between the text and slabbed box */ /* that's drawn to mark the image's position. */ box->ymax = fbox.ymax - fbox.ymin; convert_to_os(choices.font_size, &temp); if (temp < 20) temp = 20; box->ymax += temp; box->xmax += temp + temp / 2; /* Looks better to have extra horizontally */ /* Don't want to force the page width up just because of */ /* ALT text in images, especially in narrow items such */ /* as navigation frames, so limit check xmax. */ // Currently this is done by an absolute hard coded upper // limit, but ultimately it would ideally be limited e.g. // by cell width. Just as soon as I work out a nice way // of doing that... (Remember, you may not know the cell // width at times when this is being called to try and // determine it; yet you must return consistent and // appropriate values subsequently for redraw purposes). { int remain; convert_to_os(b->left_margin + b->right_margin, &remain); remain = b->display_width - remain; if (remain > 320) remain = 320; if (box->xmax > remain) box->xmax = remain; } /* Finished */ return; } /*************************************************/ /* reformat_bullet_width() */ /* */ /* Returns the width of a given bullet in OS */ /* units. */ /* */ /* Parameters: The bullet number (in the Sprites */ /* file, bullets are named b1, b2, */ /* ...bn). */ /*************************************************/ int reformat_bullet_width(int bullet) { char spr[32]; int w; sprintf(spr, "b%d\0", (bullet + bullets - 1) % bullets); if (read_sprite_size(spr, &w, NULL)) w = 32; /* See top of the file for BULLET_GAP */ return w + BULLET_GAP; } /*************************************************/ /* reformat_bullet_height() */ /* */ /* Returns the height of a given bullet in OS */ /* units. */ /* */ /* Parameters: The bullet number (in the Sprites */ /* file, bullets are named b1, b2, */ /* ...bn). */ /*************************************************/ int reformat_bullet_height(int bullet) { char spr[32]; int h; sprintf(spr, "b%d\0", (bullet + bullets - 1) % bullets); if (read_sprite_size(spr, NULL, &h)) h = 32; return h; } /*************************************************/ /* reformat_y_offset() */ /* */ /* Returns the y offset from the top of a page, */ /* in OS units, for all lines on that page. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* representing the page. */ /* */ /* Returns: y offset for all the lines on the */ /* page, in OS units, from the top. */ /*************************************************/ int reformat_y_offset (browser_data * b) { int offset; if (!controls.swap_bars) offset = toolbars_button_height(b) + toolbars_url_height(b); else offset = toolbars_status_height(b); /* The '4' accounts for the bottom window frame of the toolbars */ if (offset) offset += wimpt_dy(); if (!b->ancestor) offset += b->leading; /* Only put a gap at the top for base browsers, not for frames */ return -offset; } /*************************************************/ /* reformat_text_line_height() */ /* */ /* Works out how tall a line of text should be, */ /* returning the height above and below the */ /* notional text baseline in OS units. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the line; */ /* */ /* Pointer to an HStream which is */ /* used to determine what font the */ /* line would use; */ /* */ /* Pointer to an int, in which the */ /* height above the baseline is */ /* written; */ /* */ /* Pointer to an int, in which the */ /* height below the baseline is */ /* written. */ /* */ /* Assumes: Either int pointer may be NULL. */ /* If the HStream pointer is NULL, a */ /* value based on the base (normal) */ /* font size will be used. */ /*************************************************/ static _kernel_oserror * reformat_text_line_height(browser_data * b, HStream * tp, int * top, int * bot) { int h; int rettop, retbot; BBox box; if (tp) h = fm_find_token_font(b, tp, 1); else h = fm_find_font(b, "serif", choices.font_size, choices.font_size, 0, 0); // { // int face, size, italic, bold, scale; RetError(fm_font_box(h, &box)); /* We know the font is 'size' 16ths, so we can work */ /* out the line spacing from this. Don't use the */ /* font metrics - otherwise, bold text (say) will */ /* force the line spacing higher than plain. */ rettop = box.ymax - ((b->page_is_text && !choices.system_font) ? b->leading * 2 : 0); retbot = -box.ymin + b->leading; // fm_token_font_info(tp, &face, &size, &italic, &bold); // // bot = b->leading; // // convert_to_points(1, &scale); // top += (size * 1000) / (16 * scale); if (top) *top = rettop; if (bot) *bot = retbot; return NULL; } /*************************************************/ /* reformat_check_height() */ /* */ /* Looks at the contents of a reformat_line */ /* structure and ensures that it's y coordinate, */ /* height and font baseline offset fields are */ /* correctly filled in. */ /* */ /* Parameters: 1 for all external callers, but */ /* will be 0 if it calls itself */ /* internally; */ /* */ /* Pointer to a browser_data struct */ /* relevant to the line; */ /* */ /* Pointer to a reformat_cell struct */ /* that the line lies in; */ /* */ /* Number of the line to alter; */ /* */ /* Pointer to the token that may */ /* cause the line height to alter; */ /* */ /* Pointer to the last token dealt */ /* with by the reformatter; */ /* */ /* Offset into the current token's */ /* data. */ /* */ /* Returns: Fills in bits of the line struct, */ /* as mentioned above. */ /*************************************************/ static _kernel_oserror * reformat_check_height(int toplevel, browser_data * b, reformat_cell * d, int line, HStream * tp, HStream * tpLast, int offset) { _kernel_oserror * e; int top = 0; int bot = 0; int setpara = 0; /* Flags that a paragraph break should be added later on */ if (!d) d = b->cell; /* Some initial white space calculations. All these rely on the */ /* item being at the start of a paragraph - 'offset' is zero. */ if (!offset) { /* If the token represents a line break, include a gap, unless */ /* we're at the top of a page / cell or just after a LI tag. */ if ( line > 0 && !(tpLast->style & LI) && (tp->style & P) ) setpara = 1; /* Alternatively, if the indent has changed, insert some */ /* space above the item (but don't do this for UL and DL */ /* items, as these are handled more carefully later). */ else if ( tpLast && tpLast->indent != tp->indent && !(tp->style & UL) && !(tp->style & DL) ) setpara = 1; /* The next few require text before them to gain a gap, or if */ /* the last tag was BR and the tag before that was text, the */ /* gaps can still be added as BR just breaks the line. */ else if ( tpLast && ( (tpLast->style & PCDATA) || ( (tpLast->tagno == TAG_BR) && tpLast->prev && (tpLast->prev->style & PCDATA) ) ) ) { /* Want a space above UL items */ if (tp->style & UL) setpara = 1; /* If a header type has changed, and we are dealing with a */ /* text token, insert a gap above the item to push it away */ /* either from the text above or from the header above. */ else if ( (tp->style & PCDATA) && redraw_header(tp->style) != redraw_header(tpLast->style) ) setpara = 1; /* If we're about to enter a definition list, the 'spaces if */ /* indent changes' code above will make the spaces look odd */ /* unless we also give a space above the first item in the */ /* definition list (the DT elements are not indented, so no */ /* white space would have been inserted so far). */ else if (tp->style & DL) setpara = 1; } } /* If flagged to do so, set 'top' for an initial paragraph break */ if (setpara) { int texttop, textbot; RetError(reformat_text_line_height(b, NULL, &texttop, &textbot)); top = ((texttop + textbot) * 7) >> 3; } /* BRs - a single BR between bits of text will force a new line and be the */ /* first token in the new line, but will then have other tokens in that same */ /* line. It should not alter the line height itself under these circumstances. */ /* However, two consecutive BRs should - that is, when the second is found, it */ /* should force a P-style break, and so-on for all subsequent BRs. It is also */ /* the case that a BR at the top of a page / cell should similarly force a */ /* paragraph-like break. */ { HStream * fol; /* First On Line */ fol = d->cdata[d->ldata[line].chunks].t; if ( (fol->tagno == TAG_BR) && ( ( fol->prev && (fol->prev->tagno == TAG_BR) ) || line == 0 ) ) { int texttop, textbot; RetError(reformat_text_line_height(b, tp, &texttop, &textbot)); top = texttop + textbot; } } /* Find out the height of an image */ if ( (tp->style & IMG) || ( tp->tagno == TAG_INPUT && HtmlINPUTtype(tp) == inputtype_IMAGE ) ) { BBox box; RetError(reformat_get_image_size(b, tp, &box)); top += box.ymax; bot = -box.ymin; } /* Deal with OBJECT, APPLET and EMBED tags */ else if (ISOBJECT(tp)) { BBox box; RetError(reformat_get_object_size(b, tp, &box)); top += box.ymax; bot = -box.ymin; } /* Size of a horizontal rule; the rule is plotted */ /* centred vertically within its bounding box so */ /* there is no need to set both bot and top to */ /* the same value. With top = 0, setting bot is */ /* enough. */ else if (tp->style & HR) { int size; /* Deal with a (vertical) size specifier */ if (HR_HAS_SIZE(tp)) { /* (Only recognise pixels at present) */ switch (HR_SIZE_UNITS(tp)) { case UNITS_PIXELS: size = HR_SIZE(tp) * 2; break; default: size = 4; break; } } else size = 4; if (size < 24) size = 24; bot = size + 4; } /* A few easy to work out forms elements */ else if ( tp->tagno == TAG_INPUT && HtmlINPUTtype(tp) == inputtype_CHECKBOX ) { int size; read_sprite_size("fopton", NULL, &size); top += size - 8; bot = 8; } else if ( tp->tagno == TAG_INPUT && HtmlINPUTtype(tp) == inputtype_RADIO ) { int size; read_sprite_size("fradioon", NULL, &size); top += size - 8; bot = 8; } /* Height of a bullet point */ else if (ISBULLET(tp)) { top += reformat_bullet_height(tp->indent); } else if (tp->tagno == TAG_TABLE) { /* Don't do anything! h is already correct */ tp = tp; /* Make sure the compiler gets the if..else if.. etc. right */ } /* If the above matches aren't found, use a more general routine */ else { /* Get the bounding box of the font that should be used for the token */ /* if we've got some text, and we're either a visible forms element */ /* or the text consists of more than one single space. */ if ( ( (tp->style & FORM) && tp->tagno != TAG_FORM && tp->tagno != TAG_FORM_END && HtmlINPUTtype(tp) != inputtype_HIDDEN ) || ( (tp->text) && ( tp->text[0] && ! ( tp->text[0] == ' ' && tp->text[1] == '\0' ) ) ) ) { int texttop, textbot; RetError(reformat_text_line_height(b, tp, &texttop, &textbot)); top += texttop; bot = textbot; } } /* Work out height of various forms elements */ if (tp->tagno == TAG_TEXTAREA) { /* Text areas, based on the number of rows */ BBox box; int h; int r; int lh, lb; h = fm_find_token_font(b, tp, 0); e = fm_font_box(h, &box); if (e) return e; r = tp->rows; if (r < 2) r = 2; form_get_linesize(&box, &lh, &lb); bot += lh * (r - 1) + 8; /* + 8 for the border; r - 1 as bot is already below the first text line, so drop it by (rows - 1) more */ top += 8; } else if (tp->tagno == TAG_SELECT) { /* Selection lists - a pop-up menu icon */ int h; if (read_sprite_size("fgright", NULL, &h)) h = 44; bot += 8; top += 8; if (top + bot < h) top += h - top - bot; } else if (tp->tagno == TAG_INPUT) { /* General input types */ switch(HtmlINPUTtype(tp)) { case inputtype_TEXT: /* No break - same as PASSWORD */ case inputtype_PASSWORD: { bot += 8; top += 8; } break; case inputtype_SUBMIT: /* No break - same as RESET */ case inputtype_BUTTON: /* Again, no break */ case inputtype_RESET: { bot += 8; top += 12; } break; } } /* Round up top and bot to a multiple of 4 */ if (top & 3) top += 4 - (top & 3); if (bot & 3) bot += 4 - (bot & 3); /* 'top' will correspond to the line height above the font baseline. */ /* If it is greater than the current value, extend the top of the */ /* line by the difference. Move the line y coordinate down to make */ /* room for the extra height. */ if (top > (d->ldata[line].h - d->ldata[line].b)) { int diff; diff = top - (d->ldata[line].h - d->ldata[line].b); d->ldata[line].h += diff; d->ldata[line].y -= diff; } /* Similarly, if bot is greater than the offset of the baseline from */ /* the bottom of the line, account for the extra offset. */ if (bot > d->ldata[line].b) { int diff; diff = bot - d->ldata[line].b; d->ldata[line].h += diff; /* (Since 'h' is the total height above and below the baseline) */ d->ldata[line].b += diff; d->ldata[line].y -= diff; } return NULL; } /*************************************************/ /* reformat_check_visited() */ /* */ /* Looks at a given token and compares it to the */ /* global history, setting a bit in the flags */ /* word if a link it represents has been visited */ /* before. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the token; */ /* */ /* Pointer to the token. */ /*************************************************/ static void reformat_check_visited(browser_data * b, HStream * token) { if (ISLINK(token) && history_visited(token->anchor)) token->flags |= HFlags_LinkVisited; } /*************************************************/ /* reformat_check_extent() */ /* */ /* Sets the browser window vertical extent to */ /* match the page, by looking at the last line */ /* in the line list. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* holding details of the window to */ /* alter. */ /*************************************************/ _kernel_oserror * reformat_check_extent(browser_data * b) { /* Nothing to do for small fetch windows */ if (b->small_fetch) return NULL; /* Otherwise exit through the extent setting routines */ return reformat_set_extent(b, -reformat_return_extent(b, NULL)); } /*************************************************/ /* reformat_return_extent() */ /* */ /* Returns the height of a page in OS units, */ /* according to its current formatted state. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* holding details of the page; */ /* */ /* Pointer to a reformat_cell struct */ /* holding redraw information. */ /*************************************************/ int reformat_return_extent(browser_data * b, reformat_cell * cell) { int extent = 0; if (!cell) cell = b->cell; if (cell->ldata) extent = -cell->ldata[cell->nlines - 1].y; return extent; } /*************************************************/ /* reformat_set_extent() */ /* */ /* Sets the browser window vertical extent. The */ /* extent can only ever grow. */ /* */ /* Parameters: A pointer to a browser_data */ /* structure containing details of */ /* the window to alter; */ /* */ /* A new vertical extent in OS units */ /* - this is a *negative* number, */ /* expressed as a downward offset */ /* from the top of the window. */ /*************************************************/ _kernel_oserror * reformat_set_extent(browser_data * b, int y_extent) { WimpGetWindowStateBlock s; BBox new_extent; int hbot, width; #ifdef TRACE if (tl & (1u<<8)) Printf("\nreformat_set_extent: Called, -y_extent = %d\n",-y_extent); #endif /* For windows with frames, don't want to mess around with the extent */ if (b->nchildren) return NULL; s.window_handle = b->window_handle; RetError(wimp_get_window_state(&s)); /* Y extent is set to the requested value plus an amount for the toolbars */ /* and an extra amount for aesthetics. */ if (!controls.swap_bars) hbot = toolbars_status_height(b); else hbot = toolbars_button_height(b) + toolbars_url_height(b); y_extent -= (hbot + b->leading); /* Ensure that the extent is at least as great as the minimum height */ if ((-y_extent) < b->min_height) y_extent = -(b->min_height); /* For the height, don't want to resize below the current */ /* visible height. Things are a bit messy due to the */ /* negative signs on extent, etc. */ if ((-y_extent) < (s.visible_area.ymax - s.visible_area.ymin)) y_extent = -(s.visible_area.ymax - s.visible_area.ymin); new_extent.ymax = 0; new_extent.ymin = y_extent; /* x extent must only match or be larger than the visible */ /* area, never smaller. */ width = s.visible_area.xmax - s.visible_area.xmin; if (b->display_width < width) b->display_width = width; if (b->display_extent < b->display_width) b->display_extent = b->display_width; new_extent.xmin = 0; new_extent.xmax = b->display_extent; /* Update height, too */ { int h1, h2; h1 = toolbars_button_height(b) + toolbars_url_height(b); h2 = toolbars_status_height(b); if (h1) h1 += wimpt_dy(); if (h2) h2 += wimpt_dy(); b->display_height = s.visible_area.ymax - s.visible_area.ymin - h1 - h2; } /* Set the extent */ RetError(window_set_extent(0,b->self_id,&new_extent)); /* Can't set extent so visible area is now outside the work area; */ /* call wimp_open_window to make sure scroll positions are OK. */ { ObjectId po; ComponentId pc; RetError(toolbox_get_parent(0, b->self_id, &po, &pc)); return toolbox_show_object(0, b->self_id, 1, (void *) &s.visible_area, po, pc); } } /*************************************************/ /* reformat_add_line() */ /* */ /* Adds a line to the array of line structures. */ /* The contents are NOT initialised. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the page; */ /* */ /* Pointer to the reformat_cell */ /* structure to add the chunks to. */ /*************************************************/ static _kernel_oserror * reformat_add_line(browser_data * b, reformat_cell * cell) { _kernel_oserror * e; #ifdef TRACE if (tl & (1u<<8)) Printf("reformat_add_line: Called with cell %p, cell->nlines = %d\n",cell,cell->nlines); #endif /* Allocate memory for the new number of lines */ #ifdef TRACE if (tl & (1u<<12)) Printf("reformat_add_line: Chunk CK_LINE set to %d\n",(cell->nlines + 1) * sizeof(reformat_line)); #endif e = memory_set_chunk_size(b, cell, !cell->table ? CK_LINE : CK_LINV, /* Variable granularity allocation for table cells */ (cell->nlines + 1) * sizeof(reformat_line)); if (e) return e; /* Increment the line counter in the browser_data structure */ cell->ldata[cell->nlines].x = cell->ldata[cell->nlines].y = 0; cell->nlines++; // memset( - something! - ,0,sizeof(reformat_line)); #ifdef TRACE if (tl & (1u<<8)) Printf("reformat_add_line: Successful with cell->nlines = %d\n",cell->nlines); #endif return NULL; } /*************************************************/ /* reformat_add_line_chunk() */ /* */ /* Adds a line chunk to the array of line chunks */ /* associated with a particular line structure. */ /* This is complicated slightly by the variable */ /* length of the arrays of line chunks. The new */ /* chunk's contents are NOT initialised. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* relevant to the page; */ /* */ /* Pointer to the reformat_cell */ /* structure to add the chunks to. */ /*************************************************/ static _kernel_oserror * reformat_add_line_chunk(browser_data * b, reformat_cell * cell) { int size, cline = cell->nlines - 1; _kernel_oserror * e; #ifdef TRACE if (tl & (1u<<8)) Printf("reformat_add_line_chunk: Called\n"); #endif /* The function can only ever add a chunk to the end of the list. */ /* So the chunk must belong to the last line in the list, and */ /* if it is the first chunk for the line structure that line */ /* structure's pointer to the chunks will be filled in. The chunk */ /* counter for that line is incremented. */ #ifdef TRACE /* If there are no lines, we can't proceed - something has gone wrong */ if (!cell->nlines) { erb.errnum = 0; strcpy(erb.errmess, "Serious internal error - There are no line structures defined in reformat_add_line_chunk; must exit immediately."); show_error(&erb); } #endif /* If there is no chunk data already, this is the first */ /* chunk allocation so the block size we want to move to */ /* is easy to work out. */ if (!cell->cdata) size = sizeof(reformat_line_chunk); else { /* We want to find the current size of data and put it into size. To do this, */ /* get the last line's offset to the chunks and add the number of chunks it */ /* claims to have multiplied by the size of one chunk. The complication is */ /* that the last line(s) may not have any chunks, so we need to go back to */ /* a line that does, if that is the case. */ while (!cell->ldata[cline].n && cline >= 0) cline --; #ifdef TRACE if (cline < 0) { erb.errnum = 0; strcpy(erb.errmess, "Serious internal error - No lines have associated chunks defined in reformat_add_line_chunk; must exit immediately."); show_error(&erb); } #endif size = (cell->ldata[cline].chunks + cell->ldata[cline].n + 1) * sizeof(reformat_line_chunk); } // if (cell->cdata) Printf("!! Consistency check - Current block size for chunks = %p\n",(void *) flex_size((flex_ptr) &cell->cdata)); // else Printf("!! Consistency check - Current block size for chunks = none allocated\n"); // Printf("!! New block size to be allocated = %p\n",(void *) size); /* Now allocate the memory */ #ifdef TRACE if (tl & (1u<<12)) Printf("reformat_add_line_chunk: Chunk CK_LDAT set to %d\n",size); #endif e = memory_set_chunk_size(b, cell, !cell->table ? CK_LDAT : CK_LDAV, /* Variable granularity allocation for table cells */ size); if (e) return e; /* Update the chunk counter on the last line and the base chunk */ /* array number, if it wasn't already set (when the line is */ /* created, the chunks field is filled with -1. When the first */ /* chunk is added for the line the chunks field is filled in, */ /* and it is left alone subsequently - which is exactly what we */ /* want, as the chunks field holds the base array index of the */ /* chunks for the line, not the last chunk array index. */ cline = cell->nlines - 1; cell->ldata[cline].n++; if (cell->ldata[cline].chunks < 0) cell->ldata[cline].chunks = (int) size / sizeof(reformat_line_chunk) - 1; /* Success */ #ifdef TRACE if (tl & (1u<<8)) Printf("reformat_add_line_chunk: Successful\n"); #endif return NULL; } /*************************************************/ /* reformat_reformatter() */ /* */ /* The actual working end of the reformat */ /* routines - takes the list of HStream structs */ /* (or tokens) and turns them into reformat_line */ /* structures for the redraw routines etc. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* containing all the info on the */ /* fetch for which reformatting is */ /* to take place. */ /*************************************************/ void reformat_reformatter(browser_data * b) { reformat_reformatter_r(0, b, b->cell, b->stream); /* The browser may have the input focus inside a forms */ /* item, so after each loop of the reformatter ask the */ /* forms library to ensure the caret position is OK. */ form_caret_may_need_moving(b); } /*************************************************/ /* reformat_reformatter_r() */ /* */ /* Recursive back-end to reformat_reformatter. */ /* */ /* Parameters: Flags (see Reformat.h); */ /* */ /* Pointer to a browser_data struct */ /* relevant to the reformat session; */ /* */ /* Pointer to the reformat_cell */ /* struct that is to be acted upon; */ /* */ /* Pointer to the first token in the */ /* list of HStreams to use if there */ /* is no evidence of a previous */ /* incomplete reformat session in */ /* the browser_data structure. */ /* */ /* Returns: Width of the widest line that was */ /* generated, in millipoints. This */ /* includes the left hand margin / */ /* gap; however, if there is no */ /* stream to reformat, 0 is returned */ /* rather than purely the left hand */ /* margin plus zero to indicate this */ /* special case to the caller. */ /* Ideally the caller should never */ /* invoke the reformatter without a */ /* stream though, it's not very */ /* efficient... */ /*************************************************/ static int reformat_reformatter_r(unsigned int flags, browser_data * b, reformat_cell * d, HStream * streambase) { int lnCurr = -1; int cnCurr = -1; int cnLast = 0; _kernel_oserror * e = NULL; HStream * tpCurr = NULL, * tpLast = NULL; HStream * tnCurr = NULL, * tnLast = NULL; int bottom = 0, extent = 0; int newlines = 0, newline = 0; int newchunks = 0, newchunk = 0; int done = 0; int linewidth = 0; int widest = 0; int offset = 0; reformat_width_data wd; int displaywidth, left_margin, right_margin; int toplevel, doall, fromstart, noalloc; int token_has_text; /* Read various flags */ toplevel = !(flags & Reformatter_KeepGoingToEnd); /* (This may get more complex, hence 'doall' is not just '!toplevel') */ doall = (flags & Reformatter_KeepGoingToEnd); fromstart = (flags & Reformatter_FromStreamStart); if (doall) noalloc = (flags & Reformatter_Virtual); else noalloc = 0; /* If there is more than one line, find the y coordinate */ /* of the last one and put it into 'bottom' */ if (d->nlines > 2) bottom = d->ldata[d->nlines - 2].y; else if (d->nlines == 1) bottom = d->ldata[0].y; /* This y coordinate is for the last line, so it also gives the vertical extent */ extent = bottom; /* Work out the current width to format to, in millipoints. */ /* The width is the size of the visible area, but not less */ /* than min_width. */ if (flags & Reformatter_FindingWidest) displaywidth = Reformat_AsWideAsPossible_MP; else if (flags & Reformatter_FindingSmallest) displaywidth = 1600; else convert_to_points(redraw_display_width(b, d), &displaywidth); if (d->nlines && !noalloc) { /* If there are some lines, set up the local variables */ /* according to the last line's contents. */ int i; /* Set lnCurr to the last line, and set cnLast to the first chunk */ /* number on that line. Note that if the line has no chunks, the */ /* routine steps back one. The function will exit if it turns out */ /* that the first line has no chunks (after all, there's nothing */ /* to format if this is the case!) */ lnCurr = d->nlines - 1; while (!d->ldata[lnCurr].n && lnCurr >= 0) lnCurr --; if (lnCurr < 0) return 0; cnLast = d->ldata[lnCurr].chunks; /* Get the address of the HStream associated with the */ /* first chunk of the last line into tpLast. */ tpLast = d->cdata[cnLast].t; // /* Since the current tag is be at the start of this line, */ // /* strip out any preceeding spaces in it (the function */ // /* deals with the special case of preformatted text, etc.). */ // // reformat_strip_prespace(b, cnCurr, tpCurr); /* Set 'linewidth' to the indentation requried at this line, */ /* and add the widths of the line chunks to this. */ linewidth = redraw_left_gap(b, d, tpLast); for ( i = 0; i < d->ldata[lnCurr].n; i++, cnLast++ ) linewidth += d->cdata[cnLast].w; if (linewidth > widest) widest = linewidth; /* If the current line has at least 1 chunk... */ if (d->ldata[lnCurr].n) { /* Set cnLast to now hold the number of the last chunk for the */ /* last line, and tpCurr and tpLast to hold the address of the */ /* token associated with the chunk. */ cnLast = d->ldata[lnCurr].n + d->ldata[lnCurr].chunks - 1; tnCurr = tnLast = tpCurr = tpLast = d->cdata[cnLast].t; /* Set 'offset' to the offset into the token associated with the last */ /* chunk in the last line, plus the amount of data to take from that */ /* token - i.e. it points to the first unused byte in that token, */ /* as far as the last chunk of the last line is concerned. */ offset = d->cdata[cnLast].o + d->cdata[cnLast].l; /* If the number of bytes into the token is <=0, then find out the */ /* total size of data normally associated with the token (e.g. the */ /* string length of any text it points to) and put this in offset. */ if (d->cdata[cnLast].l <= 0) { /* In this case associated data will *almost* always be zero length, */ /* in terms of text, but there are some rare circumstances when it */ /* won't be. Hence the reformat_datasize call. */ offset = reformat_datasize(tpLast); } /* If the tag represents text, points to a text string, and that text */ /* string terminates in a new line character, then need to start on a */ /* new line. */ if ( offset > 0 && reformat_istext(tpCurr) && tpCurr->text && tpCurr->text[offset - 1] == '\n' ) newline = 1; } else newchunk = 1; /* Otherwise, flag that a chunk needs adding */ } /* There are no line structures currently present; we aren't, then, */ /* going to add to an existing line and need to flag that a new */ /* line must be created before chunks can be added. */ else newline = 1; /* Loop round the reformatter whilst there is no error, the reformat */ /* hasn't completed (we are still reformatting and the 'done' flag */ /* isn't set), and the number of new lines dealt with is less than */ /* 10 (don't want to do too much in one go, or the Desktop will */ /* feel very jerky) or we haven't flagged a new line is needed - the */ /* reformat will only keep going for a certain number of lines, but */ /* won't stop mid-line. */ while ( !e && !done && ( doall || newlines < 10 || !newline ) && reformat_formatting(b) ) { /* tpCurr points the the token we're currently dealing with. If */ /* this is NULL, or the offset into that token seems to point */ /* past its associated data, we've finished with the token. So */ /* move onto another, or if there are no more, set 'done' to 1. */ if (tpCurr) token_has_text = tpCurr->text ? reformat_istext(tpCurr) : 0; else token_has_text = 0; // Used to be 'if (!tpCurr || offset >= reformat_datasize(tpCurr))' // Really should check that the below never differs from it. if (!token_has_text || offset < 0 || !tpCurr->text[offset]) { /* Record the last token that was dealt with in the browser_data structure */ if (!fromstart) b->last_token = tnLast; /* Advance to the next token after that last one */ if (tpLast) tnCurr = tnLast->next; else tnCurr = streambase; /* Proceed if we haven't just run out of tokens */ if (tnCurr && (tnCurr->flags & HFlags_DealtWithToken)) { tpCurr = tnCurr; /* If this token isn't of any use to the reformatter (it */ /* might correspond to header information rather than */ /* body text, say), keep getting new tokens until they */ /* are useful or we run out. If we run out, set 'done' */ /* again to show that the tokens have all been dealt */ /* with. */ while ( tpCurr && (tpCurr->flags & HFlags_DealtWithToken) && reformat_useless_token(tpCurr)) { if (!fromstart) b->last_token = tpCurr; /* Update the record of the last token dealt with */ tnCurr = tpCurr = tpCurr->next; /* Get the pointer to the new token */ } /* If we've ended up off the end of the token list (tpCurr is */ /* NULL) or on a token that the fetcher hasn't dealt with yet */ /* (the HFlags_DealtWithToken bit in the flags word is unset) */ /* then signal that the reformatter should exit by setting */ /* 'done' to 1. */ if (!tpCurr) done = 1; else { if (!(tpCurr->flags & HFlags_DealtWithToken)) { b->last_token = tpCurr->prev; tpCurr = NULL; tnCurr = NULL; done = 1; } } /* Offset is set to 0 to show this is a new token, and we haven't */ /* dealt with any of the data in it yet. */ offset = 0; } /* The current token number was greater than the number of tokens - */ /* we've run out of tokens, so flag that this in 'done'. */ else done = 1; } /* Continue with the reformat if it hasn't been flagged as finished through 'done'. */ if (!done) { /* The left hand margin - zero for horizontal rules, which can span the whole */ /* display (their actual visible extent is determined by the redraw routine); */ /* else equal to the result of redraw_left_gap in Redraw.c (converted to points). */ left_margin = redraw_left_gap(b, d, tpCurr); /* Can't have a margin greater than display width, but this consideration */ /* only applies if we're not finding the smallest the stream can be. */ if (!(flags & Reformatter_FindingSmallest) && left_margin > displaywidth) left_margin = 0; /* The right hand gap value */ right_margin = redraw_right_gap(b, d, tpCurr); /* Can't have margins greater than the display width */ if (left_margin + right_margin > displaywidth) right_margin = 0; // /* If the text is not preformatted and the line width has gone over */ // /* over the available display width, flag that a new line is needed */ // if (!newline && !(tpCurr->style & PRE) && linewidth > displaywidth - right_margin) newline = 1; /* If the difference between the current and last tags say we should */ /* put in a line break, flag this in newline. */ if (!newline && reformat_newline(tpCurr,tpLast,offset)) newline = 1; /* Right, if newline is set, create a new line. */ if (newline) { int y = 0; newline = 0; newchunk = 1; newlines ++; if (!noalloc) { /* Set y to hold the y coordinate the line is to be placed */ /* at. This will either be determined by the previous */ /* line's y coordinate or if there are no lines, the */ /* toolbars and aesthetic lead-in considerations. The line */ /* at this stage is of zero height; as it's height grows, */ /* the y value worked out here has the height subtracted */ /* from it to get the actual line position. */ if (lnCurr >= 0) y = d->ldata[lnCurr].y; /* (The 4 is to allow for the window frame of the toolbars) */ else y = toplevel ? reformat_y_offset(b) : 0; /* If there aren't any new chunks, the bottom line hasn't */ /* changed so set bottom to y; this stops the unchanged */ /* line being redrawn. */ if (!newchunks) bottom = y; /* Add the new line structure */ e = reformat_add_line(b, d); } else e = NULL; /* If the addition was successful, fill in various details */ if (!e) { tpCurr = tnCurr; tpLast = tnLast; if (!noalloc) { /* Advance the current line number in lnCurr to this new line */ lnCurr = d->nlines - 1; /* Set the line height as zero, and baseline offset as zero. We */ /* have no contents with which to do anything different yet. */ /* Changing 'b' will move the font baseline relative to the */ /* line base, and force the line height up. 'h' is the line */ /* height total including the baseline offset. */ /* */ /* When reformat_check_height is called, it will go through */ /* working out values called 'top' and 'bot'. The first is the */ /* amount by which the line height above the baseline (i.e. */ /* ...[...].h - ...[...].b) is to be extended; the second is */ /* the amount by which the space below the baseline is to be */ /* extended. These can thus be played about with to do vertical */ /* alignment on images and so-forth. */ d->ldata[lnCurr].h = 0; d->ldata[lnCurr].y = y; d->ldata[lnCurr].b = 0; /* The line has no chunks associated with it yet. */ d->ldata[lnCurr].n = 0; d->ldata[lnCurr].chunks = -1; } /* In light of this new line, set linewidth back to just the left margin, */ /* as there are no previous chunks to consider in working out chunk widths */ /* in the add chunk code below. */ linewidth = left_margin; if (linewidth > widest) widest = linewidth; } /* End of newline check; if newline != 0, a new line is added to the line structure array. */ } /* If there's no error, continue with the reformat procedure. */ if (!e) { int available; // /* If the image has a known width and height, the reformatter is */ // /* about to deal with it - so the image library can mark it as */ // /* redrawable now (otherwise, for delayed reformats, some images */ // /* which were cross referenced and did not have reformat sessions */ // /* explicitly started for them, may get stuck in a */ // /* 'non-redrawable' state). */ // /* */ // /* Important to call this *before* trying to find the item height */ // /* or width, as the image size routines will lie about the real */ // /* size if they think the item is not redrawable because it has */ // /* not be reformatted yet - chicken and egg... */ // // // Mmm, nice. And robust too. Or Your Money Back (TM). // // // // Really *must* do something about this (but probably won't...) // // if ( // (tpCurr->style & IMG) || // ( // tpCurr->tagno == TAG_INPUT && // HtmlINPUTtype(tpCurr) == inputtype_IMAGE // ) // ) // image_token_check_redrawable(b, tpCurr); /* If the image has a known width and height, the reformatter */ /* has dealt with it; so both mark it as redrawable, and lock */ /* its size down. */ image_token_reformatted(b, tpCurr); /* Fill in the reformat_width_data structure */ wd.b = b; wd.d = d; wd.tp = tpCurr; wd.data = reformat_istext(tpCurr) ? tpCurr->text : NULL; wd.offset = offset; /* Tables, when specifying percentage widths, take up the whole */ /* page - e.g. 100% when in an indented list will result in the */ /* right hand edge of the table being off the page. Well, this */ /* is what MSIE / NN do anyway, despite it being stupid IMHO. */ if (tpCurr->tagno == TAG_TABLE && (TABLE_HAS_WIDTH((table_stream *) tpCurr))) { available = displaywidth - redraw_left_margin(b, d) - right_margin; } else { available = displaywidth - linewidth - right_margin; } /* The maximum width is 'very large' for preformatted text (effectively limitless). */ /* Similarly, for any text items in NOBR, the width in effect is without a limit. */ /* For other tokens, the left hand edge is at an indent equal to the left hand */ /* margin plus the summed widths of preceeding chunks (this is kept in 'linewidth'), */ /* so the width is the available display width minus this left hand value. */ wd.maxwid = ( ( (tpCurr->style & PRE) && reformat_istext(tpCurr) ) #ifdef SUPPORT_NOBR || ( (tpCurr->style & PCDATA) && (tpCurr->style & NOBR) ) #endif ) ? Reformat_AsWideAsPossible_MP : available; if (wd.maxwid < 0) wd.maxwid = 0; /* Set the (returned) bytes and width fields to zero initially */ wd.bytes = wd.width = 0; /* Find out the width */ e = reformat_token_width(&wd, flags); /* Adjust the line height for tables based on */ /* the data from the above call */ if (tpCurr->tagno == TAG_TABLE) { if (!noalloc) { /* Take the height in millipoints (overloaded into wd.bytes) and use */ /* this as the line height. */ convert_to_os(wd.bytes, &d->ldata[lnCurr].h); /* Correct the line's y coordinate given the above height. */ d->ldata[lnCurr].y -= d->ldata[lnCurr].h; /* P tags before tables get attached to the TABLE HStream itself. */ if ((tpCurr->style & P) && (lnCurr > 0)) { int texttop, textbot; e = reformat_text_line_height(b, NULL, &texttop, &textbot); if (!e) d->ldata[lnCurr].y -= texttop + textbot; } } wd.bytes = 0; } /* If the chunk width'd and defined by the above call ends in a newline character, */ /* flag a line break is needed through newline = 1. */ else if (reformat_istext(tpCurr) && wd.bytes && wd.data[wd.offset + wd.bytes - 1] == '\n') newline = 1; } /* A new chunk is forced by setting 'newchunk' if the previous item and this one */ /* should not be split up. For example, an LI bullet point and the text that */ /* follows it (if any) should appear on the same line. */ if (!e && !newchunk) { if ( !offset && tpCurr->prev && !(tpCurr->style & LI) && (tpCurr->prev->style & LI) ) newchunk = 1; } /* Proceed if there is no error, to add a chunk. If the current line has no chunks, */ /* then one will always be added. If there are already chunks present, another is */ /* added only if it will fit in the display (wd.width is less than wd.maxwid), or */ /* if the text is preformatted (in which case you keep adding chunks until there's */ /* a line break in the source). */ if ( !e && ( newchunk || /* Will be set if current line has no chunks yet */ wd.width <= wd.maxwid || (tpCurr->style & PRE) ) ) { newchunk = 0; newchunks ++; if (!noalloc) e = reformat_add_line_chunk(b, d); else e = NULL; /* Proceed if the above didn't return an error */ if (!e) { tpCurr = tnCurr; tpLast = tnLast; if (!noalloc) { /* Set cnCurr to the array index of the last (i.e new) chunk; */ /* we know that if a new chunk has been added the last line */ /* must have at least the one chunk now, so no need for all */ /* the checking for various cases of lines not having chunks */ /* that has gone on elsewhere. */ cnCurr = d->ldata[d->nlines - 1].chunks + d->ldata[d->nlines - 1].n - 1; /* Fill in the new line chunk */ d->cdata[cnCurr].t = tpCurr; d->cdata[cnCurr].w = wd.width; d->cdata[cnCurr].o = offset; d->cdata[cnCurr].l = wd.bytes; /* If this holds an image, need to invalidate the x and y position */ /* information that the redraw routines set up, as it may have */ /* moved. The new position will be set when the reformatted */ /* region is next redrawn. */ if ( (tpCurr->style & IMG) || ( tpCurr->tagno == TAG_INPUT && HtmlINPUTtype(tpCurr) == inputtype_IMAGE ) ) image_set_token_image_position(b, tpCurr, -1, -1); #ifdef TRACE if ((tl & (1u<<20)) && tpCurr->tagno == TAG_TABLE) Printf("reformat_reformatter_r: Added a table\n"); #endif // if (d->ldata[d->nlines - 1].n == 1) // { // /* Since the current chunk will be at the start of a new line, */ // /* strip out any preceeding spaces in it (the function deals */ // /* with the special case of preformatted text, etc.). */ // // reformat_strip_prespace(b, cnCurr, tpCurr); // } // if (d->ldata[d->nlines - 1].n == 1) // { // if (tpCurr->text && !(tpCurr->style & PRE)) // { // while (*(tpCurr->text + d->cdata[cnCurr].o) == ' ' && d->cdata[cnCurr].l) d->cdata[cnCurr].o++, d->cdata[cnCurr].l--, offset++; // } // } /* Set the Visited flag if this token is a link and is in the global History */ reformat_check_visited(b, tpCurr); /* Ensure the line's height details are updated in light of the new chunk */ e = reformat_check_height(toplevel, b, d, lnCurr, tpCurr, tpLast, offset); } else e = NULL; if (!e) { int w; // if (!noalloc) // { // /* If a form had input focus, hopefully the forms library can now find */ // /* the caret. This call will check if the token we're dealing with is */ // /* the currently editing one, and if so try to find its position and */ // /* move the caret as necessary. We do this only if 'noalloc' is unset, */ // /* as if we're a 'virtual reformat loop', the token won't necessarily */ // /* have presence in any lines anyway. It might not be found if noalloc */ // /* is unset anyway, but at least we increase the chances... */ // // form_caret_may_be_moving(b, tnCurr); // } /* Advance through the token data */ if (wd.bytes <= 0) offset = -1; /* Flag we need a new token */ else { int has_text; offset += wd.bytes; /* Need a new line if we've not run off the end of the text - */ /* there must have been a split point within it. This is not */ /* true, though, if NOBR is set. */ if (tpCurr) has_text = tpCurr->text ? reformat_istext(tpCurr) : 0; else has_text = 0; #ifdef SUPPORT_NOBR if (has_text && tpCurr->text[offset] && !(tpCurr->style & NOBR)) newline = 1; #else if (has_text && tpCurr->text[offset]) newline = 1; #endif } /* Make the last poken pointer is now updated */ tnLast = tnCurr; tpLast = tpCurr; /* Add the chunk's width to the line width total */ linewidth += wd.width; if (linewidth > widest) widest = linewidth; /* Convert the width to OS units and compare to the minimum width for */ /* the browser so far. If the width as gone up, i.e. there is an */ /* enforced minimum for whatever reason (preformatted text, say) then */ /* update the minimum width field appropriately. */ convert_to_os(linewidth, &w); if (linewidth >= displaywidth - right_margin) { /* ...But shouldn't flag a new line if NOBR is set... */ #ifdef SUPPORT_NOBR if (!(tpCurr->style & 0*NOBR)) newline = 1; #else newline = 1; #endif if (!d->table && w > b->display_extent) b->display_extent = w; } } } } else newline = 1; } } /* If there has been an error, stop the reformat and report it */ if (e) { reformat_stop(b); show_error_ret(e); /* This call returns to this point rather than jumping to the poll loop */ } else { browser_data * ancestor = utils_ancestor(b); /* For keyboard control, try to find something to select. */ /* Browser windows that have children can't have visible */ /* tokens, so don't bother for them, and for anything */ /* with a selected item, check that it has't acquired */ /* children over the reformat - if it has, move the */ /* selection out of there. */ if (choices.keyboard_ctrl) { /* Clear the selection if the owner has acquired children */ if (ancestor->selected) { browser_data * owner = ancestor->selected_owner; if (owner && owner->nchildren) browser_clear_selection(owner, 0); } /* If there is no selection (whether this is due to the */ /* above code or not), and the current browser has no */ /* children, try to select something in it. */ if (!ancestor->selected && !b->nchildren) { ancestor->selected = browser_find_first_selectable(b, NULL, 0); if (ancestor->selected) ancestor->selected_owner = b; } } } /* Make sure the window is the right size and ensure */ /* that the altered regions are redrawn, unless */ /* we are printing (so the reformat doesn't actually */ /* correspond to any real window). */ if (toplevel && !printing) { reformat_check_extent(b); browser_update_bottom(b, bottom + 4); } /* Return the width of the widest line generated in this session, in millipoints */ return widest; } /*************************************************/ /* reformat_format_cell() */ /* */ /* Reformats a specific table cell to a given */ /* width. */ /* */ /* Parameters: 1 for a top level call, 0 if */ /* being called as part of a nested */ /* table parse; */ /* */ /* Pointer to a browser_data struct */ /* relevant to the table; */ /* */ /* Pointer to the first HStream */ /* structure in the stream that will */ /* be formatted into the cell; */ /* */ /* Pointer to the table_stream */ /* struct representing the table; */ /* */ /* Pointer to the table's array of */ /* reformat_cell structures; */ /* */ /* Allowed cell (column) width, in */ /* millipoints; */ /* */ /* Row number of the cell; */ /* */ /* Column number of the cell. */ /* */ /* Returns: Actual final cell width in */ /* millipoints. */ /*************************************************/ int reformat_format_cell(int toplevel, browser_data * b, HStream * streambase, table_stream * table, reformat_cell * cellarray, int ColWidth, int Row, int Column) { int dheight; int cellindex = Row * table->ColSpan + Column; reformat_cell * c; /* Can't do anything if the cell index is out of range */ if (cellindex >= table->RowSpan * table->ColSpan) return 1600; else c = &cellarray[cellindex]; #ifdef TRACE if (tl & (1u<<20)) { Printf("tables_width_cell: %p %d %d %d %d\n",streambase,ColWidth,table->ColSpan,Row,Column); Printf("tables_width_cell in: %d\n", ColWidth / 400); } #endif /* Format the cell to the specified width. If 'toplevel' is 0, this is */ /* being called as part of a format for a parent table, so don't */ /* generate lines (flag Virtual in the reformatter). Otherwise, allow */ /* line generation (don't flag Virtual). */ c->width = c->cellwidth = ColWidth; if (c->width <= 400) c->width = 400; /* Give at least 1 OS unit to avoid possible problems in the reformatter */ reformat_reformatter_r(Reformatter_KeepGoingToEnd | Reformatter_FromStreamStart | (toplevel ? Reformatter_Virtual : 0), b, c, streambase); /* Set the height to the line list extent, rounded down to an */ /* integer number of pixels. */ dheight = reformat_return_extent(b, c) & ~(wimpt_dy() - 1); /* Convert to millipoints for storing in the cell */ convert_to_points(dheight, &c->height); #ifdef TRACE if (tl & (1u<<20)) { Printf("width found is %d\n", c->width); Printf("height found is %d\n",c->height); } #endif return c->width; } /*************************************************/ /* reformat_find_cell_limits() */ /* */ /* Works out the narrowest and widest widths a */ /* given table cell could be, in millipoints. */ /* */ /* Parameters: 1 for a top level call, 0 if */ /* being called as part of a nested */ /* table parse; */ /* */ /* Pointer to a browser_data struct */ /* relevant to the table; */ /* */ /* Pointer to the first HStream */ /* structure in the stream that the */ /* cell is to contain; */ /* */ /* Pointer to the table_stream */ /* struct representing the table; */ /* */ /* Pointer to the table's array of */ /* reformat_cell structures; */ /* */ /* Row number of the cell; */ /* */ /* Column number of the cell; */ /* */ /* Pointer to an int, in which the */ /* minimum width is returned; */ /* */ /* Pointer to an int, in which the */ /* maximum width is returned; */ /*************************************************/ void reformat_find_cell_limits(int toplevel, browser_data * b, HStream * streambase, table_stream * table, reformat_cell * cellarray, int Row, int Column, int * retmin, int * retmax) { int maxwidth = 0; int minwidth = 0; int cellindex = Row * table->ColSpan + Column; reformat_cell * c; /* Can't find the limits of something outside the cell range */ /* of the table... */ if (cellindex >= table->RowSpan * table->ColSpan) { if (retmin) *retmin = 1600; if (retmax) *retmax = 1600; return; } else c = &cellarray[cellindex]; /* If we've already got values for this cell, return them now */ if (c->minwid >= 0 && c->maxwid >= 0) { if (retmin) *retmin = c->minwid; if (retmax) *retmax = c->maxwid; return; } if (streambase) { /* Find the maximum width used by the cell; first, reformat */ /* to a 'large width' (effectively, no line breaks). */ maxwidth = reformat_reformatter_r(Reformatter_KeepGoingToEnd | Reformatter_FindingWidest | Reformatter_FromStreamStart | Reformatter_Virtual, b, c, streambase); #ifdef TRACE if (c->nlines || c->ldata || c->cdata) { erb.errnum = Utils_Error_Custom_Normal; strcpy(erb.errmess,"Line or chunk data allocated inside reformat_find_cell_limits for maxwidth check"); show_error_ret(&erb); } #endif /* Find the minimum width */ minwidth = reformat_reformatter_r(Reformatter_KeepGoingToEnd | Reformatter_FindingSmallest | Reformatter_FromStreamStart | Reformatter_Virtual, b, c, streambase); #ifdef TRACE if (c->nlines || c->ldata || c->cdata) { erb.errnum = Utils_Error_Custom_Normal; strcpy(erb.errmess,"Line or chunk data allocated inside reformat_find_cell_limits for minwidth check"); show_error_ret(&erb); } #endif } else minwidth = maxwidth = redraw_left_margin(b, c); /* Account for cellpadding */ { int cellpadmp = table->cellpadding * 2; /* 1 'web pixel' = 2 OS units, but only for right hand edge - redraw_left_margin takes care of the rest */ convert_to_points(cellpadmp, &cellpadmp); minwidth += cellpadmp; maxwidth += cellpadmp; } /* Store the values */ c->minwid = minwidth; c->maxwid = maxwidth; if (retmin) *retmin = minwidth; if (retmax) *retmax = maxwidth; /* Finished */ return; } /*************************************************/ /* reformat_change_text() */ /* */ /* Used to alter text in a tag, to provide (for */ /* example) smart quotes handling. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* with information on the fetch */ /* Pointer to a token to alter. */ /* */ /* Assumes: The browser_data struct may not */ /* be NULL, but the token pointer */ /* can be and the token does not */ /* have to contain text. */ /*************************************************/ void reformat_change_text(browser_data * b, HStream * tp) { // Note that this, then, does nothing right now - fix this function up some time! return; if (tp && tp->text && !(tp->style & TT) && (reformat_istext(tp))) { char * curr = tp->text; while (*curr) { if (*curr == '`') *curr = 148; /* Always make this quote an opening quote */ if (*curr == '\'') /* Dumb single quote */ { if (b->last_char == ' ' || b->last_char == '(' || b->last_char == 148 /* Opening double quote */ || b->last_char == '\"') *curr = 144; /* Opening single quote */ else *curr = 145; /* Closing single quote */ } else if (*curr == '\"') /* Dumb double quote */ { if (b->last_char == ' ' || b->last_char == '(' || b->last_char == 144 /* Opening single quote */ || b->last_char == '\'' || b->last_char == '`') *curr = 148; /* Opening double quote */ else *curr = 149; /* Closing double quote */ } else if (*curr == '-' && b->last_char == ' ') *curr = 151; /* 'en' dash */ // else if (*curr == '-' && b->last_char == 151) memmove - something! b->last_char = *curr; curr ++; } } /* If we have ALT text for an image, strip off any preceeding */ /* spaces or [s, and any trailing spaces or ]s. */ else if ( tp && tp->text && ( (tp->style & IMG) || ( tp->tagno == TAG_INPUT && HtmlINPUTtype(tp) == inputtype_IMAGE ) ) ) { char * start, * end; char last; int len; len = strlen(tp->text); /* Get rid of preceeding characters */ start = tp->text; end = tp->text + len - 1; while (*start == ' ' || *start == '[') start ++; /* If there's anything left... */ if (*start) { /* Get rid of trailing characters */ while (*end == ' ' || *end == ']') { *end = '\0'; end --; } } /* If there's still something left, move the */ /* string contents down so tp->text points */ /* past the stripped preceeding chracters. */ if (start <= end) memmove(tp->text, start, strlen(start) + 1); /* 'strlen + 1' to get the string terminator */ else { /* If there was nothing left, did we originally have */ /* enough to put '[]' to mark that there's no text left? */ if (len > 1) strcpy(tp->text,"[]"); } /* Now do smart quotes substitution. Need to do this */ /* separately from the main text routines as the */ /* last_char variable must not be changed by ALT text */ /* - it stands alone for each image. */ last = ' '; start = tp->text; while (*start) { if (*start == '\'' || *start == '`') /* Dumb single quote */ { if (last == ' ' || last == '(' || last == 148 /* Opening double quote */ || last == '\"') *start = 144; /* Opening single quote */ else *start = 145; /* Closing single quote */ } else if (*start == '\"') /* Dumb double quote */ { if (last == ' ' || last == '(' || last == 144 /* Opening single quote */ || last == '\'' || last == '`') *start = 148; /* Opening double quote */ else *start = 149; /* Closing double quote */ } else if (*start == '-' && last == ' ') *start = 151; /* 'en' dash */ last = *start; start ++; } } }