diff --git a/gumps/Book_gump.cc b/gumps/Book_gump.cc index 3e7da65bb..1433fdaa9 100644 --- a/gumps/Book_gump.cc +++ b/gumps/Book_gump.cc @@ -1,5 +1,5 @@ /* -Copyright (C) 2000-2022 The Exult Team +Copyright (C) 2000-2025 The Exult Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -40,7 +40,7 @@ void Book_gump::paint() { // Paint the gump itself. paint_shape(x, y); // Paint left page. - curend = paint_page(TileRect(36, 10, 122, 130), curtop); + curend = paint_page(TileRect(35, 8, 125, 130), curtop); // Paint right page. - curend = paint_page(TileRect(174, 10, 122, 130), curend); + curend = paint_page(TileRect(173, 8, 125, 130), curend); } diff --git a/gumps/Scroll_gump.cc b/gumps/Scroll_gump.cc index 533f4f49b..9d57b2302 100644 --- a/gumps/Scroll_gump.cc +++ b/gumps/Scroll_gump.cc @@ -1,5 +1,5 @@ /* -Copyright (C) 2000-2022 The Exult Team +Copyright (C) 2000-2025 The Exult Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -39,5 +39,5 @@ Scroll_gump::Scroll_gump(int fnt, int gump) void Scroll_gump::paint() { // Paint the gump itself. paint_shape(x, y); - curend = paint_page(TileRect(52, 30, 142, 118), curtop); + curend = paint_page(TileRect(51, 31, 142, 118), curtop); } diff --git a/gumps/Text_gump.cc b/gumps/Text_gump.cc index 5feb93b07..5594115fc 100644 --- a/gumps/Text_gump.cc +++ b/gumps/Text_gump.cc @@ -1,5 +1,5 @@ /* -Copyright (C) 2000-2022 The Exult Team +Copyright (C) 2000-2025 The Exult Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -21,10 +21,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #endif #include "Text_gump.h" - +#include "game.h" #include "gamewin.h" #include +#include using std::strchr; @@ -33,21 +34,23 @@ using std::strchr; */ void Text_gump::add_text(const char* str) { - const int slen = strlen(str); // Length of new text. - // Allocate new space. - char* newtext = new char[textlen + (textlen != 0) + slen + 1]; - if (textlen) { // Copy over old. - strcpy(newtext, text); - // Add new line if not starting a new page and if first char of new - // string is not a new line + std::string newtext; + if (textlen) { + newtext = text; if (newtext[textlen - 1] != '*') { - newtext[textlen++] = '~'; + // Scrolls in SI break page with embedded 0x00 + if (GAME_SI && get_shapenum() == game->get_shape("gumps/scroll")) { + newtext += "*"; + } else { + newtext += "~"; + } } } - strcpy(newtext + textlen, str); // Append new. + newtext += str; delete[] text; - text = newtext; - textlen += slen; + text = new char[newtext.length() + 1]; + strcpy(text, newtext.c_str()); + textlen = newtext.length(); } /* @@ -56,50 +59,172 @@ void Text_gump::add_text(const char* str) { * Output: Index past end of displayed page. */ -int Text_gump::paint_page( - const TileRect& box, // Display box rel. to gump. - int start // Starting offset into text. -) { - const int vlead = 1; // Extra inter-line spacing. +int Text_gump::paint_page(const TileRect& box, int start) { int ypos = 0; + const int vlead = 1; const int textheight = sman->get_text_height(font) + vlead; char* str = text + start; - while (*str && *str != '*' && ypos + textheight <= box.h) { - if (*str == '~') { // Empty paragraph? - ypos += textheight; - str++; - continue; + curend = start; // Initialize curend to the starting position + + char* lineBreak = nullptr; + char* pageBreakStar = nullptr; + char* extraBreak = nullptr; + char* epage = nullptr; // page break marker + char* eol = nullptr; // end-of-line marker + + while (*str && ypos + textheight <= box.h) { + lineBreak = strchr(str, '~'); + pageBreakStar = strchr(str, '*'); + + if (GAME_BG && get_shapenum() == game->get_shape("gumps/scroll")) { + extraBreak = strstr(str, " ~~"); + if (extraBreak) { + // If " ~~" is preceded by "~~", ignore it. + if (extraBreak - text >= 2 && *(extraBreak - 2) == '~' + && *(extraBreak - 1) == '~') { + extraBreak = nullptr; + } + } } - // Look for page break. - char* epage = strchr(str, '*'); - // Look for line break. - char* eol = strchr(str, '~'); - if (epage && (!eol || eol > epage)) { - eol = epage; + + // if extraBreak is found and it occurs at the same position + // as the first tilde, use it as page break. + if (extraBreak && lineBreak && extraBreak + 1 == lineBreak) { + epage = extraBreak; + eol = extraBreak; + } else if (pageBreakStar && (!lineBreak || pageBreakStar < lineBreak)) { + epage = pageBreakStar; + eol = pageBreakStar; + } else { + eol = lineBreak; + epage = nullptr; // no page break marker } - if (!eol) { // No end found? + + if (!eol) { + // No marker found: paint until the end of text. eol = text + textlen; } - const char eolchr = *eol; // Save char. at EOL. - *eol = 0; - const int endoff = sman->paint_text_box( - font, str, x + box.x, y + box.y + ypos, box.w, box.h - ypos, - vlead); - *eol = eolchr; // Restore char. - if (endoff > 0) { // All painted? - // Value returned is height. - str = eol + (eolchr == '~'); + + // Temporarily null-terminate at eol for painting. + const char eolchr = *eol; + *eol = '\0'; + + int endoff = sman->paint_text_box( + font, str, x + box.x, y + box.y + ypos, box.w, box.h - ypos, + vlead); + // Restore the character at eol. + *eol = eolchr; + + if (endoff > 0) { + // Check for a page‑break marker + if (epage && get_shapenum() == game->get_shape("gumps/scroll")) { + if (GAME_SI && start == 0 && ypos <= 5 * textheight) { + // ignore page breaks on the first 5 lines of a scroll + str = pageBreakStar + 1; + curend = str - text; + ypos += endoff; + continue; + } + // Calculate vertical space remaining after the current + // block has been painted. + int remaining = box.h - (ypos + endoff); + + // Require room for at least two extra full line. + if (remaining >= 2 * textheight) { + // Reserve two extra line for spacing; the rest is + // available for the next text chunk. + int available = remaining - (2 * textheight); + if (available < 0) { + available = 0; + } + + // Use existing markers to find earliest upcoming marker + const char* earliest + = text + textlen; // default: measure to + // end-of-text + + // Use same check for both page break markers + const char* nextBreak = nullptr; + if ((pageBreakStar > eol + (epage == extraBreak ? 3 : 1) + && pageBreakStar < earliest) + || (extraBreak > eol + (epage == extraBreak ? 3 : 1) + && extraBreak < earliest)) { + earliest = (pageBreakStar && pageBreakStar < earliest) + ? pageBreakStar + : extraBreak; + } + + // Temporarily replace the character at 'earliest' with + // '\0' to isolate the next chunk. + char saved = *earliest; + + // PEEK MEASUREMENT: + // To avoid drawing on top of already painted text, we + // use an off-screen y coordinate (e.g. -1000). + int peekHeight = sman->paint_text_box( + font, eol + (epage == extraBreak ? 3 : 1), + x + box.x, + -1000, // off-screen y coordinate for + // measurement only + box.w, available, vlead); + // Restore the character we replaced. + *((char*)earliest) = saved; + + if (peekHeight > 0 + && peekHeight + <= available + + 5) // Add 5 pixels tolerance + { + // The entire upcoming text fits in the available + // space. Consume most of the extraBreak/pageBreakStar + // marker and add an extra line spacing. + eol += (epage == extraBreak ? 2 : 1); + ypos += endoff; // update vertical offset + str = eol; // continue painting after the marker + curend = str - text; // Update curend + continue; // next iteration: the peeked block + // will be painted on this page only + } else { + // The upcoming block does not fully fit; + // consume the marker and break so that it will be + // painted on the next page. + eol += (epage == extraBreak ? 3 : 1); + str = eol; + curend = str - text; // Update curend + break; + } + } + } else if (eolchr == '~' || (lineBreak && lineBreak == eol)) { + // Normal handling for a tilde line break. + eol++; + } + + // After handling markers (or if none applied), update the text + // pointer and vertical offset. ypos += endoff; - } else { // Out of room. + str = eol; + curend = str - text; // Update curend + + // Skip any leading space at the beginning of a new line. + if (*str == ' ') { + str++; + } + } else { + // Out of room or partial paint -> back up str += -endoff; + curend = str - text; // Update curend break; } } - if (*str == '*') { // Saw end of page? - str++; + + // After the loop, check for markers at current position + if (pageBreakStar && pageBreakStar == str) { + str++; // skip "*" } - gwin->set_painted(); // Force blit. - return str - text; // Return offset past end. + + curend = str - text; // Update curend one last time after the loop + gwin->set_painted(); + return str - text; } /* @@ -113,6 +238,12 @@ int Text_gump::show_next_page() { return 0; // That's all, folks. } curtop = curend; // Start next page or pair of pages. - paint(); // Paint. This updates curend. + + // Skip any leading tildes at the beginning of the new page. + while (curtop < textlen && text[curtop] == '~') { + curtop++; + } + + paint(); // Paint. This updates curend. return 1; }