summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.github/workflows/docker-debian/entrypoint.sh2
-rw-r--r--CMakeLists.txt29
m---------lib/the_Foundation0
-rw-r--r--res/about/version.gmi8
-rw-r--r--src/app.c12
-rw-r--r--src/gmdocument.c36
-rw-r--r--src/gmdocument.h22
-rw-r--r--src/gopher.c4
-rw-r--r--src/history.c11
-rw-r--r--src/history.h1
-rw-r--r--src/ui/documentwidget.c6
-rw-r--r--src/ui/text.c2
-rw-r--r--src/ui/text_simple.c16
-rw-r--r--src/ui/util.c23
14 files changed, 90 insertions, 82 deletions
diff --git a/.github/workflows/docker-debian/entrypoint.sh b/.github/workflows/docker-debian/entrypoint.sh
index d3222c3f..33e59c44 100755
--- a/.github/workflows/docker-debian/entrypoint.sh
+++ b/.github/workflows/docker-debian/entrypoint.sh
@@ -2,7 +2,7 @@
2export LC_ALL=en_US.UTF-8 2export LC_ALL=en_US.UTF-8
3 3
4apt-get update -qq -y 4apt-get update -qq -y
5apt-get install -y -qq --no-install-recommends cmake libsdl2-dev libssl-dev libpcre3-dev zlib1g-dev libunistring-dev libmpg123-dev debhelper dh-make devscripts fakeroot git build-essential locales python3 python3-pip libharfbuzz-dev libfribidi-dev 5apt-get install -y -qq --no-install-recommends cmake libsdl2-dev libssl-dev libpcre3-dev zlib1g-dev libunistring-dev libmpg123-dev debhelper dh-make devscripts fakeroot git build-essential locales python3 python3-pip zip libharfbuzz-dev libfribidi-dev
6pip3 install git-archive-all 6pip3 install git-archive-all
7sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen && locale-gen 7sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen && locale-gen
8 8
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6abfd094..378a1bba 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,7 +18,7 @@
18cmake_minimum_required (VERSION 3.9) 18cmake_minimum_required (VERSION 3.9)
19 19
20project (Lagrange 20project (Lagrange
21 VERSION 1.8.0 21 VERSION 1.8.1
22 DESCRIPTION "A Beautiful Gemini Client" 22 DESCRIPTION "A Beautiful Gemini Client"
23 LANGUAGES C 23 LANGUAGES C
24) 24)
@@ -65,25 +65,6 @@ include (Depends.cmake)
65# Package resources. 65# Package resources.
66message (STATUS "Preparing resources...") 66message (STATUS "Preparing resources...")
67make_fontpack (res/default.fontpack) 67make_fontpack (res/default.fontpack)
68# Fonts to install as separate files.
69set (FONTPACKS
70# arabic.fontpack
71# cjk.fontpack
72# firasans.fontpack
73# literata.fontpack
74# nunito.fontpack
75# tinos.fontpack
76)
77foreach (fp ${FONTPACKS})
78 make_fontpack (res/${fp})
79 set_source_files_properties (${CMAKE_BINARY_DIR}/${fp}
80 PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
81endforeach (fp)
82macro (install_fonts dst)
83 foreach (fp ${FONTPACKS})
84 install (FILES ${CMAKE_BINARY_DIR}/${fp} DESTINATION ${dst})
85 endforeach (fp)
86endmacro ()
87set (EMBED_RESOURCES 68set (EMBED_RESOURCES
88 res/about/about.gmi 69 res/about/about.gmi
89 res/about/help.gmi 70 res/about/help.gmi
@@ -240,11 +221,6 @@ set (SOURCES
240 ${CMAKE_CURRENT_BINARY_DIR}/embedded.h 221 ${CMAKE_CURRENT_BINARY_DIR}/embedded.h
241 ${CMAKE_CURRENT_BINARY_DIR}/resources.lgr 222 ${CMAKE_CURRENT_BINARY_DIR}/resources.lgr
242) 223)
243if (APPLE)
244 foreach (fp ${FONTPACKS})
245 list (APPEND SOURCES ${CMAKE_BINARY_DIR}/${fp})
246 endforeach (fp)
247endif ()
248if (ENABLE_IPC) 224if (ENABLE_IPC)
249 list (APPEND SOURCES 225 list (APPEND SOURCES
250 src/ipc.c 226 src/ipc.c
@@ -444,7 +420,6 @@ if (MSYS)
444 if (NOT ENABLE_RESOURCE_EMBED) 420 if (NOT ENABLE_RESOURCE_EMBED)
445 install (FILES ${EMB_BIN} DESTINATION .) 421 install (FILES ${EMB_BIN} DESTINATION .)
446 endif () 422 endif ()
447 install_fonts (.)
448 install (PROGRAMS 423 install (PROGRAMS
449 ${SDL2_LIBDIR}/SDL2.dll 424 ${SDL2_LIBDIR}/SDL2.dll
450 res/urlopen.bat 425 res/urlopen.bat
@@ -460,7 +435,6 @@ elseif (HAIKU)
460 LAGRANGE_EMB_BIN="${CMAKE_INSTALL_PREFIX}/resources.lgr") 435 LAGRANGE_EMB_BIN="${CMAKE_INSTALL_PREFIX}/resources.lgr")
461 install (FILES ${EMB_BIN} DESTINATION .) 436 install (FILES ${EMB_BIN} DESTINATION .)
462 endif () 437 endif ()
463 install_fonts (.)
464elseif (UNIX AND NOT APPLE) 438elseif (UNIX AND NOT APPLE)
465 set_target_properties (app PROPERTIES 439 set_target_properties (app PROPERTIES
466 INSTALL_RPATH_USE_LINK_PATH YES 440 INSTALL_RPATH_USE_LINK_PATH YES
@@ -493,5 +467,4 @@ MimeType=x-scheme-handler/gemini;x-scheme-handler/gopher;
493 endif () 467 endif ()
494 install (FILES ${EMB_BIN} DESTINATION share/lagrange) 468 install (FILES ${EMB_BIN} DESTINATION share/lagrange)
495 endif () 469 endif ()
496 install_fonts (share/lagrange)
497endif () 470endif ()
diff --git a/lib/the_Foundation b/lib/the_Foundation
Subproject 6c7d2a3b050aae3b37adc626aa75f040b5acb93 Subproject 9554ca7cc8b26c328d870760f3aaac4e701ba7e
diff --git a/res/about/version.gmi b/res/about/version.gmi
index f4fb83d1..372f6027 100644
--- a/res/about/version.gmi
+++ b/res/about/version.gmi
@@ -6,6 +6,14 @@
6``` 6```
7# Release notes 7# Release notes
8 8
9## 1.8.1
10* Added the `zip` utility as a build requirement. It is used for making fontpacks.
11* Fixed build failure with the simple text renderer, i.e., when HarfBuzz is disabled.
12* Fixed a line spacing artifact in long headings. With some fonts, the lines were clipping each other so the spacing was restored to normal.
13* Fixed a socket I/O issue that caused received data to be ignored when the peer closed the connection prematurely.
14* macOS: Tab close buttons are on the left side (platform UI convention).
15* Gopher: Recognize both LF and CRLF line endings in page content.
16
9## 1.8 17## 1.8
10⚠️ Font settings will be reset to defaults. Only a minimal set of fonts is bundled with the app. If additional fonts are needed, one can use custom TrueType fonts or download some from the Font Library: 18⚠️ Font settings will be reset to defaults. Only a minimal set of fonts is bundled with the app. If additional fonts are needed, one can use custom TrueType fonts or download some from the Font Library:
11=> gemini://skyjake.fi/fonts/ 19=> gemini://skyjake.fi/fonts/
diff --git a/src/app.c b/src/app.c
index 73771609..63a286f8 100644
--- a/src/app.c
+++ b/src/app.c
@@ -2125,6 +2125,12 @@ void resetFonts_App(void) {
2125 } 2125 }
2126} 2126}
2127 2127
2128static void invalidateCachedDocuments_App_(void) {
2129 iForEach(ObjectList, i, iClob(listDocuments_App(NULL))) {
2130 invalidateCachedLayout_History(history_DocumentWidget(i.object));
2131 }
2132}
2133
2128iBool handleCommand_App(const char *cmd) { 2134iBool handleCommand_App(const char *cmd) {
2129 iApp *d = &app_; 2135 iApp *d = &app_;
2130 const iBool isFrozen = !d->window || d->window->isDrawFrozen; 2136 const iBool isFrozen = !d->window || d->window->isDrawFrozen;
@@ -2289,7 +2295,10 @@ iBool handleCommand_App(const char *cmd) {
2289 if (!isFrozen) { 2295 if (!isFrozen) {
2290 setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */ 2296 setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */
2291 } 2297 }
2292 d->prefs.zoomPercent = arg_Command(cmd); 2298 if (arg_Command(cmd) != d->prefs.zoomPercent) {
2299 d->prefs.zoomPercent = arg_Command(cmd);
2300 invalidateCachedDocuments_App_();
2301 }
2293 setDocumentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f); 2302 setDocumentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f);
2294 if (!isFrozen) { 2303 if (!isFrozen) {
2295 postCommand_App("font.changed"); 2304 postCommand_App("font.changed");
@@ -2306,6 +2315,7 @@ iBool handleCommand_App(const char *cmd) {
2306 delta /= 2; 2315 delta /= 2;
2307 } 2316 }
2308 d->prefs.zoomPercent = iClamp(d->prefs.zoomPercent + delta, 50, 200); 2317 d->prefs.zoomPercent = iClamp(d->prefs.zoomPercent + delta, 50, 200);
2318 invalidateCachedDocuments_App_();
2309 setDocumentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f); 2319 setDocumentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f);
2310 if (!isFrozen) { 2320 if (!isFrozen) {
2311 postCommand_App("font.changed"); 2321 postCommand_App("font.changed");
diff --git a/src/gmdocument.c b/src/gmdocument.c
index 8b24ce29..0027bdb3 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -161,10 +161,9 @@ struct Impl_GmDocument {
161 iInt2 size; 161 iInt2 size;
162 int outsideMargin; 162 int outsideMargin;
163 iBool enableCommandLinks; /* `about:command?` only allowed on selected pages */ 163 iBool enableCommandLinks; /* `about:command?` only allowed on selected pages */
164 iBool isLayoutInvalidated;
164 iArray layout; /* contents of source, laid out in document space */ 165 iArray layout; /* contents of source, laid out in document space */
165 iPtrArray links; 166 iPtrArray links;
166// enum iGmDocumentBanner bannerType;
167// iString bannerText;
168 iString title; /* the first top-level title */ 167 iString title; /* the first top-level title */
169 iArray headings; 168 iArray headings;
170 iArray preMeta; /* metadata about preformatted blocks */ 169 iArray preMeta; /* metadata about preformatted blocks */
@@ -607,6 +606,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
607// const iBool isDarkBg = isDark_GmDocumentTheme( 606// const iBool isDarkBg = isDark_GmDocumentTheme(
608// isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight); 607// isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight);
609 initTheme_GmDocument_(d); 608 initTheme_GmDocument_(d);
609 d->isLayoutInvalidated = iFalse;
610 /* TODO: Collect these parameters into a GmTheme. */ 610 /* TODO: Collect these parameters into a GmTheme. */
611 float indents[max_GmLineType] = { 5, 10, 5, isNarrow ? 5 : 10, 0, 0, 0, 5 }; 611 float indents[max_GmLineType] = { 5, 10, 5, isNarrow ? 5 : 10, 0, 0, 0, 5 };
612 if (isExtremelyNarrow) { 612 if (isExtremelyNarrow) {
@@ -968,6 +968,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
968 ? 4 : 0) * gap_Text; 968 ? 4 : 0) * gap_Text;
969 } 969 }
970 if (!isMono) { 970 if (!isMono) {
971#if 0
971 /* Upper-level headings are typeset a bit tighter. */ 972 /* Upper-level headings are typeset a bit tighter. */
972 if (type == heading1_GmLineType) { 973 if (type == heading1_GmLineType) {
973 rts.lineHeightReduction = 0.10f; 974 rts.lineHeightReduction = 0.10f;
@@ -975,6 +976,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
975 else if (type == heading2_GmLineType) { 976 else if (type == heading2_GmLineType) {
976 rts.lineHeightReduction = 0.06f; 977 rts.lineHeightReduction = 0.06f;
977 } 978 }
979#endif
978 /* Visited links are never bold. */ 980 /* Visited links are never bold. */
979 if (run.linkId && !prefs->boldLinkVisited && 981 if (run.linkId && !prefs->boldLinkVisited &&
980 linkFlags_GmDocument(d, run.linkId) & visited_GmLinkFlag) { 982 linkFlags_GmDocument(d, run.linkId) & visited_GmLinkFlag) {
@@ -1142,13 +1144,12 @@ void init_GmDocument(iGmDocument *d) {
1142 init_String(&d->source); 1144 init_String(&d->source);
1143 init_String(&d->url); 1145 init_String(&d->url);
1144 init_String(&d->localHost); 1146 init_String(&d->localHost);
1145// d->bannerType = siteDomain_GmDocumentBanner;
1146 d->outsideMargin = 0; 1147 d->outsideMargin = 0;
1147 d->size = zero_I2(); 1148 d->size = zero_I2();
1148 d->enableCommandLinks = iFalse; 1149 d->enableCommandLinks = iFalse;
1150 d->isLayoutInvalidated = iFalse;
1149 init_Array(&d->layout, sizeof(iGmRun)); 1151 init_Array(&d->layout, sizeof(iGmRun));
1150 init_PtrArray(&d->links); 1152 init_PtrArray(&d->links);
1151// init_String(&d->bannerText);
1152 init_String(&d->title); 1153 init_String(&d->title);
1153 init_Array(&d->headings, sizeof(iGmHeading)); 1154 init_Array(&d->headings, sizeof(iGmHeading));
1154 init_Array(&d->preMeta, sizeof(iGmPreMeta)); 1155 init_Array(&d->preMeta, sizeof(iGmPreMeta));
@@ -1164,7 +1165,6 @@ void init_GmDocument(iGmDocument *d) {
1164void deinit_GmDocument(iGmDocument *d) { 1165void deinit_GmDocument(iGmDocument *d) {
1165 iReleasePtr(&d->openURLs); 1166 iReleasePtr(&d->openURLs);
1166 delete_Media(d->media); 1167 delete_Media(d->media);
1167// deinit_String(&d->bannerText);
1168 deinit_String(&d->title); 1168 deinit_String(&d->title);
1169 clearLinks_GmDocument_(d); 1169 clearLinks_GmDocument_(d);
1170 deinit_PtrArray(&d->links); 1170 deinit_PtrArray(&d->links);
@@ -1720,22 +1720,28 @@ void setFormat_GmDocument(iGmDocument *d, enum iSourceFormat format) {
1720 d->format = format; 1720 d->format = format;
1721} 1721}
1722 1722
1723#if 0 1723void setWidth_GmDocument(iGmDocument *d, int width, int canvasWidth) {
1724void setBanner_GmDocument(iGmDocument *d, enum iGmDocumentBanner type) { 1724 d->size.x = width;
1725 d->bannerType = type; 1725 d->outsideMargin = iMax(0, (canvasWidth - width) / 2); /* distance to edge of the canvas */
1726 doLayout_GmDocument_(d); /* TODO: just flag need-layout and do it later */
1726} 1727}
1727#endif
1728 1728
1729void setWidth_GmDocument(iGmDocument *d, int width, int outsideMargin) { 1729iBool updateWidth_GmDocument(iGmDocument *d, int width, int canvasWidth) {
1730 d->size.x = width; 1730 if (d->size.x != width || d->isLayoutInvalidated) {
1731 d->outsideMargin = outsideMargin; /* distance to edge of the viewport */ 1731 setWidth_GmDocument(d, width, canvasWidth);
1732 doLayout_GmDocument_(d); /* TODO: just flag need-layout and do it later */ 1732 return iTrue;
1733 }
1734 return iFalse;
1733} 1735}
1734 1736
1735void redoLayout_GmDocument(iGmDocument *d) { 1737void redoLayout_GmDocument(iGmDocument *d) {
1736 doLayout_GmDocument_(d); 1738 doLayout_GmDocument_(d);
1737} 1739}
1738 1740
1741void invalidateLayout_GmDocument(iGmDocument *d) {
1742 d->isLayoutInvalidated = iTrue;
1743}
1744
1739static void markLinkRunsVisited_GmDocument_(iGmDocument *d, const iIntSet *linkIds) { 1745static void markLinkRunsVisited_GmDocument_(iGmDocument *d, const iIntSet *linkIds) {
1740 iForEach(Array, r, &d->layout) { 1746 iForEach(Array, r, &d->layout) {
1741 iGmRun *run = r.value; 1747 iGmRun *run = r.value;
@@ -2080,7 +2086,7 @@ static void convertMarkdownToGemtext_GmDocument_(iGmDocument *d) {
2080 d->format = gemini_SourceFormat; 2086 d->format = gemini_SourceFormat;
2081} 2087}
2082 2088
2083void setSource_GmDocument(iGmDocument *d, const iString *source, int width, int outsideMargin, 2089void setSource_GmDocument(iGmDocument *d, const iString *source, int width, int canvasWidth,
2084 enum iGmDocumentUpdate updateType) { 2090 enum iGmDocumentUpdate updateType) {
2085 /* TODO: This API has been set up to allow partial/progressive updating of the content. 2091 /* TODO: This API has been set up to allow partial/progressive updating of the content.
2086 Currently the entire source is replaced every time, though. */ 2092 Currently the entire source is replaced every time, though. */
@@ -2122,7 +2128,7 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width, int
2122 if (isNormalized_GmDocument_(d)) { 2128 if (isNormalized_GmDocument_(d)) {
2123 normalize_GmDocument(d); 2129 normalize_GmDocument(d);
2124 } 2130 }
2125 setWidth_GmDocument(d, width, outsideMargin); /* re-do layout */ 2131 setWidth_GmDocument(d, width, canvasWidth); /* re-do layout */
2126} 2132}
2127 2133
2128void foldPre_GmDocument(iGmDocument *d, uint16_t preId) { 2134void foldPre_GmDocument(iGmDocument *d, uint16_t preId) {
diff --git a/src/gmdocument.h b/src/gmdocument.h
index 444520c6..58fc3db3 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -120,10 +120,8 @@ enum iGmRunFlags {
120 decoration_GmRunFlag = iBit(1), /* not part of the source */ 120 decoration_GmRunFlag = iBit(1), /* not part of the source */
121 startOfLine_GmRunFlag = iBit(2), 121 startOfLine_GmRunFlag = iBit(2),
122 endOfLine_GmRunFlag = iBit(3), 122 endOfLine_GmRunFlag = iBit(3),
123// siteBanner_GmRunFlag = iBit(4), /* area reserved for the site banner */
124 quoteBorder_GmRunFlag = iBit(5), 123 quoteBorder_GmRunFlag = iBit(5),
125 wide_GmRunFlag = iBit(6), /* horizontally scrollable */ 124 wide_GmRunFlag = iBit(6), /* horizontally scrollable */
126// footer_GmRunFlag = iBit(7),
127 altText_GmRunFlag = iBit(8), 125 altText_GmRunFlag = iBit(8),
128}; 126};
129 127
@@ -177,13 +175,6 @@ enum iGmDocumentWarning {
177 missingGlyphs_GmDocumentWarning = iBit(2), 175 missingGlyphs_GmDocumentWarning = iBit(2),
178}; 176};
179 177
180/*
181enum iGmDocumentBanner {
182 none_GmDocumentBanner,
183 siteDomain_GmDocumentBanner,
184 certificateWarning_GmDocumentBanner,
185};*/
186
187enum iGmDocumentUpdate { 178enum iGmDocumentUpdate {
188 partial_GmDocumentUpdate, /* appending more content */ 179 partial_GmDocumentUpdate, /* appending more content */
189 final_GmDocumentUpdate, /* process all lines, including the last one if not terminated */ 180 final_GmDocumentUpdate, /* process all lines, including the last one if not terminated */
@@ -191,12 +182,13 @@ enum iGmDocumentUpdate {
191 182
192void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); 183void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed);
193void setFormat_GmDocument (iGmDocument *, enum iSourceFormat format); 184void setFormat_GmDocument (iGmDocument *, enum iSourceFormat format);
194//void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type); 185void setWidth_GmDocument (iGmDocument *, int width, int canvasWidth);
195void setWidth_GmDocument (iGmDocument *, int width, int outsideMargin); 186iBool updateWidth_GmDocument (iGmDocument *, int width, int canvasWidth);
196void redoLayout_GmDocument (iGmDocument *); 187void redoLayout_GmDocument (iGmDocument *);
188void invalidateLayout_GmDocument(iGmDocument *); /* will have to be redone later */
197iBool updateOpenURLs_GmDocument(iGmDocument *); 189iBool updateOpenURLs_GmDocument(iGmDocument *);
198void setUrl_GmDocument (iGmDocument *, const iString *url); 190void setUrl_GmDocument (iGmDocument *, const iString *url);
199void setSource_GmDocument (iGmDocument *, const iString *source, int width, int outsideMargin, 191void setSource_GmDocument (iGmDocument *, const iString *source, int width, int canvasWidth,
200 enum iGmDocumentUpdate updateType); 192 enum iGmDocumentUpdate updateType);
201void foldPre_GmDocument (iGmDocument *, uint16_t preId); 193void foldPre_GmDocument (iGmDocument *, uint16_t preId);
202 194
@@ -204,8 +196,6 @@ void updateVisitedLinks_GmDocument (iGmDocument *); /* check all links for
204void invalidatePalette_GmDocument (iGmDocument *); 196void invalidatePalette_GmDocument (iGmDocument *);
205void makePaletteGlobal_GmDocument (const iGmDocument *); /* copies document colors to the global palette */ 197void makePaletteGlobal_GmDocument (const iGmDocument *); /* copies document colors to the global palette */
206 198
207//void reset_GmDocument (iGmDocument *); /* free images */
208
209typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); 199typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *);
210 200
211iMedia * media_GmDocument (iGmDocument *); 201iMedia * media_GmDocument (iGmDocument *);
@@ -219,10 +209,6 @@ const iGmRun * renderProgressive_GmDocument(const iGmDocument *d, const iGmRun
219 iRangei visRangeY, iGmDocumentRenderFunc render, 209 iRangei visRangeY, iGmDocumentRenderFunc render,
220 void *context); 210 void *context);
221iInt2 size_GmDocument (const iGmDocument *); 211iInt2 size_GmDocument (const iGmDocument *);
222//const iGmRun * siteBanner_GmDocument (const iGmDocument *);
223//iBool hasSiteBanner_GmDocument (const iGmDocument *);
224//enum iGmDocumentBanner bannerType_GmDocument(const iGmDocument *);
225//const iString * bannerText_GmDocument (const iGmDocument *);
226const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ 212const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */
227const iString * source_GmDocument (const iGmDocument *); 213const iString * source_GmDocument (const iGmDocument *);
228size_t memorySize_GmDocument (const iGmDocument *); /* bytes */ 214size_t memorySize_GmDocument (const iGmDocument *); /* bytes */
diff --git a/src/gopher.c b/src/gopher.c
index ac5fe560..008a7743 100644
--- a/src/gopher.c
+++ b/src/gopher.c
@@ -103,10 +103,10 @@ static iBool convertSource_Gopher_(iGopher *d) {
103 for (;;) { 103 for (;;) {
104 /* Find the end of the line. */ 104 /* Find the end of the line. */
105 iRangecc line = { body.start, body.start }; 105 iRangecc line = { body.start, body.start };
106 while (line.end < body.end - 1 && !isCRLFLineTerminator_(line.end)) { 106 while (line.end < body.end - 1 && !isLineTerminator_(line.end)) {
107 line.end++; 107 line.end++;
108 } 108 }
109 if (line.end >= body.end - 1 || !isCRLFLineTerminator_(line.end)) { 109 if (line.end >= body.end - 1 || !isLineTerminator_(line.end)) {
110 /* Not a complete line. More may be coming later. */ 110 /* Not a complete line. More may be coming later. */
111 break; 111 break;
112 } 112 }
diff --git a/src/history.c b/src/history.c
index 208c239d..7185912f 100644
--- a/src/history.c
+++ b/src/history.c
@@ -440,6 +440,17 @@ void clearCache_History(iHistory *d) {
440 unlock_Mutex(d->mtx); 440 unlock_Mutex(d->mtx);
441} 441}
442 442
443void invalidateCachedLayout_History(iHistory *d) {
444 lock_Mutex(d->mtx);
445 iForEach(Array, i, &d->recent) {
446 iRecentUrl *url = i.value;
447 if (url->cachedDoc) {
448 invalidateLayout_GmDocument(url->cachedDoc);
449 }
450 }
451 unlock_Mutex(d->mtx);
452}
453
443size_t pruneLeastImportant_History(iHistory *d) { 454size_t pruneLeastImportant_History(iHistory *d) {
444 size_t delta = 0; 455 size_t delta = 0;
445 size_t chosen = iInvalidPos; 456 size_t chosen = iInvalidPos;
diff --git a/src/history.h b/src/history.h
index 7dad72df..d3daae80 100644
--- a/src/history.h
+++ b/src/history.h
@@ -76,6 +76,7 @@ void clearCache_History (iHistory *);
76size_t pruneLeastImportant_History (iHistory *); 76size_t pruneLeastImportant_History (iHistory *);
77size_t pruneLeastImportantMemory_History (iHistory *); 77size_t pruneLeastImportantMemory_History (iHistory *);
78void invalidateTheme_History (iHistory *); /* theme has changed, cached contents need updating */ 78void invalidateTheme_History (iHistory *); /* theme has changed, cached contents need updating */
79void invalidateCachedLayout_History (iHistory *);
79 80
80iBool atLatest_History (const iHistory *); 81iBool atLatest_History (const iHistory *);
81iBool atOldest_History (const iHistory *); 82iBool atOldest_History (const iHistory *);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 79bfea7b..8c24d4a9 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1702,6 +1702,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1702 } 1702 }
1703 if (cachedDoc) { 1703 if (cachedDoc) {
1704 replaceDocument_DocumentWidget_(d, cachedDoc); 1704 replaceDocument_DocumentWidget_(d, cachedDoc);
1705 updateWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d), width_Widget(d));
1705 } 1706 }
1706 else if (setSource) { 1707 else if (setSource) {
1707 setSource_DocumentWidget(d, &str); 1708 setSource_DocumentWidget(d, &str);
@@ -2498,7 +2499,7 @@ static iBool updateDocumentWidthRetainingScrollPosition_DocumentWidget_(iDocumen
2498 /* TODO: First *fully* visible run? */ 2499 /* TODO: First *fully* visible run? */
2499 voffset = visibleRange_DocumentWidget_(d).start - top_Rect(run->visBounds); 2500 voffset = visibleRange_DocumentWidget_(d).start - top_Rect(run->visBounds);
2500 } 2501 }
2501 setWidth_GmDocument(d->doc, newWidth, (width_Widget(d) - newWidth) / 2); 2502 setWidth_GmDocument(d->doc, newWidth, width_Widget(d));
2502 setWidth_Banner(d->banner, newWidth); 2503 setWidth_Banner(d->banner, newWidth);
2503 documentRunsInvalidated_DocumentWidget_(d); 2504 documentRunsInvalidated_DocumentWidget_(d);
2504 if (runLoc && !keepCenter) { 2505 if (runLoc && !keepCenter) {
@@ -2734,6 +2735,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2734 } 2735 }
2735 else if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed") || 2736 else if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed") ||
2736 equal_Command(cmd, "keyroot.changed")) { 2737 equal_Command(cmd, "keyroot.changed")) {
2738 if (equal_Command(cmd, "font.changed")) {
2739 invalidateCachedLayout_History(d->mod.history);
2740 }
2737 /* Alt/Option key may be involved in window size changes. */ 2741 /* Alt/Option key may be involved in window size changes. */
2738 setLinkNumberMode_DocumentWidget_(d, iFalse); 2742 setLinkNumberMode_DocumentWidget_(d, iFalse);
2739 d->phoneToolbar = findWidget_App("toolbar"); 2743 d->phoneToolbar = findWidget_App("toolbar");
diff --git a/src/ui/text.c b/src/ui/text.c
index 09c01d46..46e355d7 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -2114,7 +2114,7 @@ iTextMetrics draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) {
2114 const int width = d->mode == word_WrapTextMode 2114 const int width = d->mode == word_WrapTextMode
2115 ? tryAdvance_Text(fontId, text, d->maxWidth, &endPos).x 2115 ? tryAdvance_Text(fontId, text, d->maxWidth, &endPos).x
2116 : tryAdvanceNoWrap_Text(fontId, text, d->maxWidth, &endPos).x; 2116 : tryAdvanceNoWrap_Text(fontId, text, d->maxWidth, &endPos).x;
2117 notify_WrapText_(d, endPos, 0, width, iFalse); 2117 notify_WrapText_(d, endPos, (iTextAttrib){ .colorId = color }, 0, width);
2118 drawRange_Text(fontId, pos, color, (iRangecc){ text.start, endPos }); 2118 drawRange_Text(fontId, pos, color, (iRangecc){ text.start, endPos });
2119 text.start = endPos; 2119 text.start = endPos;
2120 pos.y += lineHeight_Text(fontId); 2120 pos.y += lineHeight_Text(fontId);
diff --git a/src/ui/text_simple.c b/src/ui/text_simple.c
index 8b1de64a..81fb94a5 100644
--- a/src/ui/text_simple.c
+++ b/src/ui/text_simple.c
@@ -61,6 +61,7 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
61 and other non-complex LTR scripts. Composed glyphs are not supported (must rely on text 61 and other non-complex LTR scripts. Composed glyphs are not supported (must rely on text
62 being in a pre-composed form). This algorithm is used if HarfBuzz is not available. */ 62 being in a pre-composed form). This algorithm is used if HarfBuzz is not available. */
63 const iInt2 orig = args->pos; 63 const iInt2 orig = args->pos;
64 iTextAttrib attrib = { .colorId = args->color };
64 iRect bounds = { orig, init_I2(0, d->height) }; 65 iRect bounds = { orig, init_I2(0, d->height) };
65 float xpos = orig.x; 66 float xpos = orig.x;
66 float xposMax = xpos; 67 float xposMax = xpos;
@@ -86,7 +87,7 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
86// } 87// }
87 const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2()); 88 const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2());
88 const iBool checkHitChar = wrap && wrap->hitChar; 89 const iBool checkHitChar = wrap && wrap->hitChar;
89 const iBool isMonospaced = d->isMonospaced && !(mode & alwaysVariableWidthFlag_RunMode); 90 const iBool isMonospaced = isMonospaced_Font(d) && !(mode & alwaysVariableWidthFlag_RunMode);
90 if (isMonospaced) { 91 if (isMonospaced) {
91 monoAdvance = glyph_Font_(d, 'M')->advance; 92 monoAdvance = glyph_Font_(d, 'M')->advance;
92 } 93 }
@@ -166,7 +167,7 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
166 /* TODO: Check out if `uc_wordbreak_property()` from libunistring can be used here. */ 167 /* TODO: Check out if `uc_wordbreak_property()` from libunistring can be used here. */
167 if (ch == '\n') { 168 if (ch == '\n') {
168 /* Notify about the wrap. */ 169 /* Notify about the wrap. */
169 if (!notify_WrapText_(wrap, chPos, 0, iMax(xpos, xposExtend) - orig.x, iFalse)) { 170 if (!notify_WrapText_(wrap, chPos, attrib, 0, iMax(xpos, xposExtend) - orig.x)) {
170 break; 171 break;
171 } 172 }
172 lastWordEnd = NULL; 173 lastWordEnd = NULL;
@@ -246,7 +247,7 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
246 wrapPos = iMin(wrapPos, args->text.end); 247 wrapPos = iMin(wrapPos, args->text.end);
247 advance = wrapAdvance; 248 advance = wrapAdvance;
248 } 249 }
249 if (!notify_WrapText_(wrap, wrapPos, 0, advance, iFalse)) { 250 if (!notify_WrapText_(wrap, wrapPos, attrib, 0, advance)) {
250 break; 251 break;
251 } 252 }
252 lastWordEnd = NULL; 253 lastWordEnd = NULL;
@@ -282,8 +283,7 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
282 } 283 }
283 /* Symbols and emojis are NOT monospaced, so must conform when the primary font 284 /* Symbols and emojis are NOT monospaced, so must conform when the primary font
284 is monospaced. Except with Japanese script, that's larger than the normal monospace. */ 285 is monospaced. Except with Japanese script, that's larger than the normal monospace. */
285 const iBool useMonoAdvance = 286 const iBool useMonoAdvance = monoAdvance > 0; // && !isJapanese_FontId(fontId_Text_(glyph->font));
286 monoAdvance > 0 && !isJapanese_FontId(fontId_Text_(glyph->font));
287 const float advance = (useMonoAdvance && glyph->advance > 0 ? monoAdvance : glyph->advance); 287 const float advance = (useMonoAdvance && glyph->advance > 0 ? monoAdvance : glyph->advance);
288 if (!isMeasuring_(mode) && ch != 0x20 /* don't bother rendering spaces */) { 288 if (!isMeasuring_(mode) && ch != 0x20 /* don't bother rendering spaces */) {
289 if (useMonoAdvance && dst.w > advance && glyph->font != d && !isEmoji) { 289 if (useMonoAdvance && dst.w > advance && glyph->font != d && !isEmoji) {
@@ -328,9 +328,9 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
328 if (enableKerning_Text && next) { 328 if (enableKerning_Text && next) {
329 const uint32_t nextGlyphIndex = glyphIndex_Font_(glyph->font, next); 329 const uint32_t nextGlyphIndex = glyphIndex_Font_(glyph->font, next);
330 int kern = stbtt_GetGlyphKernAdvance( 330 int kern = stbtt_GetGlyphKernAdvance(
331 &glyph->font->font, index_Glyph_(glyph), nextGlyphIndex); 331 &glyph->font->fontFile->stbInfo, index_Glyph_(glyph), nextGlyphIndex);
332 /* Nunito needs some kerning fixes. */ 332 /* Nunito needs some kerning fixes. */
333 if (glyph->font->family == nunito_TextFont) { 333 if (glyph->font->fontSpec->flags & fixNunitoKerning_FontSpecFlag) {
334 if (ch == 'W' && (next == 'i' || next == 'h')) { 334 if (ch == 'W' && (next == 'i' || next == 'h')) {
335 kern = -30; 335 kern = -30;
336 } 336 }
@@ -362,7 +362,7 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
362 break; 362 break;
363 } 363 }
364 } 364 }
365 notify_WrapText_(wrap, chPos, 0, xpos - orig.x, iFalse); 365 notify_WrapText_(wrap, chPos, attrib, 0, xpos - orig.x);
366 if (checkHitChar && wrap->hitChar == args->text.end) { 366 if (checkHitChar && wrap->hitChar == args->text.end) {
367 wrap->hitAdvance_out = sub_I2(init_I2(xpos, ypos), orig); 367 wrap->hitAdvance_out = sub_I2(init_I2(xpos, ypos), orig);
368 } 368 }
diff --git a/src/ui/util.c b/src/ui/util.c
index 6d0453ff..8affa57e 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -1023,11 +1023,13 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) {
1023 setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue); 1023 setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue);
1024 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse); 1024 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse);
1025 arrange_Widget(d); /* need to know the height */ 1025 arrange_Widget(d); /* need to know the height */
1026 iBool allowOverflow = iFalse;
1026 /* A vertical offset determined by a possible selected label in the menu. */ { 1027 /* A vertical offset determined by a possible selected label in the menu. */ {
1027 iConstForEach(ObjectList, child, children_Widget(d)) { 1028 iConstForEach(ObjectList, child, children_Widget(d)) {
1028 const iWidget *item = constAs_Widget(child.object); 1029 const iWidget *item = constAs_Widget(child.object);
1029 if (flags_Widget(item) & selected_WidgetFlag) { 1030 if (flags_Widget(item) & selected_WidgetFlag) {
1030 windowCoord.y -= item->rect.pos.y; 1031 windowCoord.y -= item->rect.pos.y;
1032 allowOverflow = iTrue;
1031 } 1033 }
1032 } 1034 }
1033 } 1035 }
@@ -1129,11 +1131,13 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) {
1129 rightExcess += r; 1131 rightExcess += r;
1130 } 1132 }
1131#endif 1133#endif
1132 if (bottomExcess > 0 && (!isPortraitPhone || !isSlidePanel)) { 1134 if (!allowOverflow) {
1133 d->rect.pos.y -= bottomExcess; 1135 if (bottomExcess > 0 && (!isPortraitPhone || !isSlidePanel)) {
1134 } 1136 d->rect.pos.y -= bottomExcess;
1135 if (topExcess > 0) { 1137 }
1136 d->rect.pos.y += topExcess; 1138 if (topExcess > 0) {
1139 d->rect.pos.y += topExcess;
1140 }
1137 } 1141 }
1138 if (rightExcess > 0) { 1142 if (rightExcess > 0) {
1139 d->rect.pos.x -= rightExcess; 1143 d->rect.pos.x -= rightExcess;
@@ -1455,12 +1459,17 @@ void addTabCloseButton_Widget(iWidget *tabs, const iWidget *page, const char *co
1455 iLabelWidget *tabButton = tabButtonForPage_Widget_(tabs, page); 1459 iLabelWidget *tabButton = tabButtonForPage_Widget_(tabs, page);
1456 setPadding_Widget(as_Widget(tabButton), 0, 0, 0, gap_UI / 4); 1460 setPadding_Widget(as_Widget(tabButton), 0, 0, 0, gap_UI / 4);
1457 setFlags_Widget(as_Widget(tabButton), arrangeVertical_WidgetFlag | resizeHeightOfChildren_WidgetFlag, iTrue); 1461 setFlags_Widget(as_Widget(tabButton), arrangeVertical_WidgetFlag | resizeHeightOfChildren_WidgetFlag, iTrue);
1462#if defined (iPlatformApple)
1463 const int64_t edge = moveToParentLeftEdge_WidgetFlag;
1464#else
1465 const int64_t edge = moveToParentRightEdge_WidgetFlag;
1466#endif
1458 iLabelWidget *close = addChildFlags_Widget( 1467 iLabelWidget *close = addChildFlags_Widget(
1459 as_Widget(tabButton), 1468 as_Widget(tabButton),
1460 iClob(new_LabelWidget(close_Icon, 1469 iClob(new_LabelWidget(close_Icon,
1461 format_CStr("%s id:%s", command, cstr_String(id_Widget(page))))), 1470 format_CStr("%s id:%s", command, cstr_String(id_Widget(page))))),
1462 moveToParentRightEdge_WidgetFlag | tight_WidgetFlag | frameless_WidgetFlag | 1471 edge | tight_WidgetFlag | frameless_WidgetFlag | noBackground_WidgetFlag |
1463 noBackground_WidgetFlag | hidden_WidgetFlag | visibleOnParentHover_WidgetFlag); 1472 hidden_WidgetFlag | visibleOnParentHover_WidgetFlag);
1464 if (deviceType_App() != desktop_AppDeviceType) { 1473 if (deviceType_App() != desktop_AppDeviceType) {
1465 setFlags_Widget(as_Widget(close), 1474 setFlags_Widget(as_Widget(close),
1466 hidden_WidgetFlag | visibleOnParentHover_WidgetFlag, iFalse); 1475 hidden_WidgetFlag | visibleOnParentHover_WidgetFlag, iFalse);