From 61a3dc017067be43472dadb7909094aa04d1fe9d Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Wed, 6 Oct 2021 12:16:43 +0300 Subject: Revised runtime font management The built-in fonts are loaded via FontPack, and the font table is now constructed dynamically based on available fonts. A full set of variants (style, size) are prepared for each font, but some of the data gets allocated lazily when needed. GmRun needed a larger allocation for fonts, so now all the fields are combined into a single bit field. TODO: Glyph scaling, vertical offsets, and symbol lookup are still not fully working. --- src/gmdocument.c | 103 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 53 insertions(+), 50 deletions(-) (limited to 'src/gmdocument.c') diff --git a/src/gmdocument.c b/src/gmdocument.c index 2f4c7972..ce9fdec8 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -355,7 +355,7 @@ static enum iGmDocumentTheme currentTheme_(void) { } static void alignDecoration_GmRun_(iGmRun *run, iBool isCentered) { - const iRect visBounds = visualBounds_Text(run->textParams.font, run->text); + const iRect visBounds = visualBounds_Text(run->font, run->text); const int visWidth = width_Rect(visBounds); int xAdjust = 0; if (!isCentered) { @@ -438,7 +438,7 @@ static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, trimEnd_Rangecc(&wrapRange); // printf("typeset: {%s}\n", cstr_Rangecc(wrapRange)); iRunTypesetter *d = wrap->context; - const int fontId = d->run.textParams.font; + const int fontId = d->run.font; d->run.text = wrapRange; if (~d->run.flags & startOfLine_GmRunFlag && d->lineHeightReduction > 0.0f) { d->pos.y -= d->lineHeightReduction * lineHeight_Text(fontId); @@ -450,7 +450,7 @@ static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, d->run.bounds.size.y = dims.y; d->run.visBounds = d->run.bounds; d->run.visBounds.size.x = dims.x; - d->run.textParams.isRTL = isBaseRTL; + d->run.isRTL = isBaseRTL; pushBack_Array(&d->layout, &d->run); d->run.flags &= ~startOfLine_GmRunFlag; d->pos.y += lineHeight_Text(fontId) * prefs_App()->lineSpacing; @@ -543,7 +543,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { if (*line.end == '\r') { line.end--; /* trim CR always */ } - iGmRun run = { .textParams = { .color = white_ColorId } }; + iGmRun run = { .color = white_ColorId }; enum iGmLineType type; float indent = 0.0f; /* Detect the type of the line. */ @@ -581,14 +581,16 @@ static void doLayout_GmDocument_(iGmDocument *d) { continue; } else if (type == link_GmLineType) { - line = addLink_GmDocument_(d, line, &run.linkId); + iGmLinkId linkId; + line = addLink_GmDocument_(d, line, &linkId); + run.linkId = linkId; if (!run.linkId) { /* Invalid formatting. */ type = text_GmLineType; } } trimLine_Rangecc(&line, type, isNormalized); - run.textParams.font = fonts[type]; + run.font = fonts[type]; /* Remember headings for the document outline. */ if (type == heading1_GmLineType || type == heading2_GmLineType || type == heading3_GmLineType) { pushBack_Array( @@ -609,7 +611,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { continue; } run.preId = preId; - run.textParams.font = (d->format == plainText_SourceFormat ? regularMonospace_FontId : preFont); + run.font = (d->format == plainText_SourceFormat ? regularMonospace_FontId : preFont); indent = indents[type]; } if (addSiteBanner) { @@ -624,9 +626,9 @@ static void doLayout_GmDocument_(iGmDocument *d) { banner.visBounds.size.y += iMaxi(6000 * lineHeight_Text(uiLabel_FontId) / d->size.x, lineHeight_Text(uiLabel_FontId) * 5); } - banner.text = bannerText; - banner.textParams.font = banner_FontId; - banner.textParams.color = tmBannerTitle_ColorId; + banner.text = bannerText; + banner.font = banner_FontId; + banner.color = tmBannerTitle_ColorId; pushBack_Array(&d->layout, &banner); pos.y += height_Rect(banner.visBounds) + 1.5f * lineHeight_Text(paragraph_FontId) * prefs->lineSpacing; @@ -637,13 +639,13 @@ static void doLayout_GmDocument_(iGmDocument *d) { if (type == quote_GmLineType && !prefs->quoteIcon) { /* For quote indicators we still need to produce a run. */ run.visBounds.pos = addX_I2(pos, indents[type] * gap_Text); - run.visBounds.size = init_I2(gap_Text, lineHeight_Text(run.textParams.font)); + run.visBounds.size = init_I2(gap_Text, lineHeight_Text(run.font)); run.bounds = zero_Rect(); /* just visual */ - run.flags = quoteBorder_GmRunFlag | decoration_GmRunFlag; run.text = iNullRange; + run.flags = quoteBorder_GmRunFlag | decoration_GmRunFlag; pushBack_Array(&d->layout, &run); } - pos.y += lineHeight_Text(run.textParams.font) * prefs->lineSpacing; + pos.y += lineHeight_Text(run.font) * prefs->lineSpacing; prevType = type; if (type != quote_GmLineType) { addQuoteIcon = prefs->quoteIcon; @@ -687,14 +689,14 @@ static void doLayout_GmDocument_(iGmDocument *d) { const iGmPreMeta *meta = constAt_Array(&d->preMeta, preId - 1); if (meta->flags & folded_GmPreMetaFlag) { const iBool isBlank = isEmpty_Range(&meta->altText); - iGmRun altText = { - .textParams = { .font = paragraph_FontId, .color = tmQuote_ColorId }, - .flags = (isBlank ? decoration_GmRunFlag : 0) | altText_GmRunFlag + iGmRun altText = { .font = paragraph_FontId, + .color = tmQuote_ColorId, + .flags = (isBlank ? decoration_GmRunFlag : 0) | altText_GmRunFlag }; const iInt2 margin = preRunMargin_GmDocument(d, 0); altText.text = isBlank ? range_Lang(range_CStr("doc.pre.nocaption")) : meta->altText; - iInt2 size = measureWrapRange_Text(altText.textParams.font, d->size.x - 2 * margin.x, + iInt2 size = measureWrapRange_Text(altText.font, d->size.x - 2 * margin.x, altText.text).bounds.size; altText.bounds = altText.visBounds = init_Rect(pos.x, pos.y, d->size.x, size.y + 2 * margin.y); @@ -713,19 +715,20 @@ static void doLayout_GmDocument_(iGmDocument *d) { setRange_String(&d->title, line); } /* List bullet. */ - run.textParams.color = colors[type]; + run.color = colors[type]; if (type == bullet_GmLineType) { /* TODO: Literata bullet is broken? */ iGmRun bulRun = run; if (prefs->font == literata_TextFont) { /* Something wrong this the glyph in Literata, looks cropped. */ - bulRun.textParams.font = defaultContentRegular_FontId; + bulRun.font = FONT_ID(default_FontId, regular_FontStyle, + contentRegular_FontSize); } - bulRun.textParams.color = tmQuote_ColorId; + bulRun.color = tmQuote_ColorId; bulRun.visBounds.pos = addX_I2(pos, (indents[text_GmLineType] - 0.55f) * gap_Text); bulRun.visBounds.size = init_I2((indents[bullet_GmLineType] - indents[text_GmLineType]) * gap_Text, - lineHeight_Text(bulRun.textParams.font)); + lineHeight_Text(bulRun.font)); // bulRun.visBounds.pos.x -= 4 * gap_Text - width_Rect(bulRun.visBounds) / 2; bulRun.bounds = zero_Rect(); /* just visual */ bulRun.text = range_CStr(bullet); @@ -737,11 +740,11 @@ static void doLayout_GmDocument_(iGmDocument *d) { if (type == quote_GmLineType && addQuoteIcon) { addQuoteIcon = iFalse; iGmRun quoteRun = run; - quoteRun.textParams.font = heading1_FontId; + quoteRun.font = heading1_FontId; quoteRun.text = range_CStr(quote); - quoteRun.textParams.color = tmQuoteIcon_ColorId; - iRect vis = visualBounds_Text(quoteRun.textParams.font, quoteRun.text); - quoteRun.visBounds.size = measure_Text(quoteRun.textParams.font, quote).bounds.size; + quoteRun.color = tmQuoteIcon_ColorId; + iRect vis = visualBounds_Text(quoteRun.font, quoteRun.text); + quoteRun.visBounds.size = measure_Text(quoteRun.font, quote).bounds.size; quoteRun.visBounds.pos = add_I2(pos, init_I2((indents[quote_GmLineType] - 5) * gap_Text, @@ -757,7 +760,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { if (type == link_GmLineType) { iGmRun icon = run; icon.visBounds.pos = pos; - icon.visBounds.size = init_I2(indent * gap_Text, lineHeight_Text(run.textParams.font)); + icon.visBounds.size = init_I2(indent * gap_Text, lineHeight_Text(run.font)); icon.bounds = zero_Rect(); /* just visual */ const iGmLink *link = constAt_PtrArray(&d->links, run.linkId - 1); const enum iGmLinkScheme scheme = scheme_GmLinkFlag(link->flags); @@ -775,23 +778,23 @@ static void doLayout_GmDocument_(iGmDocument *d) { } /* TODO: List bullets needs the same centering logic. */ /* Special exception for the tiny bullet operator. */ - icon.textParams.font = equal_Rangecc(link->labelIcon, "\u2219") ? regularMonospace_FontId - : regular_FontId; + icon.font = equal_Rangecc(link->labelIcon, "\u2219") ? regularMonospace_FontId + : paragraph_FontId; alignDecoration_GmRun_(&icon, iFalse); - icon.textParams.color = linkColor_GmDocument(d, run.linkId, icon_GmLinkPart); + icon.color = linkColor_GmDocument(d, run.linkId, icon_GmLinkPart); icon.flags |= decoration_GmRunFlag; pushBack_Array(&d->layout, &icon); } - run.textParams.color = colors[type]; + run.color = colors[type]; if (d->format == plainText_SourceFormat) { - run.textParams.color = colors[text_GmLineType]; + run.color = colors[text_GmLineType]; } /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ // int bigCount = 0; iBool isLedeParagraph = iFalse; if (type == text_GmLineType && isFirstText) { - if (!isMono) run.textParams.font = firstParagraph_FontId; - run.textParams.color = tmFirstParagraph_ColorId; + if (!isMono) run.font = firstParagraph_FontId; + run.color = tmFirstParagraph_ColorId; // bigCount = 15; /* max lines -- what if the whole document is one paragraph? */ isLedeParagraph = iTrue; isFirstText = iFalse; @@ -838,7 +841,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { } /* Visited links are never bold. */ if (run.linkId && linkFlags_GmDocument(d, run.linkId) & visited_GmLinkFlag) { - rts.run.textParams.font = paragraph_FontId; + rts.run.font = paragraph_FontId; } } if (!prefs->quoteIcon && type == quote_GmLineType) { @@ -854,7 +857,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { .mode = word_WrapTextMode, .wrapFunc = typesetOneLine_RunTypesetter_, .context = &rts }; - measure_WrapText(&wrapText, rts.run.textParams.font); + measure_WrapText(&wrapText, rts.run.font); if (!isLedeParagraph || size_Array(&rts.layout) <= maxLedeLines_) { if (wrapText.baseDir < 0) { /* Right-aligned paragraphs need margins and decorations to be flipped. */ @@ -880,8 +883,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { } clear_RunTypesetter_(&rts); rts.pos = pos; - rts.run.textParams.font = rts.fonts[text_GmLineType]; - rts.run.textParams.color = colors[text_GmLineType]; + rts.run.font = rts.fonts[text_GmLineType]; + rts.run.color = colors[text_GmLineType]; isLedeParagraph = iFalse; } pos = rts.pos; @@ -922,11 +925,11 @@ static void doLayout_GmDocument_(iGmDocument *d) { run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2; run.bounds.size.y = run.visBounds.size.y; } - run.text = iNullRange; - run.textParams.font = 0; - run.textParams.color = 0; - run.mediaType = image_GmRunMediaType; - run.mediaId = imageId; + run.text = iNullRange; + run.font = 0; + run.color = 0; + run.mediaType = image_GmRunMediaType; + run.mediaId = imageId; pushBack_Array(&d->layout, &run); pos.y += run.bounds.size.y + margin; } @@ -941,9 +944,9 @@ static void doLayout_GmDocument_(iGmDocument *d) { run.bounds.size.y = lineHeight_Text(uiContent_FontId) + 3 * gap_UI; run.visBounds = run.bounds; run.text = iNullRange; - run.textParams.color = 0; - run.mediaType = audio_GmRunMediaType; - run.mediaId = audioId; + run.color = 0; + run.mediaType = audio_GmRunMediaType; + run.mediaId = audioId; pushBack_Array(&d->layout, &run); pos.y += run.bounds.size.y + margin; } @@ -958,7 +961,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { run.bounds.size.y = 2 * lineHeight_Text(uiContent_FontId) + 4 * gap_UI; run.visBounds = run.bounds; run.text = iNullRange; - run.textParams.color = 0; + run.color = 0; run.mediaType = download_GmRunMediaType; run.mediaId = downloadId; pushBack_Array(&d->layout, &run); @@ -1572,11 +1575,11 @@ static void markLinkRunsVisited_GmDocument_(iGmDocument *d, const iIntSet *linkI iForEach(Array, r, &d->layout) { iGmRun *run = r.value; if (run->linkId && !run->mediaId && contains_IntSet(linkIds, run->linkId)) { - if (run->textParams.font == bold_FontId) { - run->textParams.font = paragraph_FontId; + if (run->font == bold_FontId) { + run->font = paragraph_FontId; } else if (run->flags & decoration_GmRunFlag) { - run->textParams.color = linkColor_GmDocument(d, run->linkId, icon_GmLinkPart); + run->color = linkColor_GmDocument(d, run->linkId, icon_GmLinkPart); } } } @@ -2072,7 +2075,7 @@ iRangecc findLoc_GmRun(const iGmRun *d, iInt2 pos) { return (iRangecc){ d->text.start, d->text.start }; } iRangecc loc; - tryAdvanceNoWrap_Text(d->textParams.font, d->text, x, &loc.start); + tryAdvanceNoWrap_Text(d->font, d->text, x, &loc.start); loc.end = loc.start; if (!contains_Range(&d->text, loc.start)) { return iNullRange; /* it's some other text */ -- cgit v1.2.3 From 52b6013cc01e17f4b500ea79fb786ccc14b1f7ec Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 8 Oct 2021 15:27:14 +0300 Subject: Font configuration; Prefs has a string value array Added a second version of Iosevka with more line spacing, to be used as the default monospace document font. --- CMakeLists.txt | 38 ++++++--- res/default.fontpack/fontpack.ini | 27 +++++-- src/app.c | 161 +++++++++++++++++++++++--------------- src/fontpack.c | 112 +++++++++++++++++++------- src/fontpack.h | 16 ++-- src/gmdocument.c | 60 +++++++------- src/prefs.c | 41 ++++------ src/prefs.h | 31 +++++--- src/ui/documentwidget.c | 4 +- src/ui/labelwidget.c | 4 +- src/ui/root.c | 3 +- src/ui/text.c | 77 ++++++++---------- src/ui/text.h | 116 ++++++++++----------------- src/ui/util.c | 73 +++++++++++++---- 14 files changed, 446 insertions(+), 317 deletions(-) (limited to 'src/gmdocument.c') diff --git a/CMakeLists.txt b/CMakeLists.txt index e62b6ae0..409ac2f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,16 +62,28 @@ include (res/Embed.cmake) include (res/Fontpack.cmake) include (Depends.cmake) -# Embedded resources are written to a generated source file. +# Package resources. message (STATUS "Preparing resources...") make_fontpack (res/default.fontpack) -make_fontpack (res/arabic.fontpack) -make_fontpack (res/cjk.fontpack) -make_fontpack (res/firasans.fontpack) -make_fontpack (res/literata.fontpack) -make_fontpack (res/nunito.fontpack) -make_fontpack (res/tinos.fontpack) -# Fonts are too large to comfortably embed as a C source. +# Fonts to install as separate files. +set (FONTPACKS + arabic.fontpack + cjk.fontpack + firasans.fontpack + literata.fontpack + nunito.fontpack + tinos.fontpack +) +foreach (fp ${FONTPACKS}) + make_fontpack (res/${fp}) + set_source_files_properties (${CMAKE_BINARY_DIR}/${fp} + PROPERTIES MACOSX_PACKAGE_LOCATION Resources) +endforeach (fp) +macro (install_fonts dst) + foreach (fp ${FONTPACKS}) + install (FILES ${CMAKE_BINARY_DIR}/${fp} DESTINATION ${dst}) + endforeach (fp) +endmacro () set (EMBED_RESOURCES res/about/about.gmi res/about/help.gmi @@ -221,10 +233,15 @@ set (SOURCES res/about/lagrange.gmi res/about/license.gmi res/about/version.gmi - ${CMAKE_CURRENT_BINARY_DIR}/resources.lgr ${CMAKE_CURRENT_BINARY_DIR}/embedded.c ${CMAKE_CURRENT_BINARY_DIR}/embedded.h + ${CMAKE_CURRENT_BINARY_DIR}/resources.lgr ) +if (APPLE) + foreach (fp ${FONTPACKS}) + list (APPEND SOURCES ${CMAKE_BINARY_DIR}/${fp}) + endforeach (fp) +endif () if (ENABLE_IPC) list (APPEND SOURCES src/ipc.c @@ -424,6 +441,7 @@ if (MSYS) if (NOT ENABLE_RESOURCE_EMBED) install (FILES ${EMB_BIN} DESTINATION .) endif () + install_fonts (.) install (PROGRAMS ${SDL2_LIBDIR}/SDL2.dll res/urlopen.bat @@ -439,6 +457,7 @@ elseif (HAIKU) LAGRANGE_EMB_BIN="${CMAKE_INSTALL_PREFIX}/resources.lgr") install (FILES ${EMB_BIN} DESTINATION .) endif () + install_fonts (.) elseif (UNIX AND NOT APPLE) set_target_properties (app PROPERTIES INSTALL_RPATH_USE_LINK_PATH YES @@ -471,4 +490,5 @@ MimeType=x-scheme-handler/gemini;x-scheme-handler/gopher endif () install (FILES ${EMB_BIN} DESTINATION share/lagrange) endif () + install_fonts (share/lagrange) endif () diff --git a/res/default.fontpack/fontpack.ini b/res/default.fontpack/fontpack.ini index c86dfd59..68316ef6 100644 --- a/res/default.fontpack/fontpack.ini +++ b/res/default.fontpack/fontpack.ini @@ -11,11 +11,14 @@ # monospace (bool) Monospace font; suitable for performatted text. # override (bool) Check this first regardless of active font. # priority (int) Lookup priority (largest checked first). -# scaling (float) Additional glyph scaling (1.0 = not scaled). +# glyphscale (float) Additional glyph scaling (<= 1.0). # voffset (float) Vertical offset (normalized). +# +# `glyphscale` and `voffset` can also be specified separately for the UI and +# document domains by prefixing `ui.` or `doc.` to the key. [default] -name = "Source Sans 3" +name = "Source Sans" regular = "SourceSans3-Regular.ttf" italic = "SourceSans3-It.ttf" light = "SourceSans3-ExtraLight.ttf" @@ -23,13 +26,23 @@ semibold = "SourceSans3-Semibold.ttf" bold = "SourceSans3-Bold.ttf" [iosevka] -name = "Iosevka Term Extended" +name = "Iosevka (compact)" +monospace = true +doc.height = 0.800 +regular = "IosevkaTerm-Extended.ttf" + +[iosevka-body] +# Variant of Iosevka with expanded line spacing for better readability. +# Matches the baseline and ascent of the default font. +name = "Iosevka" monospace = true +priority = -10 +glyphscale = 0.800 regular = "IosevkaTerm-Extended.ttf" [smolemoji] name = "Smol Emoji" -override = true # These Emoji are always preferred. +override = true # These Emoji/symbols are always preferred. auxiliary = true priority = 100 regular = "SmolEmoji-Regular.ttf" @@ -38,14 +51,14 @@ regular = "SmolEmoji-Regular.ttf" name = "Noto Emoji" auxiliary = true priority = 30 -scaling = 1.1 +glyphscale = 1.1 regular = "NotoEmoji-Regular.ttf" [notosymbols2] name = "Noto Sans Symbols 2" auxiliary = true priority = 20 -scaling = 1.45 +glyphscale = 1.45 voffset = 0.5 regular = "NotoSansSymbols2-Regular.ttf" @@ -53,6 +66,6 @@ regular = "NotoSansSymbols2-Regular.ttf" name = "Noto Sans Symbols" auxiliary = true priority = 10 -scaling = 2.0 +glyphscale = 2.0 voffset = 1.2 regular = "NotoSansSymbols-Regular.ttf" diff --git a/src/app.c b/src/app.c index 3a96bd40..ae68324d 100644 --- a/src/app.c +++ b/src/app.c @@ -209,12 +209,16 @@ static iString *serializePrefs_App_(const iApp *d) { } #endif } - appendFormat_String(str, "uilang id:%s\n", cstr_String(&d->prefs.uiLanguage)); + appendFormat_String(str, "uilang id:%s\n", cstr_String(&d->prefs.strings[uiLanguage_PrefsString])); appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(as_Window(d->window))); appendFormat_String(str, "prefs.dialogtab arg:%d\n", d->prefs.dialogTab); - appendFormat_String(str, "font.set arg:%d\n", d->prefs.font); - appendFormat_String(str, "font.user path:%s\n", cstr_String(&d->prefs.symbolFontPath)); - appendFormat_String(str, "headingfont.set arg:%d\n", d->prefs.headingFont); + appendFormat_String(str, + "font.set ui:%s heading:%s body:%s mono:%s monodoc:%s\n", + cstr_String(&d->prefs.strings[uiFont_PrefsString]), + cstr_String(&d->prefs.strings[headingFont_PrefsString]), + cstr_String(&d->prefs.strings[bodyFont_PrefsString]), + cstr_String(&d->prefs.strings[monospaceFont_PrefsString]), + cstr_String(&d->prefs.strings[monospaceDocumentFont_PrefsString])); appendFormat_String(str, "zoom.set arg:%d\n", d->prefs.zoomPercent); appendFormat_String(str, "smoothscroll arg:%d\n", d->prefs.smoothScrolling); appendFormat_String(str, "scrollspeed arg:%d type:%d\n", d->prefs.smoothScrollSpeed[keyboard_ScrollType], keyboard_ScrollType); @@ -247,15 +251,15 @@ static iString *serializePrefs_App_(const iApp *d) { appendFormat_String(str, "doctheme.light.set arg:%d\n", d->prefs.docThemeLight); appendFormat_String(str, "saturation.set arg:%d\n", (int) ((d->prefs.saturation * 100) + 0.5f)); appendFormat_String(str, "imagestyle.set arg:%d\n", d->prefs.imageStyle); - appendFormat_String(str, "ca.file noset:1 path:%s\n", cstr_String(&d->prefs.caFile)); - appendFormat_String(str, "ca.path path:%s\n", cstr_String(&d->prefs.caPath)); - appendFormat_String(str, "proxy.gemini address:%s\n", cstr_String(&d->prefs.geminiProxy)); - appendFormat_String(str, "proxy.gopher address:%s\n", cstr_String(&d->prefs.gopherProxy)); - appendFormat_String(str, "proxy.http address:%s\n", cstr_String(&d->prefs.httpProxy)); + appendFormat_String(str, "ca.file noset:1 path:%s\n", cstr_String(&d->prefs.strings[caFile_PrefsString])); + appendFormat_String(str, "ca.path path:%s\n", cstr_String(&d->prefs.strings[caPath_PrefsString])); + appendFormat_String(str, "proxy.gemini address:%s\n", cstr_String(&d->prefs.strings[geminiProxy_PrefsString])); + appendFormat_String(str, "proxy.gopher address:%s\n", cstr_String(&d->prefs.strings[gopherProxy_PrefsString])); + appendFormat_String(str, "proxy.http address:%s\n", cstr_String(&d->prefs.strings[httpProxy_PrefsString])); #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) - appendFormat_String(str, "downloads path:%s\n", cstr_String(&d->prefs.downloadDir)); + appendFormat_String(str, "downloads path:%s\n", cstr_String(&d->prefs.strings[downloadDir_PrefsString])); #endif - appendFormat_String(str, "searchurl address:%s\n", cstr_String(&d->prefs.searchUrl)); + appendFormat_String(str, "searchurl address:%s\n", cstr_String(&d->prefs.strings[searchUrl_PrefsString])); appendFormat_String(str, "translation.languages from:%d to:%d\n", d->prefs.langFrom, d->prefs.langTo); return str; } @@ -329,7 +333,7 @@ static void loadPrefs_App_(iApp *d) { } else if (equal_Command(cmd, "uilang")) { const char *id = cstr_Rangecc(range_Command(cmd, "id")); - setCStr_String(&d->prefs.uiLanguage, id); + setCStr_String(&d->prefs.strings[uiLanguage_PrefsString], id); setCurrent_Lang(id); } else if (equal_Command(cmd, "ca.file") || equal_Command(cmd, "ca.path")) { @@ -360,7 +364,8 @@ static void loadPrefs_App_(iApp *d) { } if (!haveCA) { /* Default CA setup. */ - setCACertificates_TlsRequest(&d->prefs.caFile, &d->prefs.caPath); + setCACertificates_TlsRequest(&d->prefs.strings[caFile_PrefsString], + &d->prefs.strings[caPath_PrefsString]); } #if !defined (LAGRANGE_ENABLE_CUSTOM_FRAME) d->prefs.customFrame = iFalse; @@ -787,7 +792,7 @@ static void init_App_(iApp *d, int argc, char **argv) { #endif init_Prefs(&d->prefs); init_SiteSpec(dataDir_App_()); - setCStr_String(&d->prefs.downloadDir, downloadDir_App_()); + setCStr_String(&d->prefs.strings[downloadDir_PrefsString], downloadDir_App_()); set_Atomic(&d->pendingRefresh, iFalse); d->isRunning = iFalse; d->window = NULL; @@ -918,7 +923,7 @@ const iString *dataDir_App(void) { } const iString *downloadDir_App(void) { - return collect_String(cleaned_Path(&app_.prefs.downloadDir)); + return collect_String(cleaned_Path(&app_.prefs.strings[downloadDir_PrefsString])); } const iString *downloadPathForUrl_App(const iString *url, const iString *mime) { @@ -1517,13 +1522,13 @@ const iString *schemeProxy_App(iRangecc scheme) { iApp *d = &app_; const iString *proxy = NULL; if (equalCase_Rangecc(scheme, "gemini")) { - proxy = &d->prefs.geminiProxy; + proxy = &d->prefs.strings[geminiProxy_PrefsString]; } else if (equalCase_Rangecc(scheme, "gopher")) { - proxy = &d->prefs.gopherProxy; + proxy = &d->prefs.strings[gopherProxy_PrefsString]; } else if (equalCase_Rangecc(scheme, "http") || equalCase_Rangecc(scheme, "https")) { - proxy = &d->prefs.httpProxy; + proxy = &d->prefs.strings[httpProxy_PrefsString]; } return isEmpty_String(proxy) ? NULL : proxy; } @@ -1727,9 +1732,9 @@ static void updateColorThemeButton_(iLabelWidget *button, int theme) { updateDropdownSelection_LabelWidget(button, format_CStr(".set arg:%d", theme)); } -static void updateFontButton_(iLabelWidget *button, int font) { - if (!button) return; - updateDropdownSelection_LabelWidget(button, format_CStr(".set arg:%d", font)); +static void updateFontButton_(iLabelWidget *button, const iString *fontId) { + if (!button || isEmpty_String(fontId)) return; + updateDropdownSelection_LabelWidget(button, format_CStr(":%s", cstr_String(fontId))); } static void updateImageStyleButton_(iLabelWidget *button, int style) { @@ -1823,11 +1828,11 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { return iFalse; } else if (equal_Command(cmd, "font.set")) { - updateFontButton_(findChild_Widget(d, "prefs.font"), arg_Command(cmd)); - return iFalse; - } - else if (equal_Command(cmd, "headingfont.set")) { - updateFontButton_(findChild_Widget(d, "prefs.headingfont"), arg_Command(cmd)); + updateFontButton_(findChild_Widget(d, "prefs.font.ui"), string_Command(cmd, "ui")); + updateFontButton_(findChild_Widget(d, "prefs.font.heading"), string_Command(cmd, "heading")); + updateFontButton_(findChild_Widget(d, "prefs.font.body"), string_Command(cmd, "body")); + updateFontButton_(findChild_Widget(d, "prefs.font.mono"), string_Command(cmd, "mono")); + updateFontButton_(findChild_Widget(d, "prefs.font.monodoc"), string_Command(cmd, "monodoc")); return iFalse; } else if (startsWith_CStr(cmd, "input.ended id:prefs.linespacing")) { @@ -2040,11 +2045,12 @@ iBool willUseProxy_App(const iRangecc scheme) { const iString *searchQueryUrl_App(const iString *queryStringUnescaped) { iApp *d = &app_; - if (isEmpty_String(&d->prefs.searchUrl)) { + if (isEmpty_String(&d->prefs.strings[searchUrl_PrefsString])) { return collectNew_String(); } const iString *escaped = urlEncode_String(queryStringUnescaped); - return collectNewFormat_String("%s?%s", cstr_String(&d->prefs.searchUrl), cstr_String(escaped)); + return collectNewFormat_String( + "%s?%s", cstr_String(&d->prefs.strings[searchUrl_PrefsString]), cstr_String(escaped)); } static void resetFonts_App_(iApp *d) { @@ -2074,9 +2080,10 @@ iBool handleCommand_App(const char *cmd) { } else if (equal_Command(cmd, "uilang")) { const iString *lang = string_Command(cmd, "id"); - if (!equal_String(lang, &d->prefs.uiLanguage)) { - set_String(&d->prefs.uiLanguage, lang); - setCurrent_Lang(cstr_String(&d->prefs.uiLanguage)); + iString *val = &d->prefs.strings[uiLanguage_PrefsString]; + if (!equal_String(lang, val)) { + set_String(val, lang); + setCurrent_Lang(cstr_String(val)); postCommand_App("lang.changed"); } return iTrue; @@ -2148,14 +2155,38 @@ iBool handleCommand_App(const char *cmd) { if (!isFrozen) { setFreezeDraw_MainWindow(get_MainWindow(), iTrue); } - d->prefs.font = arg_Command(cmd); - setContentFont_Text(text_Window(d->window), d->prefs.font); + struct { + const char *label; + enum iPrefsString ps; + int fontId; + } params[] = { + { "ui", uiFont_PrefsString, default_FontId }, + { "mono", monospaceFont_PrefsString, monospace_FontId }, + { "heading", headingFont_PrefsString, documentHeading_FontId }, + { "body", bodyFont_PrefsString, documentBody_FontId }, + { "monodoc", monospaceDocumentFont_PrefsString, documentMonospace_FontId }, + }; + iBool wasChanged = iFalse; + iForIndices(i, params) { + if (hasLabel_Command(cmd, params[i].label)) { + iString *ps = &d->prefs.strings[params[i].ps]; + const iString *newFont = string_Command(cmd, params[i].label); + if (!equal_String(ps, newFont)) { + set_String(ps, newFont); + wasChanged = iTrue; + } + } + } + if (wasChanged) { + resetFonts_Text(text_Window(get_MainWindow())); + } if (!isFrozen) { postCommand_App("font.changed"); postCommand_App("window.unfreeze"); } return iTrue; } +#if 0 else if (equal_Command(cmd, "headingfont.set")) { if (!isFrozen) { setFreezeDraw_MainWindow(get_MainWindow(), iTrue); @@ -2168,12 +2199,13 @@ iBool handleCommand_App(const char *cmd) { } return iTrue; } +#endif else if (equal_Command(cmd, "zoom.set")) { if (!isFrozen) { setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */ } d->prefs.zoomPercent = arg_Command(cmd); - setContentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f); + setDocumentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f); if (!isFrozen) { postCommand_App("font.changed"); postCommand_App("window.unfreeze"); @@ -2189,7 +2221,7 @@ iBool handleCommand_App(const char *cmd) { delta /= 2; } d->prefs.zoomPercent = iClamp(d->prefs.zoomPercent + delta, 50, 200); - setContentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f); + setDocumentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f); if (!isFrozen) { postCommand_App("font.changed"); postCommand_App("window.unfreeze"); @@ -2388,7 +2420,7 @@ iBool handleCommand_App(const char *cmd) { return iTrue; } else if (equal_Command(cmd, "searchurl")) { - iString *url = &d->prefs.searchUrl; + iString *url = &d->prefs.strings[searchUrl_PrefsString]; setCStr_String(url, suffixPtr_Command(cmd, "address")); if (startsWith_String(url, "//")) { prependCStr_String(url, "gemini:"); @@ -2399,20 +2431,20 @@ iBool handleCommand_App(const char *cmd) { return iTrue; } else if (equal_Command(cmd, "proxy.gemini")) { - setCStr_String(&d->prefs.geminiProxy, suffixPtr_Command(cmd, "address")); + setCStr_String(&d->prefs.strings[geminiProxy_PrefsString], suffixPtr_Command(cmd, "address")); return iTrue; } else if (equal_Command(cmd, "proxy.gopher")) { - setCStr_String(&d->prefs.gopherProxy, suffixPtr_Command(cmd, "address")); + setCStr_String(&d->prefs.strings[gopherProxy_PrefsString], suffixPtr_Command(cmd, "address")); return iTrue; } else if (equal_Command(cmd, "proxy.http")) { - setCStr_String(&d->prefs.httpProxy, suffixPtr_Command(cmd, "address")); + setCStr_String(&d->prefs.strings[httpProxy_PrefsString], suffixPtr_Command(cmd, "address")); return iTrue; } #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) else if (equal_Command(cmd, "downloads")) { - setCStr_String(&d->prefs.downloadDir, suffixPtr_Command(cmd, "path")); + setCStr_String(&d->prefs.strings[downloadDir_PrefsString], suffixPtr_Command(cmd, "path")); return iTrue; } #endif @@ -2421,16 +2453,16 @@ iBool handleCommand_App(const char *cmd) { return iTrue; } else if (equal_Command(cmd, "ca.file")) { - setCStr_String(&d->prefs.caFile, suffixPtr_Command(cmd, "path")); + setCStr_String(&d->prefs.strings[caFile_PrefsString], suffixPtr_Command(cmd, "path")); if (!argLabel_Command(cmd, "noset")) { - setCACertificates_TlsRequest(&d->prefs.caFile, &d->prefs.caPath); + setCACertificates_TlsRequest(&d->prefs.strings[caFile_PrefsString], &d->prefs.strings[caPath_PrefsString]); } return iTrue; } else if (equal_Command(cmd, "ca.path")) { - setCStr_String(&d->prefs.caPath, suffixPtr_Command(cmd, "path")); + setCStr_String(&d->prefs.strings[caPath_PrefsString], suffixPtr_Command(cmd, "path")); if (!argLabel_Command(cmd, "noset")) { - setCACertificates_TlsRequest(&d->prefs.caFile, &d->prefs.caPath); + setCACertificates_TlsRequest(&d->prefs.strings[caFile_PrefsString], &d->prefs.strings[caPath_PrefsString]); } return iTrue; } @@ -2465,7 +2497,7 @@ iBool handleCommand_App(const char *cmd) { return iTrue; } if (argLabel_Command(cmd, "default") || equalCase_Rangecc(parts.scheme, "mailto") || - ((noProxy || isEmpty_String(&d->prefs.httpProxy)) && + ((noProxy || isEmpty_String(&d->prefs.strings[httpProxy_PrefsString])) && (equalCase_Rangecc(parts.scheme, "http") || equalCase_Rangecc(parts.scheme, "https")))) { openInDefaultBrowser_App(url); @@ -2653,7 +2685,7 @@ iBool handleCommand_App(const char *cmd) { else if (equal_Command(cmd, "preferences")) { iWidget *dlg = makePreferences_Widget(); updatePrefsThemeButtons_(dlg); - setText_InputWidget(findChild_Widget(dlg, "prefs.downloads"), &d->prefs.downloadDir); + setText_InputWidget(findChild_Widget(dlg, "prefs.downloads"), &d->prefs.strings[downloadDir_PrefsString]); setToggle_Widget(findChild_Widget(dlg, "prefs.hoverlink"), d->prefs.hoverLink); setToggle_Widget(findChild_Widget(dlg, "prefs.smoothscroll"), d->prefs.smoothScrolling); setToggle_Widget(findChild_Widget(dlg, "prefs.imageloadscroll"), d->prefs.loadImageInsteadOfScrolling); @@ -2662,24 +2694,24 @@ iBool handleCommand_App(const char *cmd) { setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme); setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame); setToggle_Widget(findChild_Widget(dlg, "prefs.animate"), d->prefs.uiAnimations); - setText_InputWidget(findChild_Widget(dlg, "prefs.userfont"), &d->prefs.symbolFontPath); +// setText_InputWidget(findChild_Widget(dlg, "prefs.userfont"), &d->prefs.symbolFontPath); updatePrefsPinSplitButtons_(dlg, d->prefs.pinSplit); updateScrollSpeedButtons_(dlg, mouse_ScrollType, d->prefs.smoothScrollSpeed[mouse_ScrollType]); updateScrollSpeedButtons_(dlg, keyboard_ScrollType, d->prefs.smoothScrollSpeed[keyboard_ScrollType]); - updateDropdownSelection_LabelWidget(findChild_Widget(dlg, "prefs.uilang"), cstr_String(&d->prefs.uiLanguage)); + updateDropdownSelection_LabelWidget(findChild_Widget(dlg, "prefs.uilang"), cstr_String(&d->prefs.strings[uiLanguage_PrefsString])); updateDropdownSelection_LabelWidget( findChild_Widget(dlg, "prefs.returnkey"), format_CStr("returnkey.set arg:%d", d->prefs.returnKey)); setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize); setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"), collectNewFormat_String("%g", uiScale_Window(as_Window(d->window)))); - setFlags_Widget(findChild_Widget(dlg, format_CStr("prefs.font.%d", d->prefs.font)), - selected_WidgetFlag, - iTrue); - setFlags_Widget( - findChild_Widget(dlg, format_CStr("prefs.headingfont.%d", d->prefs.headingFont)), - selected_WidgetFlag, - iTrue); +// setFlags_Widget(findChild_Widget(dlg, format_CStr("prefs.font.%d", d->prefs.font)), +// selected_WidgetFlag, +// iTrue); +// setFlags_Widget( +// findChild_Widget(dlg, format_CStr("prefs.headingfont.%d", d->prefs.headingFont)), +// selected_WidgetFlag, +// iTrue); setFlags_Widget(findChild_Widget(dlg, "prefs.mono.gemini"), selected_WidgetFlag, d->prefs.monospaceGemini); @@ -2710,8 +2742,11 @@ iBool handleCommand_App(const char *cmd) { updateColorThemeButton_(findChild_Widget(dlg, "prefs.doctheme.dark"), d->prefs.docThemeDark); updateColorThemeButton_(findChild_Widget(dlg, "prefs.doctheme.light"), d->prefs.docThemeLight); updateImageStyleButton_(findChild_Widget(dlg, "prefs.imagestyle"), d->prefs.imageStyle); - updateFontButton_(findChild_Widget(dlg, "prefs.font"), d->prefs.font); - updateFontButton_(findChild_Widget(dlg, "prefs.headingfont"), d->prefs.headingFont); + updateFontButton_(findChild_Widget(dlg, "prefs.font.ui"), &d->prefs.strings[uiFont_PrefsString]); + updateFontButton_(findChild_Widget(dlg, "prefs.font.heading"), &d->prefs.strings[headingFont_PrefsString]); + updateFontButton_(findChild_Widget(dlg, "prefs.font.body"), &d->prefs.strings[bodyFont_PrefsString]); + updateFontButton_(findChild_Widget(dlg, "prefs.font.mono"), &d->prefs.strings[monospaceFont_PrefsString]); + updateFontButton_(findChild_Widget(dlg, "prefs.font.monodoc"), &d->prefs.strings[monospaceDocumentFont_PrefsString]); setFlags_Widget( findChild_Widget( dlg, format_CStr("prefs.saturation.%d", (int) (d->prefs.saturation * 3.99f))), @@ -2722,12 +2757,12 @@ iBool handleCommand_App(const char *cmd) { setText_InputWidget(findChild_Widget(dlg, "prefs.memorysize"), collectNewFormat_String("%d", d->prefs.maxMemorySize)); setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs); - setText_InputWidget(findChild_Widget(dlg, "prefs.searchurl"), &d->prefs.searchUrl); - setText_InputWidget(findChild_Widget(dlg, "prefs.ca.file"), &d->prefs.caFile); - setText_InputWidget(findChild_Widget(dlg, "prefs.ca.path"), &d->prefs.caPath); - setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gemini"), &d->prefs.geminiProxy); - setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gopher"), &d->prefs.gopherProxy); - setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.http"), &d->prefs.httpProxy); + setText_InputWidget(findChild_Widget(dlg, "prefs.searchurl"), &d->prefs.strings[searchUrl_PrefsString]); + setText_InputWidget(findChild_Widget(dlg, "prefs.ca.file"), &d->prefs.strings[caFile_PrefsString]); + setText_InputWidget(findChild_Widget(dlg, "prefs.ca.path"), &d->prefs.strings[caPath_PrefsString]); + setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gemini"), &d->prefs.strings[geminiProxy_PrefsString]); + setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.gopher"), &d->prefs.strings[gopherProxy_PrefsString]); + setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.http"), &d->prefs.strings[httpProxy_PrefsString]); iWidget *tabs = findChild_Widget(dlg, "prefs.tabs"); if (tabs) { showTabPage_Widget(tabs, tabPage_Widget(tabs, d->prefs.dialogTab)); diff --git a/src/fontpack.c b/src/fontpack.c index a1f3b829..3b42e848 100644 --- a/src/fontpack.c +++ b/src/fontpack.c @@ -32,6 +32,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "embedded.h" +/* TODO: Clean up and/or reorder this file, it's a bit unorganized. */ + float scale_FontSize(enum iFontSize size) { static const float sizes[max_FontSize] = { 1.000, /* UI sizes */ @@ -46,8 +48,7 @@ float scale_FontSize(enum iFontSize size) { 1.666, 2.000, 0.568, - 0.710, - 0.800, + 0.710, /* calibration: fits the Lagrange title screen with Normal line width */ }; if (size < 0 || size >= max_FontSize) { return 1.0f; @@ -66,9 +67,30 @@ struct Impl_Fonts { static iFonts fonts_; +static void unloadFiles_Fonts_(iFonts *d) { + /* TODO: Mark all files in font packs as not resident. */ + iForEach(PtrArray, i, &d->files) { + delete_FontFile(i.ptr); + } + clear_PtrArray(&d->files); +} + +static iFontFile *findFile_Fonts_(iFonts *d, const iString *id) { + iForEach(PtrArray, i, &d->files) { + iFontFile *ff = i.ptr; + if (equal_String(&ff->id, id)) { + return ff; + } + } + return NULL; +} + +/*----------------------------------------------------------------------------------------------*/ + iDefineTypeConstruction(FontFile) void init_FontFile(iFontFile *d) { + init_String(&d->id); d->style = regular_FontStyle; init_Block(&d->sourceData, 0); iZap(d->stbInfo); @@ -111,6 +133,7 @@ static void unload_FontFile_(iFontFile *d) { void deinit_FontFile(iFontFile *d) { unload_FontFile_(d); deinit_Block(&d->sourceData); + deinit_String(&d->id); } float scaleForPixelHeight_FontFile(const iFontFile *d, int pixelHeight) { @@ -140,8 +163,11 @@ void init_FontSpec(iFontSpec *d) { init_String(&d->name); d->flags = 0; d->priority = 0; - d->scaling = 1.0f; - d->vertOffset = 1.0f; + for (int i = 0; i < 2; ++i) { + d->heightScale[i] = 1.0f; + d->glyphScale[i] = 1.0f; + d->vertOffsetScale[i] = 1.0f; + } iZap(d->styles); } @@ -154,12 +180,12 @@ void deinit_FontSpec(iFontSpec *d) { iDeclareType(FontPack) iDeclareTypeConstruction(FontPack) - + struct Impl_FontPack { const iArchive *archive; /* opened ZIP archive */ - iArray fonts; /* array of FontSpecs */ - iString * loadPath; - iFontSpec *loadSpec; + iArray fonts; /* array of FontSpecs */ + iString * loadPath; + iFontSpec * loadSpec; }; void init_FontPack(iFontPack *d) { @@ -238,11 +264,27 @@ void handleIniKeyValue_FontPack_(void *context, const iString *table, const iStr else if (!cmp_String(key, "priority") && value->type == int64_TomlType) { d->loadSpec->priority = (int) value->value.int64; } - else if (!cmp_String(key, "scaling")) { - d->loadSpec->scaling = (float) number_TomlValue(value); + else if (!cmp_String(key, "height")) { + d->loadSpec->heightScale[0] = d->loadSpec->heightScale[1] = (float) number_TomlValue(value); + } + else if (!cmp_String(key, "glyphscale")) { + d->loadSpec->glyphScale[0] = d->loadSpec->glyphScale[1] = (float) number_TomlValue(value); } else if (!cmp_String(key, "voffset")) { - d->loadSpec->vertOffset = (float) number_TomlValue(value); + d->loadSpec->vertOffsetScale[0] = d->loadSpec->vertOffsetScale[1] = + (float) number_TomlValue(value); + } + else if (startsWith_String(key, "ui.") || startsWith_String(key, "doc.")) { + const int scope = startsWith_String(key, "ui.") ? 0 : 1; + if (endsWith_String(key, ".height")) { + d->loadSpec->heightScale[scope] = (float) number_TomlValue(value); + } + if (endsWith_String(key, ".glyphscale")) { + d->loadSpec->glyphScale[scope] = (float) number_TomlValue(value); + } + else if (endsWith_String(key, ".voffset")) { + d->loadSpec->vertOffsetScale[scope] = (float) number_TomlValue(value); + } } else if (!cmp_String(key, "override") && value->type == boolean_TomlType) { iChangeFlags(d->loadSpec->flags, override_FontSpecFlag, value->value.boolean); @@ -265,14 +307,20 @@ void handleIniKeyValue_FontPack_(void *context, const iString *table, const iStr iForIndices(i, styles) { if (!cmp_String(key, styles[i]) && !d->loadSpec->styles[i]) { iFontFile *ff = NULL; - iBlock *data = readFile_FontPack_(d, value->value.string); - if (data) { - ff = new_FontFile(); - load_FontFile_(ff, data); - pushBack_PtrArray(&fonts_.files, ff); /* centralized ownership */ - d->loadSpec->styles[i] = ff; - delete_Block(data); + iString *fontFileId = concat_Path(d->loadPath, value->value.string); + if (!(ff = findFile_Fonts_(&fonts_, fontFileId))) { + iBlock *data = readFile_FontPack_(d, value->value.string); + if (data) { + ff = new_FontFile(); + set_String(&ff->id, fontFileId); + load_FontFile_(ff, data); + pushBack_PtrArray(&fonts_.files, ff); /* centralized ownership */ + delete_Block(data); +// printf("[FontPack] loaded file: %s\n", cstr_String(fontFileId)); + } } + d->loadSpec->styles[i] = ff; + delete_String(fontFileId); break; } } @@ -290,7 +338,6 @@ static iBool load_FontPack_(iFontPack *d, const iString *ini) { setHandlers_TomlParser(toml, handleIniTable_FontPack_, handleIniKeyValue_FontPack_, d); if (parse_TomlParser(toml, ini)) { ok = iTrue; - // fprintf(stderr, "[FontPack] error parsing %s\n", cstr_String(iniPath)); } iAssert(d->loadSpec == NULL); // d->loadPath = NULL; @@ -331,7 +378,7 @@ iBool loadArchive_FontPack(iFontPack *d, const iArchive *zip) { initBlock_String(&ini, iniData); if (load_FontPack_(d, &ini)) { ok = iTrue; - } + } deinit_String(&ini); } return ok; @@ -339,14 +386,6 @@ iBool loadArchive_FontPack(iFontPack *d, const iArchive *zip) { /*----------------------------------------------------------------------------------------------*/ -static void unloadFiles_Fonts_(iFonts *d) { - /* TODO: Mark all files in font packs as not resident. */ - iForEach(PtrArray, i, &d->files) { - delete_FontFile(i.ptr); - } - clear_PtrArray(&d->files); -} - static void unloadFonts_Fonts_(iFonts *d) { iForEach(PtrArray, i, &d->packs) { iFontPack *pack = i.ptr; @@ -360,6 +399,11 @@ static int cmpPriority_FontSpecPtr_(const void *a, const void *b) { return -iCmp((*p1)->priority, (*p2)->priority); /* highest priority first */ } +static int cmpName_FontSpecPtr_(const void *a, const void *b) { + const iFontSpec **p1 = (const iFontSpec **) a, **p2 = (const iFontSpec **) b; + return cmpStringCase_String(&(*p1)->name, &(*p2)->name); +} + static void sortSpecs_Fonts_(iFonts *d) { clear_PtrArray(&d->specOrder); iConstForEach(PtrArray, p, &d->packs) { @@ -410,6 +454,18 @@ const iFontSpec *findSpec_Fonts(const char *fontId) { return NULL; } +const iPtrArray *listSpecs_Fonts(iBool (*filterFunc)(const iFontSpec *)) { + iFonts *d = &fonts_; + iPtrArray *list = collectNew_PtrArray(); + iConstForEach(PtrArray, i, &d->specOrder) { + if (filterFunc == NULL || filterFunc(i.ptr)) { + pushBack_PtrArray(list, i.ptr); + } + } + sort_Array(list, cmpName_FontSpecPtr_); + return list; +} + const iPtrArray *listSpecsByPriority_Fonts(void) { return &fonts_.specOrder; } diff --git a/src/fontpack.h b/src/fontpack.h index c5aa993b..e59154e3 100644 --- a/src/fontpack.h +++ b/src/fontpack.h @@ -50,9 +50,8 @@ enum iFontSize { contentBig_FontSize, contentLarge_FontSize, contentHuge_FontSize, - contentMonoSmall_FontSize, - contentMono_FontSize, - contentSmall_FontSize, + contentTiny_FontSize, + contentSmall_FontSize, /* e.g., preformatted block scaled smaller to fit */ max_FontSize }; @@ -84,6 +83,7 @@ iDeclareType(FontFile) iDeclareTypeConstruction(FontFile) struct Impl_FontFile { + iString id; /* for detecting when the same file is used in many places */ enum iFontStyle style; iBlock sourceData; stbtt_fontinfo stbInfo; @@ -112,13 +112,19 @@ struct Impl_FontSpec { iString name; /* human-readable label */ int flags; int priority; - float scaling; - float vertOffset; + float heightScale[2]; /* overall height scaling; ui, document */ + float glyphScale[2]; /* ui, document */ + float vertOffsetScale[2]; /* ui, document */ const iFontFile *styles[max_FontStyle]; }; + +iLocalDef int scaleType_FontSpec(enum iFontSize sizeId) { + return sizeId / contentRegular_FontSize; +} void init_Fonts (const char *userDir); void deinit_Fonts (void); const iFontSpec * findSpec_Fonts (const char *fontId); +const iPtrArray * listSpecs_Fonts (iBool (*filterFunc)(const iFontSpec *)); const iPtrArray * listSpecsByPriority_Fonts (void); diff --git a/src/gmdocument.c b/src/gmdocument.c index ce9fdec8..c98b0bb8 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -468,22 +468,23 @@ static void doLayout_GmDocument_(iGmDocument *d) { const iBool isDarkBg = isDark_GmDocumentTheme( isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight); /* TODO: Collect these parameters into a GmTheme. */ + const enum iFontId headingFont = isMono ? documentMonospace_FontId : documentHeading_FontId; + const enum iFontId bodyFont = isMono ? documentMonospace_FontId : documentBody_FontId; const int fonts[max_GmLineType] = { - isMono ? regularMonospace_FontId : paragraph_FontId, - isMono ? regularMonospace_FontId : paragraph_FontId, /* bullet */ - preformatted_FontId, - isMono ? regularMonospace_FontId : quote_FontId, - heading1_FontId, - heading2_FontId, - heading3_FontId, - isMono ? regularMonospace_FontId - : ((isDarkBg && prefs->boldLinkDark) || (!isDarkBg && prefs->boldLinkLight)) - ? bold_FontId - : paragraph_FontId, - }; - float indents[max_GmLineType] = { - 5, 10, 5, isNarrow ? 5 : 10, 0, 0, 0, 5 + FONT_ID(bodyFont, regular_FontStyle, contentRegular_FontSize), /* text */ + FONT_ID(bodyFont, regular_FontStyle, contentRegular_FontSize), /* bullet */ + preformatted_FontId, /* pre */ + isMono ? monospaceParagraph_FontId : quote_FontId, /* quote */ + FONT_ID(headingFont, bold_FontStyle, contentHuge_FontSize), /* h1 */ + FONT_ID(headingFont, bold_FontStyle, contentLarge_FontSize), /* h2 */ + FONT_ID(headingFont, regular_FontStyle, contentBig_FontSize), /* h3 */ + FONT_ID(bodyFont, + ((isDarkBg && prefs->boldLinkDark) || (!isDarkBg && prefs->boldLinkLight)) + ? semiBold_FontStyle + : regular_FontStyle, + contentRegular_FontSize) /* link */ }; + float indents[max_GmLineType] = { 5, 10, 5, isNarrow ? 5 : 10, 0, 0, 0, 5 }; if (isExtremelyNarrow) { /* Further reduce the margins. */ indents[text_GmLineType] -= 5; @@ -562,10 +563,13 @@ static void doLayout_GmDocument_(iGmDocument *d) { iGmPreMeta meta = { .bounds = line }; meta.pixelRect.size = measurePreformattedBlock_GmDocument_( d, line.start, preFont, &meta.contents, &meta.bounds.end); - if (meta.pixelRect.size.x > - d->size.x - (enableIndents ? indents[preformatted_GmLineType] : 0) * gap_Text) { - preFont = preformattedSmall_FontId; - meta.pixelRect.size = measureRange_Text(preFont, meta.contents).bounds.size; + const float oversizeRatio = + meta.pixelRect.size.x / + (float) (d->size.x - + (enableIndents ? indents[preformatted_GmLineType] : 0) * gap_Text); + if (oversizeRatio > 1.0f) { + preFont--; /* one notch smaller in the font size */ + meta.pixelRect.size = measureRange_Text(preFont, meta.contents).bounds.size; } trimLine_Rangecc(&line, type, isNormalized); meta.altText = line; /* without the ``` */ @@ -611,7 +615,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { continue; } run.preId = preId; - run.font = (d->format == plainText_SourceFormat ? regularMonospace_FontId : preFont); + run.font = (d->format == plainText_SourceFormat ? plainText_FontId : preFont); indent = indents[type]; } if (addSiteBanner) { @@ -719,11 +723,13 @@ static void doLayout_GmDocument_(iGmDocument *d) { if (type == bullet_GmLineType) { /* TODO: Literata bullet is broken? */ iGmRun bulRun = run; +#if 0 if (prefs->font == literata_TextFont) { /* Something wrong this the glyph in Literata, looks cropped. */ bulRun.font = FONT_ID(default_FontId, regular_FontStyle, contentRegular_FontSize); } +#endif bulRun.color = tmQuote_ColorId; bulRun.visBounds.pos = addX_I2(pos, (indents[text_GmLineType] - 0.55f) * gap_Text); bulRun.visBounds.size = @@ -778,7 +784,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { } /* TODO: List bullets needs the same centering logic. */ /* Special exception for the tiny bullet operator. */ - icon.font = equal_Rangecc(link->labelIcon, "\u2219") ? regularMonospace_FontId + icon.font = equal_Rangecc(link->labelIcon, "\u2219") ? preformatted_FontId : paragraph_FontId; alignDecoration_GmRun_(&icon, iFalse); icon.color = linkColor_GmDocument(d, run.linkId, icon_GmLinkPart); @@ -882,9 +888,9 @@ static void doLayout_GmDocument_(iGmDocument *d) { break; } clear_RunTypesetter_(&rts); - rts.pos = pos; - rts.run.font = rts.fonts[text_GmLineType]; - rts.run.color = colors[text_GmLineType]; + rts.pos = pos; + rts.run.font = rts.fonts[text_GmLineType]; + rts.run.color = colors[text_GmLineType]; isLedeParagraph = iFalse; } pos = rts.pos; @@ -925,7 +931,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2; run.bounds.size.y = run.visBounds.size.y; } - run.text = iNullRange; + run.text = iNullRange; run.font = 0; run.color = 0; run.mediaType = image_GmRunMediaType; @@ -944,9 +950,9 @@ static void doLayout_GmDocument_(iGmDocument *d) { run.bounds.size.y = lineHeight_Text(uiContent_FontId) + 3 * gap_UI; run.visBounds = run.bounds; run.text = iNullRange; - run.color = 0; - run.mediaType = audio_GmRunMediaType; - run.mediaId = audioId; + run.color = 0; + run.mediaType = audio_GmRunMediaType; + run.mediaId = audioId; pushBack_Array(&d->layout, &run); pos.y += run.bounds.size.y + margin; } diff --git a/src/prefs.c b/src/prefs.c index 088cc7bc..4d079402 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -25,6 +25,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include void init_Prefs(iPrefs *d) { + iForIndices(i, d->strings) { + init_String(&d->strings[i]); + } d->dialogTab = 0; d->langFrom = 3; /* fr */ d->langTo = 2; /* en */ @@ -50,8 +53,11 @@ void init_Prefs(iPrefs *d) { d->decodeUserVisibleURLs = iTrue; d->maxCacheSize = 10; d->maxMemorySize = 200; - d->font = nunito_TextFont; - d->headingFont = nunito_TextFont; + setCStr_String(&d->strings[uiFont_PrefsString], "default"); + setCStr_String(&d->strings[headingFont_PrefsString], "default"); + setCStr_String(&d->strings[bodyFont_PrefsString], "default"); + setCStr_String(&d->strings[monospaceFont_PrefsString], "iosevka"); + setCStr_String(&d->strings[monospaceDocumentFont_PrefsString], "iosevka-body"); d->monospaceGemini = iFalse; d->monospaceGopher = iFalse; d->boldLinkDark = iTrue; @@ -66,37 +72,18 @@ void init_Prefs(iPrefs *d) { d->docThemeDark = colorfulDark_GmDocumentTheme; d->docThemeLight = white_GmDocumentTheme; d->saturation = 1.0f; - initCStr_String(&d->uiLanguage, "en"); - init_String(&d->caFile); - init_String(&d->caPath); - init_String(&d->geminiProxy); - init_String(&d->gopherProxy); - init_String(&d->httpProxy); - init_String(&d->downloadDir); - init_String(&d->searchUrl); - init_String(&d->symbolFontPath); + setCStr_String(&d->strings[uiLanguage_PrefsString], "en"); /* TODO: Add some platform-specific common locations? */ if (fileExistsCStr_FileInfo("/etc/ssl/cert.pem")) { /* macOS */ - setCStr_String(&d->caFile, "/etc/ssl/cert.pem"); + setCStr_String(&d->strings[caFile_PrefsString], "/etc/ssl/cert.pem"); } if (fileExistsCStr_FileInfo("/etc/ssl/certs")) { - setCStr_String(&d->caPath, "/etc/ssl/certs"); + setCStr_String(&d->strings[caPath_PrefsString], "/etc/ssl/certs"); } - /* -#if defined (iPlatformAppleDesktop) - setCStr_String(&d->symbolFontPath, "/System/Library/Fonts/Apple Symbols.ttf"); -#endif - */ } void deinit_Prefs(iPrefs *d) { - deinit_String(&d->symbolFontPath); - deinit_String(&d->searchUrl); - deinit_String(&d->geminiProxy); - deinit_String(&d->gopherProxy); - deinit_String(&d->httpProxy); - deinit_String(&d->downloadDir); - deinit_String(&d->caPath); - deinit_String(&d->caFile); - deinit_String(&d->uiLanguage); + iForIndices(i, d->strings) { + deinit_String(&d->strings[i]); + } } diff --git a/src/prefs.h b/src/prefs.h index 37aeb260..1ba3a2ab 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -31,17 +31,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* User preferences */ iDeclareType(Prefs) + +enum iPrefsString { + uiLanguage_PrefsString, + downloadDir_PrefsString, + searchUrl_PrefsString, + /* Network */ + caFile_PrefsString, + caPath_PrefsString, + geminiProxy_PrefsString, + gopherProxy_PrefsString, + httpProxy_PrefsString, + /* Style */ + uiFont_PrefsString, + headingFont_PrefsString, + bodyFont_PrefsString, + monospaceFont_PrefsString, + monospaceDocumentFont_PrefsString, + max_PrefsString +}; /* TODO: Refactor at least the boolean values into an array for easier manipulation. Then they can be (de)serialized as a group. Need to use a systematic command naming convention for notifications. */ struct Impl_Prefs { + iString strings[max_PrefsString]; /* UI state */ int dialogTab; int langFrom; int langTo; /* Window */ - iString uiLanguage; iBool useSystemTheme; enum iColorTheme theme; enum iColorAccent accent; @@ -55,27 +74,17 @@ struct Impl_Prefs { int pinSplit; /* 0: no pinning, 1: left doc, 2: right doc */ /* Behavior */ int returnKey; - iString downloadDir; iBool hoverLink; iBool smoothScrolling; int smoothScrollSpeed[max_ScrollType]; iBool loadImageInsteadOfScrolling; iBool collapsePreOnLoad; - iString searchUrl; iBool openArchiveIndexPages; /* Network */ - iString caFile; - iString caPath; iBool decodeUserVisibleURLs; int maxCacheSize; /* MB */ int maxMemorySize; /* MB */ - iString geminiProxy; - iString gopherProxy; - iString httpProxy; /* Style */ - iString symbolFontPath; - enum iTextFont font; - enum iTextFont headingFont; iBool monospaceGemini; iBool monospaceGopher; iBool boldLinkDark; diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 2ba2fa12..45a8cf2d 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -496,7 +496,7 @@ static int documentWidth_DocumentWidget_(const iDocumentWidget *d) { -1.0f, 10.0f); /* adapt to width */ //printf("%f\n", adjust); fflush(stdout); return iMini(iMax(minWidth, bounds.size.x - gap_UI * (d->pageMargin + adjust) * 2), - fontSize_UI * emRatio_Text(paragraph_FontId) * /* dependent on avg. glyph width */ + fontSize_UI * //emRatio_Text(paragraph_FontId) * /* dependent on avg. glyph width */ prefs->lineWidth * prefs->zoomPercent / 100); } @@ -3441,7 +3441,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e return iTrue; } break; -#if 1 +#if !defined (NDEBUG) case SDLK_KP_1: case '`': { iBlock *seed = new_Block(64); diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c index cfc81863..9713e1f2 100644 --- a/src/ui/labelwidget.c +++ b/src/ui/labelwidget.c @@ -384,7 +384,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { } else if (flags & alignLeft_WidgetFlag) { draw_Text(d->font, add_I2(bounds.pos, addX_I2(padding_LabelWidget_(d, 0), iconPad)), - fg, cstr_String(&d->label)); + fg, "%s", cstr_String(&d->label)); if ((flags & drawKey_WidgetFlag) && d->key) { iString str; init_String(&str); @@ -399,6 +399,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { : colorEscape != none_ColorId ? colorEscape : uiTextShortcut_ColorId,*/ right_Alignment, + "%s", cstr_String(&str)); deinit_String(&str); } @@ -409,6 +410,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { add_I2(topRight_Rect(bounds), negX_I2(padding_LabelWidget_(d, 1))), fg, right_Alignment, + "%s", cstr_String(&d->label)); } else { diff --git a/src/ui/root.c b/src/ui/root.c index a2e6062f..f6d6f11d 100644 --- a/src/ui/root.c +++ b/src/ui/root.c @@ -566,7 +566,8 @@ static iBool willPerformSearchQuery_(const iString *userInput) { if (isEmpty_String(clean)) { return iFalse; } - return !isEmpty_String(&prefs_App()->searchUrl) && !isLikelyUrl_String(userInput); + return !isEmpty_String(&prefs_App()->strings[searchUrl_PrefsString]) && + !isLikelyUrl_String(userInput); } static void updateUrlInputContentPadding_(iWidget *navBar) { diff --git a/src/ui/text.c b/src/ui/text.c index 8adfa019..14b4e305 100644 --- a/src/ui/text.c +++ b/src/ui/text.c @@ -171,7 +171,8 @@ iLocalDef iBool isMonospaced_Font(const iFont *d) { static iFont *font_Text_(enum iFontId id); static void init_Font(iFont *d, const iFontSpec *fontSpec, const iFontFile *fontFile, - enum iFontSize sizeId, int height) { + enum iFontSize sizeId, float height) { + const int scaleType = scaleType_FontSpec(sizeId); d->fontSpec = fontSpec; d->fontFile = fontFile; /* TODO: Nunito kerning fixes need to be a font parameter of its own. */ @@ -197,15 +198,9 @@ static void init_Font(iFont *d, const iFontSpec *fontSpec, const iFontFile *font d->family = emojiAndSymbols_TextFont; } #endif -// d->isMonospaced = (fontSpec->flags & monospace_FontSpecFlag) != 0; - d->height = height; - //iZap(d->font); -// stbtt_InitFont(&d->font, constData_Block(data), 0); -// int ascent, descent, emAdv; -// stbtt_GetFontVMetrics(&d->font, &ascent, &descent, NULL); -// stbtt_GetCodepointHMetrics(&d->font, 'M', &emAdv, NULL); - const float scale = fontSpec->scaling; - d->xScale = d->yScale = scaleForPixelHeight_FontFile(fontFile, height) * scale; + d->height = (int) (height * fontSpec->heightScale[scaleType]); + const float glyphScale = fontSpec->glyphScale[scaleType]; + d->xScale = d->yScale = scaleForPixelHeight_FontFile(fontFile, d->height) * glyphScale; if (isMonospaced_Font(d)) { /* It is important that monospaced fonts align 1:1 with the pixel grid so that box-drawing characters don't have partially occupied edge pixels, leading to seams @@ -217,8 +212,9 @@ static void init_Font(iFont *d, const iFontSpec *fontSpec, const iFontFile *font } d->emAdvance = fontFile->emAdvance * d->xScale; d->baseline = fontFile->ascent * d->yScale; - d->vertOffset = height * (1.0f - scale) / 2 * fontSpec->vertOffset; + d->vertOffset = d->height * (1.0f - glyphScale) / 2 * fontSpec->vertOffsetScale[scaleType]; d->table = NULL; + // printf("{%s} height:%d baseline:%d\n", cstr_String(&d->fontSpec->id), d->height, d->baseline); } static void deinit_Font(iFont *d) { @@ -252,8 +248,8 @@ struct Impl_CacheRow { }; struct Impl_Text { - enum iTextFont contentFont; - enum iTextFont headingFont; +// enum iTextFont contentFont; +// enum iTextFont headingFont; float contentFontSize; iArray fonts; /* fonts currently selected for use (incl. all styles/sizes) */ int overrideFontId; /* always checked for glyphs first, regardless of which font is used */ @@ -290,7 +286,8 @@ static void setupFontVariants_Text_(iText *d, const iFontSpec *spec, int baseId) spec, spec->styles[style], sizeId, - (sizeId < contentRegular_FontSize ? uiSize : textSize) * scale_FontSize(sizeId)); + (sizeId < contentRegular_FontSize ? uiSize : textSize) * + scale_FontSize(sizeId)); } } } @@ -311,6 +308,11 @@ iLocalDef enum iFontStyle styleId_Text_(const iFont *d) { return (fontId_Text_(d) / max_FontSize) % max_FontStyle; } +static const iFontSpec *tryFindSpec_(enum iPrefsString ps, const char *fallback) { + const iFontSpec *spec = findSpec_Fonts(cstr_String(&prefs_App()->strings[ps])); + return spec ? spec : findSpec_Fonts(fallback); +} + static void initFonts_Text_(iText *d) { #if 0 const iBlock *regularFont = &fontNunitoRegular_Embedded; @@ -445,11 +447,11 @@ static void initFonts_Text_(iText *d) { /* First the mandatory fonts. */ d->overrideFontId = -1; resize_Array(&d->fonts, auxiliary_FontId); /* room for the built-ins */ - iAssert(auxiliary_FontId == documentHeading_FontId + maxVariants_Fonts); - setupFontVariants_Text_(d, findSpec_Fonts("default"), default_FontId); - setupFontVariants_Text_(d, findSpec_Fonts("iosevka"), monospace_FontId); - setupFontVariants_Text_(d, findSpec_Fonts("default"), documentBody_FontId); - setupFontVariants_Text_(d, findSpec_Fonts("default"), documentHeading_FontId); + setupFontVariants_Text_(d, tryFindSpec_(uiFont_PrefsString, "default"), default_FontId); + setupFontVariants_Text_(d, tryFindSpec_(monospaceFont_PrefsString, "iosevka"), monospace_FontId); + setupFontVariants_Text_(d, tryFindSpec_(headingFont_PrefsString, "default"), documentHeading_FontId); + setupFontVariants_Text_(d, tryFindSpec_(bodyFont_PrefsString, "default"), documentBody_FontId); + setupFontVariants_Text_(d, tryFindSpec_(monospaceDocumentFont_PrefsString, "iosevka-body"), documentMonospace_FontId); /* Check if there are auxiliary fonts available and set those up, too. */ iConstForEach(PtrArray, s, listSpecsByPriority_Fonts()) { const iFontSpec *spec = s.ptr; @@ -459,17 +461,6 @@ static void initFonts_Text_(iText *d) { setupFontVariants_Text_(d, spec, fontId); } } -#if 0 - iForIndices(i, fontData) { - iFont *font = font_Text_(i); - init_Font(font, - fontData[i].ttf, - fontData[i].size, - fontData[i].scaling, - fontData[i].sizeId, - fontData[i].ttf == &fontIosevkaTermExtended_Embedded); - } -#endif gap_Text = iRound(gap_UI * d->contentFontSize); } @@ -544,8 +535,8 @@ void init_Text(iText *d, SDL_Renderer *render) { iText *oldActive = activeText_; activeText_ = d; init_Array(&d->fonts, sizeof(iFont)); - d->contentFont = nunito_TextFont; - d->headingFont = nunito_TextFont; +// d->contentFont = nunito_TextFont; +// d->headingFont = nunito_TextFont; d->contentFontSize = contentScale_Text_; d->ansiEscape = new_RegExp("[[()]([0-9;AB]*)m", 0); d->render = render; @@ -579,21 +570,15 @@ void setOpacity_Text(float opacity) { SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f); } -void setContentFont_Text(iText *d, enum iTextFont font) { - if (d->contentFont != font) { - d->contentFont = font; - resetFonts_Text(d); - } -} - -void setHeadingFont_Text(iText *d, enum iTextFont font) { - if (d->headingFont != font) { - d->headingFont = font; - resetFonts_Text(d); - } -} +//void setFont_Text(iText *d, int fontId, const char *fontSpecId) { +// setupFontVariants_Text_(d, findSpec_Fonts(fontSpecId), fontId); +// if (d->contentFont != font) { +// d->contentFont = font; +// resetFonts_Text(d); +// } +//} -void setContentFontSize_Text(iText *d, float fontSizeFactor) { +void setDocumentFontSize_Text(iText *d, float fontSizeFactor) { fontSizeFactor *= contentScale_Text_; iAssert(fontSizeFactor > 0); if (iAbs(d->contentFontSize - fontSizeFactor) > 0.001f) { diff --git a/src/ui/text.h b/src/ui/text.h index af69795a..ac59e7c8 100644 --- a/src/ui/text.h +++ b/src/ui/text.h @@ -34,80 +34,49 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define FONT_ID(name, style, size) ((name) + ((style) * max_FontSize) + (size)) enum iFontId { - default_FontId = 0, /* default is always the first font */ - monospace_FontId = maxVariants_Fonts, /* 2nd font is always the monospace font */ - documentBody_FontId = maxVariants_Fonts * 2, /* 3rd font is the body font */ - documentHeading_FontId = maxVariants_Fonts * 3, /* heading font */ - auxiliary_FontId = maxVariants_Fonts * 4, /* the first auxiliary font (e.g., symbols) */ - - // defaultMedium_FontId, -// defaultBig_FontId, -// defaultLarge_FontId, -// defaultTiny_FontId, -// defaultSmall_FontId, - /* UI fonts: bold weight */ -// defaultBold_FontId, -// defaultMediumBold_FontId, -// defaultBigBold_FontId, -// defaultLargeBold_FontId, - /* content fonts */ -// bold_FontId, -// italic_FontId, -// medium_FontId, -// big_FontId, -// largeBold_FontId, -// largeLight_FontId, -// hugeBold_FontId, -// monospaceSmall_FontId, - /* extra content fonts */ -// defaultContentRegular_FontId, /* UI font but sized to regular_FontId */ -// defaultContentSmall_FontId, /* UI font but sized smaller */ - /* symbols and scripts */ -// userSymbols_FontId, -// iosevka_FontId = userSymbols_FontId + max_FontSize, -// symbols_FontId = iosevka_FontId + max_FontSize, -// symbols2_FontId = symbols_FontId + max_FontSize, -// smolEmoji_FontId = symbols2_FontId + max_FontSize, -// notoEmoji_FontId = smolEmoji_FontId + max_FontSize, -// japanese_FontId = notoEmoji_FontId + max_FontSize, -// chineseSimplified_FontId = japanese_FontId + max_FontSize, -// korean_FontId = chineseSimplified_FontId + max_FontSize, -// arabic_FontId = korean_FontId + max_FontSize, -// max_FontId = arabic_FontId + max_FontSize, + default_FontId = 0, /* default is always the first font */ + monospace_FontId = maxVariants_Fonts, /* 2nd font is always the monospace font */ + documentHeading_FontId = maxVariants_Fonts * 2, /* heading font */ + documentBody_FontId = maxVariants_Fonts * 3, /* body font */ + documentMonospace_FontId = maxVariants_Fonts * 4, + auxiliary_FontId = maxVariants_Fonts * 5, /* the first auxiliary font (e.g., symbols) */ /* Meta: */ mask_FontId = 0x0000ffff, /* font IDs are 16-bit; see GmRun's packing */ alwaysVariableFlag_FontId = 0x00010000, /* UI fonts: */ - uiLabelTiny_FontId = FONT_ID(default_FontId, semiBold_FontStyle, uiTiny_FontSize), - uiLabelSmall_FontId = FONT_ID(default_FontId, regular_FontStyle, uiSmall_FontSize), - uiLabel_FontId = FONT_ID(default_FontId, regular_FontStyle, uiNormal_FontSize), - uiLabelMedium_FontId = FONT_ID(default_FontId, regular_FontStyle, uiMedium_FontSize), - uiLabelMediumBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiMedium_FontSize), - uiLabelBig_FontId = FONT_ID(default_FontId, regular_FontStyle, uiBig_FontSize), - uiLabelBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiNormal_FontSize), - uiLabelBigBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiBig_FontSize), - uiLabelLarge_FontId = FONT_ID(default_FontId, regular_FontStyle, uiLarge_FontSize), - uiLabelLargeBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiLarge_FontSize), - uiLabelSymbols_FontId = FONT_ID(auxiliary_FontId, regular_FontStyle, uiNormal_FontSize), - uiShortcuts_FontId = FONT_ID(default_FontId, regular_FontStyle, uiNormal_FontSize), - uiInput_FontId = FONT_ID(default_FontId, regular_FontStyle, uiMedium_FontSize), - uiContent_FontId = FONT_ID(default_FontId, regular_FontStyle, uiMedium_FontSize), - uiContentBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiMedium_FontSize), - uiContentSymbols_FontId = FONT_ID(auxiliary_FontId, regular_FontStyle, uiMedium_FontSize), - /* Document fonts: */ - paragraph_FontId = FONT_ID(documentBody_FontId, regular_FontStyle, contentRegular_FontSize), - bold_FontId = FONT_ID(documentBody_FontId, semiBold_FontStyle, contentRegular_FontSize), - firstParagraph_FontId = FONT_ID(documentBody_FontId, regular_FontStyle, contentMedium_FontSize), - preformatted_FontId = FONT_ID(monospace_FontId, regular_FontStyle, contentMono_FontSize), - preformattedSmall_FontId = FONT_ID(monospace_FontId, regular_FontStyle, contentMonoSmall_FontSize), - quote_FontId = FONT_ID(documentBody_FontId, italic_FontStyle, contentRegular_FontSize), - heading1_FontId = FONT_ID(documentHeading_FontId, bold_FontStyle, contentHuge_FontSize), - heading2_FontId = FONT_ID(documentHeading_FontId, bold_FontStyle, contentLarge_FontSize), - heading3_FontId = FONT_ID(documentHeading_FontId, regular_FontStyle, contentBig_FontSize), - banner_FontId = FONT_ID(documentHeading_FontId, light_FontStyle, contentLarge_FontSize), - regularMonospace_FontId = FONT_ID(monospace_FontId, regular_FontStyle, contentRegular_FontSize), + uiLabelTiny_FontId = FONT_ID(default_FontId, semiBold_FontStyle, uiTiny_FontSize), + uiLabelSmall_FontId = FONT_ID(default_FontId, regular_FontStyle, uiSmall_FontSize), + uiLabel_FontId = FONT_ID(default_FontId, regular_FontStyle, uiNormal_FontSize), + uiLabelMedium_FontId = FONT_ID(default_FontId, regular_FontStyle, uiMedium_FontSize), + uiLabelMediumBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiMedium_FontSize), + uiLabelBig_FontId = FONT_ID(default_FontId, regular_FontStyle, uiBig_FontSize), + uiLabelBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiNormal_FontSize), + uiLabelBigBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiBig_FontSize), + uiLabelLarge_FontId = FONT_ID(default_FontId, regular_FontStyle, uiLarge_FontSize), + uiLabelLargeBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiLarge_FontSize), + uiLabelSymbols_FontId = FONT_ID(auxiliary_FontId, regular_FontStyle, uiNormal_FontSize), + uiShortcuts_FontId = FONT_ID(default_FontId, regular_FontStyle, uiNormal_FontSize), + uiInput_FontId = FONT_ID(default_FontId, regular_FontStyle, uiMedium_FontSize), + uiContent_FontId = FONT_ID(default_FontId, regular_FontStyle, uiMedium_FontSize), + uiContentBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiMedium_FontSize), + uiContentSymbols_FontId = FONT_ID(auxiliary_FontId, regular_FontStyle, uiMedium_FontSize), + + /* Document fonts: */ + paragraph_FontId = FONT_ID(documentBody_FontId, regular_FontStyle, contentRegular_FontSize), + bold_FontId = FONT_ID(documentBody_FontId, semiBold_FontStyle, contentRegular_FontSize), + firstParagraph_FontId = FONT_ID(documentBody_FontId, regular_FontStyle, contentMedium_FontSize), + preformatted_FontId = FONT_ID(monospace_FontId, regular_FontStyle, contentSmall_FontSize), + preformattedSmall_FontId = FONT_ID(monospace_FontId, regular_FontStyle, contentTiny_FontSize), + quote_FontId = FONT_ID(documentBody_FontId, italic_FontStyle, contentRegular_FontSize), + heading1_FontId = FONT_ID(documentHeading_FontId, bold_FontStyle, contentHuge_FontSize), + heading2_FontId = FONT_ID(documentHeading_FontId, bold_FontStyle, contentLarge_FontSize), + heading3_FontId = FONT_ID(documentHeading_FontId, regular_FontStyle, contentBig_FontSize), + banner_FontId = FONT_ID(documentHeading_FontId, light_FontStyle, contentLarge_FontSize), + monospaceParagraph_FontId = FONT_ID(documentMonospace_FontId, regular_FontStyle, contentRegular_FontSize), + monospaceBold_FontId = FONT_ID(documentMonospace_FontId, semiBold_FontStyle, contentRegular_FontSize), + plainText_FontId = FONT_ID(documentMonospace_FontId, regular_FontStyle, contentRegular_FontSize), }; //iLocalDef iBool isJapanese_FontId(enum iFontId id) { @@ -116,6 +85,7 @@ enum iFontId { #define emojiVariationSelector_Char ((iChar) 0xfe0f) +#if 0 /* TODO: get rid of this; configure using font ID strings, check RTL from FontFile flags */ enum iTextFont { undefined_TextFont = -1, @@ -129,6 +99,7 @@ enum iTextFont { arabic_TextFont, emojiAndSymbols_TextFont, }; +#endif extern int gap_Text; /* affected by content font size */ @@ -140,11 +111,10 @@ void deinit_Text (iText *); void setCurrent_Text (iText *); -//void loadUserFonts_Text (void); /* based on Prefs */ - -void setContentFont_Text (iText *, enum iTextFont font); -void setHeadingFont_Text (iText *, enum iTextFont font); -void setContentFontSize_Text (iText *, float fontSizeFactor); /* affects all except `default*` fonts */ +//void setContentFont_Text (iText *, enum iTextFont font); +//void setHeadingFont_Text (iText *, enum iTextFont font); +//void setFont_Text (iText *, int fontId, const char *fontSpecId); +void setDocumentFontSize_Text(iText *, float fontSizeFactor); /* affects all except `default*` fonts */ void resetFonts_Text (iText *); int lineHeight_Text (int fontId); diff --git a/src/ui/util.c b/src/ui/util.c index ae72dbee..73193c7a 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -1931,7 +1931,38 @@ static void addRadioButton_(iWidget *parent, const char *id, const char *label, id); } +static iBool proportionalFonts_(const iFontSpec *spec) { + return (spec->flags & monospace_FontSpecFlag) == 0 && ~spec->flags & auxiliary_FontSpecFlag; +} + +static iBool monospaceFonts_(const iFontSpec *spec) { + return (spec->flags & monospace_FontSpecFlag) != 0 && ~spec->flags & auxiliary_FontSpecFlag; +} + static const iArray *makeFontItems_(const char *id) { + iArray *items = collectNew_Array(sizeof(iMenuItem)); + if (!startsWith_CStr(id, "mono")) { + iConstForEach(PtrArray, i, listSpecs_Fonts(proportionalFonts_)) { + const iFontSpec *spec = i.ptr; + pushBack_Array( + items, + &(iMenuItem){ cstr_String(&spec->name), + 0, + 0, + format_CStr("!font.set %s:%s", id, cstr_String(&spec->id)) }); + } + pushBack_Array(items, &(iMenuItem){ "---" }); + } + iConstForEach(PtrArray, j, listSpecs_Fonts(monospaceFonts_)) { + const iFontSpec *spec = j.ptr; + pushBack_Array( + items, + &(iMenuItem){ cstr_String(&spec->name), + 0, + 0, + format_CStr("!font.set %s:%s", id, cstr_String(&spec->id)) }); + } +#if 0 const struct { const char * name; enum iTextFont cfgId; @@ -1943,7 +1974,6 @@ static const iArray *makeFontItems_(const char *id) { { "Tinos", tinos_TextFont }, { "---", -1 }, { "Iosevka", iosevka_TextFont } }; - iArray *items = collectNew_Array(sizeof(iMenuItem)); iForIndices(i, fonts) { pushBack_Array(items, &(iMenuItem){ fonts[i].name, @@ -1953,17 +1983,19 @@ static const iArray *makeFontItems_(const char *id) { ? format_CStr("!%s.set arg:%d", id, fonts[i].cfgId) : NULL }); } +#endif pushBack_Array(items, &(iMenuItem){ NULL }); /* terminator */ return items; } static void addFontButtons_(iWidget *parent, const char *id) { const iArray *items = makeFontItems_(id); - iLabelWidget *button = makeMenuButton_LabelWidget("Source Sans 3", + size_t widestIndex = findWidestLabel_MenuItem(constData_Array(items), size_Array(items)); + iLabelWidget *button = makeMenuButton_LabelWidget(constValue_Array(items, widestIndex, iMenuItem).label, constData_Array(items), size_Array(items)); setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), uiBackgroundMenu_ColorId); - setId_Widget(as_Widget(button), format_CStr("prefs.%s", id)); + setId_Widget(as_Widget(button), format_CStr("prefs.font.%s", id)); addChildFlags_Widget(parent, iClob(button), alignLeft_WidgetFlag); } @@ -2170,11 +2202,11 @@ iWidget *makePreferences_Widget(void) { { NULL } }; const iMenuItem lineWidthItems[] = { - { "button id:prefs.linewidth.50 text:\u20132", 0, 0, "linewidth.set arg:50" }, - { "button id:prefs.linewidth.58 text:\u20131", 0, 0, "linewidth.set arg:58" }, - { "button id:prefs.linewidth.66 label:prefs.linewidth.normal", 0, 0, "linewidth.set arg:66" }, - { "button id:prefs.linewidth.76 text:+1", 0, 0, "linewidth.set arg:76" }, - { "button id:prefs.linewidth.86 text:+2", 0, 0, "linewidth.set arg:86" }, + { "button id:prefs.linewidth.30 text:\u20132", 0, 0, "linewidth.set arg:30" }, + { "button id:prefs.linewidth.34 text:\u20131", 0, 0, "linewidth.set arg:34" }, + { "button id:prefs.linewidth.38 label:prefs.linewidth.normal", 0, 0, "linewidth.set arg:38" }, + { "button id:prefs.linewidth.43 text:+1", 0, 0, "linewidth.set arg:43" }, + { "button id:prefs.linewidth.48 text:+2", 0, 0, "linewidth.set arg:48" }, { "button id:prefs.linewidth.1000 label:prefs.linewidth.fill", 0, 0, "linewidth.set arg:1000" }, { NULL } }; @@ -2491,10 +2523,14 @@ iWidget *makePreferences_Widget(void) { /* Fonts. */ { setId_Widget(appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.fonts}", '4', &headings, &values), "prefs.page.fonts"); /* Fonts. */ { - addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.headingfont}"))); - addFontButtons_(values, "headingfont"); - addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font}"))); - addFontButtons_(values, "font"); + addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.ui}"))); + addFontButtons_(values, "ui"); + addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.heading}"))); + addFontButtons_(values, "heading"); + addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.body}"))); + addFontButtons_(values, "body"); + addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.mono}"))); + addFontButtons_(values, "mono"); addDialogPadding_(headings, values); addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.mono}"))); iWidget *mono = new_Widget(); { @@ -2511,6 +2547,9 @@ iWidget *makePreferences_Widget(void) { updateSize_LabelWidget((iLabelWidget *) tog); } addChildFlags_Widget(values, iClob(mono), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); + addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.monodoc}"))); + addFontButtons_(values, "monodoc"); + addDialogPadding_(headings, values); addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.boldlink}"))); iWidget *boldLink = new_Widget(); { /* TODO: Add a utility function for this type of toggles? (also for above) */ @@ -2528,11 +2567,11 @@ iWidget *makePreferences_Widget(void) { } addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); addDialogPadding_(headings, values); - /* Custom font. */ { - iInputWidget *customFont = new_InputWidget(0); - setHint_InputWidget(customFont, "${hint.prefs.userfont}"); - addPrefsInputWithHeading_(headings, values, "prefs.userfont", iClob(customFont)); - } +// /* Custom font. */ { +// iInputWidget *customFont = new_InputWidget(0); +// setHint_InputWidget(customFont, "${hint.prefs.userfont}"); +// addPrefsInputWithHeading_(headings, values, "prefs.userfont", iClob(customFont)); +// } } } /* Style. */ { -- cgit v1.2.3 From 960df03c17091aca37f53eaab8fc27c669d26a5e Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 11 Oct 2021 12:22:54 +0300 Subject: Media refactoring; working on FontPack management Media still needs more work to get rid of redundancies and make lookups faster. FontPacks are manipulated as Media items (not unlike images) so they can be previewed on page, and installed via a click. FontPack management is not trivial as it includes such details as versioning and whether individual packs are enabled or disabled. --- res/arabic.fontpack/fontpack.ini | 2 + res/cjk.fontpack/fontpack.ini | 2 + res/default.fontpack/fontpack.ini | 2 + res/firasans.fontpack/fontpack.ini | 2 + res/literata.fontpack/fontpack.ini | 2 + res/nunito.fontpack/fontpack.ini | 2 + res/tinos.fontpack/fontpack.ini | 2 + src/app.c | 1 + src/defs.h | 4 +- src/fontpack.c | 299 +++++++++++++++++++------- src/fontpack.h | 65 ++++-- src/gempub.c | 2 +- src/gmdocument.c | 159 +++++++------- src/gmdocument.h | 16 +- src/gmrequest.c | 9 +- src/gmutil.c | 8 +- src/media.c | 349 ++++++++++++++++++++---------- src/media.h | 92 ++++++-- src/ui/documentwidget.c | 429 +++++++++++++++++++++---------------- src/ui/documentwidget.h | 2 + src/ui/mediaui.c | 88 +++++++- src/ui/mediaui.h | 20 +- 22 files changed, 1030 insertions(+), 527 deletions(-) (limited to 'src/gmdocument.c') diff --git a/res/arabic.fontpack/fontpack.ini b/res/arabic.fontpack/fontpack.ini index 00ad5241..305878ce 100644 --- a/res/arabic.fontpack/fontpack.ini +++ b/res/arabic.fontpack/fontpack.ini @@ -1,3 +1,5 @@ +version = 1 + [arabic] name = "Noto Sans Arabic UI" auxiliary = true diff --git a/res/cjk.fontpack/fontpack.ini b/res/cjk.fontpack/fontpack.ini index fbac54be..6e0d274c 100644 --- a/res/cjk.fontpack/fontpack.ini +++ b/res/cjk.fontpack/fontpack.ini @@ -1,3 +1,5 @@ +version = 1 + [notosansjp] name = "Noto Sans JP" auxiliary = true diff --git a/res/default.fontpack/fontpack.ini b/res/default.fontpack/fontpack.ini index 68316ef6..f8ef31ce 100644 --- a/res/default.fontpack/fontpack.ini +++ b/res/default.fontpack/fontpack.ini @@ -17,6 +17,8 @@ # `glyphscale` and `voffset` can also be specified separately for the UI and # document domains by prefixing `ui.` or `doc.` to the key. +version = 1 + [default] name = "Source Sans" regular = "SourceSans3-Regular.ttf" diff --git a/res/firasans.fontpack/fontpack.ini b/res/firasans.fontpack/fontpack.ini index 4378a757..c6eb4c77 100644 --- a/res/firasans.fontpack/fontpack.ini +++ b/res/firasans.fontpack/fontpack.ini @@ -1,3 +1,5 @@ +version = 1 + [firasans] name = "Fira Sans" glyphscale = 0.85 diff --git a/res/literata.fontpack/fontpack.ini b/res/literata.fontpack/fontpack.ini index e4e49bcb..7c29491d 100644 --- a/res/literata.fontpack/fontpack.ini +++ b/res/literata.fontpack/fontpack.ini @@ -1,3 +1,5 @@ +version = 1 + [literata] name = "Literata" regular = "Literata-Regular-opsz=14.ttf" diff --git a/res/nunito.fontpack/fontpack.ini b/res/nunito.fontpack/fontpack.ini index ea4a12b8..2f2471e1 100644 --- a/res/nunito.fontpack/fontpack.ini +++ b/res/nunito.fontpack/fontpack.ini @@ -1,3 +1,5 @@ +version = 1 + [nunito] name = "Nunito" tweaks = 0x1 # some hardcoded kerning changes (`Th`, etc.) diff --git a/res/tinos.fontpack/fontpack.ini b/res/tinos.fontpack/fontpack.ini index 8759b752..a2cf811e 100644 --- a/res/tinos.fontpack/fontpack.ini +++ b/res/tinos.fontpack/fontpack.ini @@ -1,3 +1,5 @@ +version = 1 + [tinos] name = "Tinos" glyphscale = 0.850 diff --git a/src/app.c b/src/app.c index b317e7b3..cb5479e8 100644 --- a/src/app.c +++ b/src/app.c @@ -2669,6 +2669,7 @@ iBool handleCommand_App(const char *cmd) { const iBool isSplit = numRoots_Window(get_Window()) > 1; if (tabCount_Widget(tabs) > 1 || isSplit) { iWidget *closed = removeTabPage_Widget(tabs, index); + cancelAllRequests_DocumentWidget((iDocumentWidget *) closed); destroy_Widget(closed); /* released later */ if (index == tabCount_Widget(tabs)) { index--; diff --git a/src/defs.h b/src/defs.h index 65096389..f5479cf3 100644 --- a/src/defs.h +++ b/src/defs.h @@ -125,11 +125,13 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { #define delete_Icon "\u232b" #define copy_Icon "\u2398" //"\u2bba" #define check_Icon "\u2714" -#define ballotCheck_Icon "\U0001f5f9" +#define ballotChecked_Icon "\U0001f5f9" +#define ballotUnchecked_Icon "\U0001f5f9" #define inbox_Icon "\U0001f4e5" #define book_Icon "\U0001f56e" #define bookmark_Icon "\U0001f516" #define folder_Icon "\U0001f4c1" +#define file_Icon "\U0001f5ce" #define openTab_Icon "\u2750" #define openTabBg_Icon "\u2b1a" #define openExt_Icon "\u27a0" diff --git a/src/fontpack.c b/src/fontpack.c index ca1d1582..fb1c98ee 100644 --- a/src/fontpack.c +++ b/src/fontpack.c @@ -33,7 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include -/* TODO: Clean up and/or reorder this file, it's a bit unorganized. */ +const char *mimeType_FontPack = "application/lagrange-fontpack+zip"; float scale_FontSize(enum iFontSize size) { static const float sizes[max_FontSize] = { @@ -57,38 +57,9 @@ float scale_FontSize(enum iFontSize size) { return sizes[size]; } -iDeclareType(Fonts) - -struct Impl_Fonts { - iString userDir; - iPtrArray packs; - iPtrArray files; - iPtrArray specOrder; /* specs sorted by priority */ -}; - -static iFonts fonts_; - -static void unloadFiles_Fonts_(iFonts *d) { - /* TODO: Mark all files in font packs as not resident. */ - iForEach(PtrArray, i, &d->files) { - delete_FontFile(i.ptr); - } - clear_PtrArray(&d->files); -} - -static iFontFile *findFile_Fonts_(iFonts *d, const iString *id) { - iForEach(PtrArray, i, &d->files) { - iFontFile *ff = i.ptr; - if (equal_String(&ff->id, id)) { - return ff; - } - } - return NULL; -} - /*----------------------------------------------------------------------------------------------*/ -iDefineTypeConstruction(FontFile) +iDefineObjectConstruction(FontFile) void init_FontFile(iFontFile *d) { init_String(&d->id); @@ -114,7 +85,7 @@ static void load_FontFile_(iFontFile *d, const iBlock *data) { HB_MEMORY_MODE_READONLY, NULL, NULL); d->hbFace = hb_face_create(d->hbBlob, 0); d->hbFont = hb_font_create(d->hbFace); -#endif +#endif } static void unload_FontFile_(iFontFile *d) { @@ -126,12 +97,13 @@ static void unload_FontFile_(iFontFile *d) { d->hbFont = NULL; d->hbFace = NULL; d->hbBlob = NULL; -#endif +#endif clear_Block(&d->sourceData); iZap(d->stbInfo); } void deinit_FontFile(iFontFile *d) { + printf("FontFile %p {%s} is DESTROYED\n", d, cstr_String(&d->id)); unload_FontFile_(d); deinit_Block(&d->sourceData); deinit_String(&d->id); @@ -156,12 +128,12 @@ void measureGlyph_FontFile(const iFontFile *d, uint32_t glyphIndex, /*----------------------------------------------------------------------------------------------*/ - iDefineTypeConstruction(FontSpec) void init_FontSpec(iFontSpec *d) { init_String(&d->id); init_String(&d->name); + init_String(&d->sourcePath); d->flags = 0; d->priority = 0; for (int i = 0; i < 2; ++i) { @@ -173,52 +145,119 @@ void init_FontSpec(iFontSpec *d) { } void deinit_FontSpec(iFontSpec *d) { + /* FontFile references are held by FontSpecs. */ + iForIndices(i, d->styles) { + iRelease(d->styles[i]); + } + deinit_String(&d->sourcePath); deinit_String(&d->name); deinit_String(&d->id); } /*----------------------------------------------------------------------------------------------*/ -iDeclareType(FontPack) -iDeclareTypeConstruction(FontPack) +iDeclareType(Fonts) + +struct Impl_Fonts { + iString userDir; + iPtrArray packs; + iObjectList *files; + iPtrArray specOrder; /* specs sorted by priority */ +}; + +static iFonts fonts_; + +static void unloadFiles_Fonts_(iFonts *d) { + /* TODO: Mark all files in font packs as not resident. */ + clear_ObjectList(d->files); +} + +static iFontFile *findFile_Fonts_(iFonts *d, const iString *id) { + iForEach(ObjectList, i, d->files) { + iFontFile *ff = i.object; + if (equal_String(&ff->id, id)) { + return ff; + } + } + return NULL; +} + +static void releaseUnusedFiles_Fonts_(iFonts *d) { + iForEach(ObjectList, i, d->files) { + iFontFile *ff = i.object; + if (ff->object.refCount == 1) { + /* No specs use this. */ + //printf("[Fonts] releasing unused font file: %p {%s}\n", ff, cstr_String(&ff->id)); + remove_ObjectListIterator(&i); + } + } +} + +/*----------------------------------------------------------------------------------------------*/ struct Impl_FontPack { - const iArchive *archive; /* opened ZIP archive */ + iString id; /* lowercase filename without the .fontpack extension */ + int version; + iBool isStandalone; + iBool isReadOnly; iArray fonts; /* array of FontSpecs */ + const iArchive *archive; /* opened ZIP archive */ iString * loadPath; iFontSpec * loadSpec; }; +iDefineTypeConstruction(FontPack) + void init_FontPack(iFontPack *d) { - d->archive = NULL; + init_String(&d->id); + d->version = 0; + d->isStandalone = iFalse; + d->isReadOnly = iFalse; init_Array(&d->fonts, sizeof(iFontSpec)); + d->archive = NULL; d->loadSpec = NULL; d->loadPath = NULL; } void deinit_FontPack(iFontPack *d) { + iAssert(d->archive == NULL); + iAssert(d->loadSpec == NULL); delete_String(d->loadPath); iForEach(Array, i, &d->fonts) { deinit_FontSpec(i.value); } deinit_Array(&d->fonts); - iAssert(d->archive == NULL); - iAssert(d->loadSpec == NULL); + deinit_String(&d->id); + releaseUnusedFiles_Fonts_(&fonts_); } -iDefineTypeConstruction(FontPack) +iFontPackId id_FontPack(const iFontPack *d) { + return (iFontPackId){ &d->id, d->version }; +} + +const iPtrArray *listSpecs_FontPack(const iFontPack *d) { + if (!d) return NULL; + iPtrArray *list = collectNew_PtrArray(); + iConstForEach(Array, i, &d->fonts) { + pushBack_PtrArray(list, i.value); + } + return list; +} void handleIniTable_FontPack_(void *context, const iString *table, iBool isStart) { iFontPack *d = context; if (isStart) { iAssert(!d->loadSpec); - /* Each font ID must be unique. */ - if (!findSpec_Fonts(cstr_String(table))) { + /* Each font ID must be unique in the non-standalone packs. */ + if (d->isStandalone || !findSpec_Fonts(cstr_String(table))) { d->loadSpec = new_FontSpec(); set_String(&d->loadSpec->id, table); + if (d->loadPath) { + set_String(&d->loadSpec->sourcePath, d->loadPath); + } } } - else { + else if (d->loadSpec) { /* Set fallback font files. */ { const iFontFile **styles = d->loadSpec->styles; if (!styles[regular_FontStyle]) { @@ -229,11 +268,11 @@ void handleIniTable_FontPack_(void *context, const iString *table, iBool isStart return; } if (!styles[semiBold_FontStyle]) { - styles[semiBold_FontStyle] = styles[bold_FontStyle]; + styles[semiBold_FontStyle] = ref_Object(styles[bold_FontStyle]); } for (size_t s = 0; s < max_FontStyle; s++) { if (!styles[s]) { - styles[s] = styles[regular_FontStyle]; + styles[s] = ref_Object(styles[regular_FontStyle]); } } } @@ -263,7 +302,15 @@ static iBlock *readFile_FontPack_(const iFontPack *d, const iString *path) { void handleIniKeyValue_FontPack_(void *context, const iString *table, const iString *key, const iTomlValue *value) { iFontPack *d = context; - if (!d->loadSpec) return; + if (isEmpty_String(table)) { + if (!cmp_String(key, "version")) { + d->version = number_TomlValue(value); + } + return; + } + if (!d->loadSpec) { + return; + } iUnused(table); if (!cmp_String(key, "name") && value->type == string_TomlType) { set_String(&d->loadSpec->name, value->value.string); @@ -322,12 +369,13 @@ void handleIniKeyValue_FontPack_(void *context, const iString *table, const iStr ff = new_FontFile(); set_String(&ff->id, fontFileId); load_FontFile_(ff, data); - pushBack_PtrArray(&fonts_.files, ff); /* centralized ownership */ + pushBack_ObjectList(fonts_.files, ff); /* centralized ownership */ + iRelease(ff); delete_Block(data); // printf("[FontPack] loaded file: %s\n", cstr_String(fontFileId)); } } - d->loadSpec->styles[i] = ff; + d->loadSpec->styles[i] = ref_Object(ff); delete_String(fontFileId); break; } @@ -348,29 +396,6 @@ static iBool load_FontPack_(iFontPack *d, const iString *ini) { return ok; } -#if 0 -iBool loadIniFile_FontPack(iFontPack *d, const iString *iniPath) { - iBeginCollect(); - iBool ok = iFalse; - iFile *f = iClob(new_File(iniPath)); - if (open_File(f, text_FileMode | readOnly_FileMode)) { - d->loadPath = collect_String(newRange_String(dirName_Path(iniPath))); - iString *src = collect_String(readString_File(f)); - - iTomlParser *ini = collect_TomlParser(new_TomlParser()); - setHandlers_TomlParser(ini, handleIniTable_FontPack_, handleIniKeyValue_FontPack_, d); - if (!parse_TomlParser(ini, src)) { - fprintf(stderr, "[FontPack] error parsing %s\n", cstr_String(iniPath)); - } - iAssert(d->loadSpec == NULL); - d->loadPath = NULL; - ok = iTrue; - } - iEndCollect(); - return ok; -} -#endif - iBool loadArchive_FontPack(iFontPack *d, const iArchive *zip) { d->archive = zip; iBool ok = iFalse; @@ -387,6 +412,28 @@ iBool loadArchive_FontPack(iFontPack *d, const iArchive *zip) { return ok; } +void setLoadPath_FontPack(iFontPack *d, const iString *path) { + if (!d->loadPath) { + d->loadPath = new_String(); + } + set_String(d->loadPath, path); + /* Pack ID is based on the file name. */ + setRange_String(&d->id, baseName_Path(path)); + setRange_String(&d->id, withoutExtension_Path(&d->id)); +} + +void setStandalone_FontPack(iFontPack *d, iBool standalone) { + d->isStandalone = standalone; +} + +void setReadOnly_FontPack(iFontPack *d, iBool readOnly) { + d->isReadOnly = readOnly; +} + +iBool isReadOnly_FontPack(const iFontPack *d) { + return d->isReadOnly; +} + /*----------------------------------------------------------------------------------------------*/ static void unloadFonts_Fonts_(iFonts *d) { @@ -397,14 +444,23 @@ static void unloadFonts_Fonts_(iFonts *d) { clear_PtrArray(&d->packs); } +static int cmpName_FontSpecPtr_(const void *a, const void *b) { + const iFontSpec **p1 = (const iFontSpec **) a, **p2 = (const iFontSpec **) b; + return cmpStringCase_String(&(*p1)->name, &(*p2)->name); +} + static int cmpPriority_FontSpecPtr_(const void *a, const void *b) { const iFontSpec **p1 = (const iFontSpec **) a, **p2 = (const iFontSpec **) b; - return -iCmp((*p1)->priority, (*p2)->priority); /* highest priority first */ + const int cmp = -iCmp((*p1)->priority, (*p2)->priority); /* highest priority first */ + if (cmp) return cmp; + return cmpName_FontSpecPtr_(a, b); } -static int cmpName_FontSpecPtr_(const void *a, const void *b) { +static int cmpSourceAndPriority_FontSpecPtr_(const void *a, const void *b) { const iFontSpec **p1 = (const iFontSpec **) a, **p2 = (const iFontSpec **) b; - return cmpStringCase_String(&(*p1)->name, &(*p2)->name); + const int cmp = cmpStringCase_String(&(*p1)->sourcePath, &(*p2)->sourcePath); + if (cmp) return cmp; + return cmpPriority_FontSpecPtr_(a, b); } static void sortSpecs_Fonts_(iFonts *d) { @@ -422,11 +478,13 @@ void init_Fonts(const char *userDir) { iFonts *d = &fonts_; initCStr_String(&d->userDir, userDir); init_PtrArray(&d->packs); - init_PtrArray(&d->files); + d->files = new_ObjectList(); init_PtrArray(&d->specOrder); /* Load the required fonts. */ { iFontPack *pack = new_FontPack(); + setCStr_String(&pack->id, "default"); iArchive *arch = new_Archive(); + setReadOnly_FontPack(pack, iTrue); openData_Archive(arch, &fontpackDefault_Embedded); loadArchive_FontPack(pack, arch); /* should never fail if we've made it this far */ iRelease(arch); @@ -436,7 +494,7 @@ void init_Fonts(const char *userDir) { const char *locations[] = { ".", "./fonts", - "../share/lagrange", + "../share/lagrange", /* Note: These must match CMakeLists.txt install destination */ "../../share/lagrange", concatPath_CStr(userDir, "fonts"), userDir, @@ -450,7 +508,13 @@ void init_Fonts(const char *userDir) { iArchive *arch = new_Archive(); if (openFile_Archive(arch, entryPath)) { iFontPack *pack = new_FontPack(); - pack->loadPath = copy_String(entryPath); + setLoadPath_FontPack(pack, entryPath); + setReadOnly_FontPack(pack, !isWritable_FileInfo(entry.value)); +#if defined (iPlatformApple) + if (startsWith_String(pack->loadPath, cstr_String(execDir))) { + setReadOnly_FontPack(pack, iTrue); + } +#endif if (loadArchive_FontPack(pack, arch)) { pushBack_PtrArray(&d->packs, pack); } @@ -491,13 +555,18 @@ void init_Fonts(const char *userDir) { void deinit_Fonts(void) { iFonts *d = &fonts_; unloadFonts_Fonts_(d); - unloadFiles_Fonts_(d); + //unloadFiles_Fonts_(d); + iAssert(isEmpty_ObjectList(d->files)); deinit_PtrArray(&d->specOrder); deinit_PtrArray(&d->packs); - deinit_PtrArray(&d->files); + iRelease(d->files); deinit_String(&d->userDir); } +const iPtrArray *listPacks_Fonts(void) { + return &fonts_.packs; +} + const iFontSpec *findSpec_Fonts(const char *fontId) { iFonts *d = &fonts_; iConstForEach(PtrArray, i, &d->specOrder) { @@ -524,3 +593,73 @@ const iPtrArray *listSpecs_Fonts(iBool (*filterFunc)(const iFontSpec *)) { const iPtrArray *listSpecsByPriority_Fonts(void) { return &fonts_.specOrder; } + +const iString *infoPage_Fonts(void) { + iFonts *d = &fonts_; + iString *str = collectNewCStr_String("# Fonts\n"); + iPtrArray *specsByPack = collectNew_PtrArray(); + setCopy_PtrArray(specsByPack, &d->specOrder); + sort_Array(specsByPack, cmpSourceAndPriority_FontSpecPtr_); + iString *currentSourcePath = collectNew_String(); + iConstForEach(PtrArray, i, specsByPack) { + const iFontSpec *spec = i.ptr; + if (isEmpty_String(&spec->sourcePath)) { + continue; /* built-in font */ + } + if (!equal_String(&spec->sourcePath, currentSourcePath)) { + appendFormat_String(str, "=> %s %s%s\n", + cstrCollect_String(makeFileUrl_String(&spec->sourcePath)), + endsWithCase_String(&spec->sourcePath, ".fontpack") ? "\U0001f520 " : "", + cstr_Rangecc(baseName_Path(&spec->sourcePath))); + set_String(currentSourcePath, &spec->sourcePath); + } + } + return str; +} + +const iFontPack *findPack_Fonts(const iString *path) { + iFonts *d = &fonts_; + iConstForEach(PtrArray, i, &d->packs) { + const iFontPack *pack = i.ptr; + if (pack->loadPath && equal_String(pack->loadPath, path)) { + return pack; + } + } + return NULL; +} + +iBool preloadLocalFontpackForPreview_Fonts(iGmDocument *doc) { + iBool wasLoaded = iFalse; + for (size_t linkId = 1; ; linkId++) { + const iString *linkUrl = linkUrl_GmDocument(doc, linkId); + if (!linkUrl) { + break; /* ran out of links */ + } + const int linkFlags = linkFlags_GmDocument(doc, linkId); + if (linkFlags & fontpackFileExtension_GmLinkFlag && + scheme_GmLinkFlag(linkFlags) == file_GmLinkScheme) { + iMediaId linkMedia = findMediaForLink_Media(media_GmDocument(doc), linkId, fontpack_MediaType); + if (linkMedia.type) { + continue; /* got this one already */ + } + iString *filePath = localFilePathFromUrl_String(linkUrl); + iFile *f = new_File(filePath); + if (open_File(f, readOnly_FileMode)) { + iBlock *fontPackArchiveData = readAll_File(f); + setUrl_Media(media_GmDocument(doc), linkId, fontpack_MediaType, linkUrl); + setData_Media(media_GmDocument(doc), + linkId, + collectNewCStr_String(mimeType_FontPack), + fontPackArchiveData, + 0); + delete_Block(fontPackArchiveData); + wasLoaded = iTrue; + } + iRelease(f); + } + } + return wasLoaded; +} + +iDefineClass(FontFile) + diff --git a/src/fontpack.h b/src/fontpack.h index e59154e3..429afb5d 100644 --- a/src/fontpack.h +++ b/src/fontpack.h @@ -30,6 +30,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ # include #endif +extern const char *mimeType_FontPack; + /* Fontpacks are ZIP archives that contain a configuration file and one of more font files. The fontpack format is used instead of plain TTF/OTF because the text renderer uses additional metadata about each font. @@ -68,21 +70,13 @@ enum iFontStyle { float scale_FontSize (enum iFontSize size); -iDeclareType(FontSpec) -iDeclareTypeConstruction(FontSpec) +/*----------------------------------------------------------------------------------------------*/ -enum iFontSpecFlags { - override_FontSpecFlag = iBit(1), - monospace_FontSpecFlag = iBit(2), /* can be used in preformatted content */ - auxiliary_FontSpecFlag = iBit(3), /* only used for looking up glyphs missing from other fonts */ - arabic_FontSpecFlag = iBit(4), - fixNunitoKerning_FontSpecFlag = iBit(31), /* manual hardcoded kerning tweaks for Nunito */ -}; - -iDeclareType(FontFile) -iDeclareTypeConstruction(FontFile) +iDeclareClass(FontFile) +iDeclareObjectConstruction(FontFile) struct Impl_FontFile { + iObject object; /* reference-counted */ iString id; /* for detecting when the same file is used in many places */ enum iFontStyle style; iBlock sourceData; @@ -107,9 +101,26 @@ uint8_t * rasterizeGlyph_FontFile(const iFontFile *, float xScale, float yScal void measureGlyph_FontFile (const iFontFile *, uint32_t glyphIndex, float xScale, float yScale, float xShift, int *x0, int *y0, int *x1, int *y1); + +/*----------------------------------------------------------------------------------------------*/ + +/* FontSpec describes a typeface, combining multiple fonts into a group. + The user will be choosing FontSpecs instead of individual font files. */ +iDeclareType(FontSpec) +iDeclareTypeConstruction(FontSpec) + +enum iFontSpecFlags { + override_FontSpecFlag = iBit(1), + monospace_FontSpecFlag = iBit(2), /* can be used in preformatted content */ + auxiliary_FontSpecFlag = iBit(3), /* only used for looking up glyphs missing from other fonts */ + arabic_FontSpecFlag = iBit(4), + fixNunitoKerning_FontSpecFlag = iBit(31), /* manual hardcoded kerning tweaks for Nunito */ +}; + struct Impl_FontSpec { iString id; /* unique ID */ iString name; /* human-readable label */ + iString sourcePath; /* file where the path was loaded, could be a .fontpack */ int flags; int priority; float heightScale[2]; /* overall height scaling; ui, document */ @@ -121,10 +132,38 @@ struct Impl_FontSpec { iLocalDef int scaleType_FontSpec(enum iFontSize sizeId) { return sizeId / contentRegular_FontSize; } - + +/*----------------------------------------------------------------------------------------------*/ + +iDeclareType(FontPack) +iDeclareTypeConstruction(FontPack) + +iDeclareType(FontPackId) + +struct Impl_FontPackId { + const iString *id; + int version; +}; + +void setReadOnly_FontPack (iFontPack *, iBool readOnly); +void setStandalone_FontPack (iFontPack *, iBool standalone); +void setLoadPath_FontPack (iFontPack *, const iString *path); +iBool loadArchive_FontPack (iFontPack *, const iArchive *zip); + +iFontPackId id_FontPack (const iFontPack *); +const iPtrArray * listSpecs_FontPack (const iFontPack *); +iBool isReadOnly_FontPack (const iFontPack *); + +iDeclareType(GmDocument) + void init_Fonts (const char *userDir); void deinit_Fonts (void); +const iFontPack * findPack_Fonts (const iString *path); const iFontSpec * findSpec_Fonts (const char *fontId); +const iPtrArray * listPacks_Fonts (void); const iPtrArray * listSpecs_Fonts (iBool (*filterFunc)(const iFontSpec *)); const iPtrArray * listSpecsByPriority_Fonts (void); +const iString * infoPage_Fonts (void); + +iBool preloadLocalFontpackForPreview_Fonts (iGmDocument *doc); diff --git a/src/gempub.c b/src/gempub.c index 23846414..952d72a1 100644 --- a/src/gempub.c +++ b/src/gempub.c @@ -337,7 +337,7 @@ iBool preloadCoverImage_Gempub(const iGempub *d, iGmDocument *doc) { for (size_t linkId = 1; ; linkId++) { const iString *linkUrl = linkUrl_GmDocument(doc, linkId); if (!linkUrl) break; - if (findLinkImage_Media(media_GmDocument(doc), linkId)) { + if (findLinkImage_Media(media_GmDocument(doc), linkId).type) { continue; /* got this already */ } if (linkFlags_GmDocument(doc, linkId) & imageFileExtension_GmLinkFlag) { diff --git a/src/gmdocument.c b/src/gmdocument.c index c98b0bb8..c271ad94 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -27,6 +27,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "ui/color.h" #include "ui/text.h" #include "ui/metrics.h" +#include "ui/mediaui.h" #include "ui/window.h" #include "visited.h" #include "bookmarks.h" @@ -224,7 +225,7 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li setScheme_GmLink_(link, finger_GmLinkScheme); } else if (equalCase_Rangecc(parts.scheme, "file")) { - setScheme_GmLink_(link, file_GmLinkScheme); + setScheme_GmLink_(link, file_GmLinkScheme); } else if (equalCase_Rangecc(parts.scheme, "data")) { setScheme_GmLink_(link, data_GmLinkScheme); @@ -251,6 +252,9 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li endsWithCase_String(path, ".mid") || endsWithCase_String(path, ".ogg")) { link->flags |= audioFileExtension_GmLinkFlag; } + else if (endsWithCase_String(path, ".fontpack")) { + link->flags |= fontpackFileExtension_GmLinkFlag; + } delete_String(path); } /* Check if visited. */ @@ -503,7 +507,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { static const char *arrow = rightArrowhead_Icon; static const char *envelope = envelope_Icon; static const char *bullet = "\u2022"; - static const char *folder = "\U0001f4c1"; + static const char *folder = file_Icon; static const char *globe = globe_Icon; static const char *quote = "\u201c"; static const char *magnifyingGlass = "\U0001f50d"; @@ -900,77 +904,77 @@ static void doLayout_GmDocument_(iGmDocument *d) { ((iGmRun *) back_Array(&d->layout))->flags |= endOfLine_GmRunFlag; /* Image or audio content. */ if (type == link_GmLineType) { - const iMediaId imageId = findLinkImage_Media(d->media, run.linkId); - const iMediaId audioId = !imageId ? findLinkAudio_Media(d->media, run.linkId) : 0; - const iMediaId downloadId = !imageId && !audioId ? findLinkDownload_Media(d->media, run.linkId) : 0; - if (imageId) { - iGmMediaInfo img; - imageInfo_Media(d->media, imageId, &img); - const iInt2 imgSize = imageSize_Media(d->media, imageId); - linkContentWasLaidOut_GmDocument_(d, &img, run.linkId); - const int margin = lineHeight_Text(paragraph_FontId) / 2; + /* TODO: Cleanup here? Move to a function of its own. */ +// enum iMediaType mediaType = none_MediaType; + const iMediaId media = findMediaForLink_Media(d->media, run.linkId, none_MediaType); + iGmMediaInfo info; + info_Media(d->media, media, &info); + run.mediaType = media.type; + run.mediaId = media.id; + run.text = iNullRange; + run.font = uiLabel_FontId; + run.color = 0; + const int margin = lineHeight_Text(paragraph_FontId) / 2; + if (media.type) { pos.y += margin; - run.bounds.pos = pos; - run.bounds.size.x = d->size.x; - const float aspect = (float) imgSize.y / (float) imgSize.x; - run.bounds.size.y = d->size.x * aspect; - /* Extend the image to full width, including outside margin, if the viewport - is narrow enough. */ - if (isFullWidthImages) { - run.bounds.size.x += d->outsideMargin * 2; - run.bounds.size.y += d->outsideMargin * 2 * aspect; - run.bounds.pos.x -= d->outsideMargin; - } - run.visBounds = run.bounds; - const iInt2 maxSize = mulf_I2(imgSize, get_Window()->pixelRatio); - if (width_Rect(run.visBounds) > maxSize.x) { - /* Don't scale the image up. */ - run.visBounds.size.y = - run.visBounds.size.y * maxSize.x / width_Rect(run.visBounds); - run.visBounds.size.x = maxSize.x; - run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2; - run.bounds.size.y = run.visBounds.size.y; - } - run.text = iNullRange; - run.font = 0; - run.color = 0; - run.mediaType = image_GmRunMediaType; - run.mediaId = imageId; - pushBack_Array(&d->layout, &run); - pos.y += run.bounds.size.y + margin; - } - else if (audioId) { - iGmMediaInfo info; - audioInfo_Media(d->media, audioId, &info); + run.bounds.size.y = 0; linkContentWasLaidOut_GmDocument_(d, &info, run.linkId); - const int margin = lineHeight_Text(paragraph_FontId) / 2; - pos.y += margin; - run.bounds.pos = pos; - run.bounds.size.x = d->size.x; - run.bounds.size.y = lineHeight_Text(uiContent_FontId) + 3 * gap_UI; - run.visBounds = run.bounds; - run.text = iNullRange; - run.color = 0; - run.mediaType = audio_GmRunMediaType; - run.mediaId = audioId; - pushBack_Array(&d->layout, &run); - pos.y += run.bounds.size.y + margin; } - else if (downloadId) { - iGmMediaInfo info; - downloadInfo_Media(d->media, downloadId, &info); - linkContentWasLaidOut_GmDocument_(d, &info, run.linkId); - const int margin = lineHeight_Text(paragraph_FontId) / 2; - pos.y += margin; - run.bounds.pos = pos; - run.bounds.size.x = d->size.x; - run.bounds.size.y = 2 * lineHeight_Text(uiContent_FontId) + 4 * gap_UI; - run.visBounds = run.bounds; - run.text = iNullRange; - run.color = 0; - run.mediaType = download_GmRunMediaType; - run.mediaId = downloadId; - pushBack_Array(&d->layout, &run); + switch (media.type) { + case image_MediaType: { + const iInt2 imgSize = imageSize_Media(d->media, media); + run.bounds.pos = pos; + run.bounds.size.x = d->size.x; + const float aspect = (float) imgSize.y / (float) imgSize.x; + run.bounds.size.y = d->size.x * aspect; + /* Extend the image to full width, including outside margin, if the viewport + is narrow enough. */ + if (isFullWidthImages) { + run.bounds.size.x += d->outsideMargin * 2; + run.bounds.size.y += d->outsideMargin * 2 * aspect; + run.bounds.pos.x -= d->outsideMargin; + } + run.visBounds = run.bounds; + const iInt2 maxSize = mulf_I2(imgSize, get_Window()->pixelRatio); + if (width_Rect(run.visBounds) > maxSize.x) { + /* Don't scale the image up. */ + run.visBounds.size.y = + run.visBounds.size.y * maxSize.x / width_Rect(run.visBounds); + run.visBounds.size.x = maxSize.x; + run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2; + run.bounds.size.y = run.visBounds.size.y; + } + pushBack_Array(&d->layout, &run); + break; + } + case audio_MediaType: { + run.bounds.pos = pos; + run.bounds.size.x = d->size.x; + run.bounds.size.y = lineHeight_Text(uiContent_FontId) + 3 * gap_UI; + run.visBounds = run.bounds; + pushBack_Array(&d->layout, &run); + break; + } + case download_MediaType: { + run.bounds.pos = pos; + run.bounds.size.x = d->size.x; + run.bounds.size.y = 2 * lineHeight_Text(uiContent_FontId) + 4 * gap_UI; + run.visBounds = run.bounds; + pushBack_Array(&d->layout, &run); + break; + } + case fontpack_MediaType: { + run.bounds.pos = pos; + run.bounds.size.x = d->size.x; + run.bounds.size.y = height_FontpackUI(d->media, media.id, d->size.x); + run.visBounds = run.bounds; + pushBack_Array(&d->layout, &run); + break; + } + default: + break; + } + if (media.type && run.bounds.size.y) { pos.y += run.bounds.size.y + margin; } } @@ -1057,21 +1061,6 @@ const iString *url_GmDocument(const iGmDocument *d) { return &d->url; } -#if 0 -void reset_GmDocument(iGmDocument *d) { - clear_Media(d->media); - clearLinks_GmDocument_(d); - clear_Array(&d->layout); - clear_Array(&d->headings); - clear_Array(&d->preMeta); - clear_String(&d->url); - clear_String(&d->localHost); - clear_String(&d->source); - clear_String(&d->unormSource); - d->themeSeed = 0; -} -#endif - static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { set_Color(tmQuoteIcon_ColorId, mix_Color(get_Color(tmQuote_ColorId), get_Color(tmBackground_ColorId), 0.55f)); diff --git a/src/gmdocument.h b/src/gmdocument.h index b2c6d9b7..20bc9890 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h @@ -90,6 +90,7 @@ enum iGmLinkFlag { query_GmLinkFlag = iBit(14), /* Gopher query link */ iconFromLabel_GmLinkFlag = iBit(15), /* use an Emoji/special character from label */ isOpen_GmLinkFlag = iBit(16), /* currently open in a tab */ + fontpackFileExtension_GmLinkFlag = iBit(17), }; iLocalDef enum iGmLinkScheme scheme_GmLinkFlag(int flags) { @@ -126,13 +127,6 @@ enum iGmRunFlags { altText_GmRunFlag = iBit(8), }; -enum iGmRunMediaType { - none_GmRunMediaType, - image_GmRunMediaType, - audio_GmRunMediaType, - download_GmRunMediaType, -}; - /* This structure is tightly packed because GmDocuments are mostly composed of a large number of GmRuns. */ struct Impl_GmRun { @@ -146,12 +140,16 @@ struct Impl_GmRun { uint32_t color : 7; /* see max_ColorId */ uint32_t font : 10; - uint32_t mediaType : 2; - uint32_t mediaId : 10; /* zero if not an image */ + uint32_t mediaType : 3; + uint32_t mediaId : 9; /* zero if not an image */ uint32_t preId : 10; /* preformatted block ID (sequential); merge with mediaId? */ }; }; +iLocalDef iMediaId mediaId_GmRun(const iGmRun *d) { + return (iMediaId){ .type = d->mediaType, .id = d->mediaId }; +} + iDeclareType(GmRunRange) struct Impl_GmRunRange { diff --git a/src/gmrequest.c b/src/gmrequest.c index 1a9e83a9..f7a22e0a 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c @@ -361,6 +361,9 @@ static const iBlock *aboutPageSource_(iRangecc path, iRangecc query) { if (equalCase_Rangecc(path, "debug")) { return utf8_String(debugInfo_App()); } + if (equalCase_Rangecc(path, "fonts")) { + return utf8_String(infoPage_Fonts()); + } if (equalCase_Rangecc(path, "feeds")) { return utf8_String(entryListPage_Feeds()); } @@ -710,8 +713,9 @@ void submit_GmRequest(iGmRequest *d) { sort_Array(sortedInfo, (int (*)(const void *, const void *)) cmp_FileInfoPtr_); iForEach(PtrArray, s, sortedInfo) { const iFileInfo *entry = s.ptr; - appendFormat_String(page, "=> %s %s%s\n", + appendFormat_String(page, "=> %s %s%s%s\n", cstrCollect_String(makeFileUrl_String(path_FileInfo(entry))), + isDirectory_FileInfo(entry) ? folder_Icon " " : "", cstr_Rangecc(baseName_Path(path_FileInfo(entry))), isDirectory_FileInfo(entry) ? iPathSeparator : ""); iRelease(entry); @@ -808,9 +812,10 @@ void submit_GmRequest(iGmRequest *d) { const iString *subPath = e.value; iRangecc relSub = range_String(subPath); relSub.start += size_String(entryPath); - appendFormat_String(page, "=> %s/%s %s\n", + appendFormat_String(page, "=> %s/%s %s%s\n", cstr_String(&d->url), cstr_String(withSpacesEncoded_String(collectNewRange_String(relSub))), + endsWith_Rangecc(relSub, "/") ? folder_Icon " " : "", cstr_Rangecc(relSub)); } resp->statusCode = success_GmStatusCode; diff --git a/src/gmutil.c b/src/gmutil.c index 971747d4..5be7e198 100644 --- a/src/gmutil.c +++ b/src/gmutil.c @@ -21,6 +21,7 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "gmutil.h" +#include "fontpack.h" #include #include @@ -511,7 +512,8 @@ const iString *findContainerArchive_Path(const iString *path) { while (!isEmpty_String(path) && cmp_String(path, ".")) { iString *dir = newRange_String(dirName_Path(path)); if (endsWithCase_String(dir, ".zip") || - endsWithCase_String(dir, ".gpub")) { + endsWithCase_String(dir, ".gpub") || + endsWithCase_String(dir, ".fontpack")) { iEndCollect(); return collect_String(dir); } @@ -534,6 +536,9 @@ const char *mediaTypeFromFileExtension_String(const iString *d) { else if (endsWithCase_String(d, ".gpub")) { return "application/gpub+zip"; } + else if (endsWithCase_String(d, ".fontpack")) { + return mimeType_FontPack; + } else if (endsWithCase_String(d, ".xml")) { return "text/xml"; } @@ -562,6 +567,7 @@ const char *mediaTypeFromFileExtension_String(const iString *d) { return "audio/midi"; } else if (endsWithCase_String(d, ".txt") || + endsWithCase_String(d, ".ini") || endsWithCase_String(d, ".md") || endsWithCase_String(d, ".c") || endsWithCase_String(d, ".h") || diff --git a/src/media.c b/src/media.c index 26f0af4b..0ce2ac5c 100644 --- a/src/media.c +++ b/src/media.c @@ -36,6 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include +#include #include #include #include @@ -287,45 +288,112 @@ iDefineTypeConstruction(GmDownload) /*----------------------------------------------------------------------------------------------*/ +iDeclareType(GmFontpack) + +struct Impl_GmFontpack { + iGmMediaProps props; + iString packId; + iFontpackMediaInfo info; + /* TODO: Font preview images? */ +}; + +void init_GmFontpack(iGmFontpack *d) { + init_GmMediaProps_(&d->props); + init_String(&d->packId); + iZap(d->info); + d->info.names = new_StringList(); +} + +void deinit_GmFontpack(iGmFontpack *d) { + iRelease(d->info.names); + deinit_String(&d->packId); + deinit_GmMediaProps_(&d->props); +} + +static void loadData_GmFontpack_(iGmFontpack *d, const iBlock *data) { + const iString *loadPath = collect_String(localFilePathFromUrl_String(&d->props.url)); + const iFontPack *pack = findPack_Fonts(loadPath); + d->info.isValid = d->info.isInstalled = pack != NULL; + d->info.isReadOnly = iFalse; + if (!pack) { + /* Let's load it now temporarily and see what's inside. */ + iArchive *zip = new_Archive(); + if (openData_Archive(zip, data)) { + iFontPack *fp = collect_FontPack(new_FontPack()); + setLoadPath_FontPack(fp, loadPath); + setStandalone_FontPack(fp, iTrue); + if (loadArchive_FontPack(fp, zip)) { + d->info.isValid = iTrue; + pack = fp; + } + } + iRelease(zip); + } + if (pack) { + set_String(&d->packId, id_FontPack(pack).id); + d->info.packId.id = &d->packId; /* we own this String */ + d->info.packId.version = id_FontPack(pack).version; + d->info.isReadOnly = isReadOnly_FontPack(pack); + } + iPtrSet *unique = new_PtrSet(); + iConstForEach(PtrArray, i, listSpecs_FontPack(pack)) { + const iFontSpec *spec = i.ptr; + pushBack_StringList(d->info.names, &spec->name); + iForIndices(j, spec->styles) { + insert_PtrSet(unique, spec->styles[j]); + } + } + iConstForEach(PtrSet, j, unique) { + d->info.sizeInBytes += size_Block(&((const iFontFile *) *j.value)->sourceData); + } + delete_PtrSet(unique); +} + +iDefineTypeConstruction(GmFontpack) + +/*----------------------------------------------------------------------------------------------*/ + struct Impl_Media { - iPtrArray images; - iPtrArray audio; - iPtrArray downloads; + iPtrArray items[max_MediaType]; + /* TODO: Add a hash to quickly look up a link's media. */ }; iDefineTypeConstruction(Media) void init_Media(iMedia *d) { - init_PtrArray(&d->images); - init_PtrArray(&d->audio); - init_PtrArray(&d->downloads); + iForIndices(i, d->items) { + init_PtrArray(&d->items[i]); + } } void deinit_Media(iMedia *d) { clear_Media(d); - deinit_PtrArray(&d->downloads); - deinit_PtrArray(&d->audio); - deinit_PtrArray(&d->images); + iForIndices(i, d->items) { + deinit_PtrArray(&d->items[i]); + } } void clear_Media(iMedia *d) { - iForEach(PtrArray, i, &d->images) { + iForEach(PtrArray, i, &d->items[image_MediaType]) { deinit_GmImage(i.ptr); } - clear_PtrArray(&d->images); - iForEach(PtrArray, a, &d->audio) { + iForEach(PtrArray, a, &d->items[audio_MediaType]) { deinit_GmAudio(a.ptr); } - clear_PtrArray(&d->audio); - iForEach(PtrArray, n, &d->downloads) { + iForEach(PtrArray, n, &d->items[download_MediaType]) { deinit_GmDownload(n.ptr); } - clear_PtrArray(&d->downloads); + iForEach(PtrArray, f, &d->items[fontpack_MediaType]) { + deinit_GmFontpack(f.ptr); + } + iForIndices(type, d->items) { + clear_PtrArray(&d->items[type]); + } } size_t memorySize_Media(const iMedia *d) { size_t memSize = 0; - iConstForEach(PtrArray, i, &d->images) { + iConstForEach(PtrArray, i, &d->items[image_MediaType]) { const iGmImage *img = i.ptr; if (img->texture) { const iInt2 texSize = size_SDLTexture(img->texture); @@ -335,34 +403,49 @@ size_t memorySize_Media(const iMedia *d) { memSize += size_Block(&img->partialData); } } - iConstForEach(PtrArray, a, &d->audio) { + iConstForEach(PtrArray, a, &d->items[audio_MediaType]) { const iGmAudio *audio = a.ptr; if (audio->player) { memSize += sourceDataSize_Player(audio->player); } } - iConstForEach(PtrArray, n, &d->downloads) { + iConstForEach(PtrArray, n, &d->items[download_MediaType]) { const iGmDownload *down = n.ptr; memSize += down->numBytes; } return memSize; } -iBool setDownloadUrl_Media(iMedia *d, iGmLinkId linkId, const iString *url) { - iGmDownload *dl = NULL; - iMediaId existing = findLinkDownload_Media(d, linkId); - iBool isNew = iFalse; - if (!existing) { - isNew = iTrue; - dl = new_GmDownload(); - dl->props.linkId = linkId; - dl->props.isPermanent = iTrue; - set_String(&dl->props.url, url); - pushBack_PtrArray(&d->downloads, dl); +iBool setUrl_Media(iMedia *d, iGmLinkId linkId, enum iMediaType mediaType, const iString *url) { + iMediaId existing = findMediaForLink_Media(d, linkId, mediaType); + const iBool isNew = !existing.id; + iGmMediaProps *props = NULL; + if (mediaType == download_MediaType) { + iGmDownload *dl = NULL; + if (isNew) { + dl = new_GmDownload(); + pushBack_PtrArray(&d->items[download_MediaType], dl); + } + else { + dl = at_PtrArray(&d->items[download_MediaType], index_MediaId(existing)); + } + props = &dl->props; } - else { - iGmDownload *dl = at_PtrArray(&d->downloads, existing - 1); - set_String(&dl->props.url, url); + else if (mediaType == fontpack_MediaType) { + iGmFontpack *fp = NULL; + if (isNew) { + fp = new_GmFontpack(); + pushBack_PtrArray(&d->items[fontpack_MediaType], fp); + } + else { + fp = at_PtrArray(&d->items[fontpack_MediaType], index_MediaId(existing)); + } + props = &fp->props; + } + if (props) { + props->linkId = linkId; + props->isPermanent = iTrue; + set_String(&props->url, url); } return isNew; } @@ -372,16 +455,17 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo const iBool isPartial = (flags & partialData_MediaFlag) != 0; const iBool allowHide = (flags & allowHide_MediaFlag) != 0; const iBool isDeleting = (!mime || !data); - iMediaId existing = findLinkImage_Media(d, linkId); + iMediaId existing = findMediaForLink_Media(d, linkId, none_MediaType);// findLinkImage_Media(d, linkId); + const size_t existingIndex = index_MediaId(existing); iBool isNew = iFalse; - if (existing) { + if (existing.type == image_MediaType) { iGmImage *img; if (isDeleting) { - take_PtrArray(&d->images, existing - 1, (void **) &img); + take_PtrArray(&d->items[image_MediaType], existingIndex, (void **) &img); delete_GmImage(img); } else { - img = at_PtrArray(&d->images, existing - 1); + img = at_PtrArray(&d->items[image_MediaType], existingIndex); iAssert(equal_String(&img->props.mime, mime)); /* MIME cannot change */ set_Block(&img->partialData, data); if (!isPartial) { @@ -389,14 +473,14 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo } } } - else if ((existing = findLinkAudio_Media(d, linkId)) != 0) { + else if (existing.type == audio_MediaType) { iGmAudio *audio; if (isDeleting) { - take_PtrArray(&d->audio, existing - 1, (void **) &audio); + take_PtrArray(&d->items[audio_MediaType], existingIndex, (void **) &audio); delete_GmAudio(audio); } else { - audio = at_PtrArray(&d->audio, existing - 1); + audio = at_PtrArray(&d->items[audio_MediaType], existingIndex); iAssert(equal_String(&audio->props.mime, mime)); /* MIME cannot change */ updateSourceData_Player(audio->player, mime, data, append_PlayerUpdate); if (!isPartial) { @@ -408,14 +492,14 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo } } } - else if ((existing = findLinkDownload_Media(d, linkId)) != 0) { + else if (existing.type == download_MediaType) { iGmDownload *dl; if (isDeleting) { - take_PtrArray(&d->downloads, existing - 1, (void **) &dl); + take_PtrArray(&d->items[download_MediaType], existingIndex, (void **) &dl); delete_GmDownload(dl); } else { - dl = at_PtrArray(&d->downloads, existing - 1); + dl = at_PtrArray(&d->items[download_MediaType], existingIndex); if (isEmpty_String(&dl->props.mime)) { set_String(&dl->props.mime, mime); } @@ -428,6 +512,21 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo } } } + else if (existing.type == fontpack_MediaType) { + iGmFontpack *fp; + if (isDeleting) { + take_PtrArray(&d->items[fontpack_MediaType], existingIndex, (void **) &fp); + delete_GmFontpack(fp); + } + else { + iAssert(!isPartial); + fp = at_PtrArray(&d->items[fontpack_MediaType], existingIndex); + if (isEmpty_String(&fp->props.mime)) { + set_String(&fp->props.mime, mime); + } + loadData_GmFontpack_(fp, data); + } + } else if (!isDeleting) { if (startsWith_String(mime, "image/")) { /* Copy the image to a texture. */ @@ -435,7 +534,7 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo img->props.linkId = linkId; /* TODO: use a hash? */ img->props.isPermanent = !allowHide; set_String(&img->props.mime, mime); - pushBack_PtrArray(&d->images, img); + pushBack_PtrArray(&d->items[image_MediaType], img); if (!isPartial) { makeTexture_GmImage(img); } @@ -450,7 +549,7 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo if (!isPartial) { updateSourceData_Player(audio->player, NULL, NULL, complete_PlayerUpdate); } - pushBack_PtrArray(&d->audio, audio); + pushBack_PtrArray(&d->items[audio_MediaType], audio); /* Start playing right away. */ start_Player(audio->player); postCommandf_App("media.player.started player:%p", audio->player); @@ -460,125 +559,135 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo return isNew; } -iMediaId findLinkImage_Media(const iMedia *d, iGmLinkId linkId) { - /* TODO: use a hash */ - iConstForEach(PtrArray, i, &d->images) { - const iGmImage *img = i.ptr; - if (img->props.linkId == linkId) { - return index_PtrArrayConstIterator(&i) + 1; +static iMediaId findMediaPtr_Media_(const iPtrArray *items, enum iMediaType mediaType, iGmLinkId linkId) { + iConstForEach(PtrArray, i, items) { + const iGmMediaProps *props = i.ptr; + if (props->linkId == linkId) { + return (iMediaId){ + .type = mediaType, + .id = index_PtrArrayConstIterator(&i) + 1 + }; } } - return 0; -} - -size_t numAudio_Media(const iMedia *d) { - return size_PtrArray(&d->audio); + return iInvalidMediaId; } -iMediaId findLinkAudio_Media(const iMedia *d, iGmLinkId linkId) { - /* TODO: use a hash */ - iConstForEach(PtrArray, i, &d->audio) { - const iGmAudio *audio = i.ptr; - if (audio->props.linkId == linkId) { - return index_PtrArrayConstIterator(&i) + 1; +iMediaId findMediaForLink_Media(const iMedia *d, iGmLinkId linkId, enum iMediaType mediaType) { + /* TODO: Use hashes, this will get very slow if there is a large number of media items. */ + iMediaId mid; + for (int i = 0; i < max_MediaType; i++) { + if (mediaType == i || !mediaType) { + mid = findMediaPtr_Media_(&d->items[i], i, linkId); + if (mid.type) { + return mid; + } } } - return 0; + return iInvalidMediaId; } -iMediaId findLinkDownload_Media(const iMedia *d, uint16_t linkId) { - iConstForEach(PtrArray, i, &d->downloads) { - const iGmDownload *dl = i.ptr; - if (dl->props.linkId == linkId) { - return index_PtrArrayConstIterator(&i) + 1; - } - } - return 0; +size_t numAudio_Media(const iMedia *d) { + return size_PtrArray(&d->items[audio_MediaType]); } iInt2 imageSize_Media(const iMedia *d, iMediaId imageId) { - if (imageId > 0 && imageId <= size_PtrArray(&d->images)) { - const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1); + iAssert(imageId.type == image_MediaType); + const size_t index = index_MediaId(imageId); + if (index < size_PtrArray(&d->items[image_MediaType])) { + const iGmImage *img = constAt_PtrArray(&d->items[image_MediaType], index); return img->size; } return zero_I2(); } -SDL_Texture *imageTexture_Media(const iMedia *d, uint16_t imageId) { - if (imageId > 0 && imageId <= size_PtrArray(&d->images)) { - const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1); +SDL_Texture *imageTexture_Media(const iMedia *d, iMediaId imageId) { + iAssert(imageId.type == image_MediaType); + const size_t index = index_MediaId(imageId); + if (index < size_PtrArray(&d->items[image_MediaType])) { + const iGmImage *img = constAt_PtrArray(&d->items[image_MediaType], index); return img->texture; } return NULL; } -iBool imageInfo_Media(const iMedia *d, iMediaId imageId, iGmMediaInfo *info_out) { - if (imageId > 0 && imageId <= size_PtrArray(&d->images)) { - const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1); - info_out->numBytes = img->numBytes; - info_out->type = cstr_String(&img->props.mime); - info_out->isPermanent = img->props.isPermanent; - return iTrue; +iBool info_Media(const iMedia *d, iMediaId mediaId, iGmMediaInfo *info_out) { + /* TODO: Use a hash. */ + const size_t index = index_MediaId(mediaId); + switch (mediaId.type) { + case image_MediaType: + if (index < size_PtrArray(&d->items[image_MediaType])) { + const iGmImage *img = constAt_PtrArray(&d->items[image_MediaType], index); + info_out->numBytes = img->numBytes; + info_out->type = cstr_String(&img->props.mime); + info_out->isPermanent = img->props.isPermanent; + return iTrue; + } + break; + case audio_MediaType: + if (index < size_PtrArray(&d->items[audio_MediaType])) { + const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], index); + info_out->type = cstr_String(&audio->props.mime); + info_out->isPermanent = audio->props.isPermanent; + return iTrue; + } + break; + case download_MediaType: + if (index < size_PtrArray(&d->items[download_MediaType])) { + const iGmDownload *dl = constAt_PtrArray(&d->items[download_MediaType], index); + info_out->type = cstr_String(&dl->props.mime); + info_out->isPermanent = dl->props.isPermanent; + info_out->numBytes = dl->numBytes; + return iTrue; + } + break; + case fontpack_MediaType: + /* TODO */ + break; + default: + break; } iZap(*info_out); return iFalse; } iPlayer *audioData_Media(const iMedia *d, iMediaId audioId) { - if (audioId > 0 && audioId <= size_PtrArray(&d->audio)) { - const iGmAudio *audio = constAt_PtrArray(&d->audio, audioId - 1); + iAssert(audioId.type == audio_MediaType); + const size_t index = index_MediaId(audioId); + if (index < size_PtrArray(&d->items[audio_MediaType])) { + const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], index); return audio->player; } return NULL; } -iBool audioInfo_Media(const iMedia *d, iMediaId audioId, iGmMediaInfo *info_out) { - if (audioId > 0 && audioId <= size_PtrArray(&d->audio)) { - const iGmAudio *audio = constAt_PtrArray(&d->audio, audioId - 1); - info_out->type = cstr_String(&audio->props.mime); - info_out->isPermanent = audio->props.isPermanent; - return iTrue; - } - iZap(*info_out); - return iFalse; -} - iPlayer *audioPlayer_Media(const iMedia *d, iMediaId audioId) { - if (audioId > 0 && audioId <= size_PtrArray(&d->audio)) { - const iGmAudio *audio = constAt_PtrArray(&d->audio, audioId - 1); + iAssert(audioId.type == audio_MediaType); + const size_t index = index_MediaId(audioId); + if (index < size_PtrArray(&d->items[audio_MediaType])) { + const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], index); return audio->player; } return NULL; } void pauseAllPlayers_Media(const iMedia *d, iBool setPaused) { - for (size_t i = 0; i < size_PtrArray(&d->audio); ++i) { - const iGmAudio *audio = constAt_PtrArray(&d->audio, i); + for (size_t i = 0; i < size_PtrArray(&d->items[audio_MediaType]); ++i) { + const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], i); if (audio->player) { setPaused_Player(audio->player, setPaused); } } } -iBool downloadInfo_Media(const iMedia *d, iMediaId downloadId, iGmMediaInfo *info_out) { - if (downloadId > 0 && downloadId <= size_PtrArray(&d->downloads)) { - const iGmDownload *dl = constAt_PtrArray(&d->downloads, downloadId - 1); - info_out->type = cstr_String(&dl->props.mime); - info_out->isPermanent = dl->props.isPermanent; - info_out->numBytes = dl->numBytes; - return iTrue; - } - iZap(*info_out); - return iFalse; -} - void downloadStats_Media(const iMedia *d, iMediaId downloadId, const iString **path_out, float *bytesPerSecond_out, iBool *isFinished_out) { - *path_out = NULL; + iAssert(downloadId.type == download_MediaType); + *path_out = NULL; *bytesPerSecond_out = 0.0f; - *isFinished_out = iFalse; - if (downloadId > 0 && downloadId <= size_PtrArray(&d->downloads)) { - const iGmDownload *dl = constAt_PtrArray(&d->downloads, downloadId - 1); + *isFinished_out = iFalse; + const size_t index = index_MediaId(downloadId); + if (index < size_PtrArray(&d->items[download_MediaType])) { + const iGmDownload *dl = constAt_PtrArray(&d->items[download_MediaType], index); if (dl->path) { *path_out = dl->path; } @@ -587,6 +696,16 @@ void downloadStats_Media(const iMedia *d, iMediaId downloadId, const iString **p } } +void fontpackInfo_Media(const iMedia *d, iMediaId fontpackId, iFontpackMediaInfo *info_out) { + iAssert(fontpackId.type == fontpack_MediaType); + iZap(*info_out); + const size_t index = index_MediaId(fontpackId); + if (index < size_PtrArray(&d->items[fontpack_MediaType])) { + const iGmFontpack *fp = constAt_PtrArray(&d->items[fontpack_MediaType], index); + *info_out = fp->info; + } +} + /*----------------------------------------------------------------------------------------------*/ static void updated_MediaRequest_(iAnyObject *obj) { diff --git a/src/media.h b/src/media.h index f7ad6efd..47a4da93 100644 --- a/src/media.h +++ b/src/media.h @@ -22,13 +22,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once +#include "fontpack.h" + #include #include #include #include -typedef uint16_t iMediaId; - iDeclareType(Player) iDeclareType(GmMediaInfo) @@ -38,6 +38,7 @@ struct Impl_GmMediaInfo { iBool isPermanent; }; +iDeclareType(MediaId) iDeclareType(Media) iDeclareTypeConstruction(Media) @@ -46,28 +47,81 @@ enum iMediaFlags { partialData_MediaFlag = iBit(2), }; -void clear_Media (iMedia *); -iBool setDownloadUrl_Media (iMedia *, uint16_t linkId, const iString *url); -iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags); - -size_t memorySize_Media (const iMedia *); +enum iMediaType { /* Note: There is a limited number of bits for these; see GmRun below. */ + none_MediaType, + image_MediaType, + //animatedImage_MediaType, /* TODO */ + audio_MediaType, + download_MediaType, + fontpack_MediaType, + max_MediaType +}; -iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId); -iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmMediaInfo *info_out); -iInt2 imageSize_Media (const iMedia *, iMediaId imageId); -SDL_Texture * imageTexture_Media (const iMedia *, iMediaId imageId); +struct Impl_MediaId { + enum iMediaType type; + uint16_t id; /* see GmRun for actually used number of bits */ +}; -size_t numAudio_Media (const iMedia *); -iMediaId findLinkAudio_Media (const iMedia *, uint16_t linkId); -iBool audioInfo_Media (const iMedia *, iMediaId audioId, iGmMediaInfo *info_out); -iPlayer * audioPlayer_Media (const iMedia *, iMediaId audioId); -void pauseAllPlayers_Media(const iMedia *, iBool setPaused); +iLocalDef size_t index_MediaId(const iMediaId mediaId) { + return (size_t) mediaId.id - 1; +} + +#define iInvalidMediaId (iMediaId){ none_MediaType, 0 } + +void clear_Media (iMedia *); +iBool setUrl_Media (iMedia *, uint16_t linkId, enum iMediaType mediaType, const iString *url); +iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags); + +size_t memorySize_Media (const iMedia *); +iMediaId findMediaForLink_Media (const iMedia *, uint16_t linkId, enum iMediaType mediaType); + +iMediaId id_Media (const iMedia *, uint16_t linkId, enum iMediaType type); +iBool info_Media (const iMedia *, iMediaId mediaId, iGmMediaInfo *info_out); + +iLocalDef iMediaId findLinkImage_Media(const iMedia *d, uint16_t linkId) { + return findMediaForLink_Media(d, linkId, image_MediaType); +} +iLocalDef iMediaId findLinkAudio_Media (const iMedia *d, uint16_t linkId) { + return findMediaForLink_Media(d, linkId, audio_MediaType); +} +iLocalDef iMediaId findLinkDownload_Media(const iMedia *d, uint16_t linkId) { + return findMediaForLink_Media(d, linkId, download_MediaType); +} + +iLocalDef iBool imageInfo_Media(const iMedia *d, uint16_t mediaId, iGmMediaInfo *info_out) { + return info_Media(d, (iMediaId){ image_MediaType, mediaId }, info_out); +} +iLocalDef iBool audioInfo_Media(const iMedia *d, uint16_t mediaId, iGmMediaInfo *info_out) { + return info_Media(d, (iMediaId){ audio_MediaType, mediaId }, info_out); +} +iLocalDef iBool downloadInfo_Media(const iMedia *d, uint16_t mediaId, iGmMediaInfo *info_out) { + return info_Media(d, (iMediaId){ download_MediaType, mediaId }, info_out); +} + +iInt2 imageSize_Media (const iMedia *, iMediaId imageId); +SDL_Texture * imageTexture_Media (const iMedia *, iMediaId imageId); + +size_t numAudio_Media (const iMedia *); +iPlayer * audioPlayer_Media (const iMedia *, iMediaId audioId); +void pauseAllPlayers_Media (const iMedia *, iBool setPaused); -iMediaId findLinkDownload_Media (const iMedia *, uint16_t linkId); -iBool downloadInfo_Media (const iMedia *, iMediaId downloadId, iGmMediaInfo *info_out); void downloadStats_Media (const iMedia *, iMediaId downloadId, const iString **path_out, float *bytesPerSecond_out, iBool *isFinished_out); +iDeclareType(FontpackMediaInfo) + +struct Impl_FontpackMediaInfo { + iFontPackId packId; + iBool isValid; + iBool isInstalled; + iBool isReadOnly; + size_t sizeInBytes; + iStringList *names; +}; + +void fontpackInfo_Media (const iMedia *, iMediaId fontpackId, + iFontpackMediaInfo *info_out); + /*----------------------------------------------------------------------------------------------*/ iDeclareType(GmRequest) @@ -78,7 +132,7 @@ iDeclareClass(MediaRequest) struct Impl_MediaRequest { iObject object; iDocumentWidget *doc; - unsigned int linkId; + unsigned int linkId; iGmRequest * req; }; diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 45a8cf2d..44db3e5b 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -400,7 +400,18 @@ void init_DocumentWidget(iDocumentWidget *d) { addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root"); } +void cancelAllRequests_DocumentWidget(iDocumentWidget *d) { + iForEach(ObjectList, i, d->media) { + iMediaRequest *mr = i.object; + cancel_GmRequest(mr->req); + } + if (d->request) { + cancel_GmRequest(d->request); + } +} + void deinit_DocumentWidget(iDocumentWidget *d) { + cancelAllRequests_DocumentWidget(d); pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); removeTicker_App(animate_DocumentWidget_, d); removeTicker_App(prerender_DocumentWidget_, d); @@ -564,7 +575,8 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { pushBack_PtrArray(&d->visibleWideRuns, run); } } - if (run->mediaType == audio_GmRunMediaType || run->mediaType == download_GmRunMediaType) { + /* Image runs are static so they're drawn as part of the content. */ + if (run->mediaType && run->mediaType != image_MediaType) { iAssert(run->mediaId); pushBack_PtrArray(&d->visibleMedia, run); } @@ -758,14 +770,14 @@ static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) { uint32_t interval = invalidInterval_; iConstForEach(PtrArray, i, &d->visibleMedia) { const iGmRun *run = i.ptr; - if (run->mediaType == audio_GmRunMediaType) { - iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); + if (run->mediaType == audio_MediaType) { + iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); if (flags_Player(plr) & adjustingVolume_PlayerFlag || (isStarted_Player(plr) && !isPaused_Player(plr))) { interval = iMin(interval, 1000 / 15); } } - else if (run->mediaType == download_GmRunMediaType) { + else if (run->mediaType == download_MediaType) { interval = iMin(interval, 1000); } } @@ -784,8 +796,8 @@ static void updateMedia_DocumentWidget_(iDocumentWidget *d) { refresh_Widget(d); iConstForEach(PtrArray, i, &d->visibleMedia) { const iGmRun *run = i.ptr; - if (run->mediaType == audio_GmRunMediaType) { - iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); + if (run->mediaType == audio_MediaType) { + iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag && flags_Player(plr) & adjustingVolume_PlayerFlag) { setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse); @@ -1244,6 +1256,9 @@ static const char *zipPageHeading_(const iRangecc mime) { if (equalCase_Rangecc(mime, "application/gpub+zip")) { return book_Icon " Gempub"; } + else if (equalCase_Rangecc(mime, mimeType_FontPack)) { + return "\U0001f520 Fontpack"; + } iRangecc type = iNullRange; nextSplit_Rangecc(mime, "/", &type); /* skip the part before the slash */ nextSplit_Rangecc(mime, "/", &type); @@ -1258,165 +1273,175 @@ static const char *zipPageHeading_(const iRangecc mime) { static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) { iWidget *w = as_Widget(d); - delete_Gempub(d->sourceGempub); - d->sourceGempub = NULL; - if (!cmpCase_String(&d->sourceMime, "application/octet-stream") || - !cmpCase_String(&d->sourceMime, mimeType_Gempub) || - endsWithCase_String(d->mod.url, ".gpub")) { - iGempub *gempub = new_Gempub(); - if (open_Gempub(gempub, &d->sourceContent)) { - setBaseUrl_Gempub(gempub, d->mod.url); - setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub))); - setCStr_String(&d->sourceMime, mimeType_Gempub); - d->sourceGempub = gempub; - } - else { - delete_Gempub(gempub); - } - } - if (!d->sourceGempub) { - const iString *localPath = collect_String(localFilePathFromUrl_String(d->mod.url)); - iBool isInside = iFalse; - if (localPath && !fileExists_FileInfo(localPath)) { - /* This URL may refer to a file inside the archive. */ - localPath = findContainerArchive_Path(localPath); - isInside = iTrue; - } - if (localPath && equal_CStr(mediaType_Path(localPath), "application/gpub+zip")) { + /* Gempub page behavior and footer actions. */ { + /* TODO: move this to gempub.c */ + delete_Gempub(d->sourceGempub); + d->sourceGempub = NULL; + if (!cmpCase_String(&d->sourceMime, "application/octet-stream") || + !cmpCase_String(&d->sourceMime, mimeType_Gempub) || + endsWithCase_String(d->mod.url, ".gpub")) { iGempub *gempub = new_Gempub(); - if (openFile_Gempub(gempub, localPath)) { - setBaseUrl_Gempub(gempub, collect_String(makeFileUrl_String(localPath))); - if (!isInside) { - setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub))); - setCStr_String(&d->sourceMime, mimeType_Gempub); - } + if (open_Gempub(gempub, &d->sourceContent)) { + setBaseUrl_Gempub(gempub, d->mod.url); + setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub))); + setCStr_String(&d->sourceMime, mimeType_Gempub); d->sourceGempub = gempub; } else { delete_Gempub(gempub); } } - } - if (d->sourceGempub) { - if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub))) { - if (!isRemote_Gempub(d->sourceGempub)) { - iArray *items = collectNew_Array(sizeof(iMenuItem)); - pushBack_Array( - items, - &(iMenuItem){ book_Icon " ${gempub.cover.view}", - 0, - 0, - format_CStr("!open url:%s", - cstr_String(indexPageUrl_Gempub(d->sourceGempub))) }); - if (navSize_Gempub(d->sourceGempub) > 0) { - pushBack_Array( - items, - &(iMenuItem){ - format_CStr(forwardArrow_Icon " %s", - cstr_String(navLinkLabel_Gempub(d->sourceGempub, 0))), - SDLK_RIGHT, - 0, - format_CStr("!open url:%s", - cstr_String(navLinkUrl_Gempub(d->sourceGempub, 0))) }); - } - makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); + if (!d->sourceGempub) { + const iString *localPath = collect_String(localFilePathFromUrl_String(d->mod.url)); + iBool isInside = iFalse; + if (localPath && !fileExists_FileInfo(localPath)) { + /* This URL may refer to a file inside the archive. */ + localPath = findContainerArchive_Path(localPath); + isInside = iTrue; } - else { - makeFooterButtons_DocumentWidget_( - d, - (iMenuItem[]){ { book_Icon " ${menu.save.downloads.open}", - SDLK_s, - KMOD_PRIMARY | KMOD_SHIFT, - "document.save open:1" }, - { download_Icon " " saveToDownloads_Label, - SDLK_s, - KMOD_PRIMARY, - "document.save" } }, - 2); - } - if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { - redoLayout_GmDocument(d->doc); - updateVisible_DocumentWidget_(d); - invalidate_DocumentWidget_(d); + if (localPath && equal_CStr(mediaType_Path(localPath), mimeType_Gempub)) { + iGempub *gempub = new_Gempub(); + if (openFile_Gempub(gempub, localPath)) { + setBaseUrl_Gempub(gempub, collect_String(makeFileUrl_String(localPath))); + if (!isInside) { + setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub))); + setCStr_String(&d->sourceMime, mimeType_Gempub); + } + d->sourceGempub = gempub; + } + else { + delete_Gempub(gempub); + } } } - else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { - makeFooterButtons_DocumentWidget_( - d, - (iMenuItem[]){ { format_CStr(book_Icon " %s", - cstr_String(property_Gempub(d->sourceGempub, - title_GempubProperty))), - SDLK_LEFT, - 0, - format_CStr("!open url:%s", - cstr_String(coverPageUrl_Gempub(d->sourceGempub))) } }, - 1); - } - else { - /* Navigation buttons. */ - iArray *items = collectNew_Array(sizeof(iMenuItem)); - const size_t navIndex = navIndex_Gempub(d->sourceGempub, d->mod.url); - if (navIndex != iInvalidPos) { - if (navIndex < navSize_Gempub(d->sourceGempub) - 1) { + if (d->sourceGempub) { + if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub))) { + if (!isRemote_Gempub(d->sourceGempub)) { + iArray *items = collectNew_Array(sizeof(iMenuItem)); pushBack_Array( items, - &(iMenuItem){ - format_CStr(forwardArrow_Icon " %s", - cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex + 1))), - SDLK_RIGHT, - 0, - format_CStr("!open url:%s", - cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex + 1))) }); + &(iMenuItem){ book_Icon " ${gempub.cover.view}", + 0, + 0, + format_CStr("!open url:%s", + cstr_String(indexPageUrl_Gempub(d->sourceGempub))) }); + if (navSize_Gempub(d->sourceGempub) > 0) { + pushBack_Array( + items, + &(iMenuItem){ + format_CStr(forwardArrow_Icon " %s", + cstr_String(navLinkLabel_Gempub(d->sourceGempub, 0))), + SDLK_RIGHT, + 0, + format_CStr("!open url:%s", + cstr_String(navLinkUrl_Gempub(d->sourceGempub, 0))) }); + } + makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); } - if (navIndex > 0) { - pushBack_Array( - items, - &(iMenuItem){ - format_CStr(backArrow_Icon " %s", - cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex - 1))), - SDLK_LEFT, - 0, - format_CStr("!open url:%s", - cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex - 1))) }); + else { + makeFooterButtons_DocumentWidget_( + d, + (iMenuItem[]){ { book_Icon " ${menu.save.downloads.open}", + SDLK_s, + KMOD_PRIMARY | KMOD_SHIFT, + "document.save open:1" }, + { download_Icon " " saveToDownloads_Label, + SDLK_s, + KMOD_PRIMARY, + "document.save" } }, + 2); } - else if (!equalCase_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { - pushBack_Array( - items, - &(iMenuItem){ - format_CStr(book_Icon " %s", - cstr_String(property_Gempub(d->sourceGempub, title_GempubProperty))), - SDLK_LEFT, - 0, - format_CStr("!open url:%s", - cstr_String(coverPageUrl_Gempub(d->sourceGempub))) }); + if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { + redoLayout_GmDocument(d->doc); + updateVisible_DocumentWidget_(d); + invalidate_DocumentWidget_(d); } } - if (!isEmpty_Array(items)) { - makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); + else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { + makeFooterButtons_DocumentWidget_( + d, + (iMenuItem[]){ { format_CStr(book_Icon " %s", + cstr_String(property_Gempub(d->sourceGempub, + title_GempubProperty))), + SDLK_LEFT, + 0, + format_CStr("!open url:%s", + cstr_String(coverPageUrl_Gempub(d->sourceGempub))) } }, + 1); } - } - if (!isCached && prefs_App()->pinSplit && - equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { - const iString *navStart = navStartLinkUrl_Gempub(d->sourceGempub); - if (navStart) { - iWindow *win = get_Window(); - /* Auto-split to show index and the first navigation link. */ - if (numRoots_Window(win) == 2) { - /* This document is showing the index page. */ - iRoot *other = otherRoot_Window(win, w->root); - postCommandf_Root(other, "open url:%s", cstr_String(navStart)); - if (prefs_App()->pinSplit == 1 && w->root == win->roots[1]) { - /* On the wrong side. */ - postCommand_App("ui.split swap:1"); + else { + /* Navigation buttons. */ + iArray *items = collectNew_Array(sizeof(iMenuItem)); + const size_t navIndex = navIndex_Gempub(d->sourceGempub, d->mod.url); + if (navIndex != iInvalidPos) { + if (navIndex < navSize_Gempub(d->sourceGempub) - 1) { + pushBack_Array( + items, + &(iMenuItem){ + format_CStr(forwardArrow_Icon " %s", + cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex + 1))), + SDLK_RIGHT, + 0, + format_CStr("!open url:%s", + cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex + 1))) }); + } + if (navIndex > 0) { + pushBack_Array( + items, + &(iMenuItem){ + format_CStr(backArrow_Icon " %s", + cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex - 1))), + SDLK_LEFT, + 0, + format_CStr("!open url:%s", + cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex - 1))) }); + } + else if (!equalCase_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { + pushBack_Array( + items, + &(iMenuItem){ + format_CStr(book_Icon " %s", + cstr_String(property_Gempub(d->sourceGempub, title_GempubProperty))), + SDLK_LEFT, + 0, + format_CStr("!open url:%s", + cstr_String(coverPageUrl_Gempub(d->sourceGempub))) }); } } - else { - postCommandf_App( - "open newtab:%d url:%s", otherRoot_OpenTabFlag, cstr_String(navStart)); + if (!isEmpty_Array(items)) { + makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); + } + } + if (!isCached && prefs_App()->pinSplit && + equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { + const iString *navStart = navStartLinkUrl_Gempub(d->sourceGempub); + if (navStart) { + iWindow *win = get_Window(); + /* Auto-split to show index and the first navigation link. */ + if (numRoots_Window(win) == 2) { + /* This document is showing the index page. */ + iRoot *other = otherRoot_Window(win, w->root); + postCommandf_Root(other, "open url:%s", cstr_String(navStart)); + if (prefs_App()->pinSplit == 1 && w->root == win->roots[1]) { + /* On the wrong side. */ + postCommand_App("ui.split swap:1"); + } + } + else { + postCommandf_App( + "open newtab:%d url:%s", otherRoot_OpenTabFlag, cstr_String(navStart)); + } } } } } + /* Local fontpacks are automatically shown. */ + if (preloadLocalFontpackForPreview_Fonts(d->doc)) { + documentRunsInvalidated_DocumentWidget_(d); + redoLayout_GmDocument(d->doc); + updateVisible_DocumentWidget_(d); + invalidate_DocumentWidget_(d); + } } static void updateDocument_DocumentWidget_(iDocumentWidget *d, @@ -1484,7 +1509,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, } delete_String(localPath); if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) { - appendFormat_String(&str, "=> %s/ ${doc.archive.view}\n", + appendFormat_String(&str, "=> %s/ " folder_Icon " ${doc.archive.view}\n", cstr_String(withSpacesEncoded_String(d->mod.url))); } translate_Lang(&str); @@ -2089,7 +2114,7 @@ static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId, } static iBool isDownloadRequest_DocumentWidget(const iDocumentWidget *d, const iMediaRequest *req) { - return findLinkDownload_Media(constMedia_GmDocument(d->doc), req->linkId) != 0; + return findMediaForLink_Media(constMedia_GmDocument(d->doc), req->linkId, download_MediaType).type != 0; } static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { @@ -2174,7 +2199,7 @@ static void allocVisBuffer_DocumentWidget_(const iDocumentWidget *d) { static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) { iConstForEach(PtrArray, i, &d->visibleLinks) { const iGmRun *run = i.ptr; - if (run->linkId && run->mediaType == none_GmRunMediaType && + if (run->linkId && run->mediaType == none_MediaType && ~run->flags & decoration_GmRunFlag) { const int linkFlags = linkFlags_GmDocument(d->doc, run->linkId); if (isMediaLink_GmDocument(d->doc, run->linkId) && @@ -2763,8 +2788,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) else if (equalWidget_Command(cmd, w, "document.downloadlink")) { if (d->contextLink) { const iGmLinkId linkId = d->contextLink->linkId; - setDownloadUrl_Media( - media_GmDocument(d->doc), linkId, linkUrl_GmDocument(d->doc, linkId)); + setUrl_Media(media_GmDocument(d->doc), + linkId, + download_MediaType, + linkUrl_GmDocument(d->doc, linkId)); requestMedia_DocumentWidget_(d, linkId, iFalse /* no filters */); redoLayout_GmDocument(d->doc); /* inline downloader becomes visible */ updateVisible_DocumentWidget_(d); @@ -2874,7 +2901,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) const iMedia * media = media_GmDocument(d->doc); const size_t num = numAudio_Media(media); for (size_t id = 1; id <= num; id++) { - iPlayer *plr = audioPlayer_Media(media, id); + iPlayer *plr = audioPlayer_Media(media, (iMediaId){ audio_MediaType, id }); if (plr != startedPlr) { setPaused_Player(plr, iTrue); } @@ -3212,8 +3239,8 @@ static iRect runRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run } static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) { - if (run && run->mediaType == audio_GmRunMediaType) { - iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); + if (run && run->mediaType == audio_MediaType) { + iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); setFlags_Player(plr, volumeGrabbed_PlayerFlag, iTrue); d->grabbedStartVolume = volume_Player(plr); d->grabbedPlayer = run; @@ -3221,7 +3248,7 @@ static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *r } else if (d->grabbedPlayer) { setFlags_Player( - audioPlayer_Media(media_GmDocument(d->doc), d->grabbedPlayer->mediaId), + audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(d->grabbedPlayer)), volumeGrabbed_PlayerFlag, iFalse); d->grabbedPlayer = NULL; @@ -3249,11 +3276,21 @@ static iBool processMediaEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Ev const iInt2 mouse = init_I2(ev->button.x, ev->button.y); iConstForEach(PtrArray, i, &d->visibleMedia) { const iGmRun *run = i.ptr; - if (run->mediaType != audio_GmRunMediaType) { + if (run->mediaType == fontpack_MediaType) { + iFontpackUI ui; + init_FontpackUI(&ui, media_GmDocument(d->doc), run->mediaId, + runRect_DocumentWidget_(d, run)); + if (processEvent_FontpackUI(&ui, ev)) { + refresh_Widget(d); + return iTrue; + } + } + if (run->mediaType != audio_MediaType) { continue; } + /* TODO: move this to mediaui.c */ const iRect rect = runRect_DocumentWidget_(d, run); - iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); + iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); if (contains_Rect(rect, mouse)) { iPlayerUI ui; init_PlayerUI(&ui, plr, rect); @@ -3633,7 +3670,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e cstr_String(linkUrl)) }, }, 3); - if (isNative && d->contextLink->mediaType != download_GmRunMediaType) { + if (isNative && d->contextLink->mediaType != download_MediaType) { pushBackN_Array(&items, (iMenuItem[]){ { "---" }, { download_Icon " ${link.download}", 0, 0, "document.downloadlink" }, @@ -3641,7 +3678,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e } iMediaRequest *mediaReq; if ((mediaReq = findMediaRequest_DocumentWidget_(d, d->contextLink->linkId)) != NULL && - d->contextLink->mediaType != download_GmRunMediaType) { + d->contextLink->mediaType != download_MediaType) { if (isFinished_GmRequest(mediaReq->req)) { pushBack_Array(&items, &(iMenuItem){ download_Icon " " saveToDownloads_Label, @@ -3763,7 +3800,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e case drag_ClickResult: { if (d->grabbedPlayer) { iPlayer *plr = - audioPlayer_Media(media_GmDocument(d->doc), d->grabbedPlayer->mediaId); + audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(d->grabbedPlayer)); iPlayerUI ui; init_PlayerUI(&ui, plr, runRect_DocumentWidget_(d, d->grabbedPlayer)); float off = (float) delta_Click(&d->click).x / (float) width_Rect(ui.volumeSlider); @@ -3883,8 +3920,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e } if (d->hoverLink) { /* TODO: Move this to a method. */ - const iGmLinkId linkId = d->hoverLink->linkId; - const int linkFlags = linkFlags_GmDocument(d->doc, linkId); + const iGmLinkId linkId = d->hoverLink->linkId; + const iMediaId linkMedia = mediaId_GmRun(d->hoverLink); + const int linkFlags = linkFlags_GmDocument(d->doc, linkId); iAssert(linkId); /* Media links are opened inline by default. */ if (isMediaLink_GmDocument(d->doc, linkId)) { @@ -3937,6 +3975,12 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e } refresh_Widget(w); } + else if (linkMedia.type == download_MediaType || + findMediaRequest_DocumentWidget_(d, linkId)) { + /* TODO: What should be done when clicking on an inline download? + Maybe dismiss if finished? */ + return iTrue; + } else if (linkFlags & supportedScheme_GmLinkFlag) { int tabMode = openTabMode_Sym(modState_Keys()); if (isPinned_DocumentWidget_(d)) { @@ -4071,7 +4115,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol static void drawMark_DrawContext_(void *context, const iGmRun *run) { iDrawContext *d = context; - if (run->mediaType == none_GmRunMediaType) { + if (run->mediaType == none_MediaType) { fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark); fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark); } @@ -4178,8 +4222,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { d->runsDrawn.end = run; } } - if (run->mediaType == image_GmRunMediaType) { - SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), run->mediaId); + if (run->mediaType == image_MediaType) { + SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), mediaId_GmRun(run)); const iRect dst = moved_Rect(run->visBounds, origin); if (tex) { fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */ @@ -4334,31 +4378,31 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); iString text; init_String(&text); - iMediaId imageId = linkImage_GmDocument(doc, run->linkId); - iMediaId audioId = !imageId ? linkAudio_GmDocument(doc, run->linkId) : 0; - iMediaId downloadId = !imageId && !audioId ? - findLinkDownload_Media(constMedia_GmDocument(doc), run->linkId) : 0; - iAssert(imageId || audioId || downloadId); - if (imageId) { - iAssert(!isEmpty_Rect(run->bounds)); - iGmMediaInfo info; - imageInfo_Media(constMedia_GmDocument(doc), imageId, &info); - const iInt2 imgSize = imageSize_Media(constMedia_GmDocument(doc), imageId); - format_String(&text, "%s \u2014 %d x %d \u2014 %.1f%s", - info.type, imgSize.x, imgSize.y, info.numBytes / 1.0e6f, - cstr_Lang("mb")); - } - else if (audioId) { - iGmMediaInfo info; - audioInfo_Media(constMedia_GmDocument(doc), audioId, &info); - format_String(&text, "%s", info.type); - } - else if (downloadId) { - iGmMediaInfo info; - downloadInfo_Media(constMedia_GmDocument(doc), downloadId, &info); - format_String(&text, "%s", info.type); + const iMediaId linkMedia = findMediaForLink_Media(constMedia_GmDocument(doc), + run->linkId, none_MediaType); + iAssert(linkMedia.type != none_MediaType); + iGmMediaInfo info; + info_Media(constMedia_GmDocument(doc), linkMedia, &info); + switch (linkMedia.type) { + case image_MediaType: { + iAssert(!isEmpty_Rect(run->bounds)); + const iInt2 imgSize = imageSize_Media(constMedia_GmDocument(doc), linkMedia); + format_String(&text, "%s \u2014 %d x %d \u2014 %.1f%s", + info.type, imgSize.x, imgSize.y, info.numBytes / 1.0e6f, + cstr_Lang("mb")); + break; + } + case audio_MediaType: + format_String(&text, "%s", info.type); + break; + case download_MediaType: + format_String(&text, "%s", info.type); + break; + default: + break; } - if (findMediaRequest_DocumentWidget_(d->widget, run->linkId)) { + if (linkMedia.type != download_MediaType && /* can't cancel downloads currently */ + findMediaRequest_DocumentWidget_(d->widget, run->linkId)) { appendFormat_String( &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : ""); } @@ -4589,18 +4633,25 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) { static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) { iConstForEach(PtrArray, i, &d->visibleMedia) { const iGmRun * run = i.ptr; - if (run->mediaType == audio_GmRunMediaType) { + if (run->mediaType == audio_MediaType) { iPlayerUI ui; init_PlayerUI(&ui, - audioPlayer_Media(media_GmDocument(d->doc), run->mediaId), + audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)), runRect_DocumentWidget_(d, run)); draw_PlayerUI(&ui, p); } - else if (run->mediaType == download_GmRunMediaType) { + else if (run->mediaType == download_MediaType) { iDownloadUI ui; - init_DownloadUI(&ui, d, run->mediaId, runRect_DocumentWidget_(d, run)); + init_DownloadUI(&ui, constMedia_GmDocument(d->doc), run->mediaId, + runRect_DocumentWidget_(d, run)); draw_DownloadUI(&ui, p); } + else if (run->mediaType == fontpack_MediaType) { + iFontpackUI ui; + init_FontpackUI(&ui, constMedia_GmDocument(d->doc), run->mediaId, + runRect_DocumentWidget_(d, run)); + draw_FontpackUI(&ui, p); + } } } diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h index cc09c72d..1f2ecfc0 100644 --- a/src/ui/documentwidget.h +++ b/src/ui/documentwidget.h @@ -32,6 +32,8 @@ iDeclareType(History) iDeclareWidgetClass(DocumentWidget) iDeclareObjectConstruction(DocumentWidget) +void cancelAllRequests_DocumentWidget(iDocumentWidget *); + void serializeState_DocumentWidget (const iDocumentWidget *, iStream *outs); void deserializeState_DocumentWidget (iDocumentWidget *, iStream *ins); diff --git a/src/ui/mediaui.c b/src/ui/mediaui.c index 22552027..aa45d73a 100644 --- a/src/ui/mediaui.c +++ b/src/ui/mediaui.c @@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "lang.h" #include +#include static const char *volumeChar_(float volume) { if (volume <= 0) { @@ -61,7 +62,7 @@ void init_PlayerUI(iPlayerUI *d, const iPlayer *player, iRect bounds) { } } -static void drawPlayerButton_(iPaint *p, iRect rect, const char *label, int font) { +static void drawInlineButton_(iPaint *p, iRect rect, const char *label, int font) { const iInt2 mouse = mouseCoord_Window(get_Window(), 0); const iBool isHover = contains_Rect(rect, mouse); const iBool isPressed = isHover && (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LEFT) != 0; @@ -113,14 +114,14 @@ void draw_PlayerUI(iPlayerUI *d, iPaint *p) { const iBool isAdjusting = (flags_Player(d->player) & adjustingVolume_PlayerFlag) != 0; fillRect_Paint(p, d->bounds, playerBackground_ColorId); drawRect_Paint(p, d->bounds, playerFrame_ColorId); - drawPlayerButton_(p, + drawInlineButton_(p, d->playPauseRect, isPaused_Player(d->player) ? "\U0001f782" : "\u23f8", uiContent_FontId); - drawPlayerButton_(p, d->rewindRect, "\u23ee", uiContent_FontId); - drawPlayerButton_(p, d->menuRect, menu_Icon, uiContent_FontId); + drawInlineButton_(p, d->rewindRect, "\u23ee", uiContent_FontId); + drawInlineButton_(p, d->menuRect, menu_Icon, uiContent_FontId); if (!isAdjusting) { - drawPlayerButton_( + drawInlineButton_( p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId); } const int hgt = lineHeight_Text(uiLabelBig_FontId); @@ -228,24 +229,26 @@ static void drawSevenSegmentBytes_(iInt2 pos, int color, size_t numBytes) { deinit_String(&digits); } -void init_DownloadUI(iDownloadUI *d, const iDocumentWidget *doc, uint16_t mediaId, iRect bounds) { - d->doc = doc; +void init_DownloadUI(iDownloadUI *d, const iMedia *media, uint16_t mediaId, iRect bounds) { + d->media = media; d->mediaId = mediaId; d->bounds = bounds; } +/*----------------------------------------------------------------------------------------------*/ + iBool processEvent_DownloadUI(iDownloadUI *d, const SDL_Event *ev) { return iFalse; } void draw_DownloadUI(const iDownloadUI *d, iPaint *p) { - const iMedia *media = constMedia_GmDocument(document_DocumentWidget(d->doc)); iGmMediaInfo info; float bytesPerSecond; const iString *path; iBool isFinished; - downloadInfo_Media(media, d->mediaId, &info); - downloadStats_Media(media, d->mediaId, &path, &bytesPerSecond, &isFinished); + downloadInfo_Media(d->media, d->mediaId, &info); + downloadStats_Media(d->media, (iMediaId){ download_MediaType, d->mediaId }, + &path, &bytesPerSecond, &isFinished); fillRect_Paint(p, d->bounds, uiBackground_ColorId); drawRect_Paint(p, d->bounds, uiSeparator_ColorId); iRect rect = d->bounds; @@ -275,3 +278,68 @@ void draw_DownloadUI(const iDownloadUI *d, iPaint *p) { translateCStr_Lang("\u2014 ${mb.per.sec}")); } } + +/*----------------------------------------------------------------------------------------------*/ + +void init_FontpackUI(iFontpackUI *d, const iMedia *media, uint16_t mediaId, iRect bounds) { + d->media = media; + d->mediaId = mediaId; + d->bounds = bounds; + d->installRect.size = add_I2(measure_Text(uiLabel_FontId, "${media.fontpack.install}").bounds.size, + muli_I2(gap2_UI, 3)); + d->installRect.pos.x = right_Rect(d->bounds) - gap_UI - d->installRect.size.x; + d->installRect.pos.y = mid_Rect(d->bounds).y - d->installRect.size.y / 2; +} + +iBool processEvent_FontpackUI(iFontpackUI *d, const SDL_Event *ev) { + switch (ev->type) { + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: { + const iInt2 pos = init_I2(ev->button.x, ev->button.y); + if (contains_Rect(d->installRect, pos)) { + return iTrue; + } + break; + } + case SDL_MOUSEMOTION: + if (contains_Rect(d->bounds, init_I2(ev->motion.x, ev->motion.y))) { + return iTrue; + } + break; + } + return iFalse; +} + +int height_FontpackUI(const iMedia *media, uint16_t mediaId, int width) { + const iStringList *names; + iFontpackMediaInfo info; + fontpackInfo_Media(media, (iMediaId){ fontpack_MediaType, mediaId }, &info); + return lineHeight_Text(uiContent_FontId) + + lineHeight_Text(uiLabel_FontId) * (1 + size_StringList(info.names)); +} + +void draw_FontpackUI(const iFontpackUI *d, iPaint *p) { + /* Draw a background box. */ + fillRect_Paint(p, d->bounds, uiBackground_ColorId); + drawRect_Paint(p, d->bounds, uiSeparator_ColorId); + iFontpackMediaInfo info; + fontpackInfo_Media(d->media, (iMediaId){ fontpack_MediaType, d->mediaId }, &info); + iInt2 pos = topLeft_Rect(d->bounds); + const char *checks[] = { "\u2610", "\u2611" }; + draw_Text(uiContentBold_FontId, pos, uiHeading_ColorId, "\"%s\" v%d", + cstr_String(info.packId.id), info.packId.version); + pos.y += lineHeight_Text(uiContentBold_FontId); + draw_Text(uiLabelBold_FontId, pos, uiText_ColorId, "%.1f MB, %d fonts %s %s %s", + info.sizeInBytes / 1.0e6, size_StringList(info.names), +// checks[info.isValid], info.isValid ? "No errors" : "Errors detected", + checks[info.isInstalled], info.isInstalled ? "Installed" : "Not installed", + info.isReadOnly ? "Read-Only" : ""); + pos.y += lineHeight_Text(uiLabelBold_FontId); + iConstForEach(StringList, i, info.names) { + drawRange_Text(uiLabel_FontId, pos, uiText_ColorId, range_String(i.value)); + pos.y += lineHeight_Text(uiLabel_FontId); + } + /* Buttons. */ + drawInlineButton_(p, d->installRect, + "${media.fontpack.install}", uiLabel_FontId); +} diff --git a/src/ui/mediaui.h b/src/ui/mediaui.h index e79dedc0..73de1994 100644 --- a/src/ui/mediaui.h +++ b/src/ui/mediaui.h @@ -51,11 +51,27 @@ iDeclareType(Media) iDeclareType(DownloadUI) struct Impl_DownloadUI { - const iDocumentWidget *doc; + const iMedia *media; uint16_t mediaId; iRect bounds; }; -void init_DownloadUI (iDownloadUI *, const iDocumentWidget *doc, uint16_t mediaId, iRect bounds); +void init_DownloadUI (iDownloadUI *, const iMedia *media, uint16_t mediaId, iRect bounds); iBool processEvent_DownloadUI (iDownloadUI *, const SDL_Event *ev); void draw_DownloadUI (const iDownloadUI *, iPaint *p); + +/*----------------------------------------------------------------------------------------------*/ + +iDeclareType(FontpackUI) + +struct Impl_FontpackUI { + const iMedia *media; + uint16_t mediaId; + iRect bounds; + iRect installRect; +}; + +void init_FontpackUI (iFontpackUI *, const iMedia *media, uint16_t mediaId, iRect bounds); +int height_FontpackUI (const iMedia *media, uint16_t mediaId, int width); +iBool processEvent_FontpackUI (iFontpackUI *, const SDL_Event *ev); +void draw_FontpackUI (const iFontpackUI *, iPaint *p); -- cgit v1.2.3 From e884330ef73b2f557486a898a67a716f29887170 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Tue, 12 Oct 2021 12:59:12 +0300 Subject: Experimental Markdown rendering Convert Markdown to Gemtext and use ANSI escape sequences to switch fonts (bold, italic, monospace). The conversion is still a bit buggy... --- src/defs.h | 1 + src/gmdocument.c | 179 ++++++++++++++++++++++++++++++++++++++++++++++++ src/gmutil.c | 6 ++ src/ui/documentwidget.c | 4 ++ src/ui/text.c | 37 +++++++++- 5 files changed, 224 insertions(+), 3 deletions(-) (limited to 'src/gmdocument.c') diff --git a/src/defs.h b/src/defs.h index f5479cf3..40e134c9 100644 --- a/src/defs.h +++ b/src/defs.h @@ -28,6 +28,7 @@ enum iSourceFormat { undefined_SourceFormat = -1, gemini_SourceFormat = 0, plainText_SourceFormat, + markdown_SourceFormat, }; enum iFileVersion { diff --git a/src/gmdocument.c b/src/gmdocument.c index 508047a6..f0d9bf08 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -1712,8 +1712,183 @@ void setUrl_GmDocument(iGmDocument *d, const iString *url) { updateIconBasedOnUrl_GmDocument_(d); } +static int replaceRegExp_String(iString *d, const iRegExp *regexp, const char *replacement, + void (*matchHandler)(void *, const iRegExpMatch *), + void *context) { + iRegExpMatch m; + iString result; + int numMatches = 0; + const char *pos = constBegin_String(d); + init_RegExpMatch(&m); + init_String(&result); + while (matchString_RegExp(regexp, d, &m)) { + appendRange_String(&result, (iRangecc){ pos, begin_RegExpMatch(&m) }); + /* Replace any capture group back-references. */ + for (const char *ch = replacement; *ch; ch++) { + if (*ch == '\\') { + ch++; + if (*ch == '\\') { + appendCStr_String(&result, "\\"); + } + else if (*ch >= '0' && *ch <= '9') { + appendRange_String(&result, capturedRange_RegExpMatch(&m, *ch - '0')); + } + } + else { + appendData_Block(&result.chars, ch, 1); + } + } + if (matchHandler) { + matchHandler(context, &m); + } + pos = end_RegExpMatch(&m); + numMatches++; + } + appendRange_String(&result, (iRangecc){ pos, constEnd_String(d) }); + set_String(d, &result); + deinit_String(&result); + return numMatches; +} + +iDeclareType(PendingLink) +struct Impl_PendingLink { + iString *url; + iString *title; +}; + +static void addPendingLink_(void *context, const iRegExpMatch *m) { + pushBack_Array(context, &(iPendingLink){ + .url = captured_RegExpMatch(m, 2), + .title = captured_RegExpMatch(m, 1) + }); +} + +static void addPendingNamedLink_(void *context, const iRegExpMatch *m) { + pushBack_Array(context, &(iPendingLink){ + .url = newFormat_String("[]%s", cstr_Rangecc(capturedRange_RegExpMatch(m, 2))), + .title = captured_RegExpMatch(m, 1) + }); +} + +static void flushPendingLinks_(iArray *links, const iString *source, iString *out) { + iRegExp *namePattern = new_RegExp("\n\\s*\\[(.+?)\\]\\s*:\\s*([^\n]+)", 0); + if (!endsWith_String(out, "\n")) { + appendCStr_String(out, "\n"); + } + iForEach(Array, i, links) { + iPendingLink *pending = i.value; + const char *url = cstr_String(pending->url); + if (startsWith_CStr(url, "[]")) { + /* Find the matching named link. */ + iRegExpMatch m; + init_RegExpMatch(&m); + while (matchString_RegExp(namePattern, source, &m)) { + if (equal_Rangecc(capturedRange_RegExpMatch(&m, 1), url + 2)) { + url = cstrCollect_String(captured_RegExpMatch(&m, 2)); + break; + } + } + } + appendFormat_String(out, "\n=> %s %s", url, cstr_String(pending->title)); + delete_String(pending->url); + delete_String(pending->title); + } + clear_Array(links); + iRelease(namePattern); +} + +static void convertMarkdownToGemtext_GmDocument_(iGmDocument *d) { + iAssert(d->format == markdown_SourceFormat); + /* Get rid of indented preformats. */ { + iArray *pendingLinks = collectNew_Array(sizeof(iPendingLink)); + const iRegExp *imageLinkPattern = iClob(new_RegExp("\n?!\\[(.+)\\]\\(([^)]+)\\)\n?", 0)); + const iRegExp *linkPattern = iClob(new_RegExp("\\[(.+?)\\]\\(([^)]+)\\)", 0)); + const iRegExp *namedLinkPattern = iClob(new_RegExp("\\[(.+?)\\]\\[(.+?)\\]", 0)); + const iRegExp *namePattern = iClob(new_RegExp("\\s*\\[(.+?)\\]\\s*:\\s*([^\n]+)", 0)); + iString result; + init_String(&result); + iRangecc line = iNullRange; + iBool isPre = iFalse; + iBool isLastEmpty = iFalse; + while (nextSplit_Rangecc(range_String(&d->source), "\n", &line)) { + if (!isPre) { + if (*line.start == '#') { + flushPendingLinks_(pendingLinks, &d->source, &result); + } + if (isEmpty_Range(&line)) { + isLastEmpty = iTrue; + continue; + } + if (isLastEmpty) { + appendCStr_String(&result, "\n\n"); + } + else if (size_Range(&line) >= 2 && isnumber(line.start[0]) && + (line.start[1] == '.' || + (isnumber(line.start[1]) && line.start[2] == '.'))) { + appendCStr_String(&result, "\n\n"); + } + else if (*line.start == '*' || *line.start == '>' || *line.start == '#') { + appendCStr_String(&result, "\n"); + } + else { + appendCStr_String(&result, " "); + } + isLastEmpty = iFalse; + } + if (startsWith_Rangecc(line, " ")) { + line.start += 4; + if (!isPre) { + appendCStr_String(&result, "```\n"); + isPre = iTrue; + } + } + else if (isPre) { + if (!endsWith_String(&result, "\n")) { + appendCStr_String(&result, "\n"); + } + appendCStr_String(&result, "```\n"); + isPre = iFalse; + } + /* Check for image links. */ + if (isPre) { + appendRange_String(&result, line); + appendCStr_String(&result, "\n"); + } + else { + iString ln; + initRange_String(&ln, line); + replaceRegExp_String(&ln, iClob(new_RegExp("\\*\\*(.+?)\\*\\*", 0)), "\x1b[1m\\1\x1b[0m", NULL, NULL); + replaceRegExp_String(&ln, iClob(new_RegExp("\\b\\*(.+?)\\*\\b", 0)), "\x1b[3m\\1\x1b[0m", NULL, NULL); + replaceRegExp_String(&ln, iClob(new_RegExp("\\b_(.+?)_\\b", 0)), "\x1b[3m\\1\x1b[0m", NULL, NULL); + replaceRegExp_String(&ln, iClob(new_RegExp("```([^`]+?)```", 0)), "\n```\n\\1\n```\n", NULL, NULL); + replaceRegExp_String(&ln, namePattern, "", NULL, 0); + replaceRegExp_String(&ln, imageLinkPattern, "\n=> \\2 \\1\n", NULL, NULL); + replaceRegExp_String(&ln, namedLinkPattern, "\\1", addPendingNamedLink_, pendingLinks); + replaceRegExp_String(&ln, linkPattern, "\\1", addPendingLink_, pendingLinks); + replaceRegExp_String(&ln, iClob(new_RegExp("(?source, &result); + set_String(&d->source, &result); + deinit_String(&result); + } + /* Replace Markdown syntax with equivalent Gemtext, where possible. */ +// replaceRegExp_String(&d->source, iClob(new_RegExp("```([^`]+)```", 0)), "\n\n```\v\\1\v```\n\n"); +// replaceRegExp_String(&d->source, iClob(new_RegExp("\n\\s*([0-9]+)\\.", 0)), "\n\n\\1."); /* numbered list */ + replaceRegExp_String(&d->source, iClob(new_RegExp("(\\s*\n){2,}", 0)), "\n\n", NULL, NULL); /* normalize paragraph breaks */ + printf("Converted:\n%s", cstr_String(&d->source)); +// replaceRegExp_String(&d->source, iClob(new_RegExp("\n(?![*>#]\\s)", 0)), " "); /* normal line breaks */ +// replace_String(&d->source, "\f", "\n\n"); +// replace_String(&d->source, "\v", "\n"); + d->format = gemini_SourceFormat; +} + void setSource_GmDocument(iGmDocument *d, const iString *source, int width, int outsideMargin, enum iGmDocumentUpdate updateType) { + /* TODO: This API has been set up to allow partial/progressive updating of the content. + Currently the entire source is replaced every time, though. */ // printf("[GmDocument] source update (%zu bytes), width:%d, final:%d\n", // size_String(source), width, updateType == final_GmDocumentUpdate); if (size_String(source) == size_String(&d->unormSource)) { @@ -1724,6 +1899,10 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width, int set_String(&d->unormSource, source); /* Normalize. */ set_String(&d->source, &d->unormSource); + if (d->format == markdown_SourceFormat) { + convertMarkdownToGemtext_GmDocument_(d); + set_String(&d->unormSource, &d->source); + } if (isNormalized_GmDocument_(d)) { normalize_GmDocument(d); } diff --git a/src/gmutil.c b/src/gmutil.c index 5be7e198..692c1cb9 100644 --- a/src/gmutil.c +++ b/src/gmutil.c @@ -566,6 +566,12 @@ const char *mediaTypeFromFileExtension_String(const iString *d) { else if (endsWithCase_String(d, ".mid")) { return "audio/midi"; } + else if (endsWithCase_String(d, ".md") || + endsWithCase_String(d, ".markdown") || + endsWithCase_String(d, ".mdown") || + endsWithCase_String(d, ".markdn")) { + return "text/markdown"; + } else if (endsWithCase_String(d, ".txt") || endsWithCase_String(d, ".ini") || endsWithCase_String(d, ".md") || diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index ee669c1a..8c87ba1a 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -1481,6 +1481,10 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, docFormat = gemini_SourceFormat; setRange_String(&d->sourceMime, param); } + else if (equal_Rangecc(param, "text/markdown")) { + docFormat = markdown_SourceFormat; + setRange_String(&d->sourceMime, param); + } else if (startsWith_Rangecc(param, "text/") || equal_Rangecc(param, "application/json") || equal_Rangecc(param, "application/x-pem-file") || diff --git a/src/ui/text.c b/src/ui/text.c index 3ed5b327..3d2cdf5d 100644 --- a/src/ui/text.c +++ b/src/ui/text.c @@ -914,6 +914,18 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i run->logical.start = endAt; } +static iFont *withStyle_Font_(const iFont *d, enum iFontStyle styleId) { + const int fontId = (fontId_Text_(d) / maxVariants_Fonts) * maxVariants_Fonts; + const int sizeId = sizeId_Text_(d); + return font_Text_(FONT_ID(fontId, styleId, sizeId)); +} + +static iFont *withFontId_Font_(const iFont *d, enum iFontId fontId) { + const int styleId = styleId_Text_(d); + const int sizeId = sizeId_Text_(d); + return font_Text_(FONT_ID(fontId, styleId, sizeId)); +} + static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) { iAssert(isEmpty_Array(&d->runs)); size_t length = 0; @@ -976,6 +988,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh const iChar * logicalText = constData_Array(&d->logical); iBool isRTL = d->isBaseRTL; int numNonSpace = 0; + iFont * activeFont = d->font; for (int pos = 0; pos < length; pos++) { const iChar ch = logicalText[pos]; #if defined (LAGRANGE_ENABLE_FRIBIDI) @@ -1004,8 +1017,23 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh init_RegExpMatch(&m); if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) { finishRun_AttributedText_(d, &run, pos - 1); - run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), - tmParagraph_ColorId); + const iRangecc sequence = capturedRange_RegExpMatch(&m, 1); + if (equal_Rangecc(sequence, "1")) { + activeFont = withStyle_Font_(activeFont, bold_FontStyle); + } + else if (equal_Rangecc(sequence, "3")) { + activeFont = withStyle_Font_(activeFont, italic_FontStyle); + } + else if (equal_Rangecc(sequence, "4")) { + activeFont = withFontId_Font_(activeFont, monospace_FontId); + } + else if (equal_Rangecc(sequence, "0")) { + activeFont = d->font; /* restore original */ + run.fgColor = d->fgColor; + } + else { + run.fgColor = ansiForeground_Color(sequence, tmParagraph_ColorId); + } pos += length_Rangecc(capturedRange_RegExpMatch(&m, 0)); iAssert(logToSource[pos] == end_RegExpMatch(&m) - d->source.start); /* The run continues after the escape sequence. */ @@ -1047,7 +1075,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh } continue; } - iFont *currentFont = d->font; + iFont *currentFont = activeFont; if (run.font->fontSpec->flags & arabic_FontSpecFlag && isPunct_Char(ch)) { currentFont = run.font; /* remain as Arabic for whitespace */ } @@ -1766,6 +1794,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y, glyph->rect[hoff].size.x, glyph->rect[hoff].size.y }; + if (run->font->height < d->height) { + dst.y += d->baseline - run->font->baseline; + } if (mode & visualFlag_RunMode) { if (isEmpty_Rect(bounds)) { bounds = init_Rect(dst.x, dst.y, dst.w, dst.h); -- cgit v1.2.3 From 6b931c95725eef2ebb7e831c4017d3d67b33294f Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Wed, 13 Oct 2021 09:49:44 +0300 Subject: Text attributes that change inside a run These changes concern the situation when the attributes of text (i.e., font, color) are changed via escape sequences. The concept of "base attributes" was added so that the low-level text renderer knows which font/color to set when a "reset" escape sequence is encountered. This depends on what kind of text is being renderer, e.g., preformatted or regular paragraphs. The base attributes were added as variables in Text because it was getting unwieldy to pass all the information via the draw/measure/WrapText functions. GmDocument now has a GmTheme struct that collects the font and color information into a single place. --- src/fontpack.c | 2 +- src/gmdocument.c | 200 +++++++++++++++++++++++++++++++----------------- src/gmdocument.h | 30 ++++++-- src/ui/documentwidget.c | 40 ++++++---- src/ui/inputwidget.c | 18 +++-- src/ui/text.c | 196 ++++++++++++++++++++++++++++++----------------- src/ui/text.h | 26 ++++++- 7 files changed, 331 insertions(+), 181 deletions(-) (limited to 'src/gmdocument.c') diff --git a/src/fontpack.c b/src/fontpack.c index fb1c98ee..9baedc0e 100644 --- a/src/fontpack.c +++ b/src/fontpack.c @@ -48,7 +48,7 @@ float scale_FontSize(enum iFontSize size) { 1.333, 1.666, 2.000, - 0.568, + 0.650, //0.568, 0.710, /* calibration: fits the Lagrange title screen with Normal line width */ }; if (size < 0 || size >= max_FontSize) { diff --git a/src/gmdocument.c b/src/gmdocument.c index f0d9bf08..5c2a849e 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -75,6 +75,15 @@ iDefineTypeConstruction(GmLink) /*----------------------------------------------------------------------------------------------*/ +iDeclareType(GmTheme) + +struct Impl_GmTheme { + int colors[max_GmLineType]; + int fonts[max_GmLineType]; +}; + +/*----------------------------------------------------------------------------------------------*/ + struct Impl_GmDocument { iObject object; enum iSourceFormat format; @@ -91,6 +100,7 @@ struct Impl_GmDocument { iString title; /* the first top-level title */ iArray headings; iArray preMeta; /* metadata about preformatted blocks */ + iGmTheme theme; uint32_t themeSeed; iChar siteIcon; iMedia * media; @@ -101,6 +111,51 @@ struct Impl_GmDocument { iDefineObjectConstruction(GmDocument) +static iBool isForcedMonospace_GmDocument_(const iGmDocument *d) { + const iRangecc scheme = urlScheme_String(&d->url); + if (equalCase_Rangecc(scheme, "gemini")) { + return prefs_App()->monospaceGemini; + } + if (equalCase_Rangecc(scheme, "gopher") || + equalCase_Rangecc(scheme, "finger")) { + return prefs_App()->monospaceGopher; + } + return iFalse; +} + +static void initTheme_GmDocument_(iGmDocument *d) { + static const int defaultColors[max_GmLineType] = { + tmParagraph_ColorId, + tmParagraph_ColorId, /* bullet */ + tmPreformatted_ColorId, + tmQuote_ColorId, + tmHeading1_ColorId, + tmHeading2_ColorId, + tmHeading3_ColorId, + tmLinkText_ColorId, + }; + iGmTheme *theme = &d->theme; + memcpy(theme->colors, defaultColors, sizeof(theme->colors)); + const iPrefs *prefs = prefs_App(); + const iBool isMono = isForcedMonospace_GmDocument_(d); + const iBool isDarkBg = isDark_GmDocumentTheme( + isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight); + const enum iFontId headingFont = isMono ? documentMonospace_FontId : documentHeading_FontId; + const enum iFontId bodyFont = isMono ? documentMonospace_FontId : documentBody_FontId; + theme->fonts[text_GmLineType] = FONT_ID(bodyFont, regular_FontStyle, contentRegular_FontSize); + theme->fonts[bullet_GmLineType] = FONT_ID(bodyFont, regular_FontStyle, contentRegular_FontSize); + theme->fonts[preformatted_GmLineType] = preformatted_FontId; + theme->fonts[quote_GmLineType] = isMono ? monospaceParagraph_FontId : quote_FontId; + theme->fonts[heading1_GmLineType] = FONT_ID(headingFont, bold_FontStyle, contentHuge_FontSize); + theme->fonts[heading2_GmLineType] = FONT_ID(headingFont, bold_FontStyle, contentLarge_FontSize); + theme->fonts[heading3_GmLineType] = FONT_ID(headingFont, regular_FontStyle, contentBig_FontSize); + theme->fonts[link_GmLineType] = FONT_ID( + bodyFont, + ((isDarkBg && prefs->boldLinkDark) || (!isDarkBg && prefs->boldLinkLight)) ? semiBold_FontStyle + : regular_FontStyle, + contentRegular_FontSize); +} + static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc line) { if (d->format == plainText_SourceFormat) { return text_GmLineType; @@ -318,18 +373,6 @@ static iBool isGopher_GmDocument_(const iGmDocument *d) { equalCase_Rangecc(scheme, "finger")); } -static iBool isForcedMonospace_GmDocument_(const iGmDocument *d) { - const iRangecc scheme = urlScheme_String(&d->url); - if (equalCase_Rangecc(scheme, "gemini")) { - return prefs_App()->monospaceGemini; - } - if (equalCase_Rangecc(scheme, "gopher") || - equalCase_Rangecc(scheme, "finger")) { - return prefs_App()->monospaceGopher; - } - return iFalse; -} - static void linkContentWasLaidOut_GmDocument_(iGmDocument *d, const iGmMediaInfo *mediaInfo, uint16_t linkId) { iGmLink *link = at_PtrArray(&d->links, linkId - 1); @@ -402,7 +445,8 @@ struct Impl_RunTypesetter { int rightMargin; iBool isWordWrapped; iBool isPreformat; - const int *fonts; + int baseFont; + int baseColor; }; static void init_RunTypesetter_(iRunTypesetter *d) { @@ -425,39 +469,47 @@ static void commit_RunTypesetter_(iRunTypesetter *d, iGmDocument *doc) { static const int maxLedeLines_ = 10; -static const int colors[max_GmLineType] = { - tmParagraph_ColorId, - tmParagraph_ColorId, - tmPreformatted_ColorId, - tmQuote_ColorId, - tmHeading1_ColorId, - tmHeading2_ColorId, - tmHeading3_ColorId, - tmLinkText_ColorId, -}; +static int applyAttributes_RunTypesetter_(iRunTypesetter *d, iTextAttrib attrib) { + /* WARNING: This is duplicated in run_Font_(). Make sure they behave identically. */ + if (attrib.bold) { + d->run.font = fontWithStyle_Text(d->baseFont, bold_FontStyle); + d->run.color = tmFirstParagraph_ColorId; + } + else if (attrib.italic) { + d->run.font = fontWithStyle_Text(d->baseFont, italic_FontStyle); + } + else if (attrib.monospace) { + d->run.font = fontWithFamily_Text(d->baseFont, monospace_FontId); + d->run.color = tmPreformatted_ColorId; + } + else { + d->run.font = d->baseFont; + d->run.color = d->baseColor; + } +} -static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, int origin, - int advance, iBool isBaseRTL) { +static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, iTextAttrib attrib, + int origin, int advance) { iAssert(wrapRange.start <= wrapRange.end); trimEnd_Rangecc(&wrapRange); // printf("typeset: {%s}\n", cstr_Rangecc(wrapRange)); iRunTypesetter *d = wrap->context; - const int fontId = d->run.font; d->run.text = wrapRange; + applyAttributes_RunTypesetter_(d, attrib); if (~d->run.flags & startOfLine_GmRunFlag && d->lineHeightReduction > 0.0f) { - d->pos.y -= d->lineHeightReduction * lineHeight_Text(fontId); + d->pos.y -= d->lineHeightReduction * lineHeight_Text(d->baseFont); } d->run.bounds.pos = addX_I2(d->pos, origin + d->indent); - const iInt2 dims = init_I2(advance, lineHeight_Text(fontId)); + const iInt2 dims = init_I2(advance, lineHeight_Text(d->baseFont)); iChangeFlags(d->run.flags, wide_GmRunFlag, (d->isPreformat && dims.x > d->layoutWidth)); d->run.bounds.size.x = iMax(wrap->maxWidth, dims.x) - origin; /* Extends to the right edge for selection. */ d->run.bounds.size.y = dims.y; d->run.visBounds = d->run.bounds; d->run.visBounds.size.x = dims.x; - d->run.isRTL = isBaseRTL; + d->run.isRTL = attrib.isBaseRTL; pushBack_Array(&d->layout, &d->run); d->run.flags &= ~startOfLine_GmRunFlag; - d->pos.y += lineHeight_Text(fontId) * prefs_App()->lineSpacing; + d->pos.y += lineHeight_Text(d->baseFont) * prefs_App()->lineSpacing; return iTrue; /* continue to next wrapped line */ } @@ -469,25 +521,10 @@ static void doLayout_GmDocument_(iGmDocument *d) { const iBool isVeryNarrow = d->size.x <= 70 * gap_Text; const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text; const iBool isFullWidthImages = (d->outsideMargin < 5 * gap_UI); - const iBool isDarkBg = isDark_GmDocumentTheme( - isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight); +// const iBool isDarkBg = isDark_GmDocumentTheme( +// isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight); + initTheme_GmDocument_(d); /* TODO: Collect these parameters into a GmTheme. */ - const enum iFontId headingFont = isMono ? documentMonospace_FontId : documentHeading_FontId; - const enum iFontId bodyFont = isMono ? documentMonospace_FontId : documentBody_FontId; - const int fonts[max_GmLineType] = { - FONT_ID(bodyFont, regular_FontStyle, contentRegular_FontSize), /* text */ - FONT_ID(bodyFont, regular_FontStyle, contentRegular_FontSize), /* bullet */ - preformatted_FontId, /* pre */ - isMono ? monospaceParagraph_FontId : quote_FontId, /* quote */ - FONT_ID(headingFont, bold_FontStyle, contentHuge_FontSize), /* h1 */ - FONT_ID(headingFont, bold_FontStyle, contentLarge_FontSize), /* h2 */ - FONT_ID(headingFont, regular_FontStyle, contentBig_FontSize), /* h3 */ - FONT_ID(bodyFont, - ((isDarkBg && prefs->boldLinkDark) || (!isDarkBg && prefs->boldLinkLight)) - ? semiBold_FontStyle - : regular_FontStyle, - contentRegular_FontSize) /* link */ - }; float indents[max_GmLineType] = { 5, 10, 5, isNarrow ? 5 : 10, 0, 0, 0, 5 }; if (isExtremelyNarrow) { /* Further reduce the margins. */ @@ -598,7 +635,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { } } trimLine_Rangecc(&line, type, isNormalized); - run.font = fonts[type]; + run.font = d->theme.fonts[type]; /* Remember headings for the document outline. */ if (type == heading1_GmLineType || type == heading2_GmLineType || type == heading3_GmLineType) { pushBack_Array( @@ -618,7 +655,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { addSiteBanner = iFalse; /* overrides the banner */ continue; } - run.preId = preId; + run.mediaType = max_MediaType; /* preformatted block */ + run.mediaId = preId; run.font = (d->format == plainText_SourceFormat ? plainText_FontId : preFont); indent = indents[type]; } @@ -650,7 +688,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { run.visBounds.size = init_I2(gap_Text, lineHeight_Text(run.font)); run.bounds = zero_Rect(); /* just visual */ run.text = iNullRange; - run.flags = quoteBorder_GmRunFlag | decoration_GmRunFlag; + run.flags = quoteBorder_GmRunFlag | decoration_GmRunFlag; pushBack_Array(&d->layout, &run); } pos.y += lineHeight_Text(run.font) * prefs->lineSpacing; @@ -708,7 +746,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { altText.text).bounds.size; altText.bounds = altText.visBounds = init_Rect(pos.x, pos.y, d->size.x, size.y + 2 * margin.y); - altText.preId = preId; + altText.mediaType = max_MediaType; /* preformatted */ + altText.mediaId = preId; pushBack_Array(&d->layout, &altText); pos.y += height_Rect(altText.bounds); contentLine = meta->bounds; /* Skip the whole thing. */ @@ -723,7 +762,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { setRange_String(&d->title, line); } /* List bullet. */ - run.color = colors[type]; if (type == bullet_GmLineType) { /* TODO: Literata bullet is broken? */ iGmRun bulRun = run; @@ -795,18 +833,17 @@ static void doLayout_GmDocument_(iGmDocument *d) { icon.flags |= decoration_GmRunFlag; pushBack_Array(&d->layout, &icon); } - run.color = colors[type]; + run.lineType = type; + run.color = d->theme.colors[type]; if (d->format == plainText_SourceFormat) { - run.color = colors[text_GmLineType]; + run.color = d->theme.colors[text_GmLineType]; } /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ // int bigCount = 0; - iBool isLedeParagraph = iFalse; if (type == text_GmLineType && isFirstText) { if (!isMono) run.font = firstParagraph_FontId; - run.color = tmFirstParagraph_ColorId; -// bigCount = 15; /* max lines -- what if the whole document is one paragraph? */ - isLedeParagraph = iTrue; + run.color = tmFirstParagraph_ColorId; + run.isLede = iTrue; isFirstText = iFalse; } else if (type != heading1_GmLineType) { @@ -826,7 +863,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { init_RunTypesetter_(&rts); rts.run = run; rts.pos = pos; - rts.fonts = fonts; + //rts.fonts = fonts; rts.isWordWrapped = (d->format == plainText_SourceFormat ? prefs->plainTextWrap : !isPreformat); rts.isPreformat = isPreformat; @@ -859,6 +896,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { } for (;;) { /* need to retry if the font needs changing */ rts.run.flags |= startOfLine_GmRunFlag; + rts.baseFont = rts.run.font; + rts.baseColor = rts.run.color; iWrapText wrapText = { .text = line, .maxWidth = rts.isWordWrapped ? d->size.x - run.bounds.pos.x - @@ -868,7 +907,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { .wrapFunc = typesetOneLine_RunTypesetter_, .context = &rts }; measure_WrapText(&wrapText, rts.run.font); - if (!isLedeParagraph || size_Array(&rts.layout) <= maxLedeLines_) { + if (!rts.run.isLede || size_Array(&rts.layout) <= maxLedeLines_) { if (wrapText.baseDir < 0) { /* Right-aligned paragraphs need margins and decorations to be flipped. */ iForEach(Array, pr, &rts.layout) { @@ -891,11 +930,12 @@ static void doLayout_GmDocument_(iGmDocument *d) { commit_RunTypesetter_(&rts, d); break; } + /* Try again... */ clear_RunTypesetter_(&rts); rts.pos = pos; - rts.run.font = rts.fonts[text_GmLineType]; - rts.run.color = colors[text_GmLineType]; - isLedeParagraph = iFalse; + rts.run.font = rts.baseFont = d->theme.fonts[text_GmLineType]; + rts.run.color = rts.baseColor = d->theme.colors[text_GmLineType]; + rts.run.isLede = iFalse; } pos = rts.pos; deinit_RunTypesetter_(&rts); @@ -996,7 +1036,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { /* TODO: Store the dimensions and ranges for later access. */ iForEach(Array, i, &d->layout) { iGmRun *run = i.value; - if (run->preId && run->flags & wide_GmRunFlag) { + if (preId_GmRun(run) && run->flags & wide_GmRunFlag) { iGmRunRange block = findPreformattedRange_GmDocument(d, run); for (const iGmRun *j = block.start; j != block.end; j++) { iConstCast(iGmRun *, j)->flags |= wide_GmRunFlag; @@ -1800,11 +1840,11 @@ static void flushPendingLinks_(iArray *links, const iString *source, iString *ou static void convertMarkdownToGemtext_GmDocument_(iGmDocument *d) { iAssert(d->format == markdown_SourceFormat); /* Get rid of indented preformats. */ { - iArray *pendingLinks = collectNew_Array(sizeof(iPendingLink)); + iArray *pendingLinks = collectNew_Array(sizeof(iPendingLink)); const iRegExp *imageLinkPattern = iClob(new_RegExp("\n?!\\[(.+)\\]\\(([^)]+)\\)\n?", 0)); - const iRegExp *linkPattern = iClob(new_RegExp("\\[(.+?)\\]\\(([^)]+)\\)", 0)); + const iRegExp *linkPattern = iClob(new_RegExp("\\[(.+?)\\]\\(([^)]+)\\)", 0)); const iRegExp *namedLinkPattern = iClob(new_RegExp("\\[(.+?)\\]\\[(.+?)\\]", 0)); - const iRegExp *namePattern = iClob(new_RegExp("\\s*\\[(.+?)\\]\\s*:\\s*([^\n]+)", 0)); + const iRegExp *namePattern = iClob(new_RegExp("\\s*\\[(.+?)\\]\\s*:\\s*([^\n]+)", 0)); iString result; init_String(&result); iRangecc line = iNullRange; @@ -1865,7 +1905,7 @@ static void convertMarkdownToGemtext_GmDocument_(iGmDocument *d) { replaceRegExp_String(&ln, imageLinkPattern, "\n=> \\2 \\1\n", NULL, NULL); replaceRegExp_String(&ln, namedLinkPattern, "\\1", addPendingNamedLink_, pendingLinks); replaceRegExp_String(&ln, linkPattern, "\\1", addPendingLink_, pendingLinks); - replaceRegExp_String(&ln, iClob(new_RegExp("(?preId); + iAssert(preId_GmRun(run)); iGmRunRange range = { run, run }; /* Find the beginning. */ while (range.start > (const iGmRun *) constData_Array(&d->layout)) { const iGmRun *prev = range.start - 1; - if (prev->preId != run->preId) break; + if (preId_GmRun(prev) != preId_GmRun(run)) break; range.start = prev; } /* Find the ending. */ while (range.end < (const iGmRun *) constEnd_Array(&d->layout)) { - if (range.end->preId != run->preId) break; + if (preId_GmRun(range.end) != preId_GmRun(run)) break; range.end++; } return range; @@ -2240,6 +2280,22 @@ iChar siteIcon_GmDocument(const iGmDocument *d) { return d->siteIcon; } +void runBaseAttributes_GmDocument(const iGmDocument *d, const iGmRun *run, int *fontId_out, + int *colorId_out) { + /* Font and color according to the line type. These are needed because each GmRun is + a segment of a paragraph, and if the font or color changes inside the run, each wrapped + segment needs to know both the current font/color and ALSO the base font/color, so + the default attributes can be restored. */ + if (run->isLede) { + *fontId_out = firstParagraph_FontId; + *colorId_out = tmFirstParagraph_ColorId; + } + else { + *fontId_out = fontWithSize_Text(d->theme.fonts[run->lineType], run->font % max_FontSize); /* retain size */ + *colorId_out = d->theme.colors[run->lineType]; + } +} + iRangecc findLoc_GmRun(const iGmRun *d, iInt2 pos) { if (pos.y < top_Rect(d->bounds)) { return (iRangecc){ d->text.start, d->text.start }; diff --git a/src/gmdocument.h b/src/gmdocument.h index 20bc9890..6ad7efdc 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h @@ -140,16 +140,13 @@ struct Impl_GmRun { uint32_t color : 7; /* see max_ColorId */ uint32_t font : 10; - uint32_t mediaType : 3; - uint32_t mediaId : 9; /* zero if not an image */ - uint32_t preId : 10; /* preformatted block ID (sequential); merge with mediaId? */ + uint32_t mediaType : 3; /* note: max_MediaType means preformatted block */ + uint32_t lineType : 3; + uint32_t mediaId : 15; /* zero if not an image */ + uint32_t isLede : 1; }; }; -iLocalDef iMediaId mediaId_GmRun(const iGmRun *d) { - return (iMediaId){ .type = d->mediaType, .id = d->mediaId }; -} - iDeclareType(GmRunRange) struct Impl_GmRunRange { @@ -157,7 +154,20 @@ struct Impl_GmRunRange { const iGmRun *end; }; -iRangecc findLoc_GmRun (const iGmRun *, iInt2 pos); +iLocalDef iBool isMedia_GmRun(const iGmRun *d) { + return d->mediaType > 0 && d->mediaType < max_MediaType; +} +iLocalDef iMediaId mediaId_GmRun(const iGmRun *d) { + if (d->mediaType < max_MediaType) { + return (iMediaId){ .type = d->mediaType, .id = d->mediaId }; + } + return iInvalidMediaId; +} +iLocalDef uint32_t preId_GmRun(const iGmRun *d) { + return d->mediaType == max_MediaType ? d->mediaId : 0; +} + +iRangecc findLoc_GmRun (const iGmRun *, iInt2 pos); iDeclareClass(GmDocument) iDeclareObjectConstruction(GmDocument) @@ -215,6 +225,9 @@ iRangecc findText_GmDocument (const iGmDocument *, const iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); iGmRunRange findPreformattedRange_GmDocument (const iGmDocument *, const iGmRun *run); +void runBaseAttributes_GmDocument (const iGmDocument *, const iGmRun *run, + int *fontId_out, int *colorId_out); + enum iGmLinkPart { icon_GmLinkPart, text_GmLinkPart, @@ -241,3 +254,4 @@ const iGmPreMeta *preMeta_GmDocument (const iGmDocument *, uint16_t preId); iInt2 preRunMargin_GmDocument (const iGmDocument *, uint16_t preId); iBool preIsFolded_GmDocument (const iGmDocument *, uint16_t preId); iBool preHasAltText_GmDocument(const iGmDocument *, uint16_t preId); + diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 8c87ba1a..8b2d6a5a 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -569,7 +569,7 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { } d->visibleRuns.end = run; } - if (run->preId) { + if (preId_GmRun(run)) { pushBack_PtrArray(&d->visiblePre, run); if (run->flags & wide_GmRunFlag) { pushBack_PtrArray(&d->visibleWideRuns, run); @@ -635,14 +635,14 @@ static void invalidateVisibleLinks_DocumentWidget_(iDocumentWidget *d) { } static int runOffset_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) { - if (run->preId && run->flags & wide_GmRunFlag) { - if (d->animWideRunId == run->preId) { + if (preId_GmRun(run) && run->flags & wide_GmRunFlag) { + if (d->animWideRunId == preId_GmRun(run)) { return -value_Anim(&d->animWideRunOffset); } const size_t numOffsets = size_Array(&d->wideRunOffsets); const int *offsets = constData_Array(&d->wideRunOffsets); - if (run->preId <= numOffsets) { - return -offsets[run->preId - 1]; + if (preId_GmRun(run) <= numOffsets) { + return -offsets[preId_GmRun(run) - 1]; } } return 0; @@ -731,7 +731,7 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { } } else if (d->hoverPre && - preHasAltText_GmDocument(d->doc, d->hoverPre->preId) && + preHasAltText_GmDocument(d->doc, preId_GmRun(d->hoverPre)) && ~d->flags & noHoverWhileScrolling_DocumentWidgetFlag) { setValueSpeed_Anim(&d->altTextOpacity, 1.0f, 1.5f); if (!isFinished_Anim(&d->altTextOpacity)) { @@ -1812,10 +1812,10 @@ static void scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos, maxWidth = iMax(maxWidth, width_Rect(r->visBounds)); } const int maxOffset = maxWidth - documentWidth_DocumentWidget_(d) + d->pageMargin * gap_UI; - if (size_Array(&d->wideRunOffsets) <= run->preId) { - resize_Array(&d->wideRunOffsets, run->preId + 1); + if (size_Array(&d->wideRunOffsets) <= preId_GmRun(run)) { + resize_Array(&d->wideRunOffsets, preId_GmRun(run) + 1); } - int *offset = at_Array(&d->wideRunOffsets, run->preId - 1); + int *offset = at_Array(&d->wideRunOffsets, preId_GmRun(run) - 1); const int oldOffset = *offset; *offset = iClamp(*offset + delta, 0, maxOffset); /* Make sure the whole block gets redraw. */ @@ -1828,8 +1828,8 @@ static void scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos, d->foundMark = iNullRange; } if (duration) { - if (d->animWideRunId != run->preId || isFinished_Anim(&d->animWideRunOffset)) { - d->animWideRunId = run->preId; + if (d->animWideRunId != preId_GmRun(run) || isFinished_Anim(&d->animWideRunOffset)) { + d->animWideRunId = preId_GmRun(run); init_Anim(&d->animWideRunOffset, oldOffset); } setValueEased_Anim(&d->animWideRunOffset, *offset, duration); @@ -3814,7 +3814,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e } /* Fold/unfold a preformatted block. */ if (~d->flags & selecting_DocumentWidgetFlag && d->hoverPre && - preIsFolded_GmDocument(d->doc, d->hoverPre->preId)) { + preIsFolded_GmDocument(d->doc, preId_GmRun(d->hoverPre))) { return iTrue; } /* Begin selecting a range of text. */ @@ -3925,7 +3925,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e } } if (d->hoverPre) { - togglePreFold_DocumentWidget_(d, d->hoverPre->preId); + togglePreFold_DocumentWidget_(d, preId_GmRun(d->hoverPre)); return iTrue; } if (d->hoverLink) { @@ -4124,7 +4124,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol static void drawMark_DrawContext_(void *context, const iGmRun *run) { iDrawContext *d = context; - if (run->mediaType == none_MediaType) { + if (!isMedia_GmRun(run)) { fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark); fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark); } @@ -4249,7 +4249,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { } return; } - else if (run->mediaType) { + else if (isMedia_GmRun(run)) { /* Media UIs are drawn afterwards as a dynamic overlay. */ return; } @@ -4317,7 +4317,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { } } if (run->flags & altText_GmRunFlag) { - const iInt2 margin = preRunMargin_GmDocument(doc, run->preId); + const iInt2 margin = preRunMargin_GmDocument(doc, preId_GmRun(run)); fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackgroundAltText_ColorId); drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmQuoteIcon_ColorId); drawWrapRange_Text(run->font, @@ -4368,11 +4368,17 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { height_Rect(run->visBounds), tmQuoteIcon_ColorId); } + /* Base attributes. */ { + int f, c; + runBaseAttributes_GmDocument(doc, run, &f, &c); + setBaseAttributes_Text(f, c); + } drawBoundRange_Text(run->font, visPos, (run->isRTL ? -1 : 1) * width_Rect(run->visBounds), fg, run->text); + setBaseAttributes_Text(-1, -1); runDrawn:; } /* Presentation of links. */ @@ -4944,7 +4950,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { /* Alt text. */ const float altTextOpacity = value_Anim(&d->altTextOpacity) * 6 - 5; if (d->hoverAltPre && altTextOpacity > 0) { - const iGmPreMeta *meta = preMeta_GmDocument(d->doc, d->hoverAltPre->preId); + const iGmPreMeta *meta = preMeta_GmDocument(d->doc, preId_GmRun(d->hoverAltPre)); if (meta->flags & topLeft_GmPreMetaFlag && ~meta->flags & decoration_GmRunFlag && !isEmpty_Range(&meta->altText)) { const int margin = 3 * gap_UI / 2; diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 874cf2b5..e20d6f17 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c @@ -2245,31 +2245,33 @@ struct Impl_MarkPainter { iRect lastMarkRect; }; -static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance, - iBool isBaseRTL) { - iUnused(isBaseRTL); +static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, iTextAttrib attrib, + int origin, int advance) { iMarkPainter *mp = wrapText->context; const iRanges mark = mp->mark; if (isEmpty_Range(&mark)) { return iTrue; /* nothing marked */ } + int fontId = mp->d->font; + /* TODO: Apply attrib on the font */ const char *cstr = cstr_String(&mp->line->text); const iRanges lineRange = { wrappedText.start - cstr + mp->line->range.start, wrappedText.end - cstr + mp->line->range.start }; + const int lineHeight = lineHeight_Text(mp->d->font); if (mark.end <= lineRange.start || mark.start >= lineRange.end) { - mp->pos.y += lineHeight_Text(mp->d->font); + mp->pos.y += lineHeight; return iTrue; /* outside of mark */ } - iRect rect = { addX_I2(mp->pos, origin), init_I2(advance, lineHeight_Text(mp->d->font)) }; + iRect rect = { addX_I2(mp->pos, origin), init_I2(advance, lineHeight) }; if (mark.end < lineRange.end) { /* Calculate where the mark ends. */ const iRangecc markedPrefix = { wrappedText.start, wrappedText.start + mark.end - lineRange.start }; - rect.size.x = measureRange_Text(mp->d->font, markedPrefix).advance.x; + rect.size.x = measureRange_Text(fontId, markedPrefix).advance.x; } if (mark.start > lineRange.start) { /* Calculate where the mark starts. */ @@ -2277,10 +2279,10 @@ static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int or wrappedText.start, wrappedText.start + mark.start - lineRange.start }; - adjustEdges_Rect(&rect, 0, 0, 0, measureRange_Text(mp->d->font, unmarkedPrefix).advance.x); + adjustEdges_Rect(&rect, 0, 0, 0, measureRange_Text(fontId, unmarkedPrefix).advance.x); } rect.size.x = iMax(gap_UI / 3, rect.size.x); - mp->pos.y += lineHeight_Text(mp->d->font); + mp->pos.y += lineHeight; fillRect_Paint(mp->paint, rect, uiMarked_ColorId | opaque_ColorId); if (deviceType_App() != desktop_AppDeviceType) { if (isEmpty_Rect(mp->firstMarkRect)) mp->firstMarkRect = rect; diff --git a/src/ui/text.c b/src/ui/text.c index fd865fbd..52c6c4e0 100644 --- a/src/ui/text.c +++ b/src/ui/text.c @@ -262,6 +262,8 @@ struct Impl_Text { SDL_Palette * grayscale; SDL_Palette * blackAndWhite; /* unsmoothed glyph palette */ iRegExp * ansiEscape; + int baseFontId; /* base attributes (for restoring via escapes) */ + int baseColorId; }; iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) @@ -537,10 +539,10 @@ void init_Text(iText *d, SDL_Renderer *render) { iText *oldActive = activeText_; activeText_ = d; init_Array(&d->fonts, sizeof(iFont)); -// d->contentFont = nunito_TextFont; -// d->headingFont = nunito_TextFont; d->contentFontSize = contentScale_Text_; d->ansiEscape = new_RegExp("[[()]([0-9;AB]*)m", 0); + d->baseFontId = -1; + d->baseColorId = -1; d->render = render; /* A grayscale palette for rasterized glyphs. */ { SDL_Color colors[256]; @@ -581,13 +583,10 @@ void setOpacity_Text(float opacity) { SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f); } -//void setFont_Text(iText *d, int fontId, const char *fontSpecId) { -// setupFontVariants_Text_(d, findSpec_Fonts(fontSpecId), fontId); -// if (d->contentFont != font) { -// d->contentFont = font; -// resetFonts_Text(d); -// } -//} +void setBaseAttributes_Text(int fontId, int colorId) { + activeText_->baseFontId = fontId; + activeText_->baseColorId = colorId; +} void setDocumentFontSize_Text(iText *d, float fontSizeFactor) { fontSizeFactor *= contentScale_Text_; @@ -855,25 +854,45 @@ static iBool isControl_Char_(iChar c) { iDeclareType(AttributedRun) struct Impl_AttributedRun { - iRangei logical; /* UTF-32 codepoint indices in the logical-order text */ - iFont * font; - iColor fgColor; + iRangei logical; /* UTF-32 codepoint indices in the logical-order text */ + iTextAttrib attrib; + iFont *font; + iColor fgColor_; /* any RGB color; A > 0 */ struct { uint8_t isLineBreak : 1; - uint8_t isRTL : 1; - uint8_t isArabic : 1; /* Arabic script detected */ +// uint8_t isRTL : 1; + uint8_t isArabic : 1; /* Arabic script detected */ } flags; }; +static iColor fgColor_AttributedRun_(const iAttributedRun *d) { + if (d->fgColor_.a) { + return d->fgColor_; + } + if (d->attrib.colorId == none_ColorId) { + return (iColor){ 255, 255, 255, 255 }; + } + return get_Color(d->attrib.colorId); +} + +static void setFgColor_AttributedRun_(iAttributedRun *d, int colorId) { + d->attrib.colorId = colorId; + d->fgColor_.a = 0; +} + iDeclareType(AttributedText) iDeclareTypeConstructionArgs(AttributedText, iRangecc text, size_t maxLen, iFont *font, - iColor fgColor, int baseDir, iChar overrideChar) + int colorId, int baseDir, iFont *baseFont, int baseColorId, + iChar overrideChar) struct Impl_AttributedText { iRangecc source; /* original source text */ size_t maxLen; iFont * font; - iColor fgColor; + int colorId; + iFont * baseFont; + int baseColorId; + iBool isBaseRTL; iArray runs; iArray logical; /* UTF-32 text in logical order (mixed directions; matches source) */ iArray visual; /* UTF-32 text in visual order (LTR) */ @@ -881,13 +900,14 @@ struct Impl_AttributedText { iArray visualToLogical; iArray logicalToSourceOffset; /* map logical character to an UTF-8 offset in the source text */ char * bidiLevels; - iBool isBaseRTL; }; iDefineTypeConstructionArgs(AttributedText, - (iRangecc text, size_t maxLen, iFont *font, iColor fgColor, - int baseDir, iChar overrideChar), - text, maxLen, font, fgColor, baseDir, overrideChar) + (iRangecc text, size_t maxLen, iFont *font, int colorId, + int baseDir, iFont *baseFont, int baseColorId, + iChar overrideChar), + text, maxLen, font, colorId, baseDir, baseFont, baseColorId, + overrideChar) static const char *sourcePtr_AttributedText_(const iAttributedText *d, int logicalPos) { const int *logToSource = constData_Array(&d->logicalToSourceOffset); @@ -916,16 +936,22 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i run->logical.start = endAt; } -static iFont *withStyle_Font_(const iFont *d, enum iFontStyle styleId) { - const int fontId = (fontId_Text_(d) / maxVariants_Fonts) * maxVariants_Fonts; - const int sizeId = sizeId_Text_(d); - return font_Text_(FONT_ID(fontId, styleId, sizeId)); +int fontWithSize_Text(int font, enum iFontSize sizeId) { + const int familyId = (font / maxVariants_Fonts) * maxVariants_Fonts; + const int styleId = (font / max_FontSize) % max_FontStyle; + return FONT_ID(familyId, styleId, sizeId); +} + +int fontWithStyle_Text(int font, enum iFontStyle styleId) { + const int familyId = (font / maxVariants_Fonts) * maxVariants_Fonts; + const int sizeId = font % max_FontSize; + return FONT_ID(familyId, styleId, sizeId); } -static iFont *withFontId_Font_(const iFont *d, enum iFontId fontId) { - const int styleId = styleId_Text_(d); - const int sizeId = sizeId_Text_(d); - return font_Text_(FONT_ID(fontId, styleId, sizeId)); +int fontWithFamily_Text(int font, enum iFontId familyId) { + const int styleId = (font / max_FontSize) % max_FontStyle; + const int sizeId = font % max_FontSize; + return FONT_ID(familyId, styleId, sizeId); } static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) { @@ -982,15 +1008,17 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh pushBack_Array(&d->logicalToVisual, &(int){ length }); pushBack_Array(&d->visualToLogical, &(int){ length }); } - iAttributedRun run = { .logical = { 0, length }, - .font = d->font, - .fgColor = d->fgColor }; - const int * logToSource = constData_Array(&d->logicalToSourceOffset); + iAttributedRun run = { + .logical = { 0, length }, + .attrib = { .colorId = d->colorId, .isBaseRTL = d->isBaseRTL }, + .font = d->font, + }; + const int *logToSource = constData_Array(&d->logicalToSourceOffset); const int * logToVis = constData_Array(&d->logicalToVisual); const iChar * logicalText = constData_Array(&d->logical); iBool isRTL = d->isBaseRTL; int numNonSpace = 0; - iFont * activeFont = d->font; + iFont * attribFont = d->font; for (int pos = 0; pos < length; pos++) { const iChar ch = logicalText[pos]; #if defined (LAGRANGE_ENABLE_FRIBIDI) @@ -1010,7 +1038,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh #else const iBool isNeutral = iTrue; #endif - run.flags.isRTL = isRTL; + run.attrib.isRTL = isRTL; if (ch == 0x1b) { /* ANSI escape. */ pos++; const char *srcPos = d->source.start + logToSource[pos]; @@ -1020,21 +1048,36 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) { finishRun_AttributedText_(d, &run, pos - 1); const iRangecc sequence = capturedRange_RegExpMatch(&m, 1); + /* TODO: Bold/italic attributes are assumed to be inside body text. + We don't know what the current text style is supposed to be. + That should be an additional attribute passed to WrapText, or a feature of + WrapText that can be called both from here and in the run typesetter. + The styling here is hardcoded to match `typesetOneLine_RunTypesetter_()`. */ if (equal_Rangecc(sequence, "1")) { - activeFont = withStyle_Font_(activeFont, bold_FontStyle); + run.attrib.bold = iTrue; + if (d->baseColorId == tmParagraph_ColorId) { + setFgColor_AttributedRun_(&run, tmFirstParagraph_ColorId); + } + attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), bold_FontStyle)); } else if (equal_Rangecc(sequence, "3")) { - activeFont = withStyle_Font_(activeFont, italic_FontStyle); + run.attrib.italic = iTrue; + attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), italic_FontStyle)); } else if (equal_Rangecc(sequence, "4")) { - activeFont = withFontId_Font_(activeFont, monospace_FontId); + run.attrib.monospace = iTrue; + setFgColor_AttributedRun_(&run, tmPreformatted_ColorId); + attribFont = font_Text_(fontWithFamily_Text(fontId_Text_(d->baseFont), monospace_FontId)); } else if (equal_Rangecc(sequence, "0")) { - activeFont = d->font; /* restore original */ - run.fgColor = d->fgColor; + run.attrib.bold = iFalse; + run.attrib.italic = iFalse; + run.attrib.monospace = iFalse; + attribFont = d->baseFont; + setFgColor_AttributedRun_(&run, d->baseColorId); } else { - run.fgColor = ansiForeground_Color(sequence, tmParagraph_ColorId); + run.fgColor_ = ansiForeground_Color(sequence, tmParagraph_ColorId); } pos += length_Rangecc(capturedRange_RegExpMatch(&m, 0)); iAssert(logToSource[pos] == end_RegExpMatch(&m) - d->source.start); @@ -1056,7 +1099,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh colorNum = esc - asciiBase_ColorEscape; } run.logical.start = pos + 1; - run.fgColor = (colorNum >= 0 ? get_Color(colorNum) : d->fgColor); + setFgColor_AttributedRun_(&run, colorNum >= 0 ? colorNum : d->colorId); continue; } if (ch == '\n') { @@ -1077,7 +1120,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh } continue; } - iFont *currentFont = activeFont; + iFont *currentFont = attribFont; if (run.font->fontSpec->flags & arabic_FontSpecFlag && isPunct_Char(ch)) { currentFont = run.font; /* remain as Arabic for whitespace */ } @@ -1113,12 +1156,15 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh #endif } -void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, iColor fgColor, - int baseDir, iChar overrideChar) { - d->source = text; - d->maxLen = maxLen ? maxLen : iInvalidSize; - d->font = font; - d->fgColor = fgColor; +void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, int colorId, + int baseDir, iFont *baseFont, int baseColorId, iChar overrideChar) { + d->source = text; + d->maxLen = maxLen ? maxLen : iInvalidSize; + d->font = font; + d->colorId = colorId; + d->baseFont = baseFont; + d->baseColorId = baseColorId; + d->isBaseRTL = iFalse; init_Array(&d->runs, sizeof(iAttributedRun)); init_Array(&d->logical, sizeof(iChar)); init_Array(&d->visual, sizeof(iChar)); @@ -1126,7 +1172,6 @@ void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont init_Array(&d->visualToLogical, sizeof(int)); init_Array(&d->logicalToSourceOffset, sizeof(int)); d->bidiLevels = NULL; - d->isBaseRTL = iFalse; prepare_AttributedText_(d, baseDir, overrideChar); } @@ -1278,7 +1323,7 @@ static void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) { iArray glyphIndices; init_Array(&glyphIndices, sizeof(uint32_t)); iAttributedText attrText; - init_AttributedText(&attrText, text, 0, d, (iColor){}, 0, 0); + init_AttributedText(&attrText, text, 0, d, none_ColorId, 0, d, none_ColorId, 0); /* We use AttributedText here so the font lookup matches the behavior during text drawing -- glyphs may be selected from a font that's different than `d`. */ const iChar *logicalText = constData_Array(&attrText.logical); @@ -1333,17 +1378,17 @@ struct Impl_RunArgs { /* TODO: Cleanup using TextMetrics Use TextMetrics output pointer instead of return value & cursorAdvance_out. */ iInt2 * cursorAdvance_out; -// const char ** continueFrom_out; int * runAdvance_out; }; -static iBool notify_WrapText_(iWrapText *d, const char *ending, int origin, int advance, iBool isBaseRTL) { +static iBool notify_WrapText_(iWrapText *d, const char *ending, iTextAttrib attrib, + int origin, int advance) { if (d && d->wrapFunc && d->wrapRange_.start) { /* `wrapRange_` uses logical indices. */ const char *end = ending ? ending : d->wrapRange_.end; iRangecc range = { d->wrapRange_.start, end }; iAssert(range.start <= range.end); - const iBool result = d->wrapFunc(d, range, origin, advance, isBaseRTL); + const iBool result = d->wrapFunc(d, range, attrib, origin, advance); if (result) { d->wrapRange_.start = end; } @@ -1470,8 +1515,11 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { font is used and other attributes such as color. (HarfBuzz shaping is done with one specific font.) */ iAttributedText attrText; - init_AttributedText(&attrText, args->text, args->maxLen, d, get_Color(args->color), - args->baseDir, wrap ? wrap->overrideChar : 0); + init_AttributedText(&attrText, args->text, args->maxLen, d, args->color, + args->baseDir, + activeText_->baseFontId >= 0 ? font_Text_(activeText_->baseFontId) : d, + activeText_->baseColorId, + wrap ? wrap->overrideChar : 0); if (wrap) { wrap->baseDir = attrText.isBaseRTL ? -1 : +1; /* TODO: Duplicated args? */ @@ -1522,6 +1570,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { iRangei wrapPosRange = { 0, textLen }; int wrapResumePos = textLen; /* logical position where next line resumes */ size_t wrapResumeRunIndex = runCount; /* index of run where next line resumes */ + iTextAttrib attrib = { .colorId = args->color, .isBaseRTL = attrText.isBaseRTL }; + iTextAttrib wrapAttrib = attrib; + iTextAttrib lastAttrib = attrib; const int layoutBound = (wrap ? wrap->maxWidth : 0); iBool isFirst = iTrue; const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2()); @@ -1545,6 +1596,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { /* Determine ends of wrapRuns and wrapVisRange. */ for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) { const iAttributedRun *run = at_Array(&attrText.runs, runIndex); + /* Update the attributes. */ if (run->flags.isLineBreak) { if (checkHitChar && wrap->hitChar == sourcePtr_AttributedText_(&attrText, run->logical.start)) { @@ -1554,7 +1606,6 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { wrapResumePos = run->logical.end; wrapRuns.end = runIndex; wrapResumeRunIndex = runIndex + 1; - //yCursor += d->height; break; } wrapResumeRunIndex = runCount; @@ -1564,8 +1615,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { shape_GlyphBuffer_(buf); int safeBreakPos = -1; iChar prevCh = 0; + lastAttrib = run->attrib; for (unsigned int ir = 0; ir < buf->glyphCount; ir++) { - const int i = (run->flags.isRTL ? buf->glyphCount - ir - 1 : ir); + const int i = (run->attrib.isRTL ? buf->glyphCount - ir - 1 : ir); const hb_glyph_info_t *info = &buf->glyphInfo[i]; const hb_codepoint_t glyphId = info->codepoint; const int logPos = info->cluster; @@ -1603,10 +1655,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { prevCh = ch; } else { - //if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { - safeBreakPos = logPos; - breakAdvance = wrapAdvance; - //} + safeBreakPos = logPos; + breakAdvance = wrapAdvance; + wrapAttrib = run->attrib; } if (isHitPointOnThisLine) { if (wrap->hitPoint.x >= orig.x + wrapAdvance && @@ -1638,7 +1689,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { wrapPosRange.end = logPos; breakAdvance = wrapAdvance; } - wrapResumePos = wrapPosRange.end; + wrapResumePos = wrapPosRange.end; if (args->wrap->mode != anyCharacter_WrapTextMode) { while (wrapResumePos < textLen && isSpace_Char(logicalText[wrapResumePos])) { wrapResumePos++; /* skip space */ @@ -1688,7 +1739,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) { const iAttributedRun *run = at_Array(&attrText.runs, runIndex); if (!attrText.isBaseRTL) { /* left-to-right */ - if (run->flags.isRTL) { + if (run->attrib.isRTL) { if (oppositeInsertIndex == iInvalidPos) { oppositeInsertIndex = size_Array(&runOrder); } @@ -1700,7 +1751,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { } } else { /* right-to-left */ - if (!run->flags.isRTL) { + if (!run->attrib.isRTL) { if (oppositeInsertIndex == iInvalidPos) { oppositeInsertIndex = 0; } @@ -1739,11 +1790,12 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { if (wrap && wrap->wrapFunc && !notify_WrapText_(args->wrap, sourcePtr_AttributedText_(&attrText, wrapResumePos), + wrapAttrib, origin, - iRound(wrapAdvance), - attrText.isBaseRTL)) { + iRound(wrapAdvance))) { willAbortDueToWrap = iTrue; } + wrapAttrib = lastAttrib; xCursor = origin; /* We have determined a possible wrap position and alignment for the work runs, so now we can process the glyphs. */ @@ -1796,8 +1848,8 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y, glyph->rect[hoff].size.x, glyph->rect[hoff].size.y }; - if (run->font->height < d->height) { - dst.y += d->baseline - run->font->baseline; + if (run->font->height < attrText.baseFont->height) { + dst.y += attrText.baseFont->baseline - run->font->baseline; } if (mode & visualFlag_RunMode) { if (isEmpty_Rect(bounds)) { @@ -1819,7 +1871,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { iAssert(isRasterized_Glyph_(glyph, hoff)); } if (~mode & permanentColorFlag_RunMode) { - const iColor clr = run->fgColor; + const iColor clr = fgColor_AttributedRun_(run); SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b); if (args->mode & fillBackground_RunMode) { SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0); @@ -1939,9 +1991,9 @@ static int runFlagsFromId_(enum iFontId fontId) { return runFlags; } -static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, int origin, int advance, - iBool isBaseRTL) { - iUnused(origin, advance, isBaseRTL); +static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, iTextAttrib attrib, int origin, + int advance) { + iUnused(attrib, origin, advance); *((const char **) d->context) = range.end; return iFalse; /* just one line */ } diff --git a/src/ui/text.h b/src/ui/text.h index ac59e7c8..51f7754d 100644 --- a/src/ui/text.h +++ b/src/ui/text.h @@ -120,6 +120,9 @@ void resetFonts_Text (iText *); int lineHeight_Text (int fontId); float emRatio_Text (int fontId); /* em advance to line height ratio */ iRect visualBounds_Text (int fontId, iRangecc text); +int fontWithSize_Text (int fontId, enum iFontSize sizeId); +int fontWithStyle_Text (int fontId, enum iFontStyle styleId); +int fontWithFamily_Text (int fontId, enum iFontId familyId); iDeclareType(TextMetrics) @@ -149,9 +152,10 @@ enum iAlignment { right_Alignment, }; -void setOpacity_Text (float opacity); +void setOpacity_Text (float opacity); +void setBaseAttributes_Text (int fontId, int colorId); /* current "normal" text attributes */ -void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */ +void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */ void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...); void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...); @@ -173,12 +177,28 @@ enum iWrapTextMode { word_WrapTextMode, }; +iDeclareType(TextAttrib) + +/* Initial attributes at the start of a text string. These may be modified by control + sequences inside a text run. */ +struct Impl_TextAttrib { + int16_t colorId; + struct { + uint16_t bold : 1; + uint16_t italic : 1; + uint16_t monospace : 1; + uint16_t isBaseRTL : 1; + uint16_t isRTL : 1; + }; +}; + struct Impl_WrapText { /* arguments */ iRangecc text; int maxWidth; enum iWrapTextMode mode; - iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, int origin, int advance, iBool isBaseRTL); + iBool (*wrapFunc)(iWrapText *, iRangecc wrappedText, iTextAttrib attrib, int origin, + int advance); void * context; iChar overrideChar; /* use this for all characters instead of the real ones */ int baseDir; /* set to +1 for LTR, -1 for RTL */ -- cgit v1.2.3 From 1f3751216248b458b0bb1115c253b77e1057a24e Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Thu, 14 Oct 2021 18:57:20 +0300 Subject: Preferences: ANSI escape sequences in Gemtext It can be argued that using ANSI escapes to style text goes against the intended use of Gemtext. Therefore, all ANSI escapes are now disabled by default in Gemtext. --- po/en.po | 3 ++ res/lang/de.bin | Bin 26326 -> 26367 bytes res/lang/en.bin | Bin 23764 -> 23805 bytes res/lang/eo.bin | Bin 22618 -> 22659 bytes res/lang/es.bin | Bin 26710 -> 26751 bytes res/lang/es_MX.bin | Bin 24678 -> 24719 bytes res/lang/fi.bin | Bin 26622 -> 26663 bytes res/lang/fr.bin | Bin 27532 -> 27573 bytes res/lang/gl.bin | Bin 25986 -> 26027 bytes res/lang/ia.bin | Bin 25651 -> 25692 bytes res/lang/ie.bin | Bin 25856 -> 25897 bytes res/lang/isv.bin | Bin 22554 -> 22595 bytes res/lang/pl.bin | Bin 26927 -> 26968 bytes res/lang/ru.bin | Bin 39266 -> 39307 bytes res/lang/sk.bin | Bin 22887 -> 22928 bytes res/lang/sr.bin | Bin 39171 -> 39212 bytes res/lang/tok.bin | Bin 24101 -> 24142 bytes res/lang/zh_Hans.bin | Bin 22603 -> 22644 bytes res/lang/zh_Hant.bin | Bin 22744 -> 22785 bytes src/app.c | 5 +++ src/gmdocument.c | 73 +++++++++++++++++++++++++++-------- src/gmdocument.h | 1 + src/prefs.c | 1 + src/prefs.h | 1 + src/ui/documentwidget.c | 5 ++- src/ui/text.c | 100 ++++++++++++++++++++++++++++-------------------- src/ui/text.h | 1 + src/ui/util.c | 40 +++++++++---------- 28 files changed, 151 insertions(+), 79 deletions(-) (limited to 'src/gmdocument.c') diff --git a/po/en.po b/po/en.po index e30ff214..f0a5b1c7 100644 --- a/po/en.po +++ b/po/en.po @@ -1493,6 +1493,9 @@ msgstr "On Dark" msgid "prefs.boldlink.light" msgstr "On Light" +msgid "prefs.gemtext.ansi" +msgstr "Gemtext ANSI escapes:" + msgid "prefs.font.smooth" msgstr "Smoothing:" diff --git a/res/lang/de.bin b/res/lang/de.bin index b07ab90c..17f3a3d6 100644 Binary files a/res/lang/de.bin and b/res/lang/de.bin differ diff --git a/res/lang/en.bin b/res/lang/en.bin index 9db0d29a..42d62334 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/eo.bin b/res/lang/eo.bin index 050f5075..5d865707 100644 Binary files a/res/lang/eo.bin and b/res/lang/eo.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 30d7443d..5f620910 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin index 6e5c159e..de5e253f 100644 Binary files a/res/lang/es_MX.bin and b/res/lang/es_MX.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index bbf9f8be..5ca852cf 100644 Binary files a/res/lang/fi.bin and b/res/lang/fi.bin differ diff --git a/res/lang/fr.bin b/res/lang/fr.bin index 2f8c5717..b6a577b3 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/gl.bin b/res/lang/gl.bin index 6c9f8217..55ea5675 100644 Binary files a/res/lang/gl.bin and b/res/lang/gl.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 134c6f6a..63a9cb33 100644 Binary files a/res/lang/ia.bin and b/res/lang/ia.bin differ diff --git a/res/lang/ie.bin b/res/lang/ie.bin index 26eb5183..19b631aa 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/isv.bin b/res/lang/isv.bin index 80f3f4f4..2af131f1 100644 Binary files a/res/lang/isv.bin and b/res/lang/isv.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 6c5d3886..93e070de 100644 Binary files a/res/lang/pl.bin and b/res/lang/pl.bin differ diff --git a/res/lang/ru.bin b/res/lang/ru.bin index 5b9cd5b8..2ec7eaaf 100644 Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ diff --git a/res/lang/sk.bin b/res/lang/sk.bin index 8e0af448..06cf8cf8 100644 Binary files a/res/lang/sk.bin and b/res/lang/sk.bin differ diff --git a/res/lang/sr.bin b/res/lang/sr.bin index b109daf2..41f7fb4b 100644 Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ diff --git a/res/lang/tok.bin b/res/lang/tok.bin index 8f17c00c..c68303d1 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 7c60fcbf..0b243530 100644 Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index 63bb0060..d5c13885 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/app.c b/src/app.c index 2dea09d8..1f066535 100644 --- a/src/app.c +++ b/src/app.c @@ -2351,6 +2351,10 @@ iBool handleCommand_App(const char *cmd) { } return iTrue; } + else if (equal_Command(cmd, "prefs.gemtext.ansi.changed")) { + d->prefs.gemtextAnsiEscapes = arg_Command(cmd) != 0; + return iTrue; + } else if (equal_Command(cmd, "prefs.mono.gemini.changed") || equal_Command(cmd, "prefs.mono.gopher.changed")) { const iBool isSet = (arg_Command(cmd) != 0); @@ -2764,6 +2768,7 @@ iBool handleCommand_App(const char *cmd) { setFlags_Widget(findChild_Widget(dlg, "prefs.boldlink.light"), selected_WidgetFlag, d->prefs.boldLinkLight); + setToggle_Widget(findChild_Widget(dlg, "prefs.gemtext.ansi"), d->prefs.gemtextAnsiEscapes); setToggle_Widget(findChild_Widget(dlg, "prefs.font.smooth"), d->prefs.fontSmoothing); setFlags_Widget( findChild_Widget(dlg, format_CStr("prefs.linewidth.%d", d->prefs.lineWidth)), diff --git a/src/gmdocument.c b/src/gmdocument.c index 5c2a849e..f43d2758 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -78,6 +78,7 @@ iDefineTypeConstruction(GmLink) iDeclareType(GmTheme) struct Impl_GmTheme { + iBool ansiEscapesEnabled; int colors[max_GmLineType]; int fonts[max_GmLineType]; }; @@ -507,6 +508,7 @@ static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, d->run.visBounds = d->run.bounds; d->run.visBounds.size.x = dims.x; d->run.isRTL = attrib.isBaseRTL; + printf("origin:%d isRTL:%d\n{%s}\n", origin, attrib.isBaseRTL, cstr_Rangecc(wrapRange)); pushBack_Array(&d->layout, &d->run); d->run.flags &= ~startOfLine_GmRunFlag; d->pos.y += lineHeight_Text(d->baseFont) * prefs_App()->lineSpacing; @@ -580,6 +582,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { isPreformat = iTrue; isFirstText = iFalse; } + setAnsiEscapesEnabled_Text(d->theme.ansiEscapesEnabled); while (nextSplit_Rangecc(content, "\n", &contentLine)) { iRangecc line = contentLine; /* `line` will be trimmed; modifying would confuse `nextSplit_Rangecc` */ if (*line.end == '\r') { @@ -813,12 +816,12 @@ static void doLayout_GmDocument_(iGmDocument *d) { const iGmLink *link = constAt_PtrArray(&d->links, run.linkId - 1); const enum iGmLinkScheme scheme = scheme_GmLinkFlag(link->flags); icon.text = range_CStr(link->flags & query_GmLinkFlag ? magnifyingGlass - : scheme == file_GmLinkScheme ? folder : scheme == titan_GmLinkScheme ? uploadArrow : scheme == finger_GmLinkScheme ? pointingFinger : scheme == mailto_GmLinkScheme ? envelope : link->flags & remote_GmLinkFlag ? globe : link->flags & imageFileExtension_GmLinkFlag ? image + : scheme == file_GmLinkScheme ? folder : arrow); /* Custom link icon is shown on local Gemini links only. */ if (!isEmpty_Range(&link->labelIcon)) { @@ -1046,6 +1049,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { } } } + setAnsiEscapesEnabled_Text(iTrue); // printf("[GmDocument] layout size: %zu runs (%zu bytes)\n", // size_Array(&d->layout), size_Array(&d->layout) * sizeof(iGmRun)); } @@ -1843,15 +1847,24 @@ static void convertMarkdownToGemtext_GmDocument_(iGmDocument *d) { iArray *pendingLinks = collectNew_Array(sizeof(iPendingLink)); const iRegExp *imageLinkPattern = iClob(new_RegExp("\n?!\\[(.+)\\]\\(([^)]+)\\)\n?", 0)); const iRegExp *linkPattern = iClob(new_RegExp("\\[(.+?)\\]\\(([^)]+)\\)", 0)); + const iRegExp *standaloneLinkPattern = iClob(new_RegExp("^[\\s*_]*\\[(.+?)\\]\\(([^)]+)\\)[\\s*_]*$", 0)); const iRegExp *namedLinkPattern = iClob(new_RegExp("\\[(.+?)\\]\\[(.+?)\\]", 0)); const iRegExp *namePattern = iClob(new_RegExp("\\s*\\[(.+?)\\]\\s*:\\s*([^\n]+)", 0)); iString result; init_String(&result); + replace_String(&d->source, " ", "\u00a0"); + replaceRegExp_String(&d->source, iClob(new_RegExp("```", 0)), "\n```\n", NULL, NULL); iRangecc line = iNullRange; iBool isPre = iFalse; + iBool isBlock = iFalse; iBool isLastEmpty = iFalse; while (nextSplit_Rangecc(range_String(&d->source), "\n", &line)) { - if (!isPre) { + if (!isPre && !isBlock) { + if (equal_Rangecc(line, "```")) { + isBlock = iTrue; + appendCStr_String(&result, "\n```"); + continue; + } if (*line.start == '#') { flushPendingLinks_(pendingLinks, &d->source, &result); } @@ -1867,7 +1880,9 @@ static void convertMarkdownToGemtext_GmDocument_(iGmDocument *d) { (isnumber(line.start[1]) && line.start[2] == '.'))) { appendCStr_String(&result, "\n\n"); } - else if (*line.start == '*' || *line.start == '>' || *line.start == '#') { + else if (endsWith_String(&result, " ") || + *line.start == '*' || *line.start == '>' || *line.start == '#' || + (*line.start == '|' && endsWith_String(&result, "|"))) { appendCStr_String(&result, "\n"); } else { @@ -1875,6 +1890,17 @@ static void convertMarkdownToGemtext_GmDocument_(iGmDocument *d) { } isLastEmpty = iFalse; } + else if (isBlock) { + if (equal_Rangecc(line, "```")) { + isBlock = iFalse; + appendCStr_String(&result, "\n```\n"); + } + else { + appendCStr_String(&result, "\n"); + appendRange_String(&result, line); + } + continue; + } if (startsWith_Rangecc(line, " ")) { line.start += 4; if (!isPre) { @@ -1887,9 +1913,11 @@ static void convertMarkdownToGemtext_GmDocument_(iGmDocument *d) { appendCStr_String(&result, "\n"); } appendCStr_String(&result, "```\n"); + if (equal_Rangecc(line, "```")) { + line.start = line.end; /* don't repeat it */ + } isPre = iFalse; } - /* Check for image links. */ if (isPre) { appendRange_String(&result, line); appendCStr_String(&result, "\n"); @@ -1897,14 +1925,15 @@ static void convertMarkdownToGemtext_GmDocument_(iGmDocument *d) { else { iString ln; initRange_String(&ln, line); - replaceRegExp_String(&ln, iClob(new_RegExp("\\*\\*(.+?)\\*\\*", 0)), "\x1b[1m\\1\x1b[0m", NULL, NULL); - replaceRegExp_String(&ln, iClob(new_RegExp("\\b\\*(.+?)\\*\\b", 0)), "\x1b[3m\\1\x1b[0m", NULL, NULL); - replaceRegExp_String(&ln, iClob(new_RegExp("\\b_(.+?)_\\b", 0)), "\x1b[3m\\1\x1b[0m", NULL, NULL); - replaceRegExp_String(&ln, iClob(new_RegExp("```([^`]+?)```", 0)), "\n```\n\\1\n```\n", NULL, NULL); replaceRegExp_String(&ln, namePattern, "", NULL, 0); + replaceRegExp_String(&ln, standaloneLinkPattern, "\n=> \\2 \\1", NULL, NULL); replaceRegExp_String(&ln, imageLinkPattern, "\n=> \\2 \\1\n", NULL, NULL); replaceRegExp_String(&ln, namedLinkPattern, "\\1", addPendingNamedLink_, pendingLinks); replaceRegExp_String(&ln, linkPattern, "\\1", addPendingLink_, pendingLinks); + replaceRegExp_String(&ln, iClob(new_RegExp("\\*\\*(.+?)\\*\\*", 0)), "\x1b[1m\\1\x1b[0m", NULL, NULL); + replaceRegExp_String(&ln, iClob(new_RegExp("__(.+?)__", 0)), "\x1b[1m\\1\x1b[0m", NULL, NULL); + replaceRegExp_String(&ln, iClob(new_RegExp("\\*(.+?)\\*", 0)), "\x1b[3m\\1\x1b[0m", NULL, NULL); + replaceRegExp_String(&ln, iClob(new_RegExp("\\b_(.+?)_\\b", 0)), "\x1b[3m\\1\x1b[0m", NULL, NULL); replaceRegExp_String(&ln, iClob(new_RegExp("(?source, iClob(new_RegExp("```([^`]+)```", 0)), "\n\n```\v\\1\v```\n\n"); -// replaceRegExp_String(&d->source, iClob(new_RegExp("\n\\s*([0-9]+)\\.", 0)), "\n\n\\1."); /* numbered list */ replaceRegExp_String(&d->source, iClob(new_RegExp("(\\s*\n){2,}", 0)), "\n\n", NULL, NULL); /* normalize paragraph breaks */ printf("Converted:\n%s", cstr_String(&d->source)); -// replaceRegExp_String(&d->source, iClob(new_RegExp("\n(?![*>#]\\s)", 0)), " "); /* normal line breaks */ -// replace_String(&d->source, "\f", "\n\n"); -// replace_String(&d->source, "\v", "\n"); d->format = gemini_SourceFormat; } @@ -1936,12 +1960,19 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width, int // printf("[GmDocument] source is unchanged!\n"); return; /* Nothing to do. */ } + /* Normalize and convert to Gemtext if needed. */ set_String(&d->unormSource, source); - /* Normalize. */ - set_String(&d->source, &d->unormSource); - if (d->format == markdown_SourceFormat) { + set_String(&d->source, source); + if (d->format == gemini_SourceFormat) { + d->theme.ansiEscapesEnabled = prefs_App()->gemtextAnsiEscapes; + } + else if (d->format == markdown_SourceFormat) { convertMarkdownToGemtext_GmDocument_(d); - set_String(&d->unormSource, &d->source); + set_String(&d->unormSource, &d->source); /* use the converted source from now on */ + d->theme.ansiEscapesEnabled = iTrue; /* escapes are used for styling */ + } + else { + d->theme.ansiEscapesEnabled = iTrue; } if (isNormalized_GmDocument_(d)) { normalize_GmDocument(d); @@ -1983,6 +2014,7 @@ const iGmPreMeta *preMeta_GmDocument(const iGmDocument *d, uint16_t preId) { void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render, void *context) { iBool isInside = iFalse; + setAnsiEscapesEnabled_Text(d->theme.ansiEscapesEnabled); /* TODO: Check lookup table for quick starting position. */ iConstForEach(Array, i, &d->layout) { const iGmRun *run = i.value; @@ -1997,6 +2029,7 @@ void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRende render(context, run); } } + setAnsiEscapesEnabled_Text(iTrue); } static iBool isValidRun_GmDocument_(const iGmDocument *d, const iGmRun *run) { @@ -2011,6 +2044,7 @@ const iGmRun *renderProgressive_GmDocument(const iGmDocument *d, const iGmRun *f size_t maxCount, iRangei visRangeY, iGmDocumentRenderFunc render, void *context) { + setAnsiEscapesEnabled_Text(d->theme.ansiEscapesEnabled); const iGmRun *run = first; while (isValidRun_GmDocument_(d, run)) { if ((dir < 0 && bottom_Rect(run->visBounds) < visRangeY.start) || @@ -2023,6 +2057,7 @@ const iGmRun *renderProgressive_GmDocument(const iGmDocument *d, const iGmRun *f render(context, run); run += dir; } + setAnsiEscapesEnabled_Text(iTrue); return isValidRun_GmDocument_(d, run) ? run : NULL; } @@ -2280,6 +2315,10 @@ iChar siteIcon_GmDocument(const iGmDocument *d) { return d->siteIcon; } +iBool ansiEscapesEnabled_GmDocument(const iGmDocument *d) { + return d->theme.ansiEscapesEnabled; +} + void runBaseAttributes_GmDocument(const iGmDocument *d, const iGmRun *run, int *fontId_out, int *colorId_out) { /* Font and color according to the line type. These are needed because each GmRun is diff --git a/src/gmdocument.h b/src/gmdocument.h index 6ad7efdc..4a248550 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h @@ -225,6 +225,7 @@ iRangecc findText_GmDocument (const iGmDocument *, const iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); iGmRunRange findPreformattedRange_GmDocument (const iGmDocument *, const iGmRun *run); +iBool ansiEscapesEnabled_GmDocument (const iGmDocument *); void runBaseAttributes_GmDocument (const iGmDocument *, const iGmRun *run, int *fontId_out, int *colorId_out); diff --git a/src/prefs.c b/src/prefs.c index f51538ac..ea0717e5 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -61,6 +61,7 @@ void init_Prefs(iPrefs *d) { setCStr_String(&d->strings[monospaceFont_PrefsString], "iosevka"); setCStr_String(&d->strings[monospaceDocumentFont_PrefsString], "iosevka-body"); d->fontSmoothing = iTrue; + d->gemtextAnsiEscapes = iFalse; d->monospaceGemini = iFalse; d->monospaceGopher = iFalse; d->boldLinkDark = iTrue; diff --git a/src/prefs.h b/src/prefs.h index d1131aae..a44e98b7 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -88,6 +88,7 @@ struct Impl_Prefs { int maxMemorySize; /* MB */ /* Style */ iBool fontSmoothing; + iBool gemtextAnsiEscapes; iBool monospaceGemini; iBool monospaceGopher; iBool boldLinkDark; diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 0def691d..b2594997 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -4813,6 +4813,7 @@ static iBool render_DocumentWidget_(const iDocumentWidget *d, iDrawContext *ctx, } } } + setAnsiEscapesEnabled_Text(ansiEscapesEnabled_GmDocument(d->doc)); iConstForEach(PtrSet, r, d->invalidRuns) { const iGmRun *run = *r.value; if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) { @@ -4820,10 +4821,12 @@ static iBool render_DocumentWidget_(const iDocumentWidget *d, iDrawContext *ctx, drawRun_DrawContext_(ctx, run); } } + setAnsiEscapesEnabled_Text(iTrue); } endTarget_Paint(p); if (prerenderExtra && didDraw) { - return iTrue; + /* Just a run at a time. */ + break; } } clear_PtrSet(d->invalidRuns); diff --git a/src/ui/text.c b/src/ui/text.c index 52c6c4e0..7afaf583 100644 --- a/src/ui/text.c +++ b/src/ui/text.c @@ -262,6 +262,7 @@ struct Impl_Text { SDL_Palette * grayscale; SDL_Palette * blackAndWhite; /* unsmoothed glyph palette */ iRegExp * ansiEscape; + iBool enableAnsiEscapes; int baseFontId; /* base attributes (for restoring via escapes) */ int baseColorId; }; @@ -540,7 +541,7 @@ void init_Text(iText *d, SDL_Renderer *render) { activeText_ = d; init_Array(&d->fonts, sizeof(iFont)); d->contentFontSize = contentScale_Text_; - d->ansiEscape = new_RegExp("[[()]([0-9;AB]*)m", 0); + d->ansiEscape = new_RegExp("[[()]([0-9;AB]*?)m", 0); d->baseFontId = -1; d->baseColorId = -1; d->render = render; @@ -588,6 +589,10 @@ void setBaseAttributes_Text(int fontId, int colorId) { activeText_->baseColorId = colorId; } +void setAnsiEscapesEnabled_Text(iBool enableAnsiEscapes) { + activeText_->enableAnsiEscapes = enableAnsiEscapes; +} + void setDocumentFontSize_Text(iText *d, float fontSizeFactor) { fontSizeFactor *= contentScale_Text_; iAssert(fontSizeFactor > 0); @@ -1047,40 +1052,41 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh init_RegExpMatch(&m); if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) { finishRun_AttributedText_(d, &run, pos - 1); - const iRangecc sequence = capturedRange_RegExpMatch(&m, 1); - /* TODO: Bold/italic attributes are assumed to be inside body text. - We don't know what the current text style is supposed to be. - That should be an additional attribute passed to WrapText, or a feature of - WrapText that can be called both from here and in the run typesetter. - The styling here is hardcoded to match `typesetOneLine_RunTypesetter_()`. */ - if (equal_Rangecc(sequence, "1")) { - run.attrib.bold = iTrue; - if (d->baseColorId == tmParagraph_ColorId) { - setFgColor_AttributedRun_(&run, tmFirstParagraph_ColorId); + if (activeText_->enableAnsiEscapes) { + const iRangecc sequence = capturedRange_RegExpMatch(&m, 1); + /* Note: This styling is hardcoded to match `typesetOneLine_RunTypesetter_()`. */ + if (equal_Rangecc(sequence, "1")) { + run.attrib.bold = iTrue; + if (d->baseColorId == tmParagraph_ColorId) { + setFgColor_AttributedRun_(&run, tmFirstParagraph_ColorId); + } + attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), + bold_FontStyle)); + } + else if (equal_Rangecc(sequence, "3")) { + run.attrib.italic = iTrue; + attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), + italic_FontStyle)); + } + else if (equal_Rangecc(sequence, "4")) { + run.attrib.monospace = iTrue; + setFgColor_AttributedRun_(&run, tmPreformatted_ColorId); + attribFont = font_Text_(fontWithFamily_Text(fontId_Text_(d->baseFont), + monospace_FontId)); + } + else if (equal_Rangecc(sequence, "0")) { + run.attrib.bold = iFalse; + run.attrib.italic = iFalse; + run.attrib.monospace = iFalse; + attribFont = run.font = d->baseFont; + setFgColor_AttributedRun_(&run, d->baseColorId); + } + else { + run.fgColor_ = ansiForeground_Color(sequence, tmParagraph_ColorId); } - attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), bold_FontStyle)); - } - else if (equal_Rangecc(sequence, "3")) { - run.attrib.italic = iTrue; - attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), italic_FontStyle)); - } - else if (equal_Rangecc(sequence, "4")) { - run.attrib.monospace = iTrue; - setFgColor_AttributedRun_(&run, tmPreformatted_ColorId); - attribFont = font_Text_(fontWithFamily_Text(fontId_Text_(d->baseFont), monospace_FontId)); - } - else if (equal_Rangecc(sequence, "0")) { - run.attrib.bold = iFalse; - run.attrib.italic = iFalse; - run.attrib.monospace = iFalse; - attribFont = d->baseFont; - setFgColor_AttributedRun_(&run, d->baseColorId); - } - else { - run.fgColor_ = ansiForeground_Color(sequence, tmParagraph_ColorId); } pos += length_Rangecc(capturedRange_RegExpMatch(&m, 0)); - iAssert(logToSource[pos] == end_RegExpMatch(&m) - d->source.start); +// iAssert(logToSource[pos] == end_RegExpMatch(&m) - d->source.start); /* The run continues after the escape sequence. */ run.logical.start = pos--; /* loop increments `pos` */ continue; @@ -1148,7 +1154,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh iConstForEach(Array, i, &d->runs) { const iAttributedRun *run = i.value; printf(" %zu %s log:%d...%d vis:%d...%d {%s}\n", index_ArrayConstIterator(&i), - run->flags.isRTL ? "<-" : "->", + run->attrib.isRTL ? "<-" : "->", run->logical.start, run->logical.end - 1, logToVis[run->logical.start], logToVis[run->logical.end - 1], cstr_Rangecc(sourceRange_AttributedText_(d, run->logical))); @@ -1594,6 +1600,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { float breakAdvance = -1.0f; iAssert(wrapPosRange.end == textLen); /* Determine ends of wrapRuns and wrapVisRange. */ + int safeBreakPos = -1; for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) { const iAttributedRun *run = at_Array(&attrText.runs, runIndex); /* Update the attributes. */ @@ -1613,7 +1620,6 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { iGlyphBuffer *buf = at_Array(&buffers, runIndex); iAssert(run->font == buf->font); shape_GlyphBuffer_(buf); - int safeBreakPos = -1; iChar prevCh = 0; lastAttrib = run->attrib; for (unsigned int ir = 0; ir < buf->glyphCount; ir++) { @@ -1642,14 +1648,16 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { iAssert(xAdvance >= 0); if (args->wrap->mode == word_WrapTextMode) { /* When word wrapping, only consider certain places breakable. */ - if ((ch >= 128 || !ispunct(ch)) && (prevCh == '-' || prevCh == '/')) { + if (!isPunct_Char(ch) && (prevCh == '-' || prevCh == '/')) { safeBreakPos = logPos; breakAdvance = wrapAdvance; +// printf("breakAdv_A:%f\n", breakAdvance); // isSoftHyphenBreak = iFalse; } else if (isSpace_Char(ch)) { safeBreakPos = logPos; breakAdvance = wrapAdvance; +// printf("breakAdv_B:%f sbb:%d\n", breakAdvance, safeBreakPos); // isSoftHyphenBreak = iFalse; } prevCh = ch; @@ -1673,6 +1681,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { if (wrap->maxWidth > 0 && wrapAdvance + xOffset + glyph->d[0].x + glyph->rect[0].size.x > args->wrap->maxWidth) { +// printf("safeBreakPos:%d\n", safeBreakPos); if (safeBreakPos >= 0) { wrapPosRange.end = safeBreakPos; } @@ -1698,9 +1707,11 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { wrapRuns.end = runIndex + 1; /* still includes this run */ wrapResumeRunIndex = runIndex; /* ...but continue from the same one */ wrapAdvance = breakAdvance; +// printf("-> wrapAdv:%f (breakAdv)\n", wrapAdvance); break; } wrapAdvance += xAdvance; + printf("lp:%d wrap:%f\n", logPos, wrapAdvance); /* Additional kerning tweak. It would be better to use HarfBuzz font callbacks, but they don't seem to get called? */ if (i + 1 < buf->glyphCount) { @@ -1780,11 +1791,12 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { if (isRightAligned) { if (layoutBound > 0) { origin = layoutBound - wrapAdvance; + printf("orig:%d (lbo:%d wrapAdv:%f)\n", origin, layoutBound, wrapAdvance); } -// else if (args->xposLayoutBound > 0) { -// iAssert(mode & draw_RunMode); -//// origin = args->xposLayoutBound - orig.x - wrapAdvance * 2; -// } + printf("yes; base RTL\n"); + } + else { + printf("not base RTL\n"); } /* Make a callback for each wrapped line. */ if (wrap && wrap->wrapFunc && @@ -1848,8 +1860,14 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y, glyph->rect[hoff].size.x, glyph->rect[hoff].size.y }; - if (run->font->height < attrText.baseFont->height) { - dst.y += attrText.baseFont->baseline - run->font->baseline; + /* Align baselines of different fonts. */ + if (run->font != attrText.baseFont && + ~run->font->fontSpec->flags & auxiliary_FontSpecFlag) { + const int bl1 = attrText.baseFont->baseline + attrText.baseFont->vertOffset; + const int bl2 = run->font->baseline + run->font->vertOffset; + dst.y += bl1 - bl2; +// printf("baseline difference: run %d, base %d\n", +// run->font->baseline, attrText.baseFont->baseline); } if (mode & visualFlag_RunMode) { if (isEmpty_Rect(bounds)) { diff --git a/src/ui/text.h b/src/ui/text.h index 22e67a48..36a16e2c 100644 --- a/src/ui/text.h +++ b/src/ui/text.h @@ -151,6 +151,7 @@ enum iAlignment { void setOpacity_Text (float opacity); void setBaseAttributes_Text (int fontId, int colorId); /* current "normal" text attributes */ +void setAnsiEscapesEnabled_Text(iBool enableAnsiEscapes); void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */ diff --git a/src/ui/util.c b/src/ui/util.c index 653102cb..1ebdd3b3 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -2531,23 +2531,6 @@ iWidget *makePreferences_Widget(void) { addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.mono}"))); addFontButtons_(values, "mono"); addDialogPadding_(headings, values); - addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.boldlink}"))); - iWidget *boldLink = new_Widget(); { - /* TODO: Add a utility function for this type of toggles? (also for above) */ - iWidget *tog; - setTextCStr_LabelWidget( - addChild_Widget(boldLink, tog = iClob(makeToggle_Widget("prefs.boldlink.dark"))), - "${prefs.boldlink.dark}"); - setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse); - updateSize_LabelWidget((iLabelWidget *) tog); - setTextCStr_LabelWidget( - addChild_Widget(boldLink, tog = iClob(makeToggle_Widget("prefs.boldlink.light"))), - "${prefs.boldlink.light}"); - setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse); - updateSize_LabelWidget((iLabelWidget *) tog); - } - addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); - addDialogPadding_(headings, values); addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.mono}"))); iWidget *mono = new_Widget(); { iWidget *tog; @@ -2566,10 +2549,27 @@ iWidget *makePreferences_Widget(void) { addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.monodoc}"))); addFontButtons_(values, "monodoc"); addDialogPadding_(headings, values); - addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.ui}"))); - addFontButtons_(values, "ui"); - addDialogPadding_(headings, values); + addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.boldlink}"))); + iWidget *boldLink = new_Widget(); { + /* TODO: Add a utility function for this type of toggles? (also for above) */ + iWidget *tog; + setTextCStr_LabelWidget( + addChild_Widget(boldLink, tog = iClob(makeToggle_Widget("prefs.boldlink.dark"))), + "${prefs.boldlink.dark}"); + setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse); + updateSize_LabelWidget((iLabelWidget *) tog); + setTextCStr_LabelWidget( + addChild_Widget(boldLink, tog = iClob(makeToggle_Widget("prefs.boldlink.light"))), + "${prefs.boldlink.light}"); + setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse); + updateSize_LabelWidget((iLabelWidget *) tog); + } + addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); + addDialogToggle_(headings, values, "${prefs.gemtext.ansi}", "prefs.gemtext.ansi"); addDialogToggle_(headings, values, "${prefs.font.smooth}", "prefs.font.smooth"); + addDialogPadding_(headings, values); + addFontButtons_(values, "ui"); + addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.ui}"))); // addDialogPadding_(headings, values); // /* Custom font. */ { // iInputWidget *customFont = new_InputWidget(0); -- cgit v1.2.3 From f5938745dcbe567d6e52f79b63151584d2c917d8 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 15 Oct 2021 08:11:55 +0300 Subject: Preferences: Option to bold visited links, too Cleaned up serialization of the bool preferences a little. --- po/en.po | 3 +++ res/lang/de.bin | Bin 26367 -> 26398 bytes res/lang/en.bin | Bin 23805 -> 23836 bytes res/lang/eo.bin | Bin 22659 -> 22690 bytes res/lang/es.bin | Bin 26751 -> 26782 bytes res/lang/es_MX.bin | Bin 24719 -> 24750 bytes res/lang/fi.bin | Bin 26663 -> 26694 bytes res/lang/fr.bin | Bin 27573 -> 27604 bytes res/lang/gl.bin | Bin 26027 -> 26058 bytes res/lang/ia.bin | Bin 25692 -> 25723 bytes res/lang/ie.bin | Bin 25897 -> 25928 bytes res/lang/isv.bin | Bin 22595 -> 22626 bytes res/lang/pl.bin | Bin 26968 -> 26999 bytes res/lang/ru.bin | Bin 39307 -> 39338 bytes res/lang/sk.bin | Bin 22928 -> 22959 bytes res/lang/sr.bin | Bin 39212 -> 39243 bytes res/lang/tok.bin | Bin 24142 -> 24173 bytes res/lang/zh_Hans.bin | Bin 22644 -> 22675 bytes res/lang/zh_Hant.bin | Bin 22785 -> 22816 bytes src/app.c | 67 +++++++++++++++++++++++++++++++++++------------- src/gmdocument.c | 3 ++- src/prefs.c | 1 + src/prefs.h | 1 + src/ui/documentwidget.c | 1 + src/ui/util.c | 37 ++++++++++++++------------ 25 files changed, 78 insertions(+), 35 deletions(-) (limited to 'src/gmdocument.c') diff --git a/po/en.po b/po/en.po index f0a5b1c7..526a723d 100644 --- a/po/en.po +++ b/po/en.po @@ -1485,6 +1485,9 @@ msgstr "Gopher" msgid "prefs.boldlink" msgstr "Bold links:" +msgid "prefs.boldlink.visited" +msgstr "Visited" + # Interpretation: (Bold links) on dark (background). msgid "prefs.boldlink.dark" msgstr "On Dark" diff --git a/res/lang/de.bin b/res/lang/de.bin index 17f3a3d6..17a2d191 100644 Binary files a/res/lang/de.bin and b/res/lang/de.bin differ diff --git a/res/lang/en.bin b/res/lang/en.bin index 42d62334..51b9a10d 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/eo.bin b/res/lang/eo.bin index 5d865707..7b83b77f 100644 Binary files a/res/lang/eo.bin and b/res/lang/eo.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 5f620910..91121500 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin index de5e253f..df129a8a 100644 Binary files a/res/lang/es_MX.bin and b/res/lang/es_MX.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index 5ca852cf..fad0413b 100644 Binary files a/res/lang/fi.bin and b/res/lang/fi.bin differ diff --git a/res/lang/fr.bin b/res/lang/fr.bin index b6a577b3..20fd19ef 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/gl.bin b/res/lang/gl.bin index 55ea5675..1cac09b9 100644 Binary files a/res/lang/gl.bin and b/res/lang/gl.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 63a9cb33..64ebfe03 100644 Binary files a/res/lang/ia.bin and b/res/lang/ia.bin differ diff --git a/res/lang/ie.bin b/res/lang/ie.bin index 19b631aa..87064cef 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/isv.bin b/res/lang/isv.bin index 2af131f1..726441ce 100644 Binary files a/res/lang/isv.bin and b/res/lang/isv.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 93e070de..a857c547 100644 Binary files a/res/lang/pl.bin and b/res/lang/pl.bin differ diff --git a/res/lang/ru.bin b/res/lang/ru.bin index 2ec7eaaf..c1884651 100644 Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ diff --git a/res/lang/sk.bin b/res/lang/sk.bin index 06cf8cf8..981ab7e1 100644 Binary files a/res/lang/sk.bin and b/res/lang/sk.bin differ diff --git a/res/lang/sr.bin b/res/lang/sr.bin index 41f7fb4b..384665cc 100644 Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ diff --git a/res/lang/tok.bin b/res/lang/tok.bin index c68303d1..51080283 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 0b243530..0cf56935 100644 Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index d5c13885..28e7cedf 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/app.c b/src/app.c index 8442a2c8..6282ade2 100644 --- a/src/app.c +++ b/src/app.c @@ -231,22 +231,46 @@ static iString *serializePrefs_App_(const iApp *d) { appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth); appendFormat_String(str, "linespacing.set arg:%f\n", d->prefs.lineSpacing); appendFormat_String(str, "returnkey.set arg:%d\n", d->prefs.returnKey); - /* TODO: Set up an array of booleans in Prefs and do these in a loop. */ - appendFormat_String(str, "prefs.animate.changed arg:%d\n", d->prefs.uiAnimations); - appendFormat_String(str, "prefs.font.smooth.changed arg:%d\n", d->prefs.fontSmoothing); - appendFormat_String(str, "prefs.gemtext.ansi.changed arg:%d\n", d->prefs.gemtextAnsiEscapes); - appendFormat_String(str, "prefs.mono.gemini.changed arg:%d\n", d->prefs.monospaceGemini); - appendFormat_String(str, "prefs.mono.gopher.changed arg:%d\n", d->prefs.monospaceGopher); - appendFormat_String(str, "prefs.boldlink.dark.changed arg:%d\n", d->prefs.boldLinkDark); - appendFormat_String(str, "prefs.boldlink.light.changed arg:%d\n", d->prefs.boldLinkLight); - appendFormat_String(str, "prefs.biglede.changed arg:%d\n", d->prefs.bigFirstParagraph); - appendFormat_String(str, "prefs.plaintext.wrap.changed arg:%d\n", d->prefs.plainTextWrap); - appendFormat_String(str, "prefs.sideicon.changed arg:%d\n", d->prefs.sideIcon); - appendFormat_String(str, "prefs.centershort.changed arg:%d\n", d->prefs.centerShortDocs); - appendFormat_String(str, "prefs.collapsepreonload.changed arg:%d\n", d->prefs.collapsePreOnLoad); - appendFormat_String(str, "prefs.hoverlink.changed arg:%d\n", d->prefs.hoverLink); - appendFormat_String(str, "prefs.bookmarks.addbottom arg:%d\n", d->prefs.addBookmarksToBottom); - appendFormat_String(str, "prefs.archive.openindex.changed arg:%d\n", d->prefs.openArchiveIndexPages); + /* TODO: This array belongs in Prefs. It can then be used for command handling as well. */ + const struct { + const char * id; + const iBool *value; + } boolPrefs[] = { + { "prefs.animate", &d->prefs.uiAnimations }, + { "prefs.font.smooth", &d->prefs.fontSmoothing }, + { "prefs.gemtext.ansi", &d->prefs.gemtextAnsiEscapes }, + { "prefs.mono.gemini", &d->prefs.monospaceGemini }, + { "prefs.mono.gopher", &d->prefs.monospaceGopher }, + { "prefs.boldlink.visited", &d->prefs.boldLinkVisited }, + { "prefs.boldlink.dark", &d->prefs.boldLinkDark }, + { "prefs.boldlink.light", &d->prefs.boldLinkLight }, + { "prefs.biglede", &d->prefs.bigFirstParagraph }, + { "prefs.plaintext.wrap", &d->prefs.plainTextWrap }, + { "prefs.sideicon", &d->prefs.sideIcon }, + { "prefs.centershort", &d->prefs.centerShortDocs }, + { "prefs.collapsepreonload", &d->prefs.collapsePreOnLoad }, + { "prefs.hoverlink", &d->prefs.hoverLink }, + { "prefs.bookmarks.addbottom", &d->prefs.addBookmarksToBottom }, + { "prefs.archive.openindex", &d->prefs.openArchiveIndexPages }, + }; + iForIndices(i, boolPrefs) { + appendFormat_String(str, "%s.changed arg:%d\n", boolPrefs[i].id, *boolPrefs[i].value); + } +// appendFormat_String(str, "prefs.animate.changed arg:%d\n", d->prefs.uiAnimations); +// appendFormat_String(str, "prefs.font.smooth.changed arg:%d\n", d->prefs.fontSmoothing); +// appendFormat_String(str, "prefs.gemtext.ansi.changed arg:%d\n", d->prefs.gemtextAnsiEscapes); +// appendFormat_String(str, "prefs.mono.gemini.changed arg:%d\n", d->prefs.monospaceGemini); +// appendFormat_String(str, "prefs.mono.gopher.changed arg:%d\n", d->prefs.monospaceGopher); +// appendFormat_String(str, "prefs.boldlink.dark.changed arg:%d\n", d->prefs.boldLinkDark); +// appendFormat_String(str, "prefs.boldlink.light.changed arg:%d\n", d->prefs.boldLinkLight); +// appendFormat_String(str, "prefs.biglede.changed arg:%d\n", d->prefs.bigFirstParagraph); +// appendFormat_String(str, "prefs.plaintext.wrap.changed arg:%d\n", d->prefs.plainTextWrap); +// appendFormat_String(str, "prefs.sideicon.changed arg:%d\n", d->prefs.sideIcon); +// appendFormat_String(str, "prefs.centershort.changed arg:%d\n", d->prefs.centerShortDocs); +// appendFormat_String(str, "prefs.collapsepreonload.changed arg:%d\n", d->prefs.collapsePreOnLoad); +// appendFormat_String(str, "prefs.hoverlink.changed arg:%d\n", d->prefs.hoverLink); +// appendFormat_String(str, "prefs.bookmarks.addbottom arg:%d\n", d->prefs.addBookmarksToBottom); +// appendFormat_String(str, "prefs.archive.openindex.changed arg:%d\n", d->prefs.openArchiveIndexPages); appendFormat_String(str, "quoteicon.set arg:%d\n", d->prefs.quoteIcon ? 1 : 0); appendFormat_String(str, "theme.set arg:%d auto:1\n", d->prefs.theme); appendFormat_String(str, "accent.set arg:%d\n", d->prefs.accent); @@ -2376,9 +2400,13 @@ iBool handleCommand_App(const char *cmd) { return iTrue; } else if (equal_Command(cmd, "prefs.boldlink.dark.changed") || - equal_Command(cmd, "prefs.boldlink.light.changed")) { + equal_Command(cmd, "prefs.boldlink.light.changed") || + equal_Command(cmd, "prefs.boldlink.visited.changed")) { const iBool isSet = (arg_Command(cmd) != 0); - if (startsWith_CStr(cmd, "prefs.boldlink.dark")) { + if (startsWith_CStr(cmd, "prefs.boldlink.visited")) { + d->prefs.boldLinkVisited = isSet; + } + else if (startsWith_CStr(cmd, "prefs.boldlink.dark")) { d->prefs.boldLinkDark = isSet; } else { @@ -2763,6 +2791,9 @@ iBool handleCommand_App(const char *cmd) { setFlags_Widget(findChild_Widget(dlg, "prefs.mono.gopher"), selected_WidgetFlag, d->prefs.monospaceGopher); + setFlags_Widget(findChild_Widget(dlg, "prefs.boldlink.visited"), + selected_WidgetFlag, + d->prefs.boldLinkVisited); setFlags_Widget(findChild_Widget(dlg, "prefs.boldlink.dark"), selected_WidgetFlag, d->prefs.boldLinkDark); diff --git a/src/gmdocument.c b/src/gmdocument.c index f43d2758..e37d585e 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -890,7 +890,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { rts.lineHeightReduction = 0.06f; } /* Visited links are never bold. */ - if (run.linkId && linkFlags_GmDocument(d, run.linkId) & visited_GmLinkFlag) { + if (run.linkId && !prefs->boldLinkVisited && + linkFlags_GmDocument(d, run.linkId) & visited_GmLinkFlag) { rts.run.font = paragraph_FontId; } } diff --git a/src/prefs.c b/src/prefs.c index ea0717e5..673470d6 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -64,6 +64,7 @@ void init_Prefs(iPrefs *d) { d->gemtextAnsiEscapes = iFalse; d->monospaceGemini = iFalse; d->monospaceGopher = iFalse; + d->boldLinkVisited = iTrue; d->boldLinkDark = iTrue; d->boldLinkLight = iTrue; d->lineWidth = 38; diff --git a/src/prefs.h b/src/prefs.h index a44e98b7..70846be4 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -91,6 +91,7 @@ struct Impl_Prefs { iBool gemtextAnsiEscapes; iBool monospaceGemini; iBool monospaceGopher; + iBool boldLinkVisited; iBool boldLinkDark; iBool boldLinkLight; int lineWidth; diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index b2594997..8fefb95c 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -2173,6 +2173,7 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char * body_GmRequest(req->req), allowHide_MediaFlag); redoLayout_GmDocument(d->doc); + iZap(d->visibleRuns); /* pointers invalidated */ updateVisible_DocumentWidget_(d); invalidate_DocumentWidget_(d); refresh_Widget(as_Widget(d)); diff --git a/src/ui/util.c b/src/ui/util.c index 1ebdd3b3..3b146155 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -2549,22 +2549,6 @@ iWidget *makePreferences_Widget(void) { addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.monodoc}"))); addFontButtons_(values, "monodoc"); addDialogPadding_(headings, values); - addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.boldlink}"))); - iWidget *boldLink = new_Widget(); { - /* TODO: Add a utility function for this type of toggles? (also for above) */ - iWidget *tog; - setTextCStr_LabelWidget( - addChild_Widget(boldLink, tog = iClob(makeToggle_Widget("prefs.boldlink.dark"))), - "${prefs.boldlink.dark}"); - setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse); - updateSize_LabelWidget((iLabelWidget *) tog); - setTextCStr_LabelWidget( - addChild_Widget(boldLink, tog = iClob(makeToggle_Widget("prefs.boldlink.light"))), - "${prefs.boldlink.light}"); - setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse); - updateSize_LabelWidget((iLabelWidget *) tog); - } - addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); addDialogToggle_(headings, values, "${prefs.gemtext.ansi}", "prefs.gemtext.ansi"); addDialogToggle_(headings, values, "${prefs.font.smooth}", "prefs.font.smooth"); addDialogPadding_(headings, values); @@ -2604,6 +2588,27 @@ iWidget *makePreferences_Widget(void) { } addChildFlags_Widget(values, iClob(quote), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); addDialogToggle_(headings, values, "${prefs.biglede}", "prefs.biglede"); + addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.boldlink}"))); + iWidget *boldLink = new_Widget(); { + /* TODO: Add a utility function for this type of toggles? (also for above) */ + iWidget *tog; + setTextCStr_LabelWidget( + addChild_Widget(boldLink, tog = iClob(makeToggle_Widget("prefs.boldlink.visited"))), + "${prefs.boldlink.visited}"); + setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse); + updateSize_LabelWidget((iLabelWidget *) tog); + setTextCStr_LabelWidget( + addChild_Widget(boldLink, tog = iClob(makeToggle_Widget("prefs.boldlink.dark"))), + "${prefs.boldlink.dark}"); + setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse); + updateSize_LabelWidget((iLabelWidget *) tog); + setTextCStr_LabelWidget( + addChild_Widget(boldLink, tog = iClob(makeToggle_Widget("prefs.boldlink.light"))), + "${prefs.boldlink.light}"); + setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse); + updateSize_LabelWidget((iLabelWidget *) tog); + } + addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); addDialogToggle_(headings, values, "${prefs.plaintext.wrap}", "prefs.plaintext.wrap"); addDialogToggle_(headings, values, "${prefs.collapsepreonload}", "prefs.collapsepreonload"); addDialogPadding_(headings, values); -- cgit v1.2.3 From 2f3987f5e54d95658f95c6991b0644bc15eedabf Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 15 Oct 2021 12:08:27 +0300 Subject: Text: Fixed a line wrapping issue When the last safe break position was not in the current attributed run, the calculated wrap advance came out incorrect. This was possible when the first glyph in an attributed run didn't fit. --- res/arabic.fontpack/fontpack.ini | 1 + src/fontpack.c | 4 +- src/fontpack.h | 8 ++-- src/gmdocument.c | 9 ++++- src/gmdocument.h | 4 +- src/ui/documentwidget.c | 6 ++- src/ui/text.c | 79 +++++++++++++++++++++++----------------- 7 files changed, 66 insertions(+), 45 deletions(-) (limited to 'src/gmdocument.c') diff --git a/res/arabic.fontpack/fontpack.ini b/res/arabic.fontpack/fontpack.ini index 305878ce..48b40d82 100644 --- a/res/arabic.fontpack/fontpack.ini +++ b/res/arabic.fontpack/fontpack.ini @@ -3,4 +3,5 @@ version = 1 [arabic] name = "Noto Sans Arabic UI" auxiliary = true +allowspace = true # usually auxiliary fonts are not used for spaces regular = "NotoSansArabicUI-Regular.ttf" diff --git a/src/fontpack.c b/src/fontpack.c index 9baedc0e..b135ea43 100644 --- a/src/fontpack.c +++ b/src/fontpack.c @@ -350,8 +350,8 @@ void handleIniKeyValue_FontPack_(void *context, const iString *table, const iStr else if (!cmp_String(key, "auxiliary") && value->type == boolean_TomlType) { iChangeFlags(d->loadSpec->flags, auxiliary_FontSpecFlag, value->value.boolean); } - else if (!cmp_String(key, "arabic") && value->type == boolean_TomlType) { - iChangeFlags(d->loadSpec->flags, arabic_FontSpecFlag, value->value.boolean); + else if (!cmp_String(key, "allowspace") && value->type == boolean_TomlType) { + iChangeFlags(d->loadSpec->flags, allowSpacePunct_FontSpecFlag, value->value.boolean); } else if (!cmp_String(key, "tweaks")) { iChangeFlags(d->loadSpec->flags, fixNunitoKerning_FontSpecFlag, diff --git a/src/fontpack.h b/src/fontpack.h index 429afb5d..f69e2adc 100644 --- a/src/fontpack.h +++ b/src/fontpack.h @@ -110,10 +110,10 @@ iDeclareType(FontSpec) iDeclareTypeConstruction(FontSpec) enum iFontSpecFlags { - override_FontSpecFlag = iBit(1), - monospace_FontSpecFlag = iBit(2), /* can be used in preformatted content */ - auxiliary_FontSpecFlag = iBit(3), /* only used for looking up glyphs missing from other fonts */ - arabic_FontSpecFlag = iBit(4), + override_FontSpecFlag = iBit(1), + monospace_FontSpecFlag = iBit(2), /* can be used in preformatted content */ + auxiliary_FontSpecFlag = iBit(3), /* only used for looking up glyphs missing from other fonts */ + allowSpacePunct_FontSpecFlag = iBit(4), /* space/punctuation glyphs from this auxiliary font can be used */ fixNunitoKerning_FontSpecFlag = iBit(31), /* manual hardcoded kerning tweaks for Nunito */ }; diff --git a/src/gmdocument.c b/src/gmdocument.c index e37d585e..0adf5243 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -493,10 +493,15 @@ static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, int origin, int advance) { iAssert(wrapRange.start <= wrapRange.end); trimEnd_Rangecc(&wrapRange); -// printf("typeset: {%s}\n", cstr_Rangecc(wrapRange)); iRunTypesetter *d = wrap->context; d->run.text = wrapRange; applyAttributes_RunTypesetter_(d, attrib); +#if 0 + const int msr = measureRange_Text(d->run.font, wrapRange).advance.x; + if (iAbs(msr - advance) > 3) { + printf("\n[RunTypesetter] wrong wrapRange advance! actual:%d wrapped:%d\n\n", msr, advance); + } +#endif if (~d->run.flags & startOfLine_GmRunFlag && d->lineHeightReduction > 0.0f) { d->pos.y -= d->lineHeightReduction * lineHeight_Text(d->baseFont); } @@ -508,7 +513,7 @@ static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, d->run.visBounds = d->run.bounds; d->run.visBounds.size.x = dims.x; d->run.isRTL = attrib.isBaseRTL; - printf("origin:%d isRTL:%d\n{%s}\n", origin, attrib.isBaseRTL, cstr_Rangecc(wrapRange)); +// printf("origin:%d isRTL:%d\n{%s}\n", origin, attrib.isBaseRTL, cstr_Rangecc(wrapRange)); pushBack_Array(&d->layout, &d->run); d->run.flags &= ~startOfLine_GmRunFlag; d->pos.y += lineHeight_Text(d->baseFont) * prefs_App()->lineSpacing; diff --git a/src/gmdocument.h b/src/gmdocument.h index 4a248550..94a494e8 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h @@ -139,10 +139,10 @@ struct Impl_GmRun { uint32_t isRTL : 1; uint32_t color : 7; /* see max_ColorId */ - uint32_t font : 10; + uint32_t font : 13; uint32_t mediaType : 3; /* note: max_MediaType means preformatted block */ + uint32_t mediaId : 12; /* zero if not an image */ uint32_t lineType : 3; - uint32_t mediaId : 15; /* zero if not an image */ uint32_t isLede : 1; }; }; diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 8fefb95c..924dc0c4 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -4522,8 +4522,10 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { } } } -// drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); -// drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); + if (0) { + drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); + drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); + } } static int drawSideRect_(iPaint *p, iRect rect) { diff --git a/src/ui/text.c b/src/ui/text.c index 7afaf583..d22faea1 100644 --- a/src/ui/text.c +++ b/src/ui/text.c @@ -214,7 +214,6 @@ static void init_Font(iFont *d, const iFontSpec *fontSpec, const iFontFile *font d->baseline = fontFile->ascent * d->yScale; d->vertOffset = d->height * (1.0f - glyphScale) / 2 * fontSpec->vertOffsetScale[scaleType]; d->table = NULL; - // printf("{%s} height:%d baseline:%d\n", cstr_String(&d->fontSpec->id), d->height, d->baseline); } static void deinit_Font(iFont *d) { @@ -934,6 +933,12 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i iAssert(endAt >= 0 && endAt <= size_Array(&d->logical)); finishedRun.logical.end = endAt; if (!isEmpty_Range(&finishedRun.logical)) { +#if 0 + /* Colorize individual runs to see boundaries. */ + static int dbg; + static const int dbgClr[3] = { red_ColorId, green_ColorId, blue_ColorId }; + finishedRun.attrib.colorId = dbgClr[dbg++ % 3]; +#endif pushBack_Array(&d->runs, &finishedRun); run->flags.isLineBreak = iFalse; run->flags.isArabic = iFalse; @@ -1120,15 +1125,18 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh continue; } if (ch == 0x20) { - if (run.font->fontSpec->flags & auxiliary_FontSpecFlag) { + if (run.font->fontSpec->flags & auxiliary_FontSpecFlag && + ~run.font->fontSpec->flags & allowSpacePunct_FontSpecFlag) { finishRun_AttributedText_(d, &run, pos); - run.font = d->font; /* never use space from the symbols font, it's too wide */ + run.font = d->font; /* auxilitary font space not allowed, could be wrong width */ } continue; } iFont *currentFont = attribFont; - if (run.font->fontSpec->flags & arabic_FontSpecFlag && isPunct_Char(ch)) { - currentFont = run.font; /* remain as Arabic for whitespace */ + if (run.font->fontSpec->flags & auxiliary_FontSpecFlag && + run.font->fontSpec->flags & allowSpacePunct_FontSpecFlag && + isPunct_Char(ch)) { + currentFont = run.font; /* keep the current font */ } const iGlyph *glyph = glyph_Font_(currentFont, ch); if (index_Glyph_(glyph) && glyph->font != run.font) { @@ -1153,8 +1161,10 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh printf("[AttributedText] %zu runs:\n", size_Array(&d->runs)); iConstForEach(Array, i, &d->runs) { const iAttributedRun *run = i.value; - printf(" %zu %s log:%d...%d vis:%d...%d {%s}\n", index_ArrayConstIterator(&i), + printf(" %zu %s fnt:%d log:%d...%d vis:%d...%d {%s}\n", + index_ArrayConstIterator(&i), run->attrib.isRTL ? "<-" : "->", + fontId_Text_(run->font), run->logical.start, run->logical.end - 1, logToVis[run->logical.start], logToVis[run->logical.end - 1], cstr_Rangecc(sourceRange_AttributedText_(d, run->logical))); @@ -1598,6 +1608,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { wrap->hitPoint.y < orig.y + yCursor + d->height); iBool wasCharHit = iFalse; /* on this line */ float breakAdvance = -1.0f; + size_t breakRunIndex = iInvalidPos; iAssert(wrapPosRange.end == textLen); /* Determine ends of wrapRuns and wrapVisRange. */ int safeBreakPos = -1; @@ -1621,7 +1632,8 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { iAssert(run->font == buf->font); shape_GlyphBuffer_(buf); iChar prevCh = 0; - lastAttrib = run->attrib; + lastAttrib = run->attrib; +// printf("checking run %zu...\n", runIndex); for (unsigned int ir = 0; ir < buf->glyphCount; ir++) { const int i = (run->attrib.isRTL ? buf->glyphCount - ir - 1 : ir); const hb_glyph_info_t *info = &buf->glyphInfo[i]; @@ -1648,24 +1660,27 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { iAssert(xAdvance >= 0); if (args->wrap->mode == word_WrapTextMode) { /* When word wrapping, only consider certain places breakable. */ - if (!isPunct_Char(ch) && (prevCh == '-' || prevCh == '/')) { + if ((prevCh == '-' || prevCh == '/') && !isPunct_Char(ch)) { safeBreakPos = logPos; breakAdvance = wrapAdvance; -// printf("breakAdv_A:%f\n", breakAdvance); + breakRunIndex = runIndex; +// printf("sbp:%d breakAdv_A:%f\n", safeBreakPos, breakAdvance); // isSoftHyphenBreak = iFalse; } else if (isSpace_Char(ch)) { safeBreakPos = logPos; breakAdvance = wrapAdvance; -// printf("breakAdv_B:%f sbb:%d\n", breakAdvance, safeBreakPos); + breakRunIndex = runIndex; +// printf("sbp:%d breakAdv_B:%f\n", safeBreakPos, breakAdvance); // isSoftHyphenBreak = iFalse; } prevCh = ch; } else { - safeBreakPos = logPos; - breakAdvance = wrapAdvance; - wrapAttrib = run->attrib; + safeBreakPos = logPos; + breakAdvance = wrapAdvance; + breakRunIndex = runIndex; + wrapAttrib = run->attrib; } if (isHitPointOnThisLine) { if (wrap->hitPoint.x >= orig.x + wrapAdvance && @@ -1681,7 +1696,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { if (wrap->maxWidth > 0 && wrapAdvance + xOffset + glyph->d[0].x + glyph->rect[0].size.x > args->wrap->maxWidth) { -// printf("safeBreakPos:%d\n", safeBreakPos); +// printf("out of room at lp:%d! safeBreakPos:%d (idx:%zu) breakAdv:%f\n", +// logPos, safeBreakPos, +// breakRunIndex, breakAdvance); if (safeBreakPos >= 0) { wrapPosRange.end = safeBreakPos; } @@ -1697,6 +1714,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { } wrapPosRange.end = logPos; breakAdvance = wrapAdvance; + breakRunIndex = runIndex; } wrapResumePos = wrapPosRange.end; if (args->wrap->mode != anyCharacter_WrapTextMode) { @@ -1704,14 +1722,14 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { wrapResumePos++; /* skip space */ } } - wrapRuns.end = runIndex + 1; /* still includes this run */ - wrapResumeRunIndex = runIndex; /* ...but continue from the same one */ + wrapRuns.end = breakRunIndex + 1; /* still includes this run */ + wrapResumeRunIndex = breakRunIndex; /* ...but continue from the same one */ +// printf("-> wrapAdv:%f (breakAdv:%f)\n", wrapAdvance, breakAdvance); wrapAdvance = breakAdvance; -// printf("-> wrapAdv:%f (breakAdv)\n", wrapAdvance); +// printf("wrapResumePos:%d\n", wrapResumePos); break; } wrapAdvance += xAdvance; - printf("lp:%d wrap:%f\n", logPos, wrapAdvance); /* Additional kerning tweak. It would be better to use HarfBuzz font callbacks, but they don't seem to get called? */ if (i + 1 < buf->glyphCount) { @@ -1720,6 +1738,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { buf->glyphInfo[i + 1].codepoint); } } +// printf("...finished checking run %zu\n", runIndex); } if (isHitPointOnThisLine && wrap->hitPoint.x >= orig.x + wrapAdvance) { /* On the right side. */ @@ -1774,16 +1793,17 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { } } } - } #if 0 - printf("Run order: "); - iConstForEach(Array, ro, &runOrder) { - const size_t *idx = ro.value; - printf("%zu {%s}\n", *idx, - cstr_Rangecc(sourceRange_AttributedText_(&attrText, ((const iAttributedRun *) at_Array(&attrText.runs, *idx))->logical))); - } - printf("\n"); + printf("Run order: "); + iConstForEach(Array, ro, &runOrder) { + const size_t *idx = ro.value; + printf("%zu {%s}\n", *idx, + cstr_Rangecc(sourceRange_AttributedText_(&attrText, ((const iAttributedRun *) at_Array(&attrText.runs, *idx))->logical))); + } + printf("\n"); #endif + + } iAssert(size_Array(&runOrder) == size_Range(&wrapRuns)); /* Alignment. */ int origin = 0; @@ -1791,12 +1811,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { if (isRightAligned) { if (layoutBound > 0) { origin = layoutBound - wrapAdvance; - printf("orig:%d (lbo:%d wrapAdv:%f)\n", origin, layoutBound, wrapAdvance); } - printf("yes; base RTL\n"); - } - else { - printf("not base RTL\n"); } /* Make a callback for each wrapped line. */ if (wrap && wrap->wrapFunc && @@ -1866,8 +1881,6 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { const int bl1 = attrText.baseFont->baseline + attrText.baseFont->vertOffset; const int bl2 = run->font->baseline + run->font->vertOffset; dst.y += bl1 - bl2; -// printf("baseline difference: run %d, base %d\n", -// run->font->baseline, attrText.baseFont->baseline); } if (mode & visualFlag_RunMode) { if (isEmpty_Rect(bounds)) { -- cgit v1.2.3 From b3ae7efcb9adb1de3d02f0753e2a79888bdb71ac Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Sun, 17 Oct 2021 20:33:14 +0300 Subject: FontPack management via "about:fonts" --- po/en.po | 69 ++++++++++++++++- res/lang/de.bin | Bin 26398 -> 27035 bytes res/lang/en.bin | Bin 23836 -> 24559 bytes res/lang/eo.bin | Bin 22690 -> 23327 bytes res/lang/es.bin | Bin 26782 -> 27419 bytes res/lang/es_MX.bin | Bin 24750 -> 25387 bytes res/lang/fi.bin | Bin 26694 -> 27331 bytes res/lang/fr.bin | Bin 27604 -> 28241 bytes res/lang/gl.bin | Bin 26058 -> 26695 bytes res/lang/ia.bin | Bin 25723 -> 26360 bytes res/lang/ie.bin | Bin 25928 -> 26565 bytes res/lang/isv.bin | Bin 22626 -> 23263 bytes res/lang/pl.bin | Bin 26999 -> 27636 bytes res/lang/ru.bin | Bin 39338 -> 39975 bytes res/lang/sk.bin | Bin 22959 -> 23596 bytes res/lang/sr.bin | Bin 39243 -> 39880 bytes res/lang/tok.bin | Bin 24173 -> 24810 bytes res/lang/zh_Hans.bin | Bin 22675 -> 23312 bytes res/lang/zh_Hant.bin | Bin 22816 -> 23453 bytes src/app.c | 50 ++++++++++-- src/defs.h | 5 +- src/fontpack.c | 197 ++++++++++++++++++++++++++++++++++++------------ src/fontpack.h | 13 +++- src/gmdocument.c | 37 ++++++--- src/gmutil.c | 3 + src/lang.c | 9 +++ src/lang.h | 1 + src/media.c | 129 ------------------------------- src/media.h | 17 ----- src/ui/documentwidget.c | 75 ++++++++---------- src/ui/mediaui.c | 82 -------------------- src/ui/mediaui.h | 17 ----- 32 files changed, 341 insertions(+), 363 deletions(-) (limited to 'src/gmdocument.c') diff --git a/po/en.po b/po/en.po index 526a723d..51bd66b2 100644 --- a/po/en.po +++ b/po/en.po @@ -448,6 +448,16 @@ msgid_plural "num.bytes.n" msgstr[0] "%zu byte" msgstr[1] "%zu bytes" +msgid "num.files" +msgid_plural "num.files.n" +msgstr[0] "%zu file" +msgstr[1] "%zu files" + +msgid "num.fonts" +msgid_plural "num.fonts.n" +msgstr[0] "%zu font" +msgstr[1] "%zu fonts" + # strftime() formatted, split on two lines #, c-format msgid "page.timestamp" @@ -1869,4 +1879,61 @@ msgid "gempub.meta.lang" msgstr "Language" msgid "gempub.meta.license" -msgstr "License" \ No newline at end of file +msgstr "License" + +msgid "heading.fontpack.meta" +msgstr "Fonts" + +msgid "heading.fontpack.meta.enabled" +msgstr "Enabled fontpacks" + +msgid "heading.fontpack.meta.disabled" +msgstr "Disabled fontpacks" + +# Action label +msgid "fontpack.meta.viewfile" +msgstr "View file" + +#, c-format +msgid "fontpack.meta.version" +msgstr "Version %d" + +msgid "fontpack.meta.installed" +msgstr "Installed" + +msgid "fontpack.meta.notinstalled" +msgstr "Not installed" + +msgid "fontpack.meta.disabled" +msgstr ", disabled" + +#, c-format +msgid "fontpack.enable" +msgstr "Enable \"%s\"" + +#, c-format +msgid "fontpack.disable" +msgstr "Disable \"%s\"" + +#, c-format +msgid "fontpack.install" +msgstr "Install \"%s\"" + +#, c-format +msgid "fontpack.upgrade" +msgstr "Upgrade \"%s\" to version %d" + +#, c-format +msgid "fontpack.delete" +msgstr "Permanently delete \"%s\"" + +msgid "heading.fontpack.delete" +msgstr "DELETE FONTPACK" + +#, c-format +msgid "dlg.fontpack.delete.confirm" +msgstr "Do you really want to permanently delete\nthe fontpack \"%s\"?" + +msgid "dlg.fontpack.delete" +msgstr "Delete Fontpack" + diff --git a/res/lang/de.bin b/res/lang/de.bin index 17a2d191..332da0e3 100644 Binary files a/res/lang/de.bin and b/res/lang/de.bin differ diff --git a/res/lang/en.bin b/res/lang/en.bin index 51b9a10d..429b639f 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/eo.bin b/res/lang/eo.bin index 7b83b77f..2c4b98fc 100644 Binary files a/res/lang/eo.bin and b/res/lang/eo.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 91121500..bc3e23fc 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin index df129a8a..d158cc92 100644 Binary files a/res/lang/es_MX.bin and b/res/lang/es_MX.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index fad0413b..4c21d7f9 100644 Binary files a/res/lang/fi.bin and b/res/lang/fi.bin differ diff --git a/res/lang/fr.bin b/res/lang/fr.bin index 20fd19ef..42b96be1 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/gl.bin b/res/lang/gl.bin index 1cac09b9..8c0010b6 100644 Binary files a/res/lang/gl.bin and b/res/lang/gl.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 64ebfe03..2ee2884f 100644 Binary files a/res/lang/ia.bin and b/res/lang/ia.bin differ diff --git a/res/lang/ie.bin b/res/lang/ie.bin index 87064cef..3524454f 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/isv.bin b/res/lang/isv.bin index 726441ce..cbe694c0 100644 Binary files a/res/lang/isv.bin and b/res/lang/isv.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index a857c547..207f588d 100644 Binary files a/res/lang/pl.bin and b/res/lang/pl.bin differ diff --git a/res/lang/ru.bin b/res/lang/ru.bin index c1884651..5f8a3aa5 100644 Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ diff --git a/res/lang/sk.bin b/res/lang/sk.bin index 981ab7e1..766a7d1c 100644 Binary files a/res/lang/sk.bin and b/res/lang/sk.bin differ diff --git a/res/lang/sr.bin b/res/lang/sr.bin index 384665cc..d9b85067 100644 Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ diff --git a/res/lang/tok.bin b/res/lang/tok.bin index 51080283..9b80ccba 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 0cf56935..07f65240 100644 Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index 28e7cedf..d0846df8 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/app.c b/src/app.c index 70b611c2..c7e803d4 100644 --- a/src/app.c +++ b/src/app.c @@ -2566,12 +2566,23 @@ iBool handleCommand_App(const char *cmd) { const iBool fromSidebar = argLabel_Command(cmd, "fromsidebar") != 0; iUrl parts; init_Url(&parts, url); + if (equal_Rangecc(parts.scheme, "about") && equal_Rangecc(parts.path, "command") && + !isEmpty_Range(&parts.query)) { + /* NOTE: Careful here! `about:command` allows issuing UI events via links on the page. + There is a special set of pages where these are allowed (e.g., "about:fonts"). + On every other page, `about:command` links will not be clickable. */ + iString *query = collectNewRange_String((iRangecc){ + parts.query.start + 1, parts.query.end + }); + replace_String(query, "%20", " "); + postCommandString_Root(NULL, query); + return iTrue; + } if (equalCase_Rangecc(parts.scheme, "titan")) { iUploadWidget *upload = new_UploadWidget(); setUrl_UploadWidget(upload, url); setResponseViewer_UploadWidget(upload, document_App()); addChild_Widget(get_Root()->widget, iClob(upload)); -// finalizeSheet_Mobile(as_Widget(upload)); setupSheetTransition_Mobile(as_Widget(upload), iTrue); postRefresh_App(); return iTrue; @@ -3043,8 +3054,8 @@ iBool handleCommand_App(const char *cmd) { } return iFalse; } - else if (equal_Command(cmd, "media.fontpack.enable")) { - const iString *packId = collect_String(suffix_Command(cmd, "packid")); + else if (equal_Command(cmd, "fontpack.enable")) { + const iString *packId = collect_String(suffix_Command(cmd, "id")); if (arg_Command(cmd)) { remove_StringSet(d->prefs.disabledFontPacks, packId); } @@ -3052,10 +3063,35 @@ iBool handleCommand_App(const char *cmd) { insert_StringSet(d->prefs.disabledFontPacks, packId); } resetFonts_App(); - const iMedia *media = pointerLabel_Command(cmd, "media"); - if (media) { - postCommandf_App("media.fontpack.updated id:%u media:%p", - argU32Label_Command(cmd, "mediaid"), media); + postCommand_App("navigate.reload"); + return iTrue; + } + else if (equal_Command(cmd, "fontpack.delete")) { + const iString *packId = collect_String(suffix_Command(cmd, "id")); + if (isEmpty_String(packId)) { + return iTrue; + } + const iFontPack *pack = pack_Fonts(cstr_String(packId)); + if (pack && loadPath_FontPack(pack)) { + if (argLabel_Command(cmd, "confirmed")) { + remove_StringSet(d->prefs.disabledFontPacks, packId); + remove(cstr_String(loadPath_FontPack(pack))); + reload_Fonts(); + postCommand_App("navigate.reload"); + } + else { + makeQuestion_Widget( + uiTextCaution_ColorEscape "${heading.fontpack.delete}", + format_Lang("${dlg.fontpack.delete.confirm}", + cstr_String(packId)), + (iMenuItem[]){ { "${cancel}" }, + { uiTextCaution_ColorEscape " ${dlg.fontpack.delete}", + 0, + 0, + format_CStr("!fontpack.delete confirmed:1 id:%s", + cstr_String(packId)) } }, + 2); + } } return iTrue; } diff --git a/src/defs.h b/src/defs.h index 40e134c9..9a466674 100644 --- a/src/defs.h +++ b/src/defs.h @@ -126,8 +126,8 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { #define delete_Icon "\u232b" #define copy_Icon "\u2398" //"\u2bba" #define check_Icon "\u2714" -#define ballotChecked_Icon "\U0001f5f9" -#define ballotUnchecked_Icon "\U0001f5f9" +#define ballotChecked_Icon "\u2611" +#define ballotUnchecked_Icon "\u2610" #define inbox_Icon "\U0001f4e5" #define book_Icon "\U0001f56e" #define bookmark_Icon "\U0001f516" @@ -162,6 +162,7 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { #define select_Icon "\u2b1a" #define downAngle_Icon "\ufe40" #define photo_Icon "\U0001f5bc" +#define fontpack_Icon "\U0001f520" #if defined (iPlatformApple) # define shift_Icon "\u21e7" diff --git a/src/fontpack.c b/src/fontpack.c index 9603bded..9b165051 100644 --- a/src/fontpack.c +++ b/src/fontpack.c @@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include +#include #include const char *mimeType_FontPack = "application/lagrange-fontpack+zip"; @@ -236,6 +237,10 @@ iFontPackId id_FontPack(const iFontPack *d) { return (iFontPackId){ &d->id, d->version }; } +const iString *loadPath_FontPack(const iFontPack *d) { + return d->loadPath; +} + const iPtrArray *listSpecs_FontPack(const iFontPack *d) { if (!d) return NULL; iPtrArray *list = collectNew_PtrArray(); @@ -441,6 +446,20 @@ void setLoadPath_FontPack(iFontPack *d, const iString *path) { setRange_String(&d->id, withoutExtension_Path(&d->id)); } +const iString *idFromUrl_FontPack(const iString *url) { + iString *id = new_String(); + iUrl parts; + init_Url(&parts, url); + setRange_String(id, baseName_Path(collectNewRange_String(parts.path))); + setRange_String(id, withoutExtension_Path(id)); + return collect_String(id); +} + +void setUrl_FontPack(iFontPack *d, const iString *url) { + /* TODO: Should we remember the URL as well? */ + set_String(&d->id, idFromUrl_FontPack(url)); +} + void setStandalone_FontPack(iFontPack *d, iBool standalone) { d->isStandalone = standalone; } @@ -486,7 +505,7 @@ static void sortSpecs_Fonts_(iFonts *d) { clear_PtrArray(&d->specOrder); iConstForEach(PtrArray, p, &d->packs) { const iFontPack *pack = p.ptr; - if (!contains_StringSet(prefs_App()->disabledFontPacks, &pack->id)) { + if (!isDisabled_FontPack(pack)) { iConstForEach(Array, i, &pack->fonts) { pushBack_PtrArray(&d->specOrder, i.value); } @@ -620,24 +639,131 @@ const iPtrArray *listSpecsByPriority_Fonts(void) { return &fonts_.specOrder; } +iString *infoText_FontPack(const iFontPack *d) { + const iFontPack *installed = pack_Fonts(cstr_String(&d->id)); + const iBool isInstalled = (installed != NULL); + const int installedVersion = installed ? installed->version : 0; + const iBool isDisabled = isDisabled_FontPack(d); + iString *str = new_String(); + size_t sizeInBytes = 0; + iPtrSet *uniqueFiles = new_PtrSet(); + iStringList *names = new_StringList(); + iConstForEach(PtrArray, i, listSpecs_FontPack(d)) { + const iFontSpec *spec = i.ptr; + pushBack_StringList(names, &spec->name); + iForIndices(j, spec->styles) { + insert_PtrSet(uniqueFiles, spec->styles[j]); + } + } + iConstForEach(PtrSet, j, uniqueFiles) { + sizeInBytes += size_Block(&((const iFontFile *) *j.value)->sourceData); + } + appendFormat_String(str, "%.1f ${mb} (%s)\n%s: %s\n", + sizeInBytes / 1.0e6, + formatCStrs_Lang("num.files.n", size_PtrSet(uniqueFiles)), + formatCStrs_Lang("num.fonts.n", size_StringList(names)), + cstrCollect_String(joinCStr_StringList(names, ", ")), + d->version); + if (isInstalled && installedVersion != d->version) { + appendCStr_String(str, format_Lang("${fontpack.meta.version}\n", d->version)); + } + if (!isEmpty_String(&d->id)) { + appendFormat_String(str, "%s %s%s\n", + isInstalled ? ballotChecked_Icon : ballotUnchecked_Icon, + isInstalled ? (installedVersion == d->version ? "${fontpack.meta.installed}" + : format_CStr("${fontpack.meta.installed} (%s)", + format_Lang("${fontpack.meta.version}", + installedVersion))) + : "${fontpack.meta.notinstalled}", + isDisabled ? "${fontpack.meta.disabled}" : ""); + } + iRelease(names); + delete_PtrSet(uniqueFiles); + return str; +} + +const iArray *actions_FontPack(const iFontPack *d) { + iArray *items = new_Array(sizeof(iMenuItem)); + const iFontPackId fp = id_FontPack(d); + const char *fpId = cstr_String(fp.id); + const iFontPack *installed = pack_Fonts(fpId); + const iBool isEnabled = !isDisabled_FontPack(d); + if (isInstalled_Fonts(fpId)) { + if (d->version > installed->version) { + pushBack_Array(items, &(iMenuItem){ + format_Lang(add_Icon " ${fontpack.upgrade}", fpId, d->version), + SDLK_RETURN, 0, "fontpack.install" + }); + } + pushBack_Array(items, &(iMenuItem){ + format_Lang(isEnabled ? close_Icon " ${fontpack.disable}" + : leftArrowhead_Icon " ${fontpack.enable}", fpId), 0, 0, + format_CStr("fontpack.enable arg:%d id:%s", !isEnabled, fpId) }); + if (!d->isReadOnly && installed->loadPath && d->loadPath && + !cmpString_String(installed->loadPath, d->loadPath)) { + pushBack_Array(items, &(iMenuItem){ + format_Lang(delete_Icon " ${fontpack.delete}", fpId), 0, 0, + format_CStr("fontpack.delete id:%s", fpId) }); + } + } + else if (d->isStandalone) { + pushBack_Array(items, &(iMenuItem){ + format_Lang(add_Icon " ${fontpack.install}", fpId), + SDLK_RETURN, 0, "fontpack.install" + }); + } + return collect_Array(items); +} + +iBool isDisabled_FontPack(const iFontPack *d) { + return contains_StringSet(prefs_App()->disabledFontPacks, &d->id); +} + const iString *infoPage_Fonts(void) { iFonts *d = &fonts_; - iString *str = collectNewCStr_String("# Fonts\n"); + iString *str = collectNewCStr_String("# ${heading.fontpack.meta}\n" + "=> gemini://skyjake.fi/fonts Download more fonts\n" + "=> about:command?!open%20newtab:1%20gotoheading:1%20url:about:help Using fonts in Lagrange\n" + "=> about:command?!open%20newtab:1%20gotoheading:1%20url:about:help How to create a fontpack\n"); iPtrArray *specsByPack = collectNew_PtrArray(); setCopy_PtrArray(specsByPack, &d->specOrder); sort_Array(specsByPack, cmpSourceAndPriority_FontSpecPtr_); iString *currentSourcePath = collectNew_String(); - iConstForEach(PtrArray, i, specsByPack) { - const iFontSpec *spec = i.ptr; - if (isEmpty_String(&spec->sourcePath)) { - continue; /* built-in font */ - } - if (!equal_String(&spec->sourcePath, currentSourcePath)) { - appendFormat_String(str, "=> %s %s%s\n", - cstrCollect_String(makeFileUrl_String(&spec->sourcePath)), - endsWithCase_String(&spec->sourcePath, ".fontpack") ? "\U0001f520 " : "", - cstr_Rangecc(baseName_Path(&spec->sourcePath))); - set_String(currentSourcePath, &spec->sourcePath); + for (int group = 0; group < 2; group++) { + iBool isFirst = iTrue; + iConstForEach(PtrArray, i, specsByPack) { + const iFontSpec *spec = i.ptr; + if (isEmpty_String(&spec->sourcePath)) { + continue; /* built-in font */ + } + if (!equal_String(&spec->sourcePath, currentSourcePath)) { + set_String(currentSourcePath, &spec->sourcePath); + /* Print some information about this page. */ + const iFontPack *pack = packByPath_Fonts(currentSourcePath); + if (pack) { + if (!isDisabled_FontPack(pack) ^ group) { + if (isFirst) { + appendCStr_String(str, "\n## "); + appendCStr_String(str, group == 0 ? "${heading.fontpack.meta.enabled}" + : "${heading.fontpack.meta.disabled}"); + appendCStr_String(str, "\n\n"); + isFirst = iFalse; + } + const iString *packId = id_FontPack(pack).id; + appendFormat_String(str, "### %s\n=> %s ${fontpack.meta.viewfile}\n", + isEmpty_String(packId) ? "fonts.ini" : + cstr_String(packId), + cstrCollect_String(makeFileUrl_String(&spec->sourcePath))); + append_String(str, collect_String(infoText_FontPack(pack))); + iConstForEach(Array, a, actions_FontPack(pack)) { + const iMenuItem *item = a.value; + appendFormat_String(str, "=> about:command?%s %s\n", + cstr_String(withSpacesEncoded_String(collectNewCStr_String(item->command))), + item->label); + } + } + } + } } } return str; @@ -645,6 +771,9 @@ const iString *infoPage_Fonts(void) { const iFontPack *pack_Fonts(const char *packId) { iFonts *d = &fonts_; + if (!*packId) { + return NULL; + } iConstForEach(PtrArray, i, &d->packs) { const iFontPack *pack = i.ptr; if (!cmp_String(&pack->id, packId)) { @@ -675,10 +804,15 @@ void reload_Fonts(void) { delete_String(userDir); } -void install_Fonts(const iString *fontId, const iBlock *data) { +void install_Fonts(const iString *packId, const iBlock *data) { + if (!detect_FontPack(data)) { + return; + } + /* Newly installed packs will never be disabled. */ + remove_StringSet(prefs_App()->disabledFontPacks, packId); iFonts *d = &fonts_; iFile *f = new_File(collect_String(concatCStr_Path( - userFontsDirectory_Fonts_(d), format_CStr("%s.fontpack", cstr_String(fontId))))); + userFontsDirectory_Fonts_(d), format_CStr("%s.fontpack", cstr_String(packId))))); if (open_File(f, writeOnly_FileMode)) { write_File(f, data); } @@ -687,38 +821,5 @@ void install_Fonts(const iString *fontId, const iBlock *data) { reload_Fonts(); } -iBool preloadLocalFontpackForPreview_Fonts(iGmDocument *doc) { - iBool wasLoaded = iFalse; - for (size_t linkId = 1; ; linkId++) { - const iString *linkUrl = linkUrl_GmDocument(doc, linkId); - if (!linkUrl) { - break; /* ran out of links */ - } - const int linkFlags = linkFlags_GmDocument(doc, linkId); - if (linkFlags & fontpackFileExtension_GmLinkFlag && - scheme_GmLinkFlag(linkFlags) == file_GmLinkScheme) { - iMediaId linkMedia = findMediaForLink_Media(media_GmDocument(doc), linkId, fontpack_MediaType); - if (linkMedia.type) { - continue; /* got this one already */ - } - iString *filePath = localFilePathFromUrl_String(linkUrl); - iFile *f = new_File(filePath); - if (open_File(f, readOnly_FileMode)) { - iBlock *fontPackArchiveData = readAll_File(f); - setUrl_Media(media_GmDocument(doc), linkId, fontpack_MediaType, linkUrl); - setData_Media(media_GmDocument(doc), - linkId, - collectNewCStr_String(mimeType_FontPack), - fontPackArchiveData, - 0); - delete_Block(fontPackArchiveData); - wasLoaded = iTrue; - } - iRelease(f); - } - } - return wasLoaded; -} - iDefineClass(FontFile) diff --git a/src/fontpack.h b/src/fontpack.h index 2fdfa9ab..fb8d757e 100644 --- a/src/fontpack.h +++ b/src/fontpack.h @@ -148,12 +148,21 @@ struct Impl_FontPackId { void setReadOnly_FontPack (iFontPack *, iBool readOnly); void setStandalone_FontPack (iFontPack *, iBool standalone); void setLoadPath_FontPack (iFontPack *, const iString *path); +void setUrl_FontPack (iFontPack *, const iString *url); iBool loadArchive_FontPack (iFontPack *, const iArchive *zip); iBool detect_FontPack (const iBlock *data); iFontPackId id_FontPack (const iFontPack *); -const iPtrArray * listSpecs_FontPack (const iFontPack *); +const iString * loadPath_FontPack (const iFontPack *); /* may return NULL */ +iBool isDisabled_FontPack (const iFontPack *); iBool isReadOnly_FontPack (const iFontPack *); +const iPtrArray * listSpecs_FontPack (const iFontPack *); +iString * infoText_FontPack (const iFontPack *); +const iArray * actions_FontPack (const iFontPack *); + +const iString * idFromUrl_FontPack (const iString *url); + +/*----------------------------------------------------------------------------------------------*/ iDeclareType(GmDocument) @@ -170,8 +179,6 @@ const iString * infoPage_Fonts (void); void install_Fonts (const iString *fontId, const iBlock *data); void reload_Fonts (void); -iBool preloadLocalFontpackForPreview_Fonts (iGmDocument *doc); - iLocalDef iBool isInstalled_Fonts(const char *packId) { return pack_Fonts(packId) != NULL; } diff --git a/src/gmdocument.c b/src/gmdocument.c index 0adf5243..1f66e978 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -94,6 +94,7 @@ struct Impl_GmDocument { iString localHost; iInt2 size; int outsideMargin; + iBool enableCommandLinks; /* `about:command?` only allowed on selected pages */ iArray layout; /* contents of source, laid out in document space */ iPtrArray links; enum iGmDocumentBanner bannerType; @@ -256,6 +257,14 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li link->urlRange = capturedRange_RegExpMatch(&m, 1); setRange_String(&link->url, link->urlRange); set_String(&link->url, canonicalUrl_String(absoluteUrl_String(&d->url, &link->url))); + if (startsWithCase_String(&link->url, "about:command")) { + /* This is a special internal page that allows submitting UI events. */ + if (!d->enableCommandLinks) { + delete_GmLink(link); + *linkId = 0; + return line; + } + } /* Check the URL. */ { iUrl parts; init_Url(&parts, &link->url); @@ -336,7 +345,7 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li /* Check for a custom icon. */ enum iGmLinkScheme scheme = scheme_GmLinkFlag(link->flags); if ((scheme == gemini_GmLinkScheme && ~link->flags & remote_GmLinkFlag) || - scheme == file_GmLinkScheme || + scheme == about_GmLinkScheme || scheme == file_GmLinkScheme || scheme == mailto_GmLinkScheme) { iChar icon = 0; int len = 0; @@ -826,6 +835,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { : scheme == mailto_GmLinkScheme ? envelope : link->flags & remote_GmLinkFlag ? globe : link->flags & imageFileExtension_GmLinkFlag ? image + : link->flags & fontpackFileExtension_GmLinkFlag ? fontpack_Icon : scheme == file_GmLinkScheme ? folder : arrow); /* Custom link icon is shown on local Gemini links only. */ @@ -1012,14 +1022,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { pushBack_Array(&d->layout, &run); break; } - case fontpack_MediaType: { - run.bounds.pos = pos; - run.bounds.size.x = d->size.x; - run.bounds.size.y = height_FontpackUI(d->media, media.id, d->size.x); - run.visBounds = run.bounds; - pushBack_Array(&d->layout, &run); - break; - } default: break; } @@ -1069,6 +1071,7 @@ void init_GmDocument(iGmDocument *d) { d->bannerType = siteDomain_GmDocumentBanner; d->outsideMargin = 0; d->size = zero_I2(); + d->enableCommandLinks = iFalse; init_Array(&d->layout, sizeof(iGmRun)); init_PtrArray(&d->links); init_String(&d->bannerText); @@ -1760,6 +1763,10 @@ void setUrl_GmDocument(iGmDocument *d, const iString *url) { init_Url(&parts, url); setRange_String(&d->localHost, parts.host); updateIconBasedOnUrl_GmDocument_(d); + if (!cmp_String(url, "about:fonts")) { + /* This is an interactive internal page. */ + d->enableCommandLinks = iTrue; + } } static int replaceRegExp_String(iString *d, const iRegExp *regexp, const char *replacement, @@ -1973,9 +1980,15 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width, int d->theme.ansiEscapesEnabled = prefs_App()->gemtextAnsiEscapes; } else if (d->format == markdown_SourceFormat) { - convertMarkdownToGemtext_GmDocument_(d); - set_String(&d->unormSource, &d->source); /* use the converted source from now on */ - d->theme.ansiEscapesEnabled = iTrue; /* escapes are used for styling */ + /* Attempt a conversion to Gemtext when viewing local Markdown files. */ + if (equalCase_Rangecc(urlScheme_String(&d->url), "file")) { + convertMarkdownToGemtext_GmDocument_(d); + set_String(&d->unormSource, &d->source); /* use the converted source from now on */ + d->theme.ansiEscapesEnabled = iTrue; /* escapes are used for styling */ + } + else { + d->format = plainText_SourceFormat; /* just show as plain text */ + } } else { d->theme.ansiEscapesEnabled = iTrue; diff --git a/src/gmutil.c b/src/gmutil.c index 692c1cb9..e44971d0 100644 --- a/src/gmutil.c +++ b/src/gmutil.c @@ -539,6 +539,9 @@ const char *mediaTypeFromFileExtension_String(const iString *d) { else if (endsWithCase_String(d, ".fontpack")) { return mimeType_FontPack; } + else if (endsWithCase_String(d, ".ttf")) { + return "font/ttf"; + } else if (endsWithCase_String(d, ".xml")) { return "text/xml"; } diff --git a/src/lang.c b/src/lang.c index a867f911..1dac4201 100644 --- a/src/lang.c +++ b/src/lang.c @@ -247,3 +247,12 @@ const char *formatCStr_Lang(const char *formatMsgId, int count) { const char *formatCStrs_Lang(const char *formatMsgId, size_t count) { return format_CStr(cstrCount_Lang(formatMsgId, (int) count), count); } + +const char *format_Lang(const char *formatTextWithIds, ...) { + iBlock *msg = new_Block(0); + va_list args; + va_start(args, formatTextWithIds); + vprintf_Block(msg, translateCStr_Lang(formatTextWithIds), args); + va_end(args); + return cstr_Block(collect_Block(msg)); +} diff --git a/src/lang.h b/src/lang.h index 23161bb2..e3e6c433 100644 --- a/src/lang.h +++ b/src/lang.h @@ -39,3 +39,4 @@ const char * translateCStr_Lang (const char *textWithIds); const char * cstrCount_Lang (const char *msgId, int count); const char * formatCStr_Lang (const char *formatMsgId, int count); const char * formatCStrs_Lang (const char *formatMsgId, size_t count); +const char * format_Lang (const char *formatTextWithIds, ...); diff --git a/src/media.c b/src/media.c index 412205a7..a3f381ec 100644 --- a/src/media.c +++ b/src/media.c @@ -288,76 +288,6 @@ iDefineTypeConstruction(GmDownload) /*----------------------------------------------------------------------------------------------*/ -iDeclareType(GmFontpack) - -struct Impl_GmFontpack { - iGmMediaProps props; - iString packId; - iFontpackMediaInfo info; - /* TODO: Font preview images? */ -}; - -void init_GmFontpack(iGmFontpack *d) { - init_GmMediaProps_(&d->props); - init_String(&d->packId); - iZap(d->info); - d->info.names = new_StringList(); -} - -void deinit_GmFontpack(iGmFontpack *d) { - iRelease(d->info.names); - deinit_String(&d->packId); - deinit_GmMediaProps_(&d->props); -} - -static void loadData_GmFontpack_(iGmFontpack *d, const iBlock *data) { - const iString *loadPath = collect_String(localFilePathFromUrl_String(&d->props.url)); - const iFontPack *pack = packByPath_Fonts(loadPath); - d->info.isValid = d->info.isInstalled = pack != NULL; - d->info.isReadOnly = iFalse; - d->info.isDisabled = iFalse; - clear_StringList(d->info.names); - clear_String(&d->packId); - if (!pack) { - /* Let's load it now temporarily and see what's inside. */ - iArchive *zip = new_Archive(); - if (openData_Archive(zip, data)) { - iFontPack *fp = collect_FontPack(new_FontPack()); - setLoadPath_FontPack(fp, loadPath); - /* TODO: No need to load all the font files here, just the metadata. */ - setStandalone_FontPack(fp, iTrue); - if (loadArchive_FontPack(fp, zip)) { - d->info.isValid = iTrue; - pack = fp; - } - } - iRelease(zip); - } - if (pack) { - set_String(&d->packId, id_FontPack(pack).id); - d->info.packId.id = &d->packId; /* we own this String */ - d->info.packId.version = id_FontPack(pack).version; - d->info.isReadOnly = isReadOnly_FontPack(pack); - d->info.isDisabled = contains_StringSet(prefs_App()->disabledFontPacks, &d->packId); - } - iPtrSet *unique = new_PtrSet(); - iConstForEach(PtrArray, i, listSpecs_FontPack(pack)) { - const iFontSpec *spec = i.ptr; - pushBack_StringList(d->info.names, &spec->name); - iForIndices(j, spec->styles) { - insert_PtrSet(unique, spec->styles[j]); - } - } - iConstForEach(PtrSet, j, unique) { - d->info.sizeInBytes += size_Block(&((const iFontFile *) *j.value)->sourceData); - } - delete_PtrSet(unique); -} - -iDefineTypeConstruction(GmFontpack) - -/*----------------------------------------------------------------------------------------------*/ - struct Impl_Media { iPtrArray items[max_MediaType]; /* TODO: Add a hash to quickly look up a link's media. */ @@ -388,9 +318,6 @@ void clear_Media(iMedia *d) { iForEach(PtrArray, n, &d->items[download_MediaType]) { deinit_GmDownload(n.ptr); } - iForEach(PtrArray, f, &d->items[fontpack_MediaType]) { - deinit_GmFontpack(f.ptr); - } iForIndices(type, d->items) { clear_PtrArray(&d->items[type]); } @@ -436,17 +363,6 @@ iBool setUrl_Media(iMedia *d, iGmLinkId linkId, enum iMediaType mediaType, const } props = &dl->props; } - else if (mediaType == fontpack_MediaType) { - iGmFontpack *fp = NULL; - if (isNew) { - fp = new_GmFontpack(); - pushBack_PtrArray(&d->items[fontpack_MediaType], fp); - } - else { - fp = at_PtrArray(&d->items[fontpack_MediaType], index_MediaId(existing)); - } - props = &fp->props; - } if (props) { props->linkId = linkId; props->isPermanent = iTrue; @@ -517,21 +433,6 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo } } } - else if (existing.type == fontpack_MediaType) { - iGmFontpack *fp; - if (isDeleting) { - take_PtrArray(&d->items[fontpack_MediaType], existingIndex, (void **) &fp); - delete_GmFontpack(fp); - } - else { - iAssert(!isPartial); - fp = at_PtrArray(&d->items[fontpack_MediaType], existingIndex); - if (isEmpty_String(&fp->props.mime)) { - set_String(&fp->props.mime, mime); - } - loadData_GmFontpack_(fp, data); - } - } else if (!isDeleting) { if (startsWith_String(mime, "image/")) { /* Copy the image to a texture. */ @@ -591,23 +492,6 @@ iMediaId findMediaForLink_Media(const iMedia *d, iGmLinkId linkId, enum iMediaTy return mid; } -#if 0 -iMediaId findUrl_Media(const iMedia *d, const iString *url) { - iMediaId mid = iInvalidMediaId; - for (int i = 0; i < max_MediaType; i++) { - for (int j = 0; j < size_PtrArray(&d->items[i]); j++) { - const iGmMediaProps *props = constAt_PtrArray(&d->items[i], j); - if (equal_String(&props->url, url)) { - mid.type = i; - mid.id = j + 1; - break; - } - } - } - return mid; -} -#endif - size_t numAudio_Media(const iMedia *d) { return size_PtrArray(&d->items[audio_MediaType]); } @@ -662,9 +546,6 @@ iBool info_Media(const iMedia *d, iMediaId mediaId, iGmMediaInfo *info_out) { return iTrue; } break; - case fontpack_MediaType: - /* TODO */ - break; default: break; } @@ -718,16 +599,6 @@ void downloadStats_Media(const iMedia *d, iMediaId downloadId, const iString **p } } -void fontpackInfo_Media(const iMedia *d, iMediaId fontpackId, iFontpackMediaInfo *info_out) { - iAssert(fontpackId.type == fontpack_MediaType); - iZap(*info_out); - const size_t index = index_MediaId(fontpackId); - if (index < size_PtrArray(&d->items[fontpack_MediaType])) { - const iGmFontpack *fp = constAt_PtrArray(&d->items[fontpack_MediaType], index); - *info_out = fp->info; - } -} - /*----------------------------------------------------------------------------------------------*/ static void updated_MediaRequest_(iAnyObject *obj) { diff --git a/src/media.h b/src/media.h index 92a46cd3..3b329716 100644 --- a/src/media.h +++ b/src/media.h @@ -53,7 +53,6 @@ enum iMediaType { /* Note: There is a limited number of bits for these; see GmRu //animatedImage_MediaType, /* TODO */ audio_MediaType, download_MediaType, - fontpack_MediaType, max_MediaType }; @@ -74,7 +73,6 @@ iBool setData_Media (iMedia *, uint16_t linkId, const iStrin size_t memorySize_Media (const iMedia *); iMediaId findMediaForLink_Media (const iMedia *, uint16_t linkId, enum iMediaType mediaType); -//iMediaId findUrl_Media (const iMedia *, const iString *url); iMediaId id_Media (const iMedia *, uint16_t linkId, enum iMediaType type); iBool info_Media (const iMedia *, iMediaId mediaId, iGmMediaInfo *info_out); @@ -109,21 +107,6 @@ void pauseAllPlayers_Media (const iMedia *, iBool setPaused); void downloadStats_Media (const iMedia *, iMediaId downloadId, const iString **path_out, float *bytesPerSecond_out, iBool *isFinished_out); -iDeclareType(FontpackMediaInfo) - -struct Impl_FontpackMediaInfo { - iFontPackId packId; - iBool isValid; - iBool isInstalled; - iBool isDisabled; - iBool isReadOnly; - size_t sizeInBytes; - iStringList *names; -}; - -void fontpackInfo_Media (const iMedia *, iMediaId fontpackId, - iFontpackMediaInfo *info_out); - /*----------------------------------------------------------------------------------------------*/ iDeclareType(GmRequest) diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 1aca895c..a1992967 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -1257,7 +1257,7 @@ static const char *zipPageHeading_(const iRangecc mime) { return book_Icon " Gempub"; } else if (equalCase_Rangecc(mime, mimeType_FontPack)) { - return "\U0001f520 Fontpack"; + return fontpack_Icon " Fontpack"; } iRangecc type = iNullRange; nextSplit_Rangecc(mime, "/", &type); /* skip the part before the slash */ @@ -1435,13 +1435,6 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool } } } - /* Local fontpacks are automatically shown. */ - if (preloadLocalFontpackForPreview_Fonts(d->doc)) { - documentRunsInvalidated_DocumentWidget_(d); - redoLayout_GmDocument(d->doc); - updateVisible_DocumentWidget_(d); - invalidate_DocumentWidget_(d); - } } static void updateDocument_DocumentWidget_(iDocumentWidget *d, @@ -1503,6 +1496,25 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, appendFormat_String(&str, cstr_Lang("doc.archive"), cstr_Rangecc(baseName_Path(d->mod.url))); + if (isRequestFinished) { + if (equal_Rangecc(param, mimeType_FontPack)) { + /* Show some information about fontpacks, and set up footer actions. */ + iArchive *zip = iClob(new_Archive()); + if (openData_Archive(zip, &d->sourceContent)) { + iFontPack *fp = new_FontPack(); + setUrl_FontPack(fp, d->mod.url); + setStandalone_FontPack(fp, iTrue); + if (loadArchive_FontPack(fp, zip)) { + appendFormat_String(&str, "\n\n%s", + cstrCollect_String(infoText_FontPack(fp))); + } + const iArray *actions = actions_FontPack(fp); + makeFooterButtons_DocumentWidget_(d, constData_Array(actions), + size_Array(actions)); + delete_FontPack(fp); + } + } + } appendCStr_String(&str, "\n\n"); iString *localPath = localFilePathFromUrl_String(d->mod.url); if (!localPath) { @@ -2916,27 +2928,6 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) updateMedia_DocumentWidget_(d); return iFalse; } - else if (equal_Command(cmd, "media.fontpack.updated")) { - iMedia *media = pointerLabel_Command(cmd, "media"); - if (media == media_GmDocument(d->doc)) { - /*iGmMediaInfo info; - if (info_Media(media, - (iMediaId){ fontpack_MediaType, argU32Label_Command(cmd, "id")}, - &info)) { - - }*/ - -// findCachedContent_App(<#const iString *url#>, <#iString *mime_out#>, <#iBlock *data_out#>) -// setData_Media(media, - } - return iFalse; - } - else if (equal_Command(cmd, "media.fontpack.install")) { - if (pointerLabel_Command(cmd, "media") == media_GmDocument(d->doc)) { - /* TODO: This is ours, we may have a MediaRequest with the data in memory. */ - } - return iFalse; - } else if (equal_Command(cmd, "document.stop") && document_App() == d) { if (cancelRequest_DocumentWidget_(d, iTrue /* navigate back */)) { return iTrue; @@ -3253,7 +3244,16 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) return handleSwipe_DocumentWidget_(d, cmd); } else if (equal_Command(cmd, "document.setmediatype") && document_App() == d) { - setUrlAndSource_DocumentWidget(d, d->mod.url, string_Command(cmd, "mime"), &d->sourceContent); + if (!isRequestOngoing_DocumentWidget(d)) { + setUrlAndSource_DocumentWidget(d, d->mod.url, string_Command(cmd, "mime"), + &d->sourceContent); + } + return iTrue; + } + else if (equalWidget_Command(cmd, w, "fontpack.install")) { + const iString *id = idFromUrl_FontPack(d->mod.url); + install_Fonts(id, &d->sourceContent); + postCommandf_App("open gotoheading:%s url:about:fonts", cstr_String(id)); return iTrue; } return iFalse; @@ -3302,15 +3302,6 @@ static iBool processMediaEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Ev const iInt2 mouse = init_I2(ev->button.x, ev->button.y); iConstForEach(PtrArray, i, &d->visibleMedia) { const iGmRun *run = i.ptr; - if (run->mediaType == fontpack_MediaType) { - iFontpackUI ui; - init_FontpackUI(&ui, media_GmDocument(d->doc), run->mediaId, - runRect_DocumentWidget_(d, run)); - if (processEvent_FontpackUI(&ui, ev)) { - refresh_Widget(d); - return iTrue; - } - } if (run->mediaType != audio_MediaType) { continue; } @@ -4685,12 +4676,6 @@ static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) { runRect_DocumentWidget_(d, run)); draw_DownloadUI(&ui, p); } - else if (run->mediaType == fontpack_MediaType) { - iFontpackUI ui; - init_FontpackUI(&ui, constMedia_GmDocument(d->doc), run->mediaId, - runRect_DocumentWidget_(d, run)); - draw_FontpackUI(&ui, p); - } } } diff --git a/src/ui/mediaui.c b/src/ui/mediaui.c index ac9475dd..d85d0df9 100644 --- a/src/ui/mediaui.c +++ b/src/ui/mediaui.c @@ -279,85 +279,3 @@ void draw_DownloadUI(const iDownloadUI *d, iPaint *p) { translateCStr_Lang("\u2014 ${mb.per.sec}")); } } - -/*----------------------------------------------------------------------------------------------*/ - -static iMenuItem action_FontpackUI_(const iFontpackUI *d) { - if (d->info.isInstalled) { - return (iMenuItem){ d->info.isDisabled ? "${media.fontpack.enable}" - : close_Icon " ${media.fontpack.disable}", - 0, 0, format_CStr("media.fontpack.enable arg:%d", d->info.isDisabled) }; - } - return (iMenuItem){ d->info.isInstalled ? close_Icon " ${media.fontpack.disable}" - : "${media.fontpack.install}", - 0, 0, - d->info.isInstalled ? "media.fontpack.enable arg:0" : "media.fontpack.install" }; -} - -void init_FontpackUI(iFontpackUI *d, const iMedia *media, uint16_t mediaId, iRect bounds) { - d->media = media; - d->mediaId = mediaId; - fontpackInfo_Media(d->media, (iMediaId){ fontpack_MediaType, d->mediaId }, &d->info); - d->bounds = bounds; - iMenuItem action = action_FontpackUI_(d); - d->buttonRect.size = add_I2(measure_Text(uiLabel_FontId, action.label).bounds.size, - muli_I2(gap2_UI, 3)); - d->buttonRect.pos.x = right_Rect(d->bounds) - gap_UI - d->buttonRect.size.x; - d->buttonRect.pos.y = mid_Rect(d->bounds).y - d->buttonRect.size.y / 2; -} - -iBool processEvent_FontpackUI(iFontpackUI *d, const SDL_Event *ev) { - switch (ev->type) { - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: { - const iInt2 pos = init_I2(ev->button.x, ev->button.y); - if (contains_Rect(d->buttonRect, pos)) { - if (ev->type == SDL_MOUSEBUTTONUP) { - postCommandf_App("%s media:%p mediaid:%u packid:%s", - action_FontpackUI_(d).command, - d->media, d->mediaId, cstr_String(d->info.packId.id)); - } - return iTrue; - } - break; - } - case SDL_MOUSEMOTION: - if (contains_Rect(d->bounds, init_I2(ev->motion.x, ev->motion.y))) { - return iTrue; - } - break; - } - return iFalse; -} - -int height_FontpackUI(const iMedia *media, uint16_t mediaId, int width) { - const iStringList *names; - iFontpackMediaInfo info; - fontpackInfo_Media(media, (iMediaId){ fontpack_MediaType, mediaId }, &info); - return lineHeight_Text(uiContent_FontId) + - lineHeight_Text(uiLabel_FontId) * (1 + size_StringList(info.names)); -} - -void draw_FontpackUI(const iFontpackUI *d, iPaint *p) { - /* Draw a background box. */ - fillRect_Paint(p, d->bounds, uiBackground_ColorId); - drawRect_Paint(p, d->bounds, uiSeparator_ColorId); - iInt2 pos = topLeft_Rect(d->bounds); - const char *checks[] = { "\u2610", "\u2611" }; - draw_Text(uiContentBold_FontId, pos, - d->info.isDisabled ? uiText_ColorId : uiHeading_ColorId, "\"%s\" v%d", - cstr_String(d->info.packId.id), d->info.packId.version); - pos.y += lineHeight_Text(uiContentBold_FontId); - draw_Text(uiLabelBold_FontId, pos, uiText_ColorId, "%.1f MB, %d fonts %s %s %s", - d->info.sizeInBytes / 1.0e6, size_StringList(d->info.names), -// checks[info.isValid], info.isValid ? "No errors" : "Errors detected", - checks[d->info.isInstalled], d->info.isInstalled ? "Installed" : "Not installed", - d->info.isReadOnly ? "Read-Only" : ""); - pos.y += lineHeight_Text(uiLabelBold_FontId); - iConstForEach(StringList, i, d->info.names) { - drawRange_Text(uiLabel_FontId, pos, uiText_ColorId, range_String(i.value)); - pos.y += lineHeight_Text(uiLabel_FontId); - } - /* Buttons. */ - drawInlineButton_(p, d->buttonRect, action_FontpackUI_(d).label, uiLabel_FontId); -} diff --git a/src/ui/mediaui.h b/src/ui/mediaui.h index 03ea0afc..3d51e4c9 100644 --- a/src/ui/mediaui.h +++ b/src/ui/mediaui.h @@ -59,20 +59,3 @@ struct Impl_DownloadUI { void init_DownloadUI (iDownloadUI *, const iMedia *media, uint16_t mediaId, iRect bounds); iBool processEvent_DownloadUI (iDownloadUI *, const SDL_Event *ev); void draw_DownloadUI (const iDownloadUI *, iPaint *p); - -/*----------------------------------------------------------------------------------------------*/ - -iDeclareType(FontpackUI) - -struct Impl_FontpackUI { - const iMedia *media; - uint16_t mediaId; - iFontpackMediaInfo info; - iRect bounds; - iRect buttonRect; -}; - -void init_FontpackUI (iFontpackUI *, const iMedia *media, uint16_t mediaId, iRect bounds); -int height_FontpackUI (const iMedia *media, uint16_t mediaId, int width); -iBool processEvent_FontpackUI (iFontpackUI *, const SDL_Event *ev); -void draw_FontpackUI (const iFontpackUI *, iPaint *p); -- cgit v1.2.3 From 0538ae3ca02b249c784757920e3731efa8175d53 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 18 Oct 2021 08:08:16 +0300 Subject: GmDocument: Fixed a potential crash --- src/gmdocument.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/gmdocument.c') diff --git a/src/gmdocument.c b/src/gmdocument.c index 1f66e978..5d8c6e20 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -2265,6 +2265,9 @@ iLocalDef iBool isOldSchool_GmLinkScheme(enum iGmLinkScheme d) { enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId, enum iGmLinkPart part) { const iGmLink *link = link_GmDocument_(d, linkId); + if (!link) { + return none_ColorId; + } // const int www_GmLinkFlag = http_GmLinkFlag | mailto_GmLinkFlag; // const int gopherOrFinger_GmLinkFlag = gopher_GmLinkFlag | finger_GmLinkFlag; const enum iGmLinkScheme scheme = scheme_GmLinkFlag(link->flags); -- cgit v1.2.3 From baf4f8d5b02952e4adfcf60acdd1ac904c132c89 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 18 Oct 2021 09:07:21 +0300 Subject: Preferences: ANSI escape flags FG color and font style escapes can be enabled separately. FG color remains enabled by default like before. --- po/en.po | 8 +++++++- res/lang/de.bin | Bin 27035 -> 27098 bytes res/lang/en.bin | Bin 24559 -> 24622 bytes res/lang/eo.bin | Bin 23327 -> 23390 bytes res/lang/es.bin | Bin 27419 -> 27482 bytes res/lang/es_MX.bin | Bin 25387 -> 25450 bytes res/lang/fi.bin | Bin 27331 -> 27394 bytes res/lang/fr.bin | Bin 28241 -> 28304 bytes res/lang/gl.bin | Bin 26695 -> 26758 bytes res/lang/ia.bin | Bin 26360 -> 26423 bytes res/lang/ie.bin | Bin 26565 -> 26628 bytes res/lang/isv.bin | Bin 23263 -> 23326 bytes res/lang/pl.bin | Bin 27636 -> 27699 bytes res/lang/ru.bin | Bin 39975 -> 40038 bytes res/lang/sk.bin | Bin 23596 -> 23659 bytes res/lang/sr.bin | Bin 39880 -> 39943 bytes res/lang/tok.bin | Bin 24810 -> 24873 bytes res/lang/zh_Hans.bin | Bin 23312 -> 23375 bytes res/lang/zh_Hant.bin | Bin 23453 -> 23516 bytes src/app.c | 19 +++++++++++++++---- src/fontpack.c | 2 +- src/gmdocument.c | 24 ++++++++++++------------ src/gmdocument.h | 2 +- src/prefs.c | 2 +- src/prefs.h | 2 +- src/ui/documentwidget.c | 4 ++-- src/ui/text.c | 17 +++++++++-------- src/ui/text.h | 9 ++++++++- src/ui/util.c | 16 +++++++++++++++- 29 files changed, 72 insertions(+), 33 deletions(-) (limited to 'src/gmdocument.c') diff --git a/po/en.po b/po/en.po index 51bd66b2..0d5707b1 100644 --- a/po/en.po +++ b/po/en.po @@ -1507,7 +1507,13 @@ msgid "prefs.boldlink.light" msgstr "On Light" msgid "prefs.gemtext.ansi" -msgstr "Gemtext ANSI escapes:" +msgstr "ANSI escapes:" + +msgid "prefs.gemtext.ansi.fg" +msgstr "FG Color" + +msgid "prefs.gemtext.ansi.fontstyle" +msgstr "Font Style" msgid "prefs.font.smooth" msgstr "Smoothing:" diff --git a/res/lang/de.bin b/res/lang/de.bin index 332da0e3..cf3250a5 100644 Binary files a/res/lang/de.bin and b/res/lang/de.bin differ diff --git a/res/lang/en.bin b/res/lang/en.bin index 429b639f..ffdd4226 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/eo.bin b/res/lang/eo.bin index 2c4b98fc..2c1a4458 100644 Binary files a/res/lang/eo.bin and b/res/lang/eo.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index bc3e23fc..810fe574 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin index d158cc92..98234c64 100644 Binary files a/res/lang/es_MX.bin and b/res/lang/es_MX.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index 4c21d7f9..abbc9870 100644 Binary files a/res/lang/fi.bin and b/res/lang/fi.bin differ diff --git a/res/lang/fr.bin b/res/lang/fr.bin index 42b96be1..313e4043 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/gl.bin b/res/lang/gl.bin index 8c0010b6..5e909f6c 100644 Binary files a/res/lang/gl.bin and b/res/lang/gl.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index 2ee2884f..e7e4a2ea 100644 Binary files a/res/lang/ia.bin and b/res/lang/ia.bin differ diff --git a/res/lang/ie.bin b/res/lang/ie.bin index 3524454f..b29318ae 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/isv.bin b/res/lang/isv.bin index cbe694c0..80bc275f 100644 Binary files a/res/lang/isv.bin and b/res/lang/isv.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 207f588d..208e9e3c 100644 Binary files a/res/lang/pl.bin and b/res/lang/pl.bin differ diff --git a/res/lang/ru.bin b/res/lang/ru.bin index 5f8a3aa5..72c26f53 100644 Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ diff --git a/res/lang/sk.bin b/res/lang/sk.bin index 766a7d1c..f5172ada 100644 Binary files a/res/lang/sk.bin and b/res/lang/sk.bin differ diff --git a/res/lang/sr.bin b/res/lang/sr.bin index d9b85067..14ca2240 100644 Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ diff --git a/res/lang/tok.bin b/res/lang/tok.bin index 9b80ccba..fad37087 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 07f65240..2f8e6beb 100644 Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index d0846df8..2b11cbd2 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/app.c b/src/app.c index 5b12db14..485c2495 100644 --- a/src/app.c +++ b/src/app.c @@ -234,6 +234,7 @@ static iString *serializePrefs_App_(const iApp *d) { iConstForEach(StringSet, fp, d->prefs.disabledFontPacks) { appendFormat_String(str, "fontpack.disable id:%s\n", cstr_String(fp.value)); } + appendFormat_String(str, "ansiescape arg:%d\n", d->prefs.gemtextAnsiEscapes); /* TODO: This array belongs in Prefs. It can then be used for command handling as well. */ const struct { const char * id; @@ -241,7 +242,6 @@ static iString *serializePrefs_App_(const iApp *d) { } boolPrefs[] = { { "prefs.animate", &d->prefs.uiAnimations }, { "prefs.font.smooth", &d->prefs.fontSmoothing }, - { "prefs.gemtext.ansi", &d->prefs.gemtextAnsiEscapes }, { "prefs.mono.gemini", &d->prefs.monospaceGemini }, { "prefs.mono.gopher", &d->prefs.monospaceGopher }, { "prefs.boldlink.visited", &d->prefs.boldLinkVisited }, @@ -2387,8 +2387,16 @@ iBool handleCommand_App(const char *cmd) { } return iTrue; } - else if (equal_Command(cmd, "prefs.gemtext.ansi.changed")) { - d->prefs.gemtextAnsiEscapes = arg_Command(cmd) != 0; + else if (equal_Command(cmd, "ansiescape")) { + d->prefs.gemtextAnsiEscapes = arg_Command(cmd); + return iTrue; + } + else if (equal_Command(cmd, "prefs.gemtext.ansi.fg.changed")) { + iChangeFlags(d->prefs.gemtextAnsiEscapes, allowFg_AnsiFlag, arg_Command(cmd)); + return iTrue; + } + else if (equal_Command(cmd, "prefs.gemtext.ansi.fontstyle.changed")) { + iChangeFlags(d->prefs.gemtextAnsiEscapes, allowFontStyle_AnsiFlag, arg_Command(cmd)); return iTrue; } else if (equal_Command(cmd, "prefs.mono.gemini.changed") || @@ -2822,7 +2830,10 @@ iBool handleCommand_App(const char *cmd) { setFlags_Widget(findChild_Widget(dlg, "prefs.boldlink.light"), selected_WidgetFlag, d->prefs.boldLinkLight); - setToggle_Widget(findChild_Widget(dlg, "prefs.gemtext.ansi"), d->prefs.gemtextAnsiEscapes); + setToggle_Widget(findChild_Widget(dlg, "prefs.gemtext.ansi.fg"), + d->prefs.gemtextAnsiEscapes & allowFg_AnsiFlag); + setToggle_Widget(findChild_Widget(dlg, "prefs.gemtext.ansi.fontstyle"), + d->prefs.gemtextAnsiEscapes & allowFontStyle_AnsiFlag); setToggle_Widget(findChild_Widget(dlg, "prefs.font.smooth"), d->prefs.fontSmoothing); setFlags_Widget( findChild_Widget(dlg, format_CStr("prefs.linewidth.%d", d->prefs.lineWidth)), diff --git a/src/fontpack.c b/src/fontpack.c index 7ed64113..b521147c 100644 --- a/src/fontpack.c +++ b/src/fontpack.c @@ -105,7 +105,7 @@ static void unload_FontFile_(iFontFile *d) { } void deinit_FontFile(iFontFile *d) { - printf("FontFile %p {%s} is DESTROYED\n", d, cstr_String(&d->id)); +// printf("FontFile %p {%s} is DESTROYED\n", d, cstr_String(&d->id)); unload_FontFile_(d); deinit_Block(&d->sourceData); deinit_String(&d->id); diff --git a/src/gmdocument.c b/src/gmdocument.c index 5d8c6e20..23ebace3 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -78,7 +78,7 @@ iDefineTypeConstruction(GmLink) iDeclareType(GmTheme) struct Impl_GmTheme { - iBool ansiEscapesEnabled; + int ansiEscapes; int colors[max_GmLineType]; int fonts[max_GmLineType]; }; @@ -596,7 +596,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { isPreformat = iTrue; isFirstText = iFalse; } - setAnsiEscapesEnabled_Text(d->theme.ansiEscapesEnabled); + setAnsiFlags_Text(d->theme.ansiEscapes); while (nextSplit_Rangecc(content, "\n", &contentLine)) { iRangecc line = contentLine; /* `line` will be trimmed; modifying would confuse `nextSplit_Rangecc` */ if (*line.end == '\r') { @@ -1057,7 +1057,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { } } } - setAnsiEscapesEnabled_Text(iTrue); + setAnsiFlags_Text(allowAll_AnsiFlag); // printf("[GmDocument] layout size: %zu runs (%zu bytes)\n", // size_Array(&d->layout), size_Array(&d->layout) * sizeof(iGmRun)); } @@ -1977,21 +1977,21 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width, int set_String(&d->unormSource, source); set_String(&d->source, source); if (d->format == gemini_SourceFormat) { - d->theme.ansiEscapesEnabled = prefs_App()->gemtextAnsiEscapes; + d->theme.ansiEscapes = prefs_App()->gemtextAnsiEscapes; } else if (d->format == markdown_SourceFormat) { /* Attempt a conversion to Gemtext when viewing local Markdown files. */ if (equalCase_Rangecc(urlScheme_String(&d->url), "file")) { convertMarkdownToGemtext_GmDocument_(d); set_String(&d->unormSource, &d->source); /* use the converted source from now on */ - d->theme.ansiEscapesEnabled = iTrue; /* escapes are used for styling */ + d->theme.ansiEscapes = allowAll_AnsiFlag; /* escapes are used for styling */ } else { d->format = plainText_SourceFormat; /* just show as plain text */ } } else { - d->theme.ansiEscapesEnabled = iTrue; + d->theme.ansiEscapes = allowAll_AnsiFlag; } if (isNormalized_GmDocument_(d)) { normalize_GmDocument(d); @@ -2033,7 +2033,7 @@ const iGmPreMeta *preMeta_GmDocument(const iGmDocument *d, uint16_t preId) { void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render, void *context) { iBool isInside = iFalse; - setAnsiEscapesEnabled_Text(d->theme.ansiEscapesEnabled); + setAnsiFlags_Text(d->theme.ansiEscapes); /* TODO: Check lookup table for quick starting position. */ iConstForEach(Array, i, &d->layout) { const iGmRun *run = i.value; @@ -2048,7 +2048,7 @@ void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRende render(context, run); } } - setAnsiEscapesEnabled_Text(iTrue); + setAnsiFlags_Text(allowAll_AnsiFlag); } static iBool isValidRun_GmDocument_(const iGmDocument *d, const iGmRun *run) { @@ -2063,7 +2063,7 @@ const iGmRun *renderProgressive_GmDocument(const iGmDocument *d, const iGmRun *f size_t maxCount, iRangei visRangeY, iGmDocumentRenderFunc render, void *context) { - setAnsiEscapesEnabled_Text(d->theme.ansiEscapesEnabled); + setAnsiFlags_Text(d->theme.ansiEscapes); const iGmRun *run = first; while (isValidRun_GmDocument_(d, run)) { if ((dir < 0 && bottom_Rect(run->visBounds) < visRangeY.start) || @@ -2076,7 +2076,7 @@ const iGmRun *renderProgressive_GmDocument(const iGmDocument *d, const iGmRun *f render(context, run); run += dir; } - setAnsiEscapesEnabled_Text(iTrue); + setAnsiFlags_Text(allowAll_AnsiFlag); return isValidRun_GmDocument_(d, run) ? run : NULL; } @@ -2337,8 +2337,8 @@ iChar siteIcon_GmDocument(const iGmDocument *d) { return d->siteIcon; } -iBool ansiEscapesEnabled_GmDocument(const iGmDocument *d) { - return d->theme.ansiEscapesEnabled; +int ansiEscapes_GmDocument(const iGmDocument *d) { + return d->theme.ansiEscapes; } void runBaseAttributes_GmDocument(const iGmDocument *d, const iGmRun *run, int *fontId_out, diff --git a/src/gmdocument.h b/src/gmdocument.h index 94a494e8..e6388fbd 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h @@ -225,7 +225,7 @@ iRangecc findText_GmDocument (const iGmDocument *, const iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); iGmRunRange findPreformattedRange_GmDocument (const iGmDocument *, const iGmRun *run); -iBool ansiEscapesEnabled_GmDocument (const iGmDocument *); +int ansiEscapes_GmDocument (const iGmDocument *); void runBaseAttributes_GmDocument (const iGmDocument *, const iGmRun *run, int *fontId_out, int *colorId_out); diff --git a/src/prefs.c b/src/prefs.c index 523ec649..85936131 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -62,7 +62,7 @@ void init_Prefs(iPrefs *d) { setCStr_String(&d->strings[monospaceDocumentFont_PrefsString], "iosevka-body"); d->disabledFontPacks = new_StringSet(); d->fontSmoothing = iTrue; - d->gemtextAnsiEscapes = iFalse; + d->gemtextAnsiEscapes = allowFg_AnsiFlag; d->monospaceGemini = iFalse; d->monospaceGopher = iFalse; d->boldLinkVisited = iTrue; diff --git a/src/prefs.h b/src/prefs.h index bbf98370..dc0914a6 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -89,7 +89,7 @@ struct Impl_Prefs { /* Style */ iStringSet * disabledFontPacks; iBool fontSmoothing; - iBool gemtextAnsiEscapes; + int gemtextAnsiEscapes; iBool monospaceGemini; iBool monospaceGopher; iBool boldLinkVisited; diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index cf2dc5da..1039fc2b 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -4839,7 +4839,7 @@ static iBool render_DocumentWidget_(const iDocumentWidget *d, iDrawContext *ctx, } } } - setAnsiEscapesEnabled_Text(ansiEscapesEnabled_GmDocument(d->doc)); + setAnsiFlags_Text(ansiEscapes_GmDocument(d->doc)); iConstForEach(PtrSet, r, d->invalidRuns) { const iGmRun *run = *r.value; if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) { @@ -4847,7 +4847,7 @@ static iBool render_DocumentWidget_(const iDocumentWidget *d, iDrawContext *ctx, drawRun_DrawContext_(ctx, run); } } - setAnsiEscapesEnabled_Text(iTrue); + setAnsiFlags_Text(allowAll_AnsiFlag); } endTarget_Paint(p); if (prerenderExtra && didDraw) { diff --git a/src/ui/text.c b/src/ui/text.c index 6189c334..4baf60d3 100644 --- a/src/ui/text.c +++ b/src/ui/text.c @@ -261,7 +261,7 @@ struct Impl_Text { SDL_Palette * grayscale; SDL_Palette * blackAndWhite; /* unsmoothed glyph palette */ iRegExp * ansiEscape; - iBool enableAnsiEscapes; + int ansiFlags; int baseFontId; /* base attributes (for restoring via escapes) */ int baseColorId; }; @@ -588,8 +588,8 @@ void setBaseAttributes_Text(int fontId, int colorId) { activeText_->baseColorId = colorId; } -void setAnsiEscapesEnabled_Text(iBool enableAnsiEscapes) { - activeText_->enableAnsiEscapes = enableAnsiEscapes; +void setAnsiFlags_Text(int ansiFlags) { + activeText_->ansiFlags = ansiFlags; } void setDocumentFontSize_Text(iText *d, float fontSizeFactor) { @@ -1057,10 +1057,11 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh init_RegExpMatch(&m); if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) { finishRun_AttributedText_(d, &run, pos - 1); - if (activeText_->enableAnsiEscapes) { + const int ansi = activeText_->ansiFlags; + if (ansi) { const iRangecc sequence = capturedRange_RegExpMatch(&m, 1); /* Note: This styling is hardcoded to match `typesetOneLine_RunTypesetter_()`. */ - if (equal_Rangecc(sequence, "1")) { + if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "1")) { run.attrib.bold = iTrue; if (d->baseColorId == tmParagraph_ColorId) { setFgColor_AttributedRun_(&run, tmFirstParagraph_ColorId); @@ -1068,12 +1069,12 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), bold_FontStyle)); } - else if (equal_Rangecc(sequence, "3")) { + else if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "3")) { run.attrib.italic = iTrue; attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), italic_FontStyle)); } - else if (equal_Rangecc(sequence, "4")) { + else if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "4")) { run.attrib.monospace = iTrue; setFgColor_AttributedRun_(&run, tmPreformatted_ColorId); attribFont = font_Text_(fontWithFamily_Text(fontId_Text_(d->baseFont), @@ -1086,7 +1087,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh attribFont = run.font = d->baseFont; setFgColor_AttributedRun_(&run, d->baseColorId); } - else { + else if (ansi & allowFg_AnsiFlag) { run.fgColor_ = ansiForeground_Color(sequence, tmParagraph_ColorId); } } diff --git a/src/ui/text.h b/src/ui/text.h index 36a16e2c..99ddb704 100644 --- a/src/ui/text.h +++ b/src/ui/text.h @@ -149,9 +149,16 @@ enum iAlignment { right_Alignment, }; +enum iAnsiFlag { + allowFg_AnsiFlag = iBit(1), + allowBg_AnsiFlag = iBit(2), + allowFontStyle_AnsiFlag = iBit(3), + allowAll_AnsiFlag = 0x7, +}; + void setOpacity_Text (float opacity); void setBaseAttributes_Text (int fontId, int colorId); /* current "normal" text attributes */ -void setAnsiEscapesEnabled_Text(iBool enableAnsiEscapes); +void setAnsiFlags_Text (int ansiFlags); void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */ diff --git a/src/ui/util.c b/src/ui/util.c index 3b146155..e3759f09 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -2549,7 +2549,21 @@ iWidget *makePreferences_Widget(void) { addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.monodoc}"))); addFontButtons_(values, "monodoc"); addDialogPadding_(headings, values); - addDialogToggle_(headings, values, "${prefs.gemtext.ansi}", "prefs.gemtext.ansi"); + addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.gemtext.ansi}"))); + iWidget *ansi = new_Widget(); { + iWidget *tog; + setTextCStr_LabelWidget( + addChild_Widget(ansi, tog = iClob(makeToggle_Widget("prefs.gemtext.ansi.fg"))), + "${prefs.gemtext.ansi.fg}"); + setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse); + updateSize_LabelWidget((iLabelWidget *) tog); + setTextCStr_LabelWidget( + addChild_Widget(ansi, tog = iClob(makeToggle_Widget("prefs.gemtext.ansi.fontstyle"))), + "${prefs.gemtext.ansi.fontstyle}"); + setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse); + updateSize_LabelWidget((iLabelWidget *) tog); + } + addChildFlags_Widget(values, iClob(ansi), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); addDialogToggle_(headings, values, "${prefs.font.smooth}", "prefs.font.smooth"); addDialogPadding_(headings, values); addFontButtons_(values, "ui"); -- cgit v1.2.3 From 57cbcc6e864abd368bde93154b3580147936201c Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Wed, 20 Oct 2021 07:57:30 +0300 Subject: Installing individual TTF files; generate fontpack.ini --- po/en.po | 21 +++++ res/lang/de.bin | Bin 27098 -> 27722 bytes res/lang/en.bin | Bin 24622 -> 25246 bytes res/lang/eo.bin | Bin 23399 -> 24023 bytes res/lang/es.bin | Bin 27482 -> 28106 bytes res/lang/es_MX.bin | Bin 25450 -> 26074 bytes res/lang/fi.bin | Bin 27396 -> 28020 bytes res/lang/fr.bin | Bin 28304 -> 28928 bytes res/lang/gl.bin | Bin 26760 -> 27384 bytes res/lang/ia.bin | Bin 26423 -> 27047 bytes res/lang/ie.bin | Bin 26628 -> 27252 bytes res/lang/isv.bin | Bin 23326 -> 23950 bytes res/lang/pl.bin | Bin 27699 -> 28323 bytes res/lang/ru.bin | Bin 40071 -> 40695 bytes res/lang/sk.bin | Bin 23659 -> 24283 bytes res/lang/sr.bin | Bin 39943 -> 40567 bytes res/lang/tok.bin | Bin 24873 -> 25497 bytes res/lang/zh_Hans.bin | Bin 23375 -> 23999 bytes res/lang/zh_Hant.bin | Bin 23516 -> 24140 bytes src/app.c | 9 +- src/fontpack.c | 223 ++++++++++++++++++++++++++++++++++++++++-------- src/fontpack.h | 34 ++++---- src/gmdocument.c | 4 +- src/gmrequest.c | 2 +- src/ui/documentwidget.c | 82 ++++++++++++++---- src/ui/text.c | 2 +- 26 files changed, 301 insertions(+), 76 deletions(-) (limited to 'src/gmdocument.c') diff --git a/po/en.po b/po/en.po index 0d5707b1..cc6ea545 100644 --- a/po/en.po +++ b/po/en.po @@ -1921,6 +1921,9 @@ msgstr "Enable \"%s\"" msgid "fontpack.disable" msgstr "Disable \"%s\"" +msgid "fontpack.export" +msgstr "View fontpack.ini template" + #, c-format msgid "fontpack.install" msgstr "Install \"%s\"" @@ -1943,3 +1946,21 @@ msgstr "Do you really want to permanently delete\nthe fontpack \"%s\"?" msgid "dlg.fontpack.delete" msgstr "Delete Fontpack" +msgid "fontpack.help" +msgstr "Lagrange fontpacks are ZIP archives that contain a set of font files and associated configuration parameters. Once installed, the fonts can be used for document content and the UI. The active fonts are selected using Preferences > Fonts." + +msgid "fontpack.install.ttf" +msgstr "Install TrueType Font" + +msgid "fontpack.open.fontsdir" +msgstr "Open User Fonts Directory" + +msgid "fontpack.open.aboutfonts" +msgstr "Show Installed Fonts" + +msgid "truetype.help" +msgstr "Lagrange attempts to load all individual TrueType files that are copied to the user fonts directory." + +msgid "truetype.help.installed" +msgstr "This font is installed in the user fonts directory." + diff --git a/res/lang/de.bin b/res/lang/de.bin index cf3250a5..e6b1bf85 100644 Binary files a/res/lang/de.bin and b/res/lang/de.bin differ diff --git a/res/lang/en.bin b/res/lang/en.bin index ffdd4226..2ef67f55 100644 Binary files a/res/lang/en.bin and b/res/lang/en.bin differ diff --git a/res/lang/eo.bin b/res/lang/eo.bin index 8ff3294e..89ec6fea 100644 Binary files a/res/lang/eo.bin and b/res/lang/eo.bin differ diff --git a/res/lang/es.bin b/res/lang/es.bin index 810fe574..701f0478 100644 Binary files a/res/lang/es.bin and b/res/lang/es.bin differ diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin index 98234c64..3013590a 100644 Binary files a/res/lang/es_MX.bin and b/res/lang/es_MX.bin differ diff --git a/res/lang/fi.bin b/res/lang/fi.bin index 8d307700..edc0df2b 100644 Binary files a/res/lang/fi.bin and b/res/lang/fi.bin differ diff --git a/res/lang/fr.bin b/res/lang/fr.bin index 313e4043..c4b482de 100644 Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ diff --git a/res/lang/gl.bin b/res/lang/gl.bin index cee70fde..6b1ca49a 100644 Binary files a/res/lang/gl.bin and b/res/lang/gl.bin differ diff --git a/res/lang/ia.bin b/res/lang/ia.bin index e7e4a2ea..7f677aab 100644 Binary files a/res/lang/ia.bin and b/res/lang/ia.bin differ diff --git a/res/lang/ie.bin b/res/lang/ie.bin index b29318ae..6488b20f 100644 Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ diff --git a/res/lang/isv.bin b/res/lang/isv.bin index 80bc275f..0f783c31 100644 Binary files a/res/lang/isv.bin and b/res/lang/isv.bin differ diff --git a/res/lang/pl.bin b/res/lang/pl.bin index 208e9e3c..e2d8bc6a 100644 Binary files a/res/lang/pl.bin and b/res/lang/pl.bin differ diff --git a/res/lang/ru.bin b/res/lang/ru.bin index 2a8554a5..a0f75c6c 100644 Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ diff --git a/res/lang/sk.bin b/res/lang/sk.bin index f5172ada..91bd1358 100644 Binary files a/res/lang/sk.bin and b/res/lang/sk.bin differ diff --git a/res/lang/sr.bin b/res/lang/sr.bin index 14ca2240..55a289bd 100644 Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ diff --git a/res/lang/tok.bin b/res/lang/tok.bin index fad37087..938f85d4 100644 Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 2f8e6beb..c6beace1 100644 Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index 2b11cbd2..20128ba3 100644 Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ diff --git a/src/app.c b/src/app.c index 485c2495..46856e22 100644 --- a/src/app.c +++ b/src/app.c @@ -836,6 +836,7 @@ static void init_App_(iApp *d, int argc, char **argv) { loadPalette_Color(dataDir_App_()); setThemePalette_Color(d->prefs.theme); /* default UI colors */ loadPrefs_App_(d); + updateActive_Fonts(); load_Keys(dataDir_App_()); /* See if the user wants to override the window size. */ { iCommandLineArg *arg = iClob(checkArgument_CommandLine(&d->args, windowWidth_CommandLineOption)); @@ -3069,13 +3070,7 @@ iBool handleCommand_App(const char *cmd) { } else if (equal_Command(cmd, "fontpack.enable")) { const iString *packId = collect_String(suffix_Command(cmd, "id")); - if (arg_Command(cmd)) { - remove_StringSet(d->prefs.disabledFontPacks, packId); - } - else { - insert_StringSet(d->prefs.disabledFontPacks, packId); - } - resetFonts_App(); + enablePack_Fonts(packId, arg_Command(cmd)); postCommand_App("navigate.reload"); return iTrue; } diff --git a/src/fontpack.c b/src/fontpack.c index 226392bc..f22ab8dc 100644 --- a/src/fontpack.c +++ b/src/fontpack.c @@ -90,6 +90,14 @@ static void load_FontFile_(iFontFile *d, const iBlock *data) { #endif } +static iBool detectMonospace_FontFile_(const iFontFile *d) { + int em, i, period; + stbtt_GetCodepointHMetrics(&d->stbInfo, 'M', &em, NULL); + stbtt_GetCodepointHMetrics(&d->stbInfo, 'i', &i, NULL); + stbtt_GetCodepointHMetrics(&d->stbInfo, '.', &period, NULL); + return em == i && em == period; +} + static void unload_FontFile_(iFontFile *d) { #if defined(LAGRANGE_ENABLE_HARFBUZZ) /* HarfBuzz objects. */ @@ -300,6 +308,8 @@ static iBlock *readFile_FontPack_(const iFontPack *d, const iString *path) { return data; } +static const char *styles_[max_FontStyle] = { "regular", "italic", "light", "semibold", "bold" }; + void handleIniKeyValue_FontPack_(void *context, const iString *table, const iString *key, const iTomlValue *value) { iFontPack *d = context; @@ -359,9 +369,8 @@ void handleIniKeyValue_FontPack_(void *context, const iString *table, const iStr ((int) number_TomlValue(value)) & 1); } else if (value->type == string_TomlType) { - const char *styles[max_FontStyle] = { "regular", "italic", "light", "semibold", "bold" }; - iForIndices(i, styles) { - if (!cmp_String(key, styles[i]) && !d->loadSpec->styles[i]) { + iForIndices(i, styles_) { + if (!cmp_String(key, styles_[i]) && !d->loadSpec->styles[i]) { iFontFile *ff = NULL; iString *fontFileId = concat_Path(d->loadPath, value->value.string); if (!(ff = findFile_Fonts_(&fonts_, fontFileId))) { @@ -441,6 +450,7 @@ void setLoadPath_FontPack(iFontPack *d, const iString *path) { /* Pack ID is based on the file name. */ setRange_String(&d->id, baseName_Path(path)); setRange_String(&d->id, withoutExtension_Path(&d->id)); + replace_String(&d->id, " ", "-"); } const iString *idFromUrl_FontPack(const iString *url) { @@ -449,6 +459,7 @@ const iString *idFromUrl_FontPack(const iString *url) { init_Url(&parts, url); setRange_String(id, baseName_Path(collectNewRange_String(parts.path))); setRange_String(id, withoutExtension_Path(id)); + replace_String(id, " ", "-"); return collect_String(id); } @@ -592,6 +603,49 @@ void init_Fonts(const char *userDir) { } iRelease(f); } + /* Individual TrueType files in the user fonts directory. */ { + iForEach(DirFileInfo, entry, iClob(new_DirFileInfo(userFontsDirectory_Fonts_(d)))) { + const iString *entryPath = path_FileInfo(entry.value); + if (endsWithCase_String(entryPath, ".ttf")) { + iFile *f = new_File(entryPath); + iFontFile *font = NULL; + if (open_File(f, readOnly_FileMode)) { + iBlock *data = readAll_File(f); + font = new_FontFile(); + load_FontFile_(font, data); + set_String(&font->id, entryPath); + pushBack_ObjectList(fonts_.files, font); /* centralized ownership */ + iRelease(font); + delete_Block(data); + } + iRelease(f); + if (!font) { + fprintf(stderr, "[fonts] failed to load: %s\n", cstr_String(entryPath)); + continue; + } + iFontPack *pack = new_FontPack(); + setStandalone_FontPack(pack, iTrue); + iFontSpec *spec = new_FontSpec(); + spec->flags |= user_FontSpecFlag; + if (detectMonospace_FontFile_(font)) { + spec->flags |= monospace_FontSpecFlag; + } + setRange_String(&spec->id, baseName_Path(collect_String(lower_String(&font->id)))); + setRange_String(&spec->id, withoutExtension_Path(&spec->id)); + replace_String(&spec->id, " ", "-"); + setRange_String(&spec->name, baseName_Path(&font->id)); + setRange_String(&spec->name, withoutExtension_Path(&spec->name)); + set_String(&spec->sourcePath, entryPath); + iForIndices(j, spec->styles) { + spec->styles[j] = ref_Object(font); + } + pushBack_PtrArray(&pack->fonts, spec); + set_String(&pack->id, &spec->id); + pack->loadPath = copy_String(entryPath); + pushBack_PtrArray(&d->packs, pack); + } + } + } sortSpecs_Fonts_(d); } @@ -679,7 +733,7 @@ iString *infoText_FontPack(const iFontPack *d) { return str; } -const iArray *actions_FontPack(const iFontPack *d) { +const iArray *actions_FontPack(const iFontPack *d, iBool showInstalled) { iArray *items = new_Array(sizeof(iMenuItem)); const iFontPackId fp = id_FontPack(d); const char *fpId = cstr_String(fp.id); @@ -687,33 +741,44 @@ const iArray *actions_FontPack(const iFontPack *d) { const iBool isEnabled = !isDisabled_FontPack(d); if (isInstalled_Fonts(fpId)) { if (d->version > installed->version) { - pushBack_Array(items, &(iMenuItem){ - format_Lang(add_Icon " ${fontpack.upgrade}", fpId, d->version), - SDLK_RETURN, 0, "fontpack.install" - }); + pushBack_Array( + items, + &(iMenuItem){ format_Lang(add_Icon " ${fontpack.upgrade}", fpId, d->version), + SDLK_RETURN, + 0, + "fontpack.install" }); } - pushBack_Array(items, &(iMenuItem){ - format_Lang(isEnabled ? close_Icon " ${fontpack.disable}" - : leftArrowhead_Icon " ${fontpack.enable}", fpId), 0, 0, - format_CStr("fontpack.enable arg:%d id:%s", !isEnabled, fpId) }); - if (!d->isReadOnly && installed->loadPath && d->loadPath && + pushBack_Array( + items, + &(iMenuItem){ format_Lang(isEnabled ? close_Icon " ${fontpack.disable}" + : leftArrowhead_Icon " ${fontpack.enable}", + fpId), + 0, + 0, + format_CStr("fontpack.enable arg:%d id:%s", !isEnabled, fpId) }); + if (!d->isReadOnly && !d->isStandalone && installed->loadPath && d->loadPath && !cmpString_String(installed->loadPath, d->loadPath)) { - pushBack_Array(items, &(iMenuItem){ - format_Lang(delete_Icon " ${fontpack.delete}", fpId), 0, 0, - format_CStr("fontpack.delete id:%s", fpId) }); + pushBack_Array(items, + &(iMenuItem){ format_Lang(delete_Icon " ${fontpack.delete}", fpId), + 0, + 0, + format_CStr("fontpack.delete id:%s", fpId) }); } } else if (d->isStandalone) { - pushBack_Array(items, &(iMenuItem){ - format_Lang(add_Icon " ${fontpack.install}", fpId), - SDLK_RETURN, 0, "fontpack.install" - }); - pushBack_Array(items, &(iMenuItem){ - download_Icon " " saveToDownloads_Label, - 0, - 0, - "document.save" - }); + pushBack_Array(items, + &(iMenuItem){ format_Lang(add_Icon " ${fontpack.install}", fpId), + SDLK_RETURN, + 0, + "fontpack.install" }); + pushBack_Array( + items, &(iMenuItem){ download_Icon " " saveToDownloads_Label, 0, 0, "document.save" }); + } + if (showInstalled) { + pushBack_Array( + items, + &(iMenuItem){ + fontpack_Icon " ${fontpack.open.aboutfonts}", 0, 0, "!open url:about:fonts" }); } return collect_Array(items); } @@ -722,8 +787,64 @@ iBool isDisabled_FontPack(const iFontPack *d) { return contains_StringSet(prefs_App()->disabledFontPacks, &d->id); } -const iString *infoPage_Fonts(void) { +const iPtrArray *disabledSpecs_Fonts_(const iFonts *d) { + iPtrArray *list = collectNew_PtrArray(); + iConstForEach(PtrArray, i, &d->packs) { + const iFontPack *pack = i.ptr; + if (isDisabled_FontPack(pack)) { + iConstForEach(PtrArray, j, &pack->fonts) { + pushBack_PtrArray(list, j.ptr); + } + } + } + return list; +} + +static const char *boolStr_(int value) { + return value ? "true" : "false"; +} + +static const iString *exportFontPackIni_Fonts_(const iFonts *d, const iRangecc packId) { + iString *str = collectNew_String(); + const iFontPack *pack = pack_Fonts(cstr_Rangecc(packId)); + if (!pack) { + appendFormat_String(str, "Fontpack \"%s\" not found.\n", cstr_Rangecc(packId)); + return str; + } + const iFontPackId fp = id_FontPack(pack); + appendCStr_String(str, "To create a fontpack, add this fontpack.ini into a ZIP archive whose " + "name has the .fontpack file extension.\n```Fontpack configuration\n"); + appendFormat_String(str, "version = %d\n", fp.version); + iConstForEach(PtrArray, i, &pack->fonts) { + const iFontSpec *spec = i.ptr; + appendFormat_String(str, "\n[%s]\n", cstr_String(&spec->id)); + appendFormat_String(str, "name = \"%s\"\n", cstrCollect_String(quote_String(&spec->name, iFalse))); + appendFormat_String(str, "priority = %d\n", spec->priority); + appendFormat_String(str, "override = %s\n", boolStr_(spec->flags & override_FontSpecFlag)); + appendFormat_String(str, "monospace = %s\n", boolStr_(spec->flags & monospace_FontSpecFlag)); + appendFormat_String(str, "auxiliary = %s\n", boolStr_(spec->flags & auxiliary_FontSpecFlag)); + appendFormat_String(str, "allowspace = %s\n", boolStr_(spec->flags & allowSpacePunct_FontSpecFlag)); + for (int j = 0; j < 2; ++j) { + const char *scope = (j == 0 ? "ui" : "doc"); + appendFormat_String(str, "%s.height = %.3f\n", scope, spec->heightScale[j]); + appendFormat_String(str, "%s.glyphscale = %.3f\n", scope, spec->glyphScale[j]); + appendFormat_String(str, "%s.voffset = %.3f\n", scope, spec->vertOffsetScale[j]); + } + iForIndices(j, styles_) { + appendFormat_String(str, "%s = \"%s\"\n", styles_[j], + cstrCollect_String(quote_String(&spec->sourcePath, iFalse))); + } + } + appendCStr_String(str, "```\n"); + return str; +} + +const iString *infoPage_Fonts(iRangecc query) { iFonts *d = &fonts_; + if (!isEmpty_Range(&query)) { + query.start++; /* skip the ? */ + return exportFontPackIni_Fonts_(d, query); + } iString *str = collectNewCStr_String("# ${heading.fontpack.meta}\n" "=> gemini://skyjake.fi/fonts Download more fonts\n" "=> about:command?!open%20newtab:1%20gotoheading:1%20url:about:help Using fonts in Lagrange\n" @@ -734,7 +855,7 @@ const iString *infoPage_Fonts(void) { iString *currentSourcePath = collectNew_String(); for (int group = 0; group < 2; group++) { iBool isFirst = iTrue; - iConstForEach(PtrArray, i, specsByPack) { + iConstForEach(PtrArray, i, group == 0 ? specsByPack : disabledSpecs_Fonts_(d)) { const iFontSpec *spec = i.ptr; if (isEmpty_String(&spec->sourcePath)) { continue; /* built-in font */ @@ -753,15 +874,22 @@ const iString *infoPage_Fonts(void) { isFirst = iFalse; } const iString *packId = id_FontPack(pack).id; - appendFormat_String(str, "### %s\n=> %s ${fontpack.meta.viewfile}\n", + appendFormat_String(str, "### %s\n", isEmpty_String(packId) ? "fonts.ini" : - cstr_String(packId), - cstrCollect_String(makeFileUrl_String(&spec->sourcePath))); + cstr_String(packId)); append_String(str, collect_String(infoText_FontPack(pack))); - iConstForEach(Array, a, actions_FontPack(pack)) { + appendFormat_String(str, "=> %s ${fontpack.meta.viewfile}\n", + cstrCollect_String(makeFileUrl_String(&spec->sourcePath))); + if (pack->isStandalone) { + appendFormat_String(str, "=> about:fonts?%s ${fontpack.export}\n", + cstr_String(packId)); + } + iConstForEach(Array, a, actions_FontPack(pack, iFalse)) { const iMenuItem *item = a.value; - appendFormat_String(str, "=> about:command?%s %s\n", - cstr_String(withSpacesEncoded_String(collectNewCStr_String(item->command))), + appendFormat_String(str, + "=> about:command?%s %s\n", + cstr_String(withSpacesEncoded_String( + collectNewCStr_String(item->command))), item->label); } } @@ -824,5 +952,32 @@ void install_Fonts(const iString *packId, const iBlock *data) { reload_Fonts(); } +void installFontFile_Fonts(const iString *fileName, const iBlock *data) { + iFonts *d = &fonts_; + iFile *f = new_File(collect_String(concat_Path(userFontsDirectory_Fonts_(d), fileName))); + if (open_File(f, writeOnly_FileMode)) { + write_File(f, data); + } + iRelease(f); + reload_Fonts(); +} + +void enablePack_Fonts(const iString *packId, iBool enable) { + iFonts *d = &fonts_; + if (enable) { + remove_StringSet(prefs_App()->disabledFontPacks, packId); + } + else { + insert_StringSet(prefs_App()->disabledFontPacks, packId); + } + updateActive_Fonts(); + resetFonts_App(); + invalidate_Window(get_MainWindow()); +} + +void updateActive_Fonts(void) { + sortSpecs_Fonts_(&fonts_); +} + iDefineClass(FontFile) diff --git a/src/fontpack.h b/src/fontpack.h index fb8d757e..5d592822 100644 --- a/src/fontpack.h +++ b/src/fontpack.h @@ -110,22 +110,23 @@ iDeclareType(FontSpec) iDeclareTypeConstruction(FontSpec) enum iFontSpecFlags { - override_FontSpecFlag = iBit(1), - monospace_FontSpecFlag = iBit(2), /* can be used in preformatted content */ - auxiliary_FontSpecFlag = iBit(3), /* only used for looking up glyphs missing from other fonts */ - allowSpacePunct_FontSpecFlag = iBit(4), /* space/punctuation glyphs from this auxiliary font can be used */ + user_FontSpecFlag = iBit(1), /* user's standalone font, can be used for anything */ + override_FontSpecFlag = iBit(2), + monospace_FontSpecFlag = iBit(3), /* can be used in preformatted content */ + auxiliary_FontSpecFlag = iBit(4), /* only used for looking up glyphs missing from other fonts */ + allowSpacePunct_FontSpecFlag = iBit(5), /* space/punctuation glyphs from this auxiliary font can be used */ fixNunitoKerning_FontSpecFlag = iBit(31), /* manual hardcoded kerning tweaks for Nunito */ }; struct Impl_FontSpec { - iString id; /* unique ID */ - iString name; /* human-readable label */ - iString sourcePath; /* file where the path was loaded, could be a .fontpack */ - int flags; - int priority; - float heightScale[2]; /* overall height scaling; ui, document */ - float glyphScale[2]; /* ui, document */ - float vertOffsetScale[2]; /* ui, document */ + iString id; /* unique ID */ + iString name; /* human-readable label */ + iString sourcePath; /* file where the path was loaded, could be a .fontpack */ + int flags; + int priority; + float heightScale[2]; /* overall height scaling; ui, document */ + float glyphScale[2]; /* ui, document */ + float vertOffsetScale[2]; /* ui, document */ const iFontFile *styles[max_FontStyle]; }; @@ -158,25 +159,26 @@ iBool isDisabled_FontPack (const iFontPack *); iBool isReadOnly_FontPack (const iFontPack *); const iPtrArray * listSpecs_FontPack (const iFontPack *); iString * infoText_FontPack (const iFontPack *); -const iArray * actions_FontPack (const iFontPack *); +const iArray * actions_FontPack (const iFontPack *, iBool showInstalled); const iString * idFromUrl_FontPack (const iString *url); /*----------------------------------------------------------------------------------------------*/ -iDeclareType(GmDocument) - void init_Fonts (const char *userDir); void deinit_Fonts (void); +void enablePack_Fonts (const iString *packId, iBool enable); +void updateActive_Fonts (void); const iFontPack * pack_Fonts (const char *packId); const iFontPack * packByPath_Fonts (const iString *path); const iFontSpec * findSpec_Fonts (const char *fontId); const iPtrArray * listPacks_Fonts (void); const iPtrArray * listSpecs_Fonts (iBool (*filterFunc)(const iFontSpec *)); const iPtrArray * listSpecsByPriority_Fonts (void); -const iString * infoPage_Fonts (void); +const iString * infoPage_Fonts (iRangecc query); void install_Fonts (const iString *fontId, const iBlock *data); +void installFontFile_Fonts (const iString *fileName, const iBlock *data); void reload_Fonts (void); iLocalDef iBool isInstalled_Fonts(const char *packId) { diff --git a/src/gmdocument.c b/src/gmdocument.c index 23ebace3..b0851fec 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -479,7 +479,7 @@ static void commit_RunTypesetter_(iRunTypesetter *d, iGmDocument *doc) { static const int maxLedeLines_ = 10; -static int applyAttributes_RunTypesetter_(iRunTypesetter *d, iTextAttrib attrib) { +static void applyAttributes_RunTypesetter_(iRunTypesetter *d, iTextAttrib attrib) { /* WARNING: This is duplicated in run_Font_(). Make sure they behave identically. */ if (attrib.bold) { d->run.font = fontWithStyle_Text(d->baseFont, bold_FontStyle); @@ -495,7 +495,7 @@ static int applyAttributes_RunTypesetter_(iRunTypesetter *d, iTextAttrib attrib) else { d->run.font = d->baseFont; d->run.color = d->baseColor; - } + } } static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, iTextAttrib attrib, diff --git a/src/gmrequest.c b/src/gmrequest.c index f7a22e0a..03a6d999 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c @@ -362,7 +362,7 @@ static const iBlock *aboutPageSource_(iRangecc path, iRangecc query) { return utf8_String(debugInfo_App()); } if (equalCase_Rangecc(path, "fonts")) { - return utf8_String(infoPage_Fonts()); + return utf8_String(infoPage_Fonts(query)); } if (equalCase_Rangecc(path, "feeds")) { return utf8_String(entryListPage_Feeds()); diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 1039fc2b..48ce5b5f 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -1130,7 +1130,8 @@ static void makeFooterButtons_DocumentWidget_(iDocumentWidget *d, const iMenuIte d->footerButtons, iClob(newKeyMods_LabelWidget( items[i].label, items[i].key, items[i].kmods, items[i].command)), - alignLeft_WidgetFlag | drawKey_WidgetFlag); + alignLeft_WidgetFlag | drawKey_WidgetFlag | extraPadding_WidgetFlag); + setPadding1_Widget(as_Widget(button), gap_UI / 2); checkIcon_LabelWidget(button); setFont_LabelWidget(button, uiContent_FontId); } @@ -1473,7 +1474,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, trim_Rangecc(¶m); /* Detect fontpacks even if the server doesn't use the right media type. */ if (isRequestFinished && equal_Rangecc(param, "application/octet-stream")) { - if (detect_FontPack(&d->sourceContent)) { + if (detect_FontPack(&response->body)) { param = range_CStr(mimeType_FontPack); } } @@ -1492,6 +1493,42 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, docFormat = plainText_SourceFormat; setRange_String(&d->sourceMime, param); } + else if (isRequestFinished && equal_Rangecc(param, "font/ttf")) { + clear_String(&str); + docFormat = gemini_SourceFormat; + setRange_String(&d->sourceMime, param); + format_String(&str, "# TrueType Font\n"); + iString *decUrl = collect_String(urlDecode_String(d->mod.url)); + iRangecc name = baseName_Path(decUrl); + iBool isInstalled = iFalse; + if (startsWith_String(collect_String(localFilePathFromUrl_String(d->mod.url)), + cstr_String(dataDir_App()))) { + isInstalled = iTrue; + } + appendCStr_String(&str, "## "); + appendRange_String(&str, name); + appendCStr_String(&str, "\n\n"); + appendCStr_String( + &str, cstr_Lang(isInstalled ? "truetype.help.installed" : "truetype.help")); + appendCStr_String(&str, "\n"); + if (!isInstalled) { + makeFooterButtons_DocumentWidget_( + d, + (iMenuItem[]){ + { add_Icon " ${fontpack.install.ttf}", + SDLK_RETURN, + 0, + format_CStr("!fontpack.install ttf:1 name:%s", + cstr_Rangecc(name)) }, + { folder_Icon " ${fontpack.open.fontsdir}", + SDLK_d, + 0, + format_CStr("!open url:%s/fonts", + cstrCollect_String(makeFileUrl_String(dataDir_App()))) + } + }, 2); + } + } else if (isRequestFinished && (equal_Rangecc(param, "application/zip") || (startsWith_Rangecc(param, "application/") && @@ -1499,32 +1536,39 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, clear_String(&str); docFormat = gemini_SourceFormat; setRange_String(&d->sourceMime, param); - iString *key = collectNew_String(); - toString_Sym(SDLK_s, KMOD_PRIMARY, key); format_String(&str, "# %s\n", zipPageHeading_(param)); - appendFormat_String(&str, - cstr_Lang("doc.archive"), - cstr_Rangecc(baseName_Path(d->mod.url))); if (equal_Rangecc(param, mimeType_FontPack)) { /* Show some information about fontpacks, and set up footer actions. */ iArchive *zip = iClob(new_Archive()); - if (openData_Archive(zip, &d->sourceContent)) { + if (openData_Archive(zip, &response->body)) { iFontPack *fp = new_FontPack(); setUrl_FontPack(fp, d->mod.url); setStandalone_FontPack(fp, iTrue); if (loadArchive_FontPack(fp, zip)) { - appendFormat_String(&str, "\n\n%s", + appendFormat_String(&str, "## %s\n%s", + cstr_String(id_FontPack(fp).id), cstrCollect_String(infoText_FontPack(fp))); } - const iArray *actions = actions_FontPack(fp); + appendCStr_String(&str, "\n"); + appendCStr_String(&str, cstr_Lang("fontpack.help")); + appendCStr_String(&str, "\n"); + const iArray *actions = actions_FontPack(fp, iTrue); makeFooterButtons_DocumentWidget_(d, constData_Array(actions), size_Array(actions)); delete_FontPack(fp); } } - appendCStr_String(&str, "\n\n"); + else { + appendFormat_String(&str, + cstr_Lang("doc.archive"), + cstr_Rangecc(baseName_Path(d->mod.url))); + appendCStr_String(&str, "\n"); + } + appendCStr_String(&str, "\n"); iString *localPath = localFilePathFromUrl_String(d->mod.url); if (!localPath) { + iString *key = collectNew_String(); + toString_Sym(SDLK_s, KMOD_PRIMARY, key); appendFormat_String(&str, "%s\n\n", format_CStr(cstr_Lang("error.unsupported.suggestsave"), cstr_String(key), @@ -3267,10 +3311,17 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) } return iTrue; } - else if (equalWidget_Command(cmd, w, "fontpack.install")) { - const iString *id = idFromUrl_FontPack(d->mod.url); - install_Fonts(id, &d->sourceContent); - postCommandf_App("open gotoheading:%s url:about:fonts", cstr_String(id)); + else if (equal_Command(cmd, "fontpack.install") && document_App() == d) { + if (argLabel_Command(cmd, "ttf")) { + iAssert(!cmp_String(&d->sourceMime, "font/ttf")); + installFontFile_Fonts(collect_String(suffix_Command(cmd, "name")), &d->sourceContent); + postCommand_App("open url:about:fonts"); + } + else { + const iString *id = idFromUrl_FontPack(d->mod.url); + install_Fonts(id, &d->sourceContent); + postCommandf_App("open gotoheading:%s url:about:fonts", cstr_String(id)); + } return iTrue; } return iFalse; @@ -5199,6 +5250,7 @@ void updateSize_DocumentWidget(iDocumentWidget *d) { d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; updateVisible_DocumentWidget_(d); invalidate_DocumentWidget_(d); + arrange_Widget(d->footerButtons); } #if 0 diff --git a/src/ui/text.c b/src/ui/text.c index 4baf60d3..106c55e9 100644 --- a/src/ui/text.c +++ b/src/ui/text.c @@ -458,7 +458,7 @@ static void initFonts_Text_(iText *d) { /* Check if there are auxiliary fonts available and set those up, too. */ iConstForEach(PtrArray, s, listSpecsByPriority_Fonts()) { const iFontSpec *spec = s.ptr; - if (spec->flags & auxiliary_FontSpecFlag) { + if (spec->flags & (auxiliary_FontSpecFlag | user_FontSpecFlag)) { const int fontId = size_Array(&d->fonts); resize_Array(&d->fonts, fontId + maxVariants_Fonts); setupFontVariants_Text_(d, spec, fontId); -- cgit v1.2.3 From 66e10a25ef75da8722dcb757a9905f3bc685f3ef Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Wed, 20 Oct 2021 16:33:03 +0300 Subject: GmDocument: "file://" is unthemed --- src/gmdocument.c | 50 ++++++++++++++++++++++++++++++++++--------------- src/ui/documentwidget.c | 9 ++++++++- 2 files changed, 43 insertions(+), 16 deletions(-) (limited to 'src/gmdocument.c') diff --git a/src/gmdocument.c b/src/gmdocument.c index b0851fec..043d3259 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -1285,21 +1285,41 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { } } else if (theme == sepia_GmDocumentTheme) { - const iHSLColor base = { 40, 0.6f, 0.9f, 1.0f }; - setHsl_Color(tmBackground_ColorId, base); - set_Color(tmParagraph_ColorId, get_Color(black_ColorId)); - set_Color(tmFirstParagraph_ColorId, get_Color(black_ColorId)); - set_Color(tmQuote_ColorId, get_Color(brown_ColorId)); - set_Color(tmPreformatted_ColorId, get_Color(brown_ColorId)); - set_Color(tmHeading1_ColorId, get_Color(brown_ColorId)); - set_Color(tmHeading2_ColorId, mix_Color(get_Color(brown_ColorId), get_Color(black_ColorId), 0.5f)); - set_Color(tmHeading3_ColorId, get_Color(black_ColorId)); - set_Color(tmBannerBackground_ColorId, mix_Color(get_Color(tmBackground_ColorId), get_Color(brown_ColorId), 0.15f)); - set_Color(tmBannerTitle_ColorId, get_Color(brown_ColorId)); - set_Color(tmBannerIcon_ColorId, get_Color(brown_ColorId)); - set_Color(tmLinkText_ColorId, get_Color(tmHeading2_ColorId)); - set_Color(tmHypertextLinkText_ColorId, get_Color(tmHeading2_ColorId)); - set_Color(tmGopherLinkText_ColorId, get_Color(tmHeading2_ColorId)); + iHSLColor base = { 40, 0.6f, 0.9f, 1.0f }; + if (0 && isDark_ColorTheme(colorTheme_App())) { /* TODO */ + base.lum = 0.15f; + base.sat = 0.15f; + setHsl_Color(tmBackground_ColorId, base); + set_Color(tmParagraph_ColorId, get_Color(gray75_ColorId)); + set_Color(tmFirstParagraph_ColorId, get_Color(white_ColorId)); + set_Color(tmQuote_ColorId, get_Color(brown_ColorId)); + set_Color(tmPreformatted_ColorId, get_Color(brown_ColorId)); + set_Color(tmHeading1_ColorId, get_Color(brown_ColorId)); + set_Color(tmHeading2_ColorId, mix_Color(get_Color(brown_ColorId), get_Color(black_ColorId), 0.5f)); + set_Color(tmHeading3_ColorId, get_Color(black_ColorId)); + set_Color(tmBannerBackground_ColorId, mix_Color(get_Color(tmBackground_ColorId), get_Color(brown_ColorId), 0.15f)); + set_Color(tmBannerTitle_ColorId, get_Color(brown_ColorId)); + set_Color(tmBannerIcon_ColorId, get_Color(brown_ColorId)); + set_Color(tmLinkText_ColorId, get_Color(tmHeading2_ColorId)); + set_Color(tmHypertextLinkText_ColorId, get_Color(tmHeading2_ColorId)); + set_Color(tmGopherLinkText_ColorId, get_Color(tmHeading2_ColorId)); + } + else { + setHsl_Color(tmBackground_ColorId, base); + set_Color(tmParagraph_ColorId, get_Color(black_ColorId)); + set_Color(tmFirstParagraph_ColorId, get_Color(black_ColorId)); + set_Color(tmQuote_ColorId, get_Color(brown_ColorId)); + set_Color(tmPreformatted_ColorId, get_Color(brown_ColorId)); + set_Color(tmHeading1_ColorId, get_Color(brown_ColorId)); + set_Color(tmHeading2_ColorId, mix_Color(get_Color(brown_ColorId), get_Color(black_ColorId), 0.5f)); + set_Color(tmHeading3_ColorId, get_Color(black_ColorId)); + set_Color(tmBannerBackground_ColorId, mix_Color(get_Color(tmBackground_ColorId), get_Color(brown_ColorId), 0.15f)); + set_Color(tmBannerTitle_ColorId, get_Color(brown_ColorId)); + set_Color(tmBannerIcon_ColorId, get_Color(brown_ColorId)); + set_Color(tmLinkText_ColorId, get_Color(tmHeading2_ColorId)); + set_Color(tmHypertextLinkText_ColorId, get_Color(tmHeading2_ColorId)); + set_Color(tmGopherLinkText_ColorId, get_Color(tmHeading2_ColorId)); + } } else if (theme == white_GmDocumentTheme) { const iHSLColor base = { 40, 0, 1.0f, 1.0f }; diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 60580da6..0f989133 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -1093,7 +1093,13 @@ static void replaceDocument_DocumentWidget_(iDocumentWidget *d, iGmDocument *new } static void updateTheme_DocumentWidget_(iDocumentWidget *d) { - if (isEmpty_String(d->titleUser)) { + if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) { + iBlock empty; + init_Block(&empty, 0); + setThemeSeed_GmDocument(d->doc, &empty); + deinit_Block(&empty); + } + else if (isEmpty_String(d->titleUser)) { setThemeSeed_GmDocument(d->doc, collect_Block(newRange_Block(urlHost_String(d->mod.url)))); } @@ -1644,6 +1650,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, return; } d->flags |= drawDownloadCounter_DocumentWidgetFlag; + clear_PtrSet(d->invalidRuns); deinit_String(&str); return; } -- cgit v1.2.3