diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-10-17 20:33:14 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-10-17 20:33:14 +0300 |
commit | b3ae7efcb9adb1de3d02f0753e2a79888bdb71ac (patch) | |
tree | 57fb9f82f2d19a1c8e6d1aa85865f9df210e2c4b /src/fontpack.c | |
parent | 2e2b823bfb5d34d42c6b6c1b289193c854459b45 (diff) |
FontPack management via "about:fonts"
Diffstat (limited to 'src/fontpack.c')
-rw-r--r-- | src/fontpack.c | 197 |
1 files changed, 149 insertions, 48 deletions
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. */ | |||
32 | #include <the_Foundation/path.h> | 32 | #include <the_Foundation/path.h> |
33 | #include <the_Foundation/ptrarray.h> | 33 | #include <the_Foundation/ptrarray.h> |
34 | #include <the_Foundation/string.h> | 34 | #include <the_Foundation/string.h> |
35 | #include <the_Foundation/stringlist.h> | ||
35 | #include <the_Foundation/toml.h> | 36 | #include <the_Foundation/toml.h> |
36 | 37 | ||
37 | const char *mimeType_FontPack = "application/lagrange-fontpack+zip"; | 38 | const char *mimeType_FontPack = "application/lagrange-fontpack+zip"; |
@@ -236,6 +237,10 @@ iFontPackId id_FontPack(const iFontPack *d) { | |||
236 | return (iFontPackId){ &d->id, d->version }; | 237 | return (iFontPackId){ &d->id, d->version }; |
237 | } | 238 | } |
238 | 239 | ||
240 | const iString *loadPath_FontPack(const iFontPack *d) { | ||
241 | return d->loadPath; | ||
242 | } | ||
243 | |||
239 | const iPtrArray *listSpecs_FontPack(const iFontPack *d) { | 244 | const iPtrArray *listSpecs_FontPack(const iFontPack *d) { |
240 | if (!d) return NULL; | 245 | if (!d) return NULL; |
241 | iPtrArray *list = collectNew_PtrArray(); | 246 | iPtrArray *list = collectNew_PtrArray(); |
@@ -441,6 +446,20 @@ void setLoadPath_FontPack(iFontPack *d, const iString *path) { | |||
441 | setRange_String(&d->id, withoutExtension_Path(&d->id)); | 446 | setRange_String(&d->id, withoutExtension_Path(&d->id)); |
442 | } | 447 | } |
443 | 448 | ||
449 | const iString *idFromUrl_FontPack(const iString *url) { | ||
450 | iString *id = new_String(); | ||
451 | iUrl parts; | ||
452 | init_Url(&parts, url); | ||
453 | setRange_String(id, baseName_Path(collectNewRange_String(parts.path))); | ||
454 | setRange_String(id, withoutExtension_Path(id)); | ||
455 | return collect_String(id); | ||
456 | } | ||
457 | |||
458 | void setUrl_FontPack(iFontPack *d, const iString *url) { | ||
459 | /* TODO: Should we remember the URL as well? */ | ||
460 | set_String(&d->id, idFromUrl_FontPack(url)); | ||
461 | } | ||
462 | |||
444 | void setStandalone_FontPack(iFontPack *d, iBool standalone) { | 463 | void setStandalone_FontPack(iFontPack *d, iBool standalone) { |
445 | d->isStandalone = standalone; | 464 | d->isStandalone = standalone; |
446 | } | 465 | } |
@@ -486,7 +505,7 @@ static void sortSpecs_Fonts_(iFonts *d) { | |||
486 | clear_PtrArray(&d->specOrder); | 505 | clear_PtrArray(&d->specOrder); |
487 | iConstForEach(PtrArray, p, &d->packs) { | 506 | iConstForEach(PtrArray, p, &d->packs) { |
488 | const iFontPack *pack = p.ptr; | 507 | const iFontPack *pack = p.ptr; |
489 | if (!contains_StringSet(prefs_App()->disabledFontPacks, &pack->id)) { | 508 | if (!isDisabled_FontPack(pack)) { |
490 | iConstForEach(Array, i, &pack->fonts) { | 509 | iConstForEach(Array, i, &pack->fonts) { |
491 | pushBack_PtrArray(&d->specOrder, i.value); | 510 | pushBack_PtrArray(&d->specOrder, i.value); |
492 | } | 511 | } |
@@ -620,24 +639,131 @@ const iPtrArray *listSpecsByPriority_Fonts(void) { | |||
620 | return &fonts_.specOrder; | 639 | return &fonts_.specOrder; |
621 | } | 640 | } |
622 | 641 | ||
642 | iString *infoText_FontPack(const iFontPack *d) { | ||
643 | const iFontPack *installed = pack_Fonts(cstr_String(&d->id)); | ||
644 | const iBool isInstalled = (installed != NULL); | ||
645 | const int installedVersion = installed ? installed->version : 0; | ||
646 | const iBool isDisabled = isDisabled_FontPack(d); | ||
647 | iString *str = new_String(); | ||
648 | size_t sizeInBytes = 0; | ||
649 | iPtrSet *uniqueFiles = new_PtrSet(); | ||
650 | iStringList *names = new_StringList(); | ||
651 | iConstForEach(PtrArray, i, listSpecs_FontPack(d)) { | ||
652 | const iFontSpec *spec = i.ptr; | ||
653 | pushBack_StringList(names, &spec->name); | ||
654 | iForIndices(j, spec->styles) { | ||
655 | insert_PtrSet(uniqueFiles, spec->styles[j]); | ||
656 | } | ||
657 | } | ||
658 | iConstForEach(PtrSet, j, uniqueFiles) { | ||
659 | sizeInBytes += size_Block(&((const iFontFile *) *j.value)->sourceData); | ||
660 | } | ||
661 | appendFormat_String(str, "%.1f ${mb} (%s)\n%s: %s\n", | ||
662 | sizeInBytes / 1.0e6, | ||
663 | formatCStrs_Lang("num.files.n", size_PtrSet(uniqueFiles)), | ||
664 | formatCStrs_Lang("num.fonts.n", size_StringList(names)), | ||
665 | cstrCollect_String(joinCStr_StringList(names, ", ")), | ||
666 | d->version); | ||
667 | if (isInstalled && installedVersion != d->version) { | ||
668 | appendCStr_String(str, format_Lang("${fontpack.meta.version}\n", d->version)); | ||
669 | } | ||
670 | if (!isEmpty_String(&d->id)) { | ||
671 | appendFormat_String(str, "%s %s%s\n", | ||
672 | isInstalled ? ballotChecked_Icon : ballotUnchecked_Icon, | ||
673 | isInstalled ? (installedVersion == d->version ? "${fontpack.meta.installed}" | ||
674 | : format_CStr("${fontpack.meta.installed} (%s)", | ||
675 | format_Lang("${fontpack.meta.version}", | ||
676 | installedVersion))) | ||
677 | : "${fontpack.meta.notinstalled}", | ||
678 | isDisabled ? "${fontpack.meta.disabled}" : ""); | ||
679 | } | ||
680 | iRelease(names); | ||
681 | delete_PtrSet(uniqueFiles); | ||
682 | return str; | ||
683 | } | ||
684 | |||
685 | const iArray *actions_FontPack(const iFontPack *d) { | ||
686 | iArray *items = new_Array(sizeof(iMenuItem)); | ||
687 | const iFontPackId fp = id_FontPack(d); | ||
688 | const char *fpId = cstr_String(fp.id); | ||
689 | const iFontPack *installed = pack_Fonts(fpId); | ||
690 | const iBool isEnabled = !isDisabled_FontPack(d); | ||
691 | if (isInstalled_Fonts(fpId)) { | ||
692 | if (d->version > installed->version) { | ||
693 | pushBack_Array(items, &(iMenuItem){ | ||
694 | format_Lang(add_Icon " ${fontpack.upgrade}", fpId, d->version), | ||
695 | SDLK_RETURN, 0, "fontpack.install" | ||
696 | }); | ||
697 | } | ||
698 | pushBack_Array(items, &(iMenuItem){ | ||
699 | format_Lang(isEnabled ? close_Icon " ${fontpack.disable}" | ||
700 | : leftArrowhead_Icon " ${fontpack.enable}", fpId), 0, 0, | ||
701 | format_CStr("fontpack.enable arg:%d id:%s", !isEnabled, fpId) }); | ||
702 | if (!d->isReadOnly && installed->loadPath && d->loadPath && | ||
703 | !cmpString_String(installed->loadPath, d->loadPath)) { | ||
704 | pushBack_Array(items, &(iMenuItem){ | ||
705 | format_Lang(delete_Icon " ${fontpack.delete}", fpId), 0, 0, | ||
706 | format_CStr("fontpack.delete id:%s", fpId) }); | ||
707 | } | ||
708 | } | ||
709 | else if (d->isStandalone) { | ||
710 | pushBack_Array(items, &(iMenuItem){ | ||
711 | format_Lang(add_Icon " ${fontpack.install}", fpId), | ||
712 | SDLK_RETURN, 0, "fontpack.install" | ||
713 | }); | ||
714 | } | ||
715 | return collect_Array(items); | ||
716 | } | ||
717 | |||
718 | iBool isDisabled_FontPack(const iFontPack *d) { | ||
719 | return contains_StringSet(prefs_App()->disabledFontPacks, &d->id); | ||
720 | } | ||
721 | |||
623 | const iString *infoPage_Fonts(void) { | 722 | const iString *infoPage_Fonts(void) { |
624 | iFonts *d = &fonts_; | 723 | iFonts *d = &fonts_; |
625 | iString *str = collectNewCStr_String("# Fonts\n"); | 724 | iString *str = collectNewCStr_String("# ${heading.fontpack.meta}\n" |
725 | "=> gemini://skyjake.fi/fonts Download more fonts\n" | ||
726 | "=> about:command?!open%20newtab:1%20gotoheading:1%20url:about:help Using fonts in Lagrange\n" | ||
727 | "=> about:command?!open%20newtab:1%20gotoheading:1%20url:about:help How to create a fontpack\n"); | ||
626 | iPtrArray *specsByPack = collectNew_PtrArray(); | 728 | iPtrArray *specsByPack = collectNew_PtrArray(); |
627 | setCopy_PtrArray(specsByPack, &d->specOrder); | 729 | setCopy_PtrArray(specsByPack, &d->specOrder); |
628 | sort_Array(specsByPack, cmpSourceAndPriority_FontSpecPtr_); | 730 | sort_Array(specsByPack, cmpSourceAndPriority_FontSpecPtr_); |
629 | iString *currentSourcePath = collectNew_String(); | 731 | iString *currentSourcePath = collectNew_String(); |
630 | iConstForEach(PtrArray, i, specsByPack) { | 732 | for (int group = 0; group < 2; group++) { |
631 | const iFontSpec *spec = i.ptr; | 733 | iBool isFirst = iTrue; |
632 | if (isEmpty_String(&spec->sourcePath)) { | 734 | iConstForEach(PtrArray, i, specsByPack) { |
633 | continue; /* built-in font */ | 735 | const iFontSpec *spec = i.ptr; |
634 | } | 736 | if (isEmpty_String(&spec->sourcePath)) { |
635 | if (!equal_String(&spec->sourcePath, currentSourcePath)) { | 737 | continue; /* built-in font */ |
636 | appendFormat_String(str, "=> %s %s%s\n", | 738 | } |
637 | cstrCollect_String(makeFileUrl_String(&spec->sourcePath)), | 739 | if (!equal_String(&spec->sourcePath, currentSourcePath)) { |
638 | endsWithCase_String(&spec->sourcePath, ".fontpack") ? "\U0001f520 " : "", | 740 | set_String(currentSourcePath, &spec->sourcePath); |
639 | cstr_Rangecc(baseName_Path(&spec->sourcePath))); | 741 | /* Print some information about this page. */ |
640 | set_String(currentSourcePath, &spec->sourcePath); | 742 | const iFontPack *pack = packByPath_Fonts(currentSourcePath); |
743 | if (pack) { | ||
744 | if (!isDisabled_FontPack(pack) ^ group) { | ||
745 | if (isFirst) { | ||
746 | appendCStr_String(str, "\n## "); | ||
747 | appendCStr_String(str, group == 0 ? "${heading.fontpack.meta.enabled}" | ||
748 | : "${heading.fontpack.meta.disabled}"); | ||
749 | appendCStr_String(str, "\n\n"); | ||
750 | isFirst = iFalse; | ||
751 | } | ||
752 | const iString *packId = id_FontPack(pack).id; | ||
753 | appendFormat_String(str, "### %s\n=> %s ${fontpack.meta.viewfile}\n", | ||
754 | isEmpty_String(packId) ? "fonts.ini" : | ||
755 | cstr_String(packId), | ||
756 | cstrCollect_String(makeFileUrl_String(&spec->sourcePath))); | ||
757 | append_String(str, collect_String(infoText_FontPack(pack))); | ||
758 | iConstForEach(Array, a, actions_FontPack(pack)) { | ||
759 | const iMenuItem *item = a.value; | ||
760 | appendFormat_String(str, "=> about:command?%s %s\n", | ||
761 | cstr_String(withSpacesEncoded_String(collectNewCStr_String(item->command))), | ||
762 | item->label); | ||
763 | } | ||
764 | } | ||
765 | } | ||
766 | } | ||
641 | } | 767 | } |
642 | } | 768 | } |
643 | return str; | 769 | return str; |
@@ -645,6 +771,9 @@ const iString *infoPage_Fonts(void) { | |||
645 | 771 | ||
646 | const iFontPack *pack_Fonts(const char *packId) { | 772 | const iFontPack *pack_Fonts(const char *packId) { |
647 | iFonts *d = &fonts_; | 773 | iFonts *d = &fonts_; |
774 | if (!*packId) { | ||
775 | return NULL; | ||
776 | } | ||
648 | iConstForEach(PtrArray, i, &d->packs) { | 777 | iConstForEach(PtrArray, i, &d->packs) { |
649 | const iFontPack *pack = i.ptr; | 778 | const iFontPack *pack = i.ptr; |
650 | if (!cmp_String(&pack->id, packId)) { | 779 | if (!cmp_String(&pack->id, packId)) { |
@@ -675,10 +804,15 @@ void reload_Fonts(void) { | |||
675 | delete_String(userDir); | 804 | delete_String(userDir); |
676 | } | 805 | } |
677 | 806 | ||
678 | void install_Fonts(const iString *fontId, const iBlock *data) { | 807 | void install_Fonts(const iString *packId, const iBlock *data) { |
808 | if (!detect_FontPack(data)) { | ||
809 | return; | ||
810 | } | ||
811 | /* Newly installed packs will never be disabled. */ | ||
812 | remove_StringSet(prefs_App()->disabledFontPacks, packId); | ||
679 | iFonts *d = &fonts_; | 813 | iFonts *d = &fonts_; |
680 | iFile *f = new_File(collect_String(concatCStr_Path( | 814 | iFile *f = new_File(collect_String(concatCStr_Path( |
681 | userFontsDirectory_Fonts_(d), format_CStr("%s.fontpack", cstr_String(fontId))))); | 815 | userFontsDirectory_Fonts_(d), format_CStr("%s.fontpack", cstr_String(packId))))); |
682 | if (open_File(f, writeOnly_FileMode)) { | 816 | if (open_File(f, writeOnly_FileMode)) { |
683 | write_File(f, data); | 817 | write_File(f, data); |
684 | } | 818 | } |
@@ -687,38 +821,5 @@ void install_Fonts(const iString *fontId, const iBlock *data) { | |||
687 | reload_Fonts(); | 821 | reload_Fonts(); |
688 | } | 822 | } |
689 | 823 | ||
690 | iBool preloadLocalFontpackForPreview_Fonts(iGmDocument *doc) { | ||
691 | iBool wasLoaded = iFalse; | ||
692 | for (size_t linkId = 1; ; linkId++) { | ||
693 | const iString *linkUrl = linkUrl_GmDocument(doc, linkId); | ||
694 | if (!linkUrl) { | ||
695 | break; /* ran out of links */ | ||
696 | } | ||
697 | const int linkFlags = linkFlags_GmDocument(doc, linkId); | ||
698 | if (linkFlags & fontpackFileExtension_GmLinkFlag && | ||
699 | scheme_GmLinkFlag(linkFlags) == file_GmLinkScheme) { | ||
700 | iMediaId linkMedia = findMediaForLink_Media(media_GmDocument(doc), linkId, fontpack_MediaType); | ||
701 | if (linkMedia.type) { | ||
702 | continue; /* got this one already */ | ||
703 | } | ||
704 | iString *filePath = localFilePathFromUrl_String(linkUrl); | ||
705 | iFile *f = new_File(filePath); | ||
706 | if (open_File(f, readOnly_FileMode)) { | ||
707 | iBlock *fontPackArchiveData = readAll_File(f); | ||
708 | setUrl_Media(media_GmDocument(doc), linkId, fontpack_MediaType, linkUrl); | ||
709 | setData_Media(media_GmDocument(doc), | ||
710 | linkId, | ||
711 | collectNewCStr_String(mimeType_FontPack), | ||
712 | fontPackArchiveData, | ||
713 | 0); | ||
714 | delete_Block(fontPackArchiveData); | ||
715 | wasLoaded = iTrue; | ||
716 | } | ||
717 | iRelease(f); | ||
718 | } | ||
719 | } | ||
720 | return wasLoaded; | ||
721 | } | ||
722 | |||
723 | iDefineClass(FontFile) | 824 | iDefineClass(FontFile) |
724 | 825 | ||